/* ============================================================================
   SPHERE MEDIA
   Every value here is lifted from the client's own brand book.
   Measured spec: ../BRAND-SPEC.md
   ========================================================================= */

/* --------------------------------------------------------- 0. THE TYPEFACE
   Self-hosted 2026-08-13. See the comment in index.html's head for why: Google
   Fonts was five cross-origin requests on two extra hosts and 1.32s to first
   paint. Latin subset only, because this site sets no other script.

   `swap` is deliberate and matches what Google's own stylesheet did: text paints
   immediately in the fallback and re-renders in Poppins on arrival. A blocking
   value here would trade the whole gain back for a flash of nothing. */
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 300; font-display: swap;
  src: url('fonts/poppins-300.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('fonts/poppins-400.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('fonts/poppins-500.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('fonts/poppins-600.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('fonts/poppins-700.woff2') format('woff2'); }

/* ---------------------------------------------------------------- 1. TOKENS */

:root {
  /* Ground. The brand book's charcoal is the SURFACE colour; the deck grounds
     the opening and closing frames in true black so the lit surfaces read as
     lifted off it. Charcoal stays for anything raised. */
  /* ⭐ DEEPENED 2026-08-12 on Daniel's note: "increase contrasts more, less
     grey, more blacks." Every ground drops roughly two thirds of the way to
     black while the RAISED surface keeps enough lift to still read as a
     surface — the point of the note is contrast between planes, and taking
     everything to #000 would flatten the page into one plane, which is the
     opposite of what he asked for.

         was           now
     ground       #231F20  ->  #141112
     ground-deep  #1A1718  ->  #0B0A0A
     ground-lift  #2B2627  ->  #1E1A1B

     Text was already measured against these grounds and every block gains
     contrast from this change, never loses it: the ink stays put and the
     ground moves away from it. Re-measured from rendered pixels after. */
  --black:         #000000;
  --ground:        #141112;
  --ground-deep:   #0B0A0A;
  --ground-lift:   #1E1A1B;

  /* Fire — a light source in a dark room, never a flat fill */
  --fire-amber:    #F89521;
  --fire-orange:   #F04923;
  --fire-red:      #E72228;
  --fire-deep:     #9D1C20;
  --fire-ember:    #C10801;
  --fire-sand:     #D9C3AB;

  --white:         #FFFFFF;
  --ink:           rgba(255,255,255,.94);
  --ink-soft:      rgba(255,255,255,.76);
  --ink-faint:     rgba(255,255,255,.54);
  --rule:          rgba(255,255,255,.12);
  --rule-warm:     rgba(240,73,35,.34);

  /* The signature gradient, brand book p.13 */
  --grad-fire: linear-gradient(90deg, #000000 0%, var(--fire-ember) 34%, #F16001 68%, var(--fire-sand) 100%);
  --grad-accent: linear-gradient(135deg, var(--fire-amber) 0%, var(--fire-orange) 52%, var(--fire-deep) 100%);

  /* Type — ⭐ ONE FAMILY, NO SERIF (Daniel and Mano, 2026-08-12: "we wanna
     remove all the serif font from the website and just use sans").

     `--disp` was 'Bodoni Moda', 'Times New Roman', serif. Both are gone: the
     Bodoni webfont is no longer requested at all, and the Times New Roman
     fallback went with it, so nothing on this page can render in a serif even
     if a font request fails.

     The name `--disp` stays because the ROLE stays — it marks type playing the
     display part — but it now resolves to the same family as everything else.
     What carries the distinction instead is WEIGHT: display type runs at 300,
     body at 400, labels at 500. A geometric sans at 300 and 90px reads as
     refined; the same face at 400 reads as a shout, which is why every display
     rule below was retuned rather than just re-pointed. */
  --sans: 'Poppins', system-ui, -apple-system, sans-serif;
  --disp: 'Poppins', system-ui, -apple-system, sans-serif;

  /* ONE ladder. Ratio 1.333 (the perfect fourth), 16px base, every rung on it:
     12 · 16 · 21.3 · 28.4 · 37.9 · 50.5 · 67.3 · 89.7 · 119.6.

     The page had grown fifteen structural sizes with no shared ratio, nine of
     them one-off clamps invented at the point of use. A wide ratio suits a page
     built on headline moments rather than dense UI, which is what this is.

     Two consequences worth naming rather than discovering later:
     · There is exactly ONE rung below the 16px base (12px), because the next
       one down lands at 9px and 9px is not text. So `small` and `body` are the
       same size, and 12px carries only short tracked labels — never a sentence.
     · `--fs-mid` and `--fs-big` exist so the display DEVICES (the method
       numeral, the ticker words) sit on the ladder too, instead of each
       inventing its own clamp.

     DECLARED EXCEPTION, one: `.marker__t` is 200px SVG text sized to the
     giant-numeral device. It is a graphic, not prose, and putting it on a text
     ladder would shrink it into looking like a mistake. */
  --fs-micro:  .75rem;                           /*  12 */
  --fs-small:  1rem;                             /*  16 */
  --fs-body:   1rem;                             /*  16 — base */
  --fs-lede:   clamp(1.15rem, 1.6vw, 1.333rem);  /*  21.3 */
  --fs-h3:     clamp(1.4rem,  2.2vw, 1.777rem);  /*  28.4 */
  --fs-disp2:  clamp(1.75rem, 3.4vw, 2.369rem);  /*  37.9 */
  --fs-mid:    clamp(2rem,    4vw,   3.157rem);  /*  50.5 */
  --fs-big:    clamp(1.9rem,  4.4vw, 4.209rem);  /*  67.3 */
  --fs-disp:   clamp(2.4rem,  6.2vw, 5.61rem);   /*  89.7 */
  --fs-hero:   clamp(2.9rem,  8.6vw, 7.48rem);   /* 119.6 */

  /* Space */
  --wrap:      1180px;
  --gut:       clamp(1.25rem, 5vw, 3.5rem);
  --sec-y:     clamp(6rem, 13vw, 11rem);

  /* Radii — the brand's stadium/pill language */
  --r-md: 16px;
  --r-lg: 28px;
  --r-pill: 999px;
  /* ⭐ UNIFORM, 2026-08-13: "round all the corners" (Daniel, looking at the
     method cards). This was `48px 8px 8px 8px`, lifted from the deck's pricing
     cards, one corner opened wide and three held tight. It is his brand and his
     call. Changed at the TOKEN so every card in the file moves together: the
     asymmetry only ever read as deliberate while it was consistent, and half
     the cards keeping it would look like a bug. */
  --r-card: 28px;

  /* The crosshair — the deck's spine. A hairline in the fire colour. */
  --xhair: rgba(240,73,35,.32);

  /* Shadow stacks — never a single blur */
  --sh-card:
    0 1px 1px rgba(0,0,0,.42),
    0 4px 8px rgba(0,0,0,.34),
    0 14px 28px rgba(0,0,0,.30),
    0 32px 64px rgba(0,0,0,.26);
  --sh-lift:
    0 2px 3px rgba(0,0,0,.44),
    0 8px 18px rgba(0,0,0,.38),
    0 26px 52px rgba(0,0,0,.36),
    0 52px 96px rgba(0,0,0,.32),
    0 0 62px rgba(240,73,35,.14);
  --sh-fire:
    0 1px 2px rgba(0,0,0,.5),
    0 6px 16px rgba(240,73,35,.30),
    0 14px 38px rgba(231,34,40,.22);

  --ease: cubic-bezier(.16, 1, .3, 1);
  --ease-io: cubic-bezier(.65, 0, .35, 1);
  /* The corpus's proven entrance curve, used across all 58 hero prompts. */
  --ease-rise: cubic-bezier(.22, 1, .36, 1);
}

/* ------------------------------------------------------------- 2. FOUNDATION */

*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-body);
  font-weight: 300;
  line-height: 1.68;
  letter-spacing: .004em;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img { max-width: 100%; display: block; }

a { color: inherit; text-decoration: none; }

h1, h2, h3 { margin: 0; font-weight: 600; line-height: 1.05; letter-spacing: -.02em; }
p { margin: 0 0 1.1em; }
p:last-child { margin-bottom: 0; }

:focus-visible {
  outline: 2px solid var(--fire-amber);
  outline-offset: 3px;
  border-radius: 3px;
}

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gut);
  position: relative;
  z-index: 2;
}

/* The display face. Lowercase, large, sparing. Never body copy.
   ⭐ 2026-08-12: weight 400 to 300 and tracking -.015em to -.035em. A Didone
   carries a 90px headline on stroke contrast; a geometric sans has none, so it
   carries it on thin strokes set tight. Left at 400 the same headline gained
   about 30% apparent weight overnight and stopped being a display line at all. */
/* ⭐ ONE RUNG DOWN, 2026-08-13, same note and same reasoning as the hero:
   --fs-disp to --fs-big, 89.7px to 67.3px at the cap. Section headings and the
   hero now sit one step apart on the ladder instead of two, which is also why
   the page reads calmer: the hero no longer has to be shouted down by the
   heading that follows it. */
.disp {
  font-family: var(--disp);
  font-weight: 300;
  font-size: var(--fs-big);
  line-height: .98;
  letter-spacing: -.035em;
  text-transform: lowercase;
  color: var(--white);
}

/* ---- The molten sweep -------------------------------------------------------
   Deck p.13 sets every display headline in the fire gradient. The corpus adds
   the movement: a 200%-wide gradient walked across the glyphs so the metal
   catches light. 6s, not the corpus's 3s — at 3s it reads as a loading
   skeleton, at 6s it reads as molten.

   DECLARED DEPARTURE from the deck: the darkest stop is lifted from #9D1C20
   oxblood to #E8451F. Oxblood on black measures 2.6:1, under the 3:1 floor for
   large text; #E8451F measures 5.3:1 and clears even the 4.5:1 body floor. The brand keeps its fire,
   the headline stays readable. Tile captions are exempt — they sit over imagery
   and stay solid white.
   -------------------------------------------------------------------------- */

@keyframes shiny-move {
  0%   { background-position: 200% center; }
  100% { background-position: -200% center; }
}

h2.disp,
.disp--shine {
  background-image: linear-gradient(90deg,
    #E8451F 0%, #F86A22 22%, var(--fire-amber) 40%, #FFE0B0 52%,
    var(--fire-amber) 66%, var(--fire-orange) 84%, #E8451F 100%);
  background-size: 200% auto;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: shiny-move 6s linear infinite;
  /* background-clip:text paints only inside the text box, and a descender at
     display size sits below it. Still true in Poppins — "upgrade your content"
     has a g, a y and a p in one line. */
  padding-bottom: .06em;
}

/* Fallback: no sweep, solid cream. Fires for reduced motion and on paper. */
@media print {
  h2.disp, .disp--shine {
    background: none;
    color: var(--fire-sand);
    animation: none;
  }
}

/* .eyebrow is deliberately gone. "No eyebrow labels, ever" — a tiny tracked
   uppercase kicker floating above a headline is the single most recognisable
   AI-generic pattern, and it is a standing rule here, not a preference. The
   two that existed have been folded into the copy that follows them. */

.lede { font-size: var(--fs-lede); color: var(--ink); font-weight: 300; line-height: 1.55; }

/* -------------------------------------------------------------- 3. THE GRAIN */
/* The brand's signature texture. Sits over everything, drifts, never static. */

.grain {
  position: fixed;
  /* -10%, not -150%. The drift below travels at most ±2%, so -150% bought
     nothing and cost everything: it made the layer four viewports wide and four
     tall — sixteen times the area — composited through a blend mode on every
     frame. Measured cost at 1440: the first screen could not be sampled until
     ~1490ms. */
  inset: -10%;
  z-index: 90;
  pointer-events: none;
  opacity: .07;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain-drift 6s steps(6) infinite;
  will-change: transform;
}

@keyframes grain-drift {
  0%   { transform: translate(0, 0); }
  16%  { transform: translate(-2%, 1%); }
  33%  { transform: translate(1%, -2%); }
  50%  { transform: translate(-1%, 2%); }
  66%  { transform: translate(2%, 1%); }
  83%  { transform: translate(-2%, -1%); }
  100% { transform: translate(0, 0); }
}

/* ------------------------------------------------------- 3b. READ PROGRESS
   A 2px filament across the very top. It is the only element on the page that
   tells the reader how much is left, and on a page with no chrome that is
   worth two pixels. */

.progress {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 95;
  height: 2px;
  pointer-events: none;
}

.progress i {
  display: block;
  height: 100%;
  transform: scaleX(var(--p, 0));
  transform-origin: left;
  background: linear-gradient(90deg, var(--fire-deep), var(--fire-orange) 46%, var(--fire-amber) 78%, #FFD68C);
  box-shadow: 0 0 12px rgba(240,73,35,.75);
}

/* ------------------------------------------------------------------- 4. NAV */
/* The brand book's own page header: a fire strip, and the panel below it
   cut with rounded bottom corners rising into the strip. */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 80;
  /* The panel used to be pulled UP under a strip that sat above it (margin-top
     -18px). With the light now inside the bar there is no strip to tuck under,
     so the inset is stated positively here — and the bar needs one, or its top
     corners have nothing to be corners against. */
  padding-top: clamp(.6rem, 1.4vw, 1rem);
  transition: transform .5s var(--ease);
}

/* ⭐ THE LIGHT LIVES INSIDE THE BAR (Daniel, 2026-08-12).
   This element is now a self-clipping layer INSIDE .nav__inner: it inherits the
   panel's radius and hides its own overflow, so the fire it carries can be as
   large and as blurred as the brand wants and still stop dead at the bar's
   rounded edge. Nothing spills above the panel or past its corners.

   The clip lives HERE and not on .nav__inner because the mobile dropdown is a
   sibling that hangs BELOW the bar — clipping the parent would delete it. */
.nav__strip {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;
}

/* The bloom. The brand book's fire gradient, blurred, arriving from above and
   dying out before it reaches the text — the mask is what keeps the links and
   the wordmark on clean glass instead of on a wash. The lower edge is still a
   shallow arc, because no boundary on this page is a straight line. */
.nav__strip::before {
  content: "";
  position: absolute;
  top: -78%; left: -12%; right: -12%;
  height: 172%;
  background: var(--grad-fire);
  filter: blur(16px) saturate(1.25);
  opacity: .82;
  -webkit-clip-path: ellipse(150% 100% at 50% 0%);
  clip-path: ellipse(150% 100% at 50% 0%);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 46%, transparent 88%);
  mask-image: linear-gradient(180deg, #000 0%, #000 46%, transparent 88%);
}

/* The lit edge itself: a hairline of fire along the inside of the top border,
   fading out at both ends so it reads as a light source rather than a stripe. */
.nav__strip::after {
  content: "";
  position: absolute;
  top: 0; left: 8%; right: 8%;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--fire-amber) 22%, var(--fire-orange) 58%, transparent);
  filter: blur(.4px);
  opacity: .9;
}

.nav__inner {
  position: relative;
  inset: auto;
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  padding: .85rem var(--gut);
  /* Full liquid-glass recipe. Keep the brand book's rounded-bottom panel shape;
     take the corpus's material. */
  background: rgba(20,17,18,.62);
  -webkit-backdrop-filter: blur(22px) saturate(1.6);
  backdrop-filter: blur(22px) saturate(1.6);
  border-bottom: 1px solid rgba(255,255,255,.10);
  /* Daniel, 2026-08-12: "top of the bar should be rounded so it matches the
     bottom part." All four corners now, and the panel is inset from the top
     edge so the top radius has something to be a corner AGAINST — a rounded
     corner flush against the viewport edge is invisible. */
  border-radius: var(--r-lg);
  border-top: 1px solid rgba(255,255,255,.08);
  margin-inline: var(--gut);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.14),
    0 8px 32px rgba(0,0,0,.42);
  transition: padding .4s var(--ease), background .4s var(--ease);
}

.nav.is-stuck .nav__inner {
  padding-block: .55rem;
  background: rgba(11,10,10,.78);
}

/* Once the panel is stuck the fire strip behind it is decoration, not the
   header's own edge — so it steps back rather than competing with the content. */
.nav__strip { transition: opacity .5s var(--ease); }
.nav.is-stuck .nav__strip { opacity: .4; }

/* The bar's contents sit ABOVE the light. Without this the fire layer paints
   over the wordmark, because backdrop-filter on the panel puts every child in
   one stacking context and the strip is first in source order.

   Listed explicitly rather than as `> :not(.nav__strip)`: that shorthand
   out-specifies the mobile dropdown's own `position: absolute` in the 900px
   block and pins the menu inside the bar. Each selector below is a single
   class, so the later responsive rules still win where they must. */
.nav__logo, .nav__links, .nav__inner > .btn--sm, .nav__burger { position: relative; z-index: 1; }

/* The wordmark is the "home" link and it was 27px tall — under both the WCAG
   24px minimum and the 44px mobile bar, and the only target on the page that
   failed at 390. The image keeps its size; the LINK grows around it. */
.nav__logo { display: inline-flex; align-items: center; min-height: 44px; }
.nav__logo img { width: clamp(112px, 13vw, 150px); height: auto; }

.nav__links {
  display: flex;
  gap: clamp(1rem, 2.4vw, 2.1rem);
  margin-left: auto;
}

.nav__links a {
  position: relative;
  inset: auto;
  font-size: var(--fs-small);
  font-weight: 400;
  letter-spacing: .06em;
  color: var(--ink-soft);
  padding: .55rem .1rem;
  transition: color .35s var(--ease);
}

.nav__links a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: .28rem;
  height: 1px;
  background: var(--grad-accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .45s var(--ease);
}

.nav__links a:hover { color: var(--white); }
.nav__links a:hover::after { transform: scaleX(1); }

.nav__burger {
  display: none;
  width: 44px; height: 44px;
  padding: 0;
  background: none;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  cursor: pointer;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
}

.nav__burger span {
  display: block;
  width: 17px; height: 1.5px;
  background: var(--white);
  transition: transform .4s var(--ease), opacity .3s var(--ease);
}

.nav__burger[aria-expanded="true"] span:first-child { transform: translateY(3.25px) rotate(45deg); }
.nav__burger[aria-expanded="true"] span:last-child  { transform: translateY(-3.25px) rotate(-45deg); }

/* --------------------------------------------------------------- 5. BUTTONS */

.btn {
  position: relative;
  inset: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: .85rem 1.75rem;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--ground-lift);
  color: var(--white);
  font-family: var(--sans);
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: .05em;
  cursor: pointer;
  overflow: hidden;
  isolation: isolate;
  box-shadow: var(--sh-card);
  transition: transform .45s var(--ease), box-shadow .45s var(--ease);
}

.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--grad-accent);
  transform: translateY(101%);
  transition: transform .5s var(--ease);
  z-index: -1;
}

.btn span { position: relative; inset: auto; z-index: 1; }

.btn:hover { transform: translateY(-2px); box-shadow: var(--sh-fire); }
.btn:hover::before { transform: translateY(0); }
.btn:active { transform: translateY(0); }

.btn--sm { min-height: 40px; padding: .55rem 1.2rem; font-size: var(--fs-micro); }

.btn--ghost {
  background: transparent;
  border: 1px solid var(--rule-warm);
  box-shadow: none;
}
.btn--ghost:hover { border-color: transparent; }

.btn--full { width: 100%; grid-column: 1 / -1; }

/* The primary CTA is filled at rest, not on hover. Corpus rule: two buttons of
   equal weight read as indecision — one must win on sight. */
.btn--primary {
  padding: 1rem 2.5rem;
  background: var(--grad-accent);
  /* Near-black on the fire fill, not white. White measured 3.68:1 on the amber
     end of this gradient; #0B0B0D clears 4.5 across the whole sweep. */
  color: #0B0B0D;
  font-weight: 600;
  box-shadow: var(--sh-fire);
}
.btn--primary::before {
  background: linear-gradient(135deg, #FFC46B 0%, var(--fire-amber) 46%, var(--fire-orange) 100%);
}
.btn--primary:hover { box-shadow: var(--sh-fire), 0 0 44px rgba(248,149,33,.34); }

/* The demoted second action. A text link, not a button — it must never compete. */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  min-height: 48px;
  padding: 0 .35rem;
  font-size: var(--fs-small);
  font-weight: 400;
  letter-spacing: .05em;
  color: var(--ink-soft);
  transition: color .4s var(--ease);
}

.link-arrow i {
  position: relative;
  inset: auto;
  display: block;
  width: 26px; height: 1px;
  background: linear-gradient(90deg, var(--ink-faint), var(--fire-orange));
  transform-origin: left;
  transition: transform .45s var(--ease);
}

/* The arrowhead rides the end of the rule, so the whole mark grows as one. */
.link-arrow i::after {
  content: "";
  position: absolute;
  top: 50%; left: 100%;
  width: 6px; height: 6px;
  margin: -3px 0 0 -6px;
  border-top: 1px solid var(--fire-orange);
  border-right: 1px solid var(--fire-orange);
  transform: rotate(45deg);
}

.link-arrow:hover { color: var(--white); }
.link-arrow:hover i { transform: scaleX(1.34); }
/* Counter-scale so the head stays a square while the rule stretches under it. */
.link-arrow:hover i::after { transform: scaleX(.7463) rotate(45deg); }

/* ---------------------------------------------------------------- 6. SPHERE */
/* The hero device. Amber core, red mid, falling off into the ground.
   Three layers breathing out of phase so it lives instead of pulsing. */

.sphere {
  position: relative;
  inset: auto;
  width: min(64vw, 605px);
  aspect-ratio: 1;
}

.sphere > div {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  will-change: transform, opacity;
}

.sphere__halo {
  background: radial-gradient(circle at 50% 50%,
    rgba(231,34,40,.44) 0%,
    rgba(157,28,32,.32) 36%,
    rgba(157,28,32,.10) 58%,
    rgba(35,31,32,0) 78%);
  filter: blur(48px);
  animation: breathe-halo 9s var(--ease-io) infinite alternate;
}

.sphere__mid {
  inset: 13%;
  background: radial-gradient(circle at 50% 48%,
    rgba(240,73,35,.94) 0%,
    rgba(231,34,40,.82) 46%,
    rgba(157,28,32,.30) 74%,
    rgba(35,31,32,0) 88%);
  filter: blur(30px);
  animation: breathe-mid 7.5s var(--ease-io) infinite alternate;
}

.sphere__core {
  inset: 27%;
  background: radial-gradient(circle at 50% 46%,
    rgba(255,214,140,.98) 0%,
    rgba(248,149,33,.96) 30%,
    rgba(240,73,35,.72) 62%,
    rgba(231,34,40,.20) 84%,
    rgba(35,31,32,0) 100%);
  filter: blur(22px);
  animation: breathe-core 8.2s var(--ease-io) infinite alternate;
  z-index: 1;
}

@keyframes breathe-halo {
  from { transform: scale(.96); opacity: .82; }
  to   { transform: scale(1.06); opacity: 1; }
}
@keyframes breathe-mid {
  from { transform: scale(1.04); opacity: .9; }
  to   { transform: scale(.95); opacity: 1; }
}
@keyframes breathe-core {
  from { transform: scale(.93); opacity: .88; }
  to   { transform: scale(1.05); opacity: 1; }
}

/* ------------------------------------------------------------------ 7. HERO */

.hero {
  position: relative;
  inset: auto;
  /* True black opening frame (deck). It resolves into the charcoal surface at
     the very bottom so the hero never meets About on a hard straight edge. */
  background: linear-gradient(180deg, var(--black) 0%, var(--black) 74%, var(--ground) 100%);
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  /* ⛔ `safe` is doing real work here, it is not a nicety. On a SHORT viewport
     (a landscape phone, a half-height desktop window, and the 1200x630 frame the
     link-preview image is captured at) the headline plus sub plus buttons are
     taller than the hero box. Plain `center` overflows a flex container in BOTH
     directions, so the top of "Build Attention." was being pushed up underneath
     the fixed nav and clipped. `safe center` centres while it fits and falls
     back to start the moment it does not, so nothing is ever cut off the top.
     The unprefixed `center` above it is the fallback for any engine that does
     not know the keyword: it drops the whole line and behaves as before. */
  justify-content: center;
  justify-content: safe center;
  /* The top figure's MINIMUM is set by the nav, not by taste. Measured at a
     1200x630 frame (the link-preview capture, and a landscape phone): with an
     8rem floor the H1 box landed 26px under the bar, and because the bar is
     backdrop-blurred, the headline behind it smeared through the glass and read
     as a clipped letter. 10rem puts ~70px between them, so the bar has clean
     ground behind it. Tall viewports are unaffected: 16vh overtakes the floor
     above ~1000px of height. */
  padding: clamp(10rem, 16vh, 12rem) var(--gut) clamp(5rem, 10vh, 8rem);
  overflow: hidden;
}

/* ---- The reel layer.
   Corpus differentiator #2, and the one that matters most here: this is a
   video company, so a hero that plays their own work is the argument. The
   scrim is not optional — white type over unknown footage is a gamble, and
   the footage changes. */

.hero__video {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.1s var(--ease-io);
  pointer-events: none;
}

.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.1s var(--ease-io);
  background:
    linear-gradient(90deg, rgba(0,0,0,.9) 0%, rgba(0,0,0,.62) 46%, rgba(0,0,0,.24) 100%),
    linear-gradient(180deg, rgba(0,0,0,.72) 0%, rgba(0,0,0,.22) 34%, rgba(0,0,0,.86) 100%);
}

.hero.has-reel .hero__video,
.hero.has-reel .hero__scrim { opacity: 1; }

/* With footage behind it the sphere becomes a light source ON the frame
   rather than the subject of it. */
.hero.has-reel .hero__sphere { opacity: .55; mix-blend-mode: screen; }

.hero__sphere {
  position: absolute;
  top: 44%;
  right: -4%;
  transform: translate(0, -50%);
  z-index: 0;
  pointer-events: none;
  will-change: transform;
}

.hero__inner {
  position: relative;
  inset: auto;
  z-index: 2;
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
}

.hero__h1 {
  /* ⭐ ONE RUNG DOWN, 2026-08-13 (Daniel: "the headers are way too big, make it
     a little bit smaller"). --fs-hero to --fs-disp: 119.6px to 89.7px at the
     cap. It steps to the NEXT VALUE ON THE EXISTING LADDER rather than
     inventing a smaller clamp, because a one-off size here is how a type scale
     quietly stops being one. One rung of a 1.333 ratio is the smallest
     principled reduction available. */
  font-size: var(--fs-disp);
  font-weight: 600;
  line-height: .96;
  letter-spacing: -.035em;
  margin: 0 0 1.6rem;
  max-width: 15ch;
}

/* ---- The line mask.
   The padding/margin pair is not decoration and it did NOT leave with the
   serif: at 7rem with -.035em tracking and .96 line-height, Poppins still
   overshoots its line box top and bottom, so `overflow:hidden` would shave the
   cap of the B and the descender of the y. The negative margin gives the mask
   the room back without moving the text. */
.hero__h1 .line {
  display: block;
  overflow: hidden;
  padding: .12em 0 .06em;
  margin: -.12em 0 -.06em;
}

.hero__h1 .line b { display: block; font-weight: inherit; }

/* ---- The hero entrance is CSS, not JavaScript, and that is the whole point.
   Measured on a throttled connection, the JS-gated version was still settling
   at 1335ms: the reveal could not begin until site.js had downloaded, parsed
   and run. A CSS animation starts the moment the stylesheet parses — hundreds
   of milliseconds earlier on a slow connection, and with no dependency on a
   script arriving at all.

   `both` fill matters: it holds the start state before the delay and the end
   state after, so nothing flashes and nothing is left hidden if the animation
   is interrupted. Everything is on screen by ~900ms after CSS parse. */

@keyframes line-in { from { transform: translateY(108%); } to { transform: none; } }
@keyframes fade-up { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

/* The timings are constrained by a measurement, not by taste: on a throttled
   connection the stylesheet parses around 600ms, and the gate wants the first
   screen settled by 1200ms. That leaves ~550ms of total budget, so the last
   element to move must finish inside it. The previous set ran to 800ms and
   failed the gate by two pixels of remaining travel. */
.hero__h1 .line b {
  animation: line-in .5s var(--ease-rise) both;
  will-change: transform;
}

.hero__h1 .line:nth-child(1) b { animation-delay: 0s; }
.hero__h1 .line:nth-child(2) b { animation-delay: .05s; }
.hero__h1 .line:nth-child(3) b { animation-delay: .10s; }

.hero__sub    { animation: fade-up .42s var(--ease-rise) .13s both; }
.hero__cta    { animation: fade-up .42s var(--ease-rise) .17s both; }
.hero__scroll { animation: fade-up .42s var(--ease-rise) .21s both; }

/* Daniel, 2026-08-12: "change font of Build to match the others, but keep the
   colour gradient of the text." So the serif goes and the fire stays — it now
   inherits the H1's own family, weight and tracking, and reads as one line of
   type with one word lit rather than as two typefaces arguing. */
.hero__h1 em {
  font-family: inherit;
  font-style: normal;
  font-weight: inherit;
  letter-spacing: inherit;
  background: linear-gradient(100deg, var(--fire-sand) 0%, var(--fire-amber) 38%, var(--fire-orange) 78%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero__sub {
  max-width: 46ch;
  color: var(--ink-soft);
  font-size: var(--fs-lede);
  line-height: 1.6;
  margin-bottom: 2.4rem;
}

.hero__cta { display: flex; flex-wrap: wrap; gap: .85rem; }

/* ⭐ CENTRED, WITH A DOWN ARROW (Mano, 2026-08-13: "put this scroll button in
   the middle of first section and have an arrow that points down").

   It was pinned bottom-LEFT with a horizontal fire rule beside the word, which
   pointed sideways while asking the reader to go down. Now it sits on the
   section's centre line and the mark under it is an arrow travelling downward,
   so the gesture and the instruction finally agree. */
.hero__scroll {
  position: absolute;
  left: 50%;
  bottom: clamp(1.75rem, 5vh, 3rem);
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .6rem;
  font-size: var(--fs-micro);
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* The arrow is drawn, not typed: a chevron built from two borders on a rotated
   square, so it inherits the fire colour and needs no icon font or SVG. */
.hero__scroll i {
  display: block;
  width: 10px; height: 10px;
  border-right: 1.5px solid var(--fire-orange);
  border-bottom: 1.5px solid var(--fire-orange);
  transform: rotate(45deg);
  animation: scroll-hint 2.2s var(--ease-io) infinite;
}

/* The arrow travels DOWN and fades, rather than stretching sideways. The
   rotate(45deg) has to be restated in every frame: a transform is one property,
   so animating translate here without it would throw away the rotation that
   makes the chevron a chevron. */
@keyframes scroll-hint {
  0%   { transform: rotate(45deg) translate(0, 0);       opacity: .35; }
  45%  { transform: rotate(45deg) translate(3px, 3px);   opacity: 1; }
  100% { transform: rotate(45deg) translate(6px, 6px);   opacity: 0; }
}

/* -------------------------------------------------------------- 8. THE ARCS */
/* Circle edges at a scale far larger than the page — brand book p.7 and p.29. */

.arc {
  position: absolute;
  left: 50%;
  z-index: 1;
  width: 200vw;
  height: 66vw;
  transform: translateX(-50%);
  border-radius: 50%;
  pointer-events: none;
}

/* A sliver, not a field. The fire is a horizon at the bottom of the hero —
   brand book p.29 — so it must never compete with the sphere above it. */
.arc--bottom {
  bottom: -63vw;
  background: linear-gradient(12deg, var(--fire-sand) 0%, var(--fire-orange) 22%, var(--fire-red) 46%, var(--fire-deep) 68%, rgba(35,31,32,0) 92%);
  opacity: .8;
  filter: blur(1px);
}

/* A charcoal ellipse whose curved top edge is lit from beneath — brand book p.7.
   The glow IS the edge; there is no straight boundary anywhere. */
.arc--top {
  top: -64vw;
  background: var(--ground);
  box-shadow:
    0 -1px 0 1px rgba(255,214,140,.5),
    0 -6px 26px 3px rgba(240,73,35,.5),
    0 -18px 70px 10px rgba(231,34,40,.34),
    0 -46px 150px 30px rgba(157,28,32,.24);
  z-index: 2;
}

/* --------------------------------------------------------------- 9. SECTIONS */

.sec {
  position: relative;
  inset: auto;
  padding-block: var(--sec-y);
  overflow: hidden;
}

/* ---- THE CROSSHAIR ----------------------------------------------------------
   Straight from Daniel's deck, not the corpus: a 1px vertical rule at 66% and a
   1px horizontal rule above each section's header band. They cross. Nothing
   else on the web looks like it, and it costs two pseudo-elements.

   Both DRAW themselves in when the section arrives — scaled from zero along
   their own axis, 900ms, so the page assembles instead of appearing. */

.sec::after {
  content: "";
  position: absolute;
  z-index: 1;
  background: var(--xhair);
  pointer-events: none;
  transition: transform .9s var(--ease);
}

/* ⭐ THE VERTICAL RULE IS DELETED, EVERYWHERE, 2026-08-13. Daniel, second and
   final word on it: "there's also a vertical line that runs straight down the
   website on the 2/3 right side, daniel wants that orange vertical line that
   runs through all the sections removed."

   It was removed from the contact section alone a few minutes earlier, on the
   reading that it only failed where it crossed the fire glow. He looked again
   and ruled on the whole page. His call, and the broader one: a hairline the
   reader keeps noticing is not a device, it is a distraction, and he is the one
   whose visitors have to look at it.

   The HORIZONTAL rule below stays. He has never objected to it, it sits above
   each section's numeral where it reads as a header rule rather than a seam,
   and half a crosshair is still a deliberate mark. */

/* The horizontal: full width, riding just above the section numeral. */
.sec::after {
  left: 0; right: 0;
  top: calc(var(--pad-top, var(--sec-y)) - 4.75rem);
  height: 1px;
  transform-origin: left;
  background: linear-gradient(90deg,
    rgba(240,73,35,0) 0%, var(--xhair) 14%, var(--xhair) 86%, rgba(240,73,35,0) 100%);
}

.js .sec::after  { transform: scaleX(0); }
.js .sec.is-lined::before { transform: scaleY(1); }
.js .sec.is-lined::after  { transform: scaleX(1); }

/* No section gradient here on purpose: a linear-gradient starting at the section
   boundary draws a hard straight line across the page. The only edge that should
   be visible is the arc's curve, lit from beneath. */
.sec--work {
  --pad-top: clamp(9rem, 22vw, 16rem);
  padding-top: var(--pad-top);
  background: var(--ground);
}

.sec--process { background: var(--ground-deep); }

.sec__note {
  font-size: var(--fs-lede);
  font-weight: 300;
  line-height: 1.6;
  color: var(--ink-soft);
  margin: 1.35rem 0 0;
  max-width: 46ch;
}

/* Section markers — giant outlined gradient numerals, bled off the left edge.
   The numeral and the section heading share a baseline and read as one unit,
   which is how the brand book opens each of its own sections. */

/* The numeral shares a baseline with the heading it belongs to, so the two read
   as one unit. --pad-top is whatever that section pushed its content down by;
   the 3.5rem lifts the numeral's baseline onto the heading's. */
/* The numeral is a WATERMARK, not a sibling of the heading.
   It used to be pushed clear with a per-section `padding-left`, which left
   About and Method un-indented while Services, Work and Process were indented
   168px — a visible misalignment at 1440 and a collision at 1100. Sitting it
   behind the type at low opacity removes both problems at once: every heading
   now starts on the same left edge at every width, and there is no width at
   which the two can fight, because overlapping is the design. */
.marker {
  position: absolute;
  top: calc(var(--pad-top, var(--sec-y)) - 3.5rem);
  /* Daniel, 2026-08-12: "01 is a little bit cut off on the left edge." It was —
     the numeral sat at a NEGATIVE inset, so its first glyph ran off the page.
     Two things had to be true at once and the first attempt only got one: it
     must never cross x=0 (his complaint), and it must never reach the wrap
     (or it prints through the heading, which is worse than the bug). So it now
     lives INSIDE the left gutter and is sized BY that gutter, below. Under
     1220px the gutter cannot hold it and it moves above the heading instead. */
  left: 1rem;
  z-index: 1;
  display: flex;
  align-items: flex-end;
  gap: 1.25rem;
  pointer-events: none;
  opacity: .26;
}

.marker__num {
  /* Sized by the space it actually has: the gutter, less its own inset and a
     2rem gap before the wrap starts. At 1440 that is an 88px numeral in a
     120px gutter — visible in full, clear of the type, still a ghost. */
  width: min(210px, calc((100vw - var(--wrap)) / 2 - 2rem));
  height: auto;
  overflow: visible;
}

/* No per-section indent. Every section heading starts on the wrap's own left
   edge, and the numeral ghosts behind it. See the note on .marker above. */

.marker__t {
  font-family: var(--sans);
  font-size: 200px;
  font-weight: 600;
  letter-spacing: -.04em;
  fill: none;
  stroke: url(#g01);
  stroke-width: 1.4;
  paint-order: stroke;
}

/* .marker__label is gone from the DOM, not just hidden.
   It sat in the same flex row as the 210px numeral, so at 1440 it landed at
   ~208px — exactly where the wrap's content starts — and printed the word
   METHOD straight through "how we", SERVICES through "what we", PROCESS
   through "how it runs". At 100% it read as a smudge, which is worse than a
   collision because it reads as a printing fault.

   It was also pure redundancy: the numeral gives the index, the heading gives
   the name, the nav gives the label. Deleting it removes the collision and
   loses nothing. */

/* Ambient glows — bleed, never shape */

.glow {
  position: absolute;
  z-index: 0;
  width: 62vw;
  height: 62vw;
  max-width: 780px;
  max-height: 780px;
  border-radius: 50%;
  pointer-events: none;
  filter: blur(90px);
  opacity: .46;
  background: radial-gradient(circle,
    rgba(248,149,33,.5) 0%, rgba(240,73,35,.72) 26%, rgba(157,28,32,.42) 52%, rgba(35,31,32,0) 74%);
}

.glow--left  { top: 18%;  left: -28%; }
.glow--right { bottom: 4%; right: -26%; }

/* ----------------------------------------------------------------- 10. ABOUT */

/* The grid adds its own top padding, so the marker's baseline reference has to
   include it or the numeral floats above its heading. */
.sec--about { --pad-top: calc(var(--sec-y) + clamp(3rem, 8vw, 6rem)); }

.about__grid {
  display: grid;
  grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr);
  gap: clamp(2rem, 6vw, 5rem);
  align-items: start;
  padding-top: clamp(3rem, 8vw, 6rem);
}

.about__head { position: relative; inset: auto; }

.about__mark {
  margin-top: clamp(2rem, 5vw, 3.5rem);
  opacity: .8;
  pointer-events: none;
}
.about__mark .sphere { width: min(46vw, 300px); }

.about__body { max-width: 58ch; color: var(--ink-soft); }
.about__body .lede { margin-bottom: 1.4rem; }

.stats {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5rem;
  margin: 2.75rem 0 0;
  padding: 1.75rem 0 0;
  border-top: 1px solid var(--rule);
}

.stats li { display: flex; flex-direction: column; gap: .3rem; }
/* --fs-h3, not --fs-disp2: at 38px "End-to-end" is wider than its third of the
   row and runs into the neighbour beside it. */
/* 500, not 300: these are the proof numbers — 62K, 50%, 11 weeks — and a number
   at 28px in a light weight reads as a caption. Display SIZE, label WEIGHT. */
.stats b {
  font-family: var(--disp);
  font-weight: 500;
  font-size: var(--fs-h3);
  color: var(--white);
  letter-spacing: -.02em;
}
.stats span { font-size: var(--fs-micro); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint); }

/* Two audiences read as two columns with room to say something, not as three
   one-word chips. Ordered after .stats rather than marked !important, so the
   640px single-column rule still wins on a phone. */
.stats--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.stats--two span {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-soft);
}

/* --------------------------------------------------------------- OUR CLIENTS
   Two client wordmarks, supplied by them, sized by HEIGHT so two marks of very
   different proportions carry the same weight. They sit at rest slightly
   dimmed and come to full strength on hover — the same "one thing lit" logic
   the method cards use, applied to a two-item row. */
.clients {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(2.5rem, 7vw, 6rem);
  margin: clamp(2.5rem, 6vw, 4rem) 0 0;
  padding: 0;
}

.clients a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  opacity: .82;
  transition: opacity .45s var(--ease), transform .45s var(--ease), filter .45s var(--ease);
}

.clients img {
  display: block;
  width: auto;
  height: clamp(34px, 4.6vw, 52px);
  /* The Good Boys mark carries a chevron under its wordmark, so its box is
     square where Prosper's is a 10:3 strip. Optical sizing, not box sizing. */
  filter: drop-shadow(0 6px 22px rgba(0,0,0,.5));
}

.clients li:nth-child(2) img { height: clamp(52px, 7vw, 82px); }

.clients a:hover { opacity: 1; transform: translateY(-2px); filter: drop-shadow(0 0 26px rgba(240,73,35,.35)); }

/* ------------------------------------------------ WHAT WE DO, AS A STRIP
   The old section 03, merged into the method on Daniel's instruction. Pills,
   not cards: the argument is the six steps above; this is the scan underneath
   them. Reuses the roster's pill so the page does not gain a seventh shape. */
.caps {
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  padding-top: clamp(1.75rem, 3vw, 2.25rem);
  border-top: 1px solid var(--rule);
}

.caps__h {
  font-family: var(--disp);
  font-weight: 300;
  font-size: var(--fs-h3);
  letter-spacing: -.02em;
  color: var(--white);
  margin-bottom: 1.1rem;
}

.caps__list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: .6rem .5rem;
  margin: 0;
  padding: 0;
}

.caps__list li {
  padding: .5rem 1.05rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  background: rgba(255,255,255,.035);
  -webkit-backdrop-filter: blur(10px) saturate(1.3);
  backdrop-filter: blur(10px) saturate(1.3);
  font-size: var(--fs-small);
  color: var(--ink-soft);
  transition: border-color .45s var(--ease), color .45s var(--ease), background .45s var(--ease);
}

.caps__list li:hover { border-color: var(--rule-warm); color: var(--white); background: rgba(240,73,35,.1); }

/* ------------------------------------------------------------ THE PROOF BAND
   The one thing the page was missing: outcome. It sits ABOVE the work frames
   on purpose — the frames are what the work looks like, these are what it did,
   and a reader deciding in thirty seconds should hit the second one first.

   It reuses .stats rather than inventing a fourth card shape. The source line
   is small, quiet and never omitted: an unsourced number on a client page is
   the one thing that cannot be walked back. */
.proof {
  margin-top: clamp(2rem, 4vw, 3rem);
  padding: clamp(1.75rem, 3vw, 2.25rem) clamp(1.5rem, 3vw, 2.25rem);
  border: 1px solid rgba(255,255,255,.1);
  border-radius: var(--r-card);
  background:
    linear-gradient(165deg, rgba(255,255,255,.05) 0%, rgba(255,255,255,0) 46%),
    rgba(30,26,27,.55);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  backdrop-filter: blur(18px) saturate(1.4);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.12), var(--sh-card);
}

/* The band brings its own top rule in the border, so .stats does not need a
   second one 20px inside it. */
.proof .stats { margin: 0; padding: 0; border-top: 0; }

/* These three are the whole point of the section, so they take the next step up
   the ladder (37.9, not 28.4) and the fire gradient the page already uses for
   numerals in .mcard__n. Same device, same ladder — no new size invented for
   the occasion. */
.proof .stats b {
  font-size: var(--fs-disp2);
  background-image: linear-gradient(140deg, var(--fire-sand) 0%, var(--fire-amber) 44%, var(--fire-orange) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.proof__note {
  /* 68ch, because the band is full-width and this sentence ran 124 characters a
     line inside it — nearly double the 90 ceiling, measured. */
  max-width: 68ch;
  margin: 1.6rem 0 0;
  padding-top: 1.4rem;
  border-top: 1px solid var(--rule);
  font-size: var(--fs-small);
  line-height: 1.72;
  color: var(--ink-soft);
}

.proof__src {
  margin: .85rem 0 0;
  font-size: var(--fs-micro);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ----------------------------------------------------------- 10a. THE TICKER
   Two rows travelling against each other, solid over outline. The counter-
   motion is what stops it reading as a stock marquee: the eye gets parallax
   from two speeds in opposite directions with nothing else on the band.

   The set is duplicated exactly once and the row translates -50%, so the loop
   is seamless by construction rather than by a tuned magic number. */

.ticker {
  position: relative;
  inset: auto;
  z-index: 2;
  padding: clamp(2rem, 5vw, 3.5rem) 0;
  border-block: 1px solid var(--rule);
  background: var(--ground-deep);
  overflow: hidden;
  /* The band fades out at both edges instead of being cut by them. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
  /* Its lower edge is a very shallow arc, absorbed by the band's own bottom
     padding so no word is ever touched. Same law as the nav strip: the page has
     no straight boundaries. */
  -webkit-clip-path: ellipse(190% 100% at 50% 0%);
  clip-path: ellipse(190% 100% at 50% 0%);
}

.ticker__row {
  display: flex;
  width: max-content;
  animation: ticker-l 44s linear infinite;
}

.ticker__row--rev {
  margin-top: clamp(.6rem, 1.4vw, 1.1rem);
  animation-name: ticker-r;
  animation-duration: 58s;
}

.ticker__set { display: flex; align-items: center; }

.ticker__set span {
  font-family: var(--disp);
  /* Daniel, 2026-08-12: "slightly smaller text." One step DOWN the same ladder,
     67.3 to 50.5 — a step, not an arbitrary shrink, so the band still relates
     to the type around it. */
  font-size: var(--fs-mid);
  font-weight: 300;
  line-height: 1.1;
  letter-spacing: -.03em;
  text-transform: lowercase;
  white-space: nowrap;
  background-image: linear-gradient(100deg, var(--fire-sand) 0%, var(--fire-amber) 46%, var(--fire-orange) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* The lower row is drawn, not filled — an outline behind the solid words, which
   is where the band's depth used to come from. Kept as a named recipe because
   the stack row overrides it and a future row may want it back. */
.ticker__row--rev .ticker__set span {
  background: none;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255,255,255,.26);
}

/* ⭐ THE STACK ROW, 2026-08-13. Tool names are INFORMATION, not display, so they
   get the opposite treatment to the row above: small, tracked, upper, quiet, and
   filled rather than stroked. Depth in the band now comes from the size and
   weight gap between the two rows instead of from the same words twice.

   It also runs slower than the display row (72s against 58s). Two rows moving at
   visibly different speeds is parallax; two rows moving at the same speed is a
   pattern, and a pattern is what the eye stops seeing. */
.ticker__row--stack {
  animation-duration: 72s;
  margin-top: clamp(1.1rem, 2.4vw, 1.9rem);
  opacity: .78;
}

.ticker__row--stack .ticker__set span {
  font-family: var(--sans);
  font-size: var(--fs-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .18em;
  line-height: 1;
  background: none;
  -webkit-text-stroke: 0;
  color: var(--ink-soft);
}

/* The dot shrinks with the type it separates. At the display row's 10px beside
   11px caps it would out-weigh the words it is meant to punctuate. */
.ticker__row--stack .ticker__set i {
  width: 5px; height: 5px;
  margin: 0 clamp(1rem, 2.2vw, 1.8rem);
  box-shadow: 0 0 8px rgba(240,73,35,.6);
}

/* The separator is the brand's own dot: a small lit sphere, not a bullet. */
.ticker__set i {
  flex: none;
  width: 10px; height: 10px;
  margin: 0 clamp(1.5rem, 3.5vw, 3rem);
  border-radius: 50%;
  background: radial-gradient(circle at 40% 36%, #FFD68C 0%, var(--fire-amber) 42%, var(--fire-orange) 74%, var(--fire-deep) 100%);
  box-shadow: 0 0 14px rgba(240,73,35,.7);
}

@keyframes ticker-l { to { transform: translateX(-50%); } }
@keyframes ticker-r { from { transform: translateX(-50%); } to { transform: translateX(0); } }

/* ---------------------------------------------------------- 10b. THE METHOD
   The reason the page exists. Five swipeable cards carrying one real build.

   Full-bleed track so the cards run off both edges — the page reads as wider
   than the window, which is the cheapest way to say "there is more here".
   Scroll-snap does the work on touch; pointer drag and the arrow keys do it on
   desktop. The card nearest the centre lifts and the rest step back, so the
   thing you are reading is the only thing lit. */

.sec--method {
  --pad-top: var(--sec-y);
  /* The rail carries its own 2.5rem of bottom padding for the snap shadow, and
     the controls no longer sit below it, so this section's foot is pure air.
     Measured at 1440 it was ~400px of black between the proof section and the
     services — a full screen on a laptop. A third of --sec-y comes off on top
     of the rail's own padding. */
  padding-bottom: calc(var(--sec-y) * .66 - 2.5rem);
  background: var(--ground);
}

/* The heading column takes the space the controls do not. Without flex:1 it
   sized to its own content and the display line broke into three. */
.sec--method__head > div { flex: 1 1 auto; min-width: 0; }
.sec--method__head .disp { max-width: 16ch; }

/* z-index 2 puts the rail above the crosshair. The hairline is a backdrop, and
   a backdrop drawn across the face of a card is just a scratch on the lens. */
.swipe { position: relative; inset: auto; z-index: 2; margin-top: clamp(2.5rem, 6vw, 4rem); }

.swipe__track {
  display: flex;
  gap: clamp(1rem, 2vw, 1.5rem);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: max(var(--gut), calc((100vw - var(--wrap)) / 2 + var(--gut)));
  padding: .5rem max(var(--gut), calc((100vw - var(--wrap)) / 2 + var(--gut))) 2.5rem;
  scrollbar-width: none;
  cursor: grab;
}

.swipe__track::-webkit-scrollbar { display: none; }
.swipe__track.is-dragging { cursor: grabbing; scroll-snap-type: none; }
.swipe__track.is-dragging * { pointer-events: none; }

.mcard {
  /* 470, not 400: at the 16px base a 400px card wraps body copy at 42
     characters, under the 45 floor. Width is free on a horizontal rail, so the
     measure sets the card size rather than the other way round. */
  flex: 0 0 min(86vw, 470px);
  scroll-snap-align: start;
  position: relative;
  inset: auto;
  display: flex;
  flex-direction: column;
  padding: 2.5rem 2rem 2.25rem;
  border: 1px solid rgba(255,255,255,.11);
  border-radius: var(--r-card);
  background:
    linear-gradient(165deg, rgba(255,255,255,.06) 0%, rgba(255,255,255,0) 44%),
    rgba(30,26,27,.62);
  -webkit-backdrop-filter: blur(22px) saturate(1.6);
  backdrop-filter: blur(22px) saturate(1.6);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14), var(--sh-card);
  transition:
    filter .6s var(--ease-rise),
    translate .6s var(--ease-rise),
    border-color .6s var(--ease-rise),
    box-shadow .6s var(--ease-rise);
}

/* The card nearest the start of the track is the one being read.

   The dim is a FILTER and the lift is the independent `translate` property, on
   purpose: `.rise` already owns opacity and transform on these cards for their
   entrance, and two systems writing the same property is how a state silently
   stops working. These two compose with it instead of fighting it. */
/* .84, not .62. The point of the dim is FOCUS, not concealment — at .62 the
   body copy of the cards he has not swiped to measured 3.10:1 against 7.62:1
   for the lit one, i.e. below the readable floor. This is the section the page
   exists for; nothing in it may be unreadable. */
.swipe__track .mcard { filter: brightness(.84) saturate(.95); }
.swipe__track .mcard.is-active {
  filter: none;
  translate: 0 -8px;
  border-color: var(--rule-warm);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.2), var(--sh-lift);
}

/* Without JS nothing is dimmed — all five read at full strength. */
html:not(.js) .swipe__track .mcard { filter: none; }

/* ---- The step head: the brand element and the numeral, on one line.

   Stacking them would have cost ~90px of height on every card, and the note
   this answers is a note about WASTED HEIGHT. Side by side they fill a row that
   was half empty anyway, and the numeral gets an object to be weighed against
   instead of floating alone at the top of a box. */
.mcard__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.1rem;
}

/* The element carries its own light. At 72px flat it reads as clip-art; with a
   fire bloom behind it, it reads as the lit object the brand book draws. */
.mcard__art {
  position: relative;
  inset: auto;
  flex: none;
  width: 76px; height: 76px;
}

.mcard__art::before {
  content: "";
  position: absolute;
  inset: -44%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(240,73,35,.34) 0%, rgba(157,28,32,.12) 46%, rgba(35,31,32,0) 72%);
  filter: blur(18px);
  opacity: .85;
  transition: opacity .55s var(--ease), transform .55s var(--ease);
}

.mcard:hover .mcard__art::before { opacity: 1; transform: scale(1.12); }

.mcard__art svg { position: relative; inset: auto; width: 100%; height: 100%; overflow: visible; }

/* The gradient lives in a zero-size SVG so it can be referenced by every icon
   without any one card owning it. Not display:none — a hidden SVG's defs are
   still resolvable, but several engines have shipped bugs where they are not. */
.svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.mcard__n {
  font-family: var(--disp);
  font-size: var(--fs-mid);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -.03em;
  background-image: linear-gradient(140deg, var(--fire-sand) 0%, var(--fire-amber) 44%, var(--fire-orange) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.mcard h3 {
  font-size: var(--fs-h3);
  font-weight: 500;
  letter-spacing: -.015em;
  line-height: 1.15;
  color: var(--white);
  margin-bottom: .85rem;
}

/* The desktop lede wrapper must not change the rail's body typography below
   901px, where these paragraphs previously sat directly inside the card. */
.mcard > p,
.mcard__lede > p {
  color: var(--ink-soft);
  font-size: var(--fs-small);
  line-height: 1.72;
}

.mcard__facts {
  /* Each rail card now owns its natural height, so there is no shared slack for
     an auto top margin to absorb. Padding supplies the intentional separation. */
  margin: 0;
  padding-top: 1.4rem;
  border-top: 1px solid var(--rule);
}

/* The term is set in fire, in sentence case — NOT as a tiny tracked uppercase
   kicker, which is the template pattern the house rule bans.
   ⭐ 2026-08-12: it used to be separated from the definition below it by being
   a different FAMILY. With one family on the page that separation was gone and
   the term read as the first line of its own answer, so the weight goes to 500.
   Same information, same colour, the distinction now carried by weight. */
.mcard__facts dt {
  font-family: var(--disp);
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: -.005em;
  color: var(--fire-amber);
  margin-bottom: .35rem;
}

.mcard__facts dd {
  margin: 0 0 1.15rem;
  color: var(--ink-soft);
  font-size: var(--fs-small);
  line-height: 1.7;
}
.mcard__facts dd:last-child { margin-bottom: 0; }

/* Discretion, stated. Bryson is hiring an operator, and an operator who will
   not publish a client's schedule is the point being made. */
/* ⭐ 2026-08-12: the italic is gone with the serif. Poppins italic is not in the
   loaded weights, so `font-style: italic` here would have been a SYNTHESISED
   slant — the browser shearing upright glyphs, which is the one thing that
   always looks like a mistake. The fire left-rule and the faint ink already
   mark this as an aside. */
.mcard__note {
  margin: 1.4rem 0 0;
  padding-left: .9rem;
  border-left: 1px solid var(--rule-warm);
  font-size: var(--fs-micro);
  letter-spacing: .04em;
  color: var(--ink-faint);
}

/* ---- The controls */

/* Heading left, controls right, on one baseline. */
.sec--method__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
}

.swipe__ui {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex: none;
  padding-bottom: .35rem;
}

.swipe__count {
  margin: 0;
  font-family: var(--disp);
  font-size: var(--fs-h3);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -.02em;
  white-space: nowrap;
}
.swipe__count b {
  font-weight: 500;
  background-image: linear-gradient(140deg, var(--fire-sand) 0%, var(--fire-amber) 44%, var(--fire-orange) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.swipe__count span { color: var(--ink-faint); }

.swipe__dots { display: flex; gap: .5rem; }

.swipe__dots i {
  display: block;
  width: 26px; height: 2px;
  border-radius: 2px;
  background: rgba(255,255,255,.16);
  transition: background .45s var(--ease), width .45s var(--ease);
}
.swipe__dots i.is-on { width: 46px; background: var(--grad-accent); }

.swipe__btns { display: flex; gap: .6rem; }

.swipe__btn {
  width: 46px; height: 46px;
  padding: 0;
  display: grid;
  place-items: center;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  background: rgba(255,255,255,.03);
  color: var(--ink-soft);
  cursor: pointer;
  transition: border-color .4s var(--ease), color .4s var(--ease), background .4s var(--ease), opacity .4s var(--ease);
}

.swipe__btn svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.swipe__btn:hover:not(:disabled) { color: var(--white); border-color: var(--rule-warm); background: rgba(240,73,35,.1); }
.swipe__btn:disabled { opacity: .3; cursor: default; }

/* -------------------------------------------------------------- 11. SERVICES */

/* TWO columns, explicitly, not auto-fit. The section carries four cards now
   instead of six: auto-fit at minmax(272px) would lay them out four-across at
   1440 and give each one a 260px measure — 34 characters, well under the 45
   floor — and 3+1 orphan at tablet width. Two columns holds the measure and
   pairs the cards the way the copy pairs them (founder / agency). */
.cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.5rem);
  margin-top: clamp(2.5rem, 6vw, 4.5rem);
}

/* Liquid glass — the corpus's single most reused premium element. The full
   recipe is four parts and only together do they read as glass: blur, a
   saturation lift so colour behind it survives, a 1px light border, and an
   inset top highlight that fakes the lit edge of a pane. The ambient glows sit
   behind these cards, so there is genuinely something to refract. */
.card {
  position: relative;
  inset: auto;
  padding: 2.25rem 1.75rem 2.15rem;
  border: 1px solid rgba(255,255,255,.11);
  border-radius: var(--r-card);
  background:
    linear-gradient(165deg, rgba(255,255,255,.06) 0%, rgba(255,255,255,0) 44%),
    rgba(30,26,27,.62);
  -webkit-backdrop-filter: blur(22px) saturate(1.6);
  backdrop-filter: blur(22px) saturate(1.6);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.14),
    var(--sh-card);
  overflow: hidden;
  transition: transform .55s var(--ease), border-color .55s var(--ease), box-shadow .55s var(--ease);
}

.card::after {
  content: "";
  position: absolute;
  top: -1px; left: 12%; right: 12%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--fire-orange), transparent);
  opacity: 0;
  transition: opacity .55s var(--ease);
}

.card:hover {
  transform: translateY(-6px);
  border-color: var(--rule-warm);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.2), var(--sh-lift);
}
.card:hover::after { opacity: 1; }

/* Hovering a card speeds its brand element up. The card feels like it is
   reacting to being touched rather than playing a loop near the cursor.
   ⚠️ RETARGETED 2026-08-12 from .card to .mcard: the six elements moved onto
   the method cards when services merged into the method, and these rules were
   left pointing at markup that no longer exists on the page. */
/* 01's hover speed-up lives with the lamp rules below, because it drives three
   animations rather than one. */
.mcard:hover .pulse-arcs path { animation-duration: 1.5s; }
.mcard:hover .bars rect  { animation-duration: 1.6s; }
.mcard:hover .tilt-ring  { animation-duration: 3.4s; }
.mcard:hover .globe      { animation-duration: 5.5s; }
.mcard:hover .fan rect   { animation-duration: 1.25s; }

/* The brand elements carry the card, so they get room and their own light.
   At 72px on a 340px card they read as clip-art; at 96px with a glow behind
   them they read as the objects the brand book draws them as. */
.card__art {
  position: relative;
  inset: auto;
  width: 96px; height: 96px;
  margin-bottom: 1.75rem;
}

.card__art::before {
  content: "";
  position: absolute;
  inset: -46%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(240,73,35,.34) 0%, rgba(157,28,32,.12) 46%, rgba(35,31,32,0) 72%);
  filter: blur(18px);
  opacity: .85;
  transition: opacity .55s var(--ease), transform .55s var(--ease);
}

.card:hover .card__art::before { opacity: 1; transform: scale(1.12); }

.card__art svg { position: relative; inset: auto; width: 100%; height: 100%; overflow: visible; }

.card h3 {
  font-size: var(--fs-h3);
  font-weight: 500;
  letter-spacing: -.01em;
  margin-bottom: .6rem;
  color: var(--white);
}

.card p { color: var(--ink-soft); font-size: var(--fs-small); line-height: 1.72; }

/* The client's own six brand elements, alive but never distracting */

/* ⭐ 01 IS A LAMP, NOT A PINWHEEL — 2026-08-13, Mano: "its a spot light but its
   rotating around, i want it to have a light source and then have the spot light
   flicker on and off."

   He is naming the rule the whole icon set is supposed to obey and this one
   broke: the motion has to be what the OBJECT does. A spotlight is bolted to
   something. Rotating it on its own centre was the generic loop applied to a
   thing that cannot perform it, and it read as a shape spinning rather than as
   light.

   Three parts now, and each moves for a different reason:
   · the LENS never goes fully out — it is the source, and a source you can see
     is what makes the rest read as switching rather than as fading;
   · the GLOW breathes slowly and independently, so the lamp is warm between
     strikes instead of dead;
   · the BEAM carries the flicker.

   The flicker timing is deliberately IRREGULAR. Evenly spaced blinks read as a
   pulse — decorative, and the eye tunes them out inside two cycles. A real
   contact stutters, catches, holds, and drops. The stops below are that: a
   double stutter at the strike, a long steady burn, one brief dropout near the
   end, then dark. 6.4s so it is never in phase with the arcs (3.4s), the ring
   (11s) or the globe (18s) — three elements striking together would read as a
   page-wide event rather than as six separate objects. */
/* ⛔ NO transform-box HERE. The lens carries an SVG presentation transform,
   rotate(-42 95 29), whose centre is stated in user units. Setting
   transform-box:fill-box re-bases that centre onto the element's own fill box
   and throws the lens clean off the lamp — caught in a blown-up render, and
   completely invisible at the 76px the icon actually ships at. Only opacity is
   animated here, so the box never needed changing. */
.lamp__lens { animation: lamp-lens 6.4s steps(1, end) infinite; }

.lamp__glow {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: lamp-glow 6.4s var(--ease-io) infinite;
}

.lamp__beam { animation: lamp-strike 6.4s steps(1, end) infinite; }

/* Hovering strikes it faster, the same law the other five obey. */
.mcard:hover .lamp__lens,
.mcard:hover .lamp__glow,
.mcard:hover .lamp__beam { animation-duration: 3.6s; }
.pulse-arcs path { transform-origin: 50% 74%; animation: elem-arc 3.4s var(--ease-io) infinite; }
.pulse-arcs path:nth-child(2) { animation-delay: .22s; }
.pulse-arcs path:nth-child(3) { animation-delay: .44s; }
.bars rect  { transform-origin: 50% 100%; animation: elem-bar 3.8s var(--ease-io) infinite; }
.bars rect:nth-child(2) { animation-delay: .1s; }
.bars rect:nth-child(3) { animation-delay: .2s; }
.bars rect:nth-child(4) { animation-delay: .3s; }
.bars rect:nth-child(5) { animation-delay: .4s; }
.bars rect:nth-child(6) { animation-delay: .5s; }
.bars rect:nth-child(7) { animation-delay: .6s; }
.tilt-ring  { transform-origin: 50% 50%; animation: elem-tilt 11s var(--ease-io) infinite alternate; }
.globe      { transform-origin: 50% 50%; animation: elem-globe 18s var(--ease-io) infinite alternate; }
.fan rect   { transform-origin: 50% 100%; animation: elem-fan 2.9s var(--ease-io) infinite; }
.fan rect:nth-child(odd)  { animation-delay: .14s; }
.fan rect:nth-child(3n)   { animation-delay: .28s; }

/* steps(1, end) on the beam and lens so every stop is a hard SWITCH. Tweening
   opacity between these values would give a soft dip, which is a dimmer, and a
   dimmer is not what he asked for. The glow is the one part that tweens, because
   a filament genuinely does hold its heat. */
@keyframes lamp-strike {
  0%,   6%  { opacity: 0; }        /* dark */
  7%,  10%  { opacity: 1; }        /* strike */
  11%, 13%  { opacity: .12; }      /* contact stutters */
  14%, 16%  { opacity: 1; }
  17%, 19%  { opacity: .3; }       /* and again, shorter */
  20%, 74%  { opacity: 1; }        /* the long steady burn */
  75%, 77%  { opacity: .18; }      /* one dropout late, so it is never a rhythm */
  78%, 88%  { opacity: 1; }
  89%, 100% { opacity: 0; }        /* out */
}

@keyframes lamp-lens {
  0%,   6%  { opacity: .38; }
  7%,  88%  { opacity: 1; }
  89%, 100% { opacity: .38; }
}

@keyframes lamp-glow {
  0%,   6%  { opacity: .16; transform: scale(.9); }
  20%, 74%  { opacity: 1;   transform: scale(1); }
  89%, 100% { opacity: .16; transform: scale(.9); }
}
@keyframes elem-arc   { 0%,100% { opacity: .45; transform: scale(.94); } 50% { opacity: 1; transform: scale(1); } }
@keyframes elem-bar   { 0%,100% { transform: scaleY(.82); } 50% { transform: scaleY(1); } }
@keyframes elem-tilt  { from { transform: rotate(-6deg) scale(.96); } to { transform: rotate(6deg) scale(1.02); } }
@keyframes elem-globe { from { transform: rotate(-8deg); } to { transform: rotate(8deg); } }
@keyframes elem-fan   { 0%,100% { transform: scaleY(.7); opacity: .7; } 50% { transform: scaleY(1); opacity: 1; } }

/* ------------------------------------------------------------------ 12. WORK */

/* Four consecutive sections were four grids of identically-ordered rounded
   rectangles. One of them has to disobey, or the eye reads "template" — so the
   first work frame spans two columns and is landscape while the other two stay
   portrait. */
.tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.5rem);
  margin-top: clamp(2rem, 5vw, 3.5rem);
}

.tile {
  position: relative;
  inset: auto;
  display: block;
  grid-column: span 1;
  aspect-ratio: 4 / 5;
  border-radius: var(--r-card);
  overflow: hidden;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.1), var(--sh-card);
  transition: transform .6s var(--ease), box-shadow .6s var(--ease);
}

.tile__img {
  position: absolute;
  inset: 0;
  transition: transform 1.05s var(--ease), filter .6s var(--ease);
  will-change: transform;
}

/* ---- The long exposure.
   Deck p.2 is a walking figure smeared by a slow shutter. There is no
   photography yet, so the same LANGUAGE is built out of light instead: a lit
   body, a directional streak pulled across it, and a horizontal blur so the
   whole frame reads as one long-shutter frame rather than a gradient. */
.tile__img::after {
  content: "";
  position: absolute;
  inset: -10% -30% 20%;
  background: var(--streak, none);
  filter: blur(var(--streak-blur, 6px));
  opacity: .72;
  mix-blend-mode: screen;
  transition: transform 1.4s var(--ease);
}

.tile:hover .tile__img::after { transform: translateX(5%); }

/* A hairline of light along the top edge — the lit rim of the deck's frames. */
.tile__img::before {
  content: "";
  position: absolute;
  top: 0; left: 8%; right: 8%;
  height: 1px;
  z-index: 2;
  background: linear-gradient(90deg, transparent, rgba(255,214,140,.7), transparent);
}

/* The scrim. It is doing legibility work, not mood work — the caption sits on
   it, so it has to be strong enough that a bright streak underneath can never
   take the type with it. */
.tile::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(6,4,5,0) 0%, rgba(6,4,5,0) 44%, rgba(6,4,5,.72) 74%, rgba(6,4,5,.95) 100%);
  z-index: 2;
}

.tile:first-child { grid-column: span 2; aspect-ratio: 8 / 5; }

.tile:hover { transform: translateY(-6px); box-shadow: var(--sh-lift); }
.tile:hover .tile__img { transform: scale(1.055); filter: saturate(1.12) brightness(1.06); }

/* Three graded frames. There is no photography yet and pretending otherwise
   produces blur that reads as a missing image — so these are built as GRADED
   FRAMES instead: a near-black field, one hot source, and a hard light trail
   across it. Definition, not haze. When the real stills land they drop
   straight into .tile__img and everything else here still holds. */

/* Each frame is lit from a different side by a different temperature, and each
   beam runs at its own angle. Three tiles that share one recipe read as one
   asset repeated; the whole job here is that they read as three frames. */

.t1 {
  --streak: linear-gradient(114deg, rgba(255,214,140,0) 33%, rgba(255,182,120,.8) 43%, rgba(255,214,140,0) 52%);
  --streak-blur: 6px;
  background:
    radial-gradient(ellipse 46% 40% at 72% 30%, rgba(255,140,60,.9) 0%, rgba(231,34,40,.78) 26%, rgba(157,28,32,.34) 54%, rgba(6,4,5,0) 78%),
    linear-gradient(158deg, #9D2114 0%, #58120F 26%, #1C090A 62%, #060405 100%);
}

.t2 {
  --streak: linear-gradient(78deg, rgba(255,232,200,0) 30%, rgba(255,238,206,.72) 41%, rgba(255,232,200,0) 51%);
  --streak-blur: 9px;
  background:
    radial-gradient(ellipse 44% 38% at 26% 70%, rgba(255,196,96,.92) 0%, rgba(248,149,33,.72) 28%, rgba(193,8,1,.3) 56%, rgba(6,4,5,0) 80%),
    linear-gradient(206deg, #A05C16 0%, #4B2A0E 28%, #1A100A 62%, #060405 100%);
}

.t3 {
  --streak: linear-gradient(96deg, rgba(255,214,140,0) 34%, rgba(255,206,150,.78) 44%, rgba(255,214,140,0) 53%);
  --streak-blur: 4px;
  background:
    radial-gradient(ellipse 42% 36% at 62% 68%, rgba(255,120,50,.92) 0%, rgba(240,73,35,.8) 26%, rgba(157,28,32,.3) 56%, rgba(6,4,5,0) 80%),
    linear-gradient(142deg, #C13418 0%, #661610 24%, #200A0B 60%, #060405 100%);
}

/* A house numeral pressed into the frame. It gives the field a subject, which
   is the thing a gradient on its own never has. */
.tile__ghost {
  position: absolute;
  top: .35em; right: .3em;
  z-index: 2;
  font-family: var(--disp);
  font-size: var(--fs-hero);
  line-height: .8;
  letter-spacing: -.03em;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255,255,255,.16);
  pointer-events: none;
  transition: transform 1s var(--ease), -webkit-text-stroke-color .6s var(--ease);
}

.tile:hover .tile__ghost { transform: translateY(-6px); -webkit-text-stroke-color: rgba(255,214,140,.42); }

.tile__cap {
  position: absolute;
  inset: auto auto 0 0;
  z-index: 3;
  width: 100%;
  padding: 1.75rem 1.5rem 1.6rem;
  display: flex;
  flex-direction: column;
  gap: .35rem;
}

.tile__cap .disp { font-size: var(--fs-disp2); line-height: 1.02; text-transform: none; }

/* The category runs INTO the sentence instead of floating above the name as a
   kicker. Same information, no eyebrow. */
.tile__cap p i {
  font-family: var(--disp);
  font-style: normal;
  color: var(--fire-amber);
}

.tile__cap p {
  margin: .35rem 0 0;
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-soft);
  max-width: 30ch;
}

/* ---- The roster.
   Five seats on one hairline rule. Deliberately plain: a list that tries to be
   clever stops reading as a payroll, and the payroll is the point. */

/* ONE column since the tools list came off (Daniel, 2026-08-12). Two columns
   with one of them deleted is a half-empty band; at full width the five seats
   sit on a single line and read as a payroll, which is the whole point of the
   block. */
.roster {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  margin-top: clamp(2.5rem, 6vw, 4rem);
  padding-top: 1.75rem;
  border-top: 1px solid var(--rule);
}

.roster__note {
  grid-column: 1 / -1;
  margin: .5rem 0 0;
  font-size: var(--fs-small);
  color: var(--ink-faint);
}

/* `.founder` lived here — Daniel's name and title under the About copy. Deleted
   with its markup on his note "Maybe remove my name from about us"
   (2026-08-12). No person is named on the page before the contact section now. */

/* 500 for the same reason as the fact terms: this label used to be a different
   family from the pills under it, and now it is not. */
.roster__label {
  display: block;
  font-family: var(--disp);
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--ink-faint);
  margin-bottom: 1.1rem;
}

.roster ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: .6rem .5rem;
  margin: 0;
  padding: 0;
}

.roster li {
  padding: .5rem 1.05rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  background: rgba(255,255,255,.035);
  font-size: var(--fs-small);
  color: var(--ink-soft);
  transition: border-color .45s var(--ease), color .45s var(--ease), background .45s var(--ease);
}

.roster li:hover { border-color: var(--rule-warm); color: var(--white); background: rgba(240,73,35,.1); }

/* The glass recipe, a fifth and sixth time. */
.roster li, .swipe__btn {
  -webkit-backdrop-filter: blur(10px) saturate(1.3);
  backdrop-filter: blur(10px) saturate(1.3);
}

/* THE BOOKING WINDOW is a POPUP as of 2026-08-13, so this file no longer sizes
   it at all. Calendly owns its own overlay dimensions on every device.

   The inline rules that lived here were deleted, not commented out. They set a
   fixed height for a widget whose layout switches on its container width, and
   this panel measures 348px / 646px / 606px at 390 / 720 / 1440 — never the
   ~700px where Calendly stops stacking. The height was guessed twice and was
   wrong twice, because assets.calendly.com is blocked in every browser on the
   build machine and the widget could never be looked at. Reasoning, full
   history and how to revert: the comment above .contact__actions in index.html.

   ⛔ Do not reintroduce a height here without seeing the widget render. */

@media (max-width: 640px) {
  /* ⛔ The 720px that used to be here was the bug Daniel saw. Calendly stacks
     its month above its times at this width and the stacked layout is about
     1000px tall, so a 720px box cut roughly a third off the bottom of it. The
     base rule now carries the stacked height and this line is gone rather than
     re-stated, because two heights for one layout is how it went wrong. */
  /* On a phone the calendar is taller than the screen, so a visitor who lands
     mid-embed sees only calendar. The button and the direct lines come FIRST
     at this width; the embed is underneath for anyone who wants to pick a slot
     without leaving the page. */
  .contact__panel { display: flex; flex-direction: column; }
  .contact__actions { order: 1; }
  .contact__direct { order: 3; }
  .contact__founder { order: 4; }
}

.contact__founder {
  margin: .65rem 0 0;
  font-size: var(--fs-small);
  color: var(--ink-soft);
}
.contact__founder a {
  color: var(--fire-amber);
  border-bottom: 1px solid rgba(248,149,33,.4);
  transition: color .35s var(--ease), border-color .35s var(--ease);
}
.contact__founder a:hover { color: #FFD68C; border-color: #FFD68C; }

/* THE BOOKING BLOCK. Daniel's primary action, so it is the only filled button
   in the section and the email line under it is deliberately quieter type.
   Column, not row: at the panel's width a button and a sentence side by side
   squeezed the sentence to a 30-character measure. */
/* ⭐ CENTRED, 2026-08-13: "center call button" (Daniel). The panel's text stays
   left-aligned; only the action centres, so the button reads as the one thing
   the section is asking for rather than as the start of another column. */
.contact__actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .9rem;
}

.contact__hint {
  margin: 0;
  font-size: var(--fs-small);
  color: var(--ink-soft);
}

.contact__direct {
  margin: 1.25rem 0 0;
  font-size: var(--fs-small);
  color: var(--ink-soft);
}
.contact__direct a {
  color: var(--fire-amber);
  border-bottom: 1px solid rgba(248,149,33,.4);
  transition: color .35s var(--ease), border-color .35s var(--ease);
}
.contact__direct a:hover { color: #FFD68C; border-color: #FFD68C; }
.contact__direct span { margin: 0 .5rem; color: var(--ink-faint); }

/* Scrollspy: the nav says where you are, not only where you last hovered. */
.nav__links a.is-current { color: var(--white); }
.nav__links a.is-current::after { transform: scaleX(1); }

/* --------------------------------------------------------------- 13. PROCESS */

/* Four steps, four columns. auto-fit at minmax(238px) landed on three and
   orphaned "distribute" onto a row of its own, which read as a mistake because
   it was one. The count is known, so it is stated. */
.steps {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
  margin: clamp(2.5rem, 6vw, 4rem) 0 0;
  padding: 0;
}

/* Process was the last block on the page still wearing a hairline and nothing
   else, while every other repeated block — 6 service cards, 5 method cards, 3
   work tiles — carried the full glass recipe. It is the same recipe again, not
   a new one: the page already proved it three times. */
.steps li {
  position: relative;
  inset: auto;
  padding: 2.25rem 1.75rem 2rem;
  border: 1px solid rgba(255,255,255,.11);
  border-radius: var(--r-card);
  background:
    linear-gradient(165deg, rgba(255,255,255,.06) 0%, rgba(255,255,255,0) 44%),
    rgba(30,26,27,.62);
  -webkit-backdrop-filter: blur(22px) saturate(1.6);
  backdrop-filter: blur(22px) saturate(1.6);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14), var(--sh-card);
  overflow: hidden;
  transition: transform .55s var(--ease), border-color .55s var(--ease), box-shadow .55s var(--ease);
}

.steps li:hover {
  transform: translateY(-6px);
  border-color: var(--rule-warm);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.2), var(--sh-lift);
}

/* Four numeral systems on one page were speaking four languages: the marker is
   an outline gradient, .mcard__n a filled gradient, .tile__ghost an outline —
   and this one was a tiny tracked label. Now it is the filled gradient too. */
.steps b {
  display: block;
  font-family: var(--disp);
  font-size: var(--fs-disp2);
  font-weight: 400;
  letter-spacing: -.02em;
  line-height: 1;
  background-image: linear-gradient(140deg, var(--fire-sand) 0%, var(--fire-amber) 44%, var(--fire-orange) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  margin-bottom: .6rem;
}

.steps h3 {
  font-family: var(--disp);
  font-weight: 400;
  font-size: var(--fs-disp2);
  text-transform: lowercase;
  color: var(--white);
  margin-bottom: .55rem;
}

.steps p { color: var(--ink-soft); font-size: var(--fs-small); line-height: 1.72; }

/* --------------------------------------------------------------- 14. CONTACT */

/* The closing frame, black like the opening. The page starts and ends in the
   dark and only the middle is lit — the deck's own rhythm. */
.sec--contact {
  --pad-top: clamp(7rem, 15vw, 12rem);
  padding-block: var(--pad-top);
  /* The closing frame arrives as a curved plate laid over the page rather than
     as a rectangle butted against the section above it. The cut is ~7% of the
     section's height at the corners and the top padding is far deeper than
     that, so nothing inside is ever clipped. */
  -webkit-clip-path: ellipse(140% 100% at 50% 100%);
  clip-path: ellipse(140% 100% at 50% 100%);
  background: linear-gradient(180deg, var(--ground-deep) 0%, var(--black) 26%, var(--black) 100%);
}

.contact__sphere {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  z-index: 0;
  pointer-events: none;
  opacity: .55;
}

.sphere--sm { width: min(120vw, 900px); }

/* 720, not 640: at the ladder's 89.7px display size "your attention." is wider
   than 640 and broke to a third line with one word stranded on it. */
.contact__inner { max-width: 720px; text-align: center; }
.contact__h { margin-bottom: 1.1rem; }
/* The one line still sitting on bare glow. At --ink-soft it measured 4.07:1;
   full --ink plus a dark halo clears the floor without needing another plate. */
.contact__inner > p {
  color: var(--ink);
  margin-bottom: 2.75rem;
  text-shadow: 0 1px 12px rgba(0,0,0,.75), 0 0 3px rgba(0,0,0,.6);
}

/* The form gets its own dark plate.
   The breathing sphere behind this section composites the ground up to roughly
   rgb(133,68,28), so grey-on-charcoal became tan-on-tan: measured, the
   placeholders ran 1.75:1 against a 4.5 floor and every label and the button
   text failed too. The glow is the best thing in the section and it stays —
   it just becomes a halo BEHIND the plate instead of a wash across the type. */
/* The plate belongs to the whole block, not just the form. The direct-contact
   line sat outside it on bare glow and measured 3.49:1 — the same failure the
   plate exists to fix, one element further down. */
.contact__panel {
  /* Held in a variable because the booking window has to CANCEL this padding to
     get its width back. Two places that must agree are one place waiting to
     disagree. */
  padding: 2rem 1.75rem;
  border: 1px solid rgba(255,255,255,.1);
  border-radius: 24px;
  background: rgba(6,5,5,.82);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  backdrop-filter: blur(18px) saturate(1.3);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.1), var(--sh-card);
  text-align: left;
}

/* The form and its fields were deleted 2026-08-12 with the form itself. Kept
   nothing behind: unused input styling is the kind of dead weight that later
   gets "restored" by someone who assumes it is load-bearing. */

/* ------------------------------------------------- 15. THE SIGNATURE
   ⭐ THERE IS NO FOOTER, since 2026-08-13. Daniel: "the black background footer
   doesnt look good", and he dragged the wordmark into the contact section's
   bottom-right corner to show where he wanted it instead.

   He was right about why it failed. A separate black band meant the page ENDED
   TWICE: once on the fire glow that closes the design, and again on a flat
   rectangle beneath it that undid the exit. The signature now sits inside the
   closing section, so the page finishes on its own light.

   Everything the old footer carried is gone with it: the ember tiling pattern,
   the tagline, the nine flickering orbs, and "Built in the Philippines by
   Filipinos". All of it was built to his instruction and removed on his
   instruction. */

/* ⭐ 2026-08-13: this is now a sibling of .contact__inner and carries `wrap`,
   so it right-aligns to the page's 1180px edge (the nav pill's edge) instead of
   to the 720px reading column. Right-aligned inside 720px read as "slightly off
   centre"; against the page edge it reads as a corner, which is what was asked
   for. */
.sign {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .5rem;
  /* Far enough below the panel to read as a signature on the section rather
     than as another line of the contact block. */
  margin-top: clamp(4rem, 9vw, 7rem);
  text-align: right;

  /* ⭐ FLUSH TO THE CORNER — Mano, 2026-08-13 04:2x: "as far as possible while
     still remaining visible."

     Two overrides, because `wrap` is built to hold a reading column and this is
     not one:
     1. max-width: none + a tiny inline pad, so the wordmark rides the VIEWPORT's
        right edge instead of the 1180px content edge.
     2. A negative bottom margin that eats the section's bottom padding
        (--pad-top is used on both blocks) down to one small gap.

     The gap is the "still remaining visible" half of the instruction: type that
     touches a viewport edge reads as clipped on any browser with a rounded
     corner or an overlay scrollbar, and on iOS it lands under the home
     indicator. clamp() keeps it ~18px on a phone and ~30px on a desktop —
     hugging the corner without sitting on it. */
  max-width: none;
  padding-inline: clamp(1.1rem, 2.1vw, 1.9rem);
  margin-bottom: calc(clamp(1.1rem, 2.1vw, 1.9rem) - var(--pad-top));
}

.sign__logo {
  /* Deliberately smaller than the nav wordmark. A signature is an initial at
     the bottom of a page, not a second logo competing with the header. */
  width: clamp(104px, 11vw, 132px);
  opacity: .9;
}

.sign__meta {
  margin: 0;
  font-size: var(--fs-micro);
  letter-spacing: .1em;
  color: var(--ink-faint);
}

/* ------------------------------------------------------- 15b. THE CURSOR GLINT
   The only thing on this page that reacts to where the cursor is WITHIN an
   element. Everything else tracks it relative to a container, which is a
   coarser, less personal thing. A light that follows the hand plus 3.5 degrees
   of tilt is the detail nobody can name afterwards and everybody feels: the
   page noticed you.

   One recipe, fourteen elements. Desktop pointers only — the JS never binds
   under a fine pointer, so touch is satisfied by construction and not by a
   media query bolted on afterwards.

   3.5deg is deliberately a whisper. A showroom tilt is how this becomes a
   gimmick; the fix for it reading slow is a shorter transition, never a
   bigger angle. */

.cards, .swipe__track, .tiles, .steps { perspective: 1200px; }

.card, .mcard, .tile, .steps li {
  --gx: 50%;
  --gy: 50%;
  --tilt: rotateX(0deg) rotateY(0deg);
}

.card::before,
.mcard::before,
.tile::before,
.steps li::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: radial-gradient(320px circle at var(--gx) var(--gy), rgba(248,149,33,.18), transparent 60%);
  opacity: 0;
  transition: opacity .4s var(--ease);
  pointer-events: none;
}

.card:hover::before,
.mcard:hover::before,
.tile:hover::before,
.steps li:hover::before { opacity: 1; }

.mcard { overflow: hidden; }

/* The tilt joins the `transform` lane. The method rail's active-card lift owns
   `translate` precisely so the two can never clobber each other — see the note
   at .swipe__track .mcard.is-active. */
.card:hover     { transform: translateY(-6px) var(--tilt); }
.tile:hover     { transform: translateY(-6px) var(--tilt); }
.steps li:hover { transform: translateY(-6px) var(--tilt); }
.swipe__track .mcard.is-active { transform: var(--tilt); }

/* ---------------------------------------------------------- 16. SCROLL REVEAL */

/* Scoped to .js on purpose. No JavaScript means no hidden content — the page
   paints complete rather than blank. The reveal is the enhancement. */
.js .rise {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity .85s var(--ease-rise) var(--rise-d, 0ms),
    transform .85s var(--ease-rise) var(--rise-d, 0ms);
  will-change: opacity, transform;
}

.js .rise.is-in { opacity: 1; transform: none; }

/* ============================================================================
   17. RESPONSIVE — every @media block lives here, at the end of the file.
   A media rule placed earlier is silently overridden by later base rules.
   ========================================================================= */

/* ---------------------------------------------- THE METHOD, DESKTOP: A STACK
   Above 900px the rail becomes a vertical stack. Reason, stated so nobody
   "restores" it later: a rail conceals by design, and this is the section the
   whole page exists for. At 1440 the reader saw one lit card, one dimmed one,
   and three off-screen. Nothing here is hidden, nothing has to be discovered,
   and no control has to be pressed.

   Each card turns into two columns — the argument on the left, the facts on
   the right against a hairline — so five stacked cards read as a ledger
   rather than as five identical boxes in a tower.

   The rail (and its snap, drag, keys and dots) survives below 900px, where it
   is genuinely the better gesture. JS reads the same 900px breakpoint. */
@media (min-width: 901px) {
  .swipe__ui { display: none; }

  /* ⭐ TWO UP, THEN FULL WIDTH (Daniel, 2026-08-12: "lay it out so it doesn't
     waste a lot of screen space as it is currently").

     It was one card per row, every row split into two columns — and only the
     last two cards carry a fact list, so cards 01 to 04 each printed a 573px
     column of nothing. 54% of four cards, measured.

     Now the four setup steps sit two-up as compact cards, and the two steps
     that carry a ledger take the full width to print it. That is the content's
     own shape (four decisions, then the two that run every week), it removes
     the empty columns rather than filling them with air, and it takes roughly
     600px of scroll out of the section. */
  .swipe__track {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(1rem, 1.6vw, 1.4rem);
    overflow: visible;
    scroll-snap-type: none;
    /* Same horizontal padding as the rail so the cards line up with the
       heading above them; the vertical padding was there for the snap
       shadow and has nothing to hold now. */
    padding-block: 0;
    cursor: default;
  }

  .swipe__track .mcard {
    flex: initial;
    filter: none;
    translate: none;
    padding: 2.25rem 2.25rem 2.15rem;
  }

  /* A card that carries a ledger takes the whole row and splits it: the
     argument left, the facts right against a hairline. `:has()` rather than
     nth-child so the six steps can be reordered without the layout silently
     landing on the wrong pair. */
  .swipe__track .mcard:has(.mcard__facts) {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
    grid-template-rows: auto auto;
    align-content: start;
    align-items: center;
    column-gap: clamp(2rem, 4vw, 3.5rem);
    padding: 2.25rem 2.5rem 2.15rem;
  }

  /* The argument spans the ledger rows so its shorter content is centred
     against the full right column, rather than leaving its slack at the foot. */
  .swipe__track .mcard:has(.mcard__facts) .mcard__lede {
    grid-column: 1;
    grid-row: 1 / -1;
  }

  .swipe__track .mcard__facts {
    grid-column: 2;
    grid-row: 1;
    margin: 0;
    padding: 0 0 0 clamp(2rem, 4vw, 3.5rem);
    border-top: 0;
    border-left: 1px solid var(--rule);
  }

  /* The facts already draw this column's hairline, so the note must not print
     a second rule at the same horizontal position on desktop. */
  .swipe__track .mcard__note {
    grid-column: 2;
    grid-row: 2;
    margin-top: 1.2rem;
    padding-left: clamp(2rem, 4vw, 3.5rem);
    border-left: 0;
  }

  /* The hover tilt is a rail affordance: on a full-width stacked row a 3.5deg
     tilt reads as a rendering fault, not as depth. */
  .swipe__track .mcard.is-active,
  .swipe__track .mcard:hover { transform: none; }
}

@media (max-width: 1024px) {
  .about__grid { grid-template-columns: 1fr; }
  .hero__sphere { right: -22%; opacity: .82; }
  .marker { opacity: .34; }
  /* One column means the mark would sit between the heading and the copy,
     pushing the words that matter below the fold. It goes. */
  .about__mark { display: none; }
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .marker { opacity: .22; }
}

@media (max-width: 1220px) {
  /* No gutter left to sit in: the numeral would print through the heading. It
     goes ABOVE the heading instead, at a POSITIVE offset — Daniel's "cut off"
     complaint was a negative inset, and it was wrong here too. */
  .marker {
    left: var(--gut);
    top: calc(var(--pad-top, var(--sec-y)) - 7rem);
  }
  .marker__num { width: 96px; }
}

@media (max-width: 900px) {
  /* One card is read at a time here, so matching card 06 would leave the
     shorter cards mostly empty without helping the swipe interaction. */
  .swipe__track { align-items: flex-start; }

  .nav__links {
    position: absolute;
    inset: 100% 0 auto 0;
    flex-direction: column;
    gap: 0;
    padding: .5rem var(--gut) 1.25rem;
    margin: 0;
    background: rgba(11,10,10,.97);
    -webkit-backdrop-filter: blur(18px);
    backdrop-filter: blur(18px);
    border-radius: 0 0 var(--r-lg) var(--r-lg);
    border-bottom: 1px solid var(--rule);
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    transition: opacity .4s var(--ease), transform .4s var(--ease), visibility .4s;
  }
  .nav__links.is-open { opacity: 1; visibility: visible; transform: translateY(0); }
  .nav__links a { padding: .95rem 0; border-bottom: 1px solid var(--rule); font-size: var(--fs-body); }
  .nav__links a::after { display: none; }
  /* ⭐ THE MENU BUTTON GOES RIGHT (Mano, 2026-08-13: "the menu bar should be on
     the right side of the nav bar"). At this width the only two visible flex
     children are the wordmark and this button, and with nothing pushing them
     apart the button sat tucked against the logo, which read as part of the
     wordmark rather than as a control. `margin-left: auto` throws it to the far
     edge, which is also where a thumb expects it. */
  .nav__burger { display: flex; margin-left: auto; }
  .nav__inner > .btn--sm { display: none; }
  /* The dropdown is absolutely positioned at this width, so its auto margin no
     longer does anything and the button above owns the spacing instead. */
  .nav__links { margin-left: 0; }

  /* Four columns is a phone-width column at tablet size. Two, with the first
     frame still breaking the grid in landscape. */
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tile:first-child { grid-column: span 2; aspect-ratio: 16 / 9; }

  .hero__sphere { top: 26%; right: -34%; opacity: .7; }
  .hero__h1 { max-width: 100%; }
  .arc { width: 260vw; height: 96vw; }
  .arc--bottom { bottom: -74vw; }
  .arc--top { top: -78vw; }
}

/* ⛔ A WIDE-LAYOUT RULE WAS WRITTEN HERE AND THEN DELETED, 2026-08-13. Recording
   why, because it is an easy mistake to make twice.

   The plan was: above 820px of WINDOW, give the booking window the shorter
   side-by-side height. Then the container was actually measured:

     390px window  panel 350px  booking window 348px
     720px window  panel 648px  booking window 646px
    1440px window  panel 608px  booking window 606px

   The panel does not keep growing with the window, it settles near 608px. So at
   1440 the container is NARROWER than at 720, and it never reaches the ~700px
   where Calendly stops stacking. A window-width media query was answering a
   question about container width, and it would have put the tall stacked layout
   back into a short box on desktop, which is the exact defect being fixed.

   So: ONE layout, ONE height, every width. The base rule owns it. */

@media (max-width: 640px) {
  /* Two service cards side by side at phone width is a 42-character measure. */
  .cards { grid-template-columns: 1fr; }
  .stats, .stats--two { grid-template-columns: 1fr; gap: 1.4rem; }
  /* left/top are set in the 900px block above and must NOT be re-overridden
     here — a later rule at equal specificity would put the numeral back inside
     the reading column, which is the exact bug that block exists to fix. */
  .marker { gap: .75rem; }
  .hero__scroll { display: none; }
  .tile__cap { padding: 1.35rem 1.15rem 1.25rem; }
  .contact__inner { text-align: left; }
  /* The footer is a row with the wordmark in one corner and the legal line in
     the other. At phone width there are no two corners, so it stacks left. */
  /* ⭐ 2026-08-13: the old rule stacked this LEFT on a phone, and it was right
     to at the time — the signature then lived inside the 720px reading column,
     where right-aligned type reads as a mistake rather than as a corner. It is
     now flush to the viewport's own edge, which IS a corner at every width, so
     it stays right on a phone too. Mano's instruction was the corner, not the
     desktop. */

  .steps { grid-template-columns: 1fr; }
  .roster { grid-template-columns: 1fr; }
  .tiles { grid-template-columns: 1fr; }
  .tile:first-child { grid-column: span 1; aspect-ratio: 4 / 5; }
  /* The crosshair's vertical rule is a wide-screen device. On a phone it is a
     line through the middle of the reading column. */
  .sec--method__head { flex-direction: column; align-items: flex-start; gap: 1.75rem; }
  .swipe__ui { padding-bottom: 0; }
  .ticker { padding-block: clamp(1.5rem, 5vw, 2.25rem); }
}

@media (max-width: 420px) {
  .cards { grid-template-columns: 1fr; }
  .tiles { grid-template-columns: 1fr; }
  .tile:first-child { grid-column: span 1; aspect-ratio: 4 / 5; }
  .hero__cta { flex-direction: column; align-items: flex-start; gap: .35rem; }
  .hero__cta .btn { width: 100%; }
  .card { padding: 1.6rem 1.35rem 1.75rem; }
}

/* Motion is a preference, not a requirement. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js .rise { opacity: 1; transform: none; transition: none; }
  .js .sec::after { transform: none; transition: none; }

  /* The molten sweep becomes a static gradient, not a flat colour — the brand
     survives, only the movement stops. */
  h2.disp, .disp--shine { animation: none; background-position: 50% center; }

  .hero__h1 .line b,
  .hero__sub, .hero__cta, .hero__scroll { animation: none; }

  .grain,
  .sphere__core, .sphere__mid, .sphere__halo,
  .hero__scroll i,
  .ticker__row,
  /* A flashing light is close to the top of the list of things this media query
     exists to stop, so the lamp is pinned fully ON rather than slowed. None of
     the three parts declares an opacity outside its keyframes, so killing the
     animation leaves all three at 1 — the element reads as a lamp that is simply
     switched on, which is the correct still frame for it. */
  .pulse-arcs path, .bars rect, .tilt-ring, .globe, .fan rect,
  .lamp__lens, .lamp__glow, .lamp__beam {
    animation: none !important;
  }
  .lamp__glow { transform: none !important; }
  * { transition-duration: .01ms !important; }
}
