/* ============================================================================
   work-v3.css — Work page, aligned to the SERVICES page design system.
   (2026-08-06, retuned from the neofox-derived first pass)

   The interaction mechanics here (masked line reveals, dual-layer text flip,
   alternating work rows, arrow buttons, category chips, lerp cursor) were built
   for this page earlier. Everything VISUAL below is now taken from a live audit
   of /services/ so the two pages read as one site:

     font      Neue Montreal 700 display / 400 body   (was Archivo 900)
     kicker    Antonio 13px 500 uppercase ls .17em
     h1 / h2   72px, uppercase, letter-spacing -.07em (was 96 / 112)
     body      16px 400, sentence case                (was 18px uppercase)
     button    17px 700, ls .33px, SENTENCE case      (services buttons aren't caps)
     ink       #13181B                                (was #212122)
     accent    #FFBF65 fill / #854D00 text            (was #f1b84b)
     surface   #F0EEEB — inherited, never repainted
     radii     16px cards / 999px pills               (was 32px / 44px)
     motion    .5s + .28s on cubic-bezier(.2,.7,.2,1) (was .9s expo)
     content   1200px @ 120px gutters                 (matches every other band)

   ADDITIVE. Loads after work.css/site-unify.css. Touches nothing Mokko owns
   except the filter bar chrome, scoped under .wv3-filters.
   - No ScrollSmoother (banned on static pages — it kills position:sticky).
   - Reduced-motion block at the foot ships everything in its settled state.
   ========================================================================== */

/* Neue Montreal is on disk at final/fonts/ but ONLY the React bundle declared
   it — the static pages had no @font-face, so work.html could not use it at
   all. Declared here to match services/assets/FooterSection-*.css exactly
   (same family string, same weights, same files). Antonio needs no declaration:
   site-unify.css:49 already imports it from Google and work.html loads that. */
@font-face {
  font-family: "Neue Montreal";
  src: url("../fonts/NeueMontreal-Regular.woff2") format("woff2");
  font-weight: 400; font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("../fonts/NeueMontreal-Medium.woff2") format("woff2");
  font-weight: 500; font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("../fonts/NeueMontreal-Bold.woff2") format("woff2");
  font-weight: 700 800; font-display: swap;
}

:root {
  /* --- motion: the services page's signature curve, and its two speeds ----- */
  --wv3-ease: cubic-bezier(.2, .7, .2, 1);
  --wv3-dur: .5s;      /* reveals — they travel, so the longer of the two      */
  --wv3-hover: .28s;   /* hover / small moves, matching .26–.3s across /services */

  /* --- measure: 1200px of content, 120px gutters at 1440 ------------------- */
  --wv3-edge: max(clamp(20px, 6vw, 120px), calc((100vw - 1200px) / 2));

  /* --- palette, read off /services/ --------------------------------------- */
  --wv3-ink: #13181B;
  --wv3-mute: rgba(19, 24, 27, .62);
  /* Accent has TWO jobs. #FFBF65 on #F0EEEB is 1.41:1 — fine as a FILL behind
     dark glyphs, unusable AS text. The text form is --fg-accent (#854D00).
     Do not collapse these into one value. */
  --wv3-accent: #FFBF65;
  --wv3-accent-text: var(--fg-accent, #854D00);

  /* 🔴 CORRECTED 2026-08-06 (second pass). The header note above says this file
     took its type off "a live audit of /services/" and set the display face to
     Neue Montreal. THAT AUDIT READ THE TABLE BACKWARDS. site-unify.css:38-44
     records the measured system: Antonio is DISPLAY, Neue Montreal is chrome +
     prose. svc-type.css's 2026-08-06 pass did not demote Antonio — it demoted
     it out of BODY COPY, which is the opposite thing. Home, /services/ and the
     shared footer all set headlines in Antonio; this page was the only surface
     setting them in a wide grotesque, and that single line is what made the
     founder say it "doesn't look like our website".
     Display spec is site-unify.css:41 — 700 · uppercase · ls -.05em · lh .95-1. */
  --wv3-display: "Antonio", "Archivo", sans-serif;
  --wv3-body: "Neue Montreal", "Archivo", sans-serif;
  --wv3-kicker-font: "Antonio", "Neue Montreal", sans-serif;

  /* Navy is a REAL token only in site-unify.css (--u-navy). Do NOT reach for
     --bg-inset-dark: that resolves to #13181B in light theme and only becomes
     #003A6C in dark, so a band built on it is black on the page the founder is
     actually looking at. */
  --wv3-navy: var(--u-navy, #003A6C);

  /* Cards are 28px across the system, pills 40px+ (site-unify.css:45). The
     16px this file shipped is a neofox number, not ours. */
  --wv3-card-r: 28px;
}

/* ============================================================================
   1. MASKED LINE REVEAL
   A clipping wrapper, and a child parked below its own box. translateY is in %
   so it scales with the line rather than depending on a magic pixel number.
   ========================================================================== */
/* The clip box is the line box, and at line-height <1 the line box is SHORTER
   than the glyphs — overflow:hidden then shaves the bottom off caps like G / S.
   Pad the mask and pull the same amount back as margin: glyphs get room, layout
   does not move. */
.wv3-mask {
  overflow: hidden;
  display: block;
  padding-bottom: .14em;
  margin-bottom: -.14em;
}

.wv3-line {
  display: block;
  transform: translate3d(0, 110%, 0);
  opacity: 0;
  transition:
    transform var(--wv3-dur) var(--wv3-ease),
    opacity   calc(var(--wv3-dur) * .7) linear;
  transition-delay: var(--wv3-d, 0ms);
  will-change: transform;
}
.is-in .wv3-line,
.wv3-line.is-in { transform: translate3d(0, 0, 0); opacity: 1; }

.wv3-rise {
  opacity: 0;
  transform: translate3d(0, 30px, 0);
  transition:
    transform var(--wv3-dur) var(--wv3-ease),
    opacity   calc(var(--wv3-dur) * .6) linear;
  transition-delay: var(--wv3-d, 0ms);
}
/* Both forms matter: the observer marks the GROUP ([data-wv3-reveal]), so the
   descendant selector is the one that fires for children. The self form stays
   for elements that are their own reveal group. */
.is-in .wv3-rise,
.wv3-rise.is-in { opacity: 1; transform: none; }

/* ============================================================================
   2. SHARED TYPE  — the /services/ scale
   ========================================================================== */
/* The site's micro-tag: Antonio 500, .18em tracking, and it is ACCENT-coloured,
   not grey. On a light ground --fg-accent is #854D00 (5.93:1); on a dark ground
   palette.css re-points the same token to the amber base. One declaration,
   correct on both grounds — see § 7b for the navy band. */
.wv3-kicker {
  display: block;
  font-family: var(--wv3-kicker-font);
  font-weight: 500;
  font-size: clamp(11px, .8vw, 14px);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--fg-accent, #854D00);
  margin: 0 0 clamp(14px, 2vh, 20px);
}

/* h1 and h2 are the same size — the hierarchy comes from position and colour,
   not from size. Tracking and leading are the system's (-.05em / .95), not the
   -.07em/.96 this file shipped: Antonio is already condensed, and -.07em on top
   of it collides the glyphs. */
.wv3-hero__title,
.wv3-hero__title .wv3-line,
.wv3-sec__head {
  font-family: var(--wv3-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: -.05em;
  line-height: .95;
  color: var(--wv3-ink);
  font-size: clamp(34px, 5.4vw, 78px);
}

/* ---- THE SLAB — site-unify.css §4a, the signature device -------------------
   Flat amber fill, radius 0, rotate(-3deg) — the site's ONLY rotation value.
   Never a gradient, never a glow. The ink on it is --fg-on-accent, because
   #FFBF65 as TEXT on #F0EEEB is 1.41:1; as a FILL under dark glyphs it is the
   brand's loudest element.

   ⚠ THE SLAB CANNOT LIVE INSIDE .wv3-mask. That mask is `overflow:hidden` with
   .14em of bottom padding sized for upright glyphs; a box rotated -3deg puts
   roughly half-width × sin(3°) of itself outside the box on BOTH edges (~12px
   on a 460px line at 72px), and the mask shaves it. Slab lines therefore get
   .wv3-slabline + .wv3-rise — a travel reveal with no clip — while unslabbed
   lines keep the mask. Do not "unify" the two. */
.wv3-slab {
  display: inline-block;
  padding: .04em .2em .1em;
  background: var(--fill-accent, #FFBF65);
  color: var(--fg-on-accent, #13181B);
  -webkit-text-fill-color: var(--fg-on-accent, #13181B);
  border-radius: 0;
  transform: rotate(-3deg);
  -webkit-box-decoration-break: clone;
          box-decoration-break: clone;
}

/* The unclipped sibling of .wv3-mask. Vertical air only — a rotated slab needs
   room above and below, and it is the LAST line of every lockup it appears in,
   so the negative margin pulls the flow back without touching the line above. */
.wv3-slabline {
  display: block;
  padding-block: .12em;
  margin-block: -.12em;
}

/* ============================================================================
   3. HERO
   ========================================================================== */
.wv3-hero { padding: clamp(72px, 11vh, 128px) var(--wv3-edge) clamp(44px, 6vh, 72px); }

.wv3-hero__title { margin: 0; }
/* kept for any line that wants accent COLOUR rather than the slab fill */
.wv3-hero__title .wv3-line.is--accent { color: var(--wv3-accent-text); }

.wv3-hero__lead {
  margin: clamp(22px, 3vh, 30px) 0 0;
  max-width: 34em;                 /* em, NOT ch — ch is the zero glyph and a
                                      68ch cap measured 88 real characters */
  font-family: var(--wv3-body);
  font-weight: 400;
  font-size: clamp(16px, 1.15vw, 18px);
  line-height: 1.6;
  color: var(--wv3-mute);
}

/* --- the hero's two actions --------------------------------------------- */
.wv3-hero__actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: clamp(14px, 1.6vw, 22px);
  margin-top: clamp(26px, 3.6vh, 40px);
}

/* --- scroll cue ----------------------------------------------------------- */
.wv3-cue {
  display: inline-grid; place-items: center;
  margin-top: clamp(26px, 4vh, 44px);
  width: 52px; height: 52px;
  border: 1px solid rgba(19, 24, 27, .22);
  border-radius: 999px;
  padding: 0; cursor: pointer;
  background: transparent;
  color: var(--wv3-ink);
  transition: background var(--wv3-hover) ease, border-color var(--wv3-hover) ease;
}
.wv3-cue:hover { background: var(--wv3-accent); border-color: var(--wv3-accent); }
.wv3-cue svg { width: 16px; height: 16px; display: block; }
.wv3-cue:focus-visible { outline: 2px solid var(--dc-link, #1544c0); outline-offset: 4px; }

/* ============================================================================
   4. DUAL-LAYER TEXT FLIP
   Two copies stacked; the front rides up out of the mask as the back rides up
   into it — one shared transform, so they never separate.
   ========================================================================== */
.wv3-flip { position: relative; display: inline-block; overflow: hidden; vertical-align: top; }
.wv3-flip__front,
.wv3-flip__back { display: block; transition: transform var(--wv3-dur) var(--wv3-ease); }
.wv3-flip__back {
  position: absolute; inset: 0;
  transform: translate3d(0, 100%, 0);
  color: var(--wv3-accent-text);
}
.wv3-flip:hover .wv3-flip__front,
a:hover .wv3-flip .wv3-flip__front { transform: translate3d(0, -100%, 0); }
.wv3-flip:hover .wv3-flip__back,
a:hover .wv3-flip .wv3-flip__back  { transform: translate3d(0, 0, 0); }

/* ============================================================================
   5. SECTION
   ========================================================================== */
.wv3-sec { padding: clamp(52px, 8vh, 81px) var(--wv3-edge) 0; }
.wv3-sec__head { margin: 0 0 clamp(28px, 4vh, 44px); }
.wv3-sec__head .wv3-flip { line-height: .96; }

/* ============================================================================
   6. BUTTON
   /services/ buttons are 17px/700 with a hair of positive tracking and are NOT
   uppercased. The arrows stay clipped to zero width until hover so the pill
   itself never moves — only the arrows appear to slide in.
   ========================================================================== */
.wv3-btn {
  display: inline-flex; align-items: center;
  text-decoration: none; background: none; border: 0; padding: 0;
  cursor: pointer; color: inherit;
}
.wv3-btn__arrow {
  display: grid; place-items: center;
  width: 0; height: 44px;
  overflow: hidden; opacity: 0;
  color: var(--wv3-ink);
  transition: width var(--wv3-hover) var(--wv3-ease),
              opacity var(--wv3-hover) linear,
              margin var(--wv3-hover) var(--wv3-ease);
}
.wv3-btn__arrow svg { width: 14px; height: 14px; flex: none; }
.wv3-btn__arrow.is--left svg { transform: rotate(180deg); }

.wv3-btn__label {
  display: inline-block;
  padding: 13px 26px;
  border-radius: 999px;
  border: 1px solid var(--wv3-ink);
  background: transparent;
  /* Buttons are CHROME, and site-unify.css §9 is explicit that chrome stays on
     Neue Montreal — the reference sets nav, buttons, chips and the logo in it.
     Only display type moved to Antonio. */
  font-family: var(--wv3-body);
  font-weight: 700;
  font-size: 17px;
  letter-spacing: .02em;
  line-height: 1.2;
  color: var(--wv3-ink);
  transition: background var(--wv3-hover) ease, color var(--wv3-hover) ease,
              border-color var(--wv3-hover) ease;
}
.wv3-btn:hover .wv3-btn__arrow { width: 24px; opacity: 1; margin: 0 3px; }
.wv3-btn:hover .wv3-btn__label { background: var(--wv3-accent); border-color: var(--wv3-accent); }
.wv3-btn:focus-visible { outline: 2px solid var(--dc-link, #1544c0); outline-offset: 3px; border-radius: 999px; }

/* --- the PRIMARY action: a filled amber pill ------------------------------
   Every other surface on the site leads with this — the header CTA, the
   services hero, the footer's "Send the idea". The work page had no filled
   button anywhere, which is part of why the amber never appeared above the
   fold. Ink on amber is --fg-on-accent, the same pair the slab uses. */
.wv3-btn--fill .wv3-btn__label {
  background: var(--fill-accent, #FFBF65);
  border-color: var(--fill-accent, #FFBF65);
  color: var(--fg-on-accent, #13181B);
  padding: 15px 30px;
}
.wv3-btn--fill .wv3-btn__arrow { color: var(--fg-on-accent, #13181B); }
.wv3-btn--fill:hover .wv3-btn__label { filter: brightness(1.06); }

/* ============================================================================
   7. FEATURED WORK ROWS — image and copy trade sides every other row
   ========================================================================== */
.wv3-rows { display: grid; gap: clamp(44px, 6vh, 72px); }

/* Proportional, NOT a fixed media column: at ~900px a fixed one leaves the copy
   ~255px, and a 5vw title in 255px is soup. The px cap lives on the media. */
.wv3-row {
  display: grid;
  grid-template-columns: minmax(0, .95fr) minmax(0, 1fr);
  gap: clamp(24px, 3.7vw, 53px);
  align-items: center;
}
.wv3-row:nth-child(even) .wv3-row__media { order: 2; margin-left: auto; }
.wv3-row:nth-child(even) .wv3-row__body  { order: 1; }

.wv3-row__media {
  position: relative; display: block; overflow: hidden;
  border-radius: var(--wv3-card-r);
  background: #e6e3de;
  aspect-ratio: 3 / 4;
  max-width: 540px;          /* == the posters' native width; never upscale */
  width: 100%;
  text-decoration: none;
}
.wv3-row__media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transform: scale(1.01);
  transition: transform .7s var(--wv3-ease);
}
.wv3-row__media:hover img { transform: scale(1.05); }

.wv3-row__play {
  position: absolute; inset: auto auto 14px 14px;
  width: 40px; height: 40px; border-radius: 999px;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .94);
  color: var(--wv3-ink);
  transform: translate3d(0, 6px, 0); opacity: 0;
  transition: transform var(--wv3-hover) var(--wv3-ease), opacity var(--wv3-hover) linear;
}
.wv3-row__media:hover .wv3-row__play { transform: none; opacity: 1; }
.wv3-row__play svg { width: 13px; height: 13px; margin-left: 2px; }

.wv3-row__title {
  font-family: var(--wv3-display);
  font-weight: 700; text-transform: uppercase;
  letter-spacing: -.05em; line-height: .95;
  font-size: clamp(28px, 3.6vw, 52px);
  color: var(--wv3-ink);
  margin: 0;
  text-wrap: balance;        /* kills "SENSES / 2.0" style orphans */
}
.wv3-row__meta {
  margin: 12px 0 0;
  font-family: var(--wv3-body);
  font-weight: 400; font-size: 16px; line-height: 1.5;
  color: var(--wv3-mute);
}
.wv3-row__body .wv3-btn { margin-top: 22px; }

@media (max-width: 980px) {
  .wv3-row { grid-template-columns: 1fr; gap: 20px; }
  /* one column makes the alternation meaningless — media always leads, and the
     even-row margin-left:auto has to go or those rows sit right-aligned */
  .wv3-row:nth-child(even) .wv3-row__media,
  .wv3-row:nth-child(even) .wv3-row__body { order: 0; }
  .wv3-row:nth-child(even) .wv3-row__media { margin-left: 0; }
}

/* ============================================================================
   8. CATEGORY CHIPS
   Tints are set inline by work-v3.js as --wv3-chip, so a new category never
   needs a CSS edit.
   ========================================================================== */
.wv3-filters { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 clamp(26px, 4vh, 40px); padding: 0; }

/* The live rule is mokko/assets/css/style.css:9965
   `.ms-portfolio-filter-area .button-group button` → (0,2,1), and it paints the
   CHECKED state with an ::after plate at z-index:-1 rather than a background on
   the button. A plain `.wv3-filters button` (0,1,1) loses however late it
   loads. (0,3,1) below wins on specificity alone — no !important needed.
   NB work-mokko.css is NOT in this page's stylesheet list; don't target its
   `.wk-page` prefix here. */
.ms-portfolio-filter-area .button-group.wv3-filters button,
.wv3-filters button {
  appearance: none;
  border: 1px solid rgba(19, 24, 27, .28);
  border-radius: 999px;
  height: auto;
  padding: 10px 20px;
  background: var(--wv3-chip, transparent);
  color: var(--wv3-ink);
  font-family: var(--wv3-body);
  font-weight: 500;
  font-size: 15px;
  letter-spacing: .01em;
  line-height: 1.2;
  cursor: pointer;
  transition: background var(--wv3-hover) ease, border-color var(--wv3-hover) ease;
}
.ms-portfolio-filter-area .button-group.wv3-filters button::after,
.ms-portfolio-filter-area .button-group.wv3-filters button:hover::after,
.ms-portfolio-filter-area .button-group.wv3-filters button.is-checked::after,
.ms-portfolio-filter-area .button-group.wv3-filters button.is-checked:hover::after { display: none; }

.ms-portfolio-filter-area .button-group.wv3-filters button.is-checked {
  background: var(--wv3-ink);
  border-color: var(--wv3-ink);
  color: #F0EEEB;
}
/* Mokko wraps the bar in a #f7f7f7 rounded plate (style.css:10016 → (0,3,0)).
   Our chips are the surface now, so that plate reads as a stray slab. */
.ms-portfolio-filter-area.project .button-group.wv3-filters {
  background: none; border-radius: 0; padding: 0;
}
/* NOTE: do NOT force #fff anywhere on this page. main.ms-main is #F0EEEB and
   that warm off-white IS the site surface — every /services/ band sits on it.
   Forcing white only creates seams wherever a margin collapses out of a section. */

/* ============================================================================
   8b. GROUND RHYTHM — the navy band                              (NEW, 2026-08-06)
   ----------------------------------------------------------------------------
   The single biggest structural difference between this page and the rest of
   the site. Home and /services/ both alternate #F0EEEB canvas ↔ #003A6C navy
   full-bleed; this page ran one flat off-white from the header to the footer,
   which is what made it read as unbranded even before the type was wrong.

   NO 50vw TRICK IS NEEDED HERE. `.wv3-band` is a direct child of
   `main.ms-main`, which is already the full page width, so the ground bleeds by
   simply existing. The 100vw / `inset:0 calc(50% - 50vw)` idiom is for elements
   trapped inside a centred container, and it costs a horizontal scrollbar
   whenever the real scrollbar is counted in vw. Don't reach for it here.
   ========================================================================== */
.wv3-band { position: relative; }
.wv3-band--navy {
  background: var(--wv3-navy);
  padding-block: clamp(56px, 8vh, 96px) clamp(40px, 6vh, 72px);
  margin-top: clamp(64px, 9vh, 110px);
}
/* the band ships its own air — the section inside must not add a second lot */
.wv3-band--navy > .wv3-sec { padding-top: 0; }

/* --- 8c · ON-DARK INK for everything inside the band ---------------------- */
/* --fg-on-dark is 9.95:1 on #003A6C (palette.css:191). The amber base is the
   accent's on-dark form, so the kicker flips to the fill colour rather than the
   dark-gold text form, which would be invisible here. */
.wv3-band--navy .wv3-sec__head,
.wv3-band--navy .wv3-row__title { color: var(--fg-on-dark, #F0EEEB); }
.wv3-band--navy .wv3-kicker     { color: var(--fill-accent, #FFBF65); }
.wv3-band--navy .wv3-hero__lead,
.wv3-band--navy .wv3-row__meta,
.wv3-band--navy .wv3-lead       { color: var(--u-on-dark-mute, #B7B6B5); }

/* chips: the outline pair inverts, and the checked state becomes the amber the
   page has been missing. Same (0,3,1) hook § 8 documents — Mokko's ::after
   plate is already killed there. */
.wv3-band--navy .ms-portfolio-filter-area .button-group.wv3-filters button,
.wv3-band--navy .wv3-filters button {
  border-color: rgba(240, 238, 235, .34);
  color: var(--fg-on-dark, #F0EEEB);
  background: transparent;
}
.wv3-band--navy .ms-portfolio-filter-area .button-group.wv3-filters button:hover {
  border-color: rgba(240, 238, 235, .7);
}
.wv3-band--navy .ms-portfolio-filter-area .button-group.wv3-filters button.is-checked {
  background: var(--fill-accent, #FFBF65);
  border-color: var(--fill-accent, #FFBF65);
  color: var(--fg-on-accent, #13181B);
}
/* Mokko paints tile captions for a light page. On the band they need the
   on-dark pair or they vanish into the navy. */
.wv3-band--navy .ms-p-content__inner h3    { color: var(--fg-on-dark, #F0EEEB); }
.wv3-band--navy .ms-p-content__inner .ms-p-cat { color: var(--fill-accent, #FFBF65); }

/* ============================================================================
   8d. THE CLIENT LOGO MARQUEE                                    (NEW, 2026-08-06)
   ----------------------------------------------------------------------------
   The home page's `.bto-strip` band, rebuilt standalone. It is NOT imported:
   the live one is styled across hero.css / hero-phone.css / hero-panel.css, all
   scoped to `.hb-scroller`, and its marquee is assembled at runtime by
   heroPhone.js — none of which this page loads. Twenty lines of our own beats
   dragging three hero-scoped sheets onto a page with no hero pin.

   Ground is #000, for the reason brand-strip.css argues at length: it sits
   between two dark-ish surfaces and navy here would read as a doubled band.
   ========================================================================== */
.wv3-strip {
  position: relative; z-index: 1;
  background: #000;
  overflow: hidden;
  padding-block: clamp(18px, 3.2vh, 34px);
}
.wv3-strip__marquee { display: flex; width: max-content; }
@media (prefers-reduced-motion: no-preference) {
  .wv3-strip__marquee { animation: wv3-marquee 46s linear infinite; }
  .wv3-strip:hover .wv3-strip__marquee { animation-play-state: paused; }
}
.wv3-strip__track {
  display: flex; align-items: center;
  gap: clamp(38px, 5vw, 76px);
  padding-inline: clamp(19px, 2.5vw, 38px);
  flex: none;
}
.wv3-strip__track img {
  height: 40px; width: auto; display: block;
  opacity: .92; flex: none;
}
/* -50%, not -100% — the track is duplicated exactly once, so half the width is
   one full pass and the seam lands on identical content. */
@keyframes wv3-marquee { to { transform: translate3d(-50%, 0, 0); } }
@media (max-width: 760px) { .wv3-strip__track img { height: 32px; } }

/* ============================================================================
   8e. TILT-IN CARDS + MEDIA PARALLAX                             (NEW, 2026-08-06)
   ----------------------------------------------------------------------------
   The home page's card deck arrives rotated and settles upright (see the phone
   deck between the hero and the work rail). Every archive tile now does the
   same, alternating direction so a row does not lean as one block. Rotation is
   ±2.2deg — under the slab's -3deg, because these are supporting elements and
   the slab is meant to be the loudest rotation on any page.

   work-v3.js sets --wv3-d (stagger) and --wv3-tilt (direction) per tile.
   ========================================================================== */
.wv3-tilt {
  opacity: 0;
  transform: translate3d(0, 46px, 0) rotate(var(--wv3-tilt, -2.2deg)) scale(.97);
  transition:
    transform .62s var(--wv3-ease),
    opacity   .42s linear;
  transition-delay: var(--wv3-d, 0ms);
  will-change: transform;
}
.wv3-tilt.is-in { opacity: 1; transform: none; }

/* Parallax is driven by GSAP (already on the page for reveals.js) writing
   --wv3-par as a px length. The image is over-tall so the drift never exposes
   an edge — object-fit:cover plus 112% height gives ±6% of travel to spend. */
.wv3-row__media img { height: 112%; top: -6%; position: relative; }
.wv3-row__media img { transform: translate3d(0, var(--wv3-par, 0px), 0) scale(1.01); }
.wv3-row__media:hover img { transform: translate3d(0, var(--wv3-par, 0px), 0) scale(1.05); }

/* ============================================================================
   8f. THE WORD COLOUR SCRUB — the site's lead-paragraph effect   (NEW, 2026-08-06)
   ----------------------------------------------------------------------------
   `.bto-scrub` is already loaded and already wired: bto-js/reveals.js splits it
   into words and tweens --rv-lit 0%→100% on scroll. What was missing is the
   COLOUR rule — reveals.css:108 scopes its version to index's
   `.bto-banner .bto-banner-lead`, and bto-static.css:514 scopes its version to
   `.bto-static`, a class this page does not carry (body is `masonry wk-page`).
   Without a matching rule the split happens and nothing visibly changes.

   Same two tokens and the same color-mix form as both of those, so the theme
   toggle survives and no new colour enters the system. --rv-lit carries its own
   unit ("0%".."100%") and is substituted straight into the percentage slot —
   never wrapped in calc().

   ⚠ ONE PER PAGE. reveals.js resolves the lead with querySelector (singular),
   so a second `.bto-scrub` is silently inert. It is on the hero lead.
   ========================================================================== */
.wk-page .bto-scrub .bto-rv-w {
  color: color-mix(in srgb,
                   var(--fg-ink) var(--rv-lit, 100%),
                   var(--fg-mute));
}
.wk-page .wv3-band--navy .bto-scrub .bto-rv-w {
  color: color-mix(in srgb,
                   var(--fg-on-dark) var(--rv-lit, 100%),
                   var(--u-on-dark-mute, #B7B6B5));
}

/* ============================================================================
   9. LERP CURSOR — pointer-fine only, never on touch
   ========================================================================== */
.wv3-cursor { display: none; }

@media (hover: hover) and (pointer: fine) {
  .wv3-cursor {
    display: block; position: fixed; top: 0; left: 0;
    width: 11px; height: 11px; border-radius: 999px;
    background: var(--wv3-accent);
    pointer-events: none; z-index: 9999;
    transform: translate3d(-50%, -50%, 0);
    transition: width var(--wv3-hover) var(--wv3-ease),
                height var(--wv3-hover) var(--wv3-ease),
                background var(--wv3-hover) ease, opacity .2s linear;
    opacity: 0;
  }
  .wv3-cursor.is-live { opacity: 1; }
  .wv3-cursor.is-hot  { width: 40px; height: 40px; background: rgba(255, 191, 101, .38); }
}

/* ============================================================================
   10. REDUCED MOTION — ship settled; keep colour and hover, drop travel
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .wv3-line, .wv3-rise, .wv3-tilt {
    transform: none !important; opacity: 1 !important; transition: none !important;
  }
  .wv3-flip__front, .wv3-flip__back,
  .wv3-btn__arrow, .wv3-row__media img, .wv3-row__play,
  .wv3-cue { transition: none !important; }
  .wv3-cursor { display: none !important; }

  /* The marquee's `animation` is already inside a no-preference query, so it is
     never declared here — but the track must still be pinned at its start or a
     stale transform could survive a preference change mid-session. */
  .wv3-strip__marquee { animation: none !important; transform: none !important; }

  /* The scrub's end state, given outright rather than animated toward. Matches
     bto-static.css:565 — reveals.js returns before creating a split under
     reduced motion, so `.bto-rv-w` never exists and the colour must land on the
     paragraph itself. */
  .wk-page .bto-scrub { color: var(--fg-ink); }
  .wk-page .wv3-band--navy .bto-scrub { color: var(--fg-on-dark); }

  /* parallax never gets a value written, but pin it in case one is left over */
  .wv3-row__media img { --wv3-par: 0px; }
}
