/* ==========================================================================
   STILE & GRAIN — showcase build
   Built to the ph-frontend standard: cascade layers, semantic tokens,
   progressive-enhancement premium layer. One bundle, no framework.
   ========================================================================== */

@layer reset, tokens, base, layout, components, motion, utilities;

/* ---- self-hosted variable faces (latin subset) --------------------------- */
@font-face {
  font-family: 'Fraunces';
  src: url('fonts/Fraunces-var.woff2') format('woff2-variations');
  font-weight: 300 700;
  font-display: swap;
  font-style: normal;
}
@font-face {
  font-family: 'Instrument Sans';
  src: url('fonts/InstrumentSans-var.woff2') format('woff2-variations');
  font-weight: 400 700;
  font-display: swap;
  font-style: normal;
}

/* ---- cross-document view transitions (unlayered, top level) -------------- */
@view-transition { navigation: auto; }

@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; }
  html { -webkit-text-size-adjust: 100%; }
  body { min-height: 100svh; -webkit-font-smoothing: antialiased; }
  img, svg, video { display: block; max-width: 100%; }
  input, button, textarea, select { font: inherit; color: inherit; }
  ul[class] { list-style: none; padding: 0; }
  a { color: inherit; }
}

@layer tokens {
  :root {
    /* ---- primitives ---- */
    --h-warm: 68;

    /* ---- semantic colour (matched to the photography) ---- */
    --chalk:   oklch(95.5% 0.008 var(--h-warm));
    --chalk-2: oklch(92%   0.012 var(--h-warm));
    --ink:     oklch(26%   0.018 var(--h-warm));
    --ink-2:   oklch(20%   0.016 var(--h-warm));
    --brass:   oklch(66%   0.095 78);
    --timber:  oklch(58%   0.045 62);

    --bg:      var(--chalk);
    --surface: oklch(97.5% 0.006 var(--h-warm));
    --text:    var(--ink);
    --muted:   oklch(50% 0.014 var(--h-warm));
    --line:    color-mix(in oklab, var(--ink) 15%, transparent);
    --line-soft: color-mix(in oklab, var(--ink) 8%, transparent);
    --brass-dim: color-mix(in oklab, var(--brass) 18%, transparent);

    /* ---- radius as three ROLES, not one default ---- */
    --radius-inset:   2px;   /* inputs, tags            */
    --radius-surface: 0;     /* cabinetry is square     */
    --radius-feature: 50%;   /* the brass seal only     */
    --radius-pill:    999px;

    /* ---- elevation as three ROLES ---- */
    --shadow-flat:     none;
    --shadow-lifted:   0 1px 2px oklch(26% 0.02 var(--h-warm) / 0.10),
                       0 10px 24px oklch(26% 0.02 var(--h-warm) / 0.07);
    --shadow-floating: 0 28px 64px oklch(26% 0.02 var(--h-warm) / 0.16);

    /* ---- motion scale ---- */
    --dur-micro: 150ms;
    --dur-std:   300ms;
    --dur-large: 620ms;
    --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);

    /* ---- TYPE LIFT (ref-premium.md), inverted ----
       The house tokens are a dark halo for light type. Here the type is INK on
       a pale oak plate, so the halo is chalk. Same principle and the same three
       tiers: no x/y offset on the wide blurs, so it hugs each letterform rather
       than sitting in a box behind the block. The letters carrying their own
       contrast is what lets the scrim stay light enough to still see the wood. */
    --lift-display:
      0 1px 0   color-mix(in oklab, var(--chalk) 60%, transparent),
      0 0 30px  color-mix(in oklab, var(--chalk) 90%, transparent),
      0 0 92px  color-mix(in oklab, var(--chalk) 78%, transparent),
      0 0 180px color-mix(in oklab, var(--chalk) 58%, transparent);
    --lift-body:
      0 1px 0  color-mix(in oklab, var(--chalk) 62%, transparent),
      0 0 24px color-mix(in oklab, var(--chalk) 88%, transparent),
      0 0 68px color-mix(in oklab, var(--chalk) 70%, transparent);
    --lift-micro:
      0 1px 0  color-mix(in oklab, var(--chalk) 66%, transparent),
      0 0 18px color-mix(in oklab, var(--chalk) 90%, transparent),
      0 0 46px color-mix(in oklab, var(--chalk) 68%, transparent);

    /* ---- structure ---- */
    --rail-w: 232px;
    --gutter: clamp(20px, 4vw, 56px);
    --measure: 64ch;

    --display: 'Fraunces', Georgia, 'Times New Roman', serif;
    --body: 'Instrument Sans', system-ui, -apple-system, sans-serif;
  }
}

@layer base {
  body {
    background: var(--bg);
    color: var(--text);
    font-family: var(--body);
    font-size: clamp(16px, 0.55vw + 14.4px, 17.5px);
    line-height: 1.65;
    font-synthesis-weight: none;
  }

  h1, h2, h3, .display {
    font-family: var(--display);
    font-optical-sizing: auto;
    font-weight: 500;
    line-height: 1.02;
    text-wrap: balance;
    letter-spacing: -0.02em;
  }
  h1 { font-size: clamp(38px, 5vw, 66px); letter-spacing: -0.033em; line-height: 0.98; }
  h2 { font-size: clamp(29px, 3.4vw, 46px); letter-spacing: -0.026em; }
  h3 { font-size: clamp(20px, 1.5vw, 25px); letter-spacing: -0.015em; }

  p, li { text-wrap: pretty; }
  .prose { max-width: var(--measure); }
  .lead { font-size: clamp(18px, 1.2vw, 21px); line-height: 1.55; color: var(--muted); }

  .label {
    font-family: var(--body);
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.185em;
    text-transform: uppercase;
    color: var(--timber);
  }

  /* anything that lines up in a column must not jitter */
  .num, .spec-v, .tel, time { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }

  ::selection { background: var(--brass); color: var(--chalk); }

  :where(a, button, [type='submit'], input, textarea, select):focus-visible {
    outline: 2px solid var(--brass);
    outline-offset: 3px;
    border-radius: var(--radius-inset);
  }

  .skip {
    position: absolute; left: -9999px; top: 0; z-index: 100;
    background: var(--ink); color: var(--chalk); padding: 12px 18px;
  }
  .skip:focus { left: 8px; top: 8px; }
}

@layer layout {
  .shell { display: block; }

  /* ---- the rail: persists across page navigations ---- */
  .rail {
    view-transition-name: rail;
    position: sticky; top: 0; z-index: 40;
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px;
    padding: 14px var(--gutter);
    background: color-mix(in oklab, var(--bg) 88%, transparent);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--line-soft);
  }
  .mark { display: flex; align-items: baseline; gap: 9px; text-decoration: none; }
  .mark b { font-family: var(--display); font-weight: 600; font-size: 19px; letter-spacing: -0.02em; }
  .mark span { font-size: 10.5px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--muted); }
  .rail nav { display: flex; gap: clamp(14px, 2vw, 26px); align-items: center; }
  .rail nav a {
    text-decoration: none; font-size: 14.5px; color: var(--muted);
    transition: color var(--dur-micro);
    padding-block: 4px;
  }
  .rail nav a:hover, .rail nav a[aria-current='page'] { color: var(--ink); }
  .rail nav a[aria-current='page'] { box-shadow: inset 0 -1px 0 var(--brass); }

  /* the top-bar form has no room for the established line, and it made both
     the wordmark and the last nav item wrap onto two lines */
  @media (max-width: 1079px) {
    .mark span { display: none; }
    .rail nav { gap: 15px; }
    .rail nav a { font-size: 14px; }
  }

  @media (min-width: 1080px) {
    .shell { display: grid; grid-template-columns: var(--rail-w) 1fr; }
    .rail {
      flex-direction: column; align-items: flex-start; justify-content: flex-start;
      height: 100svh; padding: 34px 28px;
      border-bottom: 0; border-right: 1px solid var(--line-soft);
      gap: 0;
    }
    .mark { flex-direction: column; align-items: flex-start; gap: 2px; }
    .mark b { font-size: 23px; }
    .rail nav { flex-direction: column; align-items: flex-start; gap: 11px; margin-top: 48px; }
    .rail nav a { font-size: 15.5px; }
    .rail-foot { margin-top: auto; font-size: 12.5px; color: var(--muted); line-height: 1.5; }
  }
  .rail-foot { display: none; }
  @media (min-width: 1080px) { .rail-foot { display: block; } }

  main { min-width: 0; }
  .wrap { padding-inline: var(--gutter); max-width: 1280px; }
  section { padding-block: clamp(56px, 8vw, 104px); }

  /* rhythm: these two deliberately break the standard height */
  .band-tight { padding-block: clamp(30px, 3.5vw, 44px); }
  .band-tall  { padding-block: clamp(84px, 13vw, 168px); }
}

@layer components {
  /* ---- full-bleed hero: type sits ON the plate ----
     Blanket z-index rule: everything that overlays the photo is nested INSIDE
     .hero-bg, so a single-class child can never out-stack it. */
  .hero { position: relative; padding-block: 0; isolation: isolate; }
  .hero-bg { position: absolute; inset: 0; z-index: -1; overflow: hidden; background: var(--chalk-2); }
  .hero-bg img { width: 100%; height: 100%; object-fit: cover; object-position: 64% 52%; }
  /* the scrim comes ACROSS, not down: it clears the left plate for type and
     leaves the boards and the brass gauge on the right reading properly */
  .hero-bg::after {
    content: ''; position: absolute; inset: 0;
    background: linear-gradient(97deg,
      color-mix(in oklab, var(--chalk) 74%, transparent) 0%,
      color-mix(in oklab, var(--chalk) 62%, transparent) 34%,
      color-mix(in oklab, var(--chalk) 34%, transparent) 58%,
      color-mix(in oklab, var(--chalk) 8%, transparent) 78%,
      transparent 90%);
  }

  .hero-copy {
    display: flex; flex-direction: column; justify-content: center;
    padding: clamp(56px, 9vw, 128px) var(--gutter);
    max-width: 700px; min-height: min(86svh, 790px);
  }
  .hero-copy .label { display: block; margin-bottom: 20px; }
  .hero-copy h1 { margin-bottom: 22px; }
  .hero-copy .lead { max-width: 44ch; }
  /* keep the strip inside the treated part of the plate: at full width its last
     column landed on the bright board edge and the label washed out */
  .hero-copy .spec {
    border-top-color: color-mix(in oklab, var(--ink) 22%, transparent);
    max-width: 590px;
  }

  /* muted grey is fine on flat chalk but weak where the copy crosses the
     boards, so hero body and labels sit closer to ink than elsewhere */
  .hero-copy .lead   { color: color-mix(in oklab, var(--ink) 86%, transparent); }
  .hero-copy .spec-k { color: color-mix(in oklab, var(--ink) 74%, transparent); }

  /* type lift, applied by weight of type rather than per element */
  .hero-copy h1                        { text-shadow: var(--lift-display); }
  .hero-copy .lead                     { text-shadow: var(--lift-body); }
  .hero-copy .label, .hero-copy .spec-v, .hero-copy .spec-k { text-shadow: var(--lift-micro); }

  @media (max-width: 859px) {
    /* portrait crop: the plate has no usable left margin, so the scrim runs
       down the frame instead and the boards sit under the lower half */
    /* anchor the crop nearer the plaster wall so the copy runs down quiet
       ground rather than across the brightest boards */
    .hero-bg img { object-position: 38% 44%; }
    .hero-bg::after {
      background: linear-gradient(178deg,
        color-mix(in oklab, var(--chalk) 88%, transparent) 0%,
        color-mix(in oklab, var(--chalk) 80%, transparent) 48%,
        color-mix(in oklab, var(--chalk) 64%, transparent) 78%,
        color-mix(in oklab, var(--chalk) 50%, transparent) 100%);
    }
    .hero-copy { min-height: min(84svh, 640px); justify-content: flex-start; }
  }

  /* the spec strip: trust woven in, not four pills */
  .spec {
    display: grid; grid-template-columns: 1fr 1fr;
    gap: 16px clamp(12px, 1.6vw, 24px);
    margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--line);
  }
  /* four across once there is genuinely room; auto-fit kept giving 3 + 1 */
  @media (min-width: 560px) { .spec { grid-template-columns: repeat(4, 1fr); } }
  .spec div { padding-block: 2px; }
  .spec-v { display: block; font-family: var(--display); font-size: 25px; font-weight: 600; letter-spacing: -0.02em; }
  .spec-k { font-size: 12px; letter-spacing: 0.11em; text-transform: uppercase; color: var(--muted); }

  /* ---- THE SIGNATURE MOMENT: the dovetail divider ----
     Two halves of a hand-cut dovetail, tails splaying wider at the root the
     way a real one does. The shape is a STATIC clip-path; animated clip-path
     is banned (blank render on some GPUs), so only the transform moves and
     the joint closes as the divider scrolls into view. */
  .joint {
    position: relative; height: clamp(34px, 4.6vw, 62px); overflow: hidden;
    /* the halves take the colours of the two sections they join, so the page
       looks jointed rather than banded */
    --joint-top: var(--bg);
    --joint-bot: var(--ink);
  }
  .joint > i { position: absolute; inset: 0; display: block; }
  .joint-a {
    background: var(--joint-top);
    clip-path: polygon(0 0, 100% 0, 100% 26%, 95.25% 26%, 97.25% 74%, 88.25% 74%, 90.25% 26%, 82.75% 26%, 84.75% 74%, 75.75% 74%, 77.75% 26%, 70.25% 26%, 72.25% 74%, 63.25% 74%, 65.25% 26%, 57.75% 26%, 59.75% 74%, 50.75% 74%, 52.75% 26%, 45.25% 26%, 47.25% 74%, 38.25% 74%, 40.25% 26%, 32.75% 26%, 34.75% 74%, 25.75% 74%, 27.75% 26%, 20.25% 26%, 22.25% 74%, 13.25% 74%, 15.25% 26%, 7.75% 26%, 9.75% 74%, 0.75% 74%, 2.75% 26%, 0% 26%);
  }
  .joint-b {
    background: var(--joint-bot);
    clip-path: polygon(0 100%, 100% 100%, 100% 26%, 95.25% 26%, 97.25% 74%, 88.25% 74%, 90.25% 26%, 82.75% 26%, 84.75% 74%, 75.75% 74%, 77.75% 26%, 70.25% 26%, 72.25% 74%, 63.25% 74%, 65.25% 26%, 57.75% 26%, 59.75% 74%, 50.75% 74%, 52.75% 26%, 45.25% 26%, 47.25% 74%, 38.25% 74%, 40.25% 26%, 32.75% 26%, 34.75% 74%, 25.75% 74%, 27.75% 26%, 20.25% 26%, 22.25% 74%, 13.25% 74%, 15.25% 26%, 7.75% 26%, 9.75% 74%, 0.75% 74%, 2.75% 26%, 0% 26%);
  }
  /* dark section above, light below */
  .joint--flip { --joint-top: var(--ink); --joint-bot: var(--bg); }
  /* between two light sections: reads as a brass inlay strip, not a transition */
  .joint--inlay { --joint-bot: var(--brass-dim); height: clamp(26px, 3vw, 38px); }
  .joint--foot { --joint-bot: var(--ink-2); }

  /* ---- full-bleed statement (formula breaker) ---- */
  .statement { background: var(--ink); color: var(--chalk); }
  .statement p {
    font-family: var(--display); font-weight: 400;
    font-size: clamp(24px, 3.1vw, 42px); line-height: 1.22;
    letter-spacing: -0.025em; text-wrap: balance; max-width: 20ch;
  }
  .statement cite { display: block; margin-top: 22px; font-style: normal; font-size: 13px; letter-spacing: 0.14em; text-transform: uppercase; color: color-mix(in oklab, var(--chalk) 62%, transparent); }

  /* ---- commissions grid: subgrid keeps every card's parts on one line ---- */
  .sec-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 14px; margin-bottom: clamp(28px, 4vw, 48px); }
  .grid {
    display: grid; gap: clamp(22px, 3vw, 38px);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 290px), 1fr));
  }
  /* NB: this was a row subgrid spanning the parent's IMPLICIT tracks, which
     mis-sized every row (titles vanished, the fourth card bled into the next
     section). Row subgrid needs the parent to declare its rows. The card media
     has a fixed aspect-ratio, so the titles line up across cards anyway. */
  .card {
    display: flex; flex-direction: column; align-items: stretch;
    gap: 14px; text-decoration: none; container-type: inline-size;
  }
  .card h3 { margin-top: auto; }
  /* aspect-ratio lives on the CONTAINER, so the box is reserved whether or not
     the image has loaded. On the image alone it collapsed to nothing inside the
     subgridded row, and it would have shifted layout on a slow connection. */
  .card-media { overflow: hidden; background: var(--chalk-2); aspect-ratio: 40 / 26; }
  .card-media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur-large) var(--ease-out); }
  .card:hover .card-media img { transform: scale(1.035); }
  .card h3 { transition: color var(--dur-micro); }
  .card:hover h3 { color: var(--timber); }
  .card-meta { font-size: 13.5px; color: var(--muted); display: flex; gap: 10px; align-items: baseline; }
  .card-meta span::after { content: '·'; margin-left: 10px; color: var(--line); }
  .card-meta span:last-child::after { content: none; }

  /* Morph targets. The same name on the card and on the case-study hero is
     what makes the card physically expand into the next page. */
  [data-vt='ash']    { view-transition-name: proj-ash; }
  [data-vt='larder'] { view-transition-name: proj-larder; }
  [data-vt='walnut'] { view-transition-name: proj-walnut; }

  /* the component adapts to its container, not the viewport */
  @container (min-width: 430px) {
    .card h3 { font-size: 27px; }
    .card-meta { font-size: 14.5px; }
  }

  /* ---- editorial split (formula breaker) ---- */
  .split { display: grid; gap: clamp(28px, 5vw, 72px); align-items: center; }
  @media (min-width: 900px) { .split { grid-template-columns: 1.1fr 1fr; } .split.flip > :first-child { order: 2; } }
  .split img { width: 100%; height: auto; }

  /* ---- oversized numbered list (formula breaker) ---- */
  .steps { border-top: 1px solid var(--line); }
  .steps li {
    display: grid; gap: 6px 26px; align-items: baseline;
    padding-block: clamp(22px, 3vw, 34px);
    border-bottom: 1px solid var(--line);
  }
  @media (min-width: 760px) { .steps li { grid-template-columns: 88px 0.7fr 1.3fr; } }
  .steps .num { font-family: var(--display); font-size: clamp(30px, 4vw, 52px); font-weight: 300; color: var(--brass); line-height: 1; letter-spacing: -0.03em; }
  .steps h3 { margin-bottom: 2px; }
  .steps p { color: var(--muted); max-width: 46ch; }

  /* ---- buttons ---- */
  .btn {
    display: inline-flex; align-items: center; gap: 10px;
    padding: 14px 24px; text-decoration: none; font-size: 15px; font-weight: 600;
    background: var(--ink); color: var(--chalk);
    border: 1px solid var(--ink); border-radius: var(--radius-inset);
    transition: background var(--dur-std), color var(--dur-std), transform var(--dur-std) var(--ease-out);
  }
  .btn:hover { background: transparent; color: var(--ink); transform: translateY(-2px); }
  .btn--ghost { background: transparent; color: var(--ink); }
  .btn--ghost:hover { background: var(--ink); color: var(--chalk); }
  .btn--light { background: var(--chalk); color: var(--ink); border-color: var(--chalk); }
  .btn--light:hover { background: transparent; color: var(--chalk); }

  /* ---- the brass seal: the only round thing on the site ---- */
  .seal {
    width: 104px; height: 104px; flex: none; border-radius: var(--radius-feature);
    display: grid; place-items: center; text-align: center;
    background: var(--brass-dim); border: 1px solid var(--brass);
    font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--timber);
    line-height: 1.3;
  }

  /* ---- case study ---- */
  .cs-hero img { width: 100%; height: clamp(280px, 52vh, 560px); object-fit: cover; }
  .cs-facts { display: grid; gap: 20px clamp(24px, 4vw, 56px); grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); padding-block: clamp(28px, 4vw, 44px); border-block: 1px solid var(--line); }
  .cs-facts dt { font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); margin-bottom: 5px; }
  .cs-facts dd { font-family: var(--display); font-size: 20px; letter-spacing: -0.015em; }

  /* ---- form ---- */
  .field { display: grid; gap: 7px; margin-bottom: 18px; }
  .field label { font-size: 13px; letter-spacing: 0.06em; color: var(--muted); }
  .field :is(input, textarea, select) {
    padding: 13px 14px; background: var(--surface);
    border: 1px solid var(--line); border-radius: var(--radius-inset);
    transition: border-color var(--dur-micro), box-shadow var(--dur-micro);
  }
  .field :is(input, textarea):focus { border-color: var(--brass); box-shadow: 0 0 0 3px var(--brass-dim); outline: none; }
  .field :is(input, textarea):user-invalid { border-color: oklch(52% 0.16 28); }
  .form-note { font-size: 13px; color: var(--muted); margin-top: 14px; }
  .form-status { margin-top: 16px; padding: 14px 16px; border: 1px solid var(--line); background: var(--surface); font-size: 14.5px; display: none; }
  .form-status[data-state] { display: block; }
  .form-status[data-state='ok'] { border-color: var(--brass); background: var(--brass-dim); }

  /* ---- footer ---- */
  .foot { background: var(--ink-2); color: color-mix(in oklab, var(--chalk) 82%, transparent); }
  .foot a { color: inherit; }
  .foot-grid { display: grid; gap: 32px clamp(24px, 5vw, 72px); grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
  .foot h4 { font-size: 12px; letter-spacing: 0.15em; text-transform: uppercase; color: color-mix(in oklab, var(--chalk) 55%, transparent); margin-bottom: 14px; font-weight: 600; }
  .foot li { margin-bottom: 8px; font-size: 14.5px; }
  .foot-base { margin-top: 44px; padding-top: 22px; border-top: 1px solid color-mix(in oklab, var(--chalk) 14%, transparent); font-size: 12.5px; display: flex; flex-wrap: wrap; gap: 10px 24px; justify-content: space-between; color: color-mix(in oklab, var(--chalk) 58%, transparent); }

  /* ---- mobile CTA bar (mandatory component) ---- */
  .mob-bar { position: fixed; inset: auto 0 0 0; z-index: 60; display: flex; background: var(--ink); border-top: 1px solid color-mix(in oklab, var(--chalk) 16%, transparent); }
  .mob-bar a { flex: 1; padding: 15px 8px; text-align: center; text-decoration: none; color: var(--chalk); font-size: 14.5px; font-weight: 600; }
  .mob-bar a + a { flex: 1.25; background: var(--brass); color: var(--ink-2); }
  @media (min-width: 760px) { .mob-bar { display: none; } }
  @media (max-width: 759px) { body { padding-bottom: 54px; } }
}

@layer motion {
  /* Cross-document transition. Reduced motion still transitions, it just cuts. */
  @media (prefers-reduced-motion: no-preference) {
    ::view-transition-old(root) { animation: vt-out 170ms ease both; }
    ::view-transition-new(root) { animation: vt-in 280ms var(--ease-out) both; }
    ::view-transition-group(rail) { animation-duration: 0s; }
  }
  @keyframes vt-out { to { opacity: 0; } }
  @keyframes vt-in { from { opacity: 0; transform: translateY(10px); } }

  /* ---------- reveals ----------
     Base state is VISIBLE. Hidden states exist only inside the support and
     motion-preference guards, so the failure mode is "less motion", never
     "blank page". Two distinct styles per page, per anti-slop §1.5.        */
  .rv-line > span { display: block; }

  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .rv-line > span {
        animation: line-rise linear both;
        animation-timeline: view();
        animation-range: entry 8% cover 26%;
      }
      .rv-card {
        animation: card-in linear both;
        animation-timeline: view();
        animation-range: entry 4% cover 34%;
      }
      .joint .joint-a { animation: joint-a linear both; animation-timeline: view(); animation-range: entry 0% cover 34%; }
      .joint .joint-b { animation: joint-b linear both; animation-timeline: view(); animation-range: entry 0% cover 34%; }
    }
  }

  /* IntersectionObserver fallback. The class is only added by JS when the
     scroll timeline is unsupported, so the two paths never both run. */
  @media (prefers-reduced-motion: no-preference) {
    .io-reveal .rv-line > span { transform: translateY(1.1em); opacity: 0; transition: transform var(--dur-large) var(--ease-out), opacity var(--dur-large); }
    .io-reveal .rv-line.in > span { transform: none; opacity: 1; }
    .io-reveal .rv-card { opacity: 0; filter: blur(10px); transform: scale(0.975); transition: opacity var(--dur-large), filter var(--dur-large), transform var(--dur-large) var(--ease-out); }
    .io-reveal .rv-card.in { opacity: 1; filter: blur(0); transform: none; }
    .io-reveal .joint .joint-a { transform: translateY(-16px); transition: transform var(--dur-large) var(--ease-out); }
    .io-reveal .joint .joint-b { transform: translateY(16px); transition: transform var(--dur-large) var(--ease-out); }
    .io-reveal .joint.in :is(.joint-a, .joint-b) { transform: none; }
  }

  @keyframes line-rise { from { transform: translateY(1.1em); opacity: 0; } to { transform: none; opacity: 1; } }
  @keyframes card-in { from { opacity: 0; filter: blur(10px); transform: scale(0.975); } to { opacity: 1; filter: blur(0); transform: none; } }
  @keyframes joint-a { from { transform: translateY(-17px); } to { transform: none; } }
  @keyframes joint-b { from { transform: translateY(17px); } to { transform: none; } }

  /* choreographed load, desktop only, opener = rise */
  @media (min-width: 860px) and (prefers-reduced-motion: no-preference) {
    .rise-in > * { animation: rise var(--dur-large) var(--ease-out) both; }
    .rise-in > :nth-child(1) { animation-delay: 60ms; }
    .rise-in > :nth-child(2) { animation-delay: 150ms; }
    .rise-in > :nth-child(3) { animation-delay: 240ms; }
    .rise-in > :nth-child(4) { animation-delay: 330ms; }
    @keyframes rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
  }

  /* screenshot hook: ?shot kills all motion for clean captures */
  html.shot *, html.shot *::before, html.shot *::after {
    animation: none !important;
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }
}

@layer utilities {
  .u-center { margin-inline: auto; }
  .u-mt-s { margin-top: 14px; }
  .u-mt-m { margin-top: 26px; }
  .u-mt-l { margin-top: 44px; }
  .u-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
  .u-dark { background: var(--ink); color: var(--chalk); }
  .u-dark .label { color: var(--brass); }
  .u-dark .lead { color: color-mix(in oklab, var(--chalk) 72%, transparent); }
  .u-hide { display: none; }
}
