/* Chamber of Advocates — coming soon */

@font-face { font-family: "Fraunces"; src: url("fonts/fraunces-latin.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Geist"; src: url("fonts/geist-latin.woff2") format("woff2"); font-weight: 100 900; font-display: swap; }
@font-face { font-family: "Geist Mono"; src: url("fonts/geist-mono-latin.woff2") format("woff2"); font-weight: 500; font-display: swap; }

:root {
  --bg: oklch(9% 0.010 30);         /* the tube is the only light: the darker the ground, the further it carries */
  --ink: oklch(96% 0.008 70);
  --ink-2: oklch(76% 0.012 60);
  --ink-3: oklch(60% 0.012 50);
  --line: oklch(100% 0 0 / 0.12);
  --line-2: oklch(100% 0 0 / 0.3);
  --fill: oklch(100% 0 0 / 0.035);
  --fill-2: oklch(100% 0 0 / 0.05);
  --brand: oklch(78% 0.10 76);      /* brass: eyebrow, success tick, selection */
  --glint: oklch(97% 0.035 85);     /* eyebrow sheen: warm white */
  --accent: var(--brand);           /* button */
  --accent-2: oklch(82% 0.10 78);   /* button hover */
  --on-accent: oklch(18% 0.02 60);
  --error: oklch(72% 0.16 25);
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .7 0 0 0 0 .7 0 0 0 0 .7 0 0 0 .07 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");   /* backdrop grain tile: 7% white */
  --neon-core: oklch(99% 0.035 92);    /* fallback wash: hot filament */
  --neon-1: oklch(90% 0.12 84 / .38);  /* fallback wash: tight bloom under the filament */
  --neon-2: oklch(72% 0.14 70 / .11);  /* fallback wash: the spill down the page */
  --neon-rgb-1: 2.6 2.1 1.6;           /* lit tube: the glass itself. Over 1 on purpose - it clips to white, like neon does */
  --neon-rgb-2: .42 .19 .048;          /* lit tube: the wash that carries down the page */
  --neon-span: 1.1;                    /* share of the viewport width the lit tube covers - the shader reads this too. Over 1
                                          so the ends run 5% off each edge: the emission taper (3.5% of the tube) fades out
                                          where nobody sees it instead of darkening the two top corners. */
  --neon-a: calc(50% * (1 - var(--neon-span)));   /* ... and where that puts its two ends */
  --neon-b: calc(50% * (1 + var(--neon-span)));
  --glow: transparent;                 /* backdrop pool - off in the dark theme, see `body`; the light theme sets its own below */
  --sans: "Geist", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --serif: "Fraunces", "Iowan Old Style", "Palatino Linotype", Georgia, serif;
  --ease: cubic-bezier(.2, .7, .2, 1);
  --ease-neon: cubic-bezier(.215, .61, .355, 1);   /* the shader's own curve, 1 - (1 - p)^3 */
  --t0: .45s;                                      /* the page starts arriving */
  --t-neon: .9s;                                   /* ...and the tube starts opening, 2.4s later it is full width (app.js DELAY/DURATION) */
  --pad-x: clamp(20px, 5vw, 72px);
  --pad-y: clamp(18px, 3.5vh, 40px);
  --eyebrow-gap: clamp(1.1rem, 2.4vh, 1.75rem);   /* status lockup -> heading */
  /* Fluid type. Each step is a function of the viewport, so a 320px phone and a 1440px desktop keep the same hierarchy. */
  --text-xs: clamp(.7rem, .18vw + .6rem, .8rem);        /* mono labels: marquee tags, footer */
  --text-sm: clamp(.78rem, .2vw + .72rem, .875rem);     /* hint, eyebrow */
  --text-md: clamp(.95rem, .4vw + .85rem, 1.125rem);    /* body, lede, input */
  --text-lg: clamp(1.125rem, .55vw + .9rem, 1.375rem);  /* tagline */
  color-scheme: dark;
}

*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; overflow: hidden; }
body {
  margin: 0; color: var(--ink); font: 400 var(--text-md)/1.5 var(--sans); -webkit-font-smoothing: antialiased;
  /* Static backdrop: grain over bare ground. The pool that used to sit on the marquee column was a second
     light inside the tube's own cone - the cast landed on ground it had already lit, the page never resolved
     into dark, and the tube read as a gradient instead of a source. With --glow off and --bg five points
     lower the tube's contrast against its surround goes 11.8x -> 20.8x at the top edge and 1.27x -> 1.54x at
     mid-page, for the same shader. The gradient stays because the light theme still sets a --glow. */
  background:
    var(--grain) 0 0 / 220px 220px,
    radial-gradient(44% 50% at 74% 30%, var(--glow), transparent 70%) var(--bg);
}
/* The light: a cast in WebGL, pinned to the whole screen so its far field fades out instead of meeting an
   edge. `app.js` lays the tube along the top edge and sizes it to --neon-span. */
.neon { position: fixed; z-index: -1; left: 0; top: 0; width: 100%; height: 100%; display: block; pointer-events: none; }   /* a canvas is replaced: inset alone would leave it at its intrinsic 300x150 */
/* Fallback wash, for no-WebGL and for lost contexts. Three stacked layers stand in for the lit tube - hot
   filament, tight bloom, wide one-sided wash - revealed by animating `scale` alone, so it runs on the
   compositor. `app.js` sets `neon-gl` on <html> the moment the shader links, which hides this.
   The tube runs past both edges, and a line source that long casts the same light at every x, so the two
   glow layers fall off with height alone. The filament keeps the --neon-a/--neon-b span. */
body::before {
  content: ""; position: fixed; z-index: -1; left: 0; top: 0; width: 100%; height: min(68vh, 680px); pointer-events: none;
  background:
    linear-gradient(90deg, transparent var(--neon-a), var(--neon-core) calc(var(--neon-a) + 2%), var(--neon-core) calc(var(--neon-b) - 2%), transparent var(--neon-b)) 0 0 / 100% 2px no-repeat,
    linear-gradient(var(--neon-1), transparent 96px),
    linear-gradient(var(--neon-2), transparent);
  animation: neon-open 2.4s var(--t-neon) var(--ease-neon) both, neon-hum 7s 3.6s ease-in-out infinite;
}
@keyframes neon-open { from { scale: 0 1; } to { scale: 1 1; } }   /* ease-out cubic, matching the shader's 1-(1-p)^3 */
@keyframes neon-hum { 50% { opacity: .86; } }                      /* a lit tube is never perfectly steady */
.neon-gl body::before { display: none; }
[hidden] { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
::selection { background: color-mix(in oklab, var(--brand) 40%, transparent); }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* ---- Page grid: one viewport, no scroll. Left: copy + intake. Right: marquee, full height. ---- */
.page {
  height: 100vh; height: 100dvh;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
  gap: clamp(20px, 4vh, 48px) clamp(24px, 4vw, 64px);
  padding: max(var(--pad-y), env(safe-area-inset-top)) max(var(--pad-x), env(safe-area-inset-right)) max(var(--pad-y), env(safe-area-inset-bottom)) max(var(--pad-x), env(safe-area-inset-left));
}
.foot {
  grid-column: 1 / -1; grid-row: 2; position: relative; z-index: 1;
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: 4px 24px;
  color: var(--ink-3); font: 500 var(--text-xs)/1.4 var(--mono); letter-spacing: .04em;
}
.foot p { margin: 0; text-wrap: balance; }

/* ---- Left column: one block, vertically centred. Spacing follows the type (em) and the height (vh). ---- */
.hero { grid-column: 1; grid-row: 1; align-self: center; min-height: 0; max-width: 40rem; container-type: inline-size; }
.intro { margin-bottom: clamp(1.5rem, 4vh, 2.5rem); }
/* Status lockup, two stacked lines: the state in bold brass caps, the date under it in prose. Both
   inherit their size from .eyebrow, so one clamp moves the whole block. */
.eyebrow {
  display: flex; flex-direction: column; align-items: flex-start; gap: .62em;
  margin: 0 0 var(--eyebrow-gap); font-size: clamp(.86rem, .22vw + .78rem, 1rem);
}
/* A soft glint (gradient clipped to the glyphs) sweeps left to right in 1.4s, then rests 4.2s. */
.status {
  white-space: nowrap;
  font: 700 1.2em/1 var(--sans); letter-spacing: .16em; text-transform: uppercase;
  background: linear-gradient(105deg, var(--brand) 44%, color-mix(in oklab, var(--brand), var(--glint) 40%) 47%, var(--glint) 50%, color-mix(in oklab, var(--brand), var(--glint) 40%) 53%, var(--brand) 56%) 0 0 / 250% 100%;
  color: transparent; -webkit-background-clip: text; background-clip: text;
}
.when { color: var(--ink-2); font-size: .92em; line-height: 1.2; letter-spacing: .01em; text-wrap: balance; }
.fest { font: 400 1.3em/1 var(--serif); color: var(--brand); letter-spacing: -.015em; }
@keyframes glint { 0% { background-position: 100% 0; animation-timing-function: cubic-bezier(.45, .05, .25, 1); } 25%, 100% { background-position: 0 0; } }
/* Heading: 16% of its own column, so "Chamber of" fills ~85% of the line at every width; height-capped on short screens. */
h1 {
  margin: 0 0 .3em; font: 400 clamp(2.5rem, 5.8vw + .4rem, 6.5rem)/.96 var(--serif); letter-spacing: -0.03em; text-wrap: balance;
  font-size: clamp(2.5rem, min(16cqw, 9vh + .5rem), 6.5rem);
}
.tagline { margin: 0 0 .5em; font-size: var(--text-lg); line-height: 1.35; text-wrap: balance; }
.lede { margin: 0; max-width: 30rem; color: var(--ink-2); font-size: var(--text-md); line-height: 1.55; text-wrap: balance; }

/* ---- Email intake ---- */
.signup { max-width: 30rem; }
.field {
  display: flex; height: 56px; padding: 4px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--fill);
  transition: border-color .25s var(--ease), background .25s var(--ease);
}
.field:focus-within { border-color: var(--line-2); background: var(--fill-2); }
.field input { flex: 1; min-width: 0; padding: 0 14px; border: 0; outline: 0; background: transparent; color: var(--ink); font: 400 1rem/1 var(--sans); font-size: max(1rem, var(--text-md)); }
.field input::placeholder { color: var(--ink-3); }
.field button {
  position: relative; flex: none; padding: 0 18px; border: 0; border-radius: 5px;
  background: var(--accent); color: var(--on-accent); font: 500 14px/1 var(--sans); cursor: pointer;
  transition: background .2s var(--ease);
}
.field button:hover { background: var(--accent-2); }
.field button:disabled { cursor: default; }
.btn-busy { position: absolute; inset: 0; display: grid; place-items: center; opacity: 0; }
.is-busy .btn-label { opacity: 0; }
.is-busy .btn-busy { opacity: 1; }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.done { display: flex; align-items: center; gap: 12px; margin: 0; min-height: 56px; padding: 0 16px; border: 1px solid var(--line); border-radius: 8px; color: var(--ink); }
.done svg { flex: none; color: var(--brand); }
.hint { margin: .75rem 0 0; color: var(--ink-3); font: 400 var(--text-sm)/1.45 var(--sans); text-wrap: balance; }
.hint.is-error { color: var(--error); }

/* ---- Vertical marquee with depth-of-field blur ---- */
.marquee {
  grid-column: 2; grid-row: 1 / -1; position: relative; min-height: 0; overflow: hidden;
  margin-block: calc(-1 * var(--pad-y));   /* bleed to the top and bottom edges */
  container-type: size;                      /* cq units below = the marquee's own box */
  --n: 16; --t: 44s; --travel: 240cqh;       /* items sit travel/n apart on a loop 2.4x the visible height */
  animation: fade-in 2s calc(var(--t0) + 260ms) var(--ease-neon) both;   /* comes up under the wash, finishing with the tube */
}
/* The depth-of-field fade is a mask on the band, not opacity on the items. Two reasons, one fix: Blink will
   not accelerate an opacity animation on an element that also animates `filter`, so the old per-item fade ran
   on the main thread beside the shader's draw loop - a stall left an item carried into the band by its
   compositor-driven translate while its opacity was still 0 (a gap) until a mouse move forced a tick and it
   appeared. And an item used to meet the band edge at 24% opacity and get sliced there, which is the hard line
   above and below the band on a phone. A positional mask has neither problem: the ramp is identical (items are
   at full strength from --fade in, matching the old 42.5%/57.5% plateau) but it reaches zero at the edge
   itself, and it costs nothing per frame. */
.marquee ul {
  list-style: none; margin: 0; padding: 0; position: absolute; inset: 0;
  --fade: 32%;
  -webkit-mask-image: linear-gradient(transparent, #000 var(--fade), #000 calc(100% - var(--fade)), transparent);
  mask-image: linear-gradient(transparent, #000 var(--fade), #000 calc(100% - var(--fade)), transparent);
}
.marquee li {
  position: absolute; left: clamp(4px, 2vw, 32px); top: 50%;
  display: flex; flex-direction: column; gap: .4em; white-space: nowrap;
  /* Grows with the marquee's width and height; the 10.5cqw term keeps the longest label inside a narrow column. */
  font: 400 clamp(1.5rem, min(4cqw + 4.5cqh, 10.5cqw), 4rem)/1 var(--serif); letter-spacing: -0.02em; color: var(--ink);
  animation: rise var(--t) linear infinite both;
  animation-delay: calc(-1 * var(--i) * var(--t) / var(--n));
}
.marquee small { font: 500 var(--text-xs)/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3); }
/* Linear travel, blur ramping in and out over the same phases the mask fades across. Translate and filter are
   both compositable, and they are now the only things animated here, so the column cannot stall with the main
   thread. The 25%/75% bounds sit 1.2 band-heights apart: an item is still ramping as it crosses an edge, so
   the column runs off both ends instead of stopping an item-height short and leaving a hole. */
@keyframes rise {
  0%           { translate: 0 calc(-50% + var(--travel) / 2); filter: blur(10px); }
  25%          { filter: blur(10px); }
  42.5%, 57.5% { filter: blur(0); }
  75%          { filter: blur(10px); }
  100%         { translate: 0 calc(-50% - var(--travel) / 2); filter: blur(10px); }
}
.marquee li:nth-child(1) { --i: 0; }
.marquee li:nth-child(2) { --i: 1; }
.marquee li:nth-child(3) { --i: 2; }
.marquee li:nth-child(4) { --i: 3; }
.marquee li:nth-child(5) { --i: 4; }
.marquee li:nth-child(6) { --i: 5; }
.marquee li:nth-child(7) { --i: 6; }
.marquee li:nth-child(8) { --i: 7; }
.marquee li:nth-child(9) { --i: 8; }
.marquee li:nth-child(10) { --i: 9; }
.marquee li:nth-child(11) { --i: 10; }
.marquee li:nth-child(12) { --i: 11; }
.marquee li:nth-child(13) { --i: 12; }
.marquee li:nth-child(14) { --i: 13; }
.marquee li:nth-child(15) { --i: 14; }
.marquee li:nth-child(16) { --i: 15; }

/* ---- Intake -> success swap (View Transition, progressive) ---- */
@media (prefers-reduced-motion: no-preference) {
  :root { view-transition-name: none; }
  .signup { view-transition-name: signup; }
  ::view-transition-old(signup), ::view-transition-new(signup) { animation-duration: .45s; animation-timing-function: var(--ease); }
}

/* ---- Entrance (one-shot): the light reveals the page ----
   Every delay here hangs off --t0, the moment the tube starts opening, and every curve is the shader's own,
   so this is one event rather than two that overlap. The copy used to be fully settled at 1.45s while the
   tube was 40% open - the page arrived first and the light turned on afterwards. Now the copy rises top-down
   as the pool spreads down it, the column comes up under the wash, the footer lands last and lowest where
   the light barely carries, and the first glint catches the wordmark as the tube completes at 3.3s.
   The page leads the tube by --t-neon - --t0, so the two overlap and settle together: one gesture, not two.
   Holding the copy until the tube itself starts left the screen dead for the best part of a second. */
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes rise-in { from { opacity: 0; translate: 0 14px; } to { opacity: 1; translate: 0 0; } }
.foot { animation: fade-in 1.2s calc(var(--t0) + 900ms) var(--ease) both; }
.intro > *, .signup { animation: rise-in 1s var(--ease-neon) both; animation-delay: calc(var(--t0) + var(--d, 0) * 130ms); }
.status { animation: glint 5.6s calc(var(--t0) + 2.2s) infinite; }   /* the tube finishes; the wordmark catches it */
.intro > :nth-child(2) { --d: 1; }
.intro > :nth-child(3) { --d: 2; }
.intro > :nth-child(4) { --d: 3; }
.signup { --d: 4; }
.freeze .foot, .freeze .intro > *, .freeze .signup, .freeze .marquee, .freeze .status, .freeze body::before { animation: none; } /* ?freeze: screenshots and tests */

/* ---- Small screens: one centred column. Copy and intake first, marquee band beneath, footer last on plain ground. ---- */
@media (max-width: 820px) {
  .page { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr) auto; row-gap: clamp(16px, 3vh, 32px); }
  .hero { grid-row: 1; width: 100%; max-width: 30rem; justify-self: center; text-align: center; padding-top: clamp(8px, 2.2vh, 20px); }   /* drops the lockup clear of the tube's bloom */
  .eyebrow { align-items: center; }
  /* A viewport-wide pool sits off-centre on a portrait screen, so the body keeps grain only and the pool moves onto the band. Transparent in dark; this paints under ?light. */
  body { background: var(--grain) 0 0 / 220px 220px var(--bg); }
  body::before { height: min(50vh, 440px); }
  .marquee { grid-column: 1; grid-row: 2; margin: 0 calc(-1 * var(--pad-x)); --n: 8; --t: 28s; background: radial-gradient(70% 65% at 50% 50%, var(--glow), transparent 70%); }
  .marquee li { left: 0; right: 0; align-items: center; }
  .marquee li:nth-child(n + 9) { clip-path: inset(100%); }   /* duplicates of 1-8 (delay wraps); clipped, not display:none, so a rotation cannot restart them out of phase */
  .done { justify-content: center; }
  .foot { grid-row: 3; flex-direction: column; align-items: center; text-align: center; }
}
/* Short portrait screens (a phone with its browser toolbars showing). The lockup is the only block with
   any give, and the ~20px it returns is the difference between five marquee items below and four. */
@media (max-width: 820px) and (max-height: 700px) {
  .hero { padding-top: 0; }
  .eyebrow { gap: .45em; margin-bottom: 1rem; }
}
/* Shorter bands (phone browsers with their toolbars showing) carry fewer items per loop, so the pitch (240cqh / n) clears the
   ~45px item. Below seven items the spacing (1/n of the loop) opens past the 15% sharp window above and the band runs dry between
   items - 0.5s at six, 1.4s at five, 2.8s at four - so these steps swap in `rise-near`, which holds focus for 26% instead.
   The first step sits at 230px, not a round 260: a 390x844 phone lands at ~240 and the pitch at n=8 there (72px) still clears the
   ~49px item with room, so rounding up would drop it to six for nothing. */
/* Same travel and same 25%/75% bounds as `rise`, wider focus: the plateau runs 37-63%, holding an item sharp for 26% of the loop instead of 15%. */
@keyframes rise-near {
  0%         { translate: 0 calc(-50% + var(--travel) / 2); filter: blur(10px); }
  25%        { filter: blur(10px); }
  37%, 63%   { filter: blur(0); }
  75%        { filter: blur(10px); }
  100%       { translate: 0 calc(-50% - var(--travel) / 2); filter: blur(10px); }
}
@container (max-height: 230px) {
  .marquee ul { --fade: 19%; }   /* rise-near holds focus from 37% to 63% of the loop, which is 19% in from each edge */
  .marquee li { --n: 6; gap: .25em; animation-name: rise-near; }   /* tighter tag-to-title gap keeps each pair reading as one item when the pitch is small */
  .marquee li:nth-child(n + 7) { clip-path: inset(100%); }
}
@container (max-height: 140px) {   /* 390x664 class (iPhone in Safari): five */
  .marquee li { --n: 5; }
  .marquee li:nth-child(n + 6) { clip-path: inset(100%); }
}
@container (max-height: 110px) {   /* under ~110px a fifth item would touch its neighbour: four */
  .marquee li { --n: 4; }
  .marquee li:nth-child(n + 5) { clip-path: inset(100%); }
}
@media (max-width: 480px) {
  .field { height: 52px; }
  .field button { padding: 0 14px; }
}
/* ---- Short viewports (landscape phones, and portrait phones whose browser chrome leaves under ~600px): scroll rather
   than crush the marquee. Below this the band drops under ~100px, where even four items overlap. ---- */
@media (max-height: 600px) {
  html, body { height: auto; overflow: auto; }
  .page { height: auto; min-height: 100dvh; }
  .marquee { min-height: 50vh; }
  h1 { font-size: clamp(2.5rem, 16cqw, 6.5rem); }   /* the page scrolls here, so drop the height cap */
}
/* A size container answers height queries from its own definite height. On the one-column layout a `min-height` alone
   leaves that height indefinite, the band reads as tiny and the item count collapses to four; a definite track fixes it. */
@media (max-height: 600px) and (max-width: 820px) {
  .page { grid-template-rows: auto 50vh auto; }
  .marquee { min-height: 0; }
}

/* ---- Light theme: warm paper, ink type, brass details. Off for now: `?light` on the URL previews it (app.js adds the class).
   To follow the OS setting instead, wrap this rule in `@media (prefers-color-scheme: light)` and change `.light` to `:root`. ---- */
.light {
  --bg: oklch(96.5% 0.012 85);
  --ink: oklch(18% 0.012 40);
  --ink-2: oklch(38% 0.012 40);
  --ink-3: oklch(50% 0.012 40);
  --line: oklch(0% 0 0 / 0.14);
  --line-2: oklch(0% 0 0 / 0.35);
  --fill: oklch(0% 0 0 / 0.03);
  --fill-2: oklch(0% 0 0 / 0.045);
  --brand: oklch(46% 0.10 70);
  --glint: oklch(72% 0.13 76);
  --accent: var(--ink);
  --accent-2: oklch(30% 0.012 40);
  --on-accent: var(--bg);
  --error: oklch(50% 0.17 25);
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .08 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");   /* 8% black */
  --neon-core: oklch(56% 0.13 66);
  --neon-1: oklch(70% 0.15 74 / .4);
  --neon-2: oklch(80% 0.11 72 / .28);
  --neon-rgb-1: 1.7 1.35 .85;
  --neon-rgb-2: .34 .19 .05;
  --glow: oklch(90% 0.05 78 / .9);
  color-scheme: light;
}

/* ---- Reduced motion: a still, legible list ---- */
@media (prefers-reduced-motion: reduce) {
  .foot, .intro > *, .signup, .marquee, .status, body::before { animation: none; }
  .marquee ul { display: flex; flex-direction: column; justify-content: center; gap: clamp(14px, 3cqh, 28px); padding-left: clamp(4px, 2vw, 32px); -webkit-mask-image: none; mask-image: none; }   /* a still list is read, not glimpsed: no vignette on the first and last item */
  .marquee li { position: static; translate: none; animation: none; opacity: 1; filter: none; font-size: clamp(1.25rem, min(2.4cqw + 2.5cqh, 10.5cqw), 2.4rem); }
  .marquee li:nth-child(n + 9) { display: none; }
}
@media (prefers-reduced-motion: reduce) and (max-width: 820px) {
  .marquee ul { padding-left: 0; }
  .marquee li:nth-child(n + 5) { display: none; }
}
