/* ============================================================================
   Hibilet — "The room becomes the list"
   Tokens are the mockup's tokens, promoted verbatim after the style gate.
   Art direction lives in design/COMMIT-SHEET.md; the film in design/STORYBOARD.md.
   ========================================================================= */

/* ── Fonts, self-hosted ───────────────────────────────────────────────────
   Served from this origin, not from Google. Two reasons, and the second is
   the one that shows: it removes two third-party connections from a page
   whose whole argument is that your data stays yours, and it lets the files
   be preloaded. Loading them from fonts.gstatic.com cost a single 0.084
   layout shift at 410ms, which was the page's entire CLS.

   `font-display: optional`, after trying the other two. `swap` reflows the
   moment the real file lands. `block` reflows too — it hides the glyphs
   during the block period but still lays the text out on fallback metrics,
   which is the reflow, so CLS did not move (0.127 either way). Hand-guessed
   `size-adjust` fallbacks made it worse (0.086 → 0.126). `optional` is the
   only one that cannot reflow: the browser either has the font before first
   paint and uses it, or uses the fallback for that whole load and never
   swaps. With a same-origin preloaded woff2 the first branch is what happens
   in practice. Latin subsets. */
@font-face{
  font-family:"Archivo";
  src:url("./fonts/archivo-latin.woff2") format("woff2-variations");
  font-weight:400 800; font-stretch:62% 125%; font-style:normal; font-display:optional;
}
@font-face{
  font-family:"Newsreader";
  src:url("./fonts/newsreader-latin.woff2") format("woff2-variations");
  font-weight:300 600; font-style:normal; font-display:optional;
}
@font-face{
  font-family:"Newsreader";
  src:url("./fonts/newsreader-italic-latin.woff2") format("woff2-variations");
  font-weight:300 600; font-style:italic; font-display:optional;
}

:root{
  /* Colour. Revision 2: no black anywhere, at any lightness. Paper is a COOL
     neutral (hue 240) so the yellow stays the only warm thing on screen; the
     ink is a real brown that still clears 8:1 on the brand field. */
  --paper:      oklch(0.972 0.003 240);   /* #f4f6f8 */
  --paper-sunk: oklch(0.941 0.004 240);   /* #e9ecee */
  --ink:        oklch(0.255 0.058 78);    /* #321e00 — brown, not black */
  --ink-soft:   oklch(0.455 0.045 78);    /* 4.9:1 on paper */
  --ink-brand-soft: oklch(0.40 0.05 78);  /* 6.5:1 on brand; --ink-soft is 5.1:1 there */
  --brand:      #FFD405;                  /* the mark, verbatim */
  --brand-sunk: oklch(0.828 0.163 95);
  --amber:      oklch(0.72 0.155 88);     /* #cd9d00 — kept for the price card's focus ring */
  /* Amber as TYPE on paper, which the field value cannot do: #cd9d00 on paper
     is 2.3:1 and the emphasised word in every one of the peak's four beats was
     set in it. 4.9:1, and still clearly the scene's colour rather than the ink
     (2.97:1 against --ink, so the two never read as the same). */
  --amber-deep: oklch(0.53 0.14 80);

  /* Five cohort colours for the peak's segmentation scene, and the one
     deliberate departure from this file's single-hue commitment. A crowd
     split into five colours reads as five groups; split into five shades of
     the same hue it reads as one group with noise. Held to moderate chroma
     (0.09–0.16) so the effect stays editorial rather than a rainbow — see
     design/THEME.md for the reasoning and the re-theme checklist. */
  --seg-1: var(--ink);
  --seg-2: oklch(0.50 0.16 40);
  --seg-3: var(--amber-deep);
  --seg-4: oklch(0.42 0.11 330);
  --seg-5: oklch(0.40 0.10 190);

  /* The second field. The pitch deck's own values, verbatim (pitch/deck.html
     --paper/--ink/--ink-soft) — not lifted, not lightened. This is the
     product's real brand colour, not a generic AI-SaaS reflex, and a
     deliberate, scoped exception to the no-black-surface rule above: three
     places read as a genuine dark pocket rather than a tinted paper. Ink
     runs light-on-dark here, the opposite of every other field on the page —
     see .sec--purple and .peak.is-mark for where and how it's applied. */
  --purple:            #201142;
  --ink-on-purple:      #f6f2ff;
  --ink-on-purple-soft: #b6a8dd;

  --rule:      color-mix(in oklab, var(--ink) 18%, transparent);
  --rule-mid:  color-mix(in oklab, var(--ink) 34%, transparent);
  --rule-hard: color-mix(in oklab, var(--ink) 62%, transparent);

  --pad: clamp(1.15rem, 4vw, 4rem);
  --gap: clamp(1.25rem, 2.6vw, 2.5rem);
  --maxw: 1560px;

  --ease-out-quart: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
}

*,*::before,*::after{ box-sizing:border-box }
html{ -webkit-text-size-adjust:100%; scroll-behavior:auto }
body{
  margin:0;
  -webkit-user-select:text;user-select:text;
  background:var(--paper);
  color:var(--ink);
  font-family:"Newsreader",Georgia,serif;
  font-size:clamp(1rem, 0.955rem + 0.22vw, 1.1875rem);
  line-height:1.55;
  font-optical-sizing:auto;
  overflow-x:hidden;
}
img,svg,canvas{ display:block; max-width:100% }
a{ color:inherit }
::selection{ background:var(--brand); color:var(--ink) }

/* Archivo, expanded, tabular. Every number on this page aligns in a column. */
.wide{
  font-family:"Archivo",system-ui,sans-serif;
  font-variation-settings:"wdth" 112;
  font-feature-settings:"tnum" 1,"lnum" 1;
}
.num{ font-feature-settings:"tnum" 1,"lnum" 1; font-variant-numeric:tabular-nums lining-nums }

.wrap{ max-width:var(--maxw); margin-inline:auto; padding-inline:var(--pad) }
.sr{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
.skip{
  position:absolute;left:-9999px;top:0;z-index:80;
  background:var(--ink);color:var(--paper);padding:.75rem 1rem;
  font-family:"Archivo",system-ui,sans-serif;font-weight:600;
}
.skip:focus{ left:0 }

/* ── Typography scale ─────────────────────────────────────────────────── */
h1,h2,h3{
  font-family:"Archivo",system-ui,sans-serif;
  font-weight:700;
  letter-spacing:-.032em;
  margin:0;
  text-wrap:balance;
}
h1{ font-variation-settings:"wdth" 116 }
h2{
  font-variation-settings:"wdth" 113;
  font-size:clamp(1.9rem, 4.1vw, 3.4rem);
  line-height:.99;
}
h3{
  font-variation-settings:"wdth" 108;
  font-size:clamp(1.05rem, 1.6vw, 1.3rem);
  letter-spacing:-.012em;
  line-height:1.2;
}
p{ margin:0 }
.lede{
  font-size:clamp(1.0625rem, 1.25vw, 1.3125rem);
  line-height:1.5;
  color:var(--ink-soft);
  max-width:56ch;
  text-wrap:pretty;
}
.lede em{ font-style:italic; color:var(--ink) }

/* ── Micro-labels ─────────────────────────────────────────────────────────
   Small service text lives in the DISPLAY family, never in a mono face — the
   mono corner-label is a house tell this project skips.

   Declared in three grouped rules rather than per component. There were
   eleven near-identical copies of this treatment scattered through the file,
   which slopscan correctly flagged: eleven blocks of tracked uppercase is a
   pattern whether or not each one was individually justified. Consolidating
   is both the fix for the warning and better CSS. */
.tag,
.sheet th,
.sheet__grp td,
.doc__body th{
  font-family:"Archivo",system-ui,sans-serif;
  font-variation-settings:"wdth" 94;
  font-size:.625rem;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--ink-soft);
  line-height:1;
}
.rail__k,
.rec__rows dt,
.rec__foot,
.months__axis{
  font-family:"Archivo",system-ui,sans-serif;
  font-variation-settings:"wdth" 94;
  font-size:.5625rem;
  letter-spacing:.13em;
  text-transform:uppercase;
  color:var(--ink-soft);
  line-height:1.4;
}
/* Footnotes, and the reason this rule exists.
   `.tag` is a LABEL treatment: three or four words of tracked uppercase naming
   the thing under it. Six sentence-length notes had been wearing it, and the
   worst of them set FIVE lines of tracked caps inside the year stub, which is
   unreadable at any size and was also what pushed the uppercase-block count
   back over the four this project accepts. A footnote is prose, so it is set
   as prose: sentence case, small, quiet, and measure-capped like everything
   else that has to be read rather than scanned. */
.fine{
  font-size:.8125rem;
  line-height:1.6;
  color:var(--ink-soft);
  max-width:64ch;
}
.fine b{ color:var(--ink);font-weight:600 }
/* The notes under the manifest sheets grew from one line to a paragraph, and
   a 64ch column under a 1,020px table leaves half the band empty and reads as
   an unfinished layout. Two columns take the note to the width of the thing it
   annotates. It stays one column wherever there is not room for two, because
   a 30ch column of 13px text is worse than a long line. */
.fine--wide{ max-width:none;columns:2;column-gap:clamp(1.75rem,3vw,3rem) }
.fine--wide > *{ break-inside:avoid }
@media (max-width:900px){ .fine--wide{ columns:1;max-width:64ch } }
/* --ink-soft measures 3.9:1 on the brand field, which fails. The brand-safe
   ink exists for exactly this and every scene on yellow has to use it. */
.ask .fine,
.door .fine{ color:var(--ink-brand-soft) }

/* The name over a block: a stub, a plan, a step. Bolder and a size up. */
.stub__name,
.price__name,
.steps b{
  font-family:"Archivo",system-ui,sans-serif;
  font-variation-settings:"wdth" 106;
  font-weight:700;
  font-size:.8125rem;
  letter-spacing:.09em;
  text-transform:uppercase;
  color:var(--ink);
}

/* ── The perforation tear: this page's grid break, part one ─────────────────
   Just the dashed rule now. It used to carry a half-circle notch at each end,
   which rendered as a dome poking UP out of the boundary rather than as a bite
   taken out of it, and sat inset at the container padding rather than at the
   page edge — so it read as an artefact rather than as a die-cut. A dashed
   rule across the full width already says "tear here". */
.tear{
  position:relative;height:0;
  border-top:1.5px dashed var(--rule-hard);
}

/* ── Top rail: a ticket header. Not logo/status/action, and no longer a dark
      band: the page opens on the brand field and the ink sits on top of it. ─ */
.rail{ background:var(--brand); color:var(--ink) }
.rail__in{
  max-width:var(--maxw);margin-inline:auto;padding-inline:var(--pad);
  display:flex;align-items:stretch;min-height:58px;
}
.rail__cell{
  display:flex;flex-direction:column;justify-content:center;
  padding:.5rem clamp(.7rem,1.5vw,1.5rem);
  border-right:1px solid color-mix(in oklab, var(--ink) 24%, transparent);
}
.rail__cell:first-child{ padding-left:0 }
.rail__cell--end{ border-right:0;margin-left:auto;padding-right:0;text-align:right }
.rail__k{ color:var(--ink-brand-soft) }
.rail__v{
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 106;
  font-weight:600;font-size:.8125rem;line-height:1.25;margin-top:.3rem;
  font-feature-settings:"tnum" 1;
}
.rail__v a{ text-decoration:none }
.rail__v a:hover{ color:var(--ink-brand-soft) }
/* the logotype ships white-filled, so on the yellow rail it is inverted to ink */
.rail__logo{ height:18px;width:auto;margin-top:.25rem;filter:brightness(0) saturate(100%) invert(11%) sepia(38%) saturate(1400%) hue-rotate(8deg) }
/* The language cell takes the end position, so the cell before it gets its
   divider back — otherwise the rail reads as ending one column early. */
.rail__cell--lang{ border-right:0;padding-right:0;text-align:right;margin-left:0 }
.rail__cell--end:has(+ .rail__cell--lang){
  margin-left:auto;border-right:1px solid color-mix(in oklab, var(--ink) 24%, transparent);
  padding-right:clamp(.7rem,1.5vw,1.5rem);
}
.rail__lang{ display:flex;gap:.45rem;justify-content:flex-end }
.rail__lang a{ text-decoration:none }
.rail__lang [aria-current]{ color:var(--ink-brand-soft);text-decoration:none }
@media (max-width:860px){
  .rail__cell--hide{ display:none }
}

/* ── Scene 1 — the crowd walks in and sits down ───────────────────────────
   Centred type over a full-bleed canvas. The crowd is weather: small marks at
   low alpha, sitting behind everything, never competing with the headline. */
.hero{
  position:relative;
  min-height:min(92vh, 940px);
  display:grid;place-items:center;
  padding:clamp(3rem,7vw,6rem) var(--pad) clamp(2.5rem,5vw,4rem);
  overflow:hidden;
}
.hero__crowd{
  position:absolute;inset:0;width:100%;height:100%;
  z-index:0;pointer-events:none;
  /* The crowd clears out from behind the reading matter. Big display type wins
     against a field of dots; a 1.3rem serif does not, and the lede was the
     first thing to become hard work. A mask, not a colour wash, so the paper
     underneath stays exactly one value. */
  -webkit-mask-image:radial-gradient(ellipse 48% 42% at 50% 60%, transparent 26%, #000 100%);
  mask-image:radial-gradient(ellipse 48% 42% at 50% 60%, transparent 26%, #000 100%);
}
/* On a phone the hero is nearly all type, and the centre-clearing mask left
   the crowd as a few stray marks behind the lede, which reads as an artefact.
   It becomes a band at the foot of the scene instead: still the motif, out of
   the way of every line of text. */
@media (max-width:760px){
  .hero{ padding-bottom:0 }
  .hero__inner{ padding-bottom:8.5rem }
  .hero__crowd{
    top:auto;bottom:0;height:9rem;
    -webkit-mask-image:linear-gradient(transparent 0%, #000 62%);
    mask-image:linear-gradient(transparent 0%, #000 62%);
  }
}
.hero__inner{
  position:relative;z-index:1;
  max-width:56rem;margin-inline:auto;text-align:center;
}
.flag{
  display:inline-block;background:var(--brand);color:var(--ink);
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 102;
  font-weight:700;font-size:.6875rem;letter-spacing:.08em;text-transform:uppercase;
  padding:.35rem .6rem;margin-bottom:1.5rem;
}
.h1-a{
  display:block;
  font-size:clamp(2.35rem, 5.9vw, 4.6rem);
  line-height:.94;
}
.h1-b{
  display:block;
  font-size:clamp(1.7rem, 3.9vw, 3rem);
  line-height:1.02;letter-spacing:-.028em;
  margin-top:.45rem;
  color:var(--ink-soft);
}
.hero__sub{ margin:1.75rem auto 0; max-width:52ch }

.compose{ margin-top:2rem; max-width:38rem }
.hero__inner .compose,
.door .compose{ margin-inline:auto }
.compose__field{
  display:flex;align-items:stretch;
  border:1.5px solid var(--ink);background:var(--paper);
}
.compose__field:focus-within{ box-shadow:0 0 0 3px var(--brand) }
.compose__field input{
  flex:1;min-width:0;border:0;outline:0;background:transparent;
  font:inherit;color:var(--ink);
  font-size:clamp(1rem,1.15vw,1.125rem);
  padding:1.15rem 1.25rem;
}
.compose__field input::placeholder{ color:var(--ink-soft);opacity:1 }
.compose__go{
  border:0;background:var(--brand);color:var(--ink);cursor:pointer;
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 106;
  font-weight:700;font-size:.9375rem;padding:0 1.75rem;white-space:nowrap;
  transition:background-color 140ms var(--ease-out-quart);
}
.compose__go:hover{ background:var(--brand-sunk) }
.compose__go:active{ transform:scale(.985) }
.compose__note{ margin-top:.8rem;font-size:.875rem;color:var(--ink-soft);max-width:48ch }
.hero__inner .compose__note,
.door .compose__note{ margin-inline:auto }
.compose__note[data-answered]{ color:var(--ink) }
/* The fallback address inside the note. It sits on three different fields
   (paper, brand yellow, purple), so it takes the note's own colour and says
   "link" with the underline alone. */
.compose__note a{
  color:inherit;text-decoration:underline;text-underline-offset:.18em;
  text-decoration-thickness:.06em;
  text-decoration-color:color-mix(in oklab, currentColor 45%, transparent);
}
.compose__note a:hover{ text-decoration-color:currentColor }



/* The whole setup, as three lines. Not a numbered-section scaffold: these are
   an actual sequence, which is the one case where numbering earns its place. */
.steps{
  list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(3,1fr);
  border-top:1.5px solid var(--ink);
}
.steps li{
  /* Left padding on every step but the first: without it, two and three sat
     hard against the rule that divides them. */
  padding:1.15rem clamp(1.25rem,2.4vw,2.5rem) 1.35rem clamp(1.25rem,2.4vw,2.5rem);
  font-size:.9375rem;line-height:1.45;color:var(--ink-soft);
  border-right:1px solid var(--rule);
}
.steps li:first-child{ padding-left:0 }
.steps li:last-child{ border-right:0;padding-right:0 }
.steps b{ display:block;margin-bottom:.3rem }
@media (max-width:760px){
  .steps{ grid-template-columns:1fr }
  .steps li{ border-right:0;border-bottom:1px solid var(--rule);padding-inline:0 }
  .steps li:last-child{ border-bottom:0 }
}

@media (max-width:960px){
  .hero{ grid-template-columns:1fr;padding-right:var(--pad);min-height:0 }
  .hero__copy,.hero__plan{ grid-column:1 / -1 }
  .hero__plan{ margin-right:0;margin-top:2.5rem }
  .plan__stage{ border-right:1.5px solid var(--ink);min-height:0;aspect-ratio:4/3 }
}

/* ── Scene 2 — the manifest ───────────────────────────────────────────── */
.sec{ padding-block:clamp(3.5rem,7vw,6.5rem) }
.sec--tight{ padding-block:clamp(2.75rem,4.5vw,4rem) }
.sec--sunk{ background:var(--paper-sunk) }
/* The second colour beat — a real dark pocket, not a tint. --ink/--ink-soft
   stay the same brown everywhere else on the page, including inside here:
   overriding them in scope was the first draft of this and it quietly broke
   .stub, whose own text has no colour of its own and just inherits — an
   ambient override would have turned a white card's own numbers invisible.
   So this follows the file's actual convention (--ink-brand-soft and its
   kind): `color` is set once for anything that just inherits,
   and every selector that reads --ink/--ink-soft/--rule explicitly gets its
   own light-ink override below, scoped under .sec--purple. */
.sec--purple{ background:var(--purple);color:var(--ink-on-purple) }
.sec--purple .tag,
.sec--purple .refusal__how{ color:var(--ink-on-purple-soft) }
.sec--purple .refusal__how b{ color:var(--ink-on-purple) }
.sec--purple .refusal{ border-top-color:color-mix(in oklab, var(--ink-on-purple) 22%, transparent) }

/* One year runs on the brand field, not the purple: same stub component,
   its own light-ink overrides. */
.sec--year{ background:var(--brand);color:var(--ink) }
/* The stub's two notches are bites out of the field, so they are filled with
   the field. On paper that is --paper-sunk; here it is the brand yellow, or
   they read as two white domes stuck to the ticket. */
.sec--year .stub__tear::before,
.sec--year .stub__tear::after{ background:var(--brand) }
/* The stub is paper wherever it sits, so anything printed on it keeps paper
   ink. Without this the section's own ink colour lands on white and the
   counterfoil labels go unreadable against it. */
.sec--year .stub .tag{ color:var(--ink-soft) }

.manifest__head{
  display:grid;grid-template-columns:repeat(12,1fr);gap:var(--gap);
  align-items:end;
  border-top:1.5px solid var(--ink);padding-top:1.25rem;   /* hairline-rule opening */
}
.manifest__head h2{ grid-column:1 / span 6 }
.manifest__head .lede{ grid-column:8 / -1;font-size:1rem }
@media (max-width:960px){
  .manifest__head{ grid-template-columns:1fr }
  .manifest__head h2,.manifest__head .lede{ grid-column:1 / -1 }
  .manifest__head .lede{ margin-top:1rem }
}

.tabs{ display:flex;flex-wrap:wrap;gap:0;margin-top:2.25rem;border-bottom:1.5px solid var(--ink) }
.tab{
  appearance:none;border:0;background:transparent;cursor:pointer;
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 106;
  font-weight:600;font-size:.8125rem;letter-spacing:.03em;text-transform:uppercase;
  color:var(--ink-soft);padding:.7rem 1.05rem;position:relative;
  transition:color 140ms var(--ease-out-quart);
}
.tab:hover{ color:var(--ink) }
.tab[aria-selected="true"]{ color:var(--ink);background:var(--brand) }
.tab:focus-visible{ outline:2px solid var(--ink);outline-offset:-2px }

.manifest__body{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,320px);
  gap:clamp(1.5rem,3vw,3rem);margin-top:1.75rem;align-items:start;
}
@media (max-width:1080px){ .manifest__body{ grid-template-columns:1fr } }

/* A manifest is a wide document. Below ~700px it scrolls sideways in its own
   box rather than folding "148 / 168" onto three lines, which is what a real
   run-sheet on a phone does and is the only way the columns stay comparable. */
[role="tabpanel"]{ overflow-x:auto;overscroll-behavior-x:contain }
@media (max-width:700px){ table.sheet{ min-width:600px } }

table.sheet{ width:100%;border-collapse:collapse;font-feature-settings:"tnum" 1 }
table.sheet caption{ text-align:left;padding-bottom:.6rem }
.sheet th,.sheet td{
  text-align:left;padding:.6rem .75rem .6rem 0;
  border-bottom:1px solid var(--rule);vertical-align:baseline;
}
.sheet th{ font-weight:600;border-bottom-color:var(--rule-mid);white-space:nowrap }
.sheet td{
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 104;
  font-size:.875rem;line-height:1.35;
}
.sheet td:first-child{ font-weight:700 }
.sheet .n{ text-align:right;font-variant-numeric:tabular-nums }
.sheet tbody tr{ transition:background-color 120ms var(--ease-out-quart) }
@media (hover:hover) and (pointer:fine){
  .sheet tbody tr:hover{ background:var(--brand) }
  .sheet tbody tr:hover td{ border-bottom-color:var(--rule-mid) }
}
.pill{
  display:inline-block;font-size:.6875rem;font-weight:700;
  padding:.12rem .4rem;border:1px solid var(--rule-mid);white-space:nowrap;
}
.pill--on{ background:var(--ink);color:var(--paper);border-color:var(--ink) }
.pill--hot{ background:var(--brand);border-color:var(--ink) }

/* Settlement reads by group: a heading per ticket type, a subtotal to close
   it, and one total at the foot. */
/* The tracked-uppercase treatment itself lives in the micro-label group at the
   top of this file; only what makes a settlement group heading different from
   a `.tag` belongs here. Restating the other six declarations is what took the
   file to six near-identical uppercase blocks in the first place. */
.sheet__grp td{
  padding-top:1.35rem;border-bottom:1.5px solid var(--ink);
  font-weight:700;color:var(--ink);
}
.sheet tbody tr.sheet__grp:first-child td{ padding-top:.6rem }
.sheet__sub td{ font-weight:700;border-bottom:1.5px solid var(--ink) }
.sheet__sub td:first-child{ color:var(--ink-soft);font-weight:600 }
.sheet__tot td{ padding-top:1.1rem;border-bottom:0;font-weight:700 }
@media (hover:hover) and (pointer:fine){
  .sheet tbody tr.sheet__grp:hover,
  .sheet tbody tr.sheet__sub:hover,
  .sheet tbody tr.sheet__tot:hover{ background:transparent }
}

.aside-plan{ position:sticky;top:1.5rem }
.aside-plan__stage{
  border:1.5px solid var(--ink);background:var(--paper);
  aspect-ratio:5/4;position:relative;
}
.aside-plan__stage canvas{ position:absolute;inset:0;width:100%;height:100% }
.aside-plan p{ margin-top:.7rem;font-size:.8125rem;color:var(--ink-soft) }
@media (max-width:1080px){ .aside-plan{ position:static;max-width:420px } }

/* ── Scene 3 — four numbers ───────────────────────────────────────────── */
.figures{
  display:grid;grid-template-columns:repeat(4,1fr);gap:var(--gap);
  margin-top:2.5rem;
}
.figure{ border-top:1.5px solid var(--ink);padding-top:.9rem }
/* the column the money leaks out of gets the brand rule, everything else a hairline */
.figure--loss{ border-top-width:4px;border-top-color:var(--brand);margin-top:-2.5px }
.figure__n{
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 108;
  font-weight:700;font-size:clamp(1.9rem,3.9vw,3.1rem);line-height:.95;
  letter-spacing:-.035em;font-variant-numeric:tabular-nums;
}
.figure__l{ margin-top:.5rem;font-size:.9375rem;color:var(--ink-soft);line-height:1.35 }
@media (max-width:820px){ .figures{ grid-template-columns:repeat(2,1fr) } }

.ticker{
  margin-top:2.75rem;border-block:1.5px solid var(--ink);
  overflow:hidden;padding-block:.7rem;
}
.ticker__track{
  display:flex;gap:2.5rem;width:max-content;
  animation:ticker-run 64s linear infinite;
}
.ticker__track:hover{ animation-play-state:paused }
.ticker__i{
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 100;
  font-size:.8125rem;color:var(--ink-soft);white-space:nowrap;
  font-feature-settings:"tnum" 1;
}
.ticker__i b{ color:var(--ink);font-weight:700 }
@keyframes ticker-run{ from{ transform:translateX(0) } to{ transform:translateX(-50%) } }

/* ── Scene 5 — one year, as a ticket stub ────────────────────────────────
   A stub, not a stat row: a paper counterfoil with a perforated spine, the
   figures as line items and one total set large. Two identical stat rows on
   one page is the template this project bans, and the stub also delivers the
   "divide it like a ticket" idea somewhere other than the section joins. */
.stub{
  display:grid;grid-template-columns:minmax(0,7fr) 0 minmax(0,5fr);
  /* the counterfoil leaves over the top of the next section, not under it */
  position:relative;z-index:2;
  /* the counterfoil leaves its own column on the way off */
  overflow:visible;
  /* Always a paper object with brown ink, whatever section it sits in.
     .stub__fig and .stub__rows dd have no colour of their own and just
     inherit — without this, dropping the card into .sec--purple would
     have inherited that section's light ink straight through onto a white
     card and made those figures unreadable. */
  color:var(--ink);
}
/* Two pieces of paper, not one panel with a line drawn down it. The rip
   below carries the counterfoil away from the spine; a single background
   on .stub would have travelled with neither half and left a paper-shaped
   hole where the tear opened. */
.stub__main,
.stub__stack{ background:var(--paper);border:1.5px solid var(--ink) }
.stub__main{ padding:clamp(1.5rem,3.2vw,2.75rem);border-right:0 }
.stub__stack{ border-left:0 }

.stub__fig{
  min-height:1ch;                          /* the figure counts up; hold its line */
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 112;
  font-weight:700;font-size:clamp(3rem,7.5vw,5.75rem);line-height:.9;
  letter-spacing:-.042em;font-variant-numeric:tabular-nums;margin-top:.6rem;
}
.stub__cap{ margin-top:.9rem;max-width:44ch;color:var(--ink-soft);font-size:1rem;line-height:1.5 }
.stub__rows{ margin:2rem 0 0;display:grid;gap:0;border-top:1.5px solid var(--ink) }
/* Fixed value column, for the same reason: these count up on entry, and an
   auto-width column moves while they do. */
.stub__rows > div{
  display:grid;grid-template-columns:1fr 8ch;align-items:baseline;gap:1.5rem;
  padding:.85rem 0;border-bottom:1px solid var(--rule);
}
.stub__rows > div:last-child{ border-bottom:0 }
.stub__rows dt{ font-size:.9375rem;color:var(--ink-soft) }
.stub__rows dd{
  margin:0;
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 108;
  font-weight:700;font-size:clamp(1.15rem,2vw,1.6rem);letter-spacing:-.03em;
  font-variant-numeric:tabular-nums;white-space:nowrap;text-align:right;
}
/* The spine, PUNCHED rather than drawn. A dashed line is a line somebody put
   between two panels; a row of holes is a thing that was done to the paper,
   and — this is the point — each hole belongs half to one piece and half to
   the other. When the counterfoil pulls away the holes come apart into two
   rows of half-circles, which is the whole tell that a tear is happening. A
   dashed line cannot do that, because it belongs to neither piece.
   The two 22px circles at the ends are the same perforation running off the
   top and bottom edges of the card, in the section-join language. */
.stub__tear{ position:relative }
.stub__tear::before,.stub__tear::after{
  content:"";position:absolute;left:-11px;width:22px;height:22px;border-radius:50%;
  background:var(--paper-sunk);
}
.stub__tear::before{ top:-12px;clip-path:inset(50% 0 0 0) }
.stub__tear::after{ bottom:-12px;clip-path:inset(0 0 50% 0) }

/* One hole every 30px, in the colour of the section behind the card, so it
   reads as an absence and not as a printed dot. Both rows are the same
   gradient; each is clipped to its own half of the line. */
.stub__main{ position:relative }
.stub__main::after,
.foil::before{
  content:"";position:absolute;pointer-events:none;
  top:0;bottom:0;width:18px;
  background:radial-gradient(circle at 9px 15px,
    var(--paper-sunk) 6.5px, transparent 7px) 0 0/18px 30px repeat-y;
}
.stub__main::after{ right:-9px;clip-path:inset(0 9px 0 0) }   /* stays */
.foil::before{ left:-9px;clip-path:inset(0 0 0 9px) }         /* travels */
.sec--year .stub__tear::before,
.sec--year .stub__tear::after,
.sec--year .stub__main::after,
.sec--year .foil::before{ --paper-sunk:var(--brand) }

/* ── The tear ─────────────────────────────────────────────────────────────
   The counterfoil is the contract you are on somewhere else, and it comes off
   by itself. Every revision before this one made the reader do the tearing —
   drag it, peel it, throw it — and every one of them had the same fault: the
   sheet went wherever the hand went, so half the frames on screen were a card
   being moved about behind a window rather than paper coming apart.

   The motion is a ticket stub's, and it is four things and nothing else:
     — it pivots about the FOOT of its own perforation
     — it slides outward, away from the spine, in page axes
     — a box-shadow appears in the gap that opens, and goes when it closes
     — and it comes back
   That is one tug. It does it twice — a piece of paper that comes away on the
   first pull was never attached — and the third pull does not come back: it
   keeps the tilt, keeps going, and drops out of the bottom of the frame,
   uncovering what this platform says instead.

   The tear is READ IN THE PERFORATION, not in the outline of the sheet. The
   holes up the spine are half on the paper and half on the card beside it, so
   pulling separates them into two rows of half-circles. That is why there is
   no ragged clip-path here and no drop-shadow: with nothing clipping the
   element, a plain box-shadow is the shadow, and the sheet stays a whole,
   readable rectangle in every frame it is on screen.

   Two layers in one grid cell, and the paper is ONE piece:
     .stub__keep  the brand-yellow sheet underneath, uncovered as the paper leaves
     .foil        the contract, pivoting off the spine and out of frame

   The spine is on the LEFT of this column, where the perforation already is,
   so that is the line it comes off — desktop pivots at the bottom-left and
   pulls out to the right. Below 900px the stack sits under the main half and
   the perforation runs across the top, so the same animation is re-pointed:
   pivot top-left, pull downward, and away out of the bottom.

   auteur.js only decides WHEN. Everything the reader sees is in this file. */
/* Nothing clips the counterfoil. It used to be a window with the paper
   moving behind it, and a window is exactly what a torn-off piece does not
   go behind: it comes away from the card, over whatever the card is sitting
   on, and out of the page. The stub carries a z-index so the falling sheet
   passes IN FRONT of the section below rather than under it. */
.stub__stack{ position:relative }

/* The paper. Its own background, because it has to survive leaving. */
.foil{
  position:relative;height:100%;background:var(--paper);
  padding:clamp(1.5rem,3.2vw,2.75rem);
  display:flex;flex-direction:column;gap:1rem;
}
.stub__side p{ font-size:.9375rem;line-height:1.55;color:var(--ink-soft) }
.stub__side b{ color:var(--ink);font-weight:600 }

/* Clauses, numbered, in the register of the thing being quoted. */
.clauses{ margin:0;padding:0;list-style:none;counter-reset:clause }
.clauses li{
  counter-increment:clause;position:relative;
  padding:.65rem 0 .65rem 2.2rem;border-bottom:1px solid var(--rule);
  font-size:.9375rem;line-height:1.5;color:var(--ink-soft);
}
.clauses li::before{
  content:counter(clause,decimal-leading-zero);position:absolute;left:0;top:.85rem;
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 94;
  font-size:.625rem;letter-spacing:.14em;color:var(--ink-soft);
}
.clauses li:last-child{ border-bottom:0 }

/* The sheet underneath: the section's own field, printed on. */
.stub__keep{
  position:relative;overflow:hidden;
  padding:clamp(1.5rem,3.2vw,2.75rem);
  display:flex;flex-direction:column;gap:1rem;
  background:linear-gradient(155deg,
    color-mix(in oklab, var(--brand) 86%, #ffffff) 0%,
    var(--brand) 58%);
  color:var(--ink);
}
/* There was a label set vertically down the edge here, naming the panel while
   a strip of it showed through the un-torn sheet. The sheet is whole now, so
   there is no strip to name it in, and the peel comes down from the top —
   which uncovers the panel's own heading first, in reading order. An edge
   label would have been a second heading for the same block. */

.sec--year .stub__keep .tag{ color:var(--ink-brand-soft) }
.stub__keeps{ margin:0;padding:0;list-style:none;display:grid;gap:0 }
.stub__keeps li{
  font-size:.9375rem;line-height:1.5;color:var(--ink);
  padding:.7rem 0;
  border-bottom:1px solid color-mix(in oklab, var(--ink) 20%, transparent);
}
.stub__keeps li:last-child{ border-bottom:0 }

/* Offered only once the paper is off, and quiet: a way to run it again, not a
   second call to action competing with the one at the foot of the page. */
.tear__reset{
  display:none;align-self:flex-start;margin-top:auto;
  background:none;border:0;border-bottom:1px solid currentColor;
  padding:0 0 1px;cursor:pointer;
  font-family:"Newsreader",Georgia,serif;font-style:italic;font-size:.9375rem;
  color:var(--ink-brand-soft);
}
.tear__reset:hover{ color:var(--ink) }
.stub.is-torn .tear__reset{ display:inline-block }

.stub__hint{ display:none }

/* ── Live, once the script has said the animation may run ──────────────── */
.stub.is-tearable .stub__stack{ display:grid }
.stub.is-tearable .stub__side,
.stub.is-tearable .stub__keep{ grid-area:1/1 }
.stub.is-tearable .stub__side{
  position:relative;z-index:2;
  /* grid, not percentage heights: the sheet has to fill the cell whether the
     contract or the sheet under it is the taller of the two, and a chain of
     height:100% through three boxes is the version of that which breaks. */
  display:grid;
  /* The whole sheet is the control surface, so a drag across it has to be a
     pull at the paper and not a text selection. */
  -webkit-user-select:none;user-select:none;
}
.stub.is-tearable .foil{
  /* This used to hang 64px above its cell and 160px past its right edge,
     with padding put back to compensate, because the clip on .stub__stack
     would otherwise cut the tilted sheet with a straight line. With nothing
     clipping it, the sheet is simply its own size and the corners that swing
     past the card swing past the card. */
  /* The foot of the spine — the far end of the perforation from where it
     opens, which is where a ticket stub actually hinges. */
  transform-origin:0 100%;
  will-change:transform;
}
.stub.is-tearing .foil{ animation:stub-tear 3300ms both }

/* Three pulls at 1100ms each, and each one is the same shape: still, then the
   paper strains against the perforation, then it goes out and comes back.
   The percentages inside a pull are 0 / 50 / 60 / 75 / 100 of it — the strain
   is the first half, the shadow and the full tilt land at 60, the sheet is
   furthest out at 75, and it is home at 100. Easing is left at the default on
   every segment but the last, because a pull that is eased in and out is what
   makes it read as being pulled rather than played.

   THE AMPLITUDE IS SET BY THE PANEL UNDERNEATH, not by taste. A pull opens a
   gap of `slide + h·sin(angle)` at the top of the spine, and the panel's own
   left padding is ~35px: anything wider than that and the wedge stops showing
   a margin and starts showing the first two letters of six sentences, which
   is a sheet with a slot cut in it rather than a sheet coming loose. So the
   pull is 20px and 1.6deg on a 740px sheet — small in figures, and the shadow
   and the perforation coming apart are what carry it. The same sum caps the
   other side: the paper's text stops 35px short of its own right edge, so it
   travels that far before the frame occludes a word. */
@keyframes stub-tear{
  /* ── the first pull ── */
  0%    { transform:none;                            box-shadow:0 0 0 rgba(23,10,44,0) }
  16.7% { transform:rotate(1.1deg);                  box-shadow:0 0 0 rgba(23,10,44,0) }
  20%   { transform:translate(12px) rotate(1.6deg);  box-shadow:5px 5px 10px rgba(23,10,44,.34) }
  25%   { transform:translate(20px) rotate(1deg);    box-shadow:5px 5px 10px rgba(23,10,44,.26) }
  33.3% { transform:none;                            box-shadow:0 0 0 rgba(23,10,44,0) }
  /* ── and it holds, so it goes again ── */
  50%   { transform:rotate(1.1deg);                  box-shadow:0 0 0 rgba(23,10,44,0) }
  53.3% { transform:translate(12px) rotate(1.6deg);  box-shadow:5px 5px 10px rgba(23,10,44,.34) }
  58.3% { transform:translate(20px) rotate(1deg);    box-shadow:5px 5px 10px rgba(23,10,44,.26) }
  66.7% { transform:none;                            box-shadow:0 0 0 rgba(23,10,44,0) }
  /* ── the third one starts the same and does not come back ── */
  83.3% { transform:rotate(1.1deg);                  box-shadow:0 0 0 rgba(23,10,44,0) }
  86.7% { transform:translate(12px) rotate(1.6deg);  box-shadow:5px 5px 10px rgba(23,10,44,.34) }
  /* translate BEFORE rotate, and the order is the whole reason it falls
     straight: written the other way round the travel runs along the sheet's
     own tilted axis and walks it sideways into the opposite border. */
  91%   { transform:translate(14px,26%) rotate(1.6deg);box-shadow:9px 9px 18px rgba(23,10,44,.30);
          animation-timing-function:cubic-bezier(.5,0,.9,.5) }
  100%  { transform:translate(14px,180%) rotate(9deg); box-shadow:9px 9px 18px rgba(23,10,44,.14) }
}

/* ── The invitation ───────────────────────────────────────────────────────
   The tear is the reader's to make. Nothing on this page pulls the paper off
   on its own any more: the page's whole job here is to say that the paper is
   loose, and then to wait.

   It says it three ways, in the order a reader meets them. The sheet takes the
   cursor, so the thing under the pointer is plainly a thing you do something
   to. Under the pointer — or under a keyboard focus, which is the same
   invitation for a reader who never touches a mouse — the paper strains
   against the perforation and stops there, half of the first pull of the tear
   and none of its follow-through. And when the scene arrives it tugs twice by
   itself, unasked, because a reader who is reading the clauses is not hovering
   anything and would otherwise never learn the sheet moves.

   The nudge is deliberately smaller than a pull: 7px against the tear's 20,
   which separates the perforation by about a hole's width. Enough to see the
   paper is only resting there; not enough to be mistaken for the tear itself
   and leave the reader waiting for something that has already finished. */
.stub.is-tearable:not(.is-tearing):not(.is-torn) .stub__side{ cursor:pointer }
.stub.is-tearable:not(.is-tearing):not(.is-torn):not(.is-dragging) .foil{
  transition:transform 280ms cubic-bezier(.2,.7,.3,1), box-shadow 280ms linear;
}

/* ── Taking hold of it ────────────────────────────────────────────────────
   A click plays the tear; a pull IS the tear, and the difference is worth
   having. The script writes `transform` and `box-shadow` straight onto the
   paper while a pointer is down — the one place on this page anything is set
   from script — and these rules are what happens at either end of that.

   Nothing here transitions while the hand is on it: a curve between where the
   paper is and where the hand is would be the paper lagging its own reader.
   The transition comes back the moment they let go, and which one it is says
   what happened. Under the commit distance the perforation held, so the sheet
   goes back onto the spine on a curve that overshoots a hair — paper springs,
   it does not glide. Past it the perforation has gone, and the page finishes
   the pull from wherever the hand left off, on the same accelerating curve the
   played animation uses for its own last drop. */
/* The whole card is the handle, not just the counterfoil: the two halves are
   one ticket, and a reader who has decided to rip something should not have to
   find its hit area first. The buttons inside keep their own cursor. */
.stub.is-draggable:not(.is-tearing):not(.is-torn){ cursor:grab }
.stub.is-dragging{ cursor:grabbing }
.stub.is-dragging .stub__side{ cursor:grabbing }
.stub.is-dragging .foil{ transition:none;animation:none }
.stub.is-settling .foil{
  transition:transform 520ms cubic-bezier(.2,1.06,.4,1), box-shadow 520ms linear;
}
.stub.is-releasing .foil{
  transition:transform 620ms cubic-bezier(.5,0,.9,.5), box-shadow 620ms linear;
}
.stub.is-tearable:not(.is-tearing):not(.is-torn) .stub__side:hover .foil,
.stub.is-tearable:not(.is-tearing):not(.is-torn) .stub__side:focus-within .foil{
  transform:translate(9px) rotate(.85deg);
  box-shadow:5px 5px 11px rgba(23,10,44,.24);
}
/* Two tugs on arrival, then it holds still and leaves the reader alone. A
   hover that comes mid-nudge simply takes over — the animation is `both`, so
   it holds the sheet at rest when it ends and the transition works from there. */
.stub.is-inviting:not(.is-tearing):not(.is-torn) .foil{
  animation:stub-nudge 1500ms 2 both;
}
@keyframes stub-nudge{
  0%,100% { transform:none;                             box-shadow:0 0 0 rgba(23,10,44,0) }
  40%     { transform:rotate(.5deg);                    box-shadow:0 0 0 rgba(23,10,44,0) }
  55%     { transform:translate(7px) rotate(.8deg);     box-shadow:4px 4px 9px rgba(23,10,44,.22) }
  75%     { transform:translate(4px) rotate(.4deg);     box-shadow:3px 3px 7px rgba(23,10,44,.14) }
}

/* The control is this line, not the sheet: it is what a keyboard operates, and
   what a reader who scrolled past the animation uses to see it again. Styled
   as the aside it already was. */
.stub.is-tearable .stub__hint{
  display:block;margin-top:auto;width:100%;text-align:left;
  padding:.7rem 0 0;background:none;border:0;border-top:1px solid var(--rule);
  font-family:"Newsreader",Georgia,serif;font-style:italic;
  font-size:.9375rem;color:var(--ink-soft);cursor:pointer;
}
.stub.is-tearable .stub__hint:hover,
/* Anywhere on the sheet, so the line says what the click under the reader's
   hand is about to do. */
.stub.is-tearable:not(.is-tearing):not(.is-torn) .stub__side:hover .stub__hint{
  color:var(--ink);
}
.stub.is-tearable .stub__hint:focus-visible{ outline:2px solid var(--ink);outline-offset:3px }

/* Torn: the paper is gone and the frame stops being a paper frame. */
.stub.is-torn .stub__stack{
  border-color:color-mix(in oklab, var(--ink) 38%, transparent);
  transition:border-color 420ms linear 160ms;
}
/* Belt and braces with the aria-hidden and tabindex the script sets: a sheet
   that has left the frame is not a thing anything can reach into. */
.stub.is-torn .stub__side{ pointer-events:none;visibility:hidden }

/* Nothing to watch, so nothing to wait for: the paper is simply already off,
   and taping it back simply puts it on. */
@media (prefers-reduced-motion:reduce){
  .stub.is-tearing .foil{ animation:none }
  /* The paper still comes off when the reader asks for it; it just does not
     travel, and it does not wave at them beforehand. The hint line says in
     words what the nudge and the hover say in movement. */
  .stub.is-inviting .foil{ animation:none }
  .stub.is-tearable:not(.is-tearing):not(.is-torn):not(.is-dragging) .foil{ transition:none }
  .stub.is-tearable:not(.is-tearing):not(.is-torn) .stub__side:hover .foil,
  .stub.is-tearable:not(.is-tearing):not(.is-torn) .stub__side:focus-within .foil{
    transform:none;box-shadow:0 0 0 rgba(23,10,44,0);
  }
}

/* Below 900px the two halves stack, so the counterfoil sits UNDER the main
   half and the perforation runs across the top — and that is the line it
   comes off: pivot at the top-left, pull DOWNWARD away from it, drop out of
   the bottom. Same pulls, same shadow, turned a quarter. */
@media (max-width:900px){
  /* The two halves stack, and every edge that was vertical turns with them:
     the card's shared border moves from side-by-side to one above the other,
     and the notches that ended the spine move from the card's top and bottom
     edges to its left and right. */
  .stub{ grid-template-columns:1fr }
  .stub__main{ border-right:1.5px solid var(--ink);border-bottom:0 }
  .stub__stack{ border-top:0;border-left:1.5px solid var(--ink) }
  .stub__tear{ height:0 }
  .stub__tear::before{ top:-11px;left:-11px;clip-path:inset(0 0 0 50%) }
  .stub__tear::after{ top:-11px;bottom:auto;left:auto;right:-11px;clip-path:inset(0 50% 0 0) }

  .stub.is-tearable .foil{ transform-origin:0 0 }
  /* The invitation turns with everything else: the paper strains DOWNWARD off
     the perforation across its top. */
  .stub.is-tearable:not(.is-tearing):not(.is-torn) .stub__side:hover .foil,
  .stub.is-tearable:not(.is-tearing):not(.is-torn) .stub__side:focus-within .foil{
    transform:translate(0,9px) rotate(1deg);
    box-shadow:-5px 5px 11px rgba(23,10,44,.24);
  }
  @keyframes stub-nudge{
    0%,100% { transform:none;                          box-shadow:0 0 0 rgba(23,10,44,0) }
    40%     { transform:rotate(.6deg);                 box-shadow:0 0 0 rgba(23,10,44,0) }
    55%     { transform:translate(0,7px) rotate(1deg); box-shadow:-4px 4px 9px rgba(23,10,44,.22) }
    75%     { transform:translate(0,4px) rotate(.5deg);box-shadow:-3px 3px 7px rgba(23,10,44,.14) }
  }
  /* The row turns with the tear: holes across the top of the paper, meeting
     the row along the bottom of the card above it. */
  .stub__main::after{
    top:auto;bottom:-9px;left:0;right:0;width:auto;height:18px;
    background:radial-gradient(circle at 15px 9px,
      var(--paper-sunk) 6.5px, transparent 7px) 0 0/30px 18px repeat-x;
    clip-path:inset(0 0 9px 0);
  }
  .foil::before{
    left:0;right:0;top:-9px;bottom:auto;width:auto;height:18px;
    background:radial-gradient(circle at 15px 9px,
      var(--paper-sunk) 6.5px, transparent 7px) 0 0/30px 18px repeat-x;
    clip-path:inset(9px 0 0 0);
  }
  @keyframes stub-tear{
    0%    { transform:none;                              box-shadow:0 0 0 rgba(23,10,44,0) }
    16.7% { transform:rotate(1.3deg);                    box-shadow:0 0 0 rgba(23,10,44,0) }
    20%   { transform:translate(0,10px) rotate(2deg);    box-shadow:-5px 5px 10px rgba(23,10,44,.34) }
    25%   { transform:translate(0,16px) rotate(1.2deg);  box-shadow:-5px 5px 10px rgba(23,10,44,.26) }
    33.3% { transform:none;                              box-shadow:0 0 0 rgba(23,10,44,0) }
    50%   { transform:rotate(1.3deg);                    box-shadow:0 0 0 rgba(23,10,44,0) }
    53.3% { transform:translate(0,10px) rotate(2deg);    box-shadow:-5px 5px 10px rgba(23,10,44,.34) }
    58.3% { transform:translate(0,16px) rotate(1.2deg);  box-shadow:-5px 5px 10px rgba(23,10,44,.26) }
    66.7% { transform:none;                              box-shadow:0 0 0 rgba(23,10,44,0) }
    83.3% { transform:rotate(1.3deg);                    box-shadow:0 0 0 rgba(23,10,44,0) }
    86.7% { transform:translate(0,10px) rotate(2deg);    box-shadow:-5px 5px 10px rgba(23,10,44,.34) }
    91%   { transform:translate(0,26%) rotate(1.8deg);   box-shadow:-9px 9px 18px rgba(23,10,44,.30);
            animation-timing-function:cubic-bezier(.5,0,.9,.5) }
    100%  { transform:translate(0,180%) rotate(7deg);    box-shadow:-9px 9px 18px rgba(23,10,44,.14) }
  }
}

/* Script off: the two panels simply follow one another down the page, divided
   by their own perforation, and nothing is hidden behind anything. */
.stub:not(.is-tearable) .stub__keep{ border-top:1.5px dashed var(--rule-hard) }

/* ── Scene 4 — PEAK ───────────────────────────────────────────────────────
   Revision 1 put this scene on a near-black field, arguing "the room with the
   lights down". The no-black direction exposed that as the reflex it was: the
   drop into the peak is carried by warmth instead of by darkness, and the
   scene is now the deepest-saturated thing on the page rather than the
   darkest. Runs on the brand yellow itself, not the duller amber. */
.peak{
  /* --peak-field is what the section is actually lit by; .peak__bar reads
     the same token so the closing letterbox is always the current ground,
     never a colour of its own. .is-mark below is the only thing that ever
     changes it. */
  --peak-field: var(--brand);
  background:var(--peak-field);color:var(--ink);position:relative;
  transition:background 900ms var(--ease-out-quart);
  /* The letterbox is a token now, not a hard-coded height in three places. The
     bars, the lower third and the slate all read it, so the frame can close on
     the last line without any of them ending up behind a bar. */
  --bar:0px;
  --lift:0px;
}
/* The cinematic ending. The instant the crowd starts flying into the mark
   (peak.js: p >= B3), the ground crossfades from the film's brand yellow to
   the pitch deck's actual brand purple — gold mark, purple ground, the
   deck's own pairing, held for the reveal. */
.peak.is-mark{ --peak-field: var(--purple) }
.peak__pin{
  height:100vh;                 /* svh below: mobile toolbars make vh taller than the screen */
  height:100svh;
  position:relative;display:grid;place-items:stretch;overflow:hidden;
}
.peak__canvas{ position:absolute;inset:0;width:100%;height:100% }
.peak__beats{
  position:relative;z-index:2;
  max-width:var(--maxw);margin-inline:auto;width:100%;
  padding:clamp(1.5rem,4vw,3.5rem) var(--pad);
  display:flex;align-items:flex-end;
  pointer-events:none;
  /* Transform, not `bottom`: the lower third rides the closing frame without
     any of the four beats touching layout. Only the last one is visible while
     this is non-zero, so lifting all four costs nothing. */
  transform:translateY(calc(-1 * var(--lift)));
  transition:transform 500ms var(--ease-out-quart);
}
/* the plate is opaque anyway, so let the pointer — and a text selection — reach it */
.beat{ pointer-events:auto }
/* The beat is a lower third. It sits ON a paper plate rather than floating over
   the crowd, because 640 moving points will cross any line of type you put over
   them and legibility beats atmosphere every time. */
.beat{
  position:absolute;left:var(--pad);
  /* clears the bottom letterbox bar: a lower third belongs inside the frame,
     not behind it. Without the 5.5vh the bar crops the last line. */
  bottom:calc(5.5vh + clamp(1rem,2.5vw,1.75rem));
  max-width:min(24ch, calc(100% - var(--pad) * 2));
  background:var(--paper);color:var(--ink);
  padding:.85rem 1.15rem 1rem;
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 114;
  font-weight:700;font-size:clamp(1.35rem,3.1vw,2.5rem);line-height:1.02;
  letter-spacing:-.03em;
  text-wrap:balance;         /* "bigger." was landing alone on the last line */
  opacity:0;
}
/* The last line of the film is not another subtitle. It comes up a size, takes
   a wider measure, and the frame closes around it (see --bar/--lift). Four
   identical plates in a row is the reveal-on-every-section ban applied to
   dialogue: the punchline has to arrive in a different register or it is just
   the fourth caption. */
.beat--final{
  font-size:clamp(1.6rem,4.2vw,3.4rem);
  max-width:min(30ch, calc(100% - var(--pad) * 2));
  padding:1.05rem 1.35rem 1.2rem;
  box-shadow:10px 10px 0 color-mix(in oklab, var(--ink) 22%, transparent);
}
.beat em{ font-style:normal;color:var(--amber-deep) }
.peak__label{
  position:absolute;top:calc(5.5vh + clamp(0.9rem,2vw,1.5rem));left:var(--pad);z-index:2;
  color:var(--ink-brand-soft);
  max-width:calc(100% - var(--pad) * 2);
  transform:translateY(var(--lift));
  transition:transform 500ms var(--ease-out-quart), color 900ms var(--ease-out-quart);
}
/* The slate is the one piece of running type that sits directly on the
   ground rather than on its own paper plate, so it is the one piece that
   has to switch inks when the ground turns dark. */
.peak.is-mark .peak__label{ color:var(--ink-on-purple-soft) }
/* The real mark. Positioned and sized every frame from JS (auteur.js reads
   the same square mapping the dots use for their own final position, so the
   two agree exactly), and otherwise pure CSS: invisible until the closing
   beat, opaque once it fires, beneath the letterbox so the bars still crop
   it correctly. */
.peak__mark{
  position:absolute;z-index:2;opacity:0;pointer-events:none;
  transition:opacity 900ms var(--ease-out-quart);
}
.peak.is-final .peak__mark{ opacity:1 }
/* Letterbox. The bars are the scene's own field colour, so the ink hairline is
   what makes the frame read as closing rather than as nothing happening. */
.peak__bar{
  position:absolute;left:0;right:0;height:var(--bar);background:var(--peak-field);z-index:3;
  transition:height 500ms var(--ease-out-quart), background 900ms var(--ease-out-quart);
}
.peak__bar--t{ top:0;border-bottom:1px solid color-mix(in oklab, var(--ink) 40%, transparent) }
.peak__bar--b{ bottom:0;border-top:1px solid color-mix(in oklab, var(--ink) 40%, transparent) }
.peak.is-cinema{ --bar:5.5vh }
/* The frame closes on the last beat. Same mechanism as the letterbox opening
   the scene, run once more in the same direction, which is the only camera
   move this page makes outside the pull-back. */
.peak.is-final{ --bar:9vh;--lift:3.5vh }

/* ── The record card ──────────────────────────────────────────────────────
   HTML, not painted into the canvas. It was canvas-drawn at first, which
   meant hand-positioned baselines, no real type features and a redraw on
   every scroll frame. As an element it gets the page's own typography, it
   costs nothing to move, and the same card can serve both jobs: the beat the
   scene stops on, and whichever of the thousand you are pointing at. */
.rec{
  position:absolute;z-index:4;
  width:min(20.5rem, calc(100% - 2 * var(--pad)));
  background:var(--paper);color:var(--ink);
  border:1.5px solid var(--ink);
  box-shadow:6px 6px 0 color-mix(in oklab, var(--ink) 26%, transparent);
  padding:.85rem 1rem .8rem;
  pointer-events:none;
}
.rec__name{
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 110;
  font-weight:700;font-size:1.0625rem;letter-spacing:-.02em;line-height:1.1;
  padding-bottom:.6rem;margin-bottom:.6rem;border-bottom:1.5px solid var(--ink);
}
/* Before a dot has a name, the card shows a row ID instead. Same slot, a
   smaller size and looser tracking so it reads as a code, not a name that
   happens to be unreadable. */
.rec__name--id{
  font-size:.875rem;font-weight:600;letter-spacing:.01em;
  font-variation-settings:"wdth" 106;
}
.rec__rows{ margin:0;display:grid;gap:.28rem }
.rec__rows > div{ display:grid;grid-template-columns:5.9rem 1fr;gap:.55rem;align-items:baseline }
.rec__rows dt{ line-height:1.5 }
.rec__rows dd{
  margin:0;font-size:.78125rem;line-height:1.3;
  font-feature-settings:"tnum" 1;
}
.rec__foot{ margin-top:.8rem;padding-top:.55rem;border-top:1px solid var(--rule) }
/* The invitation. Shown only while the held beat is on screen and only until
   somebody accepts it, after which it never returns for that visit. */
.peak__prompt{
  position:absolute;left:50%;top:50%;z-index:4;
  transform:translate(-50%,-50%) scale(.96);
  background:var(--paper);color:var(--ink);
  border:1.5px solid var(--ink);
  padding:.55rem .95rem;
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 106;
  font-weight:700;font-size:.8125rem;letter-spacing:.02em;white-space:nowrap;
  opacity:0;pointer-events:none;
  transition:opacity 260ms var(--ease-out-quart), transform 260ms var(--ease-out-quart);
}
.peak__prompt.is-on{ opacity:1;transform:translate(-50%,-50%) scale(1) }
@media (hover:none),(pointer:coarse){ .peak__prompt{ display:none } }
@media (prefers-reduced-motion:reduce){ .peak__prompt{ transition:none } }

.peak__hint{ display:none }
@media (hover:hover) and (pointer:fine){ .peak__hint{ display:inline } }

/* With scripting off there is no canvas to draw into, so the peak would be a
   100vh void. The prose cut is the default and JS hides it, never the reverse. */
.peak__static{ display:block;padding-block:clamp(3.5rem,7vw,6rem) }
[data-auteur-js] .peak__static{ display:none }
html:not([data-auteur-js]) .peak__pin{ display:none }
.peak__static p{ max-width:60ch;margin-top:1rem;color:var(--ink-brand-soft) }

/* ── Scene 5 — ask the room ───────────────────────────────────────────── */
.ask{ background:var(--brand);color:var(--ink) }
.ask .lede{ color:var(--ink-brand-soft) }
.ask__grid{
  display:grid;grid-template-columns:repeat(12,1fr);gap:var(--gap);align-items:start;
}
.ask__head{ grid-column:1 / span 5 }
.ask__demo{ grid-column:7 / -1 }
@media (max-width:1080px){
  .ask__head,.ask__demo{ grid-column:1 / -1 }
  .ask__demo{ margin-top:2.5rem }
}
/* Every box in this demo reserves room for its longest state. The questions
   rotate and they are not the same length: without these, each rotation
   rewrapped a line and shifted the column, which is a layout shift every few
   seconds for as long as the section is on screen. */
.query{
  border:1.5px solid var(--ink);background:var(--brand);
  padding:1rem 1.15rem;display:flex;gap:.7rem;align-items:baseline;
  min-height:5.5rem;
}
.query__q{
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 106;
  font-weight:600;font-size:clamp(.9375rem,1.4vw,1.125rem);line-height:1.35;
}
.query__caret{
  display:inline-block;width:.55em;height:1.05em;background:var(--ink);
  vertical-align:-.15em;animation:caret 1.06s steps(1) infinite;
}
@keyframes caret{ 50%{ opacity:0 } }
.answer{
  border:1.5px solid var(--ink);border-top:0;padding:1.15rem;
  display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;
}
.answer > div{ min-height:5.25rem }
.answer__n{
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 108;
  font-weight:700;font-size:clamp(1.4rem,2.6vw,2rem);line-height:1;
  letter-spacing:-.03em;font-variant-numeric:tabular-nums;
}
.answer__l{ font-size:.8125rem;color:var(--ink-brand-soft);margin-top:.35rem;line-height:1.3 }
/* The rows behind the count. This is the difference between a chatbot and a
   query layer: the answer is a segment you can open, so the page opens it. */
.seg{ border:1.5px solid var(--ink);border-top:0 }
.seg__h{
  padding:.55rem 1.15rem;border-bottom:1px solid var(--rule-mid);
  display:flex;justify-content:space-between;gap:1rem;align-items:baseline;
}
.seg table{ width:100%;border-collapse:collapse;table-layout:fixed }
.seg td{
  padding:.5rem 1.15rem;border-bottom:1px solid var(--rule);
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 104;
  font-size:.8125rem;font-feature-settings:"tnum" 1;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.seg td:first-child{ font-weight:700;width:34% }
.seg tbody tr{ height:2.6rem }
.seg td:last-child{ text-align:right;color:var(--ink-brand-soft) }
.seg tr:last-child td{ border-bottom:0 }

.answer__do{
  border:1.5px solid var(--ink);border-top:0;padding:.85rem 1.15rem;
  font-size:.875rem;display:flex;gap:.6rem;flex-wrap:wrap;align-items:center;
  min-height:3.75rem;
}
.answer__do b{ font-family:"Archivo",system-ui,sans-serif;font-weight:700;font-size:.8125rem }
.ask__list{ margin:2rem 0 0;padding:0;list-style:none;display:grid;gap:.55rem }
.ask__list li{ font-size:.9375rem;padding-left:1.1rem;position:relative;color:var(--ink-brand-soft) }
.ask__list li::before{ content:"";position:absolute;left:0;top:.62em;width:.5rem;height:1.5px;background:var(--ink) }
.ask__list b{ color:var(--ink);font-weight:600 }
@media (max-width:560px){ .answer{ grid-template-columns:1fr } }

/* ── Scene 6 — three refusals ─────────────────────────────────────────── */
.refuse{ display:grid;grid-template-columns:repeat(12,1fr);gap:var(--gap) }
/* four columns, not three: "The money is yours." set at display size needs the
   width, and at three it broke to six cramped lines. */
.refuse__margin{ grid-column:1 / span 4 }
/* The claim is held in frame while the three mechanisms pass it, which is the
   only place on the page where one thing waits for another. It also fixes a
   real hole: the margin ran out of copy two-thirds of the way down and left
   about 600px of empty column at 1440. `align-self:start` is required — a grid
   item stretches to the row by default and a stretched item cannot stick. */
@media (min-width:961px){
  .refuse__margin{ position:sticky;top:clamp(2rem,9vh,6.5rem);align-self:start }
}
.refuse__main{ grid-column:6 / span 7 }
.refusal{ padding-block:clamp(1.5rem,3vw,2.5rem);border-top:1px solid var(--rule) }
.refusal:first-child{ border-top:0;padding-top:0 }
.refusal__say{
  font-family:"Newsreader",Georgia,serif;font-style:italic;font-weight:400;
  font-size:clamp(1.35rem,2.9vw,2.25rem);line-height:1.15;letter-spacing:-.01em;
  text-wrap:balance;
}
.refusal__how{
  margin-top:.85rem;max-width:58ch;color:var(--ink-soft);font-size:1rem;line-height:1.5;
}
.refusal__how b{ color:var(--ink);font-weight:600 }
@media (max-width:960px){
  .refuse{ grid-template-columns:1fr }
  .refuse__margin,.refuse__main{ grid-column:1 / -1 }
  .refuse__main{ margin-top:1.75rem }
}

/* ── Scene 7 — the door ───────────────────────────────────────────────── */
.door{ background:var(--brand);color:var(--ink);text-align:center }
/* Visible by default. Only the animated build hides it first, or the reduced
   and no-JS cuts would lose the one place the brand mark appears. */
.door__mark{ width:60px;height:60px;margin:0 auto 1.75rem }
.js-anim .door__mark{ transform:scale(.96);opacity:0 }
.door h2{ font-size:clamp(2rem,5vw,4rem);max-width:18ch;margin-inline:auto }
.door .lede{ color:var(--ink-brand-soft);margin:1.5rem auto 0;max-width:52ch }
.door .compose{ margin-inline:auto;margin-top:2.25rem }
.door .compose__field{ background:var(--paper) }
.door .compose__go{ background:var(--ink);color:var(--brand) }
.door .compose__go:hover{ background:oklch(0.33 0.06 78) }
.door .compose__note{ color:var(--ink-brand-soft) }

/* The last band, on the purple. Pricing keeps the brand field: yellow is the
   loudest surface on the page and the two plans are what it is for. The close
   after it goes down into the purple and runs straight into the footer, so
   the page ends on one field rather than on two more section joins.
   The mark loses its ink plate here. On yellow a yellow logo is invisible and
   needed one; on purple it is the pitch deck's own gold-on-purple pairing and
   the plate was the only thing in the way. */
.door--close{ background:var(--purple);color:var(--ink-on-purple) }
.door--close .lede,
.door--close .compose__note,
.door--close .fine{ color:var(--ink-on-purple-soft) }
.door--close .compose__go{ background:var(--brand);color:var(--ink) }
.door--close .compose__go:hover{ background:var(--brand-sunk) }
/* The close is one object on the field rather than type floating on it: the
   heading, the mark and the field it wants you to type in are inside a single
   ruled frame, the same hairline the tickets and tables are drawn with. */
.door__panel{
  border:1.5px solid color-mix(in oklab, var(--ink-on-purple) 42%, transparent);
  padding:clamp(2rem,5vw,4rem) clamp(1.25rem,4vw,3.5rem);
  max-width:64rem;margin-inline:auto;
}
.door--close .compose__field{ border-color:var(--paper) }

/* ── The year, as twelve months ───────────────────────────────────────────
   The total counts up; the months grow under it. Transform only, staggered
   off a custom property, so twelve bars cost one composited animation each
   and nothing touches layout. */
.months{ margin-top:1.75rem }
.months__bars{
  display:flex;align-items:flex-end;gap:clamp(3px,0.7vw,8px);
  height:clamp(56px,9vw,104px);
  border-bottom:1.5px solid var(--ink);
}
.months__bars span{
  flex:1;height:var(--h);background:var(--brand);
  border:1px solid var(--ink);border-bottom:0;
  transform-origin:50% 100%;
}
.js-anim .months__bars span{ transform:scaleY(0.04) }
.js-anim .months.is-in .months__bars span{
  transform:scaleY(1);
  transition:transform 760ms var(--ease-out-quart);
  transition-delay:calc(var(--i) * 55ms);
}
.months__axis{ display:flex;justify-content:space-between;margin-top:.55rem }

/* ── The worked sum ─────────────────────────────────────────────
   The argument is arithmetic, but the arithmetic was set as a three-row
   ledger and read as a spreadsheet: a reader scanning the page met six
   figures and no sentence, and the one number that matters — what is saved
   the second time the same room is filled — had to be worked out from the
   other five. So the result leads at display size and the working supports
   it: two costs drawn to scale, each with the sum that produced it written
   underneath in words. 75% is (120.00 − 30.00) / 120.00, and both ends are
   on the page to check it.

   One centred column, not a column of argument beside a column of gloss.
   The claim, the payoff and the call all sit on the page's axis; only the
   two bars stay left-aligned, because a comparison of lengths needs one
   origin to start from.

   It still sits AFTER the two plans and is still not a panel. As a paper
   card above them it was a third card of equal weight, arriving before
   either price and pushing both below the fold. */
.sum{
  max-width:62rem;margin:clamp(2.5rem,5vw,4rem) auto 0;
  padding-top:1.5rem;border-top:1.5px solid var(--ink);text-align:center;
}
/* The saving, at the size of the claim it is. */
.sum__hit{ margin:.75rem 0 clamp(1.6rem,3vw,2.4rem) }
.sum__hit b{
  display:block;
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 116;
  font-weight:700;font-size:clamp(4.5rem,12vw,9rem);line-height:.82;
  letter-spacing:-.045em;font-feature-settings:"tnum" 1;
}
.sum__hit span{
  display:block;max-width:26ch;margin:clamp(.5rem,1.2vw,.9rem) auto 0;
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 112;
  font-weight:700;font-size:clamp(1.1rem,2.1vw,1.6rem);line-height:1.15;
  letter-spacing:-.02em;
}
.sum__race{ border-top:1.5px solid var(--ink);text-align:left }
.sum__lane{ padding-block:clamp(.9rem,1.8vw,1.2rem);border-bottom:1px solid var(--rule) }
.sum__lane:last-child{ border-bottom:0 }
.sum__lane-h{
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 104;
  font-weight:700;font-size:.9375rem;letter-spacing:-.005em;
}
/* Two costs at one scale: the bar is the sentence, the figure is the proof.
   Both bars are the same solid ink and only their length differs, which is
   the whole comparison. Drawing the cheap one as a paper outline was tried
   twice and read as an empty input field at both lengths. */
.sum__meter{
  display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;
  gap:clamp(.7rem,1.6vw,1.15rem);margin-top:.55rem;
}
.sum__fill{
  height:clamp(24px,3.2vw,38px);width:var(--w);
  background:var(--ink);border:1.5px solid var(--ink);
  transform-origin:0 50%;
}
.sum__val{
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 108;
  font-weight:700;font-size:clamp(1.1rem,2.1vw,1.6rem);line-height:1;
  letter-spacing:-.025em;font-feature-settings:"tnum" 1;white-space:nowrap;
}
.sum__lane-n{
  margin-top:.55rem;max-width:64ch;
  font-size:.9375rem;line-height:1.5;color:var(--ink-brand-soft);
}
/* Grows with the section it belongs to, on the same reveal as everything
   else that arrives from nothing: the shorter bar is late by one beat, so
   the difference is watched rather than found. */
.js-anim .sum__fill{ transform:scaleX(.02) }
.js-anim .sum.is-in .sum__fill{
  transform:scaleX(1);
  transition:transform 760ms var(--ease-out-quart);
  transition-delay:calc(var(--i) * 140ms);
}
.sum__win{
  margin-top:1.15rem;padding-top:.9rem;border-top:1px solid var(--rule);
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 110;
  font-weight:700;font-size:clamp(1.05rem,1.9vw,1.5rem);line-height:1.15;
  letter-spacing:-.02em;
}
.sum__win span{ display:block;max-width:46ch;margin:.4rem auto 0;
  font-weight:400;font-size:.9375rem;line-height:1.5;letter-spacing:0;
  color:var(--ink-brand-soft);font-variation-settings:"wdth" 100;
}
/* The one thing to do after the sum, at the size of an instruction rather
   than of a footnote. The door already fills a button with ink and sets it
   in brand on this field; this follows it instead of inventing a second
   kind of button. */
.sum__go{ margin-top:clamp(1.6rem,3vw,2.4rem) }
.sum__go a{
  display:inline-block;background:var(--ink);color:var(--brand);text-decoration:none;
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 106;
  font-weight:700;font-size:clamp(1.0625rem,1.9vw,1.375rem);letter-spacing:-.015em;
  padding:clamp(.85rem,1.7vw,1.15rem) clamp(1.7rem,3.4vw,2.75rem);
  transition:background-color 140ms var(--ease-out-quart);
}
.sum__go a:hover{ background:oklch(0.33 0.06 78) }
.sum__go a:active{ transform:scale(.985) }
.sum__src{ max-width:78ch;margin:clamp(1.6rem,3vw,2.2rem) auto 0;line-height:1.7 }

.price__note{
  margin-top:1.5rem;max-width:70ch;text-align:left;
  font-size:.9375rem;line-height:1.55;color:var(--ink-brand-soft);
}
.price__note b{ color:var(--ink);font-weight:600 }

/* ── Scene 7a — what it costs ─────────────────────────────────────────────
   Two panels of equal weight, as asked. The differentiation is carried by
   hover and focus instead of by size: point at one and it comes forward
   while the other steps back, so the page never decides for you which one
   you are supposed to want. */
.price{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(1rem,2vw,1.75rem);
  margin-top:2.5rem;text-align:left;
}
@media (max-width:820px){ .price{ grid-template-columns:1fr } }
.price__card{
  background:var(--paper);border:1.5px solid var(--ink);
  padding:clamp(1.5rem,2.6vw,2.25rem);
  display:flex;flex-direction:column;
  transition:transform 220ms var(--ease-out-quart),
             box-shadow 220ms var(--ease-out-quart),
             opacity 220ms var(--ease-out-quart);
  outline:0;
}
@media (hover:hover) and (pointer:fine){
  /* Dim the CONTENT, never the card. Putting opacity on the card itself made
     its paper background translucent over the yellow field, so the unfocused
     plan turned yellow and read as a different design rather than a quieter
     one. The card stays opaque; only what is printed on it steps back. */
  .price:has(.price__card:hover) .price__card:not(:hover) > *,
  .price:has(.price__card:focus-visible) .price__card:not(:focus-visible) > *{
    opacity:.42;
  }
  .price__card:hover,
  .price__card:focus-visible{
    transform:translateY(-6px);
    box-shadow:8px 8px 0 var(--ink);
  }
}
.price__card > *{ transition:opacity 220ms var(--ease-out-quart) }
.price__card:focus-visible{ box-shadow:8px 8px 0 var(--ink), 0 0 0 3px var(--amber) }

.price__fig{
  font-family:"Archivo",system-ui,sans-serif;font-variation-settings:"wdth" 112;
  font-weight:700;font-size:clamp(2.4rem,4.6vw,3.6rem);line-height:.92;
  letter-spacing:-.04em;font-variant-numeric:tabular-nums;margin-top:.65rem;
}
/* A block, not a suffix. With a figure in the slot "€1 per paid ticket" read
   as one line of price; with words in it, an inline suffix reads as a
   sentence that has lost its verb. */
.price__fig span{
  display:block;margin-top:.7rem;max-width:34ch;
  font-family:"Newsreader",Georgia,serif;
  font-size:max(.8125rem, .29em);font-weight:400;letter-spacing:0;line-height:1.4;
  color:var(--ink-soft);
}
.price__list{ margin:1.5rem 0 0;padding:0;list-style:none;display:grid;gap:.6rem;flex:1 }
.price__list li{ font-size:.9375rem;line-height:1.5;padding-left:1.2rem;position:relative;color:var(--ink-soft) }
.price__list li::before{
  content:"";position:absolute;left:0;top:.62em;width:.6rem;height:1.5px;background:var(--ink);
}
.price__list b{ color:var(--ink);font-weight:600 }
.price__go{ margin-top:1.75rem }
.price__go a{ font-weight:600;text-underline-offset:4px }

/* Purple, continuous with the close above it, so the page ends on one field
   rather than on one more section join. */
.foot{ background:var(--purple);color:var(--ink-on-purple);padding-block:2.5rem }
.foot__in{
  display:flex;flex-wrap:wrap;gap:1.25rem 2.5rem;align-items:center;
  border-top:1.5px solid color-mix(in oklab, var(--ink-on-purple) 30%, transparent);
  padding-top:1.5rem;
}
/* No filter. The logotype ships yellow and the field is now purple, so the
   inversion that made it readable on amber would now be undoing itself. */
.foot img{ height:20px;width:auto }
.foot a{ color:var(--ink-on-purple);text-decoration:none;font-size:.875rem }
.foot a:hover{ text-decoration:underline;text-underline-offset:4px }
.foot__end{ margin-left:auto;font-size:.8125rem;color:var(--ink-on-purple-soft) }
@media (max-width:720px){ .foot__end{ margin-left:0 } }

/* ── Reveals. Every one enhances an already-visible default: the base
      state has full opacity so the page reads with JS off. JS adds
      .js-anim to <html>, which is the only thing that hides anything. ─── */
.js-anim [data-rise]{ opacity:0;transform:translateY(9px);filter:blur(4px) }
.js-anim [data-rise].is-in{
  opacity:1;transform:none;filter:blur(0);
  transition:opacity 620ms var(--ease-out-quart),
             transform 620ms var(--ease-out-quart),
             filter 620ms var(--ease-out-quart);
}
/* The panel carries the state, the row carries its index. One class toggle
   staggers the whole table; nothing writes per-row styles at switch time. */
.js-anim [role="tabpanel"] [data-row]{ opacity:0;transform:translateX(-10px) }
.js-anim [role="tabpanel"].is-in [data-row]{
  opacity:1;transform:none;
  transition:opacity 380ms var(--ease-out-quart), transform 380ms var(--ease-out-quart);
  transition-delay:calc(var(--i, 0) * 38ms);
}
.js-anim .door__mark.is-in{
  opacity:1;transform:scale(1);
  transition:opacity 700ms var(--ease-out-quart), transform 700ms var(--ease-out-quart);
}

/* ── Reduced motion: a different cut of the same film, not an absence.
      Nothing pins, nothing scrubs, nothing loops. Everything is legible. ─ */
@media (prefers-reduced-motion: reduce){
  .js-anim [data-rise],
  .js-anim [data-row],
  .js-anim .door__mark{ opacity:1;transform:none;filter:none;transition:none }
  .ticker__track{ animation:none }
  .query__caret{ animation:none;opacity:0 }
  .peak__bar{ transition:none }
  .peak__mark{ transition:none }
  /* nothing flies, so there is no flight to wait out before hiding it */
  .stub.is-torn .stub__side{ transition:none }
  .compose__go:active{ transform:none }
}
