/* Patroller Systems · Home v4 · page styles.
   Every colour, type, space, shape and motion value comes from tokens.css, a
   verbatim copy of brand-docs/visual-identity/tokens/tokens.css. Nothing in the
   brand system is edited here and no value is re-invented.

   v4 keeps v3's rule — NOTHING VISIBLE SWITCHES, EVERYTHING VISIBLE GLIDES —
   and adds the two rules the 4c sheet makes binding:

     R1  TEXT IS NEVER FADED. There is no opacity function anywhere in this file
         that acts on a text node. Text is at 1.0 whenever it is on screen. The
         only text transitions are (a) the ink wipe, which shows the written
         part at 1.0 and the unwritten part at 0 by clip-path, and (b) whole
         rows entering once at full ink in 240 ms, with their height reserved.
     R2  ONE PLACE TO LOOK. Every act is the same two zones in the same place:
         the reading rail (left, from x = the page gutter, width min(36vw,520px))
         and the stage (right, from x = 52vw). No text lives outside the rail or
         outside one stage object's border — captions are the frame's own footer
         line, never floating.

   The helper is always the same shape:
     --x: clamp(0, calc((var(--sc-p,0) - FROM) / (TO - FROM)), 1);   linear 0..1
     --e: calc(var(--x) * var(--x) * (3 - 2 * var(--x)));            smoothstep
   The engine files (scrollcraft.js / scrollcraft.css) are untouched. */

/* Local copies of the two faces tokens.css imports from Google, so the page
   renders identically with or without a network. Saira ships as ONE VARIABLE
   file: it is declared once, with a weight RANGE, so the wght axis really
   moves. IBM Plex Mono is two static cuts. */
@font-face { font-family: "Saira"; font-weight: 100 900; font-style: normal; font-display: swap; src: url("assets/saira.woff2") format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-weight: 400; font-style: normal; font-display: swap; src: url("assets/plexmono-400.woff2") format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-weight: 500; font-style: normal; font-display: swap; src: url("assets/plexmono-500.woff2") format("woff2"); }

:root {
  /* the engine, themed from the tokens */
  --sc-canvas: var(--ink-950);
  --sc-surface: var(--ink-900);
  --sc-ink: var(--paper-100);
  --sc-ink-soft: var(--ink-200);
  --sc-accent: var(--acid-500);
  --sc-accent-ink: var(--ink-950);
  --sc-hairline: var(--ink-600);
  --sc-hairline-strong: var(--ink-300);
  --sc-font-display: var(--font-display);
  --sc-font-text: var(--font-body);
  --sc-font-mono: var(--font-mono);
  --sc-track-tight: var(--tracking-display);
  --sc-track-snug: var(--tracking-display);
  --sc-track-normal: 0;
  --sc-r-sm: var(--radius-sm); --sc-r-md: var(--radius-sm); --sc-r-lg: var(--radius-sm);
  --sc-ease-out: var(--ease-out);

  --bar-h: 3.5rem;
  --gutter: clamp(20px, 4vw, 48px);   /* 48 px at 1440: the page's left type edge */
  --frame-ar: 1.7917;                 /* the official hero plate, 1376 x 768 */

  /* the two zones. The rail column is exactly wide enough that the stage
     column starts at 52vw, at every width, on every act. */
  --rail-col: calc(52vw - var(--gutter));
  --rail-w: min(36vw, 520px);
  --stage-w: min(44vw, 640px);        /* the frame width family */
  --stage-w-card: min(31vw, 450px);   /* act 3, per the sheet */
  --stage-w-res: min(30vw, 435px);    /* act 6, per the sheet */
  --stage-w-wall: min(40vw, 580px);   /* act 1, per the sheet */
  /* a 4:5 plate has to fit the fold, so its width is derived from a height cap
     and the ratio is never broken — the CSS crop depends on it */
  --stage-w-45: min(44vw, 640px, calc(0.8 * min(60svh, 660px)));
}

html { background: var(--ink-950); }
body {
  background: var(--ink-950); color: var(--paper-100);
  font: var(--body-md); font-variant-numeric: tabular-nums lining-nums;
}
img { display: block; }
a { color: inherit; text-decoration: none; }
::selection { background: var(--acid-500); color: var(--ink-950); }
:focus-visible { outline: 2px solid var(--paper-100); outline-offset: 3px; }

.skip { position: absolute; left: -9999px; top: 0; z-index: 200; padding: var(--space-12) var(--space-16); background: var(--paper-100); color: var(--ink-950); }
.skip:focus { left: var(--gutter); top: var(--space-8); }

/* ------------------------------------------------------------------ type -- */
.head { margin: 0; font-family: var(--font-display); font-weight: 600; letter-spacing: var(--tracking-headline); line-height: 1.14; text-wrap: balance; }
.head--lg { font-size: clamp(24px, 0.9rem + 1.05vw, 32px); }
.head--md { font-size: clamp(22px, 0.85rem + 0.9vw, 29px); }
.lede { margin: 0; font: var(--body-md); color: var(--paper-100); }
.body { margin: 0; font: var(--body-md); color: var(--paper-200); }
.mono { font: var(--mono-sm); letter-spacing: var(--tracking-mono-sm); color: var(--ink-200); }
/* the story eyebrow: same size, same colour, same position, every act */
.eyebrow {
  margin: 0 0 calc(-1 * var(--space-12)); font: var(--eyebrow-md);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--ink-200);
}

/* Every block of copy on this page is left-aligned, on one edge. */
.head, .lede, .body, .eyebrow, .frm__foot { text-align: left; }

/* --------------------------------------------------------------- actions -- */
.cta {
  display: inline-flex; align-items: center; justify-content: center;
  padding: var(--space-squish-md);
  background: var(--acid-500); color: var(--ink-950);
  font-family: var(--font-display); font-weight: 600; font-size: 16px; line-height: 1;
  letter-spacing: var(--tracking-display);
  border: var(--rule-hairline) solid var(--acid-500); border-radius: var(--radius-sm);
  transition: background var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.cta:hover { background: var(--paper-100); border-color: var(--paper-100); }
.cta--bar { padding: var(--space-squish-sm); font-size: 14px; }
.quiet { font-family: var(--font-display); font-weight: 500; font-size: 16px; color: var(--paper-100); border-bottom: var(--rule-hairline) solid var(--ink-300); padding-bottom: 3px; }
.quiet:hover { border-color: var(--paper-100); }
.links { display: flex; flex-wrap: wrap; gap: var(--space-inline-lg); margin: 0; }
.links a { font-family: var(--font-display); font-weight: 500; font-size: 15px; color: var(--paper-100); border-bottom: var(--rule-hairline) solid var(--paper-400); padding-bottom: 3px; }

/* ----------------------------------------------------------------- chrome -- */
.bar {
  position: fixed; inset: 0 0 auto 0; z-index: var(--sc-z-chrome);
  height: var(--bar-h); display: flex; align-items: center; gap: var(--space-24);
  padding-inline: var(--gutter);
  /* Fully opaque, not 90 % + blur. A translucent fixed bar lets 10 % of every
     line that passes under it through, which is faint text by another route —
     R1 forbids it. `Measured`: under reduced motion, act 5's H2 half-covered by
     the 90 % bar graded 1.08:1. Opaque, a covered line is simply not on screen. */
  background: var(--ink-950);
  border-bottom: var(--rule-hairline) solid var(--ink-600);
}
.bar__brand { display: inline-flex; align-items: center; gap: var(--space-12); }
.bar__mark { width: 30px; height: auto; }
.bar__word { font-family: var(--font-display); font-weight: 500; font-size: 16px; letter-spacing: var(--tracking-display); white-space: nowrap; }
.bar__nav { display: flex; gap: var(--space-24); margin-left: auto; font: var(--body-sm); color: var(--paper-200); }
.bar__nav a:hover { color: var(--paper-100); }

/* ====================================================== THE RAIL AND THE STAGE
   One grid, one geometry, every act. Column 1 is the rail track; it is exactly
   `52vw - gutter` wide, so column 2 — the stage — always starts at x = 52vw.
   The rail's own content is min(36vw, 520px), so the eyebrow, the H2 and the
   body all start at x = the gutter (48 px at 1440) on every single act. */
.duo {
  position: relative; z-index: var(--sc-z-copy);
  display: grid; grid-template-columns: var(--rail-col) minmax(0, 1fr);
  align-items: center; column-gap: 0;
  padding: calc(var(--bar-h) + var(--space-24)) var(--gutter) var(--space-24);
  min-height: var(--act-min, 100svh);
}
/* Inside a pinned stage the duo fills the pinned viewport instead. It also
   carries the pin's own drift (--lift-drift, §"the velocity match") as extra top
   padding: without it, a stage object taller than the fold ends its pin with its
   header band underneath the fixed bar. Measured at 1366x768 before this: act 6's
   band read 2.2:1 against the bar it had slid under. */
.lift > .duo { position: absolute; inset: 0; min-height: 0; padding-top: calc(var(--bar-h) + var(--space-24) + 8vh); }
.duo--wide { grid-template-columns: minmax(0, 1fr); }

.rail { width: var(--rail-w); display: grid; gap: var(--space-24); align-content: center; }
.rail--wide { width: 100%; }
.rail > * { max-width: 100%; }

.stagecol { display: grid; align-content: center; justify-items: start; }
.obj { width: var(--stage-w); }
.obj--wall { width: var(--stage-w-wall); }
.obj--card { width: var(--stage-w-card); }
.obj--results { width: var(--stage-w-res); }
.obj--plate, .obj--unit { width: var(--stage-w-45); }

/* ------------------------------------------------------------- the frame --
   Product screens are built HTML, never a picture of a product. Ink ground,
   1 px rules, mono for product, camera, time and event id, acid ONLY as the
   match marker inside a frame. Every frame carries the same header band and,
   new in v4, its own FOOTER LINE: the caption lives inside the border of the
   thing it describes, so there is nothing floating anywhere on the page. */
.frm {
  margin: 0; background: var(--ink-950);
  border: var(--rule-hairline) solid var(--ink-600); border-radius: var(--radius-sm);
  overflow: hidden;
}
.frm--card, .frm--results { border-color: var(--ink-300); }
.frm__band {
  display: flex; align-items: baseline; gap: var(--space-12); flex-wrap: wrap;
  margin: 0; padding: var(--space-12) var(--space-16);
  border-bottom: var(--rule-hairline) solid var(--ink-600);
  font: var(--mono-xs); letter-spacing: var(--tracking-mono-xs); text-transform: uppercase;
  color: var(--paper-200);
}
.frm__prod { color: var(--paper-100); }
.frm__cam { color: var(--paper-200); }
.frm__time { margin-left: auto; color: var(--paper-200); }
.frm__body { position: relative; }
.frm__body img { width: 100%; height: auto; }
.frm__box { position: absolute; border: var(--rule-emphasis) solid var(--acid-500); border-radius: var(--radius-sm); }
.frm__foot {
  margin: 0; padding: var(--space-12) var(--space-16);
  border-top: var(--rule-hairline) solid var(--ink-600);
  font: var(--body-sm); color: var(--paper-100);
}

.dot { width: 8px; height: 8px; border-radius: var(--radius-full); display: inline-block; background: var(--acid-500); flex: none; }
.tag { display: inline-block; padding: var(--space-4) var(--space-8); background: var(--acid-500); color: var(--ink-950); font: var(--mono-xs); letter-spacing: var(--tracking-mono-xs); border-radius: var(--radius-sm); }
.btn { display: inline-block; padding: var(--space-squish-xs); border: var(--rule-hairline) solid var(--ink-300); color: var(--paper-100); font: var(--body-sm); border-radius: var(--radius-sm); text-align: center; }
/* Export is a control, so it is filled paper, not an outline the same weight as
   the frame rules around it (reviewer walk, act 7). */
.btn--fill { background: var(--paper-100); color: var(--ink-950); border-color: var(--paper-100); font-weight: 600; }
.rule { display: block; background: var(--paper-400); }
.glyph { width: 13px; height: auto; flex: none; }

/* the one-shot rows: a whole row of text arrives once, at FULL INK, in 240 ms,
   with its height reserved from the start so nothing below it ever moves. */
.state {
  display: flex; align-items: center; gap: var(--space-8); margin: 0;
  min-height: 1.75em;
  font: var(--mono-md); letter-spacing: var(--tracking-mono); color: var(--paper-100);
}
.state > *, .reply > * {
  opacity: 0; transform: translate3d(0, 4px, 0);
  transition: opacity var(--duration-base) var(--ease-out), transform var(--duration-base) var(--ease-out);
}
.state.is-on > *, .reply.is-on > *, .reply.is-on .state > * { opacity: 1; transform: none; }

/* every scrim is a SIBLING of the copy it serves, never a child and never a
   pseudo-element: the verification pass hides a cue and all its descendants to
   photograph the real background under each line. */
.scrim { position: absolute; inset: 0; z-index: 5; pointer-events: none; }
/* The rail sits on ink even where the stage is a full-bleed clip. Measured on
   the brightest frame of both clips, the rail's whole width is under 0.05
   luminance with this ramp, so every line clears 4.5:1 at every scrub position
   rather than only on the dark frames. The sheet asks for 0.92 -> 0 across
   0-52vw; that leaves the rail's right-hand third at 0.28 ink, which is not
   enough under a sodium light. */
.scrim--rail {
  z-index: 6;
  background: linear-gradient(to right,
    color-mix(in oklab, var(--ink-950) 96%, transparent) 0%,
    color-mix(in oklab, var(--ink-950) 96%, transparent) 40%,
    color-mix(in oklab, var(--ink-950) 66%, transparent) 48%,
    transparent 62%);
}
/* the plate behind a frame is graded so the frame is the brightest thing on
   the right and the rail the brightest thing on the left */
.scrim--grade { z-index: 5; background: color-mix(in oklab, var(--ink-950) 45%, transparent); }

/* the acts */
.act { position: relative; background: var(--ink-950); }
.stage { background: var(--ink-950); }

/* ------------------------------------------------------- the velocity match
   A pin holds its stage still, so almost nothing changes per wheel notch; the
   notch after it releases scrolls the whole document. That step from zero to
   full speed is the "jump at the start" the audit measured. Two things happen
   here at once:

   1. a symmetric quadratic ramp over the first and last 15 % of the pin, whose
      slope is exactly document speed at both boundaries;
   2. a small CONSTANT DRIFT across the whole pin.

   v3 could rely on its opacity ramps to keep something moving inside a pin.
   v4 has none — R1 removed every one of them — so without (2) a pinned act
   with a graded night clip behind it changes less than 0.2 % of the screen for
   300 px at a time, which is the dead run the motion sheet forbids. (2) is what
   §1 of that sheet asks for in as many words: "every pinned act keeps at least
   one continuous property moving until p = 1".

     in  = p / 0.15 ,  out = (p - 0.85) / 0.15
     T(p) = (R/2)(1-in)^2 - (R/2)(out)^2 - p*D
     R    = 0.15 * (travel - D)     so that T'(0) = -travel exactly, drift and all

   --lift-h is R/2 and --lift-drift is D, per act, in viewport heights. */
.lift {
  position: absolute; inset: 0;
  --in:  clamp(0, calc(var(--sc-p, 0) / 0.15), 1);
  --out: clamp(0, calc((var(--sc-p, 0) - 0.85) / 0.15), 1);
  transform: translate3d(0, calc(
      (1 - var(--in)) * (1 - var(--in)) * var(--lift-in-h, var(--lift-h, 0vh))
    - var(--out) * var(--out) * var(--lift-h, 0vh)
    - var(--sc-p, 0) * var(--lift-drift, 0vh)), 0);
  will-change: transform;
}

/* ================================================================ ACT 0 · HERO
   span 1.7 -> 70vh of pinned travel; --lift = -(0.15 * 70 / 2) = -5.25vh */
.act--hero { --lift-h: 4.65vh; --lift-in-h: 0vh; --lift-drift: 8vh; }   /* travel 70vh */
.hero { overflow: clip; }
.scene {
  position: absolute; left: 50%; top: 50%; translate: -50% -50%;
  width: max(112%, calc(100svh * var(--frame-ar) * 1.12));
  aspect-ratio: 1376 / 768;
}
.plane { position: absolute; inset: 0; will-change: transform; }
.plane img { max-width: none; }
.plane--sky { z-index: 1; transform: translate3d(0, calc(var(--sc-p, 0) * -2vh), 0); }
.plane--sky img { position: absolute; left: 0; top: 0; width: 100%; height: auto; }
.plane--ground { z-index: 2; transform: translate3d(0, calc(var(--sc-p, 0) * -8vh), 0); }
.plane--ground img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 13%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 13%);
}
.plane--near { z-index: 3; transform: translate3d(0, calc(var(--sc-p, 0) * -16vh), 0); }
.bldg {
  position: absolute; left: 72.674%; top: 7.8125%; width: 27.326%; height: 92.1875%;
  transform-origin: 50% 100%;
  transform: scale(calc(1 + var(--sc-p, 0) * 0.05));
}
.plane--floor { z-index: 4; background: linear-gradient(to top, var(--ink-950) 0%, transparent 20%); }
/* THE SKY SCRIM. In v3 the dusk sky in the top right was the brightest area of
   the first screen and pulled against the H1. An ink gradient over the top 45 %
   puts the headline back in charge. No spend, no new plate. */
.scrim--sky {
  z-index: 4; inset: 0 0 auto 0; height: 45%;
  background: linear-gradient(to bottom,
    color-mix(in oklab, var(--ink-950) 78%, transparent) 0%,
    color-mix(in oklab, var(--ink-950) 52%, transparent) 55%,
    transparent 100%);
}
.rail--hero { gap: var(--space-24); }
.hero__title { margin: 0; font-family: var(--font-display); font-weight: 500; letter-spacing: var(--tracking-display); line-height: 1.06; font-size: clamp(28px, 1rem + 1.85vw, 44px); }
.hero__sub { margin: 0; font: var(--body-md); color: var(--paper-100); }
.hero__acts { display: flex; align-items: center; gap: var(--space-24); margin: 0; }

/* the hero frame rises; nothing about it fades, because it carries text */
.obj--hero {
  --f: clamp(0, calc(var(--sc-p, 0) / 0.45), 1);
  --fe: calc(var(--f) * var(--f) * (3 - 2 * var(--f)));
  transform: translate3d(0, calc((1 - var(--fe)) * 6vh), 0);
}
/* the PICTURE brightens, not the text: this is the one luminance ramp on the
   hero and it acts on an image only */
.obj--hero .frm__plate { filter: brightness(calc(0.6 + 0.4 * var(--fe))); }
/* Place one of the signature move. At 1.0 from p = 0, in quote marks: the watch
   that was set, now running. Act 2 shows how it was typed. */
.frm__watch {
  margin: 0; padding: var(--space-12) var(--space-16);
  border-bottom: var(--rule-hairline) solid var(--ink-600);
  font: var(--mono-sm); letter-spacing: var(--tracking-mono-sm); color: var(--paper-100);
}
.frm__watch-key { color: var(--acid-500); }
.obj--hero .frm__box {
  left: 45%; top: 47%; width: 15%; height: 34%;
  --b: clamp(0, calc((var(--sc-p, 0) - 0.40) / 0.30), 1);
  --be: calc(var(--b) * var(--b) * (3 - 2 * var(--b)));
  opacity: var(--be);
  transform: scale(calc(0.9 + 0.1 * var(--be)));
  transform-origin: 50% 50%;
}
/* The state line changes in place, WATCHING -> PERSON AT FENCE. A crossfade
   would put a text node at partial opacity for a whole window, which R1 forbids
   outright, so this is the same one-shot the other three state lines use: the
   old line goes at once, the new one enters whole, at full ink, in 240 ms, into
   a slot whose height was reserved from the first pixel. */
.frm__state {
  position: relative; display: flex; align-items: center; gap: var(--space-8);
  margin: 0; padding: var(--space-12) var(--space-16);
  border-top: var(--rule-hairline) solid var(--ink-600);
  font: var(--mono-md); letter-spacing: var(--tracking-mono);
  min-height: 3.1em;
}
.frm__state-a, .frm__state-b { position: absolute; left: calc(var(--space-16) + 8px + var(--space-8)); color: var(--paper-100); }
.frm__state-b { visibility: hidden; opacity: 0; transform: translate3d(0, 4px, 0); transition: opacity var(--duration-base) var(--ease-out), transform var(--duration-base) var(--ease-out); }
#hero-state.is-b .frm__state-a { visibility: hidden; }
#hero-state.is-b .frm__state-b { visibility: visible; opacity: 1; transform: none; }

/* ============================================================= ACT 1 · PROBLEM
   exactly one viewport tall, rail and stage on the same centre line. */
.act--problem { height: 100svh; }
.tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-8); padding: var(--space-12);
  --t: clamp(0, calc((var(--sc-p, 0) - 0.15) / 0.40), 1);
  --te: calc(var(--t) * var(--t) * (3 - 2 * var(--t)));
  transform: translate3d(0, calc((1 - var(--te)) * 4vh), 0);
}
.tile { display: grid; grid-template-rows: auto auto; }
.tile img { width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border: var(--rule-hairline) solid var(--ink-600); border-bottom: 0; }
/* the label is a band UNDER the tile at every width, so no label ever sits on
   the picture it names */
.tile__cam {
  display: block; padding: 3px 5px;
  background: var(--ink-900); border: var(--rule-hairline) solid var(--ink-600); border-top: 0;
  font: var(--mono-xs); letter-spacing: 0.02em; color: var(--paper-100);
  white-space: nowrap; overflow: hidden;
}

/* ================================================== ACTS 2 AND 5 · THE COMPOSER
   The sheet's binding note: a reader must be able to see that a PERSON TYPED
   the sentence into the product and that the product ANSWERED. So the frame is
   a conversation read top to bottom:

     [composer]  a bordered input field with a placeholder, the sentence written
                 INTO it at full ink by a clip-path wipe with a caret at the
                 write head, and a send control that is muted until the sentence
                 is complete and then fills acid;
     [reply]     the product's own row — glyph, product name, then what it says
                 back — entering whole, once, into a reserved slot;
     [state]     the confirmation line.

   Nothing else inside the frame moves. */
.conv { display: grid; gap: var(--space-16); padding: var(--space-16); }
.cmp__field {
  display: grid; gap: var(--space-12);
  padding: var(--space-12) var(--space-16); min-height: 56px;
  background: var(--ink-800);
  border: var(--rule-hairline) solid color-mix(in oklab, var(--paper-100) 30%, transparent);
  border-radius: var(--radius-sm);
}
.cmp__line {
  position: relative; margin: 0; min-height: 1.6em; overflow: hidden;
  font-family: var(--font-body); font-weight: 400; line-height: 1.6;
  /* sized from the field's own width so the sentence is ONE line with ONE write
     head at every desktop width: 73 characters is the longest of the two */
  font-size: clamp(11px, calc((0.44 * 100vw - 68px) / 36.5), 15px);
  color: var(--paper-100);
}
/* The placeholder is real UI text, so it is at full opacity, in a token colour.
   The sheet writes it as "paper 0.5"; a 50 % paper on this field ground measures
   2.6:1, and `--ink-300` (the design system's "muted on ink") measures 4.45:1 on
   G1's own reading — both under the 4.5:1 bar. It is one token step lighter,
   `--ink-200`, which is still visibly recessed against the written sentence in
   `--paper-100` beside it. */
.cmp__ph { position: absolute; left: 0; top: 0; color: var(--ink-200); white-space: nowrap; }
.cmp.is-typing .cmp__ph { visibility: hidden; }
.ink { position: relative; display: inline-block; white-space: nowrap; max-width: 100%; }
/* the written part at 1.0, the unwritten part at 0 — never a faint ghost */
.ink__full { display: inline-block; clip-path: inset(0 calc((1 - var(--wipe, 0)) * 100%) 0 0); }
/* the caret belongs to the act of typing: before the first word it would sit on
   top of the placeholder's first letter, which is not what a field does and
   which measurably darkens the placeholder's own contrast */
.ink__caret {
  position: absolute; top: 0.12em; bottom: 0.12em; left: calc(var(--wipe, 0) * 100%);
  width: 2px; background: var(--paper-100); visibility: hidden;
  animation: sc-caret 1s step-end infinite;
}
.cmp.is-typing .ink__caret { visibility: visible; }
@keyframes sc-caret { 0%, 50% { opacity: 1 } 50.01%, 100% { opacity: 0 } }
#watch-ink { --w: clamp(0, calc((var(--sc-p, 0) - 0.15) / 0.45), 1); --wipe: calc(var(--w) * var(--w) * (3 - 2 * var(--w))); }
#ask-ink   { --w: clamp(0, calc((var(--sc-p, 0) - 0.15) / 0.45), 1); --wipe: calc(var(--w) * var(--w) * (3 - 2 * var(--w))); }

.cmp__ctrl { margin: 0; display: flex; justify-content: flex-end; }
.cmp__send {
  display: inline-block; padding: var(--space-4) var(--space-12);
  font: var(--label-md); letter-spacing: var(--tracking-label-lg);
  color: var(--ink-200); background: transparent;
  border: var(--rule-hairline) solid var(--ink-300); border-radius: var(--radius-sm);
}
/* the control arms as the last word lands. It is a discrete UI state, not a
   glide: any blend between muted-on-ink and ink-on-acid passes through a pair
   that measures under 4.5:1, and G1 allows no exceptions. */
.cmp.is-armed .cmp__send { color: var(--ink-950); background: var(--acid-500); border-color: var(--acid-500); }

.reply { display: grid; gap: var(--space-8); align-content: start; }
.reply__who {
  display: flex; align-items: center; gap: var(--space-8); margin: 0;
  font: var(--mono-xs); letter-spacing: var(--tracking-mono-xs); text-transform: uppercase;
  color: var(--paper-200);
}
.reply__q { margin: 0; font: var(--body-md); color: var(--paper-100); }
.chips { display: flex; flex-wrap: wrap; gap: var(--space-8); margin: 0; }
.chipx { padding: var(--space-4) var(--space-8); border: var(--rule-hairline) solid var(--ink-300); font: var(--mono-xs); letter-spacing: var(--tracking-mono-xs); color: var(--paper-100); }

/* ========================================================== ACT 2 · SET A WATCH
   span 1.8 -> 80vh travel; --lift = -6vh */
.act--watch { --lift-h: 5.4vh; --lift-drift: 8vh; }                     /* travel 80vh */
.obj--watch, .obj--ask {
  --f: clamp(0, calc(var(--sc-p, 0) / 0.25), 1);
  --fe: calc(var(--f) * var(--f) * (3 - 2 * var(--f)));
  transform: translate3d(0, calc((1 - var(--fe)) * 6vh), 0);
}
#watch-reply { min-height: 92px; }

/* ============================================================== ACT 5 · ASK
   span 1.8 -> 80vh travel; --lift = -6vh */
.act--ask { --lift-h: 5.4vh; --lift-drift: 8vh; }                       /* travel 80vh */
#ask-reply { min-height: 108px; }
.state--inline { min-height: 1.9em; font: var(--mono-sm); letter-spacing: var(--tracking-mono-sm); }
/* the row of cameras the question is about: an IMAGE at a fixed level, never a
   ramp, and never text */
.ask__thumbs { display: grid; grid-template-columns: repeat(9, 1fr); gap: var(--space-4); opacity: 0.6; }
.ask__thumbs img { width: 100%; height: 46px; object-fit: cover; border: var(--rule-hairline) solid var(--ink-600); }

/* ============================================================= ACT 3 · ALERT
   span 1.7 -> 70vh travel; --lift = -5.25vh */
.act--moment { --lift-h: 4.65vh; --lift-drift: 8vh; }                   /* travel 70vh */
.moment { overflow: clip; }
/* the card is at full ink from the first pixel and only rises */
.obj--card {
  --c: clamp(0, calc(var(--sc-p, 0) / 0.30), 1);
  --ce: calc(var(--c) * var(--c) * (3 - 2 * var(--c)));
  transform: translate3d(0, calc((1 - var(--ce)) * 3vh), 0);
}
.card__in { display: grid; gap: var(--space-12); padding: var(--space-16) var(--space-16) var(--space-20); }
.obj--card p { margin: 0; }
.state--card { padding: var(--space-8) var(--space-16) 0; min-height: 2.1em; font: var(--mono-sm); letter-spacing: var(--tracking-mono-sm); }
.card__title { font-family: var(--font-display); font-weight: 600; font-size: 22px; letter-spacing: var(--tracking-headline); color: var(--paper-100); }
.card__watch { font: var(--body-sm); color: var(--paper-100); border-left: var(--rule-accent) solid var(--acid-500); padding-left: var(--space-12); }
.card__why { font: var(--body-sm); color: var(--paper-200); }
.card__meta { display: flex; align-items: center; gap: var(--space-12); }
.card__acts { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-8); }
/* the phone is not a second object any more: it is this card's footer row, so
   there is one border, one object, and no floating hairline anywhere */
.frm__foot--phone { display: flex; align-items: center; gap: var(--space-8); font: var(--body-xs); }
.frm__foot-t { color: var(--paper-100); }
.pglyph {
  flex: none; width: 12px; height: 19px; border-radius: 3px;
  border: var(--rule-hairline) solid var(--paper-400);
  background: linear-gradient(to bottom, transparent 0 3px, color-mix(in oklab, var(--paper-100) 22%, transparent) 3px calc(100% - 3px), transparent calc(100% - 3px));
}
.delivered { display: inline-flex; align-items: center; gap: 5px; margin-left: auto; font: var(--mono-xs); letter-spacing: var(--tracking-mono-xs); color: var(--paper-200); white-space: nowrap; }
.delivered .dot { width: 6px; height: 6px; }

/* ============================================================== ACT 4 · MEMORY */
/* act 4 */
.act--memory { --act-min: 80svh; }
.plate--45 { width: 100%; height: auto; aspect-ratio: 4 / 5; object-fit: cover; }
.memory__tag {
  position: absolute; left: 51%; top: 28%;
  display: grid; gap: 2px; padding: var(--space-8) var(--space-12);
  background: var(--ink-950); border: var(--rule-hairline) solid var(--ink-300);
  border-radius: var(--radius-sm); max-width: 46%;
  --g: clamp(0, calc((var(--sc-p, 0) - 0.30) / 0.15), 1);
  --ge: calc(var(--g) * var(--g) * (3 - 2 * var(--g)));
  transform: translate3d(0, calc((1 - var(--ge)) * 4px), 0);
}
.memory__tag-a { font: var(--mono-xs); letter-spacing: var(--tracking-mono-xs); text-transform: uppercase; color: var(--paper-100); }
.memory__tag-b { font: var(--body-xs); color: var(--paper-200); }

/* ============================================================= ACT 6 · ANSWER
   span 2.4 -> 140vh travel; --lift = -(0.15 * 140 / 2) = -10.5vh
   The clip is 12.75 s: gate 0-4.04 s, car park 4.04-8.08 s, dock 8.08-12.13 s,
   then 0.62 s holding the last frame. Mapped to the pin the three legs START at
   p = 0.000 / 0.317 / 0.634, and each card opens on its own leg. */
.act--answer { --lift-h: 9.9vh; --lift-drift: 8vh; }                    /* travel 140vh */
.answer { overflow: clip; }
.reply__who--res { padding: var(--space-12) var(--space-16) 0; }
.results__asked {
  margin: 0; padding: var(--space-4) var(--space-16) var(--space-12);
  border-bottom: var(--rule-hairline) solid var(--ink-600);
  font: var(--body-sm); color: var(--paper-100);
}
/* Every slot is reserved from the first pixel, so nothing below a card ever
   moves. A card is either absent or WHOLE: it arrives at full ink with the
   design-system 4 px enter, on the notch its own camera leg starts.
   v3 slid the cards in and they read as CUT for six sampled frames; sliding a
   card in from outside the column has the same defect at every intermediate
   position, because with opacity fixed at 1.0 a half-arrived card is a
   half-visible card. §4's own words are "enter as whole objects at full ink". */
.results { list-style: none; margin: 0; padding: var(--space-8); display: grid; gap: var(--space-8); overflow: hidden; }
.res {
  display: grid; gap: var(--space-4); padding: var(--space-8);
  background: var(--ink-950); border: var(--rule-hairline) solid var(--ink-600); border-radius: var(--radius-sm);
}
/* leg 1 starts at p = 0: result one is already there when the act arrives */
.res--2, .res--3 {
  visibility: hidden; transform: translate3d(0, 4px, 0);
  transition: transform var(--duration-base) var(--ease-out);
}
.res--2.is-on, .res--3.is-on { visibility: visible; transform: none; }
/* the panel has to fit a 1366x768 laptop as well as a 1440x900 one, so the
   thumb is capped by viewport height too */
.res img { width: 100%; height: auto; aspect-ratio: 21 / 9; max-height: min(92px, 10svh); object-fit: cover; }
.res p { margin: 0; }
.res__meta { display: flex; justify-content: space-between; gap: var(--space-8); font: var(--mono-xs); letter-spacing: var(--tracking-mono-xs); text-transform: uppercase; color: var(--paper-200); }
.res__why { font: var(--body-sm); color: var(--paper-100); }
/* the match tag is written on, not faded on: revealed left to right at full ink */
.res__tags {
  display: flex; align-items: center; gap: var(--space-12); padding-top: var(--space-4);
  --g: clamp(0, calc((var(--sc-p, 0) - 0.78) / 0.10), 1);
  --ge: calc(var(--g) * var(--g) * (3 - 2 * var(--g)));
  clip-path: inset(0 calc((1 - var(--ge)) * 100%) 0 0);
}

/* =============================================================== ACT 7 · CASE
   the card is revealed by one clip-path wipe; where it is revealed it is whole
   and at full ink. */
.act--pack { --act-min: 62svh; }
.frm--pack {
  position: relative; border-color: var(--ink-300);
  --k: clamp(0, calc((var(--sc-p, 0) - 0.10) / 0.35), 1);
  --ke: calc(var(--k) * var(--k) * (3 - 2 * var(--k)));
  clip-path: inset(0 0 calc((1 - var(--ke)) * 100%) 0);
}
.pack__thumbs { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); padding: var(--space-8); }
.pack__thumbs img { width: 100%; height: auto; border: var(--rule-hairline) solid var(--ink-600); }
.pack__note { margin: 0; padding: 0 var(--space-16) var(--space-12); font: var(--mono-sm); letter-spacing: var(--tracking-mono-sm); color: var(--paper-200); }
.pack__acts { display: flex; align-items: center; gap: var(--space-16); margin: 0; padding: 0 var(--space-16) var(--space-16); }

/* =============================================================== ACT 8 · SITE
   The plate moves into the stage and is re-cropped to 4:5 by CSS alone. The
   out-of-focus figure begins at x 850 of 1600 in the source; a 4:5 window is
   720 px wide, so `object-position: 0%` takes [0, 720] — the figure is out of
   the frame entirely and the unit sits at 53 % across, on the centre line. */
.act--site { --act-min: 72svh; }
.site__plate { object-position: 0% 50%; }
.site__label { position: absolute; z-index: 3; right: 70%; top: 54%; display: flex; flex-direction: row-reverse; align-items: center; gap: var(--space-8); }
.rule--unit { width: 18px; height: 1px; }
.site__label-t { font: var(--mono-xs); letter-spacing: var(--tracking-mono-xs); color: var(--paper-100); background: color-mix(in oklab, var(--ink-950) 84%, transparent); padding: 3px 6px; white-space: nowrap; }

/* ============================================================= ACT 9 · INDUSTRIES
   the one act with no stage: the rail runs the full width, same left edge as
   every other act's rail (see "the rail and the stage", above). Mechanism is
   MapNova-inspired — borrowed structure, not their palette/taxonomy/wording/
   rounded controls (see docs/REFERENCE-NOTES.md), reworked to read as inset
   floating cards rather than full-bleed panels: the SLOT is sticky and
   full-height, padding lives on the slot, and the card sits centered inside
   it well short of the slot's own edges — the page ground stays visible
   above, below and beside every card. As the next card's slot reaches the
   tab strip, the current one both gets covered AND keeps scaling down a
   little further with every card that stacks on top of it — a receding-deck
   depth cue, ported from a shadcn/Motion stacking-cards reference the
   founder liked; continuous scroll-linked scale can't be done in pure CSS,
   so industries.js measures scroll progress once per frame and writes
   --card-scale. Tab-strip sync and click-to-advance ride the same number. */
.act--industries {
  --industries-card-radius: 20px; /* a deliberate one-off, well past --radius-sm:
    these are meant to read as loose photographic cards, not product chrome. */
  border-top: var(--rule-hairline) solid var(--ink-600);
}
.act--industries .duo--wide { min-height: 0; padding-block: calc(var(--bar-h) + var(--space-40)) var(--space-24); }

/* height is fixed, not intrinsic, so the card slots' sticky offset below can
   sit exactly flush against it instead of guessing at a rendered height. No
   wrapping bar/background: a full-width black rectangle read as boxy and
   didn't line up with the inset cards below (founder review). Each pill
   carries its own opaque fill instead — legible if a card ends up scrolling
   underneath it, without a slab connecting them — and the row is capped to
   the same width as the cards so the two actually line up. */
.industry-tabs {
  position: sticky; top: var(--bar-h); z-index: 10;
  width: min(1180px, 92%); height: var(--space-64); align-items: center;
  margin: var(--space-32) auto 0;
  display: flex; gap: var(--space-12);
  overflow-x: auto; scrollbar-width: none;
}
.industry-tabs::-webkit-scrollbar { display: none; }
/* A strip this width clips tab names mid-word at its edges. industries.js
   marks each edge that actually hides content; fading only those edges keeps
   the cut intentional-looking while a fully-scrolled edge stays hard and
   honest instead of promising more tabs than are left. */
.industry-tabs { --tabs-fade: var(--space-48); }
.industry-tabs.edge-left {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--tabs-fade));
  mask-image: linear-gradient(to right, transparent, #000 var(--tabs-fade));
}
.industry-tabs.edge-right {
  -webkit-mask-image: linear-gradient(to left, transparent, #000 var(--tabs-fade));
  mask-image: linear-gradient(to left, transparent, #000 var(--tabs-fade));
}
.industry-tabs.edge-left.edge-right {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--tabs-fade), #000 calc(100% - var(--tabs-fade)), transparent);
  mask-image: linear-gradient(to right, transparent, #000 var(--tabs-fade), #000 calc(100% - var(--tabs-fade)), transparent);
}
.industry-tab {
  flex: none;
  background: var(--ink-950); cursor: pointer; white-space: nowrap;
  padding: var(--space-12) var(--space-20);
  border: var(--rule-hairline) solid var(--ink-600); border-radius: var(--night-control-radius, var(--radius-sm));
  font: var(--mono-md); letter-spacing: var(--tracking-mono-sm); color: var(--ink-300);
  transition: color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.industry-tab:hover { color: var(--paper-100); border-color: var(--ink-300); }
.industry-tab.is-active { color: var(--ink-950); background: var(--acid-500); border-color: var(--acid-500); font-weight: 600; }

.industry-stack__hint {
  display: flex; align-items: center; gap: var(--space-8);
  margin: var(--space-24) 0 0;
  font: var(--mono-sm); letter-spacing: var(--tracking-mono-sm); color: var(--ink-300);
}

.industry-stack { display: grid; margin-top: var(--space-16); }
.industry-card-slot {
  position: sticky; top: calc(var(--bar-h) + var(--space-64));
  height: min(80vh, 640px); min-height: 420px;
  display: flex; align-items: center; justify-content: center;
  scroll-margin-top: calc(var(--bar-h) + var(--space-64));
}
.industry-card {
  width: min(1180px, 92%);
  height: min(56vh, 480px);
  transform: scale(var(--card-scale, 1));
  transform-origin: 50% 20%;
  will-change: transform;
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  align-items: stretch;
  gap: var(--space-32);
  padding: var(--space-32);
  background: var(--ink-950);
  border: var(--rule-hairline) solid var(--ink-600);
  border-radius: var(--industries-card-radius);
}
.industry-card__media { position: relative; height: 100%; overflow: hidden; background: var(--ink-900); border-radius: calc(var(--industries-card-radius) - var(--space-12)); }
.industry-card__media img { width: 100%; height: 100%; object-fit: cover; }
.industry-card__placeholder {
  display: grid; place-items: center; width: 100%; height: 100%; padding: var(--space-16);
  border: var(--rule-hairline) dashed var(--ink-600); border-radius: inherit;
  font: var(--mono-xs); letter-spacing: var(--tracking-mono-xs); text-transform: uppercase; text-align: center;
  color: var(--ink-400);
}
.industry-card__body { display: flex; flex-direction: column; justify-content: center; gap: var(--space-12); min-height: 0; overflow: hidden; }
.industry-card__accent { width: var(--rule-accent); height: 2px; background: var(--acid-500); margin: 0 0 var(--space-4); }
.industry-card__eyebrow { margin: 0; font: var(--mono-xs); letter-spacing: var(--tracking-mono-xs); text-transform: uppercase; color: var(--ink-300); }
.industry-card__title { margin: 0; font: var(--headline-md); color: var(--paper-100); }
.industry-card__tease { margin: 0; font: var(--body-sm); color: var(--paper-200); max-width: 46ch; }
.industry-card__link { margin-top: var(--space-8); align-self: flex-start; }
.industry-stack__all { margin-top: var(--space-40); align-self: flex-start; }

/* =============================================================== ACT 10 · DOOR */
.door__grid { align-items: start; padding-top: calc(var(--bar-h) + var(--space-64)); }
.rail--door { width: min(52vw, 660px); }
.door__acts { display: flex; align-items: center; gap: var(--space-24); margin: var(--space-8) 0 0; }
/* the bookend is set apart by its own rule and mono size, so it reads as a
   signature and not as a fourth sentence */
.door__bookend {
  margin: var(--space-24) 0 0; padding-top: var(--space-16);
  border-top: var(--rule-hairline) solid var(--ink-600);
  font: var(--mono-sm); letter-spacing: var(--tracking-mono-sm); color: var(--paper-200);
}
.foot { position: absolute; left: var(--gutter); right: var(--gutter); bottom: var(--space-24); z-index: calc(var(--sc-z-copy) + 1); display: grid; gap: var(--space-12); padding-top: var(--space-20); border-top: var(--rule-hairline) solid var(--ink-600); }
.foot__lockup { width: 150px; height: auto; }
.foot__nav { display: flex; flex-wrap: wrap; gap: var(--space-20); font: var(--body-sm); color: var(--paper-200); }
.foot__nav--small { font: var(--body-xs); }
.foot__nav a:hover { color: var(--paper-100); }
.foot__where { margin: 0; font: var(--mono-sm); letter-spacing: var(--tracking-mono-sm); color: var(--paper-200); }

/* --------------------------------------------------------- reduced motion --
   Not a slower page: a shorter one. Every span collapses to 1.0 in site.js and
   every act is rendered at p = 1, so the reader gets the finished composition
   of every act. Under R1 there is nothing to un-fade any more — the only things
   to finish are the wipes, the composers and the one-shot rows. */
@media (prefers-reduced-motion: reduce) {
  /* tokens.css collapses motion under this query with
     `transition-duration: 1ms !important` on `*`. The initial value of
     `transition-property` is `all`, so that rule quietly gives every element a
     1 ms discrete transition on `visibility` too, and the harness then
     photographs the fixed bar as the background behind a headline. Nothing here
     transitions visibility on purpose, so take it off the list. The token sheet
     itself is not edited. */
  *, *::before, *::after {
    transition-property: opacity, transform, color, background-color, border-color, clip-path, filter !important;
  }
  .lift { transform: none !important; }
  .plane--sky, .plane--ground, .plane--near, .bldg { transform: none !important; }
  .obj--hero, .obj--watch, .obj--ask, .obj--card, .tiles, .res, .memory__tag { transform: none !important; }
  .res--2, .res--3 { visibility: visible !important; }
  .obj--hero .frm__plate { filter: none !important; }
  .obj--hero .frm__box { opacity: 1 !important; transform: none !important; }
  .frm__state-a { visibility: hidden !important; }
  .frm__state-b { visibility: visible !important; opacity: 1 !important; transform: none !important; }
  .ink__full { clip-path: none !important; }
  .ink__caret { display: none !important; }          /* no blink under reduced motion */
  .cmp__ph { visibility: hidden !important; }
  .cmp__send { color: var(--ink-950) !important; background: var(--acid-500) !important; border-color: var(--acid-500) !important; }
  .res__tags, .frm--pack { clip-path: none !important; }
  .state > *, .reply > * { opacity: 1 !important; transform: none !important; }
  .act--problem { height: auto; }
}

/* --------------------------------------------------------------- mobile --
   Art-directed, not shrunk: the rail sits above the stage, frames go full
   width, plane travel is halved, and the composer keeps its whole anatomy with
   an icon-sized send control. The rail is still the first thing and the stage
   still the second: one place to look, then the next. */
@media (max-width: 860px) {
  :root { --bar-h: 3rem; --rail-w: 100%; --stage-w: 100%; --stage-w-card: 100%; --stage-w-res: 100%; --stage-w-wall: 100%; --stage-w-45: 100%; }
  .bar__nav { display: none; }
  .bar__word { font-size: 14px; }
  .cta--bar { margin-left: auto; }
  .head--lg { font-size: clamp(23px, 5.6vw, 28px); }
  .head--md { font-size: clamp(21px, 5.2vw, 26px); }

  .duo { grid-template-columns: minmax(0, 1fr); align-content: start; row-gap: var(--space-20);
         padding: calc(var(--bar-h) + var(--space-16)) var(--gutter) var(--space-20); min-height: 100svh; }
  /* a pinned stage drifts up by --lift-drift over its pin; on a phone the rail is
     top-anchored, so it needs that much clearance or the eyebrow ends the pin
     underneath the fixed bar */
  .lift > .duo { padding-top: calc(var(--bar-h) + var(--space-16) + 4.5vh); }
  .rail { gap: var(--space-16); align-content: start; }
  .stagecol { justify-items: stretch; }
  .scrim--rail { background: linear-gradient(to bottom, color-mix(in oklab, var(--ink-950) 96%, transparent) 0%, color-mix(in oklab, var(--ink-950) 92%, transparent) 44%, color-mix(in oklab, var(--ink-950) 60%, transparent) 68%, transparent 100%); }

  /* hero */
  .act--hero { --lift-h: 2.325vh; --lift-in-h: 0vh; --lift-drift: 4vh; } /* travel 35vh */
  .scene { width: max(160%, calc(100svh * var(--frame-ar) * 0.82)); }
  .plane--sky { transform: translate3d(0, calc(var(--sc-p, 0) * -1vh), 0); }
  .plane--ground { transform: translate3d(0, calc(var(--sc-p, 0) * -4vh), 0); }
  .plane--near { transform: translate3d(0, calc(var(--sc-p, 0) * -8vh), 0); }
  .bldg { transform: none; }
  .scrim--sky { height: 34%; }
  .hero__title { font-size: clamp(24px, 6.6vw, 31px); }
  .hero__sub { font: var(--body-sm); }
  .obj--hero .frm__body img { max-height: 20vh; object-fit: cover; }
  .frm__watch { font: var(--mono-xs); letter-spacing: var(--tracking-mono-xs); }
  .frm__state { min-height: 2.6em; }

  /* problem: the act may be taller than one screen on a phone */
  .act--problem { height: auto; min-height: 100svh; }
  .tiles { gap: 3px; padding: var(--space-8); }
  .tile__cam { font-size: 8px; letter-spacing: 0; line-height: 1.4; padding: 3px 4px; }

  /* the composer at 100 % width. The sentence wraps here, so there is no single
     write head and the caret is not drawn; the wipe still writes it left to
     right at full ink. The send control is an icon-sized square. */
  .act--watch, .act--ask, .act--moment { --lift-h: 2.325vh; --lift-drift: 4vh; }
  .conv { padding: var(--space-12); gap: var(--space-12); }
  .cmp__field { padding: var(--space-12); }
  .cmp__line { font-size: 14px; overflow: visible; }
  .ink { white-space: normal; display: block; }
  .ink__full { display: block; }
  .ink__caret { display: none; }
  .cmp__send { min-width: 40px; min-height: 40px; display: inline-flex; align-items: center; justify-content: center; font-size: 11px; text-align: center; padding: var(--space-4); }
  #watch-reply { min-height: 128px; }
  #ask-reply { min-height: 150px; }
  .ask__thumbs { grid-template-columns: repeat(3, 1fr); }
  .ask__thumbs img { height: 52px; }

  /* the answer */
  .act--answer { --lift-h: 6.45vh; --lift-drift: 4vh; }                  /* travel 90vh */
  .res { grid-template-columns: 88px minmax(0, 1fr); align-items: center; column-gap: var(--space-8); }
  .res img { grid-row: span 3; max-height: none; }
  .res__meta { flex-direction: column; align-items: flex-start; gap: 1px; }
  .res--3 .res__tags { grid-column: 2; }

  /* case, site, industries, door */
  .act--pack, .act--site, .act--industries { --act-min: 0; }
  .site__label { right: auto; left: var(--space-12); top: 58%; flex-direction: row; }
  .rule--unit { width: 22px; }
  .industry-tabs { width: 100%; margin-inline: 0; --tabs-fade: var(--space-32); } /* matches the full-width mobile card below */
  .industry-card-slot { height: min(88vh, 620px); min-height: 480px; }
  /* fr tracks, not "auto" — a percentage height on .industry-card__media
     can't resolve against an intrinsically-sized row and was collapsing to
     the placeholder's own near-zero content height. */
  .industry-card { width: 100%; height: min(78vh, 560px); grid-template-columns: 1fr; grid-template-rows: minmax(0, 0.85fr) minmax(0, 1.15fr); padding: var(--space-16); gap: var(--space-16); }
  .industry-card__media { height: 100%; }
  .rail--door { width: 100%; }
  .door__grid { align-content: start; padding-top: calc(var(--bar-h) + var(--space-24)); padding-bottom: 40vh; }
  .foot { bottom: var(--space-16); }
}
