/* ==========================================================================
   editorial-2.css, part 2 of 2  (continues into editorial-3.css)

   SPLIT FOR A REASON, DO NOT MERGE BACK. The dev server silently stops
   sending this file at 261,120 bytes (255 KiB) - not every time, roughly
   three requests in five. Once editorial-2.css passed that size the whole
   .skyhero block (byte 279,014) fell past the cut, so on most loads the
   homepage hero rendered with no CSS at all. editorial.css was split for
   the same reason earlier.

   The split is at a brace-depth-zero boundary and the two files
   concatenate back to the original byte for byte, so the cascade is
   unchanged. Keep both under ~200KB; if one approaches 255KB, split again.
   ========================================================================== */

.aim__mission .aim__stmt {
  font-size: clamp(1.6rem, 1.2rem + 1.85vw, 2.85rem);
}
.aim__mission .aim__stmt b {
  font-weight: inherit;
  font-style: italic;
  color: var(--gold-light);
}

/* the vision sits below the slot: it is the thing the opening reveals */
.aim__vision {
  position: absolute;
  right: 0;
  top: 71%;
  max-width: 33rem;
  padding-top: clamp(1.1rem, 0.9rem + 0.9vw, 1.6rem);
  border-top: 1px solid rgba(192, 156, 96, 0.4);
}
/* --ink, NOT --vein, and that is the recurring trap avoided by construction
   rather than measured afterwards: --vein is a mid grey that clears AA against
   the flat page but not against a photograph with lit roofs in it. Copy laid
   over a picture gets the full ink value; the emphasis carries the hierarchy
   instead of a dimmer base. */
.aim__vision .aim__stmt {
  font-size: clamp(1.15rem, 0.98rem + 0.8vw, 1.62rem);
  line-height: 1.34;
  color: var(--ink);
}
.aim__vision .aim__stmt b {
  font-weight: inherit;
  color: var(--gold-light);
}

/* --------------------------------------------------------------------------
   Narrower
   -------------------------------------------------------------------------- */

@media (max-width: 1100px) {
  .aim__mission { max-width: 34rem; top: 46%; }
  .aim__vision { top: 68%; max-width: 27rem; }
}

@media (max-width: 900px) {
  /* ABSOLUTE PLACEMENT IS DROPPED, NOT RETUNED. Two text blocks pinned to
     percentages of a fixed-height band will collide the moment the type wraps,
     and at this width both of them wrap. In flow they can never overlap. */
  .aim__band {
    height: auto;
    min-height: min(92svh, 40rem);
  }
  .aim__inner {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: clamp(2rem, 1.5rem + 2.6vw, 3.25rem);
    padding-block: clamp(2.61rem, 1.74rem + 5.22vw, 4.06rem);
  }
  .aim__mission,
  .aim__vision {
    position: static;
    translate: none;
    max-width: 40rem;
  }
  /* the matte can no longer hide the vision on a flowing band, so it only
     frames the picture */
  .aim__matte { height: calc((1 - var(--open)) * 22%); }
  .aim__veil {
    background:
      linear-gradient(180deg, rgba(6, 6, 8, 0.7) 0%, rgba(6, 6, 8, 0.52) 45%, rgba(6, 6, 8, 0.86) 100%),
      radial-gradient(40rem 24rem at 12% 4%, rgba(156, 120, 72, 0.1), transparent 66%);
  }
}

@media (prefers-reduced-motion: reduce) {
  /* --open is already at its finished value; the frame is simply open */
  .aim__shot img { transform: none; }
}

/* ==========================================================================
   THE FOUNDER — "the focus pull"   (about.html, section 03)

   It replaced an `.ed` split: a blank marble panel captioned "Portrait
   pending" (its image used `data-src`, so the clean pass hid it) beside three
   paragraphs. That was `.ed`'s LAST USER SITE-WIDE, so `.ed` / `.ed__media` /
   `.ed__copy` — alongside `.ed--flip`, `.ed--overlap` and `.kicker-lg`, freed
   by the two rebuilds above — have since been deleted from css/editorial.css.

   THE GRAMMAR IS A RACK FOCUS, and it was the last optical move still free.
   This site travels X, pushes Z, climbs Y, holds the camera still while
   planes separate, turns an object, removes material, opens a letterbox and
   pulls back, swings doors inward, develops a print, runs a clock. Nothing
   moves the FOCAL PLANE. It is also the argument rather than an effect laid
   on top of one: the camera never moves and the composition never changes —
   what changes is what is allowed to be sharp. The week you had falls away,
   the runner resolves, and the number is the last thing to come clear.

   ONE NUMBER. `--focus` runs 0..1 and each plate computes its own sharpness
   from its distance to it, then sharpness drives blur, grade, bloom AND the
   frame hairline together — so a four-part move is one scrubbed value and CSS
   owns every filter (the clock's compose-in-CSS pattern). It is UNREGISTERED
   because GSAP writes it every frame and it never has to interpolate, and it
   reaches the plates for free without the site-wide recalc cost of a
   registered inherited property. Its CSS value is 1, THE FINISHED STATE, so
   no-JS, no-CDN and reduced-motion all render a fully resolved composition.

   `--fpeek` is the SEPARATE hover number, registered `inherits: false` and
   declared on the two elements that actually read it. Two numbers rather than
   one is the point: a scrub and a transition can then never fight over a
   property. Same split as the corridor's `--lamp` / `--lampx`.

   DELIBERATELY NOT A BAND. `.aim` directly above is a full-bleed photograph
   with the type laid over it, and a second one here would be the
   adjacent-section uniformity that `.wall` and `.worlds` had to be pulled
   apart for on index. This is three separate plates at three depths with the
   type in the negative space BETWEEN them — which also means no copy sits
   over a photograph anywhere in this section and it needs no scrim at all.

   NO LIKENESS, PERMANENT. The runner plate is uncaptioned on purpose, the
   same call `.lane` made on team: a name under a silhouette is the claim the
   rules forbid, so "Derrick / Founder" sits with the type. That plate is a
   drop-in slot for real photography the day it exists.

   Photography is an explicit exception to the `.media img` clean pass via the
   `.fdr__*` classes: car-dawn (the week you had), track (the standard) and
   whiteboard (the number). None is used as a real photograph anywhere else on
   about.html.
   ========================================================================== */

.fdr {
  position: relative;
  /* the far plate runs off the right edge; clip so it can never become a
     scrollbar. The corridor above bleeds LEFT — the alternation is what stops
     two bleeding sections on one page reading as one layout. */
  overflow-x: clip;
  --bleed: max(var(--gutter), calc((100vw - var(--container)) / 2));
  --focus: 1;
  /* THE BLUR CEILING IS A VARIABLE BECAUSE IT IS SIZE-DEPENDENT. A radius
     that reads as depth of field on a 700px plate is an unidentifiable
     smear on a 340px one — the same empty-screen failure the corridor's
     detail plate and the results tally both had to be graded out of. It
     drops with the layout at the breakpoint below. */
  --blur-max: 8px;
}

.fdr__ground {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(54rem 30rem at 80% 10%, rgba(156, 120, 72, 0.075), transparent 66%),
    radial-gradient(38rem 26rem at 6% 82%, rgba(128, 96, 48, 0.055), transparent 70%);
}

.fdr > .container { position: relative; z-index: 1; }

/* --------------------------------------------------------------------------
   The broken grid
   -------------------------------------------------------------------------- */

.fdr__stage {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: clamp(1rem, 0.5rem + 1.4vw, 2.25rem);
  row-gap: 0;
}

/* EYEBROW HIGH, HEADLINE LOW, AIR BETWEEN THEM. The head stretches to the
   height of the plate beside it and the h2 is pushed to the foot of that
   column, so the left half has content at the top AND the bottom instead of a
   short block floating against a 440px photograph — the empty-quarter failure
   this site has now paid for four times. */
.fdr__head {
  grid-column: 1 / 7;
  grid-row: 1;
  z-index: 4;
  display: flex;
  flex-direction: column;
  padding-bottom: clamp(0.5rem, 0.3rem + 0.8vw, 1.25rem);
}

.fdr__h {
  margin-top: auto;
  padding-top: clamp(1.75rem, 1.25rem + 3vw, 4.25rem);
  text-wrap: balance;
}
.fdr__h em {
  font-style: italic;
  color: var(--gold-light);
}

/* --------------------------------------------------------------------------
   The three plates

   Each carries `.reveal`, so NEITHER its rotation NOR anything else here may
   use `transform`: `.reveal.is-visible` sets `transform: none` and would wipe
   it the instant the observer fired. The independent `rotate` property is
   applied separately from `transform`, which is the same fix the letterbox
   needed for its centring. GSAP owns the inner image's transform alone.
   -------------------------------------------------------------------------- */

.fdr__plate {
  position: relative;
  margin: 0;
  overflow: hidden;
  /* the bloom blends with `screen`; without an explicit stacking context that
     blend resolves against whatever is painted below the plate in the section
     rather than against the photograph (the corridor's lesson) */
  isolation: isolate;
  box-shadow:
    inset 0 3px 14px -6px rgba(0, 0, 0, 0.9),
    0 24px 56px -32px rgba(0, 0, 0, 0.95);
  /* abs() is too new to rely on here, so the distance is max(a - b, b - a) */
  --dist: max(calc(var(--focus) - var(--f)), calc(var(--f) - var(--focus)));
  --sharp: clamp(0, calc(var(--min) + clamp(0, calc(1 - var(--dist) / var(--w)), 1) * (1 - var(--min))), 1);
}

/* THE FOCAL POSITIONS. `--f` is where along the pull this plane is sharp,
   `--w` how forgiving its depth of field is, and `--min` the sharpness it can
   never fall below. At the resting value (`--focus: 1`) the composition is a
   real shallow-depth photograph rather than a flat one: the number razor
   sharp in front, the runner a whisper soft, the week you had softened behind.
   That resting state is what no-JS renders, so it has to be the best-looking
   frame in the whole pull, not the start of it.

   EVERY `--min` IS SET BY WHAT THE PICTURE STILL HAS TO SAY, NOT BY TASTE.
   The far plate first ran a floor of 0.14 and settled at nearly 8px of blur,
   which turned a road at first light into a brown smear — a 700px rectangle
   carrying no information, which is the failure this site has now paid for
   five times. Softened is the brief; unreadable is not. The runner is the
   subject of the section and is never allowed to be a blur at all. Only the
   number, nearest the reader, is permitted to arrive from nothing. */
.fdr__plate--far  { --f: 0;    --w: 0.75; --min: 0.5;  }
.fdr__plate--mid  { --f: 0.62; --w: 0.72; --min: 0.62; }
.fdr__plate--near { --f: 1;    --w: 0.6;  --min: 0.1;  }

/* far · the week you had. Wide, bleeding off the right edge of the page, and
   riding up through the section's top padding so the composition breaks its
   own boundary rather than sitting inside it. */
.fdr__plate--far {
  grid-column: 7 / -1;
  grid-row: 1;
  align-self: start;
  z-index: 1;
  margin-top: clamp(-4.5rem, -1.5rem - 2.5vw, -1.5rem);
  margin-right: calc(-1 * var(--bleed));
  /* WIDE AND SHALLOW, and the ratio is doing layout work rather than framing
     work: this plate sets the height of the whole top row, and at 16/10 it
     opened a 190px void between the eyebrow and the headline beside it. */
  aspect-ratio: 2.1 / 1;
}

/* mid · the standard. NO aspect-ratio: it is `height: 100%` across two rows,
   so it is exactly as tall as the copy and the near plate beside it. With a
   fixed aspect it either fell short of them or forced the rows open. Same
   call the seat made on team. */
.fdr__plate--mid {
  grid-column: 1 / 6;
  grid-row: 2 / 4;
  z-index: 3;
  height: 100%;
  min-height: clamp(22rem, 15rem + 22vw, 36rem);
  rotate: 0.5deg;
}

/* near · the number. Small, nearest, tilted hardest, and reaching back over
   BOTH the copy above it and the runner beside it. THE NEGATIVE MARGIN HAS TO
   BEAT THE COLUMN GAP BEFORE ANY OVERLAP EXISTS AT ALL — the arithmetic the
   corridor and the careers bar both paid for. */
.fdr__plate--near {
  grid-column: 6 / 11;
  grid-row: 3;
  z-index: 4;
  aspect-ratio: 3 / 2;
  margin-top: clamp(-5.5rem, -2rem - 3vw, -2rem);
  margin-left: clamp(-7rem, -2.5rem - 3.5vw, -2.5rem);
  rotate: -1.4deg;
}

/* THE SHOT IS LARGER THAN ITS PLATE, and that is what makes the blur usable:
   a `filter: blur()` on an element flush with its clip box feathers its own
   edge into transparency. Held 3rem proud on every side, the soft edge falls
   outside the plate and the plate keeps a hard cut. The overscan also gives
   the parallax somewhere to travel. */
.fdr__shot {
  position: absolute;
  inset: -3rem;
  z-index: 0;
}

.fdr__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.fdr__plate--far  .fdr__shot img { object-position: 50% 54%; }
.fdr__plate--mid  .fdr__shot img { object-position: 50% 38%; }
.fdr__plate--near .fdr__shot img { object-position: 52% 46%; }

/* --fpeek IS REGISTERED AND --focus IS NOT, AND THE SPLIT IS DELIBERATE.
   GSAP writes --focus every frame of the scrub, so it never has to
   interpolate and can stay unregistered and inherit for free. --fpeek is a
   hover nudge CSS has to TRANSITION, and an unregistered property cannot
   interpolate — it would step. It is registered `inherits: false` and
   declared on each element that reads it, because a registered INHERITED
   property is computed on every element in every document that loads this
   stylesheet, and editorial.css is on all nine pages (the cost the clock's
   specular pair paid for).

   Note the transition is on --fpeek, NEVER on `filter`. A transition on the
   filter itself would smooth every frame of the scrub as well, which is the
   "a tween and a transition must not share a property" bug in a new coat. */
@property --fpeek { syntax: "<number>"; inherits: false; initial-value: 0; }

.fdr__shot {
  --fpeek: 0;
  --s: clamp(0, calc(var(--sharp) + var(--fpeek)), 1);
  transition: --fpeek 0.7s var(--ease-out);
  filter:
    blur(calc((1 - var(--s)) * var(--blur-max)))
    brightness(calc(0.76 + var(--s) * 0.4))
    saturate(calc(0.72 + var(--s) * 0.26))
    contrast(calc(0.98 + var(--s) * 0.1));
}

/* OUT OF FOCUS, HIGHLIGHTS BLOOM. This is the detail that makes the pull read
   as a lens rather than as a blur filter: the softer a plane is, the more its
   warm sources spread. Three pools sharing no axis, because anything on a
   regular period reads as a pattern laid over a photograph. */
.fdr__bloom {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  mix-blend-mode: screen;
  --fpeek: 0;
  --s: clamp(0, calc(var(--sharp) + var(--fpeek)), 1);
  transition: --fpeek 0.7s var(--ease-out);
  opacity: calc((1 - var(--s)) * 0.72);
  background:
    radial-gradient(9rem 9rem at 24% 30%, rgba(255, 226, 176, 0.3), transparent 68%),
    radial-gradient(13rem 13rem at 71% 22%, rgba(192, 156, 96, 0.24), transparent 70%),
    radial-gradient(7rem 7rem at 58% 78%, rgba(255, 238, 206, 0.2), transparent 66%);
}

/* THE HAIRLINE IS PART OF THE FOCUS, not a frame drawn around a picture. The
   plate that is sharp is also the one that is ringed, so the eye is told
   where to look by the site's own line language instead of by an arrow. */
.fdr__plate::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(156, 120, 72, calc(0.1 + var(--sharp) * 0.42));
  background: linear-gradient(
    180deg,
    rgba(6, 6, 8, 0.34) 0%,
    transparent 30%,
    rgba(6, 6, 8, calc(0.52 - var(--sharp) * 0.2)) 100%);
}

/* --------------------------------------------------------------------------
   The reading column
   -------------------------------------------------------------------------- */

.fdr__read {
  grid-column: 7 / 12;
  grid-row: 2;
  z-index: 2;
  min-width: 0;
  padding-top: clamp(1.5rem, 1rem + 2.4vw, 3.25rem);
  /* the near plate rides up over this block, so the column has to give it
     ground to land on or it covers the attribution */
  padding-bottom: clamp(2.24rem, 1.28rem + 3.2vw, 4.48rem);
}

.fdr__lede {
  font-size: var(--text-lg);
  line-height: 1.7;
  color: var(--ink);
  text-wrap: pretty;
}

.fdr__body {
  margin-top: clamp(0.9rem, 0.75rem + 0.6vw, 1.25rem);
  color: var(--vein);
  text-wrap: pretty;
}

/* THE NAME SITS WITH THE TYPE, NEVER UNDER A PHOTOGRAPH. */
.fdr__who {
  margin-top: clamp(1.4rem, 1.1rem + 1.2vw, 2rem);
  padding-top: clamp(0.8rem, 0.65rem + 0.6vw, 1.1rem);
  border-top: 1px solid var(--border-gold);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--vein);
}
.fdr__who b {
  font-weight: 600;
  color: var(--gold-light);
}

/* --------------------------------------------------------------------------
   The close

   NO RULE ABOVE IT, deliberately: the corridor two sections up already closes
   on a hairline plus one display statement, and repeating that device would
   make the page's two rebuilt sections end the same way. Here the hierarchy
   does it instead — the concession is set small and the payoff is set large.
   -------------------------------------------------------------------------- */

.fdr__close {
  grid-column: 2 / 11;
  grid-row: 4;
  margin-top: clamp(1.8rem, 1.296rem + 2.52vw, 3.96rem);
}

.fdr__stmt {
  max-width: 44rem;
  font-size: clamp(1.05rem, 0.98rem + 0.35vw, 1.3rem);
  line-height: 1.6;
  color: var(--vein);
  text-wrap: pretty;
}

.fdr__payoff {
  margin-top: clamp(0.85rem, 0.7rem + 0.9vw, 1.45rem);
  max-width: 52rem;
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 1.2rem + 2.2vw, 3rem);
  line-height: 1.16;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-wrap: balance;
}
/* additive only, so it is simply absent where --focus never moves */
.fdr__payoff em {
  font-style: italic;
  color: var(--gold-light);
  text-shadow: 0 0 calc(var(--focus) * 24px) rgba(192, 156, 96, calc(var(--focus) * 0.26));
}

/* --------------------------------------------------------------------------
   Narrower

   Below 901px the absolute relationships are DROPPED ENTIRELY rather than
   retuned — three overlapping plates pinned to grid lines collide the moment
   the type wraps, and at this width it wraps. The stage flows in source
   order, which is also the argument in order: the headline, the week you had,
   the runner, the copy, the number, the close. `initFocus` is gated to the
   same breakpoint, so the plates simply hold their resting depth of field.
   -------------------------------------------------------------------------- */

@media (max-width: 900px) {
  /* a smaller plate needs a smaller circle of confusion, or the background
     stops being a background and becomes a stain */
  .fdr { --blur-max: 5px; }
  .fdr__stage {
    display: flex;
    flex-direction: column;
    gap: clamp(1.5rem, 1.1rem + 1.8vw, 2.5rem);
  }
  .fdr__head { padding-bottom: 0; }
  .fdr__h { padding-top: clamp(0.9rem, 0.75rem + 0.6vw, 1.3rem); }

  /* THE PLATES ARE CAPPED, NOT RUN FULL WIDTH, and this was a measured fault
     rather than a preference. Stacked at the top of this range the three
     photographs took the full 828px column, which made the portrait 1035px
     tall on its own and took the section from 1500px at 960 to 2795px at 900
     — the layout nearly doubling across one breakpoint. Capping the width
     and flattening the crops holds it near 1900px AND keeps the three apart:
     on one column the SIZE and the CROP are the only things left doing that
     (the floor's lesson on team). */
  .fdr__plate { rotate: none; margin: 0; height: auto; min-height: 0; }
  .fdr__plate--far {
    width: calc(100% + var(--bleed));
    margin-right: calc(-1 * var(--bleed));
    aspect-ratio: 2.2 / 1;
  }
  /* the cap is sized so the two staggered plates still OVERLAP in x — at
     30rem the portrait ended at 480 and the right-aligned plate below began
     at 476 on an 828 column, which read as two isolated blocks with a void
     beside each. Overlapping their horizontal ranges is what makes a single
     column zigzag instead of stack. */
  .fdr__plate--mid {
    aspect-ratio: 4 / 5;
    width: min(100%, 33rem);
  }
  .fdr__plate--near {
    aspect-ratio: 3 / 2;
    width: min(72%, 22rem);
    margin-left: auto;
  }
  .fdr__shot { inset: -1.75rem; }

  .fdr__read { padding-top: 0; padding-bottom: 0; max-width: 42rem; }
  .fdr__close { margin-top: clamp(1.5rem, 1.2rem + 1.4vw, 2.5rem); }
}

/* on a phone the caps stop helping and start starving the pictures. The
   portrait needs no rule here: `min(100%, 30rem)` already goes full width
   once the column is narrower than the cap, and forcing `width: 100%` at this
   breakpoint made the section TALLER at 620 (2181px) than at 700 (2061px) —
   a layout that grows as the viewport shrinks. */
@media (max-width: 620px) {
  .fdr__plate--far { aspect-ratio: 16 / 9; }
  .fdr__plate--near { width: min(84%, 20rem); }
}

/* you look at it, and it comes into focus. CSS owns this because it is
   presentational and stateless, it survives GSAP being absent, and
   `pointerenter` is not reliably dispatched through CDP — so a JS version
   could not be measured in the headless sweep either (the corridor's find). */
@media (hover: hover) {
  .fdr__plate:hover .fdr__shot,
  .fdr__plate:hover .fdr__bloom { --fpeek: 0.24; }
}

@media (prefers-reduced-motion: reduce) {
  /* --focus is already at its finished value; only the hover lift stops */
  .fdr__shot, .fdr__bloom { transition: none; }
  .fdr__plate:hover .fdr__shot,
  .fdr__plate:hover .fdr__bloom { --fpeek: 0; }
}

/* ==========================================================================
   VALUES — "no adjectives"   (about.html, section 04)

   It replaced `.values` / `.value`: a four-across grid of identical boxes,
   each an h3 over one sentence. `.value` and its children have since been
   deleted from css/main.css, alongside `.ed` / `.ed--flip` / `.ed--overlap`
   and `.kicker-lg` in css/editorial.css. `.values` (the plural grid wrapper)
   is equally dead but was left standing — it was outside that pass's brief.

   TWO FAULTS, AND THE SECOND ONE IS THE INTERESTING ONE. (1) Four equal boxes
   saying four equal things is the shape team's `.seat` was rebuilt out of.
   (2) It sat DIRECTLY ABOVE the `.statbar`, which is also four things across
   — two four-up rows in succession, which reads as one template applied
   twice. Always look at the section below as well as the section above.

   **UNEQUAL SIZING DOES NOT BREAK A REPEATED COMPONENT, A DIFFERENT STRUCTURE
   DOES** — the lesson `.seat` paid for, and the trap this section sets,
   because four boxes at four different sizes still read as four boxes. So
   each value holds its sentence in a different RELATIONSHIP: 01 below the
   name, 02 beside it on one baseline, 03 above it, 04 hanging in the margin
   beside a name that runs the full width. The sizes vary too (74 / 50 / 67 /
   87px at 1440, deliberately not monotonic), but that is the second-order
   difference, not the one doing the work.

   AND THE HIERARCHY IS NOT FAKED. A mission and a vision are unequal and
   `.aim` was right to argue it; these four are not. The KB is explicit —
   "Four. Not five, not six. Each gets one line and no adjectives." Nothing
   here promotes one over another. What varies is the setting.

   THE GRAMMAR IS THE WORDS TIGHTENING, and it was the last free move because
   it is not a movement at all. Everything on this site moves a camera, an
   object, light or focus; nothing changes TYPE'S OWN METRICS. Each name
   arrives open, wide-tracked and haloed — decorative — and closes into a
   tight, hard, struck noun. The headline made mechanical: the adjectives come
   off and the word is what is left.

   **NOTHING THAT AFFECTS LEGIBILITY IS DRIVEN FROM `--set`.** The names hold
   full opacity at every value of it and the entrance is the robust `.reveal`
   observer; only tracking and an ADDITIVE halo move. That is the recurring
   "what element does the opacity actually land on" trap answered by making
   the answer "nothing" — a scrub that never fires cannot leave a heading at a
   fraction here, because no opacity is on the scrub in the first place.

   THE HALO IS `filter: drop-shadow`, NOT `text-shadow`, and that is forced:
   these names are `background-clip: text` with a transparent fill, and a
   text-shadow paints from the glyph geometry independently of the clipped
   background, so it muddies the metal instead of haloing it. drop-shadow
   operates on the composited result and does the right thing.

   EACH SENTENCE'S ENFORCING CLAUSE IS THE GOLD ONE, and that is the argument:
   none of the four is asserted, each is backed by something you could point
   at. It also does real work on a copy problem found before building — "We
   sell what we would buy" is also on index (`.wall`) and "Contracts,
   coverage, install dates ... We do not drop things" is also on team
   (`.seat`), so the emphasis is put on the clauses unique to this page. The
   KB wording itself is unchanged.

   Photography is an explicit exception to the `.media img` clean pass via
   `.four__*`: signature (the checkable artefact) running off the LEFT edge,
   and retail (a market someone runs like they own it) small and tilted.
   Neither is used as a real photograph elsewhere on about.html, and the left
   bleed alternates against `.fdr` above, which bleeds right. Both are graded
   DOWN — in `.fdr` the photographs are the composition, here the type is, and
   two adjacent sections cannot both be led by pictures.
   ========================================================================== */

.four {
  position: relative;
  /* the wide plate runs off the left edge, and an open-tracked name is wider
     than its own column by design — clip so neither can become a scrollbar */
  overflow-x: clip;
  --bleed: max(var(--gutter), calc((100vw - var(--container)) / 2));
  /* how far the letters stand apart before the word closes. In em, so it
     scales with each name's own size. */
  --track-open: 0.105em;
}

.four__ground {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(46rem 28rem at 12% 22%, rgba(156, 120, 72, 0.07), transparent 68%),
    radial-gradient(40rem 26rem at 90% 84%, rgba(128, 96, 48, 0.055), transparent 70%);
}

.four > .container { position: relative; z-index: 1; }

/* --------------------------------------------------------------------------
   The descent
   -------------------------------------------------------------------------- */

.four__stage {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: clamp(1rem, 0.5rem + 1.4vw, 2.25rem);
  row-gap: 0;
}

/* the wrapper exists only to group the four in the markup; it must not
   become a grid item or the whole descent collapses into one cell. Safe on a
   plain div — there are no list semantics here to lose. */
.four__set { display: contents; }

.four__head {
  grid-column: 1 / 5;
  grid-row: 1;
  align-self: end;
  padding-bottom: clamp(0.4rem, 0.3rem + 0.5vw, 0.9rem);
}

.four__h {
  margin-top: clamp(0.8rem, 0.65rem + 0.6vw, 1.2rem);
  text-wrap: balance;
}

/* --------------------------------------------------------------------------
   A name

   `--set` runs 0 (open, haloed, decorative) to 1 (closed, hard, struck) and
   is declared on each value so the four can settle in turn. CSS value 1 is
   the FINISHED state, so no-JS / no-CDN / reduced-motion render four solid
   words and GSAP only ever takes them backwards to animate them forwards.
   -------------------------------------------------------------------------- */

.four__v { --set: 1; min-width: 0; }

.four__n {
  display: block;
  /* the tightening is only legible if the word never rewraps mid-close */
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: 500;
  line-height: 1.02;
  letter-spacing: calc(var(--track-open) * (1 - var(--set)) - var(--set) * 0.015em);
  background: linear-gradient(172deg, #fdf8ef 0%, #f0e4cd 30%, #cdb287 64%, #9a7c4c 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 0 calc((1 - var(--set)) * 20px) rgba(192, 156, 96, calc((1 - var(--set)) * 0.4)));
}

.four__p {
  color: var(--vein);
  line-height: 1.65;
  text-wrap: pretty;
}

/* THE CLAUSE THAT ENFORCES THE VALUE. Full `--gold-light` at every value of
   --set, never dimmed — only the glow is on the number, so this can not
   become the contrast bug this codebase has now shipped three times. */
.four__p b {
  font-weight: 600;
  color: var(--gold-light);
  text-shadow: 0 0 calc(var(--set) * 15px) rgba(192, 156, 96, calc(var(--set) * 0.26));
}

/* ---- 01 · the sentence sits BELOW the name ------------------------------ */

.four__v--1 {
  grid-column: 5 / -1;
  grid-row: 1;
}
.four__v--1 .four__n { font-size: clamp(2.4rem, 1.5rem + 3.6vw, 4.6rem); }
.four__v--1 .four__p {
  margin-top: clamp(0.9rem, 0.7rem + 0.8vw, 1.45rem);
  max-width: 38rem;
}

/* ---- 02 · the sentence sits BESIDE the name, on its baseline ------------- */

.four__v--2 {
  grid-column: 7 / -1;
  grid-row: 2;
  /* ALIGNED TO THE PLATE'S BOTTOM, NOT ITS MIDDLE. Centred, this block sat
     in the vertical middle of a 440px photograph with ~170px of black above
     and below it, which reads as a hole rather than as air. Sharing a
     baseline with the plate's lower edge makes the same space deliberate. */
  align-self: end;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: clamp(0.9rem, 0.7rem + 1.2vw, 1.9rem);
  margin-top: clamp(1.62rem, 1.08rem + 2.304vw, 3.6rem);
}
.four__v--2 .four__n {
  flex: 0 0 auto;
  font-size: clamp(1.8rem, 1.3rem + 2.2vw, 3.1rem);
}
.four__v--2 .four__p { flex: 1 1 13rem; }

/* ---- 03 · the sentence sits ABOVE the name ------------------------------- */

.four__v--3 {
  grid-column: 2 / 9;
  grid-row: 3;
  align-self: end;
  /* the NAME is what aligns to the right edge; the sentence is pushed there
     but stays ragged-right, because right-aligned body copy is a slower read
     and this section already asks the eye to move around */
  text-align: right;
  margin-top: clamp(1.62rem, 1.08rem + 2.304vw, 3.6rem);
  padding-bottom: clamp(0.5rem, 0.4rem + 0.6vw, 1rem);
}
.four__v--3 .four__p {
  margin: 0 0 clamp(0.9rem, 0.7rem + 0.8vw, 1.45rem) auto;
  max-width: 34rem;
  text-align: left;
}
.four__v--3 .four__n { font-size: clamp(2.2rem, 1.4rem + 3.2vw, 4.2rem); }

/* ---- 04 · the sentence hangs in the margin beside a full-width name ------ */

.four__v--4 {
  grid-column: 1 / -1;
  grid-row: 4;
  display: grid;
  grid-template-columns: minmax(0, 0.3fr) minmax(0, 1fr);
  column-gap: clamp(1.25rem, 0.9rem + 1.8vw, 2.75rem);
  align-items: end;
  margin-top: clamp(1.8rem, 1.224rem + 2.592vw, 3.96rem);
}
.four__v--4 .four__p {
  grid-column: 1;
  margin-bottom: clamp(0.35rem, 0.3rem + 0.4vw, 0.75rem);
}
.four__v--4 .four__n {
  grid-column: 2;
  font-size: clamp(2.6rem, 1.6rem + 4.2vw, 5.4rem);
}

/* --------------------------------------------------------------------------
   The two plates

   Both carry `.reveal`, so the tilt uses the independent `rotate` property —
   `.reveal.is-visible` sets `transform: none` and would wipe a rotation the
   instant the observer fired. GSAP owns the inner image's transform alone.
   -------------------------------------------------------------------------- */

.four__plate {
  position: relative;
  margin: 0;
  overflow: hidden;
  box-shadow:
    inset 0 0 0 1px rgba(156, 120, 72, 0.26),
    inset 0 3px 14px -6px rgba(0, 0, 0, 0.9),
    0 22px 52px -32px rgba(0, 0, 0, 0.95);
}

.four__shot { position: absolute; inset: 0; overflow: hidden; }

/* GRADED DOWN ON PURPOSE. `.fdr` directly above is led by its photographs;
   this section is led by its type, and two adjacent sections cannot both be
   led by pictures.
   Laid out taller than its opening so the scroll drift never exposes an edge.
   Layout, not a base transform — GSAP owns this element's transform alone. */
.four__shot img {
  position: relative;
  top: -6%;
  width: 100%;
  height: 112%;
  object-fit: cover;
  filter: brightness(0.82) saturate(0.78) contrast(1.04);
}

.four__plate::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(6, 6, 8, 0.36) 0%, transparent 34%, rgba(6, 6, 8, 0.56) 100%);
}

.four__plate--wide {
  grid-column: 1 / 7;
  grid-row: 2;
  align-self: start;
  /* SHALLOW ON PURPOSE. This plate sets the height of its whole row and at
     16/10 it stood 440px tall beside a two-line sentence. The row is now
     sized by a photograph the copy can actually stand level with. */
  margin-top: clamp(1.62rem, 1.08rem + 2.304vw, 3.6rem);
  margin-left: calc(-1 * var(--bleed));
  aspect-ratio: 2.25 / 1;
}
.four__plate--wide .four__shot img { object-position: 54% 50%; }

.four__plate--small {
  grid-column: 9 / -1;
  grid-row: 3;
  align-self: end;
  /* A SMALLER TOP MARGIN THAN ITS ROW PARTNER IS THE INTERLOCK. Both items
     hang from the same row line, so the difference between their margins is
     exactly how far this plate rises above the copy beside it. */
  margin-top: clamp(0.5rem, 0.2rem + 1vw, 1.5rem);
  aspect-ratio: 5 / 4;
  rotate: -1.7deg;
}
.four__plate--small .four__shot img { object-position: 50% 42%; }

/* --------------------------------------------------------------------------
   Narrower

   One column, in source order — the head, then the four in turn with the two
   photographs where they already sit between them. The names stay `nowrap`
   (the tightening is the section), so the open tracking has to come down or
   "Performance" outruns a phone column before it closes.
   -------------------------------------------------------------------------- */

@media (max-width: 900px) {
  .four { --track-open: 0.06em; }

  .four__stage {
    display: flex;
    flex-direction: column;
    gap: clamp(2rem, 1.4rem + 2.6vw, 3.5rem);
  }
  /* **`align-self` FLIPS AXIS BETWEEN GRID AND FLEX, AND IT MUST BE RESET
     HERE.** In the grid above it is VERTICAL - `end` means "sit on the row's
     bottom edge". In this flex COLUMN the cross axis is horizontal, so the
     very same declarations silently became "align right", and the headline,
     Precision, Ownership and the small plate all jumped to the right margin.
     Nothing overflowed, nothing collided and nothing was hidden, so the
     numeric sweep passed it clean - it was only visible in a screenshot.
     Any grid that collapses to flex has to neutralise its own align-self. */
  .four__head,
  .four__v,
  .four__plate { align-self: stretch; margin-top: 0; }

  .four__head { padding-bottom: 0; }

  .four__v--1 .four__n { font-size: clamp(2rem, 1.5rem + 2.4vw, 3.2rem); }
  .four__v--2 .four__n { font-size: clamp(1.6rem, 1.3rem + 1.4vw, 2.4rem); }
  .four__v--3 .four__n { font-size: clamp(1.9rem, 1.45rem + 2.1vw, 3rem); }
  .four__v--4 .four__n { font-size: clamp(2.1rem, 1.55rem + 2.7vw, 3.5rem); }

  /* right alignment needs a wide column to read as alignment rather than as
     a mistake, and the margin-hung sentence needs a margin to hang in */
  .four__v--3 { text-align: left; padding-bottom: 0; }
  .four__v--3 .four__p { margin-left: 0; }
  /* ONE COLUMN IS WHERE THE FOUR STRUCTURES CONVERGE, and 03 and 04 are the
     pair that collide: both end up with the sentence above the name. 04 keeps
     its "hanging in the margin" character by staying OFFSET while 03 sits
     flush, so the two adjacent beats still differ in kind rather than only in
     size (the whole point this section is built on). */
  .four__v--4 { display: block; }
  .four__v--4 .four__p {
    margin-bottom: clamp(0.7rem, 0.6rem + 0.5vw, 1.1rem);
    margin-left: auto;
    max-width: 86%;
  }

  .four__p { max-width: 42rem; }

  .four__plate--wide {
    width: calc(100% + var(--bleed));
    margin-left: calc(-1 * var(--bleed));
    aspect-ratio: 2.1 / 1;
  }
  /* KEEPS THE LANDSCAPE CROP IT HAS ON THE GRID. Left at 4/5 it stood 418px
     tall against nothing on its left, which is a hole rather than a margin;
     the same crop at 5/4 is 269px and the air beside it reads as air. */
  .four__plate--small {
    rotate: none;
    width: min(72%, 23rem);
    margin-left: auto;
    aspect-ratio: 5 / 4;
  }
}

@media (max-width: 620px) {
  .four__plate--wide { aspect-ratio: 16 / 9; }
  .four__plate--small { width: min(82%, 19rem); }
}

/* ============================================================
   CULTURE — "THE ROOM"   (.rm, about.html section 04)

   THE CAMERA TURNS, IN PLACE. Everything else on this site travels
   X (.wall), pushes Z (.worlds), climbs Y (.rise), holds still (.lp)
   or turns an OBJECT (.forge). Rotating the VIEW was the one camera
   move still free, and the section's headline is literally about
   standing inside something.

   ONE NUMBER: --pan, the bearing in degrees, written on .rm by the
   module every frame. CSS computes the scene rotation, each frame's
   grade and shadow, the caption handover, the drifting light pool
   and the bearing strip from it. --pan is UNREGISTERED on purpose:
   it never needs to interpolate and it inherits to the HUD (which
   sits outside the 3D context) for free.

   The BASE below is a finished hand-set gallery. .is-live is added
   only at >=901px with GSAP present, so no-JS / no-CDN / reduced
   motion / phones all get a complete section, never a blank stage.
   ============================================================ */

/* the pointer nudge is a SEPARATE number from the scrub, so a tween
   and a transition can never fight over the same value. inherits:false
   keeps it off every element on all nine pages. */
@property --sway { syntax: "<number>"; inherits: false; initial-value: 0; }

.rm {
  --pan: 0;
  position: relative;
  isolation: isolate;
  overflow-x: clip;
  /* no ground of its own: the site has ONE, and it is fixed. See .ground. */
}

.rm__head { max-width: 54rem; margin-bottom: clamp(2.25rem, 1.75rem + 2.6vw, 4rem); }
.rm__title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.9rem, 1.1rem + 3.3vw, 4rem);
  line-height: 1.05;
  letter-spacing: -0.024em;
  color: var(--ink);
  margin-block: 0.75rem 1rem;
}
.rm__lead {
  font-size: var(--text-base);
  line-height: 1.62;
  color: var(--vein);
  max-width: 34rem;
}

/* ---- BASE: a hand-set gallery, uneven on purpose ------------- */

.rm__track,
.rm__stage,
.rm__space { position: relative; }

.rm__scene {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: clamp(1rem, 0.7rem + 1.4vw, 2.25rem);
  align-items: start;
  padding-inline: var(--gutter);
  max-width: calc(var(--container) + 2 * var(--gutter));
  margin-inline: auto;
}

.rmp { position: relative; margin: 0; }
.rmp--1 { grid-column: 1 / 10; grid-row: 1; }
.rmp--3 { grid-column: 1 / 5;  grid-row: 2; }
.rmp--2 { grid-column: 5 / 13; grid-row: 2; margin-top: clamp(1.28rem, 4.48vw, 5.12rem); }
.rmp--4 { grid-column: 4 / 13; grid-row: 3; }

.rmp__shot {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: 3px;
  background: var(--obsidian-raised);
  aspect-ratio: var(--w) / var(--h);
}
.rmp__shot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.92) saturate(0.86) contrast(1.03);
}
.rmp__edge {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border: 1px solid var(--border-gold);
  border-radius: 3px;
}

.rmp__cap { display: block; padding-top: 0.9rem; }
.rmp__k {
  display: block;
  font-size: 0.66rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold-light);
  margin-bottom: 0.4rem;
}
.rmp__t {
  display: block;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.05rem, 0.9rem + 0.6vw, 1.4rem);
  line-height: 1.28;
  color: var(--ink);
}

/* everything that only exists inside the room */
.rm__win,
.rm__wall,
.rm__floor,
.rm__lamp,
.rm__vig,
.rm__grain,
.rm__hud { display: none; }

.rm__stmt {
  margin-top: clamp(1.8rem, 1.44rem + 2.16vw, 3.24rem);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.5rem, 1.1rem + 1.7vw, 2.4rem);
  line-height: 1.2;
  letter-spacing: -0.018em;
  color: var(--ink);
  max-width: 47rem;
  text-wrap: balance;
}
.rm__stmt b { color: var(--gold-light); font-weight: 500; }

@media (max-width: 700px) {
  .rm__scene { grid-template-columns: minmax(0, 1fr); }
  .rmp--1, .rmp--2, .rmp--3, .rmp--4 {
    grid-column: 1;
    grid-row: auto;
    margin-top: 0;
  }
  /* the crop is the only thing keeping four frames distinct in one
     column, so the aspects stay unequal rather than normalising */
  .rmp--1 .rmp__shot { aspect-ratio: 16 / 9; }
  .rmp--3 .rmp__shot { aspect-ratio: 4 / 5; width: min(100%, 21rem); }
  .rmp--4 .rmp__shot { aspect-ratio: 5 / 4; }
}

/* ============================================================
   LIVE — the room, and the turn
   ============================================================ */

.rm.is-live { --u: 1px; }
@media (min-width: 901px) and (max-width: 1319px) { .rm.is-live { --u: 0.86px; } }
@media (min-width: 901px) and (max-width: 1099px) { .rm.is-live { --u: 0.74px; } }

.rm.is-live .rm__track { height: 200svh; }

.rm.is-live .rm__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
}

.rm.is-live .rm__space {
  position: absolute;
  inset: 0;
  perspective: calc(1400 * var(--u));
  perspective-origin: 50% 50%;
}

/* the whole room turns as one. No GSAP transform here: CSS owns it,
   composed from the scrubbed --pan and the transitioned --sway. */
.rm.is-live .rm__scene {
  display: block;
  position: absolute;
  inset: 0;
  padding: 0;
  margin: 0;
  max-width: none;
  transform-style: preserve-3d;
  transform: rotateY(calc((var(--pan) + var(--sway)) * -1deg));
  transition: --sway 0.75s cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
}

/* Every station sits on the same cylinder: rotate to its bearing, push
   out to its own depth, then lift. --y is applied AFTER translateZ so it
   moves in world Y, and the box is centred by inset+margin rather than a
   percentage translate (a % translate resolves against the element's own
   size, which is the trap this site has paid for three times).

   BOTH SIGNS ARE NEGATIVE AND BOTH MATTER. translateZ is NEGATIVE because
   positive Z is TOWARD the viewer: the first cut pushed every frame in
   front of the camera and they rendered 1592px wide instead of 515. And
   the bearing is negated so that on-screen bearing works out to the plain
   sum (--a + --pan), which is what every reaction in this section reads. */
.rm.is-live .rmp,
.rm.is-live .rm__win,
.rm.is-live .rm__wall {
  position: absolute;
  inset: 0;
  margin: auto;
  width: calc(var(--w) * var(--u));
  height: calc(var(--h) * var(--u));
  transform:
    rotateY(calc(var(--a) * -1deg))
    translateZ(calc(var(--d) * var(--u) * -1))
    translateY(calc(var(--y) * var(--u)));
}

/* --e is the station's bearing RELATIVE to where you are looking, and
   --face is how square-on it is. Squared falloff, computed with plain
   number multiplication so no JS touches any of it.
   --sway is deliberately left out: it is registered inherits:false, and
   an unregistered property's expression resolves such a reference to the
   registered INITIAL value on every child. */
.rm.is-live .rmp {
  --e: calc(var(--a) + var(--pan));
  --face: clamp(0, calc(1 - var(--e) * var(--e) / 1700), 1);
  opacity: 0;
  transition: opacity 1s cubic-bezier(0.22, 0.61, 0.36, 1);
  transition-delay: calc(var(--i) * 0.14s);
}
.rm.is-live.is-in .rmp { opacity: 1; }

.rm.is-live .rmp__shot {
  position: absolute;
  inset: 0;
  aspect-ratio: auto;
  border-radius: 2px;
  /* STATIC, and that is a performance decision. A box-shadow whose BLUR
     RADIUS is driven from --face repaints the shadow on all four frames
     every frame of the scrub - it was the second biggest cost in the
     section. The lift is carried by the scrim and the hairline instead,
     both of which are compositor work. */
  box-shadow: 0 calc(42 * var(--u)) calc(92 * var(--u)) calc(-28 * var(--u)) rgba(0, 0, 0, 0.88);
}

/* THE GRADE IS A SCRIM, NOT A FILTER, FOR THE SAME REASON. Re-filtering
   four large bitmaps every frame is the single most expensive thing this
   section could do; fading a flat rect over them is compositor work and
   looks the same. The static filter below still sets the house grade. */
.rm.is-live .rmp__shot::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--obsidian);
  opacity: calc((1 - var(--face)) * 0.64);
}

/* the picture shifts against its own frame, so each photograph keeps a
   little interior depth of its own as the room turns past it. The scale
   is headroom for that shift and nothing else. */
.rm.is-live .rmp__shot img {
  --es: clamp(-40, var(--e), 40);
  transform: translateX(calc(var(--es) * -0.5px)) scale(1.13);
  filter: brightness(1.06) saturate(0.92) contrast(1.07);
}

.rm.is-live .rmp__edge {
  border-color: var(--gold-shadow);
  /* z-index keeps it above ::after, which is later in paint order */
  z-index: 2;
  opacity: calc(0.3 + var(--face) * 0.7);
  box-shadow: inset 0 0 0 1px rgba(192, 156, 96, 0.11);
}

/* the real caption stays in the document for assistive tech; the visible
   one is drawn flat, above the 3D, out of the accessibility tree */
.rm.is-live .rmp__cap {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- the room itself ---------------------------------------- */

.rm.is-live .rm__floor {
  display: block;
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(2600 * var(--u));
  height: calc(1700 * var(--u));
  margin-left: calc(-1300 * var(--u));
  margin-top: calc(-850 * var(--u));
  /* pushed to -1500 so the whole plane stays BEHIND the camera plane. A
     floor centred at z:0 has half its depth at positive z, i.e. behind
     the viewer's eye, where the perspective divide blows up. */
  transform: translate3d(0, calc(400 * var(--u)), calc(-1180 * var(--u))) rotateX(75deg);
  background:
    radial-gradient(28% 24% at 33% 26%, rgba(214, 172, 102, 0.58), transparent 74%),
    radial-gradient(22% 20% at 71% 30%, rgba(198, 156, 92, 0.4), transparent 76%),
    radial-gradient(46% 40% at 46% 40%, rgba(150, 114, 62, 0.22), transparent 78%),
    radial-gradient(54% 50% at 50% 34%, #14131a 0%, rgba(12, 12, 17, 0.6) 58%, transparent 100%);
}

/* WHERE THE LIGHT COMES FROM. No photograph and no copy: it is
   architecture, which is also what stops the set reading as a four-up.

   ON AN OBSIDIAN BRAND THE INSTINCT IS TO KEEP GOING DARKER, and the
   first cut of this was a flat tan rectangle with a hard edge: a panel,
   not a source. What fixes it is what fixed the doorways on what-we-do -
   a genuinely blown-out core, a large bloom AROUND it, and no hard edge
   anywhere (the mask feathers the glass into its own spill). */
.rm.is-live .rm__win {
  display: block;
  background:
    radial-gradient(64% 52% at 46% 36%, rgba(255, 250, 240, 1), rgba(240, 210, 156, 0.94) 38%, rgba(178, 136, 76, 0.62) 70%, rgba(104, 78, 44, 0.1) 100%);
  box-shadow:
    0 0 calc(560 * var(--u)) calc(210 * var(--u)) rgba(178, 134, 70, 0.34),
    0 0 calc(190 * var(--u)) calc(44 * var(--u)) rgba(236, 198, 130, 0.4);
  -webkit-mask-image: radial-gradient(66% 60% at 48% 40%, #000 30%, rgba(0, 0, 0, 0.6) 68%, transparent 100%);
  mask-image: radial-gradient(66% 60% at 48% 40%, #000 30%, rgba(0, 0, 0, 0.6) 68%, transparent 100%);
}
.rm.is-live .rm__win-glass {
  position: absolute;
  inset: 0;
  background: linear-gradient(118deg, rgba(255, 255, 250, 0.5), transparent 42%);
}
/* the frame is read as a SILHOUETTE against the light, so the bars are
   soft-edged and never fully opaque - a crisp black cross on the glass
   turned the whole thing back into a graphic */
.rm.is-live .rm__win-bar {
  position: absolute;
  background: rgba(10, 9, 12, 0.55);
  filter: blur(calc(4 * var(--u)));
}
.rm.is-live .rm__win-bar--v { top: 10%; bottom: 12%; left: 47%; width: calc(13 * var(--u)); }
.rm.is-live .rm__win-bar--h { left: 10%; right: 10%; top: 26%; height: calc(11 * var(--u)); }

/* the far surfaces. Dark, but never black: they are what the frames hang
   on, and they carry the window's spill across the room. */
.rm.is-live .rm__wall {
  display: block;
  background:
    radial-gradient(50% 54% at 50% 42%, rgba(30, 28, 35, 0.97) 0%, rgba(14, 13, 18, 0.62) 56%, transparent 100%);
}
/* only the segment the window is standing against catches its spill */
.rm.is-live .rm__wall:first-of-type {
  background:
    radial-gradient(40% 36% at 64% 38%, rgba(150, 114, 64, 0.32), transparent 72%),
    radial-gradient(50% 54% at 50% 42%, rgba(34, 31, 39, 0.97) 0%, rgba(15, 14, 19, 0.62) 56%, transparent 100%);
}

/* ---- atmosphere, flat on the stage --------------------------- */

/* The pool of light is fixed in the ROOM, so as you turn it crosses your
   view. Its centre is tied to the window's own bearing.

   IT IS TRANSLATED, NEVER REDRAWN. The first cut put --pan inside the
   gradient's own `at` position, which repaints a full-viewport gradient
   on every frame of the scrub - measured as the single biggest cost in
   the section. A fixed-size element with a static gradient, moved by a
   transform, is compositor work and looks identical. Same lesson as the
   .cover gleam: move the light, do not recompute it. */
.rm.is-live .rm__lamp {
  display: block;
  position: absolute;
  top: 42%;
  left: 50%;
  width: calc(1500 * var(--u));
  height: calc(1150 * var(--u));
  margin: calc(-575 * var(--u)) 0 0 calc(-750 * var(--u));
  z-index: 2;
  pointer-events: none;
  background: radial-gradient(closest-side, rgba(150, 114, 62, 0.19), rgba(150, 114, 62, 0.07) 54%, transparent 78%);
  transform: translate3d(calc((var(--pan) - 52) * 14px), 0, 0);
  will-change: transform;
}

.rm.is-live .rm__vig {
  display: block;
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background:
    radial-gradient(120% 88% at 50% 44%, transparent 38%, rgba(4, 4, 6, 0.82) 100%),
    linear-gradient(0deg, rgba(4, 4, 6, 0.92) 0%, rgba(4, 4, 6, 0.66) 14%, transparent 34%);
}

.rm.is-live .rm__grain {
  display: block;
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: 0.36;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.42'/%3E%3C/svg%3E");
}

/* ---- the flat layer ------------------------------------------ */

.rm.is-live .rm__hud {
  display: block;
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
}

.rm.is-live .rm__caps {
  position: absolute;
  left: 50%;
  translate: -50% 0;
  bottom: clamp(6.5rem, 12vh, 10rem);
  width: min(52rem, 76%);
  height: 4.6rem;
}
/* A PLATEAU AND THEN A CLIFF, not a smooth curve. A bare parabola holds
   the caption around 0.15 for a long stretch of the handover, which reads
   as a rendering fault rather than as one statement giving way to the
   next. Multiplying by 4.5 clamps it to full within about 10deg and takes
   it to zero over the next 1.5, so exactly one line is ever on screen and
   there is a short blackout between stations (the .wall handover). */
.rm.is-live .rm__cap {
  --e: calc(var(--a) + var(--pan));
  --sh: clamp(0, calc((1 - var(--e) * var(--e) / 144) * 4.5), 1);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  text-align: center;
  opacity: var(--sh);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.25rem, 1rem + 0.9vw, 1.75rem);
  line-height: 1.24;
  letter-spacing: -0.012em;
  color: var(--ink);
  text-shadow: 0 2px 22px rgba(4, 4, 6, 0.9);
}
.rm.is-live .rm__cap::before { content: attr(data-line); }

.rm.is-live .rm__bear {
  position: absolute;
  left: 50%;
  translate: -50% 0;
  bottom: clamp(2.6rem, 5vh, 4.5rem);
  width: min(42rem, 70%);
  height: 3rem;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 30%, #000 70%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 30%, #000 70%, transparent);
}
.rm.is-live .rm__bear-rule {
  position: absolute;
  left: 0;
  right: 0;
  top: 0.6rem;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--gold-shadow) 26%, var(--gold) 50%, var(--gold-shadow) 74%, transparent);
  opacity: 0.6;
}
.rm.is-live .rm__bear-mark {
  position: absolute;
  left: 50%;
  top: 0.1rem;
  width: 0.42rem;
  height: 0.42rem;
  margin-left: -0.21rem;
  border-left: 1px solid var(--gold-light);
  border-top: 1px solid var(--gold-light);
  rotate: 45deg;
}
/* the labels slide, the mark does not, so where you are looking needs
   no explaining. Position and travel are the same 9px per degree, which
   is what puts the facing station exactly on the mark. */
.rm.is-live .rm__bear-strip {
  position: absolute;
  left: 0;
  right: 0;
  top: 1.25rem;
  height: 1.4rem;
  transform: translateX(calc(var(--pan) * 9px));
}
.rm.is-live .rmb {
  --e: calc(var(--a) + var(--pan));
  --face: clamp(0, calc(1 - var(--e) * var(--e) / 260), 1);
  position: absolute;
  left: calc(50% + var(--a) * 9px);
  top: 0;
  translate: -50% 0;
  white-space: nowrap;
  font-size: 0.68rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}
/* two stacked pseudo copies rather than colour arithmetic, and no text
   node either way, so nothing here is announced or dimmed below AA */
.rm.is-live .rmb::before,
.rm.is-live .rmb::after {
  content: attr(data-label);
  display: block;
}
.rm.is-live .rmb::before { color: var(--ink); opacity: 0.6; }
.rm.is-live .rmb::after {
  position: absolute;
  inset: 0;
  color: var(--gold-light);
  opacity: var(--face);
}

@media (prefers-reduced-motion: reduce) {
  .rm.is-live .rm__scene { transition: none; }
  .rm.is-live .rmp { transition: none; opacity: 1; }
}

/* ============================================================
   WHAT-WE-DO HERO — "THE LENS"  (.prop, what-we-do.html)

   HISTORY. A cover sheet with the firm's monogram struck as an
   object; then the same sheet with the monogram swapped for a
   two-frame film gate in the right column; now this.

   WHAT WAS WRONG WITH THE LAST ONE, measured at 1440:

     - the proposition ran at 48px inside an 867px column, because a
       434px picture was standing next to it. The note this hero has
       carried since the first build says "widen the column before
       ever enlarging the type" - and there was no column left to
       widen while the picture sat in the grid;
     - the four specification rows hung off the bottom of that same
       column as fine print, 131px of it, in the narrowest thing on
       the page;
     - and the picture was `border-radius: 3px` over
       `box-shadow: 0 34px 78px -34px` - a rounded rectangle floating
       on a drop shadow. A card. On a site that has spent nine pages
       refusing to draw one.

   THE SENTENCE IS THE DESIGN, AND IT NAMES ITS OWN SUBJECT.
   "We have THE PEOPLE who can sell it." So:

   1. THE MASTHEAD TAKES THE WHOLE MEASURE. Nothing stands beside the
      proposition, so it runs at 62px across the page instead of 48px
      in a column, and the line still breaks exactly where the <br>
      says it does. The type scale is `5.4vw - 0.3rem` rather than a
      vw-plus-rem clamp because the constraint is not aesthetic: the
      second line is ~15.6em long, so the size that keeps it on ONE
      line is a function of the measure, and this expression IS that
      function. Capped at 3.9rem so it cannot run away on a monitor.

   2. TWO OF THE WORDS ARE NOT SET IN INK. `.prop__lens` clips its
      background to the letterforms of "the people", and behind them
      is a photograph of the firm's own people. The claim and its
      evidence are one object. A specular pass travels through the
      letters as the line lands, and the picture inside them drifts
      to the pointer - so the words behave like glass over a picture
      rather than like a coloured word.

   3. THE PICTURE IS AN OPENING, NOT A PLATE. No radius, no shadow,
      no frame. It runs off the right edge of the page and off the
      bottom of the hero, so only the two sides that FACE the page
      are drawn, and both are lit from inside. A rectangle with two
      edges cannot read as a card, and nothing had to be layered over
      it to stop it.

   4. THE SPECIFICATION IS THE BASE OF THE SHEET. One drawn rule
      across the measure, four ticks cut into it, four values hanging
      below. It terminates where the picture begins, which is the
      site's own grammar - a rule that runs the measure and stops at
      the object. Out of the type column it costs the hero no height
      at all.

   THE ENTRANCE IS A FOCUS PULL, and it is the one gesture this site
   had not used. Everything else here uncovers (clip, wipe, draw) or
   settles (light, scale). The gate arrives at blur 15 and half
   exposure and is brought to sharpness over 1.5s, on the beat the
   curtain clears. --pf is one registered number carrying blur,
   brightness and saturation together, so the three can never come
   out of step with each other.

   NO RULES ON THE PROPOSITION, STILL. The client removed every
   hairline, registration mark and plinth from the masthead once
   already. The only rule in this hero is the specification's, and it
   is at the foot of the sheet where a specification belongs.

   EVERY RESTING VALUE IS THE FINISHED ONE. --pf 0 is sharp, --srule
   1 is drawn, the sweep is parked off the left of the letters. The
   from-states exist only under `.is-loading`, which an inline head
   script sets and motion.js clears with a 3.5s hard fallback - so
   with no JS, a dead CDN or reduced motion the hero simply renders
   finished. GSAP adds the sweep, the pointer parallax and the
   defocus on the way out, and nothing it does can hide a word.

   `.cover__object` / `.cover__mark` / `.cover__halo` / `.cover__gleam`
   / `.cover__echo` in editorial-1b.css are UNUSED on this page. Not
   deleted, per the .argue near-miss: removing `.prop` from the
   section and restoring that markup is all a revert takes.
   ============================================================ */

/* --pf  focus (1 = soft and dark, 0 = sharp) — INHERITS, because it is
         written on the gate and read by the images inside it.
   --pd  the defocus on the way out. A SECOND number rather than more
         of --pf: --pf is carried by a CSS transition and --pd is
         scrubbed by ScrollTrigger, and one property with two owners
         has cost this project four bugs already.
   --lsw the specular travelling through the letterforms.
   --lnx  the picture's drift inside them. NOT `--lx`, which is where
          this started: @property is GLOBAL, and slate.css reads
          `var(--lx, 24)` for its lamp. A registered property always has
          a value, so registering `--lx` here would have silently taken
          that fallback away and moved another section's light to the
          corner of the frame. Short names do not belong in a global
          registry.
   --srule how far the specification's rule has drawn. */
@property --pf    { syntax: "<number>"; inherits: true;  initial-value: 0; }
@property --pd    { syntax: "<number>"; inherits: true;  initial-value: 0; }
@property --lsw   { syntax: "<number>"; inherits: false; initial-value: -70; }
@property --lnx    { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --lny    { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --srule { syntax: "<number>"; inherits: false; initial-value: 1; }

.prop {
  /* declared here rather than read from editorial-1b, because the gate's
     negative bottom margin has to be the SAME number as the hero's bottom
     padding or the picture stops short of the fold */
  --hero-pb: clamp(2.5rem, 2rem + 2vw, 4rem);
  --pbleed: max(var(--gutter), calc((100vw - var(--container)) / 2));
  --gatecol: clamp(15rem, 26vw, 23rem);
  --gategap: clamp(1.5rem, 1rem + 2.4vw, 3rem);
  /* shorter than the 84svh/46rem this hero used to hold: the picture is
     bigger and the type is bigger, and the section still lost 80px */
  min-height: min(78svh, 43rem);
  padding-bottom: var(--hero-pb);
  overflow-x: clip;
}

/* a warm pool the picture stands in, so it lifts off the ground without
   needing a frame drawn round it */
.prop__wash {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(30rem 26rem at 84% 46%, rgba(158, 122, 70, 0.15), transparent 68%),
    radial-gradient(44rem 20rem at 56% 112%, rgba(5, 5, 7, 0.85), transparent 66%);
}

.prop .cover__inner { display: flex; flex-direction: column; }

/* --------------------------------------------------------------
   1. The masthead
   -------------------------------------------------------------- */

.prop .cover__mast { position: relative; z-index: 2; }

/* see the note above: the size is the function that keeps line two on
   one line at any measure, not a taste value */
.prop h1 {
  font-size: clamp(1.85rem, calc(5.4vw - 0.3rem), 3.9rem);
  line-height: 1.06;
  letter-spacing: -0.03em;
  margin-bottom: 0;
}

/* ---- the two words that are an aperture ----------------------
   The background is clipped to the letterforms, so what fills them is
   a photograph of the firm's own people and not a colour. Three
   layers, composed in one paint:

     1. the specular, a narrow band whose stops are driven by --lsw,
        so the light travels through the letters with no second
        element and no mask to keep registered;
     2. a lift, screened over the picture, which puts a FLOOR under
        the luminance of the letters;
     3. the photograph, `cover` against the span's own box.

   THE LIFT IS A MEASURED VALUE, NOT A TASTE ONE, and it is the only
   reason this device is usable at all. Sampled: the band of
   culture-talk.jpg that lands inside the letterforms has a 5th
   percentile luminance of 0.025 - near black, because the people in
   it are wearing dark knitwear. Against this ground (#17150F, Y
   0.0075) that is 1.8:1 through a 26% lift, so a fifth of every word
   would have been all but invisible. Screen compositing gives
   R = a + (1-a)B, which puts a hard floor at `a` whatever the
   photograph does, and the four crops that were tried all measured
   the same near-black fifth percentile - so the floor, not the crop,
   is what had to move. At 0.46 the worst pixel in the word measures
   4.18:1, comfortably past the 3:1 that 62px type needs, and the
   picture still has 0.46 -> 1.0 of range to be a picture in.

   THE POSITION IS MEASURED TOO. 16% is the band with the highest
   median (0.32 against 0.16 at the foot of the frame), so the most
   readable part of the photograph is the part inside the words.

   `background-clip: text` is 2016-era in every engine this site
   supports, but the @supports below hands the words back to solid
   gold if it is ever missing, because a transparent fill with no
   background painted behind it is an invisible headline.
   -------------------------------------------------------------- */
.prop__lens {
  position: relative;
  background-image:
    linear-gradient(100deg,
      transparent calc(var(--lsw) * 1%),
      rgba(255, 252, 244, 0.92) calc(var(--lsw) * 1% + 8%),
      transparent calc(var(--lsw) * 1% + 19%)),
    linear-gradient(180deg, rgba(255, 249, 238, 0.52), rgba(255, 238, 212, 0.46)),
    url("../assets/img/culture-talk.jpg");
  background-size: 100% 100%, 100% 100%, cover;
  background-position:
    0 0,
    0 0,
    calc(50% + var(--lnx) * 1%) calc(16% + var(--lny) * 1%);
  background-repeat: no-repeat;
  background-blend-mode: screen, screen, normal;
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .prop__lens {
    color: var(--gold-light);
    -webkit-text-fill-color: currentColor;
  }
}

/* --------------------------------------------------------------
   2. The body: the lede and the specification, beside the opening
   -------------------------------------------------------------- */

.prop .cover__body {
  flex: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--gatecol);
  column-gap: var(--gategap);
  align-items: stretch;
  padding-block: 0;
  margin-top: clamp(1.4rem, 1rem + 2.2vw, 2.6rem);
  min-height: clamp(16rem, 26svh + 4rem, 22rem);
}

/* the lede at the top, the specification on the floor, and whatever
   slack the viewport gives spent between them rather than under them */
.prop .cover__type {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: clamp(1.5rem, 1.1rem + 2vw, 2.75rem);
}

/* wider than the 34rem the old column allowed, because the column is
   wider now and a lede set to 60 characters under a 62px headline
   reads as a caption */
.prop .hero__sub {
  max-width: 46rem;
  margin-bottom: 0;
}

/* ---- the base of the sheet -----------------------------------
   One rule, four ticks cut into it, four values below. The ticks are
   what stop this reading as a four-cell table: a table divides its
   cells with lines that run their full height, and a specification
   marks its stations ON the datum. Same reason the round section
   marks its stages on one rule instead of boxing them.
   -------------------------------------------------------------- */
.prop .cover__spec {
  --sp: clamp(0.85rem, 0.7rem + 0.7vw, 1.4rem);
  position: relative;
  margin-top: 0;
  max-width: none;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: clamp(1rem, 0.7rem + 1.4vw, 2.25rem);
  padding-top: var(--sp);
}

.prop .cover__spec::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: linear-gradient(90deg,
    var(--gold-shadow) 0%,
    rgba(184, 145, 88, 0.34) 62%,
    rgba(184, 145, 88, 0.14) 100%);
  transform: scaleX(var(--srule));
  transform-origin: 0 50%;
  transition: transform 1.15s var(--ease-out) 0.52s;
}

.prop .cover__row {
  position: relative;
  display: block;
  margin-top: 0;
}
.prop .cover__row + .cover__row { margin-top: 0; }

/* the station mark, cut down from the datum */
.prop .cover__row::before {
  content: "";
  position: absolute;
  top: calc(var(--sp) * -1);
  left: 0;
  width: 1px;
  height: 0.4rem;
  background: var(--gold);
  opacity: 0.55;
}

.prop .cover__row dt {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 0.625rem;
  letter-spacing: 0.2em;
}

.prop .cover__row dd {
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--ink);
}

/* --------------------------------------------------------------
   3. The opening
   -------------------------------------------------------------- */

/* A grid item, extended past its column by negative margins rather
   than positioned absolutely: the right margin is exactly the distance
   from the container to the viewport edge and the bottom margin is
   exactly the hero's own bottom padding, so the picture lands ON both
   folds at every width instead of near them. */
.prop__gate {
  position: relative;
  align-self: stretch;
  margin-right: calc(var(--pbleed) * -1);
  margin-bottom: calc(var(--hero-pb) * -1);
  overflow: hidden;
  background: var(--obsidian-raised);
  /* NO radius and NO drop shadow. The two sides that face the page are
     lit from inside, which is what an opening cut into a surface looks
     like; the two that leave the page are not drawn at all. */
  box-shadow:
    inset 1px 0 0 rgba(255, 248, 235, 0.17),
    inset 0 1px 0 rgba(255, 248, 235, 0.13);
  /* the reveal rides on clip-path and NEVER on transform: the strip
     inside owns transform, and by now a tween and a CSS transition
     sharing one property has cost this project four bugs */
  clip-path: inset(0 0 0 0);
  transition:
    clip-path 1.15s cubic-bezier(0.22, 0.61, 0.36, 1),
    --pf 1.5s cubic-bezier(0.22, 1, 0.36, 1) 0.24s;
}

/* the transport. 300% tall for three slots, moved by one number, so
   the frames can never drift out of step with each other. --f is
   written by main.js; nothing else decides it. */
.prop__strip {
  position: absolute;
  inset: 0;
  height: 300%;
  transform: translateY(calc(var(--f, 0) * -33.33333%));
  transition: transform 1.05s cubic-bezier(0.65, 0, 0.2, 1);
}
.prop__frame {
  position: relative;
  height: 33.33333%;
  overflow: hidden;
}

/* THE FOCUS PULL LIVES HERE, on filter, and never on transform:
   motion.js parallaxes these images with yPercent, so transform has an
   owner already. One number drives blur, exposure and saturation
   together - a picture that sharpened without also getting brighter
   would read as a bug, not as a lens. */
.prop__frame img {
  position: absolute;
  inset: -6%;
  width: 112%;
  max-width: none;
  height: 112%;
  object-fit: cover;
  /* the sources are landscape and the aperture is portrait, so this
     crop is doing real work: 42% keeps faces clear of the top edge in
     both frames. The overscan is the parallax travel - an image flush
     with its own box has nowhere to move without showing an edge. */
  object-position: 50% 42%;
  filter:
    blur(calc(var(--pf) * 15px + var(--pd) * 5px))
    brightness(calc(0.94 - var(--pf) * 0.52 - var(--pd) * 0.14))
    saturate(calc(0.88 - var(--pf) * 0.46))
    contrast(1.06);
}

/* the foot scrim the caption and the marks stand on. Part of the
   photograph, not a panel laid over it. */
.prop__gate::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 42%;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(180deg, transparent, rgba(6, 6, 8, 0.88) 76%);
}

/* the glass. A fixed diagonal band, so it is lit with no JS at all,
   plus a pointer specular motion.js writes as two numbers. */
.prop__glass {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background:
    radial-gradient(52% 46% at calc(var(--gx, 26) * 1%) calc(var(--gy, 16) * 1%),
      rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.02) 46%, transparent 72%),
    linear-gradient(148deg, rgba(255, 255, 255, 0.05) 0 18%, transparent 32% 100%);
  transition: background 0.4s linear;
}

/* THE GATE FLASHES AS THE FILM PASSES. One frame of light along the
   top edge on every advance - the only thing in this hero that says a
   mechanism just did something, and it is over in 420ms. */
.prop__flash {
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(90deg, transparent, var(--gold-light), transparent);
}
.prop__gate.is-advancing .prop__flash { animation: gate-flash 0.42s ease-out; }
@keyframes gate-flash {
  0%   { opacity: 0; }
  30%  { opacity: 1; }
  100% { opacity: 0; }
}

/* ---- the foot: caption and marks ----------------------------- */

/* THE CAPTION SITS ON THE PICTURE'S OWN FLOOR, at every width.

   It used to be offset by `var(--hero-pb)` as well, on the theory that
   the gate bleeds past the hero and the foot should stay inside the
   sheet's safe area. It does not bleed past anything: the gate's
   negative bottom margin cancels the hero's bottom padding exactly, and
   `overflow: hidden` on the section cuts it there - so the picture ENDS
   on the hero's own edge and that offset was measuring off a fold that
   is not below it. What it bought instead was 65-79px of unexplained
   photograph under the caption, which reads as a caption stranded in
   the middle of the frame rather than as the foot of one.

   The right inset still reserves the bleed, because that edge really
   does leave the page and the marks belong on the container's margin,
   not the viewport's. */
.prop__foot {
  position: absolute;
  z-index: 5;
  left: clamp(0.9rem, 0.6rem + 1vw, 1.4rem);
  right: calc(var(--pbleed) + clamp(0.6rem, 0.4rem + 0.6vw, 1rem));
  bottom: clamp(0.7rem, 0.5rem + 0.7vw, 1.15rem);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  transition: opacity 0.85s ease;
  transition-delay: 1.15s;
}

/* the caption rides the SAME --f as the film, so the words and the
   picture are one movement and cannot disagree */
.prop__caps {
  overflow: hidden;
  height: 1.1em;
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold-light);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.95);
}
/* NOWRAP IS STRUCTURAL HERE, NOT TYPOGRAPHIC. The roll steps by exactly
   1.1em per slot, which is only the height of a slot while every slot is
   one line. Between 901 and ~1010 the foot got narrow enough that "At
   the counter" took a second line, the slot became 23px against an 11.6px
   step, and from there every caption was half of two. */
.prop__caps i {
  display: block;
  font-style: normal;
  line-height: 1.1;
  white-space: nowrap;
  transform: translateY(calc(var(--f, 0) * -1.1em));
  transition: transform 1.05s cubic-bezier(0.65, 0, 0.2, 1);
}

/* two marks, not a rule: they are the control AND the warning that
   the gate is about to advance, and they sit on the photograph so the
   page's layout gains no line. Dropped for good once the reader takes
   hold, because from then on nothing moves unasked. */
.prop__ctl { display: flex; align-items: center; gap: 0.5rem; flex: none; }
/* 44px OF TARGET, 26px OF BOX. The mark is 2px tall and the control sat at
   26px, which is under the minimum a thumb can be asked to hit. The height
   is raised to 44 and the negative block margin gives the layout back the 26
   it had, so the foot's position is unchanged and the hit area is not. */
.prop__seg {
  position: relative;
  width: clamp(1.4rem, 1.1rem + 1vw, 2.1rem);
  height: 44px;
  margin-block: -9px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  display: flex;
  align-items: center;
}
.prop__seg::before {
  content: "";
  flex: 1;
  height: 2px;
  background: rgba(234, 230, 223, 0.32);
  transition: background 0.4s var(--ease-out);
}
.prop__seg:hover::before { background: rgba(234, 230, 223, 0.6); }
.prop__seg:focus-visible { outline: 1px solid var(--gold); outline-offset: 2px; }
.prop__seg i {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--gold-light);
  box-shadow: 0 0 8px rgba(192, 156, 96, 0.7);
  transform: scaleX(var(--dw, 0));
  transform-origin: left center;
}
.prop__seg.is-done i { transform: scaleX(1); opacity: 0.45; }
.prop.is-held .prop__seg.is-live i { transform: scaleX(1); }

/* --------------------------------------------------------------
   4. Entrance — the curtain is the only from-state
   -------------------------------------------------------------- */

.prop .cover__spec { transition: opacity 0.9s var(--ease-out) 0.5s; }
.prop .cover__row {
  transition:
    opacity 0.8s var(--ease-out),
    transform 0.8s var(--ease-out);
  transition-delay: calc(0.68s + var(--i, 0) * 0.09s);
}

/* it opens UPWARD, the direction the film runs, so the hero's first
   movement and its every movement after are the same gesture - and it
   arrives out of focus, which is the pull */
.is-loading .prop__gate { clip-path: inset(100% 0 0 0); --pf: 1; }
.is-loading .prop__foot { opacity: 0; }
.is-loading .prop .cover__spec { opacity: 0; --srule: 0; }
.is-loading .prop .cover__row { opacity: 0; transform: translateY(10px); }
.prop__gate { transition-delay: 0.3s; }

/* --------------------------------------------------------------
   5. Widths
   -------------------------------------------------------------- */

/* the opening narrows before it moves: at 1080 the masthead still has
   the measure it needs, and stacking there would cost 300px of height
   for nothing */
@media (max-width: 1080px) {
  .prop { --gatecol: clamp(12.5rem, 23vw, 17rem); }
  .prop .hero__sub { max-width: 40rem; }
}

/* THE ONE BAND WHERE THE FOOT CANNOT AFFORD THE BLEED. Between 901 and
   1080 the opening is at its narrowest - 243px at the bottom of the band -
   and the foot has to seat a 122px caption, a 16px gap and a 61px control
   inside it. Reserving `--pbleed` on the right spends 36-40px of that on
   nothing, which is exactly the deficit that wrapped the caption. Here
   alone the marks sit off the gate's own right edge; the gate IS at the
   viewport edge in this band, so they still land on a margin, just a
   narrower one. Above 1080 the column is wide enough to pay for both. */
@media (min-width: 901px) and (max-width: 1080px) {
  .prop__foot { right: clamp(0.9rem, 0.6rem + 1vw, 1.4rem); }
}

@media (max-width: 900px) {
  .prop {
    min-height: 0;
    --hero-pb: clamp(2rem, 1.5rem + 2vw, 3rem);
  }
  .prop .cover__body {
    grid-template-columns: minmax(0, 1fr);
    row-gap: clamp(1.75rem, 1.25rem + 2.5vw, 2.75rem);
    min-height: 0;
  }
  .prop .cover__type { display: block; }
  .prop .cover__spec { margin-top: clamp(1.6rem, 1.2rem + 2vw, 2.5rem); }
  /* the aperture turns landscape once it has the full column: the
     sources ARE landscape, so a wide gate is the crop they want, and
     the phone hero gets shorter at the same time. It keeps its right
     bleed and gives up the bottom one - there is no fold to land on
     when it is not standing beside anything. */
  .prop__gate {
    height: clamp(13rem, 46vw, 20rem);
    margin-bottom: 0;
  }
}

/* TWO UP, AND IT STAYS TWO UP ALL THE WAY DOWN. Dropped to one column
   on the phone the four values ran 232px - a quarter of the hero spent
   on four short strings, and the strip stopped reading as a strip at
   all. Two columns costs 142px and the longest value ("AT&T, LeafFilter,
   Primo Water") takes the second line it was always going to take. */
@media (max-width: 700px) {
  .prop .cover__spec {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    row-gap: clamp(1rem, 0.85rem + 0.6vw, 1.35rem);
  }
  /* the ticks belong to the datum, and only the first row of cells is
     hanging off it once this wraps */
  .prop .cover__row:nth-child(n + 3)::before { display: none; }
}

/* --------------------------------------------------------------
   6. Reduced motion — the hero is already open, sharp and drawn
   -------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .prop__gate,
  .prop__strip,
  .prop__caps i,
  .prop__foot,
  .prop__glass,
  .prop__seg::before,
  .prop .cover__spec,
  .prop .cover__spec::before,
  .prop .cover__row { transition: none; }
  .prop__gate.is-advancing .prop__flash { animation: none; }
  .prop__seg i { transform: scaleX(1); opacity: 0.45; }
  .prop__seg.is-live i { opacity: 1; }
}

/* ==========================================================================
   THE FOOTER — "the plate"   (all nine pages)

   The old footer was the shape every footer on the web already has: a
   four-column grid of headed link lists with a brand block on the left and
   a rule above a copyright bar. It was also the only part of the site
   still built that way.

   THIS SITE IS ABOUT DOORS, so the footer is the plate beside ours. One
   engraved surface carrying the firm's name, the address you could drive
   to, and a way to reach a person. It is the component that appears on
   every page, so it should read as the object that identifies the firm -
   which is what a nameplate is.

   THE ADDRESS IS THE HERO, and that is the argument rather than a layout
   preference. Every claim on this site rests on Marble being a real firm
   that turns up in person; a street address is the least deniable version
   of that. It is set larger than anything else down here.

   RESTRAINT, NOT SKEUOMORPHISM. The plate is implied by how light behaves
   on it - a lit lip along the top edge, a slow sheen, a specular that
   follows the pointer, letters that sit down IN the surface and come up
   when you touch them - never by a literal metal texture. The brushed
   diagonal is at three per cent and the noise is there to stop the
   gradients banding.

   NO PHOTOGRAPH DOWN HERE, and that is a constraint rather than a
   preference: this is ONE component on NINE pages, and index.html already
   uses sixteen of the seventeen frames, so any photograph in the footer
   would duplicate a frame somewhere. Same reasoning that governs the FAQ.

   NO RULES EITHER. The three bands are separated by space and by a soft
   trough in the light, never by a drawn line - `.stop` owns the drawn
   boundary on what-we-do and the client has already asked once for rules
   to come off a composition.

   --isheen / --imx / --imy are written by GSAP every frame, so none of
   them is registered with @property: nothing needs interpolating and an
   inherited registered property would cost a recalc on all nine pages.
   Every one of them has a resting CSS value, so with no JS at all the
   plate is simply lit and still.
   ========================================================================== */

.imp {
  position: relative;
  z-index: 2;
  overflow: hidden;
  padding-top: clamp(2.75rem, 2.2rem + 2.2vw, 4.25rem);
  background:
    /* the pool of light the plate stands in */
    radial-gradient(120% 68% at 50% 118%, rgba(156, 120, 72, 0.14), transparent 68%),
    radial-gradient(80% 50% at 18% -10%, rgba(192, 156, 96, 0.06), transparent 70%),
    /* a transparent deepening, NOT a colour: the plate sits lower than
       the page but on exactly the same ground as everything else */
    linear-gradient(180deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.34) 45%, rgba(0, 0, 0, 0.52) 100%);
}

/* the brushed diagonal, deliberately almost invisible: at any strength you
   can actually see, it stops being a surface and becomes a texture */
.imp::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.5;
  background: repeating-linear-gradient(104deg, rgba(255, 255, 255, 0.03) 0 1px, transparent 1px 7px);
}

.imp__grain {
  position: absolute;
  inset: 0;
  z-index: 1;
  opacity: 0.35;
  pointer-events: none;
  /* no grain of its own: .grain::after covers the whole site */
  background: none;
}

/* THE LIP. The plate's top edge catching the light, brightest where the
   page has just come to rest on it. It does not draw itself - a rule that
   opens from the centre is `.stop`'s gesture and this component sits on
   the same page. Only its intensity moves. */
.imp__lip {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(156, 120, 72, 0.35) 14%, rgba(226, 198, 148, calc(0.5 + 0.5 * var(--ilip, 1))) 50%, rgba(156, 120, 72, 0.35) 86%, transparent);
  box-shadow: 0 0 18px rgba(192, 156, 96, calc(0.16 * var(--ilip, 1)));
}

/* the specular. No descendants, so the backdrop-filter containing-block
   trap cannot apply, and it sits under every piece of type. */
.imp__sheen {
  --imx: 50;
  --imy: 40;
  position: absolute;
  inset: 0;
  z-index: 1;
  opacity: 0;
  pointer-events: none;
  background: radial-gradient(30% 46% at calc(var(--imx) * 1%) calc(var(--imy) * 1%), rgba(255, 240, 212, 0.07), transparent 70%);
}

.imp > .container { position: relative; z-index: 4; }

/* ---- the inscription ---- */
/* the middle column is given the room because the address is the largest
   thing on the plate and must not break mid-street */
.imp__top {
  display: grid;
  grid-template-columns: 1.12fr 1.14fr 0.86fr;
  /* row gap is deliberately tighter than the column gap: at one and two
     columns the blocks stack, and the column measure would leave a hole */
  gap: clamp(1.75rem, 1.4rem + 1.2vw, 2.5rem) clamp(2rem, 1.25rem + 2.6vw, 4rem);
  align-items: start;
  padding-bottom: clamp(2rem, 1.6rem + 1.6vw, 2.9rem);
}

.imp__wordmark { display: inline-flex; align-items: center; gap: 0.9rem; }
.imp__mono { position: relative; display: block; flex: none; }
.imp .wordmark__mono { width: clamp(3.25rem, 2.8rem + 1.6vw, 4.5rem); height: auto; }

/* THE GLEAM, and this is the trick worth keeping: the element is MASKED to
   the monogram and only its background-position moves. Translating the
   element itself would slide the mask off the mark instead of moving light
   across it. Ported from the what-we-do cover, which no longer uses it. */
.imp__gleam {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(100deg, transparent 38%, rgba(255, 246, 224, 0.85) 50%, transparent 62%);
  background-size: 320% 100%;
  background-position: calc(var(--igl, 130) * 1%) 0;
  /* stylesheet-relative, not document-relative - a 404 here does not fail
     quietly, it drops the mask and paints the gradient as a rectangle */
  -webkit-mask: url("../assets/mark-240.png") center / contain no-repeat;
          mask: url("../assets/mark-240.png") center / contain no-repeat;
  opacity: 0.75;
}

/* the name carries the plate, so it is set larger here than in the nav */
.imp .wordmark__name { font-size: clamp(1.15rem, 1rem + 0.55vw, 1.52rem); }
.imp .wordmark__sub { letter-spacing: 0.28em; }

.imp__line {
  margin-top: clamp(1.1rem, 0.9rem + 0.7vw, 1.6rem);
  max-width: 24rem;
  color: var(--vein);
  font-size: var(--text-sm);
  line-height: 1.75;
}

.imp__label {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
}

/* the address, set larger than anything else on the plate */
.imp__addr {
  margin-top: clamp(0.9rem, 0.75rem + 0.5vw, 1.25rem);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.18rem, 1.02rem + 0.62vw, 1.5rem);
  line-height: 1.3;
  letter-spacing: -0.008em;
  color: var(--white);
  /* the street must never break mid-line; if it has to wrap, the two
     halves should be even rather than one long line and one orphan */
  text-wrap: balance;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.7);
}

.imp__reach {
  margin-top: clamp(0.9rem, 0.75rem + 0.5vw, 1.25rem);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  font-size: var(--text-sm);
}
.imp__reach a {
  color: var(--vein);
  overflow-wrap: anywhere;
  transition: color 0.35s ease;
}
.imp__reach a:hover { color: var(--gold-light); }

/* ---- the field ----
   A hairline the type sits on, never a boxed input: a bordered rectangle
   with a filled button beside it is the one piece of default UI this site
   had left. */
.imp__news form { margin-top: clamp(0.9rem, 0.75rem + 0.5vw, 1.25rem); }
.imp__field {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  max-width: 22rem;
  padding-bottom: 0.65rem;
}
.imp__field::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg, rgba(156, 120, 72, 0.55), rgba(156, 120, 72, 0.16));
  transform: scaleX(1);
  transform-origin: 0 50%;
  transition: background 0.45s ease, box-shadow 0.45s ease;
}
.imp__field:focus-within::after {
  background: linear-gradient(90deg, var(--gold-light), rgba(192, 156, 96, 0.4));
  box-shadow: 0 0 14px rgba(192, 156, 96, 0.35);
}
.imp__field input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: none;
  padding: 0;
  color: var(--ink);
  font: inherit;
  font-size: var(--text-sm);
}
.imp__field input::placeholder { color: rgba(160, 160, 158, 0.9); }
.imp__field input:focus { outline: none; }
.imp__field button {
  flex: none;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--gold-light);
  cursor: pointer;
  transition: transform 0.4s var(--ease-out), color 0.35s ease;
}
.imp__field button svg { width: 1rem; height: 1rem; }
.imp__field button:hover { transform: translateX(3px); color: var(--white); }

.footer__newsnote { margin-top: 0.7rem; font-size: var(--text-xs); color: var(--vein); letter-spacing: 0.02em; }
.footer__newsnote.is-ok { color: var(--gold-light); }

/* ---- the run ----
   Every page name cut along the base as ONE inscription. Three headed
   columns of links is the shape this footer was rebuilt to get away from,
   and it is also the shape that made the old one read as a template.

   The channel is a soft trough in the light rather than a pair of rules:
   no hard edge anywhere, because nothing down here is allowed to draw a
   line across the page.

   THAT IS WHAT THIS SAYS AND IT WAS NOT WHAT IT DID. The trough was
   `radial-gradient(130% 130% ...)` painted on .imp__run itself, and a
   background paints the element's OWN box - here the container measure,
   inset from the plate by a gutter. Two things followed. The gradient was
   cut off vertically at the container edges, so the channel stopped dead
   in the middle of the plate and left a seam down both sides. And at
   130% of each axis the ramp had barely started by the time it reached
   the box: ~0.22 black still standing at the top and bottom edges, ~0.13
   at the corners. So the section drew the one thing it is written to
   avoid - a lit panel behind the links, ruled across the page top and
   bottom. It read as a UI chip, not as a trough in a plate.

   It is now a bled pseudo-element, and the geometry is the whole rule:
   the ellipse is 58% of each axis and the ramp finishes at 82% of that,
   so the ink runs out 0.476 of the box from the centre while every edge
   sits at 0.5. No edge can be drawn because there is no ink at any edge -
   on any of the four sides, at any viewport. The trough simply dissolves
   into the plate, which is what the paragraph above always claimed. */
.imp__run {
  /* the box is the plate, not the column: the channel has to reach both
     edges of the footer or it is a panel again. .imp clips it. */
  --ibleed: max(var(--gutter), calc((100vw - var(--container)) / 2));
  position: relative;
  /* holds the trough's negative layer inside the run, so it can only ever
     sink behind these links and never through the plate's own light */
  isolation: isolate;
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1rem, 0.6rem + 1.6vw, 2.5rem);
  padding-block: clamp(1.5rem, 1.2rem + 1.2vw, 2.25rem);
}
.imp__run::before {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
  left: calc(-1 * var(--ibleed));
  right: calc(-1 * var(--ibleed));
  /* the falloff is given room ABOVE and BELOW the row. Confined to the
     padding box it would have to reach zero within the row's own height,
     and a fade that steep is a band with soft edges - still a shape. */
  top: clamp(-2.5rem, -2rem - 0.6vw, -1.5rem);
  bottom: clamp(-2.5rem, -2rem - 0.6vw, -1.5rem);
  background: radial-gradient(58% 58% at 50% 50%, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0) 82%);
}
.imp__run a {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(190, 190, 190, 0.92);
  /* sunk into the plate, and it comes up when you touch it */
  text-shadow: 0 1px 0 rgba(226, 200, 150, 0.07);
  transition: color 0.35s ease, text-shadow 0.4s ease;
}
.imp__run a:hover {
  color: var(--gold-light);
  text-shadow: 0 0 18px rgba(192, 156, 96, 0.5);
}

/* ---- the base ---- */
.imp__base {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding-block: clamp(1.35rem, 1.1rem + 1vw, 2rem);
  font-size: var(--text-sm);
  color: var(--vein);
}
.imp__top-link {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(190, 190, 190, 0.92);
  text-shadow: 0 1px 0 rgba(226, 200, 150, 0.07);
  transition: color 0.35s ease, text-shadow 0.4s ease;
}
.imp__top-link svg { width: 0.9rem; height: 0.9rem; transition: transform 0.45s var(--ease-out); }
.imp__top-link:hover { color: var(--gold-light); text-shadow: 0 0 18px rgba(192, 156, 96, 0.5); }
.imp__top-link:hover svg { transform: translateY(-3px); }

/* ---- the inscription ----
   The name cut into the base of the plate and running off the bottom of
   the page. This is the giant-wordmark footer the client asked for, made
   Marble's rather than generic in three ways:

   1. IT IS SET IN THE SITE'S OWN DISPLAY FACE, not a grotesque. A didone
      at 290px is a different object from the heavy sans this device
      usually uses - the hairlines are the point.
   2. IT IS FILLED WITH THE SITE'S METALLIC CLIP-TEXT RAMP, the same
      device the engraved numerals use. Largest possible instance of a
      device the site already speaks, rather than a new one.
   3. IT IS LIT FROM BELOW, because the plate's pool of light is at its
      foot. The letters are brightest where they meet it and dissolve
      upward into the surface. Same rule as everywhere else on this site:
      one light source, honestly obeyed.

   Note it is FILLED. `.proofx__ghost` on index is outlined with
   -webkit-text-stroke and drifts behind the evidence panels; two giant
   words on one page had to read as different objects.

   Sized in container query units so the word spans the container measure
   exactly at every width, with a vw clamp behind it that errs small. */
.imp__inscription {
  container-type: inline-size;
  overflow: hidden;
  margin-top: clamp(0.25rem, 0.1rem + 0.8vw, 1rem);
}
.imp__name {
  display: block;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(2.85rem, 20vw, 18rem);
  line-height: 0.78;
  letter-spacing: -0.045em;
  text-align: center;
  white-space: nowrap;
  /* the base of the letters runs off the page rather than resting on it */
  margin-bottom: -0.135em;
  background-image:
    linear-gradient(104deg, transparent 36%, rgba(255, 248, 232, 0.5) 50%, transparent 64%),
    linear-gradient(180deg,
      rgba(226, 226, 224, calc(0.025 * var(--nlit, 1))) 0%,
      rgba(226, 226, 224, calc(0.075 * var(--nlit, 1))) 30%,
      rgba(216, 202, 178, calc(0.22 * var(--nlit, 1))) 56%,
      rgba(240, 231, 212, calc(0.46 * var(--nlit, 1))) 80%,
      rgba(255, 249, 236, calc(0.62 * var(--nlit, 1))) 100%);
  background-size: 300% 100%, 100% 100%;
  background-position: calc(var(--nx, 130) * 1%) 0, 0 0;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}
@supports (font-size: 1cqw) {
  /* tuned by measuring the ink width against the container measure: this
     lands the word at 0.995 of the measure at every width */
  .imp__name { font-size: 24cqw; }
}
/* if the text is not clippable the word must still be a word, not a hole */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .imp__name { background-image: none; color: rgba(232, 232, 230, 0.2); }
}

@media (min-width: 1081px) {
  .imp__run { justify-content: space-between; }
}

@media (max-width: 1080px) {
  .imp__top { grid-template-columns: 1fr 1fr; }
  .imp__id { grid-column: 1 / -1; }
  .imp__line { max-width: 34rem; }
}

@media (max-width: 720px) {
  .imp__top { grid-template-columns: 1fr; }
  .imp__field { max-width: none; }
  .imp__base { justify-content: flex-start; gap: 1rem; }
}

@media (prefers-reduced-motion: reduce) {
  .imp__field::after,
  .imp__field button,
  .imp__run a,
  .imp__top-link,
  .imp__top-link svg,
  .imp__reach a { transition: none; }
}

/* ==========================================================================
   CONTACT HERO — "THE ANSWER"   (.ans)   DELETED

   `.ans` set a claim column on the left and a 734px stage on the right
   carrying two overlapping photographs at two aspect ratios, with the reply
   and the phone number in a 324px pocket in the bottom right corner. It is
   replaced on contact.html by "the open line" (.opl, css/open-line.css).

   THE BEAT IS NOT DELETED, only the composition around it. `.opl` keeps the
   hold-then-respond clock verbatim, because it is still the only timed
   movement on a site where everything else waits for the reader to scroll.
   What went is the shape: type on the left and an object on the right, with
   an 82px channel between them at 1440 and the whole lower left corner
   empty, which is the arrangement this project rejects by name.

   No other page ever loaded it, so the block and initAnswer() in
   js/motion.js go together. `.ans__now` went with them and had already been
   dead for a while: the hours line it styled was pulled from the markup when
   the opening hours turned out to be unverified, and the rules for it were
   left behind.
   ========================================================================== */

/* ==========================================================================
   REACH US — "THE SHEET"   (.sht, contact.html section 02)

   HISTORY, BECAUSE IT IS THE WHOLE ARGUMENT FOR HOW PLAIN THIS IS. The slot
   first held a `.grid` col-5 / col-7 — a bulleted contact list beside a boxed
   form, the shape every contact page on the web has. That was replaced by an
   engraved "sheet": serials 01-05 cut into the ground, floating labels, bare
   underlines instead of fields, and a submit that lit as the message got
   longer. It was the better-looking object and the worse form. It read as a
   printed document rather than as something to fill in, and the client could
   not tell where to type — which is the one thing a contact form has to make
   obvious. It was replaced by `.cf` (css/sections-2.css): a visible label
   above every field, a real bordered box a shade lighter than the page, a
   focus ring. A form is the one component on a site where being boring IS the
   design, and that still stands.

   SO THIS SHEET STYLES THE SECTION AROUND THE FORM, NOT THE FORM. Everything
   the engraved version left behind — `.sht__form`, every `.fld*`, the lit
   `.sht__send` and its sheen, and their three media queries — is deleted
   here. No markup had matched any of it since `.cf` went in.

   WHAT WAS WRONG WITH WHAT WAS LEFT, MEASURED AT 1440. `.cf` caps itself at
   46rem and ranges left, so the form ran x=40 to x=776 inside a 1280 content
   box: FIVE HUNDRED AND FORTY FOUR PIXELS of dead page down its entire 541px
   right side. That is the exact failure this brief names. And the form began
   1px under the rule — `.cf` carries no top margin, and the `.sht__form` that
   used to supply 4rem of it had gone with the engraved version, so the first
   label was jammed against the hairline.

   THE FIX IS REGISTRATION, NOT DECORATION. The masthead and the rule keep the
   full measure of the page. The form is centred under them on its own
   measure, and two short ticks drop off the rule at that measure's edges, so
   the centring reads as a mount rather than as a margin:auto nobody thought
   about. ONLY THE BLOCK IS CENTRED: labels and inputs stay ranged left,
   because centred label text over a left-aligned input gives the eye no edge
   to run down while filling the form in. `.apply` on the role pages made the
   same call for the same reason.

   NO PHOTOGRAPH AND NO LIGHT IN THIS SECTION, DELIBERATELY, and the reason is
   stronger now than when it was first written. The hero directly above is a
   full-bleed room of three lit photographs that opens on a beat. Two adjacent
   sections sharing a medium is exactly the uniformity that reads as
   generated, so this one stays colder and purely structural — the same
   temperature drop `.jobx` uses against its neighbours. Every instinct to put
   a warm pool behind these fields should be spent on the hero instead.

   `--rdy` STAYS REGISTERED even though `.sht__send` is gone: `.run` on
   services.html reads it for the drift of the picture behind its openings,
   and js/motion.js tweens it there. @property is document-global, so the
   registration still works from this sheet — but it is no longer this
   section's property, and the next person deleting something here should
   know that before reaching for it.
   ========================================================================== */

@property --rdy {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

.sht {
  /* the form's own measure. The ticks on the rule are derived from it, so the
     two can never drift apart. */
  --fw: 52rem;
  overflow-x: clip;
}

/* ---- the masthead ------------------------------------------------------ */

.sht__head {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr);
  gap: clamp(1rem, 0.6rem + 1.6vw, 2.5rem);
  align-items: end;
}

.sht h2 {
  font-size: clamp(1.75rem, 1.35rem + 1.7vw, 2.6rem);
  line-height: 1.12;
  letter-spacing: -0.02em;
}

.sht__lead {
  font-size: var(--text-base);
  line-height: 1.6;
  color: var(--vein);
  max-width: 22rem;
  justify-self: end;
  text-align: right;
  text-wrap: pretty;
}

/* static: it is structure, not motion. Nothing on this site draws a rule
   except `.rules`, and that pen belongs to results.html. */
.sht__rule {
  position: relative;
  display: block;
  height: 1px;
  margin-top: clamp(1.75rem, 1.35rem + 1.8vw, 3rem);
  background: linear-gradient(90deg,
    rgba(156, 120, 72, 0.5) 0%,
    rgba(156, 120, 72, 0.24) 58%,
    rgba(156, 120, 72, 0.05) 100%);
}

/* THE TWO TICKS. They hang off the rule exactly where the form's measure
   begins and ends, which is the whole reason a centred block reads as mounted
   rather than adrift. max(0px, ...) pins them to the rule's own ends once the
   page is narrower than the measure, so they can never sit outside the line
   they belong to. */
.sht__rule::before,
.sht__rule::after {
  content: "";
  position: absolute;
  top: 0;
  width: 1px;
  height: 0.85rem;
  background: linear-gradient(180deg, rgba(156, 120, 72, 0.5), rgba(156, 120, 72, 0));
}
.sht__rule::before { left: max(0px, calc(50% - var(--fw) / 2)); }
.sht__rule::after { right: max(0px, calc(50% - var(--fw) / 2)); }

/* ---- the form, mounted ------------------------------------------------- */

.sht .cf {
  width: min(100%, var(--fw));
  max-width: none;
  margin-inline: auto;
  margin-top: clamp(2.25rem, 1.8rem + 1.9vw, 3.5rem);
}

/* The field that matters gets the room. It is the only one anybody actually
   has to think about, and at 9.5rem it carried the same visual weight as a
   name. */
.sht textarea.cf__in { min-height: 11.5rem; }

/* ---- responsive -------------------------------------------------------- */

@media (max-width: 860px) {
  .sht__head { grid-template-columns: minmax(0, 1fr); }
  .sht__lead {
    justify-self: start;
    text-align: left;
    max-width: 30rem;
    margin-top: clamp(0.75rem, 0.6rem + 0.6vw, 1.1rem);
  }
}

/* ==========================================================================
   THE OFFICE — "WHERE THE MAP ENDS"   (.ofc, contact.html section 03)

   It replaced a `.media--pano` frame: a 21:8 grey letterbox sitting under a
   `.section-head`, which is the shape every contact page on the web has and
   the last piece of template markup left on this page.

   THE MAP IS THE GROUND, NOT A PICTURE IN A BOX, and that alone is what stops
   the section reading as generated. It runs the full width of the page, the
   head sits above it on clean obsidian, and the composition is laid over it.

   THE MECHANIC IS LIGHT GATHERING TO A POINT, and it was still free. Every
   other section on this site moves in some way already claimed - `.wall`
   travels X, `.worlds` pushes Z, `.rise` climbs Y, `.lp` holds the camera
   still, `.forge` turns, `.hire` strikes type through, `.cuts` register,
   `.latent` develops, `.lane` slides hairlines in, `.floor` spreads, `.clk`
   runs on its own clock, `.board` reorders, `.barx` rakes, `.jobx` cuts,
   `.scp` widens, `.trn` backlights, `.hld` waits, `.opn` closes a brace,
   `.ans` holds a beat, `.sht` is finished by the reader. Nothing CONTRACTS.
   Here the warmth arrives spread thin across the whole territory and pulls in
   until only one point is lit, which is the section's own sentence: out of
   all of it, this is the address.

   THE ADDRESS SITS BESIDE THE POINT, SO THE POINT IS ITS MARKER. That is the
   test this client's rejected devices kept failing (the queue of 18 marks,
   the aperture, the machined plate): the meaning has to survive without a
   caption explaining it. A lit point with an address set next to it needs no
   explaining at all. It is also why there is no tether drawn between them -
   a hairline reaching a terminal is `.stop`'s gesture on what-we-do, and
   adjacency already says it.

   THE PHOTOGRAPH IS THE ARGUMENT, NOT DECORATION. A map gives you the street
   and stops. What it cannot show you is who answers, which is the one thing
   this firm sells, so a hand at a door is laid across the band and the line
   under it names the boundary. It also occludes the band's top edge, which is
   the `.lane` trick: a plate that crosses an edge reads as lying ON the
   surface rather than as a panel beside it.

   THE MAP IS INERT UNTIL ASKED FOR. An embedded map swallows the wheel, so
   this one carries `pointer-events: none` and a real toggle. Live, the grade
   opens up toward a usable map and the decorative layers stand down - the art
   never has to fight the utility, because they are never on at once.

   TWO NUMBERS, NEVER ONE, the pattern this project has paid for repeatedly.
   `--gth` (0 spread, 1 gathered) is written by GSAP every frame and NOTHING
   computed from it carries a transition. `--lit` (0 art, 1 usable) is a class
   toggle CSS transitions on its own. Both are UNREGISTERED: they are written
   per frame or per click so nothing needs interpolating, and a registered
   inherited property costs a style recalc on all nine pages that load this
   stylesheet. `--gth`'s declared value is 1, THE FINISHED STATE, so no-JS /
   no-CDN / reduced motion all render a gathered, lit point.

   GOOGLE'S OWN MARKER IS KEPT AND RINGED rather than covered or cropped
   around. The embed is queried BY ADDRESS, so its pin is geocoded rather than
   asserted from coordinates we would be inventing, and on a page whose whole
   promise is that everything is checkable that is the only defensible way to
   put a pin on a map. `grayscale(1)` first in the filter chain kills every
   colour Google ships - the red pin, blue water, green parks - so the plate
   can be re-tinted to this brand's two colours instead of fighting them.
   Nothing opaque is placed over the bottom of the map, where the embed's own
   attribution sits.
   ========================================================================== */

.ofc {
  /* 0 the territory is evenly, dimly lit; 1 the light has gathered onto the
     one point. GSAP owns it, and 1 IS the finished state. */
  --gth: 1;
  /* 0 the composition; 1 the map the reader asked to use. A SECOND number, so
     a scrubbed value and a toggled one can never fight over one property. */
  --lit: 0;
  overflow-x: clip;
}

/* the measure belongs to the HEADING, never to the container it sits in -
   `.container` carries `margin-inline: auto`, so capping the container's own
   width centres the whole head on the page */
.ofc__head h2 { max-width: 46rem; }

/* ---- the band ---------------------------------------------------------- */

.ofc__band {
  position: relative;
  /* clears the head by more than the door plate rises, so the plate can cross
     the band's top edge without ever reaching the headline above it */
  margin-top: clamp(1.98rem, 1.44rem + 2.448vw, 3.78rem);
  /* the pool blends with `screen`; isolate so it lifts the MAP and not the
     page ground behind the section */
  isolation: isolate;
}

.ofc__map {
  position: relative;
  /* sized to the composition, not to a picture ratio: the door and the
     address both finish around the same height, and a band deeper than that
     leaves a strip of unlit map under everything */
  height: clamp(21rem, 11.5rem + 21vw, 31rem);
  overflow: hidden;
  border-block: 1px solid rgba(156, 120, 72, 0.16);
  /* THE GROUND UNDER THE MAP IS THE SITE'S OWN MARBLE. A WebGL map on a
     third-party basemap is the one thing in this section that can fail
     outright, and a band that fails to black is the empty-screen failure this
     project has produced six times. Under the canvas is the same crafted
     panel the clean pass paints everywhere else, so the worst case is a
     marble slab carrying the composition rather than a hole. */
  background-color: var(--obsidian-raised);
  background-image:
    radial-gradient(140% 120% at 50% 46%, rgba(156, 120, 72, 0.14), transparent 56%),
    linear-gradient(155deg, #15151b 0%, var(--obsidian-raised) 58%, #0d0c11 100%);
}

.ofc__gl {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1s var(--ease-out);
}

/* only once the basemap has actually painted */
.ofc.is-mapped .ofc__gl { opacity: 1; }

/* the reader has asked for the map, so it is handed over whole: open, live,
   labelled, and with the composition standing down in front of it */
.ofc.is-live { --lit: 1; }

/* MapLibre's own furniture, brought onto the brand. The attribution is
   REQUIRED and is never covered by anything opaque - it only ever sits under
   the same grade the rest of the map does. */
.ofc .maplibregl-ctrl-attrib {
  background: rgba(6, 6, 8, 0.62);
  color: rgba(190, 186, 178, 0.86);
  font-family: var(--font-sans);
  font-size: 10px;
  border-radius: 3px 0 0 0;
}

.ofc .maplibregl-ctrl-attrib a { color: rgba(214, 182, 128, 0.92); }

.ofc .maplibregl-ctrl-attrib-button {
  background-color: rgba(192, 156, 96, 0.16);
  border-radius: 50%;
}

.ofc .maplibregl-ctrl-bottom-right { z-index: 6; }

/* ---- the mark (<MapMarker> + <MarkerContent> + <MarkerLabel>) ----------
   It rides the map's own coordinate, so it is over the building at every
   zoom - which is the real gain over the ring the embed version had to hold
   at the centre of the frame and hope. */

.ofcm {
  position: relative;
  width: 0;
  height: 0;
}

/* the rings CONTRACT with the light rather than fading up, so the mark and
   the pool around it are visibly the same event. Nothing here carries a
   transition: every value is computed from the variable GSAP writes each
   frame, and a transition on top of that would only fight it. */
.ofcm__ring {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc((2.9 + (1 - var(--gth, 1)) * 4.2) * 1rem);
  aspect-ratio: 1;
  translate: -50% -50%;
  border-radius: 50%;
  border: 1px solid rgba(192, 156, 96, calc(0.14 + var(--gth, 1) * 0.52));
  box-shadow:
    0 0 calc((0.4 + var(--gth, 1) * 1.5) * 1rem) rgba(192, 156, 96, calc(var(--gth, 1) * 0.22)),
    inset 0 0 calc((0.5 + var(--gth, 1) * 1.2) * 1rem) rgba(192, 156, 96, calc(var(--gth, 1) * 0.14));
}

/* a second ring still on its way in. Two radii arriving read as one light
   closing; a single ring reads as a UI target. */
.ofcm__ring--out {
  width: calc((4.6 + (1 - var(--gth, 1)) * 8.5) * 1rem);
  border-color: rgba(192, 156, 96, calc(0.05 + var(--gth, 1) * 0.2));
  box-shadow: none;
}

.ofcm__dot {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 7px;
  height: 7px;
  translate: -50% -50%;
  border-radius: 50%;
  background: var(--gold-light);
  box-shadow: 0 0 10px rgba(214, 176, 112, 0.9);
}

/* SET TO THE LEFT OF THE MARK, NOT CENTRED OVER IT. Centred, the name runs
   symmetrically out of the point and its right half lands under the address
   column, which is the one half of this band that is already occupied. Left
   is the open side, and it clears the outer ring at every value of `--gth`
   because the offset is computed from the same number the ring is. */
.ofcm__label {
  position: absolute;
  right: calc(50% + 1.9rem + (1 - var(--gth, 1)) * 2.4rem);
  top: 50%;
  translate: 0 -50%;
  white-space: nowrap;
  font-family: var(--font-sans);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(240, 236, 228, 0.92);
  text-shadow: 0 1px 10px rgba(6, 6, 8, 0.98), 0 0 3px rgba(6, 6, 8, 0.9);
  opacity: var(--gth, 1);
  pointer-events: none;
}

/* ---- the zoom controls (<MapControls>) --------------------------------- */

.ofc__ctrl {
  position: absolute;
  right: clamp(0.9rem, 0.6rem + 1.2vw, 1.6rem);
  bottom: clamp(2.4rem, 2rem + 1.2vw, 3.1rem);
  z-index: 7;
  display: none;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--border-gold);
  border-radius: 3px;
  background: rgba(8, 8, 12, 0.78);
}

.ofc.is-live .ofc__ctrl { display: flex; }

.ofc__cbtn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border: 0;
  background: none;
  color: var(--gold-light);
  cursor: pointer;
  transition: color var(--dur-fast) ease, background var(--dur-fast) ease;
}

.ofc__cbtn + .ofc__cbtn { border-top: 1px solid var(--border-gold); }
.ofc__cbtn:hover,
.ofc__cbtn:focus-visible { color: var(--white); background: rgba(156, 120, 72, 0.16); }
.ofc__cbtn svg { width: 14px; height: 14px; }

/* ---- the light ---------------------------------------------------------

   THE STACK IS THE WHOLE TRICK AND IT IS EASY TO GET BACKWARDS: the grade
   (`__edge`) sits UNDER the light (`__pool`), because a vignette painted over
   a pool simply erases it - the gathered and spread states rendered
   identically until this order was fixed. The shade then sits over BOTH, so
   the light never reaches the type it would otherwise wash out. Grade, then
   light, then protect the copy. */

/* Layer one is the pool that TIGHTENS and brightens as `--gth` rises; layer
   two is the diffuse warmth over the whole territory that gives way as it
   does. The two moving against each other is the gather - one alone would
   only be a light coming up. */
.ofc__pool {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  mix-blend-mode: screen;
  background:
    radial-gradient(circle at 50% 50%,
      rgba(238, 204, 146, calc(0.12 + var(--gth) * 0.42)) 0%,
      rgba(176, 134, 76, calc(0.06 + var(--gth) * 0.2)) calc((6 + (1 - var(--gth)) * 16) * 1%),
      transparent calc((21 + (1 - var(--gth)) * 44) * 1%)),
    radial-gradient(circle at 50% 50%,
      rgba(140, 106, 60, calc((1 - var(--gth)) * 0.22)) 0%,
      transparent 84%);
  opacity: calc(1 - var(--lit) * 0.82);
  transition: opacity 0.8s var(--ease-out);
}

/* the grade, kept OFF the bottom edge on purpose - the embed's attribution
   sits there and nothing opaque may cross it */
.ofc__edge {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  /* the fall-off has to be STEEP. Blurred map tiles are tonally flat, so a
     gentle vignette leaves an even haze across the band and the point stops
     being the brightest thing in it, which is the whole composition.

     The third gradient buries the embed's own controls, which sit hard
     against its left and right edges. It is a GRADE, not a cover: it stands
     down with everything else the moment the map goes live, so the reader
     using the map gets Google's chrome and its attribution intact. */
  background:
    radial-gradient(112% 74% at 50% 46%,
      transparent 16%, rgba(6, 6, 8, 0.52) 50%, rgba(6, 6, 8, 0.86) 100%),
    linear-gradient(180deg, rgba(6, 6, 8, 0.78), transparent 28%, transparent 84%, rgba(6, 6, 8, 0.42)),
    linear-gradient(90deg, rgba(6, 6, 8, 0.78), transparent 13%, transparent 87%, rgba(6, 6, 8, 0.62));
  opacity: calc(1 - var(--lit) * 0.93);
  transition: opacity 0.8s var(--ease-out);
}

/* the side the address stands in. Masked top and bottom so the tint DISSOLVES
   instead of ending on an edge - the `.wall` lesson, where a scrim that stops
   dead leaves a seam across the screen. */
.ofc__shade {
  position: absolute;
  inset: 0 0 0 auto;
  width: min(60%, 56rem);
  z-index: 4;
  pointer-events: none;
  background: linear-gradient(270deg,
    rgba(6, 6, 8, 0.92) 0%,
    rgba(6, 6, 8, 0.85) 40%,
    rgba(6, 6, 8, 0.46) 74%,
    transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 16%, #000 84%, transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 16%, #000 84%, transparent 100%);
}

/* ---- the toggle -------------------------------------------------------- */

.ofc__use {
  position: absolute;
  top: clamp(0.9rem, 0.6rem + 1.2vw, 1.6rem);
  right: clamp(0.9rem, 0.6rem + 1.2vw, 1.6rem);
  z-index: 7;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.5rem 0.95rem;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-light);
  background: rgba(8, 8, 12, 0.66);
  border: 1px solid var(--border-gold);
  border-radius: 999px;
  cursor: pointer;
  transition: color var(--dur-fast) ease, border-color var(--dur-fast) ease,
              background var(--dur-fast) ease;
}

.ofc__use:hover,
.ofc__use:focus-visible {
  color: var(--white);
  border-color: var(--border-gold-strong);
  background: rgba(12, 12, 16, 0.86);
}

.ofc__usedot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gold-shadow);
  box-shadow: 0 0 0 0 rgba(192, 156, 96, 0);
  transition: background var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
}

.ofc.is-live .ofc__usedot {
  background: var(--gold-light);
  box-shadow: 0 0 0 3px rgba(192, 156, 96, 0.18);
}

/* ---- the composition laid over the band -------------------------------- */

.ofc__over {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: grid;
  /* two halves, so the divide falls on the container's centre line - which is
     the viewport's centre line, which is where the embed puts the pin. The
     address therefore starts a fixed distance right of the point at EVERY
     width, with no per-breakpoint tuning. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: clamp(1.5rem, 0.8rem + 2.6vw, 3.75rem);
  align-items: center;
  /* the map underneath stays reachable once it is live */
  pointer-events: none;
  transition: opacity 0.65s var(--ease-out);
}

.ofc__over > * { pointer-events: auto; }

/* live, the composition stands down to the address alone: the reader asked
   for a map, and everything else is in front of it */
.ofc.is-live .ofc__door { opacity: 0; pointer-events: none; }
.ofc__door { transition: opacity 0.55s var(--ease-out); }

/* ---- the door ---------------------------------------------------------- */

.ofc__door {
  grid-column: 1;
  justify-self: start;
  align-self: start;
  /* rises across the band's top edge. The band's own margin-top is larger, so
     it can never reach the headline. */
  margin: clamp(-3.25rem, -2rem - 1.6vw, -2rem) 0 0 clamp(0rem, -1.5rem + 3vw, 2.5rem);
  width: min(100%, 19.5rem);
}

.ofc__plate {
  display: block;
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--obsidian);
  border: 1px solid rgba(156, 120, 72, 0.22);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.03) inset,
    0 36px 74px -34px rgba(0, 0, 0, 0.96);
  /* the independent property, so GSAP owns `transform` on the shot inside
     uncontested - the lesson this project has paid for five times */
  rotate: -1.15deg;
}

.ofc__shot {
  position: absolute;
  inset: -5% 0;
  display: block;
}

.ofc__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* the knocker and the hand, not the dark left third of the frame */
  object-position: 62% 46%;
  filter: brightness(1.02) contrast(1.04) saturate(0.96);
}

.ofc__cap {
  margin-top: 1rem;
  margin-left: 0.15rem;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-size: var(--text-xl);
  line-height: 1.25;
  color: var(--gold-light);
  text-shadow: 0 2px 14px rgba(6, 6, 8, 0.95);
}

/* ---- the address ------------------------------------------------------- */

.ofc__addr {
  grid-column: 2;
  justify-self: start;
  align-self: center;
  max-width: 25rem;
  padding-left: clamp(0.5rem, 0.1rem + 1.6vw, 2.25rem);
}

.ofc__num {
  display: block;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(2.9rem, 1.9rem + 4.2vw, 5.6rem);
  line-height: 0.84;
  letter-spacing: -0.035em;
  /* near-vertical ramp on purpose: run it across a wide figure at a shallow
     angle and the outer glyphs fall into its darkest stop */
  background: linear-gradient(174deg,
    rgba(248, 244, 236, 0.96),
    rgba(202, 165, 107, 0.9) 46%,
    rgba(128, 96, 56, 0.62));
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  /* one shadow on the whole figure guarantees it separates from any tone in
     the map it crosses, without retuning a single colour */
  filter: drop-shadow(0 3px 12px rgba(0, 0, 0, 0.92));
}

.ofc__street,
.ofc__city {
  font-size: var(--text-lg);
  line-height: 1.45;
  color: var(--ink);
  text-shadow: 0 2px 12px rgba(6, 6, 8, 0.9);
}

.ofc__street { margin-top: clamp(1rem, 0.8rem + 0.8vw, 1.5rem); }

.ofc__city { color: var(--vein); }

.ofc__rule {
  display: block;
  height: 1px;
  width: 3.5rem;
  margin: clamp(1.1rem, 0.9rem + 0.8vw, 1.6rem) 0;
  background: linear-gradient(90deg, rgba(156, 120, 72, 0.62), rgba(156, 120, 72, 0.08));
}

.ofc__note {
  font-size: var(--text-base);
  line-height: 1.6;
  color: var(--vein);
  max-width: 21rem;
  text-shadow: 0 2px 12px rgba(6, 6, 8, 0.9);
}

.ofc__dir {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: clamp(1.1rem, 0.9rem + 0.8vw, 1.6rem);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold-light);
}

.ofc__dir svg {
  width: 15px;
  height: 15px;
  transition: transform var(--dur-fast) var(--ease-out);
}

.ofc__dir:hover { color: var(--white); }
.ofc__dir:hover svg { transform: translateX(4px); }

/* ---- narrow ------------------------------------------------------------
   The composition is DROPPED rather than retuned below 940px. Type laid over
   a map needs horizontal room to stay off the roads, and a 19rem photograph
   on a 390px band is a sticker rather than a plate. The band keeps the map
   and its gathered point; the door and the address fall into normal flow
   underneath, in source order, so the shown and spoken sequences match. */

@media (max-width: 940px) {
  .ofc__map { height: clamp(16rem, 10rem + 24vw, 22rem); }

  .ofc__over {
    position: static;
    display: block;
    margin-top: clamp(1.75rem, 1.4rem + 1.6vw, 2.75rem);
  }

  .ofc__shade { display: none; }

  .ofc__door {
    margin: 0;
    width: min(100%, 17rem);
  }

  .ofc__cap { font-size: var(--text-lg); }

  .ofc__addr {
    max-width: none;
    padding-left: 0;
    margin-top: clamp(2rem, 1.6rem + 1.8vw, 3rem);
  }

  .ofc__note { max-width: 26rem; }

  /* nothing is laid over anything at this width, so no copy needs lifting
     off a photograph */
  .ofc__street,
  .ofc__city,
  .ofc__note,
  .ofc__cap { text-shadow: none; }
}

@media (max-width: 560px) {
  .ofc__door { width: 100%; }
  .ofc__plate { aspect-ratio: 3 / 2; rotate: -0.7deg; }
}

/* the mark's name is set beside it, so on a narrow band it runs off the left
   edge before it can be read. The address is directly underneath at this
   width, so the label is dropped rather than shrunk to nothing. */
@media (max-width: 640px) {
  .ofcm__label { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .ofc__gl,
  .ofc__pool,
  .ofc__edge,
  .ofc__over,
  .ofc__door,
  .ofc__dir svg { transition: none; }
}

/* ==========================================================================
   Home hero — the cinematic skyline.

   A canvas canyon on a tall sticky track. The camera flies down it as the
   page scrolls and four type beats hand off above it: the lead statement,
   then three numbers. Everything below is layout and type; the canyon itself
   is drawn in js/skyhero.js.
   ========================================================================== */

.skyhero { position: relative; }

.skyhero__track {
  position: relative;
  /* Was 600vh: six screens of scroll for three stat beats, and on its own
     38% of the whole homepage. The track length is only the DISTANCE the
     choreography is spread over - every beat, the typing and the camera are
     driven by progress through it, so shortening it costs no content and no
     motion, it just stops asking for six screens of thumb to read three
     numbers. */
  height: 340vh;
  height: 340svh;
}

.skyhero__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  background: var(--obsidian);
}

.skyhero__canvas {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* The four beats share one stage; only ever one of them is legible. */
.skyhero__beat {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 0;
  padding: clamp(6.5rem, 13vh, 9rem) var(--gutter) clamp(6rem, 13vh, 8.5rem);
  overflow: hidden;
  text-align: center;
  pointer-events: none;
  will-change: transform, opacity;
}

.skyhero__beat a,
.skyhero__beat button { pointer-events: auto; }

.skyhero__beat--stat { opacity: 0; }

/* ---- the lead statement ---- */

.skyhero__beat--lead .container { width: 100%; }

.skyhero__beat--lead .hero__content {
  /* A hook has to be readable in one glance, which means a narrow measure and
     a short line, not the widest type the viewport allows. At 68rem the two
     statements ran ~1200px each and read as a paragraph set large. */
  max-width: 58rem;
  margin-inline: auto;
}

/* THE DATUM ROW. The canyon's own instrument is the HUD bottom-left: a
   hairline, then a tracked value that swaps live. All four beats are set on
   that same instrument so they read as four frames of one read-out rather
   than a hero followed by three unrelated slides.

   Here the three facts are three READINGS separated by hairline ticks. Set as
   one run-on of middots it measured 730px on one line, and broke to three
   ragged lines of centred micro-caps on a phone. */
.skyhero__beat--lead .eyebrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem clamp(0.7rem, 1.8vw, 1.15rem);
  letter-spacing: 0.26em;
  color: var(--gold-light);
  margin-bottom: clamp(1.35rem, 2.8vh, 2rem);
  text-shadow: 0 1px 18px rgba(6, 6, 8, 0.9);
}
/* Tracking adds its space AFTER the last glyph, which walks centred caps off
   true centre by exactly one track. Give it back. */
.skyhero__beat--lead .eyebrow > span { text-indent: 0.26em; }
.skyhero__beat--lead .eyebrow i {
  width: 1px;
  height: 0.72em;
  background: currentColor;
  opacity: 0.4;
}

.skyhero__beat--lead h1 {
  /* Sized to keep each statement on ONE line inside the 58rem measure. The
     old ceiling (4.75rem) wrapped both of them, which turned a two-line hook
     into a four-line paragraph and buried the offer below the fold. */
  font-size: clamp(1.9rem, 4.4vw, 3.6rem);
  line-height: 1.12;
  letter-spacing: -0.028em;
  font-weight: 600;
  margin-bottom: clamp(1.15rem, 2.4vh, 1.7rem);
  text-shadow: 0 2px 44px rgba(6, 6, 8, 0.85);
}

/* The lead is a STATEMENT — two lines of equal rank, the second carrying the
   whole offer. It deliberately does NOT take the figure's value-and-unit
   relationship below: shrinking "You pay for what we close." would demote
   the one clause the page is selling. */

.skyhero__beat--lead .hero__sub {
  max-width: 36rem;
  margin-inline: auto;
  margin-bottom: clamp(1.9rem, 4vh, 2.85rem);
  font-size: clamp(1rem, 0.95rem + 0.3vw, 1.1875rem);
  line-height: 1.6;
  color: rgba(232, 232, 230, 0.72);
  text-shadow: 0 1px 26px rgba(6, 6, 8, 0.85);
  text-wrap: pretty;
}
.skyhero__beat--lead .hero__sub strong {
  color: rgba(232, 232, 230, 0.95);
  font-weight: 500;
}

.skyhero__beat--lead .hero__actions { justify-content: center; }

/* ---- the three numbers ---- */

/* the same datum the lead is set on, closed at both ends because a reading
   has a start and a finish */
.skyhero__eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(0.7rem, 1.6vw, 1.1rem);
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--gold-light);
  text-shadow: 0 1px 18px rgba(6, 6, 8, 0.9);
}
.skyhero__eyebrow > span { text-indent: 0.3em; }
.skyhero__eyebrow i {
  display: block;
  width: clamp(1.25rem, 3.4vw, 2.25rem);
  height: 1px;
  background: currentColor;
  opacity: 0.5;
}

.skyhero__figure {
  position: relative;
  margin: clamp(1.4rem, 3.2vh, 2.3rem) 0 0;
  font-size: clamp(2.1rem, 5.4vw, 4.75rem);
  line-height: 1.12;
  letter-spacing: -0.022em;
  font-weight: 500;
  text-shadow: 0 2px 44px rgba(6, 6, 8, 0.85);
  /* the figures are the substance of these three beats: lining, tabular, so
     38,000 and $3.5M set on the same digit width and neither jitters as the
     typewriter lays it down character by character */
  font-variant-numeric: lining-nums tabular-nums;
}

/* VALUE, THEN UNIT. Both lines used to set at 76px/500 - identical - so the
   reading and the word that qualifies it carried the same rank and the whole
   figure read as one flat block. The gold line is now stamped under the
   reading: smaller, tracked open, still gold. It reads as the conclusion of a
   measurement rather than a second headline.

   Sized in em so it scales with the clamp, and so the caret - which is parked
   off measured character rects - lands on the right glyph at any width. */
.skyhero__figure > .u-gold {
  font-size: 0.82em;
  letter-spacing: 0.012em;
  margin-top: 0.1em;
}

/* THE EXCEPTION, AND IT IS A REAL ONE. Beats one and two are readings —
   "38,000 customers / acquired." is a value and the word that qualifies it,
   so stamping the second line smaller is right. Beat three is not a reading;
   it is a rhetorical pair, and "We reach people." is the payoff the whole
   hero is driving at. Demoting it to 0.82em would sell the punchline short,
   so that beat keeps both lines at parity and separates them on colour. */
.skyhero__beat--pair .skyhero__figure > .u-gold {
  font-size: 1em;
  letter-spacing: inherit;
  margin-top: 0;
}

/* Direct children only — the script wraps every character of a line in its
   own span for the typewriter, and those have to stay inline. */
.skyhero__figure > span { display: block; }
.skyhero__figure > span > span { display: inline; }

/* the typewriter caret, parked by measured character rects. The blink is
   switched on and off through animation-name from the script. */
.skyhero__caret {
  position: absolute;
  width: 2px;
  background: var(--gold-light);
  opacity: 0;
  pointer-events: none;
}

@keyframes skyhero-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

.skyhero__line {
  margin: clamp(1.5rem, 3.4vh, 2.3rem) auto 0;
  max-width: 40rem;
  font-size: clamp(1rem, 0.95rem + 0.3vw, 1.1875rem);
  line-height: 1.6;
  color: rgba(232, 232, 230, 0.72);
  text-shadow: 0 1px 26px rgba(6, 6, 8, 0.85);
  /* two or three centred lines under a big figure: balance beats ragged, and
     it is what stops "Signed, activated accounts." dropping a lone word */
  text-wrap: balance;
}

/* WHEN THE DATUM ROW STOPS FITTING ON ONE LINE, IT STACKS.

   The three readings plus their ticks measure 736px, and the lead sits inside
   two nested gutters, so the row only holds one line down to about 890px of
   viewport. Below that a wrapping flex row breaks in the worst possible
   place: at 820px it put "B2B and direct to consumer" on a second line and
   left the second TICK dangling off the end of the first - a separator
   separating nothing. Letting the row reflow as inline text fails the same
   way, just less predictably.

   The cut is at 960 rather than the true 890 on purpose. At 910 the row
   cleared by only 18px, and this measures a live font: a different fallback,
   a rounded-up rendering or a slightly wider Inter re-strands the tick. 960
   buys about 70px of slack, so the stack takes over well before the row is
   in danger.

   Below it the readings STACK: a deliberate centred column, tight leading.
   Two short lines, no orphans, at any width. */
@media (max-width: 960px) {
  .skyhero__beat--lead .eyebrow {
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 0.34rem;
    line-height: 1.35;
    letter-spacing: 0.18em;
  }
  .skyhero__beat--lead .eyebrow > span { text-indent: 0.18em; }
  /* THE TICK TURNS WITH THE ROW. It used to switch off here, which left two
     centred lines of gold micro-caps reading as one line that happened to
     wrap. Laid flat it is the same hairline the stat beats and the HUD are
     set on, so the stack stays a read-out rather than becoming a caption. */
  .skyhero__beat--lead .eyebrow i {
    width: 1.5rem;
    height: 1px;
    opacity: 0.45;
  }
}

/* The two blocks that used to live here - max-width:700 and max-width:700 +
   max-height:740 - are folded into the single phone system further down. They
   sized the same four properties from two different axes and were in turn
   overruled by the vh-based max-height:640 rule, so no phone got the size
   any of the three intended. See "THE PHONE" below. */

/* ---- the market read-out, bottom left ---- */

.skyhero__hud {
  position: absolute;
  left: var(--gutter);
  bottom: clamp(1.5rem, 3.4vh, 2.75rem);
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(192, 156, 96, 0);
  transition: color 420ms ease;
  pointer-events: none;
}

.skyhero__hud.is-on { color: rgba(192, 156, 96, 0.95); }
.skyhero__hud i { display: block; width: 22px; height: 1px; background: currentColor; }

/* ---- scroll cue ---- */

.skyhero__cue-wrap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  display: flex;
  justify-content: center;
}

.skyhero__cue {
  position: static;
  flex-direction: column;
  align-items: center;
  gap: 0;
}

.skyhero__cue .scroll-cue__rail {
  height: clamp(3rem, 7.5vh, 5.4rem);
  background: linear-gradient(180deg, rgba(192, 156, 96, 0), rgba(192, 156, 96, 0.85));
}

.skyhero__cue-pill {
  display: block;
  width: 26px;
  height: 8px;
  border-radius: 99px;
  background: var(--gold-light);
  margin: 0.75rem 0 1.4rem;
  animation: skyhero-pill 2600ms ease-in-out infinite;
}

@keyframes skyhero-pill {
  0%, 100% { transform: translateY(0); opacity: 0.5; }
  50% { transform: translateY(9px); opacity: 1; }
}

/* SHORT AND WIDE: the canyon needs the height more than the type does. A
   laptop at 1280x620, a half-height desktop window, a phone turned sideways.

   min-width is doing real work. Written open, this rule reached every SHORT
   viewport - including every short PHONE - and because it sizes type in vh
   and sits later in the file than the width-based phone rules, it won there.
   A 360x640 got a 39.7px headline in a 280px measure (five ragged lines) and
   a 320x568 got 35.2px pushed up under the fixed nav. Height is the right
   axis for a wide short window and the wrong one for a narrow tall phone;
   below 761 the phone system owns the size. */
@media (max-height: 640px) and (min-width: 761px) {
  .skyhero__cue-wrap { display: none; }

  /* The nav is fixed, 74–86px tall and transparent, and this rule was paying
     it 51px — so on a phone held sideways (844x390) the datum row and the
     first line of the headline ran underneath the bar. Clearing it properly
     costs height that a 390px window has none of, so the type comes down one
     step to pay for the clearance: measured, the statement lands at 201px of
     content in 266px of room instead of 300px in 266px. */
  .skyhero__beat {
    padding-top: max(clamp(3.19rem, 6.38vh, 4.06rem), calc(var(--nav-h) + 0.5rem));
    padding-bottom: 2.5rem;
  }

  .skyhero__beat--lead h1,
  .skyhero__figure { font-size: clamp(1.5rem, 5.6vh, 2.9rem); }

  .skyhero__beat--lead .eyebrow { margin-bottom: 0.7rem; }
  .skyhero__beat--lead h1 { margin-bottom: 0.7rem; }
  .skyhero__beat--lead .hero__sub {
    font-size: 0.9375rem;
    line-height: 1.5;
    margin-bottom: 1rem;
  }
  .skyhero__beat--lead .hero__actions .btn { padding-block: 0.7rem; }

  .skyhero__figure { margin-top: 0.9rem; }
  .skyhero__line { margin-top: 0.9rem; font-size: 0.9375rem; line-height: 1.5; }
}

@media (max-width: 900px) {
  .skyhero__hud { display: none; }
}

/* ==========================================================================
   THE PHONE — one system, not four.

   The hero was sized by three overlapping queries (max-width:700,
   max-width:700 + max-height:740, max-height:640) that set the same four
   properties off two different axes, and the vh-based one sat last so it won
   on anything short. Measured, the headline came out at 39.7px on a 360x640,
   24.8px on a 375x667, and 30.4px — the clamp FLOOR, i.e. not scaling at all
   — on every phone from 375 to 430. Four devices, four unrelated heroes.

   Width is the axis that decides a phone headline, because width is what
   makes it wrap. Everything below is sized off width alone; the height is
   spent on composition instead of on the type scale. One long scroll is also
   a lot of thumb, so the track stays shorter than the desktop's.
   ========================================================================== */
@media (max-width: 760px) {

  /* ---- the frame ----------------------------------------------------------
     svh is the viewport with the browser chrome SHOWING. A sticky stage cut
     to it leaves a 60–115px strip of bare page under the canvas the moment
     the URL bar retracts — which, on a track this tall, is nearly the whole
     hero. The stage takes the LARGE viewport so the canyon is full bleed at
     both chrome states, and everything that has to stay clear of the chrome
     is pushed up by the difference between the two. Each of those carries a
     plain fallback first, so a browser without lvh keeps a working hero
     rather than dropping the declaration and getting none. */
  .skyhero__track { height: 300vh; height: 300svh; height: calc(200svh + 100lvh); }
  .skyhero__stage { height: 100lvh; }
  .skyhero__canvas { height: 100lvh; }

  .skyhero__beat {
    padding-inline: var(--gutter);
    padding-block: calc(var(--nav-h) + 0.75rem) clamp(4.5rem, 11svh, 6.5rem);
  }

  /* ---- the lead: centred on the space it actually has ---------------------
     Centred in the RAW frame the statement sits low, because the frame is not
     what the eye centres against: a fixed 74px nav caps the top and a 70px
     cue caps the bottom, and only the band between them reads as the picture.
     Centring inside the padding box is therefore only true if each padding
     matches the furniture it is clearing. Top pays for the nav, bottom pays
     for the cue, both plus the same slack — so the midpoint of the box lands
     exactly on the midpoint between the nav's bottom edge and the cue's top
     edge, and the block sits optically centred rather than nominally so. */
  .skyhero__beat--lead {
    justify-content: center;
    padding-bottom: calc(clamp(2rem, 5svh, 3rem) + 2.6rem);
    padding-bottom: calc(clamp(2rem, 5svh, 3rem) + 2.6rem + (100lvh - 100svh));
  }

  /* THE TITLE-SAFE SCRIM. Anchoring the statement low puts it over the part
     of the canyon that is closest to camera and therefore most densely lit —
     four blocks of type against a field of windows. The beats have always
     answered that with a 44px text-shadow on every line, which is a halo
     around glyphs rather than a ground under them.

     A gradient does the job the way a title card does, and it is shaped to
     the block it is seating: weight gathers through the middle band where the
     centred statement actually sits, the top stays clear so the towers keep
     converging into open frame, and the foot eases back off so the canyon
     still carries light down behind the cue. It rides the beat's own opacity,
     so it lifts away with the statement rather than sitting on the canyon
     after the words have gone. */
  .skyhero__beat--lead::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(
      180deg,
      rgba(6, 6, 8, 0) 0%,
      rgba(6, 6, 8, 0.26) 20%,
      rgba(6, 6, 8, 0.55) 45%,
      rgba(6, 6, 8, 0.66) 70%,
      rgba(6, 6, 8, 0.58) 100%
    );
    pointer-events: none;
  }

  /* The beat already pays the gutter; the container was charging a second
     one. Two nested gutters left a 320 with 240px of measure — which is the
     real reason the headline was breaking into five lines there, not the type
     size. Dropping the inner one buys back 40px on every phone. */
  .skyhero__beat--lead .container { padding-inline: 0; }

  /* ---- type ---------------------------------------------------------------
     Each of these is one fluid ramp across the whole phone range: 320 → 430
     now moves, and nothing sits on a clamp bound in between. */
  .skyhero__beat--lead .eyebrow {
    font-size: 0.6875rem;
    letter-spacing: 0.17em;
    margin-bottom: clamp(0.85rem, 2.4svh, 1.35rem);
  }

  .skyhero__beat--lead h1 {
    font-size: clamp(1.5rem, 7.7vw, 2.6rem);
    line-height: 1.14;
    letter-spacing: -0.022em;
    margin-bottom: clamp(0.8rem, 2.2svh, 1.25rem);
    /* Both statements run to two lines at every phone width, and left ragged
       the second one drops "face." onto a line of its own. Balanced, the
       break lands on the comma. */
    text-wrap: balance;
  }

  .skyhero__beat--lead .hero__sub {
    font-size: clamp(0.9375rem, 4.15vw, 1.0625rem);
    line-height: 1.55;
    max-width: 30rem;
    margin-bottom: clamp(1.15rem, 3svh, 1.85rem);
  }

  /* ---- the actions --------------------------------------------------------
     Two full-bleed pills stacked cost 131px of a frame with none to spare,
     and they are the single most generic thing a phone hero can do. Side by
     side the pair measures 253px against the 280px a 320 gives, so the row
     holds at every width — and it hands 74px back to the statement above it.
     They flex from their own labels, so the primary stays the wider of the
     two instead of the split being arbitrary. */
  .skyhero__beat--lead .hero__actions {
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: center;
    align-items: stretch;
    gap: 0.625rem;
    width: 100%;
    max-width: 25rem;
    margin-inline: auto;
  }

  .skyhero__beat--lead .hero__actions .btn {
    flex: 1 1 auto;
    padding: 0.9rem 1rem;
    font-size: 0.875rem;
    white-space: nowrap;
  }

  /* ---- the three numbers --------------------------------------------------
     Same treatment: the figure was pinned at its 2.1rem floor from 320 all
     the way to 430 unless a short phone handed it 6.2vh, at which point it
     jumped to 39.7px and broke the reading across two lines. */
  .skyhero__eyebrow {
    font-size: 0.6875rem;
    letter-spacing: 0.18em;
  }
  .skyhero__eyebrow > span { text-indent: 0.18em; }
  .skyhero__eyebrow i { width: clamp(1rem, 5vw, 1.75rem); }

  .skyhero__figure {
    margin-top: clamp(1rem, 2.8svh, 1.7rem);
    font-size: clamp(1.7rem, 8.6vw, 3rem);
    line-height: 1.1;
  }

  .skyhero__line {
    margin-top: clamp(1.1rem, 3svh, 1.8rem);
    max-width: 26rem;
    font-size: clamp(0.9375rem, 4.15vw, 1.0625rem);
    line-height: 1.55;
  }

  /* ---- the cue ------------------------------------------------------------
     Parked at the bottom of the SMALL viewport so the browser chrome never
     sits on top of it, and short enough to leave the buttons room. */
  .skyhero__cue-wrap { bottom: 0; bottom: calc(100lvh - 100svh); }
  .skyhero__cue .scroll-cue__rail { height: clamp(2rem, 5svh, 3rem); }
  .skyhero__cue-pill { margin: 0.5rem 0 0.85rem; }
}

/* THE SHORT PHONE keeps the cue — it just stops asking for 70px to say one
   thing. Below 700px of viewport the bottom padding is already sitting on its
   floor, so the only way left to buy clearance between the buttons and the
   cue is out of the cue itself. 54px instead of 70 puts the gap back to the
   ~46px every taller phone gets. */
@media (max-width: 760px) and (max-height: 700px) {
  .skyhero__cue .scroll-cue__rail { height: 1.75rem; }
  .skyhero__cue-pill { margin: 0.4rem 0 0.7rem; }
  /* the bottom padding is paying for the cue, so it comes down with it —
     otherwise the block would centre against furniture that is no longer
     there and drift high */
  .skyhero__beat--lead {
    padding-bottom: 4.1rem;
    padding-bottom: calc(4.1rem + (100lvh - 100svh));
  }
}

/* Reduced motion: no flight, no typing. The canyon is drawn once as a still
   and the beats become plain stacked blocks so nothing is withheld. */
.skyhero--still .skyhero__track { height: auto; }

.skyhero--still .skyhero__stage {
  position: relative;
  height: auto;
  min-height: 100svh;
  /* sticky made the live stage its own stacking context; relative does
     not. Without this the beats' z-index: 2 escapes to the page and paints
     over everything after the hero - over the site grain, and over the
     client band's feather, which then cannot blend the hero's last rows and
     a hard seam comes back at the band's top edge. */
  isolation: isolate;
}

/* the still only backs the lead — it must not stretch behind the whole stack */
.skyhero--still .skyhero__canvas { height: 100vh; height: 100svh; }

.skyhero--still .skyhero__beat {
  position: relative;
  inset: auto;
  opacity: 1 !important;
  transform: none !important;
  visibility: visible !important;
}

.skyhero--still .skyhero__beat--lead { min-height: 100svh; }

.skyhero--still .skyhero__beat--stat {
  padding-block: var(--section-sm);
  background: linear-gradient(180deg, rgba(6, 6, 8, 0.4), rgba(6, 6, 8, 0.72));
}

.skyhero--still .skyhero__caret,
.skyhero--still .skyhero__cue-wrap { display: none; }

.skyhero--still .skyhero__eyebrow,
.skyhero--still .skyhero__figure,
.skyhero--still .skyhero__line {
  opacity: 1 !important;
  transform: none !important;
}
