

    /* ════ HERO — Figma node 1:27 (1440×1024, pinned; next section scrolls over it) ════ */
    .hero {
      position: sticky;
      top: 0;
      height: 100vh;
      overflow: hidden;
      z-index: 1;
      /* base radial gradient (from Figma hero fill) — stops flip to the night sky via tokens */
      background: radial-gradient(ellipse 1000px 1340px at 68.65% 23.5%, var(--hero-from) 0%, var(--hero-to) 100%);
    }

    /* background shader export (node 1:28 — gradient mesh) */
    .hero-shader {
      position: absolute;
      inset: 0;
      width: 100%; height: 100%;
      object-fit: cover;
      pointer-events: none;
    }

    /* day cirrus overlay — the counterpart to .hero-stars at night. Same trick as
       the starfield: the tile carries ONLY the feature (cream wisps on transparent,
       high-passed out of the source photo) and screen-blends it on. Compositing the
       raw photo instead did nothing visible — its own blue cancelled the shader's,
       and the wisps sit just 6–14% above their local sky, so any blend at a sane
       opacity flattened them back into the gradient. */
    .hero-sky {
      position: absolute;
      /* 10% of overscan each side, NOT inset:0 — the gust below slides this layer
         horizontally, and a full-bleed layer would drag a bare edge into view.
         .hero is overflow:hidden, so the spill costs nothing. */
      inset: 0 -10%;
      background-image: url("../images/hero-sky-day.webp");
      background-size: cover;
      /* bias up: the densest wisps are in the photo's lower half, and the hero's
         own bottom is where the headline and recorder sit */
      background-position: center 35%;
      background-repeat: no-repeat;
      mix-blend-mode: screen;
      opacity: 0.6;
      pointer-events: none;
    }

    /* wind gust — plays once when the theme flips night → day. A gust decays, so
       this is a hard front-loaded ease into rest, with no overshoot: a spring
       curve here would read as elastic, which air is not. One-shot on purpose —
       a permanently drifting sky turns the still hero into a video loop. */
    @keyframes hero-sky-gust {
      0%   { transform: translate3d(-7%, 0, 0); opacity: 0.34; }
      55%  { opacity: 0.6; }
      100% { transform: translate3d(0, 0, 0);   opacity: 0.6; }
    }

    /* noise overlay (node 1:32 — opacity 10%, 2048px tile) */
    .hero-noise {
      position: absolute;
      inset: 0;
      background-image: url("../images/imgNoiseTexture.png");
      background-size: 2048px 2048px;
      opacity: 0.1;
      pointer-events: none;
    }

    /* sun group (sun.svg, node 1:69 — glow + hatched sun, natural 601×545).
       The whole sky group (sun + both clouds) is sized from one shared var
       --sun-w (set on .hero-content) so it scales CONTINUOUSLY with the
       viewport — every dimension/offset is a fixed ratio of --sun-w, so the
       sun and clouds keep their relative positions (they stay a group) and
       there is no sudden jump at a breakpoint. Ratios are taken from the
       desktop design (sun 725.138 wide, top -185.649, right -124.138). */
    .sun-group {
      position: absolute;
      width: var(--sun-w);
      top:   calc(var(--sun-w) * -0.256087);
      right: calc(var(--sun-w) * -0.171192);
      height: auto;
      pointer-events: none;
      z-index: 2;
    }

    /* hero clouds (cloud.svg — nodes 1:67 / 1:68, subtle cream wisps) */
    .hero-cloud {
      position: absolute;
      height: auto;
      pointer-events: none;
      z-index: 1;
    }
    .hero-cloud.cloud-a {
      width: calc(var(--sun-w) * 0.723655);
      right: calc(var(--sun-w) * -0.466696);
      top:   calc(var(--sun-w) *  0.264775);
    }
    .hero-cloud.cloud-b {
      width: calc(var(--sun-w) * 0.384752);
      right: calc(var(--sun-w) *  0.406185);
      top:   calc(var(--sun-w) *  0.238576);
    }

    /* eyebrow (node 1:36) — left edge (dot) aligns with the "M" of Multidisciplinary */
    .hero-eyebrow {
      position: absolute;
      left: calc(50% - 459px);
      top: calc(50vh - 240px);
      display: flex;
      align-items: flex-start;
      gap: 8px;
      font-family: 'JetBrains Mono', monospace;
      font-size: 14px;
      letter-spacing: 1.4px;
      text-transform: uppercase;
      color: white;
      white-space: nowrap;
    }
    .hero-eyebrow img { width: 10.49px; height: 10.49px; }

    /* vertical label (node 1:39) */
    .hero-vertical {
      position: absolute;
      left: calc(50% - 506px);
      top: calc(50vh - 66px);
      transform: translate(-50%, -50%) rotate(-90deg);
      font-family: 'JetBrains Mono', monospace;
      font-size: 14px;
      letter-spacing: 1.4px;
      color: white;
      white-space: nowrap;
    }

    /* heading (nodes 1:34 / 1:35) — DM Serif Display 156px #fff9e9 +3.1px */
    .h-line {
      position: absolute;
      font-family: 'DM Serif Display', serif;
      font-size: 156px;
      font-weight: 400;
      letter-spacing: 3.1px;
      color: var(--cream-text);
      white-space: nowrap;
      line-height: normal;
    }
    .h-line.multidisciplinary { left: calc(50% - 459px); top: calc(50vh - 232px); }
    .h-line.designer {
      left: calc(50% + 46px); top: calc(50vh - 34px);
      /* clip the roll, but allow a 30px margin so the hover lift isn't cut off */
      overflow: clip; overflow-clip-margin: 30px; height: 1.2em; line-height: 1.2;
    }
    /* vertical roll track — words stack and slide up behind the .designer clip */
    .h-line.designer .roll { display: block; will-change: transform; }
    .h-line.designer .roll-word { display: block; line-height: 1.2; }

    /* per-letter tilt + lift on hover (letters split in via JS) */
    .h-letter {
      display: inline-block;
      transform-origin: 50% 100%;
      transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1),
                  color 0.25s ease;
    }
    /* will-change only while the pointer is on the heading. As a permanent
       declaration it held ~17 composited layers alive for the whole session, for a
       tilt that only ever plays on hover — and the compositor re-walks every live
       layer each scroll frame. Promoting on the parent still gets the letters onto
       their layers before any individual one is hovered. */
    .h-line:hover .h-letter { will-change: transform; }
    .h-letter:hover {
      transform: translateY(-0.16em) rotate(-7deg);
      color: #F4D24A;
    }

    /* ════ RECORDER — Figma node 1:40 ════ */
    .recorder {
      position: absolute;
      left: calc(50% - 229px);
      top: calc(50vh + 58px);
      transform: translate(-50%, -50%) scale(1.08);
      display: flex;
      align-items: flex-start;
      gap: 22px;
      padding: 16px;
      border-radius: 9999px;
      background: #dfdcd5;
      border: 1px solid #c9c9c9;
      box-shadow: 0 11px 9.6px 0 rgba(0,0,0,0.25);
    }
    /* neumorphic inner bevel */
    .recorder::after {
      content: '';
      position: absolute;
      inset: -1px;
      border-radius: inherit;
      pointer-events: none;
      box-shadow:
        inset 0 5px 7.3px 2px #f5f1ee,
        inset -2px -10px 2.3px 0 #b8b7b2;
    }

    /* disk (nodes 1:41 / 1:62) */
    .disk {
      position: relative;
      width: 89px; height: 89px;
      border-radius: 9999px;
      border: 3px solid #373535;
      box-shadow: 0 0 0 2px #62615e;
      overflow: hidden;
      flex-shrink: 0;
    }

    .disk-face {
      position: absolute;
      inset: 0;
      border-radius: 9999px;
      background: conic-gradient(from 180deg,
        rgb(18,18,18) 0%, rgb(18,18,18) 8.76%, rgb(39,39,39) 10.18%,
        rgb(59,59,59) 11.59%, rgb(80,80,80) 13.01%, rgb(100,100,100) 14.42%,
        rgb(79,79,79) 18.19%, rgb(59,59,59) 21.95%, rgb(38,38,38) 31%,
        rgb(18,18,18) 40.04%, rgb(18,18,18) 59.51%, rgb(39,39,39) 61.29%,
        rgb(59,59,59) 63.07%, rgb(80,80,80) 64.84%, rgb(100,100,100) 66.62%,
        rgb(79,79,79) 68.72%, rgb(57,57,57) 70.82%, rgb(38,38,38) 80.96%,
        rgb(18,18,18) 91.1%, rgb(18,18,18) 100%);
    }

    .disk-texture {
      position: absolute;
      inset: 0;
      width: 100%; height: 100%;
      object-fit: cover;
      border-radius: 9999px;
      mix-blend-mode: color-dodge;
      opacity: 0.1;
    }

    .disk-cap {
      position: absolute;
      left: 50%; top: 50%;
      transform: translate(-50%, -50%);
    }
    .disk-cap.c1 { width: 32.19px; height: 32.19px; }
    .disk-cap.c2 { width: 18.45px; height: 18.45px; }
    .disk-cap.c3 { width: 5.94px;  height: 5.94px;  }

    .disk.spinning { animation: disk-spin 1.8s linear infinite; }
    @keyframes disk-spin { to { transform: rotate(360deg); } }

    /* center column (node 1:45) */
    .rec-center {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      width: 171.42px;
      align-self: stretch;
    }

    .rec-title {
      font-family: 'JetBrains Mono', monospace;
      /* 11 not 12: the title has to clear .rec-center's fixed 171.42px in ONE
         line, and this string measures 180px at 12px. Kept as a wrap (not
         nowrap) so a font fallback degrades to two lines instead of spilling
         out of the pill. */
      font-size: 11px;
      letter-spacing: -0.12px;
      color: #070707;
      width: 100%;
      margin-bottom: 4px;
    }

    /* play slider (node 1:49) */
    .play-slider {
      position: relative;
      width: 100%;
      height: 23.45px;
      cursor: pointer;
      touch-action: none;
    }
    /* smooth follow during playback; disabled mid-drag so scrubbing stays 1:1 */
    .play-slider:not(.scrubbing) .slider-knob { transition: left .12s linear; }
    .play-slider:not(.scrubbing) .slider-fill { transition: width .12s linear; }
    .play-slider.scrubbing { cursor: grabbing; }

    .slider-track {
      position: absolute;
      left: 0; right: 0;
      top: 5.58px;
      height: 12.29px;
      border-radius: 9999px;
      background: #686360;
      box-shadow: 0 3px 1px 0 #ededed;
      overflow: hidden;
    }
    .slider-track::after {
      content: '';
      position: absolute;
      inset: 0;
      border-radius: inherit;
      pointer-events: none;
      box-shadow:
        inset 0 -1px 3px 0 rgba(0,0,0,0.2),
        inset 3px 3px 3.4px 0 rgba(0,0,0,0.5);
    }

    .slider-fill {
      position: absolute;
      left: 0;
      top: 0.46px;
      height: 11.83px;
      width: 0;
      background: var(--gold);
    }

    .slider-knob {
      position: absolute;
      top: 0;
      left: 0;
      width: 23.45px; height: 23.45px;
      border-radius: 9999px;
      border: 1px solid #e4e5e5;
      background: radial-gradient(circle at 50% 50%, #f6f4f5 0%, #caccc9 100%);
      box-shadow: 2px 4px 4px 0 rgba(0,0,0,0.25);
      display: flex;
      align-items: flex-start;
      justify-content: center;
    }
    .slider-knob::after {
      content: '';
      width: 7.32px; height: 7.32px;
      border-radius: 9999px;
      background: linear-gradient(150deg, #ffffff 15.95%, #8d8b8f 70.17%);
      box-shadow: 0 1px 1.9px 0 rgba(0,0,0,0.51);
    }

    .timestamps {
      display: flex;
      justify-content: space-between;
      width: 100%;
      font-family: 'JetBrains Mono', monospace;
      font-size: 10px;
      letter-spacing: -0.1px;
      color: #070707;
    }

    /* play circle (node 1:57) */
    .play-circle {
      position: relative;
      width: 41.32px; height: 41.32px;
      border: none;
      background: none;
      padding: 0;
      cursor: pointer;
      border-radius: 50%;
      transition: transform 0.18s cubic-bezier(0.34,1.56,0.64,1);
    }
    .play-circle:hover  { transform: scale(1.1); }
    .play-circle:active { transform: scale(0.92); }
    .play-circle:focus-visible { outline: 2px solid #070707; outline-offset: 2px; }

    .play-circle .ring { position: absolute; inset: 0; width: 100%; height: 100%; }
    .play-circle .tri  { position: absolute; inset: 8.33%; width: 83.34%; height: 83.34%; }

    .pause-bars {
      position: absolute;
      inset: 0;
      display: none;
      align-items: flex-start;
      justify-content: center;
      gap: 4px;
    }
    .pause-bars span {
      width: 4px; height: 14px;
      background: #070707;
      border-radius: 2px;
    }
    .play-circle.playing .tri { display: none; }
    .play-circle.playing .pause-bars { display: flex; }

    /* hero content wrapper — scroll-driven scale + fade */
    .hero-content {
      position: absolute;
      inset: 0;
      transform-origin: center center;
      will-change: transform, opacity;
      /* drives the whole sky group's size (sun + clouds). 48vw keeps the desktop
         art near full size on wide screens but scales the sky down faster on
         narrower laptops (e.g. 13") so it stops overlapping the hero text; the
         300px floor keeps the sun prominent on phones instead of collapsing. */
      --sun-w: clamp(300px, 48vw, 725.138px);
    }

    /* ════ PROJECTS SECTION (scrolls over pinned hero) — node 1:104 ════ */
    .projects {
      position: relative;
      z-index: 10;
      background: var(--bg-base);
      padding: 317px 0 200px;
    }

    /* cream cloud band (node 128:3824 "sky") — cream horizon (#fff9f1, Vector 27)
       rises up into the blue hero; #fef2e2 accent wisps float above it.
       Clean Figma vector export, no color-key filter. */
    .sky-band {
      position: absolute;
      left: 0;
      width: 100%;
      aspect-ratio: 1440 / 471;
      /* Anchor the cream seam (band bottom) at projects +200px. At 1440px this
         resolves to top:-271px — the design value (correct, do not change).
         height = 32.71vw; bottom = top + height = 200px. */
      top: calc(200px - 32.71vw);
      background-image: url("../images/sky-band.svg");
      background-size: 100% 100%;
      background-repeat: no-repeat;
      background-position: top;
      pointer-events: none;
    }
    /* Above 1440px the band scaled taller and rode too high into the hero.
       Gentler slope keeps it lower; continuous with the base at exactly 1440px
       (both give top:-271px there), so there's no jump. Only affects >1440. */
    @media (min-width: 1440px) {
      .sky-band { top: calc(35px - 21.25vw); }
      /* scale the client logos up a touch on larger viewports */
      .client-logo { height: 50px; }
    }

    /* laptops (13"–15", ~1280–1536 CSS px — a 13" MacBook Air reports ~1470,
       a 13/14" Pro ~1512): give the sun a bit more presence while trimming the
       headline + recorder so the two balance. Wide monitors (>1536) keep base. */
    @media (max-width: 1536px) {
      .hero-content { --sun-w: clamp(300px, 44vw, 680px); }
      .h-line { font-size: 136px; letter-spacing: 2.70px; }
      /* center the whole text block on the viewport: the 1440-tuned base anchors
         sit ~61px left of centre on narrower laptops, so shift every text element
         right by 61px together (keeps all internal alignments intact). Also push
         the whole block DOWN by 40px (all five tops +40) so it sits lower in the
         hero on 13"/tablet instead of riding high — internal spacing unchanged. */
      .hero-eyebrow             { left: calc(50% - 398px); top: calc(50vh - 200px); }
      .hero-vertical            { left: calc(50% - 445px); top: calc(50vh - 26px); }
      .h-line.multidisciplinary { left: calc(50% - 398px); top: calc(50vh - 192px); }
      /* rotating word: centered on the recorder, tucked close and lifted up */
      .h-line.designer { left: calc(50% + 59px); top: calc(50vh - 2px); }
      /* scale down; left edge stays on the "D"; lifted to tighten under "Designer who" */
      .recorder { left: calc(50% - 184px); top: calc(50vh + 80px); transform: translate(-50%, -50%) scale(1.0); }
    }

    /* intro paragraph (node 1:115) */
    .intro-copy {
      position: relative;
      width: 982.9px;
      max-width: calc(100% - 96px);
      margin: 0 auto;
      text-align: center;
      font-size: 32px;
      letter-spacing: -1.28px;
      line-height: 1.358;
      color: var(--text-heading);
    }
    .intro-copy .bold {
      font-weight: 700;
      display: block;
      margin-top: 1.358em;
    }

    /* ════ FEATURED WORK (node 1:117) ════ */
    .featured-work {
      /* sticky-pinned at its tail: scrolls normally through the tiles, then holds
         its last screen while the testimonials section scrolls up over it (the same
         pinned-reveal the hero uses). `top` is set from JS = 100vh − section height
         so the bottom pins to the viewport bottom. */
      position: sticky;
      top: 0;
      z-index: 10;
      background: var(--bg-base);
      padding-top: 80px;
      /* cream clearance below the tiles: extends the section's own background
         (covering the pinned hero behind) and pushes the testimonials' green
         crest down to ~142px below the tiles, per Figma (frame 128:3787) */
      padding-bottom: 338px;
      overflow: visible;
    }

    /* scroll-driven paper-plane decoration.
       The overlay extends UPWARD over the projects section (sky + intro) so the
       flight starts in the cloud band above. top/height are set in JS from the
       live projects→featured span; left/width fill the column. */
    .plane-fly {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      pointer-events: none;
      overflow: visible;
      z-index: 3;
    }
    .plane-trail {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      overflow: visible;
    }
    /* dotted trail stroke tracks the theme (overrides the SVG's stroke attr) */
    #planeTrailPath { stroke: var(--trail-stroke); }
    .plane-sprite {
      position: absolute;
      top: 0;
      left: 0;
      width: 88px;
      height: auto;
      transform-origin: center center;
      transform: translate(-50%, -50%);
      will-change: transform, opacity;
    }

    .featured-heading {
      font-family: 'Inter', 'Space Grotesk', sans-serif;
      font-size: 40px;
      font-weight: 300;
      letter-spacing: -1.6px;
      line-height: 1.358;
      color: var(--text-heading);
      text-align: center;
      margin-bottom: 38px;
    }

    /* clients marquee (node 1:124) */
    .logo-marquee-wrap {
      position: relative;
      overflow: hidden;
      width: 50%;
      margin: 0 auto 96px;
    }

    /* edge fade via pseudo-elements so mix-blend-mode on children works against page bg */
    .logo-marquee-wrap::before,
    .logo-marquee-wrap::after {
      content: '';
      position: absolute;
      top: 0;
      bottom: 0;
      width: 80px;
      z-index: 2;
      pointer-events: none;
    }
    .logo-marquee-wrap::before { left: 0;  background: linear-gradient(to right, var(--bg-base), transparent); }
    .logo-marquee-wrap::after  { right: 0; background: linear-gradient(to left,  var(--bg-base), transparent); }

    .logo-marquee-inner {
      display: flex;
      align-items: flex-start;
      gap: 72px;
      width: max-content;
      animation: logo-scroll 24s linear infinite;
    }

    .logo-marquee-inner:hover { animation-play-state: paused; }

    @keyframes logo-scroll {
      0%   { transform: translateX(0); }
      100% { transform: translateX(-50%); }
    }

    /* two-row opposite-direction variant — mobile only */
    .logo-marquee-mobile { display: none; }

    .client-logo {
      height: 45px;
      width: auto;
      flex-shrink: 0;
      display: block;
      object-fit: contain;
    }
    
    body.night .client-logo {
      background: rgba(255, 255, 255, 0.9);
      padding: 6px 12px;
      border-radius: 8px;
      border: 1px solid rgba(255, 255, 255, 0.15);
      box-shadow: 0 4px 12px rgba(0,0,0,0.3);
    }
    /* staggered 2×2 tile grid (nodes 61:2057 / 61:2063 / 61:2074 / 61:2084) */
    .project-tiles {
      display: flex;
      flex-direction: column;
      gap: 25px;
      padding: 0 60px;
    }

    .tiles-row {
      display: flex;
      gap: 1.97%;
      /* scroll-reveal: JS scrubs --reveal 0→1 as the row enters the viewport,
         scaling it up from half size to full. The row spans the full content
         width, so scaling from its centre grows the tiles toward the page centre. */
      --reveal: 0;
      transform: scale(calc(0.5 + 0.5 * var(--reveal)));
      transform-origin: center center;
      opacity: calc(0.35 + 0.65 * var(--reveal));
      will-change: transform, opacity;
    }

    .project-tile {
      border-radius: 40px;
      overflow: hidden;
      height: 595px;
      flex-shrink: 0;
      position: relative;
      cursor: pointer;
      box-shadow: inset 0 0 0 0px #323131;
      transition: box-shadow 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    }

    .project-tile:hover {
      box-shadow: inset 0 0 0 1px #323131;
    }

    .project-tile img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      display: block;
    }

    .project-tile.tile-wide   { width: 59.39%; }
    .project-tile.tile-narrow { width: 38.64%; }

    /* ── tile hover overlay ── */
    .tile-overlay {
      position: absolute;
      inset: 0;
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      justify-content: center;
      padding: 24px;
      backdrop-filter: blur(7.7px);
      -webkit-backdrop-filter: blur(7.7px);
      background: var(--overlay-scrim);
      opacity: 0;
      transition: opacity 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    }

    .project-tile:hover .tile-overlay {
      opacity: 1;
    }

    .tile-overlay-title {
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 400;
      font-size: 40px;
      letter-spacing: -1.6px;
      line-height: 1.358;
      text-align: center;
      color: var(--text-contrast);
      text-transform: uppercase;
      width: 100%;
      margin: 0;
    }

    .tile-overlay-subtitle {
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 400;
      font-size: 19px;
      letter-spacing: -0.76px;
      line-height: 1.358;
      text-align: center;
      color: var(--text-contrast);
      width: 100%;
      margin: 0;
    }
    /* dark mode: description reads lighter than the all-caps title */
    body.night .tile-overlay-subtitle { font-weight: 300; }

    /* desktop-only line break inside tile subtitles */
    .tile-break { display: inline; }
    @media (max-width: 1100px) { .tile-break { display: none; } }

    /* ── playable tile: poster frame, click (or Enter/Space) to run the clip ──
       Same box as any other tile — the recording fills it cover-cropped, so the
       poster shows through until playback starts. Sources ship as data-src with
       preload="none" (see the tile-video block in index.js) so the ~10MB pair
       only streams once the tile is actually played. */
    .project-tile.tile-playable { isolation: isolate; }
    .tile-vid {
      position: absolute; inset: 0; z-index: 0;
      width: 100%; height: 100%; object-fit: cover; display: block;
      background: #111;
    }
    .tile-play {
      position: absolute; inset: 0; z-index: 1;
      display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
      pointer-events: none;
      opacity: 1;
      transition: opacity 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    }
    .tile-play svg {
      width: 86px; height: 86px; padding: 24px; fill: #fff;
      border-radius: 50%;
      background: rgba(18, 18, 18, 0.42);
      backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
    }
    .tile-play i {
      font-style: normal; font-family: 'Space Grotesk', sans-serif; font-weight: 400;
      font-size: 13px; letter-spacing: 0.14em; text-transform: uppercase;
      color: rgba(255, 255, 255, 0.92);
      text-shadow: 0 1px 8px rgba(0, 0, 0, 0.45);
    }
    /* the badge steps aside once the clip is running, and whenever the hover
       scrim is up (it would otherwise sit blurred behind the overlay) */
    .project-tile.tile-playable.is-playing .tile-play,
    .project-tile.tile-playable:hover .tile-play,
    .project-tile.tile-playable:focus-visible .tile-play { opacity: 0; }
    .project-tile.tile-playable:focus-visible {
      outline: 2px solid var(--text-heading); outline-offset: -2px;
    }
    @media (max-width: 900px) {
      .tile-play svg { width: 62px; height: 62px; padding: 17px; }
      .tile-play i { font-size: 11px; }
    }


    /* ════ TESTIMONIALS — Figma node 128:3851 (1440×900) ════ */
    .testimonials {
      /* slides up over the pinned featured-work, then PINS once it fills the
         viewport so the card deck can fan out on further scroll (scrubbed via the
         spacer below) before the footer scrolls over it. */
      position: sticky;
      top: 0;
      z-index: 11;
      height: max(1010px, 100vh);  /* ≥ viewport so the pinned section fully covers */
      /* willow-forest painting (Figma node 128:3851) as the section background.
         Uses a MIRRORED tile (painting + its horizontal flip) so it can repeat across
         the full width with no visible seams — the mirror edges line up, so it reads as
         one continuous forest, never as tiles. Sized `auto 100%` so the full painting
         height (cream sky → willows → meadow) maps to the section height: the cream sky
         lands at the top edge and the lush willows fill the body. The painting itself
         now lives on the .t-willow layer below so it can sway in the breeze; this rule
         just holds the sky-cream base colour behind it. */
      background: var(--surface-raised);
      /* clip horizontal land bleed, but let the wavy grass overflow the top —
         it sits at top:-196 and a plain overflow:hidden cropped the whole wave */
      overflow-x: clip;
      overflow-y: visible;
    }

    /* willow-painting layer — carries the mirrored-tile background so it can be
       transformed independently of the section (cards/heading stay still). Two nested
       skins sway on slightly different clocks; anchored at the bottom (transform-origin
       50% 100%) so the roots stay planted and the canopy leans, like a breeze moving
       through the fronds. Compound motion (two out-of-sync periods) keeps it organic
       rather than metronomic. Only transform is animated → stays on the compositor. */
    .t-willow {
      position: absolute;
      inset: 0;
      z-index: 0;
      overflow: hidden;
      pointer-events: none;
    }
    .t-willow-sway,
    .t-willow-skin {
      position: absolute;
      /* horizontal overscan so the sway never exposes the cream base at the edges;
         no vertical overscan → the cream sky stays pinned to the top edge */
      inset: 0 -7%;
      transform-origin: 50% 100%;
      will-change: transform;
      backface-visibility: hidden;
    }
    .t-willow-skin {
      inset: 0;
      background: url("../images/testimonials-willow-tile.jpg") top center / auto 100% repeat-x;
      animation: willow-breeze 7.5s ease-in-out infinite alternate;
    }
    .t-willow-sway {
      animation: willow-drift 12.5s ease-in-out infinite alternate;
    }
    /* Both skins lean the SAME direction on different clocks so their skews REINFORCE
       (an earlier version had them in opposite phase, which cancelled to near-zero and
       looked static). The 6.5s / 11s beat keeps the sway from feeling metronomic. */
    @keyframes willow-breeze {
      from { transform: skewX(-1deg)   translateX(-0.45%) scaleX(1.007); }
      to   { transform: skewX(1.15deg) translateX(0.45%)  scaleX(1); }
    }
    @keyframes willow-drift {
      from { transform: skewX(-0.4deg) translateX(-0.2%); }
      to   { transform: skewX(0.45deg) translateX(0.2%); }
    }
    @media (prefers-reduced-motion: reduce) {
      .t-willow-sway,
      .t-willow-skin { animation: none; }
    }

    /* (the node 128:4830 noise overlay was removed — it shipped disabled and only
       held a dead 2MB texture reference) */

    /* wavy grass band — node 128:3978. The cream above the crest was made
       transparent (testimonials-grass.png) so only the green grass overlaps the
       pinned work tiles; a soft upward drop-shadow casts the crest onto them. */
    .t-wavy {
      position: absolute;
      /* Anchored by its BOTTOM, not top:-196px. The crest is a 2880x395 image at
         width:100%, so it stands ~13.7% of the viewport tall — every fixed top
         offset only held at one width. top:-196px let the section's straight top
         edge show as a flat band under the wave below ~1429px; the narrow-width
         top:-30px did the same above ~421px, where the wave's troughs (51.9% down
         the art) sank past that edge and the silhouette flattened into a line.
         Anchoring the BOTTOM fixes both: the art's solid lower half always laps
         over the edge, whatever the width. The 4px keeps that lap off hairline. */
      bottom: calc(100% - 4px);
      left: 0;
      width: 100%;
      height: auto;
      z-index: 2;
      pointer-events: none;
    }

    /* tree clusters — exported groups 128:3857 (left) / 128:3852 (right).
       Hidden: the willow-painting background now supplies the trees, so the flat
       SVG ornaments would double up. (Matches Figma 128:3851, which has no ornament
       trees over the willow fill.) */
    .t-trees-left,
    .t-trees-right {
      display: none;
      position: absolute;
      width: 199px;
      height: auto;
      z-index: 2;
      pointer-events: none;
    }
    .t-trees-left  { left: 43px;  top: 473px; }
    .t-trees-right { right: 36px; top: 434px; }

    /* land / meadow — exported group 128:3997. Lowered so the meadow reads as a
       slimmer strip beneath the centred card deck (bleeds off the bottom). */
    .t-land {
      position: absolute;
      bottom: -96px;
      left: 0;
      width: 100%;
      height: auto;
      z-index: 2;
      pointer-events: none;
    }

    /* full-width content layer (heading + card positioned vs section centre) */
    .t-inner {
      position: relative;
      width: 100%;
      height: 100%;
      margin: 0 auto;
      z-index: 3;
    }

    /* eyebrow — node 238:1857 (Inter Regular 14px, centred above the heading) */
    .t-eyebrow {
      position: absolute;
      top: -15.31px;
      /* centred via left/right + text-align (NOT translateX): the .reveal class owns
         `transform` for the entrance animation and would otherwise clobber the centring */
      left: 0;
      right: 0;
      text-align: center;
      margin: 0;
      font-family: 'Inter', sans-serif;
      font-weight: 400;
      font-size: 14px;
      line-height: 1.3576;
      letter-spacing: -0.04em;
      color: var(--text-eyebrow);
      white-space: nowrap;
    }

    /* heading — node 128:4197 (Inter Regular 40px, centred near top) */
    .t-heading {
      position: absolute;
      top: 0.96px;
      left: 0;
      right: 0;
      text-align: center;
      margin: 0;
      font-family: 'Inter', sans-serif;
      font-weight: 400;
      font-size: 40px;
      line-height: 1.3576;
      letter-spacing: -0.04em;
      color: var(--text-title);
      white-space: nowrap;
    }

    /* testimonial card stack — node 203:2130.
       The cards begin stacked at centre and fan out left/right as --spread goes
       0 → 1 (set from JS on scroll). --pos is -1 / 0 / +1 per card. */
    .t-cards {
      position: absolute;
      /* centred in the viewport height while the section is pinned (deck half-height
         = 255px) — the deck fans out from the middle of the screen */
      top: calc(50vh - 255px);
      left: 50%;
      width: 430px;
      height: 510px;                 /* reserves the deck's footprint */
      transform: translateX(-50%);
      --spread: 0;
    }
    .t-card {
      position: absolute;
      top: 0;
      left: 0;
      width: 430px;
      min-height: 510px;             /* equal-height deck (tallest card) */
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      gap: 20px;
      padding: 34px;
      margin: 0;
      border-radius: 34px;
      background: linear-gradient(180deg, rgba(251,252,248,0.5) 0%, rgba(149,150,148,0.5) 100%);
      border: 1px solid #999;
      /* 18px, not 45px: three big glass cards blurring a continuously-swaying
         willow backdrop inside a pinned section re-rasterized every frame — the
         heaviest paint on the desktop page. 18px is what phones already use and
         still reads as glass. (user-approved trim, 2026-07-30) */
      backdrop-filter: blur(18px);
      -webkit-backdrop-filter: blur(18px);
      box-shadow: 4px 4px 20px 0 rgba(0,0,0,0.3);
      will-change: transform;
      /* straight horizontal fan: side cards slide out from directly behind the
         front card by ±462px × --spread (430px card + 32px gap). No tilt — pure
         translateX. At --spread 0 all three overlap exactly (only the front shows). */
      transform: translateX(calc(var(--pos) * var(--spread) * 462px));
    }

    .t-quoteicon {
      display: block;
      width: 82px;
      height: 82px;
      flex-shrink: 0;
    }

    .t-quote {
      margin: 0;
      width: 100%;
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 400;                /* regular (Figma 203:2134) */
      font-size: 14px;
      line-height: 1.45;
      color: #000;                     /* black (Figma 203:2134) */
      word-break: break-word;
    }

    .t-divider {
      width: 100%;
      height: 0;
      margin: 0;
      margin-top: auto;             /* pin the divider + author to the card bottom */
      border: 0;
      border-top: 1px solid rgba(0,0,0,0.5);   /* Figma Line 57: black @ 50% */
    }

    .t-author {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      gap: 4px;
      width: 100%;
    }
    .t-authorrow {
      display: flex;
      align-items: flex-start;
      gap: 8px;
      width: 100%;
    }
    .t-avatar {
      width: 40px;
      height: 40px;
      flex-shrink: 0;
      border-radius: 999px;
      object-fit: cover;
    }
    .t-name {
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 700;
      font-size: 22px;
      line-height: normal;
      color: #000;
      white-space: nowrap;
    }
    .t-role {
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 400;
      font-size: 16px;
      line-height: normal;
      color: #000;
    }

    /* scrub zone — adds scroll length while the testimonials section is pinned, so
       the deck can fan out before the footer rises over it. Sits behind the pinned
       section (no z-index) so it's never seen; only provides scroll distance. */
    .t-scrub { height: 620px; }
    /* Mobile bottom-pins the testimonials (JS), so keep a scrub spacer here too —
       it holds the pinned section on screen for a beat before the footer rises.
       Kept short (was 65vh) so the footer is reached in a scroll or two, not many. */
    @media (max-width: 1100px) {
      .t-scrub {
        display: block;
        height: 32vh;
        /* Willow scene behind the spacer so the footer's CREAM crest (which pokes up
           over this region) always overlaps willow, not the blank page bg. On desktop
           the pinned willow section covers this; on phones the sticky can slip, leaving
           the crest over cream (invisible) — this guarantees willow is there either way.
           Same tile as the testimonials, so no seam when the section covers it. */
        background: url("../images/testimonials-willow-tile.jpg") center bottom / cover no-repeat;
      }
    }


    /* ════ RESPONSIVE — below 1100px the absolute 1440 canvas reflows ════ */
    @media (max-width: 1100px) {


      .hero {
        /* THE BLUE FLICKER: the hero is a sticky blue panel; the cream .projects
           section (z-index 10, opaque) begins immediately after it in flow. If the
           hero is only as tall as the SMALL viewport (svh, toolbar shown), then the
           moment the mobile browser toolbar RETRACTS the visual viewport grows past
           the hero and the top of the cream section slides into view at the bottom;
           when the toolbar returns it snaps back to full blue. That is the flicker.
           dvh does NOT fix it — iOS Safari jumps dvh at the end of the toolbar
           animation instead of tracking it. The stable fix: size the hero to the
           LARGE viewport (lvh) so the cream section starts below even the tallest
           (toolbar-hidden) viewport and can never peek in. lvh is a fixed value, so
           the hero never resizes as the toolbar toggles — no snap, no flicker.
           The CONTENT box is pinned to svh below so the recorder still fits on load. */
        height: 100svh;    /* fallback for browsers without lvh */
        height: 100lvh;
        min-height: 100svh;
        min-height: 100lvh;
      }
      /* keep the hero's laid-out content (heading + bottom-anchored recorder) inside
         the SMALL viewport so it's fully visible on first load (toolbar shown), even
         though the blue panel itself extends down to lvh behind it. */
      .hero-content {
        height: 100svh;
        bottom: auto;
      }

      /* NOTE: the hero no longer reflows here. Through tablet the staggered
         absolute layout is KEPT and continuously fit-scaled by JS (see
         baseHeroScale) so it never snaps. The flex-column reflow now happens
         only at mobile (<=640px) — see that block below. */

      /* The hero is sized to lvh (see above) to kill the blue flicker, which pushes
         this section — and the sky-band cloud seam anchored to its top — down by the
         toolbar's height (lvh − svh). Pull it back up by exactly that amount so the
         cloud seam sits where it did before (anchored to the svh fold). This is 0 on
         desktop (svh == lvh) and only lifts on mobile/tablet where the toolbar exists.
         The seam still meets the cream cleanly because the sky-band moves up with it. */
      .projects {
        padding: 150px 24px 120px;
        margin-top: calc(100svh - 100lvh);
      }
      .sky-band {
        top: -120px;
        left: -40px;
        width: calc(100% + 40px);
      }
      .intro-copy {
        width: 100%;
        max-width: 640px;
        font-size: clamp(19px, 4.6vw, 28px);
        letter-spacing: -0.5px;
      }

      /* ── featured work responsive (tablet) ── */
      /* the paper plane's opening arc is fixed-size (tuned to the 1440 canvas);
         on narrow screens its trail crosses the intro copy — hide the decoration */
      .plane-fly { display: none; }
      .featured-work { padding-top: 60px; padding-bottom: 160px; }
      .featured-heading { font-size: clamp(24px, 4vw, 36px); margin-bottom: 44px; }
      .logo-marquee-wrap { width: min(620px, 86vw); margin-bottom: 72px; }
      .project-tile { height: clamp(300px, 40vw, 500px); }
      .project-tiles { padding: 0 24px; }
      .tile-overlay-title { font-size: clamp(22px, 3.4vw, 32px); letter-spacing: -0.8px; }
      .tile-overlay-subtitle { font-size: clamp(15px, 2.2vw, 19px); letter-spacing: -0.4px; }

      /* ── testimonials responsive — reflow the 1440 canvas into one column ── */
      .testimonials {
        position: relative;          /* JS re-pins it (sticky, bottom-anchored) */
        top: auto;
        height: auto;
        /* cover the pinned work section at LARGE viewport height (toolbar
           collapsed mid-page) — svh left a sliver of the tiles showing below */
        
        min-height: 100lvh;
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 80px 24px calc(21vw + 24px);  /* bottom reserves room for the meadow */
      }
      .t-land { bottom: -16px; }
      .t-trees-left, .t-trees-right, .t-grid { display: none; }  /* ornaments crowd the column */
      .t-inner { width: 100%; max-width: 620px; height: auto; }
      .t-eyebrow {
        position: static;
        transform: none;
        text-align: center;
        margin-bottom: 6px;
      }
      .t-heading {
        position: static;
        transform: none;
        font-size: clamp(34px, 6vw, 48px);
        letter-spacing: -1.6px;
        line-height: 1.15;
        white-space: normal;
        margin-bottom: 36px;
        text-align: center;
      }
      /* below the fan breakpoint the deck becomes a horizontal snap carousel —
         swipe sideways through the cards; the next card peeks in at the edge */
      .t-cards {
        position: static;
        transform: none;
        left: auto;
        width: auto;
        height: auto;
        display: flex;
        flex-direction: row;
        align-items: stretch;
        gap: 16px;
        overflow-x: auto;
        /* proximity (not mandatory): the deck still centres a card when you release
           near one, but the browser stops constantly re-snapping — mandatory inside a
           sticky section traps the vertical gesture (the "stuck / flashes" glitch). */
        scroll-snap-type: x proximity;
        /* allow BOTH pans: the browser routes a horizontal-dominant swipe to this
           row's scroll and a vertical-dominant swipe to the page (footer). `pan-x`
           alone blocks vertical on this element, so a vertical swipe over the cards
           was swallowed instead of scrolling the page — the reported "can't scroll
           down over the cards" bug. */
        touch-action: pan-x pan-y;
        /* don't chain horizontal fling into the page scroll (and vice-versa). */
        overscroll-behavior-x: contain;
        /* bleed to the section edges (cancel its 24px side padding) */
        margin: 0 -24px;
        padding: 8px 24px 28px;      /* bottom room for the card shadow */
        scrollbar-width: none;
      }
      .t-cards::-webkit-scrollbar { display: none; }
      .t-card,
      .t-card--side {
        position: static;
        transform: none;
        left: auto;
        flex: 0 0 min(430px, 84vw);
        width: min(430px, 84vw);
        min-height: 0;               /* row-stretch equalises heights instead */
        margin: 0;
        scroll-snap-align: center;
        /* the base rule sets will-change:transform for the desktop fan; on mobile the
           cards never transform, so drop the permanent GPU layers (9 of them) — they
           add memory pressure and cause the repaint flash on scroll. */
        will-change: auto;
      }
      .t-quote { width: 100%; }

    }

    /* tablet: pull the cream sky-band higher into the hero so it reads clearly at
       the bottom of the pinned hero (the ≤1100 rule sits at -120px; -190px lifts
       it ~70px more). Scoped to >=641px because on narrower phones the band is
       shorter than 190px and this would open a blue gap below its seam — mobile
       keeps the -120px value from the reflow block. The seam (band bottom) stays
       below the projects top here, so it still joins the cream page cleanly. */
    @media (min-width: 641px) and (max-width: 1100px) {
      .sky-band { top: -190px; }
      /* sit the sky group (sun + clouds, and the night moon) a little higher in
         the taller tablet hero so it isn't stranded mid-canvas. translateY is in
         the pre-fit-scale space, so the on-screen lift is ~56px × fit-scale. */
      .sun-group, .hero-cloud, .moon-group { transform: translateY(-56px); }
    }

    /* tablet fit-range: the whole staggered canvas is JS fit-scaled
       (baseHeroScale), which already shrinks the sky group. --sun-w is normally
       vw-based, so it would shrink a SECOND time and the sun/clouds end up too
       small relative to the text. Pin it to a fixed px here so the sky group
       scales ONLY through the fit transform — same as the px-sized headline —
       keeping the sun/clouds prominent across tablet. 560px == 44vw at 1272px,
       so it meets the laptop tier continuously at the top and never disturbs the
       already-tuned 1280 layout. */
    @media (min-width: 641px) and (max-width: 1272px) {
      .hero-content { --sun-w: 560px; }
    }

    /* phones */
    /* landscape phones are short: size the sky group off the viewport HEIGHT so
       the sun stays in the corner instead of swallowing the heading (portrait
       keeps the larger width-based floor above). NB: this is AFTER the tablet
       rule above so short-landscape keeps its smaller height-based sun. */
    @media (orientation: landscape) and (max-height: 480px) {
      .hero-content { --sun-w: clamp(180px, 62vh, 320px); }
    }

    /* ════ HERO REFLOW — only at mobile: narrow widths (<=640, phone portrait) and
       short landscape (phone landscape). Above this, through tablet, the staggered
       absolute canvas is KEPT and JS-fit-scaled (no snap). Here it flattens into a
       centered flex column so it reads on a phone. ════ */
    @media (max-width: 640px), (max-height: 480px) and (orientation: landscape) {
      /* stays hidden in short landscape — the label is 225px tall, so on a 375px
         -high viewport its bottom third falls into the cream sea band and white
         text vanishes. Portrait restores it in the max-width:640 block below. */
      .hero-vertical { display: none; }

      /* the content wrapper (absolute, full-bleed) becomes the flex column —
         flex on .hero itself never reached the flow content inside this wrapper */
      .hero-content {
        display: flex;
        flex-direction: column;
        justify-content: flex-start;   /* push the eyebrow + header up (not centred) */
        padding: 96px 24px 0px;
  margin-bottom: -92px;       /* the phones block below refines top/bottom */
      }

      /* flow content must stay positioned + above the absolute art layers */
      .hero-eyebrow {
        position: relative;
        z-index: 2;
        left: auto; top: auto;
        transform: none;
        font-size: 13px;
        margin-bottom: 22px;
      }

      h1 { display: block; position: relative; z-index: 2; }
      .h-line {
        position: static;
        display: block;
        /* much bigger on mobile; nowrap keeps each word intact (no mid-word
           breaks between the per-letter spans) — the forced break below splits
           "Developer who" cleanly into two lines. */
        font-size: clamp(36px, 11vw, 120px);
        letter-spacing: -0.5px;
        line-height: 1.0;
        white-space: nowrap;
      }
      /* break "Designer who" after "Designer": the tagged word-space becomes a
         zero-size block, dropping "who" to its own line (rotating word is line 3). */
      .h-line.multidisciplinary .h-space { display: block; width: 0; height: 0; }

      .recorder {
        position: relative;
        z-index: 2;
        left: auto; top: auto;
        transform: none;
        width: fit-content;
        align-self: flex-start;   /* LEFT align the recorder */
        margin: 44px 0 0;
      }
    }

    @media (max-width: 640px) {

      /* vertical label, portrait phones only. It sits inside h1 (position:relative
         here), so top:50% centres it on the HEADING box — it tracks the header at
         any viewport height instead of drifting with the viewport's own centre.
         vertical-rl + 180deg gives the same bottom-to-top read as the desktop
         rotate(-90deg), and its box hugs `left` exactly (no half-width math against
         an unknown text length). left backs out of the column's own padding so the
         label lands 16px from the viewport edge. */
      .hero-vertical {
        display: block;
        top: 50%;
        left: calc(16px - var(--col-pad-l));
        transform: translateY(-50%) rotate(180deg);
        writing-mode: vertical-rl;
        z-index: 2;
        font-size: 13px;   /* matches the "Hello, I'm…" eyebrow's mobile size */
      }

      /* smaller, corner-tucked sun on mobile so the much-bigger header has room
         up top; the sky group (sun + clouds + moon) rides high in the corner. */
      .hero-content {
        --sun-w: clamp(258px, 66vw, 340px);
        /* left pad clears the vertical label's gutter; the label reads it back to
           park itself 16px from the viewport edge, so the two stay in sync */
        --col-pad-l: 64px;
        /* iOS Safari's toolbars make the visible (svh) height short, so the header
           sits just under the sun — NOT pushed far down. The +45 constant is tuned
           so a bigger sun bleeds further into the top-right corner instead of
           moving the header. Bottom pad keeps the recorder clear of the sky-band. */
        padding: calc(var(--sun-w) * 0.5 + 45px) 24px 124px var(--col-pad-l);
      }
      /* nudge the sky group down so it sits just below the nav buttons */
      .sun-group, .hero-cloud, .moon-group { transform: translateY(70px); }

      .recorder {
        transform: scale(0.66);
        transform-origin: top left;    /* keep the left edge fixed when scaling */
        /* was `auto`, which bottom-pinned the recorder and dumped ALL the column's
           spare height into this one gap — fine on a 667px phone, but it grew to
           ~170px on a tall viewport and the hero read as two split-apart blocks.
           A bounded gap keeps eyebrow → header → recorder reading as one group at
           any height; the leftover space now falls below, into the sky art. */
        margin: clamp(24px, 4.5vh, 56px) 0 0;
      }

      /* ── featured work mobile ── */
      .featured-work { padding-top: 48px; padding-bottom: 120px; }
      .featured-heading { font-size: 28px; letter-spacing: -0.8px; margin-bottom: 32px; }
      .logo-marquee-wrap { width: 100%; margin-bottom: 48px; }
      .logo-marquee-wrap::before, .logo-marquee-wrap::after { width: 40px; }
      .logo-marquee-inner { gap: 44px; }

      /* swap the single row for two rows moving in opposite directions */
      .logo-marquee-wrap > .logo-marquee-inner { display: none; }
      .logo-marquee-mobile { display: flex; flex-direction: column; gap: 18px; }
      /* uniform margin-right (incl. last item) instead of gap so the duplicated
         half is exactly equal width — translateX(-50%) then loops seamlessly */
      .logo-marquee-mobile .mrow { gap: 0; }
      .logo-marquee-mobile .mrow .client-logo { margin-right: 44px; height: 32px; }
      .logo-marquee-mobile .mrow-a { animation: logo-scroll 18s linear infinite; }
      .logo-marquee-mobile .mrow-b { animation: logo-scroll 18s linear infinite reverse; }
      .tiles-row { flex-direction: column; gap: 16px; }
      .project-tile.tile-wide,
      .project-tile.tile-narrow { width: 100%; height: clamp(240px, 60vw, 380px); }
      .project-tiles { gap: 16px; }

      /* stacked column: reveal each tile on its own (one by one) instead of scaling
         the whole row (two tiles) at once. Neutralise the row-level transform and
         move the same scrub onto each tile, driven by its own --reveal (set in JS). */
      .tiles-row { transform: none; opacity: 1; will-change: auto; }
      .project-tile {
        --reveal: 0;
        transform: scale(calc(0.5 + 0.5 * var(--reveal)));
        transform-origin: center center;
        opacity: calc(0.35 + 0.65 * var(--reveal));
        will-change: transform, opacity;
      }

      /* ── testimonials mobile ── */
      .testimonials { padding-top: 84px; }
      .t-heading { font-size: clamp(30px, 8vw, 40px); letter-spacing: -1.2px; margin-bottom: 28px; }
      /* lighter glass blur on phones — blur(45px) over the pinned deck is a heavy
         per-frame repaint that makes the scroll stutter; 18px still reads as glass. */
      .t-card { padding: 28px; border-radius: 28px; backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); }
      .t-name { font-size: 22px; }
      .t-role { font-size: 17px; }
      .t-arrows { gap: 44px; }

    }

    /* very narrow phones: the recorder (natural ~425px) needs a deeper scale */
    @media (max-width: 360px) {
      .recorder { transform: scale(0.58); }
    }


    /* ── About intro: line-by-line reveal on scroll ──
       No pin — the blurb scrolls normally. JS splits each paragraph into visual
       lines (.tw-line); each fades + rises as a whole line as the blurb travels
       up the viewport, driven by its scroll position so it can't be outrun. */
    .intro-copy .tw-line {
      display: block;
      opacity: 0;
      transform: translateY(0.55em);
      transition: opacity 0.5s ease,
                  transform 0.6s cubic-bezier(0.22, 0.61, 0.36, 1);
      /* no will-change: this reveal plays once. The browser composites a running
         opacity/transform transition on its own, whereas the declaration would keep
         a layer per line alive for the rest of the session. */
    }
    .intro-copy .tw-line.is-in {
      opacity: 1;
      transform: none;
    }

    @media (prefers-reduced-motion: reduce) {
      .intro-copy .tw-line { opacity: 1; transform: none; transition: none; }
    }

    /* ── Work featured on: logos cascade in when the marquee scrolls into view ──
       The reveal (opacity + translateY) rides on top of the marquee's translateX
       (which animates the parent), so they don't conflict. Triggered as a group
       via .logos-in so logos don't pop individually as the marquee loops. */
    .logo-marquee-inner .client-logo {
      opacity: 0;
      transform: translateY(20px);
      transition: opacity 0.6s ease,
                  transform 0.6s cubic-bezier(0.22, 0.61, 0.36, 1);
      transition-delay: var(--logo-delay, 0s);
      /* no will-change — one-shot group reveal, same reasoning as .tw-line above */
    }
    .logo-marquee-wrap.logos-in .client-logo {
      opacity: 1;
      transform: none;
    }
    /* stagger both duplicate sets identically (children 1–5 and 6–10) */
    .logo-marquee-inner .client-logo:nth-child(5n + 1) { --logo-delay: 0.10s; }
    .logo-marquee-inner .client-logo:nth-child(5n + 2) { --logo-delay: 0.18s; }
    .logo-marquee-inner .client-logo:nth-child(5n + 3) { --logo-delay: 0.26s; }
    .logo-marquee-inner .client-logo:nth-child(5n + 4) { --logo-delay: 0.34s; }
    .logo-marquee-inner .client-logo:nth-child(5n + 5) { --logo-delay: 0.42s; }

    @media (prefers-reduced-motion: reduce) {
      .logo-marquee-inner .client-logo { opacity: 1; transform: none; transition: none; }
    }

    /* ════ NIGHT THEME — Figma node 256:1861 (Home page, night) ════
       Dark mode = re-point the Tier-2 role tokens (values read 1:1 from the night
       frame). Everything themed by a token flips automatically; only asset swaps,
       filters, blend modes and display toggles need explicit rules below.
       Toggled by body.night (nav switch; persisted in localStorage). */

    /* hero geometry is identical day/night; only the gradient stops (tokens) change.
       night aurora shader export (node 256:1865) */
    body.night .hero-shader { content: url("../images/hero-shader-night.webp"); }
    /* finer noise grain at night (day tiles at 2048px) */
    body.night .hero-noise { background-size: 1024px 1024px; }
    /* starfield overlay — night only, screen blend so only the stars show */
    .hero-stars {
      display: none;
      position: absolute;
      inset: 0;
      /* seamless mirrored 2×2 tile built from the starfield (edges mirror → no seam) */
      background-image: url("../images/hero-stars-tile.webp");
      background-size: 700px auto;
      background-repeat: repeat;
      background-position: center;
      mix-blend-mode: screen;
      pointer-events: none;
    }
    body.night .hero-stars { display: block; }
    /* the two sky overlays are mutually exclusive — starfield at night, cirrus by day */
    body.night .hero-sky { display: none; }

    /* shooting star — night only. Colours are sampled straight out of
       hero-stars-tile (core #fffbff, halo #d6ebff, glow #a5caf6) so it reads as
       one of the starfield's own stars breaking loose rather than a new element.
       Geometry comes in as custom properties the JS randomises per pass; the
       keyframes animate `transform` (not the properties themselves) so this
       still interpolates on Safari without @property support. Rotating first and
       translating along the element's OWN x-axis keeps the streak aligned with
       its direction of travel, which is what sells it as motion. */
    .hero-meteor {
      display: none;
      position: absolute;
      left: 0;
      top: var(--m-top, 12%);
      width: var(--m-len, 200px);
      height: 2.5px;
      border-radius: 999px;
      transform-origin: 0 50%;
      opacity: 0;             /* base state between passes — the animation owns the fade */
      background: linear-gradient(90deg,
        rgba(165,202,246,0) 0%,
        rgba(214,235,255,0.55) 55%,
        rgba(255,251,255,1) 100%);
      /* the trail needs its own glow — without it the line reads as a 1px hairline
         at desktop width and the streak barely registers against the starfield */
      filter: drop-shadow(0 0 4px rgba(165,202,246,0.6));
      mix-blend-mode: screen;
      pointer-events: none;
    }
    /* the head: a hot core with the starfield's two-stop halo */
    .hero-meteor::after {
      content: '';
      position: absolute;
      right: -1px;
      top: 50%;
      width: 5px; height: 5px;
      margin-top: -2.5px;
      border-radius: 50%;
      background: #fffbff;
      box-shadow: 0 0 8px 2px rgba(214,235,255,0.95),
                  0 0 20px 7px rgba(165,202,246,0.6);
    }
    body.night .hero-meteor { display: block; }

    @keyframes hero-meteor-fly {
      0%   { opacity: 0; transform: rotate(var(--m-angle, 14deg)) translateX(calc(-1 * var(--m-len, 200px))); }
      10%  { opacity: 1; }
      72%  { opacity: 1; }
      100% { opacity: 0; transform: rotate(var(--m-angle, 14deg)) translateX(var(--m-travel, 1600px)); }
    }
    @media (prefers-reduced-motion: reduce) {
      .hero-meteor { display: none !important; }
    }

    /* sun ↔ moon (256:1905) */
    body.night .sun-group { display: none; }
    .moon-group {
      display: none;
      position: absolute;
      inset: 0;
      pointer-events: none;
      z-index: 2;
    }
    body.night .moon-group { display: block; }
    /* moon glow + disc scale from the SAME --sun-w as the sun (ratios taken from
       the desktop design: glow 418.34, disc 194.63 against the 725.138 art) so the
       moon tracks the sun's size + position identically across every viewport —
       day and night stay consistent. */
    .moon-glow {
      /* ellipse (white 45%), blur scales with the group too */
      position: absolute;
      top:   calc(var(--sun-w) * -0.037579);
      right: calc(var(--sun-w) *  0.039441);
      width:  calc(var(--sun-w) * 0.576911);
      height: calc(var(--sun-w) * 0.576911);
      border-radius: 50%;
      background: rgba(255,255,255,0.45);
      filter: blur(calc(var(--sun-w) * 0.105773));
    }
    /* hatched moon (moon.svg) — same cross-hatch pattern as the light-mode sun */
    .moon-disc {
      position: absolute;
      top:   calc(var(--sun-w) * 0.116668);
      right: calc(var(--sun-w) * 0.193702);
      width:  calc(var(--sun-w) * 0.268405);
      height: calc(var(--sun-w) * 0.268405);
    }

    /* hero clouds (256:1903/1904): #000 @ 50% */
    body.night .hero-cloud { filter: brightness(0); opacity: 0.5; }

    /* — nav: label + pin colour are now driven by the backdrop-contrast rules in
       site.css (#nav.nav-on-dark / .nav-on-light, set by initNavContrast), so the
       pin stays legible over any section regardless of theme. No night override. — */

    /* — sky band: same vectors recolored (Vector 27 #0a0a0f, wisps #0f0f2d) — */
    body.night .sky-band { background-image: url("../images/sky-band-night.svg"); }

    /* projects/featured bg + intro/heading text + tile-overlay (256:1927/1945) all
       flip via tokens (--bg-base, --text-heading, --overlay-scrim, --text-contrast). */

    /* logos row (256:1930): opacity 70%; svg logos go light (#fff9f1 / white variants),
       photo logos blend against the dark bg (luminosity / difference, per Figma) */
    body.night .logo-marquee-inner { opacity: 0.7; }
    body.night .client-logo.logo-existence,
    body.night .client-logo.logo-google    { filter: brightness(0) invert(0.93); }
    body.night .client-logo.logo-pga      { mix-blend-mode: luminosity; }
    /* jumpable + vibes: dedicated white night-mode logo exports swapped in via content.
       Keep these filenames free of "+" — Vercel decodes a literal + in a path back to a
       space, so /assets/vibes+logic-night mode.png 404s in the browser (curl with %2B
       hides it). Spaces are fine; the plus is not. */
    body.night .client-logo.logo-jumpable { content: url("../images/jumpable-night%20mode.png"); }
    body.night .client-logo.logo-vibes    { content: url("../images/logo-vibes-night.png"); }
    /* marquee edge-fades track --bg-base; plane trail tracks --trail-stroke — no night rule needed */

    /* — testimonials night art: purple willow watercolour on the
       willow layer (seamless horizontal mirror-tile, same repeat-x/sway as day) and the
       purple night meadow on the land layer; green day crest hidden for the dark one — */
    /* section bg + eyebrow/heading text flip via tokens; only art swaps stay here */
    body.night .t-willow-skin { background-image: url("../images/testimonials-willow-night-tile.png"); }
    /* night meadow art has a thin transparent left column + tapered bottom corners;
       bleed it a few % past both edges (section clips overflow-x) so opaque meadow
       reaches edge-to-edge, matching the day land strip */
    body.night .t-land { content: url("../images/land%20meadow-night.png"); width: 106%; left: -3%; }
    body.night .t-wavy { visibility: hidden; }
    .t-wavy-night {
      display: none;
      background: var(--surface-raised);   /* night-only crest; token resolves to navy */
      -webkit-mask-image: url("../images/testimonials-grass-cream.png");
      -webkit-mask-size: 100% 100%;
      mask-image: url("../images/testimonials-grass-cream.png");
      mask-size: 100% 100%;
      /* .t-wavy gives position/top/width; height:auto has no box on a div, so match the img ratio */
      aspect-ratio: 2880 / 395;
      height: auto;
    }
    body.night .t-wavy-night { display: block; visibility: visible; }

    /* — footer (256:2317): bg, crest fill and head/sub text all flip via tokens
       (--surface-raised, --text-heading); only the meadow art swap stays here — */
    /* footer meadow: swap in the purple night meadow (same asset as testimonials night
       land); bleed a few vw past both edges (footer clips overflow-x) so the art's thin
       transparent edges fall off-screen and the meadow spans full viewport width */
    body.night .ft-land { content: url("../images/land%20meadow-night.png"); width: 106vw; }

    /* menu-overlay gradient stops follow the night sky via --hero-from/--hero-to (no rule needed) */

    /* ════════════════════════════════════════════════════════════════════
       ABOUT PAGE — in-page view (Figma node 1:452). Phase A: content only.
       ════════════════════════════════════════════════════════════════════ */
    /* no box of its own: the about sections flow directly in <body> so their
       position:sticky is confined to the page (not this wrapper) — this lets
       fun-facts stay pinned while the (body-level) footer scrolls up over it.
       The [hidden] rule is more specific, so routing still hides the view. */
    #about-view { display: contents; }
    #about-view[hidden] { display: none; }

    /* — Intro / hero (node 1:453, 1440×1024) — */
    .about-intro {
      position: sticky;                  /* pin the hero so the next section scrolls up over it */
      top: 0;
      overflow: hidden;
      background: linear-gradient(180deg, #fff9f1 0%, #e7d6bd 100%);  /* light warm gradient; night restored below */
      color: var(--text-heading);
      /* body ink: near-black in light, warm cream in night */
      --about-ink: #2b2b2b;
      /* JS shrinks this (≤1) so the whole 1440×1024 frame — land included —
         fits within the viewport height; section collapses to the scaled height */
      --intro-scale: 1;
      height: calc(1024px * var(--intro-scale));
      min-height: 100vh;                 /* always fill the viewport when pinned (no gap behind) */
      /* JS-set vertical offset that drops the scaled frame down so the content
         column sits at the viewport's vertical centre (esp. ≤1440px, where the
         frame scales down and would otherwise float near the top) */
      --intro-offset: 0px;
    }
    .about-intro-stage {
      position: relative;
      width: 1440px;
      height: 1024px;
      margin: 0 auto;
      transform-origin: top center;
      /* translate is in final px (applied after scale) → centres content vertically */
      transform: translateY(var(--intro-offset, 0px)) scale(var(--intro-scale));
    }
    /* scene wrapper (holds art + content): shrinks from its centre as the page
       scrolls, mirroring the home page's .hero-content (1 → 0.88). Kept separate
       from the stage so the fit-scale / centring offset stay independent. */
    .ai-scene {
      position: absolute;
      inset: 0;
      transform-origin: center center;
      transform: scale(var(--intro-scroll, 1));
      will-change: transform;
    }

    /* decorative art (behind content) */
    .ai-sun-clouds { position: absolute; pointer-events: none; }
    .ai-sun-clouds{ left: 713px;  top: 93.6px;   width: 697px;  height: auto;  z-index: 1; }

    /* content row (node 1:563): 1320px centred, cols vertically centred */
    .ai-content {
      position: absolute;
      left: 50%;
      top: 233.75px;
      transform: translateX(-50%);
      width: 1320px;
      display: flex;
      align-items: flex-start;
      justify-content: space-between;
      z-index: 3;
    }
    .ai-copy {
      width: 720px;
      display: flex;
      flex-direction: column;
      gap: 32px;
    }
    .ai-heading {
      font-family: 'DM Serif Display', serif;
      font-weight: 400;
      font-size: 40px;
      line-height: 1.358;
      color: var(--text-heading);
    }
    .ai-body {
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 400;
      font-size: 24px;
      line-height: 1.68;
      letter-spacing: -0.96px;
      color: var(--about-ink);
      display: flex;
      flex-direction: column;
      gap: 20px;                          /* visible break between paragraphs */
    }
    .ai-body p { margin: 0; }
    .ai-body strong { font-weight: 700; }

    /* polaroid stack (node 1:568, 516×588.695) */
    .ai-photos { position: relative; width: 516px; height: 588.695px; flex-shrink: 0; }
    .ai-polaroid {
      position: absolute;
      display: flex;
      align-items: flex-start;
      justify-content: center;
    }
    .ai-polaroid .ai-card {
      background: #fff;
      border: 0.723px solid rgba(0,0,0,0.2);
      border-radius: 2.89px;
      padding: 11.561px 11.561px 26.012px;
      box-shadow: 0 8px 24px rgba(0,0,0,0.12);
    }
    .ai-polaroid .ai-photo {
      display: block;
      width: 212.431px;
      height: 299.137px;
      border-radius: 2.89px;
      overflow: hidden;
    }
    .ai-polaroid .ai-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

    .ai-polaroid.p1 { left: 170.7px;  top: 49.43px;  width: 247.505px; height: 344.96px;  }
    .ai-polaroid.p1 .ai-card { transform: rotate(-2.06deg); }
    .ai-polaroid.p2 { left: 43.32px;  top: 133.79px; width: 283.772px; height: 368.465px; }
    .ai-polaroid.p2 .ai-card { transform: rotate(-8.7deg); }
    .ai-polaroid.p3 { left: 206.09px; top: 181.63px; width: 266.583px; height: 357.635px; }
    .ai-polaroid.p3 .ai-card { transform: rotate(5.47deg); }
    /* front polaroid (node 1:575) — larger, on top */
    .ai-polaroid.p4 { left: 106.41px; top: 74px; width: 291.539px; height: 401.654px; z-index: 4; }
    .ai-polaroid.p4 .ai-card {
      transform: rotate(3.06deg);
      border-width: 0.832px;
      border-radius: 3.328px;
      padding: 13.313px 13.313px 29.955px;
    }
    .ai-polaroid.p4 .ai-photo { width: 244.63px; height: 344.479px; border-radius: 3.328px; }

    /* — drag interaction — pick up & move polaroids one by one — */
    .ai-polaroid {
      cursor: grab;
      touch-action: none;                 /* let pointer drag beat page scroll */
      will-change: transform;
      transform: translate3d(0, 0, 0);
    }
    .ai-polaroid .ai-card { pointer-events: none; }   /* drag the whole tile, not the image */
    .ai-polaroid.is-dragging { cursor: grabbing; }
    .ai-polaroid.is-dragging .ai-card { box-shadow: 0 22px 48px rgba(0,0,0,0.24); }
    /* drop: spring the lift-scale back with a gentle overshoot → subtle bounce */
    .ai-polaroid.is-dropping {
      transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
    }
    /* reset: glide each tile home with a smooth, un-bouncy settle */
    .ai-polaroid.is-settling {
      transition: transform 0.72s cubic-bezier(0.22, 1, 0.36, 1);
    }
    @media (prefers-reduced-motion: reduce) {
      .ai-polaroid.is-settling,
      .ai-polaroid.is-dropping { transition: none; }
    }

    /* restart / reset button (bottom-right of the photo stage) */
    .ai-photos-reset {
      position: absolute;
      right: 2px;
      bottom: 6px;
      z-index: 10;
      display: inline-flex;
      align-items: flex-start;
      justify-content: center;
      width: 46px;
      height: 46px;
      padding: 0;
      border: 0.9px solid rgba(43,43,43,0.18);
      border-radius: 999px;
      background: rgba(255,255,255,0.72);
      backdrop-filter: blur(6px);
      color: var(--about-ink);
      cursor: pointer;
      opacity: 0;
      transform: translateY(6px) scale(0.9);
      transition: opacity 0.32s ease, transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
                  background-color 0.2s ease, box-shadow 0.2s ease;
      box-shadow: 0 6px 18px rgba(43,43,43,0.14);
    }
    .ai-photos.has-moved .ai-photos-reset {
      opacity: 1;
      transform: translateY(0) scale(1);
    }
    .ai-photos-reset svg { width: 22px; height: 22px; display: block; transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1); }
    .ai-photos-reset:hover { background: rgba(255,255,255,0.95); box-shadow: 0 10px 24px rgba(43,43,43,0.2); }
    .ai-photos-reset:hover svg { transform: rotate(-160deg); }
    .ai-photos-reset:active { transform: translateY(1px) scale(0.96); }
    .ai-photos-reset:focus-visible { outline: 2px solid var(--about-ink); outline-offset: 3px; }
    body.night .ai-photos-reset {
      background: rgba(20,20,26,0.6);
      border-color: rgba(255,249,233,0.24);
      box-shadow: 0 6px 18px rgba(0,0,0,0.4);
    }
    body.night .ai-photos-reset:hover { background: rgba(30,30,38,0.85); }

    /* — Intro night treatment (Figma node 339:3219) — */
    /* deep charcoal→navy sky (#0a0a0f → #152f5d), light #eee ink, and the
       night-painted sun+clouds / land art swapped in. */
    body.night .about-intro   {
      background: linear-gradient(180deg, #0a0a0f 0%, #152f5d 100%);
      --about-ink: #eee;
      --text-heading: #eee;
    }
    body.night .ai-sun-clouds { content: url("../images/about-sun-clouds-night.png"); }
    body.night .ai-land       { content: url("../images/about-land-night.png"); }
    body.night .ai-polaroid .ai-card { box-shadow: 0 8px 24px rgba(0,0,0,0.4); }

    /* — Professional Experience (node 1:707, 1440×1024) — */
    .about-exp {
      position: sticky;                  /* pin it too, so fun-facts scrolls up over it */
      top: 0;
      z-index: 2;                        /* scroll up and over the pinned hero */
      /* clip sideways (the land is wider than the viewport) but let the land
         spill upward past the top edge so it laps over the hero on scroll */
      overflow-x: clip;
      overflow-y: visible;
      background: var(--bg-base);         /* light warm base (#fff9f1) */
      --about-ink: #2b2b2b;
      min-height: 100vh;                 /* always fill the viewport when pinned (no gap behind) */
    }
    /* land: the crest is the section's visible top edge (like the home-page grass
       wave). The land's solid cream band must fully cover the section's straight top
       edge across the whole width, so only the wavy crest shows — never a flat line.
       Its deepest valley (~80% down the art) is kept just above the section edge,
       and the land's cream base matches the section fill (#fff9f1) so the two are seamless. */
    .about-exp > .ai-land {
      position: absolute;
      left: 50%;
      bottom: calc(100% - 20px);         /* solid base laps 20px into the green; crest rises over the hero */
      width: 1920px;
      transform: translateX(-50%);
      z-index: 3;
      pointer-events: none;
    }
    .about-exp-stage {
      position: relative;
      width: 1440px;
      max-width: 100%;
      height: 1024px;
      margin: 0 auto;
    }
    /* rotated section label (node 1:708) */
    .aexp-label {
      position: absolute;
      left: 103.5px;
      top: 50%;
      transform: translate(-50%, -50%) rotate(-90deg);
      transform-origin: center;
      font-family: 'DM Serif Display', serif;
      font-weight: 400;
      font-size: 44px;
      line-height: 1.358;
      color: rgba(0,0,0,0.53);
      white-space: nowrap;
      pointer-events: none;
    }
    /* accordion list (node 1:709) */
    .aexp-list {
      position: absolute;
      left: calc(50% + 98px);
      top: 101.5px;
      transform: translateX(-50%);
      width: 1124px;
      max-width: calc(100% - 220px);
      display: flex;
      flex-direction: column;
      gap: 24px;
    }
    .aexp-divider { width: 100%; height: 1px; background: rgba(0,0,0,0.18); }

    .aexp-head {
      display: flex;
      align-items: flex-start;
      justify-content: space-between;
      gap: 24px;
      width: 100%;
      min-height: 89px;
      padding: 0;
      border: 0;
      background: none;
      text-align: left;
      cursor: pointer;
      transition: opacity 0.25s ease;
    }
    .aexp-head:hover { opacity: 0.72; }
    .aexp-head:active { opacity: 0.55; }
    .aexp-head:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 6px; border-radius: 4px; }

    .aexp-company { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
    .aexp-name {
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 500;
      font-size: 30px;
      line-height: 1.358;
      letter-spacing: -1.2px;
      color: var(--text-heading);       /* black day → cream night */
    }
    .aexp-role {
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 400;
      font-size: 20px;
      line-height: 1.68;
      letter-spacing: -0.8px;
      color: var(--about-ink);
    }
    .aexp-meta { display: flex; align-items: flex-start; gap: 16px; flex-shrink: 0; }
    .aexp-date {
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 400;
      font-size: 20px;
      line-height: 1.68;
      letter-spacing: -0.8px;
      color: var(--about-ink);
      text-align: right;
      white-space: nowrap;
    }
    /* +/× toggle (node 1:719/1:733) — a plus that rotates 45° to a cross when open */
    .aexp-toggle {
      position: relative;
      width: 40px;
      height: 40px;
      flex-shrink: 0;
      transition: transform 0.35s cubic-bezier(0.34,1.56,0.64,1);
    }
    .aexp-toggle::before,
    .aexp-toggle::after {
      content: "";
      position: absolute;
      background: var(--text-heading);
      border-radius: 2px;
    }
    .aexp-toggle::before { left: 50%; top: 8px; width: 2.4px; height: 24px; transform: translateX(-50%); }
    .aexp-toggle::after  { top: 50%; left: 8px; height: 2.4px; width: 24px; transform: translateY(-50%); }
    .aexp-item.is-open .aexp-toggle { transform: rotate(45deg); }

    /* expandable panel (node 1:722) */
    .aexp-panel { overflow: hidden; max-height: 0; transition: max-height 0.42s ease; }
    .aexp-item.is-open .aexp-panel { max-height: 700px; }
    .aexp-panel-inner {
      padding: 17px 8px 8px;
      width: 1029.55px;
      max-width: 100%;
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 400;
      font-size: 20px;
      line-height: 1.68;
      letter-spacing: -0.8px;
      color: var(--about-ink);
    }
    .aexp-panel-inner p { margin: 0; }
    .aexp-panel-inner strong { font-weight: 700; }
    /* bulleted highlights (e.g. gskinner) */
    .aexp-bullets {
      margin: 14px 0 0;
      padding-left: 1.2em;
      list-style: disc;
      display: flex;
      flex-direction: column;
      gap: 10px;
    }
    .aexp-bullets li { padding-left: 4px; }
    .aexp-bullets li::marker { color: rgba(0,0,0,0.4); }
    body.night .aexp-bullets li::marker { color: rgba(255,249,233,0.5); }

    /* — Experience night treatment (deep-green surface derived for dark mode) — */
    body.night .about-exp     { --about-ink: rgba(255,249,233,0.82); }
    body.night .aexp-label    { color: rgba(255,249,233,0.5); }
    body.night .aexp-divider  { background: rgba(255,249,233,0.18); }

    /* — Fun facts (node 1:767, 1440×1088) — */
    .about-fun {
      position: sticky;                  /* pin it too, so the footer scrolls up over it */
      top: 0;
      z-index: 3;                        /* scroll up and over the pinned experience */
      overflow-x: clip;                  /* clip sideways, let the crest spill up over experience */
      overflow-y: visible;
      background: #9eafbe;               /* fallback behind the photo */
      min-height: 100vh;                 /* always fill the viewport when pinned (no gap behind) */
    }
    /* photo background — one continuous image whose TOP edge is the wavy crest, lapping
       up over the experience on scroll. A single layer (image + wave mask) keeps the
       photo seamless across the crest → body: no duplicated sky, no straight line. */
    .about-fun > .aff-bg {
      position: absolute;
      left: 0;
      right: 0;
      top: -94px;                        /* rise 94px above the section top (the crest poke) */
      bottom: 0;
      z-index: 0;
      background: url("../images/fun%20facts%20background.webp") center bottom / cover no-repeat;
      --aff-wave: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201440%20116'%20preserveAspectRatio='none'%3E%3Cpath%20fill='%23fff'%20d='M0,116%20L0,50%20Q%2090,24%20180,50%20T%20360,50%20T%20540,50%20T%20720,50%20T%20900,50%20T%201080,50%20T%201260,50%20T%201440,50%20L1440,116%20Z'/%3E%3C/svg%3E");
      /* wave silhouette across the top 116px + fully-opaque below → union: the photo's
         visible top edge follows the wave, everything below shows the full photo */
      -webkit-mask: var(--aff-wave) top left / 100% 116px no-repeat,
                    linear-gradient(#000, #000) bottom left / 100% calc(100% - 115px) no-repeat;
      mask: var(--aff-wave) top left / 100% 116px no-repeat,
            linear-gradient(#000, #000) bottom left / 100% calc(100% - 115px) no-repeat;
      pointer-events: none;
    }
    /* the 1440×1088 card grid, absolutely centred in the pinned viewport and scaled down
       (never up) by --ff-fit so every card stays visible on shorter/narrower viewports.
       Taken out of flow so it never forces the section taller than the viewport. */
    .about-fun-stage {
      position: absolute;
      left: 50%;
      top: 50%;
      width: 1440px;
      height: 1088px;
      transform: translate(-50%, -50%) scale(var(--ff-fit, 1));
      transform-origin: center center;
      z-index: 1;
      --ff-open: 1;
    }
    /* trailing flow space after the pinned fun-facts section: keeps it pinned (grid held
       on screen) for this much extra scroll before the footer rises over it */
    .aff-hold { height: 900px; pointer-events: none; }

    .ff-card {
      position: absolute;
      width: 305px; height: 300px;
      /* --ff-open (set on the stage, scroll-driven): 1 = grid, 0 = stacked pile.
         --dx/--dy/--rot are each card's stacked-pile offset from its grid spot (set by JS).
         At open=1 this collapses to the original translateX(-50%) grid layout. */
      transform:
        translateX(-50%)
        translate(calc(var(--dx, 0px) * (1 - var(--ff-open, 1))),
                  calc(var(--dy, 0px) * (1 - var(--ff-open, 1))))
        rotate(calc(var(--rot, 0deg) * (1 - var(--ff-open, 1))))
        scale(calc(1 - 0.06 * (1 - var(--ff-open, 1))));
      transform-origin: center center;
      will-change: transform;
      border-radius: 32px;
      overflow: hidden;
      background: #fff;
      box-shadow: 10px 10px 20px rgba(0,0,0,0.15);
    }
    .ff-card.b1 { border: 1px solid rgba(0,0,0,0.15); }
    .pos-lt { left: 345px;  top: 203.5px; }
    .pos-ct { left: 720px;  top: 203.5px; }
    .pos-rt { left: 1098px; top: 203.5px; }
    .pos-lb { left: 345px;  top: 584.5px; }
    .pos-cb { left: 720px;  top: 584.5px; }
    .pos-rb { left: 1098px; top: 584.5px; }

    /* hikes 2025 (node 1:838) */
    .ff-hikes-label { position: absolute; left: 27.78px; top: 29.61px; display: flex; flex-direction: column; gap: 8px; z-index: 2; }
    .ff-hikes-label .k { font-family: 'Space Grotesk', sans-serif; font-size: 16px; text-transform: uppercase; color: #6d6d6d; }
    .ff-hikes-label .y { font-family: 'DM Serif Display', serif; font-size: 60px; letter-spacing: -2.4px; color: #2b2b2b; line-height: 1; }
    .ff-photo { position: absolute; width: 211px; height: 253px; border: 6px solid #fff; border-radius: 27px; box-shadow: 0 0 16px 8px rgba(0,0,0,0.15); overflow: hidden; box-sizing: border-box; }
    .ff-photo img { width: 100%; height: 100%; object-fit: cover; }
    .ff-photo.h1 { left: 140.6px; top: 370.8px; transform: translate(-50%,-50%) rotate(-8.86deg); }
    .ff-photo.h2 { left: 218.2px; top: 299px;   transform: translate(-50%,-50%) rotate(-5.54deg); z-index: 1; }
    .ff-photo.h3 { left: 295.9px; top: 235.3px; transform: translate(-50%,-50%) rotate(-1.56deg); z-index: 2; }
    /* hikes card hover: grow 1.3x + tilt right; photos slide up to reveal more.
       Uses the independent scale/rotate/translate longhands so it composes with the
       scroll-driven `transform` on .ff-card without transitioning that transform. */
    .ff-card.ff-hikes { transition: scale 0.4s cubic-bezier(0.22,1,0.36,1), rotate 0.4s cubic-bezier(0.22,1,0.36,1), box-shadow 0.4s cubic-bezier(0.22,1,0.36,1); }
    .ff-card.ff-hikes:hover { scale: 1.12; rotate: 5deg; z-index: 5; box-shadow: 20px 24px 44px rgba(0,0,0,0.26); }
    .ff-hikes .ff-photo { transition: translate 0.45s cubic-bezier(0.22,1,0.36,1); }
    .ff-hikes:hover .ff-photo.h1 { translate: 0 -56px; }
    .ff-hikes:hover .ff-photo.h2 { translate: 0 -46px; }
    .ff-hikes:hover .ff-photo.h3 { translate: 0 -36px; }

    /* weekend to do (node 1:845) */
    .ff-wk { background: #f4cd44; }
    .ff-wk-friday { position: absolute; left: 19px; top: 19px; font-family: 'Space Grotesk', sans-serif; font-size: 16px; text-transform: uppercase; color: #6d6d6d; }
    .ff-wk-dots { position: absolute; left: 256px; top: 24px; display: flex; gap: 4px; }
    .ff-wk-dots i { width: 5px; height: 5px; border-radius: 50%; background: #6d6d6d; }
    .ff-wk-panel { position: absolute; left: -1px; top: 55.94px; width: 305px; height: 243px; background: #fff; border-radius: 22px; overflow: hidden; }
    .ff-wk-inner { position: absolute; left: 20px; top: 20px; width: 264px; display: flex; flex-direction: column; gap: 16px; }
    .ff-wk-title { font-family: 'DM Serif Display', serif; font-size: 32px; letter-spacing: -1.28px; color: #00315c; line-height: 0.97; }
    .ff-wk-list { display: flex; flex-direction: column; gap: 16px; }
    .ff-wk-list .r { display: flex; gap: 8px; align-items: flex-start; }
    .ff-wk-list .cb { width: 16px; height: 16px; border: 1px solid rgba(0,0,0,0.25); border-radius: 4px; flex-shrink: 0; }
    .ff-wk-list .t { font-family: 'Space Grotesk', sans-serif; font-size: 16px; line-height: 1.15; color: #6d6d6d; text-transform: capitalize; position: relative; transition: color 0.3s cubic-bezier(0.22,1,0.36,1); }
    /* weekend card hover: grow + tilt (independent scale/rotate longhands so it composes
       with the scroll-driven `transform` on .ff-card). Alternate rows (1,3,5) animate to a
       "completed" state — checkbox fills + ticks, label greys and strikes through. */
    .ff-card.ff-wk { transition: scale 0.4s cubic-bezier(0.22,1,0.36,1), rotate 0.4s cubic-bezier(0.22,1,0.36,1), box-shadow 0.4s cubic-bezier(0.22,1,0.36,1); }
    .ff-card.ff-wk:hover { scale: 1.07; rotate: -4deg; z-index: 5; box-shadow: -16px 24px 44px rgba(0,0,0,0.24); }
    .ff-wk-list .cb { position: relative; transition: background 0.3s cubic-bezier(0.22,1,0.36,1), border-color 0.3s cubic-bezier(0.22,1,0.36,1); }
    .ff-wk-list .cb::after { content: ""; position: absolute; left: 5px; top: 1.5px; width: 4px; height: 8px; border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg) scale(0); transform-origin: center; transition: transform 0.3s cubic-bezier(0.22,1,0.36,1); }
    .ff-wk-list .t::after { content: ""; position: absolute; left: 0; top: 52%; width: 100%; height: 1.5px; background: currentColor; transform: scaleX(0); transform-origin: left center; transition: transform 0.35s cubic-bezier(0.22,1,0.36,1); }
    .ff-wk:hover .ff-wk-list .r:nth-child(odd) .cb { background: #00315c; border-color: #00315c; }
    .ff-wk:hover .ff-wk-list .r:nth-child(odd) .cb::after { transform: rotate(45deg) scale(1); }
    .ff-wk:hover .ff-wk-list .r:nth-child(odd) .t { color: #a2a2a2; }
    .ff-wk:hover .ff-wk-list .r:nth-child(odd) .t::after { transform: scaleX(1); }

    /* gully boy music (node 1:885) */
    .ff-gully { background: #111; }
    /* white frame drawn as an overlay (not a layout border) so the image fills the whole
       card — corners included — with the frame sitting on top of it. */
    .ff-gully::after { content: ""; position: absolute; inset: 0; border: 11px solid #fff; border-radius: 32px; pointer-events: none; z-index: 6; }
    /* clip-path (geometric) reliably contains the zoomed/composited image to the rounded
       frame even while the card is transformed on hover — plain overflow+border-radius
       drops the rounded clip on transformed children in WebKit/Blink. */
    .ff-media { position: absolute; inset: 0; overflow: hidden; border-radius: 32px; clip-path: inset(0 round 32px); }
    /* image starts slightly zoomed in and eases out on hover to add motion */
    .ff-media img { width: 100%; height: 100%; object-fit: cover; transform: scale(1.08); transform-origin: center; transition: transform 0.6s cubic-bezier(0.22,1,0.36,1); }
    .ff-gully:hover .ff-media img { transform: scale(1); }
    .ff-gully-grad { position: absolute; inset: 0; background: linear-gradient(to bottom, rgba(0,0,0,0) 40%, rgba(50,50,50,0.52) 78%); border-radius: 32px; clip-path: inset(0 round 32px); }
    .ff-gully-pill { position: absolute; left: 20px; top: 22px; display: flex; gap: 6px; align-items: flex-start; background: rgba(0,0,0,0.45); border-radius: 999px; padding: 4px 12px 4px 4px; backdrop-filter: blur(2px); }
    .ff-vinyl { width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0; position: relative;
      background: repeating-radial-gradient(circle at 50% 50%, #121212 0 2px, #2a2a2a 2px 3px); border: 0.64px solid #373535; box-shadow: 0 0 0 0.5px #62615e; }
    .ff-vinyl::after { content: ""; position: absolute; left: 50%; top: 50%; width: 7px; height: 7px; border-radius: 50%; background: #8a8a8a; transform: translate(-50%,-50%); }
    .ff-gully-pill .meta { display: flex; flex-direction: column; gap: 3px; }
    .ff-gully-pill .meta .a { font-family: 'Inter', sans-serif; font-size: 12px; color: #fff; white-space: nowrap; }
    .ff-gully-pill .meta .a em { font-style: italic; }
    .ff-gully-pill .meta .b { font-family: 'Inter', sans-serif; font-size: 8px; color: rgba(255,255,255,0.7); }
    .ff-gully-player { position: absolute; left: 28px; top: 207px; width: 241px; display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
    .ff-gully-prog { display: flex; flex-direction: column; gap: 8px; width: 100%; }
    .ff-gully-time { display: flex; justify-content: space-between; font-family: 'Inter', sans-serif; font-size: 10px; color: #fff; }
    .ff-gully-bar { height: 4px; border-radius: 99px; background: rgba(137,137,137,0.28); position: relative; overflow: hidden; }
    .ff-gully-bar span { position: absolute; left: 0; top: 0; height: 4px; width: 0; background: #fff; border-radius: 99px; }
    .ff-gully-ctrls { display: flex; gap: 32px; align-items: flex-start; }
    .ff-gully-ctrls svg { width: 30px; height: 30px; display: block; fill: #fff; }
    /* gully card hover: grow + tilt right (independent scale/rotate longhands so it composes
       with the scroll-driven `transform` on .ff-card). Whole card links out to YouTube. */
    .ff-card.ff-gully { cursor: pointer; transition: scale 0.4s cubic-bezier(0.22,1,0.36,1), rotate 0.4s cubic-bezier(0.22,1,0.36,1), box-shadow 0.4s cubic-bezier(0.22,1,0.36,1); }
    .ff-card.ff-gully:hover { scale: 1.07; rotate: 5deg; z-index: 6; box-shadow: 20px 24px 44px rgba(0,0,0,0.3); }
    .ff-gully-link { position: absolute; inset: 0; z-index: 5; display: block; }

    /* quote (node 1:778) — graph-paper note */
    .ff-quote-grid { position: absolute; inset: 0; pointer-events: none;
      background-image: linear-gradient(#cfe0f0 1px, transparent 1px), linear-gradient(90deg, #cfe0f0 1px, transparent 1px);
      background-size: 18px 18px; opacity: 0.7; }
    /* format-quote icon (node 1:783) — inset from the top-left with padding, rotated 180° */
    .ff-quote-mark { position: absolute; left: 16px; top: 16px; width: 84px; height: 84px; transform: rotate(180deg); pointer-events: none; }
    .ff-quote-mark img { width: 100%; height: 100%; display: block; }
    /* quote text + citation — Figma spec (nodes 1:785 / 1:786), left-aligned */
    .ff-quote-text { position: absolute; left: 15px; top: 102px; width: 264px; font-family: 'DM Serif Display', serif; color: #00315c; letter-spacing: -2.4px; line-height: 0.97; }
    .ff-quote-text .a { font-size: 60px; } .ff-quote-text .b { font-size: 52px; } .ff-quote-text .c { font-size: 60px; font-style: italic; }
    .ff-quote-cite { position: absolute; left: 108px; top: 245px; font-family: 'Space Grotesk', sans-serif; font-size: 16px; color: #5b5b5b; text-transform: uppercase; white-space: nowrap; }
    /* scale flush to the right edge — translucent column + tick ruler + numbers (nodes 1:787–1:837) */
    .ff-quote-spine { position: absolute; right: 1px; top: 0; width: 25px; height: 100%; display: flex; flex-direction: column; align-items: flex-start; padding-top: 22px; padding-left: 2px; gap: 22px; background: rgba(255,255,255,0.45); }
    .ff-quote-spine i { font-family: 'Inter', sans-serif; font-size: 10px; color: #939393; font-style: normal; letter-spacing: -0.4px; line-height: 1; }
    .ff-quote-ruler { position: absolute; right: 0; top: -1px; width: 16px; height: 328px; pointer-events: none; }
    .ff-quote-ruler img { width: 100%; height: 100%; display: block; }
    /* quote card hover: grow + tilt (independent scale/rotate longhands so it composes with
       the scroll-driven `transform` on .ff-card). "details." shifts to the quote-mark yellow. */
    .ff-card.ff-quote { transition: scale 0.4s cubic-bezier(0.22,1,0.36,1), rotate 0.4s cubic-bezier(0.22,1,0.36,1), box-shadow 0.4s cubic-bezier(0.22,1,0.36,1); }
    .ff-card.ff-quote:hover { scale: 1.07; rotate: 4deg; z-index: 5; box-shadow: 20px 24px 44px rgba(0,0,0,0.22); }
    .ff-quote-text .c { background-image: linear-gradient(currentColor, currentColor); background-repeat: no-repeat; background-position: 0 100%; background-size: 0% 3px; transition: background-size 0.35s cubic-bezier(0.22,1,0.36,1); }
    .ff-quote:hover .ff-quote-text .c { background-size: 100% 3px; }

    /* blog (node 342:5315) */
    .ff-blog-img { position: absolute; left: 9.5px; top: 9.79px; width: 284px; height: 178px; border-radius: 22px; overflow: hidden; box-shadow: 0 4px 16px rgba(0,0,0,0.16); }
    /* resting zoom via width/height (keeps the image crisp); transform is used only for the
       hover zoom-out motion. own border-radius keeps it clipped while transformed on hover. */
    .ff-blog-img img { position: absolute; top: -4%; left: -4%; width: 108%; height: 108%; object-fit: cover; border-radius: 22px; transform-origin: center; transition: transform 0.6s cubic-bezier(0.22,1,0.36,1); }
    .ff-blog:hover .ff-blog-img img { transform: scale(0.93); }
    .ff-blog-title { position: absolute; left: 14.45px; top: 201.79px; width: 279px; font-family: 'DM Serif Display', serif; font-size: 22px; line-height: 1.16; color: #00315c; }
    .ff-blog-foot { position: absolute; left: 14.5px; top: 259.79px; width: 272px; display: flex; align-items: flex-start; justify-content: space-between; }
    .ff-blog-foot .rl { font-family: 'Space Grotesk', sans-serif; font-size: 14px; color: #6d6d6d; text-transform: uppercase; }
    .ff-play-btn svg, .ff-play-btn .pl-icon { width: 20px; height: 20px; display: block; }
    /* icon via CSS mask so its colour can change on hover (an <img> SVG can't be recoloured) */
    .ff-blog-foot .rl-icon { width: 20px; height: 20px; display: block; background-color: #6d6d6d;
      -webkit-mask: url("../images/about-blog-extlink.svg") center / contain no-repeat; mask: url("../images/about-blog-extlink.svg") center / contain no-repeat; }
    .ff-bookmark { position: absolute; right: -1px; top: 1.79px; width: 63px; height: 63px; }
    /* blog card hover: grow + tilt (matches the checklist card's hover) */
    .ff-card.ff-blog { cursor: pointer; transition: scale 0.4s cubic-bezier(0.22,1,0.36,1), rotate 0.4s cubic-bezier(0.22,1,0.36,1), box-shadow 0.4s cubic-bezier(0.22,1,0.36,1); }
    .ff-card.ff-blog:hover { scale: 1.07; rotate: -4deg; z-index: 5; box-shadow: -16px 24px 44px rgba(0,0,0,0.24); }
    /* on hover the CTA (READ BLOG + link-out icon) grows for emphasis */
    .ff-blog-foot .rl { transform-origin: left center; background-image: linear-gradient(currentColor, currentColor); background-repeat: no-repeat; background-position: 0 100%; background-size: 0% 1.5px; transition: transform 0.35s cubic-bezier(0.22,1,0.36,1), background-size 0.35s cubic-bezier(0.22,1,0.36,1); }
    .ff-blog-foot .rl-icon { transform-origin: right center; transition: transform 0.35s cubic-bezier(0.22,1,0.36,1), background-color 0.35s cubic-bezier(0.22,1,0.36,1); }
    .ff-blog:hover .ff-blog-foot .rl { transform: scale(1.18); background-size: 100% 1.5px; }
    .ff-blog:hover .ff-blog-foot .rl-icon { transform: scale(1.22); background-color: #00315c; }
    .ff-blog-link { position: absolute; inset: 0; z-index: 5; display: block; }

    /* starry night game (node 342:5417) */
    .ff-starry { border: 9px solid #fff; }
    /* white panel is a shaped SVG (Vector 214) — rounded body with a raised tab on the top-left */
    .ff-starry-panel { position: absolute; left: 0; right: 0; bottom: 0; top: 118px; background: url("../images/about-starry-panel.svg") no-repeat; background-size: 100% 100%; }
    .ff-starry-panel .k { position: absolute; left: 12px; top: 22px; font-family: 'Space Grotesk', sans-serif; font-size: 14px; text-transform: uppercase; color: #6d6d6d; }
    .ff-starry-panel .t { position: absolute; left: 12px; top: 42px; font-family: 'Space Grotesk', sans-serif; font-size: 19px; color: #101010; }
    .ff-starry-row { position: absolute; left: 12px; right: 12px; bottom: 18px; display: flex; align-items: flex-start; justify-content: space-between; }
    .ff-avatars { display: flex; align-items: flex-start; }
    .ff-avatars .av { width: 32px; height: 32px; border-radius: 50%; border: 2px solid #fff; margin-right: -14px; overflow: hidden; background: #d9e0e6; }
    .ff-avatars .av img { width: 100%; height: 100%; object-fit: cover; display: block; }
    .ff-avatars .plus { margin-left: 22px; font-family: 'Space Grotesk', sans-serif; font-size: 14px; text-transform: uppercase; color: #6d6d6d; }
    .ff-play-btn { display: flex; gap: 8px; align-items: flex-start; background: #f4cd44; border: 1px solid #000; border-radius: 16px; height: 40px; padding: 12px 16px; cursor: pointer; transition: transform 0.2s ease; }
    .ff-play-btn:hover { transform: translateY(-1px); }
    .ff-play-btn:active { transform: translateY(0); }
    .ff-play-btn span { font-family: 'Space Grotesk', sans-serif; font-weight: 500; font-size: 22px; line-height: 1.16; color: #00315c; }
    /* starry card hover: grow + tilt right + game image zoom-out (matches the gully card) */
    .ff-card.ff-starry { cursor: pointer; transition: scale 0.4s cubic-bezier(0.22,1,0.36,1), rotate 0.4s cubic-bezier(0.22,1,0.36,1), box-shadow 0.4s cubic-bezier(0.22,1,0.36,1); }
    .ff-card.ff-starry:hover { scale: 1.07; rotate: 5deg; z-index: 6; box-shadow: 20px 24px 44px rgba(0,0,0,0.3); }
    .ff-starry:hover .ff-media img { transform: scale(1); }
    .ff-starry-link { position: absolute; inset: 0; z-index: 5; display: block; }

    /* — Fun facts night treatment (photo dimmed a touch; cards stay light) — */
    body.night .about-fun { background: #0f1830; }
    body.night .about-fun > .aff-bg { filter: brightness(0.82) saturate(0.9); }

    /* ════ ABOUT — RESPONSIVE: below 1100px the 1440 canvases reflow into a
       single flow column (no pins, no fixed stages). ════ */
    @media (max-width: 1100px) {

      /* — intro: flow column, vertically centred in the first screen — */
      .about-intro {
        position: relative;
        height: auto;
        min-height: 100vh;      /* lvh (below): the pinned hold must cover a collapsed-toolbar viewport */
        min-height: 100lvh;
        /* generous bottom buffer so the experience section slides up through empty
           space first — the end of the description stays readable during the reveal */
        padding: 128px 24px 200px;
        display: flex;
        align-items: flex-start;
      }
      .about-intro-stage {
        width: 100%;
        height: auto;
        transform: none;
      }
      .ai-scene {
        position: relative;
        inset: auto;
        transform: none;
      }
      .ai-sun-clouds {
        left: auto;
        right: -28px;
        top: -52px;               /* sit in the gap below the fixed nav, not behind it */
        width: min(52vw, 280px);
      }
      .ai-content {
        position: relative;
        left: auto;
        top: auto;
        transform: none;
        width: 100%;
        flex-direction: column;
        align-items: flex-start;
        gap: 48px;
      }
      /* unwrap copy so heading / photos / body order independently:
         header → polaroids → description (mobile only) */
      .ai-copy { display: contents; }
      .ai-heading {
        order: 1;
        width: 100%;
        font-size: clamp(28px, 5.4vw, 38px);
        line-height: 1.28;
      }
      .ai-body {
        order: 3;
        width: 100%;
        font-size: clamp(16px, 4.4vw, 20px);
        line-height: 1.65;
        letter-spacing: -0.4px;
        gap: 16px;
      }
      /* polaroid stack scales as one unit — --pol-scale is set from JS
         (min(1, (vw − 48) / 516)); the negative margin returns the ghost
         space the transform leaves under the scaled box */
      .ai-photos {
        order: 2;
        align-self: center;
        transform: scale(var(--pol-scale, 1));
        transform-origin: top center;
        margin-bottom: calc(-588px * (1 - var(--pol-scale, 1)));
      }

      /* — experience: static flow, horizontal section heading — */
      .about-exp {
        position: relative;
        min-height: 0;
      }
      .about-exp > .ai-land { width: max(1100px, 150vw); }
      .about-exp-stage {
        width: 100%;
        height: auto;
        padding: 96px 24px;
      }
      .aexp-label {
        position: static;
        transform: none;
        font-size: clamp(30px, 7vw, 40px);
        line-height: 1.2;
        white-space: normal;
        margin-bottom: 40px;
      }
      .aexp-list {
        position: static;
        transform: none;
        width: 100%;
        max-width: none;
        gap: 20px;
      }
      /* rows wrap: company + toggle share the first line, date drops below */
      .aexp-head {
        min-height: 0;
        padding: 4px 0;
        gap: 16px;
        row-gap: 6px;
        flex-wrap: wrap;
        align-items: flex-start;
      }
      .aexp-meta { display: contents; }
      .aexp-company { flex: 1 1 0; order: 1; }
      .aexp-toggle { order: 2; width: 32px; height: 32px; }
      .aexp-toggle::before { top: 7px; width: 2px; height: 18px; }
      .aexp-toggle::after  { left: 7px; width: 18px; height: 2px; }
      .aexp-date {
        order: 3;
        width: 100%;
        text-align: left;
        font-size: 14px;
        line-height: 1.4;
        letter-spacing: -0.3px;
        white-space: normal;
      }
      .aexp-name { font-size: clamp(20px, 5.2vw, 26px); letter-spacing: -0.8px; line-height: 1.3; }
      .aexp-role { font-size: 16px; line-height: 1.5; letter-spacing: -0.4px; }
      .aexp-panel-inner {
        width: 100%;
        padding: 12px 0 8px;
        font-size: 16px;
        line-height: 1.6;
        letter-spacing: -0.3px;
      }
      .aexp-bullets { gap: 8px; }

      /* — fun facts: cards stack in a centred column — */
      .about-fun {
        position: relative;
        min-height: 0;
        padding: 110px 24px 140px;
      }
      .about-fun-stage {
        position: static;
        width: 100%;
        height: auto;
        transform: none;
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(305px, 1fr));
        gap: 28px;
      }
      .ff-card {
        position: relative;
        left: auto;
        top: auto;
        transform: none;
        width: 100%;
        height: 300px;
        flex-shrink: 0;
      }
      .aff-hold { display: none; }
    }

    @media (max-width: 640px) {
      /* generous bottom buffer: the experience section slides up through this empty
         space first, so the end of the description stays readable during the reveal */
      .about-intro { padding: 112px 20px 200px; }
      .about-exp-stage { padding: 80px 20px; }
      .about-fun { padding: 96px 24px 0px;
  margin-bottom: -92px; }
    }

    /* — About routing: when #about is active, hide the landing sections and
       reveal #about-view (shared <site-nav> + <site-footer> stay). — */
    body.route-about .hero,
    body.route-about #story,
    body.route-about .featured-work,
    body.route-about .testimonials,
    body.route-about .t-scrub { display: none !important; }

    /* ═══════════════════════════════════════════════════════════════
       PLAYGROUND PAGE (Figma node 128:6061) — in-page view, routed via
       #playground exactly like About. Uniform brick grid: every tile is
       the same size (3-column even grid); the note fills a 2-wide brick.
       ═══════════════════════════════════════════════════════════════ */
    body.route-projects .hero,
    body.route-projects #story,
    body.route-projects .featured-work,
    body.route-projects .testimonials,
    body.route-projects .t-scrub { display: none !important; }

    #projects-view { display: contents; }
    #projects-view[hidden] { display: none; }

    /* — Work / Hatcha case study: routed via #hatcha, hides landing sections — */
    body.route-hatcha .hero,
    body.route-hatcha #story,
    body.route-hatcha .featured-work,
    body.route-hatcha .testimonials,
    body.route-hatcha .t-scrub { display: none !important; }
    #hatcha-view { display: contents; }
    #hatcha-view[hidden] { display: none; }

    /* — Work / Fireflut case study: routed via #fireflut, hides landing sections — */
    body.route-fireflut .hero,
    body.route-fireflut #story,
    body.route-fireflut .featured-work,
    body.route-fireflut .testimonials,
    body.route-fireflut .t-scrub { display: none !important; }
    #fireflut-view { display: contents; }
    #fireflut-view[hidden] { display: none; }

    /* — Work / Existence case study: routed via #existence, hides landing sections — */
    body.route-existence .hero,
    body.route-existence #story,
    body.route-existence .featured-work,
    body.route-existence .testimonials,
    body.route-existence .t-scrub { display: none !important; }
    #existence-view { display: contents; }
    #existence-view[hidden] { display: none; }

    /* — Work / Jumpable case study: routed via #jumpable, hides landing sections — */
    body.route-jumpable .hero,
    body.route-jumpable #story,
    body.route-jumpable .featured-work,
    body.route-jumpable .testimonials,
    body.route-jumpable .t-scrub { display: none !important; }
    #jumpable-view { display: contents; }
    #jumpable-view[hidden] { display: none; }

    /* first project tile is a link to the case study */
    a.project-tile { display: block; text-decoration: none; color: inherit; }

    /* frame: tiles start at y141 on the 1440 design; content column is
       1336 wide (x50 → x1386) with 16px gutters between tiles */
    .playground {
      background: var(--bg-base);
      padding: 141px 32px 200px;          /* bottom pad so the footer eases over, not abruptly */
      position: relative;                 /* JS bottom-pins it (sticky) so the footer scrolls over */
      z-index: 1;                         /* below the footer (z-index 12) so it laps over */
      overflow-x: clip;                   /* contains the full-bleed immersive gallery; no page scroll */
    }
    .pg-grid {
      max-width: 1720px;                   /* wider cap → larger tiles on roomy screens */
      margin: 0 auto;
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 32px;                           /* more breathing room between tiles */
    }
    /* intro band above the staggered field — header and paragraph side by side */
    .pg-intro {
      grid-column: 1 / -1;
      display: grid;
      grid-template-columns: repeat(3, 1fr);  /* mirror the tile columns */
      gap: 32px;                              /* match tile gutters so edges line up */
      align-items: start;
      margin-bottom: 8px;
      font-size: 16px;
      line-height: 30px;
      color: var(--text-heading);
    }
    .pg-intro h2 {
      grid-column: 1;                      /* sits over the first tile column */
      font-family: 'DM Serif Display', serif;
      font-weight: 400;
      font-size: 40px;                     /* matches About's .ai-heading */
      line-height: 1.358;
      margin: 0;
      color: var(--text-heading);
    }
    .pg-intro p { grid-column: 2 / 4; margin: 0; }  /* aligns with tile columns 2 & 3 */
    .pg-tile {
      /* --pg-offset = the running-bond stagger (per column, below).
         --reveal = scroll-scrub 0→1 (JS), scaling the tile up to full size as
         it enters the viewport — same treatment as the home project tiles. */
      --pg-offset: 0px;
      --reveal: 1;
      position: relative;
      border-radius: 40px;
      overflow: hidden;
      background: var(--c-ink);           /* dark base while video buffers */
      aspect-ratio: 4 / 3;                 /* uniform brick — all tiles identical */
      transform: translateY(var(--pg-offset)) scale(calc(0.5 + 0.5 * var(--reveal)));
      transform-origin: center center;
      opacity: calc(0.35 + 0.65 * var(--reveal));
      will-change: transform, opacity;
      cursor: pointer;
      box-shadow: inset 0 0 0 0 #323131;   /* hover ring grows in (matches home) */
      transition: box-shadow 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    }
    .pg-tile:hover { box-shadow: inset 0 0 0 1px #323131; }
    .pg-tile video,
    .pg-tile img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      display: block;
    }
    /* hover overlay — blurred scrim + label, fades in (mirrors home .tile-overlay) */
    .pg-overlay {
      position: absolute;
      inset: 0;
      z-index: 1;                          /* above the video so the ring/label show */
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      justify-content: center;
      gap: 6px;
      padding: 24px;
      text-align: center;
      backdrop-filter: blur(7.7px);
      -webkit-backdrop-filter: blur(7.7px);
      background: var(--overlay-scrim);
      opacity: 0;
      transition: opacity 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    }
    .pg-tile:hover .pg-overlay { opacity: 1; }
    .pg-overlay-title {
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 400;
      font-size: 28px;
      letter-spacing: -1.12px;
      line-height: 1.2;
      text-transform: uppercase;
      color: var(--text-contrast);
      margin: 0;
    }
    .pg-overlay-sub {
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 400;
      font-size: 16px;
      letter-spacing: -0.4px;
      color: var(--text-contrast);
      margin: 0;
    }
    /* the stagger: push the 2nd and 3rd columns down so the rows interlock
       (running-bond brick). nth-of-type counts only the <figure> tiles, so
       the full-width intro band above doesn't shift the column cycle. */
    .pg-tile:nth-of-type(3n+2) { --pg-offset: 136px; }
    .pg-tile:nth-of-type(3n+3) { --pg-offset: 68px; }

    @media (max-width: 900px) {
      .playground { padding: 112px 20px 140px; }
      .pg-grid { grid-template-columns: repeat(2, 1fr); }
      .pg-intro { grid-template-columns: 1fr; gap: 16px; }        /* stack header over para */
      .pg-intro h2, .pg-intro p { grid-column: auto; }           /* full width once stacked */
      .pg-tile:nth-of-type(3n+2),
      .pg-tile:nth-of-type(3n+3) { --pg-offset: 0px; }           /* reset 3-col stagger */
      .pg-tile:nth-of-type(2n) { --pg-offset: 64px; }            /* offset the right column */
    }
    @media (max-width: 560px) {
      .playground { padding: 112px 20px 96px; }
      .pg-grid { grid-template-columns: 1fr; }
      .pg-tile:nth-of-type(2n) { --pg-offset: 0px; }             /* no stagger in one column */
    }

    /* ═══════════════════════════════════════════════════════════════
       PLAYGROUND — 3D COVERFLOW CAROUSEL
       Cards arranged on a rotating ring: the centred card sits upright and
       plays; neighbours recede in Z, rotate away and dim. Drag / wheel /
       ← → to rotate. Click the centre card (or "Open full screen") to
       expand into a lightbox. Page scroll is locked while #playground is
       active (fully immersive, footer hidden). ════════════════════════ */
    body.route-projects { overflow: hidden; }              /* lock page scroll on this route */
    body.route-projects site-footer { display: none; }     /* fully immersive — no footer */
    /* .pg-flow (below) owns the section's box on this route — padding clears the nav */

    .pg-flow {
      position: fixed; inset: 0; z-index: 2;
      --pgf-accent: #000;                          /* accent: black in light mode… */
      display: flex; flex-direction: column; align-items: center; justify-content: center;
      gap: clamp(6px, 1.5vh, 16px);
      padding: clamp(112px, 17vh, 156px) 0 24px;   /* clear the fixed nav + breathing room before the copy */
      background: var(--bg-base); overflow: hidden;
      -webkit-user-select: none; user-select: none;
    }
    body.night .pg-flow { --pgf-accent: #fff; }    /* …white in dark mode */

    /* head */
    .pgf-head { text-align: center; padding: 0 20px; }
    .pgf-eyebrow {
      font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 700;
      letter-spacing: .22em; text-transform: uppercase; color: var(--gold); margin-bottom: 8px;
    }
    .pgf-title {
      font-family: 'DM Serif Display', serif; font-weight: 400; letter-spacing: -0.02em;
      font-size: clamp(30px, 5vw, 52px); line-height: 1; color: var(--text-heading);
    }
    .pgf-lead {
      font-family: 'Space Grotesk', sans-serif; font-size: 15px; line-height: 1.55;
      color: var(--text-heading); opacity: .78; max-width: 56ch; margin: 12px auto 0;
    }
    .pgf-hint {
      font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: .08em;
      color: var(--text-heading); opacity: .5; margin-top: 10px;
    }

    /* stage / coverflow */
    .pgf-stage {
      position: relative; width: 100%;
      flex: 1 1 auto; min-height: 0;               /* fills the space between head + caption */
      perspective: 1600px;
      display: flex; align-items: flex-start; justify-content: center;
      touch-action: none; overscroll-behavior: contain;
    }
    .pgf-track { position: relative; width: 100%; height: 100%; transform-style: preserve-3d; }
    .pgf-card {
      position: absolute; top: 50%; left: 50%; translate: -50% -50%;
      height: min(100%, 480px); width: auto; aspect-ratio: 4 / 3; max-width: 92vw;
      border-radius: 14px; overflow: hidden; background: #0a090c;
      border: 1px solid rgba(255,255,255,.10);
      box-shadow: 0 30px 90px -30px rgba(0,0,0,.7);
      transform-style: preserve-3d; cursor: pointer; will-change: transform, opacity;
      transition: transform .7s cubic-bezier(.22,.61,.36,1),
                  opacity .7s cubic-bezier(.22,.61,.36,1),
                  filter .7s cubic-bezier(.22,.61,.36,1);
    }
    .pgf-card-media { width: 100%; height: 100%; }
    .pgf-card-media img, .pgf-card-media video {
      width: 100%; height: 100%; object-fit: cover; display: block;
      -webkit-user-drag: none; pointer-events: none;
    }
    .pgf-card-vhs { position: absolute; inset: 0; pointer-events: none;
      box-shadow: inset 0 0 120px 20px rgba(0,0,0,.55); }
    .pgf-card-expand {
      position: absolute; bottom: 12px; right: 12px; z-index: 3;
      display: inline-flex; align-items: flex-start; gap: 7px; padding: 6px 11px; border-radius: 999px;
      font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
      color: #fff; background: rgba(0,0,0,.5); border: 1px solid rgba(255,255,255,.22);
      backdrop-filter: blur(4px); pointer-events: none;
      opacity: 0; transform: translateY(6px); transition: opacity .25s ease, transform .25s ease;
    }
    .pgf-card-expand i { font-style: normal; }
    .pgf-card.is-active:hover .pgf-card-expand { opacity: 1; transform: none; }
    .pgf-card:not(.is-active) { filter: brightness(.45) saturate(.75); }
    .pgf-card:not(.is-active) .pgf-card-vhs { box-shadow: inset 0 0 140px 40px rgba(0,0,0,.85); }

    /* arrows */
    .pgf-arrow {
      position: absolute; top: 50%; transform: translateY(-50%); z-index: 20;
      width: 52px; height: 52px; border-radius: 50%; display: grid; place-items: center;
      background: rgba(10,9,12,.55); border: 1px solid rgba(255,255,255,.18);
      color: var(--text-heading); font-size: 26px; line-height: 1; cursor: pointer;
      backdrop-filter: blur(6px); transition: border-color .25s ease, color .25s ease, transform .2s ease;
    }
    body:not(.night) .pgf-arrow { background: rgba(255,255,255,.6); border-color: rgba(0,0,0,.12); }
    .pgf-arrow:hover { border-color: var(--pgf-accent); color: var(--pgf-accent); }
    .pgf-arrow:active { transform: translateY(-50%) scale(.94); }
    .pgf-arrow--prev { left: clamp(10px, 3vw, 40px); }
    .pgf-arrow--next { right: clamp(10px, 3vw, 40px); }

    /* caption */
    /* fixed min-height fits the tallest caption (2-line description), so the caption
       never changes size between assets — that keeps the flex stage, the card, and the
       prev/next arrows at a constant vertical position while rotating. */
    .pgf-caption { max-width: 640px; text-align: center; padding: 0 20px; min-height: 152px; }
    .pgf-cap-meta { display: flex; align-items: flex-start; justify-content: center; gap: 16px; flex-wrap: wrap; margin-bottom: 10px; }
    .pgf-cap-index { font-family: 'JetBrains Mono', monospace; font-size: 12px; letter-spacing: .2em; color: var(--pgf-accent); }
    .pgf-cap-tags { font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--text-heading); opacity: .55; }
    .pgf-cap-tags mark {
      background: linear-gradient(135deg, #7c3aed, #4f46e5);
      color: #fff;
      padding: 1px 7px 2px;
      border-radius: 4px;
      font-size: 10px;
      font-weight: 700;
      letter-spacing: .15em;
      opacity: 1;
      box-shadow: 0 0 8px rgba(124,58,237,.4);
      text-shadow: none;
      display: inline-block;
      line-height: 1.5;
    }
    .pgf-cap-title { font-family: 'DM Serif Display', serif; font-weight: 400; font-size: clamp(24px, 4vw, 40px); line-height: 1.05; color: var(--text-heading); margin-bottom: 10px; }
    .pgf-cap-desc { font-family: 'Space Grotesk', sans-serif; font-weight: 400; font-size: 15px; line-height: 1.55; color: #8a8a8a; max-width: 52ch; margin: 0 auto 16px; }
    .pgf-caption > * { transition: opacity .35s ease; }
    .pgf-caption.swap > * { opacity: 0; }

    /* click-to-expand lightbox */
    .pgf-lightbox {
      position: fixed; inset: 0; z-index: 120;   /* above the nav (z-index 100) */
      display: flex; flex-direction: column; align-items: center; justify-content: center;
      gap: 22px; padding: clamp(16px, 4vw, 48px);
      background: rgba(4,4,6,.94); backdrop-filter: blur(14px);
      opacity: 0; visibility: hidden; transition: opacity .4s ease, visibility .4s;
    }
    .pgf-lightbox.open { opacity: 1; visibility: visible; }
    /* fixed 4:3 frame so every expanded asset opens at the same max size,
       regardless of its native aspect ratio; media is contained (never cropped),
       black letterbox blends with the assets' own black backgrounds. */
    .pgf-lb-stage {
      position: relative;
      width: min(1000px, 92vw, calc(74vh * 4 / 3)); aspect-ratio: 4 / 3;
      display: flex; align-items: flex-start; justify-content: center;
      background: #000; border-radius: 10px; overflow: hidden;
      box-shadow: 0 40px 120px -30px #000;
    }
    .pgf-lb-stage video, .pgf-lb-stage img {
      width: 100%; height: 100%; object-fit: contain; display: block;
    }
    .pgf-lb-info { text-align: center; max-width: 60ch; color: #fff; }
    .pgf-lb-index { font-family: 'JetBrains Mono', monospace; font-size: 12px; letter-spacing: .2em; color: #fff; }
    .pgf-lb-title { font-family: 'DM Serif Display', serif; font-weight: 400; font-size: clamp(22px, 4vw, 34px); line-height: 1.05; margin: 6px 0 8px; }
    .pgf-lb-desc { font-family: 'Space Grotesk', sans-serif; font-weight: 400; font-size: 15px; line-height: 1.55; color: #9a9a9a; margin-bottom: 8px; }
    .pgf-lb-tags { font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: rgba(255,255,255,.55); }
    .pgf-lb-close {
      position: absolute; top: clamp(16px, 3vw, 28px); right: clamp(16px, 3vw, 28px); z-index: 5;
      width: 46px; height: 46px; border-radius: 50%; display: grid; place-items: center;
      background: rgba(20,22,26,.8); border: 1px solid rgba(255,255,255,.2); color: #fff;
      font-size: 16px; cursor: pointer; transition: border-color .25s ease, color .25s ease, transform .25s ease;
    }
    .pgf-lb-close:hover { border-color: #fff; color: #fff; transform: rotate(90deg); }
    .pgf-lb-nav {
      position: absolute; top: 50%; transform: translateY(-50%); z-index: 5;
      width: 50px; height: 50px; border-radius: 50%; display: grid; place-items: center;
      background: rgba(20,22,26,.7); border: 1px solid rgba(255,255,255,.2); color: #fff;
      font-size: 26px; cursor: pointer; transition: border-color .25s ease, color .25s ease;
    }
    .pgf-lb-nav:hover { border-color: #fff; color: #fff; }
    .pgf-lb-nav--prev { left: clamp(10px, 3vw, 32px); }
    .pgf-lb-nav--next { right: clamp(10px, 3vw, 32px); }

    @media (max-width: 620px) {
      .pgf-card { width: 84vw; height: auto; max-width: none; }   /* width-driven on phones */
      .pgf-arrow { width: 44px; height: 44px; font-size: 22px; }
      .pgf-cap-desc { font-size: 14px; }
    }

    @media (prefers-reduced-motion: reduce) {
      .pgf-card { transition: opacity .3s ease, filter .3s ease; }
      .pgf-lightbox, .pgf-caption > * { transition: opacity .3s ease; }
    }

    /* Awwwards ribbon is desktop-only: pinned at 50vh it lands on the hero
       recorder at phone widths. Same breakpoint as the rest of the mobile layout. */
    @media (max-width: 1100px) { #awwwards { display: none; } }
  

    #hatcha-view { --cs-serif:'DM Serif Display', Georgia, serif;
                   --cs-sans:'Space Grotesk', sans-serif;
                   --cs-cream:#fff9f1; --cs-black:#000; --cs-ink:#0b0b0b;
                   --cs-coral:#fc9073;
                   --cs-max:1320px; --cs-pad:60px; --cs-rail:355px;
                   --cs-navpad:112px;        /* panel top clearance = 80px nav + 32px (Phase 2) */
                   --cs-palette-ar:949 / 860; --cs-guardrail-ar:1001 / 860; }

    .cs-page { font-family:var(--cs-sans); background:var(--cs-cream); color:var(--cs-ink);
               -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale; }
    .cs-page *, .cs-page *::before, .cs-page *::after { box-sizing:border-box; }

    /* section shells: cream / black bands */
    .cs-band { width:100%; }
    .cs-band.dark  { background:var(--cs-black); color:#fff9ea; }
    .cs-band.cream { background:var(--cs-cream); color:var(--cs-ink); }
    .cs-wrap { max-width:var(--cs-max); margin:0 auto; padding-left:var(--cs-pad); padding-right:var(--cs-pad); }

    /* numbered-section rail (number + eyebrow) + content column */
    .cs-numbered { display:grid; grid-template-columns:var(--cs-rail) minmax(0,1fr); gap:0; }
    .cs-rail { padding-left:128px; }
    .cs-num { font-family:var(--cs-serif); font-weight:400; font-size:118px; line-height:0.9; letter-spacing:0.02em; color:#c9c4ba; }
    .cs-band.dark .cs-num { color:#3c3c3c; }
    .cs-eyebrow { font-family:var(--cs-sans); font-size:13px; font-weight:400; letter-spacing:0.06em;
                  text-transform:uppercase; color:#8f8a80; margin-top:20px; }

    /* headings + body */
    .cs-h { font-family:var(--cs-serif); font-weight:400; letter-spacing:0.02em; line-height:1.13; }
    .cs-h i, .cs-h em { font-style:italic; }
    .cs-sec-h { font-size:50px; margin-bottom:48px; }
    .cs-p { font-size:17px; line-height:1.65; max-width:940px; color:inherit; }
    .cs-band.dark .cs-p { color:rgba(255,249,233,0.85); }
    .cs-band.cream .cs-p { color:rgba(11,11,11,0.9); }
    .cs-p + .cs-p { margin-top:32px; }

    /* note card (WHAT I DID / MY CONTRIBUTION) */
    .cs-note { background:#4c4a49; border-radius:20px; padding:34px 38px; margin-top:64px; max-width:960px; }
    .cs-note-label { font-size:14px; font-weight:400; letter-spacing:0.06em; text-transform:uppercase;
                     color:#9a958c; margin-bottom:16px; }
    .cs-note-body { font-size:16.5px; line-height:1.7; color:#ece8df; }

    /* ── HERO ── */
    .cs-hero { padding:138px 0 0; }
    .cs-hero-grid { display:grid; grid-template-columns:minmax(0,1fr) 540px; gap:40px; align-items:start; max-width:none; padding-bottom:60px; }
    .cs-hero-h { font-family:var(--cs-serif); font-weight:400; font-size:60px; line-height:1.13; letter-spacing:0.02em; }
    .cs-hero-h i { font-style:italic; display:block; }
    .cs-hero-intro { margin-top:32px; font-size:17px; line-height:1.6; max-width:560px; color:rgba(11,11,11,0.92); }
    .cs-meta { display:grid; grid-template-columns:232px 1fr; gap:40px 0; margin-top:168px; }
    .cs-meta-k { font-size:13px; font-weight:400; letter-spacing:0.06em; text-transform:uppercase; color:#8f8a80; margin-bottom:10px; }
    .cs-meta-v { font-size:18px; color:var(--cs-ink); }

    /* placeholder blocks (swap for assets) */
    .cs-ph { position:relative; display:flex; align-items:center; justify-content:center; text-align:center;
             background:repeating-linear-gradient(45deg,#e7e0d2,#e7e0d2 14px,#ddd5c5 14px,#ddd5c5 28px);
             color:#8a8474; font-family:var(--cs-sans); font-size:14px; letter-spacing:0.04em;
             border-radius:0; overflow:hidden; }
    .cs-ph.dark { background:repeating-linear-gradient(45deg,#161616,#161616 14px,#1e1e1e 14px,#1e1e1e 28px); color:#6b6b6b; }
    .cs-ph span { padding:8px 14px; background:rgba(0,0,0,0.35); color:#fff; border-radius:100px; font-size:12px; }
    .cs-ph.light span { background:rgba(0,0,0,0.5); }

    .cs-hero-img { height:1095px; margin-top:0; width:100%; object-fit:cover; display:block; }

    /* ── live-video laptop mockup (hero) ──
       Plate PNG carries the empty black screen; the recording is warped into
       the screen quad with a matrix3d homography (see the .cs-hero-mock script).
       Corner fractions were measured off the plate, so the video tracks the
       screen at any width. */
    /* full-bleed, one viewport tall; the room photo covers the band naturally
       and the warp script mirrors the cover-crop so the video stays on the screen */
    .cs-hero-mock { position:relative; width:100%; height:100vh; height:100svh; overflow:hidden; }
    /* ambient loop: the whole scene (plate + warped video) breathes together;
       scale only ever goes above 1 so the cover-fit never exposes an edge */
    .cs-hero-zoom { position:absolute; inset:0; animation:csHeroZoom 18s ease-in-out infinite; will-change:transform; }
    @keyframes csHeroZoom { 0%,100% { transform:scale(1); } 50% { transform:scale(1.05); } }
    @media (prefers-reduced-motion: reduce){ .cs-hero-zoom { animation:none; } }
    .cs-hero-plate { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; z-index:0; }
    .cs-hero-screen { position:absolute; inset:0; z-index:1; pointer-events:none; overflow:hidden; }
    .cs-hero-vid { position:absolute; top:0; left:0; width:1000px; height:671px; object-fit:cover; transform-origin:0 0; backface-visibility:hidden; border-radius:22px; }
    /* narrow viewports: a 100svh box on this 2.357 plate cover-crops to x 0.38–0.62,
       which clips the laptop's own edges (it spans x 0.38–0.64). Size the band by
       aspect instead — 4/5 keeps x 0.33–0.67 visible at any width. */
    @media (max-width: 900px){ .cs-hero-mock { height:auto; aspect-ratio:4 / 5; max-height:100svh; } }
    /* same mockup inside a fixed-size project tile: fill the tile (cover) and
       isolate so the hover overlay still paints above the warped video. */
    .project-tile .tile-mock { position:absolute; inset:0; width:100%; height:100%; aspect-ratio:auto; margin:0; isolation:isolate; }
    /* phone-plate variant of the same mockup (Fireflut's MWC hero + its home tile):
       portrait video base box and a phone-sized screen radius. The measured quad
       and bezel insets ride along in each element's data-warp. */
    .cs-mock-phone .cs-hero-vid { width:450px; height:1000px; border-radius:62px; }
    /* Existence variant (its case-study hero + its home tile): the measured screen quad
       on that plate is 201.8x437.2px (aspect 0.4616), which is exactly the 1180x2556
       recording's aspect, so the base box takes the video's own ratio and
       object-fit:cover crops nothing. Radius is the iPhone display corner (~14% of
       screen width) measured in this box. Declared after the rule above so it wins at
       equal specificity when both classes are present. */
    .cs-mock-ex .cs-hero-vid { width:462px; height:1000px; border-radius:64px; }
    /* Jumpable variant (Apple Watch Ultra on a desk plate). The recording is used WHOLE —
       nothing cut from the file — but its capture carries a 72px near-black strip down the
       left and 108px of dead black at the bottom (verified never to hold content: 316 frames
       sampled at 20fps across the clip, transitions included). Showing the frame edge-to-edge
       would leave the app UI floating ~6% in from the glass. So the element is scaled up to
       the FULL frame (1212x1476 x 0.731) and the warp's sx/sw map only its live area onto the
       screen quad — the empty strips land just outside the glass on the plate's black bezel,
       where they're invisible. Radius is the watch display corner (~30px on the 1584px plate,
       ~28% of screen width) in this box. Declared last so it wins at equal specificity. */
    .cs-mock-watch .cs-hero-vid { width:885.96px; height:1078.95px; border-radius:230px; }
    /* ── watch scene zoom: geometry, not a wrapper transform ──
       Same 100svh band as Fireflut and Existence, but this plate frames the watch smaller
       (its display is ~6% of the plate width vs Existence's ~13%), so the scene sits in
       closer: 1.3 on desktop takes the screen from 131 to 170 CSS px with the concrete
       stand, notebooks and plant still in frame; the home tile needs 1.8 (a tile box
       cover-crops the plate to ~1403px wide, where the display would read just 87px)
       which puts it at ~156 CSS px, between Fireflut's tile (150) and Existence's (178).
       Unlike those two the zoom is NOT transform:scale() on .cs-hero-zoom — a composited
       layer is rasterised once and GPU-stretched, which measured 17% softer on the app's
       type. --warp-zoom drives both the plate's box below and the warp script's cover-fit
       rect, so the video is sampled straight to its final size and the two stay locked.
       Mobile stays at 1: the 100svh band already crops to x 0.44–0.56 at 390px wide. */
    .cs-mock-watch { --warp-zoom: 1; }
    @media (min-width: 901px){ .cs-mock-watch:not(.tile-mock) { --warp-zoom: 1.3; } }
    .cs-mock-watch.tile-mock { --warp-zoom: 1.8; }
    /* grow the plate about its centre with layout, not a transform, so it resamples cleanly */
    .cs-mock-watch .cs-hero-plate {
      width: calc(100% * var(--warp-zoom)); height: calc(100% * var(--warp-zoom));
      left: calc((100% - 100% * var(--warp-zoom)) / 2);
      top:  calc((100% - 100% * var(--warp-zoom)) / 2);
      right: auto; bottom: auto; }
    /* nothing left to animate on the wrapper — the scene is static and sharp */
    .cs-mock-watch .cs-hero-zoom { animation: none; transform: none; will-change: auto; }
    /* this plate's phone is wide in frame (x 0.345–0.658) where Fireflut's is narrow, so
       the tile-in base scale below would clip its ends — keep the shared 1→1.05 breathe,
       whose crop still leaves x 0.327–0.673 visible in a tile-sized box. */
    .cs-mock-ex.tile-mock .cs-hero-zoom { animation-name:csHeroZoom; }
    /* narrow viewports: the rule above swaps in the laptop plate's aspect, which is
       both the wrong plate and short of a full viewport. This plate's phone sits
       mid-frame (x 0.43–0.58), so a full-height box cover-crops straight to it.
       Tiles keep their own box. */
    @media (max-width: 900px){
      .cs-mock-phone:not(.tile-mock) { height:100svh; aspect-ratio:auto; }
      /* the watch plate shares the 2.357 room-photo ratio, and its watch sits dead
         centre (x 0.472–0.560), so a full-height box cover-crops straight onto it —
         the laptop-plate aspect above would instead squash the band to ~300px tall
         and shrink the watch to ~43px. */
      .cs-mock-watch:not(.tile-mock) { height:100svh; aspect-ratio:auto; }
    }
    /* in a tile-sized box the cover-crop leaves this device reading small, so sit
       the scene in closer by default. Same 18s / 5% breathe as the shared loop,
       just a higher base scale — plate + video still scale together so the warp
       stays locked, and it never drops below 1 so the cover fill can't expose an
       edge. Kept under the plate's native width at tile sizes so it stays sharp. */
    @keyframes csHeroZoomTileIn { 0%,100% { transform:scale(1.18); } 50% { transform:scale(1.23); } }
    .cs-mock-phone.tile-mock .cs-hero-zoom { animation-name:csHeroZoomTileIn; }
    /* the laptop plate is a wide room shot and its device only spans x 0.38–0.64, so a
       tile-sized cover-crop leaves it reading as a photo of a sofa. Same breathe, higher
       base — 1.32 still samples the plate at ~1263px, well under its 2376px native width. */
    @keyframes csHeroZoomTileLap { 0%,100% { transform:scale(1.32); } 50% { transform:scale(1.37); } }
    .cs-mock-lap.tile-mock .cs-hero-zoom { animation-name:csHeroZoomTileLap; }
    @media (prefers-reduced-motion: reduce){
      .cs-mock-lap.tile-mock .cs-hero-zoom { animation:none; transform:scale(1.32); }
    }
    /* the shared reduced-motion rule kills the animation, which would also drop
       the base scale — hold the zoomed-in framing statically instead. */
    @media (prefers-reduced-motion: reduce){
      .cs-mock-phone.tile-mock .cs-hero-zoom { animation:none; transform:scale(1.18); }
    }
    /* full-bleed phone mockup (portrait plate); reuses the .cs-hero-* mechanics
       and the shared warp script via data-warp, but its own band sizing + a
       portrait video base box and larger (phone) screen radius. */
    .cs-phone-bleed { position:relative; width:100%; height:1090px; overflow:hidden; }
    /* Glass fill, warped onto the iPad's measured screen quad by the script below.
       Base box 703x1000 = the GLASS's own aspect (the measured quad is 788x1121px on
       the 2400x1800 plate), so the fill covers the plate's screen exactly — this plate
       ships a chroma-green screen, and any gap would show as a green fringe.
       The recording is 0.644 wide-to-tall against the glass's 0.703, and UI sits hard
       against BOTH its edges (× and sparkle buttons 20px down, the "Looks Good" CTA
       23px up), so a cover-crop would eat one or the other. It plays at full height,
       its true aspect, centred — the ~29px each side is near-black, exactly what an
       iPad shows letterboxing a phone app. No crop, no stretch.
       Radius is the display corner measured off the plate (21px on 788 → 19 here). */
    .cs-phone-frame { position:absolute; top:0; left:0; width:703px; height:1000px; transform-origin:0 0;
      background:#0a0a0c; border-radius:19px; overflow:hidden; backface-visibility:hidden; }
    .cs-phone-vid { position:absolute; top:0; left:29.4px; width:644.2px; height:1000px;
      object-fit:cover; display:block; }
    @media (max-width: 900px){ .cs-phone-bleed { height:auto; aspect-ratio:2400 / 1800; } }

    /* ── tile → hero shared-element zoom ──
       The home tile and the case-study hero share a view-transition-name, so
       document.startViewTransition (see the routing script) morphs the tile
       rect into the hero rect on #hatcha open, and back on exit. The tile and the
       hero never collide because only one of their views is rendered at a time
       (the other is display:none). Browsers without View Transitions keep the
       instant swap.

       The names are gated on body.vt-hero, which the router adds only for the
       duration of a case-study nav. A named element is lifted out of the page
       into the view-transition top layer, ABOVE the root snapshot — so if the
       names were always on, any other transition (the day/night toggle) would
       hoist the pinned home tiles over the sticky testimonials covering them.

       Scoped to `.cs-hero .cs-hero-mock` (the same node rtHero.open picks up), NOT
       every .cs-hero-mock in the view — the read-next panel's asset is one too. That
       one is visibility:hidden at every scroll depth so it is never captured and the
       name does not actually collide today, but two capturable elements sharing a
       name aborts the whole transition (InvalidStateError), so keep this precise. */
    body.vt-hero a.project-tile[href="/work/hatcha"] { view-transition-name: hatcha-hero; }
    body.vt-hero #hatcha-view .cs-hero .cs-hero-mock { view-transition-name: hatcha-hero; }
    /* both snapshots cover the morphing box, so the move reads as a zoom
       instead of a stretch */
    ::view-transition-old(hatcha-hero),
    ::view-transition-new(hatcha-hero) { width:100%; height:100%; object-fit:cover; }
    ::view-transition-group(hatcha-hero) {
      animation-duration: 0.6s;
      animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
    }
    @media (prefers-reduced-motion: reduce) {
      ::view-transition-group(hatcha-hero),
      ::view-transition-old(hatcha-hero),
      ::view-transition-new(hatcha-hero) { animation: none; }
    }

    /* ── SHOWCASE (event planner) ── */
    .cs-showcase { padding:110px 0; }
    .cs-showcase-grid { display:grid; grid-template-columns:minmax(0,613fr) minmax(0,718fr); gap:40px; align-items:start; max-width:none; }
    .cs-showcase-eye { font-size:16px; font-weight:400; letter-spacing:0.06em; text-transform:uppercase; color:rgba(0,0,0,0.5); margin-bottom:12px; }
    .cs-showcase-h { font-family:var(--cs-serif); font-weight:400; font-size:48px; line-height:1.25; letter-spacing:0.02em; margin-bottom:32px; }
    .cs-showcase-grid .cs-p { font-size:16px; line-height:1.875; }
    .cs-hgp-rows { display:flex; flex-direction:column; gap:4px; }
    .cs-hgp { display:grid; grid-template-columns:68px 1fr; background:rgba(0,0,0,0.1); border-radius:0; overflow:hidden; }
    .cs-hgp-tab { display:flex; align-items:center; justify-content:center; writing-mode:vertical-rl; transform:rotate(180deg);
                  font-size:16px; font-weight:400; letter-spacing:normal; text-transform:uppercase; color:#fdf1e1; padding:22px 0; }
    .cs-hgp:nth-child(1) .cs-hgp-tab { background:#000; }
    .cs-hgp:nth-child(2) .cs-hgp-tab { background:#3a3a38; }
    .cs-hgp:nth-child(3) .cs-hgp-tab { background:#7a7a76; }
    .cs-hgp-body { padding:24px 16px; }
    .cs-hgp-k { font-size:12px; font-weight:400; letter-spacing:0.06em; text-transform:uppercase; color:rgba(0,0,0,0.5); margin-bottom:0; }
    .cs-hgp-v { font-size:16px; line-height:1.875; color:#0b0b0b; }

    /* logo band — a full-viewport black moment with the mark centred */
    .cs-logoband { background:#0d0c0c;  display:flex; align-items:center; justify-content:center; }

    /* — Hatcha logo-band animation (Figma 384:1971, Wireframe-7 → Wireframe-6 smart animate) —
       Stage is the 1440×1024 design canvas, scaled to the viewport via --s (set in JS).
       Starts zoomed into the splash phone (scale 5.353 at the spinner), zooms out to the
       five-phone row, then cycles "Hatcha ‹Birthday/Potluck/Hackathon/Anniversary›". */
    /* viewport bg goes pure #000 while zoomed so the splash covers the full
       viewport width on any screen (the phone's own black can fall short) */
    .cs-anim-viewport { width:100%; height:100vh; overflow:hidden; background:#000;
                        display:flex; align-items:center; justify-content:center; }
    .play .cs-anim-viewport { animation:hatchaBg 11s linear infinite; }
    @keyframes hatchaBg {
      0%,31.82% { background-color:#000; }
      39.09%,90.91% { background-color:#0d0c0c; }
      98.18%,100% { background-color:#000; }
    }
    .cs-anim-stage { flex:none; width:1440px; height:1024px; position:relative;
                     transform:scale(var(--s,1)); }
    /* One shared 11s loop, fully seamless (starts and ends zoomed into the splash).
       Splash compressed so the petals whip through faster and the zoom-out fires
       sooner; the word phase keeps its original real pacing, just shifted earlier.
       0–3.5s      splash — petals slide v1→v2→v3→v4→v1 (moves ~0.13/1.0/2.1/3.1s)
       3.5–4.3s    zoom out to the five-phone row (800ms ease-in-out)
       4.1–4.6s    "Hatcha Birthday" fades in (overlaps the zoom tail)
       5.1–9.0s    four word turns (Potluck→Hackathon→Anniversary→Birthday)
       9.6–9.9s    texts fade out
       10.0–10.8s  zoom back into the splash (800ms ease-in-out) → replay */
    .cs-anim-zoom { position:absolute; inset:0;
                    transform:scale(5.353); transform-origin:720px 434.18px; }
    .play .cs-anim-zoom { animation:hatchaZoom 11s linear infinite; }
    @keyframes hatchaZoom {
      0%,31.82%  { transform:scale(5.353); animation-timing-function:cubic-bezier(0.42,0,0.58,1); }
      39.09%,90.91% { transform:scale(1); animation-timing-function:cubic-bezier(0.42,0,0.58,1); }
      98.18%,100% { transform:scale(5.353); }
    }

    /* side phones — frames baked into the PNGs, so shadow follows alpha.
       Hidden while zoomed in (they'd bleed into the splash on wide screens);
       fade in with the zoom-out so the outer two render fully unclipped. */
    .cs-ph-img { position:absolute; top:136.45px; width:341.5px; height:754px;
                 filter:drop-shadow(9px 12px 18.3px rgba(0,0,0,0.71)); opacity:0; }
    .play .cs-ph-img { animation:hatchaSides 11s linear infinite; }
    @keyframes hatchaSides {
      0%,31.82% { opacity:0; }
      39.09%,90.91% { opacity:1; }
      94.09%,100% { opacity:0; }
    }
    .cs-ph-img.i1 { left:-243.68px; }
    .cs-ph-img.i2 { left:152.82px; }
    .cs-ph-img.i3 { left:945.68px; }
    .cs-ph-img.i4 { left:1342.18px; }

    /* center splash phone — border hidden while zoomed in, appears with the zoom-out */
    .cs-hphone { position:absolute; left:549.32px; top:136.46px; width:341.367px; height:753.99px;
                 background:#000; border:6.628px solid transparent; border-radius:26.514px;
                 box-shadow:9px 12px 36.6px 0 rgba(0,0,0,0.71); overflow:hidden; }
    .play .cs-hphone { animation:hatchaBorder 11s linear infinite; }
    @keyframes hatchaBorder {
      0%,31.82% { border-color:transparent; }
      39.09%,87.27% { border-color:#6b6b6b; }
      90%,100% { border-color:transparent; } /* fully gone before the zoom-in starts */
    }
    .cs-hphone-in { position:absolute; left:-6.628px; top:-6.628px; width:341.367px; height:753.99px; }

    /* status bar */
    .cs-hsb { position:absolute; left:0; right:0; top:0; height:43.085px;
              padding:8.286px 19.885px; display:flex; align-items:flex-end; justify-content:space-between; }
    .cs-hsb-time { font-family:'Roboto',sans-serif; font-weight:500; font-size:11.6px;
                   line-height:16.571px; letter-spacing:0.116px; color:#fff; }
    .cs-hsb-icons { position:relative; width:32.72px; height:14.086px; }
    .cs-hsb-icons img { position:absolute; display:block; }
    .cs-hsb-icons .wifi    { left:0; top:0; width:14.086px; height:14.086px; }
    .cs-hsb-icons .signal  { left:10.98px; top:0; width:14.086px; height:14.086px; }
    .cs-hsb-icons .battery { left:26.09px; top:0.69px; width:6.628px; height:12.428px; }
    .cs-hcam { position:absolute; left:50%; top:14.91px; width:19.885px; height:19.885px;
               transform:translateX(-50%); }
    .cs-hnav { position:absolute; left:0; right:0; bottom:0; height:19.885px; }
    .cs-hnav span { position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
                    width:89.485px; height:3.314px; border-radius:12px; background:#fff; }

    /* spinner mark — literal smart animate (Figma 388:2075): all 4 variants use
       the SAME four petal shapes, only translated. Each petal slides through its
       4 variant positions (a circuit of the mark) with a small per-petal delay,
       exactly like the prototype. SVG viewBox 0 0 48 48; translate units = design px.
         P1 (17,0)→(0,0)→(0,27)→(17,27)   P2 (0,0)→(32,0)→(32,27)→(0,27)
         P3 (17,22)→(0,22)→(0,0)→(17,0)   P4 (0,22)→(32,22)→(32,0)→(0,0)  */
    .cs-hspin { position:absolute; left:50%; top:274.25px; width:46.941px; height:46.941px;
                transform:translateX(-50%); }
    /* 48-unit design space scaled onto the 46.941px box; petals are separate
       composited elements so the slides run off the main thread */
    .cs-hspin-in { position:absolute; left:0; top:0; width:48px; height:48px;
                   transform:scale(0.97794); transform-origin:0 0; }
    .cs-hspin .hp { position:absolute; left:0; top:0; display:block; will-change:transform; }
    .cs-hspin .hp1 { transform:translate(17px,0); }
    .cs-hspin .hp2 { transform:translate(0,0); }
    .cs-hspin .hp3 { transform:translate(17px,22px); }
    .cs-hspin .hp4 { transform:translate(0,22px); }
    .play .cs-hspin .hp1 { animation:hatchaP1 11s linear infinite; }
    .play .cs-hspin .hp2 { animation:hatchaP2 11s linear infinite; }
    .play .cs-hspin .hp3 { animation:hatchaP3 11s linear infinite; }
    .play .cs-hspin .hp4 { animation:hatchaP4 11s linear infinite; }
    /* splash moves compressed into 0–3.5s (~0.7s each) so the mark whips through
       its circuit faster and the zoom-out fires sooner; word-turn moves at
       5.1/6.4/7.7/9.0s run 560ms, synced with the word slides;
       spring ease with slight overshoot + settle (Figma "gentle");
       all four petals move together on identical timing */
    @keyframes hatchaP1 {
      0%,1.15% { transform:translate(17px,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      7.52%,9.25% { transform:translate(0,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      15.62%,17.35% { transform:translate(0,27px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      23.72%,25.45% { transform:translate(17px,27px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      31.82%,46.36% { transform:translate(17px,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      51.45%,58.18% { transform:translate(0,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      63.27%,70% { transform:translate(0,27px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      75.09%,81.82% { transform:translate(17px,27px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      86.91%,100% { transform:translate(17px,0); }
    }
    @keyframes hatchaP2 {
      0%,1.15% { transform:translate(0,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      7.52%,9.25% { transform:translate(32px,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      15.62%,17.35% { transform:translate(32px,27px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      23.72%,25.45% { transform:translate(0,27px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      31.82%,46.36% { transform:translate(0,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      51.45%,58.18% { transform:translate(32px,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      63.27%,70% { transform:translate(32px,27px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      75.09%,81.82% { transform:translate(0,27px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      86.91%,100% { transform:translate(0,0); }
    }
    @keyframes hatchaP3 {
      0%,1.15% { transform:translate(17px,22px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      7.52%,9.25% { transform:translate(0,22px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      15.62%,17.35% { transform:translate(0,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      23.72%,25.45% { transform:translate(17px,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      31.82%,46.36% { transform:translate(17px,22px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      51.45%,58.18% { transform:translate(0,22px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      63.27%,70% { transform:translate(0,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      75.09%,81.82% { transform:translate(17px,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      86.91%,100% { transform:translate(17px,22px); }
    }
    @keyframes hatchaP4 {
      0%,1.15% { transform:translate(0,22px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      7.52%,9.25% { transform:translate(32px,22px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      15.62%,17.35% { transform:translate(32px,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      23.72%,25.45% { transform:translate(0,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      31.82%,46.36% { transform:translate(0,22px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      51.45%,58.18% { transform:translate(32px,22px); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      63.27%,70% { transform:translate(32px,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      75.09%,81.82% { transform:translate(0,0); animation-timing-function:cubic-bezier(0.3,1.25,0.4,1); }
      86.91%,100% { transform:translate(0,22px); }
    }

    /* wordmark + rotating word — hidden while zoomed (Wireframe-7 has them at opacity 0) */
    .cs-hfade { opacity:0; }
    .play .cs-hfade { animation:hatchaFade 11s linear infinite; }
    @keyframes hatchaFade {
      0%,37.27% { opacity:0; animation-timing-function:ease-out; }
      41.82%,87.27% { opacity:1; animation-timing-function:ease-in; }
      90%,100% { opacity:0; }
    }
    .cs-hword { position:absolute; left:calc(50% - 125px); top:50%; transform:translateY(-50%);
                font-family:'Darker Grotesque',sans-serif; font-weight:700;
                font-size:38.942px; line-height:normal; color:#fff; white-space:nowrap; }
    /* +5.7/+5 nudge vs Figma coords: web font metrics render "Hatcha" wider, was colliding */
    .cs-hrot { position:absolute; left:152.5px; top:358.8px; width:169.855px; height:38.942px;
               overflow:hidden; }
    .cs-hrot-stack { position:absolute; inset:0; }
    .play .cs-hrot-stack { animation:hatchaWords 11s linear infinite; }
    @keyframes hatchaWords {
      0%,46.36%  { transform:translateY(0); animation-timing-function:cubic-bezier(0.65,0,0.35,1); }
      51.45%,58.18% { transform:translateY(-45.57px); animation-timing-function:cubic-bezier(0.65,0,0.35,1); }
      63.27%,70% { transform:translateY(-91.14px); animation-timing-function:cubic-bezier(0.65,0,0.35,1); }
      75.09%,81.82% { transform:translateY(-136.71px); animation-timing-function:cubic-bezier(0.65,0,0.35,1); }
      86.91%,100% { transform:translateY(-182.28px); }
    }
    .cs-hrot-stack span { position:absolute; left:0; display:flex; align-items:center;
                          height:38.942px; padding:0 6.628px; white-space:nowrap;
                          font-family:'Charter','Bitter',Georgia,serif; font-style:italic;
                          font-size:32.13px; letter-spacing:-0.6426px; color:#fff; line-height:normal; }
    .cs-hrot-stack span:nth-child(1) { top:0; font-size:33.14px; letter-spacing:-0.6628px; }
    .cs-hrot-stack span:nth-child(2) { top:45.57px; }
    .cs-hrot-stack span:nth-child(3) { top:91.14px; }
    .cs-hrot-stack span:nth-child(4) { top:136.71px; }
    .cs-hrot-stack span:nth-child(5) { top:182.28px; font-size:33.14px; letter-spacing:-0.6628px; }

    @media (prefers-reduced-motion: reduce) {
      .cs-anim-zoom, .play .cs-anim-zoom { transform:scale(1); animation:none; }
      .cs-hfade, .play .cs-hfade { opacity:1; animation:none; }
      .cs-hphone, .play .cs-hphone { border-color:#6b6b6b; animation:none; }
      .cs-anim-viewport, .play .cs-anim-viewport { background:#0d0c0c; animation:none; }
      .cs-ph-img, .play .cs-ph-img { opacity:1; animation:none; }
      .play .cs-hspin .hp, .play .cs-hrot-stack { animation:none; }
      .play .iv-scroll, .play .es-scroll { animation:none; }
    }

    /* ── problem / numbered section body ── */
    .cs-numbered.pad { padding:120px 0 150px; }

    /* pull quote */
    .cs-quote { padding:36px 0 190px; }
    .cs-quote-inner { max-width:var(--cs-max); margin:0 auto; padding-left:0; padding-right:0; }
    .cs-quote p { font-family:var(--cs-serif); font-weight:400; font-size:44px; line-height:1.3; letter-spacing:0.02em; margin:0; padding:80px 0 80px var(--cs-rail); }
    .cs-quote-center .cs-quote-inner { text-align:center; padding-left:var(--cs-pad); padding-right:var(--cs-pad); }
    .cs-quote-center p { padding-left:0; }
    .cs-quote i, .cs-quote em { font-style:italic; }
    .cs-quote .coral { color:var(--cs-coral); font-style:italic; }
    .cs-band.dark .cs-quote-line { border-top:1px solid rgba(255,255,255,0.12); }
    .cs-band.cream .cs-quote-line { border-top:1px solid rgba(0,0,0,0.1); }

    .cs-shift-eye { color:rgba(255,255,255,0.5); font-size:16px; letter-spacing:normal; margin-top:52px; margin-bottom:0; }

    /* comparison table (before / after) */
    .cs-cmp { margin-top:40px; border:1px solid rgba(255,255,255,0.25); overflow:hidden; max-width:960px; }
    .cs-cmp-row { display:grid; grid-template-columns:1fr 60px 1fr; }
    .cs-cmp-cell { padding:16px; font-size:16px; line-height:30px; }
    .cs-cmp-arrow { display:flex; align-items:center; justify-content:center; border-left:1px solid rgba(255,255,255,0.25);
                    border-right:1px solid rgba(255,255,255,0.25); color:#fdf1e1; font-size:16px; }
    .cs-cmp-row:not(:last-child) .before,
    .cs-cmp-row:not(:last-child) .cs-cmp-arrow { border-bottom:1px solid rgba(255,255,255,0.25); }
    .cs-cmp-row:not(:last-child) .after { border-bottom:1px solid rgba(0,0,0,0.25); }
    .cs-cmp .before { color:#fdf1e1; }
    .cs-cmp .after  { background:#fff9f1; color:#141414; }
    .cs-cmp-head .cs-cmp-cell { font-size:16px; font-weight:400; letter-spacing:normal; line-height:1.358; text-transform:uppercase; }
    .cs-cmp-head .before { background:rgba(255,255,255,0.1); }
    .cs-cmp-head .cs-cmp-arrow { background:rgba(255,255,255,0.1); }
    .cs-cmp-head .after { background:#fdf1e1; color:#141414; }

    /* fixed / composed table (cream section) */
    .cs-fx { margin-top:52px; display:grid; grid-template-columns:1fr 1fr; max-width:940px; border:1px solid rgba(0,0,0,0.25); }
    .cs-fx + .cs-note { margin-top:52px; }
    .cs-fx-cell { display:flex; align-items:center; padding:16px; min-height:60px; font-size:16px; line-height:30px; }
    .cs-fx-cell:nth-child(2n) { background:#000; color:#fdf1e1; border-top:1px solid rgba(255,255,255,0.25); }
    .cs-fx-cell:nth-child(2n+1){ border-top:1px solid rgba(0,0,0,0.25); }
    .cs-fx-head { padding:16px; font-size:16px; font-weight:400; letter-spacing:normal; line-height:1.358; text-transform:uppercase; white-space:nowrap; }
    .cs-fx-head.f { background:#7a7a76; color:#fdf1e1; }
    .cs-fx-head.g { background:#000; color:#fdf1e1; }

    /* criteria cards (section 02) */
    .cs-crit-eye { font-size:16px; font-weight:500; text-transform:uppercase; color:#fff; line-height:1.5; margin:84px 0 40px; margin-left:calc(-1 * var(--cs-rail)); }
    .cs-crit { display:grid; grid-template-columns:repeat(3,1fr); gap:80px; align-items:start; margin-left:calc(-1 * var(--cs-rail)); }
    .cs-crit-head { display:flex; align-items:center; gap:12px; margin-bottom:14px; }
    .cs-crit-num { font-family:var(--cs-sans); font-weight:400; font-size:30px; line-height:1; color:rgba(255,255,255,0.5); }
    .cs-crit-lbl { font-size:12px; font-weight:500; letter-spacing:0.01em; text-transform:uppercase; color:rgba(255,255,255,0.75); }
    .cs-card-img { display:block; width:100%; height:auto; border-radius:18px; }
    .cs-card { position:relative; border:1px solid rgba(255,255,255,0.5); border-radius:18px; padding:12px; overflow:hidden;
               font-family:'Lato',var(--cs-sans); backdrop-filter:blur(6px);
               background:radial-gradient(125% 125% at 50% 46%, rgba(255,255,255,0) 30%, rgba(255,255,255,0.16) 100%),linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0)); }
    .cs-card-title { font-size:12px; font-weight:400; color:#fff; margin-bottom:11px; }
    .cs-opt { display:flex; align-items:center; gap:9px; height:42px; padding:0 11px; margin-bottom:9px;
              border:1px solid rgba(255,255,255,0.5); border-radius:9px; font-size:12px; }
    .cs-opt:last-child { margin-bottom:0; }
    .cs-opt.on  { background:linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0)); }
    .cs-opt.off { background:rgba(255,255,255,0.15); }
    .cs-opt > span { flex:1; }
    .cs-opt.on  > span { font-weight:400; color:#fff; }
    .cs-opt.off > span { font-weight:300; color:rgba(255,255,255,0.8); }
    .cs-cbx { width:18px; height:18px; border-radius:5px; border:1.5px solid rgba(255,255,255,0.45); flex-shrink:0; position:relative; }
    .cs-cbx.on { background:#e5c07b; border-color:#e5c07b; }
    .cs-cbx.on::after { content:""; position:absolute; left:5.5px; top:2px; width:4px; height:8px; border:solid #1c1c1c; border-width:0 2px 2px 0; transform:rotate(45deg); }
    .cs-donut-wrap { display:flex; align-items:center; gap:22px; }
    .cs-donut { width:140px; height:140px; flex-shrink:0; display:block; }
    .cs-legend { display:flex; flex-direction:column; gap:16px; }
    .cs-legend-row { display:flex; align-items:center; gap:10px; font-size:11px; line-height:1; }
    .cs-legend .sw { width:12px; height:12px; border-radius:3px; flex-shrink:0; }
    .cs-legend .pct { color:#fff; min-width:24px; }
    .cs-legend .lbl { color:rgba(255,255,255,0.5); white-space:nowrap; }
    .cs-list { display:flex; flex-direction:column; }
    .cs-list-row { display:flex; align-items:center; gap:9px; padding:9px; font-size:12px; font-weight:300; color:#fff; }
    .cs-list-sep { height:1px; background:rgba(255,255,255,0.12); }

    /* phone mockups (fixed / flexible) — annotations sit beside each phone.
       The block escapes the text column and centers the two phone units in the viewport. */
    .cs-phones { display:grid; grid-template-columns:auto auto; justify-content:center; gap:48px; margin-top:116px;
                 margin-left:calc(-1 * var(--cs-rail)); }
    .cs-phone-row { display:flex; gap:24px; align-items:stretch; }
    .cs-phone-fig { position:relative; flex:0 0 350px; max-width:350px; }
    /* translucent annotation bands — geometry from Figma 1:2873, % of the phone box */
    .cs-band-hl { position:absolute; left:-7.1%; width:112.9%; background:#e5c07b; border:2px solid #000; opacity:0.4; pointer-events:none; z-index:5; }
    .cs-band-hl.green { background:#8bff49; }

    /* ── scrolling phone stages — fixed 350×772.62 design space scaled via --ps
       (set in JS). Fixed chrome (bg, nav, title, CTA, device frame) is layered
       around a clipped window whose card strip auto-scrolls.
       Timeline (16s ping-pong): hold 1s → scroll 5.9s → hold 2.2s → back 5.9s → hold 1s ── */
    .cs-pshell { position:relative; aspect-ratio:350/772.62; overflow:hidden; border-radius:44px; }
    .cs-pstage { position:absolute; left:0; top:0; width:350px; height:772.62px;
                 transform:scale(var(--ps,1)); transform-origin:0 0; font-family:'Lato',sans-serif; }
    .cs-pstage img { display:block; position:absolute; }
    .cs-pstage .pbg    { left:0; top:0; width:350px; height:772.62px; }
    .cs-pstage .pnav   { left:0; top:30.59px; width:350px; z-index:3; }
    .cs-pstage .ptitle { left:0; top:105px; width:350px; z-index:3; }
    .cs-pstage .pcta   { left:0; bottom:0; width:350px; z-index:3; }
    .cs-pstage .pbar   { left:0; bottom:0; width:350px; z-index:3; }
    .cs-pstage .phead  { left:24px; top:91.7px; width:301.8px; z-index:3; }
    .cs-pstage .pframe { left:0; top:0; width:350px; z-index:4; }
    .cs-pwin { position:absolute; left:0; overflow:hidden; z-index:2; }
    .iv-win { top:170.79px; width:350px; height:480px; }
    .es-win { top:167.86px; width:350px; height:418px; }
    .iv-scroll { position:absolute; left:0; top:0; width:350px; height:970.69px; will-change:transform; }
    .es-scroll { position:absolute; left:24px; top:0; width:1292.9px; height:418px; will-change:transform; }
    .play .iv-scroll { animation:hatchaVScroll 16s linear infinite; }
    .play .es-scroll { animation:hatchaHScroll 16s linear infinite; }
    @keyframes hatchaVScroll {
      0%, 6%   { transform:translateY(0); animation-timing-function:cubic-bezier(0.45,0.05,0.25,1); }
      43%, 57% { transform:translateY(-490.7px); animation-timing-function:cubic-bezier(0.45,0.05,0.25,1); }
      94%, 100% { transform:translateY(0); }
    }
    @keyframes hatchaHScroll {
      0%, 6%   { transform:translateX(0); animation-timing-function:cubic-bezier(0.45,0.05,0.25,1); }
      43%, 57% { transform:translateX(-990.9px); animation-timing-function:cubic-bezier(0.45,0.05,0.25,1); }
      94%, 100% { transform:translateX(0); }
    }
    .iv-scroll > * { position:absolute; left:20.38px; }
    .iv-scroll .ivc1 { top:0; width:309.9px; height:445.29px; padding:13.58px; border-radius:20.38px; border-width:0.85px; }
    .ivc1 .hc-sub { line-height:20.38px; margin-top:6.79px; }
    .ivc1 .hc-sep { margin-top:13.58px; }
    .ivc1 .hc-btn { height:33.96px; font-size:11.89px; }
    /* dual-axis slider visual — glow + dot grid + four axis labels */
    .hs-axis { position:relative; width:291.22px; height:291.22px; border-radius:22px; overflow:hidden; margin-top:13.58px; }
    .hs-axis img { position:absolute; }
    .ax-glow { left:28.86px; top:28.86px; width:234.22px; height:234.22px; }
    .ax-dots { left:13.58px; top:13.58px; width:263.58px; height:271.69px; }
    .ax-lbl { position:absolute; font-size:13.58px; line-height:20.38px; letter-spacing:0.14px;
              white-space:nowrap; transform:translate(-50%,-50%); }
    .ax-lbl.top { left:calc(50% + 0.42px); top:7.64px; }
    .ax-lbl.bottom { left:calc(50% + 0.85px); top:280.18px; }
    .ax-lbl.left { left:calc(50% - 127.35px); top:calc(50% - 3.4px); transform:translate(-50%,-50%) rotate(-90deg); }
    .ax-lbl.right { left:calc(50% + 126.93px); top:calc(50% - 3.4px); transform:translate(-50%,-50%) rotate(90deg); }
    /* glass cards — border/radius/padding/blur from Figma card specs */
    .hcard { position:absolute; box-sizing:border-box; border:0.9px solid #fff; color:#fff;
             background:radial-gradient(120% 130% at 50% 49%, rgba(255,255,255,0) 0%, rgba(255,255,255,0.25) 100%);
             backdrop-filter:blur(6.8px); -webkit-backdrop-filter:blur(6.8px); }
    .iv-scroll .hcard { border-radius:21.8px; padding:14.53px; }
    .iv-scroll .ivc2 { top:465.67px; width:309.25px; height:170.87px; }
    .iv-scroll .ivc3 { top:656.91px; width:304px; height:313.78px; }
    .es-scroll .hcard { border-radius:20.38px; padding:13.58px; overflow:hidden; }
    .es-scroll .esc1 { left:0; top:0; width:302px; height:418px; }
    .es-scroll .esc2 { left:322.38px; top:0; width:309.9px; height:418px; }
    .es-scroll .esc3 { left:652.65px; top:0; width:309.9px; height:418px; }
    .es-scroll .esc4 { left:982.92px; top:0; width:309.9px; height:418px; }
    .hc-title { font-weight:400; font-size:13.58px; line-height:20.4px; letter-spacing:0.14px; }
    .hc-sub { font-weight:300; font-size:13.58px; line-height:17px; letter-spacing:0.14px; color:rgba(255,255,255,0.5); margin-top:7.27px; }
    .hc-sep { height:13.58px; position:relative; }
    .hc-sep::after { content:""; position:absolute; left:0; right:0; top:6.4px; border-top:0.9px solid rgba(255,255,255,0.2); }
    /* interview card 2 — centered dual slider */
    .hs-box { position:relative; height:64px; margin-top:14.53px; }
    .hs-track { position:absolute; top:29.27px; height:17px; border-radius:99px; }
    .hs-track.l { left:0; width:49%; background:linear-gradient(to right,#e5c07b,#8a7bff); }
    .hs-track.r { left:51%; width:49%; background:linear-gradient(to right,#8a7bff,#2b2452); }
    .hs-pill { position:absolute; left:43.5%; top:0; width:35.4px; height:22px; border-radius:10.9px;
               background:rgba(255,255,255,0.15); display:flex; align-items:center; justify-content:center;
               font-size:10px; color:#f4f1e8; }
    .hs-tick { position:absolute; left:49.95%; top:22px; width:1.4px; height:24.27px; border-radius:99px; background:rgba(255,255,255,0.15); }
    .hs-vals { position:absolute; left:0; right:0; top:48.9px; display:flex; justify-content:space-between;
               font-size:10.9px; line-height:14.53px; color:#f4f1e8; }
    /* interview card 3 — multi-line field + action */
    .hf-input { box-sizing:border-box; height:128.08px; margin-top:14.53px; padding:10.9px; border-radius:10.9px;
                background:rgba(255,255,255,0.15); border:0.45px solid rgba(255,255,255,0.5);
                font-weight:300; font-size:14.53px; line-height:21.8px; letter-spacing:0.15px; }
    .hf-helper { font-size:10.9px; line-height:14.53px; color:rgba(255,255,255,0.5); margin-top:7.3px; }
    .hc-btn { display:inline-flex; align-items:center; justify-content:center; height:36.33px; padding:0 11px;
              border:0.9px solid rgba(255,255,255,0.5); border-radius:99px; background:none; color:#fff;
              font-family:'Lato',sans-serif; font-size:12.72px; letter-spacing:0.13px; margin-top:3.6px; }
    /* event summary — property rows + simple lists */
    .hp-row { display:flex; align-items:center; gap:6.79px; min-height:33.96px; }
    .hp-row .k { flex:0 0 84.9px; font-weight:300; font-size:11.89px; letter-spacing:0.12px; }
    .hp-row .v { padding:0 10.19px; font-weight:400; font-size:11.53px; line-height:17.3px; letter-spacing:0.12px; }
    .hl-row { padding:3.4px 10.19px; }
    .hl-row .t { font-weight:300; font-size:13.58px; line-height:20.38px; letter-spacing:0.14px; }
    .hl-row .d { font-weight:400; font-size:10.19px; line-height:13.58px; color:rgba(255,255,255,0.5); margin-top:2px; }
    .es-list { margin-top:13.58px; }
    .esc3 .es-list, .esc4 .es-list { margin-top:20.38px; }
    .hc-fade { position:absolute; left:0; right:0; bottom:0; width:100%; z-index:1; transform:rotate(180deg); }
    .cs-annot { position:relative; flex:0 0 180px; }
    .cs-annot-k { position:absolute; left:0; right:0; font-size:12px; font-weight:500; text-transform:uppercase; color:rgba(0,0,0,0.5); line-height:1.358; }
    .cs-annot-v { display:block; font-size:16px; font-weight:500; text-transform:uppercase; line-height:1.358; margin-top:1px; }
    .cs-annot-v.fixed { color:#000; }
    .cs-annot-v.flex  { color:#297000; }

    /* numbered steps (section 04) */
    .cs-steps { display:grid; grid-template-columns:repeat(4,1fr); gap:0; margin-top:72px; }
    .cs-step { padding:0 26px; border-left:1px solid rgba(255,255,255,0.14); }
    .cs-step:first-child { padding-left:0; border-left:0; }
    .cs-step-n { font-family:var(--cs-serif); font-weight:400; font-size:24px; letter-spacing:0.02em; color:#6a6a6a; margin-bottom:22px; }
    .cs-step-h { font-size:20px; font-weight:600; color:#f4efe6; margin-bottom:14px; }
    .cs-step-p { font-size:14px; line-height:1.55; color:rgba(255,249,233,0.6); }

    /* image gallery collage (section 04) — staggered tile heights like the comp */
    .cs-gallery { display:flex; gap:47px; padding:64px var(--cs-pad); overflow-x:hidden; align-items:center; scrollbar-width:none; -ms-overflow-style:none; }
    .cs-gallery::-webkit-scrollbar { display:none; }
    .cs-ds-tile { flex:0 0 auto; border-radius:10px; overflow:hidden; background:#0d0d0d; }
    .cs-ds-tile.t-sq   { width:470px; height:470px; }
    .cs-ds-tile.t-tall { width:450px; height:610px; }
    .cs-ds-tile.t-port { width:415px; height:470px; }
    .cs-ds-tile > img, .cs-ds-tile > video { width:100%; height:100%; object-fit:cover; display:block; }

    /* asset reveal on scroll — hard-edge clip-mask unveil (MetaLab-style): the
       media is clipped to a zero-height line at its TOP, then the clip sweeps
       DOWNWARD to reveal it behind a crisp horizontal edge while the block settles
       down a few px (top-to-bottom reveal). --clip-round (set in JS from the
       element's own border-radius) keeps rounded frames rounded through the wipe. */
    #hatcha-view .cs-clip {
      clip-path: inset(0px 0px 100% 0px round var(--clip-round, 0px));
      transform: translateY(-44px);
      transition: clip-path 1.15s cubic-bezier(0.16, 1, 0.3, 1),
                  transform 1.15s cubic-bezier(0.16, 1, 0.3, 1);
      transition-delay: var(--reveal-delay, 0s);
      /* no will-change — one-shot clip reveal, browser promotes the running transition itself */
    }
    #hatcha-view .cs-clip.is-visible {
      clip-path: inset(0px 0px 0% 0px round var(--clip-round, 0px));
      transform: none;
    }
    /* the phones assembly overflows sideways (annotation bands sit at left:-7.1%),
       so give the clip horizontal breathing room — clip only the bottom edge, never
       the sides — otherwise the bands get shaved at rest. */
    #hatcha-view .cs-clip-wide { clip-path: inset(0px -8% 100% -8% round 0px); }
    #hatcha-view .cs-clip-wide.is-visible { clip-path: inset(0px -8% 0% -8% round 0px); }
    @media (prefers-reduced-motion: reduce) {
      #hatcha-view .cs-clip, #hatcha-view .cs-clip-wide {
        clip-path: none; transform: none; transition: none;
      }
    }

    /* theme system (section 05) */
    .cs-theme-grid { display:grid; grid-template-columns:340px minmax(0,1fr); gap:60px; margin-top:64px; align-items:center; max-width:none; }
    .cs-theme-txt-h { font-size:13px; font-weight:400; letter-spacing:0.06em; text-transform:uppercase; color:#8f8a80; margin-bottom:20px; }
    .cs-mini { margin-top:26px; border:0; }
    .cs-mini-head { background:none; color:var(--lg-faint,#a79c89); padding:0 0 11px; border-bottom:1px solid var(--lg-line2,rgba(255,255,255,.22));
      font-family:var(--cs-sans); font-size:12px; font-weight:500; letter-spacing:0.16em; text-transform:uppercase; }
    .cs-mini-row { padding:10px 0; font-family:var(--cs-sans); font-size:15px; color:var(--lg-mut,#8a8271); border-bottom:1px solid var(--lg-line,rgba(255,255,255,.14)); border-top:0; }
    .cs-mini-row:last-child { border-bottom:0; }
    @media (max-width:900px){
      .cs-mini-pair { display:flex; gap:12px; }
      .cs-mini-pair .cs-mini { flex:1 1 0; margin-top:34px; }
      .cs-mini-pair .cs-mini-head { padding:10px 12px; font-size:11px; }
      .cs-mini-pair .cs-mini-row { padding:10px 12px; font-size:13px; }
    }
    .cs-palette { position:relative; width:100%; aspect-ratio:var(--cs-palette-ar); justify-self:stretch; overflow:hidden; border-radius:8px; }
    .cs-palette-art { position:relative; display:flex; align-items:flex-end; padding:52px;
      background:radial-gradient(120% 90% at 30% 15%,#6f63e8 0%,#2b7fd4 35%,#1fd4d4 62%,#0a2224 100%); }
    .cs-palette-art h3 { font-family:var(--cs-serif); font-weight:400; font-size:48px; line-height:1.2; letter-spacing:0.02em; color:#fff; position:relative; z-index:1; }
    .cs-palette-art h3 i { font-style:italic; }
    .cs-palette-vid { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:0; }
    .cs-guardrail-img { display:block; width:100%; height:auto; aspect-ratio:var(--cs-guardrail-ar); justify-self:stretch; object-fit:cover; border-radius:8px; }
    .cs-swatches { display:flex; flex-direction:column; }
    .cs-swatch { flex:1; display:flex; align-items:center; justify-content:center; writing-mode:vertical-rl; transform:rotate(180deg);
                 font-size:11px; font-weight:600; letter-spacing:0.1em; text-transform:uppercase; color:#0a0a0a; }
    .cs-theme-lower { display:grid; grid-template-columns:minmax(0,1fr) 360px; gap:56px; margin-top:110px; align-items:center; max-width:none; }

    /* outcome stats (section 06) — separate hairline boxes with a small gap */
    .cs-stats { display:grid; grid-template-columns:repeat(4,1fr); gap:6px; margin-top:104px; }
    .cs-stat { padding:48px 30px 58px; border:1px solid #d8d2c4; }
    .cs-stat-h { font-family:var(--cs-serif); font-weight:400; font-size:38px; letter-spacing:0.02em; margin-bottom:22px; }
    .cs-stat-p { font-size:14px; line-height:1.55; color:rgba(11,11,11,0.72); }

    /* closing + read next */
    .cs-closing { padding:230px 0; text-align:center; }
    .cs-closing-h { font-family:var(--cs-serif); font-weight:400; font-size:60px; line-height:1.18; letter-spacing:0.02em; }
    .cs-closing-h i { font-style:italic; }
    .cs-closing-p { max-width:640px; margin:44px auto 0; font-size:16.5px; line-height:1.65; color:rgba(11,11,11,0.78); }
    /* ── READ NEXT — full-bleed 100vh slide (Figma 702:2688). Copy on the rail at
       left; the NEXT case study's live hero mock as a rounded card bleeding 13px
       past the right edge. The hairline under the copy fills over 8s and then
       hands the reader on (see the read-next auto-advance script).
       Both children are absolute against the 100vh slide box so the geometry maps
       straight off the comp instead of fighting the panel's own flex centring —
       the deck translates .cs-slide-inner, which makes it the containing block,
       and its padding box is exactly the full frame. */
    /* min-height matters on the reduced-motion path: the deck bails there, so there's
       no .cs-slide-inner to give the absolute children a box and the section would
       otherwise collapse to 0. In the deck it's a no-op (the section is inset:0). */
    .cs-readnext { position:relative; padding:0; overflow:hidden;  }
    /* the footer's .ft-wavy crest (z-index 12) bleeds ~95px above the footer box, and
       the deck stage was z-index:auto — so the cream wave sliced across this panel's
       black field. The stage already isolates its own sections, so lifting the whole
       stage over the footer keeps the crest for the real footer reveal (once the
       sticky stage has released) and off the slides. */
    .cs-slide-stage { z-index:13; }

    /* ── SECTION PAGER — shared case-study chrome ─────────────────────────────
       A scale drawing of the deck pinned to the bottom of the viewport: one tick
       per chapter, each tick's WIDTH proportional to how many 100vh panels that
       chapter holds. So the bar shows page length and chapter weight at a glance,
       and doubles as the progress bar — ticks fill left-to-right as you read, the
       active one filling panel by panel.
       Built by csPager() (see the script block with the deck IIFEs); appended to
       the routed view, which is display:contents with [hidden]{display:none}, so
       it inherits --cs-* and disappears on route change with no per-route JS.
       z-index 80: over the deck stage (13) and footer (12), under .rt-hero (90)
       so a route-open transition still sweeps across it, and under #nav (100). */
    /* The bar floats over whatever the panel happens to be painting — a cream band, a
       black band, or a full-bleed photo (the Hatcha hero is cream up top and a dark
       sofa shot exactly where the bar sits). Nothing derived from the band class can be
       right in all three, so the bar carries its own surface, the same way the site's
       nav floats in a translucent .nav-pill instead of matching what's behind it. That
       makes the polarity a single decision — day or night — with no per-panel sensing. */
    /* No container treatment — the ticks sit bare on the panel. Polarity is therefore
       per-panel: csPager sets .on-dark / .on-light from the ACTIVE band, and since night
       mode inverts what .dark paints (body.night .cs-band.dark) the test is the band
       class XOR the theme. The soft halo on each line is what keeps them legible where a
       panel is a full-bleed photo rather than a flat band. */
    .cs-pager {
      box-sizing:border-box; position:fixed; z-index:80; bottom:30px; left:50%;
      /* wide enough that even the longest case study (Hatcha, 10 chapters) reaches the
         per-rectangle cap, so a rectangle is the same length on every case study */
      width:min(620px, 44vw); transform:translateX(-50%);
      display:flex; align-items:center; justify-content:center; gap:6px;
      --pg-line:rgba(11,11,11,0.24); --pg-live:#0b0b0b; --pg-halo:rgba(255,249,234,0.5);
      --pg-pill:#0b0b0b; --pg-pill-ink:#fff9ea; --pg-pill-rule:rgba(255,249,234,0.28);
    }
    .cs-pager.on-dark {
      --pg-line:rgba(255,249,234,0.30); --pg-live:#fff9ea; --pg-halo:rgba(0,0,0,0.45);
      --pg-pill:#fff9ea; --pg-pill-ink:#0b0b0b; --pg-pill-rule:rgba(11,11,11,0.2);
    }
    /* the tick is a hit target several times taller than the line it draws, so the bar
       stays hairline-thin without being fiddly to click.
       One rectangle per chapter, all the same width — a chapter is one unit of the page
       whether it runs for one slide or four. flex 1 1 0 (not a % width) because
       percentages resolve against the content box and the gaps are then ADDED on top,
       which made the row overflow by 9 gaps; growing from a 0 basis divides what is
       left AFTER the gaps. */
    /* max-width caps how long a single rectangle can get, so a short case study doesn't
       stretch its few chapters across the whole bar — Existence has 4 chapters and they
       were ~125px each. Capped, the rectangle reads at roughly the same length on every
       case study and the ROW's total length is what signals how long the page is; the
       row centres itself in the (transparent) full-width box. */
    .cs-pg-t {
      box-sizing:border-box; position:relative; flex:1 1 0; max-width:56px; height:16px;
      appearance:none; -webkit-appearance:none; border:0; padding:0; background:none;
      cursor:pointer; display:flex; align-items:center;
    }
    .cs-pg-t::before {
      content:''; display:block; width:100%; height:2px; border-radius:2px; background:var(--pg-line);
      box-shadow:0 0 7px var(--pg-halo);
      transition:height 0.34s cubic-bezier(0.22,1,0.36,1);
    }
    /* the read-so-far fill, scaled in place — the active tick fills panel by panel, so
       the bar is a progress bar and a map at the same time.
       top/bottom:0 + margin:auto keeps it centred as its height grows on hover */
    .cs-pg-f {
      position:absolute; left:0; right:0; top:0; bottom:0; margin:auto 0;
      height:2px; border-radius:2px; box-shadow:0 0 7px var(--pg-halo);
      background:var(--pg-live); transform:scaleX(var(--f,0)); transform-origin:0 50%;
      transition:transform 0.5s cubic-bezier(0.22,1,0.36,1), height 0.34s cubic-bezier(0.22,1,0.36,1);
    }
    /* the whole bar wakes together — hovering any tick thickens every line, so it reads
       as one object rather than a row of independent buttons */
    .cs-pager:hover .cs-pg-t::before, .cs-pager:hover .cs-pg-f,
    .cs-pager:focus-within .cs-pg-t::before, .cs-pager:focus-within .cs-pg-f { height:5px; }
    .cs-pg-t:hover::before { background:var(--pg-live); opacity:0.4; }
    .cs-pg-t:focus-visible { outline:2px solid var(--pg-live); outline-offset:5px; border-radius:1px; }
    .cs-pg-t:active .cs-pg-f { opacity:0.6; }

    /* one shared pill, translated to the hovered tick — a per-tick pill would be
       clipped by its own 22px box and couldn't glide between ticks */
    .cs-pg-pill {
      position:absolute; left:0; bottom:calc(100% + 8px); transform:translateX(var(--px,0px));
      display:flex; align-items:center; gap:11px; white-space:nowrap; pointer-events:none;
      padding:9px 15px; border-radius:100px; background:var(--pg-pill); color:var(--pg-pill-ink);
      font-family:var(--cs-sans); font-size:13px; line-height:1; letter-spacing:0.01em;
      box-shadow:0 10px 26px -12px rgba(11,11,11,0.55), 0 2px 6px -2px rgba(11,11,11,0.3);
      opacity:0; visibility:hidden;
      transition:opacity 0.24s cubic-bezier(0.22,1,0.36,1),
                 transform 0.42s cubic-bezier(0.22,1,0.36,1),
                 visibility 0s linear 0.24s;
    }
    .cs-pg-pill.on { opacity:1; visibility:visible; transition-delay:0s, 0s, 0s; }
    .cs-pg-num { font-size:11.5px; letter-spacing:0.08em; text-transform:uppercase; opacity:0.62; }
    .cs-pg-rule { width:1px; height:11px; background:var(--pg-pill-rule); }
    /* an unnumbered chapter (Overview / Closing / Read next) drops the number and rule */
    .cs-pg-pill.bare .cs-pg-num, .cs-pg-pill.bare .cs-pg-rule { display:none; }

    /* touch + mobile: the deck doesn't run under html.cs-static and there's no hover
       to raise the pill, so the bar has no job. csPager bails on the same conditions —
       belt and braces, so CSS and JS can never disagree (same pattern as the deck). */
    @media (max-width:820px), (hover:none) { .cs-pager { display:none; } }
    @media (prefers-reduced-motion: reduce) {
      .cs-pg-t::before, .cs-pg-t .cs-pg-f, .cs-pg-pill { transition-duration:0.01ms; }
    }

    .rn-copy { position:absolute; top:48.4%; transform:translateY(-50%); z-index:2;
               left:max(var(--cs-pad), calc((100% - var(--cs-max)) / 2 + var(--cs-pad)));
               width:min(520px, calc(50% - var(--cs-pad) - 48px)); }
    .rn-kicker { font-family:var(--cs-sans); font-size:16px; line-height:24px;
                 letter-spacing:0.06em; text-transform:uppercase; color:#fff; }
    /* comp sets Lato 60px; this site has no Lato and every case-study title is
       serif, so the name takes --cs-serif at the same 60px step as .cs-closing-h */
    .rn-title-link { display:inline-block; margin-top:23px; text-decoration:none; color:inherit;
                     transition:opacity 0.3s cubic-bezier(0.22,1,0.36,1); }
    .rn-title { margin:0; font-family:var(--cs-serif); font-weight:400;
                font-size:clamp(38px,4.17vw,60px); line-height:1.06; letter-spacing:0.01em; color:#fff; }
    .rn-title-link:hover .rn-title { opacity:0.72; }
    .rn-title-link:focus-visible { outline:2px solid currentColor; outline-offset:6px; border-radius:2px; }
    .rn-desc { margin:28px 0 0; font-family:var(--cs-sans); font-weight:400;
               font-size:clamp(17px,1.53vw,22px); line-height:1.45; letter-spacing:0.01em;
               color:rgba(255,255,255,0.75); }

    /* 8s progress hairline — driven by rAF, NOT a transition, so it can be reset
       mid-flight and so a backgrounded tab stalls it for free */
    .rn-bar { margin-top:44px; height:1px; width:100%; background:rgba(255,255,255,0.22); overflow:hidden; }
    .rn-bar-fill { display:block; height:100%; width:100%; background:#fff;
                   transform:scaleX(0); transform-origin:0 50%; will-change:transform; }

    .rn-asset { position:absolute; top:16%; bottom:16%; left:50%; right:-13px; z-index:1;
                display:block; border-radius:16px; overflow:hidden; isolation:isolate;
                text-decoration:none; background:#0e0e0e;
                box-shadow:0 34px 90px -34px rgba(0,0,0,0.72), 0 2px 12px rgba(0,0,0,0.38);
                transition:transform 0.55s cubic-bezier(0.22,1,0.36,1); }
    /* same fill contract the home tiles give the mock (see .project-tile .tile-mock) */
    .rn-asset .tile-mock { position:absolute; inset:0; width:100%; height:100%;
                           aspect-ratio:auto; margin:0; isolation:isolate; }
    .rn-asset:hover { transform:scale(1.012); }
    .rn-asset:active { transform:scale(0.997); }
    .rn-asset:focus-visible { outline:2px solid #fff; outline-offset:5px; }

    /* mobile (html.cs-static, <=820px) — no deck here, just vertical scroll, so the
       absolute geometry above would collapse the section to nothing. Unwind it into a
       stack: card on top (still bleeding into the right gutter), copy under it. */
    html.cs-static .cs-readnext { display:flex; flex-direction:column; justify-content:center;
      gap: 40px;
  padding: 20px; min-height:100svh;
      padding:var(--cs-navpad) 0 var(--cs-pad) var(--cs-pad); }
    html.cs-static .rn-asset { order:1; position:relative; inset:auto;
      flex:1 1 auto; min-height:240px; max-height:52svh;
      border-radius:16px 0 0 16px; box-shadow:none; }
    html.cs-static .rn-asset:hover, html.cs-static .rn-asset:active { transform:none; }
    html.cs-static .rn-copy { order:2; position:static; transform:none;
      left:auto; width:auto; padding-right:var(--cs-pad); }
    html.cs-static .rn-title-link { margin-top:14px; }
    html.cs-static .rn-desc { margin-top:16px; font-size:17px; line-height:1.5; }
    html.cs-static .rn-bar { margin-top:26px; }

    /* ── NIGHT THEME — re-point the case-study tokens (pairs with body.night
       in css/site.css; cream bands go night-dark, ink goes cream) ── */
    body.night #hatcha-view { --cs-cream:var(--c-night); --cs-ink:var(--c-cream-2); }
    body.night #hatcha-view .cs-band.cream .cs-p { color:rgba(255,249,233,0.85); }
    body.night #hatcha-view .cs-hero-intro { color:rgba(255,249,233,0.88); }
    #hatcha-view .cs-hero-tagline { margin:16px 0 0; font-family:var(--cs-serif);
      font-style:normal; font-weight:400; font-size:34px; line-height:1.2;
      letter-spacing:0.01em; color:rgba(11,11,11,0.6); }
    body.night #hatcha-view .cs-hero-tagline { color:rgba(255,249,233,0.7); }
    body.night #hatcha-view .cs-num { color:#43434b; }
    body.night #hatcha-view .cs-ph:not(.dark) {
      background:repeating-linear-gradient(45deg,#14141a,#14141a 14px,#1b1b22 14px,#1b1b22 28px);
      color:#6b6b6b; }
    body.night #hatcha-view .cs-hgp { background:#15151c; }
    body.night #hatcha-view .cs-hgp-v { color:#e9e5dc; }
    body.night #hatcha-view .cs-hgp:nth-child(1) .cs-hgp-tab { background:#000; box-shadow:inset -1px 0 0 #23232b; }
    body.night #hatcha-view .cs-fx,
    body.night #hatcha-view .cs-fx-cell,
    body.night #hatcha-view .cs-fx-cell:nth-child(2n+1) { border-color:rgba(255,249,233,0.22); }
    body.night #hatcha-view .cs-fx-cell:nth-child(2n) { box-shadow:inset 0 0 0 1px rgba(255,249,233,0.08); }
    body.night #hatcha-view .cs-stat { border-color:rgba(255,249,233,0.22); }
    body.night #hatcha-view .cs-stat-p { color:rgba(255,249,233,0.7); }
    body.night #hatcha-view .cs-closing-p { color:rgba(255,249,233,0.78); }
    body.night #hatcha-view .cs-annot-k { color:rgba(255,249,233,0.5); }
    body.night #hatcha-view .cs-annot-v.fixed { color:var(--c-cream-2); }
    body.night #hatcha-view .cs-annot-v.flex { color:#7dc36a; }
    body.night #hatcha-view .cs-band-hl { border-color:rgba(255,249,233,0.7); }

    /* night: dark bands INVERT to day-cream with ink text (cream bands already went
       night-dark via the token re-point above — the two swap roles). Literal day
       values here, since --cs-cream/--cs-ink now hold night colors. */
    body.night #hatcha-view .cs-band.dark { background:#fff9f1; color:#0b0b0b; }
    body.night #hatcha-view .cs-band.dark .cs-num { color:#c9c4ba; }
    body.night #hatcha-view .cs-band.dark .cs-p { color:rgba(11,11,11,0.9); }
    body.night #hatcha-view .cs-band.dark .cs-quote-line { border-top-color:rgba(0,0,0,0.1); }
    body.night #hatcha-view .cs-band.dark .cs-shift-eye { color:rgba(0,0,0,0.5); }
    /* comparison table — before/after swap chrome for the cream ground */
    body.night #hatcha-view .cs-cmp { border-color:rgba(0,0,0,0.25); }
    body.night #hatcha-view .cs-cmp .before { color:#141414; }
    body.night #hatcha-view .cs-cmp .after { background:#000; color:#fdf1e1; }
    body.night #hatcha-view .cs-cmp-arrow { color:#141414; border-left-color:rgba(0,0,0,0.25); border-right-color:rgba(0,0,0,0.25); }
    body.night #hatcha-view .cs-cmp-row:not(:last-child) .before,
    body.night #hatcha-view .cs-cmp-row:not(:last-child) .cs-cmp-arrow { border-bottom-color:rgba(0,0,0,0.25); }
    body.night #hatcha-view .cs-cmp-row:not(:last-child) .after { border-bottom-color:rgba(255,255,255,0.25); }
    body.night #hatcha-view .cs-cmp-head .before { background:rgba(0,0,0,0.08); }
    body.night #hatcha-view .cs-cmp-head .cs-cmp-arrow { background:rgba(0,0,0,0.08); }
    body.night #hatcha-view .cs-cmp-head .after { background:#141414; color:#fdf1e1; }
    /* criteria labels (section 02) */
    body.night #hatcha-view .cs-crit-eye { color:#0b0b0b; }
    body.night #hatcha-view .cs-crit-num { color:rgba(0,0,0,0.5); }
    body.night #hatcha-view .cs-crit-lbl { color:rgba(0,0,0,0.75); }
    /* numbered steps (section 04) */
    body.night #hatcha-view .cs-step { border-left-color:rgba(0,0,0,0.16); }
    body.night #hatcha-view .cs-step-h { color:#141414; }
    body.night #hatcha-view .cs-step-p { color:rgba(11,11,11,0.65); }
    /* mini spec tables (section 05) — day/night handled by scoped --lg-* tokens */
    /* read next — the panel rides .cs-band.dark, which INVERTS to cream at night
       (see the swap above), so the whole block flips to ink. Written unscoped so
       all four case studies share it. */
    body.night .cs-readnext .rn-kicker,
    body.night .cs-readnext .rn-title { color:#0b0b0b; }
    body.night .cs-readnext .rn-desc { color:rgba(11,11,11,0.72); }
    body.night .cs-readnext .rn-bar { background:rgba(11,11,11,0.2); }
    body.night .cs-readnext .rn-bar-fill { background:#0b0b0b; }
    body.night .rn-asset:focus-visible { outline-color:#0b0b0b; }
    body.night #hatcha-view .cs-band.dark .cs-ph.dark {
      background:repeating-linear-gradient(45deg,#e7e0d2,#e7e0d2 14px,#ddd5c5 14px,#ddd5c5 28px);
      color:#8a8474; }

    /* responsive */
    @media (max-width:1100px){
      #hatcha-view { --cs-pad:28px; --cs-rail:180px; }
      .cs-rail { padding-left:0; }
      .cs-hero-grid, .cs-showcase-grid, .cs-theme-grid, .cs-theme-lower { grid-template-columns:1fr; gap:40px; }
      .cs-palette { height:auto; width:100%; justify-self:stretch; }
      .cs-guardrail-img { height:auto; width:100%; }
      .cs-hero-h { font-size:44px; }
      #hatcha-view .cs-hero-tagline { font-size:26px; }
      .cs-meta { margin-top:8px; }
      .cs-hero-img { height:520px; }
      .cs-sec-h, .cs-showcase-h { font-size:34px; }
      .cs-steps, .cs-stats, .cs-phones { grid-template-columns:1fr; }  /* .cs-crit governed by its panel's auto-fit rule */
      .cs-crit-eye { margin-left:0; }
      .cs-ds-tile.t-sq   { width:280px; height:280px; }
      .cs-ds-tile.t-tall { width:268px; height:363px; }
      .cs-ds-tile.t-port { width:247px; height:280px; }
      .cs-palette { margin-right:0; }
      .cs-theme-lower > .cs-ph:first-child { margin-left:0; }
      .cs-phones { margin-left:0; }
      .cs-cmp-row { grid-template-columns:1fr 56px 1fr; }
      .cs-cmp-cell { padding:16px; font-size:16px; }
      .cs-step { border-left:0; padding:0 0 30px; }
      .cs-stat { border-left:0; border-top:1px solid #d8d2c4; }
      .cs-stat:first-child { border-top:0; }
      .cs-quote p { font-size:30px; }
      .cs-closing-h { font-size:40px; }
      .cs-num { font-size:64px; }
      .cs-logoband { min-height:70vh; }
    }
    @media (max-width:900px){
      .cs-guardrail-txt { order:1; }
      .cs-guardrail-img { order:2; }
    }
    @media (max-width:720px){
      .cs-phones { margin-left:calc(-1 * var(--cs-rail)); }  /* re-escape the rail column */
      .cs-phone-row { gap:16px; }
      .cs-phone-fig { flex:0 1 55%; }
      .cs-annot { flex:1 1 auto; }  /* absorb remaining width instead of a fixed 180px (avoids sideways scroll) */
      .cs-annot-k { font-size:9px; }
      .cs-annot-v { font-size:11px; }
    }
    /* keep a min 16px side gutter once the centered max-width stops providing one */
    @media (max-width:1320px){
      #hatcha-view .cs-numbered { padding-left:16px !important; padding-right:16px !important; }
      #hatcha-view .cs-quote-inner { padding-left:16px; padding-right:16px; }
    }

    /* Hatcha's Role value ("Co-Lead Product Designer") runs to 228px inside the
       232px first column, so with the shared 0 column-gap it butts against the
       Client value. Scoped here so the other three decks keep their own rhythm. */
    #hatcha-view .cs-meta { column-gap:40px; }

    /* ═══ Case-study hero meta · mobile static flow (all four views) ═══
       The 232px + 1fr pair leaves each value ~150px, so anything longer than
       two words ("Beta – 30k waitlist, releasing 2026") wraps to three lines
       against a half-empty neighbour. One row per item reads cleanly instead. */
    html.cs-static .cs-meta { grid-template-columns:1fr; gap:26px 0; }
  

    #fireflut-view { --cs-serif:'DM Serif Display', Georgia, serif;
                     --cs-sans:'Space Grotesk', sans-serif;
                     --cs-cream:#fff9f1; --cs-black:#000; --cs-ink:#0b0b0b;
                     --cs-coral:#4A9EFF;
                     --cs-max:1320px; --cs-pad:60px; --cs-rail:355px;
                     --cs-navpad:112px; }   /* panel top clearance = 80px nav + 32px gap */

    /* tile → hero shared-element zoom (same mechanism as Hatcha) */
    body.vt-hero a.project-tile[href="/work/fireflut"] { view-transition-name: fireflut-hero; }
    body.vt-hero #fireflut-view .cs-hero .cs-hero-mock { view-transition-name: fireflut-hero; }
    ::view-transition-old(fireflut-hero),
    ::view-transition-new(fireflut-hero) { width:100%; height:100%; object-fit:cover; }
    ::view-transition-group(fireflut-hero) {
      animation-duration: 0.6s;
      animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
    }
    @media (prefers-reduced-motion: reduce) {
      ::view-transition-group(fireflut-hero),
      ::view-transition-old(fireflut-hero),
      ::view-transition-new(fireflut-hero) { animation: none; }
    }

    /* comparison table (the "what needed to shift" table) — Fireflut uses it once,
       on a cream band, so scope cream chrome here (night flips it back to dark). */
    #fireflut-view .cs-cmp { border-color:rgba(0,0,0,0.16); }
    #fireflut-view .cs-cmp .before { color:rgba(11,11,11,0.6); }
    #fireflut-view .cs-cmp .after { background:#0b0b0b; color:#fdf1e1; }
    #fireflut-view .cs-cmp-arrow { color:var(--cs-coral); border-left-color:rgba(0,0,0,0.16); border-right-color:rgba(0,0,0,0.16); }
    #fireflut-view .cs-cmp-row:not(:last-child) .before,
    #fireflut-view .cs-cmp-row:not(:last-child) .cs-cmp-arrow { border-bottom:1px solid rgba(0,0,0,0.16); }
    #fireflut-view .cs-cmp-row:not(:last-child) .after { border-bottom:1px solid rgba(255,255,255,0.18); }
    #fireflut-view .cs-cmp-head .before { background:rgba(0,0,0,0.04); color:rgba(11,11,11,0.55); }
    #fireflut-view .cs-cmp-head .cs-cmp-arrow { background:rgba(0,0,0,0.04); }
    #fireflut-view .cs-cmp-head .after { background:#0b0b0b; color:#fdf1e1; }

    /* role tag (dark-band default; cream override below) */
    #fireflut-view .cs-ff-role { display:inline-flex; align-items:center; gap:9px; margin-bottom:26px;
      font-family:var(--cs-sans); font-size:12px; font-weight:400; letter-spacing:0.08em; text-transform:uppercase;
      color:rgba(255,249,233,0.7); border:1px solid rgba(255,249,233,0.18); border-radius:100px; padding:7px 15px; }
    #fireflut-view .cs-band.cream .cs-ff-role { color:#8f8a80; border-color:rgba(0,0,0,0.14); }
    #fireflut-view .cs-ff-role .dot { width:6px; height:6px; border-radius:50%; background:var(--cs-coral); }

    /* section-internal eyebrow (rail-escaped so it aligns with escaped visuals) */
    #fireflut-view .cs-ff-eye { font-family:var(--cs-sans); font-size:13px; font-weight:400; letter-spacing:0.06em;
      text-transform:uppercase; color:#8f8a80; margin:64px 0 20px; }

    /* rail-escaped image grids (placeholders swap for assets) */
    #fireflut-view .cs-ff-duo,
    #fireflut-view .cs-ff-full { display:grid; gap:14px; margin-top:64px; margin-left:calc(-1 * var(--cs-rail)); }
    #fireflut-view .cs-ff-duo  { grid-template-columns:3fr 2fr; }
    #fireflut-view .cs-ff-duo.rev { grid-template-columns:2fr 3fr; }
    #fireflut-view .cs-ff-full { grid-template-columns:1fr; }
    #fireflut-view .cs-ff-duo .cs-ph  { height:440px; border-radius:8px; }
    /* chat-details row: a single horizontal-scroll row (not a grid that wraps
       to 2-then-1 columns) — cards keep the same 720:626 aspect-ratio as the
       section 03 trio (gauge/location/recs) so proportions match across the
       page, with height always derived from width instead of a fixed px.
       Lives outside .cs-wrap as a full-bleed sibling (same as .cs-ff-trio),
       with matching viewport-edge padding — nesting it inside the
       rail-constrained wrap would cap it at the wrap's 1320px max-width on
       large screens, where .cs-ff-trio keeps extending to the viewport edge.
       Auto-scrolling marquee — same translateX(-50%) loop as the clients
       logo marquee (.logo-marquee-wrap/-inner): the card set is duplicated
       once in the markup so the seamless wrap point is exactly halfway,
       the wrap clips it, and the track just keeps sliding. Hover pauses it
       so a card can actually be looked at. */
    #fireflut-view .cs-ff-quad-wrap { position:relative; overflow:hidden; margin-top:0;
      padding:0; }
    #fireflut-view .cs-ff-quad-wrap::before,
    #fireflut-view .cs-ff-quad-wrap::after { content:''; position:absolute; top:0; bottom:0;
      width:min(120px, 8vw); z-index:2; pointer-events:none; }
    #fireflut-view .cs-ff-quad-wrap::before { left:0;  background:linear-gradient(to right, var(--cs-black), transparent); }
    #fireflut-view .cs-ff-quad-wrap::after  { right:0; background:linear-gradient(to left,  var(--cs-black), transparent); }
    #fireflut-view .cs-ff-quad { display:flex; gap:14px; width:max-content;
      padding-left:var(--cs-pad);
      animation:cs-ff-quad-scroll 42s linear infinite; }
    /* match the trio's card width (same aspect-ratio → same height) so sections
       03 and 04 read as twins */
    #fireflut-view .cs-ff-quad .cs-ph { flex:0 0 calc((100vw - 2 * var(--cs-pad) - 28px) / 3);
      width:calc((100vw - 2 * var(--cs-pad) - 28px) / 3);
      aspect-ratio:720/626; height:auto; border-radius:8px; }
    @keyframes cs-ff-quad-scroll { to { transform:translateX(calc(-50% - 7px)); } }
    /* full-bleed trio: lives outside .cs-wrap entirely now, so it gets the same
       viewport-edge padding as .cs-ff-shots (not the rail-escape margin trick).
       A real grid whose card height derives from column width via aspect-ratio
       — matching the 718:622 gauge/location footage and the 720:626 recs stage
       almost exactly — so content stays proportionate at any viewport size
       instead of the old fixed 500px height distorting at the extremes. */
    #fireflut-view .cs-ff-trio { display:grid; grid-template-columns:repeat(3,1fr); gap:14px;
      padding-left:var(--cs-pad); padding-right:var(--cs-pad); }
    #fireflut-view .cs-ff-trio .cs-ph { aspect-ratio:720/626; height:auto; border-radius:8px; }
    /* mobile landscape and narrower: 3 columns squeeze each card too thin.
       Wrap to 2 (third card drops to its own row) across the whole landscape-
       phone width range (~560–900px) — deterministic breakpoints rather than
       auto-fit/minmax, whose fit-or-drop math falls right on the edge at
       common device widths (e.g. 667px landscape came up 3px short of fitting
       2×300px columns and silently collapsed to one oversized card). Below
       true portrait-phone widths it drops to 1 column, same as before. */
    @media (max-width:900px){
      #fireflut-view .cs-ff-trio { grid-template-columns:repeat(2,1fr); }
    }
    @media (max-width:500px){
      #fireflut-view .cs-ff-trio { grid-template-columns:1fr; }
    }
    #fireflut-view .cs-ff-full .cs-ph { height:560px; border-radius:8px; }
    /* closing hero shot: the final-app montage, full viewport width (edge-to-
       edge minus the standard --cs-pad gutter) and full viewport tall — a
       cinematic beat before the outro, not just another grid card. */
    #fireflut-view .cs-ff-out {  display:flex; flex-direction:column;
      justify-content:center; gap:56px; padding:118px 0; box-sizing:border-box; }
    #fireflut-view .cs-ff-final { margin-top:64px; }
    #fireflut-view .cs-ff-final .cs-ph { height:100vh; border-radius:0;
      padding:0; overflow:hidden; border:0; background:#000; }
    /* the final-app montage rebuilt from native-res stills instead of the old
       960×720 Jitter render, so nothing is upscaled at any viewport. Same
       choreography and 5s loop as the recording (geometry and timings measured
       off its frames): hold on the app row → the collage rotates 90° while
       pulling back → the splash card slides down from the cell above and wipes
       over the home phone → slow push into the splash → hard cut back. The
       camera never pans; one rotate+scale on the stage plus the splash's own
       slide carries the whole piece. Authored at the
       video's 960×720 design size and cover-fitted to the card via container
       units so it composes identically at every viewport. */
    #fireflut-view .cs-ff-final .cs-ph { container-type:size; }
    #fireflut-view .ff-final-fit { position:absolute; left:50%; top:50%; width:960px; height:720px;
      transform:translate(-50%,-50%) scale(calc(max(100cqw / 960px, 100cqh / 720px))); }
    #fireflut-view .ff-final-cam { position:absolute; inset:0; will-change:transform;
      animation:ffFinalCam 5.03s linear infinite; }
    #fireflut-view .ffp { position:absolute; left:50%; top:50%; width:190px; border-radius:16px;
      transform:translate(-50%,-50%) translate(var(--x), var(--y)); display:block; }
    /* the home phone counter-spins against the group (own −180° on top of the
       camera's +90° = net −90°, top pointing left — matching the recording,
       where the centre phone visibly rotates opposite to the collage) */
    #fireflut-view .ffp-home { animation:ffFinalHome 5.03s ease-in-out infinite; }
    #fireflut-view .ff-final-splash { position:absolute; left:50%; top:50%; width:520px; height:322px;
      transform:translate(-50%,-50%) rotate(-90deg); border-radius:24px; overflow:hidden; background:#000;
      animation:ffFinalSplash 5.03s linear infinite; }
    #fireflut-view .ff-final-splash img { width:100%; height:100%; object-fit:cover; display:block; }
    /* per-keyframe timing so each phase eases on its own instead of the whole
       loop sharing one curve (which forced velocity to zero at every keyframe
       — the source of the jagged zoom). Base is linear; each segment sets the
       curve that carries it TO the next keyframe. */
    @keyframes ffFinalCam {
      0%   { transform:rotate(0deg) scale(1.45); animation-timing-function:linear; }                 /* hold */
      6%   { transform:rotate(0deg) scale(1.45); animation-timing-function:cubic-bezier(.5,0,.2,1); } /* rotate + pull back */
      30%  { transform:rotate(90deg) scale(1);   animation-timing-function:linear; }                 /* settle, wipe happens here */
      48%  { transform:rotate(90deg) scale(1);   animation-timing-function:cubic-bezier(.4,0,.25,1);} /* one smooth push in, begins as card lands */
      97%,100% { transform:rotate(90deg) scale(1.5); }
    }
    @keyframes ffFinalHome   {
      0%, 5%    { transform:translate(-50%,-50%) translate(var(--x), var(--y)) rotate(0deg); }
      28%, 100% { transform:translate(-50%,-50%) translate(var(--x), var(--y)) rotate(-180deg); }
    }
    /* splash wipe: the card slides down from the cell above (screen-down =
       local +y once its −90° pre-rotation meets the camera's +90°) and covers
       the home phone in place — a wipe, not a crossfade, per the recording.
       −560px keeps it fully outside the frame at every earlier camera state;
       home never fades, it just gets overdrawn (splash is opaque and larger
       than home's sideways footprint). */
    @keyframes ffFinalSplash {
      /* wider window (30%→48%, ~0.9s vs the old 0.5s) with an ease-out landing
         so the card decelerates into place instead of snapping. Settled before
         the camera begins its push at 46%. */
      0%, 30%   { transform:translate(-50%,-50%) rotate(-90deg) translateY(-560px);
                  animation-timing-function:cubic-bezier(.36,0,.2,1); }
      48%, 100% { transform:translate(-50%,-50%) rotate(-90deg) translateY(0); }
    }
    @media (prefers-reduced-motion: reduce){
      #fireflut-view .ff-final-cam { animation:none; transform:rotate(90deg) scale(1.43); }
      #fireflut-view .ffp-home { animation:none; opacity:0; }
      #fireflut-view .ff-final-splash { animation:none; opacity:1; }
      #fireflut-view .cs-ff-quad { animation:none; overflow-x:auto; -webkit-overflow-scrolling:touch; }
    }
    /* quote 1's section padding was asymmetric (36px top / 190px bottom,
       inherited from the shared .cs-quote rule) — even it out without
       touching the shared class, so Hatcha's quotes are unaffected. Same
       total height (226px), just split evenly instead of front-loaded. */
    #fireflut-view .cs-quote { padding:113px 0; }
    /* before/after table — shared .cs-cmp-cell's fixed 30px line-height plus
       16px padding runs tall; tighten just for this view (Hatcha's table is
       untouched) so the row height matches the rest of the case study. */
    #fireflut-view .cs-cmp-cell { padding:12px 16px; line-height:1.5; }

    /* cs-ff-cap is normally a rail-escaped caption inside .cs-wrap; the
       chat-details one now sits outside the wrap (full-bleed, next to
       .cs-ff-quad) so it needs the section's own edge padding instead. */
    #fireflut-view .cs-ff-cap { margin:18px 0 0; margin-left:calc(-1 * var(--cs-rail));
      font-size:14px; color:rgba(11,11,11,0.55); }
    #fireflut-view section > .cs-ff-cap { margin-left:0; padding-left:var(--cs-pad); }
    #fireflut-view .cs-band.dark .cs-ff-cap { color:rgba(255,249,233,0.5); }

    /* full-bleed before / after shots — each half the viewport minus its side
       padding, 16px gap, one full viewport tall. The shot is centred inside the
       card at a capped, reduced size (full shot visible, nothing cropped) with
       the card background framing it. Tune the max-height clamp to taste. */
    #fireflut-view .cs-ff-shots { display:flex; gap:16px; align-items:flex-start;
      padding-left:var(--cs-pad); padding-right:var(--cs-pad); }
    #fireflut-view .cs-ff-shot { flex:1 1 0; min-width:0; border-radius:8px; overflow:hidden; height:100vh;
      display:flex; align-items:center; justify-content:center; }
    #fireflut-view .cs-ff-shot.before  { background:#f4f4f4; }
    #fireflut-view .cs-ff-shot.after   { background:#010412; }
    #fireflut-view .cs-ff-shot.moodvis { background:#010412; }
    #fireflut-view .cs-ff-shot.moodcol { background:#181818; }
    /* variants mockup cycles through 4 brand themes like a looping gif — a hard
       cut (no dissolve): each frame swaps in instantly and the card background
       switches to that frame's brand colour at the same beat, so the colour
       reads full-width. The phone is an identical dead-centre box in every
       frame, so only the colour changes — the mockup never shifts. */
    #fireflut-view .cs-ff-shot.variants { background:#3456C8; }
    #fireflut-view .cs-ff-shot.variants .ff-vstack { position:relative; width:100%; height:100%; }
    #fireflut-view .ff-vframe { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
      width:auto; height:auto; max-width:100%; max-height:min(94vh, 940px); object-fit:contain;
      opacity:0; }
    #fireflut-view .ff-vframe.is-active { opacity:1; }
    /* text cell dropped beside the variants mockup in the same grid row —
       reuses the shot's 100vh/centred box so it lines up with the phone. */
    #fireflut-view .cs-ff-shot.copy { background:transparent; justify-content:flex-start; }
    #fireflut-view .ff-vcopy { max-width:400px; padding:0 0 0 64px; text-align:left; }
    #fireflut-view .ff-vcopy h3 { font-family:var(--cs-serif); font-weight:400; font-size:32px;
      letter-spacing:-0.02em; line-height:1.13; color:#fff9ea; margin:0 0 16px; }
    #fireflut-view .ff-vcopy h3 i { font-style:italic; }
    #fireflut-view .ff-vcopy p { font-family:var(--cs-sans); font-size:16px; line-height:1.65;
      color:rgba(255,249,233,0.6); margin:0; }
    /* moodboard grid: two half-width columns with 16px gaps — the variants
       mockup drops onto the second row at the same size as the assets above */
    #fireflut-view .cs-ff-shots.grid { display:grid; grid-template-columns:1fr 1fr; align-items:stretch;
      padding-bottom:120px; }
    #fireflut-view .cs-ff-shot img { display:block; width:auto; height:auto;
      max-width:100%; max-height:min(94vh, 940px); object-fit:contain; }

    /* moodboard colours — rebuilt as full-bleed colour blocks that span the whole
       card (per the Figma layout) instead of the contained image, so the colours
       reach every edge. Proportions mirror the 720×1024 source. */
    #fireflut-view .cs-ff-colors { width:100%; height:100%; display:flex; flex-direction:column;
      gap:8px; padding:8px; background:#181818; font-family:var(--cs-sans); }
    #fireflut-view .cs-ff-colors .cc-cell { position:relative; padding:16px 18px; overflow:hidden; }
    #fireflut-view .cs-ff-colors .cc-name { display:block; font-size:12.5px; line-height:1.2; letter-spacing:0.01em; color:#fff; }
    #fireflut-view .cs-ff-colors .cc-hex { display:block; margin-top:5px; font-size:11px; letter-spacing:0.03em; color:rgba(255,255,255,0.7); }
    #fireflut-view .cs-ff-colors .cc-cell.light .cc-name { color:#0b0b0b; }
    #fireflut-view .cs-ff-colors .cc-cell.light .cc-hex { color:rgba(11,11,11,0.62); }
    #fireflut-view .cc-bg { flex:1 1 50%; background:#020202; }
    #fireflut-view .cc-lower { flex:1 1 50%; display:flex; gap:8px; min-height:0; }
    #fireflut-view .cc-glow { flex:1 1 50%; background:radial-gradient(130% 100% at 26% 18%, #3456C8 0%, #131a3f 42%, #000000 80%); }
    #fireflut-view .cc-right { flex:1 1 50%; display:flex; flex-direction:column; gap:8px; min-width:0; }
    #fireflut-view .cc-primary { flex:1 1 50%; background:#3456C8; }
    #fireflut-view .cc-swatches { flex:1 1 50%; display:flex; gap:8px; min-height:0; }
    #fireflut-view .cc-swatches .cc-cell { flex:1 1 0; min-width:0; padding:14px 12px; }
    #fireflut-view .cc-swatches .cc-name { font-size:11px; }
    #fireflut-view .cc-swatches .cc-hex { font-size:10px; }
    #fireflut-view .cc-text { background:#FFFFFF; }
    #fireflut-view .cc-secondary { background:#4B9BFF; }
    #fireflut-view .cc-accent { background:#3700A4; }

    /* — Motion cards: looping screen recordings, cropped to fill the card with no
         letterbox (/assets/fireflut/gauge.*, location.*). — */
    #fireflut-view .cs-ff-gauge,
    #fireflut-view .cs-ff-location,
    #fireflut-view .cs-ff-voice { padding:0; overflow:hidden; border:0; background:#000; }
    /* voice card background = Figma node 507:2527 — a navy glow from the
       top-left fading to transparent over black (exact angle/stops from the
       design). Same in light and dark mode (it's a functional media card,
       already excluded from the night placeholder-stripe rule). The floated
       recording sits centred on top, so this glow frames it on all sides. */
    #fireflut-view .cs-ff-voice {
      background:
        linear-gradient(118.879deg, rgb(10,21,55) 4%, rgba(0,0,0,0) 52%),
        #000; }
    #fireflut-view .gg-video { position:absolute; inset:0; width:100%; height:100%;
      object-fit:cover; display:block; }
    /* the location recording is bottom-anchored (its phone content runs to the
       very bottom edge, all the empty headroom is at the top) — crop from the
       top so cover-fit never eats into the phone or its copy */
    #fireflut-view .cs-ff-location .gg-video { object-position:center bottom; }
    /* voice recording: the chat card floats in the footage with its focus
       glow rendered on all four sides, so show the full 472/320 stage —
       no cropping — centred and sized to ~80% of the cell height so it sits
       in proportion with the phone stage beside it. The footage was captured
       on a solid black background; mix-blend-mode:screen drops that black
       (black adds nothing under screen) so only the lit card + glow rings
       show, melting into the card's navy gradient instead of sitting in a
       visible black rectangle. */
    #fireflut-view .cs-ff-voice { container-type:size; }
    #fireflut-view .cs-ff-voice .gg-video { inset:auto; left:50%; top:50%;
      width:min(86cqw, calc(80cqh * 472 / 320)); height:auto; aspect-ratio:472/320;
      transform:translate(-50%,-50%); object-fit:cover; mix-blend-mode:screen; }
    /* gauge card: the card carries the recording's own bg gradient (sampled off
       the footage: blue top-left → black bottom-right) so the backdrop covers
       any container size, while the video sits centred at 88% of the card,
       sized to its true 718×622 aspect via container units so the edge-feather
       mask always hugs the video's real edges and melts them into the card bg */
    #fireflut-view .cs-ff-gauge { container-type:size; background:
      linear-gradient(124.17deg, #213078 0%, #10193f 35%, #04060f 70%, #000 100%); }
    #fireflut-view .cs-ff-gauge .gg-video { inset:auto; left:50%; top:50%;
      width:min(88cqw, calc(88cqh * 718 / 622)); height:auto; aspect-ratio:718/622;
      transform:translate(-50%,-50%); object-fit:cover;
      -webkit-mask:radial-gradient(75% 75% at 50% 50%, #000 55%, rgba(0,0,0,0) 95%);
      mask:radial-gradient(75% 75% at 50% 50%, #000 55%, rgba(0,0,0,0) 95%); }

    /* — Recommendations card (node 475:3504): a Gemini suggestions list whose
         rows cascade in one by one, on loop, while on-screen. Built as a fixed
         720×626 design stage (matches the Figma frame 1:1) scaled to fit the
         trio cell, same technique as the gauge card, so type/icon proportions
         never drift from the design. */
    #fireflut-view .cs-ff-recs { padding:0; overflow:hidden; border:0; text-align:left;
      background:
        linear-gradient(115.81deg, rgba(0,0,0,0) 10.3%, #3456C8 136.58%),
        #000; }
    #fireflut-view .rc-inner { position:absolute; inset:0; }
    #fireflut-view .rc-stage { position:absolute; left:50%; top:50%; width:720px; height:626px;
      transform:translate(-50%,-50%) scale(var(--rc-scale,0.62)); transform-origin:center center;
      font-family:'Figtree', var(--cs-sans); color:#fff; }
    #fireflut-view .rc-head { position:absolute; left:148.35px; top:132.54px; width:423.3px; min-height:27px;
      display:flex; align-items:center; justify-content:space-between; }
    #fireflut-view .rc-head-title { font-size:22px; font-weight:400; line-height:30px; }
    #fireflut-view .rc-gemini { width:66.59px; height:24.46px; background:#fff;
      -webkit-mask:url("../images/rec-gemini-mark.svg") center/contain no-repeat;
      mask:url("../images/rec-gemini-mark.svg") center/contain no-repeat; }
    #fireflut-view .rc-list { position:absolute; left:148.35px; top:186.01px; width:423.3px; }
    #fireflut-view .rc-item { display:flex; align-items:flex-start; gap:8.91px;
      padding:13.37px 0; border-bottom:1.114px solid rgba(255,255,255,0.1); }
    #fireflut-view .rc-ico { flex:none; width:26.735px; height:26.735px; }
    #fireflut-view .rc-content { flex:1 1 0; min-width:0; display:flex; flex-direction:column; gap:4.46px; }
    #fireflut-view .rc-title { font-size:22px; font-weight:400; line-height:30px; color:#fff; }
    #fireflut-view .rc-desc { font-size:18px; font-weight:400; line-height:26px; color:rgba(255,255,255,0.66); }
    #fireflut-view .rc-chev { flex:none; width:26.735px; height:26.735px; }
    /* items loop one by one while the card is on-screen — each row fades up,
       holds, fades out, then cycles again; staggered per row via animation-delay
       so the cascade reads continuously rather than as a single one-shot reveal */
    #fireflut-view .rc-item { opacity:0; transform:translateY(16px);
      animation:ffRcItem 5s cubic-bezier(0.16,1,0.3,1) infinite;
      animation-play-state:paused; }
    #fireflut-view .rc-item:nth-child(1) { animation-delay:0s; }
    #fireflut-view .rc-item:nth-child(2) { animation-delay:0.4s; }
    #fireflut-view .rc-item:nth-child(3) { animation-delay:0.8s; }
    #fireflut-view .cs-ff-recs.rc-play .rc-item { animation-play-state:running; }
    @keyframes ffRcItem {
      0%   { opacity:0; transform:translateY(16px); }
      10%  { opacity:1; transform:translateY(0); }
      75%  { opacity:1; transform:translateY(0); }
      88%  { opacity:0; transform:translateY(-10px); }
      100% { opacity:0; transform:translateY(16px); }
    }
    @media (prefers-reduced-motion: reduce) {
      #fireflut-view .rc-item { opacity:1; transform:none; animation:none; }
    }

    /* — Pre-prompts card (node 507:2529, "prompts in chat"): a real screen
         recording of the AI start screen's five suggested prompts cascading
         in — same cropped-recording technique as the gauge/location cards
         (/assets/fireflut/pre-prompts.*), looped via the shared [data-gg-video]
         play/pause-on-scroll handler. */
    /* card background: a blue glow behind the phone. A centred top radial (not
       the top-left linear the sibling cards use) so it spreads evenly to both
       sides and frames the recording symmetrically instead of pooling in one
       corner. Fades to black before the card edges. */
    #fireflut-view .cs-ff-preprompts { padding:0; overflow:hidden; border:0;
      background:
        radial-gradient(115% 85% at 50% 6%, #1f2c58 0%, rgba(31,44,88,0.27) 32%, rgba(0,0,0,0) 66%),
        #000; }
    /* the recording is portrait (584:622) inside a landscape card, so contain-fit
       is height-bound with big empty side margins — scale it up a little around
       its own centre; the card's overflow:hidden crops the small top/bottom
       overshoot, which is a much smaller loss than the side letterbox it closes */
    #fireflut-view .cs-ff-preprompts .gg-video { object-fit:contain; transform:scale(1.12);
      mix-blend-mode:screen; }

    /* — Loading reply card (node 507:2551 "loading reply" — a 585×626
         canvas — wrapping the 507:2552 "Processing" phone at its exact
         Figma offset: left 87.92, top 174.13, width 409.162, out of a
         903.73-tall device). The canvas is CONTAIN-fit into the quad cell
         (not scaled to the cell's width), so the phone's size relative to
         the surrounding black is exactly what Figma drew — a small phone
         with generous headroom above it, cropped at the canvas's bottom
         edge (only the top ~half of the device shows, no bottom bezel).
         All child offsets below are the raw, unscaled Figma px. */
    /* card background = Figma node 507:2551 — a navy glow from the top-left
       fading to transparent over black (same treatment as the voice card).
       The phone stage floats centred on top; this frames it with the glow. */
    #fireflut-view .cs-ff-loading { padding:0; overflow:hidden; border:0; position:relative;
      background:
        linear-gradient(120.55deg, #101d46 -17.43%, rgba(0,0,0,0) 92.06%),
        #000; }
    #fireflut-view .lr-stage { position:absolute; left:50%; top:50%; width:585px; height:626px;
      transform:translate(-50%,-50%) scale(var(--lr-scale,0.4)); transform-origin:center center;
      overflow:hidden; font-family:'Figtree', var(--cs-sans); letter-spacing:normal; text-align:left; }
    #fireflut-view .lr-phone { position:absolute; left:87.92px; top:174.13px; width:409.162px; height:700px;
      border:10.029px solid #2d2d2d; border-bottom:0; border-radius:70.202px 70.202px 0 0;
      overflow:hidden; background:#020202; }
    #fireflut-view .lr-glow { position:absolute; inset:0;
      background:radial-gradient(120% 60% at 32% 14%, #223360 0%, #1b294e 22%, #131f3f 40%, #0a1128 60%, #04060c 82%, #020202 100%); }
    #fireflut-view .lr-status { position:absolute; left:0; right:0; top:0; height:65px;
      display:flex; align-items:flex-end; justify-content:space-between; padding:0 30px 12.5px;
      font-size:17.5px; color:#fff; opacity:0.35; }
    #fireflut-view .lr-status .cutout { position:absolute; left:50%; top:22.5px; width:30px; height:30px;
      transform:translateX(-50%); border-radius:50%; background:#000; }
    #fireflut-view .lr-icons { display:flex; gap:8px; align-items:center; }
    #fireflut-view .lr-icons span { display:block; width:17px; height:12px; border:1px solid #fff; border-radius:2px; }
    #fireflut-view .lr-back { position:absolute; top:66px; left:16px; font-size:28px; line-height:1; color:#fff; opacity:0.9; }
    #fireflut-view .lr-bubble-row { position:absolute; left:0; top:128.32px; width:409.162px; height:103.61px;
      display:flex; align-items:center; justify-content:flex-end; padding:15.89px; box-sizing:border-box; }
    #fireflut-view .lr-bubble { max-width:377.38px; background:rgba(52,86,200,0.25); border:0.993px solid #3456c8;
      border-radius:23.835px 23.835px 3.972px 23.835px; padding:11.917px 19.862px;
      font-size:15.78px; line-height:23.671px; color:#fff; text-align:left; }
    #fireflut-view .lr-reply { position:absolute; left:0; top:231.93px; width:409.162px;
      display:flex; flex-direction:column; gap:7.945px;
      padding:7.945px 15.89px 7.945px 23.835px; box-sizing:border-box; }
    #fireflut-view .lr-spark { width:23.835px; height:23.835px; opacity:0.6; }
    #fireflut-view .lr-spark svg { width:100%; height:100%; display:block; }
    #fireflut-view .lr-bars { display:flex; flex-direction:column; gap:11.917px; width:100%; }
    #fireflut-view .lr-bar { position:relative; height:15.89px; border-radius:23.835px; overflow:hidden;
      background:linear-gradient(90deg, #38049f 0%, #4b9bff 98%); }
    #fireflut-view .lr-bar:nth-child(1) { width:92%; opacity:0.75; }
    #fireflut-view .lr-bar:nth-child(2) { width:100%; opacity:0.5; }
    #fireflut-view .lr-bar:nth-child(3) { width:92%; opacity:0.25; }
    /* shimmer sweep — a soft highlight band travels across each bar,
       staggered per row, paused off-screen (battery, matches recs pattern) */
    #fireflut-view .lr-bar::after { content:''; position:absolute; inset:0;
      background:linear-gradient(100deg, transparent 30%, rgba(255,255,255,0.55) 50%, transparent 70%);
      transform:translateX(-120%); animation:ffLrShimmer 1.8s ease-in-out infinite; animation-play-state:paused; }
    #fireflut-view .lr-bar:nth-child(1)::after { animation-delay:0s; }
    #fireflut-view .lr-bar:nth-child(2)::after { animation-delay:0.2s; }
    #fireflut-view .lr-bar:nth-child(3)::after { animation-delay:0.4s; }
    #fireflut-view .cs-ff-loading.load-play .lr-bar::after { animation-play-state:running; }
    @keyframes ffLrShimmer { 0% { transform:translateX(-120%); } 100% { transform:translateX(120%); } }
    /* sparkle "thinking" pulse */
    #fireflut-view .lr-spark { animation:ffLrSpark 1.6s ease-in-out infinite; animation-play-state:paused; }
    #fireflut-view .cs-ff-loading.load-play .lr-spark { animation-play-state:running; }
    @keyframes ffLrSpark { 0%,100% { opacity:0.5; transform:scale(0.9); } 50% { opacity:1; transform:scale(1.08); } }
    @media (prefers-reduced-motion: reduce) {
      #fireflut-view .lr-bar::after { animation:none; opacity:0; }
      #fireflut-view .lr-spark { animation:none; opacity:0.85; transform:none; }
    }

    /* Yes/No quick-reply card: static composite (/assets/fireflut/yes-no-prompt.webp).
       The source carries its own dark→navy gradient background, so it fills the
       card edge-to-edge (cover) — the baked-in bg reaches the card edges with no
       black letterbox seam. It's portrait in a landscape card, so cover crops a
       little empty gradient off top/bottom; the bubble + buttons stay centred. */
    #fireflut-view .cs-ff-yesno { padding:0; overflow:hidden; border:0; background:#000; }
    #fireflut-view .cs-ff-yesno img { position:absolute; inset:0; width:100%; height:100%;
      object-fit:cover; object-position:center; display:block; }
    /* section 4 recordings + yes/no still carry a baked-in blue background that
       reads too bright next to the toned-down card gradients — knock back its
       saturation/brightness so the whole marquee sits at one calmer blue. */
    #fireflut-view .cs-ff-voice .gg-video,
    #fireflut-view .cs-ff-preprompts .gg-video,
    #fireflut-view .cs-ff-yesno img { filter:saturate(0.8) brightness(0.86); }

    /* three principle cards (dark band). Dividers are borders drawn on each
       card, not a 1px grid-gap with background peeking through — at odd
       container widths repeat(3,1fr) rounds column widths to whole pixels
       and can round one gap down to 0, making that seam vanish. A border
       lives on the element itself so it's always there regardless. */
    #fireflut-view .cs-ff-prin { display:grid; grid-template-columns:repeat(3,1fr); margin-top:52px;
      margin-left:calc(-1 * var(--cs-rail));
      border:1px solid rgba(255,249,233,0.12); border-radius:14px; overflow:hidden; }
    #fireflut-view .cs-ff-prin-card { background:#0b0b0b; padding:30px 28px 34px; box-sizing:border-box;
      border-right:1px solid rgba(255,249,233,0.12); }
    #fireflut-view .cs-ff-prin-card:last-child { border-right:0; }
    #fireflut-view .cs-ff-prin-ico { width:26px; height:26px; margin-bottom:20px; }
    #fireflut-view .cs-ff-prin-ico svg { width:24px; height:24px; stroke:var(--cs-coral); fill:none;
      stroke-width:1.4; stroke-linecap:round; stroke-linejoin:round; }
    #fireflut-view .cs-ff-prin-h { font-family:var(--cs-serif); font-weight:400; font-size:24px;
      letter-spacing:0.02em; color:#fff9ea; margin-bottom:10px; }
    #fireflut-view .cs-ff-prin-p { font-size:14.5px; line-height:1.55; color:rgba(255,249,233,0.6); }

    /* four-brand swatch row (dark band) */
    #fireflut-view .cs-ff-swatch { display:grid; grid-template-columns:repeat(4,1fr); gap:10px;
      margin-top:40px; margin-left:calc(-1 * var(--cs-rail)); }
    #fireflut-view .cs-ff-swatch .sw { height:64px; border-radius:8px; }
    #fireflut-view .cs-ff-swatch .lbl { margin-top:9px; font-size:11px; letter-spacing:0.08em;
      text-transform:uppercase; color:rgba(255,249,233,0.5); }

    /* micro-interaction list (dark-band default; cream override below) */
    #fireflut-view .cs-ff-micro { margin-top:20px; border:1px solid rgba(255,249,233,0.14);
      border-radius:14px; overflow:hidden; }
    #fireflut-view .cs-band.cream .cs-ff-micro { border-color:rgba(0,0,0,0.12); }
    #fireflut-view .cs-ff-mrow { display:grid; grid-template-columns:200px minmax(0,1fr); }
    #fireflut-view .cs-ff-mrow + .cs-ff-mrow { border-top:1px solid rgba(255,249,233,0.14); }
    #fireflut-view .cs-band.cream .cs-ff-mrow + .cs-ff-mrow { border-top-color:rgba(0,0,0,0.12); }
    #fireflut-view .cs-ff-mtag { display:flex; align-items:center; gap:10px; padding:22px 24px;
      border-right:1px solid rgba(255,249,233,0.14);
      font-family:var(--cs-serif); font-weight:400; font-size:19px; letter-spacing:0.02em; color:#fff9ea; }
    #fireflut-view .cs-band.cream .cs-ff-mtag { color:var(--cs-ink); border-right-color:rgba(0,0,0,0.12); }
    #fireflut-view .cs-ff-mtag .dot { width:7px; height:7px; border-radius:50%; background:var(--cs-coral); flex-shrink:0; }
    #fireflut-view .cs-ff-mdesc { padding:22px 26px; font-size:15px; line-height:1.6; color:rgba(255,249,233,0.62); }
    #fireflut-view .cs-band.cream .cs-ff-mdesc { color:rgba(11,11,11,0.7); }

    /* outcome stats — Fireflut has three */
    #fireflut-view .cs-stats { grid-template-columns:repeat(3,1fr); }

    /* asset reveal (clip-mask unveil) — mirror of the Hatcha rule, scoped */
    #fireflut-view .cs-clip {
      clip-path: inset(0px 0px 100% 0px round var(--clip-round, 0px));
      transform: translateY(-44px);
      transition: clip-path 1.15s cubic-bezier(0.16, 1, 0.3, 1),
                  transform 1.15s cubic-bezier(0.16, 1, 0.3, 1);
      transition-delay: var(--reveal-delay, 0s); }
    #fireflut-view .cs-clip.is-visible { clip-path: inset(0px 0px 0% 0px round var(--clip-round, 0px)); transform: none; }
    @media (prefers-reduced-motion: reduce) {
      #fireflut-view .cs-clip { clip-path: none; transform: none; transition: none; } }

    /* ── Section cover-slide (pinned choreography) ──────────────────────────
       The case study becomes a scroll-driven stage: all sections stack inside a
       sticky, viewport-sized stage, and a tall scroller drives everything by
       transform. Per section you scroll through its own content (read time),
       then it PINS (holds its last frame) while the next section sweeps in
       full-height from the right over it. When the last section is read the
       stage releases so the shared footer shows. Built by the JS enhancer below;
       skipped under reduced motion (DOM left untouched → normal scrolling). */
    #fireflut-view .cs-slide-scroller { position: relative; }
    #fireflut-view .cs-slide-stage { position: sticky; top: 0; height: 100vh; overflow: hidden;
                                     isolation: isolate; }   /* contain section z-index below the nav */
    #fireflut-view .cs-slide-sec { position: absolute; inset: 0; overflow: hidden; }
    #fireflut-view .cs-slide-sec.cv { box-shadow: -26px 0 60px rgba(0,0,0,0.16); }
    #fireflut-view .cs-slide-inner { }
    /* the sections were display-flow bands; as absolute stage layers they still
       need to fill the frame so their band colour covers the full height */
    #fireflut-view .cs-slide-sec.cs-band { min-height: 0; }
    /* hero reads as two full-screen beats: the title/meta chunk, then the device
       mockup. Read-scroll travel is inner.scrollHeight − 100vh, which ignores the
       SECTION's own padding — so with .cs-hero's 138px nav clearance on the section
       the mockup could only ever rise to 138px and stayed clipped. Move that
       clearance onto the text chunk instead: the text lands exactly where it did,
       and the mockup's rest state is a full 100vh × 100vw frame. */
    #fireflut-view .cs-slide-sec.cs-hero { padding-top: 0; }
    #fireflut-view .cs-slide-sec.cs-hero .cs-hero-grid { padding-top: 138px; }
    /* a .cs-panel is a single 100vh chunk — centre its content vertically in the
       frame so it reads as a full-screen slide with no internal scroll. Each
       chunk keeps its own horizontal layout (constrained wrap / full-bleed shots). */
    #fireflut-view .cs-slide-sec.cs-panel .cs-slide-inner {
      min-height: 100vh; box-sizing: border-box;
      /* top clears the fixed nav (80px) + 32px; sides/bottom keep the --cs-pad gutter */
      padding-top: var(--cs-navpad); padding-bottom: var(--cs-pad);
      display: flex; flex-direction: column; justify-content: center;
    }
    /* the old scroll layout gave the shot grids a 120px bottom breather; inside a
       fixed 100vh panel that just overflows, so drop it. */
    #fireflut-view .cs-slide-sec.cs-panel .cs-ff-shots.grid { padding-bottom: 0; }
    /* full-height media was sized to the whole viewport; inset it by the same
       --cs-pad top/bottom so every panel has a uniform gutter on all four sides. */
    #fireflut-view .cs-slide-sec.cs-panel .cs-ff-shot { height: calc(100vh - var(--cs-navpad) - var(--cs-pad)); }
    #fireflut-view .cs-slide-sec.cs-panel .cs-ff-shot img,
    #fireflut-view .cs-slide-sec.cs-panel .ff-vframe { max-height: 100%; }
    /* Outcome split: .cs-ff-out and .cs-ff-final each carry their own 100vh
       sizing for the old scroll layout; inside a .cs-panel the panel already
       provides the 100vh frame + centring + gutter, so strip the built-in
       min-height/padding/margin to avoid doubling up, and inset the montage. */
    #fireflut-view .cs-slide-sec.cs-panel .cs-ff-out { min-height: 0; padding: 0; }
    #fireflut-view .cs-slide-sec.cs-panel .cs-ff-final { margin-top: 0; }
    #fireflut-view .cs-slide-sec.cs-panel .cs-ff-final .cs-ph { height: calc(100vh - var(--cs-navpad) - var(--cs-pad)); }
    /* the final montage is a cinematic full-bleed beat — drop the panel gutter
       entirely so it covers the whole screen, edge to edge. */
    #fireflut-view .cs-slide-sec.cs-panel.cs-final .cs-slide-inner { padding: 0; }
    #fireflut-view .cs-slide-sec.cs-panel.cs-final .cs-ff-final .cs-ph { height: 100vh; }
    /* home-screen mockup (after the outcome) — a photographic product shot framed
       exactly like every other asset panel (navpad top, --cs-pad bottom + L/R via
       .cs-ff-shots). Unlike the contained UI shots, this photo fills the framed box
       edge to edge (cover) so the fabric reaches every corner of the card. */
    #fireflut-view .cs-ff-shot.mockup { background:#080808; }
    #fireflut-view .cs-ff-shot.mockup img { width:100%; height:100%;
      max-width:none; max-height:none; object-fit:cover;
      /* ambient Ken-Burns breathe: a slow centred scale loop (never below 1, so
         the cover fill never exposes an edge). Only transform animates; the deck
         drives the section/inner transforms, so the img's own transform is free. */
      transform-origin:center; will-change:transform;
      animation:cs-ff-mockup-zoom 20s ease-in-out infinite; }
    @keyframes cs-ff-mockup-zoom {
      0%   { transform:scale(1); }
      50%  { transform:scale(1.05); }
      100% { transform:scale(1); }
    }
    @media (prefers-reduced-motion: reduce) {
      #fireflut-view .cs-ff-shot.mockup img { animation:none; }
    }
    /* the quote carries a 113px band padding for the old scroll layout; inside a
       centred panel that just pushes it below the vertical centre — drop it so
       the panel's own centring lands the quote dead-centre. */
    #fireflut-view .cs-slide-sec.cs-panel.cs-quote { padding-top: 0; padding-bottom: 0; }
    /* closing + read-next carry large band paddings for the old scroll layout;
       inside a centred panel they shove the content off-centre — drop them so
       the panel's own centring lands the content in the middle. */
    #fireflut-view .cs-slide-sec.cs-panel.cs-closing { padding-top: 0; padding-bottom: 0; }
    /* two-up tiles (moodboard / colour scheme) get a 16px label above each; the
       tile flexes to fill the height left under its label so the panel still
       lands at exactly 100vh. */
    #fireflut-view .cs-ff-title { margin:0 0 16px; font-family:var(--cs-sans);
      font-size:16px; font-weight:500; letter-spacing:.08em; text-transform:uppercase;
      color:rgba(255,249,233,.55); }
    body.night #fireflut-view .cs-ff-title { color:rgba(11,11,11,.55); }
    #fireflut-view .cs-slide-sec.cs-panel .cs-ff-titled { display:flex; flex-direction:column;
      min-width:0; height:calc(100vh - var(--cs-navpad) - var(--cs-pad)); }
    #fireflut-view .cs-slide-sec.cs-panel .cs-ff-titled .cs-ff-shot { height:auto; flex:1 1 0; min-height:0; }

    /* ── Hatcha · section cover-slide (Phase 1: scroll interaction only) ──────
       Same pinned stage as Fireflut, but Hatcha keeps its classic band layouts:
       sections are NOT .cs-panel, so each READ-SCROLLS through its own natural
       height (content taller than 100vh translates up during read time) before
       it pins and the next sweeps in from the right over it. Editorial-ledger /
       100vh-panel reframing is Phase 2. Built by the JS enhancer below; skipped
       under reduced motion (DOM untouched → normal scrolling). */
    #hatcha-view .cs-slide-scroller { position: relative; }
    #hatcha-view .cs-slide-stage { position: sticky; top: 0; height: 100vh; overflow: hidden;
                                   isolation: isolate; }   /* contain section z-index below the nav */
    #hatcha-view .cs-slide-sec { position: absolute; inset: 0; overflow: hidden; }
    #hatcha-view .cs-slide-sec.cv { box-shadow: -26px 0 60px rgba(0,0,0,0.16); }
    #hatcha-view .cs-slide-inner { }
    #hatcha-view .cs-slide-sec.cs-band { min-height: 0; }
    /* hero reads as two full-screen beats: the title/meta chunk, then the device
       mockup. Read-scroll travel is inner.scrollHeight − 100vh, which ignores the
       SECTION's own padding — so with .cs-hero's 138px nav clearance on the section
       the mockup could only ever rise to 138px and stayed clipped under a band of
       cream. Move that clearance onto the text chunk instead: the text lands exactly
       where it did, and the mockup's rest state is a full 100vh × 100vw frame.
       (Same fix as #fireflut-view / #existence-view / #jumpable-view.) */
    #hatcha-view .cs-slide-sec.cs-hero { padding-top: 0; }
    #hatcha-view .cs-slide-sec.cs-hero .cs-hero-grid { padding-top: 138px; }

    /* ══ Phase 2 · Hatcha 100vh cover-slide PANELS (editorial-ledger model) ══
       Mirrors Fireflut §10–11, scoped to #hatcha-view. A .cs-panel is one 100vh
       chunk: content centred, top gutter clears the fixed 80px nav (--cs-navpad).
       Sections taller than one screen are SPLIT into sibling panels. */
    #hatcha-view .cs-slide-sec.cs-panel .cs-slide-inner {
      min-height: 100vh; box-sizing: border-box;
      padding-top: var(--cs-navpad); padding-bottom: var(--cs-pad);
      display: flex; flex-direction: column; justify-content: center; }

    /* neutralise OLD band vertical paddings inside a panel (they double-count the
       100vh frame + centring and shove content off-centre). §10 "#1 regression" */
    #hatcha-view .cs-slide-sec.cs-panel.cs-quote,
    #hatcha-view .cs-slide-sec.cs-panel.cs-showcase,
    #hatcha-view .cs-slide-sec.cs-panel.cs-closing { padding-top: 0; padding-bottom: 0; }
    #hatcha-view .cs-slide-sec.cs-panel .cs-wrap { padding-top: 0; padding-bottom: 0; }

    /* full-bleed cinematic panel (splash animation, phone bleed): no gutter */
    #hatcha-view .cs-slide-sec.cs-panel.cs-final .cs-slide-inner { padding: 0; min-height: 100vh; width: 100%; }
    /* the logo-band flex-centres its inner, which would otherwise shrink to the
       stage's fixed 1440px width — force full width so fitAnimStage measures the
       real viewport and the splash fills edge-to-edge. */
    #hatcha-view .cs-slide-sec.cs-panel.cs-final.cs-logoband .cs-slide-inner { flex: 1 0 100%; }
    /* the cinematic rigs carry cs-final on the section itself (inset:0 already
       forces 100vh); reset their old fixed height / aspect so nothing conflicts */
    #hatcha-view .cs-slide-sec.cs-panel.cs-final.cs-logoband,
    #hatcha-view .cs-slide-sec.cs-panel.cs-final.cs-phone-bleed { height: 100vh; margin: 0; aspect-ratio: auto; }

    /* full-height media inside a padded panel keeps the same top budget so it
       never slides back under the nav (§10) */
    #hatcha-view .cs-slide-sec.cs-panel .cs-panel-media {
      height: calc(100vh - var(--cs-navpad) - var(--cs-pad)); }

    /* ── Editorial ledger (§11) — narrow serif-number rail + content column.
       Fonts stay design-system; palette maps onto scoped --lg-* so night flips. */
    #hatcha-view .cs-prob, #hatcha-view .cs-home, #hatcha-view .cs-out, #hatcha-view .cs-fw, #hatcha-view .cs-show {
      --lg-num:#c9bea9; --lg-ink:#17150f;
      --lg-mut:#8a8271; --lg-faint:#a79c89; --lg-meta:#8a7f6c;
      --lg-line:rgba(23,21,15,.12); --lg-line2:rgba(23,21,15,.2);
      --lg-accent:var(--cs-coral); }
    #hatcha-view .cs-dir, #hatcha-view .cs-chat, #hatcha-view .cs-mind,
    #hatcha-view .cs-theme-p, #hatcha-view .cs-theme-b {
      --lg-num:#3c3c3c; --lg-ink:#fff9ea;
      --lg-mut:rgba(255,249,233,.62); --lg-faint:rgba(255,249,233,.45); --lg-meta:rgba(255,249,233,.5);
      --lg-line:rgba(255,255,255,.14); --lg-line2:rgba(255,255,255,.22);
      --lg-accent:var(--cs-coral); }
    body.night #hatcha-view .cs-prob, body.night #hatcha-view .cs-home, body.night #hatcha-view .cs-out, body.night #hatcha-view .cs-fw, body.night #hatcha-view .cs-show {
      --lg-num:#3a3a44; --lg-ink:#fff9e9;
      --lg-mut:rgba(255,249,233,.6); --lg-faint:rgba(255,249,233,.5); --lg-meta:rgba(255,249,233,.55);
      --lg-line:rgba(255,255,255,.12); --lg-line2:rgba(255,255,255,.22);
      --lg-accent:var(--cs-coral); }
    body.night #hatcha-view .cs-dir, body.night #hatcha-view .cs-chat, body.night #hatcha-view .cs-mind,
    body.night #hatcha-view .cs-theme-p, body.night #hatcha-view .cs-theme-b {
      --lg-num:#c9bea9; --lg-ink:#17150f;
      --lg-mut:#8a8271; --lg-faint:#a79c89; --lg-meta:#8a7f6c;
      --lg-line:rgba(23,21,15,.12); --lg-line2:rgba(23,21,15,.2);
      --lg-accent:var(--cs-coral); }

    /* 02b · criteria media panel — 3-up widget shots centred (drop the rail escape) */
    #hatcha-view .cs-slide-sec.cs-panel .cs-crit-panel { width:100%;
      max-width:calc(var(--cs-max) + 2 * var(--cs-gut)); margin:0 auto;
      padding-left:var(--cs-gut); padding-right:var(--cs-gut);
      box-sizing:border-box; }
    #hatcha-view .cs-slide-sec.cs-panel .cs-crit-panel .cs-crit-eye { margin:0 0 34px; }
    #hatcha-view .cs-slide-sec.cs-panel .cs-crit-panel .cs-crit-note { margin:0 0 44px; max-width:900px; }
    /* proportionate assets — fixed cells that WRAP the count (3→2→1) as width
       shrinks, instead of stretching any one asset to full width. Desktop: up
       to native 362px (crisp). Tablet/phone: capped smaller (see media below). */
    #hatcha-view .cs-slide-sec.cs-panel .cs-crit-panel .cs-crit {
      grid-template-columns:repeat(auto-fit, minmax(300px, 362px));
      justify-content:start; gap:30px 28px; margin-left:0; }
    #hatcha-view .cs-slide-sec.cs-panel .cs-crit-panel .cs-crit > div { max-width:362px; }
    #hatcha-view .cs-slide-sec.cs-panel .cs-crit-panel .cs-card-img { height:auto; max-width:100%; max-height:none; object-fit:contain; object-position:top; }
    @media (max-width: 900px) {
      #hatcha-view .cs-slide-sec.cs-panel .cs-crit-panel .cs-crit {
        grid-template-columns:repeat(auto-fit, minmax(200px, 280px)); }
      #hatcha-view .cs-slide-sec.cs-panel .cs-crit-panel .cs-crit > div { max-width:280px; }
    }
    /* wide screens (≥1400px): let the three assets fill the row for presence on
       large displays (~420px each — slightly past native 362, mild upscaling). */
    @media (min-width: 1400px) {
      #hatcha-view .cs-slide-sec.cs-panel .cs-crit-panel .cs-crit {
        grid-template-columns:repeat(3, minmax(0, 1fr)); }
      #hatcha-view .cs-slide-sec.cs-panel .cs-crit-panel .cs-crit > div { max-width:none; }
    }
    @media (min-width: 1441px) {
      #hatcha-view .cs-slide-sec.cs-panel .cs-crit-panel .cs-crit-note { margin:0 0 88px; }
    }

    /* 03a is the densest ledger (4 rows + note) — tighten to hold 100vh */
    #hatcha-view .cs-slide-sec.cs-panel.cs-home .cs-ledger-label { margin-top:34px; padding-bottom:24px; }
    #hatcha-view .cs-slide-sec.cs-panel.cs-home .cs-ledger-row { padding:14px 0; }
    #hatcha-view .cs-slide-sec.cs-panel.cs-home .cs-ledger-note { margin-top:28px; }

    /* 04b · design-system gallery — the existing auto-scroll marquee, hosted
       full-bleed in a 100vh panel (its clone/rAF loop script is untouched) */
    #hatcha-view .cs-slide-sec.cs-panel.cs-gal .cs-slide-inner { padding-left:0; padding-right:0; }
    #hatcha-view .cs-gal-marq { position:relative; width:100%; overflow:hidden; }
    #hatcha-view .cs-gal-marq::before, #hatcha-view .cs-gal-marq::after {
      content:''; position:absolute; top:0; bottom:0; width:90px; z-index:2; pointer-events:none; }
    #hatcha-view .cs-gal-marq::before { left:0;  background:linear-gradient(to right, var(--cs-black), transparent); }
    #hatcha-view .cs-gal-marq::after  { right:0; background:linear-gradient(to left,  var(--cs-black), transparent); }
    body.night #hatcha-view .cs-gal-marq::before { background:linear-gradient(to right, var(--cs-cream), transparent); }
    body.night #hatcha-view .cs-gal-marq::after  { background:linear-gradient(to left,  var(--cs-cream), transparent); }
    #hatcha-view .cs-slide-sec.cs-panel.cs-gal .cs-gallery { padding-top:0; padding-bottom:0; margin:0; width:100%; }

    /* 05b/c · theme panels — centre the existing 2-col grids, cap the big media to 100vh */
    #hatcha-view .cs-slide-sec.cs-panel.cs-theme-p .cs-theme-grid,
    /* no --cs-gut here: .cs-theme-lower is a .cs-wrap, which already pads to --cs-pad */
    #hatcha-view .cs-slide-sec.cs-panel.cs-theme-b .cs-theme-lower { margin:0 auto; width:100%; max-width:var(--cs-max); }
    #hatcha-view .cs-slide-sec.cs-panel.cs-theme-p .cs-theme-grid {
      max-width:none;   /* full-width: L/R gutter (--cs-pad) matches the bottom padding */
      grid-template-columns:minmax(0,1fr) auto;   /* text flexes on the left, video is auto (native width) pinned to the right gutter */
      height:calc(100vh - var(--cs-navpad) - var(--cs-pad)); align-items:center; }
    /* keep the text ↔ video gap at exactly the 60px grid gap — the text block hugs the video's left edge (floats right, empty space collects on the far left) */
    #hatcha-view .cs-slide-sec.cs-panel.cs-theme-p .cs-theme-grid > div:first-child {
      justify-self:end; width:100%; max-width:340px; }
    #hatcha-view .cs-slide-sec.cs-panel.cs-theme-p .cs-mini { margin-top:52px; }
    #hatcha-view .cs-slide-sec.cs-panel.cs-theme-p .cs-palette {
      aspect-ratio:var(--cs-palette-ar);   /* keep native ratio so the baked title/swatches never crop */
      width:auto; height:100%; min-width:0; max-width:100%; max-height:none;
      align-self:center; justify-self:end;  /* full height, sat against the right gutter */
      margin-right:0; border-radius:8px; }
    /* guardrails — mirror of the palette panel: full-width, symmetric 60px gutters, ASSET on the left / content on the right */
    #hatcha-view .cs-slide-sec.cs-panel.cs-theme-b .cs-theme-lower {
      max-width:none;   /* full-width: L/R gutter (--cs-pad) matches the bottom padding */
      grid-template-columns:auto minmax(0,1fr);   /* asset is auto (native width) pinned to the left gutter, text flexes on the right */
      grid-template-rows:minmax(0,1fr);   /* definite single row so the bare <img loading="lazy" decoding="async"> can't inflate it past 100vh */
      gap:60px;
      height:calc(100vh - var(--cs-navpad) - var(--cs-pad)); align-items:center; }
    /* keep the asset ↔ text gap at exactly the 60px grid gap — the text hugs the asset's right edge (floats left, empty space collects on the far right) */
    #hatcha-view .cs-slide-sec.cs-panel.cs-theme-b .cs-guardrail-txt {
      justify-self:start; width:100%; max-width:360px; }
    #hatcha-view .cs-slide-sec.cs-panel.cs-theme-b .cs-guardrail-img {
      aspect-ratio:var(--cs-guardrail-ar);   /* keep native ratio so the asset never crops */
      width:auto; height:100%; min-width:0; min-height:0; max-width:100%; max-height:none;
      align-self:stretch; justify-self:start;  /* full height, sat against the left gutter */
      margin-left:0; border-radius:8px; }

    /* narrow viewports — a full-height aspect-locked asset (~square, but tall) gets
       too WIDE and starves the text column. Below this point, revert both theme
       panels to a stacked single column with the asset at full-width natural size. */
    @media (max-width:1200px) {
      #hatcha-view .cs-slide-sec.cs-panel.cs-theme-p .cs-theme-grid,
      #hatcha-view .cs-slide-sec.cs-panel.cs-theme-b .cs-theme-lower {
        grid-template-columns:1fr; grid-template-rows:auto; gap:32px;
        height:auto; align-items:stretch; }
      #hatcha-view .cs-slide-sec.cs-panel.cs-theme-p .cs-theme-grid > div:first-child,
      #hatcha-view .cs-slide-sec.cs-panel.cs-theme-b .cs-guardrail-txt {
        justify-self:stretch; max-width:none; }
      #hatcha-view .cs-slide-sec.cs-panel.cs-theme-p .cs-palette {
        width:100%; height:auto; aspect-ratio:var(--cs-palette-ar);
        justify-self:start; align-self:auto; max-width:600px; max-height:none; margin:0; }
      #hatcha-view .cs-slide-sec.cs-panel.cs-theme-b .cs-guardrail-img {
        width:100%; height:auto; aspect-ratio:var(--cs-guardrail-ar);
        justify-self:start; align-self:auto; max-width:600px; max-height:none; margin:0; }
    }

    /* 03b · fixed/flexible — two composed phone surfaces in one panel */
    #hatcha-view .cs-slide-sec.cs-panel.cs-fw .cs-fw-inner { width:100%; max-width:920px; margin:0 auto; }
    #hatcha-view .cs-slide-sec.cs-panel.cs-fw .cs-fw-note { margin:0 0 30px; max-width:none; }
    #hatcha-view .cs-slide-sec.cs-panel.cs-fw .cs-fw-cap { font-family:var(--cs-sans); font-size:12px;
      font-weight:500; letter-spacing:.16em; text-transform:uppercase; color:var(--lg-faint); margin:0 0 26px; }
    #hatcha-view .cs-slide-sec.cs-panel.cs-fw .cs-phones { margin-top:0; margin-left:0;
      --fw-figw:min(250px, calc((100vh - 336px) * 0.45)); }
    #hatcha-view .cs-slide-sec.cs-panel.cs-fw .cs-phone-fig { flex:0 0 var(--fw-figw); max-width:var(--fw-figw); }
    #hatcha-view .cs-slide-sec.cs-panel.cs-fw .cs-annot { flex:0 0 150px; }

    /* ── narrow-window gutter (--cs-gut) ──────────────────────────────────
       A deck panel's side gutter is a side-effect of centring: --cs-max is
       1320px, so a 1440px window leaves 60px of air either side. Under
       ~1320px there is nothing left to centre and the column runs flush to
       the glass. Widening the cap by exactly the padding keeps every window
       ≥1352px pixel-identical and puts a 16px floor under everything below.
       Applied to each centred column rather than to .cs-slide-inner so the
       genuinely full-bleed panels (hero, mockups, video beats) stay bleeding.
       The gutter is --cs-pad, the same token .cs-wrap uses for the hero and
       quote bands — a slide with its own smaller value reads as if one side
       is missing when it sits next to a band that pads properly.
       Static mobile is unaffected — its html.cs-static rules set --cs-pad. */
    #hatcha-view, #fireflut-view, #existence-view, #jumpable-view { --cs-gut:var(--cs-pad); }

    #hatcha-view .cs-ledger { display:grid; grid-template-columns:220px minmax(0,1fr); gap:64px;
      width:100%; max-width:calc(var(--cs-max) + 2 * var(--cs-gut)); margin:0 auto; align-self:center;
      padding-left:var(--cs-gut); padding-right:var(--cs-gut); }
    #hatcha-view .cs-ledger.header-only { margin-bottom:40px; }
    #hatcha-view .cs-ledger-rail { }
    #hatcha-view .cs-ledger-num { font-family:var(--cs-serif); font-weight:400;
      font-size:96px; line-height:.9; letter-spacing:.02em; color:var(--lg-num); }
    #hatcha-view .cs-ledger-eye { margin-top:18px; font-family:var(--cs-sans);
      font-size:12px; font-weight:500; letter-spacing:.18em; text-transform:uppercase;
      color:var(--lg-faint); }
    #hatcha-view .cs-ledger-h { margin:0; font-family:var(--cs-serif); font-weight:400;
      font-size:52px; line-height:1.07; letter-spacing:0; color:var(--lg-ink); text-wrap:balance; }
    #hatcha-view .cs-ledger-h i, #hatcha-view .cs-ledger-h em { font-style:italic; }
    #hatcha-view .cs-ledger-p { margin:30px 0 0; max-width:780px; font-family:var(--cs-sans);
      font-size:19px; line-height:1.6; color:var(--lg-ink); opacity:.86; }
    #hatcha-view .cs-ledger-p + .cs-ledger-p { margin-top:18px; }
    #hatcha-view .cs-ledger-label { margin-top:48px; padding-bottom:32px; display:flex; align-items:center; gap:16px; }
    #hatcha-view .cs-ledger-label > span:first-child { font-family:var(--cs-sans);
      font-size:12px; font-weight:500; letter-spacing:.16em; text-transform:uppercase; color:var(--lg-faint); }
    #hatcha-view .cs-ledger-rule { flex:1; height:1px; background:var(--lg-line); }
    #hatcha-view .cs-ledger-rows { margin-top:10px; }
    #hatcha-view .cs-ledger-row { display:grid; grid-template-columns:1fr 44px 1fr;
      align-items:center; padding:18px 0; border-bottom:1px solid var(--lg-line); }
    #hatcha-view .cs-ledger-row:last-child { border-bottom:0; }
    #hatcha-view .cs-ledger-hrow { align-items:end; padding:0 0 14px; border-bottom:1px solid var(--lg-line2); }
    #hatcha-view .cs-ledger-before { padding-right:24px; font-family:var(--cs-sans);
      font-size:18px; line-height:1.4; color:var(--lg-mut); }
    #hatcha-view .cs-ledger-after { padding-left:24px; font-family:var(--cs-sans);
      font-size:18px; line-height:1.4; color:var(--lg-ink); font-weight:500; }
    #hatcha-view .cs-ledger-arrow { display:flex; justify-content:center;
      font-family:var(--cs-sans); font-size:20px; color:var(--lg-accent); }
    #hatcha-view .cs-ledger-hrow .cs-ledger-before,
    #hatcha-view .cs-ledger-hrow .cs-ledger-after { font-size:11px; font-weight:600;
      letter-spacing:.14em; text-transform:uppercase; }
    #hatcha-view .cs-ledger-hrow .cs-ledger-before { color:var(--lg-faint); }
    #hatcha-view .cs-ledger-hrow .cs-ledger-after { color:var(--lg-accent); }
    /* term → definition rows (02 mindset, on the dark band) */
    #hatcha-view .cs-dir .cs-ledger-row,
    #hatcha-view .cs-chat .cs-ledger-row,
    #hatcha-view .cs-show .cs-ledger-row { grid-template-columns:300px minmax(0,1fr); align-items:baseline; }
    #hatcha-view .cs-ledger-key { padding-right:32px; font-family:var(--cs-sans);
      font-size:19px; font-weight:600; line-height:1.35; color:var(--lg-ink); }
    #hatcha-view .cs-ledger-sub { display:block; margin-top:5px; font-family:var(--cs-sans);
      font-size:13px; font-weight:400; letter-spacing:.02em; color:var(--lg-faint); }
    #hatcha-view .cs-ledger-desc { font-family:var(--cs-sans); font-size:16px;
      line-height:1.55; color:var(--lg-mut); }
    /* stat → caption rows (06 outcome) */
    #hatcha-view .cs-out .cs-ledger-row { grid-template-columns:210px minmax(0,1fr); align-items:baseline; }
    #hatcha-view .cs-ledger-stat { padding-right:32px; font-family:var(--cs-serif);
      font-weight:400; font-size:30px; letter-spacing:.02em; line-height:1.1; color:var(--lg-ink); }
    /* flush "What I did" note — replaces the old grey card inside ledger panels */
    #hatcha-view .cs-ledger-note { margin-top:34px; max-width:820px; padding-left:20px;
      border-left:2px solid var(--lg-accent); }
    #hatcha-view .cs-ledger-note .lbl { font-family:var(--cs-sans); font-size:12px; font-weight:500;
      letter-spacing:.16em; text-transform:uppercase; color:var(--lg-accent); margin-bottom:10px; }
    #hatcha-view .cs-ledger-note .bd { font-family:var(--cs-sans); font-size:15.5px;
      line-height:1.62; color:var(--lg-mut); }
    /* 05 · space the "What I did" note off the step table (label removed) — height-responsive so it still fits 100vh, with the rows tightened to reclaim room */
    #hatcha-view .cs-slide-sec.cs-panel.cs-chat .cs-ledger-rows { margin-top:min(44px, calc((100vh - 700px) * 0.4)); }
    #hatcha-view .cs-slide-sec.cs-panel.cs-chat .cs-ledger-row { padding:12px 0; }
    /* 06 · more breathing room between the intro copy and the note */
    #hatcha-view .cs-slide-sec.cs-panel.cs-dir .cs-ledger-note { margin-top:60px; }

    /* ── 01 · Problem — editorial "ledger" card (reframed from reference 1A).
       Design-system fonts kept (DM Serif Display / Space Grotesk); the
       reference's warm-grey palette is mapped onto scoped --lg-* tokens so the
       card inverts under night mode. A cream card floats on a slightly deeper
       cream mat: a meta rail (number / eyebrow / divider / meta) beside the
       headline, intro, and a before → after ledger with a Before/After head. */
    #fireflut-view .cs-prob, #fireflut-view .cs-home, #fireflut-view .cs-out {
      --lg-num:#c9bea9; --lg-ink:#17150f;
      --lg-mut:#8a8271; --lg-faint:#a79c89; --lg-meta:#8a7f6c;
      --lg-line:rgba(23,21,15,.12); --lg-line2:rgba(23,21,15,.2);
      --lg-accent:var(--cs-coral);
    }
    /* 05 · Outcome stats as editorial rows: a serif display key (keeps the
       stat's prominence) beside its description, hairline-separated like 01/02. */
    #fireflut-view .cs-out .cs-ledger-row { grid-template-columns:210px minmax(0,1fr); align-items:baseline; }
    #fireflut-view .cs-ledger-stat { padding-right:32px; font-family:var(--cs-serif);
      font-weight:400; font-size:30px; letter-spacing:.02em; line-height:1.1; color:var(--lg-ink); }
    /* a ledger used as a section header only (with full-bleed media below it)
       drops its bottom breathing room so the media sits close under the intro */
    #fireflut-view .cs-ledger.header-only { margin-bottom:40px; }
    /* 03 (home) and 04 (chat) are twins — same editorial header + a media row of
       equal-height cards — so both use the default centred panel (no special
       anchor) and land identically. */
    /* laid flush on the cream band — no floating card, one continuous surface */
    #fireflut-view .cs-ledger {
      align-self:center; width:100%; max-width:calc(var(--cs-max) + 2 * var(--cs-gut));
      padding-left:var(--cs-gut); padding-right:var(--cs-gut);
      display:grid; grid-template-columns:220px minmax(0,1fr);
      gap:64px; box-sizing:border-box;
    }
    #fireflut-view .cs-ledger-num { font-family:var(--cs-serif); font-weight:400;
      font-size:96px; line-height:.9; letter-spacing:.02em; color:var(--lg-num); }
    #fireflut-view .cs-ledger-eye { margin-top:18px; font-family:var(--cs-sans);
      font-size:12px; font-weight:500; letter-spacing:.18em; text-transform:uppercase;
      color:var(--lg-faint); }
    #fireflut-view .cs-ledger-div { margin-top:26px; height:1px; background:var(--lg-line); }
    #fireflut-view .cs-ledger-meta { margin-top:22px; font-family:var(--cs-sans);
      font-size:13px; line-height:1.6; color:var(--lg-meta); }
    #fireflut-view .cs-ledger-h { margin:0; font-family:var(--cs-serif); font-weight:400;
      font-size:52px; line-height:1.07; letter-spacing:0; color:var(--lg-ink); text-wrap:balance; }
    #fireflut-view .cs-ledger-h i, #fireflut-view .cs-ledger-h em { font-style:italic; }
    #fireflut-view .cs-ledger-p { margin:30px 0 0; max-width:780px; font-family:var(--cs-sans);
      font-size:19px; line-height:1.6; color:var(--lg-ink); opacity:.86; }
    #fireflut-view .cs-ledger-label { margin-top:48px; padding-bottom:32px; display:flex; align-items:center; gap:16px; }
    #fireflut-view .cs-ledger-label > span:first-child { font-family:var(--cs-sans);
      font-size:12px; font-weight:500; letter-spacing:.16em; text-transform:uppercase; color:var(--lg-faint); }
    #fireflut-view .cs-ledger-rule { flex:1; height:1px; background:var(--lg-line); }
    #fireflut-view .cs-ledger-rows { margin-top:10px; }
    #fireflut-view .cs-ledger-row { display:grid; grid-template-columns:1fr 44px 1fr;
      align-items:center; padding:18px 0; border-bottom:1px solid var(--lg-line); }
    #fireflut-view .cs-ledger-row:last-child { border-bottom:0; }
    #fireflut-view .cs-ledger-hrow { align-items:end; padding:0 0 14px; border-bottom:1px solid var(--lg-line2); }
    #fireflut-view .cs-ledger-before { padding-right:24px; font-family:var(--cs-sans);
      font-size:18px; line-height:1.4; color:var(--lg-mut); }
    #fireflut-view .cs-ledger-after { padding-left:24px; font-family:var(--cs-sans);
      font-size:18px; line-height:1.4; color:var(--lg-ink); font-weight:500; }
    #fireflut-view .cs-ledger-arrow { display:flex; justify-content:center;
      font-family:var(--cs-sans); font-size:20px; color:var(--lg-accent); }
    #fireflut-view .cs-ledger-hrow .cs-ledger-before,
    #fireflut-view .cs-ledger-hrow .cs-ledger-after { font-size:11px; font-weight:600;
      letter-spacing:.14em; text-transform:uppercase; line-height:1.4; }
    #fireflut-view .cs-ledger-hrow .cs-ledger-before { color:var(--lg-faint); }
    #fireflut-view .cs-ledger-hrow .cs-ledger-after { color:var(--lg-accent); }

    /* 02 · Direction — same editorial ledger, on the DARK band. Its --lg-*
       tokens are the light-on-dark set for day; night flips the band to cream,
       so the night override restores the warm-cream set (matching 01). The
       "ledger" here is the three guiding principles as a term → definition list. */
    #fireflut-view .cs-dir, #fireflut-view .cs-chat {
      --lg-num:#3c3c3c; --lg-ink:#fff9ea;
      --lg-mut:rgba(255,249,233,.62); --lg-faint:rgba(255,249,233,.45); --lg-meta:rgba(255,249,233,.5);
      --lg-line:rgba(255,255,255,.14); --lg-line2:rgba(255,255,255,.22);
      --lg-accent:var(--cs-coral);
    }
    #fireflut-view .cs-dir .cs-ledger-row { grid-template-columns:300px minmax(0,1fr); align-items:baseline; }
    #fireflut-view .cs-ledger-key { padding-right:32px; font-family:var(--cs-sans);
      font-size:19px; font-weight:600; line-height:1.35; color:var(--lg-ink); }
    #fireflut-view .cs-ledger-desc { font-family:var(--cs-sans); font-size:16px;
      line-height:1.55; color:var(--lg-mut); }

    /* ── NIGHT THEME (cream bands go night-dark, dark bands invert to day-cream) ── */
    body.night #fireflut-view { --cs-cream:var(--c-night); --cs-ink:var(--c-cream-2); }
    /* 01 · Problem ledger — invert the mat/card and warm-grey text at night */
    body.night #fireflut-view .cs-prob, body.night #fireflut-view .cs-home, body.night #fireflut-view .cs-out {
      --lg-num:#3a3a44; --lg-ink:#fff9e9;
      --lg-mut:rgba(255,249,233,.6); --lg-faint:rgba(255,249,233,.5); --lg-meta:rgba(255,249,233,.55);
      --lg-line:rgba(255,255,255,.12); --lg-line2:rgba(255,255,255,.22);
    }
    /* 03 uses the same cream editorial tokens as 01 (shared selector above) */
    /* 02 ledger — night flips the dark band to cream, so use the warm-cream set */
    body.night #fireflut-view .cs-dir, body.night #fireflut-view .cs-chat {
      --lg-num:#c9bea9; --lg-ink:#17150f;
      --lg-mut:#8a8271; --lg-faint:#a79c89; --lg-meta:#8a7f6c;
      --lg-line:rgba(23,21,15,.12); --lg-line2:rgba(23,21,15,.2);
    }
    body.night #fireflut-view .cs-band.cream .cs-p { color:rgba(255,249,233,0.85); }
    body.night #fireflut-view .cs-hero-intro { color:rgba(255,249,233,0.88); }
    #fireflut-view .cs-hero-tagline { margin:16px 0 0; font-family:var(--cs-serif);
      font-style:normal; font-weight:400; font-size:34px; line-height:1.2;
      letter-spacing:0.01em; color:rgba(11,11,11,0.6); }
    body.night #fireflut-view .cs-hero-tagline { color:rgba(255,249,233,0.7); }
    body.night #fireflut-view .cs-num { color:#43434b; }
    /* night stripe is for EMPTY placeholders only — the functional media/
       content cards (gauge, location, recs, voice, pre-prompts, loading,
       yes/no) carry their own real backgrounds and must keep them at night,
       identical to day (e.g. the gauge's gradient shows through its feathered
       video mask, so a striped fill would leak in around the edges). */
    body.night #fireflut-view .cs-ph:not(.dark):not(.cs-ff-gauge):not(.cs-ff-location):not(.cs-ff-recs):not(.cs-ff-voice):not(.cs-ff-preprompts):not(.cs-ff-loading):not(.cs-ff-yesno) {
      background:repeating-linear-gradient(45deg,#14141a,#14141a 14px,#1b1b22 14px,#1b1b22 28px); color:#6b6b6b; }
    body.night #fireflut-view .cs-ff-cap { color:rgba(255,249,233,0.5); }
    body.night #fireflut-view .cs-stat { border-color:rgba(255,249,233,0.22); }
    body.night #fireflut-view .cs-stat-p { color:rgba(255,249,233,0.7); }
    body.night #fireflut-view .cs-closing-p { color:rgba(255,249,233,0.78); }
    /* comparison table on the now-dark cream band → dark chrome */
    body.night #fireflut-view .cs-cmp { border-color:rgba(255,255,255,0.22); }
    body.night #fireflut-view .cs-cmp .before { color:#fdf1e1; }
    body.night #fireflut-view .cs-cmp .after { background:#fff9f1; color:#141414; }
    body.night #fireflut-view .cs-cmp-arrow { color:#fdf1e1; border-left-color:rgba(255,255,255,0.22); border-right-color:rgba(255,255,255,0.22); }
    body.night #fireflut-view .cs-cmp-row:not(:last-child) .before,
    body.night #fireflut-view .cs-cmp-row:not(:last-child) .cs-cmp-arrow { border-bottom-color:rgba(255,255,255,0.22); }
    body.night #fireflut-view .cs-cmp-row:not(:last-child) .after { border-bottom-color:rgba(0,0,0,0.22); }
    body.night #fireflut-view .cs-cmp-head .before { background:rgba(255,255,255,0.08); color:#fdf1e1; }
    body.night #fireflut-view .cs-cmp-head .cs-cmp-arrow { background:rgba(255,255,255,0.08); }
    body.night #fireflut-view .cs-cmp-head .after { background:#fdf1e1; color:#141414; }

    /* night: dark bands INVERT to day-cream with ink text */
    body.night #fireflut-view .cs-band.dark { background:#fff9f1; color:#0b0b0b; }
    body.night #fireflut-view .cs-band.dark .cs-num { color:#c9c4ba; }
    body.night #fireflut-view .cs-band.dark .cs-p { color:rgba(11,11,11,0.9); }
    body.night #fireflut-view .cs-band.dark .cs-quote-line { border-top-color:rgba(0,0,0,0.1); }
    body.night #fireflut-view .cs-band.dark .cs-ff-role { color:#8f8a80; border-color:rgba(0,0,0,0.14); }
    body.night #fireflut-view .cs-band.dark .cs-ff-prin { border-color:rgba(0,0,0,0.1); }
    body.night #fireflut-view .cs-band.dark .cs-ff-prin-card { background:#fff9f1; border-right-color:rgba(0,0,0,0.1); }
    body.night #fireflut-view .cs-band.dark .cs-ff-prin-h { color:#0b0b0b; }
    body.night #fireflut-view .cs-band.dark .cs-ff-prin-p { color:rgba(11,11,11,0.62); }
    body.night #fireflut-view .cs-band.dark .ff-vcopy h3 { color:#0b0b0b; }
    body.night #fireflut-view .cs-band.dark .ff-vcopy p { color:rgba(11,11,11,0.62); }
    body.night #fireflut-view .cs-band.dark .cs-ff-swatch .lbl { color:rgba(11,11,11,0.5); }
    body.night #fireflut-view .cs-band.dark .cs-ff-micro { border-color:rgba(0,0,0,0.12); }
    body.night #fireflut-view .cs-band.dark .cs-ff-mrow + .cs-ff-mrow { border-top-color:rgba(0,0,0,0.12); }
    body.night #fireflut-view .cs-band.dark .cs-ff-mtag { color:#0b0b0b; border-right-color:rgba(0,0,0,0.12); }
    body.night #fireflut-view .cs-band.dark .cs-ff-mdesc { color:rgba(11,11,11,0.7); }
    body.night #fireflut-view .cs-band.dark .cs-ff-cap { color:rgba(11,11,11,0.55); }
    body.night #fireflut-view .cs-band.dark .cs-ph.dark {
      background:repeating-linear-gradient(45deg,#e7e0d2,#e7e0d2 14px,#ddd5c5 14px,#ddd5c5 28px); color:#8a8474; }

    /* responsive */
    @media (max-width:1100px){
      #fireflut-view { --cs-pad:28px; --cs-rail:180px; --cs-navpad:104px; }   /* mobile nav 72px + 32px */
      #fireflut-view .cs-hero-h { font-size:44px; }
      #fireflut-view .cs-hero-tagline { font-size:26px; }
      #fireflut-view .cs-meta { margin-top:8px; }
      #fireflut-view .cs-sec-h { font-size:34px; }
      #fireflut-view .cs-ff-duo, #fireflut-view .cs-ff-duo.rev,
      #fireflut-view .cs-ff-prin, #fireflut-view .cs-ff-swatch { grid-template-columns:1fr; margin-left:0; }
      #fireflut-view .cs-ff-prin-card { border-right:0; border-bottom:1px solid rgba(255,249,233,0.12); }
      #fireflut-view .cs-ff-prin-card:last-child { border-bottom:0; }
      body.night #fireflut-view .cs-band.dark .cs-ff-prin-card { border-bottom-color:rgba(0,0,0,0.1); }
      #fireflut-view .cs-ff-cap, #fireflut-view .cs-ff-full, #fireflut-view .cs-ff-eye { margin-left:0; }
      #fireflut-view .cs-stats { grid-template-columns:1fr; }
      #fireflut-view .cs-ff-mrow { grid-template-columns:1fr; }
      #fireflut-view .cs-ff-mtag { border-right:0; border-bottom:1px solid rgba(255,249,233,0.14); }
      #fireflut-view .cs-band.cream .cs-ff-mtag { border-bottom-color:rgba(0,0,0,0.12); }
      #fireflut-view .cs-num { font-size:64px; }
    }
    /* phones: side-by-side halves get too small, so stack them full-width.
       flex:0 0 auto so each card sizes to its image (a column flex-basis of 0
       would collapse the card to 0 height and clip the shot). */
    @media (max-width:820px){
      /* Problem ledger stacks: meta rail above the content, tighter card */
      #fireflut-view .cs-ledger { grid-template-columns:1fr; gap:32px; padding:40px 28px 44px; }
      #fireflut-view .cs-ledger-num { font-size:72px; }
      #fireflut-view .cs-ledger-h { font-size:34px; }
      #fireflut-view .cs-ledger-p { font-size:16px; }
      #fireflut-view .cs-ledger-before, #fireflut-view .cs-ledger-after { font-size:15px; }
      #fireflut-view .cs-ledger-row, #fireflut-view .cs-ledger-hrow { grid-template-columns:1fr 32px 1fr; }
    }
    @media (max-width:760px){
      #fireflut-view .cs-ff-shots { flex-direction:column; }
      #fireflut-view .cs-ff-shots.grid { grid-template-columns:1fr; }
      #fireflut-view .cs-ff-shot { flex:0 0 auto; height:auto; display:block; }
      #fireflut-view .cs-ff-shot img { width:100%; height:auto; max-width:100%; max-height:none; object-fit:fill; }
      #fireflut-view .cs-ff-colors { height:auto; aspect-ratio:720/1024; }
      /* the absolutely-stacked variant frames need an explicit box on mobile,
         since the card collapses to auto height here */
      #fireflut-view .cs-ff-shot.variants .ff-vstack { aspect-ratio:720/1024; }
      #fireflut-view .ff-vframe { width:100%; height:100%; max-height:none; object-fit:contain; }
      /* the rail+content column narrows below these videos' own aspect ratio
         here — cover would crop into real content, so contain instead, centred.
         (Matches the .cs-ff-location .gg-video selector's specificity so this
         wins and resets its bottom-anchored object-position back to centred.) */
      #fireflut-view .gg-video,
      #fireflut-view .cs-ff-location .gg-video { object-fit:contain; object-position:center; }
    }
    @media (max-width:1320px){
      #fireflut-view .cs-numbered { padding-left:16px !important; padding-right:16px !important; }
      #fireflut-view .cs-quote-inner { padding-left:16px; padding-right:16px; }
    }

    /* ═══ Fireflut · mobile static flow (html.cs-static, set once at load ≤820px) ═══
       The cover-slide deck is skipped, sections stack and scroll natively.
       Ledgers keep headline + data rows and drop the prose; media goes
       full-bleed. Class-scoped (not @media) so it tracks the JS gate exactly. */

    /* ledger panels: own vertical rhythm (the deck's flex-centring is gone)
       plus the side gutter the base ledger never carries */
    html.cs-static #fireflut-view .cs-ledger { grid-template-columns:1fr; gap:24px;
      padding:56px var(--cs-pad) 48px; }
    html.cs-static #fireflut-view .cs-ledger-num { font-size:64px; }
    html.cs-static #fireflut-view .cs-ledger-eye { margin-top:10px; }
    html.cs-static #fireflut-view .cs-ledger-h { font-size:32px; }
    html.cs-static #fireflut-view .cs-ledger-p { display:none; }
    html.cs-static #fireflut-view .cs-ledger-label { margin-top:24px; padding-bottom:16px; }
    html.cs-static #fireflut-view .cs-ledger-row,
    html.cs-static #fireflut-view .cs-ledger-hrow { grid-template-columns:1fr 28px 1fr; }
    html.cs-static #fireflut-view .cs-ledger-row { padding:14px 0; }
    html.cs-static #fireflut-view .cs-ledger-before,
    html.cs-static #fireflut-view .cs-ledger-after { font-size:15px; }
    html.cs-static #fireflut-view .cs-ledger-before { padding-right:6px; }
    html.cs-static #fireflut-view .cs-ledger-after { padding-left:6px; }
    /* key/desc rows (02 Direction): keep the term, drop the description */
    html.cs-static #fireflut-view .cs-dir .cs-ledger-row { grid-template-columns:1fr; }
    html.cs-static #fireflut-view .cs-dir .cs-ledger-desc { display:none; }
    html.cs-static #fireflut-view .cs-ledger-key { padding-right:0; font-size:17px; }
    /* stat rows (05 Outcome): the caption is the stat's meaning — keep it, stacked */
    html.cs-static #fireflut-view .cs-out .cs-ledger-row { grid-template-columns:1fr; gap:4px; align-items:start; }
    html.cs-static #fireflut-view .cs-out .cs-ledger-desc { display:block; }
    /* the source comment calls 05 Outcome "one 100vh panel" by design — the
       desktop deck gives it that via .cs-slide-inner; static flow needs its
       own min-height + vertical centring to match, keeping the existing
       56/pad/48 padding rather than replacing it. */
    html.cs-static #fireflut-view .cs-out .cs-ledger { min-height:100svh;
      display:flex; flex-direction:column; justify-content:center; box-sizing:border-box; }

    /* pull quotes: scale down for phone measure */
    html.cs-static #fireflut-view .cs-quote p { font-size:22px; }

    /* before/after, moodboard, variants: full-bleed vertical stack */
    html.cs-static #fireflut-view .cs-ff-shots { flex-direction:column; gap:12px;
      padding-left:0; padding-right:0; }
    html.cs-static #fireflut-view .cs-ff-shots.grid { grid-template-columns:1fr; padding-bottom:0; }
    html.cs-static #fireflut-view .cs-ff-shot { flex:0 0 auto; height:auto; display:block; border-radius:0; }
    html.cs-static #fireflut-view .cs-ff-shot img { width:100%; height:auto; max-width:100%; max-height:none; }
    /* tiles whose content is absolutely positioned need an explicit box once
       the card collapses to auto height (mirrors the ≤760 rules, which stop
       at 760 while the gate runs to 820) */
    html.cs-static #fireflut-view .cs-ff-colors { height:auto; aspect-ratio:720/1024; }
    html.cs-static #fireflut-view .cs-ff-shot.variants .ff-vstack { aspect-ratio:720/1024; }
    html.cs-static #fireflut-view .ff-vframe { width:100%; height:100%; max-height:none; object-fit:contain; }
    html.cs-static #fireflut-view .cs-ff-title { padding:0 var(--cs-pad); }
    html.cs-static #fireflut-view .cs-ff-shot.copy { padding:40px var(--cs-pad) 48px; }
    html.cs-static #fireflut-view .ff-vcopy { max-width:none; padding:0; }

    /* section 03 trio: full-bleed single column */
    html.cs-static #fireflut-view .cs-ff-trio { grid-template-columns:1fr; gap:12px;
      padding-left:0; padding-right:0; }
    html.cs-static #fireflut-view .cs-ff-trio .cs-ph { border-radius:0; }

    /* section 04 chat details: same treatment as the 03 trio — the marquee
       stops and its cards stack full-bleed, one per row. The aria-hidden set
       only ever existed to make the translateX loop wrap seamlessly. */
    html.cs-static #fireflut-view .cs-ff-quad-wrap::before,
    html.cs-static #fireflut-view .cs-ff-quad-wrap::after { display:none; }
    html.cs-static #fireflut-view .cs-ff-quad { display:grid; grid-template-columns:1fr;
      gap:12px; width:auto; padding:0; animation:none; }
    html.cs-static #fireflut-view .cs-ff-quad .cs-ph { flex:none; width:auto;
      aspect-ratio:720/626; height:auto; border-radius:0; }
    html.cs-static #fireflut-view .cs-ff-quad .cs-ph[aria-hidden="true"] { display:none; }

    /* home mockup: the flagship full-bleed beat — one full viewport */
    html.cs-static #fireflut-view .cs-ff-shot.mockup { height:100svh; display:flex; }
    html.cs-static #fireflut-view .cs-ff-shot.mockup img { width:100%; height:100%; object-fit:cover; }

    /* final montage: full viewport, svh so iOS toolbars never crop it */
    html.cs-static #fireflut-view .cs-ff-final { margin-top:0; }
    html.cs-static #fireflut-view .cs-ff-final .cs-ph { height:100svh; }


    /* ═══════════════ Work / Existence case study (routed via #existence) ═══════════════
       Cover-slide 100vh panels + editorial ledger (the Fireflut/Hatcha model). Accent is
       MONOCHROME — no chroma tint; contrast (ink on cream / cream on dark) is the accent,
       and italic <i> carries emphasis. Scoped tokens + scoped ledger/stage CSS. */
    #existence-view { --cs-serif:'DM Serif Display', Georgia, serif;
                      --cs-sans:'Space Grotesk', sans-serif;
                      --cs-cream:#fff9f1; --cs-black:#000; --cs-ink:#0b0b0b;
                      --cs-coral:#0b0b0b;               /* monochrome: "accent" is ink/cream contrast */
                      --cs-max:1320px; --cs-pad:60px; --cs-rail:355px;
                      --cs-navpad:112px; }             /* panel top clearance = 80px nav + 32px */

    /* tile → hero shared-element zoom (same mechanism as Hatcha/Fireflut) */
    body.vt-hero a.project-tile[href="/work/existence"] { view-transition-name: existence-hero; }
    body.vt-hero #existence-view .cs-hero .cs-hero-mock { view-transition-name: existence-hero; }
    ::view-transition-old(existence-hero),
    ::view-transition-new(existence-hero) { width:100%; height:100%; object-fit:cover; }
    ::view-transition-group(existence-hero) {
      animation-duration: 0.6s; animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1); }
    @media (prefers-reduced-motion: reduce) {
      ::view-transition-group(existence-hero),
      ::view-transition-old(existence-hero),
      ::view-transition-new(existence-hero) { animation: none; } }

    /* hero reads as two full-screen beats: the title/meta chunk, then the device mockup
       (same fix as #fireflut-view). Read-scroll travel is inner.scrollHeight − 100vh,
       which ignores the SECTION's own padding — so with .cs-hero's 138px nav clearance
       on the section the mockup could only ever rise to 138px and its bottom stayed
       clipped. Move that clearance onto the text chunk: the text lands where it did and
       the mockup's rest state is a full 100vh × 100vw frame. */
    #existence-view .cs-slide-sec.cs-hero { padding-top: 0; }
    #existence-view .cs-slide-sec.cs-hero .cs-hero-grid { padding-top: 138px; }

    /* hero placeholder — own class (not .cs-ph) so its clip never fights the tile→hero morph */
    #existence-view .cs-ex-heroph { position:absolute; inset:0; width:100%; height:100%;
      display:flex; align-items:center; justify-content:center;
      background:repeating-linear-gradient(45deg,#e7e0d2,#e7e0d2 14px,#ddd5c5 14px,#ddd5c5 28px); color:#8a8474; }
    #existence-view .cs-ex-heroph span { padding:8px 14px; background:rgba(0,0,0,0.5); color:#fff;
      border-radius:100px; font-size:12px; letter-spacing:0.04em; }

    /* quote stays monochrome — emphasis is the serif italic, not colour */
    #existence-view .cs-quote { padding:113px 0; }
    #existence-view .cs-quote .coral { color:var(--cs-ink); font-style:italic; }

    /* hero tagline — a subtitle under the "Existence" display title, not equal weight:
       smaller italic serif, muted, so the title clearly leads. */
    #existence-view .cs-hero-tagline { margin:16px 0 0; font-family:var(--cs-serif);
      font-style:normal; font-weight:400; font-size:34px; line-height:1.2;
      letter-spacing:0.01em; color:rgba(11,11,11,0.6); }
    body.night #existence-view .cs-hero-tagline { color:rgba(255,249,233,0.7); }
    /* align the ROLE/CLIENT meta labels with the top of the intro paragraph */
    #existence-view .cs-meta { margin-top:157px; }

    /* ── cover-slide stage (scoped; identical mechanic to Fireflut/Hatcha) ── */
    #existence-view .cs-slide-scroller { position: relative; }
    #existence-view .cs-slide-stage { position: sticky; top: 0; height: 100vh; overflow: hidden; isolation: isolate; perspective: 1700px; perspective-origin: 50% 50%; }
    #existence-view .cs-slide-sec { position: absolute; inset: 0; overflow: hidden; }
    #existence-view .cs-slide-sec.cv { box-shadow: -26px 0 60px rgba(0,0,0,0.16); }
    #existence-view .cs-slide-inner { }
    #existence-view .cs-slide-sec.cs-band { min-height: 0; }
    #existence-view .cs-slide-sec.cs-panel .cs-slide-inner {
      min-height: 100vh; box-sizing: border-box;
      padding-top: var(--cs-navpad); padding-bottom: var(--cs-pad);
      display: flex; flex-direction: column; justify-content: center; }
    /* quote / closing / read-next carry old-scroll band paddings — zero them inside a centred panel */
    #existence-view .cs-slide-sec.cs-panel.cs-quote,
    #existence-view .cs-slide-sec.cs-panel.cs-closing { padding-top: 0; padding-bottom: 0; }

    /* ── full-bleed media panels (placeholder screens; swap real exports later) ── */
    #existence-view .cs-ex-shots { display:grid; gap:16px; width:100%;
      max-width:calc(var(--cs-max) + 2 * var(--cs-gut)); margin:0 auto;
      padding-left:var(--cs-gut); padding-right:var(--cs-gut); }
    #existence-view .cs-ex-shots.two   { grid-template-columns:repeat(2,1fr); }
    #existence-view .cs-ex-shots.three { grid-template-columns:repeat(3,1fr); }
    #existence-view .cs-slide-sec.cs-panel .cs-ex-shots .cs-ph {
      height: calc(100vh - var(--cs-navpad) - var(--cs-pad)); border-radius:10px; }

    /* app-screen recordings — phone-shaped looping videos, sized to the panel height */
    #existence-view .cs-ex-screens { align-self:center; width:100%;
      max-width:calc(var(--cs-max) + 2 * var(--cs-gut));
      padding-left:var(--cs-gut); padding-right:var(--cs-gut);
      margin:0 auto; display:flex; gap:56px; justify-content:center; align-items:flex-start; }
    #existence-view .cs-ex-screen { margin:0; display:flex; flex-direction:column;
      align-items:center; gap:16px; }
    /* CSS phone frame: metal bezel + rounded screen + subtle side buttons */
    #existence-view .cs-ex-screen-vid { position:relative; box-sizing:border-box;
      height:calc(100vh - var(--cs-navpad) - var(--cs-pad) - 150px); max-height:640px;
      aspect-ratio:390 / 850; padding:7px; border-radius:46px;
      background:linear-gradient(150deg,#26262a,#0c0c0e 42%);
      box-shadow:0 30px 60px rgba(0,0,0,0.45), inset 0 0 0 1px rgba(255,255,255,0.09); }
    #existence-view .cs-ex-screen-vid video { width:100%; height:100%; object-fit:cover;
      display:block; border-radius:40px; background:#000; }
    /* side buttons (volume left, power right) */
    #existence-view .cs-ex-screen-vid::before, #existence-view .cs-ex-screen-vid::after {
      content:''; position:absolute; width:2.5px; border-radius:2px; background:#1b1b1e; }
    #existence-view .cs-ex-screen-vid::before { left:-2px; top:23%; height:118px; }
    #existence-view .cs-ex-screen-vid::after  { right:-2px; top:31%; height:92px; }
    #existence-view .cs-ex-screen-label { font-family:var(--cs-sans); font-size:14px; font-weight:500;
      letter-spacing:.1em; text-transform:uppercase; color:rgba(255,249,233,0.6); }
    body.night #existence-view .cs-ex-screen-label { color:rgba(11,11,11,0.6); }

    /* CTA panel — "reach out to know more" (pre-launch) */
    #existence-view .cs-cta-inner { max-width:820px; margin:0 auto; text-align:center; }
    #existence-view .cs-cta-h { margin:0; font-family:var(--cs-serif); font-weight:400;
      font-size:58px; line-height:1.1; letter-spacing:0.01em; color:var(--cs-ink); }
    #existence-view .cs-cta-h i { font-style:italic; }
    #existence-view .cs-cta-p { margin:24px auto 0; max-width:560px; font-family:var(--cs-sans);
      font-size:18px; line-height:1.6; color:rgba(11,11,11,0.66); }
    body.night #existence-view .cs-cta-p { color:rgba(255,249,233,0.72); }
    #existence-view .cs-cta-btn { margin-top:40px; display:inline-flex; align-items:center;
      padding:16px 32px; border:0; border-radius:100px; background:var(--cs-ink); color:var(--cs-cream);
      font-family:var(--cs-sans); font-size:15px; font-weight:500; letter-spacing:0.02em; cursor:pointer;
      transition:transform .3s cubic-bezier(.22,.61,.36,1), opacity .3s; }
    #existence-view .cs-cta-btn:hover { transform:translateY(-2px); opacity:.92; }
    #existence-view .cs-cta-btn:active { transform:translateY(0); }
    #existence-view .cs-cta-btn:focus-visible { outline:2px solid var(--cs-ink); outline-offset:3px; }
    @media (max-width:900px){ #existence-view .cs-cta-h { font-size:38px; } }

    @media (max-width:900px){
      #existence-view .cs-ex-screens { gap:12px; }
      #existence-view .cs-ex-screen { flex:1 1 0; min-width:0; gap:10px; }
      #existence-view .cs-ex-screen-vid { height:auto; width:100%; padding:6px; border-radius:26px;
        max-height:calc(100vh - var(--cs-navpad) - var(--cs-pad) - 50px); }
      #existence-view .cs-ex-screen-vid video { border-radius:21px; }
      #existence-view .cs-ex-screen-vid::before { height:56px; }
      #existence-view .cs-ex-screen-vid::after { height:44px; }
      #existence-view .cs-ex-screen-label { font-size:11px; letter-spacing:.06em; }
    }

    /* ── Instagram intro reel panel (Rob Dyrdek) — centred portrait video ── */
    #existence-view .cs-ex-reel { align-self:center; width:100%;
      max-width:calc(var(--cs-max) + 2 * var(--cs-gut));
      padding-left:var(--cs-gut); padding-right:var(--cs-gut);
      display:flex; flex-direction:column; align-items:center; gap:22px; }
    #existence-view .cs-reel-head { display:flex; align-items:center; gap:14px; }
    #existence-view .cs-reel-eye { font-family:var(--cs-sans); font-size:13px; font-weight:500;
      letter-spacing:.14em; text-transform:uppercase; color:rgba(255,249,233,.55); }
    /* Rob's reel is a LANDSCAPE 16:9 video; the IG /embed/ iframe wraps it in a username
       header (~56px, kept) plus a caption/likes/comment white footer below (cropped). The
       frame height = header + the 16:9 video (width * 9/16); overflow hides the footer as
       the taller iframe runs past the bottom. --ig-head / --reel-w tune the header height
       and overall size. */
    #existence-view .cs-reel-frame { --ig-head:56px; --reel-w:min(960px, 92vw);
      width:var(--reel-w);
      height:calc(var(--ig-head) + var(--reel-w) * 0.5625);
      max-height:calc(100vh - var(--cs-navpad) - var(--cs-pad) - 60px);
      border-radius:16px; overflow:hidden; background:#000; }
    #existence-view .cs-reel-frame iframe { display:block; width:100%;
      height:calc(100% + 400px); margin-top:0; border:0; }
    #existence-view .cs-reel-frame .cs-ph { width:100%; height:100%; border-radius:0; }
    body.night #existence-view .cs-reel-eye { color:rgba(11,11,11,.55); }

    /* ── editorial ledger (cloned from Hatcha; monochrome via --lg-* tokens) ── */
    #existence-view .cs-ledger { align-self:center; width:100%;
      max-width:calc(var(--cs-max) + 2 * var(--cs-gut));
      padding-left:var(--cs-gut); padding-right:var(--cs-gut);
      display:grid; grid-template-columns:220px minmax(0,1fr); gap:64px; box-sizing:border-box; }
    #existence-view .cs-ledger.header-only { margin-bottom:40px; }
    #existence-view .cs-ledger-num { font-family:var(--cs-serif); font-weight:400;
      font-size:96px; line-height:.9; letter-spacing:.02em; color:var(--lg-num); }
    #existence-view .cs-ledger-eye { margin-top:18px; font-family:var(--cs-sans);
      font-size:12px; font-weight:500; letter-spacing:.18em; text-transform:uppercase; color:var(--lg-faint); }
    #existence-view .cs-ledger-h { margin:0; font-family:var(--cs-serif); font-weight:400;
      font-size:52px; line-height:1.07; letter-spacing:0; color:var(--lg-ink); text-wrap:balance; }
    #existence-view .cs-ledger-h i, #existence-view .cs-ledger-h em { font-style:italic; }
    #existence-view .cs-ledger-p { margin:30px 0 0; max-width:780px; font-family:var(--cs-sans);
      font-size:19px; line-height:1.6; color:var(--lg-ink); opacity:.86; }
    #existence-view .cs-ledger-p + .cs-ledger-p { margin-top:18px; }
    #existence-view .cs-ledger-label { margin-top:40px; padding-bottom:26px; display:flex; align-items:center; gap:16px; }
    #existence-view .cs-ledger-label > span:first-child { font-family:var(--cs-sans);
      font-size:12px; font-weight:500; letter-spacing:.16em; text-transform:uppercase; color:var(--lg-faint); }
    #existence-view .cs-ledger-rule { flex:1; height:1px; background:var(--lg-line); }
    #existence-view .cs-ledger-rows { margin-top:8px; }
    #existence-view .cs-ledger-row { display:grid; grid-template-columns:1fr 44px 1fr;
      align-items:center; padding:16px 0; border-bottom:1px solid var(--lg-line); }
    #existence-view .cs-ledger-row:last-child { border-bottom:0; }
    #existence-view .cs-ledger-hrow { align-items:end; padding:0 0 12px; border-bottom:1px solid var(--lg-line2); }
    #existence-view .cs-ledger-before { padding-right:24px; font-family:var(--cs-sans);
      font-size:18px; line-height:1.4; color:var(--lg-mut); }
    #existence-view .cs-ledger-after { padding-left:24px; font-family:var(--cs-sans);
      font-size:18px; line-height:1.4; color:var(--lg-ink); font-weight:500; }
    #existence-view .cs-ledger-arrow { display:flex; justify-content:center;
      font-family:var(--cs-sans); font-size:20px; color:var(--lg-accent); }
    #existence-view .cs-ledger-hrow .cs-ledger-before,
    #existence-view .cs-ledger-hrow .cs-ledger-after { font-size:11px; font-weight:600;
      letter-spacing:.14em; text-transform:uppercase; }
    #existence-view .cs-ledger-hrow .cs-ledger-before { color:var(--lg-faint); }
    #existence-view .cs-ledger-hrow .cs-ledger-after { color:var(--lg-accent); }
    /* term → definition rows (02 / 03 / 04 / 05 / 06) */
    #existence-view .cs-dir .cs-ledger-row,
    #existence-view .cs-home .cs-ledger-row,
    #existence-view .cs-chat .cs-ledger-row,
    #existence-view .cs-gate .cs-ledger-row,
    #existence-view .cs-work .cs-ledger-row { grid-template-columns:300px minmax(0,1fr); align-items:baseline; }
    #existence-view .cs-ledger-key { padding-right:32px; font-family:var(--cs-sans);
      font-size:19px; font-weight:600; line-height:1.35; color:var(--lg-ink); }
    #existence-view .cs-ledger-desc { font-family:var(--cs-sans); font-size:16px; line-height:1.55; color:var(--lg-mut); }
    /* stat → caption rows (07 outcome) */
    /* 250px, not 210: the stat column holds a unit now ("8,000+ signups"), and at
       210 the widest one wrapped to two lines while its neighbours stayed on one,
       breaking the baseline rhythm down the rows. Collapses to a single column at
       the breakpoint below, so this only governs the two-column desktop layout. */
    #existence-view .cs-out .cs-ledger-row { grid-template-columns:250px minmax(0,1fr); align-items:baseline; }
    #existence-view .cs-ledger-stat { padding-right:32px; font-family:var(--cs-serif);
      font-weight:400; font-size:30px; letter-spacing:.02em; line-height:1.1; color:var(--lg-ink); }
    /* flush "My contribution" note (left-border, monochrome accent) */
    #existence-view .cs-ledger-note { margin-top:36px; max-width:820px; padding-left:20px;
      border-left:2px solid var(--lg-accent); }
    #existence-view .cs-ledger-note .lbl { font-family:var(--cs-sans); font-size:12px; font-weight:500;
      letter-spacing:.16em; text-transform:uppercase; color:var(--lg-accent); margin-bottom:10px; }
    #existence-view .cs-ledger-note .bd { font-family:var(--cs-sans); font-size:15.5px; line-height:1.62; color:var(--lg-mut); }

    /* ── ledger tokens ── cream bands (01/03/05/07) warm-grey; dark bands (02/04/06) light-on-dark */
    #existence-view .cs-prob, #existence-view .cs-home, #existence-view .cs-out, #existence-view .cs-work {
      --lg-num:#c9bea9; --lg-ink:#17150f; --lg-mut:#8a8271; --lg-faint:#a79c89; --lg-meta:#8a7f6c;
      --lg-line:rgba(23,21,15,.12); --lg-line2:rgba(23,21,15,.2); --lg-accent:#17150f; }
    #existence-view .cs-dir, #existence-view .cs-chat, #existence-view .cs-gate {
      --lg-num:#3c3c3c; --lg-ink:#fff9ea; --lg-mut:rgba(255,249,233,.62); --lg-faint:rgba(255,249,233,.45); --lg-meta:rgba(255,249,233,.5);
      --lg-line:rgba(255,255,255,.14); --lg-line2:rgba(255,255,255,.22); --lg-accent:#fff9ea; }

    /* ── NIGHT THEME (cream bands go night-dark, dark bands invert to day-cream) ── */
    body.night #existence-view { --cs-cream:var(--c-night); --cs-ink:var(--c-cream-2); }
    body.night #existence-view .cs-prob, body.night #existence-view .cs-home,
    body.night #existence-view .cs-out, body.night #existence-view .cs-work {
      --lg-num:#3a3a44; --lg-ink:#fff9e9; --lg-mut:rgba(255,249,233,.6); --lg-faint:rgba(255,249,233,.5); --lg-meta:rgba(255,249,233,.55);
      --lg-line:rgba(255,255,255,.12); --lg-line2:rgba(255,255,255,.22); --lg-accent:#fff9e9; }
    body.night #existence-view .cs-dir, body.night #existence-view .cs-chat, body.night #existence-view .cs-gate {
      --lg-num:#c9bea9; --lg-ink:#17150f; --lg-mut:#8a8271; --lg-faint:#a79c89; --lg-meta:#8a7f6c;
      --lg-line:rgba(23,21,15,.12); --lg-line2:rgba(23,21,15,.2); --lg-accent:#17150f; }
    body.night #existence-view .cs-band.cream .cs-p { color:rgba(255,249,233,0.85); }
    body.night #existence-view .cs-hero-intro { color:rgba(255,249,233,0.88); }
    body.night #existence-view .cs-quote .coral { color:var(--cs-ink); }
    body.night #existence-view .cs-closing-p { color:rgba(255,249,233,0.78); }
    body.night #existence-view .cs-ph:not(.dark), body.night #existence-view .cs-ex-heroph {
      background:repeating-linear-gradient(45deg,#14141a,#14141a 14px,#1b1b22 14px,#1b1b22 28px); color:#6b6b6b; }
    /* dark bands INVERT to day-cream with ink text at night */
    body.night #existence-view .cs-band.dark { background:#fff9f1; color:#0b0b0b; }
    body.night #existence-view .cs-band.dark .cs-p { color:rgba(11,11,11,0.9); }
    body.night #existence-view .cs-band.dark .cs-quote-line { border-top-color:rgba(0,0,0,0.1); }
    body.night #existence-view .cs-band.dark .cs-ph.dark {
      background:repeating-linear-gradient(45deg,#e7e0d2,#e7e0d2 14px,#ddd5c5 14px,#ddd5c5 28px); color:#8a8474; }

    /* ── responsive ── */
    @media (max-width:1100px){
      #existence-view { --cs-pad:28px; --cs-rail:180px; --cs-navpad:104px; }
      #existence-view .cs-hero-h { font-size:44px; }
      #existence-view .cs-hero-tagline { font-size:26px; }
      #existence-view .cs-meta { margin-top:8px; }
    }
    @media (max-width:900px){
      #existence-view .cs-ledger { grid-template-columns:1fr; gap:26px; }
      #existence-view .cs-ledger-num { font-size:64px; }
      #existence-view .cs-ledger-eye { margin-top:10px; }
      #existence-view .cs-ledger-h { font-size:32px; }
      #existence-view .cs-ledger-p { font-size:16px; margin-top:22px; }
      #existence-view .cs-ledger-label { margin-top:30px; padding-bottom:20px; }
      #existence-view .cs-ledger-before, #existence-view .cs-ledger-after { font-size:15px; }
      #existence-view .cs-ledger-row, #existence-view .cs-ledger-hrow { grid-template-columns:1fr 30px 1fr; }
      #existence-view .cs-dir .cs-ledger-row, #existence-view .cs-chat .cs-ledger-row,
      #existence-view .cs-home .cs-ledger-row,
      #existence-view .cs-gate .cs-ledger-row, #existence-view .cs-work .cs-ledger-row,
      #existence-view .cs-out .cs-ledger-row { grid-template-columns:1fr; gap:6px; }
    }
    @media (max-width:760px){
      #existence-view .cs-ex-shots.two, #existence-view .cs-ex-shots.three { grid-template-columns:1fr; }
      #existence-view .cs-slide-sec.cs-panel .cs-ex-shots .cs-ph { height:auto; aspect-ratio:320/560; }
    }
    @media (max-width:1320px){
      #existence-view .cs-quote-inner { padding-left:16px; padding-right:16px; }
    }

    /* ═══ Existence · mobile static flow (html.cs-static, set once at load ≤820px) ═══
       Deck skipped → sections stack and scroll natively. The ≤900 block above
       already stacks the ledger grid and steps the type down; this adds the side
       gutter the ledger has never carried, the prose cut, and full-bleed assets. */

    html.cs-static #existence-view { --cs-navpad:104px; }

    html.cs-static #existence-view .cs-ledger { padding:56px var(--cs-pad) 48px; gap:24px; }
    html.cs-static #existence-view .cs-ledger-p { display:none; }
    html.cs-static #existence-view .cs-ledger-label { margin-top:24px; padding-bottom:16px; }
    html.cs-static #existence-view .cs-ledger-row { padding:14px 0; }
    /* restate the single-column stacking: the ≤900 block that sets it is
       out-specified by any html.cs-static rule touching .cs-ledger-row */
    html.cs-static #existence-view .cs-work .cs-ledger-row,
    html.cs-static #existence-view .cs-out .cs-ledger-row {
      grid-template-columns:1fr; gap:4px; align-items:start; }
    /* term → definition rows: keep the term, drop the prose */
    html.cs-static #existence-view .cs-work .cs-ledger-desc { display:none; }
    html.cs-static #existence-view .cs-ledger-key { padding-right:0; font-size:17px; }
    /* stat rows keep their caption — the number means nothing without it */
    html.cs-static #existence-view .cs-out .cs-ledger-desc { display:block; }

    /* 02 → 03 divider: both bands are cream, so in the static stack they run
       together with nothing but padding between them. A hairline at the top of
       03 marks the seam. Inset to the gutter rather than full-bleed so it reads
       as part of the ledger's own rule system, and drawn as a pseudo-element
       because a border-top would sit on the full-width padding box. --lg-line is
       declared on the band and already flips for night, so the colour follows. */
    html.cs-static #existence-view .cs-out .cs-ledger { position:relative; }
    html.cs-static #existence-view .cs-out .cs-ledger::before {
      content:''; position:absolute; top:0; left:var(--cs-pad); right:var(--cs-pad);
      height:1px; background:var(--lg-line); }

    /* app-screen recordings: three phones side by side leaves ~110px each, so
       stack them full-bleed — one phone per row, same as every other mobile
       asset run. The frame is a CSS bezel, so it scales with the width. */
    html.cs-static #existence-view .cs-ex-screens { flex-direction:column;
      align-items:center; gap:64px;
      padding:var(--cs-navpad) var(--cs-pad) calc(var(--cs-pad) * 2); }
    html.cs-static #existence-view .cs-ex-screen { flex:0 0 auto; width:100%; gap:14px; }
    html.cs-static #existence-view .cs-ex-screen-vid { width:100%; height:auto;
      max-height:none; padding:8px; border-radius:44px; }
    html.cs-static #existence-view .cs-ex-screen-vid video { border-radius:38px; }
    html.cs-static #existence-view .cs-ex-screen-vid::before { height:96px; }
    html.cs-static #existence-view .cs-ex-screen-vid::after  { height:76px; }
    html.cs-static #existence-view .cs-ex-screen-label { font-size:12px; letter-spacing:.08em; }

    /* intro reel: give the beat the whole screen instead of the 306px sliver it
       was collapsing to between two full-height sections. Rob's reel is a
       LANDSCAPE 16:9 video, so the frame's height is capped by the column width
       — 100svh buys air around it, it does not make the video taller. svh not
       vh: dvh is broken on iOS here and lvh would sit under the toolbar.
       --reel-w narrows to the padded column so the frame lines up with the
       heading instead of overshooting the gutter (min(960px, 92vw) = 345px in a
       319px column). It must stay a LENGTH, not 100%: --reel-w also feeds the
       frame's height calc, where a percentage would resolve against the
       container's height and collapse the frame. */
    html.cs-static #existence-view .cs-ex-reel { min-height:100svh;
      justify-content:center; gap:28px;
      padding-top:var(--cs-navpad); padding-bottom:var(--cs-pad); }
    html.cs-static #existence-view .cs-reel-frame {
      --reel-w:calc(100vw - 2 * var(--cs-pad)); }

    /* CTA + read-next */
    /* full-screen close: give the final ask its own beat instead of a
       content-height sliver between the screens stack and read-next.
       Same recipe as .cs-ex-reel above — flex-centre inside 100svh, padded
       to nav clearance / --cs-pad. Targets .cs-cta-inner (the .cs-wrap),
       not the .cs-band section, since the section has no box to centre in. */
    html.cs-static #existence-view .cs-cta-inner { min-height:100svh;
      display:flex; flex-direction:column; align-items:center; justify-content:center;
      padding-top:var(--cs-navpad); padding-bottom:var(--cs-pad); box-sizing:border-box; }
    html.cs-static #existence-view .cs-cta-h { font-size:34px; }
    /* full-screen close, same recipe as .cs-cta-inner above: this is the last
       beat of the case study, so it gets its own screen rather than a
       content-height sliver. The two .cs-ph thumbnails stay hidden (they're
       desktop-grid artwork with no mobile crop yet) so centring the bare
       kicker + heading is what fills the space. */

    /* ── Hatcha mobile/tablet: single-column ledger + smaller type (placed after
       the base .cs-ledger rules so it wins). Panels that then exceed 100vh simply
       read-scroll (readH>0) — the cover-slide still works. */
    @media (max-width: 900px) {
      #hatcha-view .cs-ledger { grid-template-columns:1fr; gap:26px; }
      #hatcha-view .cs-ledger-num { font-size:64px; }
      #hatcha-view .cs-ledger-eye { margin-top:10px; }
      #hatcha-view .cs-ledger-h { font-size:32px; }
      #hatcha-view .cs-ledger-p { font-size:16px; margin-top:22px; }
      #hatcha-view .cs-ledger-label { margin-top:34px; padding-bottom:22px; }
      #hatcha-view .cs-ledger-before, #hatcha-view .cs-ledger-after { font-size:15px; }
      #hatcha-view .cs-ledger-row, #hatcha-view .cs-ledger-hrow { grid-template-columns:1fr 30px 1fr; }
      #hatcha-view .cs-dir .cs-ledger-row, #hatcha-view .cs-chat .cs-ledger-row,
      #hatcha-view .cs-show .cs-ledger-row {
        grid-template-columns:1fr; gap:4px; }
      #hatcha-view .cs-out .cs-ledger-row { grid-template-columns:1fr; gap:4px; }
      #hatcha-view .cs-ledger-key, #hatcha-view .cs-ledger-stat { padding-right:0; }
    }

    /* ═══ Hatcha · mobile static flow (html.cs-static, set once at load ≤820px) ═══
       Deck skipped → sections stack and scroll natively. IMPORTANT: nearly all of
       Hatcha's panel CSS above is scoped `.cs-slide-sec.cs-panel`, and the JS
       creates that class — so in static flow every one of those rules is inert and
       these overrides answer to the BASE band layout instead. That base layout
       assumes a rail column to escape from; with no rail here, its negative
       margins would hang content off the left edge. The ≤900 block above already
       stacks the ledger grid and steps the type down; this adds the gutter the
       ledger has never carried, the prose cut, and full-bleed assets. */

    html.cs-static #hatcha-view { --cs-navpad:104px; }

    html.cs-static #hatcha-view .cs-ledger { padding:56px var(--cs-pad) 48px; gap:24px; }
    html.cs-static #hatcha-view .cs-ledger-p { display:none; }
    html.cs-static #hatcha-view .cs-ledger-label { margin-top:24px; padding-bottom:16px; }
    html.cs-static #hatcha-view .cs-ledger-row { padding:14px 0; }
    html.cs-static #hatcha-view .cs-ledger-row,
    html.cs-static #hatcha-view .cs-ledger-hrow { grid-template-columns:1fr 28px 1fr; }
    html.cs-static #hatcha-view .cs-ledger-before { padding-right:6px; }
    html.cs-static #hatcha-view .cs-ledger-after { padding-left:6px; }
    /* The two-column row rule above out-specifies the ≤900 block's single-column
       stacking, so every non-before/after row has to restate it here — otherwise
       terms and stat captions get squeezed into a ~160px column and wrap to one
       word per line. */
    html.cs-static #hatcha-view .cs-show .cs-ledger-row,
    html.cs-static #hatcha-view .cs-dir .cs-ledger-row,
    html.cs-static #hatcha-view .cs-chat .cs-ledger-row,
    html.cs-static #hatcha-view .cs-out .cs-ledger-row {
      grid-template-columns:1fr; gap:4px; align-items:start; }
    /* term → definition rows: keep the term and its sub-label, drop the prose */
    html.cs-static #hatcha-view .cs-show .cs-ledger-desc,
    html.cs-static #hatcha-view .cs-dir .cs-ledger-desc,
    html.cs-static #hatcha-view .cs-chat .cs-ledger-desc { display:none; }
    /* stat rows keep their caption — the number means nothing without it */
    html.cs-static #hatcha-view .cs-out .cs-ledger-desc { display:block; }

    html.cs-static #hatcha-view .cs-quote p { font-size:22px; }

    /* rail-escape resets: there is no rail column to escape in static flow */
    html.cs-static #hatcha-view .cs-crit,
    html.cs-static #hatcha-view .cs-crit-eye,
    html.cs-static #hatcha-view .cs-phones { margin-left:0; }

    /* 02b criteria cards — one per row inside the gutter */
    /* padding-top does double duty: it's the panel's own top clearance AND it
       stops margin collapse. The desktop override that zeroes .cs-crit-note's
       top margin is scoped to .cs-slide-sec, a class the JS deck never adds in
       static flow — so without a padding/border barrier here, the base
       .cs-ledger-note { margin-top:34px } collapses straight through this panel
       and the dark <section>, pushing the section's rendered box down 34px and
       exposing the cream body background in the gap. Zero the note's own
       margin-top too so it doesn't just re-add the same gap below the padding. */
    html.cs-static #hatcha-view .cs-crit-panel { padding:40px var(--cs-pad) 64px; }
    html.cs-static #hatcha-view .cs-crit-note { margin-top:0; }
    html.cs-static #hatcha-view .cs-crit { grid-template-columns:1fr; gap:28px; }
    html.cs-static #hatcha-view .cs-crit > div { max-width:none; }
    html.cs-static #hatcha-view .cs-crit-eye { margin:64px 0 24px; font-size:14px; }

    /* 03b framework phones — small phone + label column side by side, same
       mechanism as desktop: .cs-annot is position:relative and stretches to
       the phone's height (align-items:stretch on the row, inherited from the
       base rule), which is what gives the % top-positioned labels something
       to measure against. An earlier pass stacked this into a full-bleed phone
       with the labels dropped below as a plain list; the user asked for the
       real side-by-side layout instead, shrunk to fit the mobile gutter. */
    html.cs-static #hatcha-view .cs-fw-inner { padding:0 var(--cs-pad) 72px; }
    /* the desktop rule giving .cs-fw-note a 30px bottom margin is scoped to
       .cs-slide-sec.cs-panel.cs-fw, which static flow never adds — so the base
       .cs-ledger-note has no margin-bottom at all here and the caption below it
       sits flush against the note's last line. */
    html.cs-static #hatcha-view .cs-fw-note { margin-bottom:56px; }
    html.cs-static #hatcha-view .cs-phones { grid-template-columns:1fr; gap:44px; margin-top:40px; }
    html.cs-static #hatcha-view .cs-phone-row { gap:14px; }
    /* alternate the two rows — asset/labels, then labels/asset — instead of the
       same order twice, so the eye zigzags down the block instead of reading
       two identical-looking rows in a row. */
    html.cs-static #hatcha-view .cs-phones .cs-phone-row:nth-child(2) { flex-direction:row-reverse; }
    html.cs-static #hatcha-view .cs-phone-fig { flex:0 1 46%; max-width:46%; }
    html.cs-static #hatcha-view .cs-annot { flex:1 1 auto; }
    html.cs-static #hatcha-view .cs-annot-k { font-size:9px; }
    html.cs-static #hatcha-view .cs-annot-v { font-size:11.5px; }

    /* 04b design-system gallery — stacked full-bleed, same treatment as every
       other mobile asset run (its clone + auto-scroll loop is gated off in JS) */
    html.cs-static #hatcha-view .cs-gal-marq::before,
    html.cs-static #hatcha-view .cs-gal-marq::after { display:none; }
    html.cs-static #hatcha-view .cs-gallery { display:grid; grid-template-columns:1fr;
      gap:12px; padding:0; overflow:visible; }
    html.cs-static #hatcha-view .cs-ds-tile { width:auto; height:auto; border-radius:0; }
    html.cs-static #hatcha-view .cs-ds-tile.t-sq   { aspect-ratio:1/1; }
    html.cs-static #hatcha-view .cs-ds-tile.t-tall { aspect-ratio:450/610; }
    html.cs-static #hatcha-view .cs-ds-tile.t-port { aspect-ratio:415/470; }

    /* 05b/c theme panels — same white-band bug as the crit panel earlier: the
       desktop deck wraps every panel in .cs-slide-inner, which has real
       padding-top and blocks margin collapse. .cs-theme-grid (64px) and
       .cs-theme-lower (110px) both carry an unscoped top margin meant to be
       absorbed by that padding; without it in static flow, the margin
       collapses straight through .cs-wrap and the dark <section>, pushing the
       section's rendered box down and exposing the cream body background. */
    html.cs-static #hatcha-view .cs-theme-p .cs-theme-grid { margin-top:0; padding-top:56px; }
    html.cs-static #hatcha-view .cs-theme-b .cs-theme-lower { margin-top:0; padding-top:56px; }
    /* taller than the native 949/860 (desktop) ratio — the video is object-fit:
       cover, so this crops its left/right edges a bit to gain height rather
       than letterboxing; the swatch strip sits at the far right edge with
       margin to spare, so it survives this crop. */
    html.cs-static #hatcha-view .cs-theme-p .cs-palette { aspect-ratio:5/6; }
    /* .cs-ledger.header-only carries its own unscoped 40px margin-bottom —
       same collapse-through-the-section bug, just on the bottom edge instead
       of the top: with no padding-bottom on the <section> to contain it, the
       margin escapes into the gap before the NEXT section instead of showing
       up in this section's own rect, which is why it doesn't show up when
       measuring the section's own box (only in the gap to its sibling). */
    html.cs-static #hatcha-view .cs-ledger.header-only { margin-bottom:0; }

    /* read-next — drop the placeholder tiles, keep the centred link */
    /* full-screen close, same recipe used on Existence's read-next: this is
       the case study's last beat, so it gets its own screen instead of a
       content-height sliver. The two .cs-ph thumbnails stay hidden (desktop-
       grid artwork, no mobile crop exists) so the centred kicker + heading
       fills the space alone. */
  

    /* ═══════════════ Work / Jumpable case study (routed via #jumpable) ═══════════════
       Cover-slide 100vh panels + editorial ledger (the Existence/Fireflut model). Accent
       is a single CHROMA brand hue — the app moved from a rainbow palette to one primary
       colour, so the accent carries that story. Scoped tokens + scoped ledger/stage CSS. */
    #jumpable-view { --cs-serif:'DM Serif Display', Georgia, serif;
                     --cs-sans:'Space Grotesk', sans-serif;
                     --cs-cream:#fff9f1; --cs-black:#000; --cs-ink:#0b0b0b;
                     --cs-accent:#0FB9D6;              /* TODO: replace with Zainab's Jumpable brand hex */
                     --cs-max:1320px; --cs-pad:60px; --cs-rail:355px;
                     --cs-navpad:112px; }             /* panel top clearance = 80px nav + 32px */

    /* tile → hero shared-element zoom (same mechanism as Existence/Fireflut) */
    body.vt-hero a.project-tile[href="/work/jumpable"] { view-transition-name: jumpable-hero; }
    body.vt-hero #jumpable-view .cs-hero .cs-hero-mock { view-transition-name: jumpable-hero; }
    ::view-transition-old(jumpable-hero),
    ::view-transition-new(jumpable-hero) { width:100%; height:100%; object-fit:cover; }
    ::view-transition-group(jumpable-hero) {
      animation-duration: 0.6s; animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1); }
    @media (prefers-reduced-motion: reduce) {
      ::view-transition-group(jumpable-hero),
      ::view-transition-old(jumpable-hero),
      ::view-transition-new(jumpable-hero) { animation: none; } }

    /* hero reads as two full-screen beats: the title/meta chunk, then the device mockup
       (same fix as #fireflut-view and #existence-view). Read-scroll travel is
       inner.scrollHeight − 100vh, which ignores the SECTION's own padding — so with
       .cs-hero's 138px nav clearance on the section the mockup could only ever rise to
       138px and its bottom stayed clipped. Move that clearance onto the text chunk: the
       text lands where it did and the mockup's rest state is a full 100vh × 100vw frame. */
    #jumpable-view .cs-slide-sec.cs-hero { padding-top: 0; }
    #jumpable-view .cs-slide-sec.cs-hero .cs-hero-grid { padding-top: 138px; }

    /* hero placeholder — own class (not .cs-ph) so its clip never fights the tile→hero morph */
    #jumpable-view .cs-jp-heroph { position:absolute; inset:0; width:100%; height:100%;
      display:flex; align-items:center; justify-content:center;
      background:repeating-linear-gradient(45deg,#e7e0d2,#e7e0d2 14px,#ddd5c5 14px,#ddd5c5 28px); color:#8a8474; }
    #jumpable-view .cs-jp-heroph span { padding:8px 14px; background:rgba(0,0,0,0.5); color:#fff;
      border-radius:100px; font-size:12px; letter-spacing:0.04em; }

    /* hero tagline — italic serif subtitle under the "Jumpable" title, muted so the title leads */
    #jumpable-view .cs-hero-tagline { margin:16px 0 0; font-family:var(--cs-serif);
      font-style:normal; font-weight:400; font-size:34px; line-height:1.2;
      letter-spacing:0.01em; color:rgba(11,11,11,0.6); }
    body.night #jumpable-view .cs-hero-tagline { color:rgba(255,249,233,0.7); }
    /* align the ROLE/CLIENT meta labels with the top of the intro paragraph */
    #jumpable-view .cs-meta { margin-top:157px; }

    /* ── cover-slide stage (scoped; identical mechanic to Existence) ── */
    #jumpable-view .cs-slide-scroller { position: relative; }
    #jumpable-view .cs-slide-stage { position: sticky; top: 0; height: 100vh; overflow: hidden; isolation: isolate; perspective: 1700px; perspective-origin: 50% 50%; }
    #jumpable-view .cs-slide-sec { position: absolute; inset: 0; overflow: hidden; }
    #jumpable-view .cs-slide-sec.cv { box-shadow: -26px 0 60px rgba(0,0,0,0.16); }
    #jumpable-view .cs-slide-inner { }
    #jumpable-view .cs-slide-sec.cs-band { min-height: 0; }
    #jumpable-view .cs-slide-sec.cs-panel .cs-slide-inner {
      min-height: 100vh; box-sizing: border-box;
      padding-top: var(--cs-navpad); padding-bottom: var(--cs-pad);
      display: flex; flex-direction: column; justify-content: center; }
    /* closing / read-next carry old-scroll band paddings — zero them inside a centred panel */
    #jumpable-view .cs-slide-sec.cs-panel.cs-closing { padding-top: 0; padding-bottom: 0; }

    /* ── full-bleed media panels (placeholder Watch screens; swap real exports later) ── */
    #jumpable-view .cs-jp-shots { display:grid; gap:16px; width:100%;
      max-width:calc(var(--cs-max) + 2 * var(--cs-gut)); margin:0 auto;
      padding-left:var(--cs-gut); padding-right:var(--cs-gut); }
    #jumpable-view .cs-jp-shots.two   { grid-template-columns:repeat(2,1fr); }
    #jumpable-view .cs-jp-shots.three { grid-template-columns:repeat(3,1fr); }
    #jumpable-view .cs-slide-sec.cs-panel .cs-jp-shots .cs-ph {
      height: calc(100vh - var(--cs-navpad) - var(--cs-pad)); border-radius:10px; }

    /* full-bleed device shot panel (Jumpable on Apple Watch Ultra) — fills the panel within the gutters */
    #jumpable-view .cs-slide-sec.cs-panel.cs-jp-device .cs-slide-inner {
      padding-left: var(--cs-pad); padding-right: var(--cs-pad); }
    #jumpable-view .cs-jp-device-media { position:relative; width:100%;
      height: calc(100vh - var(--cs-navpad) - var(--cs-pad)); border-radius:10px; overflow:hidden;
      background:#0a0a0a; display:flex; align-items:center; justify-content:center; }
    #jumpable-view .cs-jp-device-ph { font-family:var(--cs-sans); font-size:12px; letter-spacing:.06em;
      text-transform:uppercase; color:rgba(255,255,255,.5); }
    #jumpable-view .cs-jp-device-media img { position:absolute; inset:0; width:100%; height:100%;
      object-fit:cover; object-position:center; display:block; }
    /* 3-watch product shot: transparent PNG — let the band show through (adapts day/night) and show the whole shot */
    #jumpable-view .cs-threewatch .cs-jp-device-media { background:transparent; }
    #jumpable-view .cs-threewatch .cs-jp-device-media img { object-fit:contain; }
    /* Phase 2 media: single home-screen recording, full height within the gutters, centred; the band shows around it so the bg adapts day/night */
    /* Phase 2 media: home-screen recording (design points baked into the asset), full height within the gutters, centred */
    #jumpable-view .cs-home-video-wrap { width:100%; height:calc(100vh - var(--cs-navpad) - var(--cs-pad));
      display:flex; align-items:center; justify-content:center; }
    #jumpable-view .cs-home-video-wrap video { height:100%; width:auto; max-width:100%; display:block;
      border-radius:16px; object-fit:contain; }
    /* desktop: drop the tall nav reservation on the recording panels (the nav floats over the dark top
       edge, like the full-bleed device shots) and centre the video with symmetric gutters. This reclaims
       the empty top band AND lets the video grow, with the wrap height matched to the box so it never scrolls. */
    @media (min-width:1100px){
      #jumpable-view .cs-slide-sec.cs-panel.cs-onb-video .cs-slide-inner,
      #jumpable-view .cs-slide-sec.cs-panel.cs-home-video .cs-slide-inner {
        padding-top: var(--cs-pad); padding-bottom: var(--cs-pad); }
      #jumpable-view .cs-home-video-wrap { height:calc(100vh - (var(--cs-pad) * 2)); }
    }
    /* EXCEPTION: keep the recording slides (home + onboarding) black in BOTH day and night (matches the video's baked black bg) */
    #jumpable-view .cs-home-video.cs-band.dark, #jumpable-view .cs-onb-video.cs-band.dark,
    body.night #jumpable-view .cs-home-video.cs-band.dark, body.night #jumpable-view .cs-onb-video.cs-band.dark { background:#000; }

    /* ── editorial ledger (cloned from Existence; chroma accent via --lg-* tokens) ── */
    #jumpable-view .cs-ledger { align-self:center; width:100%;
      max-width:calc(var(--cs-max) + 2 * var(--cs-gut));
      padding-left:var(--cs-gut); padding-right:var(--cs-gut);
      display:grid; grid-template-columns:220px minmax(0,1fr); gap:64px; box-sizing:border-box; }
    #jumpable-view .cs-ledger.header-only { margin-bottom:40px; }
    #jumpable-view .cs-ledger-num { font-family:var(--cs-serif); font-weight:400;
      font-size:96px; line-height:.9; letter-spacing:.02em; color:var(--lg-num); }
    #jumpable-view .cs-ledger-eye { margin-top:18px; font-family:var(--cs-sans);
      font-size:12px; font-weight:500; letter-spacing:.18em; text-transform:uppercase; color:var(--lg-faint); }
    #jumpable-view .cs-ledger-h { margin:0; font-family:var(--cs-serif); font-weight:400;
      font-size:52px; line-height:1.07; letter-spacing:0; color:var(--lg-ink); text-wrap:balance; }
    #jumpable-view .cs-ledger-h i, #jumpable-view .cs-ledger-h em { font-style:italic; }
    #jumpable-view .cs-ledger-p { margin:30px 0 0; max-width:780px; font-family:var(--cs-sans);
      font-size:19px; line-height:1.6; color:var(--lg-ink); opacity:.86; }
    #jumpable-view .cs-ledger-p + .cs-ledger-p { margin-top:18px; }
    /* problem intro (slide 01): match the heading's single-line measure on desktop+ so right edges align */
    @media (min-width:1100px){ #jumpable-view .cs-show .cs-ledger-p { max-width:925px; } }
    /* approach (slide 03): questions on the left, illustration on the right */
    #jumpable-view .cs-appr-split { grid-template-columns:minmax(0,1fr) minmax(0,620px); gap:56px;
      max-width:calc(var(--cs-max) + 2 * var(--cs-gut)); align-items:center; }
    #jumpable-view .cs-appr-media { width:100%; }
    #jumpable-view .cs-appr-media img { width:100%; height:auto; display:block; border-radius:12px;
      /* top edge fades to transparent so it blends into the band (any theme colour) */
      -webkit-mask-image:linear-gradient(to bottom, transparent 0%, #000 22%);
              mask-image:linear-gradient(to bottom, transparent 0%, #000 22%); }
    /* night: the band flips to cream — invert the dark illustration so it blends (hue-rotate keeps the teal glow) */
    body.night #jumpable-view .cs-appr-media img { filter:invert(1) hue-rotate(180deg); }
    @media (max-width:900px){ #jumpable-view .cs-appr-split { grid-template-columns:1fr; gap:32px; } }
    /* closing heading: break "Now they land" onto its own line on desktop+ */
    #jumpable-view .cs-closing-br { display:none; }
    @media (min-width:1100px){ #jumpable-view .cs-closing-br { display:inline; } }
    #jumpable-view .cs-ledger-label { margin-top:40px; padding-bottom:26px; display:flex; align-items:center; gap:16px; }
    #jumpable-view .cs-ledger-label > span:first-child { font-family:var(--cs-sans);
      font-size:12px; font-weight:500; letter-spacing:.16em; text-transform:uppercase; color:var(--lg-faint); }
    #jumpable-view .cs-ledger-rule { flex:1; height:1px; background:var(--lg-line); }
    #jumpable-view .cs-ledger-rows { margin-top:8px; }
    #jumpable-view .cs-ledger-row { display:grid; grid-template-columns:1fr 44px 1fr;
      align-items:center; padding:16px 0; border-bottom:1px solid var(--lg-line); }
    #jumpable-view .cs-ledger-row:last-child { border-bottom:0; }
    #jumpable-view .cs-ledger-hrow { align-items:end; padding:0 0 12px; border-bottom:1px solid var(--lg-line2); }
    #jumpable-view .cs-ledger-before { padding-right:24px; font-family:var(--cs-sans);
      font-size:18px; line-height:1.4; color:var(--lg-mut); }
    #jumpable-view .cs-ledger-after { padding-left:24px; font-family:var(--cs-sans);
      font-size:18px; line-height:1.4; color:var(--lg-ink); font-weight:500; }
    #jumpable-view .cs-ledger-arrow { display:flex; justify-content:center;
      font-family:var(--cs-sans); font-size:20px; color:var(--lg-accent); }
    #jumpable-view .cs-ledger-hrow .cs-ledger-before,
    #jumpable-view .cs-ledger-hrow .cs-ledger-after { font-size:11px; font-weight:600;
      letter-spacing:.14em; text-transform:uppercase; }
    #jumpable-view .cs-ledger-hrow .cs-ledger-before { color:var(--lg-faint); }
    #jumpable-view .cs-ledger-hrow .cs-ledger-after { color:var(--lg-accent); }
    /* term → definition rows (showcase / approach / design language) */
    #jumpable-view .cs-show .cs-ledger-row,
    #jumpable-view .cs-appr .cs-ledger-row,
    #jumpable-view .cs-lang .cs-ledger-row { grid-template-columns:300px minmax(0,1fr); align-items:baseline; }
    #jumpable-view .cs-ledger-key { padding-right:32px; font-family:var(--cs-sans);
      font-size:19px; font-weight:600; line-height:1.35; color:var(--lg-ink); }
    #jumpable-view .cs-ledger-key .cs-ledger-sub { display:block; margin-top:6px; font-size:13px;
      font-weight:400; letter-spacing:.02em; color:var(--lg-mut); }
    #jumpable-view .cs-ledger-desc { font-family:var(--cs-sans); font-size:16px; line-height:1.55; color:var(--lg-mut); }
    /* stat → caption rows (outcome / impact) */
    #jumpable-view .cs-out .cs-ledger-row,
    #jumpable-view .cs-impact .cs-ledger-row { grid-template-columns:210px minmax(0,1fr); align-items:baseline; }
    #jumpable-view .cs-ledger-stat { padding-right:32px; font-family:var(--cs-serif);
      font-weight:400; font-size:30px; letter-spacing:.02em; line-height:1.1; color:var(--lg-ink); }
    /* impact: big figures read best centred against their caption (baseline sits the number too high) */
    #jumpable-view .cs-impact .cs-ledger-row { align-items:center; }
    /* impact: the stat column carries hard figures, so scale it up to read as a hero metric */
    #jumpable-view .cs-impact .cs-ledger-stat { font-size:46px; letter-spacing:0; line-height:1; }
    /* qualitative impact row: a word label, not a figure — sit it back down so it reads as a caption, not a stat */
    #jumpable-view .cs-impact .cs-ledger-row.cs-qual .cs-ledger-stat { font-size:26px; letter-spacing:.01em; }
    /* flush "My contribution" note (left-border, chroma accent) */
    #jumpable-view .cs-ledger-note { margin-top:36px; max-width:820px; padding-left:20px;
      border-left:2px solid var(--lg-accent); }
    #jumpable-view .cs-ledger-note .lbl { font-family:var(--cs-sans); font-size:12px; font-weight:500;
      letter-spacing:.16em; text-transform:uppercase; color:var(--lg-accent); margin-bottom:10px; }
    #jumpable-view .cs-ledger-note .bd { font-family:var(--cs-sans); font-size:15.5px; line-height:1.62; color:var(--lg-mut); }
    #jumpable-view .cs-note-list { margin:14px 0 0; padding-left:18px; list-style:disc; }
    #jumpable-view .cs-note-list li { font-family:var(--cs-sans); font-size:15.5px; line-height:1.6; color:var(--lg-mut); margin-top:9px; }
    #jumpable-view .cs-note-list li::marker { color:var(--lg-accent); }
    #jumpable-view .cs-note-list strong { color:var(--lg-ink); font-weight:600; }

    /* ── ledger tokens ── cream bands warm-grey; dark bands light-on-dark. Accent stays chroma. */
    #jumpable-view .cs-show, #jumpable-view .cs-prob, #jumpable-view .cs-onb,
    #jumpable-view .cs-home, #jumpable-view .cs-lang, #jumpable-view .cs-out {
      --lg-num:#c9bea9; --lg-ink:#17150f; --lg-mut:#8a8271; --lg-faint:#a79c89; --lg-meta:#8a7f6c;
      --lg-line:rgba(23,21,15,.12); --lg-line2:rgba(23,21,15,.2); --lg-accent:var(--cs-accent); }
    #jumpable-view .cs-appr, #jumpable-view .cs-impact {
      --lg-num:#3c3c3c; --lg-ink:#fff9ea; --lg-mut:rgba(255,249,233,.62); --lg-faint:rgba(255,249,233,.45); --lg-meta:rgba(255,249,233,.5);
      --lg-line:rgba(255,255,255,.14); --lg-line2:rgba(255,255,255,.22); --lg-accent:var(--cs-accent); }

    /* ── NIGHT THEME (cream bands go night-dark, dark bands invert to day-cream) ── */
    body.night #jumpable-view { --cs-cream:var(--c-night); --cs-ink:var(--c-cream-2); }
    body.night #jumpable-view .cs-show, body.night #jumpable-view .cs-prob,
    body.night #jumpable-view .cs-onb, body.night #jumpable-view .cs-home,
    body.night #jumpable-view .cs-lang, body.night #jumpable-view .cs-out {
      --lg-num:#3a3a44; --lg-ink:#fff9e9; --lg-mut:rgba(255,249,233,.6); --lg-faint:rgba(255,249,233,.5); --lg-meta:rgba(255,249,233,.55);
      --lg-line:rgba(255,255,255,.12); --lg-line2:rgba(255,255,255,.22); --lg-accent:var(--cs-accent); }
    body.night #jumpable-view .cs-appr, body.night #jumpable-view .cs-impact {
      --lg-num:#c9bea9; --lg-ink:#17150f; --lg-mut:#8a8271; --lg-faint:#a79c89; --lg-meta:#8a7f6c;
      --lg-line:rgba(23,21,15,.12); --lg-line2:rgba(23,21,15,.2); --lg-accent:var(--cs-accent); }
    body.night #jumpable-view .cs-hero-intro { color:rgba(255,249,233,0.88); }
    body.night #jumpable-view .cs-closing-p { color:rgba(255,249,233,0.78); }
    body.night #jumpable-view .cs-ph:not(.dark), body.night #jumpable-view .cs-jp-heroph {
      background:repeating-linear-gradient(45deg,#14141a,#14141a 14px,#1b1b22 14px,#1b1b22 28px); color:#6b6b6b; }
    /* dark bands INVERT to day-cream with ink text at night */
    body.night #jumpable-view .cs-band.dark { background:#fff9f1; color:#0b0b0b; }
    body.night #jumpable-view .cs-band.dark .cs-p { color:rgba(11,11,11,0.9); }
    body.night #jumpable-view .cs-band.dark .cs-ph.dark {
      background:repeating-linear-gradient(45deg,#e7e0d2,#e7e0d2 14px,#ddd5c5 14px,#ddd5c5 28px); color:#8a8474; }

    /* ── responsive ── */
    @media (max-width:1100px){
      #jumpable-view { --cs-pad:28px; --cs-rail:180px; --cs-navpad:104px; }
      #jumpable-view .cs-hero-h { font-size:44px; }
      #jumpable-view .cs-hero-tagline { font-size:26px; }
      #jumpable-view .cs-meta { margin-top:8px; }
    }
    @media (max-width:900px){
      #jumpable-view .cs-ledger { grid-template-columns:1fr; gap:26px; }
      #jumpable-view .cs-ledger-num { font-size:64px; }
      #jumpable-view .cs-ledger-eye { margin-top:10px; }
      #jumpable-view .cs-ledger-h { font-size:32px; }
      #jumpable-view .cs-ledger-p { font-size:16px; margin-top:22px; }
      #jumpable-view .cs-ledger-label { margin-top:30px; padding-bottom:20px; }
      #jumpable-view .cs-ledger-before, #jumpable-view .cs-ledger-after { font-size:15px; }
      #jumpable-view .cs-ledger-row, #jumpable-view .cs-ledger-hrow { grid-template-columns:1fr 30px 1fr; }
      #jumpable-view .cs-show .cs-ledger-row, #jumpable-view .cs-appr .cs-ledger-row,
      #jumpable-view .cs-lang .cs-ledger-row,
      #jumpable-view .cs-out .cs-ledger-row,
      #jumpable-view .cs-impact .cs-ledger-row { grid-template-columns:1fr; gap:6px; }
      #jumpable-view .cs-ledger-key, #jumpable-view .cs-ledger-stat { padding-right:0; }
    }
    @media (max-width:760px){
      #jumpable-view .cs-jp-shots.two, #jumpable-view .cs-jp-shots.three { grid-template-columns:1fr; }
      #jumpable-view .cs-slide-sec.cs-panel .cs-jp-shots .cs-ph { height:auto; aspect-ratio:320/560; }
    }
    @media (max-width:1320px){
      #jumpable-view .cs-quote-inner { padding-left:16px; padding-right:16px; }
    }

    /* ═══ Jumpable · mobile static flow (html.cs-static, set once at load ≤820px) ═══
       Deck skipped → sections stack and scroll natively. The ≤900 block above
       already stacks the ledger grid and steps the type down, and the ≤1100 block
       already drops --cs-navpad to 104px; this adds the side gutter the ledger has
       never carried, the prose cut, and full-bleed media. */

    html.cs-static #jumpable-view .cs-ledger { padding:56px var(--cs-pad) 48px; gap:24px; }
    html.cs-static #jumpable-view .cs-ledger-label { margin-top:24px; padding-bottom:16px; }
    html.cs-static #jumpable-view .cs-ledger-row { padding:14px 0; }
    /* restate the single-column stacking — any html.cs-static rule touching
       .cs-ledger-row out-specifies the ≤900 block that sets it */
    html.cs-static #jumpable-view .cs-impact .cs-ledger-row,
    html.cs-static #jumpable-view .cs-appr .cs-ledger-row {
      grid-template-columns:1fr; gap:4px; align-items:start; }
    /* 02 impact: stat rows keep their caption — the label means nothing without it */
    html.cs-static #jumpable-view .cs-impact .cs-ledger-desc { display:block; }
    /* 04 approach: keep the three founder questions, drop their answers */
    html.cs-static #jumpable-view .cs-appr .cs-ledger-desc { display:none; }
    html.cs-static #jumpable-view .cs-ledger-key { padding-right:0; font-size:17px; }
    /* drop the intro prose everywhere it sits above data rows or a media panel.
       01 is the exception: its paragraph is the whole panel — there are no rows
       and no asset under it, so hiding it would leave a bare heading. */
    html.cs-static #jumpable-view .cs-ledger-p { display:none; }
    html.cs-static #jumpable-view .cs-show .cs-ledger-p { display:block; }
    /* 05 + 06 are the exception to that rule: they're header-only ledgers whose whole
       panel is a number, an eyebrow and a heading, so hiding the prose left the two
       phase sections stating WHAT changed with no word on why. The recordings that
       follow show the result but can't carry the reasoning. Restored at a slightly
       tighter measure than desktop so each still fits its one screen. */
    html.cs-static #jumpable-view .cs-onb .cs-ledger-p,
    html.cs-static #jumpable-view .cs-home .cs-ledger-p {
      display:block; margin-top:22px; font-size:16px; line-height:1.62; }

    /* device shots + phase recordings: one full viewport, edge to edge.
       svh so the iOS toolbar never crops them. */
    html.cs-static #jumpable-view .cs-jp-device-media { height:100svh; border-radius:0; }
    /* The two phase recordings are wide 1294×918 frames with the watch filling only
       ~35% of the width — the rest is side annotations. Fitting that whole frame to
       a 393px viewport left the prototype ~137px wide, which is the thing people
       actually came to look at. So oversize the video and clip the gutters: the side
       text goes (the ledger copy already says all of it) and the watch lands ~2.3×
       bigger.

       The zoom is driven off WIDTH, not height, so the crop window is a fixed slice
       of the source at every viewport: width:259% shows 100/259 = 38.6% of the frame,
       i.e. 500 of 1294px, centred → video-x 397–897. That matters because the frame
       has teal annotation bars at x=100 and x=914 (measured, both clips) — a window
       driven off viewport height widens on short screens and slides one of them into
       view. This one can't. The watch spans 424–866, so it clears the crop by 27px
       left and 31px right.

       Wrap height still caps at 100svh so the panel never outgrows its screen; when
       that cap bites, the overflow is the frame's own black top/bottom margin, which
       is invisible against this panel (pinned to #000 in both themes). */
    html.cs-static #jumpable-view .cs-home-video-wrap {
      /* vh, matching the panel below — capping at svh would re-open the same gap the
         panel exception exists to close */
      height: min(183vw, 100vh);
      overflow: hidden;
    }
    html.cs-static #jumpable-view .cs-home-video-wrap video {
      width: 259%; height: auto;
      max-width: none;          /* the base rule caps at 100% — that's the fit we're undoing */
      flex: none;               /* the wrap is a flex row; without this it shrinks straight back */
      border-radius: 0;
    }
    /* landscape has no height to crop into — 259% would run the watch off the top and
       bottom. Fall back to fitting the whole frame; the annotations come back, which
       is fine at that width. */
    @media (orientation: landscape) {
      html.cs-static #jumpable-view .cs-home-video-wrap { height: auto; }
      html.cs-static #jumpable-view .cs-home-video-wrap video {
        width: 100%; max-height: 100svh;
      }
    }

    /* every panel gets its own full screen, same "one beat, one screen" rule
       already applied section-by-section elsewhere. Scoped to .cs-panel,
       which excludes only the hero (the one section without that class) —
       the hero already runs well past 100vh on its own from its copy + full-
       bleed mock, so forcing it into a flex-centred box would overflow its
       content symmetrically past both the top (behind the nav) and bottom.
       For every other panel, centering is provably safe: measured content
       heights top out at 857px (02 · impact) against an 812px viewport —
       whenever content already meets or exceeds min-height there is no
       slack left to redistribute, so justify-content:center is a no-op
       there and only pulls slack space for panels that are genuinely
       shorter than one screen (the two header-only ledgers, the two phase
       recordings, the three device shots already sized to 100svh by their
       own media rule below, and the closing quote). */
    html.cs-static #jumpable-view .cs-band.cs-panel { min-height:100svh;
      display:flex; flex-direction:column; justify-content:center; box-sizing:border-box; }
    /* EXCEPTION — the two recording panels take the LARGE viewport height. svh is the
       toolbar-VISIBLE height, so the moment iOS retracts its toolbar mid-scroll a
       100svh panel stops filling the screen and the neighbouring cream band slides
       into view above or below the black. Plain vh is the large height on iOS and
       Android alike, which is what these need. With the toolbar extended the panel
       just runs a little past the fold, and the overrun is the recording's own black
       margin — the watch still clears it (measured: bottom at 723px of 741 visible). */
    html.cs-static #jumpable-view .cs-onb-video.cs-band.cs-panel,
    html.cs-static #jumpable-view .cs-home-video.cs-band.cs-panel {  }

    /* read-next — drop the placeholder tiles, keep the centred link. Padding
       now matches the nav-aware recipe used on the other three case studies
       (var(--cs-navpad) top / var(--cs-pad) sides+bottom) instead of the old
       flat 96px, now that the section is flex-centred full height. */

:root {
  --ide-bg: #f9fafb;
  --ide-border-color: #000;
  --ide-text: #111827;
  --ide-kw: #c026d3;
  --ide-func: #0284c7;
  --ide-str: #16a34a;
  --ide-prop: #ca8a04;
  --ide-cmt: #6b7280;
  --ide-punct: #374151;
}

body.night {
  --ide-bg: #18181b;
  --ide-border-color: rgba(255, 255, 255, 0.1);
  --ide-text: #e4e4e7;
  --ide-kw: #f472b6;
  --ide-func: #38bdf8;
  --ide-str: #a3e635;
  --ide-prop: #fbbf24;
  --ide-cmt: #71717a;
  --ide-punct: #a1a1aa;
}

.ide-kw { color: var(--ide-kw); }
.ide-func { color: var(--ide-func); }
.ide-str { color: var(--ide-str); }
.ide-prop { color: var(--ide-prop); }
.ide-cmt { color: var(--ide-cmt); font-style: italic; }
.ide-punct { color: var(--ide-punct); }



.ts-bg-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: clamp(60px, 14vw, 210px);
  width: 100%;
  text-align: center;
  overflow: hidden;
  font-weight: 900;
  color: rgba(0, 0, 0, 0.08);
  white-space: nowrap;
  pointer-events: none;
  text-transform: uppercase;
  letter-spacing: -2px;
  z-index: 0;
}

.modern-card {
  background: rgba(255, 255, 255, 0.6);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 24px;
  padding: 32px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.05);
  display: flex;
  flex-direction: column;
  transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  position: relative;
  overflow: hidden;
  height: 100%;
}

body.night .modern-card {
  background: rgba(30, 30, 35, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.05);
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}

.modern-card:hover {
  transform: translateY(-8px) scale(1.02);
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.1);
  border-color: rgba(255, 255, 255, 0.8);
}

body.night .modern-card:hover {
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
  border-color: rgba(255, 255, 255, 0.15);
}

.card-icon {
  font-size: 32px;
  margin-bottom: 20px;
  background: linear-gradient(135deg, #f0f0f0, #e0e0e0);
  width: 60px;
  height: 60px;
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}

body.night .card-icon {
  background: linear-gradient(135deg, #2a2a30, #1a1a20);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.modern-card h3 {
  font-family: var(--font-head);
  font-size: 24px;
  font-weight: 700;
  margin: 0 0 16px 0;
  color: var(--c-ink);
  letter-spacing: -0.5px;
}

body.night .modern-card h3 {
  color: var(--c-cream);
}

.modern-card ul {
  list-style: none;
  padding: 0;
  margin: 0;
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.modern-card li {
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--c-ink);
  opacity: 0.8;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px dashed rgba(0, 0, 0, 0.1);
  padding-bottom: 8px;
}

body.night .modern-card li {
  color: var(--c-cream);
  opacity: 0.7;
  border-bottom: 1px dashed rgba(255, 255, 255, 0.1);
}

.modern-card li:last-child {
  border-bottom: none;
}

.modern-card li span {
  font-size: 12px;
  background: rgba(0, 0, 0, 0.05);
  padding: 4px 10px;
  border-radius: 20px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

body.night .modern-card li span {
  background: rgba(255, 255, 255, 0.1);
}
body.night .ts-bg-text {
  color: rgba(255, 255, 255, 0.08);
}



.ts-card {
  background: transparent;
  border-radius: 20px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.1);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
  min-height: 250px;
}

.ts-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 20px 40px rgba(0,0,0,0.2);
}

body.night .ts-card:hover {
  box-shadow: 0 20px 40px rgba(0,0,0,0.6);
}
