/* Rysing — new direction. Built from the design reference, not from the
   earlier builds. Copy is lifted verbatim from rysing-comments.html.

   LICENSING: Kinfolk's web licence is NOT yet cleared — resolve before
   production. Aileron is public domain (Sora Sagano). Qualy, the logo face,
   is never loaded: the wordmark ships as artwork. */

@font-face { font-family:"Kinfolk"; src:url("../rysing-assets/Kinfolk-Regular.woff2") format("woff2"); font-weight:400; font-display:swap; }
@font-face { font-family:"Aileron"; src:url("../rysing-assets/Aileron-Light.woff2") format("woff2"); font-weight:300; font-display:swap; }
@font-face { font-family:"Aileron"; src:url("../rysing-assets/Aileron-Regular.woff2") format("woff2"); font-weight:400; font-display:swap; }
@font-face { font-family:"Aileron"; src:url("../rysing-assets/Aileron-SemiBold.woff2") format("woff2"); font-weight:600; font-display:swap; }
@font-face { font-family:"Aileron"; src:url("../rysing-assets/Aileron-Bold.woff2") format("woff2"); font-weight:700; font-display:swap; }

/* --- Tokens ------------------------------------------------- */
:root {
  /* The four brand colours, and all four are now the guideline's own values.
     `rysing brand assets/Branding-Guidelines.pdf` §3.0 Color Palette sets
     exactly four: #F9F9F9, #000000 (CMYK 75/68/67/90), #3524D5 and #F04222.
     Paper, red and blue already matched it to the byte. The page black did
     not — it was #141414, a near-black grey, and it is now the guideline's
     #000000 on the client's instruction to follow the document.

     THIS IS NOT A COSMETIC CHANGE. Most of the shape work on this site was
     graded against #141414, and several comments below still argue from it:
     every brand pattern asset is artwork painted on an OPAQUE #000 ground,
     so against a #141414 page its surround read as a drawn ring and three
     separate masks exist to hide that. At #000 the surround and the page are
     the same colour and the ring cannot occur. Those masks are now solving a
     problem that no longer exists — see §28 before removing one, because two
     of them are also doing a second job that does still matter.

     `--ink-deep` was "darker than the page", used for the hero's ground and
     the menu overlay. Nothing is darker than #000, so it is the same black
     now. It is kept as a token rather than deleted because it still names a
     distinct intent — the ground a full-bleed object sits on — and because
     collapsing ~20 call sites would bury the colour change in noise. */
  --ink:#000000;
  --ink-deep:#000000;
  --paper:#f9f9f9;
  --red:#f04222;
  --blue:#3524d5;
  --muted:#9a9a9a;
  --line:rgba(249,249,249,.14);

  --gutter:clamp(22px,4vw,74px);
  --ease:cubic-bezier(.2,.75,.2,1);

  /* One grade across every photograph on the page. The supplied images come
     from different cameras, lights and decades of client archive, and the
     spread between the sharp studio frames and the soft ones is the single
     most expensive-looking thing we can fix without a reshoot. Pulling
     saturation down and contrast up narrows that spread. Replacement
     photography drops in under the same grade, so nothing needs retuning.
     Deliberately not applied to the logo marquee — those marks carry their
     own brightness(0) invert(1) treatment. */
  --photo-grade:contrast(1.07) saturate(.62) brightness(.97);
  --mark:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 269.87 273.69'%3E%3Cpath d='M262.42,136.55c-12.82-.17-25.39-1.76-37.88-4.67l-20.93-5.54-13.69-5c-13.95-5.97-25.03-14.22-33.04-27.25-5.44-8.83-9.03-18.37-11.98-28.3l-5.16-20.33c-1.87-8.29-3.28-16.58-3.96-25v-.36c-.1-1.23-.19-2.45-.27-3.68-.07-1.32-.12-2.64-.15-3.97l-.24-10.68V.33s-.01-.05-.04-.09V0s-.08.01-.15.08c-.07-.06-.13-.11-.13-.08v.23s-.06.07-.06.09v1.46s-.23,10.68-.23,10.68c-.03,1.33-.08,2.65-.15,3.97-.08,1.23-.17,2.45-.27,3.68v.36c-.68,8.41-2.09,16.7-3.96,25l-5.16,20.33c-2.96,9.93-6.55,19.47-11.98,28.3-8.02,13.02-19.09,21.27-33.04,27.25l-13.69,5-20.93,5.54c-12.49,2.91-25.07,4.5-37.88,4.67l-7.45.25c.53.22,1.11.32,1.62.33l5.98.13c12.33.26,24.47,1.65,36.53,4.4l20.86,5.41,13.65,4.84c14.67,6.08,26.28,14.51,34.61,28.21,5.62,9.24,9.23,19.23,12.23,29.61l4.5,17.88c2.54,11.12,4.08,22.23,4.52,33.63l.23,10.21v1.66c0,.06.02.19.04.28,0,.13.03.33.07.3l.09-.09.09.09s.06-.17.07-.3c.03-.09.04-.22.04-.28v-1.66l.23-10.21c.44-11.4,1.99-22.51,4.52-33.63l4.5-17.88c3-10.37,6.61-20.37,12.23-29.61,8.32-13.7,19.93-22.14,34.61-28.21l13.65-4.84,20.86-5.41c12.05-2.75,24.19-4.15,36.53-4.4l5.98-.13c.51-.01,1.09-.11,1.62-.33l-7.45-.25Z'/%3E%3C/svg%3E");
}

/* `.glow-soft` IS GONE, and so is `.hero-light`'s one-sided variant of it.
   It faded every glow to nothing before the top and bottom of its own
   section, which is per-section behaviour by definition — it hid the cut
   instead of removing it, and §27's contract removes the cut. The homepage
   was its last user and came over in §36; about (§31), contact (§32),
   process (§34) and the service page had already stopped using it.

   Do not bring it back. A glow that needs masking at its own edge is a glow
   whose box is the wrong size: give the element a box that contains the
   light's falloff and remap the gradient into it (§31's transform), keeping
   the box inside the bottom of `body` (§33). Both rules, always.

   Worth keeping from the old note, because it is why several glows on this
   site are written the way they are: the mask erased the top and bottom 15%
   of whatever it was applied to, so a core placed near its box's edge — the
   natural way to write "light entering from the corner" — was almost
   entirely masked away and read as a faint smudge however high its alpha
   went. Three glows were authored against that distortion and got brighter,
   not different, when it was lifted. HANDOFF §25, §31, §36. */

* { box-sizing:border-box; }
/* clip, not hidden: the drifting glows are allowed to bleed off the
   viewport without opening a horizontal scrollbar, and unlike `hidden`
   this does not turn the root into a containing block for the fixed
   header. Sections must not clip them locally — see .stats. */
html { background:var(--ink); scroll-behavior:smooth; overflow-x:clip; }
/* ONE COMPOSITING GROUP FOR THE WHOLE PAGE. This is the backdrop
   architecture, and it is what stops the shapes being cut into per-section
   pieces.

   Until now every section carried its own `isolation:isolate` plus its own
   `background`, which made each one a private group that a shape could not
   exist across — so each shape had to be clipped at the section edge, and the
   page read as a column of separate lit boxes rather than one lit page.

   Clipping was not only cosmetic, though. A `screen`-blended shape crossing a
   section edge left its isolation group, its black surround stopped
   cancelling, and it painted a dark halo over whatever was below — the fault
   the note above `.process` describes. `overflow:clip` was what hid it.

   Hoisting the group here removes the cause rather than the symptom: with one
   group for the document, a shape can never leave it, so the halo cannot come
   back and the clipping it justified is no longer needed.

   The layering is unchanged, just moved up a level. This element's background
   paints first; shapes at `z-index:-1` paint on top of that fill but behind
   all content — exactly the arrangement `.process` proved for a single section
   before it was converted to this one (§34). That conversion is the proof in
   the other direction: `.process` hosts the only `screen`-blended shape on the
   site, and it screens against this fill now with no halo.

   A section that wants to host a shape therefore needs `position:relative`
   and NOTHING else: no `isolation`, no `overflow:clip`, no opaque
   `background`. `position:relative` alone does not create a stacking context,
   so the shape stays anchored to the section's geometry while compositing at
   page level. Anchoring to the section rather than to document coordinates is
   deliberate — page coordinates break the moment a paragraph gets longer.

   `isolation` creates a stacking context but NOT a containing block for fixed
   positioning (only transform/filter/will-change do that), so the fixed
   header is unaffected. */
body {
  margin:0; background:var(--ink); color:var(--paper); isolation:isolate;
  font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-size:clamp(15px,1.1vw,17px); line-height:1.6;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
}
a { color:inherit; text-decoration:none; }
img,video { display:block; }
:focus-visible { outline:2px solid var(--red); outline-offset:4px; }

/* Film grain: every branded surface in the guide carries it. Generated
   rather than loaded — the supplied plate is 4.3 MB and does not tile. */
body::after {
  content:""; position:fixed; inset:0; z-index:90; pointer-events:none;
  opacity:.055; mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:180px 180px;
}

.skip-link {
  position:fixed; z-index:120; top:12px; left:12px; padding:13px 17px;
  background:var(--paper); color:var(--ink); transform:translateY(-180%);
  transition:transform .2s var(--ease);
  font-size:11px; font-weight:600; letter-spacing:.18em; text-transform:uppercase;
}
.skip-link:focus { transform:none; }

/* --- Type --------------------------------------------------- */
/* The reference's signature: the grotesque and the didone alternate inside
   one sentence. Kinfolk is the default voice of a headline; .alt flips a run
   back to Aileron. Kinfolk is caps-only by design. */
.display {
  font-family:"Kinfolk",Didot,"Bodoni 72",Georgia,serif; font-weight:400;
  text-transform:uppercase; letter-spacing:.01em; line-height:.96;
}
.display .alt {
  font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-weight:600; letter-spacing:-.022em;
}
.label {
  font-size:11px; font-weight:600; letter-spacing:.17em; text-transform:uppercase;
}

/* Alternation is a device, not a texture. Running it through all eleven
   headlines is what turned the page's signature move into wallpaper — if
   every sentence mixes faces, the mix stops meaning anything. It now
   appears at the three moments the page is allowed to be loud: the hero,
   the manifesto, and the closing ask. Every other headline sets in one
   face, so when the mix arrives it reads as emphasis.

   The markup is untouched — the .alt spans stay exactly where they are for
   the loud moments and simply inherit elsewhere. */
.band-head h2 .alt,
.proof-title .alt,
.founder-title .alt,
.system-title .alt,
.spot-copy h2 .alt {
  font-family:inherit; font-weight:inherit; letter-spacing:inherit;
}
/* The about page is the exception, and §14 says why: it is one continuous
   argument rather than eleven unrelated sections, and its designs alternate
   in nearly every headline. `.band-head h2 .alt` is neutralised above for
   the homepage's section heads; the about team head is drawn alternating,
   so it takes the device back. Scoped to this one head rather than lifted
   from the list, or the homepage's heads would light up with it. */
.about-team .band-head h2 .alt {
  font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-weight:600; letter-spacing:-.022em;
}

/* Didone wants less air the larger it gets; a single tracking value across
   a 32px section head and a 152px hero leaves the hero looking loose. */
.hero-title,.manifesto h2,.closing h2 { letter-spacing:-.012em; }

/* --- Header ------------------------------------------------- */
.site-head {
  position:fixed; z-index:20; inset:0 0 auto; height:92px;
  padding:0 var(--gutter); display:flex; align-items:center; gap:clamp(20px,3vw,60px);
  border-bottom:1px solid var(--line);
}
.site-head::before {
  content:""; position:absolute; z-index:-1; inset:0;
  /* Hand-written rather than var(): these are scrims with alpha, and a
     token cannot carry one. Moved 13,13,13 → 0,0,0 with the page black. */
  background:linear-gradient(to bottom,rgba(0,0,0,.82),rgba(0,0,0,.34) 70%,transparent);
  pointer-events:none;
}
.wordmark { flex:none; line-height:0; }
.wordmark img { width:auto; height:27px; }
.head-right { display:flex; align-items:center; gap:clamp(10px,1.4vw,18px); margin-left:auto; }

/* Burger, always present. The inline nav moved into the overlay, so the
   header carries three things only: mark, ask, menu. */
.menu-toggle {
  display:grid; place-items:center; flex:none; width:46px; height:46px;
  border:1px solid rgba(249,249,249,.4); border-radius:50%;
  background:none; color:inherit; cursor:pointer;
  transition:background var(--t-detail) var(--ease-detail),border-color var(--t-detail) var(--ease-detail),color var(--t-detail) var(--ease-detail);
}
.menu-toggle:hover { background:var(--paper); border-color:var(--paper); color:var(--ink); }
.menu-toggle-lines,.menu-toggle-lines::before,.menu-toggle-lines::after {
  display:block; width:17px; height:1px; background:currentColor;
  transition:transform var(--t-detail) var(--ease-detail),background var(--t-detail) var(--ease-detail);
}
.menu-toggle-lines { position:relative; }
.menu-toggle-lines::before,.menu-toggle-lines::after { content:""; position:absolute; left:0; }
.menu-toggle-lines::before { top:-5px; }
.menu-toggle-lines::after { top:5px; }
.menu-toggle[aria-expanded="true"] .menu-toggle-lines { background:transparent; }
.menu-toggle[aria-expanded="true"] .menu-toggle-lines::before { top:0; transform:rotate(45deg); }
.menu-toggle[aria-expanded="true"] .menu-toggle-lines::after { top:0; transform:rotate(-45deg); }

/* --- Menu overlay ------------------------------------------- */
.menu-overlay {
  position:fixed; z-index:110; inset:0; width:100vw; min-height:100svh;
  padding:clamp(96px,12vh,130px) var(--gutter) clamp(28px,4vh,44px);
  background:var(--ink-deep); color:var(--paper);
  display:flex; flex-direction:column; justify-content:space-between; gap:40px;
  transform:translateX(100%); visibility:hidden;
  transition:transform .62s var(--ease-weight),visibility 0s .62s;
}
.menu-overlay.is-open { transform:none; visibility:visible; transition-delay:0s; }
.overlay-close {
  position:absolute; top:23px; right:var(--gutter); width:46px; height:46px;
  display:grid; place-items:center; padding:0;
  border:1px solid rgba(249,249,249,.4); border-radius:50%;
  background:none; color:inherit; cursor:pointer;
  transition:background var(--t-detail) var(--ease-detail),border-color var(--t-detail) var(--ease-detail),color var(--t-detail) var(--ease-detail);
}
.overlay-close:hover { background:var(--red); border-color:var(--red); color:var(--paper); }
.overlay-close svg { width:20px; height:20px; }
.overlay-nav { display:flex; flex-direction:column; align-items:flex-end; }
.overlay-nav a {
  font-size:clamp(38px,6vw,86px); line-height:1.02;
  transition:color var(--t-detail) var(--ease-detail);
}
.overlay-nav a:hover { color:var(--red); }
/* The page you are already on, marked as the design marks it. Applied by the
   build to whichever link points at the current file, so a new page never has
   to hand-edit a copy of the nav. */
.overlay-nav a[aria-current="page"] { text-decoration:underline; text-underline-offset:.14em; text-decoration-thickness:1px; }
/* The stagger index. This list must cover every link in the nav, and it did
   not: it stopped at 6 while the nav has 7 items, so Contact fell through to
   `var(--i,0)` and animated in the FIRST slot, simultaneously with Home. It
   still faded in — it was simply at the top of the queue instead of the end
   of it, which against a stagger reads as the link already being there.

   That is the failure mode to recognise: an uncovered item does not fail to
   animate, it animates too early, which looks like no animation at all.

   Deliberately run well past the current count. A services index is expected
   (`HANDOFF.md` §37), which makes 8 items, and the nav is shared chrome —
   adding a link should not require anyone to remember this list exists. */
.overlay-nav a:nth-child(1){--i:0}.overlay-nav a:nth-child(2){--i:1}.overlay-nav a:nth-child(3){--i:2}
.overlay-nav a:nth-child(4){--i:3}.overlay-nav a:nth-child(5){--i:4}.overlay-nav a:nth-child(6){--i:5}
.overlay-nav a:nth-child(7){--i:6}.overlay-nav a:nth-child(8){--i:7}.overlay-nav a:nth-child(9){--i:8}
.overlay-nav a:nth-child(10){--i:9}
/* The links arrive one after another rather than together — the one place
   a stagger is allowed to be noticeable, because the panel is otherwise
   empty while it travels. */
.menu-overlay.is-open .overlay-nav a {
  animation:overlayIn .5s var(--ease-weight) both;
  animation-delay:calc(.14s + var(--i,0) * .045s);
}
@keyframes overlayIn { from { opacity:0; transform:translateY(16px); } to { opacity:1; transform:none; } }

.overlay-bottom { display:flex; flex-wrap:wrap; justify-content:space-between; align-items:flex-end; gap:30px; }
/* Supplied artwork, keyed to white by `tools/make-award-mono.js` — the
   colour originals are drawn for a white background and read as three
   pasted stickers on this black. Nothing here can do that keying: a CSS
   filter cannot knock out a background, and `invert()` turns KMU's green
   magenta. The marks are equalised on height, not width — the three aspect
   ratios run 1:1 to 3.1:1, so equal width would shrink the square Meta
   badge to a third of the others. */
.award-lockups { display:flex; flex-wrap:wrap; align-items:center; gap:26px; }
.award-lockup { display:grid; place-items:center; }
/* Optical, not equal. One height for all three put KMU at roughly twice
   the visual mass of its neighbours — it is a two-line wordmark that fills
   its own box edge to edge, where the other two are seals with their own
   internal margin. The seals go up, the wordmark comes down. */
.award-lockup img { display:block; height:var(--h,42px); width:auto; object-fit:contain; }
.award-lockup-hipe img { --h:46px; }
.award-lockup-kmu img  { --h:32px; }
.award-lockup-meta img { --h:44px; }
.social-links { display:flex; gap:9px; }
.social-links a {
  width:44px; height:44px; display:grid; place-items:center;
  border:1px solid var(--line); border-radius:50%;
  transition:background var(--t-detail) var(--ease-detail),border-color var(--t-detail) var(--ease-detail),color var(--t-detail) var(--ease-detail);
}
.social-links a:hover { background:var(--paper); border-color:var(--paper); color:var(--ink); }
.social-links svg { width:17px; height:17px; fill:currentColor; }

.btn-pill {
  flex:none; display:inline-flex; align-items:center; gap:12px;
  min-height:44px; padding:0 22px; border:1px solid rgba(249,249,249,.4); border-radius:999px;
  font-size:11px; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
  transition:background .3s var(--ease),color .3s var(--ease),border-color .3s var(--ease);
}
.btn-pill:hover { background:var(--paper); border-color:var(--paper); color:var(--ink); }
.btn-pill .arrow { width:11px; height:11px; background:currentColor;
  -webkit-mask:var(--mark) center/contain no-repeat; mask:var(--mark) center/contain no-repeat;
  transition:transform .35s var(--ease); }
.btn-pill:hover .arrow { transform:rotate(90deg) scale(1.2); }

@media (max-width:1080px) {
  /* The header is now mark + ask + burger at every width, so there is no
     desktop nav to hide. Only the ask collapses on the narrowest screens,
     where the burger already carries it. */
  .btn-pill-head { display:none; }
}

/* --- Hero --------------------------------------------------- */
/* No video here — it moves to the section below. The ground is black with
   two large out-of-focus lights bleeding in from opposing corners, which is
   the identity's own banner composition. */
/* §27's contract: a section hosting a shape is `position:relative` and
   NOTHING else. All three of the forbidden things were here, and on this
   section they were load bearing together — removing the isolation alone
   would have deleted the hero's lights outright.

   `isolation` made .hero its own stacking context, which is the only reason
   its opaque background painted UNDER the z-index:-1 lights. Without it the
   lights join `body`'s stacking context and paint behind every in-flow box
   in it, .hero's own background included — so the fill had to go in the same
   edit or the hero would have gone black. It cost nothing: it read
   `--ink-deep 0 78%, --ink 100%` and §28 resolved both tokens to #000000, so
   it was a flat black fill painted by a gradient.

   `overflow:hidden` was cutting the blue disc, which reaches ~158px below
   this section. That cut is the thing the client asked us to stop doing. */
.hero {
  position:relative;
  min-height:100svh; padding:clamp(140px,17vh,210px) var(--gutter) clamp(48px,6vh,80px);
  display:flex; flex-direction:column; justify-content:flex-end;
}
/* No mask. This was `.glow-soft`'s one-sided variant — it faded the whole
   light layer out across the hero's bottom 30%, which is per-section
   behaviour by definition and was erasing most of the blue.

   The box stays `inset:0` because it is only a positioning container: it
   paints nothing itself, and each disc below is a radial gradient that
   reaches transparent well inside its own circular box, so §29's rule is
   satisfied per shape rather than by this element. */
.hero-light {
  position:absolute; z-index:-1; inset:0; pointer-events:none;
  /* Horizontal only, and it is NOT the section clip §27 forbids. The two
     discs are anchored off both sides (`right:-14vw`, `left:-18vw`), so
     uncut they push the root's scrollWidth to 1644 at a 1440 viewport — and
     `html{overflow-x:clip}` does not actually hold that: measured,
     `scrollTo(400,0)` still lands at scrollX=204, so the page can be pushed
     sideways with a trackpad into dead black. (The service page ships that
     today at scrollX=400; §27 recorded it as "no horizontal scrolling",
     which does not survive measurement. Flagged, not changed here.)

     Clipping the SHAPE CONTAINER rather than the section is the same
     distinction `.footer-light` keeps (§27): it is a clip for its own
     reason, not a section boundary. The cut lands exactly where the
     viewport already cuts — this element is `inset:0` on a full-width
     section — so nothing visible changes, which is §4's long-standing rule
     that a left/right cut at the viewport edge is invisible.

     `overflow-y` stays `visible` on purpose and must not become `hidden`:
     `clip` is the one value that does not coerce the other axis, and the
     blue disc reaching ~158px down into `.reel` is the whole point of the
     conversion. Setting `overflow:hidden` here puts the cut back. */
  overflow-x:clip; overflow-y:visible;
}
/* THE OFFSETS ARE CAPPED AGAINST THE DISC, AND THAT IS §33'S RULE AGAIN.
   Each disc is sized from the viewport WIDTH (`min(72vw,1020px)`) and was
   offset from the viewport HEIGHT (`top:-24vh`, `bottom:-30vh`). Those two
   do not scale together, so at a narrow viewport the offset stays large
   while the disc gets small, and the light walks clean off the section: at
   390px the blue sits ENTIRELY below the hero, a 242px ball floating in the
   top of the showreel.

   That was invisible until now only because the old one-sided mask reached
   zero at the hero's bottom edge and deleted it — the mobile hero shipped
   with no blue at all, which is not the composition the design draws either.
   Uncut, the misplacement is what you see. §33 names the general form of
   this: cap both or neither.

   So each offset is capped against the disc's own diameter, held in `--d`.
   The multipliers are read off the authored desktop values rather than
   chosen — at 1440x900, 24vh/1020px = .212 and 30vh/860px = .314 — so where
   the viewport is tall enough for the vh term to win, which is every desktop
   and laptop size, the result is EXACTLY what it was. The cap only engages
   where the disc has shrunk, which is where it was broken. Verified
   unchanged to 0px at 1440 and 1920, and the light back inside the hero's
   lower-left corner at 360 and 390. */
/* THE ORANGE IS A REAL ELEMENT AND THE BLUE IS STILL A PSEUDO, which looks
   like an inconsistency and is the whole §48 change.

   The orange now parts for the cursor (`pattern-stars.js`, variant E of the
   playground — HANDOFF §47, §48). The script needs the disc to be three
   things at once: the lattice's GEOMETRY ANCHOR, so the glow it builds is
   this gradient's box and not a second statement of the same clamps (§33);
   the FALLBACK, left on the page untouched if the script never runs or the
   canvas never gets a context; and something it can hide once it is
   actually running. A pseudo-element can be none of them — it has no node to
   measure, no node to carry `data-stars-disc`, and `.hero-light::before`
   cannot be read by `getBoundingClientRect`.

   So `::before` is gone and `.hero-disc--orange` carries exactly what it
   carried, declaration for declaration, including the drift. The blue is
   untouched and stays a pseudo: nothing is pointed at it, and converting it
   too would be a change with no reason behind it.

   The authored stops are ALSO restated in `pattern-stars.js`'s `profile()`,
   because neither a shader nor a per-mark alpha can sample a CSS gradient.
   That is the one duplicated geometry in this effect and it is duplicated
   because there is no way not to: CHANGE THE STOPS HERE AND CHANGE THEM
   THERE, then re-run the fidelity measurement in §48. */
/* `content` is NOT in the shared declaration. A string `content` on a real
   element is invalid rather than inherited-harmless, and the one browser
   that ever decides to honour it would blank the node. The pseudo declares
   its own. */
.hero-light::after { content:""; }
.hero-light::after,.hero-disc {
  position:absolute; aspect-ratio:1; border-radius:50%; filter:blur(30px);
  width:var(--d);
}
/* THE STOPS CARRY THE BRAND ORANGE AT FULL STRENGTH — §50.
   The client read this glow as "a dull orange, not her brand orange". It was
   never the wrong colour: measured at the core it ran #cc361a against the
   brand's #f04222 — hue 9.4deg against 9.3, saturation 87% against 86%, and
   a PEAK CHANNEL OF 204 AGAINST 240. Identical hue, 85% of the brightness.
   That is §25's finding a third time: the colour is right and the luminance
   is not, and the eye reports the shortfall as the wrong colour.

   Two things were taking it down and both are in this one declaration. The
   core was authored at `.95` alpha over black, which is 228 before anything
   else happens; and the old stops began falling IMMEDIATELY from the centre,
   so the 30px blur had no plateau to work with and pulled the peak down
   again to 204. The stops below hold full strength across the middle fifth
   and then fall, which gives the blur something flat to spread and lands the
   core on the brand value.

   THE SAME STOPS ARE RESTATED IN `pattern-stars.js`'s `profile()` and in the
   playground's `.pp-oglow`. A canvas cannot sample a CSS gradient, so there
   is no way to have one copy; §48 flagged it as the one duplicated geometry
   in this effect. CHANGE ONE AND CHANGE ALL THREE, then re-run
   `tools/measure-fidelity.js` — the blob field is calibrated against this
   gradient and silently stops matching it otherwise. */
.hero-disc--orange {
  --d:min(72vw,1020px);
  top:calc(-1 * min(24vh, var(--d) * .212)); right:-14vw;
  background:radial-gradient(circle at 50% 50%,rgba(240,66,34,1),rgba(240,66,34,.98) 18%,rgba(240,66,34,.62) 40%,rgba(240,66,34,.12) 64%,transparent 76%);
  animation:driftA 28s var(--ease) infinite alternate,
            heroBreathe 8s var(--ease) infinite;
}
/* Hidden only once the field is built and painted, which is §45's
   arrangement: every early return in the script leaves the authored gradient
   on the page. The canvas inherits the same `driftA`, so the ambient motion
   the client asked for survives the swap (`.rs-blobs` below). */
.hero.is-stars .hero-disc--orange { opacity:0; }
.hero-light::after {
  --d:min(62vw,860px);
  bottom:calc(-1 * min(30vh, var(--d) * .314)); left:-18vw;
  background:radial-gradient(circle at 50% 50%,rgba(53,36,213,.95),rgba(53,36,213,.5) 38%,rgba(53,36,213,.08) 62%,transparent 74%);
  animation:driftB 35s var(--ease) -11s infinite alternate;
}
@keyframes driftA { to { transform:translate3d(-3%,4%,0) scale(1.08); } }
@keyframes driftB { to { transform:translate3d(4%,-3%,0) scale(1.07); } }

/* ===========================================================================
   THE HERO'S BREATH — §53

   The client asked for the orange to "move a tiny bit, smoothly". It already
   did: `driftA` has translated and scaled this glow by a few per cent for
   twenty-eight seconds since long before she asked. MEASURED, THAT MOTION
   CHANGES AT MOST 2 LEVELS OF PIXEL OVER EIGHT SECONDS
   (`tools/probe-drift.js`), which is not a subtle effect — it is an absent
   one.

   THE REASON IS THE SHAPE, NOT THE AMOUNT, and it generalises to every glow
   on this site: A BLURRED RADIAL FIELD HAS NO EDGE TO TRACK. Sliding a
   featureless gradient sideways moves almost no pixel anywhere, because
   every pixel's neighbour is nearly the same colour. Displacement is the
   wrong lever for soft light. LUMINANCE IS THE RIGHT ONE — the eye reads a
   large field getting brighter and dimmer immediately, which is the same
   sensitivity §50 ran into from the other side when 85% brightness was
   reported as "the wrong colour".

   So the breath is opacity, on its own clock, running alongside the drift
   rather than replacing it. Two animations on one element is fine here
   BECAUSE THEY TOUCH DIFFERENT PROPERTIES — `transform` and `opacity`
   compose; two keyframes both writing `transform` would not, the last would
   simply win.

   IT ONLY EVER DIMS. §50 spent a cycle getting this core to the brand's own
   240, so the keyframe holds 1 at both ends and takes the trough down,
   never up: the authored peak is still the brightest this glow ever gets,
   and `--breathe` is the one number to tune. Measured peak-to-trough across
   the hero with `tools/probe-drift.js`, drift held still:

     1    0 levels   0% of the hero     (no breath — the state before §53)
     .97  4 levels   13%
     .95  5 levels   27%
     .93  6 levels   33%   <- shipped
     .90  8 levels   40%

   RATE MATTERS MORE THAN SWING, which is the part that was got wrong first
   time. A slow luminance change is noticed by how fast it moves, not by how
   far it eventually travels, so the period is half of the setting. .93 over
   13s shipped at 0.92 levels per second and the client still could not tell
   it was moving. .88 over 8s is 2.25 — about two and a half times the rate
   for one and a half times the swing.

   THE TROUGH IS THE CONSTRAINT, NOT THE PEAK. §50 found that this glow at
   85% of its brightness gets reported as "a dull orange, not her brand
   orange". That was a STATIC 85%, and this returns to 100% every four
   seconds, but it is why the dim stops at 12% rather than buying
   visibility with a deeper trough: deepening it walks straight into a
   complaint this project has already had twice.

   Raise the RATE before the amplitude if it is still not enough.

   BOTH THE CANVAS AND THE FALLBACK DISC CARRY IT, so the hero breathes
   identically whether or not the lattice built. */
@keyframes heroBreathe { 0%,100% { opacity:1; } 50% { opacity:var(--breathe,.88); } }

/* ===========================================================================
   THE LATTICE THAT PARTS FOR THE CURSOR — §48
   ---------------------------------------------------------------------------
   Two things on the site run `pattern-stars.js`: the homepage hero's ORANGE,
   rebuilt as a field of soft blobs on a canvas (`.rs-blobs`, variant E of the
   playground), and the FOOTER LOCKUP on every page, ringed by brand marks
   that get out of the cursor's way (`.rs-star`, variant C). Both are the same
   physics driving different marks — HANDOFF §46, §47, §48.

   These rules are here rather than in the playground page because they are
   now shared by three documents. The playground's own `.pp-*` hero copies
   stay where they are: those exist so that experimenting on the hero's SHAPES
   cannot reach the homepage, and that reason has not changed.

   THE FIELD LAYER IS ALWAYS `pointer-events:none` AND THE LISTENER IS ALWAYS
   ON THE SECTION. §44's sixth finding: a listener bound to a shape layer here
   receives nothing at all and throws no error while doing it. It broke twice.
   =========================================================================== */
.rs-field { position:absolute; inset:0; z-index:0; pointer-events:none; overflow:hidden; }
/* THE HERO'S FIELD MUST NOT CLIP, and this is the same argument as the
   padding on the script's `CLIP` clamp — the other half of it.

   `overflow:hidden` on a field carrying `filter:blur(30px)` clips first and
   blurs second, so the orange would be faded to nothing along the hero's own
   top edge: a soft cut 30px inside the viewport, where the authored gradient
   ran on upward off screen. The canvas is already trimmed to the section plus
   a blur's margin by the script, and `.hero-light` still clips the
   horizontal, so there is nothing left for this to clip but the margin that
   exists to be sampled.

   `z-index` goes too: `.hero-light` is already `z-index:-1`, and a stacking
   context here would lift the canvas over the headline. */
.hero .rs-field { overflow:visible; z-index:auto; }

/* The outer box carries ONLY the per-frame translate. Scale, rotation and the
   twinkle are on the inner `<b>`, so the thing written a few hundred times a
   frame is one short transform and nothing else needs rebuilding with it.

   NO `will-change`. It reads as the obvious optimisation for an element whose
   transform changes every frame and at this scale it is the opposite: it
   forces a permanent composited layer per mark. Measured at 1440x900, taking
   it off moved the playground's variant C from 33.3ms a frame to 16.7. The
   browser promotes a transforming element on its own and un-promotes it when
   it stops. */
.rs-star { position:absolute; }
.rs-star > b {
  display:block; width:100%; height:100%;
  background:rgba(249,249,249,.9);
  -webkit-mask:var(--mark) center/contain no-repeat;
          mask:var(--mark) center/contain no-repeat;
  opacity:var(--o);
  animation-name:rsTwinkle; animation-iteration-count:infinite;
  animation-timing-function:var(--ease); animation-direction:alternate;
}
/* Red is SCARCE here, roughly one mark in seven. §11's rule about rationing
   the red applies to a pattern more than to anything else, because a pattern
   repeats whatever it is given. */
.rs-star > b.is-red { background:var(--red); }
@keyframes rsTwinkle { to { opacity:calc(var(--o) * .42); } }

/* THE BLOB FIELD IS ONE CANVAS, NOT HUNDREDS OF ELEMENTS, and that is the
   most expensive thing §47 learned. Built as DOM first — several hundred
   overlapping soft layers six gaps wide — it cost 183ms a frame against
   16.7 for star marks, and halving everything only got it to 67ms. The DOM is
   not the wrong tool by a constant there, it is the wrong tool.

   `left`/`top`/`width`/`height` are all written by the script: the canvas is
   sized to the DISC rather than to the section, so the hero's orange does not
   pay for the 37% of itself that hangs off the top and right of the viewport.

   ON THE HERO IT CARRIES THE HERO'S OWN DRIFT. The glow it replaces animated
   under `driftA`, ambient motion is half of what the client asked for (§44),
   and the keyframe is percentages of the element's own box — which is now
   this canvas, laid out as the disc's box. So the same keyframe is the same
   motion. The script reads the pointer through this element's rendered rect,
   which is what keeps the gap under the cursor while the whole field drifts;
   see `point()` for why that is exact rather than approximate.

   THE DRIFT IS SCOPED TO `.hero` AND MUST STAY SCOPED. The playground's
   variant E is the calibration rig for this glow — §47 measures it against
   the authored gradient by screenshot difference — and §47's measuring note
   says to pause the drift before taking any before/after pair. A drift
   inherited there would quietly invalidate the one measurement that says
   this is still the hero's light. */
.rs-blobs { position:absolute; left:0; top:0; }
.hero .rs-blobs { animation:driftA 28s var(--ease) infinite alternate; }
/* The breath sits on the FIELD rather than the canvas so that one rule
   covers the whole layer whatever the marks are — §53.

   A COST ARGUMENT WAS WRITTEN HERE AND THE MEASUREMENT KILLED IT, which is
   worth leaving in place. The hero runs `BLUR:30` as `filter:blur(30px)` on
   this element, and animating opacity on the canvas INSIDE it looked like
   it had to re-run that blur every frame — the acceptance run had just
   printed 16.7ms at 1920 against §50's 11.3, which fitted perfectly.

   It was not true. `tools/ab-breathe.js` turns the breath off and leaves
   `driftA` running: 16.80ms with it and 16.81ms without, and at 1440 the
   OFF case measured HIGHER than the ON case. The breath is free in either
   position, and the gap against §50 is this harness on this day, not a
   regression. §50's numbers came from another session and are not a
   baseline you can diff against — measure the same page both ways instead,
   which costs one minute. */
.hero .rs-field { animation:heroBreathe 8s var(--ease) infinite; }
/* NO CSS BLUR HERE. `BLUR` is applied by the script to the FIELD, one layer
   up, so it blurs the composed cloud rather than each sprite. */
/* The script does not start its loop at all under reduced motion, so the
   lattice is laid out and left still; these two are the decorative layers on
   top of that. */
@media (prefers-reduced-motion:reduce) {
  .rs-star > b,.hero .rs-blobs,.hero .rs-field { animation:none; }
}

.hero-eyebrow { margin:0 0 auto; text-align:right; }

/* The reference sets 6 words; this headline is 23. The opening clause keeps
   the poster scale and alternation, and the qualifier that follows drops to a
   supporting line — the full sentence is intact, the hierarchy does the work. */
.hero-title { margin:0; max-width:13ch; font-size:clamp(46px,8.6vw,152px); }
/* Keeps the closing phrase together so it wraps as a unit instead of leaving
   "and" stranded at the end of the previous line. */
.hero-title .keep { white-space:nowrap; }
.hero-title .tail {
  display:block; margin-top:clamp(18px,2.2vw,34px); max-width:46ch;
  font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-weight:300; font-size:clamp(15px,1.45vw,21px); line-height:1.5;
  letter-spacing:.01em; text-transform:none; color:rgba(249,249,249,.78);
}
.hero-title em { font-style:normal; }

/* --- Showreel ----------------------------------------------- */
/* A contained player in normal document flow, on client instruction, after
   the reference they sent.

   THE STAR STAYS, AND SO DOES SCROLL DRIVING IT. What went is only the
   scroll-jacking: the reel used to be a 260vh section with a sticky
   full-viewport stage, which held the viewport for two and a half screens.
   That pin is what the client asked to lose — not the opening, and not the
   scroll link.

   The two were never actually joined. The pin existed to MANUFACTURE scroll
   distance, but the frame already travels a screen's worth on its own just
   by rising through the viewport, and that travel is the progress source
   now. The page scrolls at its natural speed and the star opens as the
   frame comes up. Same mark, same 27%->620% range, same `1-(1-p)^2.6` curve
   as the original, same grade riding along.

   The frame around it is contained now, after the reference the client sent:
   inset from the page, 16:9, soft corners. The star opens INSIDE that frame
   rather than across the whole viewport. */
.reel {
  position:relative;
  /* Inset well past the page gutter — the whole point of the change is that
     it reads as an object ON the page rather than a full-bleed backdrop.
     The max-width then stops it growing back to near-full-bleed on very wide
     screens, where a vw inset alone keeps a constant ratio and the frame
     creeps wider in absolute terms than anything else on the site. */
  padding:clamp(34px,5vh,70px) clamp(20px,12vw,300px) clamp(56px,7vh,110px);
}
.reel-stage { position:relative; max-width:1340px; margin-inline:auto; }
/* No `overflow:hidden` on the section and none on the stage. The hero's blue
   disc reaches ~158px down into the top of this section and fades out there
   — see the hero notes above — and a clip at this boundary cuts it flat. The
   rounded corners are clipped on the frame itself, which the disc is behind
   and never enters. */
.reel-frame {
  position:relative; overflow:hidden;
  aspect-ratio:16 / 9;
  border-radius:clamp(9px,.9vw,17px);
  /* MIN is 27 — raised from 16, then 22, on instruction, purely as a size
     decision. It is the starting value here too, so the star is already the
     right size before any scroll handler has run. */
  -webkit-mask:var(--mark) center / calc(var(--aperture,27) * 1%) no-repeat;
          mask:var(--mark) center / calc(var(--aperture,27) * 1%) no-repeat;
}
/* Once the star has opened past the frame the mask and the grade are both
   no-ops, but the compositor still re-rasterises a masked, filtered video on
   every frame of playback — which cost the reel about a fifth of its
   playback rate when this was full-viewport. Shedding both is identical to
   look at and hands the frames back to decoding. */
.reel.is-revealed .reel-frame { -webkit-mask:none; mask:none; }
.reel.is-revealed .reel-frame video { filter:none; }
.reel-frame video {
  display:block; width:100%; height:100%; object-fit:cover;
  /* A light hold on the footage through the middle of the opening, gone by
     the time the star is no longer the subject. Driven by the same scroll
     progress, and clearing well ahead of the aperture — see the handler. */
  filter:brightness(calc(.72 + .28 * var(--reel-progress,0)))
         saturate(calc(.6 + .4 * var(--reel-progress,0)));
}
.reel-bar {
  position:absolute; z-index:2; inset:auto clamp(16px,2vw,30px) clamp(16px,2vw,28px);
  display:flex; align-items:flex-end; justify-content:space-between; gap:20px;
  pointer-events:none;
}
.reel-bar span { color:rgba(249,249,249,.6); }
.reel-toggle {
  pointer-events:auto; display:grid; place-items:center; width:46px; height:46px;
  border:1px solid rgba(249,249,249,.75); border-radius:50%;
  /* The reel cuts between white cards and dark footage, so the control
     carries its own ground rather than trusting the frame behind it. */
  background:rgba(0,0,0,.62); color:var(--paper); cursor:pointer;
  transition:background .3s var(--ease),border-color .3s var(--ease),color .3s var(--ease);
}
.reel-toggle:hover { background:var(--paper); border-color:var(--paper); color:var(--ink); }
.reel-toggle svg { width:15px; height:15px; fill:currentColor; }

/* The frame is tall at phone widths once it is inset and 16:9, so the inset
   drops back to roughly the page gutter there. */
@media (max-width:640px) {
  .reel { padding-inline:var(--gutter); }
  .reel-bar { inset:auto 14px 14px; }
}

/* Both of these must switch the mask off explicitly. The frame HOLDS at the
   small star by default and only the animation opens it, so anywhere the
   animation does not run the reel would otherwise sit as a 27% star forever
   — the failure mode is the whole video missing, not a missing flourish. */
@media (prefers-reduced-motion:reduce) {
  .reel-frame { -webkit-mask:none; mask:none; }
  .reel-frame video { filter:none; }
  /* `.hero-light::before` USED TO BE THE ORANGE AND IS NOT THE ORANGE ANY
     MORE — it is a real element, `.hero-disc--orange`, so this rule had
     stopped covering it and the glow kept drifting and now breathing under
     reduced motion. It only showed when the lattice did NOT build, since
     `.hero .rs-blobs` is covered separately, which is why it survived. */
  .hero-disc--orange,.hero-light::after { animation:none; }
}
.no-js .reel-frame { -webkit-mask:none; mask:none; }
.no-js .reel-frame video { filter:none; }

/* --- Stats ------------------------------------------------- */
/* The reference sets these as four oversized didone numerals on black, with
   soft red light drifting behind them. The figures carry the section; the
   labels stay small and quiet underneath. */
.stats-light { display:none; }
.stats {
  /* No overflow:hidden — clipping a soft glow leaves a hard edge at the
     section boundary. The page is one continuous black, so the light is
     allowed to bleed past the section the way the reference's does.
     No `isolation` either (§27): this section hosts no shape of its own —
     `.stats-light` is `display:none` — but an isolating box is a floor that
     a neighbour's light cannot be composited across, and the point of the
     conversion is that the page has exactly one compositing group. */
  position:relative;
  padding:clamp(80px,8.5vw,160px) var(--gutter) clamp(110px,12.5vw,230px);
}
/* The stats glow is gone. Red and blue were appearing as large blurred
   haze in three separate places, which is a generic gradient treatment and
   the opposite of decisive. They now bookend the page only — hero and
   footer — so the middle of the page is clean black and the colour means
   arrival and departure rather than atmosphere.
   Removing it also retires the 4px horizontal overflow it caused. */
/* Four equal columns, as the reference sets them. A hierarchy was tried
   here and reverted: leading on "35+" made the smallest figure the loudest,
   which fought the numbers themselves. An even row reads as a ledger —
   stated once, confidently, without a pitch. */
.stats-grid {
  display:grid; grid-template-columns:repeat(4,1fr); gap:clamp(24px,4vw,70px);
}
/* Kinfolk is a didone and sets very wide, so the row read loose and thin
   against the reference. Tighter tracking pulls the figures in, a hairline
   stroke adds weight the family has no bold cut for, and the size comes
   down nearer the reference's 76px. */
.stat strong {
  display:block;
  font-family:"Kinfolk",Didot,"Bodoni 72",Georgia,serif; font-weight:400;
  font-size:clamp(40px,5.3vw,84px); line-height:.88; letter-spacing:-.035em;
  -webkit-text-stroke:.7px currentColor;
}
/* Only while counting: proportional figures reflow as digits are added,
   and a number that shuffles sideways undoes the point of animating it. */
.stat strong.is-counting { font-variant-numeric:tabular-nums; }
.stat span {
  display:block; margin-top:clamp(10px,1.2vw,18px); max-width:24ch;
  color:var(--muted); font-size:clamp(12px,.95vw,14px); line-height:1.45;
}
@media (max-width:860px) {
  .stats-grid { grid-template-columns:repeat(2,1fr); row-gap:clamp(34px,6vw,54px); }
}
@media (prefers-reduced-motion:reduce) { .stats-light span { animation:none; } }

/* --- Services ---------------------------------------------- */
/* Two columns: an oversized numeral on the left, everything else on the
   right. Closed rows show the numeral alone — the reference hides the name
   until the row opens, so the list reads as four huge figures. */
.system { padding:clamp(92px,10vw,180px) var(--gutter) clamp(54px,5.6vw,96px); }
.system-head { margin:0 0 clamp(44px,6vh,80px); }
.system-title { margin:0; max-width:20ch; font-size:clamp(38px,5.4vw,76px); }
.system-meta {
  margin:clamp(20px,2.6vh,34px) 0 0; max-width:40ch;
  color:var(--paper); font-size:11px; font-weight:600;
  letter-spacing:.14em; text-transform:uppercase; line-height:1.5;
}
/* Separator is drawn, not written, so no character is added to the copy. */
.system-meta .sep::before { content:"\00b7"; margin:0 .5em; }

.system-list { border-top:1px solid var(--line); }
/* The numeral sits beside the whole row, not inside the button. When it
   lived in the button it set that row's height, so an open row showed the
   name, then ~170px of dead air, then the detail. Now a closed row is
   still numeral-tall and an open one flows tight under the name. */
.system-item {
  position:relative; border-bottom:1px solid var(--line);
  display:grid; grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);
  gap:clamp(20px,4vw,70px);
  align-items:end;
  /* NO BOTTOM PADDING ON THE ROW. The numeral is cut off BY the rule, so it
     has to reach it; the gap the name needs is carried by `.system-body`
     instead. One value cannot be both the name's breathing room and the
     numeral's reach to the rule — they pull opposite ways. */
  padding:clamp(34px,3.6vw,64px) 0 0;
  /* The whole row toggles, numeral included — see the click handler. */
  cursor:pointer;
}
.system-toggle {
  width:100%; display:flex; align-items:flex-start; justify-content:space-between; gap:24px;
  padding:0; border:0; background:none; color:inherit; text-align:left; font:inherit; cursor:pointer;
}
/* The reference sets these in the hairline grotesque, not the didone. The
   didone's thick/thin at 260px fought the headline above it, and the
   -.16em pull sliced the tops and tails of the glyphs against the row
   hairlines, which read as a clipping bug rather than a crop. */
.system-num {
  display:block;
  font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif; font-weight:300;
  /* Client reference: the numeral is larger than the row that holds it and
     runs up over the hairline into the row above. Two pieces make that work
     and both are needed:

     1. The SIZE is set past what the old padding allowed. 186px fitted
        comfortably inside the row, which is why the numerals read as a
        column of labels rather than the subject of the section.
     2. The BOX is shorter than the glyph, by negative margins. They take
        the overflow out of the grid's height calculation, so the row does
        not grow to contain the numeral — the glyph runs to the hairline
        instead of the row backing off to clear it. Remove them and every
        row gets ~60px taller and the tension goes.

     MEASURED LIMIT, do not raise the top margin past about -.12em. The
     numerals are bottom-anchored, so an upward bleed runs into the BOTTOM
     of the numeral in the row above, not into empty space. At -.26em the
     glyphs overlapped by 43px on every pair of rows and 03/04 collided
     outright. The reference does not overlap either: its numerals are
     contained and simply sit tight on their rule.

     Weight stays Light on instruction: the reference is thinner than this
     and the client does not want it thinner.

     THE COEFFICIENT IS MEASURED OFF THE REFERENCE, not chosen. A shut
     numeral there is 185px of ink on a 1210px page — 15.3% of the width,
     which is a ~21vw type size. The first pass used 15.5vw and the client's
     answer was "where is the change?", from a 1000px window where that is
     155px against the old 115px. At the cap it looked right; at the width
     she was actually using it was a 35% bump and read as nothing. Measure
     the proportion, then check it at the width in use, not at the cap. */
  font-size:clamp(118px,21vw,356px); line-height:.72; letter-spacing:-.04em;

  /* THE NUMERAL IS CUT OFF BY THE ROW RULE. The lower part of every glyph
     is not drawn — the row ends and the numeral ends with it, mid-stroke.
     This is the effect, and it is the one thing the client was asking for
     through three rounds of it being read as "make them bigger".

     IT IS A CLIP, NOT AN OVERFLOW. Everything before this tried to make the
     glyph reach further — past the rule, into the next row — and the answer
     was always no. The glyph does not travel at all: the BOX is shortened
     to `--cut` and `overflow:hidden` throws the rest away, so the bottom of
     the box lands on the rule and the digit is sliced there.

     THE CLIP CANNOT GO ON THE ROW. `.system-item` paints §42's blue fill
     from a pseudo-element pulled out past the gutter to the viewport edges,
     and `overflow` on the row would cut that back to the row's own width —
     a hidden value on one axis forces the other to `auto`, so there is no
     "vertical only". Clipping the numeral itself is what keeps the blue
     full-bleed.

     `--cut` is the visible slice as a fraction of the line box, so it
     scales with the type and is the one number to tune. The line box is
     .72em, so .46em shows about two-thirds of the digit. */
  --cut:.46em;
  align-self:end;
  height:var(--cut); overflow:hidden;
  margin:0;
}
/* A shut row carries its name at display size — it is the headline of that
   stage. Opening it demotes the name to a label so the detail underneath
   becomes the subject, which is the whole gesture of the row. */
.system-name {
  margin:0; font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-weight:400; font-size:clamp(30px,4.4vw,64px); line-height:1.04;
  text-transform:uppercase; letter-spacing:-.025em;
  transition:font-size .45s var(--ease),letter-spacing .45s var(--ease);
}
/* The name's clearance from the rule now lives here rather than on the row,
   so the numeral can reach the rule. */
.system-body { padding-bottom:clamp(12px,1.3vw,22px); }
.system-item.is-open { align-items:start; }
.system-item.is-open .system-body { padding-bottom:clamp(30px,3.2vw,56px); }
/* THE OPEN ROW SHOWS ITS NUMERAL WHOLE, which is how the client's reference
   has it: the cut rows are the shut ones, and the open stage is complete.
   It also has to be — the open row's cut would land nowhere near its own
   rule, because the detail makes that row several times taller than the
   numeral, so the digit would be sliced in mid-air. */
.system-item.is-open .system-num { align-self:start; height:auto; overflow:visible; }
.system-item.is-open .system-name { font-size:clamp(21px,2.3vw,34px); letter-spacing:-.012em; }
.system-symbol {
  position:relative; flex:none; width:38px; height:38px; margin-top:6px;
  display:grid; place-items:center; border:1px solid rgba(249,249,249,.34); border-radius:50%;
  /* Always present now that the whole row is the control — it is the only
     thing saying the row opens. */
  opacity:.5; transition:opacity .35s var(--ease),border-color .3s;
}
.system-item:hover .system-symbol,
.system-item.is-open .system-symbol { opacity:1; }
.system-symbol::before,.system-symbol::after {
  content:""; position:absolute; width:13px; height:1px; background:currentColor;
  transition:transform .35s var(--ease);
}
.system-symbol::after { transform:rotate(90deg); }
.system-item.is-open .system-symbol::after { transform:rotate(0); }

/* The open row stands on brand blue, on client instruction. Three things
   make it read as a band of the page rather than a card that appeared:

   1. It FULL-BLEEDS. The fill is a pseudo-element pulled out to the viewport
      edges past the section gutter, so the colour runs edge to edge and the
      row looks like a stripe through the list. Boxed inside the gutter it
      read as a panel floating on black.
   2. It WIPES, it does not fade. scaleY from the top origin on the same
      duration and curve as the panel's own growth, so one gesture: the
      ground arrives under the detail at exactly the speed the detail opens.
      A cross-fade here looked like a hover state misfiring.
   3. It carries NO radius and NO shadow. The row hairlines stay the only
      edges in the section.

   Closing one row while opening another runs a scale-out against a
   scale-in, which reads as the blue handing off down the list. That is why
   the fill is per-row and not one shared element that travels. */
.system-item::before,
.system-item::after {
  content:""; position:absolute; z-index:0;
  top:0; bottom:0; left:calc(var(--gutter) * -1); right:calc(var(--gutter) * -1);
  background:var(--blue); pointer-events:none;
  transform:scaleY(0); transform-origin:top;
}
.system-item::before { opacity:0; }
/* Closed rows get the same colour at a whisper on hover — the fill then
   looks like it was already there waiting, rather than switched on. */
.system-item::after {
  opacity:0;
  transition:opacity var(--t-detail) var(--ease-detail),transform var(--t-detail) var(--ease-detail);
}
.system-item:hover::after { opacity:.09; transform:scaleY(1); }
.system-item.is-open::after { opacity:0; }
.system-item.is-open::before { opacity:1; transform:scaleY(1); }
/* Content has to sit above the fill. The numeral and body are the row's only
   two children — see the grid above. */
.system-num,.system-body { position:relative; z-index:1; }

/* On blue, the paper-alpha hairlines and the grey label go muddy, which
   makes the open row look lower-contrast than the closed ones above it. The
   open row should be the loudest thing in the section, so lift them. */
.system-item.is-open .system-tags li { border-color:rgba(249,249,249,.52); }
.system-item.is-open .system-outcome span { color:rgba(249,249,249,.78); }
.system-item.is-open .system-for::before { border-color:rgba(249,249,249,.85); }
.system-item.is-open .system-symbol { border-color:rgba(249,249,249,.6); }

.system-panel { display:grid; grid-template-rows:0fr; transition:grid-template-rows .45s var(--ease); }
.system-item.is-open .system-panel { grid-template-rows:1fr; }
.system-panel > div { overflow:hidden; }
.system-detail { padding-top:clamp(26px,2.6vw,44px); }
.system-for {
  display:flex; gap:12px; margin:0 0 clamp(26px,3.4vh,42px);
  max-width:44ch; color:var(--paper); font-size:clamp(13px,1.1vw,17px); line-height:1.5;
}
.system-for::before {
  content:""; flex:none; width:9px; height:9px; margin-top:.5em;
  border:1px solid rgba(249,249,249,.65); border-radius:50%;
}
.system-outcome span { display:block; margin-bottom:12px; color:var(--muted); font-size:10px; font-weight:600; letter-spacing:.2em; text-transform:uppercase; }
.system-outcome p {
  margin:0 0 clamp(26px,3.4vh,42px);
  font-family:"Kinfolk",Didot,"Bodoni 72",Georgia,serif;
  font-size:clamp(18px,1.9vw,30px); text-transform:uppercase; letter-spacing:.03em; line-height:1.2;
}
.system-tags { display:flex; flex-wrap:wrap; gap:9px; margin:0; padding:0; list-style:none; }
.system-tags li {
  padding:10px 17px; border:1px solid rgba(249,249,249,.3); border-radius:999px;
  font-size:10px; font-weight:600; letter-spacing:.11em; text-transform:uppercase;
}
@media (max-width:820px) {
  /* Stacked, the numeral sits ABOVE its name rather than beside it, so an
     upward bleed would run into the row above with nothing alongside it to
     read against. The overflow is cut back to a crop at the top rule only
     and the row goes back to top alignment. */
  .system-item { grid-template-columns:1fr; gap:14px; align-items:start; }
  /* No bottom pull here. Beside the name it buys tightness against the row
     rule; above the name it eats the gap instead — measured at 0px clear at
     620 and 768, where the name was touching the numeral's descender line. */
  /* NO CUT STACKED. The numeral sits above its own name, nowhere near the
     row rule, so there is no edge for it to be cut against — sliced there
     it reads as a rendering fault rather than a crop. */
  .system-num {
    font-size:clamp(96px,27vw,170px);
    align-self:start; height:auto; overflow:visible; margin:-.14em 0 0;
  }
}
@media (prefers-reduced-motion:reduce) {
  .system-panel { transition:none; }
  /* The fill still has to appear — it is state, not decoration — so the
     wipe is dropped and the colour is simply there. */
  .system-item::before,.system-item::after { transition:none; transform:scaleY(1); }
  .system-item::before { opacity:0; }
  .system-item.is-open::before { opacity:1; }
}


/* --- Shared editorial primitives ---------------------------- */
/* Everything below the services list sits on the same black ground.
   The reference never cuts to a light panel, so neither does this: the
   only colour that interrupts the page is the red scribble, the red
   marks in the marquee and the blue behind one word. */
/* The quiet baseline. Most sections now sit closer together than they did,
   which is what buys the room for the two moments that open right out.
   An even rhythm across fourteen sections is what made the page read flat:
   when everything is spaced like a feature, nothing is one. */
/* Breathing room top and bottom, so a section owns its own space instead of
   being separated only by the one above it ending. Sized in vw, not vh: the
   previous clamp pinned to its 72px floor on any viewport under ~1350px
   tall, which is most laptops — the page was crowded for the people most
   likely to be reading it. Section rhythm should follow the measure. */
.band { padding-inline:var(--gutter); padding-block:clamp(60px,6.8vw,114px); }
/* The header is fixed at 92px, so every in-page link would otherwise land
   its heading underneath it. */
:is(section,footer)[id] { scroll-margin-top:112px; }
.band-head {
  display:flex; flex-wrap:wrap; align-items:flex-end; justify-content:space-between;
  gap:24px; margin-bottom:clamp(28px,4vh,40px);
}
.band-head h2 { margin:0; font-size:clamp(32px,3.7vw,54px); }
.band-note { max-width:46ch; color:rgba(249,249,249,.85); font-size:14px; line-height:1.6; }
.eyebrow { display:block; margin:0 0 clamp(20px,3vh,28px); }
.vh { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; }

/* --- Entrance -------------------------------------------- */
/* Restrained on purpose: 14px of travel and a long settle, never a slide or
   a bounce. The point is that content arrives composed rather than that
   anything is seen to move — if the animation is legible as an animation it
   has already failed at this price point.

   The hidden state is applied by script (.reveal-ready) rather than sitting
   in the stylesheet, so a failed or slow script can never leave the page
   blank, and there is no flash of hidden content before JS runs. */
.reveal-ready [data-reveal] { opacity:0; transform:translateY(14px); }
.reveal-ready [data-reveal].is-in {
  opacity:1; transform:none;
  transition:opacity .95s var(--ease-weight),transform .95s var(--ease-weight);
  transition-delay:calc(var(--i,0) * 65ms);
}
/* The hero is the only thing that moves on load rather than on scroll, and
   it travels further because nothing has been established yet to disturb. */
.hero-eyebrow,.hero-title { animation:heroIn 1.15s var(--ease-weight) both; }
.hero-title { animation-delay:.1s; }
@keyframes heroIn { from { opacity:0; transform:translateY(22px); } to { opacity:1; transform:none; } }

@media (prefers-reduced-motion:reduce) {
  .reveal-ready [data-reveal] { opacity:1; transform:none; transition:none; }
  .hero-eyebrow,.hero-title { animation:none; }
  .menu-overlay { transition:none; }
  .menu-overlay.is-open .overlay-nav a { animation:none; }
}

/* --- Craft layer -------------------------------------------- */
/* Motion had one duration and one curve on everything, which reads as a
   default rather than a decision. Weight should move slowly, detail should
   move quickly — that difference is most of what makes an interface feel
   considered rather than assembled. */
:root {
  --ease-weight:cubic-bezier(.16,1,.3,1);   /* large masses, long settle */
  --ease-detail:cubic-bezier(.3,.7,.2,1);   /* small elements, crisp */
  --t-weight:.72s;
  --t-detail:.22s;
}
.system-panel { transition:grid-template-rows var(--t-weight) var(--ease-weight); }
/* The blue fill is weight, not detail — it moves with the panel it grounds.
   Opacity is deliberately shorter than the transform: the colour is fully
   present early and the wipe finishes under it, so the row never looks like
   it is still deciding. */
.system-item::before { transition:transform var(--t-weight) var(--ease-weight),opacity .3s var(--ease-detail); }
.system-name { transition:font-size var(--t-weight) var(--ease-weight),letter-spacing var(--t-weight) var(--ease-weight); }
.btn-pill { transition:background var(--t-detail) var(--ease-detail),color var(--t-detail) var(--ease-detail),border-color var(--t-detail) var(--ease-detail); }
.btn-pill .arrow { transition:transform var(--t-detail) var(--ease-detail); }

/* Testimonials. The rail is the weight and moves on its own long curve;
   the controls are detail and snap. */
.treel-step { transition:background var(--t-detail) var(--ease-detail),color var(--t-detail) var(--ease-detail),border-color var(--t-detail) var(--ease-detail); }

/* --- Selected work ------------------------------------------ */
/* A hand-set gallery, not a grid: six panels of deliberately unequal
   width and vertical offset. Widths total 94% per row and the 2% column
   gap makes up the rest, so the row fills the measure exactly. */
/* Rows of three. Each row declares its own column proportions and its own
   vertical offsets, so three rows of the same count never read as a
   repeated template. The wide column is the one that carries film. */
.work-row {
  display:grid; gap:clamp(18px,2.4vw,40px);
  align-items:start; margin-bottom:clamp(40px,5.5vw,96px);
}
.work-row:last-of-type { margin-bottom:0; }
.work-row--a { grid-template-columns:0.78fr 1.5fr 0.92fr; }
/* Row B runs landscape, narrow portrait, tall portrait — three different
   shapes rather than one frame repeated, with the right-hand panel the
   largest so the row has somewhere to land. */
.work-row--b { grid-template-columns:1.55fr 0.95fr 1.6fr; }
.work-row--a > :nth-child(1) { margin-top:clamp(28px,4vw,76px); }
.work-row--a > :nth-child(3) { margin-top:clamp(10px,1.4vw,26px); }
.work-row--b > :nth-child(1) { margin-top:clamp(12px,1.7vw,32px); }
.work-row--b > :nth-child(2) { margin-top:clamp(26px,3.8vw,72px); }
.work-row--b > :nth-child(3) { margin-top:0; }
.work-row--b > :nth-child(1) .shot { aspect-ratio:4/3; }
.work-row--b > :nth-child(2) .shot { aspect-ratio:4/5; }
.work-row--b > :nth-child(3) .shot { aspect-ratio:5/6; }

/* Row C: wide landscape, portrait, near-square — the widest spread of the
   three rows, and the only one whose panels step down in size left to
   right. */
.work-row--c { grid-template-columns:1.75fr 1.05fr 0.85fr; }
.work-row--c > :nth-child(1) { margin-top:0; }
.work-row--c > :nth-child(2) { margin-top:clamp(34px,5vw,96px); }
.work-row--c > :nth-child(3) { margin-top:clamp(20px,3vw,62px); }
.work-row--c > :nth-child(1) .shot { aspect-ratio:16/10; }
.work-row--c > :nth-child(2) .shot { aspect-ratio:4/5; }
.work-row--c > :nth-child(3) .shot { aspect-ratio:9/10; }

/* The client's name in the didone, what we did for them in the grotesque.
   A distinction with a reason behind it, rather than mixing faces for
   texture — which is the test every other alternation on this page has to
   pass too. */
.shot-title .name {
  font-family:"Kinfolk",Didot,"Bodoni 72",Georgia,serif; font-weight:400;
  letter-spacing:.01em; text-transform:uppercase;
}
/* Row C has no index to carry the caption, so its link label stands rather
   than waiting for a hover. */
.shot-more.is-standing { opacity:1; transform:none; color:rgba(249,249,249,.6); }
.work-item:hover .shot-more.is-standing { color:var(--paper); }

.work-item { display:flex; flex-direction:column; gap:12px; min-width:0; }
.shot { position:relative; overflow:hidden; background:var(--ink); aspect-ratio:4/5; }
/* The film panel is the widest in its row, so it takes a landscape frame
   while the stills beside it stay portrait — the contrast in shape is what
   stops a row of three reading as a strip. */
.work-item--film .shot { aspect-ratio:16/9; }
.shot img,.shot video {
  width:100%; height:100%; object-fit:cover;
  object-position:var(--pos,center center); filter:var(--photo-grade);
  transition:transform .7s var(--ease),filter .7s var(--ease);
}
/* Hover lifts the grade rather than removing it, so a hovered panel does
   not jump to a different colour world than the ones beside it. */
.work-item:hover .shot img,.work-item:hover .shot video,
.work-item:focus-visible .shot img,.work-item:focus-visible .shot video {
  transform:scale(1.04); filter:contrast(1.04) saturate(.82);
}

.shot-title {
  font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif; font-weight:400;
  font-size:17px; line-height:1.15; text-transform:uppercase; letter-spacing:-.01em;
}
.shot-meta { color:rgba(249,249,249,.6); font-size:10px; }
.shot-meta .sep::before { content:"\00b7"; margin:0 .5em; }
/* The whole panel is the link, so standing "learn more" labels only repeat
   an affordance the cursor already gives. Kept in the markup for screen
   readers and revealed on intent. */
.shot-more {
  color:var(--paper); font-size:10px; font-weight:600; letter-spacing:.17em; text-transform:uppercase;
  opacity:0; transform:translateY(-4px);
  transition:opacity .3s var(--ease),transform .3s var(--ease);
}
.work-item:hover .shot-more,
.work-item:focus-visible .shot-more { opacity:1; transform:none; }
/* Touch has no hover state to reveal it with. */
@media (hover:none) { .shot-more { opacity:1; transform:none; color:rgba(249,249,249,.6); } }

/* --- Manifesto ---------------------------------------------- */
/* The measure is set in em, not ch. ch is the width of the resolved font's
   "0" — Kinfolk's is 0.70em and the macOS Didot fallback's is 0.52em, so a
   ch-based measure silently narrows by a quarter whenever the webfont does
   not arrive, which is what turns this headline into seven lines instead
   of five. 13.3em is the same measure Kinfolk gave, now independent of
   which serif actually resolves. */
/* One of the two places the page is allowed to open right out. The
   statement arrives after a long silence and is followed by another, so it
   reads as a held breath rather than as the next item in a list. Empty
   space is the cheapest thing to produce and the most expensive thing to
   look at, and the page had almost none. */
.manifesto { padding-block:clamp(118px,13.5vw,250px); }
.manifesto h2 { margin:0; max-width:13.3em; font-size:clamp(40px,5.6vw,88px); line-height:.94; }
/* The circle is drawn, not typed: a single open stroke that overshoots
   its own start, so it reads as a hand mark rather than an ellipse. */
.ring { position:relative; display:inline-block; }
.ring svg {
  position:absolute; left:-9%; top:-22%; width:118%; height:150%;
  overflow:visible; pointer-events:none;
}
.manifesto .btn-pill { margin-top:clamp(28px,4vh,36px); }

/* --- Founder ------------------------------------------------ */
/* The full-width founder plate is gone on the client's instruction — she
   did not want her photograph large and above the team. She appears in the
   team row instead (see .team-lead), and this is now the copy alone. */
.duo { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:clamp(32px,5vw,48px); align-items:start; }
/* No top margin: it existed only to clear the "Founder" eyebrow above it,
   and that eyebrow has been removed. The body column's padding drops with
   it for the same reason — it was set to meet a headline that started
   lower down, and kept it would leave the two columns out of step. */
.founder-title { margin:0; font-size:clamp(30px,3.3vw,46px); line-height:.95; }
.founder-body { padding-top:8px; color:rgba(249,249,249,.88); font-size:16px; line-height:1.7; }
.founder-body p { margin:0 0 20px; }
.founder-body .btn-pill { margin-top:6px; }

/* --- Team --------------------------------------------------- */
/* The founder is a fixed card and the rest of the team drifts past her.
   Emphasis by behaviour rather than by size: she is the one figure that
   stays put, the first face in reading order, and the only one without a
   photographic background behind her. Nothing here is enlarged, because
   scale is exactly what the client asked to remove. */
/* Her column is wider than a rail card on purpose, and it has to be: the
   cutout carries transparent margins either side, so her body fills only
   about two thirds of its box. Matched box widths would have made her the
   smallest figure of the five — measured at 248 against the rail's 328. */
.team {
  display:grid;
  /* minmax(0,…) on the rail, or its flex track's min-content wins the
     negotiation and squeezes her column back to its own minimum. */
  grid-template-columns:minmax(240px,1.18fr) minmax(0,2.7fr);
  gap:clamp(22px,3vw,54px); align-items:start;
}

.team-lead { margin:0; display:flex; flex-direction:column; gap:14px; }
/* A framed photograph, like every other face here. This card was built for
   anzhelika-cutout.webp and carried the machinery that an alpha cutout
   needs — no frame, an upward scale to crop its transparent margins, and a
   mask fading her legs out so the crop did not read as a cut. The supplied
   portrait has a real background, so all three were removed rather than
   left to act on a picture that does not want them.

   Her emphasis is now entirely positional: the one card that does not
   move, first in reading order, a larger frame and a larger name. */
.team-lead-shot { display:block; aspect-ratio:4/5; overflow:hidden; }
/* The asset is cropped to this ratio on disk, so cover is an exact fit and
   nothing is thrown away at render time. */
.team-lead-shot img {
  width:100%; height:100%; object-fit:cover; object-position:var(--pos,center center);
  filter:var(--photo-grade);
}

/* The rail breaks the right gutter so the next face is always entering the
   margin — the same move the testimonials and the work rows make. */
.team-rail { --gap:clamp(14px,1.7vw,28px); --dwell:5.2s; }
.team-view { overflow:hidden; margin-right:calc(var(--gutter) * -1); }
.team-track {
  display:flex; gap:var(--gap); align-items:flex-start;
  transition:transform 1.05s var(--ease-weight);
}
.team-track.is-jumping { transition:none; }
/* Three sit on the measure and the fourth begins in the gutter. */
.team-item { flex:0 0 calc((100% - var(--gutter) - 2 * var(--gap)) / 3); }
/* Hand-authored, never nth-child and never random: the offset travels with
   the person through the loop, and a value that cannot change between
   passes is the only kind that stays designed. */
.team-item { margin:0; margin-top:var(--drop,0px); display:flex; flex-direction:column; gap:12px; }
/* Only what sits on the measure is lit, as the quote rail does. */
.team-item { opacity:.26; transition:opacity var(--t-weight) var(--ease-weight); }
.team-item.is-lit { opacity:1; }
.team-item .shot { aspect-ratio:3/4; }

.team-lead figcaption,.team-item figcaption { display:flex; flex-direction:column; gap:12px; }
.team-lead h3,.team-item h3 {
  margin:0; font-family:"Kinfolk",Didot,"Bodoni 72",Georgia,serif; font-weight:400;
  line-height:1; text-transform:uppercase; letter-spacing:.01em;
}
/* One size step between her and the rest. Any more and it is a hierarchy
   rather than a nudge. */
.team-lead h3 { font-size:34px; }
.team-item h3 { font-size:26px; }
.team-lead p,.team-item p { margin:0; max-width:24ch; color:rgba(249,249,249,.7); font-size:10px; font-weight:600; letter-spacing:.17em; text-transform:uppercase; line-height:1.35; }
/* Her role line holds full strength; the rail's sit back a step. */
.team-lead p { color:rgba(249,249,249,.88); }
.team-rail .treel-foot { margin-top:clamp(24px,2.6vw,40px); }
.roster-placeholder {
  display:grid; place-items:center; width:100%; height:100%;
  background:linear-gradient(145deg,#34312e,#171717); color:rgba(249,249,249,.18);
  font:400 clamp(90px,11vw,200px)/1 "Kinfolk",Didot,"Bodoni 72",Georgia,serif;
}

/* --- Recognition -------------------------------------------- */
/* Client names, not logo files: thirteen marks drawn in thirteen
   palettes never sit together, and the reference sets the names as type
   with the brand mark between them. */
.marquee {
  --logo-w:clamp(150px,16vw,220px);
  --logo-mark:clamp(25px,2.7vw,38px);
  --logo-h:clamp(40px,4.2vw,56px);
  border-block:1px solid rgba(249,249,249,.28); padding:26px 0;
  overflow:hidden; display:flex; width:max-content; max-width:100%;
}
.marquee-track {
  display:flex; flex:none; align-items:center; white-space:nowrap;
  animation:namesScroll 46s linear infinite;
}
.marquee:hover .marquee-track { animation-play-state:paused; }
/* The supplied marks are black artwork on transparency, so on this page
   they were all but invisible — inverting makes them white without
   touching the transparent ground. They are also 400px canvases with a lot
   of built-in padding, so the box has to run large for the mark to read at
   a normal size. */
/* Every mark gets the same box and is fitted inside it. Constraining by
   height alone let aspect ratio decide optical weight: these range from
   1.00 (a square badge) to 5.10 (a long wordmark), so the square one
   rendered as a 74px block beside 37px strips and owned the whole row.
   A fixed box makes a wide mark fill the width and a square one fill the
   height, which is what actually reads as even. */
.marquee-track figure {
  position:relative; margin:0; flex:none;
  width:var(--logo-w); height:var(--logo-h);
}
/* Each file is a square canvas with the mark letterboxed inside it, and
   the mark fills anywhere from 16% to 80% of that canvas. Fitting the
   canvas therefore sizes the padding, not the logo, which is why one mark
   came out 26px tall and another 6px. --f carries each file's measured
   content fraction, so every mark lands on the same optical height and
   the transparent surplus simply overhangs the box unseen. */
/* Sized, not scaled. The previous version laid each logo out small and then
   transform-scaled it up by as much as 3x, which resamples a 400px source
   past its own resolution — that is the softness that shows the moment
   opacity lifts on hover. Setting the height directly means the browser
   rasterises once, downscaling from the 400px master, so every mark is
   sharp. Centring is absolute rather than grid/flex because an item taller
   than its box stops being centred by those, which is what threw the marks
   onto different baselines when this was last tried. */
.marquee-track img {
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:auto; height:calc(var(--logo-mark) / var(--f,.25)); max-width:none;
  object-fit:contain;
  /* brightness(0) crushes every opaque pixel to black and keeps the alpha,
     then invert lifts it to flat white. A plain invert() leaves greys as
     greys, which is why the halftone behind one wordmark came back as mud
     rather than as part of the mark. */
  filter:brightness(0) invert(1); opacity:.78; transition:opacity .4s var(--ease);
}
/* Two of the supplied marks are already light artwork on a dark ground.
   Inverting those turned them into solid pale blocks; lifting their
   brightness turned the dark ground into a grey slab. They want the grade
   and nothing else, so the ground sinks into the page and only the light
   part of the mark is left. */
.marquee-track img[data-light] { filter:grayscale(1); }
.marquee-track figure:hover img { opacity:1; }
.marquee-track i {
  /* Smaller than the marks it separates — at 18px it was competing with
     the shorter wordmarks instead of punctuating them. */
  flex:none; width:13px; height:13px; margin:0 clamp(14px,1.6vw,24px); background:var(--red);
  -webkit-mask:var(--mark) center/contain no-repeat; mask:var(--mark) center/contain no-repeat;
}
@keyframes namesScroll { to { transform:translateX(-100%); } }

/* --- Proof -------------------------------------------------- */
/* The heading stands alone (§24). The lead paragraph and the two badges
   that sat beside it are gone, so the flex row that held them is gone with
   them and the head is simply a heading. `.stars` stays: it is still worn by
   all six quotes, and it is one of the few red objects §11 left on the
   site. */
.proof-title { margin:0 0 clamp(28px,4vh,40px); font-size:clamp(32px,3.7vw,54px); }
.stars { color:var(--red); font-size:13px; letter-spacing:.2em; }
/* Three quotes on the measure, drifting one column at a time.

   What makes a three-up testimonial slider read as a template is three
   bordered boxes of equal height — that turns quotes into a comparison
   table. There are no boxes here: one hairline runs the full measure and
   the columns hang off it, which is the page's own separator language
   doing the work a border would have done badly. The columns are held
   together instead by the baseline their attributions share.

   Every quote stays authored in the markup. The loop is made from clones
   the script marks aria-hidden, exactly as the logo marquee already does,
   so no copy is written twice and the markup stays the source of truth. */
.treel-wrap {
  border-top:1px solid rgba(249,249,249,.28); padding-top:clamp(30px,3.6vw,52px);
  --gap:clamp(18px,2.2vw,40px);
  --dwell:4.6s;
}
/* Breaks the right gutter, so the next quote is always entering the margin.
   That is how the row says there is more without a dot row underneath it,
   and it is the move the work rows already make. */
.treel {
  overflow:hidden;
  margin-right:calc(var(--gutter) * -1);
}
.treel-track {
  display:flex; gap:var(--gap); align-items:stretch;
  transition:transform 1.05s var(--ease-weight);
}
.treel-track.is-jumping { transition:none; }
/* Three fill the measure exactly; the fourth begins in the gutter. The
   gutter is subtracted because this element is a gutter wider than the
   measure it has to divide. */
.treel-item { flex:0 0 calc((100% - var(--gutter) - 2 * var(--gap)) / 3); }
/* Only what sits on the measure is lit. Asking the eye to read a column
   that is half off the page is the other half of why these feel cheap. */
.treel-item { opacity:.26; transition:opacity var(--t-weight) var(--ease-weight); }
.treel-item.is-lit { opacity:1; }

.tquote {
  margin:0; display:flex; flex-direction:column; gap:clamp(20px,2.4vw,32px);
}
.tquote blockquote {
  margin:0;
  font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif; font-weight:400;
  font-size:clamp(18px,1.55vw,23px); line-height:1.26; text-transform:uppercase; letter-spacing:-.005em;
}

/* Controls. A drifting rail with nothing to steer it reads as broken rather
   than as restraint, so this one earns its furniture — but the least of it:
   two marks, and nothing else.

   There was a filling hairline here and it is gone. Six quotes three at a
   time is a sliding window, not a sequence of pages, so there is no honest
   fraction to show: the window covers positions 5,6,7 of six, which is
   either 133% or a wrap, and it read as a bar overflowing and then jumping
   backwards once a cycle. A progress rule has to measure something real. */
.treel-foot {
  display:flex; align-items:center; gap:10px;
  margin-top:clamp(30px,3.4vw,52px);
}
.treel-step {
  flex:none; width:44px; height:44px; display:grid; place-items:center;
  border:1px solid rgba(249,249,249,.4); border-radius:50%;
  background:none; color:inherit; cursor:pointer;
  transition:background var(--t-detail) var(--ease-detail),color var(--t-detail) var(--ease-detail),border-color var(--t-detail) var(--ease-detail);
}
.treel-step:hover { background:var(--paper); border-color:var(--paper); color:var(--ink); }
.treel-step::before {
  content:""; width:12px; height:12px; background:currentColor;
  -webkit-mask:var(--mark) center/contain no-repeat; mask:var(--mark) center/contain no-repeat;
  transition:transform var(--t-detail) var(--ease-detail);
}
.treel-step:hover::before { transform:rotate(90deg) scale(1.15); }
/* The mark is the page's arrow everywhere else; back is simply it turned
   around, rather than a second glyph introduced for one button. */
.treel-step[data-treel-step="-1"]::before { transform:rotate(180deg); }
.treel-step[data-treel-step="-1"]:hover::before { transform:rotate(90deg) scale(1.15); }

.tattrib { margin-top:auto; display:flex; flex-direction:column; align-items:flex-start; gap:12px; }
.tattrib strong {
  font-family:"Kinfolk",Didot,"Bodoni 72",Georgia,serif; font-weight:400;
  font-size:clamp(22px,1.8vw,26px); line-height:1; text-transform:uppercase; letter-spacing:.01em;
}
.tattrib small { color:rgba(249,249,249,.65); font-size:11px; font-weight:600; letter-spacing:.17em; text-transform:uppercase; }

/* A small square above the stars. Deliberately small: a large portrait
   makes the section about faces, and at this size a client photograph
   reads as a signature on the quote rather than a second subject competing
   with it. */
/* No fill of its own. While the mark is still small the square is mostly
   empty, and any tone at all reads as a lighter patch sitting on the
   section — so the uncovered part is simply the page. Transparent rather
   than a matched hex, so it keeps matching if the section ever changes. */
.tthumb {
  position:relative; overflow:hidden; flex:none;
  width:clamp(52px,5vw,68px); aspect-ratio:1; background:none;
}
.tthumb img {
  width:100%; height:100%; object-fit:cover;
  object-position:var(--pos,center 28%);
  /* The one grade every photograph on the page carries. Six client
     portraits will arrive from six cameras; this is what narrows them. */
  filter:var(--photo-grade);
  /* The picture is always seen through the mark. At rest the mark is scaled
     far past the square, so nothing is actually cropped — the shape only
     becomes visible while it is small, which is the whole of the effect.
     1400% keeps the star's waist clear of the corners; the arms run on the
     axes, so the diagonals are the part that has to be checked. */
  -webkit-mask:var(--mark) center/1400% no-repeat; mask:var(--mark) center/1400% no-repeat;
}

/* The portrait blooms out of the mark. The same move the showreel aperture
   makes at the top of the page, played small: the star opens and the image
   arrives through it, rather than a photograph simply fading up. It is the
   one flourish in this section, and it only fires on the quote being
   turned to. */
/* The timing is carried on the keyframes, not on the element, and the
   element runs linear. The reason is geometric: mask-size interpolates
   linearly, and the mark covers the square at about 250% — only 16% of the
   way from 38% to 1400%. On the page's usual --ease-weight that point
   arrives 25ms in, so the star was a star for a frame and a half and the
   remaining 900ms crawled invisibly from 1300% to 1400%. The star has to
   hold near its own size first and accelerate late, which is the opposite
   curve to everything else here — hence ease-in on the opening segment. */
@keyframes tbloom {
  0%   { -webkit-mask-size:38%; mask-size:38%; opacity:0; animation-timing-function:var(--ease-detail); }
  20%  { -webkit-mask-size:54%; mask-size:54%; opacity:1; animation-timing-function:cubic-bezier(.66,0,.86,.36); }
  100% { -webkit-mask-size:1400%; mask-size:1400%; opacity:1; }
}
/* Hands over as the aperture passes the size of the square. */
@keyframes tseed { 0% { opacity:.34; } 62% { opacity:0; } 100% { opacity:0; } }
/* Gated on the section having arrived, so the bloom is not spent while the
   testimonials are still below the fold. Its finished state is identical to
   the resting state, so if the entrance never runs the picture is simply
   there — the animation can only ever add. */
.treel-wrap.is-in .treel-item.is-entering .tthumb img { animation:tbloom .8s linear both; }
.treel-wrap.is-in .treel-item.is-entering .tthumb::before { animation:tseed .8s linear both; }

/* The seed the bloom appears to grow from, and the stand-in when a quote
   has no portrait yet: the mark, low, on the square. Sitting behind the
   image it costs nothing once the picture covers it. */
.tthumb::before {
  content:""; position:absolute; inset:0; opacity:0; background:var(--paper);
  -webkit-mask:var(--mark) center/42% no-repeat; mask:var(--mark) center/42% no-repeat;
}
.tthumb:empty::before { opacity:.17; animation:none; }

/* Without JavaScript the rail is simply a row that wraps, every quote lit
   and readable. The copy never depends on a script running — the same
   argument as the entrance's hidden state being applied by script rather
   than sitting in the stylesheet. */
.no-js .treel { overflow:visible; margin-right:0; }
.no-js .treel-track { flex-wrap:wrap; row-gap:clamp(34px,4vw,56px); }
.no-js .treel-item { opacity:1; }
.no-js .treel-foot { display:none; }
/* Same contract for the team rail: nothing lights itself without the
   script, so unstyled it would be four people at .26 with the fourth
   clipped out of a hidden overflow. It becomes a row that wraps. */
.no-js .team-view { overflow:visible; margin-right:0; }
.no-js .team-track { flex-wrap:wrap; row-gap:clamp(30px,3.6vw,48px); }
.no-js .team-item { opacity:1; margin-top:0; }
.no-js .team-rail .treel-foot { display:none; }

/* The rows used to carry 36px of their own padding, so this rule inherited
   its breathing room from the last one. It holds its own gap now, but a
   smaller one than when the attribution sat directly above it — the rail's
   own foot already closes the section. */
.proof-foot { margin-top:clamp(30px,3.4vw,58px); padding-top:28px; border-top:1px solid rgba(249,249,249,.28); }

/* --- Belonging ---------------------------------------------- */
/* The qualifier. It sells nothing and lists nothing: it names a feeling
   the reader is meant to recognise in themselves and answers it with
   belonging rather than a service. That is why it sits here — the
   testimonials are other people's words, the closing is the ask, and this
   is what turns "these people got results" into "and you are one of them"
   immediately before being asked to apply.

   So it carries no eyebrow, no button and no body copy. One sentence held
   in space is the whole design; anything else added here would give the
   eye somewhere to go other than the line. */
.belong {
  position:relative;
  padding-inline:var(--gutter); padding-block:clamp(120px,15vw,260px);
  display:flex; flex-direction:column; align-items:center; text-align:center;
  gap:clamp(26px,3.4vw,52px);
}
/* Deliberately below the screenshot's saturation. At full strength this is
   the brightest object on a page whose entire argument is restraint, and it
   would still be ringing when the red closing ask arrives a screen later.
   It has to read as light behind the words, not as a panel they sit on. */
/* Painted as a gradient that reaches transparent inside its own box, not
   as an oval larger than the section. The first build sized a 1254px
   ellipse inside a 725px section under overflow:clip, so the glow was
   guillotined into a rectangle — a hard top edge and hard sides, straight
   across the page. It was invisible to every check because the section was
   screenshotted on its own bounds, which is exactly where the cut fell.

   Deliberately below the saturation of the supplied mock: at full strength
   this is the brightest object on a page whose whole argument is restraint,
   and it would still be ringing when the red closing ask arrives a screen
   later. It has to read as light behind the words, never as a panel they
   sit on — so the corners of the section stay black. */
/* This is the one glow on the page that needed no box change, because it was
   already built to §29's rule: 46% vertical radius centred at 47% reaches
   from 1% to 93% of its own box, so the light finishes inside the element and
   nothing was ever cutting it. Only `.glow-soft` was — its 15% top and bottom
   fades were erasing the gradient's outer shoulders, which is why this glow
   now reads slightly fuller at the same position and the same peak. */
/* THE SIDE INSETS ABSORB THE BREATHE, AND THE LIGHT IS UNCHANGED.
   `inset:0` plus `scale(1.045)` makes this element 4.5% wider than the page
   at the top of its cycle. While `.belong` clipped, that was contained; once
   it stopped, those 8px at a 1440 viewport became real horizontal scroll —
   dead black, since the gradient is already transparent out there (measured:
   brightest pixel in the overflowing columns is 10/255 and nothing exceeds
   24), but scrollable, which the page was not before.

   So the box is inset by 2.153% each side: 95.694% × 1.045 = 100.0%, exactly
   the viewport at full breath. The horizontal radius is then re-expressed as
   a share of the narrower box — 46 / 0.95694 = 48.07% — so the light is the
   same size it always was, at rest and at full scale alike, because both
   states scale by the same factor. The vertical radius is untouched: the box
   height does not change, and only left/right were inset.
   The centre stays 50% 47%, which is what the authored-centre assertion
   checks, so a mistake here shows up as a moved light rather than hiding. */
.belong-glow {
  position:absolute; z-index:-1; inset:0 2.153%; pointer-events:none;
  background:radial-gradient(ellipse 48.07% 46% at 50% 47%,
    rgba(53,36,213,.60) 0%, rgba(53,36,213,.38) 30%,
    rgba(53,36,213,.14) 54%, rgba(53,36,213,.04) 74%, transparent 100%);
  /* Scale only. A translating drift would walk the faded edge back into the
     section boundary and the hard edge would return at one end. */
  animation:belongBreathe 26s var(--ease) infinite alternate;
}
@keyframes belongBreathe { to { transform:scale(1.045); } }

/* The one red on this screen, and the smallest object in the section. */
.belong-mark {
  display:block; width:clamp(26px,2.6vw,38px); aspect-ratio:1; background:var(--red);
  -webkit-mask:var(--mark) center/contain no-repeat; mask:var(--mark) center/contain no-repeat;
}
/* Kinfolk is the default voice of a headline and .alt flips a run back to
   the grotesque — the page's existing mechanism, used unchanged. Here the
   grotesque carries the long clauses and the didone lands on the two
   phrases doing the work: what they are meant for, and what Rysing is. */
/* 20em is measured, not chosen: it is the width at which the sentence
   breaks on its own clauses — "...and your brand / are meant for something
   bigger, / Rysing is the place that gets you." At 16em it split "meant
   for" from "something bigger" and ran the second clause into the third,
   which reads as text that happened to wrap rather than a line that was
   set. It holds at both ends of the size clamp. */
.belong h2 {
  margin:0; max-width:20em;
  font-size:clamp(30px,4.6vw,76px); line-height:1.04; letter-spacing:-.012em;
  text-wrap:balance;
}

/* --- Spotlight ---------------------------------------------- */
/* One photograph with the copy laid over its lower-left, as the client's
   design draws it. This was split into three panels once and has been put
   back; the three keynote case studies still exist further down the page,
   standing on their own without this head. */
/* Hosts no shape, but it carried two of the three things §27's contract
   forbids and both were doing nothing. The `background:var(--ink)` was the
   same black `body` paints, and because .spot does not isolate it painted
   ABOVE any z-index:-1 light reaching this far down the page — a latent
   version of the cut `.reel` was actually shipping. `overflow:hidden` had
   nothing to clip either: the photograph is in flow, the scrim is `inset:0`,
   and `.spot-copy` is inset on all four sides. */
.spot { position:relative; }
/* Every photograph on the page carries the same grade; the stage shot's
   LED wash was the one image still running at full saturation. */
.spot img {
  width:100%; aspect-ratio:16/8; object-fit:cover;
  object-position:var(--pos,center 32%); filter:var(--photo-grade);
}
/* The photograph is dark at the bottom already; this only guarantees the
   copy holds at small sizes where the crop tightens. */
.spot::after {
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(to top,rgba(0,0,0,.82),rgba(0,0,0,.12) 58%,transparent 80%);
}
.spot-copy {
  position:absolute; z-index:1; left:clamp(20px,4vw,56px); right:clamp(20px,4vw,56px);
  bottom:clamp(20px,4vw,56px); display:flex; flex-direction:column; align-items:flex-start; gap:18px;
}
/* In em, never ch (§4). The original set 17ch, and that is exactly the trap
   §4 describes: Kinfolk's zero is 0.70em, so 17ch resolves to ~11.9em with
   the webfont and ~8.8em without it — both of which break this headline
   into three lines, not the two the design draws. Swept 16–22em: the
   design's break "We bring company founders / into the spotlight" holds
   from 17.2 to 20.2em, so this takes the midpoint rather than an edge. */
.spot-copy h2 { margin:0; max-width:18.7em; font-size:clamp(32px,4.1vw,62px); line-height:.98; }
/* One word on blue — besides the belonging glow, the only place the
   secondary brand colour appears between the hero and the footer. */
.spot-copy h2 mark { background:var(--blue); color:inherit; padding:0 .12em; }
.spot-copy p { margin:0; font-size:16px; }

/* --- Newsletter --------------------------------------------- */
/* A centred column, not the two-up with a printed cover beside it. The ask
   is a single field, so everything above it narrows toward that field
   rather than sitting beside a second object competing for the eye.

   This is the one headline below the fold allowed to alternate faces. It
   is also the page's last ask before the closing, and the alternation is
   what distinguishes it from the three section heads around it. */
.fudge { position:relative; text-align:center; }
/* The blue returns at the right edge as the page approaches the footer,
   where it is already waiting behind the wordmark — read as the footer's
   light rising rather than a fourth use of the colour. Painted as a
   gradient that reaches transparent inside its own box: an oversized shape
   under overflow:clip gets guillotined into a rectangle. */
/* The box is 24% taller than the section at the bottom, and the gradient is
   remapped so the light does not move. This glow genuinely overran `inset:0`:
   a 58% vertical radius centred at 64% reaches 122% of the section, so 22% of
   it was being cut — the hard edge `.glow-soft` was hiding rather than fixing.

   §31's transform, for a box grown `inset:-a% 0 -b% 0`: the centre `p`
   becomes `(p + a)/(1 + a + b)` and a vertical radius expressed as a SHARE OF
   THE BOX becomes `r/(1 + a + b)`. Here a=0, b=24, so the divisor is 1.24:
   64 → 51.61 and 58 → 46.77. The light now reaches 4.84%–98.38% of its box,
   inside on both sides. Horizontal values are untouched because the
   element's width does not change (§31), and the right-hand cut at the
   viewport edge is the established exception (§4).

   §33's second rule holds: 24% of a 685px section is 164px of overhang, and
   `.closing` plus the footer sit below it, so the box stops 1252px short of
   the bottom of `body`. */
.fudge-glow {
  position:absolute; z-index:-1; inset:0 0 -24% 0; pointer-events:none;
  background:radial-gradient(ellipse 38% 46.77% at 96% 51.61%,
    rgba(53,36,213,.52), rgba(53,36,213,.30) 34%,
    rgba(53,36,213,.10) 58%, rgba(53,36,213,.02) 76%, transparent 100%);
}
.fudge-copy {
  max-width:1150px; margin-inline:auto;
  display:flex; flex-direction:column; align-items:center; gap:clamp(22px,2.6vw,34px);
}
/* 17.3em is measured: it is the middle of the 17.0–17.6em window in which
   the headline breaks after "business", which is how the supplied design
   sets it. Narrower and it falls to three lines, one per face; wider and
   "and" is dragged up to hang at the end of line one. In em, not px —
   a px measure holds the break at one font size and loses it at the next
   step of the clamp. */
.fudge h2 { margin:0; max-width:17.3em; font-size:clamp(30px,4.1vw,62px); line-height:1.02; letter-spacing:-.012em; }
.fudge-copy > p { margin:0; max-width:58ch; color:rgba(249,249,249,.85); font-size:15px; line-height:1.75; }
.fudge-points { display:flex; flex-wrap:wrap; justify-content:center; gap:10px; margin:0; padding:0; list-style:none; }
.fudge-points li {
  display:inline-flex; align-items:center; min-height:34px; padding:0 18px;
  border:1px solid rgba(249,249,249,.42); border-radius:999px;
  font-size:10px; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
}
.fudge form { width:100%; max-width:820px; }
/* The field is the whole point of the section, so it runs wider than the
   sentence above it — the one element here allowed to be the widest. */
.fudge-field { display:flex; align-items:center; gap:18px; border-bottom:1px solid var(--paper); text-align:left; }
.fudge-field:focus-within { border-color:var(--red); }
.fudge-field input {
  flex:1 1 auto; min-width:0; min-height:56px; padding:0; border:0; outline:0; background:none;
  color:var(--paper); font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-weight:300; font-size:17px;
}
.fudge-field input::placeholder { color:rgba(249,249,249,.5); }
.fudge-field button {
  flex:none; min-height:56px; padding:0; border:0; background:none; color:inherit; cursor:pointer;
  display:inline-flex; align-items:center; gap:8px;
  /* A <button> does not inherit font-family, and this one never declared it:
     the label shipped in the UA default — Arial — set at 11px uppercase on
     .14em tracking, directly beside an input that names Aileron explicitly
     two rules above. Caught by diffing computed styles against the Divi port,
     which inherits the family through its own wrapper reset and was therefore
     the more correct of the two. Every other property here was already set;
     only the family was missed. */
  font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-size:11px; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
  transition:color var(--t-detail) var(--ease-detail);
}
.fudge-field button:hover { color:var(--red); }
.fudge-note { margin:18px 0 0; color:rgba(249,249,249,.55); font-size:12px; }

/* --- Final CTA ---------------------------------------------- */
/* The last thing before the footer, and the only ask on the page that
   should feel inevitable rather than repeated. Centred, and nothing in it
   but the question and the answer. */
.closing {
  position:relative;
  padding-block:clamp(132px,15.5vw,290px);
  display:flex; flex-direction:column; align-items:center; text-align:center;
}
/* Red only. The blue that used to sit high right is gone — the newsletter
   above still bleeds its own blue down into the top of this section, so
   the colour is present without this section adding a second source of it,
   and the ask is left on one light rather than two.

   The red sat at 98%, which put almost all of it under the bottom fade of
   .glow-soft; raised to 74% it reads properly and still clears the fade. */
.closing-glow {
  position:absolute; z-index:-1; inset:0 0 -20% 0; pointer-events:none;
  /* The source is rgba(255,40,8), not --red. Dimming a colour toward
     --ink (#141414, a grey rather than black) raises its green channel
     relative to red, and that is what turns a warm glow brown: --red at
     .26 composites to rgb(77,32,24), green/red .42, against the brand
     red's own .275. Starting hotter and greener-free lands at rgb(81,25,17)
     — .31 — which still reads as red. §25.

     Same retune as the about page's warm glows (§25): the old ramp held
     .26 → .12 → .03 across a 38%x40% ellipse, and the wide middle of that
     curve composites to rgb(77,32,24) over --ink — brown. Brighter core,
     faster falloff, same position and the same read of "one red light, low
     left" that §11 asks for. */
  /* Box 20% taller at the bottom, gradient remapped by §31's transform so the
     light stays where §11 put it: divisor 1.20, so 76 → 63.33 and 42 → 35.
     A 42% radius centred at 76% reached 118% of the section, so 18% of this
     glow was being cut at the footer boundary.

     §33 is about THIS section specifically — it is the last one before the
     footer, which is where a blanket overhang shipped a light that stopped
     dead at the bottom of `body` and left dead scroll behind it. Checked
     rather than assumed: 20% of a 737px section is 147px, and the footer
     below it is 679px tall, so the box ends 532px inside the document. The
     overhang is expressed in % so it scales with the section; the footer
     scales with it, so the margin holds at every width — verified at
     360–1920 rather than argued. */
  background:radial-gradient(ellipse 38% 35% at 10% 63.33%,
    rgba(255,40,8,.56), rgba(255,40,8,.25) 26%,
    rgba(255,40,8,.07) 50%, rgba(255,40,8,.015) 70%, transparent 86%);
}
/* The question breaks between its two faces — "Ready to build a" on the
   grotesque line, "legacy?" alone on the didone. Measured: that holds from
   8.6em up to roughly 12.7em, so 9.4em sits well inside it rather than on
   an edge. In em regardless, because a px measure holds a break at one
   step of the size clamp and loses it at the next. */
.closing h2 { margin:0; max-width:9.4em; font-size:clamp(48px,7.6vw,118px); line-height:.92; }
/* The fill is gone, per the supplied design. Every pill on the page is now
   an outline, so nothing is left to mark this as the one that matters —
   its weight has to come from the space around it and from being the only
   thing on the screen. */
.closing .btn-pill { margin-top:clamp(34px,5vh,52px); }

/* --- Footer ------------------------------------------------- */
/* The page opens and closes on the same two lights. They sit behind the
   wordmark rather than beside it, so the mark reads as lit from within.
   The inner element clips them: left unclipped they would extend the
   document past the footer and add dead scroll at the very bottom. */
/* The top padding used to live under the wordmark, which opened the footer.
   The wordmark is the last thing on the page now, so the hairline above the
   grid opens it instead and needs that room above it or it sits hard against
   the closing section. */
/* No `isolation` any more. The footer was its own compositing group, which
   made it a hard floor that no shape from the page above could cross — the
   note above `.process` wants its blue to "carry on down behind the footer
   and the wordmark, as one light", and this was what stopped it. The group
   now lives on `body`, so a shape reaching the bottom of the last section
   simply continues into the footer instead of being cut off at its top edge.

   `.footer-light` keeps its own `overflow:hidden` and that is unrelated to
   section clipping: it stops the footer's own glows extending the document
   and adding dead scroll past the end, which is still a real problem. */
.site-footer { position:relative; padding:56px var(--gutter) 28px; }
/* overflow:hidden stays — unclipped these extend the document past the
   footer and add dead scroll at the very bottom. The mask is a separate
   job: it fades the top so the red is not cut flat against the closing
   section above, which was the last hard line left on the page. The bottom
   needs no fade; it is the end of the document. */
.footer-light {
  position:absolute; z-index:-1; inset:0; overflow:hidden; pointer-events:none;
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 20%,#000 100%);
          mask-image:linear-gradient(to bottom,transparent 0,#000 20%,#000 100%);
}
.footer-light::before,.footer-light::after {
  content:""; position:absolute; aspect-ratio:1; border-radius:50%; filter:blur(30px);
}
.footer-light::before {
  width:min(58vw,820px); top:-18%; right:-12vw;
  background:radial-gradient(circle at 50% 50%,rgba(240,66,34,.5),rgba(240,66,34,.24) 38%,rgba(240,66,34,.05) 62%,transparent 74%);
  animation:driftA 31s var(--ease) infinite alternate;
}
.footer-light::after {
  width:min(52vw,720px); bottom:-26%; left:-16vw;
  background:radial-gradient(circle at 50% 50%,rgba(53,36,213,.5),rgba(53,36,213,.23) 38%,rgba(53,36,213,.05) 62%,transparent 74%);
  animation:driftB 38s var(--ease) -9s infinite alternate;
}
@media (prefers-reduced-motion:reduce) {
  .footer-light::before,.footer-light::after,.belong-glow { animation:none; }
}
/* THREE QUARTERS OF THE MEASURE, WHICH IS THE PLAYGROUND'S SIZE (§46, §49).
   It ran the full measure from the start — the sign-off, and the only place
   the logo face appears — and came down one step when the star field arrived
   around it, so that the marks have somewhere to be at the ENDS of the
   wordmark and not only above and below it.

   75% IS NOW THE SAME NUMBER IN BOTH PLACES AND THAT IS NOT A COINCIDENCE.
   `.pp-starlogo` is 75% of the playground section's content box, which
   carries the same `var(--gutter)` padding as this footer — §46 chose the
   percentage precisely so the two lockups track each other at every width
   with no number to keep in step. The footer was the reference then and
   matches it now. Change one and change the other, or §46's rig stops
   describing what ships.

   DO NOT REWRITE THIS AS A VW VALUE. 75vw is a different size at every
   width, and it stops tracking the moment `--gutter`'s clamp is touched.

   The anchor has to be a block for the centring to have anything to work
   with: it wraps the image and is inline by default, so `margin:auto` on the
   image alone would centre it inside a box already shrunk to fit it. */
.footer-lockup > a { display:block; }
.footer-mark { display:block; width:75%; height:auto; margin-inline:auto; }
/* THE BAND THE STARS LIVE IN — §48, variant C of the playground.
   `position:relative` and nothing else beyond its own room, which is §27's
   contract for a section hosting a shape: no `isolation`, no `overflow`, no
   opaque background. The field inside it paints nothing of its own — every
   mark is a small box well inside the band — so there is no gradient here for
   §29 or §33 to cut, which is why this one needs no overhang.

   THE PADDING IS WHAT MAKES THE STARS READ AS "AROUND" THE LOCKUP, and it is
   not spacing. The script keeps an ellipse of `logoW/2 + 1.05 gaps` by
   `logoH/2 + 1.35 gaps` clear for the artwork, and the marks live in
   whatever the band has that the ellipse does not. At the full measure that
   was a sweep above and below and nothing at the ends; at 75% (§49) the
   ellipse clears the band's sides as well, so the field closes around the
   wordmark instead of banding across it.

   THE LOWER CLAMPS ARE A CORRECTNESS BOUND, NOT RHYTHM. The hole's vertical
   half-height is `logoH/2 + 1.35 gaps`, which at the footer's 44px gap is
   about 86px at 390 — so with less padding than that the band is ENTIRELY
   inside the hole and the field comes out empty. That is a blank strip
   rather than an obvious bug, and nothing else would catch it. The 150/96
   floors clear it at every width with room to spare; do not take either
   below about 100px without rechecking the count at 390.

   The old `margin-top:72px` on the lockup is this element's top padding now;
   the mark is flush inside the band so the hole the script measures is the
   artwork's box and nothing else. */
.footer-lockup {
  position:relative;
  padding:clamp(150px,12vw,200px) 0 clamp(96px,7.5vw,135px);
}
.footer-grid {
  display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:32px;
  padding-top:32px; border-top:1px solid rgba(249,249,249,.28);
}
.footer-grid > * { display:flex; flex-direction:column; gap:8px; }
.footer-grid h3 { margin:0 0 6px; color:rgba(249,249,249,.5); font-family:inherit; font-size:11px; font-weight:600; letter-spacing:.17em; text-transform:uppercase; }
.footer-blurb { max-width:28ch; }
.footer-legal { align-items:flex-end; text-align:right; }
.footer-grid a { opacity:.82; transition:opacity .25s,color .25s; }
.footer-grid a:hover { opacity:1; color:var(--red); }
.footer-meta { display:flex; flex-wrap:wrap; justify-content:space-between; gap:16px; margin-top:40px; }

/* THERE IS ONE BLUE LIGHT ON THIS PAGE, AND IT LIVES ON .about-studio.
   There was briefly a second on the team section directly above, placed
   bottom-left, with the studio's placed top-left — two glows of the same
   colour in the same corner of two adjacent sections. They met at the
   boundary and read as one doubled blob with a dark notch through it, since
   each is separately masked by .glow-soft at its own edge. The client's
   design shows a single continuous light there; the section boundary is an
   artefact of this build, not of the design.

   Do not add a glow to a section whose neighbour already has one on the
   same side. Two sections cannot share a gradient — they clip independently
   — so the answer is one section carrying it, placed to read as the whole
   moment. */
/* The design's loudest light: a saturated blue filling the upper left
   behind the headline. It is the one place on this page a glow is allowed
   to be big, because the mock draws it that way and because blue does not
   muddy the way the warm ones do. */
.about-studio { position:relative; }
.about-studio-glow {
  position:absolute; z-index:-1; inset:-60% 0 -20%; pointer-events:none;
  /* THE BOX IS TALLER THAN THE SECTION, AND THE LIGHT HAS NOT MOVED.
     §27 / §29: a gradient paints only inside its own element, so an
     `inset:0` glow is cut by its own section and needs `.glow-soft` to hide
     the cut — which is per-section behaviour by definition. The element is
     `inset:-70% 0` instead, 2.4x the section's height, centred on the same
     point so the falloff completes above and below the boundary instead of
     being masked away.

     Horizontal values are untouched — the element's width is unchanged. */
  /* The overhang is sized to THIS light, and asymmetrically. §31 used a
     blanket `inset:-70% 0` on every glow; §33 is the bug that caused — on a
     last-before-footer section the overhang reached past the end of `body`,
     where there is no page fill, so the light stopped at a hard horizontal
     line and the extra height became dead scroll. Two rules, both required:

       1. the box must contain the light's own falloff on every side;
       2. the box must not reach past the bottom of the document.

     With `inset:-a% 0 -b% 0` the box is (1+a+b) times the section height,
     a gradient's vertical centre p becomes (p+a)/(1+a+b), and a vertical
     radius expressed as a share of the box becomes r/(1+a+b). A radius in
     absolute units (vw, px) does not scale and must NOT be divided — §32. */
  background:radial-gradient(ellipse 58% 52.22% at 4% 50%,
    rgba(80,56,250,.84), rgba(64,44,228,.48) 26%,
    rgba(50,34,186,.18) 50%, rgba(40,28,150,.045) 70%, transparent 88%);
}

/* --- About: team grid --------------------------------------- */
/* The homepage's roster as a static grid. None of the rail's machinery is
   here — no clones, no autoplay, no controls, nothing dimmed — because
   nothing is off the measure: four cards, four columns. The stagger is the
   only thing carried over, and it is per-person `--drop` for the same reason
   it is on the homepage (HANDOFF §15), even though with no cloning an
   nth-child pattern could not invert here. */
.about-team-grid {
  display:grid; grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(18px,2.2vw,34px); align-items:start;
  margin-top:clamp(30px,3.6vw,56px);
}
.about-member { margin:0; margin-top:var(--drop,0px); display:flex; flex-direction:column; gap:clamp(14px,1.5vw,20px); }
.about-member .shot { aspect-ratio:4/5; }
.about-member figcaption { display:flex; flex-direction:column; gap:10px; }
.about-member h3 {
  margin:0; font-family:"Kinfolk",Didot,"Bodoni 72",Georgia,serif; font-weight:400;
  font-size:clamp(21px,1.9vw,27px); line-height:1; text-transform:uppercase; letter-spacing:.01em;
}
.about-member p {
  margin:0; max-width:26ch; color:rgba(249,249,249,.7);
  font-size:10px; font-weight:600; letter-spacing:.17em; text-transform:uppercase; line-height:1.4;
}

/* --- About: courage photograph ------------------------------ */
/* `center`, not `end`. Bottom-aligning the copy against a frame this tall
   pins the eyebrow near the foot of the column and leaves a screen of dead
   space above it — which is what the first build did. The design sets the
   photograph running past the copy at both ends, so the copy sits centred
   in it. */
.about-courage-grid {
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,.84fr);
  gap:clamp(34px,5vw,86px); align-items:center;
}
.about-courage-copy { display:flex; flex-direction:column; }
/* The headline and the body were a two-column split before the photograph
   arrived; they stack in one column now, so the body needs its own room off
   the headline rather than inheriting the gap between columns. */
.about-courage-copy .about-body { margin-top:clamp(26px,3vw,46px); }
.about-courage-shot { margin:0; display:flex; flex-direction:column; gap:14px; }
.about-courage-shot img {
  display:block; width:100%; height:auto; aspect-ratio:4/5;
  max-height:min(78vh,760px);
  object-fit:cover; object-position:var(--pos,center center); filter:var(--photo-grade);
}
/* Right-aligned under the frame, as drawn — it reads as a photographer's
   slug rather than a caption introducing the picture. */
.about-courage-shot figcaption { align-self:flex-end; color:rgba(249,249,249,.52); }


/* --- About: recognition ------------------------------------- */
.about-recognition { position:relative; }
.about-recog-title {
  /* 30.2em. The design breaks after "themselves, and". Swept 14–46em at
     1920/1680/1440/1280/1024/900/768; the window differs by width — the
     column is a viewport fraction while the size is a vw clamp — so this is
     the intersection, 28.4–32em, not one width's window. Same conflict §14
     records for .about-h. */
  margin:0 0 clamp(34px,4.4vw,70px); max-width:30.2em;
  font-size:clamp(24px,2.9vw,44px); line-height:1.18;
}
/* The page's own mark, at text size, standing in for a bullet. It is the
   one red object in this section and it is deliberately tiny — §11 wants
   red scarce, and this is the smallest possible use of it. */
.mark-star {
  display:inline-block; width:.42em; height:.42em; margin-right:.5em;
  vertical-align:.2em; background:var(--red);
  -webkit-mask:var(--mark) center/contain no-repeat; mask:var(--mark) center/contain no-repeat;
}
.about-recognition .marquee { margin-top:clamp(10px,1.4vw,20px); }

/* --- About: the standard ------------------------------------ */
.about-standard-title {
  /* 24em, and left alone on purpose. Swept 10–40em: the headline holds one
     line from 19.7 to 39.9em at every width, because it is short enough to
     fit across the whole range. 24em is well inside that, so like the
     conviction measure (§20) it is not doing work and is not worth moving
     to a midpoint. */
  margin:0; max-width:24em;
  font-size:clamp(28px,3.5vw,54px); line-height:1.04;
}
.about-standard-list {
  margin:clamp(28px,3.4vw,52px) 0 0;
  border-top:1px solid rgba(249,249,249,.18);
}
/* Name left, meaning right, on one baseline. `align-items:baseline` rather
   than centre: the didone name and the grotesque description have different
   cap heights, and centring two faces of different size reads as a
   misalignment even when the boxes are even. */
.about-standard-row {
  display:grid; grid-template-columns:minmax(0,.44fr) minmax(0,1fr);
  gap:clamp(20px,3vw,56px); align-items:baseline;
  padding:clamp(18px,2.1vw,30px) 0;
  border-bottom:1px solid rgba(249,249,249,.18);
}
.about-standard-row dt {
  font-size:clamp(22px,2.4vw,36px); line-height:1.05;
}
.about-standard-row dd {
  margin:0; max-width:40em; color:rgba(249,249,249,.84);
  font-size:15px; line-height:1.66;
}

/* --- About: closing ----------------------------------------- */
/* Centred, like the conviction. No glow: the design draws it on flat black,
   and after the standard's five hairlines the page wants one quiet object
   rather than another light. */
.about-closing { text-align:center; }
.about-closing-title {
  /* 13em, not the 11em this was first built at. The design breaks "READY TO
     BUILD YOUR / LEGACY?"; swept 5–24em, intersection 10.4–15.5em across the
     seven widths, so 13 is the midpoint and 11 sat near the bottom edge. */
  margin:0 auto; max-width:13em;
  font-size:clamp(38px,6vw,108px); line-height:1.0;
}
.about-closing-note {
  /* 35.2em. This one was actually wrong at 30em — the window for the
     design's break, after "the size of your", is 33.5–36.9em at every
     width, so 30em broke a word early. Caught by sweeping rather than by
     looking at it. */
  max-width:35.2em; margin:clamp(24px,2.8vw,40px) auto 0; font-size:15px;
  color:rgba(249,249,249,.82); line-height:1.7;
}
.about-closing-actions {
  display:flex; flex-wrap:wrap; justify-content:center; align-items:center;
  gap:clamp(18px,2.4vw,34px); margin-top:clamp(32px,3.8vw,56px);
}
/* The second filled pill on the site. §11 removed the closing ask's red fill
   so every pill became an outline, and §13 notes the contact form's send
   button as the only filled one. The design draws this one filled too, and
   it is the page's single destination, so it takes the weight. Flagged in
   §22 rather than quietly made an outline. */
.btn-pill--solid { background:var(--paper); border-color:var(--paper); color:var(--ink); }
.btn-pill--solid:hover { background:transparent; border-color:rgba(249,249,249,.4); color:var(--paper); }
/* The quiet half of the pair: a rule under a word, so the two controls are
   obviously not equals. */
.btn-quiet {
  font-size:11px; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
  padding-bottom:4px; border-bottom:1px solid rgba(249,249,249,.5);
  transition:border-color .3s var(--ease),color .3s var(--ease);
}
.btn-quiet:hover { border-color:var(--paper); }

/* --- Responsive --------------------------------------------- */
@media (max-width:980px) {
  /* Two on the measure, then one. The basis is the only thing that changes
     — the step, the window and the loop are all measured from the rendered
     geometry, so nothing else has to be told. */
  .treel-item { flex-basis:calc((100% - var(--gutter) - var(--gap)) / 2); }
  .team-item { flex-basis:calc((100% - var(--gutter) - var(--gap)) / 2); }
  /* Below the measure she stops being a column beside the rail and becomes
     the card above it. The order is unchanged, so she is still first. */
  .duo,.team { grid-template-columns:minmax(0,1fr); }
  /* She keeps her portrait proportion and is capped instead of stretched:
     a contained standing figure in a full-width box shrinks to nothing. */
  .team-lead { max-width:300px; }
  .founder-body { padding-top:0; }
  .footer-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }
  .footer-legal { align-items:flex-start; text-align:left; }
}
@media (max-width:820px) {
  /* The hand-set offsets only read at full measure; below it they stack. */
  /* The hand-set proportions and offsets only read at full measure. Below
     it every panel takes the same frame and the offsets are dropped. */
  .work-row,.work-row--a,.work-row--b { grid-template-columns:1fr; gap:34px; }
  .work-row > :nth-child(n) { margin-top:0; }
  .work-item--film .shot { aspect-ratio:16/9; }
  .work-item:not(.work-item--film) .shot { aspect-ratio:4/5; }
  /* Below the measure the copy drops out from over the photograph and
     stands under it, so the scrim has nothing left to protect. */
  .spot-copy { position:static; padding:22px 0 0; }
  .spot::after { display:none; }
  .fudge-field { max-width:none; }
}
@media (max-width:560px) {
  .treel-item { flex-basis:calc(100% - var(--gutter)); }
  .team-item { flex-basis:calc(100% - var(--gutter)); }
  .team-lead h3 { font-size:28px; }
  .team-item h3 { font-size:24px; }
  /* The field and its 27-character button cannot share a line here. */
  .fudge-field { flex-wrap:wrap; }
  .fudge-field button { min-height:44px; margin-bottom:8px; }
  .footer-grid { grid-template-columns:minmax(0,1fr); }
}
@media (prefers-reduced-motion:reduce) {
  .marquee-track { animation:none; }
  .shot img { transition:none; }
  /* Autoplay is off here and the rail steps without easing. The bloom is a
     flourish, so it is the first thing to go; the picture resolves to
     exactly the same frame without it. */
  .treel-track { transition:none; }
  /* The bloom is a flourish, so it is the first thing to go. The picture
     resolves to exactly the same frame without it. */
  .tthumb img,.tthumb::before { animation:none !important; }
}

/* --- Contact page ------------------------------------------- */
/* The eyebrow, the question and the three details are one section so the red
   light runs behind all of them without breaking at a join. */
.contact-top {
  /* `position:relative` and nothing else — §27's contract, §31's conversion.
     The red and the blue below it now meet in the middle of the page
     instead of each stopping at the join they share. */
  position:relative;
  padding-top:clamp(120px,13vw,210px); padding-bottom:clamp(54px,6vw,96px);
}
/* A gradient that reaches transparent inside its own box, never an ellipse
   larger than the section under overflow:clip — that is the hard-edged
   rectangle described in HANDOFF §8. Placed high right, as drawn. */
/* The design draws a body, not a haze: a warm mass with a readable curved
   edge sitting off the top-right corner. Two stops do that — a wide, low
   falloff for the atmosphere, and a tighter, brighter core inside it that
   gives the edge. Held to .52 at the core: at full strength it is the
   loudest thing on a site whose argument is restraint, and the footer's own
   red is only a screen below it. */
.contact-top-glow {
  position:absolute; z-index:-1; inset:-50% 0 -35%; pointer-events:none;

  /* CAREFUL: THE TWO LAYERS DO NOT TRANSFORM THE SAME WAY. The core is
     `circle 30vw` — a radius in viewport units, which does not scale with
     the element, so dividing it would have shrunk the light to nothing.
     Only its centre moves. The second layer's radii are percentages of the
     box and do divide. Check the unit before applying the transform. */
  background:
    radial-gradient(circle 30vw at 88% 49.73%,
      rgba(246,92,48,.52), rgba(227,64,30,.34) 42%,
      rgba(186,46,24,.14) 68%, rgba(140,34,20,.04) 84%, transparent 100%),
    radial-gradient(ellipse 58% 37.84% at 95% 43.24%,
      rgba(240,66,34,.22), rgba(240,66,34,.07) 52%, transparent 86%);
}
.contact-top .label { color:var(--muted); }
/* 14.5em keeps the break after "build a", so the question sets on two lines
   with "legacy?" alone on the didone — the same break the homepage closing
   makes. In em, never px: a px measure holds a break at one step of the
   clamp and loses it at the next (HANDOFF §4). */
.contact-title {
  margin:clamp(20px,2.4vw,34px) 0 0; max-width:10.2em;
  font-size:clamp(40px,6.2vw,96px); line-height:.94;
}
.contact-title .keep { white-space:nowrap; }

.contact-details {
  display:grid; gap:clamp(26px,3vw,48px);
  grid-template-columns:repeat(3,minmax(0,1fr));
  margin:clamp(72px,9vw,150px) 0 0;
}
.contact-details dt { color:var(--muted); margin-bottom:14px; }
.contact-details dd { margin:0; }
.contact-details dd,
.contact-details address {
  font-family:"Kinfolk",Didot,"Bodoni 72",Georgia,serif; font-weight:400;
  text-transform:uppercase; letter-spacing:.01em; line-height:1.25;
  font-size:clamp(17px,1.5vw,23px); font-style:normal;
}
.contact-details a { color:inherit; text-decoration:none; background-image:none; }
.contact-details a:hover { color:var(--red); }

.contact-body { position:relative;
  padding-top:clamp(70px,8vw,128px); padding-bottom:clamp(90px,10vw,170px); }
/* The blue answers the red, low on the opposite side, as drawn. */
.contact-body-glow {
  position:absolute; z-index:-1; inset:-10% 0 -28%; pointer-events:none;
  /* The overhang is sized to THIS light, and asymmetrically. §31 used a
     blanket `inset:-70% 0` on every glow; §33 is the bug that caused — on a
     last-before-footer section the overhang reached past the end of `body`,
     where there is no page fill, so the light stopped at a hard horizontal
     line and the extra height became dead scroll. Two rules, both required:

       1. the box must contain the light's own falloff on every side;
       2. the box must not reach past the bottom of the document.

     With `inset:-a% 0 -b% 0` the box is (1+a+b) times the section height,
     a gradient's vertical centre p becomes (p+a)/(1+a+b), and a vertical
     radius expressed as a share of the box becomes r/(1+a+b). A radius in
     absolute units (vw, px) does not scale and must NOT be divided — §32. */
  background:radial-gradient(ellipse 42% 33.33% at 2% 60.87%,
    rgba(53,36,213,.52), rgba(53,36,213,.26) 40%,
    rgba(53,36,213,.06) 70%, transparent 100%);
}
.contact-cols {
  display:grid; gap:clamp(40px,5vw,96px);
  grid-template-columns:minmax(0,1fr) minmax(0,.92fr); align-items:start;
}
.contact-h { margin:0 0 clamp(26px,3vw,44px); font-size:clamp(30px,3.6vw,52px); }

/* Booking panel. Fixed height so the outline is a drawn shape before Calendly
   loads, rather than a box that snaps open when a third-party script lands. */
/* Taller than the design's panel on purpose: Calendly's booking UI needs
   roughly 700px and a shorter box crops the time slots, which makes the
   widget unusable rather than merely tight. If the embed is ever dropped for
   the designed placeholder, this can come back down to ~580px. */
.calendly-panel {
  position:relative; border:1px solid var(--line);
  height:clamp(560px,58vw,760px); overflow:hidden; background:rgba(0,0,0,.4);
}
.calendly-inline-widget { position:relative; z-index:1; height:100%; }
/* Collapsed until Calendly fills it. An empty but stacked div would otherwise
   cover the fallback and swallow clicks on its link. */
.calendly-inline-widget:empty { display:none; }
/* What is on screen until — or instead of — the embed: blocked, failed, or
   JavaScript off. Also exactly what the design draws. It sits behind the
   widget rather than inside it, so Calendly's opaque iframe simply covers it. */
.calendly-fallback {
  position:absolute; inset:0; margin:0; display:flex; flex-direction:column;
  align-items:center; justify-content:center; gap:16px; text-align:center;
  padding:30px;
}
.calendly-mark {
  width:26px; height:26px; background:var(--red);
  -webkit-mask:var(--mark) center/contain no-repeat; mask:var(--mark) center/contain no-repeat;
}
.calendly-fallback .display { font-size:clamp(22px,2.3vw,30px); }
.calendly-fallback a { color:var(--muted); text-decoration:none; background-image:none; }
.calendly-fallback a:hover { color:var(--paper); }

.contact-form .field { margin-bottom:clamp(26px,3vw,40px); }
.contact-form .label { display:block; color:var(--muted); margin-bottom:12px; }
.contact-form input,
.contact-form textarea {
  width:100%; padding:8px 0; color:var(--paper); background:transparent;
  border:0; border-bottom:1px solid var(--line); border-radius:0;
  font-family:inherit; font-size:16px; line-height:1.5;
  transition:border-color var(--t-detail) var(--ease-detail);
}
.contact-form textarea { resize:vertical; min-height:120px; }
.contact-form input:focus,
.contact-form textarea:focus { outline:none; border-bottom-color:var(--paper); }
/* Flagged only after a submit attempt, never while the field is still being
   typed into for the first time. */
.contact-form .field.is-invalid input,
.contact-form .field.is-invalid textarea { border-bottom-color:var(--red); }
/* The design draws this one filled. It is the only filled pill on the site —
   HANDOFF §11 removed the closing ask's red fill — but this is a form's submit
   rather than a navigation CTA, and a form with no visible commit control is
   the thing that reads as unfinished. */
.btn-pill-solid {
  background:var(--paper); border-color:var(--paper); color:var(--ink);
  cursor:pointer; font-family:inherit;
}
.btn-pill-solid:hover { background:transparent; border-color:var(--paper); color:var(--paper); }
.contact-status { display:block; margin:18px 0 0; color:var(--muted); letter-spacing:.08em; min-height:1em; }
.contact-status.is-error { color:var(--red); }

@media (max-width:900px) {
  .contact-cols { grid-template-columns:minmax(0,1fr); }
  .contact-details { grid-template-columns:minmax(0,1fr); gap:30px; }
}

/* --- About page --------------------------------------------- */
/* Alternation runs through almost every headline here because the supplied
   design sets it that way. That is the opposite of the homepage, where it was
   cut to three moments so it would read as emphasis rather than texture
   (HANDOFF §2). It survives here because this page is one continuous argument
   rather than eleven different sections, but it is the first thing to pull
   back if the page starts reading as wallpaper. */

/* The page opens on a photograph, full bleed, running under the fixed
   header. There is no glow here any more: the red one existed because the
   hero was type on black and needed something behind it, and a light behind
   a photograph is two sources competing. The photograph is the light now. */
.about-hero {
  /* `position:relative` and nothing else, per §27's contract. The photograph
     and its scrim are both `inset:0`, so neither can overflow and neither
     needed the clip; the `isolation` was the real cost — it made the hero a
     private compositing group that the conviction glow below could not reach
     into, which is the opposite of what that glow is for (§25 calls it spill
     from this photograph). */
  position:relative;
  display:flex; align-items:flex-end;
  min-height:clamp(620px,92vh,980px);
  padding:clamp(150px,18vw,260px) var(--gutter) clamp(44px,5vw,78px);
}
/* Two focal points, not one. The box goes from 1.74:1 on a desktop to 0.47:1
   on a phone, so `cover` throws away three quarters of the frame's width at
   the narrow end — a centre crop of a two-person interior lands on whatever
   happens to be between the two people. Both points are authored inline next
   to the src, so swapping the photograph is still one place in the markup. */
.about-hero-shot {
  position:absolute; z-index:-2; inset:0;
  width:100%; height:100%; object-fit:cover;
  object-position:var(--pos,center center); filter:var(--photo-grade);
}
@media (max-width:760px) {
  .about-hero-shot { object-position:var(--pos-narrow,var(--pos,center center)); }
}
/* Two scrims in one box, not one. The bottom carries the copy and has to go
   properly dark; the top only has to hold the header's own links, which are
   small and already light. A single gradient strong enough for the copy
   would flatten the whole frame. */
.about-hero-scrim {
  position:absolute; z-index:-1; inset:0; pointer-events:none;
  background:
    linear-gradient(to top,rgba(0,0,0,.86) 0,rgba(0,0,0,.68) 22%,rgba(0,0,0,.42) 44%,rgba(0,0,0,.15) 64%,transparent 82%),
    linear-gradient(to bottom,rgba(0,0,0,.52) 0,rgba(0,0,0,.14) 14%,transparent 30%);
}
/* The photograph's bottom edge is the one place this section meets the black
   page, and a hard cut there reads as a pasted-in picture rather than a page
   that opens on one. The last band resolves to page black so there is no
   boundary to find — the same move .glow-soft makes, one-sided. */
.about-hero::after {
  content:""; position:absolute; z-index:-1; inset:auto 0 0 0; height:12%;
  pointer-events:none;
  background:linear-gradient(to top,var(--ink),transparent);
}
.about-hero-copy { width:100%; }
/* The eyebrow is a pill here, not a bare label — the design gives this one a
   border because it is the page's first object and has nothing above it. */
.about-pill {
  display:inline-flex; align-items:center; min-height:32px; padding:0 16px;
  margin:0 0 clamp(26px,3vw,52px);
  border:1px solid rgba(249,249,249,.4); border-radius:999px;
  font-size:11px; font-weight:600; letter-spacing:.17em; text-transform:uppercase;
}
.about-title {
  /* 13em: the design sets "RYSING IS A BRANDING / AND VISIBILITY STUDIO".
     Swept 8–26em at 1920/1680/1440/1280/1024/900/768 — the window is
     12.2–13.7em and is identical at every one of them, because the measure
     is in em and the size clamp moves with it. 13 is the midpoint rather
     than an edge. In em, never px, and never ch (HANDOFF §4). */
  margin:0; max-width:13em;
  font-size:clamp(40px,6.6vw,118px); line-height:1.0;
}
/* It sat under the headline and to the right when the hero was type on
   black — the headline was the claim and this the footnote qualifying it.
   Over a photograph everything hangs off one left axis instead, because the
   copy block is now an object sitting on an image and a second axis inside
   it reads as two captions rather than one. */
.about-hero-note {
  max-width:33em; margin:clamp(26px,3vw,46px) 0 0;
  color:rgba(249,249,249,.86); font-size:15px; line-height:1.72;
}

/* Two-column sections: the headline holds the left, the argument the right. */
.about-split {
  display:grid; gap:clamp(34px,5vw,90px);
  grid-template-columns:minmax(0,1fr) minmax(0,1fr); align-items:start;
  margin-top:clamp(26px,3vw,48px);
}
/* 11.6em. The courage headline keeps its three-line break ("A courageous
   brand / refuses to / conform.") from 11 to 20em at 1440 and below, but only
   to 12.25em at 1680 and 1920, where the column is wide enough to pull
   "conform." up. 11.6 is the middle of the intersection, so one value holds at
   every width rather than one that is correct only at the width it was drawn
   at. The why headline is narrower than this cap in its own column at all
   widths, so the nowrap does that work instead. */
.about-h { margin:0; font-size:clamp(30px,3.9vw,58px); line-height:1.04; max-width:11.6em; }
.about-body p { margin:0 0 1.5em; color:rgba(249,249,249,.86); font-size:15px; line-height:1.72; }
.about-body p:last-child { margin-bottom:0; }

.about-conviction {
  position:relative; text-align:center;
  padding-top:clamp(84px,10vw,150px); padding-bottom:clamp(84px,10vw,150px);
}
/* Hangs off the top edge as a body with a readable curve, the way the design
   draws it — the section's own corners stay black so it reads as an object
   rather than a panel the words sit on (HANDOFF §8). */
.about-conviction-glow {
  position:absolute; z-index:-1; inset:-25% 0 0; pointer-events:none;
  /* Small, high and faint — spill from the photograph above, not a lamp
     behind the headline. It was a 34vw circle peaking at .56 directly
     behind the type, which over --ink composites to rgb(124,48,29): brown.
     The curve matters as much as the alpha. A slow ramp spends most of its
     AREA between .15 and .40, and that band is the brown. A bright core
     that falls off fast keeps the mud down to a thin ring nobody reads. */
  /* Deliberately the quietest warm on the page. The design draws this
     section almost black, and the headline sits dead centre — anything
     strong here is a lamp behind the type, which is exactly what the first
     build got wrong. Small, high, and well under the others. */
  /* THE BOX IS TALLER THAN THE SECTION, AND THE LIGHT HAS NOT MOVED.
     §27 / §29: a gradient paints only inside its own element, so an
     `inset:0` glow is cut by its own section and needs `.glow-soft` to hide
     the cut — which is per-section behaviour by definition. The element is
     `inset:-70% 0` instead, 2.4x the section's height, centred on the same
     point so the falloff completes above and below the boundary instead of
     being masked away.

     Horizontal values are untouched — the element's width is unchanged. */
  /* The overhang is sized to THIS light, and asymmetrically. §31 used a
     blanket `inset:-70% 0` on every glow; §33 is the bug that caused — on a
     last-before-footer section the overhang reached past the end of `body`,
     where there is no page fill, so the light stopped at a hard horizontal
     line and the extra height became dead scroll. Two rules, both required:

       1. the box must contain the light's own falloff on every side;
       2. the box must not reach past the bottom of the document.

     With `inset:-a% 0 -b% 0` the box is (1+a+b) times the section height,
     a gradient's vertical centre p becomes (p+a)/(1+a+b), and a vertical
     radius expressed as a share of the box becomes r/(1+a+b). A radius in
     absolute units (vw, px) does not scale and must NOT be divided — §32. */
  background:radial-gradient(ellipse 34% 30.4% at 50% 33.6%,
    rgba(255,40,8,.52), rgba(255,40,8,.22) 28%,
    rgba(255,40,8,.06) 52%, rgba(255,40,8,.015) 72%, transparent 88%);
}
.about-conviction .label { color:rgba(249,249,249,.78); }
/* 11em, and the window here is unusually forgiving: "responsibility." is a
   single unbreakable word, so the measure only has to stop "Visibility is"
   from splitting at one end and from pulling "responsibility." up to join it
   at the other. Swept 3–26em at 1920/1680/1440/1280/1024/900/768 — the break
   the design draws holds from 5.6 to 15em at every width. 11em is kept
   rather than re-centred on 10.3: it is already well inside the window, and
   a measure that is not doing work is not worth moving. */
.about-conviction-title {
  margin:clamp(18px,2vw,30px) auto 0; max-width:11em;
  font-size:clamp(34px,5.8vw,104px); line-height:1.02;
}
/* font-size here is not decoration — it is what makes the measure mean
   anything. The paragraph is a fixed 15px, but `em` on this wrapper used to
   resolve against the inherited body size, which is clamp(15px,1.1vw,17px).
   So the measure grew to 17px-ems at 1920 while the text it was measuring
   stayed at 15px, and the designed line breaks held over three different
   windows that shared no common value — 34.4–35.9em at 1920, 36.9–38.6em at
   1440, 39–40.7em at 1280 and below. Same family of fault as the `ch` trap
   in §4: a measure expressed in units of a different font than the text.
   Setting the size here pins the em, and the window becomes one window.

   39.9em: the design breaks after "the courage to" and after "When you".
   Swept 28–60em at 1920/1680/1440/1280/1024/900/768 — with the em pinned the
   window is 39–40.7em and is now the same at every one of them. 39.9 is the
   midpoint. */
.about-conviction-body {
  max-width:39.9em; margin:clamp(34px,4.4vw,64px) auto 0; font-size:15px;
}
.about-conviction-body p {
  margin:0 0 1.5em; color:rgba(249,249,249,.86); font-size:inherit; line-height:1.72;
}
.about-conviction-body p:last-child { margin-bottom:0; }

.about-founder { position:relative; }
/* Blue, low left, answering the red that has run down the page so far. */
/* The founder section is drawn on flat black and its glow is gone. It was
   a blue ellipse low-left that bled up behind the portrait and read as a
   bruise under her. The blue moves to the team and studio sections below,
   where the designs actually put it. */
.about-founder-grid {
  display:grid; gap:clamp(34px,5vw,86px);
  grid-template-columns:minmax(0,.86fr) minmax(0,1fr);
  align-items:center; margin-top:clamp(26px,3vw,48px);
}
.about-portrait { margin:0; }
.about-portrait img {
  display:block; width:100%; height:auto; aspect-ratio:4 / 5;
  object-fit:cover; object-position:var(--pos,center center);
  filter:var(--photo-grade);
}
/* 7.7em puts "Anzhelika" and "Tauber" on their own lines, one face each,
   as the design sets them. Window is 6 to 9.5em. */
.about-name { max-width:7.7em; margin:0 0 clamp(22px,2.6vw,38px); font-size:clamp(32px,4.4vw,66px); line-height:1.04; }
.about-founder-copy p {
  margin:0 0 1.5em; color:rgba(249,249,249,.86); font-size:15px; line-height:1.72;
}
.about-founder-copy p:last-child { margin-bottom:0; }

/* 20.7em: breaks after "and", as drawn. Window is 20 to 21.5em. */
.about-studio-title { max-width:20.7em; margin-top:clamp(18px,2vw,30px); }
/* The rule is the section's spine: the headline states it, the three columns
   carry it. */
.about-cols {
  display:grid; gap:clamp(26px,3.4vw,64px);
  grid-template-columns:repeat(3,minmax(0,1fr));
  margin-top:clamp(40px,5vw,78px); padding-top:clamp(34px,4vw,62px);
  border-top:1px solid var(--line);
}
.about-cols p { margin:0; color:rgba(249,249,249,.86); font-size:15px; line-height:1.72; }

.about-courage { position:relative; }
/* The courage section carries NO glow. It had one, low right — and the
   stats section directly below has one high right, so the two met at the
   boundary and read as a single doubled blob with a notch through it. The
   same fault as the team/studio pair above, one section further down, found
   by measuring rendered light rather than by eye. The stats glow is the
   bigger moment in the design (it lights the figures and the award block),
   and this section already has warmth of its own from the stage photograph,
   so this is the one that goes.

   THE RULE: no two adjacent sections may carry a glow on the same side.
   They clip independently, so they cannot share a gradient — they can only
   collide. Check it by measuring, not by looking: §25. */

.about-stats { position:relative; }
.about-stats-glow {
  position:absolute; z-index:-1; inset:-35% 0 0; pointer-events:none;
  /* THE BOX IS TALLER THAN THE SECTION, AND THE LIGHT HAS NOT MOVED.
     §27 / §29: a gradient paints only inside its own element, so an
     `inset:0` glow is cut by its own section and needs `.glow-soft` to hide
     the cut — which is per-section behaviour by definition. The element is
     `inset:-70% 0` instead, 2.4x the section's height, centred on the same
     point so the falloff completes above and below the boundary instead of
     being masked away.

     Horizontal values are untouched — the element's width is unchanged. */
  /* The overhang is sized to THIS light, and asymmetrically. §31 used a
     blanket `inset:-70% 0` on every glow; §33 is the bug that caused — on a
     last-before-footer section the overhang reached past the end of `body`,
     where there is no page fill, so the light stopped at a hard horizontal
     line and the extra height became dead scroll. Two rules, both required:

       1. the box must contain the light's own falloff on every side;
       2. the box must not reach past the bottom of the document.

     With `inset:-a% 0 -b% 0` the box is (1+a+b) times the section height,
     a gradient's vertical centre p becomes (p+a)/(1+a+b), and a vertical
     radius expressed as a share of the box becomes r/(1+a+b). A radius in
     absolute units (vw, px) does not scale and must NOT be divided — §32. */
  background:radial-gradient(ellipse 44% 42.96% at 92% 43.7%,
    rgba(255,40,8,.62), rgba(255,40,8,.28) 26%,
    rgba(255,40,8,.08) 50%, rgba(255,40,8,.018) 70%, transparent 88%);
}
/* Five, not the homepage's four, and the same rule above them. auto-fit so
   they fold to a readable number of columns instead of five slivers. */
.about-stats-grid {
  display:grid; gap:clamp(24px,3vw,44px);
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  padding-top:clamp(34px,4vw,62px); border-top:1px solid var(--line);
}
.about-stats-grid .stat { display:flex; flex-direction:column; gap:10px; }
.about-stats-grid .stat strong {
  font-family:"Kinfolk",Didot,"Bodoni 72",Georgia,serif; font-weight:400;
  font-size:clamp(34px,3.6vw,52px); line-height:1; letter-spacing:.01em;
}
.about-stats-grid .stat span {
  font-size:11px; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
  color:rgba(249,249,249,.78);
}
.about-stats-foot {
  display:grid; gap:clamp(26px,4vw,70px);
  grid-template-columns:minmax(0,1fr) auto; align-items:end;
  margin-top:clamp(44px,5.6vw,92px);
}
.about-stats-foot p {
  margin:0; max-width:38em; color:rgba(249,249,249,.86); font-size:15px; line-height:1.72;
}
.about-award { text-align:right; display:flex; flex-direction:column; gap:8px; }
.about-award strong { font-size:clamp(19px,2vw,28px); font-weight:400; letter-spacing:.04em; }
.about-award .label { color:rgba(249,249,249,.72); }

@media (max-width:1080px) {
  /* Four across stops being a row of portraits and becomes a row of stamps
     well before the page is actually narrow. Two up keeps the face readable,
     and the stagger stays, because it is what stops a 2x2 reading as a
     table. */
  .about-team-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }
  .about-recog-title { max-width:24em; }
}
@media (max-width:900px) {
  .about-split,
  .about-founder-grid,
  .about-courage-grid,
  .about-cols { grid-template-columns:minmax(0,1fr); }
  /* The photograph already follows the argument in source order, so it
     simply falls below it — and it stops being a tall portrait, which on a
     phone would push the next section a full screen away. */
  .about-courage-shot img { aspect-ratio:4/3; }
  .about-standard-row { grid-template-columns:minmax(0,1fr); gap:10px; }
  .about-stats-foot { grid-template-columns:minmax(0,1fr); align-items:start; }
  .about-award { text-align:left; }
}
@media (max-width:620px) {
  /* One up. The stagger goes with it — a --drop on a single column is just
     an uneven gap between cards. */
  .about-team-grid { grid-template-columns:minmax(0,1fr); }
  .about-member { margin-top:0; }
}
.about-h .keep { white-space:nowrap; }

/* --- Process: the spotlight brand framework ------------------ */

/* The page is one section and one light. See src/process.html for why it is
   not four. */
/* Converted to the page-level backdrop contract (§34): `position:relative`
   and NOTHING else. No `isolation`, no `overflow:clip`, no `background`.

   The note that stood here said all three were load bearing, because the blue
   blends with `screen` and screen only cancels its black surround if there is
   something to screen *against* — so the section had to be its own group with
   its own fill, and had to clip, or the surround composited toward
   transparency and painted a dark halo over the footer.

   That reasoning was true of a page where `body` was not the compositing
   group. It is not true now. `body` carries `isolation:isolate` and
   `background:var(--ink)`, so a shape at `z-index:-1` screens against the
   page's own black exactly as it used to screen against this section's — the
   same arrangement `.svc-process-blue` already runs on. Verified: no halo,
   measured below.

   The clip was costing the thing §26 actually asked for. The blue is meant to
   carry on down behind the footer and the wordmark as one light, and the clip
   was cutting it flat at the footer boundary — a hard row 42% of the page wide
   at y=1120, mean channel delta 57.6, in the shipped build. */
.process {
  position:relative;
  padding-block:clamp(74px,8vw,128px) clamp(90px,10vw,170px);
}

/* THE BACKGROUND SHAPES ARE THE BRAND ASSETS, NOT GRADIENTS.
   `pattern-red.webp` and `pattern-blue.webp` are the studio's own artwork and
   they are what the design draws — an irregular lit mass inside a soft
   circular vignette, which no radial-gradient reproduces. This page shipped
   once with hand-written gradients in their place and read as four fuzzy
   blobs. If a shape is needed, place the asset.

   THEY ARE PLACED WHOLE, WITH NO BLEND MODE. The asset is a lit mass inside
   a dark circular vignette, and **the vignette is what makes it read as a
   sphere** — it is the only thing giving the shape a circular silhouette.
   `mix-blend-mode:screen` deletes it: screening pure black is a no-op, so the
   surround vanishes and all that is left is the bare orange mass, which is
   an irregular lump and looks like one. That shipped, and the client's word
   for it was "weird". The surround is #000 against a #141414 page, so it
   reads as a soft dark disc — exactly what the design draws.

   The blur is small for the same reason. The asset carries its own softness;
   blurring it hard melts the lit mass into the vignette and the sphere goes
   back to being a blob. (The 24-26px blurs in rysing-branding are for the
   preloader, where the shapes are meant to be formless.) */
.process-blue,
.process-orb {
  position:absolute; z-index:-1; aspect-ratio:1; pointer-events:none;
  background-repeat:no-repeat; background-position:center; background-size:contain;
}

/* THE BLUE IS LIT DIFFERENTLY FROM THE RED, ON PURPOSE. In the design the
   red shapes are objects with an edge and the blue is ambient light with
   none. So the blue — and only the blue — keeps `screen`, which dissolves its
   vignette, plus a heavy blur. Placed whole like the spheres it reads as a
   large dark ball sitting in the corner, which is the opposite of light.

   `screen` adds, which is why it is kept clear of the climb: it was tried
   reaching up behind the modules and every red sphere over it turned PINK. */
/* The overhang is capped in px as well as in per cent, and both caps are
   required — §33's rule, cap both or neither. `bottom:-62%` alone is 62% of a
   section whose height is most of the page, which put the box bottom 15px past
   the bottom of `body` at 1440 and further at every wider viewport. That was
   invisible while the section clipped; uncut it is dead scroll plus a light
   that stops before the document does.
   540px lands the disc's centre on the section/footer join, which is what
   makes it read as one light crossing the boundary rather than two. */
.process-blue {
  left:-24vw; bottom:calc(-1 * min(62%, 540px)); width:min(82vw,980px);
  background-image:url("../rysing-assets/pattern-blue.webp");
  mix-blend-mode:screen; filter:blur(46px); opacity:.92;
}

.process-head { max-width:1680px; margin:0 auto; }
/* Sized to hold "Four modules. One climb." on one line from roughly 1180px
   up, which is how the design sets it. Above that the line is the page's
   whole top edge and a wrap halves its impact. */
.process-title { margin:.34em 0 0; font-size:clamp(34px,5.1vw,74px); line-height:1.0; }
.process-intro {
  margin:clamp(20px,2.4vw,34px) 0 0; max-width:42ch;
  color:rgba(249,249,249,.84); font-size:15px; line-height:1.72;
}

/* The climb. Four columns, bottom-heavy on the left: `--rise` is a step count
   and the margin converts it into the ascent, so module 01 sits three steps
   low and 04 sits at the top. It is margin and NOT transform on purpose —
   `[data-stagger] > *` already owns transform for the reveal, and a transform
   here would be overwritten the moment each step scrolled into view. */
.process-climb {
  --step:clamp(34px,4.6vw,86px);
  list-style:none; margin:clamp(40px,5vw,78px) auto 0; padding:0;
  max-width:1680px;
  display:grid; grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(18px,2.2vw,40px); align-items:start;
}
/* Counts down, so a larger --rise sits higher. */
.process-step {
  position:relative;
  margin-top:calc((3 - var(--rise)) * var(--step));
}
/* The rule above each module, which the design runs full column width. It is
   the only hairline on the page and it is what makes the four read as rungs
   rather than as floating paragraphs. */
.process-step::before {
  /* Stops short of the column rather than filling it, so the four rules read
     as marks on the climb rather than as a table rule running edge to edge.
     Left-anchored: the gradient fades rightward and the bright end has to stay
     with the number it belongs to. */
  content:""; display:block; height:1px; width:88%; margin-bottom:clamp(14px,1.5vw,24px);
  background:linear-gradient(to right,rgba(249,249,249,.42),rgba(249,249,249,.12));
}
.process-step-h { margin:0; font-weight:400; }
.process-num {
  display:block; font-size:clamp(40px,4.9vw,74px); line-height:.9;
  color:var(--paper);
}
.process-step-h .display:not(.process-num) {
  display:block; margin-top:.42em; font-size:clamp(15px,1.42vw,21px); line-height:1.2;
}
.process-step p {
  margin:clamp(12px,1.3vw,20px) 0 0;
  color:rgba(249,249,249,.76); font-size:13.5px; line-height:1.68;
}

/* One light per module, rising with it.

   SIZED AND GRADED AGAINST THE MOCK BY MEASUREMENT, not by eye — eye got it
   wrong three times running. Sampling strongly-saturated orange pixels in the
   client's mock and in the build:

     client mock          12.6% of page, median R 158, p95 rgb(239,65,33)
     ours, first attempt  13.0% of page, median R 209, p95 rgb(239,65,32)

   Identical coverage, identical peak — so the SIZE was never the problem and
   neither was the brightest orange. The fault was the MIDTONE: our spheres
   were flat bright orange across the whole disc where the mock's hold the
   asset's dark internal lobe and a real falloff. They read as solid painted
   rocks for that reason, not because they were too big.

   `brightness()` is the control, and **`contrast()` is the wrong tool here** —
   tried, measured, discarded. The asset's interior sits above mid-grey, so
   contrast pushes the midtone UP (186 → 209 → 230 as it rises) and collapses
   the green channel with it, which breaks the hue: the peak went from
   rgb(239,65,33) to rgb(241,12,4). Darkening and a touch of desaturation land
   on the mock at median R 162.

   Blur stays low — the dark internal lobe and the vignette are the structure
   being preserved, and past ~10px both dissolve and the sphere goes back to
   being a featureless blob. Blur cannot do the darkening either: it only
   widens the orange-to-black ramp, and even 20px reaches a median of 183.

   The copy sits over them, as it does in the mock. That is only safe while
   the midtone stays down; if these are ever brightened, check the body text
   on module 01 before shipping it.

   THE MASK IS WHAT KILLS THE BLACK RING. The asset is a glow painted ON
   BLACK, and that black is opaque artwork, not a transparent surround —
   verified by compositing it over a bright colour, where the ring stays
   solid. So it is #000 meeting a #141414 page, and it reads as an outline
   drawn around every sphere.

   Switching to the cutout does NOT fix it. `Brand Asset_15.png` in the
   client's Pattern Assets folder is the same artwork with alpha (mean
   difference 0.9/255 from our webp — ours is literally that file flattened
   onto black), but its alpha only trims the square corners OUTSIDE the disc.
   The ring is inside and stays. It is a 2.5MB file for no visible change.

   `closest-side` is load bearing: it makes the gradient's 100% equal the
   element's half-width, which is the asset's own circle radius, so the stops
   are in units of that circle. The default (farthest-corner) is 1.41x larger
   and the fade lands outside the artwork entirely, doing nothing.

   The real fix is an asset re-exported to fade to alpha instead of to black.
   Until then this trades the hard ring for a soft edge, which also costs the
   sphere a little of its defined silhouette — that is the trade, and it was
   made knowingly. */
.process-orb {
  left:-44%; bottom:calc(100% - clamp(86px,10vw,170px));
  width:clamp(236px,29.5vw,437px);
  background-image:url("../rysing-assets/pattern-red.webp");
  /* Per-module angle, set in the markup beside --rise. The asset has one
     prominent dark lobe, so four unrotated copies read as one stamp repeated
     four times. Safe to use transform here: the reveal animates transform on
     .process-step, the parent, never on the orb itself. */
  transform:rotate(var(--spin,0deg));
  filter:blur(3px) brightness(.84) saturate(.92);
  -webkit-mask-image:radial-gradient(circle closest-side,#000 44%,rgba(0,0,0,0) 80%);
          mask-image:radial-gradient(circle closest-side,#000 44%,rgba(0,0,0,0) 80%);
}

/* Lower case, and right where the climb tops out. */
.process-rise {
  max-width:1680px; margin:clamp(52px,7vw,120px) auto 0;
  text-align:right; color:rgba(249,249,249,.72);
  font-size:12px; letter-spacing:.04em;
}

@media (max-width:1040px) {
  /* Two up. The step halves rather than going flat: the ascent still reads
     across each pair, which is the point of the section. */
  .process-climb { grid-template-columns:repeat(2,minmax(0,1fr)); row-gap:clamp(34px,5vw,56px); }
  .process-step:nth-child(odd) { margin-top:var(--step); }
  .process-step:nth-child(even) { margin-top:0; }
  /* Fully inboard. The spheres are opaque artwork with a dark rim, so a
     sphere crossing the page edge is not light bleeding off it — it is a
     disc sliced flat, which reads as a clipping bug. On the wide layout only
     01 crosses and the design draws it that way; in two columns it would
     happen to every odd module. */
  .process-orb { left:0; width:clamp(224px,33vw,366px); }
}
@media (max-width:620px) {
  /* One up, and the climb goes flat — a stagger in a single column is just an
     uneven gap. The orbs shrink and tuck behind each number so they stay
     light behind the module rather than drifting off the page edge. */
  .process-climb { grid-template-columns:minmax(0,1fr); row-gap:clamp(30px,8vw,46px); }
  .process-step,
  .process-step:nth-child(odd),
  .process-step:nth-child(even) { margin-top:0; }
  /* Smaller, and alternating sides. Four of them in a single column at the
     wider size stack into one continuous red band down the page — the
     spheres stop being four lights and the body copy has to be read on top
     of it. Alternating breaks the band and keeps one light per module. */
  .process-orb { left:0; right:auto; width:min(61vw,271px); }
  .process-step:nth-child(even) .process-orb { left:auto; right:0; }
  .process-rise { text-align:left; }
}

/* --- Service hero (communication-strategy) ------------------- */
/* One full-height composition rather than a stack: eyebrow high, headline
   filling the middle, note and ask on the floor. `justify-content` is not
   used — the three parts are pinned by `auto` margins on the title so the
   headline keeps its own optical position as the copy wraps at narrow
   widths.

   This section is the first one written to the page-level backdrop contract
   described on `body`: `position:relative` and nothing else. No `isolation`,
   no `overflow`, no `background` — its shapes are free to carry on past the
   section edge and into the footer, which is the whole point.

   The hero's darker ground is gone with the background. It existed to seat
   the shapes against something; they now sit on the page's own fill, and a
   local gradient here would reintroduce exactly the horizontal seam at the
   section boundary that this change exists to remove. */
.svc-hero {
  position:relative;
  min-height:100svh;
  padding:clamp(130px,16vh,190px) var(--gutter) clamp(44px,6vh,74px);
  display:flex; flex-direction:column;
}

/* The parallax contract. JS writes `--py` and nothing else; every shape
   composes it into its own transform, so a shape's rotation and its drift
   never fight over the one `transform` property. A shape that also turns
   declares `--spin`, and the order matters: translate first, then rotate, or
   the drift would be applied along the rotated axis and the shape would
   wander sideways as it scrolls. `--py` defaults to 0, so with JS absent or
   reduced-motion set, every shape simply sits where CSS put it. */
.svc-hero-orb {
  transform:translate3d(0,var(--py,0px),0) rotate(var(--spin,0deg));
}

/* The sphere. Same asset, same grade and same mask as .process-orb — see the
   long note above that rule for why the brightness/saturate pair and the
   `closest-side` mask are what they are. Repeated rather than shared because
   the geometry is the only thing these two have in common: this one is a
   single large light anchored to the page's lower-right corner, not one of
   four stepping up a climb. */
.svc-hero-orb {
  position:absolute; z-index:-1; aspect-ratio:1; pointer-events:none;
  right:-20vw; bottom:-34vh; width:min(104vw,1180px);
  background:url("../rysing-assets/pattern-red.webp") center/contain no-repeat;
  /* Turned off the vertical so the asset's dark internal lobe sits up-left,
     away from the corner — unrotated it lands under the ask and the pill
     loses its ground. Declared as `--spin` rather than written straight into
     `transform`, so the shared parallax rule above can compose it with the
     scroll drift. */
  --spin:-28deg;
  filter:blur(4px) brightness(.72) saturate(.9);
  -webkit-mask-image:radial-gradient(circle closest-side,#000 44%,rgba(0,0,0,0) 80%);
          mask-image:radial-gradient(circle closest-side,#000 44%,rgba(0,0,0,0) 80%);
}

.svc-hero-eyebrow { margin:0 0 clamp(18px,2.4vw,30px); color:rgba(249,249,249,.86); }

/* Three drawn lines, and they have to stay three — the design's shape is the
   step down from a long sans line to a short serif one. `max-width` in ch is
   set against the longest of them so the break points hold rather than being
   forced with <br>, which would wrap badly on a phone. */
.svc-hero-title {
  margin:0 0 auto; max-width:15ch;
  font-size:clamp(33px,7.4vw,118px); line-height:1.02; letter-spacing:-.012em;
}
/* The design breaks after "with", not before it, so the closing word stands
   alone on the last line. Held by a nowrap unit rather than a wider
   `max-width` — the three lines mix a sans and a serif at very different
   widths, so any width that lets "with" up also lets "online" up one line
   earlier and the shape collapses to two lines. Same device as
   `.hero-title .keep`. */
.svc-hero-title .keep { white-space:nowrap; }

/* Note left, ask right, sharing the floor. They sit on the lit side of the
   sphere, so the note keeps near-full opacity — the .76 used on dark ground
   elsewhere is not legible over the orange. */
.svc-hero-foot {
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:clamp(24px,5vw,80px); margin-top:clamp(36px,6vh,72px);
}
.svc-hero-note {
  margin:0; max-width:40ch;
  color:rgba(249,249,249,.92); font-size:14.5px; line-height:1.7;
}

@media (max-width:760px) {
  /* The ask drops under the note: side by side at this width the pill
     squeezes the note to a four-word column. The sphere comes inboard and
     loses its rotation offset so it stays behind the headline rather than
     sliding off the corner. */
  .svc-hero-foot { flex-direction:column; align-items:flex-start; }
  /* The nowrap unit is released here. "Online presence with" is wider than a
     phone at any size the headline is still a headline at, so holding it on
     one line does not preserve the design's shape — it just runs the line off
     the right edge. Below this width the three-line shape is gone anyway and
     the copy wraps naturally. */
  .svc-hero-title { max-width:none; }
  .svc-hero-title .keep { white-space:normal; }
  .svc-hero-orb { right:-28vw; bottom:-20vh; width:min(140vw,760px); }
}

/* The intro: the claim on the left, the argument on the right, and the three
   outcomes it promises on a row beneath.

   Backdrop contract again — `position:relative` only. The hero's sphere
   finishes inside this section's top right and must not be cut at the seam. */
.svc-intro {
  position:relative;
  padding:clamp(78px,10vw,150px) var(--gutter) clamp(90px,12vw,180px);
}

/* Two columns that do NOT meet in the middle. The design sets the headline
   against roughly the left half and starts the body past the centre line, so
   the gap between them is part of the composition rather than a gutter to be
   minimised. */
.svc-intro-head {
  max-width:1680px; margin:0 auto;
  display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);
  gap:clamp(30px,6vw,110px); align-items:start;
}
/* Sized to break into four lines as the design draws it, with "communication"
   owning a line of its own — it is the longest word and what the section is
   about, and "for personal" carried up onto the third line beside "strategy".
   Measured off the mock rather than guessed: its caps run about 2.2% of the
   page width, which is ~41px at 1440 and noticeably smaller than this headline
   was first set. The size is what controls the break, so it is the thing to
   get right before reaching for a nowrap unit. */
.svc-intro-title {
  margin:0; font-size:clamp(27px,3.05vw,46px); line-height:1.1;
}
/* Set slightly down from the headline's cap line. The headline is uppercase
   and the body is not, so aligning their boxes leaves the body looking high. */
.svc-intro-copy { margin-top:.5em; }
.svc-intro-copy p {
  margin:0; max-width:56ch;
  color:rgba(249,249,249,.84); font-size:15px; line-height:1.74;
}
.svc-intro-copy p + p { margin-top:1.5em; }

/* The three outcomes. Each is a word and its completion, under a rule.

   The rules run the FULL column width and at an even weight, unlike
   `.process-step::before` which stops at 88% and fades — there the marks are
   rungs on a climb and the gradient points along it, here the three are equal
   and simultaneous, so an even rule is what reads as parallel. */
.svc-pillars {
  list-style:none; padding:0;
  max-width:1680px; margin:clamp(62px,9vw,140px) auto 0;
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(24px,4vw,76px);
}
.svc-pillar::before {
  content:""; display:block; height:1px; margin-bottom:clamp(26px,3vw,48px);
  background:rgba(249,249,249,.3);
}
.svc-pillar h3 {
  margin:0; font-weight:400; font-size:clamp(27px,3.3vw,49px); line-height:1;
}
/* Lower case and no leading capital: this is the back half of the sentence
   the heading starts, not a caption under it. */
.svc-pillar p {
  margin:clamp(14px,1.6vw,24px) 0 0;
  color:rgba(249,249,249,.82); font-size:14.5px; line-height:1.7;
}

@media (max-width:1000px) {
  /* The headline stops sharing a row with the argument. Side by side below
     this the title loses its four-line shape and the body drops under 40ch. */
  .svc-intro-head { grid-template-columns:minmax(0,1fr); gap:clamp(26px,5vw,44px); }
  .svc-intro-copy { margin-top:0; }
}
@media (max-width:760px) {
  /* Three to one, not three to two — two columns would leave a single pillar
     stranded on its own row, and the rules above them are what make the set
     read as parallel. */
  .svc-pillars { grid-template-columns:minmax(0,1fr); gap:clamp(34px,8vw,52px); }
}

/* The deliverables. Four lit masses in a row with the copy reading on top of
   them — the process climb's composition, flattened into a row.

   Backdrop contract: `position:relative` only. The spheres overlap each other
   and run past the section on both edges, which is the design and is only
   possible because nothing clips at this level any more. */
.svc-get {
  position:relative;
  padding:clamp(70px,9vw,140px) var(--gutter) clamp(100px,13vw,200px);
}

/* THE BLUE IS LIT DIFFERENTLY FROM THE RED, ON PURPOSE — the same split the
   process page makes. The reds are objects with an edge; the blue is ambient
   light with none, so it keeps `screen` (which dissolves its vignette) plus a
   heavy blur.

   `screen` works here only because `body` is now the page's one isolation
   group: the blue composites against the page's own fill, which is what makes
   its black surround cancel. Under the old per-section groups this shape
   would have had to live inside a section that painted its own background.

   KEPT CLEAR OF THE ROW ABOVE IT, and the first attempt was not. `screen`
   adds, so any red sphere sitting over this turns PINK — the process page
   states that outright and this shape was placed over the first sphere
   anyway, which flooded the lower left and dragged the whole corner purple.
   It is now small, well below the row and deep into the corner, which is
   where the design puts it: a wedge of cool at the bottom edge, not a wash
   across the section. */
.svc-get-blue {
  position:absolute; z-index:-1; aspect-ratio:1; pointer-events:none;
  left:-30vw; bottom:-64%; width:min(58vw,700px);
  background:url("../rysing-assets/pattern-blue.webp") center/contain no-repeat;
  mix-blend-mode:screen; filter:blur(46px); opacity:.72;
  transform:translate3d(0,var(--py,0px),0);
}

.svc-get-title {
  max-width:1680px; margin:0 auto; font-size:clamp(31px,4.4vw,66px); line-height:1;
}

.svc-get-list {
  list-style:none; padding:0;
  /* Set so the spheres rise to just graze the title above rather than
     clearing it — in the design the title and the row of lights read as one
     block, not a heading with a gap beneath it. */
  max-width:1680px; margin:clamp(110px,13vw,200px) auto 0;
  display:grid; grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(20px,2.6vw,52px); align-items:start;
}
/* THE TEXT DOES NOT STAGGER — only the lights do. In the design all four
   headings sit on one line and it is the spheres behind them that are set at
   different heights. Offsetting the whole item instead (the first attempt)
   steps the copy down across the row and loses the single strong horizontal
   the four headings make. `--lift` therefore belongs to the orb, below. */
.svc-get-item { position:relative; }

/* One light per item, sitting behind and above its copy so the heading lands
   on the lit mass and the body text runs off its lower edge.

   GRADED BRIGHTER THAN THE PROCESS SPHERES, because the design asks for it:
   these are the page's loud moment and the mock's masses are strongly
   saturated rather than banked down. `contrast()` is still the wrong tool for
   it — the long note above `.process-orb` measured why: the asset's interior
   sits above mid-grey, so contrast pushes the midtone up AND collapses the
   green channel, taking the hue from rgb(239,65,33) to near-pure red.

   The body copy reads over the lower edge of these, which is the one thing to
   re-check if they are ever brightened further.

   THE VIGNETTE IS KEPT HERE, and that is the opposite of the call made on
   the process page. There the asset's black surround met a #141414 ground and
   read as a hard ring drawn around every sphere, so a radial mask traded that
   ring for a soft edge. In this design the spheres OVERLAP, and the dark rim
   is what separates one from the next — mask it away and the four dissolve
   into a single continuous red band with no silhouettes left. The surround is
   doing useful work here, so it stays, and the mask only feathers the last of
   the outer edge rather than cutting into the disc.

   `--lift` is the per-slot height offset, applied here rather than to the
   item so the headings stay on one line. Composed into the same transform as
   the spin and the parallax drift; translate before rotate, or the offset
   would be applied along the rotated axis. */
.svc-get-orb {
  position:absolute; z-index:-1; aspect-ratio:1; pointer-events:none;
  /* Measured off the mock: the heading lands about 60% of the way down its
     sphere, and this offset is what sets that. Raising it to close the gap
     under the title was the wrong lever — it lifted every sphere clear of its
     heading and left the copy sitting on bare ground. The gap belonged to the
     list's top margin; this controls the heading's position on the disc. */
  left:50%; bottom:calc(100% - clamp(150px,19vw,290px));
  width:clamp(250px,24vw,370px); margin-left:clamp(-185px,-12vw,-125px);
  background:url("../rysing-assets/pattern-red.webp") center/contain no-repeat;
  transform:translate3d(0,calc(var(--py,0px) + var(--lift,0px)),0) rotate(var(--spin,0deg));
  filter:blur(3px) brightness(.95) saturate(1.02);
  -webkit-mask-image:radial-gradient(circle closest-side,#000 92%,rgba(0,0,0,0) 100%);
          mask-image:radial-gradient(circle closest-side,#000 92%,rgba(0,0,0,0) 100%);
}

.svc-get-item h3 {
  margin:0; font-weight:400; font-size:clamp(18px,1.72vw,27px); line-height:1.18;
}
.svc-get-item p {
  margin:clamp(14px,1.5vw,22px) 0 0;
  /* Near-full strength, unlike the .76 used on plain dark ground elsewhere:
     this copy sits on the spheres and the dimmer value is not legible there. */
  color:rgba(249,249,249,.93); font-size:13.5px; line-height:1.7;
}

@media (max-width:1080px) {
  /* Two up. The lifts flatten: a stagger across two columns is an uneven gap
     rather than a rhythm. */
  .svc-get-list { grid-template-columns:repeat(2,minmax(0,1fr)); row-gap:clamp(120px,17vw,190px); }
  /* No per-item offset to flatten any more; the lift lives on the orb. */
  .svc-get-orb { --lift:0px; }
  .svc-get-orb { width:clamp(240px,34vw,330px); }
}
@media (max-width:620px) {
  /* One up, and the spheres alternate sides. Four of them centred in a single
     column stack into one continuous red band down the page — the lights stop
     being four and the body copy has to be read on top of all of it. The same
     failure the process climb documents at this width, and the same fix. */
  .svc-get-list { grid-template-columns:minmax(0,1fr); row-gap:clamp(96px,24vw,150px); }
  .svc-get-orb { left:0; margin-left:0; width:min(68vw,300px); }
  .svc-get-item:nth-child(even) .svc-get-orb { left:auto; right:0; }
  .svc-get-list { margin-top:clamp(110px,28vw,190px); }
}

/* The six steps. A table, not a composition: the rows do the work and the
   only shape is the blue disc behind the left column. */
.svc-process {
  position:relative;
  padding:clamp(76px,10vw,150px) var(--gutter) clamp(90px,12vw,180px);
}

/* PLACED WHOLE, VIGNETTE INTACT — lit like the red spheres rather than like
   the other blues on the site. The design draws a disc whose circular edge
   sweeps down across the rows, and that edge only exists while the asset's
   surround is left alone. `screen` is what the process page and the section
   above use to dissolve exactly this surround, because there the blue is
   ambient light with no boundary; using it here would delete the one feature
   the design is asking for.

   The cost of not screening is that the asset's black meets a #141414 page,
   so the mask feathers the last few per cent of the disc to keep that from
   reading as a drawn ring. The same trade the process spheres make. */
.svc-process-blue {
  position:absolute; z-index:-1; aspect-ratio:1; pointer-events:none;
  left:-33vw; top:-19vw; width:min(96vw,1120px);
  background:url("../rysing-assets/pattern-blue.webp") center/contain no-repeat;
  transform:translate3d(0,var(--py,0px),0);
  filter:blur(7px) brightness(.92) saturate(1.04);
  -webkit-mask-image:radial-gradient(circle closest-side,#000 93%,rgba(0,0,0,0) 100%);
          mask-image:radial-gradient(circle closest-side,#000 93%,rgba(0,0,0,0) 100%);
}

.svc-process-eyebrow { max-width:1680px; margin:0 auto; color:rgba(249,249,249,.9); }
.svc-process-title {
  max-width:1680px; margin:clamp(14px,1.8vw,26px) auto 0;
  font-size:clamp(38px,5.6vw,86px); line-height:1;
}

.svc-steps {
  list-style:none; padding:0;
  max-width:1680px; margin:clamp(40px,5vw,78px) auto 0;
  /* The rule under the last row. Every other rule is the next row's own top
     border, so the set closes without a special case in the markup. */
  border-bottom:1px solid rgba(249,249,249,.18);
}
/* Three columns: the numeral, what it is, and what you get. The numeral's
   column is wide because the design sets the name well clear of it — they are
   not a label and its value sitting together, they are two separate columns
   of the table. */
.svc-step {
  border-top:1px solid rgba(249,249,249,.18);
  padding:clamp(22px,2.8vw,40px) 0;
  display:grid; grid-template-columns:minmax(0,.8fr) minmax(0,1.38fr) minmax(0,1fr);
  gap:clamp(16px,2.4vw,44px); align-items:center;
}
/* The heading spans the first two columns and lays them out itself, so the
   number and the name stay one heading for a screen reader while still
   sitting in separate columns visually. */
.svc-step-h {
  grid-column:1 / 3; margin:0; font-weight:400;
  display:grid; grid-template-columns:subgrid; align-items:center;
}
/* Light, large and lining — the design's numerals are the thinnest thing on
   the page, which is what keeps a column of six big digits from shouting
   over the names beside them. */
.svc-step-num {
  font-weight:300; font-size:clamp(38px,4.7vw,72px); line-height:.94;
  letter-spacing:-.01em; color:var(--paper);
}
.svc-step-name {
  font-size:clamp(14px,1.32vw,19px); line-height:1.3;
  text-transform:uppercase; letter-spacing:.005em;
}
.svc-step p {
  margin:0; color:rgba(249,249,249,.82); font-size:13.5px; line-height:1.68;
}

@media (max-width:900px) {
  /* Two columns, and the description drops under the name rather than beside
     it — at this width a third column leaves both it and the name at about
     twenty characters. The numeral keeps its own column so the rows still
     read as a numbered table. */
  .svc-step { grid-template-columns:minmax(0,auto) minmax(0,1fr); gap:clamp(14px,3vw,28px); align-items:start; }
  .svc-step-h { grid-column:1 / 3; grid-template-columns:subgrid; }
  .svc-step p { grid-column:2; margin-top:clamp(8px,1.4vw,12px); }
  .svc-step-num { font-size:clamp(32px,7vw,48px); }
}
@media (max-width:560px) {
  /* The blue stops being a disc with an edge and becomes ambient. At this
     width the whole row sits inside it, and a defined boundary running
     through the numerals reads as a stray line rather than as a shape. */
  .svc-process-blue { left:-42vw; top:-30vw; width:min(150vw,620px); filter:blur(40px) brightness(.9); }
}

/* The evidence. Structurally the pillars row again — hairline, large figure,
   supporting line — and the rule is kept at the same weight and full column
   width on purpose, so the page reads as one device used twice. */
.svc-proof {
  position:relative;
  padding:clamp(60px,8vw,120px) var(--gutter) clamp(90px,12vw,180px);
}
.svc-proof-eyebrow {
  max-width:1680px; margin:0 auto clamp(18px,2.2vw,32px);
  color:rgba(249,249,249,.9);
}
.svc-proof-list {
  list-style:none; padding:0;
  max-width:1680px; margin:0 auto;
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(24px,4vw,76px);
}
.svc-proof-item::before {
  content:""; display:block; height:1px; margin-bottom:clamp(26px,3.2vw,52px);
  background:rgba(249,249,249,.3);
}
/* Larger than the pillar words, which is the design's hierarchy: there the
   heading is a word and here it is the number the whole section exists for.
   `display:block` because <strong> is inline and the line beneath has to
   start on its own row. */
.svc-figure {
  display:block; font-weight:400;
  font-size:clamp(44px,5.6vw,86px); line-height:1;
  /* The figures carry an en dash, a multiplication sign and a percent sign.
     The didone sets those at a different optical weight to its digits, and
     the default tracking leaves "6–7×" looking gappy at this size. */
  letter-spacing:-.01em;
}
.svc-proof-item p {
  margin:clamp(16px,1.8vw,26px) 0 0;
  color:rgba(249,249,249,.82); font-size:14.5px; line-height:1.7;
}

@media (max-width:860px) {
  /* Three to one. Two columns strand the third figure on a row of its own,
     and the rules above them are what make the set read as parallel — the
     same reasoning as the pillars. */
  .svc-proof-list { grid-template-columns:minmax(0,1fr); gap:clamp(32px,7vw,48px); }
}

/* The multiplication sign, set in the sans. The didone's own × is a hairline
   — far finer than the digits beside it — and against the design's heavier,
   geometric mark it read as a stray crossing rather than as part of the
   figure.

   Aileron draws × small inside its em — about a quarter of it — so matching
   the design's mark means setting it well ABOVE 1em and then centring it:
   left on the baseline at a smaller size it hangs low and detaches from the
   figure, which is what the first attempt did. `vertical-align:middle` puts
   the glyph's centre on the parent's x-height midpoint, which is where the
   design sits it. */
.svc-figure-x {
  font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-size:1.45em; vertical-align:middle;
  margin-left:.04em; line-height:0;
  /* No `font-weight` here, and no outline stroke either — both were tried.
     `font-weight` is inert on this glyph: Aileron draws one × and reuses it
     across Regular, SemiBold and Bold (verified by rendering all three, where
     the marks come out pixel-identical while the capital X does change).

     A `-webkit-text-stroke` was then added to force the weight, and it was
     unnecessary: at 1.45em the glyph already carries the design's weight.
     The stroke only looked justified because this rule was sitting behind an
     unbalanced comment terminator at the time, so none of it was applied —
     the × on the page was still the serif's hairline, and every stroke value
     measured against that read as an improvement.

     (Note for whoever edits this: a CSS comment cannot contain the two
     characters that end one, so that sequence cannot be quoted here. Writing
     it literally terminates the comment early and silently drops every
     declaration after it — which is the exact fault described above.) */
}

/* The ask. Centred, on flat black, with no glow — see the markup for why. */
.svc-why {
  position:relative;
  padding:clamp(100px,13vw,210px) var(--gutter) clamp(110px,15vw,240px);
  display:flex; flex-direction:column; align-items:center; text-align:center;
}

/* The brand mark, standing alone above the headline. `.mark-star` is the
   same glyph and the same masking trick, but it is built to sit INLINE
   before a line of type — it carries `margin-right` and a baseline shift,
   and is sized in `em` off the headline it precedes. Here the mark is its
   own object on its own line, so the geometry is different even though the
   artwork is identical. Shares the `--mark` token rather than a second copy
   of the SVG. */
.svc-why-mark {
  display:block; width:clamp(26px,2.6vw,40px); aspect-ratio:1;
  margin-bottom:clamp(26px,3.4vw,52px);
  background:var(--red);
  -webkit-mask:var(--mark) center/contain no-repeat;
          mask:var(--mark) center/contain no-repeat;
}

.svc-why-title {
  margin:0; max-width:13em;
  font-size:clamp(38px,6.4vw,100px); line-height:.96; letter-spacing:-.012em;
}

/* Centred measure. Narrower than the left-aligned body copy elsewhere on the
   page: centred text has no common left edge to return to, so a long line is
   markedly harder to track back from — the two paragraphs are set to break
   into two lines each, as the design draws them. */
.svc-why-copy {
  margin:clamp(28px,3.6vw,54px) 0 0; max-width:62ch;
}
.svc-why-copy p {
  margin:0; color:rgba(249,249,249,.88); font-size:15px; line-height:1.72;
}
.svc-why-copy p + p { margin-top:1.7em; }

/* Solid, and this page is the exception. The note on `.closing` records that
   the homepage's fill was removed so every pill became an outline — this
   design asks for a filled one here, and it is the only filled pill on the
   page, which is what marks it as the ask rather than a link. */
.svc-why .btn-pill { margin-top:clamp(32px,4.4vw,60px); }

/* --- Service page: the price -------------------------------- */
/* Headline left, one plan card right, and the page's largest shape behind
   both. The two columns do not meet in the middle, the same composition as
   `.svc-intro` — the design sets the headline against the left half and the
   card past the centre line, and the space between them is part of the
   layout rather than a gutter to be closed. */
.svc-price {
  position:relative;
  padding:clamp(84px,11vw,170px) var(--gutter) clamp(100px,13vw,200px);
}
.svc-price-wrap {
  max-width:1680px; margin:0 auto;
  display:grid; grid-template-columns:minmax(0,1fr) minmax(0,.82fr);
  gap:clamp(34px,6vw,120px); align-items:start;
}

/* A GLOW, NOT THE DISC ASSET, and that is a deliberate departure from the
   mock. Built first with `pattern-red.webp` placed whole the way
   `.svc-process-blue` is — the mock appears to draw a circular edge sweeping
   down past the card — and it reproduced §27's recorded `.svc-get` failure
   exactly: the lit mass floods the section, the card sits inside it and the
   body copy ends up on saturated orange. The asset is a dense object; this
   corner wants light.

   THE FAINT ARC IN THE MOCK IS NOT BUILT. §27: a decorative arc was read out
   of a mock once before, added to this page's hero, and rejected by the
   client outright — "if a thin curved line ever reappears on a service page,
   it is a bug". A second thin curve read out of a second mock is the same
   judgement call that was already made and already overturned, so it is
   flagged rather than shipped. See §29.

   THE BOX IS NOT THE SECTION, AND THAT IS THE WHOLE POINT. This shipped once
   as `inset:0` — a box exactly the size of `.svc-price` — with the gradient's
   centre near its top edge. The light therefore began AT the section
   boundary and was cut flat across the full width of the page: the cropped
   backdrop §27 exists to abolish, reintroduced by hand in the first new
   shape written after it. The client caught it immediately, and had already
   given the instruction once.

   `inset:0` on a glow is the tell. The rule is §8's and it is about the box,
   not about clipping: a gradient must reach transparent INSIDE its own
   element. A centre at `2%` cannot — 98% of that light falls outside the box
   and the box edge becomes a drawn line. Nothing has to clip for this to
   happen; the background simply is not painted beyond its own element.

   So the element is sized and placed by the light rather than by the
   section. It is a square two-thirds wider than the viewport whose CENTRE is
   parked on the section's top-right corner, which puts the full falloff
   inside it on every side. It then bleeds up into the testimonial rail above
   and down behind the card, which is what makes it read as the page's light
   rather than this section's decoration. That bleed is legal only because of
   §27's page-level compositing group — on the old per-section model it would
   have left a halo at the boundary.

   The source is `--red` itself, unlike `.closing-glow` which has to start
   hotter. §25's brown is an artefact of compositing red toward #141414, a
   grey, which raises the green channel relative to red. Over the #000 page
   (§28) the composite is proportional and the hue holds, so the brand red
   can be used directly here. */
.svc-price-glow {
  position:absolute; z-index:-1; pointer-events:none;
  top:0; right:0; width:min(166vw,2100px); aspect-ratio:1;
  /* Centre the box on the section's top-right corner, then carry it 42% of
     its own width back to the right so the core sits where the mock puts it.
     The parallax drift composes here rather than replacing the placement —
     translate before anything else, as §27 requires. */
  transform:translate3d(0,var(--py,0px),0) translate(42%,-50%);
  background:radial-gradient(circle closest-side,
    rgba(240,66,34,.52) 0%, rgba(240,66,34,.3) 26%,
    rgba(240,66,34,.1) 48%, rgba(240,66,34,0) 70%);
}

/* Four lines, breaking where the design breaks them — "Set the foundation /
   with a clear / message and brand / communication", with the longest word
   owning the last line.

   11.6em is the midpoint of the window, not a value that happened to work.
   Swept 10 to 13em at 1680, 1440 and 1200: at 10em it falls to five lines
   and "Set the" is left hanging alone; at 13em it collapses to three at 1680
   only, so that end is also a breakpoint-dependent edge. 11 to 12.2em holds
   the design's break identically at all three widths.

   In em and never ch — §4. Kinfolk's zero is 0.70em against the Didot
   fallback's 0.52em, so a ch measure here would narrow by a quarter on every
   cold load under font-display:swap and take this to five lines. */
.svc-price-title {
  margin:0; max-width:11.6em;
  font-size:clamp(32px,3.9vw,60px); line-height:1.04; letter-spacing:-.005em;
}

/* No fill. The disc behind the card has to read THROUGH it — in the mock the
   card's top right is lit by the same light that lights the page around it,
   which only happens while the card is a hairline and nothing else. An
   opaque panel here would also break §27's contract by making the card its
   own ground. */
.svc-plan {
  border:1px solid rgba(249,249,249,.22);
  padding:clamp(28px,3.2vw,48px) clamp(24px,2.9vw,44px) clamp(28px,3.2vw,46px);
}
.svc-plan-name { margin:0; color:var(--paper); }
.svc-plan-note {
  margin:clamp(10px,1.1vw,16px) 0 0;
  color:rgba(249,249,249,.78); font-size:15px; line-height:1.5;
}

/* The figure and its qualifier are one line but two objects. `+ VAT` sets
   small and in the grotesque against the didone digits, and sits low rather
   than on the cap line — superscripting it would read as a footnote marker
   on a number, which is a different claim. */
.svc-plan-price {
  display:flex; align-items:baseline; gap:clamp(10px,1vw,16px);
  margin:clamp(16px,1.9vw,28px) 0 clamp(22px,2.6vw,38px);
}
.svc-plan-figure {
  font-size:clamp(46px,5.6vw,88px); line-height:1; letter-spacing:.005em;
}
/* A touch of air after the symbol, which the didone does not give it. */
.svc-plan-cur { margin-right:.16em; }
.svc-plan-vat {
  font-size:12px; font-weight:600; letter-spacing:.13em; text-transform:uppercase;
  color:rgba(249,249,249,.84);
}

/* Each inclusion stands on its own rule. The rule is the item's own top
   border rather than a separator element, so the first one lands under the
   price without anything extra being drawn for it. */
.svc-plan-list { list-style:none; margin:0; padding:0; }
.svc-plan-list li {
  position:relative;
  border-top:1px solid rgba(249,249,249,.15);
  padding:clamp(16px,1.7vw,24px) 0 clamp(16px,1.7vw,24px) clamp(30px,2.4vw,38px);
  color:rgba(249,249,249,.9); font-size:15px; line-height:1.55;
}
/* The bullet is the brand mark, in red — the same glyph the closing ask
   stands under and the same masking trick, sized here to sit on the first
   line of its item rather than to stand alone. */
.svc-plan-list li::before {
  content:""; position:absolute; left:0; top:calc(clamp(16px,1.7vw,24px) + .32em);
  width:clamp(11px,.85vw,13px); aspect-ratio:1;
  background:var(--red);
  -webkit-mask:var(--mark) center/contain no-repeat;
          mask:var(--mark) center/contain no-repeat;
}

/* The funding line carries its own rule and a wider gap above it, because it
   qualifies the whole card rather than continuing the list. Full strength
   paper, not the list's .9: it is the one line on the card that changes what
   the figure above it costs. */
.svc-plan-fine {
  margin:clamp(26px,3vw,44px) 0 0;
  padding-top:clamp(20px,2.2vw,32px);
  border-top:1px solid rgba(249,249,249,.15);
  color:var(--paper); font-size:13.5px; line-height:1.6;
}
.svc-plan .btn-pill { margin-top:clamp(20px,2.2vw,32px); }

@media (max-width:1000px) {
  /* The card stops sharing a row with the headline. Side by side below this
     the card falls under ~300px and the four inclusions each wrap to three
     lines, which turns the list into a paragraph. */
  .svc-price-wrap { grid-template-columns:minmax(0,1fr); gap:clamp(40px,7vw,64px); }
  .svc-price-title { max-width:11em; }
  .svc-plan { max-width:560px; }
}
@media (max-width:560px) {
  /* Taken down, not repositioned. On a phone the card is directly under the
     light rather than beside it, so the same strength puts the price and the
     first two inclusions on lit ground — the thing §27 says to re-check
     whenever one of these is brightened. Only the stops change: the box
     geometry is what keeps the light uncut, so it must not be touched here. */
  .svc-price-glow {
    background:radial-gradient(circle closest-side,
      rgba(240,66,34,.38) 0%, rgba(240,66,34,.2) 26%,
      rgba(240,66,34,.07) 48%, rgba(240,66,34,0) 70%);
  }
}

/* --- Service page: the closing ------------------------------ */
/* Two sentences and one pill, centred on black. The page's only blue, and
   it arrives at the very bottom where the footer's own blue is already
   waiting below it — so it reads as one light rising into the footer rather
   than as a third colour introduced on the last screen. §26 asked for that
   and §27's page-level group is what finally allows it. */
.svc-close {
  position:relative;
  padding:clamp(110px,15vw,240px) var(--gutter) clamp(120px,16vw,260px);
  display:flex; flex-direction:column; align-items:center; text-align:center;
}

/* PLACED WHOLE, VIGNETTE INTACT — the mock draws a disc whose circular edge
   sweeps up out of the bottom-left corner, and that edge only survives while
   the asset's own surround is left alone. `screen` would dissolve exactly
   the feature being asked for. Same call as `.svc-process-blue`; §26's table
   says which shapes take which of the two lightings.

   THE BOX IS THE DISC, NOT THE SECTION. It is anchored past two edges on
   purpose, so the shape carries off the left of the page and down behind the
   footer as one light. That is legal because of §27 and it is the opposite
   of what §29's glow did wrong — there the element was `inset:0`, the
   gradient was cut by its own box and drew a line across the page. An image
   placed `contain` cannot be cropped that way as long as its box is sized to
   the artwork, which is what these numbers do.

   No `.glow-soft`: that mask fades a glow before its own section's top and
   bottom edges, which is precisely the per-section behaviour this shape is
   not supposed to have. */
.svc-close-blue {
  position:absolute; z-index:-1; aspect-ratio:1; pointer-events:none;
  /* The offset is capped the same way the width is. `width` stops growing at
     900px (viewport 1429) but a raw `-42vw` offset keeps going, so above that
     the disc drops further for no reason — and at 1920 it reached 32px past
     the bottom of `body`, which is §33: light past the end of the page fill,
     plus dead scroll. Cap both or neither. */
  left:-14vw; bottom:calc(-1 * min(42vw,600px)); width:min(63vw,900px);
  background:url("../rysing-assets/pattern-blue.webp") center/contain no-repeat;
  transform:translate3d(0,var(--py,0px),0);
  filter:blur(7px) brightness(.76) saturate(1.04);
  /* Feathers only the last few per cent of the outer edge. It is not hiding
     a ring any more — at #000 the asset's surround matches the page exactly
     (§28) — but it still keeps the artwork's own square corners out of the
     silhouette. */
  -webkit-mask-image:radial-gradient(circle closest-side,#000 95%,rgba(0,0,0,0) 100%);
          mask-image:radial-gradient(circle closest-side,#000 95%,rgba(0,0,0,0) 100%);
}

.svc-close-title {
  margin:0; max-width:16em;
  font-size:clamp(34px,5.4vw,86px); line-height:1.12; letter-spacing:-.008em;
}
/* One sentence per line. Block rather than a <br>: the break is structural —
   these are two sentences the design pairs — and a block span keeps each one
   wrapping inside itself on a narrow screen instead of breaking in the same
   place regardless of room. */
.svc-close-line { display:block; }

.svc-close .btn-pill { margin-top:clamp(34px,4.6vw,64px); }

@media (max-width:620px) {
  /* The disc is sized in vw against a desktop viewport, so on a phone the
     same rule leaves a ~250px blob sitting in the corner rather than a light
     sweeping out of it — the shape stops reading as a disc at all. Scaled to
     the viewport and pushed further out, so the visible part is still an arc
     crossing the corner and not a whole small circle. */
  .svc-close-blue { left:-40vw; bottom:-56vw; width:min(100vw,470px); }
}

/* === Case study ================================================
   The Gerd Bommer page, and the shape every case study after it
   takes. Prefixed `.case-` so a second case study is a new source
   file and no new CSS.

   ONE RULE RUNS THROUGH ALL OF IT: this page shows another brand's
   work. Royal blue and sharp yellow are Gerd's colours, not ours,
   and they appear here as artwork — so `--photo-grade` is applied
   to the PHOTOGRAPHS only and never to the logo lockups, the
   website screens or the LinkedIn posts. Grading those would pull
   the saturation out of the very thing the page is presenting as
   evidence. See `.case-shot` against `.case-logos img`. */

.case-hero {
  position:relative;
  padding:clamp(120px,15vh,180px) var(--gutter) clamp(80px,10vw,150px);
}

/* Same parallax contract as the service shapes: JS writes --py, the
   shape composes it with its own --spin. */
.case-hero-orb,
.case-close-blue,
.case-close-red {
  transform:translate3d(0,var(--py,0px),0) rotate(var(--spin,0deg));
}

/* BLUE low-right, not red. The mock places its blue sphere here and
   saves the red one for the closing — checked against the export's
   own assets rather than guessed from the rendered page, where the
   two read alike once they are dimmed this far. The service hero
   uses red in the same corner; this page is the other way round on
   purpose, and that is the client's drawing.

   Page-level backdrop, §27: the shape carries down out of the hero
   and into the section below, so this section declares
   `position:relative` and nothing else. Placed WHOLE with the
   vignette intact, so it keeps a circular edge. */
.case-hero-orb {
  position:absolute; z-index:-1; aspect-ratio:1; pointer-events:none;
  /* Only a corner of the disc is meant to be in frame. The mock hangs
     it 55% of the section below the fold and 18% off the right, so
     what reads is an arc of light in the corner — not a sphere
     parked behind the portrait. Brightness is well under the service
     page's because that one lights an empty corner and this one
     lights the corner the body copy starts in. */
  right:-20vw; bottom:calc(-1 * min(54vw,760px)); width:min(74vw,1060px);
  background:url("../rysing-assets/pattern-blue.webp") center/contain no-repeat;
  /* Turned so the asset's bright lobe hangs below the fold and only
     its falloff reaches up into the section. Unrotated the lobe
     lands square behind the body copy. */
  --spin:148deg;
  filter:blur(8px) brightness(.34) saturate(1.04);
  -webkit-mask-image:radial-gradient(circle closest-side,#000 92%,rgba(0,0,0,0) 100%);
          mask-image:radial-gradient(circle closest-side,#000 92%,rgba(0,0,0,0) 100%);
}

/* THE FIX THE MOCK NEEDS. Its hero is a flex row of `3 1 420px` and
   `2 1 300px` with no measure on the <h1>, so at desktop widths the
   headline overruns its own column and "UNTAMED" sits under the
   photograph. Explicit columns instead: the text column cannot
   spill into the image's, whatever the headline says. A later case
   study with a longer name therefore wraps instead of colliding. */
.case-hero-grid {
  display:grid;
  grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);
  gap:clamp(32px,5vw,72px);
  align-items:end;
}
.case-hero-text { min-width:0; }

.case-hero-eyebrow { margin:0 0 clamp(20px,2.6vw,34px); color:rgba(249,249,249,.86); }

/* Measured in em, never ch — §"decisions already settled" 4: ch is
   the resolved font's zero width and Kinfolk's differs from the
   Didot fallback's by a quarter, so a ch measure silently reflows
   on every cold load under font-display:swap. */
.case-hero-title {
  margin:0; max-width:7em;
  font-size:clamp(46px,8.2vw,132px); line-height:.89; letter-spacing:-.006em;
}

.case-hero-note {
  margin:clamp(26px,3.4vw,40px) 0 0; max-width:34em;
  color:rgba(249,249,249,.92);
  font-size:clamp(15px,1.25vw,19px); line-height:1.6;
}

/* Scope, not navigation — see the markup note. Hairline rather than
   the full-strength border `.btn-pill` carries, and no hover state,
   so the block does not read as ten controls. */
.case-tags {
  list-style:none; margin:clamp(30px,3.8vw,46px) 0 0; padding:0;
  display:flex; flex-wrap:wrap; gap:8px; max-width:46em;
}
.case-tag {
  border:1px solid rgba(249,249,249,.42); border-radius:999px;
  padding:7px 16px;
  color:rgba(249,249,249,.88);
  font-size:12.5px; line-height:1.05; letter-spacing:.005em;
}

.case-hero-portrait {
  width:100%; height:auto; aspect-ratio:4/5; object-fit:cover;
  filter:var(--photo-grade);
}

/* --- The body column ------------------------------------------ */
/* One measure and one rhythm for everything between the hero and
   the closing, as the mock stacks it. */
.case-body {
  position:relative;
  max-width:1224px; margin:0 auto;
  padding:0 var(--gutter) clamp(100px,13vw,200px);
  display:flex; flex-direction:column; gap:clamp(86px,11vw,150px);
}

/* Heading left, copy right, and they do NOT meet in the middle —
   same two-column device as the service page's intro. */
.case-row {
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1.72fr);
  gap:clamp(22px,4.6vw,72px);
  align-items:start;
}
.case-row-title {
  margin:0;
  font-size:clamp(34px,4.4vw,64px); line-height:.91; letter-spacing:-.004em;
}
/* The micro-label standing in for a heading, where the design sets
   the big type on the statement above instead. */
.case-row-label { margin:0; color:rgba(249,249,249,.86); padding-top:.5em; }

.case-copy { min-width:0; max-width:38em; display:flex; flex-direction:column; gap:1.1em; }
.case-copy p { margin:0; color:rgba(249,249,249,.86); font-size:clamp(15px,1.2vw,18px); line-height:1.62; }

/* The narrative three-up: about, the brief and the strategy side by
   side. Three columns rather than three stacked rows, because this is
   the page's only purely verbal stretch and as rows it ran to three
   screens with nothing to look at before the first photograph.

   The hairline above each column is `.svc-pillars`' device, reused
   deliberately — the site already says "three parallel things" this
   way, and a second grammar for the same idea is one too many.

   The columns are TOP-ALIGNED and the rules sit on one line, so the
   three read as parallel even though the copy runs to different
   lengths. Nothing is stretched to match; an even bottom edge here
   would mean padding one column with air. */
.case-notes {
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(24px,3.6vw,64px);
  align-items:start;
}

/* THE CASCADE. Each column starts lower than the one before it, so the
   three unequal lengths read as a descent rather than as a grid that
   failed to line up. The first attempt set them flush and the ragged
   bottoms left a pit of dead air under the middle and right columns —
   stepping them turns that same inequality into the composition.

   Stepped on the slot, not the item, which is the rule `.svc-get-list`
   follows for its own `--lift`: the rhythm belongs to the row, so it
   survives the copy being reordered.

   The rule sits above each column rather than running across all three
   precisely because they no longer start at the same height; one
   continuous line is not available here, and three stepped ones are
   what draws the descent. */
.case-note::before {
  content:""; display:block; height:1px; margin-bottom:clamp(22px,2.6vw,40px);
  background:rgba(249,249,249,.3);
}
.case-note:nth-child(2) { margin-top:clamp(30px,4.4vw,78px); }
.case-note:nth-child(3) { margin-top:clamp(60px,8.8vw,156px); }

/* The numeral. Aileron 300 — the face and weight the homepage service
   numerals take (§"decisions already settled" 3); the didone never sets
   a numeral on this site. Muted, because it is a counter and not a
   heading: the <h2> under it is what carries the section. */
.case-note-num {
  margin:0 0 clamp(10px,1.1vw,16px);
  font-family:"Aileron","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-weight:300; font-size:clamp(30px,3.4vw,52px); line-height:1;
  color:rgba(249,249,249,.34); letter-spacing:-.02em;
}
/* Well below `.case-row-title`'s 64px: a third of the measure holds a
   third of the type. At the row size "STRATEGY" alone overruns its
   column, and the didone is wide in caps. */
.case-note-title {
  margin:0 0 clamp(16px,1.8vw,26px);
  font-size:clamp(25px,2.7vw,40px); line-height:.98; letter-spacing:0;
}
/* The shared copy block is measured for a half-page column; in a third
   of one the measure is the column itself. */
.case-note .case-copy { max-width:none; gap:1em; }
.case-note .case-copy p { font-size:clamp(14px,1.02vw,15.5px); line-height:1.66; }

/* A section that is a row plus its evidence beneath. */
.case-block { display:flex; flex-direction:column; gap:clamp(34px,4.6vw,64px); }

/* The brand line, the page's largest type. */
.case-statement {
  margin:0; max-width:13em;
  font-size:clamp(40px,8vw,128px); line-height:.89; letter-spacing:-.008em;
}

/* --- Evidence ------------------------------------------------- */
/* Photographs take the site grade; artwork never does. */
.case-shot {
  width:100%; height:auto; display:block;
  filter:var(--photo-grade);
}

.case-pair,
.case-trio,
.case-logos {
  list-style:none; margin:0; padding:0;
  display:grid; gap:16px;
}
.case-pair li,
.case-trio li,
.case-logos li { min-width:0; }
.case-pair img,
.case-trio img,
.case-logos img { width:100%; height:auto; display:block; }

/* Both bike details photographs, so both graded — and both at their
   own ratio. The mock boxed them 4/3 under `object-fit:cover` and
   cut the top line off the side panel, which is the only thing
   either frame is on the page for. */
.case-pair { grid-template-columns:repeat(2,minmax(0,1fr)); }
.case-pair img { filter:var(--photo-grade); }

/* Three screens or three posts, equal columns. UNGRADED: these are
   Gerd's blue and yellow, and desaturating them to .62 would mute
   the brand the section exists to show. */
.case-trio { grid-template-columns:repeat(3,minmax(0,1fr)); }

/* Wordmark then bolt, twice, colours swapped. Both rows the same
   height and the bolt square, so the pairing the copy describes
   survives: the mock set `height:100%` on one cell against
   `aspect-ratio:1` on its neighbour and the two rows came out
   different heights. The wordmark cell's ratio follows from the
   artwork (2.29:1) rather than being asserted a second time. */
.case-logos { grid-template-columns:minmax(0,2.29fr) minmax(0,1fr); }

/* --- Result --------------------------------------------------- */
/* The client's own placeholder, drawn as she drew it. Dashed,
   because a placeholder that looks finished is one that ships. */
.case-pending {
  border:1px dashed rgba(249,249,249,.48);
  padding:16px 20px;
  color:rgba(249,249,249,.72);
  font-size:14px; line-height:1.5; letter-spacing:.02em;
}

/* --- The quote ------------------------------------------------ */
.case-quote {
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1.9fr);
  gap:clamp(32px,5vw,72px);
  align-items:center;
}
.case-quote-photo {
  width:100%; height:auto; aspect-ratio:4/5; object-fit:cover;
  filter:var(--photo-grade);
}
.case-quote-body { margin:0; min-width:0; display:flex; flex-direction:column; gap:1.3em; }
.case-quote-lead {
  margin:0; max-width:17em;
  font-size:clamp(24px,3.1vw,46px); line-height:1.04; letter-spacing:-.004em;
}
.case-quote-body p:not(.case-quote-lead) {
  margin:0; max-width:36em;
  color:rgba(249,249,249,.84); font-size:clamp(14.5px,1.1vw,17px); line-height:1.62;
}
/* Set in the grotesque, and an em dash ahead of the name as the
   design draws it — in CSS rather than the markup, so the name is
   what gets copied and announced. */
.case-quote-cite { font-style:normal; font-size:clamp(16px,1.4vw,22px); color:rgba(249,249,249,.92); }
.case-quote-cite::before { content:"\2014\00a0"; }

/* --- The closing ---------------------------------------------- */
.case-close {
  position:relative;
  padding:clamp(100px,13vw,200px) var(--gutter) clamp(110px,14vw,230px);
  display:flex; flex-direction:column; align-items:center; text-align:center;
}

/* Two discs, as the mock draws them: blue up-left, red down-right.
   Both PLACED WHOLE, vignette intact, so each keeps the circular
   edge the design sweeps out of its corner — same call and same
   reasoning as `.svc-close-blue`, and the same warning applies.

   THE BOX IS THE DISC, NOT THE SECTION. Both offsets are capped the
   same way their widths are, so neither drops past the bottom of
   `body` at wide viewports — §33: light past the end of the page
   fill is a hard edge plus dead scroll, and an uncapped vw offset
   against a capped width is exactly how that happens. */
.case-close-blue {
  position:absolute; z-index:-1; aspect-ratio:1; pointer-events:none;
  left:-26vw; top:calc(-1 * min(44vw,620px)); width:min(68vw,960px);
  background:url("../rysing-assets/pattern-blue.webp") center/contain no-repeat;
  --spin:-46deg;
  filter:blur(8px) brightness(.32) saturate(1.04);
  -webkit-mask-image:radial-gradient(circle closest-side,#000 95%,rgba(0,0,0,0) 100%);
          mask-image:radial-gradient(circle closest-side,#000 95%,rgba(0,0,0,0) 100%);
}
.case-close-red {
  position:absolute; z-index:-1; aspect-ratio:1; pointer-events:none;
  right:-24vw; bottom:calc(-1 * min(40vw,560px)); width:min(64vw,900px);
  background:url("../rysing-assets/pattern-red.webp") center/contain no-repeat;
  --spin:132deg;
  filter:blur(8px) brightness(.3) saturate(.92);
  -webkit-mask-image:radial-gradient(circle closest-side,#000 92%,rgba(0,0,0,0) 100%);
          mask-image:radial-gradient(circle closest-side,#000 92%,rgba(0,0,0,0) 100%);
}

.case-close-title {
  margin:0; max-width:9em;
  font-size:clamp(38px,6.4vw,124px); line-height:.89; letter-spacing:-.008em;
}
.case-close .btn-pill { margin-top:clamp(36px,4.8vw,66px); }

@media (max-width:980px) {
  /* The three-up screens get too narrow to read as pages at this
     width; two columns with the third dropping under is worse than
     a single scanning column, so they go to two. */
  .case-trio { grid-template-columns:repeat(2,minmax(0,1fr)); }
}

@media (max-width:980px) {
  /* The narrative three-up goes to one column rather than two: an
     orphaned third column under a pair reads as a mistake, and the
     three are peers. */
  .case-notes { grid-template-columns:minmax(0,1fr); gap:clamp(34px,6vw,52px); }
  /* The steps come off with the columns. Stacked, a top margin is not a
     cascade — it is just three different gaps between the same three
     things, and the rules stop aligning with anything. */
  .case-note:nth-child(2),
  .case-note:nth-child(3) { margin-top:0; }
  .case-note .case-copy { max-width:38em; }
}

@media (max-width:860px) {
  /* Every two-column row stacks. The headline column is sized
     against a desktop measure and at this width it is a narrow
     well with four words in it. */
  .case-hero-grid,
  .case-row,
  .case-quote { grid-template-columns:minmax(0,1fr); }
  .case-hero-grid { align-items:start; }
  /* The portrait led on the right of the hero; stacked, it reads
     better under the copy than between the headline and the note. */
  .case-hero-portrait { max-width:420px; }
  .case-row-label { padding-top:0; }
  .case-hero-title { max-width:none; }
  .case-statement { max-width:none; }
  .case-quote-lead { max-width:none; }
}

@media (max-width:620px) {
  .case-pair,
  .case-trio,
  .case-logos { grid-template-columns:minmax(0,1fr); }
  /* Sized in vw against a desktop viewport, these leave a small
     whole circle sitting in the corner on a phone rather than an
     arc sweeping out of it — the shape stops reading as a disc.
     Same correction as `.svc-close-blue` takes. */
  .case-hero-orb { right:-34vw; bottom:-40vw; width:min(124vw,560px); }
  .case-close-blue { left:-38vw; top:-50vw; width:min(104vw,480px); }
  .case-close-red { right:-38vw; bottom:-46vw; width:min(100vw,460px); }
}

/* --- Holding page: the star rises -------------------------- */
/* One gesture, once, then stillness. No footage and no full-bleed payoff.

   THE REVEAL IS NOT A FADE AND NOT A WIPE. The star climbs in from below and
   the wordmark is uncovered by an edge travelling UPWARD behind it, so the
   name is lit from underneath as the star passes rather than appearing in
   place. The studio's own verb, performed by its own mark.

   THE ORDER IS CAUSAL AND THE TIMINGS EXIST TO KEEP IT THAT WAY. The sweep
   starts only once the star has climbed past the wordmark's line. Run them
   together and they stop reading as one event — they become two animations
   that happen to overlap, which is the difference between a logo revealed BY
   something and a logo that merely faded in near it. */
.rise {
  position:relative; min-height:100dvh;
  display:grid; place-items:center;
  padding:clamp(30px,5vh,58px) clamp(20px,5vw,64px);
  text-align:center; overflow:hidden;
}

/* THE LIGHT IS SMALL AND LOW, AND IT TOOK TWO GOES. Built first at
   min(104vw,1180px) anchored to the bottom, which put a 1180px disc with its
   centre two thirds up the screen: not light rising behind a mark but a
   red-brown fog over the whole page, with the star floating in the middle of
   it. Same failure as the first build of version 1 and the same rule from
   §8 — spread wide enough, a glow stops being an object.

   It is the glow the mark carries up with it, so it is barely wider than the
   wordmark and it stays low. The top of the screen never lights at all. */
/* Pushed most of the way off the bottom and blurred hard, so what lights the
   page is the TOP ARC of the disc and never its circumference. At
   `bottom:-14vh` with a 22px blur the whole circle sat on screen and read as
   a ball of light under the wordmark with a visible edge — §26's note from
   the other direction: a shape with an edge is an object, and this one is
   meant to be ambient. */
.rise-light {
  position:absolute; z-index:-1; left:50%; bottom:-34vh;
  width:min(76vw,760px); aspect-ratio:1; translate:-50% 0;
  background:radial-gradient(circle at 50% 50%,rgba(240,66,34,.44),rgba(240,66,34,.17) 36%,rgba(240,66,34,.03) 62%,transparent 76%);
  filter:blur(44px); opacity:0; pointer-events:none;
  animation:riseLight 3.1s var(--ease-weight) forwards;
}
@keyframes riseLight {
  0%   { opacity:0; transform:translateY(34vh) scale(.5); }
  55%  { opacity:1; }
  100% { opacity:.8; transform:translateY(0) scale(1); }
}

.rise-core { display:flex; flex-direction:column; align-items:center; width:100%; max-width:660px; }

/* The climb. Decelerating hard — a rise that arrives, rather than a tween
   that stops. The overshoot in the curve is small on purpose: enough that
   the star settles INTO its position, not so much that it bounces, which
   would turn the brand mark into a UI element. */
.rise-star {
  position:relative; flex:none;
  width:clamp(54px,6.2vw,92px); aspect-ratio:1;
  margin-bottom:clamp(34px,5vh,60px);
  background:var(--red);
  -webkit-mask:var(--mark) center/contain no-repeat; mask:var(--mark) center/contain no-repeat;
  opacity:0; animation:riseStar 2.6s cubic-bezier(.17,.92,.26,1) forwards;
}
@keyframes riseStar {
  0%   { opacity:0; transform:translateY(46vh) scale(.3) rotate(-24deg); }
  20%  { opacity:1; }
  100% { opacity:1; transform:translateY(0) scale(1) rotate(0deg); }
}

/* One bloom at the top of the climb, which is what makes the landing read as
   an arrival rather than a stop. */
.rise-star i {
  position:absolute; left:50%; top:50%; translate:-50% -50%;
  width:460%; aspect-ratio:1; border-radius:50%;
  background:radial-gradient(circle at 50% 50%,rgba(240,66,34,.8),rgba(240,66,34,.24) 32%,transparent 66%);
  filter:blur(13px); opacity:0; pointer-events:none;
  animation:riseFlare 1.4s var(--ease) 2.1s forwards;
}
@keyframes riseFlare {
  0%   { opacity:0; transform:scale(.3); }
  24%  { opacity:.9; transform:scale(1); }
  100% { opacity:0; transform:scale(1.45); }
}

/* THE SWEEP. The mask is three times the height of the wordmark and its
   POSITION is animated, which walks the gradient's edge up through the box.
   Done this way rather than by animating the gradient's own colour stops:
   `mask-image` is not interpolable in every engine that supports masks at
   all, but `mask-position` always is, so the edge is moved rather than
   redrawn. Same reason the marquee translates a track instead of restating
   its background.

   Reading the positions: the gradient runs black at its bottom to
   transparent at its top. At `0 0%` the box sees the mask's top third and
   the wordmark is hidden; at `0 100%` it sees the bottom third and the
   wordmark is solid. So 0% -> 100% walks the lit edge upward.

   The soft edge is 18%–42% of the mask rather than 34%–66%. At the wider
   setting the fade was most of the mask's height, so the whole wordmark
   lightened at once and the sweep read as a dissolve. A tighter band keeps a
   travelling EDGE, which is the thing that reads as a rise. */
.rise-wordwrap { width:100%; display:flex; justify-content:center; }
.rise-word {
  width:clamp(188px,25vw,356px); height:auto;
  -webkit-mask-image:linear-gradient(to top,#000 0 18%,rgba(0,0,0,0) 42%);
          mask-image:linear-gradient(to top,#000 0 18%,rgba(0,0,0,0) 42%);
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-size:100% 300%; mask-size:100% 300%;
  -webkit-mask-position:0 0%; mask-position:0 0%;
  animation:riseSweep 1.7s var(--ease-weight) 1.45s forwards,
            riseLift   1.7s var(--ease-weight) 1.45s forwards;
}
@keyframes riseSweep {
  to { -webkit-mask-position:0 100%; mask-position:0 100%; }
}
/* The wordmark travels a little as it is uncovered. Without it the letters
   light up in a box that is plainly standing still, and the sweep reads as a
   gradient passing over a logo rather than the logo rising into light. */
@keyframes riseLift {
  from { transform:translateY(18px); }
  to   { transform:translateY(0); }
}

.rise-note {
  margin:clamp(30px,4.2vh,46px) 0 0; color:rgba(249,249,249,.58);
  opacity:0; animation:riseFade 1s var(--ease) 3.25s forwards;
}
@keyframes riseFade { from { opacity:0; transform:translateY(10px); }
                      to   { opacity:1; transform:translateY(0); } }

@media (max-height:680px) {
  .rise-star { width:clamp(44px,5vw,68px); margin-bottom:clamp(20px,3.4vh,32px); }
  .rise-word { width:clamp(160px,21vw,260px); }
  .rise-note { margin-top:22px; }
}

/* Reduced motion gets the settled frame: everything in place, nothing moves,
   and the wordmark is unmasked outright rather than left mid-sweep. */
@media (prefers-reduced-motion:reduce) {
  .rise-light { animation:none; opacity:.8; transform:none; }
  .rise-star { animation:none; opacity:1; transform:none; }
  .rise-star i { animation:none; opacity:0; }
  .rise-note { animation:none; opacity:1; transform:none; }
  .rise-word { animation:none; -webkit-mask-image:none; mask-image:none; transform:none; }
}
