/* ============================================================
   Proof
   "Marking up a galley proof in a darkroom."
   Warm near-black, cream type, one amber accent, rust for cuts.
   ============================================================ */

/* Self-hosted faces (P6 §4, W11) — variable woff2 vendored under fonts/, all
   OFL. The two MEASURING faces (Literata = paper, Spline Sans Mono = chips)
   are font-display: block — a fallback swap mid-measure is the lie pretext
   can't survive (G5); boot awaits them before the first measurement anyway,
   so block never shows. Hanken is chrome only and may swap freely. */
@font-face {
  font-family: 'Literata';
  font-style: normal;
  font-weight: 300 700;
  font-display: block;
  src: url(/fonts/literata-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Literata';
  font-style: normal;
  font-weight: 300 700;
  font-display: block;
  src: url(/fonts/literata-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Literata';
  font-style: italic;
  font-weight: 300 700;
  font-display: block;
  src: url(/fonts/literata-italic-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Literata';
  font-style: italic;
  font-weight: 300 700;
  font-display: block;
  src: url(/fonts/literata-italic-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Spline Sans Mono';
  font-style: normal;
  font-weight: 300 600;
  font-display: block;
  src: url(/fonts/spline-sans-mono-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Spline Sans Mono';
  font-style: normal;
  font-weight: 300 600;
  font-display: block;
  src: url(/fonts/spline-sans-mono-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(/fonts/hanken-grotesk-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Spectral SemiBold — the WORDMARK face only (the ¶Proof brand note). Chrome, not
   a measuring face, so it may swap freely (like Hanken); not preloaded. Single
   latin subset (covers the pilcrow U+00B6); missing glyphs fall back to serif. */
@font-face {
  font-family: 'Spectral';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/fonts/spectral-600-latin.woff2) format('woff2');
}
/* EDEN IMPLEMENTATION — the editor frames use these three faces explicitly:
   Work Sans for chrome, Solway for the transcript, and DM Mono for time/value
   readouts. They are self-hosted so the analytic transcript measurer and the
   browser always see the same bytes. */
@font-face {
  font-family: 'Work Sans';
  font-style: normal;
  font-weight: 100 900;
  font-display: block;
  src: url(/fonts/work-sans-latin.woff2) format('woff2');
}
@font-face {
  font-family: 'Solway';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url(/fonts/solway-400-latin.woff2) format('woff2');
}
@font-face {
  font-family: 'Solway';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url(/fonts/solway-500-latin.woff2) format('woff2');
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url(/fonts/dm-mono-400-latin.woff2) format('woff2');
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url(/fonts/dm-mono-500-latin.woff2) format('woff2');
}

:root {
  /* Native controls render part of their UI outside Proof's CSS box tree
     (notably the operating-system-owned <select> option menu). Keep that
     chrome in the same dark scheme as the token palette instead of allowing
     a white UA popup behind our inherited light text. */
  color-scheme: dark;

  /* Z-AXIS CONTRACT — body-level surfaces share one small semantic ladder.
     Local paint order inside a component still uses 0/1/2; these roles are
     only for chrome that must compare with another independently mounted
     surface. The generous gaps are maintenance space, not an invitation to
     mint intermediate one-offs: choose the nearest named role instead. */
  --layer-sticky: 70;
  --layer-notice: 110;
  --layer-editor-strip: 120;
  --layer-job-dock: 130;
  --layer-drawer-scrim: 140;
  --layer-drawer: 150;
  --layer-skip-link: 160;
  --layer-popover: 180;
  --layer-toast: 190;
  --layer-dialog-scrim: 200;
  --layer-dialog: 201;
  --layer-search-scrim: 220;
  --layer-search: 221;
  --layer-coachmark-target: 300;
  --layer-coachmark-tether: 301;
  --layer-coachmark-scrim: 302;
  --layer-coachmark: 303;

  /* TONE PASS (Jesse 2026-06-29: "the yellow and brown has lost its charm — fresh eyes").
     The dark fills carried too much chroma (0.010–0.014 at hue 65–70), which on large
     surfaces read as muddy yellow/brown. Slashed the chroma toward the canonical browse-
     mockup's restraint (its darks #100f0e/#161514 are ~0.004) while HOLDING every lightness
     value — contrast is governed by L in oklch, so the WCAG floors (contrast.test.ts) are
     untouched. COOLED (Jesse 2026-06-29 round 2: "still a little browney, a little cooler"):
     hue swung 70→255 so the residual chroma reads cool-slate, harmonizing with the blue
     --accent (also hue ~250) instead of brown. The saturated life still comes from the
     accents (blue/amber/status), not the walls. */
  /* EDEN PASS 1 (2026-08-11) — grounds/inks/accent re-valued from Eden's first
     Figma pass (Proof-WiP DStDtVZUSiStJrcRexfAGL, his bound Variables + frame
     samples), converted hex→oklch verbatim EXCEPT --ink-faint, lifted 0.576→0.60
     to clear the AA floor his dimmed label misses (gate: contrast.test.ts).
     The gold interaction accent replaces the blue; washes/sel/hot derive. */
  /* round 5 (Jesse: "our BGs are literally different than his still") — the
     page drops to his deeper black; floors only get easier on darker grounds. */
  --bg:        oklch(0.155 0.005 286);
  --bg-deep:   oklch(0.135 0.004 286);
  --surface:   oklch(0.205 0.006 271);
  /* Shared darkroom depth. The editor established this tonal ladder; periphery
     surfaces borrow the atmosphere while retaining their own geometry. */
  --room-canvas: oklch(0.184 0.006 286);
  --room-canvas-lift: color-mix(in oklab, var(--room-canvas) 88%, var(--surface));
  --room-header: color-mix(in oklab, var(--bg-deep) 92%, var(--room-canvas));
  --room-panel: color-mix(in oklab, var(--room-canvas) 82%, var(--surface));
  --room-control: color-mix(in oklab, var(--room-canvas) 94%, var(--bg-deep));
  /* The recessed ground around contained media. It deliberately reuses the
     former gutter relationship instead of literal video-black, so letterbox
     space belongs to the room while the picture still reads as projected. */
  --projection-well: color-mix(in oklab, var(--bg) 68%, var(--surface));
  /* a SUBTLE semi-opaque lift over the warm-dark bg (Jesse 2026-06-29) — a low-opacity
     CREAM wash (the browse-mockup's #ece6d6 ink at ~5%), NOT a heavier warm slab like
     --surface (which read as an ugly light-brown band on large surfaces). Matches the
     canonical darkroom-browser mock's tone-shift architecture: surfaces lift via gentle
     semi-opaque cream over the page, never a hard jump. Reuse app-wide. */
  --lift-1:    rgb(236 230 216 / 0.05);
  --line:      oklch(0.337 0.001 271);
  /* inks settle to a near-neutral, faintly-cool off-white (round 2) — the warm cream read
     as part of the "browney" cast against the now-cool walls, so they swung cool too, at a
     whisper of chroma so text stays soft, not clinical-blue. */
  --ink:       oklch(0.882 0.001 271);
  --ink-dim:   oklch(0.725 0.001 271);
  /* the faint split (P2 audit P0): --ink-faint is TEXT — anything read as
     words — and must clear WCAG AA 4.5:1 on --bg AND --surface; --ink-deco is
     decorative strokes/glyphs only (dotted fill underline, fdel line-through,
     grip, disclosure chevron) and holds the 3:1 non-text floor. The whisper
     register lives in --ink-deco now; never put text on it.
     tests/contrast.test.ts pins the floors — retune freely above them. */
  /* Default clear-a11y lift: secondary text must stay readable on editor
     grounds (tab band, control cards), not only --bg/--surface. Floor is
     still pinned by tests/contrast.test.ts; HC remains the AAA tier. */
  --ink-faint: oklch(0.66 0.002 106);
  --ink-deco:  oklch(0.524 0.003 107);
  /* THE accent — renamed from --amber the day it turned blue (Jesse ruling:
     the token names a ROLE, not a color). The cool INTERACTION/wayfinding role.
     SINGLE KNOB (Jesse 2026-06-29): --accent is the ONLY value to edit — the soft
     FILL and the selection tint now DERIVE from it via relative-color (like
     --accent-wash/-sel-hot already do), so retinting the blue propagates to the
     tab/button fills + selection in one edit instead of nine hardcoded literals.
     NOT the only loud role anymore — the call-to-action is its own
     --primary-action (DECISIONS #27(1) retired "one accent only"). */
  --accent:     oklch(0.630 0.070 81);
  --accent-soft:oklch(from var(--accent) l c h / 0.18);
  --accent-sel: oklch(from var(--accent) l c h / 0.40);  /* selection reads stronger than the playback wash */
  /* THE primary-action role — the call-to-action fill (the mockup's amber "New").
     DECISIONS #27(1): Jesse retired "one accent only" in favour of a small CODIFIED
     SET of role tokens, each globally swappable. This is the CTA role, deliberately
     DISTINCT from the two neighbours it must never collapse into:
       · --accent (shared wayfinding/interaction) — a call-to-action is not a
         wayfinding mark, so it owns a distinct role like the status trio below;
       · --working amber (a STATUS, "processing") — borrowing it for an ACTION is the
         --accent overload sin (token names a role, Law 1). Its own token, own value.
     Amber by Jesse's mockup; the exact value is his live feel-tune. Ink on the fill
     is --bg-deep (a light fill) — contrast.test.ts pins the 4.5:1 floor. */
  --primary-action: oklch(0.745 0.091 82);
  --rust:      oklch(0.572 0.040 61);
  /* THE stricken role — rust is the color, stricken is the ROLE (the --accent
     lesson): ghosts, line-throughs, the section/chip ×-hover, and the CUT
     clock all speak it. New stricken-meaning rules use --stricken. */
  --stricken:  var(--rust);
  --rust-dim:  oklch(0.46 0.060 38);
  --moved:     oklch(0.72 0.085 145);
  /* THE working role — semantic STATUS amber (brief §3: green=ready · amber=
     working · grey=archived; "every mark means something"). DISTINCT from the
     interaction --accent: a processing/ingesting/transcribing cue is a status,
     not a wayfinding mark, so it gets its OWN role. Before this token the
     working cues borrowed --accent, conflating two roles. Non-text (progress
     fill / spinner head), so no contrast gate; the
     exact amber is Jesse's live feel-tune. Soft = the spinner track. */
  --working:      oklch(0.80 0.135 80);
  --working-soft: oklch(from var(--working) l c h / 0.20);
  /* THE comment-position role (COMMENT-WORD-ANCHOR-1) — a guest comment's marker on a
     recipient scrub bar. Its OWN role, not a borrow (the --accent lesson): the marker
     must read against BOTH the --accent playback fill AND the neutral --line track,
     on BOTH the dark room and the light review card — so annotation gets a distinct
     warm role. A note means the same on any surface, and this mark is
     non-text (a 6px pip), so no contrast gate; Jesse feel-tunes the exact amber. */
  --comment-mark: oklch(0.80 0.14 68);
  /* THE ready role — the green that closes the brief §3 status trio (green=ready ·
     amber=working · grey=archived). A POSITIVE "settled, openable" mark, its OWN
     role: distinct in hue from --working amber (a status must never read as still-
     working) AND from the cool library --accent teal (a status is not a wayfinding
     mark) AND from the editor's reorder --moved green (a different role on a
     different surface — never share a token across roles, the --accent lesson).
     Profile-constant like --working: status means the same everywhere, so the cool
     swap (interaction triad only) leaves it untouched. Non-text dot ⇒ no contrast
     gate; the exact green is Jesse's live feel-tune. */
  --ready:        oklch(0.72 0.115 150);
  /* THE archived role — the GREY that completes the brief §3 status trio (green=
     ready · amber=working · grey=archived). The trio's NEUTRAL member: a status
     that means "set aside, no active state" is deliberately DESATURATED (near-zero
     chroma) so it never competes with the saturated ready/working marks. Its OWN
     named role (not a borrowed --wash/--ink-dim) so "grey = archived" is a law, not
     an accident of which neutral it happened to reuse — the same lesson --working
     taught when it silently turned teal borrowing --accent. Profile-constant: a
     status means the same in every profile, so the cool library swap leaves it
     untouched. Non-text mark (a dim + a grey pill) ⇒ no contrast gate; the exact
     grey is Jesse's live feel-tune. Wash = the pill surface, derived so a retune
     carries it. */
  --archived:      oklch(0.60 0.010 250);
  --archived-wash: color-mix(in oklab, var(--archived) 14%, transparent);
  /* the set-aside RECEDE — how far an archived item dims without disappearing.
     A named micro-token (taxonomy gap #2), not a magic 0.6 buried in a rule. */
  --set-aside-opacity: 0.6;

  /* washes — hover/selection tints DERIVED from the inks above, so a retheme
     carries them for free. Never hand-mix an overlay color in a rule. */
  --wash:       color-mix(in oklab, var(--ink) 7%, transparent);
  --wash-soft:  color-mix(in oklab, var(--ink) 5%, transparent);
  --accent-wash: color-mix(in oklab, var(--accent) 7%, transparent);
  --working-wash: color-mix(in oklab, var(--working) 8%, transparent);
  --rust-wash:  color-mix(in oklab, var(--rust) 12%, transparent);
  /* T9 combination layer (states.md laws 2+3): hover-on-washed and sel∧cur read
     as the SAME wash one lightness step hotter. The step writes the wash ITSELF
     (channel A only — filter:brightness would lift ink and tint the mark,
     channel B/D crosstalk, so it's banned; tests/states.test.ts pins that).
     Hot pairs derive from their wash token + the shared delta, so a wash retune
     carries them for free. Step size (and sign) is a look-lane placeholder. */
  --hover-delta: 0.05;
  --accent-sel-hot:  oklch(from var(--accent-sel) calc(l + var(--hover-delta)) c h);
  --accent-soft-hot: oklch(from var(--accent-soft) calc(l + var(--hover-delta)) c h);
  /* T10's presence tint, tokenized (the sheet's own law: never hand-mix in a rule) */
  --presence-wash: color-mix(in oklab, var(--accent) 3.5%, transparent);
  --scrim:      oklch(0.1 0.008 255 / 0.45);
  /* the DEFAULT project color — every project HAS a color (Jesse 2026-07-09: no
     colorless state, no Clear-color verb); an UNSET color renders as this slate
     wherever a project color shows (swatch · rails · tile faces). ≈ the recolor
     picker's Slate (#64748b), so "default" and "picked Slate" read identically. */
  --project-slate: oklch(0.554 0.046 257);
  /* ink that reads OVER a scrimmed dark backdrop (the --scrim is dark in every theme,
     so its over-text is a constant near-white in every theme — NOT --ink, which flips
     with the surface). Used by the grid tile's blurred poster hero (PT-2); a named
     role so "text on media" is a token, not a hand-typed white in a rule. */
  --ink-on-media: oklch(0.97 0.015 85);

  /* AK-3 — the audio-only thumbnail's decorative waveform amber. Its OWN role token,
     NOT --working (status) or --primary-action (CTA): borrowing either would be the
     --accent-overload sin (a token names a role, Law 1). Non-text/decorative (the card
     name carries identity; the svg is aria-hidden) ⇒ no contrast gate. Profile-constant
     like the status trio; the exact amber is Jesse's live feel-tune lane. */
  --audio-wave: oklch(0.72 0.15 55);

  /* speaker palette — equal lightness/chroma, hue-spread (extend in order).
     EDEN PASS 1: spk1/spk2 are his Text-Speaker Variables; spk3/4 keep their
     hues, re-leveled to his darker register so the family law holds. */
  /* Speaker labels are text (hue + copy identity). Keep equal L/C family law
     while clearing AA 4.5:1 on --surface as well as --bg. */
  --spk1: oklch(0.62 0.064 143);
  --spk2: oklch(0.62 0.045 224);
  --spk3: oklch(0.62 0.055 280);
  --spk4: oklch(0.62 0.055 20);

  /* clip provenance — shared by the strip canvas (strip.js reads these) and
     the legend; the canvas and its key can't drift apart */
  --clip-ord: oklch(0.78 0.05 85);
  --clip-reo: oklch(0.72 0.12 145);
  --clip-dup: oklch(0.66 0.13 300);
  /* Timeline media objects use their own NLE material role. It is intentionally
     independent from interaction gold and provenance colors. Keep the Premiere-blue
     cast as material identity, but close its value/chroma toward the editor surface:
     the resting clip is substrate; words, waveform, and interaction state lead. */
  --timeline-clip: oklch(0.31 0.036 250);
  --timeline-wave: oklch(0.68 0.032 245);

  --sp1: 4px; --sp2: 8px; --sp3: 12px; --sp4: 16px;
  --sp5: 24px; --sp6: 32px; --sp7: 48px; --sp8: 64px;

  /* The corner-radius ladder — most of the app's ~280 border-radius rules
     were one-off px values (2 through 14) with no shared name. These three
     steps are the values that number rounded onto when normalized to the
     nearest multiple of 4. --radius-full/--radius-pill are their own roles,
     not steps on this ladder: a circle is proportional (can't be "rounded"
     to a px number) and a pill already means "as round as possible", a
     different intent than "slightly rounded corner". */
  --radius-xs:   4px;
  --radius-sm:   8px;
  --radius-md:  12px;
  --radius-pill: 999px;
  --radius-full: 50%;

  /* The Radix Popover/DropdownMenu card radius (components/ui/menu-surface
     .module.css). A plain token, not baked into the composed surface class,
     so per-file mobile breakpoints can still override it unambiguously. */
  --menu-radius: 10px;

  /* One logo seat across Library -> Opening cut -> Editor. Keeping the shell
     geometry here prevents route handoff from resizing or nudging the mark. */
  --app-header-height: 56px;
  --app-wordmark-width: 108px;
  --app-wordmark-height: 41px;
  --app-header-pad-inline: var(--sp5);

  /* type ramp — every font-size in the app is one of these roles.
     (--geo-* sizes are NOT here: transcript geometry is published by
     js/layout.js, the single source of truth pretext measures with.)
     P3(b): the chrome ramp is THREE steps (micro/standard/body) — the old
     9/10/11/12/13 ladder was 1px noise pretending to be hierarchy. Roles
     keep their names (usage stays semantic; a future re-split is one line)
     but their values converge. tests/states.test.ts pins the step COUNT,
     never the values — sizes stay Jesse's lane. */
  --fs-label:   10px;  /* uppercase micro-labels: clock/panel/strip headings (tracking carries the smallness) */
  --fs-chip:    12px;  /* chips, tabs, kbd, small buttons — bumped 10→12 (Jesse 2026-07-09: button legibility too low everywhere; the canonical mockup's toolbar text is 12px) */
  --fs-note:    12px;  /* annotations: hints, speakers, counts, sublines */
  --fs-ui:      12px;  /* standard chrome text: filename, toasts, scrub values */
  --fs-control: 14px;  /* interactive controls: toggles, inputs, selection meta — interactive text earns body size */
  --fs-base:    14px;  /* body default */
  --fs-clock:   18px;  /* the big TRT readout */
  --fs-brand:   21px;  /* wordmark */

  --track-label:   0.08em;  /* uppercase micro labels (rails, kickers, colheads, badges) */
  --track-display: -0.025em; /* compact display titles */
  --measure-prose: 52ch;     /* reading copy ceiling; leave existing ch below this */

  --f-ui: "Hanken Grotesk", sans-serif;
  --f-body: "Literata", serif;
  --f-display: "Work Sans", sans-serif;
  --f-brand: "Spectral", "Literata", serif;  /* the wordmark face — Spectral SemiBold; falls back to the prior Literata wordmark */
  --f-tc: "Spline Sans Mono", monospace;
}

@media (min-width: 48.001rem) and (max-width: 1120px) {
  :root {
    --app-wordmark-width: 88px;
    --app-wordmark-height: 33px;
    --app-header-pad-inline: var(--sp4);
  }
}

/* P9(c) — HIGH CONTRAST: a token-TIER override, never a redesign. The tier
   re-values ink-side tokens only; the grounds (--bg/--bg-deep/--surface) keep
   the darkroom, and every wash/hot/presence tint derives from these tokens
   (color-mix / oklch-from), so the whole state system rethemes from this one
   block — no rule below ever mentions the tier. Floors the gate pins
   (tests/contrast.test.ts): text inks AAA 7:1 on both grounds, --ink-deco and
   the stricken rust ≥ 4.5:1, --bg-deep ink ≥ 4.5:1 on the accent fill, and
   every name here must already exist in :root (a swap, never a fork). Exact
   values are Jesse's lane — retune freely above the floors. main.ts applies
   the tier as html[data-contrast="high"] from the proof:ui envelope. */
:root[data-contrast="high"] {
  --line:      oklch(0.42 0.018 70);
  --ink:       oklch(0.97 0.015 85);
  --ink-dim:   oklch(0.78 0.020 78);
  --ink-faint: oklch(0.76 0.020 76);
  --ink-deco:  oklch(0.68 0.018 75);
  --accent:     oklch(0.82 0.100 250);
  --accent-soft:oklch(from var(--accent) l c h / 0.26);  /* derives hue; HC keeps a stronger fill alpha */
  --accent-sel: oklch(from var(--accent) l c h / 0.42);
  --rust:      oklch(0.68 0.100 38);
  --rust-dim:  oklch(0.58 0.075 38);
  --moved:     oklch(0.78 0.085 145);
  --working:   oklch(0.85 0.120 80);
  --ready:     oklch(0.80 0.110 150);
  --archived:  oklch(0.74 0.014 250);
  --audio-wave: oklch(0.78 0.15 55);
  --hover-delta: 0.07;
  --scrim:      oklch(0.1 0.01 65 / 0.65);
  --spk1: oklch(0.80 0.080 80);
  --spk2: oklch(0.80 0.070 150);
  --spk3: oklch(0.80 0.075 280);
  --spk4: oklch(0.80 0.075 20);
  --clip-ord: oklch(0.84 0.05 85);
  --clip-reo: oklch(0.78 0.12 145);
  --clip-dup: oklch(0.74 0.13 300);
  --timeline-clip: oklch(0.42 0.052 250);
  --timeline-wave: oklch(0.80 0.036 245);
}

/* App-shell, landing, and editor surfaces intentionally inherit one role palette
   from :root. Route markers control layout, never color authority. */
/* §look-lane — the DESCEND-INTO-EDITOR motion. shell.ts sets body[data-nav=
   "descend"] for one browse→editor hop (ephemeral, like layout-snap); the editor
   stage rises+fades forward so the browse→editor relationship reads as "one app,
   two depths," not two pages. Transform/opacity only — compositor-cheap, no
   layout thrash, no new hex (a SPATIAL cue, never a colored one; the status/
   interaction hues are untouched). A keyframe (not a transition) so it plays
   the instant the editor surface mounts, with no from→to reflow to coordinate.
   Reuses the established 220ms cubic-bezier(0.2,0.8,0.2,1) family. */
@keyframes nav-descend-rise {
  from { opacity: 0; transform: translateY(10px) scale(0.994); }
  to   { opacity: 1; transform: none; }
}
body[data-route="editor"][data-nav="descend"] main {
  animation: nav-descend-rise 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* Reduced-motion SNAPS to today's hard cut — the safety floor, the existing
   :root[data-motion="reduced"] convention (style.css ~877). */
:root[data-motion="reduced"] body[data-route="editor"][data-nav="descend"] main {
  animation: none;
}

/* §look-lane — the ASCEND (editor→browse): the symmetric inverse of the descend.
   Leaving the canvas, the library OVERVIEW settles back into view from slightly
   above + larger — you rise back OUT to the wider depth, the mirror of the canvas
   rising forward on the way in. shell.ts sets body[data-nav="ascend"] for the one
   editor→browse hop; the WHOLE browse shell (rail + content together = the
   overview) plays the settle. Same transform/opacity-only, no-new-hex,
   spatial-not-colored discipline as the descend; reduced-motion SNAPS it. */
@keyframes nav-ascend-settle {
  from { opacity: 0; transform: translateY(-8px) scale(1.006); }
  to   { opacity: 1; transform: none; }
}
body[data-nav="ascend"] .browse-shell {
  animation: nav-ascend-settle 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
:root[data-motion="reduced"] body[data-nav="ascend"] .browse-shell {
  animation: none;
}

/* Entering a project keeps the persistent library shell still while the new
   project scene settles into it. The selected card has already acknowledged
   the click; this is the second beat, not a generic animation on every route. */
@keyframes nav-project-enter {
  from { opacity: .35; transform: translateY(8px) scale(.997); }
  to { opacity: 1; transform: none; }
}
body[data-nav="project-enter"] .browse-shell main {
  animation: nav-project-enter 240ms cubic-bezier(.22, 1, .36, 1);
}
:root[data-motion="reduced"] body[data-nav="project-enter"] .browse-shell main {
  animation: none;
}

/* Mode settle pins the outgoing transcript with WAAPI. Kill the shelf's
   transform transition so releasing the pin cannot fly it to the parked sheet. */
body[data-route="editor"] main[data-mode-settle] > .transcript,
body[data-route="editor"] main[data-mode-settle] > .monitor {
  transition: none !important;
}
:root[data-motion="reduced"] body[data-route="editor"] main[data-mode-settle] > .transcript,
:root[data-motion="reduced"] body[data-route="editor"] main[data-mode-settle] > .monitor {
  animation: none;
  transition: none;
}

* { box-sizing: border-box; }
html { height: 100%; }
/* Keep horizontal trackpad gestures in the editor, including at timeline limits
   and over its ruler/chrome. The viewport owns browser history overscroll. */
html:has(body[data-editor-access]), body[data-editor-access] {
  overscroll-behavior-x: none;
}
body {
  margin: 0;
  /* dvh: track the REAL visual viewport (mobile URL bars, folds, rotation) */
  height: 100vh;
  height: 100dvh;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--f-ui);
  font-size: var(--fs-base);
  display: grid;
  grid-template-rows: auto 1fr auto;
  overflow: hidden;
}

button {
  font: inherit; color: inherit; background: none;
  border: 1px solid var(--line); border-radius: 4px;
  padding: 6px 10px; cursor: pointer;
}
button:hover { border-color: var(--ink-dim); }
button:active { background: var(--surface); }

/* Native date controls appear in metadata, share expiry, automations, dialogs,
   and browser filters. The calendar glyph is UA chrome, so normalize it once
   instead of letting individual pages inherit a low-contrast black icon. */
input[type="date"] { color-scheme: dark; }
input[type="date"]::-webkit-calendar-picker-indicator {
  opacity: .72;
  cursor: pointer;
}
/* ★ SELECTED-CONTROL LAW (RFC active-tab-selected-control / DECISIONS #49):
   a control that marks itself "active/selected" (segmented tabs, mode pickers,
   rate picks, dock tabs, view/handle toggles) reads as a selected AREA —
   `background: var(--accent-soft)` + `color: var(--accent)` — and NEVER as a
   line. Lines (underline/edge accents) are reserved for the TEMPORAL marks
   (playhead/scrubber/progress); an "active" border-line collides with them.
   Keep the control's RESTING border; the active state fills, it does not
   escalate the edge. New active/selected controls copy THIS, not a one-off. */
/* capability-hidden chrome stays hidden no matter what display a reveal rule
   sets — the author-display-beats-UA-[hidden] gotcha, solved once for all */
button[hidden] { display: none !important; }
/* …except the peek handle: its BOX stays reserved so the header never
   reflows when it appears/disappears (geometric inertness, Jesse) */
/* P3(b) — BUTTON VERB HIERARCHY, three registers (audit P1): QUIET
   (borderless inline glyphs — grip, ×, ⤒) for verbs living inside content;
   STANDARD (the bordered default above) for panel verbs; PRIMARY (accent
   fill) for THE verb a panel exists to deliver — at most one on screen, and
   today exactly one in the app: the render command, because the cut itself
   is what Proof exports. Hierarchy is treatment, never new behavior; hover/
   active ride the same --hover-delta as every other hot state (channel A's
   one knob). Ink is --bg-deep on the accent fill — contrast gate pins it. */
button.primary {
  background: var(--accent); border-color: transparent;
  color: var(--bg-deep); font-weight: 600;
}
button.primary:hover {
  background: oklch(from var(--accent) calc(l + var(--hover-delta)) c h);
  border-color: transparent;
}
button.primary:active {
  background: oklch(from var(--accent) calc(l - var(--hover-delta)) c h);
}
kbd {
  font-family: var(--f-tc); font-size: var(--fs-chip); color: var(--ink-faint);
  border: 1px solid var(--line); border-radius: 3px; padding: 0 4px; margin-left: 4px;
}
.tc { font-family: var(--f-tc); font-variant-numeric: tabular-nums; }
.dim { color: var(--ink-dim); }
.count {
  font-family: var(--f-tc); font-size: var(--fs-note); color: var(--accent);
  margin-left: var(--sp2);
}
/* ============================================================
   Proof — the shared ROW primitive (ROW-PRIMITIVE-PILOT · BROWSER-DESIGN-COHERENCE)
   The app has ~15 list/row surfaces (version history, org members, browse
   projects, account workspaces, api-keys, billing, …) and, before this sheet,
   ~15 bespoke naming schemes doing the SAME leading·title·meta·trailing job —
   each re-improvising its own kebab placement, hover rule and keyboard reach
   (the RFC audit, investigations/browser-row-primitive.md). This sheet names the
   pattern the surfaces independently rediscovered: it is a codification, NOT a
   redesign — nearly every text row is already `display:flex; align-items:baseline`
   with a `flex:1` title and a pushed-right trailing edge, so a surface adopts the
   slot classes with ZERO geometry change (RFC finding #2).

   CSS-class contract FIRST, TS builder DEFERRED (RFC decision 1): each surface
   opts into the slot classes incrementally, one file per shift, verified in
   isolation. Pilot = browse projects (`.project-item`) — see browse.css, where
   the orphaned ⋯ kebab finally lands in a real `.row-trail` slot.

   Canonical inset: a surface that pads the ROW itself uses `var(--sp2) var(--sp3)`
   (the value `.project-link`/`.recent-link` already carry). This sheet does NOT
   impose padding on `.row` — many surfaces (the pilot included) pad their inner
   interactive child and keep the border/rail on the wrapper, so a base padding
   here would DOUBLE the inset. Padding stays a per-surface concern; the token is
   the shared law, the placement is the surface's.
   ============================================================ */

/* the row itself — the convergent idiom, named once. baseline so a tall leading
   glyph and small meta text sit on the same type baseline; the gap is the
   inter-slot rhythm (only in-flow siblings pay it — an absolutely-placed
   `.row-trail`, the table-row case, contributes none). */
.row {
  display: flex;
  align-items: baseline;
  gap: var(--sp2);
}

/* optional LEADING slot — avatar / thumb / icon / status disc. Fixed size, never
   grows or shrinks. OMIT it entirely and the title starts at the row's edge (the
   pilot's list row has no leading glyph — its identity is the accent rail border). */
.row-lead { flex: 0 0 auto; }

/* the TITLE — the primary label, the one growing slot. min-width:0 is load-bearing:
   without it a long title refuses to shrink below its content width and SHOVES the
   trailing edge off-row; with it the title truncates and the trailing actions stay
   pinned. (The pilot's `.project-link` is itself a grid of table cells — a `.row`
   child can carry its own internal layout; `.row-title` only governs how it flexes.) */
.row-title { flex: 1; min-width: 0; }

/* the META slot — secondary text (counts, dates, roles). Recedes to the cool chrome
   register. The middot `::before` separator (lifted from the account/billing
   convention, RFC's cleanest of the six meta dialects) lets multi-part meta compose
   without bespoke spans: "Created May 3 · last used today" is two `.row-meta`
   spans, each self-separating. `--nums` opts into tabular figures for numeric reads
   (billing/quota) so columns of digits align. */
.row-meta {
  font-family: var(--f-ui);
  font-size: var(--fs-note);
  color: var(--ink-dim);
}
.row-meta::before {
  content: "·";
  margin-right: var(--sp2);
  color: var(--ink-faint);
}
.row-meta--nums { font-variant-numeric: tabular-nums; }

/* the TRAILING slot — the actions/kebab edge, pinned to the far side (margin-left:auto
   so it hugs the trailing edge regardless of how many meta slots precede it). This is
   the kebab's new HOME: before this primitive, the browse kebab was the ONLY one in the
   app, an absolutely-placed overlay belonging to no convention. A grid-table row (the
   pilot) overrides the in-flow pin with absolute placement so the trailing actions claim
   NO column track and the colhead stays aligned — that override lives with the surface,
   not here (analogous to how `.browse-card > .row-actions` was already absolute). */
.row-trail {
  flex: 0 0 auto;
  margin-left: auto;
}

/* the trailing-reveal — the single behavior the whole app was missing. On a pointer
   device the trailing actions rest hidden and surface on row hover / focus-within, so a
   dense list reads clean until you reach for a row (the comment-card spotlight is the
   adjacent precedent — hover already means "reveal affordances"). opacity, NOT display,
   so the slot keeps its width at rest: no reflow on hover, and a grid-table row's columns
   never twitch. GATED under @media (hover:hover) on purpose: a coarse / no-hover pointer
   can't hover-reveal, so on touch the trailing actions stay VISIBLE. Ungating this (a
   plain always-on rule) breaks the touch tier — the design-audit test bites that. */
@media (hover: hover) {
  .row-trail {
    opacity: 0;
    transition: opacity 0.12s ease;
  }
  .row:hover .row-trail,
  .row:focus-within .row-trail {
    opacity: 1;
  }
}

/* opt-in SELECTABLE behaviour — a row that acts as a listbox option. The role/tabindex
   and the Enter/Space→activate handler (lifted from history-row, the app's one correct
   keyboard row) are applied in JS by the DEFERRED `.row` TS builder (RFC decision 1); no
   surface opts in yet (the pilot's project rows carry wireMasterSelect's own roving-tabindex
   grammar, which this must not fight). This sheet ships the STYLING hooks the future opt-in
   reads — the pointer affordance + the current/selected mark — so a migrating surface gets
   the look for free the moment it adds the class. */
.row--selectable { cursor: pointer; }
.row--selectable[aria-current="true"],
.row--selectable.selected { background: var(--wash); }
/* ------------------------------------------------ header
   The header must have NO intrinsic min-width beyond a phone's width —
   content that can't shrink is what makes device emulation scale-zoom the
   whole page. Chrome wraps; labels hide progressively on narrow screens. */
.top {
  display: flex; align-items: baseline;
  flex-wrap: wrap;
  gap: 2px clamp(8px, 2vw, 32px);
  padding: var(--sp2) clamp(10px, 2vw, 24px);
  border-bottom: 1px solid var(--line);
  background: var(--bg-deep);
}
.top > * { min-width: 0; }
/* TOOLS-IA slice 1 — the header menubar button (Edit; File/Transcript later). A
   quiet desktop-menu label: dim ink at rest, wash + full ink on hover/open, a
   hairline focus ring. flex:none so it never competes with the crumbtrail's
   stretch; sits just right of the brand as the first menubar item. */
.menubtn {
  flex: none;
  font: inherit; font-size: var(--fs-ui);
  color: var(--ink-dim);
  background: none; border: 1px solid transparent; border-radius: 6px;
  padding: var(--sp1) var(--sp2); cursor: pointer;
}
.menubtn:hover, .menubtn[aria-expanded="true"] { color: var(--ink); background: var(--wash-soft); }
.menubtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* EDITOR A11Y item 1a — skip-to-transcript link. Off-screen until focused (the
   standard bypass-blocks recipe: it must stay reachable by Tab, so it is clipped
   off-screen, NOT display:none which would drop it from the tab order). On :focus
   it snaps to a visible chip pinned top-left over the header. Reuses the house
   focus-ring tokens; the accent fill makes it unmissable when a keyboard user
   tabs into the editor. */
.skiplink {
  position: absolute; left: -9999px; top: 0; z-index: var(--layer-skip-link);
  padding: var(--sp2) var(--sp3);
  background: var(--accent); color: var(--bg-deep);
  border-radius: 0 0 6px 0; font: inherit; text-decoration: none;
}
.skiplink:focus { left: 0; outline: 2px solid var(--accent); outline-offset: 2px; }
/* L3.b — the canvas tab-bar ("tabs = cuts"): the Cut SWITCHER, presented as
   Google-Sheets/Chrome-style tabs at the TOP of the transcript region (Jesse
   2026-06-29). Tabs sit on a hairline baseline; the active cut reads as a filled,
   top-rounded tab. Painted by mount from cardinality ≥1; [hidden] keeps it out of
   the flow on the home/single-canvas editor. Token-only. */
.canvastabs { display: flex; min-width: 0; }
.canvastabs[hidden] { display: none; }
/* the top band — a flex:none row above the scroller (sibling BEFORE .tscroll, so the
   paper's scroll origin is untouched). PRES-IN-TRANSCRIPT-1: the nav is now a two-column
   row — the cuts scroll-column (flexible middle) + the presence seats (right-pinned).
   The nav itself no longer scrolls; each column owns its OWN overflow (below). */
.transcript-header .canvastabs {
  flex: none;
  align-items: stretch; gap: var(--sp3);
  padding: var(--sp2) var(--sp3) 0;
  border-bottom: 1px solid var(--line);
}
/* "your cuts" — the flexible middle. Takes the free width, hangs its tabs on the
   bottom baseline, and scrolls HORIZONTALLY on its own when the cuts overrun (20 cuts
   scroll here without ever shoving the seats). min-width:0 lets it actually shrink. */
.version-control {
  position: relative;
  flex: none;
  align-self: center;
  margin-bottom: var(--sp1);
}
.crumbtrail .version-control {
  align-self: center;
  margin: 0;
}
.version-control[hidden] { display: none; }
@media (max-width: 1100px) { .brand .tag { display: none; } }
@media (max-width: 900px)  { .clock.dim { display: none; } }
@media (max-width: 700px)  { .filename { display: none; } }
@media (max-width: 700px) {
  .transcript-header .canvastabs { gap: var(--sp2); padding-inline: var(--sp2); }
}
/* phone tier (MOBILE-3a) — the last step of the progressive-hide ladder above.
   At phone width the header's fixed-size controls (mode toggle · Share · avatar)
   plus the breadcrumb plus the save-state no longer share one 380px row: the
   shrinkable .crumbtrail (flex:1) collapses below its content and its nowrap legs
   overflow, so the .syncstatus label paints OVER .crumb-proj ("Sav[T]ed"). The
   two lowest-priority LABELS give way — the routine save-state reassurance (the
   actionable .warn state STAYS, it borrows the strike rust and must stay seen)
   and the project crumb (the ‹ back button, primary nav, stays). No forked
   layout, no JS — the same display:none progressive-hide as .filename above. */
@media (max-width: 430px) {
  .syncstatus:not(.warn) { display: none; }
  .crumb-proj, .crumb-slash { display: none; }
}
.brand {
  font-family: var(--f-brand); font-weight: 600; font-size: var(--fs-brand);
  letter-spacing: 0.01em;
  /* the wordmark is an <a href="/browse"> (logo = home); neutralize the default
     anchor chrome so it reads as the brand, not a link, but still invites a click */
  color: inherit; text-decoration: none; cursor: pointer;
}
/* the editor wordmark is an <a class="brand"> injected into #route-page, so the broad
   `.route-page a { color:--accent }` (specificity 0,1,1) out-paints `.brand`'s
   `color:inherit` (0,1,0) and LEAKS interaction-teal onto the brand. Reclaim it to the
   cream --ink with a descendant selector that out-specifies that rule — the exact
   "brand is never accent" temperature split the browser .app-header-brand already
   carries (§3). Route/profile-invariant: .brand only ever lives inside #route-page. */
.route-page .brand, .route-page .brand .brand-mark { color: var(--ink); }
.brand .tag {
  font-family: var(--f-ui); font-weight: 400; font-size: var(--fs-note);
  color: var(--ink-dim); margin-left: var(--sp3);
  text-transform: uppercase; letter-spacing: 0.14em;
}
/* EDITOR-CRUMB — the header's enclosing-context trail: ‹ Project / filename.
   Frame-style STRATEGIC truncation: both text crumbs ellipsize, but the PROJECT
   gives way 8× faster (flex-shrink 8 vs 1) so the leaf stays readable down to
   narrow widths — the middle collapses first, exactly Frame's `3BPS2 / … / file`
   behavior with one middle segment. The nav owns .filename's old flex:1 stretch;
   on the local-file pair the back/project/slash legs are [hidden] and this is
   byte-identical to the old filename-only header. */
.crumbtrail {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: var(--sp2);
  font-size: var(--fs-ui);
}
.crumb-back {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 22px; block-size: 22px;
  border-radius: 5px;
  color: var(--ink-dim); text-decoration: none;
  font-size: var(--fs-clock); line-height: 1;
  cursor: pointer;
}
.crumb-back:hover { color: var(--accent); background: var(--wash); }
.crumb-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.crumb-proj {
  flex: 0 8 auto; min-width: 4ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink-dim); text-decoration: none;
}
.crumb-proj:hover { color: var(--accent); text-decoration: underline; }
.crumb-proj:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.crumb-slash { flex: none; color: var(--ink-faint); user-select: none; }
.filename {
  flex: 0 1 auto; min-width: 0; color: var(--ink-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* save-state indicator (main.ts syncStatusLabel) — flex:none so it never
   competes with .filename's ellipsis for the row's stretch space; .warn
   (offline/error) borrows the same rust the strike-through uses (--stricken)
   so a stalled sync reads as the SAME "something needs you" color, not a new one. */
.syncstatus {
  flex: none; font-size: var(--fs-ui); white-space: nowrap;
}
.syncstatus.warn { color: var(--stricken); }
.guidehandle {
  /* Help belongs to the app corner, below the floating transport tray. */
  position: fixed;
  left: max(var(--sp3), env(safe-area-inset-left));
  bottom: max(8px, env(safe-area-inset-bottom));
  z-index: 8;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 50%;
  color: var(--ink-faint);
  font-family: var(--f-doc);
  font-size: 15px;
  background: color-mix(in oklab, var(--bg-deep) 82%, transparent);
}
.guidehandle:hover,
.guidehandle:focus-visible { color: var(--ink); border-color: var(--line); background: var(--wash); }
/* the CUTS/TRT readout follows your attention (Jesse, CHAPTERS note 4):
   WATCHING ⇒ it rides the picture (one quiet line above the video, ≈20px);
   EDITING ⇒ workspace.ts reparents it to band the transcript's top. The single
   node moves; these base rules ride it to either home. */
.clocks { display: flex; gap: var(--sp4); align-items: baseline; padding: 0 2px var(--sp2); }
.clocks .big { font-size: inherit; }  /* TRT sits level with cut/source (Jesse) — accent alone carries the emphasis */
.clocks .stricken { color: var(--stricken); }
/* the readout BAND above the video (edit mode) — over the picture it measures,
   not over the transcript (Jesse 2026-06-29: the Cut switcher took the transcript's
   top band). A direct flex:none child of .monitor, so it's the row above the <video>;
   in watch mode the readout lives in .transport instead, so this never matches there. */
.monitor > .clocks {
  flex: none;
  padding: 0 0 var(--sp2);
}
/* the transport-control row (watch mode) — the readout SHARES the strip with
   play/mute/rate (Jesse, note-4 follow-up). margin-auto anchors it past the
   rates at the row's right edge; padding zeroed so it sits balanced on the
   centered row rather than carrying the above-video band's bottom gap. */
main[data-mode="watch"] .transport > .clocks {
  margin-left: auto;
  padding: 0;
}

.clock label {
  text-transform: uppercase; letter-spacing: 0.14em; font-size: var(--fs-label);
  color: var(--ink-faint); margin-right: var(--sp2);
}
.clock .big { color: var(--accent); }
/* ------------------------------------------------ main split
   Layout is SOLVED by js/workspace.js (template + sizes from viewport and
   video aspect ratio); these are the finite templates it chooses between. */
main {
  display: grid;
  min-height: 0;
  position: relative;   /* anchors the P8 seam dividers */
  box-sizing: border-box;
  --vidW: 390px; --vidH: 280px; --toolsW: 360px;
  /* The floating transport gets a real layout seat. Its fixed positioning keeps
     the Eden silhouette, while this reserve keeps video, transcript, and dock
     content physically above it. Timeline-open raises the same variable. */
  padding-bottom: var(--editor-strip-reserve, 96px);
  --eden-bar-clearance: 0px;
  --monitor-toolbarH: 42px;
}
body.layout-snap main { transition: none !important; }
.tscroll { padding-bottom: var(--sp6); overflow-x: hidden; }
main[data-layout="side"] {
  grid-template-columns: minmax(0, 1fr) var(--vidW);
  grid-template-rows: calc(var(--vidH) + var(--monitor-toolbarH)) minmax(0, 1fr);
  grid-template-areas: "transcript monitor" "transcript rail";
}
main[data-layout="side"][data-rail-open="false"] {
  grid-template-rows: minmax(0, 1fr) 0px;
}
main[data-rail-open="false"] > .rail {
  overflow: hidden;
  min-width: 0;
  min-height: 0;
}
main[data-layout="top"] {
  grid-template-columns: minmax(0, 1fr) var(--toolsW);
  grid-template-rows: calc(var(--vidH) + var(--monitor-toolbarH)) minmax(0, 1fr);
  grid-template-areas: "monitor rail" "transcript rail";
}
main[data-layout="tall"] {
  /* monitor hugs the transcript — the eye line is read ↔ glance; tools are reference */
  grid-template-columns: minmax(0, 1fr) var(--vidW) var(--toolsW);
  grid-template-rows: minmax(0, 1fr);
  grid-template-areas: "transcript monitor rail";
}
/* A user-hidden rail is not a transparent third column. The same tall
   transcript/viewer composition simply releases that track. */
main[data-layout="tall"][data-tools="sheet"] {
  grid-template-columns: minmax(0, 1fr) var(--vidW);
  grid-template-areas: "transcript monitor";
}
/* DEMOTED: tools summoned as a sheet — single column, monitor banners the text */
main[data-layout="top"][data-tools="sheet"] {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: calc(var(--vidH) + var(--monitor-toolbarH)) minmax(0, 1fr);
  grid-template-areas: "monitor" "transcript";
}
/* WATCH mode: video-primary, the dock stays DOCKED (notes tab by default —
   tools are one tab away); only the transcript is summoned */
main[data-layout="watch-side"] {
  grid-template-columns: minmax(0, 1fr) var(--comW);
  grid-template-rows: minmax(0, 1fr);
  grid-template-areas: "monitor rail";
}
main[data-layout="watch-stack"] {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: calc(var(--vidH) + var(--monitor-toolbarH)) minmax(0, 1fr);
  grid-template-areas: "monitor" "rail";
}
/* View's sidebar disclosure lives in the viewer toolbar when the rail is closed.
   Release the named rail track as well as the rail element itself so that toolbar
   and picture become one continuous full-width row — the standard video-review
   posture, not a two-column grid with an invisible second column. The compact
   stack has the same requirement vertically: no empty Notes row below the media. */
main[data-layout="watch-side"][data-tools="sheet"] {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "monitor";
}
main[data-layout="watch-stack"][data-tools="sheet"] {
  grid-template-rows: minmax(0, 1fr);
  grid-template-areas: "monitor";
}
.transcript { grid-area: transcript; }
.monitor { grid-area: monitor; }
.rail { grid-area: rail; }
/* Like the summoned Watch transcript below, a fixed/summoned rail must give up
   its named grid area. Otherwise Grid synthesizes an implicit `rail` track even
   though the drawer is off-canvas, so closing it leaves a blank column behind. */
main[data-tools="sheet"] > .rail { grid-area: auto; }
/* In Watch/View the transcript is a fixed, summoned shelf. Leaving its named
   `grid-area: transcript` on that fixed child still makes Grid synthesize implicit
   transcript tracks outside the watch template; those tracks steal the available
   1fr row and collapse both the viewer and Notes rail to zero height. Remove only
   its grid placement here — the shelf positioning/handle remains unchanged. */
main[data-mode="watch"] > .transcript { grid-area: auto; }

/* P8 dividers — drag a seam to PIN its size over the solver (resize.md §6).
   Invisible at rest, a hairline in --line on hover/drag — the seam is the
   affordance, no grip furniture. 8px hit area straddling the seam; each
   divider positions on the SAME var its grid template reads, so it tracks
   the seam with zero JS. Present only when its template is active and the
   rail is docked — a demoted layout reads no pins (resize.md decision #6). */
.divider { display: none; position: absolute; z-index: 9; touch-action: none; user-select: none; }
.divider[data-axis="x"] { top: 0; bottom: 0; width: 8px; cursor: col-resize; }
.divider[data-axis="y"] { left: 0; right: 0; height: 8px; cursor: row-resize; }
.divider::after { content: ""; position: absolute; background: transparent; transition: background 120ms ease-out; }
.divider[data-axis="x"]::after { top: 0; bottom: 0; left: 50%; width: 1px; }
.divider[data-axis="y"]::after { left: 0; right: 0; top: 50%; height: 1px; }
.divider:hover::after, .divider.dragging::after { background: var(--line); transition: none; }
body[data-tier-rail="docked"] main[data-layout="side"] .divider[data-pin="side.vidW"]   { display: block; right: calc(var(--vidW) - 4px); }
body[data-tier-rail="docked"] main[data-layout="side"] .divider[data-pin="side.vidH"]   {
  display: block;
  top: calc(var(--vidH) - 4px);
  right: 0;
  /* Start beyond the vertical seam's hit box: the two independent controls
     meet visually, but never compete for the same pointer target. */
  left: calc(100% - var(--vidW) + 8px);
}
body[data-tier-rail="docked"] main[data-layout="top"]  .divider[data-pin="top.vidH"]    { display: block; top: calc(var(--vidH) - 4px); right: var(--toolsW); }
body[data-tier-rail="docked"] main[data-layout="top"]  .divider[data-pin="top.toolsW"]  { display: block; right: calc(var(--toolsW) - 4px); }
/* The transcript/picture seam belongs to those two regions, not the sidebar.
   Keep it live when the sidebar releases its track (`--toolsW:0px`). */
main[data-layout="tall"] .divider[data-pin="tall.vidW"]   { display: block; right: calc(var(--toolsW) + var(--vidW) - 4px); }
body[data-tier-rail="docked"] main[data-layout="tall"] .divider[data-pin="tall.toolsW"] { display: block; right: calc(var(--toolsW) - 4px); }
body[data-tier-rail="docked"] main[data-layout="watch-side"] .divider[data-pin="watch.comW"] { display: block; right: calc(var(--comW) - 4px); }

/* no display:none for whole regions — a non-docked region is SUMMONED
   (lives in the shelf with a visible handle), never gone */
main[data-mode="watch"] .monitor video { flex: 1; min-height: 0; object-fit: contain; }
/* ------------------------------------------------ transcript
   The REGION is a column: .tscroll scrolls the paper; docked rows (the verb
   bar) and edge chrome (the follow pill) belong to the region, not the page. */
.transcript {
  position: relative;
  display: flex; flex-direction: column;
  overflow: hidden; min-height: 0;
}
/* TRANSCRIPT-ZONE-1 (Jesse) — the region is three stacked bands. The HEADER (cut
   tabs) and FOOTER (verb bar) are flex:none rails; the BODY is the flex:1 middle and
   the SOLE position:relative context the edge lanes anchor to. Because the lanes live
   in .transcript-body, their `top:0; bottom:0` spans only this band — they can never
   paint up over the header's tabs (the swallow bug) by construction. The history
   drawer stays a direct .transcript child (its own overlay, above all three bands). */
.transcript-header { flex: none; }
.transcript-body {
  --transcript-masthead-height: 40px;
  position: relative;
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
  overflow: hidden;
}
.transcript-footer { flex: none; }
.tscroll {
  flex: 1 1 auto; min-height: 0;
  /* The transcript is a vertically scrolling document, never a two-axis canvas.
     Floating gutter chrome must overlay rather than enlarge its scrollable width. */
  overflow-y: auto; overflow-x: hidden;
  scrollbar-color: var(--line) transparent;
  /* Render publishes the analytic start/end chrome for this frame so CSS and
     transcript line-breaking remove the same space from the paper. */
  padding-inline: var(--transcript-pad-start, 1.25rem) var(--transcript-pad-end, 1.25rem);
}
/* Contextual comments now consume vertical paragraph bands, never a horizontal
   lane, so they never alter these inline paper insets. */
/* CHAPTERS INDEX lane (RFC slice 1) — the LEFT mirror of the comment margin. Docked to
   the .transcript region's LEFT edge, width --chap-lane-w (published by render() from the
   JS CHAP_LANE_W seat). Shown only when data-chap-open; the .tscroll gains left padding so
   the centered paper shifts RIGHT into the reserved slack (RESERVE, never overlay). */
.chapters-index {
  display: flex; flex-direction: column;
  position: absolute; left: 0; top: 0; bottom: 0; z-index: 6;
  width: 48px; box-sizing: border-box; overflow: hidden;
  pointer-events: none;
  transition: width 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.chapters-index-head { pointer-events: auto; }
.chapters-disclosure-label { display: none; }
.transcript[data-chap-open] .chapters-disclosure-label { display: inline; }
.transcript:not([data-chap-open]) .chapters-index-list,
.transcript:not([data-chap-open]) .chapters-index-resizer {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
.chapters-index-list,
.chapters-index-resizer {
  transition: opacity 180ms ease-out, visibility 0s linear 180ms;
}
.transcript[data-chap-open] .chapters-index-list,
.transcript[data-chap-open] .chapters-index-resizer {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 180ms ease-out, visibility 0s;
}
.transcript[data-chap-open] .chapters-index {
  gap: var(--sp2); z-index: 2; pointer-events: auto;
  width: var(--chap-lane-w, 320px);
  /* The outline head occupies the masthead's second tier. Its controls stay
     aligned with the closed-state chapter controls instead of jumping to a
     different vertical system when the lane opens. */
  padding: 40px var(--sp4) var(--sp4) 1.25rem;
  box-sizing: border-box;
  overflow: hidden;
  background: color-mix(in oklab, var(--bg) 50%, var(--bg-deep));
  scrollbar-color: var(--line) transparent;
}
.transcript[data-chap-open] .tscroll { padding-left: var(--transcript-pad-start, calc(1.25rem + var(--chap-lane-w, 320px))); }

/* CHAPTERS-ALWAYS — terminal responsive presentation. When an expanded outline cannot
   clear its readable dock floor, view.ts publishes tier=overlay and reserve=0. The SAME
   .chapters-index aside paints over the left edge; the scroller keeps its natural padding,
   so no duplicate list/state/handlers exist and the reading measure is never squeezed.
   The width keeps a 48px escape edge even in a very narrow transcript region. */
.transcript[data-tier-chap='overlay'][data-chap-open] {
  --chap-overlay-w: min(var(--chap-panel-w, 320px), calc(100% - 48px));
}
.transcript[data-tier-chap='overlay'][data-chap-open] .chapters-index {
  width: var(--chap-overlay-w);
  z-index: 6;
  background: color-mix(in oklab, var(--bg) 92%, var(--surface));
  border-right: 1px solid var(--line);
  box-shadow: 16px 0 28px rgb(0 0 0 / .2);
}
.transcript[data-tier-chap='overlay'][data-chap-open] .tscroll {
  padding-left: var(--transcript-pad-start, 1.25rem);
}
.chapters-index-list {
  display: flex; flex: 1 1 auto; min-height: 0;
  flex-direction: column; overflow-y: auto;
  scrollbar-color: var(--line) transparent;
}
.chapters-index-head {
  position: relative; z-index: 2;
  display: flex; align-items: center; gap: var(--sp2); min-height: 32px; flex: none;
}
.chapters-index-resizer {
  position: absolute; z-index: 8; top: 0; right: -4px; bottom: 0;
  width: 8px; touch-action: none; cursor: col-resize;
}
.chapters-index-resizer::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 4px; width: 1px;
  background: transparent; transition: background-color 120ms ease-out;
}
.chapters-index-resizer:hover::after,
.chapters-index-resizer:focus-visible::after,
.chapters-index-resizer.dragging::after { background: var(--accent); }
.chapters-index-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 24px;
  align-items: start; gap: var(--sp2);
  padding: var(--sp2) var(--sp1);
  border-bottom: 1px solid color-mix(in oklab, var(--line) 45%, transparent);
  border-radius: 4px; cursor: pointer;   /* the outline is live navigation — a row click seeks */
}
.chapters-index-actions {
  display: flex; flex-direction: column; gap: var(--sp1);
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--sp2);
  margin-bottom: var(--sp2);
  container: chapter-actions / inline-size;
}
.chapters-index-create-row {
  display: flex; align-items: stretch; gap: var(--sp1);
}
.chapters-index-create {
  min-width: 0; min-height: 28px; display: inline-flex; align-items: center;
  justify-content: center; gap: 5px; flex: 1 1 0; padding: 4px 7px;
  border: 1px solid color-mix(in oklab, var(--line) 70%, transparent);
  border-radius: 5px; background: transparent; color: var(--ink-dim);
  font: 600 var(--fs-note)/1 var(--f-ui); cursor: pointer;
}
.chapters-index-auto-label { white-space: nowrap; }
/* Content breakpoint: preserve the verb instead of allowing Auto-chapter to
   become a tall two-line button in a user-narrowed/overlay chapter lane. The
   full accessible name and tooltip remain on the button. */
@container chapter-actions (max-width: 236px) {
  .chapters-index-auto-suffix { display: none; }
}
.chapters-index-create:hover,
.chapters-index-create:focus-visible {
  border-color: color-mix(in oklab, var(--accent) 38%, var(--line));
  background: color-mix(in oklab, var(--accent) 7%, transparent);
  color: var(--accent);
}
.chapters-index-create[aria-busy] {
  border-color: color-mix(in oklab, var(--working) 38%, var(--line));
  color: var(--working);
  cursor: wait;
}
.chapters-index-suggest-status {
  display: flex; align-items: flex-start; gap: 6px;
  padding: 2px var(--sp1) 0; color: var(--ink-dim);
  font: 500 var(--fs-note)/1.35 var(--f-ui);
}
.chapters-index-suggest-status::before {
  content: ''; flex: 0 0 auto; width: 6px; height: 6px; margin-top: 4px;
  border-radius: 50%; background: var(--working);
}
.chapters-index-suggest-status[data-busy]::before {
  animation: chapter-suggest-pulse 1.4s ease-in-out infinite;
}
.chapters-index-suggest-status[data-tone="error"] { color: var(--rust); }
.chapters-index-suggest-status[data-tone="error"]::before {
  background: var(--rust); animation: none;
}
@keyframes chapter-suggest-pulse {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 1; }
}
:root[data-motion="reduced"] .chapters-index-suggest-status::before { animation: none; }
.chapters-index-fold {
  align-self: flex-start; min-height: 24px; display: inline-flex; align-items: center;
  gap: 5px; padding: 2px 4px; border: 0; border-radius: 4px;
  background: transparent; color: var(--ink-faint);
  font: 500 var(--fs-note)/1 var(--f-ui); cursor: pointer;
}
.chapters-index-fold .icn { width: 11px; height: 11px; }
.chapters-index-fold:hover,
.chapters-index-fold:focus-visible { background: var(--wash); color: var(--ink-dim); }
.chapters-index-fold .chapters-index-title {
  display: inline; color: inherit; font: inherit; white-space: nowrap;
}
.chapters-index-row:hover { background: color-mix(in oklab, var(--ink) 6%, transparent); }
/* Current chapter is a typography state, not a selected row. The title carries
   the emphasis in the Eden layer; the row surface stays continuous. */
.chapters-index-row[data-current-chapter] { background: transparent; }
/* Slice 3 — drag-reorder the outline. The dragged row dims (it's travelling); the
   whole lane wears a grabbing cursor for the gesture. The insertion bar is an
   --accent hairline drawn as an inset shadow (zero layout shift, so the list never
   reflows mid-drag): `.drop-above` lights the TOP edge of the target row (insert
   before it), `.drop-tail` the BOTTOM edge of the list (append past the last). */
.transcript.chap-dnd .chapters-index-row { cursor: grabbing; }
.chapters-index-row.dragging { opacity: 0.3; }   /* DRAG-GHOST-1: match the block drag ghost (one drag-dim rule) */
.chapters-index-row.drop-above { box-shadow: inset 0 2px 0 0 var(--accent); }
.chapters-index-list.drop-tail { box-shadow: inset 0 -2px 0 0 var(--accent); }
.chapters-index-title {
  min-width: 0; display: -webkit-box; overflow: hidden;
  -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  font-family: var(--f-ui); font-size: var(--fs-base); line-height: 1.28; color: var(--ink);
  overflow-wrap: anywhere;
}
/* CIX-A — the synthetic leading row (the orphan lead-in before the first real
   chapter): a quiet placeholder, never mistaken for a named chapter. Not draggable
   (see the JS row markup) — dropping ONTO it, not dragging it, is how it re-sorts. */
.chapters-index-title--placeholder { color: var(--ink-faint); font-style: italic; }
/* Slice 2b — the inline rename editor: an input that stands in for the title span
   (dblclick a row). Matches the title's UI type + fills the row's flex measure so the
   editor sits exactly where the name read. */
.chapters-index-input {
  font-family: var(--f-ui); font-size: var(--fs-base); color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line); border-radius: 3px; padding: 0 4px;
  min-width: 0; flex: 1;
}
.chapters-index-more {
  width: 24px; height: 24px; display: grid; place-items: center; align-self: start;
  padding: 0; border: 1px solid transparent; border-radius: 50%;
  background: transparent; color: var(--ink-faint); cursor: pointer;
  font: 700 17px/1 var(--f-ui); opacity: .58;
  transition: color 120ms ease-out, border-color 120ms ease-out,
    background-color 120ms ease-out, opacity 120ms ease-out;
}
.chapters-index-row:hover .chapters-index-more,
.chapters-index-more:focus-visible,
.chapters-index-more[aria-expanded="true"] { opacity: 1; }
.chapters-index-more:hover,
.chapters-index-more:focus-visible,
.chapters-index-more[aria-expanded="true"] {
  border-color: var(--line); background: var(--wash); color: var(--ink);
}
.chapters-index-empty {
  font-family: var(--f-ui); font-size: var(--fs-note); line-height: 1.5;
  color: var(--ink-dim); margin: 0;
}
.chapters-index-empty b { color: var(--ink); font-weight: 600; }

/* Legacy tick markup remains hidden in the full chapter rows for compatibility with the
   shared renderer. Compact navigation now belongs to the width-neutral outline map. */
.chapters-index-tick { display: none; }
.transcript[data-chap-minimized] .chapters-index {
  align-items: center;
  padding-left: var(--sp2); padding-right: var(--sp2);
}
.transcript[data-chap-minimized] .chapters-index-empty,
.transcript[data-chap-minimized] .chapters-index-title,
.transcript[data-chap-minimized] .chapters-index-more { display: none; }
.transcript[data-chap-minimized] .chapters-index-row {
  justify-content: center; padding: var(--sp1) 0; border-bottom: none;
}
.transcript[data-chap-minimized] .chapters-index-tick {
  display: grid; place-items: center;
  width: 24px; height: 24px; border-radius: 50%;
  font-family: var(--f-tc); font-variant-numeric: tabular-nums;
  font-size: var(--fs-note); color: var(--ink-dim);
  background: var(--surface); border: 1px solid var(--line);
}
.transcript[data-chap-minimized] .chapters-index-row--new .chapters-index-tick,
.transcript[data-chap-minimized] .chapters-index-row--fold .chapters-index-tick {
  border-radius: 6px;
  font-size: 16px;
  background: color-mix(in oklab, var(--accent) 15%, transparent);
  color: var(--accent);
  border: 1px solid color-mix(in oklab, var(--accent) 30%, transparent);
}
/* the current-chapter tick wears the presence wash (the row's dedicated current attribute already carries it; lift it
   onto the disc so the wash reads in the tiny rail too) */
.transcript[data-chap-minimized] .chapters-index-row[data-current-chapter] .chapters-index-tick {
  background: var(--presence-wash); color: var(--ink);
}

/* OUTLINE-MAP-1 — the minimized chapter lane becomes a width-neutral transcript
   minimap at the reading edge. Ordinary paragraphs are quiet ticks; authored chapter
   starts are heavier landmarks. The full chapter outline remains the editing surface. */
.transcript-outline {
  display: none;
  /* Share the sidebar's usable band, below its disclosure and above transport.
     The closed chapter rail is opaque at z:6; the map must sit above it. */
  position: absolute; top: 48px; left: 8px; z-index: 7;
  bottom: var(--editor-strip-reserve, 0px);
  width: 30px;
  pointer-events: none;
}
.transcript-outline[data-active] { display: block; }
/* The minimap owns a permanent, modest gutter while it is present. At the
   transcript's narrow floor its marks must never compete with document copy. */
.transcript-body[data-outline-active] .tscroll { padding-left: 3.5rem; }
.transcript-outline-track {
  position: relative; top: 15%; width: 100%; height: 70%; cursor: ns-resize;
  pointer-events: auto;
  touch-action: none;
}
.transcript-outline-track:focus-visible::after {
  content: ''; position: absolute; inset: -4px 0; border: 1px solid var(--line);
  border-radius: 9px; pointer-events: none;
}
.transcript-outline-marks { position: absolute; inset: 0; pointer-events: none; }
.transcript-outline-mark {
  --outline-grow: 1;
  --outline-presence: 0;
  position: absolute; left: 8px; top: var(--outline-y); width: 8px; height: 2px;
  border-radius: 2px; background: var(--ink-faint); opacity: calc(.46 + var(--outline-presence) * .3);
  transform: translateY(-50%) scaleX(max(var(--outline-grow), calc(1 + var(--outline-presence) * .3))); transform-origin: left center;
  transition: transform 160ms cubic-bezier(.25,1,.5,1), opacity 120ms ease-out, background-color 120ms ease-out;
}
.transcript-outline-mark--chapter {
  height: 2px; opacity: calc(.75 + var(--outline-presence) * .15);
  background: color-mix(in oklab, var(--accent) 68%, var(--ink-dim));
}
.transcript-outline-mark--chapter::before {
  content: ""; position: absolute; left: -2px; top: -1px;
  width: 4px; height: 4px; border-radius: 50%; background: inherit;
}
.transcript-outline-mark[data-near] { opacity: 1; background: var(--ink); }
.transcript-outline-mark--chapter[data-near] { background: var(--accent); }
.transcript-outline-preview {
  position: absolute; left: 38px; top: var(--outline-preview-y);
  width: min(290px, calc(100vw - 96px)); padding: 11px 13px;
  transform: translateY(-50%); box-sizing: border-box;
  border: 1px solid var(--line); border-radius: 10px;
  background: color-mix(in oklab, var(--surface) 94%, var(--bg));
  color: var(--ink); pointer-events: none;
}
.transcript-outline-preview b,
.transcript-outline-preview span { display: block; font-family: var(--f-ui); }
.transcript-outline-preview b {
  margin-bottom: 5px; font-size: var(--fs-note); font-weight: 650;
  letter-spacing: .04em; color: var(--ink);
}
.transcript-outline-preview span {
  display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  font-size: var(--fs-note); line-height: 1.45; color: var(--ink-dim);
}

/* Numbered bubbles were a second compact chapter UI. The map now carries navigation;
   retain only the persistent chapter toggle in the minimized left rail. */
.transcript[data-chap-minimized] .chapters-index-list { display: none; }

:root[data-motion="reduced"] .transcript-outline-mark { transition: none; }

/* INDEX-IA-1 slice 2 — the chapters-lane minimize/expand toggle, the LEFT mirror of
   the comment lane's affordance. Anchors to the top center of the .chapters-index lane. */
.chap-lane-toggle {
  display: flex; align-items: center; justify-content: center; gap: 4px;
  position: static; z-index: 7;
  width: auto; min-width: 22px; height: 22px; padding: 0 8px; border-radius: 11px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-dim);
  cursor: pointer; transition: color 140ms ease, border-color 140ms ease;
}
.chap-lane-toggle:hover { color: var(--ink); border-color: var(--ink-deco); }
/* PANEL-DISCLOSURE-1 — identity markup for text-bearing pane doors. Chapters
   uses the dedicated integrated sidebar glyph in the masthead instead. */
.pane-disclosure { display: inline-flex; align-items: center; gap: 6px; }
.pane-disclosure .icn { width: 16px; height: 16px; }
.pane-disclosure-label { font: inherit; letter-spacing: inherit; text-transform: inherit; }

/* SUMMONED lane identity. Only an explicitly minimized lane reaches this tier when no slack
   remains for its rail. Expanding chapters always resolves to a real dock or full overlay. */
.transcript[data-tier-comment='summoned'] .comment-lane-toggle--top {
  display: flex; align-items: center; justify-content: center; gap: 4px;
  position: absolute; top: var(--sp4); z-index: 7; transform: none;
  min-width: 22px; height: 22px; padding: 0 8px; border-radius: 11px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-dim);
  cursor: pointer; transition: color 140ms ease, border-color 140ms ease;
}
.transcript[data-tier-comment='summoned'] .comment-lane-toggle--top { right: var(--sp2); }
/* FEEL-0710-3(c) LANE-HOVER-INFO — the contextual comment hover PEEK. Folded chapter
   comment pills can list the discussion they hide without unfolding. Hidden by default,
   revealed only on a hover-capable pointer, so the
   touch path (where :hover sticks after a tap) never traps it open — a tap just toggles
   the lane. pointer-events:none keeps it a true peek: it can't be hovered to stay alive
   nor eat a click, so leaving the toggle box dismisses it. */
.lane-peek {
  display: none; position: absolute; top: calc(100% + 6px); z-index: 20;
  min-width: 160px; max-width: 240px; padding: var(--sp2) var(--sp3);
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink-dim);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
  font-family: var(--f-ui); font-size: var(--fs-chip); line-height: 1.5;
  text-align: left; pointer-events: none;
}
.comment-lane-toggle .lane-peek { right: 0; }    /* right lane → open down-left */
.lane-peek-row { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lane-peek-row + .lane-peek-row { margin-top: 2px; }
/* the hover/focus REVEAL of .lane-peek lives in the single hover-gated block below
   (design-audit consolidation pin) — collapsed-only, hover-capable pointers only. */

/* ── VERSION-HISTORY drawer (VH-1 Stage A) ─────────────────────────────────────
   A right-docked OVERLAY (z-index above the paper, absolutely positioned to the
   .transcript region), NOT the reserve-lane mechanism the chapters/comment lanes
   use — so opening it never reflows the live editor. Two panes: the version LIST
   and the read-only PREVIEW of the doc as-of the selected point. */
.history-panel {
  /* z-index above the region chrome it overlays — the sticky viewbar (8) and the
     comment-lane divider (9) — but below the app-global dialogs/toasts. */
  position: absolute; top: 0; right: 0; bottom: 0; z-index: 15;
  display: flex; flex-direction: column;
  width: min(720px, 82%);
  background: var(--bg);
  border-left: 1px solid var(--line);
  box-shadow: -12px 0 32px -18px rgba(0, 0, 0, 0.45);
}
.history-panel[hidden] { display: none; }
.history-head {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp3); padding: var(--sp3) var(--sp4);
  border-bottom: 1px solid var(--line);
}
.history-title {
  font-family: var(--f-ui); font-size: var(--fs-chip);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-faint);
}
.history-close {
  font-family: var(--f-ui); font-size: var(--fs-base); line-height: 1;
  color: var(--ink-dim); background: none; border: none; cursor: pointer;
  padding: 4px 6px; border-radius: 4px;
}
.history-close:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 6%, transparent); }
.history-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.history-body { flex: 1; display: flex; min-height: 0; }
/* The left pane: a column stacking the status note above the scrolling listbox.
   The pane owns the fixed width + divider; the listbox scrolls within it. */
.history-listpane {
  flex: none; width: 240px; min-height: 0;
  display: flex; flex-direction: column;
  border-right: 1px solid var(--line);
}
.history-note:not(:empty) {
  padding: var(--sp3) var(--sp2); border-bottom: 1px solid color-mix(in oklab, var(--line) 40%, transparent);
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp2);
}
.history-retry {
  font-family: var(--f-ui); font-size: var(--fs-note); color: var(--accent);
  background: none; border: 1px solid var(--accent); border-radius: 5px;
  padding: 2px var(--sp2); cursor: pointer;
}
.history-retry:hover { background: color-mix(in oklab, var(--accent) 10%, transparent); }
.history-retry:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.history-list {
  flex: 1; min-height: 0; overflow-y: auto; padding: var(--sp2);
  scrollbar-color: var(--line) transparent;
}
.history-row {
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--sp2) var(--sp2); border-radius: 5px; cursor: pointer;
  border-bottom: 1px solid color-mix(in oklab, var(--line) 40%, transparent);
}
.history-row:hover { background: color-mix(in oklab, var(--ink) 6%, transparent); }
.history-row.cur { background: var(--presence-wash); }
.history-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.history-row-when {
  font-family: var(--f-ui); font-size: var(--fs-base); color: var(--ink);
}
.history-row-sub {
  font-family: var(--f-ui); font-size: var(--fs-note); color: var(--ink-dim);
}
.history-row-when .history-row-live {
  font-size: var(--fs-chip); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--accent); margin-left: var(--sp2);
}
.history-preview {
  flex: 1; min-width: 0; overflow-y: auto; padding: var(--sp4);
  scrollbar-color: var(--line) transparent;
}
.history-hint { font-family: var(--f-ui); font-size: var(--fs-note); color: var(--ink-dim); margin: 0; }
.history-preview-head {
  font-family: var(--f-ui); font-size: var(--fs-note); color: var(--ink-faint);
  margin: 0 0 var(--sp3) 0; padding-bottom: var(--sp2); border-bottom: 1px solid var(--line);
}
.history-preview-para {
  font-family: var(--f-body, var(--f-ui)); font-size: var(--fs-base); line-height: 1.6;
  color: var(--ink); margin: 0 0 var(--sp3) 0; white-space: pre-wrap;
}
.history-preview-para .history-preview-spk { color: var(--ink-dim); font-weight: 600; }
/* VH-1 Stage B — the restore affordance, sat below the read-only preview. */
.history-restore { margin-top: var(--sp4); padding-top: var(--sp3); border-top: 1px solid var(--line); }
.history-restore-btn {
  font-family: var(--f-ui); font-size: var(--fs-base); color: var(--accent);
  background: none; border: 1px solid var(--accent); border-radius: 5px;
  padding: var(--sp2) var(--sp3); cursor: pointer;
}
.history-restore-btn:hover { background: color-mix(in oklab, var(--accent) 10%, transparent); }
.history-restore-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.history-restore-q {
  font-family: var(--f-ui); font-size: var(--fs-note); color: var(--ink-dim);
  line-height: 1.5; margin: 0 0 var(--sp3) 0;
}
.history-restore-actions { display: flex; gap: var(--sp2); }
.history-restore-go {
  font-family: var(--f-ui); font-size: var(--fs-base); color: var(--bg);
  background: var(--accent); border: 1px solid var(--accent); border-radius: 5px;
  padding: var(--sp2) var(--sp3); cursor: pointer;
}
.history-restore-go:hover { background: color-mix(in oklab, var(--accent) 88%, var(--ink)); }
.history-restore-cancel {
  font-family: var(--f-ui); font-size: var(--fs-base); color: var(--ink-dim);
  background: none; border: 1px solid var(--line); border-radius: 5px;
  padding: var(--sp2) var(--sp3); cursor: pointer;
}
.history-restore-cancel:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 6%, transparent); }
.history-restore-go:focus-visible,
.history-restore-cancel:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* EDIT-LINK-1c: the per-burst revert bar. Mirrors the restore bar's ceremony but wears
   --rust (the palette's negative/reversal role) so undoing a burst reads distinct from
   restoring a version. Sits below restore on an earlier point; stands alone on the head. */
.history-revert { margin-top: var(--sp3); }
.history-revert-btn {
  font-family: var(--f-ui); font-size: var(--fs-base); color: var(--rust);
  background: none; border: 1px solid var(--rust); border-radius: 5px;
  padding: var(--sp2) var(--sp3); cursor: pointer;
}
.history-revert-btn:hover { background: var(--rust-wash); }
.history-revert-btn:focus-visible { outline: 2px solid var(--rust); outline-offset: 1px; }
.history-revert-q {
  font-family: var(--f-ui); font-size: var(--fs-note); color: var(--ink-dim);
  line-height: 1.5; margin: 0 0 var(--sp3) 0;
}
.history-revert-actions { display: flex; gap: var(--sp2); }
.history-revert-go {
  font-family: var(--f-ui); font-size: var(--fs-base); color: var(--bg);
  background: var(--rust); border: 1px solid var(--rust); border-radius: 5px;
  padding: var(--sp2) var(--sp3); cursor: pointer;
}
.history-revert-go:hover { background: color-mix(in oklab, var(--rust) 88%, var(--ink)); }
.history-revert-cancel {
  font-family: var(--f-ui); font-size: var(--fs-base); color: var(--ink-dim);
  background: none; border: 1px solid var(--line); border-radius: 5px;
  padding: var(--sp2) var(--sp3); cursor: pointer;
}
.history-revert-cancel:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 6%, transparent); }
.history-revert-go:focus-visible,
.history-revert-cancel:focus-visible { outline: 2px solid var(--rust); outline-offset: 1px; }
/* HISTORY-DRAWER-UX-1 (c): while a restore/revert is committing, the sibling confirm
   bar is disarmed — its buttons go disabled so the write can't be double-fired. */
.history-restore button:disabled,
.history-revert button:disabled { opacity: 0.45; cursor: default; }
/* GEOMETRY CONTRACT: every layout-affecting value below CONSUMES a --geo-*
   var published by js/layout.js (publishGeo) — pretext computes positions
   analytically from those numbers and the browser, reading the same vars,
   cannot disagree. Fallbacks only cover the pre-module first paint. */
.paper {
  position: relative;            /* sections are absolutely positioned */
  max-width: var(--geo-paper-max, 56ch);
  margin: 0 auto;
  font-family: var(--geo-font-family, var(--f-body));
  font-size: var(--geo-font-size, 22px);
  font-weight: var(--geo-font-weight, 400);
  user-select: none;
  -webkit-user-select: none;
}
.boot { color: var(--ink-faint); font-style: italic; padding-top: var(--sp8); text-align: center; }

/* A playable cut is the normal editor waiting on one data layer. Do not fake
   transcript rows or tool cards: a single document-owned note states the two
   useful facts while the settled frame and live player remain unchanged. */
.transcript-pending-message {
  display: grid;
  gap: var(--sp2);
  max-width: 34ch;
  padding: calc(var(--transcript-masthead-height) + var(--sp8)) var(--geo-pad-x, 32px) var(--sp8);
  text-align: start;
}
.transcript-pending-message[hidden] { display: none; }
.transcript-pending-message strong {
  color: var(--ink-dim);
  font: 600 22px/1.3 var(--geo-font-family, var(--f-body));
}
.transcript-pending-message span {
  color: var(--ink-faint);
  font: 500 13px/1.5 var(--f-ui);
}
.transcript-pending-rail-note {
  padding: var(--sp5) var(--sp4);
  color: var(--ink-faint);
  font: 500 12px/1.5 var(--f-ui);
}
.transcript-pending-rail-note[hidden] { display: none; }
body[data-transcript-pending] #dock .notes-contextbar,
body[data-transcript-pending] #dock .dock-pane > :not(.transcript-pending-rail-note) { display: none; }

/* Media-first editor open is the settled editor waiting on one data layer, not
   an alternate empty-state page. Preserve the normal frame and put the changing
   fact beside the Transcript label so nothing jumps when the word-base arrives. */
.transcript-pending-status {
  display: inline-flex;
  align-items: center;
  gap: var(--sp1);
  margin-inline-start: var(--sp2);
  color: var(--ink-faint);
  font: 600 10px/1 var(--f-ui);
  letter-spacing: .12em;
  text-transform: uppercase;
  white-space: nowrap;
}
.transcript-pending-status[hidden] { display: none; }
.transcript-pending-status--delayed {
  color: var(--ink-dim);
  letter-spacing: .04em;
  text-transform: none;
}
.transcript-pending-spinner {
  width: 10px;
  height: 10px;
  flex: none;
  box-sizing: border-box;
  border: 1px solid color-mix(in oklab, var(--accent) 28%, var(--line));
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: transcript-pending-spin 900ms linear infinite;
}
@keyframes transcript-pending-spin { to { transform: rotate(1turn); } }
:root[data-motion="reduced"] .transcript-pending-spinner { animation: none; }

/* Transcript-derived tools do not pretend to work before the document exists,
   but they retain their normal visual weight. Playback itself stays live. */
body[data-transcript-pending] .viewbar,
body[data-transcript-pending] .transcript-header {
  pointer-events: none;
}
@media (max-width: 640px) {
  body[data-transcript-pending] .transcript-masthead-title {
    flex-direction: column;
    justify-content: center;
    gap: var(--sp1);
  }
  body[data-transcript-pending] #transcript-settings-host { display: none; }
  body[data-transcript-pending] .transcript-pending-status { margin-inline-start: 0; }
  .transcript-pending-message { padding-inline: var(--sp4); }
}

/* One quiet transcript-owned row. Chapter chrome lives in the left gutter. */
.viewbar {
  position: sticky; top: 0; height: var(--transcript-masthead-height); z-index: 10;
  margin-bottom: calc(-1 * var(--transcript-masthead-height));
  display: block;
  padding: 0 var(--sp3);
  background: var(--editor-canvas, var(--bg));
  pointer-events: none;
}
.transcript-masthead-primary {
  height: var(--transcript-masthead-height); flex: none;
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center; gap: var(--sp3);
  pointer-events: auto;
}
.transcript-masthead-balance { min-width: 0; }
.transcript-masthead-actions {
  display: inline-flex; align-items: center; justify-self: end;
  min-width: 0; gap: var(--sp1);
}
.transcript-masthead-title {
  display: inline-flex; align-items: center; gap: var(--sp1);
  color: var(--ink-dim);
  font: 700 12px/1 var(--f-ui);
  text-transform: uppercase; letter-spacing: 2.2px;
}
.lens-strip-slot { min-width: 0; justify-self: end; }
.chapter-fold-all--closed {
  display: none;
  align-items: center;
  gap: 5px;
  min-height: 24px;
  padding: 2px 4px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--ink-faint);
  font: 500 var(--fs-note)/1 var(--f-ui);
  cursor: pointer;
}
.chapter-fold-all--closed:not([hidden]) { display: inline-flex; }
.chapter-fold-all--closed:hover,
.chapter-fold-all--closed:focus-visible { background: var(--wash); color: var(--ink-dim); }
.chapter-fold-all--closed .icn { width: 11px; height: 11px; }
.chapter-fold-all--closed .chapters-index-title {
  display: inline;
  color: inherit;
  font: inherit;
  white-space: nowrap;
}
.viewbtn {
  padding: 4px 10px; font-size: var(--fs-chip); border-radius: 99px;
  color: var(--ink-dim); background: var(--surface);
  text-transform: uppercase; letter-spacing: 0.08em;
}
.viewbtn:hover { color: var(--ink); }
/* icon doctrine (specs/icons.md): hand-kept SVG glyphs ride the text line.
   The defs block is reference-only — it must never take layout space. */
.icndefs { position: absolute; width: 0; height: 0; }
.icn { width: 12px; height: 12px; vertical-align: -2px; }
/* The title is inert. Its compact adjacent gear is the only settings affordance,
   barely present at rest and strengthened by intent anywhere in the region. */
.viewgear {
  display: grid; place-items: center;
  width: 24px; height: 24px; padding: 0;
  border: 0; border-radius: 4px; background: transparent;
  color: var(--ink-dim);
  pointer-events: auto;
}
.viewgear:hover,
.viewgear:focus-visible { background: var(--wash); }
.viewgear .g {
  display: grid; place-items: center; line-height: 1;
  opacity: 0.16;
  transition: opacity 120ms ease-out;
}
.transcript:hover .viewgear .g,
.transcript:focus-within .viewgear .g { opacity: 0.52; }
.viewgear:hover .g,
.viewgear:focus-visible .g,
.viewgear.mod .g { opacity: 0.78; }
.viewgear .icn { width: 13px; height: 13px; vertical-align: 0; }
/* any non-default view = amber, same "deviates from policy" voice as pins */
.viewbtn.mod { color: var(--accent); background: var(--accent-soft); opacity: 1; }

/* the transcript's recall chip — header chrome, shown only while it's away */

.viewpop {
  position: absolute; left: 50%; top: calc(100% + var(--sp1)); right: auto; bottom: auto; transform: translateX(-50%);
  width: 250px; padding: var(--sp3) var(--sp4) var(--sp4);
  background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 10px 30px oklch(0 0 0 / 0.45);
  pointer-events: auto;
  overflow-y: auto;   /* main.ts caps max-height to the region at open — past that, scroll, never clip */
}
.viewpop[hidden] { display: none; }
.viewpop h3 {
  margin: 0 0 var(--sp2); font-size: var(--fs-label); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.18em; color: var(--ink-faint);
}
.viewpop .hint { margin-top: var(--sp3); }
.view-reset {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp3);
  margin-top: var(--sp3); padding-top: var(--sp3); border-top: 1px solid var(--line);
}
.view-reset button {
  flex: none; padding: 5px 9px; border: 1px solid color-mix(in oklab, var(--rust) 55%, var(--line));
  border-radius: 5px; color: var(--rust); background: transparent;
  font-size: var(--fs-note);
}
.view-reset button:hover { background: color-mix(in oklab, var(--rust) 10%, transparent); }
.view-reset span { color: var(--ink-faint); font-size: var(--fs-chip); line-height: 1.3; }

/* STARS-1a (restyle, Jesse 2026-08-10; refined 2026-09-03): the select mark is a FINE YELLOW
   UNDERLINE, not a highlight — it never fights selection/karaoke/comment washes
   or the .del strikethrough (inset box-shadow, no layout shift, composes with
   every text-decoration). Struck starred words keep the line under the rust
   voice: stars persist on rejected content; the lens decides display. */
.transcript-token.starred {
  box-shadow: inset 0 -1px 0 var(--star-ink, #e9b64a);
}

/* Media review remains available while transcript-only controls wait. */
body[data-transcript-pending] #dock .dock-pane[data-pane="notes"] > * { display: block; }
body[data-transcript-pending] #dock .dock-pane[data-pane="notes"] { display: block; }
body[data-transcript-pending] #dock .dock-pane[data-pane="tools"] { display: none; }
body[data-transcript-pending] .stripbar { display: block; visibility: visible; }
body[data-transcript-pending] .transport-history { visibility: hidden; }
body[data-transcript-pending] .tl-toolpicker:not(.tl-zoom-controls),
body[data-transcript-pending] #timeline-zoom-selection { display: none; }

#media-retry-home { position: absolute; z-index: 3; inset: 45% 8% auto; text-align: center; background: var(--surface, #151516); }
#media-retry-home:empty { display: none; }

#transcript-issue-home:not(:empty) { position: relative; padding-top: calc(var(--transcript-masthead-height, 40px) + var(--sp5)); padding-inline-start: var(--sp5); }
/* SHARE-FACE 1b-i — the Share panel (share.ts renders into #sharepop; the design
   brief is the notebook's investigations/share-face-brief.md). The viewpop idiom
   at panel width: a --surface card anchored under the header's Share handle
   (.top becomes its positioning context — its only other absolute child is the
   skiplink, pinned to the same top-left either way). Each row is a bordered
   mini-card whose TITLE is the derived share type — the consequential fact leads;
   the /s/:token address is a quiet copyable inset with Copy docked in its edge;
   Revoke is an --ink-faint text act that goes --rust when armed. Zero new color:
   --accent on interaction only, no amber (minting is not the global create verb). */
.top { position: relative; }
/* the CARD is shared with /look-share's .share-panel harness root (fidelity by
   construction — the harness shows the same pixels); only the ANCHORING under
   the header's Share handle is .sharepop's own. */
.sharepop, .share-panel {
  width: min(400px, calc(100vw - 2 * var(--sp4)));
  padding: var(--sp3) var(--sp4) var(--sp4);
  background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 10px 30px oklch(0 0 0 / 0.45);
}
.sharepop {
  position: absolute; top: calc(100% + 6px); right: var(--sp4); z-index: var(--layer-popover);
  max-height: min(70vh, 560px); overflow-y: auto;
}
.sharepop[hidden] { display: none; }
.share-panel { margin: var(--sp4) auto; }

/* SHARE-MODAL — one centered, blocking creation surface across Browse and Editor. */
.share-dialog-backdrop {
  position: fixed; inset: 0; z-index: var(--layer-dialog-scrim);
  display: grid; place-items: center; padding: var(--sp4);
  background: var(--scrim);
  backdrop-filter: blur(2px);
  animation: share-scrim-in 140ms ease-out;
}
.sharepop.share-dialog {
  position: relative; inset: auto; z-index: auto;
  width: min(620px, calc(100vw - 2 * var(--sp4)));
  max-height: min(820px, calc(100vh - 2 * var(--sp4)));
  overflow: hidden; padding: 0;
  display: flex; flex-direction: column;
  border-radius: 12px;
  box-shadow: 0 24px 80px oklch(0 0 0 / 0.62);
  animation: share-dialog-in 180ms cubic-bezier(.2,.8,.2,1);
}
.share-dialog-head {
  display: flex; align-items: flex-start; gap: var(--sp3);
  padding: var(--sp4) var(--sp4) var(--sp3);
}
.share-dialog-heading { min-width: 0; flex: 1; }
.share-dialog-kicker {
  margin: 0 0 var(--sp1); color: var(--ink-faint); font-size: var(--fs-chip);
  font-weight: 600; letter-spacing: var(--track-label); text-transform: uppercase;
}
.share-dialog-title {
  margin: 0; color: var(--ink);
  font: 600 clamp(1rem, 2.4vw, 1.25rem)/1.3 var(--f-ui);
  letter-spacing: 0;
}
.share-dialog-close {
  flex: none; width: 36px; height: 36px; padding: 0;
  border: 0; background: transparent; color: var(--ink-dim);
  font-size: 0; transition: color 120ms ease-out, transform 120ms ease-out;
}
.share-dialog-close::before { content: '×'; font-size: 1.5rem; font-weight: 300; }
.share-dialog-close:hover { color: var(--ink); border-color: transparent; }
.share-dialog-close:active { transform: scale(.92); }
.share-dialog-body {
  min-height: calc(var(--sp8) * 3 + var(--sp6)); overflow-y: auto; padding: var(--sp4);
  scrollbar-gutter: stable;
}
.share-subject {
  display: flex; align-items: center; gap: var(--sp3); min-width: 0;
  padding-bottom: var(--sp3); border-bottom: 1px solid var(--line);
}
.share-subject-name {
  flex: 1; min-width: 0; margin: 0; color: var(--ink-dim);
  font-size: var(--fs-note); line-height: 1.4; overflow-wrap: anywhere;
}
.share-subject-detail {
  flex-basis: 100%; margin: calc(-1 * var(--sp1)) 0 0; color: var(--ink-faint);
  font-size: var(--fs-chip); line-height: 1.45;
}
.share-subject-items {
  flex-basis: 100%; display: flex; flex-wrap: wrap; gap: var(--sp1); margin: 0; padding: 0;
  list-style: none;
}
.share-subject-items li {
  max-width: 100%; overflow: hidden; padding: 3px var(--sp2); border-radius: var(--radius-pill);
  background: var(--wash); color: var(--ink-dim); font-size: var(--fs-chip);
  text-overflow: ellipsis; white-space: nowrap;
}
.share-subject:has(.share-subject-detail) { flex-wrap: wrap; }
.share-subject:has(.share-subject-items) { flex-wrap: wrap; }
.share-cut-picker {
  display: inline-flex; align-items: center; flex: none;
  color: var(--ink-faint); font-size: var(--fs-chip);
}
.share-cut-picker select {
  max-width: 150px; min-height: 32px; padding: 0 28px 0 var(--sp2);
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg-deep); color: var(--ink); font: inherit;
}
.share-intent-body { display: grid; gap: var(--sp4); }
.share-intent-group { display: grid; gap: var(--sp2); }
.share-intent-label {
  margin: 0; color: var(--ink); font-size: var(--fs-note); font-weight: 650;
}
.share-intent-choices {
  display: grid; grid-template-columns: minmax(0, 1fr);
  border-top: 1px solid var(--line);
}
.share-intent-choice {
  display: grid; grid-template-columns: 28px minmax(72px, auto) minmax(0, 1fr) auto;
  align-items: center; gap: var(--sp3); min-height: 68px;
  padding: var(--sp2) 0; border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
  background: transparent; color: var(--ink); text-align: left;
  transition: color 140ms ease-out, background-color 140ms ease-out, transform 120ms ease-out;
}
.share-intent-choice::after { content: '›'; color: var(--ink-faint); font-size: 1.15rem; font-weight: 300; transition: color 140ms ease-out, transform 140ms ease-out; }
.share-intent-choice:hover, .share-intent-choice:focus-visible {
  border-color: var(--line); background: var(--wash-soft);
}
.share-intent-icon.icn {
  width: 22px; height: 22px; margin: 0; color: var(--ink-dim);
  vertical-align: 0; transition: color 140ms ease-out;
}
.share-intent-choice:hover .share-intent-icon,
.share-intent-choice:focus-visible .share-intent-icon { color: var(--accent); }
.share-intent-choice:hover::after, .share-intent-choice:focus-visible::after { color: var(--accent); transform: translateX(2px); }
.share-intent-choice:active { transform: translateY(1px); }
.share-intent-choice strong { font-size: var(--fs-note); font-weight: 650; }
.share-intent-choice small { color: var(--ink-dim); font-size: var(--fs-chip); line-height: 1.4; }
.share-config-body { display: grid; gap: var(--sp3); }
.share-config-back {
  justify-self: start; margin: 0; padding: 0; border: 0; background: transparent;
  color: var(--accent); font-size: var(--fs-chip); font-weight: 600;
  transition: color 120ms ease-out, transform 120ms ease-out;
}
.share-config-back:hover { color: var(--ink); border-color: transparent; }
.share-config-back:active { transform: translateX(-1px); }
.share-content-access {
  min-width: 0; margin: var(--sp4) 0 0; padding: 0; border: 0;
}
.share-content-enter { animation: share-content-in 160ms ease-out both; }
.share-section-title {
  margin: 0; padding: 0; color: var(--ink); font-size: var(--fs-note); font-weight: 650;
  text-transform: none; letter-spacing: 0;
}
.share-section-hint {
  margin: var(--sp1) 0 var(--sp2); color: var(--ink-dim);
  font-size: var(--fs-chip); line-height: 1.45;
}
.share-mode-detail {
  min-height: 1.4em; margin: 0; color: var(--ink-dim);
  font-size: var(--fs-chip); line-height: 1.4;
}
.share-content-copy { display: grid; gap: 2px; }
.share-content-copy strong { color: var(--ink); font-size: var(--fs-note); font-weight: 650; }
.share-content-copy small { color: var(--ink-dim); font-size: var(--fs-chip); line-height: 1.4; }
.share-content-option {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  align-items: start; gap: var(--sp2); min-height: 44px;
  padding: var(--sp1) 0; cursor: pointer;
}
.share-content-option input { margin-top: 3px; accent-color: var(--accent); }
.share-field { display: grid; gap: var(--sp1); margin-top: var(--sp2); color: var(--ink-dim); font-size: var(--fs-chip); }
.share-field input, .share-setting-input {
  width: 100%; min-height: 42px; box-sizing: border-box; padding: var(--sp2) var(--sp3);
  background: var(--bg-deep); border: 1px solid var(--line); border-radius: 6px;
  color: var(--ink); font: inherit;
}
.share-field > small {
  color: var(--ink-faint); font-size: var(--fs-chip); line-height: 1.4;
}
.share-settings-row {
  display: flex; align-items: center; width: 100%; margin-top: var(--sp4); padding: var(--sp3);
  border: 1px solid var(--line); border-radius: 8px; background: transparent; text-align: left;
  transition: border-color 140ms ease-out, background-color 140ms ease-out, transform 120ms ease-out;
}
.share-settings-row:hover { border-color: var(--accent); background: var(--wash); }
.share-settings-row:active { transform: translateY(1px); }
.share-settings-copy { display: grid; gap: var(--sp2); min-width: 0; flex: 1; }
.share-settings-copy strong { color: var(--ink); font-size: var(--fs-note); }
.share-settings-summary { display: flex; flex-wrap: wrap; gap: var(--sp1); }
.share-settings-fact {
  padding: 2px var(--sp2); border-radius: 999px; background: var(--wash);
  color: var(--ink-dim); font-size: var(--fs-chip); font-weight: 450; line-height: 1.35;
}
.share-settings-fact.is-access { color: var(--ink); }
.share-settings-fact.is-default {
  background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent);
}
.share-settings-arrow {
  color: var(--ink-dim); font-size: 1rem; transform: rotate(-90deg);
  transition: transform 180ms cubic-bezier(.25,1,.5,1), color 140ms ease-out;
}
.share-settings-disclosure.is-open .share-settings-arrow { color: var(--ink); transform: rotate(0); }
.share-settings-panel {
  display: grid; grid-template-rows: 0fr; opacity: 0;
  transition: grid-template-rows 240ms cubic-bezier(.25,1,.5,1), opacity 140ms ease-out;
}
.share-settings-disclosure.is-open .share-settings-panel { grid-template-rows: 1fr; opacity: 1; }
.share-settings-panel-inner { min-height: 0; overflow: hidden; }
.share-settings { display: grid; gap: var(--sp3); padding-top: var(--sp3); }
.share-setting-toggle {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp3);
  min-height: 54px; margin: 0; padding: 0 var(--sp3);
  border: 1px solid var(--line); border-radius: 8px; color: var(--ink); cursor: pointer;
  transition: border-color 140ms ease-out, background-color 140ms ease-out;
}
.share-setting-toggle:hover { border-color: var(--accent); background: var(--wash); }
.share-settings .share-content-access { margin: 0; }
.share-link-settings-title { margin: var(--sp1) 0 0; }
.share-setting-toggle input { accent-color: var(--accent); }
.share-setting-reveal {
  display: grid; grid-template-rows: 0fr; margin-top: calc(-1 * var(--sp2)); opacity: 0;
  transition: grid-template-rows 180ms cubic-bezier(.25,1,.5,1), opacity 120ms ease-out;
}
.share-setting-reveal.is-open { grid-template-rows: 1fr; opacity: 1; }
.share-setting-reveal-inner { min-height: 0; overflow: hidden; }
.share-setting-input { margin-top: var(--sp2); color-scheme: dark; }
.share-default-row { display: flex; align-items: center; gap: var(--sp3); min-height: 42px; }
.share-save-default {
  min-height: 38px; padding: var(--sp2) var(--sp3); border: 1px solid var(--line);
  border-radius: 7px; background: transparent; color: var(--ink); font-weight: 600;
  transition: border-color 140ms ease-out, background-color 140ms ease-out, opacity 140ms ease-out;
}
.share-save-default:hover { border-color: var(--accent); background: var(--wash); }
.share-save-default[aria-busy="true"] { cursor: wait; opacity: .7; }
.share-default-status { color: var(--ink-dim); font-size: var(--fs-chip); }
.share-dialog-actions {
  display: flex; justify-content: flex-end; gap: var(--sp2); padding: var(--sp3) var(--sp4);
  border-top: 1px solid var(--line); background: var(--surface);
}
.share-dialog-actions[hidden] { display: none; }
.share-cancel, .share-mint, .share-done { min-height: 42px; padding: var(--sp2) var(--sp4); border-radius: 7px; }
.share-cancel { border-color: var(--line); background: transparent; color: var(--ink-dim); }
.share-mint, .share-done { border-color: var(--accent); background: var(--accent); color: var(--bg-deep); font-weight: 650; }
.share-mint:hover, .share-done:hover { background: var(--ink); border-color: var(--ink); color: var(--bg-deep); }
.share-mint, .share-done {
  transition: background-color 140ms ease-out, border-color 140ms ease-out, color 140ms ease-out,
    transform 120ms ease-out, opacity 140ms ease-out;
}
.share-mint:active, .share-done:active { transform: translateY(1px); }
.share-mint[aria-busy="true"] { cursor: wait; opacity: .78; }
.share-success { display: grid; gap: var(--sp3); padding: var(--sp4); }
.share-success-status {
  display: flex; align-items: center; gap: var(--sp2); margin: 0;
  color: var(--ink-dim); font-size: var(--fs-note); font-weight: 600;
}
.share-success-status::before {
  content: ''; width: 7px; height: 7px; flex: none; border-radius: 50%; background: var(--ink-faint);
  transition: background-color 140ms ease-out, transform 140ms cubic-bezier(.25,1,.5,1);
}
.share-success-status[data-copy-state="copied"] { color: var(--ink); }
.share-success-status[data-copy-state="copied"]::before { background: var(--ready); transform: scale(1.15); }
.share-success-status[data-copy-state="sent"] { color: var(--ink); }
.share-success-status[data-copy-state="sent"]::before { background: var(--ready); transform: scale(1.15); }
.share-success-status[data-copy-state="failed"]::before { background: var(--rust); }
.share-success .share-address { min-height: 44px; }
.share-copy.is-copied { color: var(--ready); }
.share-copy.is-failed { color: var(--rust); }
.share-copy[aria-busy="true"] { cursor: wait; }
.share-recipient-field { margin-top: 0; }
.share-delivery-recovery { display: grid; justify-items: start; gap: var(--sp1); }
.share-delivery-recovery p {
  margin: 0; color: var(--ink-dim); font-size: var(--fs-chip); line-height: 1.4;
}
.share-retry-delivery {
  justify-self: start; min-height: 36px; padding: var(--sp1) 0; border: 0;
  background: transparent; color: var(--accent); font-size: var(--fs-chip); font-weight: 650;
}
.share-retry-delivery:hover { color: var(--ink); border-color: transparent; }
.share-retry-delivery[aria-busy="true"] { cursor: wait; opacity: .7; }
.share-export-body { display: grid; gap: var(--sp3); }
.share-export-intro { display: grid; gap: var(--sp1); }
.share-export-intro .share-section-hint { margin: 0; }
.share-export-list { display: grid; gap: var(--sp2); }
.share-export-action {
  display: flex; align-items: center; gap: var(--sp3); width: 100%; min-height: 64px;
  padding: var(--sp3); border: 1px solid var(--line); border-radius: 8px;
  background: transparent; color: var(--ink); text-align: left;
  transition: border-color 140ms ease-out, background-color 140ms ease-out, transform 120ms ease-out;
}
.share-export-action:hover, .share-export-action:focus-visible { border-color: var(--accent); }
.share-export-action:active:not(:disabled) { transform: translateY(1px); }
/* EDEN PASS 1 — the primary destination is his gold EXPORT verb: the
   --primary-action CTA fill (contrast.test.ts pins --bg-deep ink on it). */
.share-export-action.is-primary { background: var(--primary-action); border-color: var(--primary-action); }
.share-export-action.is-primary .share-export-copy strong,
.share-export-action.is-primary .share-export-copy small,
.share-export-action.is-primary .share-export-arrow { color: var(--bg-deep); }
.share-export-action.is-primary:hover { border-color: var(--ink); }
.share-export-action:disabled { opacity: .45; cursor: default; }
.share-export-copy { display: grid; gap: 2px; min-width: 0; flex: 1; }
.share-export-copy strong { color: var(--ink); font-size: var(--fs-note); font-weight: 650; }
.share-export-copy small { color: var(--ink-dim); font-size: var(--fs-chip); line-height: 1.4; }
.share-export-arrow { flex: none; color: var(--ink-dim); font-size: 1.35rem; }
.share-export-progress {
  display: grid; gap: var(--sp2); padding: var(--sp4) 0 var(--sp2);
}
.share-export-progress strong { color: var(--ink); font-size: var(--fs-note); font-weight: 650; }
.share-export-progress p { max-width: var(--measure-prose); margin: 0; color: var(--ink-dim); font-size: var(--fs-chip); line-height: 1.5; }
.share-export-progress progress { width: 100%; height: 4px; accent-color: var(--accent); }
.share-export-progress[data-phase="done"] strong { color: var(--ready); }
.share-export-progress[data-phase="failed"] strong { color: var(--rust); }
.share-export-progress .share-config-back { margin-top: var(--sp2); }
@keyframes share-scrim-in { from { opacity: 0; } }
@keyframes share-dialog-in { from { opacity: 0; transform: translateY(8px) scale(.985); } }
@keyframes share-step-in { from { opacity: 0; transform: translateY(4px); } }
@keyframes share-footer-in { from { opacity: 0; transform: translateY(3px); } }
@keyframes share-content-in { from { opacity: 0; transform: translateY(-3px); } }
.share-step-enter { animation: share-step-in 160ms cubic-bezier(.25,1,.5,1) both; }
.share-footer-enter { animation: share-footer-in 140ms cubic-bezier(.25,1,.5,1) both; }
.share-note-enter { animation: share-step-in 140ms ease-out both; }
:root[data-motion="reduced"] {
  .share-dialog-backdrop, .sharepop.share-dialog, .share-step-enter, .share-footer-enter,
  .share-content-enter, .share-note-enter { animation: none; }
  .share-settings-panel, .share-settings-arrow, .share-setting-reveal { transition: none; }
}
@media (max-width: 560px) {
  .share-dialog-backdrop { align-items: end; padding: 0; }
  .sharepop.share-dialog { width: 100%; max-height: 92vh; border-radius: 12px 12px 0 0; }
  .share-dialog-body { min-height: 0; }
  .share-dialog-actions { padding-bottom: max(var(--sp3), env(safe-area-inset-bottom)); }
  .share-intent-choice { grid-template-columns: 26px minmax(64px, auto) minmax(0, 1fr) auto; min-height: 64px; gap: var(--sp2); }
  .share-subject { align-items: flex-start; }
}
.share-scope {
  margin: 0 0 var(--sp2); font-size: var(--fs-label); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--track-label); color: var(--ink-faint);
}
.share-scope-note {
  max-width: 48ch;
  margin: calc(-1 * var(--sp1)) 0 var(--sp2);
  color: var(--ink-dim);
  font-size: var(--fs-note);
  line-height: 1.45;
}
.share-empty { margin: var(--sp2) 0 0; color: var(--ink-dim); font-size: var(--fs-note); }
.share-note { margin: var(--sp2) 0 0; color: var(--rust); font-size: var(--fs-chip); }
.share-row {
  margin-top: var(--sp2); padding: var(--sp3);
  border: 1px solid var(--line); border-radius: 6px;
}
.share-row-head { display: flex; align-items: baseline; gap: var(--sp2); }
.share-meter { font-weight: 600; color: var(--ink); font-size: var(--fs-note); }
.share-kind { margin: 2px 0 var(--sp2); color: var(--ink-dim); font-size: var(--fs-chip); }
.share-address {
  display: flex; align-items: stretch;
  background: var(--wash); border: 1px solid var(--line); border-radius: 4px;
}
.share-url {
  flex: 1; min-width: 0; padding: 5px var(--sp2);
  background: none; border: 0; color: var(--ink-dim);
  font-family: 'Spline Sans Mono', monospace; font-size: var(--fs-chip);
  text-overflow: ellipsis;
}
.share-url:focus { color: var(--ink); }
.share-copy {
  flex: none; padding: 0 var(--sp3);
  background: none; border: 0; border-left: 1px solid var(--line); border-radius: 0;
  color: var(--ink); font-size: var(--fs-chip);
  text-transform: uppercase; letter-spacing: var(--track-label);
}
.share-copy:hover { color: var(--accent); border-color: var(--line); }
.share-revoke {
  margin-left: auto; padding: 0;
  background: none; border: 0; color: var(--ink-faint); font-size: var(--fs-chip);
}
.share-revoke:hover { color: var(--ink); border-color: transparent; }
.share-revoke.confirming, .share-revoke.confirming:hover { color: var(--rust); font-weight: 600; }
.share-meta { margin: var(--sp2) 0 0; color: var(--ink-faint); font-size: var(--fs-chip); }
.share-create {
  display: block; width: 100%; margin-top: var(--sp3); padding: var(--sp2) var(--sp3);
  background: var(--surface); border: 1px solid var(--line); border-radius: 6px;
  color: var(--ink); font-size: var(--fs-note);
}
.share-create:hover, .share-create:focus-visible { border-color: var(--accent); color: var(--accent); }
.share-create:disabled { opacity: 0.5; cursor: default; }
/* FACE-F5 — the ⌘F find bar. A floating pill docked to the transcript's
   top-right, above the sticky viewbar (z 9 > the viewbar's 8) so it never fights
   the layout chip. Absolutely positioned within .transcript (its positioning
   context), so revealing it shifts nothing — the transport-row lesson. */
.findbar {
  position: absolute; top: var(--sp3); right: var(--sp4); z-index: 9;
  display: flex; align-items: center; gap: var(--sp2);
  padding: 5px 6px 5px 10px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 10px 30px oklch(0 0 0 / 0.45);
}
.findbar[hidden] { display: none; }
.findbar .find-icn { width: 14px; height: 14px; color: var(--ink-faint); flex: none; }
.findbar #findinput {
  width: 190px; padding: 2px 0; border: 0; background: none;
  color: var(--ink); font-size: var(--fs-body);
}
.findbar #findinput::placeholder { color: var(--ink-faint); }
/* the pill itself carries the focus signal (accent border) when the field is
   focused — the input keeps its own accessible focus ring (never suppressed). */
.findbar:focus-within { border-color: var(--accent); }
.findcount {
  min-width: 4.5em; text-align: right; font-size: var(--fs-chip);
  color: var(--ink-faint); font-variant-numeric: tabular-nums;
}
.findnav, .findclose {
  display: flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 5px;
  color: var(--ink-dim); background: none; font-size: 15px; line-height: 1;
}
.findnav:hover, .findclose:hover { color: var(--ink); background: var(--accent-soft); }
.findclose { color: var(--ink-faint); }
/* the current match — a single warm highlight, distinct from the playback wash
   (--accent-soft on .word.cur) and the selection (--accent-sel). The all-matches
   highlight is Jesse's non-blocking look-lane feel-check, deliberately unbuilt. */
.word.findcur {
  background: var(--find-hot, oklch(0.78 0.14 90 / 0.42));
  border-radius: 3px; box-shadow: 0 0 0 1px var(--find-hot, oklch(0.78 0.14 90 / 0.42));
}

.block {
  position: absolute;
  left: var(--geo-pad-x, 32px); right: var(--geo-pad-x, 32px);
  margin: 0;
}

/* INLINE-COMMENTS — paragraph-owned contextual PREVIEWS live in pretext's analytic
   tail band. Variable-height prose and replies belong to Notes; every inline entry
   is one native fixed row, so layout depends on count—not guessed text height. */
.inline-comment-stack {
  position: absolute; left: var(--geo-pad-x, 16px); right: var(--geo-pad-x, 16px);
  display: grid;
  /* Pretext and CSS share this exact solved count. An independent auto-fit here
     can choose a different row count by a rounding pixel, letting a card escape
     the analytic tail band and collide with the following paragraph. */
  grid-template-columns: repeat(var(--inline-comment-columns, 1), minmax(0, 20rem));
  justify-content: end;
  align-content: start;
  gap: var(--sp2);
  padding: var(--sp2) 0;
  border-top: 1px solid color-mix(in oklab, var(--line) 72%, transparent);
  box-sizing: border-box;
  container-type: inline-size;
  font-family: var(--f-ui);
  user-select: auto; -webkit-user-select: auto;
}
.inline-comment-stack[data-columns="2"] .inline-comment-slot:last-child:nth-child(odd) {
  grid-column: 2;
}
.inline-comment-slot { min-width: 0; block-size: var(--inline-comment-card-h, 88px); }
.inline-comment-stack .comment-card {
  position: relative;
  height: 100%;
  width: 100%;
  margin: 0;
  border-color: color-mix(in oklab, var(--line) 82%, transparent);
  background: color-mix(in oklab, var(--surface) 58%, var(--bg));
  box-shadow: none;
  overflow: hidden;
}
.inline-comment-stack .comment-card-open {
  position: relative;
  height: 100%;
  box-sizing: border-box;
  padding: 8px 76px 28px 10px;
}
.inline-comment-stack .comment-card .cmt-head { min-width: 0; }
.inline-comment-stack .comment-card :is(.cmt-body) {
  -webkit-line-clamp: 1;
  white-space: normal;
}
.inline-comment-stack .comment-card-tc {
  position: absolute;
  right: 10px;
  top: 10px;
  margin: 0;
}
.inline-comment-stack .comment-card .cmt-actions {
  position: absolute;
  left: 10px;
  right: 8px;
  bottom: 5px;
  padding: 0;
}
.inline-comment-stack .comment-card .cmt-replies { display: none; }
.inline-comment-stack .inline-reply-compose {
  position: absolute;
  z-index: 4;
  inset: 7px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 6px;
  padding: 7px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--editor-raised, var(--surface));
}
.inline-comment-stack .inline-reply-compose[hidden] { display: none; }
.inline-comment-stack .inline-reply-input {
  min-width: 0;
  height: 30px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--bg);
  color: var(--ink);
  font: var(--fs-chip)/1 var(--f-ui);
  line-height: 1.35;
  resize: none;
}
.inline-comment-stack :is(.inline-reply-cancel, .inline-reply-submit) {
  min-height: 30px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 4px;
  font: 600 var(--fs-chip)/1 var(--f-ui);
}
.inline-comment-stack .inline-reply-cancel { color: var(--ink-dim); background: transparent; }
.inline-comment-stack .inline-reply-submit { color: var(--editor-control-ground); background: var(--accent); }
.inline-comment-stack .inline-reply-submit:disabled { opacity: .5; cursor: default; }
.inline-comment-stack .inline-reply-error {
  grid-column: 1 / -1;
  overflow: hidden;
  color: var(--rust);
  font: var(--fs-chip)/1.2 var(--f-ui);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.inline-comment-stack .inline-reply-error[hidden] { display: none; }
.inline-comment-stack .comment-card[data-reply-error] .inline-reply-input { border-color: var(--rust); }
.inline-comment-stack .comment-card:hover,
.inline-comment-stack .comment-card:focus-within {
  border-color: color-mix(in oklab, var(--comment-mark) 42%, var(--line));
}
/* FOLD ANIMATION slice 1 (RFC fold-animation) — FLIP survivor-slide. The layout
   snaps instantly (analytic SSOT untouched, §22); view.ts foldWithFlip inverts a
   survivor to its pre-fold top then clears the transform under this class, so it
   rides a compositor-only translateY to its new slot. Transform-only → 60fps, no
   layout thrash. The reduced-motion floor lives in the JS gate (the class is never
   added under :root[data-motion="reduced"]) — ONE source of truth, no @media fork. */
.block.fold-slide { transition: transform 200ms cubic-bezier(0.22, 0.61, 0.36, 1); }
/* FOLD ANIMATION slice 2 (RFC fold-animation) — the collapsing band's fade. A
   member section folds to ZERO height (sectionHTML returns '' — its element is
   GONE), so the vanish is dressed with a short-lived CLONE ghost pinned at the
   band's pre-fold top (view.ts fadeVanishingBands), fading 1→0 while the survivors
   slide up over it. Pointer-inert and a11y-hidden (set in JS). The reduced-motion
   floor lives in the same JS gate as slice 1 — the ghost is never created under
   :root[data-motion="reduced"] (ONE source of truth, no @media fork). */
.block.fold-ghost { pointer-events: none; opacity: 1; transition: opacity 200ms ease-out; }
.block.fold-ghost.fold-ghost-leaving { opacity: 0; }
.line {
  position: absolute; left: 0; right: 0;
  height: var(--geo-line-h, 32px); line-height: var(--geo-line-h, 32px);
  white-space: nowrap;                           /* layout.js owns line breaks */
}
.bhead {
  position: absolute; top: 0; left: 0; right: 0;
  height: var(--geo-header-h, 20px);
  display: flex; align-items: center; gap: var(--sp3);
  font-family: var(--f-ui);
  margin: 0;
}
/* §9 3b — a chapter section reserves the .chead band ABOVE its .bhead
   (layout.ts CHEAD_H), so the section header drops by exactly the band height.
   The geometry contract: CSS reads the same --geo-chead-h pretext reserved. */
.block.chap .bhead { top: var(--geo-chead-h, 44px); }
/* the document-outline header — reads a clear tier ABOVE a section break, in the
   brand serif voice, and BREAKS OUT past the measure-capped text column to the paper edge
   (left/right:-PAD_X negates the .block inset — a pure constant, no DOM read). */
.chead {
  position: absolute; top: 0;
  left: calc(-1 * var(--geo-pad-x, 32px)); right: calc(-1 * var(--geo-pad-x, 32px));
  height: var(--geo-chead-h, 44px);
  display: flex; align-items: center;
  /* The title begins 6px before paragraph copy while the caret remains inside
     the paper gutter: hierarchy without negative overflow or paragraph drift. */
  padding-block: 0; padding-inline-start: 18px;
  box-sizing: border-box;
  border-bottom: 1px solid var(--line);
}
.chead-copy {
  display: block;
  min-width: 0;
  max-width: calc(100% - 42px);
  line-height: 1.05;
}
.chead-title {
  display: inline;
  font-family: var(--f-body); font-weight: 600;
  font-size: 20px; line-height: 1.05;
  color: var(--ink); letter-spacing: 0.005em;
  cursor: pointer;   /* CH3 — single-click / Enter SEEKS to the chapter; double-click still renames */
  transition: color 120ms ease-out;
}
/* CH3 — the seek affordance: hover + keyboard focus shift the serif header toward
   the accent so the title reads as the seekable target it is (the triangle owns
   the fold next to it). focus-visible mirrors .chead-tri so the tabindex stop is seen. */
.chead-title:hover { color: var(--accent); }
.chead-title:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
/* CV-2a — a chapter whose every member word is removed reads as removed at the
   document-outline tier: the title derives the EXACT word-strike voice (.word.del)
   — rust-dim ink + a rust line-through, no new token, no new color. view.ts paints
   the .del class from chapterAllStruck (derive-only); the title is still a seekable
   landmark, so hover still warms it (toward --rust here, mirroring .word.del:hover). */
.chead-title.del {
  color: var(--rust-dim);
  text-decoration: line-through;
  text-decoration-color: var(--rust);
  text-decoration-thickness: 1px;
}
.chead-title.del:hover { color: var(--rust); }
/* §22 inline rename — the title-edit input wears the SAME serif header voice so
   the swap is seamless; only a hairline border + surface say "now editable". */
.chead-input {
  font: 600 20px/1.05 var(--f-body); letter-spacing: 0.005em;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: 3px; padding: 0 4px;
}
/* §22.6 #1 — the disclosure triangle: the chead's OWN toggle target, left of the
   title, baseline-aligned to it. The title still seeks; only this flips the fold. */
.chead-tri {
  position: absolute; left: 0;
  display: grid; place-items: center;
  width: 16px; height: 16px; flex: 0 0 16px;
  border: none; background: none; padding: 0; margin: 0;
  color: var(--ink-dim); cursor: pointer;
  transition: color 120ms ease-out;
}
.chead-tri:hover { color: var(--ink); }
.chead-tri:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.chead-tri svg,
.chapters-index-row--fold .chapters-index-tick svg {
  transition: transform 0.2s ease;
}
.chead-tri[aria-expanded="false"] svg,
.chapters-index-row--fold[aria-pressed="true"] .chapters-index-tick svg {
  transform: rotate(-90deg);
}
.chead-tri svg,
.chapters-index-row--fold .chapters-index-tick svg {
  transition: transform 0.2s ease;
}
.chead-tri[aria-expanded="false"] svg,
.chapters-index-row--fold[aria-pressed="true"] .chapters-index-tick svg {
  transform: rotate(-90deg);
}
/* Duration stays in the title's inline flow, after the final word. It therefore
   follows a wrapping title onto line two without a measured-height system. */
.chead-duration {
  display: inline;
  margin-inline-start: .55em;
  white-space: nowrap;
  font: 500 var(--fs-note)/1 var(--f-tc);
  color: var(--ink-faint);
  letter-spacing: 0;
}
/* COLLAPSED-CHAPTER-COMMENTS-ROLLUP — the "💬 N" pill a folded head wears when it
   hides comments (the shipped overflow-pill idiom): quiet at rest at the meta tier,
   warms to --accent on hover/focus (it is a live opener — clicking unfolds the
   chapter). position:relative anchors its LANE-HOVER-INFO peek popover. */
.chead-comments {
  position: relative; margin-left: var(--sp2);
  display: inline-flex; align-items: center; gap: 3px;
  border: none; background: none; padding: 0 2px;
  font-family: var(--f-ui); font-size: var(--fs-note); line-height: 1;
  color: var(--ink-faint); cursor: pointer;
  transition: color 120ms ease-out;
}
.chead-comments:hover, .chead-comments:focus-visible { color: var(--accent); }
.chead-comments:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.chead-comments svg { width: 13px; height: 13px; }
.chead-comments-n { font-variant-numeric: tabular-nums; }
/* the peek opens down-left from the pill (it sits mid-header, right of the count) */
.chead-comments .lane-peek { left: auto; right: 0; }
/* One quiet overflow door replaces the dedicated trash action. It remains
   touch/keyboard reachable; pointer interfaces reveal it with the header. */
.chead-more {
  margin-left: auto; align-self: center; flex: 0 0 28px;
  width: 28px; height: 28px;
  border: none; border-radius: 999px; background: transparent; padding: 0;
  display: grid; place-items: center;
  font: 700 18px/1 var(--f-ui); letter-spacing: .04em;
  color: var(--ink-faint); cursor: pointer;
  opacity: .45;
  transition: color 120ms ease-out, background-color 120ms ease-out, opacity 120ms ease-out;
}
.chead:hover .chead-more,
.chead-more:hover,
.chead-more:focus-visible,
.chead-more[aria-expanded="true"] { opacity: 1; color: var(--ink); }
.chead-more:hover,
.chead-more[aria-expanded="true"] { background: var(--surface-hover); }
.chead-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (hover: hover) and (pointer: fine) {
  .chead-more:not(:focus-visible):not([aria-expanded="true"]) { opacity: .08; }
  .chead:hover .chead-more { opacity: .72; }
}
.grip {
  /* T4(e) + Jesse 2026-06-11 v2: the BUTTON is the full-height rail (whole
     strip clickable, hover answers it); the DOTS are a fixed 2×6 cluster,
     centered — same clean vibe as the original ⠿, just bigger. The cluster
     box is an EXACT multiple of the dot tile (12=2×6px, 42=6×7px), so a
     partial/cut-off dot is geometrically impossible. */
  position: absolute; left: -28px; top: 0; bottom: 0; width: 16px;
  border: none; padding: 0; border-radius: 4px;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-deco); cursor: grab;
  /* Reveal immediately: virtualized block remounts under a stationary pointer
     must not replay an entrance pulse. Only the interactive ink change eases. */
  opacity: 0; transition: color 120ms ease-out;
}
.grip::after {
  content: ""; width: 12px; height: 42px;
  background-image: radial-gradient(currentColor 1px, transparent 1.5px);
  background-size: 6px 7px;
}
/* the rail's OWN hover answers "is this whole strip clickable?" — yes */
.grip:hover { color: var(--ink-dim); background-color: var(--wash-soft); }
.grip:active { cursor: grabbing; color: var(--accent); }
/* §22.7 — on a chapter section the grip drops below the .chead band (the same
   --geo-chead-h drop .bhead takes, l.695), so its full-height hit-box never sits
   on top of the chead's left edge and steal the chapter title's caret/click.
   Pure constant, no DOM read — honors the §22 in-transcript-chrome law. */
.block.chap .grip { top: var(--geo-chead-h, 44px); }

.secmenu, .seckill {
  align-self: stretch; height: 100%; min-width: 24px;
  display: grid; place-items: center;
  border: none; padding: 0 2px; border-radius: 4px;
  color: var(--ink-faint); line-height: 1;
  opacity: 0; transition: opacity 120ms ease-out;
}
/* DESIGN-0710 SECTION-HAMBURGER — the trailing control group floats right: the ☰
   carries the margin-left:auto (it's the first of the pair now that the inline ⤒
   merge is gone), the × follows it on the flex gap. Both stay opacity-revealed, so
   the × never reflows when the ☰ shows/hides. */
.secmenu {
  margin-left: auto;
  font-size: 20px; font-weight: 600;
}
.seckill { font-size: 24px; font-weight: 400; }
/* STARS-1a — the section star leads the trailing group (★ ☰ ×), so IT carries the
   margin-left:auto and the ☰ rides the flex gap behind it. Own declarations (not
   appended to the .secmenu/.seckill lists) so the design-audit's pinned selector
   strings stay byte-stable. Filled state stays visible without hover — a starred
   section announces itself; the empty ☆ reveals on hover like its neighbors. */
.secstar {
  align-self: stretch; height: 100%; min-width: 24px;
  display: grid; place-items: center;
  border: none; padding: 0 2px; border-radius: 4px;
  color: var(--ink-faint); line-height: 1;
  margin-left: auto;
  font-size: 19px; font-weight: 400;
  opacity: 0; transition: opacity 120ms ease-out, color 120ms ease-out;
}
.secstar:hover { color: var(--star-ink, #d9a441); }
.secstar.secstar-on { opacity: 1; color: var(--star-ink, #d9a441); }
/* the PARTIAL state: the solid ★ at half voice — visible without hover, honestly
   between ☆ and ★; the dominant toggle click completes it to all. */
.secstar.secstar-some { opacity: 1; color: color-mix(in srgb, var(--star-ink, #d9a441) 50%, transparent); }
.secmenu { margin-left: 0; }   /* the ★ owns the auto-push now (declared later, wins the cascade) */
/* §W1.6 P1 — the transcript reveals fire ONLY on a true-hover pointer. On
   coarse/touch the base opacity:0 gives way to the coarse rest-fallback
   (l.~2198: .grip/.secmenu/.seckill rest at 0.55) — no sticky-tap reveal, no
   wasted first tap (Jesse bug #2). Pinned by the "no ungated reveal" audit. */
@media (hover: hover) {
  .block:hover .grip { opacity: 1; }
  .block:hover .secmenu, .block:hover .seckill { opacity: 1; }
  .block:hover .secstar { opacity: 1; }   /* STARS-1a: same true-hover gate, own line */
  /* FEEL-0710-3(c) LANE-HOVER-INFO — the collapsed-lane peek reveals ONLY on a hover-
     capable pointer (never the touch path, where :hover sticks after a tap) and ONLY when
     the lane is collapsed. Consolidated in the single hover-gated block (design-audit pin). */
  .transcript[data-margin-minimized] .comment-lane-toggle:hover .lane-peek,
  .transcript[data-margin-minimized] .comment-lane-toggle:focus-visible .lane-peek,
  /* A summoned comments lane can still preview its discussion. Chapters keep only
     their compact count/title so hover never manufactures a second chapter menu. */
  .transcript[data-tier-comment='summoned'] .comment-lane-toggle:hover .lane-peek,
  .transcript[data-tier-comment='summoned'] .comment-lane-toggle:focus-visible .lane-peek,
  /* COLLAPSED-CHAPTER-COMMENTS-ROLLUP — the folded-head comment pill peeks its rolled-up
     threads on hover/focus too (same hover-only gate; a folded chapter is always the
     "collapsed" case here, so no data-tier guard is needed). */
  .chead-comments:hover .lane-peek,
  .chead-comments:focus-visible .lane-peek {
    display: block;
  }
}
.secmenu:hover { color: var(--ink); background: var(--wash-soft); }
.seckill:hover { color: var(--rust); background: var(--rust-wash); }
.seckill[data-restore]:hover,
.seckill[data-restore]:focus-visible { color: var(--moved); background: var(--wash); }

.speaker {
  display: inline-flex; align-items: center; gap: 6px;
  border: none; border-radius: 4px; padding: 2px 4px;
  font-size: var(--fs-note); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.13em;
  cursor: pointer;
}
.speaker:hover, .speaker:focus-visible, .speaker[aria-expanded="true"] { background: var(--wash-soft); }
.speaker-extra { color: var(--ink-dim); font-size: var(--fs-chip); letter-spacing: .04em; }
.speaker.sp1 { color: var(--spk1); }
.speaker.sp2 { color: var(--spk2); }
.speaker.sp3 { color: var(--spk3); }
.speaker.sp4 { color: var(--spk4); }
.speaker-input {
  font: 600 var(--fs-note) var(--f-ui); letter-spacing: 0.13em; text-transform: uppercase;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: 3px; padding: 1px 4px;
}
/* HEADER-TC RETIRED (Jesse 2026-07-20): the per-section header timecode is no longer
   rendered (view.ts keeps the template as a commented restore path). This rule stays as
   the inert look half of that reference — it costs nothing with no .btc in the DOM. */
.btc { font-size: var(--fs-chip); color: var(--ink-faint); }

.bp { margin: 0; }

/* TRANSCRIPT SURFACES — words, silence chips, and section speaker labels share one
   response clock. Tokens additionally share the selection/current state matrix below;
   speaker labels only borrow the motion contract because they are section controls,
   not timed transcript objects. Keeping the class on the DOM makes this boundary
   explicit and prevents each variant quietly acquiring a different hover speed. */
.transcript-surface {
  transition:
    background-color 140ms cubic-bezier(0.25, 1, 0.5, 1),
    color 140ms cubic-bezier(0.25, 1, 0.5, 1),
    opacity 180ms cubic-bezier(0.25, 1, 0.5, 1);
}
:root[data-motion="reduced"] .transcript-surface { transition: none; }

.word { border-radius: 2px; cursor: pointer; }
.token-flight {
  display: inline-flex;
  align-items: baseline;
  flex: none;
  min-width: 0;
  gap: inherit;
  transform-origin: 50% 70%;
}
/* The silence action uses margin-left:auto at rest. Give its temporary shared
   flight wrapper the same content width, so ×/+ is already at its resting x
   before cleanup instead of making a final little rightward settling hop. */
.sil > .token-flight { width: 100%; }
.word:hover { background: var(--wash); }
/* SELECTION SPOTLIGHT (Jesse 2026-06-29) — a word-range selection dims everything that
   isn't selected so the eye lands on the picked run; the soft opacity settle reads as the
   selection "finalizing". SUPPRESSED while a move-drag is active (.paper.dnd) — dragging the
   selected words must not fade their surroundings (you're placing them, not focusing them).
   Opacity only (no reflow); tunable via --sel-dim-opacity. */
.paper[data-sel-focus]:not(.dnd) .word:not(.sel),
.paper[data-sel-focus]:not(.dnd) .sil:not(.sel) { opacity: var(--sel-dim-opacity, 0.4); }
/* Speaker labels participate in an active word selection. Comment hover is only
   contextual reference and therefore never dims document chrome or other prose. */
.paper[data-sel-focus]:not(.dnd) .speaker { opacity: var(--sel-dim-opacity, 0.4); }
/* Comment hover is additive and local: the anchored run gains a warm proof-mark
   wash and underline while every surrounding word stays at its authored contrast. */
.word.cmt-lit,
.sil.cmt-lit {
  background: color-mix(in oklab, var(--accent) 22%, transparent);
  box-shadow: inset 0 -2px color-mix(in oklab, var(--accent) 72%, transparent);
}
/* filler candidate — dotted underline says "the policy may remove this" */
.word.fill { text-decoration: underline dotted var(--ink-deco); text-underline-offset: 4px; }
/* pinned to stay — amber underline says "protected from the policy" */
.word.fill.kept { text-decoration-color: var(--accent); }
/* R15 s2 — long-press selection-entry progress (touch-input.md §4). While a
   finger/pen rests on a word, an accent ring grows over the long-press window
   (--lp-ms, published from the resolver's LONG_PRESS_MS so the two can't drift);
   entering selection or yielding to a pan clears the class. A CLASS, never an
   inline style — Spooky's long-press indicator mutated style.removeProperty in
   the gesture handler, the exact sin this investigation exists to retire. */
.word.lp-hold { animation: lp-hold-grow var(--lp-ms, 500ms) ease-out forwards; }
@keyframes lp-hold-grow {
  from { box-shadow: 0 0 0 0 var(--accent-sel); }
  to   { box-shadow: 0 0 0 2px var(--accent-sel); }
}
/* the karaoke/caret word keeps only the wash — the unified .playhead mark (one
   element in the paper layer) owns both the playing underline and the paused
   left-edge stroke now */
/* CURSOR-1 — a live peer's caret, tinted in their identity hue (the SAME hue as their
   presence seat + their comments, sin #5: one face everywhere). A left-edge bar marks the
   exact word; a faint wash of the same hue tints the word so a peer's position reads at a
   glance. The hue rotates inside the darkroom register (chroma/lightness fixed here), never
   garish. Word-id anchored — repainted on every remount, so the caret follows the WORD
   through reflow and can never land on the wrong text. Applies to words AND silence chips. */
.peer-cursor {
  position: relative;
  /* CURSOR-LIVE-1 — a STRONGER caret post: a 3px saturated bar at the word's leading edge
     (was a 2px, 0.11-chroma inset that read "too subtle" on the first two-up) plus a firmer
     wash. Still the identity register (chroma/lightness fixed, hue rotates) — legible, never
     garish. */
  box-shadow: inset 3px 0 0 0 oklch(0.6 0.15 var(--cursor-hue, 250));
  background: color-mix(in oklab, oklch(0.62 0.14 var(--cursor-hue, 250)) 22%, transparent);
  border-radius: 2px;
}
/* PRES-WS-2 receiver glide — a caret that MOVED this paint eases onto its new word (a brief
   wash+bar fade rising from the leading edge) instead of popping. The word-tint is discrete
   (word-id anchored, not a positioned element — the floating x/y glide is CURSOR-XY-1), so
   the "interpolation" here is an entry ease on the tint itself. Applied only on a real move
   (presenceCursors keys off the last-seen word), so a stationary peer's every-beat re-paint
   never re-pulses. The name flag rides along via the same element. */
@keyframes peer-caret-in {
  from { box-shadow: inset 3px 0 0 0 oklch(0.6 0.15 var(--cursor-hue, 250) / 0); background: transparent; }
  to   { box-shadow: inset 3px 0 0 0 oklch(0.6 0.15 var(--cursor-hue, 250)); background: color-mix(in oklab, oklch(0.62 0.14 var(--cursor-hue, 250)) 22%, transparent); }
}
.peer-cursor.peer-cursor-moved { animation: peer-caret-in 180ms ease-out; }
/* Reduced-motion floor (project SSOT — :root[data-motion="reduced"], NOT the OS media query:
   the pref/OS tier owns that key so gating here keeps ONE source of truth). The caret still
   moves — instantly, no ease. */
:root[data-motion="reduced"] .peer-cursor.peer-cursor-moved { animation: none; }
/* the peer's selection range. CURSOR-VIS-1 (SEL-PROP-1 legibility half, Jesse two-up
   2026-07-03): the 13% wash read "too subtle to notice" — the caret word got a legibility
   bump but the selection run did not. A firmer tint (13%→20%, chroma 0.13→0.15) plus a
   bottom hairline in the identity hue so the run reads as a deliberate BAND, not a faint
   ghost. Still lighter than the caret word's own 22% + 3px bar, so the focus word stays the
   focal point. Bottom-only inset (no side/top ticks) stays continuous across wrapped words. */
.peer-sel {
  background: color-mix(in oklab, oklch(0.62 0.15 var(--cursor-hue, 250)) 20%, transparent);
  box-shadow: inset 0 -1.5px 0 0 color-mix(in oklab, oklch(0.6 0.15 var(--cursor-hue, 250)) 55%, transparent);
}
/* CURSOR-LIVE-1 — a PERSISTENT name flag rooted at the caret post (was hover-only, so you
   couldn't tell WHO was there without hovering). A small tag pinned to the word's top-left,
   its square bottom-left corner sitting on the caret bar like a pennant on its pole. Quiet at
   rest (0.9 alpha), full on hover; never intercepts pointer. Few-peers v1 — one tag per peer
   caret, so no clutter; live carets/labels-de-clutter is a v2 concern. */
.peer-cursor::after {
  content: attr(data-peer);
  position: absolute; left: -3px; bottom: 100%; transform: translateY(-1px);
  font-size: 9.5px; line-height: 1; font-weight: 600; letter-spacing: 0.01em;
  white-space: nowrap; padding: 1.5px 5px; border-radius: 4px 4px 4px 0;
  background: oklch(0.56 0.11 var(--cursor-hue, 250)); color: var(--ink-on-media);
  pointer-events: none; z-index: 5; opacity: 0.9;
}
.peer-cursor:hover::after { opacity: 1; }

/* ============================ CURSOR-XY-1 — the floating peer pointer ============================
   The OTHER half of a peer's presence: not where they're READING (the .peer-cursor word-tint
   above) but where their MOUSE points. Element-anchored, not raw x/y — presencePointers.ts places
   each pointer's tip at a fractional offset inside a hovered word's box (reflow-safe: the anchor's
   box, not a pixel), so it lands on the same content in every viewport. The layer is a positioned
   child of .paper (scrolls with the text); the pointers never eat a click. */
.peer-pointer-layer { position: absolute; inset: 0; pointer-events: none; z-index: 6; }
.peer-pointer {
  position: absolute; left: 0; top: 0;               /* placed via inline left/top per paint */
  pointer-events: none; will-change: left, top;
  /* The glide: a pointer that moves eases to its new spot (Figma's cursor feel). A first
     placement is snapped (.peer-pointer-fresh suppresses this for one paint) so it appears,
     not slides in from the corner. */
  transition: left 180ms cubic-bezier(0.22, 0.61, 0.36, 1), top 180ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.peer-pointer-fresh { transition: none; }
.peer-pointer-arrow { display: block; filter: drop-shadow(0 1px 1.5px oklch(0 0 0 / 0.35)); }
.peer-pointer-arrow path {
  fill: oklch(0.62 0.16 var(--cursor-hue, 250));
  stroke: var(--ink-on-media, #fff); stroke-width: 1; paint-order: stroke;
}
/* The name flag rides just off the arrow tip, in the peer's identity hue (the caret's twin). */
.peer-pointer-name {
  position: absolute; left: 12px; top: 12px;
  font-size: 9.5px; line-height: 1; font-weight: 600; letter-spacing: 0.01em;
  white-space: nowrap; padding: 1.5px 5px; border-radius: 4px 4px 4px 0;
  background: oklch(0.56 0.13 var(--cursor-hue, 250)); color: var(--ink-on-media);
}
/* Reduced-motion floor (project SSOT — :root[data-motion="reduced"], the same key the caret
   glide uses): the pointer still MOVES, instantly, no slide. */
:root[data-motion="reduced"] .peer-pointer { transition: none; }

/* COMMENTS Shift-3b — the at-rest in-text anchor affordance (synthesis E/C). A
   commented word wears a QUIET underline in the accent register (reuses --accent,
   NOT a second highlighter — sin #5). A STRAIGHT, continuous underline via
   text-decoration (Jesse 2026-06-29): a box-shadow/background underline follows the
   word's border-radius → janky curves + bracket ticks at the span ends; text-decoration
   decorates the GLYPHS, so it ignores border-radius (the hover/selection HIGHLIGHT stays
   rounded while the underline is dead straight). Each word span carries its trailing space
   (view.ts:1173 `…text </span>`), so the underline bridges word-to-word with NO gaps across
   the run — the bracket-tick span ends are retired (a clean continuous rule reads better).
   Recessed at rest (--accent-soft) so it never competes with the playhead wash or selection
   fill; on the rare struck/filler word the line-through/dotted rule wins (the comment still
   shows in the margin), an accepted trade for a straight, gap-free underline. */
.word.cmt-anchor {
  text-decoration: underline;
  text-decoration-color: var(--accent-soft);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
.word.cmt-anchor:hover { text-decoration-color: var(--accent); }
.word.del {
  color: var(--rust-dim);
  text-decoration: line-through;
  text-decoration-color: var(--rust);
  text-decoration-thickness: 1px;
}
.word.del:hover { color: var(--rust); background: var(--rust-wash); }
/* T11 hover unity — .span-hot lights EVERY member of the hovered span with the
   stricken hover wash, channel A ONLY (no ink shift — law 5's lift stays on the
   element under the pointer). Members are click targets now (span-select). */
.word.del.span-hot { background: var(--rust-wash); }
.sil-ghost.span-hot, .sil-ghost.span-hot:hover { background: var(--rust-wash); }
.sil-ghost[data-span], .sil-ghost[data-span]:hover { cursor: pointer; }
/* filler removed by the global policy — lighter than a hand cut, click to keep */
.word.fdel {
  color: var(--ink-faint);
  text-decoration: line-through dotted var(--ink-deco);
  text-decoration-thickness: 1px;
}
.word.fdel:hover { color: var(--ink-dim); background: var(--wash-soft); }
/* T9 laws 2+3 — combination cells write channel A ONLY: the fill winner's wash.
   Selection is already the strongest local state, so hovering a selected token
   is deliberately idempotent (no chip-only hot→base blink while crossing a
   paragraph gap). Placed AFTER the provenance hovers above so a selected
   stricken word hovers the SELECTION wash (its fill winner) while del's rust
   ink-lift (channel B) still applies — channels compose, fills never stack. */
/* pretext-lab: current + selected reads as JUST selected — the playhead never
   recolours a live selection (Jesse). A drilled single word IS both .cur and
   .sel; making the combo a special hot cell made it look unlike a plain selected
   word, hiding that the scope narrowed section→word. So .sel.cur = .sel, and a
   selected words and chips stay visually fixed on hover; the playhead word inside
   that selection follows the same no-op rule. */
/* T11: a SELECTED span member under span-hot shows its fill winner (selection)
   one step hotter — same cell as sel ∧ hover, stated so span-hot can't paint
   rust-wash over a live selection by specificity accident. */
/* pretext-lab: a STRUCK word in a selection keeps the BLUE selection (background =
   selection state ONLY, per the T9 channel laws — no dynamic per-content highlight).
   The clash was rust TEXT on blue; rejection now lives in the TEXT + the STRIKE, not
   the wash. ── KNOBS (Jesse): all clean, channel-safe — they touch only the word's
   own ink + strike, never the selection background:
     • color                       text brightness → --ink (bright) / --ink-dim / --ink-faint
     • text-decoration-color       the strike colour → --rust (red) / --rust-dim
     • text-decoration-thickness   the strike line width → 1px / 2px / 3px
   (NOTE: do NOT use `opacity` here — it would fade the blue selection too, not just
   the text. `color` is the clean text-dim knob.) Placed last so it wins the cascade. */
.word.del.sel {
  color: var(--ink-dim);                  /* dimmer than a live selected word → reads as "rejected", still legible */
  text-decoration-color: var(--rust);     /* red strike */
  text-decoration-thickness: 1px;
}
/* hiding struck content is done by EXCLUSION FROM LAYOUT (layout.js
   showCuts), never display:none — geometry and DOM must always agree. */

/* The inline RIP — a vertical torn edge at every splice. A moved span gets one
   on each side (the bookend); supersedes the old ⤷ arrow ("go here" was the wrong
   metaphor — a splice is a CUT, not a jump). Always-on, derived in layout from the
   one spliceBefore predicate; the heavier "Section at moved joins" tear stays a
   separate opt-in. Box = var(--geo-seam-size) wide + 2px margin = layout's L.seamW
   EXACTLY (both are GEO.SEAM_SIZE + 2), so text stays pixel-aligned; --moved carries
   the (themed) colour via mask. The jaggedness/thickness is Jesse-tunable in the ONE
   --rip-mask data-URI + its stroke-width below — the whole visual knob is right here. */
.word.seam::before {
  content: ""; display: inline-block;
  width: var(--geo-seam-size, 12px); height: 0.92em; vertical-align: -0.16em;
  margin-right: 2px; background: var(--moved);
  -webkit-mask: var(--rip-mask) center / contain no-repeat;
          mask: var(--rip-mask) center / contain no-repeat;
}
/* K2: a DISPLAYED struck word KEEPS the seam's reserved width (layout marks .seam
   on shown cuts too, so striking a moved span doesn't reflow — its box is frozen),
   but the move-squiggle is meaningless on rejected content, so HIDE the mark while
   keeping its box. visibility:hidden (not display:none) preserves the ::before's
   var(--geo-seam-size)+2px so the painted geometry still matches the analytic
   L.seamW — no drift, no shift, no green rip on a corpse. */
.word.del.seam::before, .word.fdel.seam::before { visibility: hidden; }
/* RUST CUT-SEAM (Jesse ruling 2026-07-02): the SAME rip, the removed-voice colour.
   Green (--moved) = content arrived; rust (--stricken) = content LEFT (a hidden cut,
   duration-gated in layout). Identical box → identical L.seamW reserve, so toggling
   Show Removed swaps the cause without reflowing a pixel. Only ever painted on an
   ALIVE arriving word, but mirror the struck-hides rule for symmetry if it's later cut. */
.word.cutseam::before {
  content: ""; display: inline-block;
  width: var(--geo-seam-size, 12px); height: 0.92em; vertical-align: -0.16em;
  margin-right: 2px; background: var(--stricken);
  -webkit-mask: var(--rip-mask) center / contain no-repeat;
          mask: var(--rip-mask) center / contain no-repeat;
}
.word.del.cutseam::before, .word.fdel.cutseam::before { visibility: hidden; }
/* RUST-SEAM-1 Stage 3 — the TRAILING half of the inline rip. The full-width serrated
   `.tear` band is retired; a splice now reads as a rip on BOTH edge words: the leading
   ::before on the arriving word (above) + this trailing ::after on the upper block's
   last shown word. Absolute + zero flow width (no L.seamW reserve → no reflow), so it
   hangs just past the word into the trailing gutter. Same --rip-mask + box as the
   leading mark; --moved for a move-join, --stricken for a hidden cut, matching the
   leading colour across the junction. Hidden on a struck corpse (the ::before rule's
   twin) — no rip on rejected content. */
.word.seamtail, .word.cutseamtail { position: relative; }
.word.seamtail::after, .word.cutseamtail::after {
  content: ""; position: absolute; left: 100%; top: 50%; transform: translateY(-50%);
  width: var(--geo-seam-size, 12px); height: 0.92em; margin-left: 2px; pointer-events: none;
  -webkit-mask: var(--rip-mask) center / contain no-repeat;
          mask: var(--rip-mask) center / contain no-repeat;
}
.word.seamtail::after { background: var(--moved); }
.word.cutseamtail::after { background: var(--stricken); }
.word.del.seamtail::after, .word.fdel.seamtail::after,
.word.del.cutseamtail::after, .word.fdel.cutseamtail::after { display: none; }
:root {
  --rip-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 48'%3E%3Cpath d='M8.5 0 L3 7 L9.5 14 L2.8 21 L9.2 28 L3.2 35 L9 42 L4 48' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* Silence is quiet transcript punctuation. Dot count gives a restrained,
   capped duration cue; exact time remains in the hover label. Editing actions
   are progressively disclosed in the selection popover, never parked inline. */
.sil {
  /* pretext-lab: the chip is WORD-SCALE text on the WORD BASELINE — that's what
     aligns the strike + selection with words for free (no per-marker calibration).
     Mono family + ink-dim keep its "meta" voice so it never reads as a spoken word.
     Selection box matched to a word's PIXEL-FOR-PIXEL (measured in a browser with
     the REAL reading face: the marker follows the same published word size.
     The proportional line-height and nudge scale with the S/M/L
     presets; the 0.088em nudge re-centers the box on the word baseline (relative,
     not a fixed px, so it scales too). */
  line-height: 1.47; vertical-align: 0.088em;
  display: inline-flex; align-items: baseline;
  gap: 0;
  /* The temporary wordfly wrapper must remain visible while translated from its
     old slot. Clipping here was why pauses appeared to pop while words floated. */
  position: relative;
  overflow: visible; justify-content: flex-start;
  cursor: pointer; user-select: none;
  font-family: var(--f-body); color: var(--ink-dim);
  font-size: var(--geo-font-size, 22px);   /* WORD size — pretext measures the chip at this size too */
  padding: 0 var(--geo-chip-pad, 4px);   /* no vertical pad — line-height carries the word-matching height */
  border-radius: 3px;
}
.sil:hover { background: var(--wash-soft); color: var(--ink); }
/* Pause affinity uses the same fine star underline as speech. Keep this beside the
   pause painter so later generic chip styles cannot erase the shared state. */
.sil.starred { box-shadow: inset 0 -1px 0 var(--star-ink, #e9b64a); }
.sil[data-duration]:not(.sil-ghost)::before {
  content: attr(data-duration);
  position: absolute;
  left: 50%; bottom: calc(100% + 3px);
  z-index: 4;
  transform: translate(-50%, 2px);
  padding: 2px 5px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--surface);
  color: var(--ink-dim);
  font: 500 var(--fs-chip)/1.1 var(--f-tc);
  font-style: normal;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 100ms ease-out, transform 100ms ease-out;
}
.sil[data-duration]:not(.sil-ghost):is(:hover, :focus-within, :active)::before {
  opacity: 1;
  transform: translate(-50%, 0);
}
:root[data-motion="reduced"] .sil[data-duration]::before { transition: none; }
/* T4(f): a chip inside a span selection wears the WORD selection token — derived
   membership, painted by the view iff both flanking words are selected. Ordered
   like words: sel beats hover; the stricken rust voice (.sil-tight, below) stays
   readable inside a selection. */
/* pretext-lab: a selected chip bridges its trailing joiner (--join-w, the inter-
   token space) with a flat offset box-shadow — the next word is always selected
   too (chip-sel ⇒ both flanks selected), so the span paints gap-free. Box-shadow
   is paint-only: no layout, no width drift. The bridge's left corners hide under
   the chip, so it reads as a few-px wider selection, not a second rounded blob. */
.sil.sel { box-shadow: var(--join-w, 0) 0 0 0 var(--accent-sel); }
.sil-glyph {
  font-weight: var(--geo-font-weight, 350);
  font-style: italic;
  letter-spacing: 0.015em;
  white-space: nowrap;
}
.sil-cut {
  display: inline-block;
  width: var(--geo-chip-rule-cut, 8px);
  margin-right: var(--geo-chip-gap, 5px);
  height: 0.92em;
  background: var(--stricken);
  -webkit-mask: var(--rip-mask) center / contain no-repeat;
  mask: var(--rip-mask) center / contain no-repeat;
  align-self: center;
}
/* Legacy inline pause controls remain styled for compatibility fixtures and
   coarse-pointer fallbacks, but production chips disclose actions in the shared
   selection popover and therefore do not render this child. */
.sil-x {
  margin-left: auto; flex: none;
  border: none; background: none; cursor: pointer;
  padding: 0 2px; line-height: 1; color: var(--ink-dim);
  font-size: 17px; font-weight: 500;
  opacity: 0;
  pointer-events: none;
  transition: opacity 100ms ease-out, color 120ms ease-out;
}
.sil:is(:hover, :focus-within) .sil-x,
.sil-x:focus-visible { opacity: 1; pointer-events: auto; }
.sil-x:hover { color: var(--rust); }                     /* × = remove → red on hover only */
.sil-x[data-untight]:hover { color: var(--moved); }      /* + = restore → green on hover */
:root[data-motion="reduced"] .sil-x { transition: none; }
.sil > .sil-x { display: none; }
/* UNRAZOR-DOOR-1 (b): the transcript razor mark — a thin inline glyph that names a
   `cut` entry's hard clip edge AND is the un-razor focus target. Reset button chrome
   to an inline, quiet mark that sits in the prose flow before its word. Structural
   only — glyph/weight/hue are look-lane placeholders (Jesse's pass). */
.razor-mark {
  border: none; background: none; cursor: pointer; padding: 0; margin: 0 1px 0 0;
  font-size: 0.72em; line-height: 1; color: var(--ink-faint); vertical-align: 0.05em;
  transition: color 120ms;
}
.razor-mark:hover, .razor-mark:focus-visible { color: var(--accent); }   /* join is a wayfinding verb */
/* a TIGHT pause — hand or policy, same state: a struck label. Click the body to
   hear it; ↺ restores the full pause (the pause-ghost analog of restoring a
   policy-removed filler). */
.sil-tight { color: var(--rust); }              /* same stricken voice as .word.del */
.sil-tight b { text-decoration: line-through var(--rust-dim); text-decoration-thickness: 1px; }  /* solid — same stricken voice + thickness as words (the small chip font thinned it) */
.sil-tight:hover { color: var(--rust); background: var(--rust-wash); }  /* match .word.del:hover */
/* The shared strike sweep temporarily owns the visible line. Silence variants
   draw their resting strike below/through generated content, so suppress that
   final-state ink until the same pencil overlay used by words has arrived. */
.sil-tight.strike-sweeping b { text-decoration-color: transparent; }

/* Timed transcript objects share one state machine. The semantic painters may derive
   membership differently (a word is an entry; a silence is the gap between two entries),
   but once a token receives .sel/.cur/.cmt-lit it responds identically. Variant rules
   below this block may add structure such as the silence join shadow; they do not choose
   a competing fill or animation clock. */
.transcript-token.sel { background: var(--accent-sel); }
.transcript-token.cur {
  background: var(--accent-soft);
  /* Presence is state, not a momentary response. A silence chip may be repainted
     every video frame or remounted by transcript virtualization; letting the
     generic surface transition own the fill made that stable state look
     as though it timed out and returned. Snap the current wash into place and
     leave the animated caret as the only clock. Color/opacity retain the shared
     response easing for selection spotlight and provenance changes. */
  transition-property: color, opacity;
}
.transcript-token.sel.cur { background: var(--accent-sel); }
.transcript-token.sel:hover { background: var(--accent-sel); }
.transcript-token.cur:hover { background: var(--accent-soft); }
.transcript-token.sel.cur:hover { background: var(--accent-sel); }
.word.sel.span-hot { background: var(--accent-sel); }
/* MOVE-INVITE-1 — a settled word-range is also a draggable object. Cursor is the
   invitation; the fill deliberately stays unchanged so merely hovering a selected
   word cannot make the selection blink or appear to change membership. */
.paper[data-sel-focus]:not([data-sel-dragging]) .transcript-token.sel { cursor: move; }
.paper[data-sel-dragging] .transcript-token { cursor: text; }
/* Once the pointer commits to a real destination, the selection reads as lifted:
   stronger wash + a tight lower edge, with no transform on inline words (their
   typesetting and the FLIP position animation remain untouched). */
.paper[data-drag-kind="words"],
.paper[data-drag-kind="words"] .transcript-token { cursor: grabbing; }
.paper[data-drag-kind="words"] .transcript-token.sel {
  background: var(--accent-sel-hot);
  box-shadow: 0 2px 0 color-mix(in oklab, var(--accent) 72%, transparent);
}
/* any hand override (kept-whole OR hand-tightened): amber tick, policy-immune */
.sil.pinned b { border-bottom: 1px solid var(--accent); }
/* …but a STRICKEN chip never wears the pin underline — strike + underline is
   visual noise; stricken silences style identically to stricken words (Jesse).
   The pin still counts in the release-pins UI; it just doesn't double-mark. */
.sil-tight.pinned b { border-bottom: none; }

/* ── T2 (c) / TB3: a kept silence the playhead is currently SOUNDING ─────────
   A kept pause plays through as continuous source absorbed into the clip, so
   the chip becomes the current object. It wears the SAME playhead grammar a
   word does — amber wash + the inside-perimeter stroke HERE on the chip, and the
   growing fill + pause caret are the ONE unified .playhead SVG mark, now mounted
   on the chip box (view.applyGapChip → placeMark). The old ::after run/caret
   morph is gone (TB3): one corner-travel implementation, zero copies, so pausing
   mid-silence corners exactly like pausing on a word. Feel lives in the .playhead
   block below (Jesse's lane); this rule is just the chip's current-object wash. */
.sil.cur {
  /* NO perimeter stroke (Jesse, 2026-06-10): current = wash + the bottom run,
     same grammar as words — the full outline read as ugly chrome, not state */
  color: var(--ink);
}
/* T9 laws 2+3 for chips — the IDENTICAL matrix (T7d), same hot tokens; placed
   after .sil-tight:hover so a selected tight chip hovers its fill winner */
/* pretext-lab: same rule as words — current+selected chip reads as just selected */
.sil.sel.cur { box-shadow: var(--join-w, 0) 0 0 0 var(--accent-sel); }
.sil.sel:hover { box-shadow: var(--join-w, 0) 0 0 0 var(--accent-sel); }
.sil.sel.cur:hover { box-shadow: var(--join-w, 0) 0 0 0 var(--accent-sel); }
/* T12: a GHOST chip — the silence a strike removed, kept for shape. Provenance,
   not affordance: the stricken-word voice (rust-dim ink, struck label), default
   cursor, and NO hover wash or ink lift even selected — hover never animates a
   corpse (states.md). Placed after every chip hover rule so cascade order wins
   the specificity ties. Values are placeholders per the states.md row (Jesse's
   lane). .cur is unreachable on a ghost by construction (severed gaps aren't in
   clips), so only hover/sel need silencing. */
/* pretext-lab: a ghost has NO button but keeps the live chip's reserved width (a
   strike must not reflow). So CENTER the label in that width (kills the left-heavy
   imbalance) and draw the strike as a full-width line across the whole box — it
   crosses the label AND the empty button space, one continuous red rule, instead
   of a short strike that stops at the text. */
.sil-ghost, .sil-ghost:hover { color: var(--rust-dim); background: none; cursor: default; justify-content: center; position: relative; }
.sil-ghost b { text-decoration: none; }   /* the line is the full-width ::after now, not a text strike */
.sil-ghost::after {
  content: ''; position: absolute; pointer-events: none;
  left: var(--geo-chip-pad, 4px); right: var(--geo-chip-pad, 4px); top: 50%;
  border-top: 1px solid var(--rust-dim);   /* stays RED even when selected (Jesse) — not currentColor, which goes ink on selection */
}
.sil-ghost.strike-sweeping::after { opacity: 0; }
/* T11: still selectable — the span's wash applies, rust stays readable in it
   (law 4); no hot step on hover (same corpse rule) */
/* pretext-lab: same as struck words — a selected ghost chip keeps the BLUE
   selection; only the TEXT goes legible ink, the red strike (from .sil-ghost b)
   stays. No rust/green highlight — selection background is one channel. */
.sil-ghost.sel, .sil-ghost.sel:hover { background: var(--accent-sel); box-shadow: var(--join-w, 0) 0 0 0 var(--accent-sel); color: var(--ink-dim); }  /* match the struck-WORD ink (.word.del.sel) — was --ink, read brighter than its neighbours */
/* T11: selection beats span-hot on chips too (fill winner, no hot step — the
   chip's quieter-by-design asymmetry holds) */
.sil-ghost.sel.span-hot, .sil-ghost.sel.span-hot:hover { background: var(--accent-sel); }

/* the unified playhead mark — ONE svg element holding ONE L-path (T1 slice B):
   playing → the lit dash parks over the underline; paused → it retreats around
   the bottom-left corner onto the inside-left edge, then blinks. Geometry + the
   dash metrics (--ph-edge/--ph-line/--ph-total) are set inline from Layout
   (analytic, virtualization-proof). The CSS owns only the motion + the feel —
   durations/curve/blink rhythm are intentionally tunable here (jesse's lane). */
.playhead {
  position: absolute;
  overflow: visible;
  pointer-events: none;
  z-index: 2;
}
.playhead[hidden] { display: none; }
.playhead path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: butt;   /* the spec's flat ends */
  stroke-linejoin: miter; /* a crisp square corner, not a bevel */
}
/* the lit dash is a <length> window followed by a gap longer than the whole
   path (so it never repeats); its length + offset park it over one of the two
   ends. Animating both at once slides it AROUND the corner between them. */
.playhead.playing path {
  stroke-dasharray: var(--ph-line) var(--ph-total);
  stroke-dashoffset: calc(-1 * var(--ph-edge));   /* parked over the underline */
  animation: ph-untravel 220ms cubic-bezier(.4, 0, .2, 1);
}
.playhead.paused path {
  stroke-dasharray: var(--ph-edge) var(--ph-total);
  stroke-dashoffset: 0;                            /* retreated onto the left edge */
  animation:
    ph-travel 220ms cubic-bezier(.4, 0, .2, 1),
    ph-blink 1100ms 220ms infinite;                /* blink ONLY after the travel lands */
}
@keyframes ph-travel {   /* underline → corner → left edge, on pause */
  from { stroke-dasharray: var(--ph-line) var(--ph-total); stroke-dashoffset: calc(-1 * var(--ph-edge)); }
  to   { stroke-dasharray: var(--ph-edge) var(--ph-total); stroke-dashoffset: 0; }
}
@keyframes ph-untravel { /* and back the way it came, on play */
  from { stroke-dasharray: var(--ph-edge) var(--ph-total); stroke-dashoffset: 0; }
  to   { stroke-dasharray: var(--ph-line) var(--ph-total); stroke-dashoffset: calc(-1 * var(--ph-edge)); }
}
/* hard on/off — a planted caret blink, not a fade (the 0.01% ramp is instant).
   Asymmetric duty cycle (Jesse, 2026-06-10): the caret DWELLS then winks —
   ~65% on / 35% off at a calmer 1.1s period beats a 50/50 strobe. */
@keyframes ph-blink { 0%, 64.99% { opacity: 1; } 65%, 100% { opacity: 0; } }
/* P9(d) — the ONE reduced-motion tier. The attribute (not the media query)
   is the key: main.ts resolves pref-or-OS through applyMotion, so the OS
   default and the in-app toggle land on the same selector instead of forking
   into two rule sets that drift. MOTION dies here; informational fades stay —
   the caret blink (it carries play-state), the word-flash locator, hover
   reveals (color/opacity, not movement). */
:root[data-motion="reduced"] .playhead.playing path { animation: none; }                  /* snap, no travel */
:root[data-motion="reduced"] .playhead.paused path { animation: ph-blink 1100ms infinite; } /* snap, but the blink persists */
:root[data-motion="reduced"] .rail,
:root[data-motion="reduced"] .transcript { transition: none !important; }  /* summoned sheets snap, like layout-snap holds */
@keyframes flashin { from { background: var(--accent-soft); } to { background: none; } }
.word.flash { animation: flashin 1.2s ease-out; }

/* Word-granular drop: a crisp caret plus a quiet wash in the exact gap. The
   double shadow is intentionally local; paragraph drops use the full-width rule
   below, so the two destinations cannot be mistaken for one another. */
.word.drop-left { box-shadow: -2px 0 0 var(--accent), -5px 0 0 var(--accent-wash); }
.word.drop-right { box-shadow: 2px 0 0 var(--accent), 5px 0 0 var(--accent-wash); }
.block.dragging { opacity: 0.3; }   /* DRAG-GHOST-1: the drag ghost reads lighter (Jesse feel-tune) */
/* CDR-1 — a folded chapter band is a drag handle (reorder the collapse-all index):
   signal it with the grab cursor, and grabbing flips it to grabbing. */
.chap.collapsed[draggable="true"] { cursor: grab; }
.chap.collapsed.dragging { cursor: grabbing; }

/* TRAIL-RETIRE-1: the `.trail` append-dropzone (+ its `.drop-into` affordance) is
   retired. It was always pointer-events:none, so it never caught a real drop; the
   interior merge it named is resolved analytically (a drop at a section's tail →
   end+1 word-granular). Nothing styles it any more. */

/* T10: current-section presence — the playhead's section, quieter than any
   selection. Placeholder value derived from the accent token; the look lane
   owns the final voice, T9's matrix owns compositing (sel-section follows in
   the cascade, so selection paints over presence when both apply). */
.block.cur-section {
  background: var(--presence-wash);
  border-radius: 5px;
}

/* section-scope: the broad selection tints the whole paragraph. The 8px halo gives
   the section content a touch of breathing room inside its highlight (still short of
   the gap between blocks, so two selected sections never bump). */
.block.sel-section {
  background: var(--accent-wash);
  border-radius: 5px;
  box-shadow: 0 0 0 8px var(--accent-wash);
}

/* FEEL-0710-2 (Jesse, flagged 3×): a word-span that ends mid-paragraph gives its
   boundary section(s) a fill too — but a SUBTLER one than a fully-covered section:
   the accent-wash at reduced strength, and NO halo (the 8px halo is the full-cover
   tier's tell). Both the opening and the closing boundary carry it, so the band
   never fills one edge paragraph and drops the other. Zero new color — a weaker mix
   of the SAME --accent-wash token (AT-1). */
.block.sel-section-partial {
  background: color-mix(in oklab, var(--accent-wash) 55%, transparent);
  border-radius: 5px;
}

/* chapter-scope (CS-2): the chapter is the BROADER tier — same accent-wash token
   (zero new color, the AT-1 discipline), a WIDER 12px halo so a whole-chapter
   span reads as one bigger unit than a section (kept 4px wider than .sel-section's
   8px so the tier contrast holds). Placed AFTER .sel-section so a chapter span
   composites over any incidental section paint on a member block. */
.block.sel-chapter {
  background: var(--accent-wash);
  border-radius: 5px;
  box-shadow: 0 0 0 12px var(--accent-wash);
}

/* section-granular drop: ONE full-width rule per boundary, drawn below the
   upper block ('above' survives only at the topmost boundary — TB5). No
   position override here: .block is already absolutely positioned, and a
   `position: relative` on the hovered block yanks it into document flow. */
.block.drop-above::before, .block.drop-below::after {
  content: ""; position: absolute; left: -8px; right: -8px; height: 2px;
  background: var(--accent); border-radius: 2px;
  box-shadow: 0 0 0 3px var(--accent-wash);
}
.block.drop-above::before { top: -10px; }
.block.drop-below::after { bottom: -10px; }

/* DRAG-TACTILE-1 — the drop boundary opens a REAL gap: the sections at/after the
   insertion point slide down so there's visible room to drop into. Pure transform
   (compositor-only, no reflow), and gated to an ACTIVE drag (`.paper.dnd`) so it
   never touches the FLIP transforms outside a drag (which set their own inline
   transition and win regardless). Coherence rests on two things done in JS, NOT here:
   (1) the resolver reads the static pretext (dropTargetAt staticOnly) so the moving
   content can't feed back — no judder; (2) the indicator repaints only when the
   boundary changes (gestureBind lastDropKey) so this transition completes instead of
   resetting every frame. Because the analytic resolver reports a between-sections gap
   as 'below' the UPPER section, the insertion line (::after on that upper, unmoving
   block) stays glued to the cursor while the space opens beneath it — no line drift. */
/* The base-state transition governs the RETURN (block eases back when the boundary
   leaves it) — 160ms on the FLIP easing. */
.paper.dnd .block { transition: transform 160ms cubic-bezier(.22, .9, .27, 1); }
/* Only the ONE block adjacent to the insertion scoots aside — "this one's getting
   out of the way for you" — the rest of the doc holds still. A restrained 10px
   nudge is enough to reveal the boundary without making the paragraph jump. Between-sections gaps
   resolve as 'below' the upper block, so its immediate next sibling is the one that
   moves; the topmost boundary is the lone 'above' case (the first block itself).
   The opening uses a short 90ms settle: fast enough to track the hand, but long
   enough to read as the document making room. The base rule above eases it closed. */
.paper.dnd .block.drop-above,
.paper.dnd .block.drop-below + .block {
  transform: translateY(10px);
  transition: transform 90ms cubic-bezier(.22, .9, .27, 1);
}
:root[data-motion="reduced"] .paper.dnd .block { transition: none; }

/* CDR-2 — the collapse-all index reorders a WHOLE chapter, a coarser move than a
   section nudge, so its drop boundary wears a bolder insertion bar than the section
   hairline above: a taller 3px accent pill running a touch wider, wrapped in a soft
   accent-wash glow — the SAME tier-contrast language the sel-chapter halo speaks
   (12px vs the section's 8px). Pure restyle of the drop-above/drop-below classes the
   one DnD mechanism already sets on a band; no new token (accent + accent-wash), no
   new class. */
.block.chap.collapsed.drop-above::before,
.block.chap.collapsed.drop-below::after {
  height: 3px; left: -12px; right: -12px;
  box-shadow: 0 0 0 3px var(--accent-wash);
}

/* SELECTS-1b — the transcript SCOPE menu. Its resting footprint is one compact
   “Show: All” trigger; the complete one-of-four choice set lives in a popover.
   This keeps navigation available without spending the cut band's width. */
.lens-strip-slot {
  position: relative;
  display: flex; align-items: center; justify-content: flex-end; flex: none;
}
.lens-strip { position: relative; display: flex; align-items: center; }
.lens-trigger {
  min-height: 28px; display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 6px; border: 1px solid transparent; border-radius: 5px;
  background: transparent; color: var(--ink-dim); cursor: pointer;
}
.lens-trigger:hover,
.lens-trigger:focus-visible,
.lens-trigger[aria-expanded="true"] {
  border-color: color-mix(in oklab, var(--line) 72%, transparent);
  background: color-mix(in oklab, var(--ink) 4%, transparent);
  color: var(--ink);
}
.lens-label {
  font: 500 .75rem/1.2 var(--f-ui); letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--ink); user-select: none;
}
.lens-value { font: 500 .75rem/1.2 var(--f-ui); letter-spacing: .04em; text-transform: uppercase; }
.lens-chevron {
  color: var(--ink-faint); font: 600 12px/1 var(--f-ui);
  transform: translateY(-1px); transition: transform 120ms ease-out;
}
.lens-trigger[aria-expanded="true"] .lens-chevron { transform: rotate(180deg) translateY(1px); }
.lens-menu {
  position: absolute; top: calc(100% + 4px); right: 0; z-index: var(--layer-popover);
  min-width: 148px; padding: 4px;
  border: 1px solid var(--line); border-radius: 7px;
  background: var(--surface); box-shadow: 0 12px 28px rgb(0 0 0 / .28);
}
.lens-menu[hidden] { display: none; }
.lens-menu .lens-seg {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  min-height: 32px; padding: 6px 9px; border: 0; border-radius: 4px;
  background: transparent; color: var(--ink-dim);
  font: 500 12px/1.2 var(--f-ui); text-align: left; cursor: pointer;
}
.lens-seg {
  border: 0; background: transparent; cursor: pointer;
}
.lens-menu .lens-seg:hover,
.lens-menu .lens-seg:focus-visible { background: var(--wash); color: var(--ink); }
.lens-menu .lens-seg.lens-on { color: var(--accent); }
.lens-menu .lens-seg.lens-on::after { content: '✓'; color: var(--accent); }
.lens-apply {
  width: calc(100% - 8px); min-height: 30px; margin: 4px 4px 0; padding: 5px 7px;
  border: 0; border-top: 1px solid var(--line); background: transparent;
  border-radius: 4px; font-size: 11px; cursor: pointer;
  color: var(--star-ink, #d9a441);
}
.lens-apply:hover { background: color-mix(in srgb, var(--star-ink, #d9a441) 14%, transparent); }
/* the armed "Sure?" reads as consequential — the same rust register a strike wears */
.lens-apply.lens-armed { color: var(--rust, #c46a5a); background: color-mix(in srgb, #c46a5a 14%, transparent); }

/* SELECTS-1d — the empty-lens voice: quiet, centered, unmistakably intentional. */
.lens-empty {
  padding: 56px 24px;
  text-align: center;
  color: var(--ink-faint);
  font-size: 14px;
  user-select: none;
}
.lens-empty-message { margin: 0; }
.lens-empty-clear {
  margin-top: 16px;
  padding: 8px 14px;
  border: 1px solid var(--ink-faint);
  border-radius: 6px;
  background: transparent;
  color: var(--ink);
  font: 500 14px/1.4 var(--f-ui);
  cursor: pointer;
}
.lens-empty-clear:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); }
.lens-empty-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Chapter playback occupies the heading underline, including folded headings. */
.chead:has(.chapter-progress) { border-bottom: none; }
.chapter-progress {
  position: absolute; left: 18px; right: 4px; bottom: -8px; height: 12px;
  cursor: ew-resize; touch-action: none; user-select: none;
}
.chapter-progress::before, .chapter-progress::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 2px;
  height: 2px; border-radius: 2px; background: currentColor;
}
.chapter-progress::before { opacity: .14; }
.chapter-progress::after { opacity: .48; transform: scaleX(var(--chapter-progress, 0)); transform-origin: left; }
.chapter-progress:hover::before { opacity: .24; }
.chapter-progress:hover::after { opacity: .65; }
.chapter-progress[aria-disabled="true"] { cursor: default; }
/* ------------------------------------------------ rail */
.rail {
  border-left: 1px solid var(--line);
  background: var(--bg-deep);
  overflow-y: auto; min-height: 0;
  display: flex; flex-direction: column;
}
/* SUMMONED tier — the shelf. ONE consistent home for any non-docked region,
   parked off-canvas until its handle summons it. The shelf's EDGE is solved
   (body[data-shelf-edge]): a right side-sheet on wide screens, a bottom sheet
   on narrow ones — same tier, same handles, different attachment. Demotion
   moves a region here; nothing is ever display:none'd away.
   Transitions run ONLY on user open/close — body.layout-snap suppresses them
   during tier/edge flips so resizing never shows panels flying around. */
body[data-tier-rail="summoned"] .rail,
body[data-tier-transcript="summoned"] .transcript {
  position: fixed; z-index: var(--layer-drawer);
  background: var(--bg-deep);
  transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
body.layout-snap .rail,
body.layout-snap .transcript { transition: none !important; }
body[data-tier-transcript="summoned"] .transcript { background: var(--bg); }   /* .tscroll scrolls */

/* edge: right — side sheet */
body[data-shelf-edge="right"][data-tier-rail="summoned"] .rail,
body[data-shelf-edge="right"][data-tier-transcript="summoned"] .transcript {
  top: 49px; right: 0; bottom: 58px;
  border-left: 1px solid var(--line);
  transform: translateX(103%);
  box-shadow: -16px 0 40px oklch(0 0 0 / 0.35);
}
body[data-shelf-edge="right"][data-tier-rail="summoned"] .rail { width: min(360px, 92vw); }
body[data-shelf-edge="right"][data-tier-transcript="summoned"] .transcript { width: min(560px, 94vw); }

/* edge: bottom — bottom sheet with a grab notch (the phone presentation) */
body[data-shelf-edge="bottom"][data-tier-rail="summoned"] .rail,
body[data-shelf-edge="bottom"][data-tier-transcript="summoned"] .transcript {
  left: 0; right: 0; bottom: 58px; top: auto;
  height: min(62vh, 560px);
  border-top: 1px solid var(--line);
  border-radius: 10px 10px 0 0;
  /* park FULLY off-screen: 100% + the bottom anchor + shadow reach */
  transform: translateY(calc(100% + 120px));
  box-shadow: 0 -16px 40px oklch(0 0 0 / 0.4);
  padding-top: 14px;
}
body[data-shelf-edge="bottom"][data-tier-rail="summoned"] .rail::before,
body[data-shelf-edge="bottom"][data-tier-transcript="summoned"] .transcript::before {
  content: ""; position: absolute; top: 6px; left: 50%;
  width: 36px; height: 4px; border-radius: 2px;
  transform: translateX(-50%);
  background: var(--line);
}

body[data-shelf="rail"] .rail,
body[data-shelf="transcript"] .transcript { transform: none !important; }

.toolsback {
  position: fixed; inset: 0; z-index: var(--layer-drawer-scrim); display: none;
  background: var(--scrim);
}
/* main intentionally isolates the editor paint stack. Raise that complete
   context only while a summoned region is open: the scrim can then cover
   header/transport/content, while the summoned rail/transcript
   remains the sole interactive surface above it. */
body[data-shelf="rail"] main,
body[data-shelf="transcript"] main { z-index: var(--layer-drawer); }
body[data-shelf="rail"] .toolsback,
body[data-shelf="transcript"] .toolsback { display: block; }

/* layout pins are moot when the solver has no docked choices / in watch */
/* ------------------------------------------------ the dock
   One chrome region; tabs pick the primary PANEL (tools | notes). */
.dock-tabs {
  display: flex; flex: none;
  border-bottom: 1px solid var(--line);
}
body[data-mode="watch"] .dock-tabs button[data-tab="tools"] { display: none; }
/* A review grant is the full editor document in a read-only posture. Keep playback,
   chapters, timeline, transcript navigation, and Notes; remove controls whose only
   meaning is to mutate the cut. The model gate remains the security boundary. */
body[data-editor-access="review"] .dock-tabs button[data-tab="tools"],
body[data-editor-access="review"] .dock-pane[data-pane="tools"],
body[data-editor-access="review"] #editmenu,
body[data-editor-access="review"] #addchapter,
body[data-editor-access="review"] .grip,
body[data-editor-access="review"] .chead-del,
body[data-editor-access="review"] .chapters-index-del,
body[data-editor-access="review"] .chapters-index-row--new { display: none; }
body[data-editor-access="review"] .speaker { pointer-events: none; }
/* Reviewers cannot mutate the model, but they must still be able to bring the
   transcript forward from Watch. main.ts relabels this safe presentation
   switch to “read”; the capability gate, not CSS, keeps edit verbs inert. */

/* Token guests never inherit the account's client/server export affordances. Export
   requires an explicit grant; current review/edit presets confer none. */
body[data-guest] .dock-pane[data-pane="tools"] [data-sec="export"] { display: none; }
.dock-tabs button {
  flex: 1; border: none; border-radius: 0;
  padding: 9px 8px; font-size: var(--fs-chip);
  color: var(--ink-dim); text-transform: uppercase; letter-spacing: 0.1em;
  border-bottom: 2px solid transparent;
}
.dock-tabs button.on { color: var(--ink); background: var(--accent-soft); }
.dock-tabs button:hover { color: var(--ink); }
.dock-tabs button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Notes owns the contextual transcript lens. It appears only while Notes and
   the transcript are both present; inline previews never consume workspace width. */
.notes-contextbar { display: none; flex: none; border-bottom: 1px solid var(--line); }
body[data-tier-transcript="docked"] #dock[data-tab="notes"] .notes-contextbar:not([hidden]),
body[data-shelf="transcript"] #dock[data-tab="notes"] .notes-contextbar:not([hidden]) {
  display: block;
}
.notes-inline-control {
  min-height: 40px; padding: var(--sp2) var(--sp4);
  display: flex; align-items: center; gap: var(--sp2);
  color: var(--ink-dim); cursor: pointer;
}
.notes-inline-label {
  min-width: 0; flex: 1;
  display: inline-flex; align-items: center; gap: var(--sp2);
  font: 600 var(--fs-control)/1.25 var(--f-ui);
}
.notes-inline-label .icn { width: 16px; height: 16px; flex: none; }
.notes-inline-count {
  color: var(--ink-faint);
  font: 600 var(--fs-chip)/1 var(--f-tc);
  font-variant-caps: normal; letter-spacing: 0;
}
.notes-inline-input {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
}
.notes-inline-switch {
  width: 28px; height: 16px; padding: 2px; flex: none;
  display: flex; align-items: center;
  border: 1px solid var(--line); border-radius: 9px;
  background: var(--bg); transition: border-color 120ms ease-out, background-color 120ms ease-out;
}
.notes-inline-switch span {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--ink-faint); transform: translateX(0);
  transition: transform 120ms ease-out, background-color 120ms ease-out;
}
.notes-inline-input:checked + .notes-inline-switch {
  border-color: color-mix(in oklab, var(--accent) 45%, var(--line));
  background: color-mix(in oklab, var(--accent) 10%, var(--bg));
}
.notes-inline-input:checked + .notes-inline-switch span {
  background: var(--accent); transform: translateX(12px);
}
.notes-inline-input:focus-visible + .notes-inline-switch {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
:root[data-motion="reduced"] .notes-inline-switch,
:root[data-motion="reduced"] .notes-inline-switch span { transition: none; }
.dock-pane { display: none; }
#dock[data-tab="tools"] .dock-pane[data-pane="tools"],
#dock[data-tab="notes"] .dock-pane[data-pane="notes"],
#dock[data-tab="fields"] .dock-pane[data-pane="fields"] {
  display: flex; flex-direction: column; flex: 1; min-height: 0;
  overflow-y: auto;
}

/* The Notes dock is React-owned and styled by CommentsDock.module.css. Only the
   engine-owned, geometry-anchored margin projection remains global below. */
.comments-dock-react-host { display: contents; }

.modepick { display: flex; gap: 2px; }
.modepick button[data-mode] {
  padding: 3px 10px; font-size: var(--fs-chip); border-radius: 3px;
  color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.08em;
}
.modepick button[data-mode].on { color: var(--accent); background: var(--accent-soft); }
.monitor {
  padding: var(--sp4);
  background: var(--bg-deep);
  display: flex; flex-direction: column; min-height: 0; min-width: 0;
}
.monitor video {
  width: 100%; background: black;
  border-radius: 4px; display: block;
}
/* side: monitor row is auto-height — the video's own aspect ratio sets it */
main[data-layout="side"] .monitor { border-left: 1px solid var(--line); border-bottom: 1px solid var(--line); }
main[data-layout="side"] .monitor video { aspect-ratio: var(--ar, 16/9); height: auto; }
/* top / tall: the monitor AREA is solved — the video fills it, letterboxing
   whatever sub-pixel remainder the solve leaves */
main[data-layout="top"] .monitor { border-bottom: 1px solid var(--line); }
main[data-layout="tall"] .monitor { border-left: 1px solid var(--line); }
main[data-layout="top"] .monitor video,
main[data-layout="tall"] .monitor video { flex: 1; min-height: 0; object-fit: contain; }
/* MOBILE-2e-video (B1, the OPEN half) — a PORTRAIT clip no longer letterboxes as a
   tiny strip inside the full-width phone banner. In the single-column `top` layout
   the video BOX itself takes the media aspect (aspect-ratio: --ar) and centres in
   the solved band, capped by BOTH dims (max-width/height 100%): a portrait clip
   renders as a tall centred frame that reclaims the wasted vertical band, a 16:9
   clip stays width-bound exactly as before (max-width wins, same box as flex:1
   gave). `top` is the phone single-column stack only — `tall`/`side` (the desktop
   3-/2-column layouts) keep the rule above untouched, so desktop is unaffected. */
main[data-layout="top"] .monitor video {
  flex: 0 1 auto; width: auto; height: auto;
  max-width: 100%; max-height: 100%;
  aspect-ratio: var(--ar, 16 / 9);
  margin: auto;
}
main[data-layout="tall"] .rail { border-left: 1px solid var(--line); }
/* media-dead notice (P6 §3) — the .boot register worn in the monitor: quiet,
   text-first, no theater. Numbers are placeholders in the look lane. */
.medianote {
  color: var(--ink-faint); font-style: italic; font-size: var(--fs-ui);
  padding: var(--sp2) 0; text-align: center;
}
.transport {
  display: flex; align-items: center; gap: var(--sp2);
  margin-top: var(--sp3);
}
.playbtn {
  /* FULLY fixed box — ❚❚ and ▶︎ have different glyph metrics; width alone
     left the HEIGHT free to bump the whole transport row (Jesse) */
  width: 38px; height: 28px; padding: 0; line-height: 26px;
  font-size: var(--fs-control); flex: none; text-align: center;
}
.mutebtn {
  /* same fixed-box discipline — speaker glyphs swap, the box never moves */
  width: 34px; height: 28px; padding: 0; line-height: 26px;
  flex: none; display: inline-flex; align-items: center; justify-content: center;
}
.mutebtn.muted { color: var(--stricken); border-color: var(--stricken); }
.transport .tc {
  font-size: var(--fs-note); flex: none;
  /* Each asset uses one contextual field count, so values stay aligned without
     reserving hour-width space for a short cut. */
  min-width: 5ch; text-align: right;
}
.rates { display: flex; gap: 2px; flex: none; }
.rates button { padding: 3px 6px; font-size: var(--fs-chip); border-radius: 3px; color: var(--ink-dim); }
.rates button.on { color: var(--accent); background: var(--accent-soft); }

.panel { padding: var(--sp4); border-bottom: 1px solid var(--line); }
.panel h3 {
  margin: 0 0 var(--sp3);
  font-size: var(--fs-label); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--ink-faint);
}
/* T6 — collapsible tools sections. The h3 holds a real button (Tab + Enter +
   aria-expanded); collapsing hides every sibling of the h3, which also drops
   the body's controls out of the tab order for free. Height snaps — nothing
   animates, nothing for reduced-motion to disable. Glyphs (⌄/›) and ink are
   PLACEHOLDERS — weight and voice are Jesse's lane. */
.sec-toggle {
  border: 0; border-radius: 0; padding: 0; background: none;
  text-transform: inherit; letter-spacing: inherit;
}
.sec-toggle::before { content: '⌄ '; color: var(--ink-deco); }
.sec-toggle[aria-expanded="false"]::before { content: '› '; }
.sec-toggle:hover { color: var(--ink-dim); }
.sec-toggle:active { background: none; }
.panel.closed > :not(h3) { display: none; }
.panel.closed h3 { margin-bottom: 0; }
.btnrow { display: flex; gap: var(--sp2); flex-wrap: wrap; }
.btnrow.stack { flex-direction: column; }
.wide { width: 100%; text-align: left; display: flex; justify-content: space-between; align-items: baseline; }
.hint { margin-top: var(--sp3); font-size: var(--fs-note); color: var(--ink-faint); line-height: 1.5; }
.hint b { color: var(--ink-dim); }

/* FACE-F2b — the export panel as a DESTINATIONS surface. Each `.dest` is one place the cut
   can go (Captions · Chapters · Data): its hidden compatibility commands stack under a
   quiet uppercase label. Server MP4/Premiere actions now live in the React Export modal. */
.dest { display: flex; flex-direction: column; gap: var(--sp1); }
.dest-label {
  font-size: var(--fs-note); color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.dest .hint { margin-top: var(--sp1); }

.toggle {
  display: grid; grid-template-columns: auto 1fr; gap: 2px var(--sp3);
  align-items: baseline; margin-top: var(--sp3); cursor: pointer; font-size: var(--fs-control);
}
.toggle input { accent-color: var(--accent); margin: 0; position: relative; top: 1px; }
.toggle em {
  grid-column: 2; font-style: normal; font-size: var(--fs-note); color: var(--ink-faint);
}
/* Shared preset-segment specimen used by the state matrix. Product Transcript
   Settings owns its equivalent locally in the React component module. */
.tsz-btns { display: inline-flex; gap: 4px; }
.tsz-btns button {
  min-width: 26px; padding: 2px 0; font-size: var(--fs-chip);
  border-radius: 3px; color: var(--ink-dim);
}
.tsz-btns button:hover { color: var(--ink); }
.pw-btns button { padding: 2px 6px; }
.tsz-btns button[aria-pressed="true"] {
  background: var(--accent); border-color: transparent;
  color: var(--bg-deep); font-weight: 600;
}
.ctl { margin-top: var(--sp3); }
.ctl .toggle { margin-top: 0; }
.subline {
  margin: 2px 0 0 24px; font-size: var(--fs-note); color: var(--ink-faint); min-height: 14px;
}

/* the verb bar — selection actions DOCKED to the region's bottom edge: a real
   row of the transcript column, so it never floats over text and never fights
   other floaters. Grows into the text-correction / content editor later. */
/* the .selbar / #selpanel rules are RETIRED with the docked verb bar
   (popover-only verbs — Jesse 2026-07-09); the popover carries its own skin. */

/* T11 §4 — the selection popover: the complete action set appears at the settled
   selection's focus end. It lives IN the paper (so it scrolls with the transcript
   natively). Nothing precedes the verbs: selection itself supplies the context. */
.selpop { position: absolute; z-index: 12; }
.selpop[hidden] { display: none; }   /* author display beats UA [hidden] */
/* The action tray yields once the selection itself is being carried. Keeping it
   visible competes with the insertion target and can sit under the pointer. */
.paper[data-drag-kind="words"] .selpop { opacity: 0; pointer-events: none; }
.selpop-verbs {
  display: flex; align-items: center; gap: 1px;
  width: max-content; max-width: min(420px, calc(100vw - 24px));
  padding: 4px;
  color: var(--ink-dim); background: var(--surface);
  border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 10px 28px -8px var(--scrim), 0 2px 8px var(--scrim);
}
.selpop[data-align="center"] .selpop-verbs {
  transform-origin: center center;
  translate: -50% 0;
}
.selpop-verbs button {
  white-space: nowrap; min-height: 29px; padding: 4px 8px;
  display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--fs-control); font-weight: 550; color: inherit;
  background: transparent; border-color: transparent; border-radius: 5px;
  transition: color 110ms, background-color 110ms, transform 90ms;
}
.selpop-verbs button:hover { color: var(--ink); background: var(--wash); border-color: transparent; }
.selpop-verbs button:active { transform: translateY(1px); }
.selpop-icon {
  width: 16px; height: 16px; flex: 0 0 16px;
  fill: none; stroke: currentColor; stroke-width: 1.4;
  stroke-linecap: round; stroke-linejoin: round;
}
.selpop-verbs [data-verb="remove"] .selpop-icon { color: var(--rust); }
.selpop-verbs [data-verb="copy"] .selpop-icon,
.selpop-verbs [data-verb="star"] .selpop-icon { color: var(--accent); }
.selpop-verbs [data-verb="restore"] .selpop-icon { color: var(--moved); }
.selpop-verbs [data-verb="remove"]:hover { color: var(--rust); background: var(--rust-wash); }
.selpop-verbs [data-verb="restore"]:hover { color: var(--moved); background: var(--wash); }
.selpop[data-side="below"] { --selpop-enter-y: -5px; }
.selpop[data-side="above"] { --selpop-enter-y: 5px; }
.selpop-verbs.selpop-reveal { animation: selpop-reveal 170ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.selpop-verbs.selpop-reveal button { animation: selpop-action-in 150ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.selpop-verbs.selpop-reveal button:nth-of-type(2) { animation-delay: 14ms; }
.selpop-verbs.selpop-reveal button:nth-of-type(3) { animation-delay: 28ms; }
.selpop-verbs.selpop-reveal button:nth-of-type(4) { animation-delay: 42ms; }
@keyframes selpop-reveal {
  from { opacity: 0; transform: translateY(var(--selpop-enter-y)) scale(.965); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes selpop-action-in {
  from { opacity: 0; transform: translateX(-3px); }
  to { opacity: 1; transform: translateX(0); }
}
:root[data-motion="reduced"] .selpop-verbs,
:root[data-motion="reduced"] .selpop-verbs button { animation: none; }
/* R14 §8.2 — the suggestion margin: a recessed gutter docked in the paper's right
   margin (paper is position:relative, so this anchors to it like .selpop does), one
   dot per anchored suggestion. NEUTRAL/non-final feel this slice — recessed neutral
   hue (NOT the teal accent), narrow width, simple disc; the dot hue / width / shape
   pass is BANKED to feel-check-queue.md, not tuned blind. A dot graduates to a live
   button when a caller wires onOpen (thread-open slice) — clicking it opens the
   .suggestion-thread panel naming the proposed ops; clicking again toggles it shut.
   Off-screen markers emit no dot (the engine's locate→null), so this never floats. */
.suggestion-gutter {
  position: absolute; top: 0; right: -1.5rem;
  width: 0.5rem; height: 100%;
  pointer-events: none;          /* the gutter itself is inert; live dots/threads opt back in below */
}
.suggestion-rail { position: absolute; inset: 0; }
.suggestion-dot {
  position: absolute; left: 50%; width: 6px; height: 6px; margin-left: -3px;
  padding: 0; border: 0;
  border-radius: 50%;
  background: var(--ink-deco);   /* recessed neutral, feel-gated — never the accent */
  opacity: 0.6;
}
/* a clickable dot (thread-open slice) is a real button — opt back into pointers + a
   pointer cursor; the inert <div> form stays untouched by the gutter's none above. */
button.suggestion-dot { pointer-events: auto; cursor: pointer; }
button.suggestion-dot:hover, button.suggestion-dot:focus-visible { opacity: 1; }
/* a COMMENT dot rides the SAME rail/gutter/geometry (sin #5) and differs only by
   skin: a hollow RING vs the suggestion's solid disc — structurally distinct at a
   glance without committing a final hue (feel-banked, neutral — never the accent).
   Its click opens the dock thread, so it's always the live <button> form. */
.comment-dot {
  position: absolute; left: 50%; width: 6px; height: 6px; margin-left: -3px;
  padding: 0; border: 1.5px solid var(--ink-deco); /* ring, not fill — the distinct read */
  border-radius: 50%;
  background: transparent;
  opacity: 0.6;
}
button.comment-dot { pointer-events: auto; cursor: pointer; }
button.comment-dot:hover, button.comment-dot:focus-visible { opacity: 1; }

/* CM-LANE close/open (Jesse 2026-06-29) — the minimize/expand toggle at the comments lane's
   top center. */
.comment-lane-bg { display: none; }
.transcript[data-margin-open] .comment-lane-bg {
  display: block; position: absolute; top: 0; bottom: 0;
  left: var(--lane-x, 100%); right: 0; z-index: 1; pointer-events: none;
  background: color-mix(in oklab, var(--bg) 50%, var(--bg-deep));
}

.comment-lane-toggle { display: none; }
.transcript[data-margin-open] .comment-lane-toggle {
  display: flex; align-items: center; justify-content: center; gap: 4px;
  position: absolute; top: var(--sp4); z-index: 7;
  /* Centered within the right lane */
  left: calc(var(--lane-x, 100%) + (100% - var(--lane-x, 100%)) / 2); transform: translateX(-50%);
  width: auto; min-width: 22px; height: 22px; padding: 0 8px; border-radius: 11px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-dim);
  cursor: pointer; transition: color 140ms ease, border-color 140ms ease;
}
.comment-lane-toggle:hover { color: var(--ink); border-color: var(--ink-deco); }
/* the arrow glyph */
.comment-lane-arrow::before { content: "\203A"; font-size: 13px; line-height: 1; }
.transcript[data-margin-minimized] .comment-lane-arrow::before { content: "\2039"; }
/* CM-LANE (Jesse 2026-06-29) — the COLLAPSED comment marker is the author AVATAR disc
   (same recipe as the dock's .cmt-avatar), sitting in the reserved lane where its card
   would (left:100% = .paper's right edge, into the --margin-w strip), push-down-slotted
   by AVATAR_PITCH so two never overlap. Identity-bearing, not the neutral ring — a
   person at a glance; click opens the dock thread. This is the right-lane half of the
   symmetric collapsed-form (the left chapter lane's collapsed form is a marker icon). */
.comment-avatar.comment-marker {
  /* RIGHT-ANCHORED to the scroll area's right edge, same as the card (Jesse 2026-06-29) —
     the mini avatar hugs the scrollbar in the narrow collapsed strip, never floats inward. */
  position: absolute; left: auto; right: calc(-1 * var(--lane-pull, 0px)); z-index: 3;
  width: 22px; height: 22px; border-radius: 50%; padding: 0; border: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 600; line-height: 1; letter-spacing: 0.02em;
  color: var(--ink-on-media); background: oklch(0.56 0.072 var(--cmt-hue, 250));
  cursor: pointer; user-select: none;
  pointer-events: auto;   /* opt back into the inert gutter so a real click opens the thread */
}
.comment-avatar.comment-marker:hover, .comment-avatar.comment-marker:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
/* A fully CLOSED comment lane still leaves its spatial avatar pins in the transcript's
   slim paper-edge gutter. Hover/focus unfolds a compact read-only preview toward the
   transcript (away from the Notes dock); click remains owned by the avatar and opens the
   matching Notes thread. The UI's ordinary “closed gutter” is the minimized-but-open
   lane, while narrow/no-room layouts can fully reclaim the lane; both avatar forms share
   this behavior so “closed” never means silent. */
.comment-marker-preview {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  /* Viewport-fixed overlay: the persistent preview survives gutter repaints without
     becoming part of .tscroll's horizontal overflow geometry. */
  position: fixed; z-index: 14;
  width: min(16rem, calc(100vw - 5rem)); padding: var(--sp2) var(--sp3);
  border: 1px solid var(--line); border-radius: 4px;
  background: color-mix(in oklab, var(--surface) 96%, var(--bg));
  box-shadow: 0 8px 24px var(--scrim);
  color: var(--ink); font: 500 var(--fs-note)/1.4 var(--f-ui); letter-spacing: 0;
  text-align: left; white-space: normal;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translate(4px, -50%);
  transition: opacity 120ms ease, transform 120ms ease, visibility 120ms step-end;
  overflow: hidden;
}
.comment-marker-preview[data-open="true"] {
  opacity: 1; visibility: visible; transform: translate(0, -50%);
  transition: opacity 120ms ease, transform 120ms ease, visibility 0s;
}
:root[data-motion="reduced"] .comment-marker-preview { transition: none; }
/* COMMENTS-INLINE CI-2/CI-4 — a margin comment's EXPANDED inline card (RFC §4): the
   density chooser's 'card' verdict swaps the dot for this, the dock's own row shown
   in the gutter. A READ surface + single entry point (one <button>): no composer ever,
   so its height stays the CI-1 analytic value and the gutter stays decoupled.
   READABLE-WIDTH pass (CI-4): the 0.5rem rail can't hold a legible card, so the card
   FLOATS — a fixed readable width anchored at the rail's right edge, expanding LEFT
   over the paper's right margin as an absolutely-positioned, shadowed, z-raised layer
   (the SAME decoupled-overlay pattern .suggestion-thread already uses on this gutter,
   sin #5). Out of flow ⇒ it perturbs NOTHING in the word column (Probe B 0-delta holds).
   The dot's solved Y rides in via inline `top`. Expansion DIRECTION + exact width stay
   a Jesse feel-gate (banked to feel-check-queue.md); 14rem matches the sibling thread. */
.comment-card {
  /* CM-LANE (Jesse 2026-06-29): the card lives in the RESERVED lane to the RIGHT of
     the text, never over it. `left: 100%` plants its left edge at `.paper`'s right edge
     (the rail spans `.paper`), so it expands RIGHTWARD into the `.tscroll` padding-right
     strip render() reserved (`--margin-w`) — two disjoint regions, word-overlap is
     impossible by construction. Width sizes to the lane (responsive squish) so it never
     spills past the reserved strip; the lane only opens when it clears MARGIN_MIN. */
  /* RIGHT-ANCHORED to the scroll area's right edge (Jesse 2026-06-29): the card's right
     edge sits at the scrollbar via `right: -(--lane-pull)` (published by render() = how far
     right of paper's edge the strip is), NOT at the centered paper's edge. So full cards
     and mini avatars both hug the scrollbar; the text just gains margin when the pane is
     wide. left:auto so the fixed width grows leftward from the anchored right edge. */
  position: absolute; left: auto; right: calc(-1 * var(--lane-pull, 0px)); z-index: 2;
  /* The gutter is pointer-events:none (words underneath stay clickable); every live
     child must opt BACK IN or real clicks fall through to the transcript. Without this
     the card's open-button + replies-toggle look interactive but a real click misses
     them (a synthetic .click() still fires — the trap that hid this). CM-LANE bite. */
  pointer-events: auto;
  /* RESPONSIVE width (Jesse 2026-06-29): render() picks a width in [MIN,MAX] from the pane
     size and publishes it as --card-w; the card MUST render at exactly that width because
     cardHeight() derives its wrap-chars (and thus line count) from the SAME width — render
     at any other width and the prediction drifts and cards overlap. Falls back to 14rem
     (the MIN) before the first publish. */
  width: var(--card-w, 14rem);
  border: 1px solid var(--line);
  border-radius: 6px; background: var(--bg-deep);
  box-shadow: 0 2px 8px rgb(0 0 0 / 0.12);   /* floats above the paper — a decoupled layer, like .suggestion-thread */
  font: inherit; color: var(--ink);
  overflow: hidden;   /* the open/toggle rows' hover wash clips to the rounded corners */
}
/* CM-LANE: the card is now a CONTAINER (div), not a single button — a root open-button,
   a show/hide-replies toggle, and the expanded reply rows are siblings (no nested
   interactive). focus-within so keyboarding into any child lights the card border. */
.comment-card:hover, .comment-card:focus-within { border-color: var(--ink-deco); }

/* Retained engine card skin. These selectors are deliberately scoped beneath
   `.comment-card`; the React dock has no global `.cmt-*` styling authority. */
.comment-card .cmt { display: flex; flex-direction: column; gap: var(--sp2); }
.comment-card .cmt-head { display: flex; align-items: center; gap: var(--sp2); flex-wrap: nowrap; }
.comment-card .cmt-avatar {
  display: inline-flex; width: 22px; height: 22px; flex: 0 0 auto;
  align-items: center; justify-content: center; border-radius: 50%;
  color: var(--ink-on-media); background: oklch(0.56 0.072 var(--cmt-hue, 250));
  font-size: 10px; font-weight: 600; line-height: 1; letter-spacing: 0.02em;
  user-select: none;
}
.comment-card .cmt-author {
  min-width: 0; overflow: hidden; color: var(--ink); font-size: var(--fs-ui);
  font-weight: 600; text-overflow: ellipsis; white-space: nowrap;
}
.comment-card .cmt-visibility,
.comment-card .cmt-resolved {
  color: var(--ink-faint); font-size: var(--fs-chip);
  letter-spacing: 0.08em; text-transform: uppercase;
}
.comment-card .cmt-visibility {
  display: inline-flex; align-items: center; gap: 4px;
}
.comment-card .cmt-visibility::before {
  width: 9px; height: 9px; flex: 0 0 auto; background-color: currentColor; content: '';
  -webkit-mask: var(--lock-mask) center / contain no-repeat;
  mask: var(--lock-mask) center / contain no-repeat;
  --lock-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='5' y='11' width='14' height='10' rx='2'/%3E%3Cpath d='M8 11V7a4 4 0 0 1 8 0v4'/%3E%3C/svg%3E");
}
.comment-card .cmt-body {
  display: -webkit-box; overflow: hidden; color: var(--ink); cursor: text;
  font-size: var(--fs-base); line-height: 1.5; white-space: pre-wrap;
  -webkit-box-orient: vertical; -webkit-line-clamp: 6;
  user-select: text; -webkit-user-select: text;
}
.comment-card .cmt-more {
  align-self: flex-start; margin-top: var(--sp1); color: var(--accent);
  font-size: var(--fs-chip);
}
.comment-card .cmt-actions { display: flex; min-height: 22px; align-items: center; gap: var(--sp2); }
.comment-card .cmt-action-icons { display: flex; margin-left: auto; align-items: center; gap: 2px; }
.comment-card .cmt-reply-btn,
.comment-card .cmt-resolve {
  display: inline-grid; width: 22px; height: 22px; padding: 0; place-items: center;
  border: 1px solid transparent; border-radius: 6px; background: transparent;
  color: var(--ink-dim); cursor: pointer;
}
.comment-card .cmt-reply-btn:hover,
.comment-card .cmt-resolve:hover { border-color: var(--line); background: var(--surface); color: var(--ink); }
.comment-card .cmt-reply-btn:focus-visible,
.comment-card .cmt-resolve:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.comment-card .cmt-replies { display: flex; flex-direction: column; gap: var(--sp2); }
.comment-card .cmt-reply .cmt-author { font-weight: 500; }

/* GLOBAL-ANCHOR-COMMENT-SURFACE — the pinned "general" slot at the comment lane's HEAD:
   the cut-level (global-anchor) notes the word-Y rail can't place (a note posted with no
   word under the playhead). Sticky to the top of the scroll so it rides the lane head as
   the transcript scrolls; anchored into the SAME reserved strip the cards use (right:
   -lane-pull, width --card-w) so it never overlaps the text — the disjoint-column
   invariant, reused. The gutter is pointer-events:none, so the slot opts its rows back in. */
.cmt-general {
  /* GENERAL-SLOT-CAP (ux board P2): the slot is sticky at the lane head, so an
     unbounded stack of general notes glued a 300px+ block over the transcript and its
     z-index:3 ECLIPSED the word-anchored cards (z:2) scrolling past underneath. Two
     teeth: (1) cap the block at 40vh + scroll internally so it never dominates the
     lane no matter how many globals exist; (2) drop below the word-card z (z:1 < the
     .comment-card z:2) so a word-anchored card always scrolls OVER the slot, never
     behind it — the sticky head yields to the card it would otherwise hide. */
  position: sticky; top: var(--sp2); z-index: 1;
  left: auto; right: calc(-1 * var(--lane-pull, 0px));
  width: var(--card-w, 14rem);
  max-height: 40vh; overflow-y: auto;
  pointer-events: auto;
  display: flex; flex-direction: column; gap: var(--sp2);
}
.cmt-general[hidden] { display: none; }
/* GENERAL-SLOT-CAP: the cap is a flex COLUMN, so its children default to flex-shrink:1
   and would COMPRESS to fit 40vh (each card squished, its .comment-card overflow:hidden
   clipping the body) instead of overflowing into the scroll. Pin them to natural height so
   the cap scrolls, never squishes — the whole point of the overflow-y:auto. */
.cmt-general > * { flex-shrink: 0; }
/* the reused inline cards are position:absolute in the rail — re-flow them in the slot */
.cmt-general .comment-card { position: static; }
/* MINIMIZED lane → the author-avatar disc, the SAME initials/hue recipe as the collapsed
   word marker (.comment-avatar.comment-marker) but STATIC in the slot's flow — a distinct
   class so it never pollutes the word-rail's .comment-marker set. */
.cmt-general-avatar {
  width: 22px; height: 22px; border-radius: 50%; padding: 0; border: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 600; line-height: 1; letter-spacing: 0.02em;
  color: var(--ink-on-media); background: oklch(0.56 0.072 var(--cmt-hue, 250));
  cursor: pointer; user-select: none; pointer-events: auto;
}
.cmt-general-avatar:hover, .cmt-general-avatar:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.transcript[data-margin-minimized] .cmt-general { width: auto; align-items: flex-end; }
/* the root READ surface — a full-width transparent button that opens the dock thread */
.comment-card-open {
  display: block; width: 100%; text-align: left;
  padding: var(--sp2); border: 0; background: none;
  font: inherit; color: inherit; cursor: pointer;
}
.comment-card-tc {
  display: inline-block; margin-top: var(--sp1);
  font: var(--fs-chip)/1 var(--f-tc); font-variant-numeric: tabular-nums;
  letter-spacing: 0.015em; color: var(--ink-faint);
}
.comment-card .cmt-replies-toggle {
  display: inline-flex; align-items: center; gap: var(--sp1);
  padding: 0; border: 0; background: none;
  font: inherit; font-size: var(--fs-chip); color: var(--ink-faint); cursor: pointer;
}
.comment-card .cmt-replies-toggle::before {
  content: "\25B8"; display: inline-block; font-size: 0.85em;   /* ▸ */
  transition: transform 120ms ease;
}
.comment-card .cmt-replies-toggle[aria-expanded="true"]::before { transform: rotate(90deg); }
.comment-card .cmt-replies-toggle:hover { color: var(--ink); }
/* the inline card's footer row (replies-toggle + reply/resolve) — inset to the card's sp2
   gutter so the disclosure caret clears the rounded border (Jesse 2026-06-29: it bumped the
   edge). Matches the open-button + replies padding, so the whole card reads on one margin. */
.comment-card .cmt-actions { padding: 0 var(--sp2); }
/* in the margin card the expanded reply rows read compact (the lane is tight) — no dock
   indent; the dock keeps its own .cmt-replies indent rule (left-border thread cue) above. */
.comment-card .cmt-replies {
  margin-left: 0; padding: 0 var(--sp2) var(--sp2); border-left: 0;
}
/* R14 §8.2 thread-open — the opened suggestion's panel: a neutral card naming the
   proposed ops, docked left of the dot rail and placed at the anchor's Y by the
   shared entryLocator (no second positioner). NEUTRAL/non-final feel this slice —
   the width, hue, and the accept/reject controls are BANKED to feel-check-queue.md. */
.suggestion-thread {
  position: absolute; top: 0; right: 0.75rem;
  width: 14rem; pointer-events: auto;
  padding: var(--sp3); border: 1px solid var(--line);
  border-radius: 6px; background: var(--bg-deep);
  box-shadow: 0 2px 8px rgb(0 0 0 / 0.12);
  font-size: var(--fs-chip); color: var(--ink);
  z-index: 2;
}
.suggestion-thread-ops { margin: 0; padding-left: 1.1rem; }
.suggestion-thread-ops li { margin: 0; }
/* the × close affordance — header position only; hue/skin BANKED to feel-check-queue */
.suggestion-thread-close {
  position: absolute; top: var(--sp1); right: var(--sp2);
  border: 0; background: none; cursor: pointer;
  color: var(--ink-faint); font-size: 1rem; line-height: 1; padding: 2px 4px;
}

/* BS-4 BELONG — the in-flow comment bubble LAYER (commentBubbles.ts). A paper-
   relative overlay the band math paints positioned bubbles into. The layer is INERT
   so the word column underneath stays clickable (the gutter's pointer-events rule);
   each bubble opts back in. The bubbles seat in REAL reserved tail space (layout.ts
   bands) BELOW the words they comment on, never over them — so this overlay perturbs
   nothing in the word flow (Probe B 0-delta holds). STRUCTURAL skin only this slice:
   the body content (Shift 2), final hue / density / click-target / seat voice are all
   a Jesse feel-gate (BANKED to feel-check-queue.md), never tuned blind here. */
.comment-bubble-layer { position: absolute; inset: 0; pointer-events: none; }
.comment-bubble {
  position: absolute;            /* left/top/width/height ride in inline from the band rects */
  box-sizing: border-box;
  pointer-events: auto; cursor: pointer;
  text-align: left; overflow: hidden;
  display: flex; align-items: flex-start; gap: var(--sp2);   /* disc | body — the dock row idiom, compacted */
  padding: var(--sp1) var(--sp2);
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg-deep); color: var(--ink); font: inherit;
}
.comment-bubble:hover, .comment-bubble:focus-visible { border-color: var(--ink-deco); }
/* the avatar disc — the dock's --cmt-hue law, shrunk to the in-flow scale (the disc
   warms the bubble; final size/feel is banked to feel-check-queue). */
.comment-bubble-avatar {
  flex: 0 0 auto;
  width: 16px; height: 16px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 8px; font-weight: 600; line-height: 1; letter-spacing: 0.02em;
  color: var(--ink-on-media);
  background: oklch(0.56 0.072 var(--cmt-hue, 250));
  user-select: none;
}
/* the body snippet — the warm --ink read surface the bubble shrinkwrapped to; clips
   to the bubble's height (overflow:hidden on .comment-bubble) when the band is tight. */
.comment-bubble-body { flex: 1 1 auto; min-width: 0; color: var(--ink); }
/* the three seats read distinct without a second lookup (the class carries the band
   math's chosen placement — drop it and they look alike). A QUIET structural tier
   only; the final per-seat voice is feel-banked. */
.comment-bubble.seat-inline { border-left: 2px solid var(--ink-deco); }
.comment-bubble.seat-full-section { border-left-width: 3px; }
/* tight widths: the verbs matter, the annotations go first */
/* MOBILE-2-STRUCT (b) — at phone width the meta readout drops its nowrap so a long
   "section · N words · 0:03.2" line BREAKS instead of forcing the wrapped row wider
   than the screen (the clipped-both-edges bar in Jesse's shot). Pairs with the
   the retired verb bar once used; desktop keeps the single nowrap meta line. */
@media (max-width: 560px) { .sel-meta { white-space: normal; } }
/* the thumb handles share the bottom-right corner — step over the docked bar */

.keys {
  margin-top: auto;
  padding: var(--sp4);
  display: flex; flex-wrap: wrap; gap: var(--sp2) var(--sp4);
  font-size: var(--fs-chip); color: var(--ink-faint);
}
.keys kbd { margin: 0 2px 0 0; }

/* Explicit pause action: one quiet control, separate from range-selection tools. */
.silence-action-popover { position: absolute; z-index: 12; }
.silence-action-popover[hidden] { display: none; }
.silence-action-popover button {
  display: grid; place-items: center; width: 28px; height: 28px; padding: 0;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--surface); color: var(--ink); font: 20px/1 system-ui;
  box-shadow: 0 2px 6px #0006;
  cursor: pointer;
}
.silence-action-popover button:hover { color: var(--ink); background: color-mix(in oklab, var(--surface) 90%, var(--ink)); }
.silence-action-popover button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* ------------------------------------------------ strip */
.stripbar {
  /* EDEN PASS 1 — the transport strip becomes his FUSED BAR: a raised pill
     floating at the page's bottom edge. Round 2 (Jesse's side-by-side): his
     proportions — generous inner padding, a taller labeled control row, and
     the progress as a THIN flush line at the bar's bottom edge with the knob
     overhanging (the canvas paint is proportional to H, so the thin height
     scales the whole grammar down cleanly). */
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  /* round 3 (Jesse: "straddle") — the bar leaves the flow entirely and FLOATS
     over the columns: fixed above the viewport bottom, transcript/viewer/rail
     run beneath it into the fade (his bottom_fade + main_toolbar stack).
     Bottom-anchored, so the timeline room still grows upward. */
  position: fixed; z-index: var(--layer-editor-strip);
  left: clamp(16px, 3vw, 40px); right: clamp(16px, 3vw, 40px);
  bottom: 22px;
  margin: 0;
  /* round 5 — the bottom 14px of the pill IS the progress line's room: the
     stack is absolutely pinned to the pill's bottom edge (his frame's
     progress-as-edge), so the padding reserves exactly that seam. */
  padding: var(--sp2) var(--sp5) 14px;
  box-shadow: 0 14px 44px oklch(0 0 0 / 0.45);
}
@media (max-width: 640px) { .striplabels span:first-child { display: none; } }
/* EDEN PASS 1 — the fused control row (his main_toolbar): playback verbs in the
   bar, above the scrub they drive. Scoped to .stripbar so the share-room
   transports (same classname, their own composition) are untouched. Left: the
   verbs. Right-of-center: the TIME cluster wearing his micro-caps label, current
   time at clock size. Right: SPEED. Labels are ::before pseudos — pure voice,
   no DOM. */
.stripbar .transport {
  margin: 0;
  min-height: 46px; align-items: center;
  gap: var(--sp4);
}
/* his micro-caps register for in-row labels (VOLUME) and the TIMELINE door */
.stripbar .barlabel {
  font: 600 var(--fs-label)/1 var(--f-ui);
  text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--ink-faint);
  margin-right: calc(-1 * var(--sp2));
}
.stripbar .transport #timelinetoggle {
  background: none; border: none; padding: 2px var(--sp1); cursor: pointer;
  font: 600 var(--fs-label)/1 var(--f-ui);
  text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--ink-faint);
}
.stripbar .transport #timelinetoggle:hover { color: var(--ink); }
/* outside the open room the sub-row has nothing left to say — his bar ends at
   the progress line. Mode 3 brings it back for the legend + zoom readout. */
.stripbar:not([data-tmode="3"]) .striplabels { display: none; }
/* his quiet caps verbs (UNDO · REDO, bar left) — text is the whole affordance */
.stripbar .transport .barverb {
  background: none; border: none; padding: 2px var(--sp1);
  font: 600 var(--fs-label)/1 var(--f-ui);
  text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--ink-faint); cursor: pointer;
}
.stripbar .transport .barverb:hover { color: var(--ink); }
/* auto margins EITHER side of the time→speed run = his centered cluster */
.stripbar .transport #curtc {
  margin-left: auto;
  font: 500 var(--fs-clock)/1 var(--f-tc);
  /* Size the TIME label and clock together; a fixed 8ch box let the label
     push the digits outside their box and into Play. */
  display: inline-flex;
  align-items: center;
  width: auto;
  min-width: 8ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.stripbar .transport #curtc::before {
  content: 'time';
  font: 600 var(--fs-label)/1 var(--f-ui);
  text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--ink-faint);
  margin-right: var(--sp2);
}
.stripbar .transport .rates { align-items: center; }
.stripbar .transport .rates::before {
  content: 'speed';
  font: 600 var(--fs-label)/1 var(--f-ui);
  text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--ink-faint);
  margin-right: var(--sp2);
}
/* round 5 — his SPEED is ONE value that cycles (main.ts advances the ladder
   on click); the resting rung is plain cream text, no chip box. */
.stripbar .transport .rates button:not(.on) { display: none; }
.stripbar .transport .rates button.on {
  background: none; border: none; padding: 2px var(--sp1);
  color: var(--ink); font: 500 var(--fs-control)/1 var(--f-tc);
  cursor: pointer;
}
/* his volume goes VERTICAL (Jesse round 5: don't spend horizontal space) —
   the label is the resting affordance; hover/focus raises the upright slider
   above the bar. */
.stripbar .volwrap { position: relative; display: inline-flex; align-items: center; min-width: 58px; }
.stripbar .transport .volslider {
  position: absolute; left: 50%; bottom: 54px;
  width: 84px; height: 3px;
  transform: translateX(-50%) rotate(-90deg);
  transform-origin: center;
  opacity: 0; pointer-events: none;
  transition: opacity 140ms ease-out;
}
.stripbar .volwrap:hover .volslider,
.stripbar .volwrap:focus-within .volslider { opacity: 1; pointer-events: auto; }
.stripbar .transport #timelinetoggle .tl-glyph { display: inline-block; vertical-align: -2px; margin-left: var(--sp1); }
.stripbar .transport #timelinetoggle[aria-expanded="true"] .tl-glyph { color: var(--accent); }
.stripbar .transport .mutebtn { margin-left: auto; }
/* Keep the secondary mute control available on hover or keyboard entry.
   Total duration lives in TRT, not a second transport clock. */
.stripbar .transport .mutebtn { opacity: 0; transition: opacity 140ms ease-out; }
.stripbar .transport:hover .mutebtn,
.stripbar .transport:focus-within .mutebtn { opacity: 1; }
/* his volume line — a thin accent slider, the same token the playhead speaks */
.stripbar .transport .volslider {
  width: 110px; height: 3px; align-self: center;
  accent-color: var(--accent); cursor: pointer;
}
/* touch-action none: a horizontal scrub must never fight page scroll — the
   strip is not a scroll surface (transport-strip §2, same pin as .divider) */
/* round 5 — the stack pins to the pill's BOTTOM EDGE (progress-as-edge):
   absolutely positioned inside the fixed bar, flush across its width; the
   comment avatars ride ABOVE the line, escaping upward into the row's air. */
.stripstack {
  position: absolute; left: 10px; right: 10px; bottom: 3px;
  height: 16px; padding-top: 0;
}
/* A transparent input plane makes the compact scrubber easy to acquire without
   changing the authored canvas geometry. Comment controls sit above it at z:3,
   so their own clicks never become seeks. */
.striphit {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  height: 32px; cursor: crosshair; touch-action: none;
}
/* The footer is the recessed tray—do not add an inset progress rectangle. Its
   background is the unplayed material; one flush paint layer supplies elapsed
   progress beneath the upper shell and is clipped by the footer itself. */
body[data-route="editor"] footer.stripbar {
  background: var(--editor-progress-track, var(--bg-deep));
}
body[data-route="editor"] footer.stripbar::before {
  content: '';
  position: absolute;
  z-index: 0;
  top: auto;
  bottom: 0;
  left: 0;
  width: var(--strip-progress, 0%);
  height: 24px;
  pointer-events: none;
  border-radius: 0 0 0 12px;
  background: var(--editor-progress-played, var(--accent));
}
.stripcomments { top: -20px; height: 20px; }
/* EDEN PASS 1 round 2 — the scrub is his thin bottom-edge line: 20px of
   proportional canvas (track ≈3px, knob ≈4.5px) with rounded lozenge ends,
   run nearly edge-to-edge inside the pill. */
#strip { width: 100%; height: 16px; display: block; cursor: crosshair; border-radius: 999px; touch-action: none; }
.stripcomments {
  position: absolute; inset: 0 0 auto; height: 22px; z-index: 3;
  pointer-events: none;
}
.strip-comment {
  position: absolute; top: 1px; height: 18px; min-width: 18px; padding: 0;
  border: 0; background: none; color: var(--comment-mark); cursor: pointer;
  pointer-events: auto;
}
.strip-comment-line {
  position: absolute; left: 8px; right: 0; top: 8px; height: 2px;
  border-radius: 2px; background: currentColor; opacity: 0.68;
}
.strip-comment-avatar {
  position: absolute; left: 0; top: 1px; width: 16px; height: 16px;
  display: grid; place-items: center; border-radius: 50%;
  color: var(--bg-deep); background: hsl(var(--cmt-hue) 54% 68%);
  box-shadow: 0 0 0 2px var(--bg-deep);
  font: 700 7px/1 var(--f-ui); letter-spacing: -0.03em;
}
.strip-comment:hover .strip-comment-line,
.strip-comment:focus-visible .strip-comment-line,
.strip-comment[data-current="true"] .strip-comment-line { opacity: 1; height: 3px; }
.strip-comment:hover .strip-comment-avatar,
.strip-comment:focus-visible .strip-comment-avatar,
.strip-comment[data-current="true"] .strip-comment-avatar { transform: scale(1.12); }
.strip-comment:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 9px; }
.stripbar[data-tmode="3"] .stripstack { display: none; }
.striplabels {
  display: flex; justify-content: space-between;
  font-size: var(--fs-label); text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--ink-faint); margin-top: 3px;
  font-family: var(--f-ui);
}
.striplabels b { font-weight: 600; }
/* LOOK-2: the provenance colour key (.lg-ord/.lg-reo/.lg-dup) was retired with
   the default provenance paint — the legend follows the neutral paint (empty)
   until a provenance overlay reinstates the colours. */
/* D-1: the legend + zoom readout are the ROOM's chrome — they name marks that
   only exist with the room open, so they show only in mode 3 (the panel's own
   visibility). The right span keeps the resting "kept of" info in every mode. */
#striplegend, #stripzoom { display: none; }
.stripbar[data-tmode="3"] #striplegend,
.stripbar[data-tmode="3"] #stripzoom { display: inline; }
.stripbar[data-tmode="3"] #strip { display: none; }
#stripzoom { color: var(--ink-faint); }

/* T15 transport states — every number here is a structure placeholder for
   Jesse's pass (digest riders). The panel precedes the canvas in the footer,
   so opening it grows the footer UPWARD; the bar row persists beneath. */
.timelinepanel {
  --timeline-track-height: 80px;
  --timeline-nav-height: 16px;
  position: relative;   /* anchors the gap-Target pause popover (slice-5 remainder) */
  border-bottom: 1px solid var(--line);
  padding: var(--sp3) 0 var(--sp2); margin-bottom: var(--sp1);
  min-height: 96px;
  display: flex; flex-direction: column; justify-content: center; gap: var(--sp2);
  color: var(--ink-faint); font-family: var(--f-ui); font-size: var(--fs-ui);
}
.tl-toolpicker {
  flex: none; display: inline-flex; align-items: center; gap: 2px; padding: 2px;
  border: 1px solid var(--line); border-radius: 7px; background: var(--bg);
}
.tl-toolpicker button {
  min-height: 30px; display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 8px; border: 1px solid transparent; border-radius: 4px;
  color: var(--ink-faint); background: transparent;
  font: 500 var(--fs-control)/1 var(--f-ui); cursor: pointer;
}
.tl-toolpicker button:hover { color: var(--ink); background: var(--wash); }
.tl-toolpicker button[aria-pressed="true"] {
  color: var(--accent); border-color: color-mix(in oklab, var(--accent) 42%, var(--line));
  background: var(--accent-soft);
}
.tl-toolpicker button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tl-toolpicker svg {
  width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.tl-toolpicker #timeline-tool-select svg { fill: currentColor; stroke-width: 1.2; }
.tl-toolpicker kbd {
  color: currentColor; opacity: 0.72; font: 600 9px/1 var(--f-mono);
}
/* SELECTS-2-SCOPE-1: the timeline's own "View removals" toggle is retired — the
   transcript-masthead scope menu (.lens-strip) is the one door onto rejected-material
   visibility, and the timeline reads it through scopeShowsRemoved(). */
.tl-range-state {
  min-width: 0; display: flex; align-items: baseline; gap: var(--sp2);
  font-size: var(--fs-label); letter-spacing: 0.08em; text-transform: uppercase;
}
.tl-range-state > span { color: var(--ink-faint); }
.tl-range-state #range-readout {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink); letter-spacing: 0.025em; text-transform: none;
  font-variant-numeric: tabular-nums;
}
.tl-range-actions { justify-self: end; display: flex; align-items: center; gap: 5px; }
.tl-range-actions button {
  min-height: 30px; display: inline-flex; align-items: center; gap: 7px;
  padding: 4px 9px; border: 1px solid var(--line); border-radius: 6px;
  background: transparent; color: var(--ink-faint);
  font: 500 var(--fs-control)/1 var(--f-ui); cursor: pointer;
}
.tl-range-actions button:hover:not(:disabled) {
  border-color: color-mix(in oklab, var(--accent) 55%, var(--line));
  color: var(--ink); background: var(--accent-soft);
}
.tl-range-actions button[data-set="true"] {
  border-color: color-mix(in oklab, var(--accent) 52%, var(--line));
  color: var(--ink);
}
.tl-range-actions button[data-provisional="true"] {
  border-style: dashed;
  color: color-mix(in oklab, var(--accent) 72%, var(--ink-faint));
  background: color-mix(in oklab, var(--accent-soft) 46%, transparent);
}
.tl-range-actions button:disabled { opacity: 0.38; cursor: default; }
.tl-range-actions kbd {
  color: var(--ink-faint); font: 600 10px/1 var(--f-mono);
  border: 1px solid var(--line); border-radius: 3px; padding: 2px 3px;
}
.tl-range-actions output {
  min-width: 42px; color: var(--ink); font-variant-numeric: tabular-nums;
}
/* One contextual surface for concrete timeline objects. It stays compact enough
   for precision work, but names both the object (Pause/Cut) and the available verb.
   The notch follows the actual clicked x even when the surface clamps at an edge. */
.tl-actionpop {
  position: absolute; z-index: 12;
  display: flex; align-items: center; gap: 2px; padding: 3px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 7px;
  box-shadow: 0 8px 24px -10px rgb(0 0 0 / 0.62);
}
.tl-actionpop[data-side="above"] { transform: translateY(calc(-100% - 8px)); }
.tl-actionpop[data-side="below"] { transform: translateY(8px); }
.tl-actionpop::before {
  content: ""; position: absolute; left: var(--tl-action-anchor-x); width: 7px; height: 7px;
  margin-left: -4px; background: var(--surface); transform: rotate(45deg);
}
.tl-actionpop[data-side="above"]::before {
  bottom: -5px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.tl-actionpop[data-side="below"]::before {
  top: -5px; border-left: 1px solid var(--line); border-top: 1px solid var(--line);
}
.tl-actionpop[hidden] { display: none; }
.tl-actionpop-context {
  padding: 0 7px; color: var(--ink-faint);
  font: 600 var(--fs-note)/1 var(--f-ui); letter-spacing: 0.08em; text-transform: uppercase;
}
.tl-actionpop-verb {
  min-height: 30px; white-space: nowrap; padding: 4px 9px;
  border: none; border-radius: 4px;
  font: 500 var(--fs-control)/1 var(--f-ui);
  color: var(--ink-faint); background: none; cursor: pointer;
}
.tl-actionpop-verb:hover { color: var(--ink); background: var(--wash); }
.tl-actionpop-verb[aria-pressed="true"] {
  color: var(--accent); background: var(--accent-soft);
}
/* the single-verb popovers (Join, Restore) read as the primary act they are */
.tl-razorpop-verb:hover { color: var(--accent); background: var(--accent-soft); }
.tl-rustpop-verb { display: inline-flex; align-items: center; gap: 7px; }
.tl-rustpop-verb .selpop-icon { color: var(--moved); }
.tl-rustpop-verb:hover { color: var(--moved); background: var(--wash); }
.timelinepanel[hidden] { display: none; }
/* D-1: the timecode ruler above the room. Slim; height is a structure placeholder
   for Jesse's pass. touch-action none so a scrub never fights page scroll (same pin
   as the room + strip). Sits flush above the room canvas (the panel's gap is the
   only seam) so the ticks read as belonging to the strip below. */
.timelineruler { width: 100%; height: 20px; display: block; cursor: crosshair; border-radius: 3px; touch-action: none; }
/* R13 slice 2 → D-9: the timeline canvas. Fills the panel's width. The word
   objects are the primary lane; waveform energy tucks beneath their lower edge
   instead of reserving a second tall row, keeping the NLE room compact.
   touch-action none so a tap-to-seek never fights page scroll (same pin as strip). */
.timelinecanvas { width: 100%; height: var(--timeline-track-height); display: block; cursor: crosshair; border-radius: 3px; touch-action: none; }
/* Z-c: the double-bubble navigator — a slim whole-cut overview beneath the room.
   Its height is a structure placeholder for Jesse's pass; touch-action none so a
   drag never fights page scroll (same pin as the room + strip). The cursor is set
   inline by navigator.ts (grab / ew-resize / pointer) per the grabbed bubble part. */
.timelinenav {
  width: 100%; height: var(--timeline-nav-height); display: block;
  box-sizing: border-box; border: 1px solid color-mix(in oklab, var(--accent) 14%, var(--line));
  border-radius: 4px; background: color-mix(in oklab, var(--accent) 5%, var(--bg-deep));
  cursor: pointer; touch-action: none;
}
/* LOOK-1 item B: Alt (Option) held over the strip arms the razor — a scissors
   cursor (Premiere-style) so cut-mode is legible before the click. The SVG is
   inline (no asset fetch); crosshair is the fallback for no-SVG-cursor engines.
   The class is toggled by render.ts (refreshCursor) only between gestures. */
.timelinecanvas.razor-arm {
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='6' cy='6' r='3'/%3E%3Ccircle cx='6' cy='18' r='3'/%3E%3Cline x1='20' y1='4' x2='8.12' y2='15.88'/%3E%3Cline x1='14.47' y1='14.48' x2='20' y2='20'/%3E%3Cline x1='8.12' y1='8.12' x2='12' y2='12'/%3E%3C/svg%3E") 8 11, crosshair;
}
/* D-6 grip language: the cursor names both the verb AND the object before the grab.
   The shaded side of the seam glyph is the edge that will move; its hotspot remains
   exactly on the seam. These are look-only — pointerMount's larger hit zone never
   alters timeline/source geometry. */
.timelinecanvas.grip-arm { cursor: col-resize; }
.timelinecanvas.grip-arm.grip-left-arm {
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Crect x='2.5' y='6' width='10.5' height='16' rx='2' fill='%238ea5ff' fill-opacity='.34' stroke='%23e8ebf4' stroke-width='1.4'/%3E%3Cpath d='M14 3.5v21M11 10l-4 4 4 4' fill='none' stroke='%23e8ebf4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 14 14, w-resize;
}
.timelinecanvas.grip-arm.grip-right-arm {
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Crect x='15' y='6' width='10.5' height='16' rx='2' fill='%238ea5ff' fill-opacity='.34' stroke='%23e8ebf4' stroke-width='1.4'/%3E%3Cpath d='M14 3.5v21M17 10l4 4-4 4' fill='none' stroke='%23e8ebf4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 14 14, e-resize;
}
.timelinecanvas.range-edge-arm { cursor: ew-resize; }
.timelinecanvas.band-grab { cursor: grab; }
.timelinecanvas.object-grab { cursor: grab; }
.timelinecanvas.move-dragging { cursor: grabbing; }
#timelinetoggle {
  background: none; border: none; padding: 0; margin-left: var(--sp3);
  font: inherit; letter-spacing: inherit; text-transform: inherit;
  color: var(--ink-faint); cursor: pointer;
}
#timelinetoggle:hover { color: var(--ink); }
/* the chevron flips while the room is open — same door, now an exit */
.stripbar[data-tmode="3"] #timelinetoggle .chev { display: inline-block; transform: rotate(180deg); }


/* Chapter dots' hover bubble (Jesse 2026-07-16 — the podcast-player grammar):
   a small floating title chip above the bar. strip.ts owns position + text
   (fixed coords from the canvas rect); pointer-inert so the bar keeps every
   event. Ink/размер are look-lane placeholders. */
.strip-chaptip {
  position: fixed; z-index: var(--layer-popover); pointer-events: none;
  transform: translate(-50%, -100%);
  background: var(--surface); color: var(--ink);
  border: 1px solid color-mix(in oklab, var(--ink) 16%, transparent);
  border-radius: 6px; padding: 3px 10px;
  font-size: 12px; letter-spacing: 0.01em; white-space: nowrap;
  box-shadow: 0 4px 14px rgb(0 0 0 / 0.35);
}

.tl-zoom-controls button { min-width: 28px; justify-content: center; }
@media (max-width: 1050px) {
  body[data-route="editor"] .stripbar[data-tmode="3"] .timeline-transport-left { flex-wrap: wrap; gap: 6px; }
}

/* Timing is an explicit editorial posture, with the cut-changing option named. */
.tl-timing-tools select {
  max-width: 118px; min-height: 30px; border: 0; border-left: 1px solid var(--line);
  background: var(--bg); color: var(--ink); font: 500 var(--fs-control)/1 var(--f-ui);
  padding: 4px; border-radius: 3px;
}
.tl-timing-tools select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.tl-actionpop:is(.tl-timingpop, .tl-rangepop) {
  display: flex; flex-direction: column; align-items: stretch; gap: 10px;
  width: min(360px, calc(100% - 12px)); padding: 14px;
}
:is(.tl-timingpop, .tl-rangepop)[hidden] { display: none; }
:is(.tl-timingpop, .tl-rangepop) strong { color: var(--ink); font: 600 14px/1.3 var(--f-ui); }
:is(.tl-timingpop, .tl-rangepop) p, :is(.tl-timingpop, .tl-rangepop) output { margin: 0; color: var(--ink-faint); font: 12px/1.5 var(--f-ui); }
.tl-timing-fields { display: flex; align-items: center; gap: 7px; }
.tl-timing-fields input, .tl-timing-fields select, .tl-timing-fields button {
  min-height: 30px; border: 1px solid var(--line); border-radius: 4px; padding: 4px 7px;
  background: var(--bg); color: var(--ink); font: 12px/1.3 var(--f-ui);
}
.tl-timing-fields input { min-width: 0; width: 112px; font-variant-numeric: tabular-nums; }
.tl-timing-fields button { cursor: pointer; white-space: nowrap; }
.tl-timing-fields button:disabled { opacity: .4; cursor: default; }
.tl-timing-fields :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

#timeline.range-arm:not(.range-edge-arm) { cursor: crosshair; }

/* Canvas focus is navigation context, not another selected media range. */
#timeline:focus-visible { outline: 1px solid var(--ink-faint); outline-offset: 2px; }

.tl-range-state #range-readout {
  font-size: inherit; font-family: inherit; padding: 3px 0; border: 0;
  border-bottom: 1px dotted var(--line); border-radius: 0; background: transparent;
  text-align: left; cursor: pointer;
}
.tl-range-state #range-readout:hover:not(:disabled) { border-bottom-color: var(--ink-faint); }
.tl-range-state #range-readout:disabled { cursor: default; border-bottom-color: transparent; }
.tl-range-state #range-readout:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.tl-range-fields label { min-width: 25px; color: var(--ink-faint); font: 12px/1.3 var(--f-ui); }
.tl-range-fields input { flex: 1; width: 100px; }

/* Retired experimental controls keep their wiring but take no toolbar space. */
.tl-audition-controls[hidden], #timeline-tool-range[hidden], #range-readout[hidden] { display: none; }
.tl-range-state:has(#range-readout[hidden]) { display: none; }
/* EDEN — authoritative editor composition
   Source: Proof-WiP / DStDtVZUSiStJrcRexfAGL, frames 1:48, 1:195, 1:374.
   Keep this sheet editor-scoped: Browse and account surfaces retain their own
   system while the cutting room follows Eden's type, rhythm, and proportions. */

body[data-route="editor"] {
  --f-ui: "Work Sans", sans-serif;
  --f-body: "Solway", serif;
  --f-tc: "DM Mono", monospace;
  --f-brand: "Work Sans", sans-serif;
  --editor-canvas: #16181a;
  /* Shared editor chrome roles. Inactive navigation used to borrow Figma's
     #373737 construction stroke as text, which falls below useful contrast on
     the cutting-room ground and made available modes/tabs read as disabled.
     Boundaries and labels now have separate jobs and follow global contrast. */
  /* Inactive tabs used ink-faint and read as disabled beside the darker tab
     band. Dim/ink keeps hierarchy without requiring High contrast. */
  --editor-nav-muted: var(--ink-dim);
  --editor-nav-active: var(--ink);
  --editor-control-line: var(--ink-deco);
  /* One semantic depth ladder for every editor arrangement. These derive from
     the app roles, so contrast/theme changes cannot leave the monitor, Notes,
     transcript overlays, and transport as unrelated literal black slabs. */
  --editor-header: color-mix(in oklab, var(--bg-deep) 92%, var(--editor-canvas));
  --editor-canvas-lift: color-mix(in oklab, var(--editor-canvas) 88%, var(--surface));
  --editor-control-ground: color-mix(in oklab, var(--editor-canvas) 94%, var(--bg-deep));
  --editor-overlay: color-mix(in oklab, var(--editor-canvas) 86%, var(--surface));
  --editor-raised: color-mix(in oklab, var(--editor-canvas) 78%, var(--surface));
  /* One continuous ground for the cut switcher. This is Eden's authored
     #121215 expressed as a role, so the active cut can open cleanly into the
     transcript canvas without adjacent hard-coded slabs drifting apart. */
  --editor-tab-band: oklch(0.184 0.006 286);
  --editor-chapter-rail-closed: 48px;
  /* Eden's opaque toolbar shares the slate family of his tab bands. The seek
     track can then spend the near-black role and remain plainly clickable. */
  --editor-transport: var(--editor-tab-band);
  /* Contained media shares the cut-switcher tone: one quiet chrome surface,
     not a black player box or a decorative picture-derived glow. */
  --projection-well: var(--editor-tab-band);
  /* A barely-there proofing margin confirms the complete frame is visible.
     Keep this semantic so every edit/view aspect ratio spends the same inset. */
  --projection-safe-inset: 4px;
  /* Eden's canvas is #16181a → #171a1e. The frame-to-frame change is only
     1–2 RGB values; use the authored top stop as the flat editor ground so
     transparent transcript chrome matches the rail instead of exposing the
     legacy near-black --bg (#0b0b0d). */
  background: var(--editor-canvas);
}

body[data-route="editor"] #route-page:has(> .top) {
  grid-template-rows: 74px minmax(0, 1fr) 0;
  background: var(--editor-canvas);
}
body[data-route="editor"] main {
  isolation: isolate;
  background: var(--editor-canvas);
}
/* Figma's editor ground moves from #16181a to #171a1e. Proof's surface law
   disallows a painted CSS gradient, so a solid bottom stop is alpha-masked over
   the solid top stop; the rendered pixels are the same without introducing a
   gradient paint surface. */
body[data-route="editor"] main::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: var(--editor-canvas-lift);
  mask-image: linear-gradient(to bottom, transparent, black);
}
body[data-route="editor"] main > .transcript,
body[data-route="editor"] main > .monitor,
body[data-route="editor"] main > .rail {
  position: relative;
  z-index: 1;
}
body[data-route="editor"] main > .rail { background: transparent; }

body[data-route="editor"] .chap-lane-toggle--top .chapters-disclosure-icon {
  width: 21px;
  height: 21px;
  color: currentColor;
}
/* Header — 74px, landscape mark, centered asset identity, then EDIT/VIEW + SHARE. */
body[data-route="editor"] .top {
  position: relative;
  /* Header controls must remain the top interaction layer. The cut band starts
     immediately below this row and may paint later in DOM order; without an
     explicit stack the band can intercept the account trigger's lower pixels. */
  z-index: 20;
  height: 74px;
  box-sizing: border-box;
  flex-wrap: nowrap;
  align-items: center;
  gap: 12px;
  padding: 0 44px 0 50px;
  border-bottom: 0;
  background: var(--editor-header);
}
body[data-route="editor"] .top .brand {
  flex: 0 0 108px;
  width: 108px;
  height: 41px;
  margin: 0;
  overflow: hidden;
  color: transparent;
  font-size: 0;
  background: url(/landing/proof-landscape-logo.png) left center / contain no-repeat;
}
body[data-route="editor"] .top .brand-mark { display: none; }
body[data-route="editor"] .top .fileslot,
body[data-route="editor"] .top .menubtn,
body[data-route="editor"] .top .syncstatus,
body[data-route="editor"] .top .avatar { display: none; }
body[data-route="editor"] .top .crumbtrail {
  position: absolute;
  left: 50%; top: 50%;
  width: min(780px, 52vw);
  transform: translate(-50%, -50%);
  justify-content: center;
  gap: 0;
  font: 500 20px/1.2 var(--f-ui);
}
body[data-route="editor"] .top .crumb-back { position: absolute; left: 0; }
body[data-route="editor"] .top .crumb-proj,
body[data-route="editor"] .top .filename {
  flex: 0 1 auto;
  font: inherit;
  letter-spacing: 0;
  text-transform: uppercase;
}
body[data-route="editor"] .top .crumb-proj { color: var(--accent); font-weight: 700; }
body[data-route="editor"] .top .crumb-slash { margin: 0 6px; color: var(--ink-dim); }
body[data-route="editor"] .top .filename { color: #a6a6a6; font-weight: 500; }
body[data-route="editor"] .top .version-control { margin-left: 12px; }
body[data-route="editor"] .top .version-trigger {
  height: 36px; min-height: 36px;
  padding: 0 18px;
  border: 0; border-radius: 7px;
  background: #373737;
  font: 700 20px/1 var(--f-ui);
  text-transform: uppercase;
  box-shadow: 0 20px 67px rgb(0 0 0 / .5);
}
body[data-route="editor"] .top .modepick {
  margin-left: auto;
  flex: 0 0 157px;
  height: 36px;
  border: 2px solid var(--editor-control-line);
  border-radius: 7px;
  box-sizing: border-box;
  overflow: hidden;
  box-shadow: 0 20px 67px rgb(0 0 0 / .5);
}
body[data-route="editor"] .monitor-toolbar .sidebar-toggle {
  flex: 0 0 36px;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--editor-control-line);
  border-radius: 7px;
  background: transparent;
  color: var(--editor-nav-muted);
  box-shadow: none;
}
body[data-route="editor"] .monitor-toolbar .sidebar-toggle .icn { width: 17px; height: 17px; }
body[data-route="editor"] .monitor-toolbar .sidebar-toggle:hover,
body[data-route="editor"] .monitor-toolbar .sidebar-toggle:focus-visible {
  border-color: var(--ink-dim);
  color: var(--ink);
}
body[data-route="editor"] .monitor-toolbar .sidebar-toggle[aria-expanded="true"] {
  color: var(--accent);
  background: var(--accent-soft);
}
body[data-route="editor"] .top .modepick button[data-mode] {
  flex: 1;
  padding: 0;
  font: 600 20px/32px var(--f-ui);
  letter-spacing: 0;
  text-transform: lowercase;
  color: var(--editor-nav-muted);
}
body[data-route="editor"] .top .modepick button[data-mode].on {
  color: var(--editor-control-ground);
  background: var(--accent);
}
body[data-route="editor"] .top .sharehandle {
  flex: 0 0 95px;
  width: 95px; height: 36px;
  padding: 0;
  border: 2px solid var(--editor-control-line);
  border-radius: 7px;
  font: 600 20px/32px var(--f-ui);
  letter-spacing: 0;
  text-transform: none;
  color: var(--accent);
  box-shadow: 0 20px 67px rgb(0 0 0 / .5);
}
/* Eden's standing frame has one right-edge action. Export stays available on
   header intent without permanently widening that composition. */
body[data-route="editor"] .top #exportbtn {
  position: absolute;
  right: 151px;
  opacity: 0;
  pointer-events: none;
  transform: translateY(4px);
  transition: opacity 140ms ease-out, transform 140ms ease-out;
}
body[data-route="editor"] .top:hover #exportbtn,
body[data-route="editor"] .top:focus-within #exportbtn {
  opacity: 1;
  pointer-events: auto;
  transform: none;
}

/* Tabs — Eden's shared 56px cut/tools bands. Active controls sink into the
   canvas instead of borrowing the old selected-area wash. */
body[data-route="editor"] .transcript-header,
body[data-route="editor"] .transcript-header .canvastabs,
body[data-route="editor"] .dock-tabs { height: 56px; }
body[data-route="editor"] .transcript-header .canvastabs {
  padding: 0;
  gap: 0;
  border-bottom: 0;
}
body[data-route="editor"] .transcript-header,
body[data-route="editor"] .transcript-header .canvastabs,
body[data-route="editor"] .canvastabs-scroll { background: var(--editor-tab-band); }
body[data-route="editor"] .canvastabs-scroll { gap: 0; }
body[data-route="editor"] .canvastabs .tab,
body[data-route="editor"] .canvastabs .newcut,
body[data-route="editor"] .dock-tabs button {
  min-height: 56px;
  padding: 0 36px;
  border: 0;
  border-radius: 0;
  font: 700 20px/56px var(--f-ui);
  letter-spacing: 3.6px;
  text-transform: uppercase;
}
body[data-route="editor"] .canvastabs .tab { color: var(--editor-nav-muted); background: var(--editor-tab-band); }
body[data-route="editor"] .canvastabs .tabslot:has(.tab.active) {
  position: relative;
  background: var(--editor-canvas);
  box-shadow: 0 1px 0 var(--editor-canvas);
}
body[data-route="editor"] .canvastabs .tab.active {
  color: var(--editor-nav-active);
  background: transparent;
}
body[data-route="editor"] .canvastabs .newcut { color: var(--editor-nav-muted); }
body[data-route="editor"] .dock-tabs { border: 0; background: var(--editor-tab-band); }
body[data-route="editor"] .dock-tabs button {
  padding: 0 8px;
  color: var(--editor-nav-muted);
  background: var(--editor-tab-band);
  border: 0;
  /* Eden 1:153–1:159: inactive tabs remain in the #121215 band and acquire
     a slight leading-edge depth cue. A shadow expresses his overlay strip
     without introducing a decorative gradient surface. */
  box-shadow: inset 14px 0 18px -20px rgb(0 0 0 / .72);
}
body[data-route="editor"] .dock-tabs button.on {
  color: var(--editor-nav-active);
  /* The active tab is the open edge of the editor canvas, not merely brighter
     text on the same band. This is Eden's TOOLS state in widescreen 1:48. */
  background: var(--editor-canvas);
  box-shadow: none;
}
body[data-route="editor"] .dock-tabs button[data-tab]:hover:not(.on) {
  color: var(--editor-nav-active);
  background: color-mix(in oklab, var(--editor-tab-band) 86%, var(--editor-canvas));
}
body[data-route="editor"] .dock-tabs .icn { display: none; }

/* Chapters keeps its lane label. Transcript now owns a real sticky masthead
   with an adjacent settings door and room for future scoped filters. */
body[data-route="editor"] .transcript-body::before {
  position: absolute;
  top: 28px;
  z-index: 5;
  pointer-events: none;
  font: 700 20px/1 var(--f-ui);
  letter-spacing: 3.6px;
  text-transform: lowercase;
  color: var(--ink-dim);
}
body[data-route="editor"] .transcript-body::before { content: "chapters"; left: 42px; }
body[data-route="editor"] .transcript[data-chap-open] .chapters-index {
  width: 298px;
  padding: 40px 16px 16px 42px;
  background: transparent;
}
body[data-route="editor"] .transcript[data-chap-open] .tscroll {
  padding-left: 318px;
}
body[data-route="editor"] main[data-layout="top"] .transcript[data-chap-open] .tscroll {
  padding-left: 346px;
}
body[data-route="editor"] .paper {
  font-family: var(--f-body);
  color: #d8d8d8;
}
body[data-route="editor"] .speaker {
  font: 700 16px/19px var(--f-ui);
  letter-spacing: 2.88px;
  text-transform: capitalize;
}
body[data-route="editor"] .chead {
  border-bottom: 0;
  align-items: center;
  /* Preserve an editorial margin for the fold control. The chapter marker
     already breaks out past paragraph copy; this 24px slot lets the caret hang
     in that margin while the serif title still begins left of normal text. */
  padding-block: 0;
  padding-inline: 24px 0;
}
body[data-route="editor"] .chead-tri {
  top: 50%;
  left: 0;
  width: 20px;
  height: 28px;
  transform: translateY(-50%);
}
body[data-route="editor"] .chead-tri .icn {
  width: 12px;
  height: 12px;
}
body[data-route="editor"] .chead-title {
  font: 600 20px/1.05 var(--f-body);
  letter-spacing: 0;
}
body[data-route="editor"] .chapters-index-row {
  padding: 7px 0;
  border: 0;
  border-radius: 0;
  font: 500 20px/1.2 var(--f-ui);
}
body[data-route="editor"] .chapters-index-title { font: inherit; color: #a6a6a6; }
body[data-route="editor"] .chapters-index-row {
  transition: background-color 150ms cubic-bezier(.25, 1, .5, 1),
              color 150ms cubic-bezier(.25, 1, .5, 1);
}
body[data-route="editor"] .chapters-index-row[data-current-chapter] {
  background: transparent;
}
body[data-route="editor"] .chapters-index-row[data-current-chapter] .chapters-index-title {
  color: color-mix(in oklab, var(--ink) 88%, var(--accent));
}
body[data-route="editor"] .viewbar .chap-lane-toggle--top {
  display: inline-flex;
  position: static;
  transform: none;
  min-width: 0;
  width: 32px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: transparent;
  opacity: .66;
}

/* Viewer — a four-pixel projection margin makes the contained frame's complete
   bounds perceptible without reading as a bezel. The legacy TRT trio is still
   available on viewer hover, but no longer consumes a 42px layout band. */
body[data-route="editor"] .monitor {
  position: relative;
  box-sizing: border-box;
  padding: var(--projection-safe-inset);
  overflow: hidden;
  /* A flat app-native projection field: no copied frame, blur, or glow layer. */
  background: var(--projection-well);
  contain: paint;
}
body[data-route="editor"] .monitor-toolbar {
  position: relative;
  z-index: 5;
  flex: 0 0 var(--monitor-toolbarH, 42px);
  width: calc(100% + var(--projection-safe-inset) + var(--projection-safe-inset));
  min-height: var(--monitor-toolbarH, 42px);
  margin: calc(-1 * var(--projection-safe-inset)) calc(-1 * var(--projection-safe-inset)) var(--projection-safe-inset);
  box-sizing: border-box;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  padding-inline: 8px;
  border-bottom: 0;
  background: var(--editor-tab-band);
}
body[data-route="editor"] .monitor-toolbar .sidebar-toggle {
  grid-column: 3;
  grid-row: 1;
  justify-self: end;
  width: 42px;
  height: 42px;
  flex-basis: 42px;
  border: 0;
  border-radius: 0;
  background: var(--bg-deep);
}
body[data-route="editor"] .monitor-toolbar .sidebar-toggle .icn {
  width: 15px;
  height: 15px;
}
body[data-route="editor"] .monitor video {
  position: relative;
  z-index: 1;
  background: transparent;
  opacity: 0;
  transition: opacity 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
body[data-route="editor"] .monitor video[data-media-ready] {
  opacity: 1;
}
body[data-route="editor"] .monitor .medianote { position: relative; z-index: 3; }
.removed-preview-note:not([hidden]) {
  position: absolute;
  z-index: 4;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  box-sizing: border-box;
  max-width: calc(100% - 32px);
  padding: 8px 12px;
  border: 1px dashed var(--ink-faint);
  border-radius: 6px;
  background: var(--bg-deep);
  color: var(--ink);
  font: 500 13px/1.4 var(--f-ui);
  text-align: center;
  pointer-events: none;
}
body[data-route="editor"] .monitor-toolbar > .clocks {
  grid-column: 2;
  grid-row: 1;
  justify-self: center;
  gap: clamp(10px, 1.2vw, 20px);
  padding: 0;
  white-space: nowrap;
}
body[data-route="editor"] .monitor-toolbar .clock {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
}
body[data-route="editor"] .monitor-toolbar .clock label {
  margin: 0;
  font-size: 8px;
  letter-spacing: .14em;
}
body[data-route="editor"] .monitor-toolbar .clock .tc {
  font-size: 11px;
  line-height: 1;
}
body[data-route="editor"] main[data-layout="top"] .monitor video,
body[data-route="editor"] main[data-layout="tall"] .monitor video,
body[data-route="editor"] main[data-layout="side"] .monitor video {
  flex: 1;
  width: 100%; height: 100%;
  max-width: none; max-height: none;
  margin: 0;
  aspect-ratio: auto;
  object-fit: cover;
  border-radius: 0;
}

/* Tools rail — 40px inset, 20px control register, 35px outlined checks. */
body[data-route="editor"] .rail { border-left: 0; background: var(--editor-canvas); }
body[data-route="editor"] .dock-pane[data-pane="tools"] {
  padding: 50px 40px 420px;
  box-sizing: border-box;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .panel[data-sec="cleanup"] h3 {
  margin: 0 0 34px;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .panel[data-sec="cleanup"] .sec-toggle {
  padding: 0;
  border: 0;
  font: 700 20px/1 var(--f-ui);
  letter-spacing: 3.6px;
  text-transform: lowercase;
  color: var(--accent);
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .panel[data-sec="cleanup"] {
  font-family: var(--f-ui);
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .panel[data-sec="cleanup"] .ctl {
  margin-bottom: 26px;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .panel[data-sec="cleanup"] .toggle {
  gap: 18px;
  font: 500 20px/1.2 var(--f-ui);
  color: #a6a6a6;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] input[type="checkbox"] {
  appearance: none;
  width: 35px; height: 35px;
  flex: 0 0 35px;
  margin: 0;
  border: 2px solid #373737;
  border-radius: 6px;
  background: var(--editor-control-ground);
  box-shadow: 0 5px 20px rgb(0 0 0 / .2);
}
body[data-route="editor"] .dock-pane[data-pane="tools"] input[type="checkbox"]:checked {
  background-color: var(--editor-control-ground);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 35 35'%3E%3Cpath d='m9 18 5 5 12-13' fill='none' stroke='%239f8557' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .count,
body[data-route="editor"] .dock-pane[data-pane="tools"] .scrubval {
  font: 500 16px/1 var(--f-tc);
  color: #797978;
}

/* Floating comment card — exactly the rail inset and vertical station in Eden. */
body[data-route="editor"] .floatnote {
  top: auto;
  right: 40px;
  bottom: 180px;
  width: calc(var(--toolsW, 533px) - 80px);
  height: 260px;
  box-sizing: border-box;
  padding: 16px;
  border: 2px solid #373737;
  border-radius: 14px;
  background: var(--editor-raised);
  box-shadow: inset 0 6px 27px #7e7e7e, 0 20px 67px rgb(0 0 0 / .5);
}
body[data-route="editor"] main[data-layout="top"] .floatnote { bottom: 201px; height: 239px; }
body[data-route="editor"] .floatnote-label {
  margin: 0;
  font: 700 12px/1 var(--f-ui);
  letter-spacing: 2.16px;
  color: #797978;
}
body[data-route="editor"] .floatnote-label::first-letter { text-transform: uppercase; }
body[data-route="editor"] .floatnote .cmt-compose-input {
  min-height: 150px;
  font: 400 15px/1.45 var(--f-ui);
}
body[data-route="editor"] main[data-layout="top"] .floatnote .cmt-compose-input { min-height: 130px; }
body[data-route="editor"] .floatnote .cmt-compose-submit {
  font: 700 12px/1 var(--f-ui);
  letter-spacing: 2.16px;
  color: #797978;
}

/* Floating transport — Eden's 1886×90 composition at the 2048 reference. */
body[data-route="editor"] .stripbar {
  left: 4.2vw;
  right: 3.7vw;
  bottom: 54px;
  width: auto;
  height: 90px;
  box-sizing: border-box;
  padding: 0 50px 16px;
  border: 2px solid #373737;
  border-radius: 14px;
  background: rgb(11 11 11 / .8);
  box-shadow: 0 20px 33px rgb(0 0 0 / .5), inset 0 6px 27px #7e7e7e;
}
body[data-route="editor"] .stripbar .transport {
  height: 74px;
  min-height: 74px;
  gap: 28px;
}
body[data-route="editor"] .stripbar .barverb,
body[data-route="editor"] .stripbar .barlabel,
body[data-route="editor"] .stripbar .transport #timelinetoggle,
body[data-route="editor"] .stripbar .transport #curtc::before,
body[data-route="editor"] .stripbar .transport .rates::before {
  font: 700 12px/1 var(--f-ui);
  letter-spacing: 2.16px;
}
body[data-route="editor"] .stripbar .transport #curtc {
  font: 500 24px/1 var(--f-tc);
}
body[data-route="editor"] .stripbar .playbtn {
  width: 34px; height: 34px;
  font-size: 29px;
  line-height: 34px;
}
body[data-route="editor"] .stripbar .transport .rates button.on {
  font: 500 20px/1 var(--f-tc);
}
body[data-route="editor"] .stripbar .mutebtn { display: none; }
body[data-route="editor"] .stripbar .volwrap { min-width: 185px; gap: 16px; }
body[data-route="editor"] .stripbar .transport .volslider {
  position: static;
  width: 110px; height: 10px;
  transform: none;
  opacity: 1;
  pointer-events: auto;
}
body[data-route="editor"] .stripstack {
  left: 0; right: 0; bottom: 0;
  height: 18px;
}
body[data-route="editor"] #strip { height: 18px; }
body[data-route="editor"] main > .transcript::after { height: 180px; }
body[data-route="editor"] main { --eden-bar-clearance: 180px; }

@media (max-width: 900px) {
  body[data-route="editor"] .top { padding-inline: 16px; }
  body[data-route="editor"] .top .crumbtrail { display: none; }
  body[data-route="editor"] .top .modepick { flex-basis: 130px; }
  body[data-route="editor"] .top #exportbtn { display: none; }
  body[data-route="editor"] .stripbar { left: 16px; right: 16px; bottom: 18px; }
}

/* EDEN RECONCILIATION — the Figma frames are 2× presentation canvases, not
   literal CSS-pixel specifications. Preserve their composition and palette at
   Proof's logical desktop density, and make the floating chrome participate in
   the workspace solver instead of covering the work. */
body[data-route="editor"] {
  --editor-strip-reserve: 132px;
}
body[data-route="editor"]:has(.stripbar[data-tmode="3"]) {
  /* 209px open transport + its 54px viewport seat, rounded for the outline.
     Keep this derived seat close to the actual compact timeline geometry. */
  --editor-strip-reserve: 264px;
}
body[data-route="editor"] #route-page:has(> .top) {
  grid-template-rows: var(--app-header-height) minmax(0, 1fr) 0;
}
/* Header: one elastic identity column and three compact action groups. The
   filename always ellipsizes before it can collide with Edit/View/Export/Share. */
body[data-route="editor"] .top {
  display: grid;
  grid-template-columns: var(--app-wordmark-width) minmax(0, 1fr) auto auto auto auto;
  align-items: center;
  height: var(--app-header-height);
  gap: 10px;
  padding: 0 var(--app-header-pad-inline);
}
body[data-route="editor"] .top .brand {
  grid-column: 1;
  width: var(--app-wordmark-width);
  height: var(--app-wordmark-height);
  flex-basis: var(--app-wordmark-width);
  background-size: contain;
}
body[data-route="editor"] .top .crumbtrail {
  grid-column: 2;
  position: static;
  width: auto;
  max-width: 100%;
  transform: none;
  justify-content: center;
  overflow: hidden;
  font-size: 14px;
}
body[data-route="editor"] .top .crumb-back { position: static; }
body[data-route="editor"] .top .crumb-proj,
body[data-route="editor"] .top .filename {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
}
body[data-route="editor"] .top .version-control { margin-left: 8px; }
body[data-route="editor"] .top .version-trigger {
  min-height: 28px;
  height: 28px;
  padding: 0 10px;
  border-radius: 6px;
  font-size: 13px;
  box-shadow: none;
}
body[data-route="editor"] .top .modepick {
  grid-column: 3;
  grid-row: 1;
  width: 140px;
  height: 30px;
  flex-basis: 140px;
  /* Modes describe the workspace; delivery actions are a separate group. */
  margin: 0 var(--sp3) 0 0;
  border-width: 1px;
  border-color: var(--line);
  border-radius: 6px;
  background: var(--bg-deep);
  box-shadow: none;
}
body[data-route="editor"] .top .modepick button[data-mode] {
  height: 100%;
  min-height: 0;
  margin: 0;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  font-size: 13px;
  line-height: 1;
}
body[data-route="editor"] .top .modepick button[data-mode].on {
  color: var(--ink);
  background: var(--wash);
}
body[data-route="editor"] .top .modepick button[data-mode]:hover {
  color: var(--ink);
}
/* Same silhouette, different pane split; selected-mode classes remain the
   authority. The center control is a direct, accessible two-way switch. */
body[data-route="editor"] .top .modepick .mode-layout-toggle {
  flex: 0 0 24px;
  width: 24px;
  height: 100%;
  min-height: 0;
  display: grid;
  place-items: center;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  color: var(--ink-dim);
  background: transparent;
}
body[data-route="editor"] .top .modepick .mode-layout-toggle:hover {
  color: var(--ink);
}
.mode-layout-icon {
  flex: 0 0 20px;
  width: 20px;
  height: 16px;
  align-self: center;
  color: var(--ink-dim);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  pointer-events: none;
}
.mode-layout-view { display: none; }
.modepick:has(button[data-mode="watch"].on) .mode-layout-edit { display: none; }
.modepick:has(button[data-mode="watch"].on) .mode-layout-view { display: initial; }
body[data-route="editor"] .top .sharehandle {
  width: 72px;
  height: 32px;
  flex-basis: 72px;
  border-width: 1px;
  border-radius: 6px;
  font-size: 14px;
  line-height: 30px;
  box-shadow: none;
}
body[data-route="editor"] .top #sharebtn {
  grid-column: 5;
  grid-row: 1;
  /* Share is the primary delivery action, not another selected mode. */
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg-deep);
}
body[data-route="editor"] .top #exportbtn {
  grid-column: 4;
  grid-row: 1;
  position: static;
  right: auto;
  opacity: 1;
  pointer-events: auto;
  transform: none;
  transition: none;
  color: var(--accent);
  border-color: var(--accent);
  background: transparent;
}
body[data-route="editor"] .top #sharebtn:hover {
  background: color-mix(in oklab, var(--accent) 85%, var(--ink));
}
body[data-route="editor"] .top #exportbtn:hover {
  background: var(--accent-soft);
}
body[data-route="editor"] .top .sharehandle:focus-visible,
body[data-route="editor"] .top .modepick button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
body[data-route="editor"] .top #editor-account-control {
  grid-column: 6;
  grid-row: 1;
  align-self: center;
  justify-self: end;
}

/* Cut and dock tab bands keep Eden's flush geometry at a practical 42px. */
body[data-route="editor"] .transcript-header,
body[data-route="editor"] .transcript-header .canvastabs,
body[data-route="editor"] .dock-tabs { height: 42px; }
body[data-route="editor"] .transcript-header .canvastabs {
  /* The band stays borderless so the active cut can open directly into the
     canvas. Keep its sizing stable without reintroducing a full-width seam. */
  box-sizing: border-box;
}
body[data-route="editor"] .canvastabs .tab,
body[data-route="editor"] .canvastabs .newcut,
body[data-route="editor"] .dock-tabs button {
  min-height: 42px;
  padding: 0 24px;
  font-size: 13px;
  line-height: 42px;
  letter-spacing: 2.2px;
}
body[data-route="editor"] .dock-tabs button[data-tab] {
  height: 42px;
  margin: 0;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  position: relative;
  background: var(--bg-deep);
}
body[data-route="editor"] .dock-tabs button { padding-inline: 6px; }
/* Keep selection legible without boxing the active tab into a heavy three-sided
   tooth. The brighter label and canvas surface do most of the work; a short,
   neutral marker supplies the non-color cue without competing with the playhead. */
body[data-route="editor"] .dock-tabs button[data-tab].on {
  padding-block: 0;
  background: var(--editor-canvas);
  z-index: 1;
}
body[data-route="editor"] .dock-tabs button[data-tab].on::after {
  content: "";
  position: absolute;
  inset: auto auto 0 50%;
  width: 32px;
  height: 2px;
  background: var(--ink-dim);
  transform: translateX(-50%);
}
/* The structural disclosure is the final member of Eden's continuous tab band.
   It stays icon-sized because it changes the layout rather than the selected
   panel, but it shares the tabs' surface and state language instead of reading
   as a separate little control cell on the right. */
body[data-route="editor"] .dock-tabs .sidebar-toggle {
  flex: 0 0 42px;
  width: 42px;
  height: 42px;
  min-height: 42px;
  padding: 0;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 0;
  background: var(--bg-deep);
  color: var(--editor-nav-muted);
  font-size: 0;
  line-height: 1;
  letter-spacing: 0;
}
body[data-route="editor"] .dock-tabs .sidebar-toggle .icn {
  display: block;
  width: 15px;
  height: 15px;
}
body[data-route="editor"] .dock-tabs .sidebar-toggle[aria-expanded="true"] {
  color: var(--editor-nav-muted);
  background: var(--bg-deep);
}
body[data-route="editor"] .dock-tabs .sidebar-toggle:hover {
  color: var(--ink);
  background: var(--editor-canvas-lift);
}
body[data-route="editor"] .dock-tabs .sidebar-toggle:focus-visible {
  color: var(--ink);
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Chapters remains the real collapsible lane. Its live toggle becomes Eden's
   CHAPTERS heading; the transcript heading follows the lane's solved width. */
body[data-route="editor"] .transcript-body::before { content: none; }
body[data-route="editor"] .transcript-body::after {
  top: 20px;
  left: 24px;
  font-size: .875rem;
  font-variant-caps: all-small-caps;
  letter-spacing: .16em;
  text-transform: uppercase;
}
body[data-route="editor"] .transcript[data-chap-open] .transcript-body::after {
  left: calc(var(--chap-lane-w, 320px) + 24px);
}
body[data-route="editor"] .transcript .chapters-index {
  transition:
    width 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
    padding 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
    bottom 240ms cubic-bezier(.22, 1, .36, 1);
}
body[data-route="editor"] .transcript[data-chap-open] .chapters-index {
  width: var(--chap-lane-w, 320px);
  /* The sticky transcript masthead sits at z:10. Chapters starts in the same
     body band, so keep the whole sidebar above that layer; otherwise its first
     row remains interactive but the masthead visually swallows the close door.
     Applying this to the surface (not only the button) keeps the control
     visibly contained in both the docked and overlay presentations. */
  z-index: 11;
  /* The outline is part of the document chrome, but it must not continue into
     the floating transport's interaction/shadow field. Reuse the same solved
     reserve as the transport, timeline, and transcript composer. */
  bottom: var(--editor-strip-reserve, 132px);
  /* Tier one belongs to TRANSCRIPT + Show. Start the outline head at tier two
     so Chapters/Collapse stays on the same baseline open or closed. */
  padding: 8px 20px 20px 24px;
  border-right: 1px solid rgb(255 255 255 / .035);
  background: var(--editor-control-ground);
}
body[data-route="editor"] .transcript[data-chap-open] .chapters-index-list {
  /* A scrollable tail lets the final chapter clear the fade instead of being
     permanently dimmed, while the resting list dissolves before the strip. */
  padding-bottom: 32px;
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 calc(100% - 32px), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0, #000 calc(100% - 32px), transparent 100%);
}
body[data-route="editor"] .transcript[data-chap-open] .tscroll,
body[data-route="editor"] main[data-layout="top"] .transcript[data-chap-open] .tscroll {
  padding-left: var(--transcript-pad-start, calc(var(--chap-lane-w, 320px) + 24px));
}
body[data-route="editor"] .transcript .chap-lane-toggle--top {
  position: static;
  transform: none;
  min-width: 0;
  width: 32px;
  height: 30px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 5px;
  background: transparent;
  color: var(--ink-dim);
  font: 700 .875rem/1 var(--f-ui);
  font-variant-caps: all-small-caps;
  letter-spacing: .16em;
  text-transform: uppercase;
  opacity: .7;
  transition: color 140ms ease, border-color 140ms ease, background-color 140ms ease, opacity 140ms ease;
}
body[data-route="editor"] .transcript .chap-lane-toggle--top[aria-expanded="true"] {
  display: inline-flex;
  align-items: center;
  gap: var(--sp2);
  width: auto;
  flex-shrink: 0;
  color: var(--accent);
  opacity: 1;
}
body[data-route="editor"] .transcript .chap-lane-toggle--top:hover,
body[data-route="editor"] .transcript .chap-lane-toggle--top:focus-visible {
  border-color: var(--editor-control-line);
  background: var(--editor-control-ground);
  color: var(--accent);
  opacity: 1;
}
body[data-route="editor"] .transcript[data-chap-open] .chapters-index-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  width: 100%;
  min-width: 0;
  min-height: 28px;
  margin-bottom: 8px;
  background: var(--editor-control-ground);
}
body[data-route="editor"] .chapters-index-head .chap-lane-toggle--top,
body[data-route="editor"] .chapters-index-head .chapter-fold-all--closed {
  flex: 0 1 auto;
  min-width: 0;
}
/* A collapsed outline is one icon-wide structural lane. Reserve that width in
   the transcript scroller so its sticky masthead cannot paint over the door;
   collapse-all belongs to the expanded outline, where its label has room. */
body[data-route="editor"] .transcript:not([data-chap-open]) .tscroll {
  padding-left: var(--transcript-pad-start, var(--editor-chapter-rail-closed));
}
body[data-route="editor"] .tscroll {
  transition: padding-left 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
body[data-route="editor"] .transcript:not([data-chap-open]) .chapter-fold-all--closed {
  display: none;
}
body[data-route="editor"] .transcript:not([data-chap-open]) .chapters-index-head .chap-lane-toggle--top {
  flex: 0 0 32px;
  min-width: 32px;
}
/* When a readable dock cannot seat, chapters keep Eden's left-hand hierarchy as a
   same-DOM overlay. It is visibly a layer (opaque enough to read, one edge + quiet depth),
   while the transcript retains its full solved width underneath. */
body[data-route="editor"] .transcript[data-tier-chap="overlay"][data-chap-open] .chapters-index {
  width: var(--chap-overlay-w);
  padding: 8px 20px 20px 24px;
  background: var(--editor-control-ground);
  border-right-color: rgb(255 255 255 / .08);
  box-shadow: 18px 0 32px rgb(0 0 0 / .28);
}
body[data-route="editor"] .transcript[data-tier-chap="overlay"][data-chap-open] .tscroll,
body[data-route="editor"] main[data-layout="top"] .transcript[data-tier-chap="overlay"][data-chap-open] .tscroll {
  padding-left: var(--transcript-pad-start, 24px);
}
body[data-route="editor"] .transcript[data-tier-chap="overlay"][data-chap-open] .transcript-body::after {
  left: calc(var(--chap-overlay-w) + 24px);
}
body[data-route="editor"] .transcript[data-chap-minimized] .chap-lane-toggle--top {
  padding: 0;
  border: 1px solid transparent;
  border-radius: 5px;
  background: transparent;
}
body[data-route="editor"] .transcript:not([data-chap-open]) .transcript-body::after { left: 84px; }
body[data-route="editor"] .transcript:not([data-chap-open]) .chapters-index {
  width: var(--editor-chapter-rail-closed);
  bottom: var(--editor-strip-reserve, 132px);
  padding: 8px 0 0 8px;
  background: var(--editor-control-ground);
  border-right: 1px solid rgb(255 255 255 / .035);
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .panel[data-sec] .sec-toggle,
body[data-route="editor"] .lens-seg,
body[data-route="editor"] .lens-apply {
  font-variant-caps: all-small-caps;
  letter-spacing: .12em;
  text-transform: uppercase;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .panel[data-sec] .sec-toggle {
  font-size: .875rem;
  line-height: 1.1;
}
body[data-route="editor"] .speaker {
  font-size: 12px;
  line-height: 15px;
  letter-spacing: 2px;
}
body[data-route="editor"] .chead-title { font-size: 20px; }
body[data-route="editor"] .chead {
  /* The marker row is part of the transcript page, not a card nested inside
     the section's presence/selection wash. Let the owning block show through. */
  background: transparent;
  transition: background 150ms cubic-bezier(.25, 1, .5, 1);
}
body[data-route="editor"] .chead[data-current-chapter] {
  background: transparent;
}
body[data-route="editor"] .chead[data-current-chapter] .chead-title {
  /* Playback presence belongs to the marker typography, never the section
     surface: current-word and range paint remain the only transcript washes.
     A tight core plus a broad, faint falloff reads as proof-gold light rather
     than a neon effect, and stays steady while the editor is working. */
  color: color-mix(in oklab, var(--ink) 58%, var(--accent));
  font-weight: 700;
  text-shadow:
    0 0 8px color-mix(in oklab, var(--accent) 34%, transparent),
    0 0 22px color-mix(in oklab, var(--accent) 17%, transparent);
}
body[data-route="editor"] .chead[data-current-chapter] .chead-duration {
  color: color-mix(in oklab, var(--ink-dim) 72%, var(--accent));
  opacity: 1;
}
:root[data-motion="reduced"] body[data-route="editor"] .chapters-index-row,
:root[data-motion="reduced"] body[data-route="editor"] .chead { transition: none; }
body[data-route="editor"] .chapters-index-row {
  padding: 5px 0;
  font-size: 14px;
}
body[data-route="editor"] .comment-dot {
  border-color: var(--comment-mark);
  background: transparent;
  opacity: .88;
}
body[data-route="editor"] .comment-avatar.comment-marker {
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--ink) 8%, transparent);
}

/* Scope belongs to the transcript, not the cut band. Its trigger rests at full
   legibility in the primary masthead tier and the menu supplies the detail. */
body[data-route="editor"] .viewbar .lens-strip-slot { opacity: 1; }

/* Picture: show the complete media inside the solved box. The workspace's
   bottom reserve guarantees this box ends before the control strip begins. */
body[data-route="editor"] main[data-layout="top"] .monitor video,
body[data-route="editor"] main[data-layout="tall"] .monitor video,
body[data-route="editor"] main[data-layout="side"] .monitor video {
  object-fit: contain;
}

/* Eden renamed the existing Watch posture to View. Its behavior is unchanged:
   media first, Notes docked, transcript summonable. Give its two solver layouts
   the same complete-picture contract as Edit so the flex item's intrinsic size
   can never collapse the viewer into a thumbnail strip. */
body[data-route="editor"] main[data-layout="watch-side"] .monitor,
body[data-route="editor"] main[data-layout="watch-stack"] .monitor {
  align-items: stretch;
  justify-content: stretch;
  background: var(--projection-well);
}
body[data-route="editor"] main[data-layout="watch-side"] .monitor video,
body[data-route="editor"] main[data-layout="watch-stack"] .monitor video {
  flex: 1 1 0;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  max-width: none;
  max-height: none;
  margin: 0;
  aspect-ratio: auto;
  object-fit: contain;
  border-radius: 0;
}
body[data-route="editor"] main[data-mode="watch"] .rail {
  border-left: 1px solid rgb(255 255 255 / .035);
  background: var(--editor-canvas);
}
/* The generic Eden child-layer rule is more specific than rail.css's summoned
   shelf selector and was downgrading the Watch transcript from fixed to relative.
   Keep it out of the grid while preserving the existing right/bottom shelf motion. */
body[data-route="editor"][data-tier-transcript="summoned"] main > .transcript {
  position: fixed;
  z-index: var(--layer-drawer);
}

/* Right rail density and flat surfaces. */
body[data-route="editor"] .rail {
  overflow: hidden;
  background: transparent;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] {
  padding: 24px;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .panel[data-sec="cleanup"] h3 {
  margin-bottom: 18px;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .panel[data-sec="cleanup"] .sec-toggle {
  font-size: 12px;
  letter-spacing: 2.2px;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .panel[data-sec="cleanup"] .ctl {
  margin-bottom: 14px;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .panel[data-sec="cleanup"] .toggle {
  gap: 10px;
  font-size: 14px;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] input[type="checkbox"] {
  width: 18px;
  height: 18px;
  flex-basis: 18px;
  border-width: 1px;
  border-radius: 4px;
  box-shadow: none;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .count,
body[data-route="editor"] .dock-pane[data-pane="tools"] .scrubval { font-size: 11px; }

/* One economical comment shell follows the active workspace edge. Frame's useful
   precedent is structural: time, field, audience and action share ONE bounded
   object, with secondary controls disclosed only during composition. Proof keeps
   its own quiet charcoal/gold language and the existing live composer DOM. */
body[data-route="editor"] .rail-comment-dock {
  flex: none;
  position: relative;
  width: min(440px, calc(100% - 24px));
  box-sizing: border-box;
  margin: 10px 12px 12px auto;
  padding: 0;
  overflow: hidden;
  border: 1px solid #3b3b3a;
  border-radius: 8px;
  background: var(--editor-raised);
  box-shadow: 0 12px 28px rgb(0 0 0 / .28);
  transition: border-color 140ms ease-out, box-shadow 140ms ease-out;
}
body[data-route="editor"] .rail-comment-dock:is(:focus-within, :has([data-compose-draft-open])) {
  border-color: color-mix(in oklab, var(--accent) 58%, #3b3b3a);
  box-shadow: 0 16px 34px rgb(0 0 0 / .34);
}
body[data-route="editor"] .rail-comment-label {
  position: absolute;
  z-index: 2;
  top: 9px;
  left: 11px;
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  pointer-events: none;
  color: #848482;
  font: 700 10px/1 var(--f-ui);
  font-variant-caps: all-small-caps;
  letter-spacing: 1.35px;
}
body[data-route="editor"] .rail-comment-label-time {
  color: var(--accent);
  font: 600 10px/1 var(--f-tc);
  font-variant-caps: normal;
  letter-spacing: .04em;
}
body[data-route="editor"] .rail-comment-dock .cmt-compose {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 0;
  row-gap: 0;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
}
body[data-route="editor"] .rail-comment-dock .cmt-compose-target { display: none; }
body[data-route="editor"] .rail-comment-dock .cmt-compose > [data-doodle-controls] {
  grid-column: 1 / -1;
  grid-row: 3;
  min-width: 0;
  padding: 0 8px 8px;
}
body[data-route="editor"] .rail-comment-dock .cmt-compose-input {
  grid-column: 1 / -1;
  grid-row: 2;
  min-height: 62px;
  max-height: 112px;
  box-sizing: border-box;
  padding: 27px 82px 8px 11px;
  resize: none;
  overflow-y: auto;
  border: 0;
  border-radius: 0;
  background: transparent;
  font-size: 13px;
  line-height: 1.35;
  transition: min-height 140ms ease-out, padding 140ms ease-out;
}
/* The enclosing comment shell owns focus across text and doodle tools. */
body[data-route="editor"] .rail-comment-dock { --focus-ring: var(--accent); }
body[data-route="editor"] .rail-comment-dock[data-focus-field]:has([data-focus-input]:focus-visible) {
  outline: 1px solid color-mix(in oklab, var(--accent) 58%, #3b3b3a) !important;
  outline-offset: -1px !important;
}
@media (forced-colors: active) {
  body[data-route="editor"] .rail-comment-dock { --focus-ring: Highlight; }
  body[data-route="editor"] .rail-comment-dock:focus-within { border-color: Highlight; }
}
body[data-route="editor"] .rail-comment-dock[data-compose-expanded] .cmt-compose-input,
body[data-route="editor"] .rail-comment-dock:has(.cmt-compose-input:not(:placeholder-shown)) .cmt-compose-input {
  min-height: 94px;
  padding-bottom: 38px;
}
body[data-route="editor"] .rail-comment-dock .cmt-reply-context:not([hidden]) {
  grid-column: 1 / -1;
  grid-row: 1;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
}
body[data-route="editor"] .rail-comment-dock:has(.cmt-reply-context:not([hidden])) .rail-comment-label {
  display: none;
}
body[data-route="editor"] .rail-comment-dock .cmt-compose-controls {
  grid-column: 1;
  grid-row: 2;
  z-index: 2;
  align-self: end;
  justify-self: start;
  margin: 0 0 8px 8px;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transform: translateY(3px);
  transition: opacity 120ms ease-out, transform 120ms ease-out, visibility 0s linear 120ms;
}
body[data-route="editor"] .rail-comment-dock[data-compose-controls-ready] .cmt-compose-controls,
body[data-route="editor"] .rail-comment-dock:has(.cmt-compose-input:not(:placeholder-shown)) .cmt-compose-controls {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
  transform: none;
  transition-delay: 0s;
}
body[data-route="editor"] .rail-comment-dock .cmt-compose-visibility,
body[data-route="editor"] .rail-comment-dock .cmt-compose-general {
  min-height: 24px;
  padding: 2px 7px;
  border-color: #353638;
  font-size: 10px;
}
body[data-route="editor"] .rail-comment-dock .cmt-compose-general {
  width: 24px;
  padding: 0;
}
body[data-route="editor"] .rail-comment-dock .cmt-compose-submit {
  grid-column: 2;
  grid-row: 2;
  z-index: 2;
  align-self: end;
  justify-self: end;
  min-height: 27px;
  margin: 0 8px 8px 0;
  padding: 4px 10px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .02em;
}
body[data-route="editor"] .rail-comment-dock .cmt-compose-error {
  grid-column: 1 / -1;
  grid-row: 3;
  padding: 0 10px 8px;
}

/* Closed-sidebar composer: a real final row in the projection column. The video
   remains the flexible first child, so portrait and landscape media both contain
   themselves inside whatever height the composer leaves. */
body[data-route="editor"] .monitor-comment-home {
  display: none;
  flex: none;
  width: 100%;
  padding: 8px 8px 4px;
  box-sizing: border-box;
  background: var(--projection-well);
}
body[data-route="editor"] .monitor {
  container-name: proof-monitor;
  container-type: inline-size;
}
body[data-route="editor"] .monitor-comment-home:not(:empty) { display: block; }
body[data-route="editor"] .monitor-comment-home .rail-comment-dock {
  width: 100%;
  max-width: none;
  margin: 0;
}
/* Full-width View needs a compact authoring prompt, not a second content
   horizon. At rest it is only the invitation, centered under picture; focus or
   a draft restores the complete composer without creating a second component. */
body[data-route="editor"] main[data-mode="watch"][data-tools="sheet"] .monitor-comment-home .rail-comment-dock {
  width: min(360px, 100%);
  max-width: 560px;
  margin-inline: auto;
}
body[data-route="editor"] main[data-mode="watch"][data-tools="sheet"] .monitor-comment-home .rail-comment-dock[data-compose-expanded],
body[data-route="editor"] main[data-mode="watch"][data-tools="sheet"] .monitor-comment-home .rail-comment-dock:has(.cmt-compose-input:not(:placeholder-shown)) {
  width: min(560px, 100%);
}
body[data-route="editor"] main[data-mode="watch"][data-tools="sheet"] .monitor-comment-home .rail-comment-dock:not([data-compose-expanded]) .rail-comment-label,
body[data-route="editor"] main[data-mode="watch"][data-tools="sheet"] .monitor-comment-home .rail-comment-dock:not([data-compose-expanded]) .cmt-compose-submit {
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}
body[data-route="editor"] main[data-mode="watch"][data-tools="sheet"] .monitor-comment-home .rail-comment-dock:not([data-compose-expanded]) .cmt-compose-input {
  min-height: 38px;
  max-height: 38px;
  padding: 9px 12px;
  overflow: hidden;
}
body[data-route="editor"] .monitor:has(.monitor-comment-home:not(:empty)) video {
  flex: 1 1 0;
  min-height: 0;
}
@container proof-monitor (max-width: 380px) {
  body[data-route="editor"] .monitor-comment-home .rail-comment-label-action { display: none; }
  body[data-route="editor"] .monitor-comment-home .rail-comment-dock .cmt-compose-input {
    padding-right: 68px;
  }
}
@media (max-width: 640px) and (pointer: coarse) {
  body[data-route="editor"] .monitor-comment-home {
    padding-bottom: calc(6px + env(safe-area-inset-bottom));
  }
  body[data-route="editor"] .monitor-comment-home .rail-comment-dock[data-compose-expanded] .cmt-compose-input,
  body[data-route="editor"] .monitor-comment-home .rail-comment-dock:has(.cmt-compose-input:not(:placeholder-shown)) .cmt-compose-input {
    min-height: 104px;
    padding-bottom: 48px;
  }
  body[data-route="editor"] .monitor-comment-home .rail-comment-dock .cmt-compose-submit {
    min-width: 72px;
    min-height: 40px;
  }
  body[data-route="editor"] .monitor-comment-home .rail-comment-dock .cmt-compose-visibility,
  body[data-route="editor"] .monitor-comment-home .rail-comment-dock .cmt-compose-general {
    min-height: 40px;
    padding-inline: 10px;
  }
}
:root[data-motion="reduced"] body[data-route="editor"] .rail-comment-dock,
:root[data-motion="reduced"] body[data-route="editor"] .rail-comment-dock .cmt-compose-input,
:root[data-motion="reduced"] body[data-route="editor"] .rail-comment-dock .cmt-compose-controls {
  transition: none;
}

/* One attached transport/timeline surface. The base scrub remains visible when
   the timeline rises, so there is always one simple whole-cut navigator. */
body[data-route="editor"] .stripbar {
  /* Literal toolbar primitives from Eden's Figma (widescreen/main_toolbar):
     keep the seek treatment semantic and local instead of weakening the app's
     global proof-gold for every other control. */
  --editor-progress-played: #9f8557;
  --editor-progress-track: #0b0b0b;
  --editor-progress-outline: #373737;
  --editor-playhead-fill: #373737;
  --editor-playhead-stroke: #a6a6a6;
  --editor-transport-stroke: 2px;
  left: clamp(18px, 3vw, 54px);
  right: clamp(18px, 3vw, 54px);
  bottom: clamp(38px, 4.5vh, 54px);
  width: auto;
  height: auto;
  min-height: 76px;
  padding: 0 28px 19px;
  border: var(--editor-transport-stroke) solid var(--editor-progress-outline);
  border-radius: 14px;
  /* Eden's toolbar is an opaque slate object. The workspace veil below still
     softens transcript content passing behind it; the bar itself no longer
     borrows that content or changes tone as the page scrolls. */
  background: var(--editor-progress-track);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: 0 20px 44px rgb(0 0 0 / .44);
}
/* Two authored boxes, full stop: the footer is the recessed black tray and this
   child is Eden's slate control body. The seek canvas is deliberately inset
   from both corner systems instead of attempting to draw a third one. */
body[data-route="editor"] .strip-main-shell {
  position: absolute;
  z-index: 1;
  /* This is the transport body, not the footer's expandable background. Keep
     its authored height when Timeline opens; filling the taller footer made
     the opaque shell paint over the Timeline room while its controls remained
     mounted and interactive underneath. */
  right: calc(-1 * var(--editor-transport-stroke));
  bottom: 14px;
  left: calc(-1 * var(--editor-transport-stroke));
  height: 64px;
  pointer-events: none;
  border: var(--editor-transport-stroke) solid var(--editor-progress-outline);
  border-radius: 14px;
  background: var(--editor-transport);
}
/* The strip itself separates from transcript copy directly beneath it. Keep the
   softening veil inside the transcript region: a main-wide backdrop filter also
   blurred the adjacent video wherever the floating transport crossed it. */
body[data-route="editor"] main > .transcript::after {
  content: '';
  position: absolute;
  z-index: 110;
  right: 0;
  bottom: 0;
  left: 0;
  height: calc(var(--editor-strip-reserve, 132px) + 32px);
  pointer-events: none;
  /* The first 32px are the transport's shadow approach; by the time copy reaches
     the bar itself it must stop reading as transcript. A fast mask knee plus a
     dense canvas tint makes the veil an occlusion boundary, while the blur keeps
     the transition soft instead of looking like a hard clipping rectangle. */
  background: color-mix(in oklab, var(--editor-canvas) 62%, transparent);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, rgb(0 0 0 / .76) 22%, #000 48%);
  mask-image: linear-gradient(to bottom, transparent 0, rgb(0 0 0 / .76) 22%, #000 48%);
  transition: height 240ms cubic-bezier(.22, 1, .36, 1);
}
body[data-route="editor"] .stripbar .transport {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(110px, 1fr) auto minmax(110px, 1fr);
  align-items: center;
  height: 57px;
  min-height: 57px;
  gap: clamp(16px, 2vw, 34px);
}
body[data-route="editor"] .transport-left,
body[data-route="editor"] .transport-center,
body[data-route="editor"] .transport-right,
body[data-route="editor"] .timecluster {
  display: flex;
  align-items: center;
}
body[data-route="editor"] .transport-history { display: flex; align-items: center; gap: clamp(16px, 2vw, 32px); }
body[data-route="editor"] .timeline-transport-left,
body[data-route="editor"] .transport-right > .tl-range-actions { display: none; }
/* Extra editing tools appear above transport only while the timeline is open. */

body[data-route="editor"] .stripbar[data-tmode="3"] .timeline-transport-left {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Comment ears now expose the shared range controls; keep the older transport
   readouts hidden without changing keyboard marks or progress-bar handles. */
body[data-route="editor"] .transport-left { justify-self: start; gap: clamp(16px, 2vw, 32px); }
body[data-route="editor"] .transport-center {
  /* Keep Play on the strip's actual centerline. Time is wider than Speed, so a
     flex row centered the trio while leaving its primary verb visibly adrift. */
  display: grid;
  grid-template-columns: minmax(96px, 1fr) auto minmax(96px, 1fr);
  width: clamp(240px, 31vw, 380px);
  justify-self: center;
  gap: 8px;
}
body[data-route="editor"] .transport-center .timecluster { justify-self: end; }
body[data-route="editor"] .transport-center .playbtn { justify-self: center; }
body[data-route="editor"] .transport-center .rates { justify-self: start; }
body[data-route="editor"] .transport-right { justify-self: end; gap: clamp(14px, 1.6vw, 28px); }
body[data-route="editor"] .stripbar[data-tmode="3"] .tl-range-state {
  max-width: 150px;
  flex: 0 1 auto;
}
body[data-route="editor"] .stripbar[data-tmode="3"] .tl-toolpicker button {
  min-height: 28px;
  padding-block: 3px;
}
@media (max-width: 1500px) {
  body[data-route="editor"] .stripbar[data-tmode="3"] .tl-range-state > span,
  body[data-route="editor"] .stripbar[data-tmode="3"] .tl-toolpicker span,
  body[data-route="editor"] .stripbar[data-tmode="3"] .tl-toolpicker kbd { display: none; }
  body[data-route="editor"] .stripbar[data-tmode="3"] .tl-range-state { max-width: 92px; }
  body[data-route="editor"] .stripbar[data-tmode="3"] .tl-toolpicker button { padding-inline: 7px; }
}
body[data-route="editor"] .timecluster { gap: 7px; }
body[data-route="editor"] .stripbar .transport #curtc { margin-left: 0; }
body[data-route="editor"] .stripbar .transport .mutebtn { margin-left: 0; }
body[data-route="editor"] .stripbar .barverb,
body[data-route="editor"] .stripbar .barlabel,
body[data-route="editor"] .stripbar .transport #timelinetoggle,
body[data-route="editor"] .stripbar .transport #curtc::before,
body[data-route="editor"] .stripbar .transport .rates::before {
  font-size: 10px;
  letter-spacing: 1.7px;
}
body[data-route="editor"] .stripbar .transport #curtc { font-size: 18px; }
body[data-route="editor"] .stripbar .playbtn {
  position: relative;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-dim);
  font-size: 0;
  line-height: 0;
}
body[data-route="editor"] .stripbar .playbtn::before {
  content: '';
  display: block;
  width: 31px;
  height: 31px;
  margin: auto;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath d='M9 5.5 25 16 9 26.5Z' fill='none' stroke='black' stroke-width='2.4' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath d='M9 5.5 25 16 9 26.5Z' fill='none' stroke='black' stroke-width='2.4' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
body[data-route="editor"] .stripbar .playbtn[aria-pressed="true"]::before {
  width: 5px;
  height: 22px;
  -webkit-mask: none;
  mask: none;
  background: currentColor;
  box-shadow: 13px 0 currentColor;
  transform: translateX(-6.5px);
}
body[data-route="editor"] .stripbar .playbtn:hover,
body[data-route="editor"] .stripbar .playbtn:focus-visible {
  color: var(--accent);
  background: transparent;
}
body[data-route="editor"] .stripbar .transport .rates button.on { font-size: 14px; }
body[data-route="editor"] .stripbar .volwrap { min-width: 132px; gap: 10px; }
body[data-route="editor"] .stripbar .transport .volslider {
  width: 80px;
  height: 6px;
}
body[data-route="editor"] .stripstack {
  /* Do not create one stacking context for two incompatible jobs. The channel
     canvas participates below the decorative body so that body's rounded edge
     masks the played fill; the dedicated playhead child escapes above it. */
  z-index: auto;
  left: 0;
  right: 0;
  bottom: calc(-1 * var(--editor-transport-stroke));
  height: 24px;
}
body[data-route="editor"] #strip {
  position: relative;
  /* Range grips and chapter dots are interactive information, not tray paint.
     Keep them above the opaque control shell; the sibling hit plane owns all
     pointer input and remains one layer higher. */
  z-index: 2;
  pointer-events: none;
  display: block;
  height: 24px;
  border-radius: 0;
  cursor: pointer;
}
body[data-route="editor"] .striphit { z-index: 3; cursor: pointer; }
body[data-route="editor"] .strip-playhead {
  --strip-playhead-size: 18px;
  position: absolute;
  z-index: 3;
  bottom: calc(-1 * var(--editor-transport-stroke));
  width: var(--strip-playhead-size);
  height: var(--strip-playhead-size);
  box-sizing: border-box;
  visibility: hidden;
  pointer-events: none;
  transform: translateX(-50%);
  border: 3px solid var(--editor-playhead-stroke);
  border-radius: 50%;
  background: var(--editor-playhead-fill);
}
body[data-route="editor"] .stripbar[data-tmode="3"] .stripstack,
body[data-route="editor"] .stripbar[data-tmode="3"] #strip { display: block; }
body[data-route="editor"] .striplabels { display: none; }
body[data-route="editor"] .stripcomments {
  /* Comment annotations hang in their own lane beneath the seek rail. At this
     station even a current marker's enlarged dot + halo clears the locator's
     ring, so two events at the same time read as aligned—not collided. */
  top: 30px;
  height: 14px;
}
body[data-route="editor"] .strip-comment {
  top: 0;
  height: 12px;
  min-width: 10px;
}
/* A point timestamp runs through the middle of its hit target. A range begins
   at its timestamp and extends rightward; its leading dot straddles that edge.
   This keeps visual time centered without altering seek or comment data. */
body[data-route="editor"] .strip-comment--point { transform: translateX(-50%); }
/* The bar is an overlay over the transcript, not over the picture. Main keeps a
   protected seat so video/tools are never obscured; the transcript alone grows
   back through that seat, allowing copy to pass visibly beneath the floating
   strip while its scroll tail keeps the final paragraph reachable above it. */
body[data-route="editor"] main {
  padding-bottom: var(--editor-strip-reserve);
  transition: padding-bottom 240ms cubic-bezier(.22, 1, .36, 1);
}
body[data-route="editor"] main[data-layout-motion="user"] {
  transition:
    padding-bottom 240ms cubic-bezier(.22, 1, .36, 1),
    grid-template-columns 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
    grid-template-rows 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
body[data-route="editor"] main > .transcript {
  margin-bottom: calc(-1 * var(--editor-strip-reserve));
  transition: margin-bottom 240ms cubic-bezier(.22, 1, .36, 1);
}
body[data-route="editor"] .tscroll { padding-bottom: 170px; }
/* The transcript deliberately continues beneath the floating transport, so its
   lower playhead-return affordance cannot use the transcript body's literal
   bottom edge. Ride the same live reserve as the transport/timeline instead:
   132px at rest, 300px with Timeline open, and the responsive values below.
   The pill sits above the softening veil (110), but below the transport (120). */
body[data-route="editor"] #followpill { z-index: 112; }
/* The chapters sheet is the active top layer in the overlay regime. Keep the
   return affordance in its stable transcript position and let the sheet cover it. */
body[data-route="editor"] .transcript[data-tier-chap="overlay"][data-chap-open] #followpill {
  z-index: 5;
}
body[data-route="editor"] #followpill[data-edge="bottom"],
body[data-route="editor"] #followpill:not([data-edge]) {
  bottom: calc(var(--editor-strip-reserve, 132px) + var(--sp3));
  transition: bottom 240ms cubic-bezier(.22, 1, .36, 1);
}
body[data-route="editor"] .strip-comment-line { display: none; }
body[data-route="editor"] .strip-comment--range .strip-comment-line {
  display: block;
  left: 0;
  right: 0;
  top: 5px;
  height: 2px;
  border-radius: 999px;
  background: var(--comment-mark);
  opacity: .62;
}
body[data-route="editor"] .strip-comment-avatar {
  left: 0;
  top: 2px;
  width: 8px;
  height: 8px;
  color: transparent;
  background: var(--comment-mark);
  box-shadow: 0 0 0 2px var(--editor-transport), 0 0 0 3px rgb(255 255 255 / .12);
  font-size: 0;
  transition: transform 120ms ease-out;
  z-index: 1;
}
body[data-route="editor"] .strip-comment--point .strip-comment-avatar {
  left: 50%;
  transform: translateX(-50%);
}
body[data-route="editor"] .strip-comment--range .strip-comment-avatar {
  transform: translateX(-50%);
}
body[data-route="editor"] .strip-comment:hover .strip-comment-avatar,
body[data-route="editor"] .strip-comment:focus-visible .strip-comment-avatar,
body[data-route="editor"] .strip-comment[data-current="true"] .strip-comment-avatar {
  transform: translateX(-50%) scale(1.4);
}
body[data-route="editor"] .timelinepanel {
  --timeline-ruler-height: 32px;
  --timeline-track-height: 68px;
  --timeline-nav-height: 10px;
  --timeline-panel-padding-top: 12px;
  --timeline-panel-padding-bottom: 10px;
  --timeline-panel-gap: 6px;
  min-height: 0;
  margin: 0;
  padding: var(--timeline-panel-padding-top) 0 var(--timeline-panel-padding-bottom);
  gap: var(--timeline-panel-gap);
  border-bottom: 1px solid #373737;
  background: transparent;
  box-sizing: border-box;
  height: calc(
    var(--timeline-ruler-height) + var(--timeline-track-height) + var(--timeline-nav-height)
    + var(--timeline-panel-padding-top) + var(--timeline-panel-padding-bottom)
    + var(--timeline-panel-gap) + var(--timeline-panel-gap) + 1px
  );
  overflow: visible;
  opacity: 1;
  transform: none;
  visibility: visible;
  transition:
    height 240ms cubic-bezier(.22, 1, .36, 1),
    padding 240ms cubic-bezier(.22, 1, .36, 1),
    opacity 160ms ease-out 50ms,
    transform 240ms cubic-bezier(.22, 1, .36, 1),
    border-color 160ms ease-out,
    visibility 0s;
  /* The fixed-height transport shell is a sibling layer beneath the room. */
  position: relative;
  z-index: 2;
}
/* Keep [hidden]'s semantic/AT state while overriding its UA display:none so the
   layout can collapse instead of teleporting. Visibility waits for the exit. */
body[data-route="editor"] .timelinepanel[hidden] {
  display: flex;
  height: 0;
  overflow: hidden;
  padding-block: 0;
  border-color: transparent;
  opacity: 0;
  transform: translateY(8px);
  visibility: hidden;
  pointer-events: none;
  transition:
    height 180ms cubic-bezier(.22, 1, .36, 1),
    padding 180ms cubic-bezier(.22, 1, .36, 1),
    opacity 120ms ease-in,
    transform 180ms cubic-bezier(.22, 1, .36, 1),
    border-color 120ms ease-in,
    visibility 0s linear 180ms;
}
/* The entire visible ruler is the scrub target. A 32px band is forgiving enough
   for fast editing while the restrained well and inset edge distinguish it from
   the otherwise black air above the timeline without adding panel chrome. */
body[data-route="editor"] .timelineruler {
  height: var(--timeline-ruler-height);
  border-radius: 4px;
  background: color-mix(in oklab, var(--editor-progress-outline) 24%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--editor-progress-outline) 72%, transparent);
  cursor: ew-resize;
  transition: background-color 120ms ease-out, box-shadow 120ms ease-out;
}
body[data-route="editor"] .timelineruler:hover {
  background: color-mix(in oklab, var(--editor-progress-outline) 38%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--editor-playhead-stroke) 48%, transparent);
}

@media (max-width: 1120px) {
  body[data-route="editor"] .top { grid-template-columns: var(--app-wordmark-width) minmax(0, 1fr) auto auto auto; padding-inline: var(--app-header-pad-inline); }
  body[data-route="editor"] .top .brand { background-size: contain; }
  body[data-route="editor"] .top #exportbtn { display: none; }
  body[data-route="editor"] .top #sharebtn { grid-column: 4; }
  body[data-route="editor"] .top #editor-account-control { grid-column: 5; }
  body[data-route="editor"] .stripbar .barverb { display: none; }
  body[data-route="editor"] .stripbar .volwrap { min-width: 96px; }
  body[data-route="editor"] .stripbar .transport .volslider { width: 56px; }
  /* Tablet: TIME/SPEED prefixes and equal 1fr columns squeeze Play into the
     current timecode before the phone composition at 560px. Drop the prefixes
     and size the center trio to content so Play stays on the centerline. */
  body[data-route="editor"] .stripbar .transport #curtc::before,
  body[data-route="editor"] .stripbar .transport .rates::before { display: none; }
  body[data-route="editor"] .transport-center {
    grid-template-columns: auto auto auto;
    width: auto;
    justify-self: center;
  }
}

@media (max-width: 900px) {
  body[data-route="editor"] { --editor-strip-reserve: 96px; }
  body[data-route="editor"] .top { grid-template-columns: 52px minmax(0, 1fr) auto auto; }
  body[data-route="editor"] .top .crumbtrail { display: none; }
  body[data-route="editor"] .top .modepick { grid-column: 2; justify-self: end; }
  body[data-route="editor"] .top #sharebtn { grid-column: 3; }
  body[data-route="editor"] .top #editor-account-control { grid-column: 4; }
  body[data-route="editor"] .monitor-toolbar .sidebar-toggle { width: 36px; height: 36px; flex-basis: 36px; }
  body[data-route="editor"] main[data-layout="top"][data-tools="sheet"] {
    /* The solver has demoted the rail at this tier. Use an explicit vertical
       stack instead of letting a stale desktop grid track survive the resize
       for one frame and collapse the transcript column. */
    display: flex;
    flex-direction: column;
  }
  body[data-route="editor"] main[data-layout="top"][data-tools="sheet"] .monitor {
    width: 100%;
    height: calc(var(--vidH) + var(--monitor-toolbarH));
    flex: 0 0 calc(var(--vidH) + var(--monitor-toolbarH));
    order: 0;
  }
  body[data-route="editor"] main[data-layout="top"][data-tools="sheet"] .transcript {
    width: 100%;
    flex: 1 1 0;
    min-height: 0;
    order: 1;
  }
  body[data-route="editor"][data-tier-rail="summoned"] main > .rail {
    /* Restore the shelf positioning that the editor's generic direct-child
       stacking rule would otherwise outrank at this specificity. The complete
       main context owns the semantic drawer rung; the scrim and sheet retain
       their named order inside it. */
    position: fixed;
    z-index: var(--layer-drawer);
  }
  body[data-route="editor"][data-tier-transcript="docked"] main[data-layout="top"] .transcript {
    position: relative;
    transform: none;
    visibility: visible;
    opacity: 1;
  }
  body[data-route="editor"] .stripbar { left: 12px; right: 12px; bottom: 20px; padding-inline: 14px; }
  body[data-route="editor"] .stripbar .barlabel { display: none; }
  body[data-route="editor"] .stripbar .volwrap { min-width: 58px; }
}

/* Below 640px the timeline range tools stack into two rows (strip.css). The
   workspace seat must grow with it; desktop's 264px reserve describes the
   denser single-row timeline only. */
@media (max-width: 640px) {
  body[data-route="editor"]:has(.stripbar[data-tmode="3"]) {
    --editor-strip-reserve: 364px;
  }
}

/* Phone transport: preserve every useful control while dropping words the
   surrounding object already explains. The old invisible total-duration slot
   plus TIME/SPEED prefixes pushed Timeline past the right edge at 390px. */
@media (max-width: 560px) {
  body[data-route="editor"] .stripbar .transport {
    grid-template-columns: 0 minmax(0, 1fr) auto;
    gap: 8px;
  }
  body[data-route="editor"] .stripbar .transport-left { display: none; }
  body[data-route="editor"] .transport-center {
    grid-template-columns: auto auto auto;
    width: auto;
    min-width: 0;
    justify-self: start;
    gap: 4px;
  }
  body[data-route="editor"] .transport-right { gap: 0; }
  /* Volume remains available from hardware controls and desktop/tablet. On a
     phone it consumed the space needed by the primary play/time and Timeline
     doors, then clipped all three because the footer deliberately cannot
     scroll. Give the phone a composed transport rather than a squeezed desktop
     one. */
  body[data-route="editor"] .stripbar .volwrap { display: none; }
  body[data-route="editor"] .stripbar .transport #curtc::before,
  body[data-route="editor"] .stripbar .transport .rates::before { display: none; }
  body[data-route="editor"] .stripbar .transport #timelinetoggle {
    min-width: 44px;
    min-height: 44px;
    padding: 0;
    font-size: 0;
    letter-spacing: 0;
  }
  body[data-route="editor"] .stripbar .transport #timelinetoggle .tl-glyph {
    margin-left: 0;
  }
}

@media (max-width: 360px) {
  body[data-route="editor"] .top #sharebtn { display: none; }
}

:root[data-motion="reduced"] body[data-route="editor"] .timelinepanel,
:root[data-motion="reduced"] body[data-route="editor"] main,
:root[data-motion="reduced"] body[data-route="editor"] main > .transcript::after,
:root[data-motion="reduced"] body[data-route="editor"] main > .transcript,
:root[data-motion="reduced"] body[data-route="editor"] .transcript .chapters-index,
:root[data-motion="reduced"] body[data-route="editor"] .chapters-index-list,
:root[data-motion="reduced"] body[data-route="editor"] .chapters-index-resizer,
:root[data-motion="reduced"] body[data-route="editor"] .tscroll,
:root[data-motion="reduced"] body[data-route="editor"] .monitor video,
:root[data-motion="reduced"] body[data-route="editor"] #followpill {
  transition: none;
}
:root[data-motion="reduced"] body[data-route="editor"] .monitor video {
  opacity: 1;
}

/* TOOLS ACTION PLATES — four complete verbs, no settings-form preamble. The
   shell owns only this register; each React component owns its plate styling,
   state, configuration strip, and accessible action. */
body[data-route="editor"] .dock-pane[data-pane="tools"] {
  padding: 20px 18px;
}
body[data-route="editor"] .dock-pane[data-pane="tools"] .tools-actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-flow: row;
  align-content: start;
  gap: 12px;
  flex: none;
  padding: 0;
  border: 0;
  background: transparent;
  container: tools-panel / inline-size;
}
body[data-route="editor"] .tools-actions > .ctl {
  min-width: 0;
  margin: 0;
  padding: 0;
}
body[data-route="editor"] .tools-actions > #cleanup-panel { display: contents; }
body[data-route="editor"] .dock-pane[data-pane="tools"] > .keys { display: none; }

/* Eden's generic editor child-layer rule gives direct children relative
   positioning. A summoned rail is the exception at every width, not only the
   old <=900px demotion breakpoint: it is a real off-canvas drawer. */
body[data-route="editor"][data-tier-rail="summoned"] main > .rail {
  position: fixed;
  z-index: var(--layer-drawer);
  /* The docked rail can share the canvas ground. Once summoned it is a real
     sheet over transcript/picture and must be opaque enough to read. */
  background: var(--editor-canvas);
  border-top-color: var(--editor-control-line);
}

/* Timeline expansion adds one tool row above the unchanged transport grid. */
body[data-route="editor"] .stripbar[data-tmode="3"] > .timeline-transport-left {
  position: relative;
  z-index: 2;
  min-height: 42px;
  box-sizing: border-box;
  padding-block: 6px;
}
body[data-route="editor"] .stripbar[data-tmode="3"] .strip-main-shell { height: 106px; }
@media (min-width: 641px) {
  body[data-route="editor"]:has(.stripbar[data-tmode="3"]) { --editor-strip-reserve: 306px; }
}
body[data-route="editor"] .history-icon { display: none; }

/* Selection audition shares the existing transport; stage owns its active state. */
#timeline-play-selection .audition-stop { display: none; }
#timeline-play-selection[data-playing="true"] .audition-play { display: none; }
#timeline-play-selection[data-playing="true"] .audition-stop { display: block; }

/* Zoom is spatial navigation: align its group above the Timeline toggle. */
body[data-route="editor"] .timeline-transport-left .tl-zoom-controls { margin-left: auto; }
/* I/O grips must clear both the tray lip and the foreground playhead ball. */
body[data-route="editor"] #strip { z-index: 4; }
body[data-route="editor"] .striphit { z-index: 5; }

/* Quick chapter creation stays in the closed outline rail. */
body[data-route="editor"] #addchapter { display: none; }
body[data-route="editor"][data-editor-access="edit"] .transcript:not([data-chap-open]) #addchapter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 32px;
  width: 32px;
  height: 30px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 5px;
  background: transparent;
  color: var(--ink-dim);
  opacity: .7;
}
body[data-route="editor"] #addchapter:hover,
body[data-route="editor"] #addchapter:focus-visible {
  color: var(--ink);
  background: var(--wash);
  opacity: 1;
}
body[data-route="editor"] .transcript:not([data-chap-open]) .chapters-index-head {
  flex-direction: column;
  align-items: flex-start;
}
body[data-route="editor"] #addchapter .icn { width: 24px; height: 24px; }
/* Keep the full paragraph grip clear of the collapsed chapter rail. */
body[data-route="editor"] .transcript:not([data-chap-open]) .grip { left: -24px; }

/* A failed save must remain visible even in the minimal editor masthead. */
body[data-route="editor"] .top .syncstatus.warn {
  display: block;
  position: fixed;
  top: 64px;
  right: 16px;
  max-width: min(560px, calc(100vw - 32px));
  white-space: normal;
  padding: 10px 14px;
  background: var(--bg, #151617);
  border: 1px solid currentColor;
  border-radius: 6px;
  z-index: 100;
}

/* Text focus or an open doodle editor keeps the whole comment active, including
   when pointer input moves to the video. Ordinary unfocused text drafts stay flat. */
body[data-route="editor"] .rail-comment-dock:has([data-range-controls]) {
  overflow: visible;
  margin-top: 10px;
}
body[data-route="editor"] .rail-comment-label[data-range-controls] {
  top: 5px;
  right: 12px;
  left: 12px;
  align-items: center;
  transform: translateY(0);
  transition: transform 220ms cubic-bezier(.25, .8, .25, 1);
}
body[data-route="editor"] .rail-comment-dock:is(:focus-within, :has([data-compose-draft-open])) .rail-comment-label[data-range-controls] {
  transform: translateY(-33px);
}
body[data-route="editor"] .rail-comment-label[data-range-controls] .rail-comment-label-action { display: none; }
body[data-route="editor"] .rail-comment-dock:not(:is(:focus-within, :has([data-compose-draft-open]))) .rail-comment-label [role='slider'] {
  border-color: transparent;
  background: transparent;
}
body[data-route="editor"] .rail-comment-dock:has([data-range-controls]) .cmt-compose-input {
  padding-top: 34px;
  border-radius: 8px 8px 0 0;
}
body[data-route="editor"] .rail-comment-dock:is(:focus-within, :has([data-compose-draft-open])):has([data-range-controls]) .cmt-compose-input { padding-top: 12px; }
html[data-motion='reduced'] body[data-route="editor"] .rail-comment-label[data-range-controls] { transition: none; }

body[data-route="editor"] .rail-comment-dock:has([data-range-controls]):is(:focus-within, :has([data-compose-draft-open])) { margin-top: 34px; }
body[data-route="editor"] .rail-comment-label[data-range-controls] > [role='group'] {
  visibility: hidden;
  opacity: 0;
  transition: opacity 140ms ease, width 220ms cubic-bezier(.25, .8, .25, 1), gap 220ms cubic-bezier(.25, .8, .25, 1);
}
body[data-route="editor"] .rail-comment-dock:is(:focus-within, :has([data-compose-draft-open])) .rail-comment-label[data-range-controls] > [role='group'] {
  visibility: visible;
  opacity: 1;
}
body[data-route="editor"] .rail-range-rest-time { position: absolute; left: 0; white-space: nowrap; }
body[data-route="editor"] .rail-comment-dock:is(:focus-within, :has([data-compose-draft-open])) .rail-range-rest-time { visibility: hidden; }
html[data-motion='reduced'] body[data-route="editor"] .rail-comment-label[data-range-controls] > [role='group'] { transition: none; }

/* Range endpoints get their own lane ABOVE the progress channel. */
body[data-route="editor"] .strip-range-lane {
  position: absolute;
  left: 0;
  top: -10px;
  width: 100%;
  height: 10px;
  z-index: 6;
  pointer-events: none;
}
body[data-route="editor"] .stripstack:has(.strip-range-lane) .striphit {
  top: -10px;
  height: 34px;
  z-index: 7;
}
body[data-route="editor"] .rail-comment-dock:not(:is(:focus-within, :has([data-compose-draft-open]))) .rail-comment-label[data-range-controls] > button { visibility: hidden; }
/* ------------------------------------------------ floaters */
/* follow pill — region-owned: absolute within .transcript, on the edge the
   playhead left by (data-edge set by view.syncFollowPill) */
.pill {
  position: absolute; left: 50%; transform: translateX(-50%);
  background: var(--surface); border: 1px solid var(--accent);
  color: var(--accent); border-radius: 99px; padding: 6px 14px;
  font-size: var(--fs-ui); z-index: 9;
  white-space: nowrap;
}
.pill .dir { font-family: var(--f-tc); }
/* The top edge begins below the sticky transcript masthead. Both surfaces read
   the same body-owned height token, so a future masthead resize cannot strand
   the return control underneath its chrome again. */
.pill[data-edge="top"] { top: calc(var(--transcript-masthead-height, 40px) + var(--sp3)); }
.pill[data-edge="bottom"], .pill:not([data-edge]) { bottom: var(--sp3); }
/* TRANSCRIPT-ZONE-1 — the pill lives in .transcript-body and the verb bar now docks
   in the .transcript-footer band BELOW it, so the body (and its pill) already end
   above the retired verb bar. The old bottom-lift step-over is obsolete:
   the reserved footer clears the pill structurally, no magic offset to drift. */
.toast {
  position: fixed; bottom: 86px; left: var(--transMid, 40vw);
  transform: translateX(-50%);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 4px; padding: 8px 14px; font-size: var(--fs-ui); z-index: var(--layer-toast);
  max-width: 380px;
}
/* ---------- shell pages (A1 s3c) ---------- */
/* While a shell page shows, anything that isn't the page container OR a
   body-level overlay leaves the flow (icndefs, the editor chrome). :not(#route-page)
   carries id specificity, so this outranks every sibling's own display rule.
   :not([data-overlay]) is the one seam every transient overlay (dialogs, the
   context menu, the quota panel, the toast) rides so it PIERCES the curtain —
   an overlay fired FROM a shell page (a browse Rename, a card menu, a rename
   toast, a quota refusal) must paint, not sit dead at 0×0. mountOverlayLayer
   stamps the marker (overlayLayer.ts / DLG-CURTAIN-1); a static overlay carries
   it inline (#toast). (Slice B retired the FLAG-A "boot" cold-load seed: the
   editor chrome no longer ships inline in index.html — showEditor injects
   EDITOR_CHROME into #route-page on the editor route — so there is no inline
   chrome to hold out of the first paint. A data-neutral opening plane occupies
   #route-page until renderRoute resolves the route.) */
body[data-route="page"] > :not(#route-page):not([data-overlay]), body[data-route="landing"] > :not(#route-page):not([data-overlay]) { display: none; }
/* Editor and browse routes intentionally lock body scrolling and give it to
   their internal panes. Public/ceremony pages are documents: on a short phone
   their column must be allowed to grow past 100dvh and scroll, or the footer and
   recovery doors are clipped with no route to reach them. */
body[data-route="page"], body[data-route="landing"] {
  height: auto;
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: hidden;
  overflow-y: auto;
}
.route-page {
  grid-row: 1 / -1;            /* the body grid's only visible child fills it */
  justify-self: center; align-self: start;
  padding: 22vh var(--sp4) var(--sp4);
  max-width: 460px;
}

.route-page h1 {
  font-family: var(--f-display); font-weight: 700; font-size: 26px;
  margin: 0 0 var(--sp2);
}
.route-page p { color: var(--ink-dim); margin: 0 0 var(--sp4); }
.route-page a { color: var(--accent); text-decoration: none; }
.route-page a:hover { text-decoration: underline; }

/* A cold load has to wait for the bundle and the session projection. The only
   useful first-paint behavior is continuity with the app ground: transient
   branding or progress motion flashes for a frame before the real header mounts.
   Route mounting replaces this neutral plane atomically. */
.page-opening {
  position: fixed;
  inset: 0;
  background: var(--bg-deep);
}
/* ------------------------------------------------ touch affordances (P5)
   One coarse-pointer tier, ADDITIVE sizing/visibility only — never layout,
   never display, never a forked UI per input. Gesture GRAMMAR (long-press
   selection, drag disambiguation, pencil) is R15's surface; this tier only
   guarantees that what already exists can be SEEN and HIT by a finger.
   `pointer: coarse` is the one key — no hover:none second axis (two queries
   = two rule sets that drift; same one-key ruling as the motion tier). */

/* tap responsiveness everywhere: interactive chrome opts out of double-tap
   zoom (scroll + pinch stay native — G11). NOT .divider or the Cleanup Module's
   threshold control: those are pointer-captured drag surfaces and keep none. */
button, .word, .sil, .speaker { touch-action: manipulation; }

@media (pointer: coarse) {
  /* Same visual 24px seek rail, full finger-sized acquisition plane. This lives
     in the one coarse-pointer tier so touch behavior has one authority. */
  .striphit { height: 44px; }

  /* The viewer-band disclosure stays visually compact enough to share the 42px
     Cuts/tab rhythm, while its transparent halo reaches the 44px touch floor. */
  body[data-route="editor"] .monitor-toolbar .sidebar-toggle { position: relative; }
  body[data-route="editor"] .monitor-toolbar .sidebar-toggle::before {
    content: "";
    position: absolute;
    inset: -7px;
  }

  /* a finger can't hover: the hover-revealed block controls rest quietly
     visible. The block:hover reveal is now true-hover-gated (§W1.6 P1), so on a
     coarse pointer this 0.55 rest is the whole story — no sticky tap-reveal */
  .grip, .secmenu, .seckill { opacity: 0.55; }
  .secstar { opacity: 0.55; }   /* STARS-1a: same coarse rest, own line (audit pins stay byte-stable) */

  /* the seam can't be discovered by hover: the divider hairline rests visible */
  .divider::after { background: var(--line); }

  /* 8px grab zone → 24px tap zone — HIT AREA only, via a transparent ::before
     bleed; the divider's box (and the -4px math centering it on the seam)
     never moves, so placement rules stay untouched */
  .divider::before { content: ""; position: absolute; }
  .divider[data-axis="x"]::before { top: 0; bottom: 0; left: -8px; right: -8px; }
  .divider[data-axis="y"]::before { left: 0; right: 0; top: -8px; bottom: -8px; }

  /* the chip's one action gets a real tap pad — it grows INTO the chip body's
     seek zone, and that's the right trade on touch: the action wins, words
     still seek. Visual glyph size unchanged. The chip box is 25px tall (it sits
     IN the text line like a word), so the 44px AAA floor is geometrically
     impossible here without breaking the inline rhythm — this is the WCAG 2.5.5
     "inline target" exception; 24px is the 2.5.8-AA spacing floor, the honest
     ceiling for an in-line chip. (Same exception holds for the divider above:
     its hit zone is deliberately 24px, not 44px — a 44px seam grab would bleed
     22px into each flanking panel and swallow clicks on adjacent content.) */
  .sil-x { padding: 0 6px; min-width: 24px; opacity: 1; pointer-events: auto; }

  /* the section-move grip (⠿) is the block's PRIMARY drag handle but rests only
     16px wide — under a finger that's a miss. A transparent ::before bleeds the
     HIT AREA to the 44px WCAG 2.5.5 floor (16 + 14 each side) without moving the
     visible dot cluster (::after) or the grip's own box; it lives in the left
     gutter so the right edge bleeds only ~2px toward content. Full block height
     already clears 44px on any real paragraph. 44px is the convergent target of
     WCAG 2.5.5 (AAA), Apple HIG, and Material — a primary drag affordance earns
     the AAA floor (touch-input law #7; review-enforced, jsdom can't measure it). */
  .grip::before {
    content: ""; position: absolute;
    top: 0; bottom: 0; left: -14px; right: -14px;
  }

  /* the section-header ☰ MENU and STRIKE (×) buttons rest only ~22px wide —
     a near-miss under a finger. A transparent ::before bleeds the HIT AREA
     HORIZONTALLY without moving the button box (the flex header never reflows —
     same box-untouched discipline as .grip / .divider). The bleed is
     horizontal-ONLY by design: the paragraph's first line sits directly under
     the 20px header, so a downward halo would steal taps from the first word —
     the same adjacent-content exception that caps .sil-x / .divider at the AA
     floor instead of the gutter grip's 44px AAA. 6px each side stays well inside
     the gap to its neighbour, so neither halo reaches one; the controls are
     already AA via the WCAG 2.5.8 spacing rule (well separated), and this widens
     the actual tap pad. */
  .secmenu, .seckill { position: relative; }
  .secstar { position: relative; }
  .secstar::before {
    content: ""; position: absolute;
    top: 0; bottom: 0; left: -6px; right: -6px;
  }
  .secmenu::before, .seckill::before {
    content: ""; position: absolute;
    top: 0; bottom: 0; left: -6px; right: -6px;
  }

  /* the browse/project-row ⋯ kebab (.row-actions) rests opacity:0 +
     pointer-events:none and is revealed ONLY by :hover / :focus-within / a
     tab-driven :focus-visible — a finger issues none of those, so the SAME
     menu reachable by right-click/hover on desktop is UNREACHABLE on touch.
     Under a coarse pointer it rests fully shown (RFC W1.4 P0). Covers the two
     hidden-rest forms: the asset card (list + its .browse-grid descendant) and
     the home project-row (.project-item). The .project-header and .project-tile
     forms already rest visible, and the base .row-actions never hides — so they
     need no fallback here. */
  .browse-card > .row-actions,
  .project-item > .row-actions { opacity: 1; pointer-events: auto; }

  /* MOBILE-2b — the STANDALONE chrome buttons: the selection verb bar (Strike/
     Cut/Copy…), the dock's panel tabs (Tools/Notes), and the cut-tab bar's tab +
     "+ New cut". Each rests ~28–33px — a near-miss under a finger. Unlike the
     inline-in-text .sil-x (capped at the 24px AA exception), these sit in their
     OWN bars with room below, so they earn the full 44px WCAG 2.5.5 floor on
     their own box. min-height only: additive, the box never moves on a fine
     pointer and no ::before halo is needed (the button itself grows). */
  .dock-tabs button { min-height: 44px; }
  .tl-toolpicker button { min-width: 44px; min-height: 44px; justify-content: center; }
  .browse-new-project { min-height: 44px; }
  .tl-range-actions button { min-height: 44px; }
  .tl-actionpop { gap: 3px; padding: 4px; }
  .tl-actionpop-verb { min-height: 44px; padding-inline: 13px; }

  /* The transcript selection handle is intentionally a 26px inline mark so it
     does not disturb line rhythm. On touch, a transparent halo raises its real
     target to 44px without changing the placement box; once open, the detached
     action tray has room for full 44px controls. */
  .selpop-icon { position: relative; }
  .selpop-icon::before {
    content: ""; position: absolute; inset: -9px;
  }
  .selpop-verbs button { min-height: 44px; padding-inline: 13px; }

  /* MOBILE-2c — the EDITOR HEADER chrome pills: the mode toggle (edit/watch), the
     transcript-recall ¶ peek, and the Share entry. They rest 21–32px — near-misses
     under a finger. Like the MOBILE-2b bar buttons they sit in the header with room,
     so they earn the full 44px WCAG 2.5.5 floor on their own box: min-height only
     (additive, the box never moves on a fine pointer, no forked layout — a button
     centres its label natively so no alignment rule is needed). The React account
     control owns its circular touch target in its CSS Module. The header stays
     flex-wrap, so the taller pills reflow to a new row instead of forcing horizontal
     overflow at phone width. */
  /* The segmented control clips its children to its rounded frame. Grow that
     frame with the touch targets; otherwise 44px buttons inside Eden's 30px
     desktop frame are bottom-clipped and the active label appears displaced. */
  body[data-route="editor"] .top .modepick { height: 44px; }
  .modepick button,
  .sharehandle { min-height: 44px; }

}
/* B2.e I3-B — the empty-scope drag-drop zone. The empty-state box becomes a
   full-bleed drop target (position context for the overlay); the overlay is a
   dashed, accent-washed sheet shown only while a drag is over it, labelled so
   the affordance reads. Token-only, no hard-coded color (the contrast swaps
   carry through --accent/--line). The wiring (drop → same ingest path) lives in
   browse.ts; this is purely the look-lane feedback. */
.browse-state.dropzone { position: relative; }
.drop-overlay {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border: 2px dashed var(--accent);
  border-radius: 8px;
  background: var(--accent-wash);
  color: var(--accent);
  font-weight: 600;
  pointer-events: none;   /* the sheet must not eat the drop it announces */
}
/* the [hidden] attribute is the source of truth (JS toggles it) — this rule must
   out-specify `.drop-overlay { display: grid }` so the UA hidden rule isn't lost. */
.drop-overlay[hidden] { display: none; }

/* B2.i — the determinate upload bar on an 'ingesting' pending card. A thin,
   token-washed track that fills with --accent as bytes land (the byte-PUT drives
   `value` via setCardProgress); with no value attribute the UA renders its own
   indeterminate sweep, which we leave alone (the `Uploading…` label carries the
   state). Token-only, cross-engine reset so the fill reads on WebKit + Gecko. */
.card-progress {
  display: block;
  width: 100%;
  height: 4px;
  margin: 4px 0;
  border: none;
  border-radius: 2px;
  background: var(--line);
  appearance: none;
  -webkit-appearance: none;
}
.card-progress::-webkit-progress-bar { background: var(--line); border-radius: 2px; }
.card-progress::-webkit-progress-value { background: var(--working); border-radius: 2px; }
.card-progress::-moz-progress-bar { background: var(--working); border-radius: 2px; }

/* B2.i slice-2 — the 'processing' card's INDETERMINATE affordance. Once the bytes
   are in the provider's hands there's no client ETA, so the determinate bar gives
   way to a leading spun glyph (::before) beside the status copy (Processing… /
   Transcribing…) — read: "working, no %". Token-only color. The reduced-motion
   floor swaps the spin for a static accent dot (a11y, never an animation). */
.card-spinner {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 4px 0;
  font-size: var(--fs-note);
  color: var(--ink-dim);
}
.card-spinner::before {
  content: "";
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--working-soft);
  border-top-color: var(--working);
  animation: card-spin 0.8s linear infinite;
}
@keyframes card-spin { to { transform: rotate(360deg); } }
/* a11y floor: the attribute tier (main.ts resolves pref-or-OS into it) swaps the
   spin for a static accent dot — never an animation. Same selector the rest of
   the reduced-motion rules use (no @media fork, per uiprefs). */
:root[data-motion="reduced"] .card-spinner::before { animation: none; border-color: var(--working); }

/* B2.i slice-3 — the pending card's blurred LOCAL preview thumbnail. The picked
   File rides an objectURL into a muted, never-played <video> whose first frame is
   the static poster; CSS blurs it + lays a token-tinted wash over it so the card
   reads "this specific video, working" instead of a bare text tile. The box
   reserves a FIXED 16/9 slot from byte zero so the later handoff to the real
   storyboard thumbnail (creds-gated, a follow-up slice) moves zero pixels — the
   "no layout shift" acceptance made concrete. Token-only; if blur is unsupported
   the frame stays a dimmed poster (the wash), never a raw bright frame fighting
   the card chrome. The blur radius + wash opacity are look-lane (tunable). */
.card-thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 4px;
  background: var(--line);   /* a settled placeholder behind a not-yet-decoded frame */
}
.card-thumb-media {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(8px);
  transform: scale(1.08);    /* bleed past the blur's transparent edge halo */
}
.card-thumb::after {         /* the token-tinted working wash that keeps copy legible over the frame */
  content: "";
  position: absolute;
  inset: 0;
  background: var(--working-wash);
}
/* the READY-side poster (B2.i slice-3b) is the real storyboard frame, not a
   working-state placeholder — show it sharp: drop the blur/scale and the tint
   wash the pending local-preview carried. Same 16/9 box ⇒ zero-shift handoff. */
.card-thumb.ready .card-thumb-media {
  filter: none;
  transform: none;
}
.card-thumb.ready::after {
  display: none;
}

/* browse-chrome 3 — the no-thumbnail placeholder. A settled asset that never minted
   a poster keeps the SAME reserved 16/9 .card-thumb box every sibling tile has (the
   gallery row stays even), painted as a quiet --line-tinted panel with a faint
   centered frame glyph. Terminal/neutral, NOT a working state — drop the
   working-wash overlay like .ready does. Restrained by design (this is not the loud
   audio waveform). The mark dims to a hint via opacity; --ink is the only color. */
.card-thumb.blank {
  display: grid;
  place-items: center;
}
.card-thumb.blank::after {
  display: none;
}
.card-thumb-blankmark {
  width: 26%;
  max-width: 40px;
  color: var(--ink);
  opacity: 0.16;
}
.card-thumb-blankmark svg {
  width: 100%;
  height: auto;
  display: block;
}

/* AK-3 — the AUDIO-ONLY poster. A ready asset with no video frame (MUX skips the
   storyboard on an audio ingest, and the playback-id thumbnail the route mints would
   404) fills the SAME reserved 16/9 box with the canonical waveform (browse.ts
   AUDIO_WAVE_D) on a FLAT darkroom ground, instead of a broken <img> or a bare box.
   The ground is the deepest surface token (--bg-deep) so the amber waveform + glow
   pops — a flat paint, NOT a gradient blend (the sheet bans painted surface gradients;
   design-audit.test pins that). Zero-shift with the video poster it stands in for. The
   working-state wash (::after) is suppressed — this is a settled poster, not a
   processing tint. Static (no motion ⇒ no reduced-motion fork); the svg is aria-hidden
   (the card name carries identity). Glow = the token at a soft drop-shadow. Token-only. */
.card-thumb.audio {
  background: var(--bg-deep);
}
.card-thumb.audio::after { display: none; }
.card-thumb.audio .audio-wave { display: block; width: 100%; height: 100%; }
.card-thumb.audio .audio-wave path {
  fill: none;
  stroke: var(--audio-wave);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 0 3px var(--audio-wave));
}

/* B2.i slice-4 — the terminal 'errored' card's failure fold. The working states
   (bar/spinner) speak --working (the semantic status amber); failure speaks
   --rust (the palette's negative/"stricken" role — there is NO dedicated
   --error/--danger token yet; this is the sane semantic default, flagged for the
   look-lane). The three statuses now each own a hue. A quiet rust-washed region
   reads as a STOPPED dead-end, not a working state. Token-only, no hard-coded
   color; static (no motion, so no reduced-motion fork needed). The glyph is a
   decorative rust disc (the copy + role=alert carry the meaning). */
.card-error {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 4px 0;
  padding: 6px 8px;
  border-radius: 4px;
  background: var(--rust-wash);
  font-size: var(--fs-note);
}
.card-error-glyph {
  flex: none;
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--rust);
  color: var(--bg-deep);             /* dark mark on the rust disc — reads cleanly */
  font-weight: 700;
  line-height: 1;
}
.card-error-glyph::before { content: "!"; }
.card-error .card-state { color: var(--rust); font-weight: 600; }
/* the reason wraps to its own line under the headline+glyph (the flex row) so it
   reads as the explanatory subline, with Retry trailing as the recovery control */
.card-error-reason { flex: 1 1 100%; color: var(--ink-dim); }
.card-error .card-retry { flex: none; }   /* the standard button treatment (style above); Retry sits inline */

/* brief §3 — the SEMANTIC STATUS DOT. Every settled card's state label (and the
   detail pane's) leads with a small disc whose hue MEANS the status: green=ready ·
   amber=working (ingesting/processing/Transcribing…) · rust=errored. The copy
   carries the meaning; the dot is decorative reinforcement that lets the status
   read at a glance down a list — so no contrast gate. The hue is profile-constant
   (browse.ts stamps a state-* kind class; status never swaps under the cool
   library). Token-only, no hex; an unkinded label gets the neutral --ink-faint
   default. Dot size/gap are look-lane placeholders. */
.card-state::before, .preview-state::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 6px;
  border-radius: 50%;
  vertical-align: middle;
  background: var(--ink-faint);
}
.state-ready::before   { background: var(--ready); }
.state-working::before { background: var(--working); }
.state-errored::before { background: var(--rust); }

/* The BROWSE RAIL (browse.ts browseRail) — the persistent left nav (§4): the
   library's wayfinding as cool-neutral chrome that RECEDES (brief §3). The rail
   sits in a flex shell beside the content `main`; when the rail is present the
   route-page column drops its narrow signin measure and stretches full-bleed
   (:has — the shell is on EVERY browse state). Role discipline: the chrome is
   --bg-deep/--line/--ink-dim (recede); teal (--accent) appears ONLY on the
   active nav item / active project (aria-current) — the lone interaction note;
   disabled entries sit at --ink-deco WITH their reason in the title. Token-only,
   no hex. The active-project marker dot is a uniform restrained neutral (no
   per-project color data yet — rainbow borders are an .impeccable reject). */
/* MOBILE-1 — cap the shell's grid COLUMN to the viewport. body is a grid whose single
   implicit column is `auto`, so it grows to the content's min-content — on a narrow
   phone the browse list's meta tracks push that past the viewport and the whole page
   pans (the ◆ Jesse-from-his-phone gap). minmax(0, 1fr) pins the column to the available
   width (0 minimum defeats the min-content blowout), so the shell never exceeds the
   viewport and inner content that's genuinely too wide is contained, not a page pan.
   Scoped to the shell routes (browse + the MANAGE surfaces) via :has — the editor route
   (body:has(.top)) keeps its own grid untouched. Byte-inert on desktop (1fr == the wide
   viewport there), load-bearing only where the content would otherwise overflow. */
body:has(.browse-shell) { grid-template-columns: minmax(0, 1fr); }
#route-page:has(.browse-shell) {
  max-width: none;
  justify-self: stretch;
  align-self: stretch;
  padding: 0;
  min-width: 0;   /* let the capped grid column shrink the item below its content */
  /* C1 — pin the 1fr grid row's automatic minimum to 0 so a tall shell can't
     grow the track past the viewport (the default min-height:auto is what let
     the rail run off-screen and clip under body's overflow:hidden). */
  min-height: 0;
}

/* React route hosts own the same full-bleed shell tier as browse. The generic
   `.route-page` rule is intentionally a narrow, 22vh-padded ceremony column;
   without this stable host marker a migrated route inherits that sign-in geometry
   (especially obvious on a phone). Keep this boundary in the shell stylesheet so
   every future React route receives the correct canvas before its own layout runs. */
body:has(#route-page > [data-react-route-host]) {
  grid-template-columns: minmax(0, 1fr);
}
#route-page:has(> [data-react-route-host]) {
  width: 100%;
  max-width: none;
  min-width: 0;
  min-height: 100vh;
  min-height: 100dvh;
  box-sizing: border-box;
  justify-self: stretch;
  align-self: stretch;
  padding: 0;
}

/* Asset Viewer owns the whole route canvas. Keep these document-shell selectors
   in the global cascade: a CSS Module treats an otherwise-global `#route-page`
   nested inside `:not()` as a local identifier and rewrites it, which makes a
   cold `/a/:assetId` hide the real route host. Component geometry remains in the
   Asset Viewer module; document ownership belongs here beside the shared host. */
body[data-route="asset-viewer"] {
  min-height: 100vh;
  min-height: 100dvh;
  overflow: hidden;
}
body[data-route="asset-viewer"] > :not(#route-page):not([data-overlay]) {
  display: none;
}
body[data-route="asset-viewer"] #route-page {
  grid-row: 1 / -1;
  width: 100%;
  max-width: none;
  min-width: 0;
  min-height: 0;
  align-self: stretch;
  justify-self: stretch;
  padding: 0;
}
@media (max-width: 640px) {
  body[data-route="asset-viewer"] { overflow: auto; }
}
/* App-Shell Slice B / B3 LAYOUT PRE-STAGE — the editor's full-bleed escape.
   Today the editor lives INLINE in index.html and `body` IS its grid
   (display:grid; grid-template-rows:auto 1fr auto — header.top / main / footer
   .stripbar are body-grid children). The B3 mount switch will instead inject
   EDITOR_CHROME into #route-page (mirroring showBrowse/showManage) so the editor
   becomes a mounted route like the others and the data-route=boot band-aid can
   retire. When it does, #route-page must RE-HOST that body grid or the editor
   collapses into the 460px .route-page measure — the named #1 B3 risk.
   This rule pre-stages exactly that re-host: it mirrors the body editor grid
   (the auto/1fr/auto rows + overflow so `main` scrolls INSIDE, never the body)
   and the browse-shell full-bleed escape above. It is BYTE-INERT today — `.top`
   is the editor header class, an editor-exclusive marker that only ever lives as
   a body child (index.html:40), so `#route-page:has(.top)` matches NOTHING until
   B3 moves the chrome inside. Landing + pinning it now turns B3's biggest layout
   risk into a proven, reviewed quantity so the flip is a small, verifiable diff. */
#route-page:has(.top) {
  display: grid;
  grid-template-rows: auto 1fr auto;
  /* MOBILE-NANO-0711 — cap the editor grid's COLUMN to the viewport, mirroring the
     MOBILE-1 browse-shell fix (`body:has(.browse-shell)` above). The implicit single
     column is `auto`, which sizes to the header's MAX-content: on a narrow phone the
     .top row's fixed controls (mode · Share · account control) push that track past the
     viewport, so .top inflates to ~427px and the overflow:hidden below clips the
     right-pinned account control off-screen — silently unreachable, no h-scroll
     (proven live @380: .top right=427, account x=391 past the 380 edge). minmax(0,1fr) pins the
     column to the available width (0 minimum defeats the min-content blowout), so .top
     fits 100% and the account control's margin-left:auto lands at the real right edge. Byte-inert
     on desktop (1fr == the wide viewport there), load-bearing only at phone width. */
  grid-template-columns: minmax(0, 1fr);
  max-width: none;
  justify-self: stretch;
  align-self: stretch;
  padding: 0;
  min-height: 0;
  /* mirror body's overflow:hidden — the editor's `main` owns its own scroll, the
     route container clips so a tall transcript can't grow the body past 100dvh. */
  overflow: hidden;
}
.browse-shell {
  display: flex;
  /* C2 — the shell is a COLUMN: the shared app header spans the top, the rail +
     content row fills the rest. (Was a bare row; the header lands above it.) */
  flex-direction: column;
  align-items: stretch;
  /* C1 — the shell is viewport-BOUNDED, not growable: height (not min-height)
     pins it to the 1fr grid row, so over-tall content scrolls INSIDE the rail
     and main regions rather than overflowing the body (which clips silently,
     overflow:hidden). min-height:0 lets the flex children shrink and own scroll. */
  height: 100%;
  min-height: 0;
}
/* C1 — the rail + content row beneath the header. flex:1 takes the leftover
   column height; min-height:0 lets its scroll regions (rail, main) shrink and
   own their overflow rather than growing the column past the viewport. */
.browse-body {
  display: flex;
  align-items: stretch;
  flex: 1 1 auto;
  min-height: 0;
}

/* The shared APP HEADER (appHeader.ts) — the shell's top bar: wordmark · account.
   Cool-recede chrome (a --bg-deep surface under a --line hairline,
   same temperature as the rail); the authored landscape lockup is the lone brand note,
   teal stays for interaction. flex:0 0 auto so it never scrolls or shrinks.
   Token-only — no hex. */
.app-header {
  position: relative;
  min-height: var(--app-header-height);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: var(--sp3);
  flex: 0 0 auto;
  padding: 0 var(--app-header-pad-inline);
  background: var(--room-header);
  border-bottom: 0;
}
/* The descendant selector out-specifies `.route-page a` (which would otherwise
   paint this in-page anchor with --accent) so the authored wordmark remains intact. */
.app-header .app-header-brand {
  width: var(--app-wordmark-width);
  height: var(--app-wordmark-height);
  flex: 0 0 var(--app-wordmark-width);
  overflow: hidden;
  color: transparent;
  background: url(/landing/proof-landscape-logo.png) left center / contain no-repeat;
  font-size: 0;
  text-decoration: none;
}
/* The legacy text mark remains in the accessible link markup but is visually
   replaced by the authored image; its decorative pilcrow stays hidden. */
.app-header .app-header-brand .brand-mark { display: none; }
/* ── the header SEARCH surface (headerSearch.ts, SEARCH-1c) ──────────────────
   The centre slot: a flexible field that takes the slack between the wordmark and
   the right cluster, capped so it never sprawls across a wide monitor. It is the
   POSITIONING CONTEXT for its own results popover — the pop is absolutely placed
   against this box, so it scrolls and moves with the field natively (G11: no
   reposition loop, no scroll listener).
   Token-only; teal is the interaction note (focus ring), the field itself recedes. */
.app-header-search {
  position: absolute;
  left: 50%;
  flex: 0 1 30rem;
  min-width: 0;
  width: min(30rem, 46vw);
  max-width: 30rem;
  margin: 0;
  transform: translateX(-50%);
}
.app-header-search-input {
  width: 100%;
  min-height: 36px;
  box-sizing: border-box;
  padding: var(--sp1) var(--sp3);
  background: var(--room-control);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--ink);
  font-family: var(--f-ui); font-size: var(--fs-control);
}
.app-header-search-input::placeholder { color: var(--ink-dim); }
/* the focus ring is channel E and is never suppressed (law 8 — the outline-none ban):
   the accent border is an ADDITION to the platform ring, not a replacement for it. */
.app-header-search-input:focus-visible {
  border-color: var(--accent);
}
/* the results popover — a floating card under the field, the same panel chrome the
   other in-shell popovers wear. Capped in height so a full result set scrolls inside
   the pop rather than growing the header. */
.app-header-search-pop {
  position: absolute;
  z-index: var(--layer-popover);
  top: calc(100% + 6px);
  left: 0; right: 0;
  max-height: min(60vh, 30rem);
  overflow-y: auto;
  padding: var(--sp1);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 8px 28px -12px rgba(0, 0, 0, 0.55);   /* the .ctx-menu popover shadow idiom */
}
.app-header-search-pop[hidden] { display: none; }
/* the status line — Searching… / no matches / couldn’t search. Quiet by design: it
   is never an option, so it never carries the selected treatment. */
.app-header-search-note {
  padding: var(--sp2);
  color: var(--ink-dim);
  font-family: var(--f-ui); font-size: var(--fs-note);
}
.app-header-search-retry {
  padding: 0;
  background: none;
  border: 0;
  color: var(--accent);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
/* one result — poster · (name + breadcrumb) / excerpt / meta. The active row (arrowed
   onto or hovered) lifts on --wash, the same active-row note the menus use. */
.app-header-search-hit {
  display: flex;
  gap: var(--sp2);
  align-items: flex-start;
  padding: var(--sp1) var(--sp2);
  border-radius: 5px;
  cursor: pointer;
}
.app-header-search-hit[aria-selected='true'] { background: var(--wash); }
.app-header-search-hit .card-thumb {
  flex: 0 0 auto;
  width: 64px;
  border-radius: 4px;
  overflow: hidden;
}
.app-header-search-hit-body { min-width: 0; flex: 1 1 auto; }
.app-header-search-hit-head {
  display: flex;
  gap: var(--sp2);
  align-items: baseline;
  justify-content: space-between;
}
.app-header-search-hit-title {
  display: flex; align-items: baseline; min-width: 0; gap: var(--sp1);
}
.app-header-search-hit-name {
  min-width: 0;
  color: var(--ink);
  font-family: var(--f-ui); font-size: var(--fs-control);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.app-header-search-hit-version {
  flex: 0 0 auto; padding: 1px 5px;
  border: 1px solid var(--line); border-radius: 999px;
  color: var(--ink-dim); font: 600 var(--fs-chip)/1.3 var(--f-ui);
  white-space: nowrap;
}
.app-header-search-hit-crumb {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: var(--sp1);
  color: var(--ink-dim);
  font-family: var(--f-ui); font-size: var(--fs-note);
}
.app-header-search-hit-chip {
  width: 8px; height: 8px; border-radius: 50%;
  border: 1px solid var(--line);
}
/* the excerpt — two lines at most, so a long snippet can't push a result set off
   the screen; the highlighted terms are the reason the row is here, so they carry
   the accent while the surrounding speech recedes. */
.app-header-search-hit-snippet {
  margin-top: 2px;
  color: var(--ink-dim);
  font-family: var(--f-ui); font-size: var(--fs-note);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
.app-header-search-mark {
  background: none;
  color: var(--accent);
  font-weight: 600;
}
/* a name/cut-lane row has no excerpt — this says why it matched instead. */
.app-header-search-hit-lane {
  margin-top: 2px;
  color: var(--ink-dim);
  font-family: var(--f-ui); font-size: var(--fs-note);
  font-style: italic;
}
.app-header-search-hit-meta {
  margin-top: 2px;
  display: flex; gap: var(--sp2);
  color: var(--ink-dim);
  font-family: var(--f-ui); font-size: var(--fs-note);
}

/* the New shortcut — a STANDARD button (inherits the base button skin; never the
   accent-fill PRIMARY, P3(b)). margin-left:auto leads the right cluster; the header
   `gap` spaces it from the account. */
.app-header-new { flex: 0 0 auto; margin-left: auto; }
/* The compact account link used by React Library/Settings frames. The editor's
   account control is independently scoped by EditorAccountControl.module.css. */
.app-header-account {
  flex: 0 0 auto;
  margin-left: auto;
  width: 24px; height: 24px;
  padding: 0;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink-dim);
  font: var(--fs-chip) var(--f-ui);
  cursor: default;
  overflow: hidden;
}
.app-header-account:not(:disabled) { cursor: pointer; }
/* MOBILE-1 — the header hamburger that summons the off-canvas rail drawer. HIDDEN on
   desktop (the in-flow rail + its collapse toggle serve there); it lights up only under
   the 48rem breakpoint where the rail goes off-canvas. Prepended into the shared header
   (appShellFrame), so it leads the bar left of the wordmark — the universal mobile-nav
   seat. Quiet recede chrome at rest, the lone teal note on hover/focus (interaction,
   never wayfinding — it borrows no active-item role). Token-only, no icon font. */
.app-header-menu {
  display: none;   /* desktop: the rail is in-flow — no drawer, no summon control */
  flex: 0 0 auto;
  align-items: center; justify-content: center;
  width: 30px; height: 30px;
  padding: 0; border: none; border-radius: 6px;
  background: none; color: var(--ink-dim); cursor: pointer;
}
.app-header-menu:hover, .app-header-menu:focus-visible { color: var(--accent); background: var(--wash); }
/* the three-bar glyph, drawn in CSS (a bar + two pseudo-bars) — decorative; the button
   carries the accessible name. currentColor tracks the button ink (recede → teal). */
.app-header-menu-bars { position: relative; }
.app-header-menu-bars,
.app-header-menu-bars::before,
.app-header-menu-bars::after {
  display: block; width: 16px; height: 2px; border-radius: 1px;
  background: currentColor; content: "";
}
.app-header-menu-bars::before { position: absolute; top: -5px; }
.app-header-menu-bars::after { position: absolute; top: 5px; }

/* MOBILE-1 — the drawer scrim. Only real while the drawer is open ([hidden] keeps it
   out of the a11y tree + display:none at rest, flipped by appShellFrame). Fixed full-
   viewport, the app's standard --scrim dim under the sliding rail; a tap anywhere on it
   dismisses (wired in appShellFrame). z-index below the rail(50), above the content. */
.browse-scrim {
  position: fixed; inset: 0; z-index: var(--layer-drawer-scrim);
  background: var(--scrim);
}
.browse-scrim[hidden] { display: none; }

.browse-rail {
  flex: 0 0 var(--rail-w, 208px);
  display: flex;
  flex-direction: column;
  gap: var(--sp1);
  padding: var(--sp3) var(--sp2);
  background: var(--bg-deep);
  border-right: 1px solid var(--line);
  font-family: var(--f-ui);
  /* C1 — the rail owns its OWN vertical scroll (a long project list never
     clips); overflow-x stays hidden so labels still clip cleanly on collapse. */
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
  /* §4 Slice-B: the collapse animates the basis; the reduced-motion floor below
     snaps it. cubic mirrors the layout transition vocabulary already in use. */
  transition: flex-basis 200ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* The ORGANIZATION identity row — the account root, named as such. The eyebrow is
   the legibility fix (WORKSPACE-LEGIBILITY-1): an unlabelled name here reads as the
   workspace picker, which is exactly the mistake it caused live. Tier word above,
   name below — the same eyebrow-over-value shape the workspace lens wears, so the
   two tiers are legible as PEERS IN A LADDER rather than sibling pickers. */
.rail-org {
  display: flex;
  align-items: center;
  gap: var(--sp2);
  min-height: 40px;
  padding: var(--sp1) var(--sp1) var(--sp1) var(--sp2);
  margin-bottom: var(--sp3);
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
}
.rail-org-chip {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--sp2);
}
.rail-org-avatar {
  flex: 0 0 auto;
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 4px;
  background: var(--bg);
  color: var(--ink-dim);
  font-size: var(--fs-chip); font-weight: 600;
}
.rail-org-copy {
  flex: 1; min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.rail-org-eyebrow {
  color: var(--ink-faint);
  font-family: var(--f-ui);
  font-size: var(--fs-chip);
  letter-spacing: var(--track-label);
  line-height: 1.1;
  text-transform: uppercase;
}
.rail-org-name {
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-note); color: var(--ink-dim);
}
.rail-org-chip--pending .rail-org-avatar,
.rail-org-chip--pending .rail-org-name {
  min-width: 18px;
  min-height: 12px;
  border-radius: 4px;
  background: var(--wash);
}
/* The eyebrow is the ONE thing that stays legible while the name loads — it names a
   tier, which is true before any read lands, so it never joins the pending wash. */
.rail-nav { display: flex; flex-direction: column; gap: 1px; margin-bottom: var(--sp3); }
.rail-settings-label {
  padding: var(--sp3) var(--sp2) var(--sp1);
  color: var(--ink-faint);
  font-size: var(--fs-chip);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.rail-settings-label:first-child { padding-top: 0; }
.rail-nav-item {
  position: relative;   /* anchors the active inset accent bar (::before) */
  display: flex; align-items: center; gap: var(--sp2);
  padding: var(--sp1) var(--sp2);
  border-radius: 6px;
  font-size: var(--fs-note); color: var(--ink-dim);
  text-decoration: none; cursor: pointer;
}
.rail-nav-icon {
  flex: 0 0 auto;
  width: 17px;
  height: 17px;
}
.rail-nav-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a.rail-nav-item:hover { background: var(--wash); }
.rail-nav-item[aria-current="page"] {
  background: var(--accent-wash);
  color: var(--accent);
}
.rail-workspaces {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 0 var(--sp3);
}
.rail-saved-views {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin: 0 0 var(--sp3);
}
.rail-saved-views[hidden] { display: none; }
.rail-saved-views-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--sp2) var(--sp1);
}
.rail-saved-views-label {
  padding: 0;
  color: var(--ink-faint);
  font-size: 11px;
  font-weight: 650;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.rail-workspaces[hidden] { display: none; }
.rail-workspaces-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--sp2) var(--sp1);
}
.rail-workspaces-label {
  color: var(--ink-faint);
  font-family: var(--f-ui);
  font-size: var(--fs-chip);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.rail-section-add {
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--ink-dim);
  cursor: pointer;
}
.rail-add-mark {
  position: relative;
  width: 10px;
  height: 10px;
}
.rail-add-mark::before,
.rail-add-mark::after {
  content: '';
  position: absolute;
  inset: 50% auto auto 50%;
  border-radius: 1px;
  background: currentColor;
  transform: translate(-50%, -50%);
}
.rail-add-mark::before { width: 10px; height: 1.5px; }
.rail-add-mark::after { width: 1.5px; height: 10px; }
.rail-section-add:hover, .rail-section-add:focus-visible {
  background: var(--wash);
  color: var(--accent);
}
.rail-workspace-item {
  position: relative;
  width: 100%;
  overflow: hidden;
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  align-items: center;
  gap: var(--sp2);
  padding: var(--sp1) var(--sp2);
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-dim);
  cursor: pointer;
  font-family: var(--f-ui);
  font-size: var(--fs-note);
  text-align: left;
}
.rail-workspace-item:hover { background: var(--wash); }
.rail-workspace-item[aria-current="true"] { background: var(--accent-wash); color: var(--ink); }
.rail-workspace-glyph {
  width: 18px;
  height: 18px;
  display: grid;
  grid-template-columns: repeat(2, 4px);
  grid-template-rows: repeat(2, 4px);
  place-content: center;
  gap: 2px;
  border-radius: 5px;
  background: var(--wash);
  color: var(--ink-deco);
}
.rail-workspace-glyph i {
  width: 4px;
  height: 4px;
  border: 1px solid currentColor;
  border-radius: 1px;
}
.rail-workspace-item[aria-current="true"] .rail-workspace-glyph {
  background: color-mix(in oklab, var(--accent) 14%, transparent);
  color: var(--accent);
}
/* "All workspaces" = the lens wide open. Same four-square glyph as a single workspace,
   but FILLED — the shape says "workspace", the fill says "every one of them", so the
   open state reads as a member of the list rather than a separate kind of control. */
.rail-workspace-item--all .rail-workspace-glyph i { background: currentColor; }
.rail-workspace-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* (The rail's PROJECTS list + its section head / dot / name / count / load-state note were
   RETIRED 2026-07-20 — projects live on the home + the /projects index; the sidebar list was
   redundant. Only the workspace-switcher chrome + the nav items remain.) */
/* ACTIVE marker = a separate inset accent bar in the gutter, NOT a left-border on the row
   (a single-side border fights the 6px rounded corners). A short centered pill reads as a
   clean document-outline tick; the row fill (--accent-wash) carries the rest. Absolutely-
   positioned so it adds no layout width. Teal reserved for the ACTIVE item only (§3). */
.rail-nav-item[aria-current="page"]::before {
  content: ""; position: absolute;
  left: 2px; top: 50%; transform: translateY(-50%);
  width: 2px; height: 1.05em;
  border-radius: 1px;
  background: var(--accent);
}

/* RAIL ROLE-DISCIPLINE armor — the rail's anchors (Home + each project) are
   chrome, but the broad `.route-page a` rule (style.css ~1367: color:--accent +
   hover:underline) out-specifies their bare rules at RUNTIME, leaking interaction-
   teal onto a RESTING link and underlining it on hover — a cascade the source-level
   recede pins above can't see. Container-scope reclaims it so the rail stays recede
   --ink-dim, teal reserved for the ACTIVE item only (§3, the same armor the wordmark
   carries). `a.rail-nav-item` targets the Home anchor so the disabled `<span>` nav
   items keep their dimmer --ink-deco. */
.browse-rail a.rail-nav-item { color: var(--ink-dim); text-decoration: none; }
.browse-rail a.rail-nav-item:hover { text-decoration: none; }
.browse-rail a.rail-nav-item[aria-current="page"] { color: var(--accent); }

.rail-back {
  margin-top: auto;
  color: var(--ink-faint);
}

/* The panel control is a recognizable sidebar diagram rather than an ambiguous
   chevron. Its 30px target is easy to acquire; the arrow within the diagram
   reverses with state, and the label remains explicit for assistive technology. */
.rail-toggle {
  flex: 0 0 auto;
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 7px;
  background: transparent;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color 140ms ease, background 140ms ease, border-color 140ms ease,
    transform 100ms ease;
}
.rail-toggle svg {
  width: 18px;
  height: 18px;
  transition: transform 180ms cubic-bezier(.22, 1, .36, 1);
}
.rail-toggle:hover, .rail-toggle:focus-visible {
  color: var(--ink);
  background: var(--wash);
  border-color: color-mix(in oklab, var(--line) 55%, var(--ink-dim));
}
.rail-toggle:active { transform: scale(.94); }

/* Collapsed = a compact tool rail. Work destinations remain recognizable and usable;
   their visible labels and the workspace list fold away while aria-label preserves
   explicit names. Settings mode stays folded because its destinations have no compact
   icon vocabulary and the avatar remains the honest doorway back into administration. */
.browse-shell--rail-collapsed .browse-rail { flex-basis: var(--rail-w-collapsed, 46px); }
.browse-shell--rail-collapsed .rail-org-chip,
.browse-shell--rail-collapsed .rail-nav--settings,
.browse-shell--rail-collapsed .rail-saved-views,
.browse-shell--rail-collapsed .rail-workspaces,
.browse-shell--rail-collapsed .rail-back { display: none; }
.browse-shell--rail-collapsed .rail-nav--work {
  display: flex;
  align-items: center;
  gap: 3px;
}
.browse-shell--rail-collapsed .rail-nav--work .rail-nav-item {
  width: 34px;
  height: 34px;
  justify-content: center;
  padding: 0;
}
.browse-shell--rail-collapsed .rail-nav--work .rail-nav-label { display: none; }
.browse-shell--rail-collapsed .rail-org {
  min-height: 40px;
  padding: var(--sp1);
  justify-content: center;
}
.browse-shell--rail-collapsed .rail-toggle svg { transform: scaleX(-1); }

/* Reduced-motion floor — the width change SNAPS, same posture as the summoned
   sheets above (a width animation is exactly the vestibular trigger the tier
   kills). The pref/OS tier owns the attribute; this rail honors it. */
:root[data-motion="reduced"] .browse-rail { transition: none; }

/* ─────────────────────────────────────────── MOBILE-1 — the off-canvas rail drawer
   Below the 48rem breakpoint the fixed-column rail can't share the width with the
   content (a 375px phone would get a shrunk desktop shell that pans horizontally —
   ◆ Jesse, from his phone). So the rail leaves the flow and becomes a summoned DRAWER:
   the hamburger lights up, the rail slides in over a scrim, the content grid runs the
   full single-column width. Everything is analytic CSS keyed off the .browse-shell
   --drawer-open flag appShellFrame toggles — zero geometry reads. */
@media (max-width: 48rem) {
  /* The hamburger appears and the two persistent header actions graduate from dense
     desktop controls to 44px touch targets. Their glyphs/faces keep their own visual
     size; the larger box is acquisition room, not louder chrome. */
  .app-header {
    flex-wrap: nowrap;
    gap: var(--sp1) var(--sp2);
    padding: var(--sp1) var(--sp2) var(--sp2);
  }
  .app-header-menu { display: inline-flex; width: 44px; height: 44px; }
  .app-header-account { width: 44px; height: 44px; }
  /* Search becomes a deliberate second row instead of being squeezed between four
     header actions. It stays the same combobox and result hierarchy as desktop; only
     its composition changes for a thumb-first, narrow viewport. */
  .app-header-search {
    position: relative;
    left: auto;
    order: 10;
    flex: 1 0 100%;
    max-width: none;
    margin: 0;
    transform: none;
  }
  .app-header-search-input {
    min-height: 44px;
    font-size: max(16px, var(--fs-control)); /* 16px prevents iOS focus zoom */
  }
  .app-header-search-pop {
    max-height: min(60dvh, 28rem);
    overscroll-behavior: contain;
  }
  .app-header-search-hit {
    min-height: 64px;
    padding: var(--sp2);
  }
  .app-header-search-hit .card-thumb { width: 56px; }
  .app-header-search-hit-head {
    align-items: flex-start;
    flex-direction: column;
    gap: 2px;
  }
  .app-header-search-hit-title { width: 100%; }
  .app-header-search-hit-crumb { max-width: 100%; }
  .app-header-search-retry { min-height: 44px; padding-inline: var(--sp1); }
  /* the rail goes OFF-CANVAS: fixed to the left edge, slid fully out of view, summoned
     in by --drawer-open. Its own width (the desktop flex-basis is inert on a fixed box);
     a hair under the viewport so the scrim always shows a dismiss margin on the right. */
  .browse-rail {
    position: fixed; left: 0; top: 0; bottom: 0; z-index: var(--layer-drawer);
    width: min(82vw, 300px);
    flex-basis: auto;
    transform: translateX(-100%);
    transition: transform 200ms cubic-bezier(0.2, 0.8, 0.2, 1);
    box-shadow: 2px 0 18px oklch(0.1 0.01 255 / 0.35);
  }
  .browse-shell--drawer-open .browse-rail { transform: translateX(0); }
  /* the desktop collapse toggle is meaningless here (the drawer + hamburger replace it) */
  .rail-toggle { display: none; }

  /* a desktop "collapsed" pref must never empty the mobile drawer — inside the
     breakpoint the drawer is always its full self (collapse is a desktop width concern).
     Restore the sections the .browse-shell--rail-collapsed rule folds away, and undo the
     thin-strip workspace layout, so a user who narrows from a collapsed desktop rail
     still gets a complete, usable drawer. */
  .browse-shell--rail-collapsed .browse-rail { width: min(82vw, 300px); flex-basis: auto; }
  .browse-shell--rail-collapsed .rail-nav,
  .browse-shell--rail-collapsed .rail-saved-views:not([hidden]),
  .browse-shell--rail-collapsed .rail-back { display: flex; }
  .browse-shell--rail-collapsed .rail-org-chip { display: flex; }
  .browse-shell--rail-collapsed .rail-workspaces:not([hidden]) { display: flex; }
  .browse-shell--rail-collapsed .rail-nav--work .rail-nav-label { display: inline; }
  .browse-shell--rail-collapsed .rail-nav--work .rail-nav-item {
    width: auto;
    height: auto;
    justify-content: flex-start;
    padding: var(--sp1) var(--sp2);
  }
  .browse-shell--rail-collapsed .rail-org { justify-content: flex-start; }

  /* Drawer destinations are touched, not moused. Preserve the desktop density while
     giving every navigation/workspace action the platform-standard 44px hit area. */
  .rail-nav-item,
  .rail-workspace-item { min-height: 44px; }
  .rail-workspaces-add { min-width: 44px; min-height: 44px; }
}

/* Reduced-motion floor for the drawer slide — the summon SNAPS (a transform-in from
   off-screen is the same vestibular trigger the rail-width animation is; honor the same
   attribute tier, no @media prefers-reduced-motion fork — uiprefs invariant). */
:root[data-motion="reduced"] .browse-shell--drawer-open .browse-rail { transition: none; }
:root[data-motion="reduced"] .rail-toggle,
:root[data-motion="reduced"] .rail-toggle svg { transition: none; }

/* C1 — main owns its OWN vertical scroll independent of the rail; min-height:0
   lets it shrink below its content inside the bounded shell so the card list
   scrolls HERE, never the body. This default carries the loading / empty / error
   states (and the home), which render one box straight into main and want main's
   own padding + scroll. The populated scoped view opts OUT via .browse-canvas. */
.browse-main { flex: 1 1 auto; min-width: 0; min-height: 0; overflow-y: auto; padding: var(--sp4); }

/* BC-1 controls-above-objects (the pin). The populated scoped view turns `main`
   into a column: a non-scrolling control BAND over a single scrolling object
   CANVAS — Spooky's FileBrowser arbitration (its one right call), so the toolbar,
   breadcrumb, and project header stay reachable while only the cards scroll under
   them. Pure CSS flex (band = flex-shrink:0; scroll = flex:1 + min-height:0 +
   overflow) — ZERO getBoundingClientRect; the analytic-layout invariant binds the
   browse chrome too. main sheds its own padding/scroll here and hands them to the
   two children, so the band's top/side gutter and the scroll's gutter stay honest
   (the column-flex sidesteps position:sticky fighting a padded container). */
.browse-main.browse-canvas { position: relative; overflow: hidden; padding: 0; display: flex; flex-direction: column; }
/* BC-2: the pinned band reads as a HEADER over the scroll — a quiet --line hairline
   (token-only, matching the v2 mockup + Spooky's `border-b`) seats it above the
   objects. The band carries its OWN sp4 bottom padding now (was 0), so content
   breathes above the rule; the last child's trailing margin is collapsed so the
   gap below the toolbar/header doesn't DOUBLE with the scroll's padding-top — the
   wsp_-scope no-projectHeader double-gap the BC-1 pass left owed (toolbar's
   margin-bottom + scroll padding-top both fired). One rhythm: band content → sp4 →
   hairline → sp4 → first card, with or without a projectHeader. */
.browse-canvas > .browse-band { flex-shrink: 0; padding: var(--sp4); border-bottom: 1px solid var(--line); }
.browse-canvas > .browse-band > :last-child { margin-bottom: 0; }
.browse-canvas > .browse-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--sp4); }
.browse-canvas:has(.browse-collection-savebar) > .browse-scroll {
  padding-bottom: calc(var(--sp4) + 4.5rem);
}
/* The browse toolbar (browse.ts renderToolbar) — the View's controls in a row:
   Appearance (the layout toggles) + "Sorted by" (the sort select). A quiet chrome
   strip that recedes (cool-neutral, brief §3); the controls speak --accent on
   their active/focus states like the rest of the library surface. Token-only. */
.browse-toolbar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp2);
  margin-bottom: var(--sp4);
}
.browse-toolbar--collection {
  flex-direction: column;
  align-items: stretch;
}
.browse-collection-context {
  display: flex;
  align-items: center;
  gap: var(--sp2);
  min-inline-size: 0;
  min-block-size: 2rem;
}
.browse-collection-identity {
  display: flex;
  align-items: center;
  gap: var(--sp2);
  min-inline-size: 0;
}
.browse-collection-kicker {
  flex: none;
  color: var(--ink-faint);
  font: 600 var(--fs-label)/1 var(--f-ui);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.browse-collection-context .browse-views { min-inline-size: 0; }
.browse-collection-context .browse-views > summary {
  max-inline-size: min(28rem, 55vw);
  border-color: transparent;
  padding-inline-start: var(--sp1);
  color: var(--ink);
  font-weight: 600;
}
.browse-collection-context .browse-views > summary:hover { background: var(--wash); }
.browse-collection-context .browse-collection-actions { margin-inline-start: auto; }
/* The LEFT facet cluster (mockup, toolbar far-left) — "what am I looking at":
   All-assets + Archived grouped as ONE flex child, so the bar's space-between reads
   [facets | tools] (three loose siblings floated the Archived chip dead-center). */
.browse-facets { display: flex; align-items: center; gap: var(--sp2); }
/* The facet chips — recede chrome: cool bordered filter pills, TWINS (the Archived
   chip had no rule at all — a raw UA button — half of why the strip read random).
   aria-pressed = the teal-soft active fill, the same state language the layout
   segments speak (honest voice: pressed IS "you're looking at it"). */
.browse-scope,
.browse-archived {
  font: inherit;
  font-size: var(--fs-chip);
  color: var(--ink-dim);
  background: none;
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 4px 9px;
  cursor: pointer;
}
.browse-scope:hover:not(:disabled),
.browse-archived:hover { background: var(--wash); color: var(--ink); }
.browse-scope[aria-pressed="true"],
.browse-archived[aria-pressed="true"] {
  background: var(--accent-soft);
  color: var(--ink);
  border-color: color-mix(in oklab, var(--accent) 35%, var(--line));
}
.browse-scope:focus-visible,
.browse-archived:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-scope:disabled { cursor: not-allowed; }
/* "New" action (mockup, toolbar far-right) — the CREATE call-to-action. It now
   wears the codified --primary-action role (DECISIONS #27(1)), the mockup's amber
   CTA fill — NOT the cool --accent (wayfinding) and NOT status --working (the
   --accent overload sin). Ink is --bg-deep on the light fill (contrast-pinned);
   hover/active ride the shared --hover-delta (channel A's one knob), exactly like
   button.primary. New is now LIVE (it opens the name dialog → mints a project,
   265811e); the :disabled recede below stays only as a defensive cue for any
   future disabled posture (the shared --set-aside-opacity dim, never a dead grey). */
.browse-new {
  font: inherit;
  font-size: var(--fs-chip);
  font-weight: 600;
  color: var(--bg-deep);
  background: var(--primary-action);
  border: 1px solid transparent;
  border-radius: 5px;
  padding: 4px 10px;
  cursor: pointer;
}
.browse-new:not(:disabled):hover {
  background: oklch(from var(--primary-action) calc(l + var(--hover-delta)) c h);
}
.browse-new:not(:disabled):active {
  background: oklch(from var(--primary-action) calc(l - var(--hover-delta)) c h);
}
/* the create verb earns the same 2px --accent focus ring its toolbar cluster-mates
   carry (.browse-sort, .browse-search-input) — it was the lone keyboard-reachable
   browse control without one. OUTSET (offset:1px) like those siblings, NOT the inset
   .layout-toggle needs: .browse-new has no overflow:hidden parent to clip the ring. */
.browse-new:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-new:disabled { cursor: not-allowed; opacity: var(--set-aside-opacity); }
.browse-add {
  /* One size owns BOTH axes. Previously the phone rule enlarged only inline-size
     while the generic touch-target rule enlarged min-block-size; under a tight
     toolbar flex pass the visible control could become a clipped upright pill.
     Keep this compact icon action intrinsically square at every breakpoint. */
  --browse-add-size: 30px;
  inline-size: var(--browse-add-size);
  block-size: var(--browse-add-size);
  min-inline-size: var(--browse-add-size);
  min-block-size: var(--browse-add-size);
  max-inline-size: var(--browse-add-size);
  max-block-size: var(--browse-add-size);
  aspect-ratio: 1;
  box-sizing: border-box;
  flex: 0 0 var(--browse-add-size);
  align-self: center;
  padding: 0;
  display: inline-grid;
  place-items: center;
  position: relative;
}
.browse-add-mark {
  position: relative;
  display: block;
  inline-size: 14px;
  block-size: 14px;
}
.browse-add-mark::before,
.browse-add-mark::after {
  content: '';
  position: absolute;
  inset: 50% auto auto 50%;
  border-radius: 1px;
  background: currentColor;
  transform: translate(-50%, -50%);
}
.browse-add-mark::before { inline-size: 14px; block-size: 2px; }
.browse-add-mark::after { inline-size: 2px; block-size: 14px; }
.browse-folder-new {
  min-height: 30px;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 5px;
  color: var(--ink);
  background: transparent;
  font: 500 var(--fs-chip)/1.2 var(--f-ui);
}
.browse-folder-new:hover { background: var(--wash); border-color: var(--ink-dim); }
.browse-folder-new:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* the view controls travel as ONE cluster (mockup) — layout switch · Fields · Sort,
   side by side on the right, not split far-left/far-right (the old margin-left:auto) */
.browse-tools {
  display: flex;
  align-items: center;
  gap: var(--sp2);
  inline-size: 100%;
}
.browse-tools .browse-search { margin-inline-end: auto; }
/* the layout toggles read as one segmented control — a joined group, the mockup's
   view-mode switch. The GROUP owns the border + radius; the buttons inside shed
   their own box and share a hairline divider, so the trio is one object, not three
   loose chips. Token-only — teal-soft fills the active segment (cool profile). */
.layout-group {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 5px;
  overflow: hidden;
}
.layout-toggle { display: inline-flex; align-items: center; gap: 5px; }
.layout-toggle .icn { inline-size: 14px; block-size: 14px; flex: none; }
/* Scoped to .layout-group (NOT .browse-toolbar) so BOTH instances get the segmented
   chrome AND the active state — the home lens switch (.browse-home--populated) and the
   in-project View toolbar band, matching the focus-ring rule below. Previously the
   active-state background was .browse-toolbar-only, so on the home page the pressed
   segment rendered transparent — indistinguishable from its siblings (which mode am I in?). */
.layout-group .layout-toggle {
  font-size: var(--fs-chip);
  padding: 4px 10px;
  text-transform: capitalize;
  border: 0;
  border-right: 1px solid var(--line);
  border-radius: 0;
  color: var(--ink-dim);                 /* inactive segments RECEDE so the active one leads */
}
.layout-group .layout-toggle:last-child { border-right: 0; }
.layout-group .layout-toggle:hover { background: var(--surface); color: var(--ink); }
/* the active segment is unmistakable: accent-tinted fill + accent label + a touch of
   weight — the "you are here" of the segmented control, on-theme (accent is the note). */
.layout-group .layout-toggle[aria-pressed="true"] {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 500;
}
/* the toggles are plain tab stops (each a focusable <button>), so a keyboard user
   ROVES onto them — they earn the same visible focus ring their siblings already
   carry (.browse-sort, .browse-search-input, the React cut tabs). UNSCOPED like
   .layout-group itself, so BOTH instances get it: the home lens switch (.browse-
   home--populated) and the in-project View toolbar band. The ring is INSET
   (outline-offset:-2px) because .layout-group's `overflow:hidden` would clip an
   outset one — the .canvastabs .tab-in-a-bar precedent. Token-only, 2px --accent. */
.layout-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.browse-sort,
.browse-group {
  font: inherit;
  font-size: var(--fs-chip);
  color: var(--ink-dim);   /* recede with its chip siblings — the value is chrome, not content */
  background: none;
  border: 1px solid var(--line);
  border-radius: 5px;      /* the toolbar chips' shared radius — one object family */
  padding: 4px 8px;
  cursor: pointer;
}
/* Chromium/macOS can paint the native option sheet with its light system
   surface even when the surrounding app declares a dark scheme. Pin both
   halves of the contract so inherited pale toolbar ink never lands on white. */
.browse-sort { color-scheme: dark; }
.browse-sort option {
  background-color: var(--surface);
  color: var(--ink);
}
.browse-group {
  color: var(--ink);
  font-weight: 500;
}
.browse-group[data-active="true"] {
  border-color: color-mix(in oklab, var(--accent) 45%, var(--line));
  background: var(--accent-soft);
  color: var(--accent);
}
.browse-group-dir {
  inline-size: 30px;
  block-size: 30px;
  flex: 0 0 30px;
  display: inline-grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 5px;
  color: var(--ink-dim);
  background: none;
  font: 600 var(--fs-chip)/1 var(--f-ui);
  cursor: pointer;
}
.browse-lifecycle {
  min-height: 30px;
  padding: 4px 28px 4px 9px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--surface);
  color: var(--ink-dim);
  font: 500 var(--fs-chip)/1.2 var(--f-ui);
}
.browse-lifecycle:hover { color: var(--ink); border-color: var(--line-strong); }
.browse-lifecycle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-display-option {
  white-space: nowrap;
  color: var(--ink-faint);
}
.browse-sort:hover,
.browse-group:hover,
.browse-group-dir:hover { color: var(--ink); border-color: var(--ink-dim); }
.browse-sort:focus-visible,
.browse-group:focus-visible,
.browse-group-dir:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* "Search" — the toolbar text facet (mockup, cluster-left). A quiet bordered field
   matching the .browse-sort chrome (same hairline, radius, fs-chip, teal focus ring);
   the form is layout-only so the input carries the box. Token-only, the cool profile. */
.browse-search { display: inline-flex; align-items: center; min-inline-size: 36px; }
.browse-search-toggle,
.browse-sort-toggle,
.browse-display-option {
  inline-size: 36px; block-size: 36px; flex: 0 0 36px;
  display: inline-grid; place-items: center; padding: 0;
  border: 1px solid transparent; border-radius: 4px;
  background: transparent; color: var(--ink-dim); cursor: pointer;
}
.browse-search-toggle svg,
.browse-sort-toggle svg,
.browse-display-option svg { inline-size: 18px; block-size: 18px; }
.browse-search-toggle:hover,
.browse-sort-toggle:hover,
.browse-display-option:hover { color: var(--ink); border-color: var(--line); }
.browse-search-toggle:focus-visible,
.browse-sort-toggle:focus-visible,
.browse-display-option:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-search.is-open { flex: 1 1 18rem; max-inline-size: 28rem; }
.browse-search-input {
  inline-size: 100%; min-block-size: 36px;
  font: inherit;
  font-size: var(--fs-chip);
  color: var(--ink);
  background: none;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 4px 8px;
}
.browse-search-input::placeholder { color: var(--ink-dim); }
.browse-search-input:hover { border-color: var(--ink-dim); }
.browse-search-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.browse-sort-menu { position: relative; }
.browse-sort-toggle { list-style: none; }
.browse-sort-toggle::-webkit-details-marker { display: none; }
.browse-sort-menu[open] .browse-sort-toggle { color: var(--accent); border-color: var(--line); background: var(--wash); }
.browse-sort-options {
  position: absolute; inset-block-start: calc(100% + var(--sp1)); inset-inline-end: 0; z-index: var(--layer-popover);
  min-inline-size: 11rem; max-block-size: min(24rem, 70vh); overflow-y: auto;
  display: grid; gap: 2px; padding: var(--sp1);
  border: 1px solid var(--line); border-radius: 4px; background: var(--surface);
  box-shadow: 0 10px 24px oklch(0 0 0 / .28);
}
.browse-sort-option {
  padding: 7px var(--sp2); border: 0; border-radius: 3px;
  background: transparent; color: var(--ink-dim); text-align: start; white-space: nowrap;
  font: 500 var(--fs-chip)/1.2 var(--f-ui); cursor: pointer;
}
.browse-sort-option:hover,
.browse-sort-option:focus-visible { background: var(--wash); color: var(--ink); }
.browse-sort-option[aria-checked="true"] { color: var(--accent); }

/* "Fields" — the metadata projection control (brief §5), the third member of the
   toolbar trio. It rides INSIDE the `.browse-tools` cluster now (no longer pinned to
   the far edge by margin-left:auto) — Appearance · Fields · Sorted by sit together.
   A native <details> popover: the summary is a quiet chrome chip, the panel a
   token-framed checkbox column. Token-only — teal interaction (the cool profile
   re-values --accent for the library surface), cool-neutral chrome ground, no hex. */
.browse-fields { position: relative; }
.browse-filters { position: relative; }
.browse-fields > summary,
.browse-filters > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-chip);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 4px 10px;
  cursor: pointer;
  list-style: none;            /* drop the default disclosure triangle */
  user-select: none;
}
.browse-fields-icon { width: 16px; height: 16px; flex: 0 0 16px; }
.browse-fields > summary::-webkit-details-marker,
.browse-filters > summary::-webkit-details-marker { display: none; }
.browse-fields > summary:hover,
.browse-filters > summary:hover { border-color: var(--ink-dim); }
.browse-fields > summary:focus-visible,
.browse-filters > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-fields[open] > summary,
.browse-filters[open] > summary { border-color: var(--accent); color: var(--ink); }
.browse-fields-panel {
  position: absolute;
  z-index: var(--layer-popover);
  top: calc(100% + 4px);
  right: 0;
  inline-size: 18rem;
  max-block-size: min(70dvh, 36rem);
  display: flex;
  flex-direction: column;
  gap: var(--sp1);
  padding: var(--sp3);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 6px 20px oklch(0 0 0 / 0.45);   /* the menu/popover shadow idiom */
}
.browse-filters-panel {
  position: absolute;
  z-index: var(--layer-popover);
  top: calc(100% + 4px);
  left: 0;
  inline-size: min(28rem, calc(100vw - 2rem));
  display: grid;
  gap: var(--sp3);
  padding: var(--sp3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 6px 20px oklch(0 0 0 / 0.45);
}
.browse-filter-lifecycle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp3);
  padding-block-end: var(--sp2);
  border-bottom: 1px solid var(--line);
}
.browse-filter-lifecycle-label {
  color: var(--ink-dim);
  font-size: var(--fs-chip);
}
.browse-filter-lifecycle-select {
  min-block-size: 2.25rem;
  padding-inline: var(--sp2);
  border: 1px solid var(--line);
  border-radius: var(--r1);
  color: var(--ink);
  background: var(--surface-raised);
  font: inherit;
}
.browse-filter-lifecycle-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-filter-rules { display: grid; gap: var(--sp1); }
.browse-filter-empty { margin: 0; color: var(--ink-dim); font-size: var(--fs-chip); }
.browse-filter-rule {
  display: flex;
  align-items: center;
  gap: var(--sp2);
  min-block-size: 2rem;
  padding-inline-start: var(--sp2);
  border-radius: 4px;
  background: var(--wash);
}
.browse-filter-rule-label { flex: 1; min-inline-size: 0; font-size: var(--fs-chip); color: var(--ink); }
.browse-filter-remove,
.browse-filter-clear {
  border: 0;
  background: transparent;
  color: var(--ink-dim);
  font: 500 var(--fs-chip)/1 var(--f-ui);
}
.browse-filter-remove { inline-size: 2rem; block-size: 2rem; }
.browse-filter-clear { justify-self: start; padding: var(--sp1) var(--sp2); }
.browse-filter-remove:hover,
.browse-filter-clear:hover { color: var(--accent); background: var(--accent-soft); }
.browse-filter-remove:focus-visible,
.browse-filter-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-filter-add {
  display: grid;
  grid-template-columns: minmax(7rem, 1fr) minmax(7rem, 1fr);
  gap: var(--sp2);
  padding-block-start: var(--sp3);
  border-block-start: 1px solid var(--line);
}
.browse-filter-field,
.browse-filter-operator,
.browse-filter-value {
  inline-size: 100%;
  min-block-size: 2.25rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--ground);
  color: var(--ink);
  padding: var(--sp1) var(--sp2);
  font: 500 var(--fs-chip)/1.2 var(--f-ui);
}
.browse-filter-value-slot { grid-column: 1 / -1; }
.browse-filter-file-size {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 5.5rem;
  gap: var(--sp2);
}
.browse-filter-file-size-unit {
  inline-size: 100%;
  min-block-size: 2.25rem;
  padding: var(--sp1) var(--sp2);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--ink);
  background: var(--ground);
  font: 600 var(--fs-chip)/1.2 var(--f-ui);
}
.browse-filter-date {
  display: grid;
  gap: var(--sp3);
  padding: var(--sp2);
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--ground);
}
.browse-filter-date-intro,
.browse-filter-date-exact { display: grid; gap: var(--sp1); }
.browse-filter-date-label {
  color: var(--ink-dim);
  font: 500 var(--fs-chip)/1.2 var(--f-ui);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.browse-filter-date-presets {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp1);
}
.browse-filter-date-preset {
  min-block-size: 2.25rem;
  padding-inline: var(--sp1);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--ink-dim);
  background: var(--surface);
  font: 600 var(--fs-chip)/1.15 var(--f-ui);
  cursor: pointer;
}
.browse-filter-date-preset:hover { color: var(--ink); border-color: var(--line-strong); }
.browse-filter-date-preset[aria-pressed="true"] {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-soft);
}
.browse-filter-date-preset:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-filter-add.is-presence-filter .browse-filter-add-button { grid-column: 1 / -1; }
.browse-filter-add-button {
  grid-column: 1 / -1;
  min-block-size: 2.25rem;
  border: 1px solid var(--accent);
  border-radius: 4px;
  background: transparent;
  color: var(--accent);
  font: 600 var(--fs-chip)/1.2 var(--f-ui);
}
.browse-filter-add-button:hover { background: var(--accent-soft); }
.browse-filter-field:focus-visible,
.browse-filter-operator:focus-visible,
.browse-filter-value:focus-visible,
.browse-filter-file-size-unit:focus-visible,
.browse-filter-add-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-filter-error { grid-column: 1 / -1; margin: 0; color: var(--rust); font-size: var(--fs-chip); }
.browse-field-opt {
  display: flex;
  align-items: center;
  gap: 2px;
  min-block-size: 2rem;
  font-size: var(--fs-chip);
  color: var(--ink);
}
/* the visibility checkbox + label take the row's slack so the ↑/↓ reorder buttons
   hug the right edge of the popover. */
.browse-field-label {
  display: flex;
  align-items: center;
  gap: var(--sp2);
  flex: 1;
  min-width: 0;
  cursor: pointer;
}
.browse-field-label-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.browse-field-label input { accent-color: var(--accent); cursor: pointer; }
/* the per-row reorder affordance — a quiet ghost icon button mirroring the toolbar's
   restraint (no fill, a wash only on hover), dimmed + inert at the ends of the run. */
.browse-field-move {
  inline-size: 1.75rem;
  block-size: 1.75rem;
  flex: 0 0 1.75rem;
  display: grid;
  place-items: center;
  border: 0;
  background: none;
  color: var(--ink-dim);
  cursor: pointer;
  padding: 0;
  line-height: 1;
  font-size: var(--fs-chip);
  border-radius: 4px;
}
.browse-field-move:hover:not(:disabled) { color: var(--ink); background: var(--wash); }
.browse-field-move:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-field-move:disabled { opacity: 0.3; cursor: default; }
/* Rename ✎ / Retire × on a custom mf_ field row — the schema-edit verbs, sharing the
   saved-views inline-edit idiom (recede → teal on hover/focus). Quiet ghost buttons that
   hug the row's right edge beside the ↑/↓ reorder controls. */
.browse-field-rename,
.browse-field-retire {
  inline-size: 1.75rem;
  block-size: 1.75rem;
  flex: 0 0 1.75rem;
  display: grid;
  place-items: center;
  border: 0;
  background: none;
  color: var(--ink-dim);
  cursor: pointer;
  padding: 0;
  line-height: 1;
  font-size: var(--fs-chip);
  border-radius: 4px;
}
.browse-field-rename:hover:not(:disabled) { color: var(--accent); background: var(--wash); }
.browse-field-retire:hover:not(:disabled) { color: var(--rust); background: var(--rust-wash); }
.browse-field-rename:focus-visible,
.browse-field-retire:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-field-retire:disabled { opacity: 0.5; cursor: default; }
/* the inline edit form that replaces the row on rename — the input claims the row. */
.browse-field-rename-form { display: flex; flex-wrap: wrap; gap: var(--sp1); flex: 1; }
.browse-field-rename-name {
  flex: 1;
  min-width: 6rem;
  font: inherit;
  font-size: var(--fs-chip);
  color: var(--ink);
  background: var(--wash);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: var(--sp1) var(--sp2);
}
.browse-field-rename-name:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-field-rename-name:disabled { opacity: 0.5; cursor: default; }
.browse-field-rename-err { flex: 1 0 100%; font-size: var(--fs-chip); color: var(--rust); }

/* "＋ Add field" — the define form at the popover foot, fenced off the field rows by a
   hairline so it reads as the catalog-authoring affordance, not another toggle. The name
   input takes the slack, the type select hugs beside it, the button drops to its own row. */
.browse-field-add {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp1);
  margin-top: var(--sp1);
  padding-top: var(--sp2);
  border-top: 1px solid var(--line);
}
.browse-field-add-name {
  flex: 1;
  min-width: 5rem;
  font: inherit;
  font-size: var(--fs-chip);
  color: var(--ink);
  background: var(--wash);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: var(--sp1) var(--sp2);
}
.browse-field-add-name:focus-visible,
.browse-field-add-type:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-field-add-type {
  font: inherit;
  font-size: var(--fs-chip);
  color: var(--ink);
  background: var(--wash);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: var(--sp1);
}
.browse-field-add-btn {
  flex: 1 0 100%;
  font: inherit;
  font-size: var(--fs-chip);
  color: var(--accent);
  background: none;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: var(--sp1) var(--sp2);
  cursor: pointer;
}
.browse-field-add-btn:hover:not(:disabled) { background: var(--wash); }
.browse-field-add-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-field-add-btn:disabled { opacity: 0.5; cursor: default; }
.browse-field-add-err { flex: 1 0 100%; font-size: var(--fs-chip); color: var(--rust); }
.browse-field-manage {
  margin-top: var(--sp1); padding: var(--sp2); border: 0; border-top: 1px solid var(--line);
  background: none; color: var(--accent); text-align: left; font: var(--fs-chip)/1.2 var(--f-ui); cursor: pointer;
}
.browse-field-manage:hover { background: var(--wash); }

/* "Views" — the saved-view popover (browse.ts renderToolbar, SAVED VIEWS slice-3).
   Same details/summary + panel chrome as .browse-fields (one popover idiom, sin #34):
   recede-by-default summary, teal --accent on focus/open; the list entries are quiet
   wayfinding buttons (teal on hover/focus — re-applying a view IS navigation); the
   Save form mirrors the Add-field foot. Role tokens only — no hardcoded hex. */
.browse-views { position: relative; }
.browse-views > summary {
  font-size: var(--fs-chip);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 4px 10px;
  cursor: pointer;
  list-style: none;
  user-select: none;
  max-inline-size: 14rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.browse-collection-context--draft .browse-views > summary {
  display: flex;
  align-items: center;
  gap: var(--sp1);
}
.browse-collection-draft-name {
  overflow: hidden;
  text-overflow: ellipsis;
}
.browse-collection-draft-state {
  flex: none;
  color: var(--ink-faint);
  font-size: var(--fs-chip);
  font-weight: 600;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.browse-collection-draft-save {
  margin-inline-start: auto;
  min-block-size: 2rem;
  padding-inline: var(--sp3);
  border: 1px solid transparent;
  border-radius: 5px;
  background: var(--accent);
  color: var(--bg-deep);
  font: 600 var(--fs-chip)/1 var(--f-ui);
  cursor: pointer;
}
.browse-collection-draft-save:hover {
  background: oklch(from var(--accent) calc(l + var(--hover-delta)) c h);
}
.browse-collection-draft-save:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.browse-views > summary::-webkit-details-marker { display: none; }
.browse-views > summary:hover { border-color: var(--ink-dim); }
.browse-views > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-views[open] > summary { border-color: var(--accent); color: var(--ink); }
.browse-views-panel {
  position: absolute;
  z-index: var(--layer-popover);
  top: calc(100% + 4px);
  right: 0;
  min-width: 17rem;
  display: flex;
  flex-direction: column;
  gap: var(--sp1);
  padding: var(--sp2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 6px 20px oklch(0 0 0 / 0.45);
}
.browse-views-empty { font-size: var(--fs-chip); color: var(--ink-dim); margin: 0; }
.browse-collection-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--sp1);
}
.browse-collection-share,
.browse-collection-more {
  min-block-size: 2rem;
  padding: 4px 9px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: transparent;
  color: var(--ink-dim);
  font: var(--fs-chip)/1.2 var(--f-ui);
  cursor: pointer;
  white-space: nowrap;
}
.browse-collection-share:hover,
.browse-collection-more:hover { color: var(--ink); background: var(--wash); }
.browse-collection-share:focus-visible,
.browse-collection-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-collection-more { inline-size: 2rem; padding-inline: 0; }
.browse-collection-savebar {
  position: absolute;
  z-index: var(--layer-notice);
  inset-inline-start: 50%;
  inset-block-end: var(--sp4);
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: var(--sp3);
  inline-size: min(42rem, calc(100% - 2 * var(--sp4)));
  min-block-size: 3.5rem;
  padding: var(--sp2) var(--sp3);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: color-mix(in oklab, var(--surface) 94%, transparent);
  box-shadow: 0 12px 32px oklch(0 0 0 / 0.38);
  backdrop-filter: blur(12px);
}
.browse-collection-savebar-copy {
  display: flex;
  flex: 1 1 auto;
  min-inline-size: 0;
  flex-direction: column;
  gap: 2px;
  color: var(--ink);
  font: var(--fs-chip)/1.25 var(--f-ui);
}
.browse-collection-savebar-copy strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.browse-collection-savebar-copy span { color: var(--ink-faint); }
.browse-collection-savebar-error { color: var(--rust); font: var(--fs-chip)/1.25 var(--f-ui); }
.browse-collection-savebar-actions { display: inline-flex; flex: none; gap: var(--sp1); }
.browse-collection-discard,
.browse-collection-save {
  min-block-size: 2.25rem;
  padding-inline: var(--sp2);
  border: 1px solid var(--line);
  border-radius: 5px;
  background: transparent;
  color: var(--ink-dim);
  font: 600 var(--fs-chip)/1 var(--f-ui);
  cursor: pointer;
}
.browse-collection-save {
  border-color: transparent;
  background: var(--accent);
  color: var(--bg-deep);
}
.browse-collection-discard:hover { background: var(--wash); color: var(--ink); }
.browse-collection-save:hover {
  background: color-mix(in oklab, var(--accent) 88%, var(--ink));
}
.browse-collection-discard:focus-visible,
.browse-collection-save:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.browse-collection-discard:disabled,
.browse-collection-save:disabled { cursor: wait; opacity: var(--set-aside-opacity); }
.browse-views-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
/* a Collection entry = its recipe plus one standard overflow. */
.browse-views-item { display: flex; align-items: center; gap: 2px; }
.browse-views-apply {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
  text-align: left;
  font: inherit;
  font-size: var(--fs-chip);
  color: var(--ink);
  background: none;
  border: 0;
  border-radius: 4px;
  padding: var(--sp1) var(--sp2);
  cursor: pointer;
}
.browse-views-apply:hover { color: var(--accent); background: var(--wash); }
.browse-views-apply:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-views-name { display: flex; align-items: center; gap: var(--sp1); font-weight: 600; }
.browse-views-item.is-current .browse-views-name::before {
  content: '✓';
  color: var(--accent);
  font-weight: 700;
}
.browse-views-recipe { color: var(--ink-dim); font-size: var(--fs-chip); line-height: 1.25; }
.browse-views-state { display: flex; flex-wrap: wrap; gap: var(--sp1); font-size: var(--fs-chip); line-height: 1.25; }
.browse-views-owner { color: var(--ink-dim); }
.browse-views-pinned { color: var(--accent); }
.browse-views-owner + .browse-views-pinned::before { content: '·'; margin-inline-end: var(--sp1); color: var(--ink-faint); }
.browse-views-apply:hover .browse-views-recipe { color: var(--ink); }
.browse-views-more {
  flex: 0 0 auto;
  min-inline-size: 2rem;
  min-block-size: 2rem;
  padding: 0 var(--sp1);
  border: 0;
  border-radius: 4px;
  color: var(--ink-dim);
  background: none;
  font: 600 var(--fs-chip)/1 var(--f-ui);
  cursor: pointer;
}
.browse-views-more:hover { color: var(--accent); background: var(--wash); }
.browse-views-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* the inline edit form that replaces the row on rename — the input claims the row. */
.browse-views-rename-form { display: flex; flex-wrap: wrap; gap: var(--sp1); flex: 1; }
.browse-views-rename-name {
  flex: 1;
  min-width: 6rem;
  font: inherit;
  font-size: var(--fs-chip);
  color: var(--ink);
  background: var(--wash);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: var(--sp1) var(--sp2);
}
.browse-views-rename-name:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-views-rename-name:disabled { opacity: 0.5; cursor: default; }
.browse-views-rename-actions { display: flex; gap: var(--sp1); }
.browse-views-rename-cancel,
.browse-views-rename-submit {
  min-block-size: 2rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0 var(--sp2);
  color: var(--ink-dim);
  background: transparent;
  font: 600 var(--fs-chip)/1 var(--f-ui);
  cursor: pointer;
}
.browse-views-rename-submit { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.browse-views-rename-cancel:hover,
.browse-views-rename-cancel:focus-visible,
.browse-views-rename-submit:hover,
.browse-views-rename-submit:focus-visible { color: var(--ink); background: var(--wash); }
.browse-views-rename-cancel:focus-visible,
.browse-views-rename-submit:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-views-rename-cancel:disabled,
.browse-views-rename-submit:disabled { opacity: 0.5; cursor: default; }
.browse-views-rename-err { flex: 1 0 100%; font-size: var(--fs-chip); color: var(--rust); }
.browse-views-remove-confirm {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp1) var(--sp2);
  padding: var(--sp1) var(--sp2);
  border-radius: 4px;
  background: color-mix(in oklab, var(--rust) 8%, transparent);
}
.browse-views-remove-prompt { color: var(--ink); font-size: var(--fs-chip); font-weight: 600; }
.browse-views-remove-actions { display: inline-flex; gap: var(--sp1); }
.browse-views-remove-cancel,
.browse-views-remove-submit {
  min-block-size: 2rem;
  padding-inline: var(--sp2);
  border: 1px solid var(--line);
  border-radius: 4px;
  background: transparent;
  color: var(--ink);
  font: 600 var(--fs-chip)/1 var(--f-ui);
  cursor: pointer;
}
.browse-views-remove-cancel:hover { background: var(--wash); }
.browse-views-remove-submit { border-color: color-mix(in oklab, var(--rust) 55%, var(--line)); color: var(--rust); }
.browse-views-remove-submit:hover { background: color-mix(in oklab, var(--rust) 12%, transparent); }
.browse-views-remove-cancel:focus-visible,
.browse-views-remove-submit:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-views-remove-cancel:disabled,
.browse-views-remove-submit:disabled { opacity: 0.5; cursor: default; }
.browse-views-remove-err { flex: 1 0 100%; color: var(--rust); font-size: var(--fs-chip); }
.browse-views-update-confirm {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp2);
  padding: var(--sp1) var(--sp2);
  border-radius: 4px;
  background: var(--wash);
}
.browse-views-update-copy { display: grid; flex: 1 1 14rem; gap: 2px; }
.browse-views-update-title { color: var(--ink); font-size: var(--fs-chip); }
.browse-views-update-change,
.browse-views-update-note { color: var(--ink-dim); font-size: var(--fs-chip); line-height: 1.3; }
.browse-views-update-note { color: var(--rust); }
.browse-views-update-actions { display: inline-flex; gap: var(--sp1); }
.browse-views-update-cancel,
.browse-views-update-submit {
  min-block-size: 2rem;
  padding-inline: var(--sp2);
  border: 1px solid var(--line);
  border-radius: 4px;
  background: transparent;
  color: var(--ink);
  font: 600 var(--fs-chip)/1 var(--f-ui);
  cursor: pointer;
}
.browse-views-update-submit { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); color: var(--accent); }
.browse-views-update-cancel:hover,
.browse-views-update-submit:hover { background: var(--paper); }
.browse-views-update-cancel:focus-visible,
.browse-views-update-submit:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-views-update-cancel:disabled,
.browse-views-update-submit:disabled { opacity: 0.5; cursor: default; }
.browse-views-update-err { flex: 1 0 100%; color: var(--rust); font-size: var(--fs-chip); }
/* the save-current-view form at the popover foot — the Add-field foot's idiom. */
.browse-views-save {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp1);
  margin-top: var(--sp1);
  padding-top: var(--sp2);
  border-top: 1px solid var(--line);
}
.browse-views-save-intro {
  display: flex;
  flex: 1 0 100%;
  flex-direction: column;
  gap: 2px;
  margin-block-end: var(--sp1);
  color: var(--ink);
  font: var(--fs-chip)/1.25 var(--f-ui);
}
.browse-views-save-intro span { color: var(--ink-dim); font-size: var(--fs-chip); }
.browse-views-save-name {
  flex: 1;
  min-width: 6rem;
  font: inherit;
  font-size: var(--fs-chip);
  color: var(--ink);
  background: var(--wash);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: var(--sp1) var(--sp2);
}
.browse-views-save-name:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-views-save-shared,
.browse-views-save-pinned {
  flex: 1 0 100%;
  display: flex;
  align-items: center;
  gap: var(--sp1);
  font-size: var(--fs-chip);
  color: var(--ink-dim);
  cursor: pointer;
}
.browse-views-save-shared input,
.browse-views-save-pinned input { accent-color: var(--accent); }
.browse-views-save-btn {
  flex: 1 0 100%;
  font: inherit;
  font-size: var(--fs-chip);
  color: var(--accent);
  background: none;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: var(--sp1) var(--sp2);
  cursor: pointer;
}
.browse-views-save-btn:hover:not(:disabled) { background: var(--wash); }
.browse-views-save-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.browse-views-save-btn:disabled { opacity: 0.5; cursor: default; }
.browse-views-save-err { flex: 1 0 100%; font-size: var(--fs-chip); color: var(--rust); }

/* Responsive toolbar-sheet chrome. It stays inert on desktop, where these
   controls remain compact anchored popovers. Phone layouts reveal the same DOM
   as a true bottom sheet: an explicit title/close affordance and a dismissal
   scrim, rather than a desktop dropdown merely squeezed into the viewport. */
.browse-sheet-backdrop,
.browse-sheet-header { display: none; }

/* Slice-2 grouped-list lane divider (browse.ts laneHeader). A quiet uppercase
   micro-label marking each group bucket — the same idiom as the panel/strip
   headings (uppercase + tracking carries the smallness). Token-only; the full
   look (sticky lane, count badge) is the deferred look lane's. `grid-column:1/-1`
   makes it span the row when the grid skin lands; inert in today's unstyled list. */
.browse-lane {
  list-style: none;
  grid-column: 1 / -1;
  margin: var(--sp4) 0 var(--sp1);
  padding-top: var(--sp2);
  border-top: 1px solid var(--line);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  font-size: var(--fs-label);
  color: var(--ink-faint);
}
.browse-lane:first-child { margin-top: 0; border-top: 0; padding-top: 0; }
/* Slice-3c — the bucket's TOTAL count (all pages), a dim parenthetical pill trailing
   the lane label ("ARCHIVED (12)"). Cool-neutral recede (chrome, not content): it's
   wayfinding metadata, so it stays quieter than the label and never competes with a
   teal interaction or warm content name. The parens are the CSS's so the DOM text is
   the raw number (one source: the server's count). */
.browse-lane-count {
  margin-left: var(--sp1);
  color: var(--ink-faint);
  opacity: 0.75;
  letter-spacing: 0;
}
.browse-lane-count::before { content: '('; }
.browse-lane-count::after { content: ')'; }

/* LIST-AS-TABLE (browse.ts list layout) — the brief §5 headline surface: the
   `list` layout is a dense metadata TABLE, not a stack of cards. A column header
   names each field; every settled row aligns into the SAME columns via the shared
   --list-cols track set (computed in browse.ts from the active Fields projection,
   so a hidden field drops its column in lock-step). Role tokens only (§3): the cool
   chrome RECEDES — faint uppercase header labels, --line row rules, dim meta inks —
   while the warm cream serif NAME leads as content and teal stays the lone
   interaction note (the openable hover/focus). The Name cell includes a compact
   media thumbnail; grid uses the same image language at gallery scale. */
.browse-list[data-layout="list"] {
  list-style: none; margin: 0; padding: 0;
  /* One parent grid resolves max-content ONCE across the header and every row.
     The former sibling grids reused the same template string but independently
     measured their contents, so Project/Duration/Status visibly wandered. */
  display: grid; grid-template-columns: var(--list-cols); column-gap: var(--sp3);
}
.browse-list[data-layout="list"] .browse-card,
.browse-list[data-layout="list"] .browse-colhead {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: center;
  /* the column rhythm rides the --sp3 step (was --sp4): the mockup's meta columns
     sit in a tighter cadence than the airy 16px spread read as. One token down
     pulls the name→meta gap onto the mockup's denser table rhythm. */
  column-gap: inherit;
  padding: var(--sp2);
  border-bottom: 1px solid var(--line);
  border-left: 3px solid transparent;   /* the project-color rail's gutter; header/colorless rows keep it clear */
}
/* the column-header row — quiet uppercase micro-labels, the same register as the
   group-lane divider; chrome recedes in --ink-faint with a firmer head rule. */
.browse-list[data-layout="list"] .browse-colhead {
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  font-family: var(--f-ui);
  font-size: var(--fs-label);
  color: var(--ink-faint);
  border-bottom-color: var(--ink-deco);
}
.browse-list[data-layout="list"] .browse-colhead > *,
.browse-list[data-layout="list"] .browse-card > * { justify-self: start; text-align: left; }
/* the asset NAME is the content hero: warm serif, full ink, truncating (never
   wrapping) so the row rhythm holds down a long list. */
.browse-list[data-layout="list"] .card-name {
  font-family: var(--f-body);
  font-size: var(--fs-base);
  color: var(--ink);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.browse-list[data-layout="list"] .asset-name-cell {
  display: grid;
  grid-template-columns: 5rem minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp2);
  min-width: 0;
  width: 100%;
}
.asset-version-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp1);
  min-height: 1.75rem;
  padding: 0 var(--sp2);
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-dim);
  font-family: var(--f-ui);
  font-size: var(--fs-chip);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
}
.asset-version-toggle:hover {
  border-color: var(--line-strong);
  background: var(--wash);
  color: var(--ink);
}
.asset-version-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.asset-version-icon { font-size: 0.9em; }
.asset-version-chevron {
  transition: transform 120ms ease;
  transform-origin: center;
}
.asset-version-toggle[aria-expanded="true"] .asset-version-chevron { transform: rotate(180deg); }
:root[data-motion="reduced"] .asset-version-chevron { transition: none; }
.browse-grid .asset-version-toggle {
  position: absolute;
  z-index: 1;
  top: var(--sp3);
  left: var(--sp3);
  border-color: color-mix(in oklab, var(--ink) 22%, transparent);
  background: color-mix(in oklab, var(--canvas) 82%, transparent);
  color: var(--ink);
  backdrop-filter: blur(10px);
}
.version-stack-drawer {
  min-width: 0;
  margin-top: var(--sp2);
  padding: var(--sp3);
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
  color: var(--ink);
}
.version-stack-drawer[hidden] { display: none; }
.version-stack-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp3);
  padding-bottom: var(--sp2);
  color: var(--ink);
  font: 600 var(--fs-ui)/1.3 var(--f-ui);
}
.version-stack-summary {
  display: flex;
  align-items: baseline;
  gap: var(--sp2);
  min-width: 0;
}
.version-stack-summary > span {
  color: var(--ink-faint);
  font-size: var(--fs-note);
  font-weight: 400;
}
.version-stack-add {
  min-height: 36px;
  padding: 0 var(--sp3);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: none;
  color: var(--accent);
  font: 600 var(--fs-note)/1 var(--f-ui);
  white-space: nowrap;
  cursor: pointer;
}
.version-stack-add:hover { background: var(--wash); }
.version-stack-add:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.version-stack-list {
  display: grid;
  gap: 1px;
  margin: 0;
  padding: 0;
  list-style: none;
  border-block: 1px solid var(--line);
}
.version-stack-list > li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 44px;
  align-items: stretch;
}
.version-stack-list > li + li { border-top: 1px solid var(--line); }
.version-stack-member {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp3);
  width: 100%;
  min-height: 44px;
  padding: var(--sp2);
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.version-stack-member:hover { background: var(--wash); }
.version-stack-member:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.version-stack-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.25rem;
  min-height: 1.75rem;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  color: var(--ink-dim);
  font: 600 var(--fs-chip)/1 var(--f-ui);
  font-variant-numeric: tabular-nums;
}
.version-stack-copy {
  display: grid;
  min-width: 0;
  gap: 2px;
}
.version-stack-copy strong {
  overflow: hidden;
  color: var(--ink);
  font: 500 var(--fs-ui)/1.25 var(--f-ui);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.version-stack-copy span,
.version-stack-open,
.version-stack-state {
  color: var(--ink-faint);
  font: var(--fs-note)/1.3 var(--f-ui);
}
.version-stack-open { color: var(--accent); }
.version-stack-actions {
  display: grid;
  place-items: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-left: 1px solid var(--line);
  border-radius: 0;
  background: none;
  color: var(--ink-faint);
  font: 600 var(--fs-ui)/1 var(--f-ui);
  cursor: pointer;
}
.version-stack-actions:hover {
  background: var(--wash);
  color: var(--ink);
}
.version-stack-actions:focus-visible {
  z-index: 1;
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.version-stack-state { margin: 0; padding: var(--sp3); }
.version-stack-error { color: var(--rust); }
.browse-list[data-layout="list"] .version-stack-drawer {
  grid-column: 1 / -1;
  justify-self: stretch;
  width: 100%;
  margin: var(--sp1) 0;
}
.browse-grid .version-stack-drawer { margin: var(--sp1) var(--sp2) var(--sp2); }
.asset-version-toggle--recent {
  position: absolute;
  z-index: 2;
  /* Anchor to the upload ROW, not 50% of the whole .recent-item: that wrapper
     grows when history expands, and percentage centering would drag this trigger
     down into its own drawer. */
  top: 1.9rem;
  right: calc(44px + var(--sp2));
  transform: translateY(-50%);
}
.recent-item:has(.asset-version-toggle--recent) .recent-link {
  padding-right: calc(9rem + var(--sp4));
}
.recent-item > .version-stack-drawer {
  margin: 0 var(--sp3) var(--sp3);
}
@media (max-width: 48rem) {
  .asset-version-toggle { min-height: 44px; }
  .asset-version-toggle--recent {
    position: static;
    transform: none;
    margin: var(--sp1) calc(44px + var(--sp2)) var(--sp2) var(--sp3);
  }
  .recent-item:has(.asset-version-toggle--recent) .recent-link {
    padding-right: calc(44px + var(--sp2));
  }
  .version-stack-summary { align-items: flex-start; flex-direction: column; gap: 2px; }
  .version-stack-add { min-height: 44px; }
  .version-stack-open { display: none; }
}
.browse-list[data-layout="list"] .asset-name-cell .card-thumb {
  width: 5rem;
  aspect-ratio: 16 / 9;
  border-radius: 3px;
}
.browse-list[data-layout="list"] .pending-status {
  grid-column: 2 / -1;
  justify-self: stretch;
  display: flex;
  align-items: center;
  gap: var(--sp3);
  min-width: 0;
  color: var(--ink-dim);
  font-family: var(--f-ui);
  font-size: var(--fs-note);
}
.browse-list[data-layout="list"] .pending-status .card-progress {
  flex: 1 1 12rem;
  width: min(18rem, 100%);
  max-width: 18rem;
  margin: 0;
}
.browse-list[data-layout="list"] .pending-status .card-spinner,
.browse-list[data-layout="list"] .pending-status .card-error { margin: 0; }
.browse-list[data-layout="list"] .pending-status .card-error {
  flex-wrap: nowrap;
  width: min(100%, 36rem);
}
.browse-list[data-layout="list"] .pending-status .card-error-reason {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* the meta columns recede to chrome ink + the UI face. */
.browse-list[data-layout="list"] .card-project,
.browse-list[data-layout="list"] .card-dur,
.browse-list[data-layout="list"] .card-state {
  font-family: var(--f-ui);
  font-size: var(--fs-note);
  color: var(--ink-dim);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.browse-list[data-layout="list"] .card-dur { font-variant-numeric: tabular-nums; }
.browse-list[data-layout="list"] .card-mf-select { justify-self: stretch; }
.card-mf-link { color: var(--accent); text-decoration: none; display: block; max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-mf-link:hover { text-decoration: underline; }
/* CUSTOM mf_ FIELDS — the look-lane skin for user-defined columns (8f0360e landed
   the cells UNSTYLED, so a custom column read at full base size and broke the row
   rhythm). A custom field is the user's OWN authored datum, NOT system chrome: it
   joins the table recede (cool UI face, note size, truncating) so the column aligns
   into the same rhythm as the built-in meta, but reads at full --ink — one ink-step
   UP from the --ink-dim built-ins — so a column the user deliberately added carries
   a touch more presence as their data, WITHOUT borrowing teal (--accent stays the
   lone interaction note, §3 theme discipline). tabular-nums because custom fields
   skew numeric (fps, frame rate, day #). Token-only; Jesse owns the final feel. */
.browse-list[data-layout="list"] .card-mf {
  font-family: var(--f-ui);
  font-size: var(--fs-note);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The project-color rail is RETIRED on ASSET rows (Jesse 2026-07-20): a project's
   identity color belongs on the project DISPLAY (its circle/tile), not echoed onto every
   asset inside it — that read as noise. Assets keep the transparent gutter (line ~506) so
   the row rhythm holds; a genuine per-ASSET color (none by default) is a separate,
   low-priority idea. The rail stays on the project rows/tiles (.project-item/.project-tile). */
/* the openable row earns a teal-adjacent hover wash (the focus ring is the shared
   .openable:focus-visible below). */
.browse-list[data-layout="list"] .browse-card.openable:hover { background: var(--wash-soft); }
/* Selection is a working SET, not a hover accident. One click and Shift/Cmd-click
   therefore earn a persistent field + complete inset frame in both projections.
   The frame is deliberately uniform (never a side stripe) so it cannot be confused
   with project identity or lifecycle state. */
.browse-card.selected {
  background: color-mix(in oklab, var(--wash) 76%, var(--accent) 6%);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--accent) 72%, var(--line));
}
.browse-card.selected:hover {
  background: color-mix(in oklab, var(--wash) 84%, var(--accent) 8%);
}
.asset-selection-mark {
  display: none;
  position: absolute;
  inset-block-start: var(--sp2);
  inset-inline-start: var(--sp2);
  inline-size: 1.25rem;
  block-size: 1.25rem;
  place-items: center;
  border: 1px solid color-mix(in oklab, var(--accent) 78%, var(--line));
  border-radius: 4px;
  background: var(--accent);
  color: var(--bg);
  font-family: var(--f-ui);
  font-size: var(--fs-chip);
  font-weight: 700;
  line-height: 1;
  pointer-events: none;
  z-index: 2;
}
.browse-card.selected .asset-selection-mark { display: grid; }
.browse-list[data-layout="list"] .asset-selection-mark {
  inset-block-start: 50%;
  transform: translateY(-50%);
}
/* an archived row keeps its dim + the named "Archived" chip, but in the grid the
   chip's ::after must not claim a metadata column — span it full-width on its own
   line below the row so the columns stay aligned. */
.browse-list[data-layout="list"] .browse-card.archived::after {
  grid-column: 1 / -1;
  justify-self: start;
  margin-left: 0;
}

/* GRID GALLERY (browse.ts grid layout, Slice-3) — the brief §5 card grid: the SAME
   server rows the list TABLE renders, only RE-SHAPED into a thumbnail gallery (sin
   #34 — no re-query, no client predicate; the layout decides shape, never data). An
   auto-fill track lays as many cards per row as the content measure allows and
   reflows responsively; `cardSize` (sm|md|lg, stamped data-card-size) tunes the min
   card width so the same view goes denser/larger without a new layout. Role tokens
   only (§3): the cool --surface tile recedes as chrome, the warm serif name leads as
   content, the project-color rail carries identity, teal stays the lone interaction
   note (the shared .openable hover/focus below). The grouped-lane band already spans
   the row (.browse-lane carries grid-column:1/-1) so buckets read across the gallery. */
.browse-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--grid-card-min, 15rem), 1fr));
  gap: var(--sp4);
  align-items: start;
}
.browse-grid[data-card-size="sm"] { --grid-card-min: 11rem; }
.browse-grid[data-card-size="md"] { --grid-card-min: 15rem; }
.browse-grid[data-card-size="lg"] { --grid-card-min: 20rem; }
/* a gallery card is a STACKED tile — the 16/9 poster on top (the shared .card-thumb
   box), the warm serif name beneath, then the cool meta run. The project-color rail
   wraps the tile's left edge in the SAME --card-accent the list row carries, so a
   project reads the same one mode over. */
.browse-grid .browse-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp2);
  padding: var(--sp2);
  border: 1px solid var(--line);   /* uniform hairline — the project-color left rail is RETIRED on asset tiles (Jesse 2026-07-20; color lives on the project display) */
  border-radius: 8px;
  background: var(--surface);
  min-width: 0;
}
.browse-grid .browse-card.openable:hover { background: var(--wash-soft); }
.browse-grid .card-name {
  font-family: var(--f-body);   /* warm serif = content/name */
  font-size: var(--fs-base);
  color: var(--ink);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* the card's meta run wraps under the name (cool UI face, dim ink) — the SAME Fields
   projection the list cells lay, only stacked instead of column-aligned. */
.browse-grid .card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp1) var(--sp2);
  font-family: var(--f-ui);
  font-size: var(--fs-note);
  color: var(--ink-dim);
  min-width: 0;
}
.browse-grid .card-meta .card-mf { color: var(--ink); font-variant-numeric: tabular-nums; }
.card-mf-select {
  display: inline-flex; align-items: center; width: max-content; min-height: 1.5rem;
  padding: 0 .55rem; border: 1px solid var(--line); border-radius: 999px;
  color: var(--ink-dim); background: color-mix(in oklab, var(--surface) 78%, transparent);
  font-size: var(--fs-note); white-space: nowrap;
}
.card-mf-select[data-color="amber"] { color: var(--accent); border-color: color-mix(in oklab, var(--accent) 45%, var(--line)); }
.card-mf-select[data-color="rust"] { color: var(--rust); border-color: color-mix(in oklab, var(--rust) 45%, var(--line)); }
.card-mf-select[data-color="green"] { color: var(--ready); border-color: color-mix(in oklab, var(--ready) 45%, var(--line)); }
.card-mf-picker {
  max-width: 12rem; border: 0; background: transparent; color: inherit;
  font: inherit; cursor: pointer;
}
.card-mf-picker:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 999px; }
.card-mf-picker:disabled { cursor: wait; opacity: .65; }

/* Slice-3 — the detail-arm master–detail surface (browse.ts detailLayout). The
   master list keeps its `.browse-list` skin; this only lays the optional preview
   pane beside it (a two-column split that collapses to one when the pane is absent
   or the viewport is narrow). FUNCTIONAL structure only — the preview's full voice
   (player surface, field rows, chrome) is the deferred look lane's. Token-only. */
.browse-detail { display: grid; gap: var(--sp5); align-items: start; }
.browse-detail:has(.browse-preview) { grid-template-columns: minmax(0, 1fr) minmax(0, 22rem); }
.browse-preview {
  position: sticky;
  top: var(--sp4);
  display: flex;
  flex-direction: column;
  gap: var(--sp2);
  padding: var(--sp4);
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
}
.browse-preview.empty { color: var(--ink-faint); }
/* the pane's asset name is CONTENT, not chrome — the SAME warm serif (--f-body) at
   the content size (--fs-base) the list/grid .card-name carry, so one name reads as
   one voice across all three skins (sin #34). It's the largest instance of the name
   (the pane heading), so it must NOT recede to the cool --f-ui/--fs-ui chrome face. */
.preview-name { margin: 0; font-family: var(--f-body); font-size: var(--fs-base); color: var(--ink); }
.preview-meta { display: flex; flex-wrap: wrap; gap: var(--sp2); font-size: var(--fs-note); color: var(--ink-dim); }
/* the same custom-field signature in the detail pane: the user's authored datum
   reads a step UP (--ink) from the --ink-dim sibling notes, tabular figures, no
   teal. The pane wraps, so no truncation here. (mirrors .card-mf above). */
.preview-meta .preview-mf { color: var(--ink); font-variant-numeric: tabular-nums; }

@media (max-width: 48rem) {
  /* narrow: stack the pane under the list, drop the sticky follow */
  .browse-detail:has(.browse-preview) { grid-template-columns: 1fr; }
  .browse-preview { position: static; }

  /* A phone is not a compressed desktop asset table. The prior subgrid kept all
     five desktop headings while the row contents overflowed beneath one another,
     producing the mashed "NAMEPROJECT" label and metadata with no reliable track.
     Recompose the same row into a compact record: identity owns the first read,
     while state + the primary custom field (Status by default) remain available.
     Project is redundant inside a project and Duration yields at this width; the
     Fields control still governs which surviving custom fields are rendered. */
  .browse-list[data-layout="list"] {
    display: block;
  }
  .browse-list[data-layout="list"] .browse-colhead {
    display: none;
  }
  .browse-list[data-layout="list"] .browse-card {
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--sp2);
    row-gap: var(--sp1);
  }
  .browse-list[data-layout="list"] .asset-name-cell {
    grid-column: 1 / -1;
    grid-template-columns: 4rem minmax(8rem, 1fr);
  }
  .browse-list[data-layout="list"] .asset-name-cell .card-thumb { width: 4rem; }
  .browse-list[data-layout="list"] .card-project,
  .browse-list[data-layout="list"] .card-dur {
    display: none;
  }
  .browse-list[data-layout="list"] .card-state {
    grid-column: 1;
    grid-row: 2;
    padding-inline-start: calc(4rem + var(--sp2));
  }
  .browse-list[data-layout="list"] .card-mf[data-field="mf_status"] {
    grid-column: 2;
    grid-row: 2;
  }
  /* Status remains directly editable in the compact record. Its native select
     previously inherited only the 17px text line inside a visual pill; make the
     control itself—not a decorative wrapper—the phone-sized target. */
  .card-mf-picker { min-height: 44px; }
  .browse-list[data-layout="list"] .pending-status { gap: var(--sp2); }
}

/* a READY card opens its canvas on click/Enter (browse.ts, LANE-1 slice 3). The
   functional affordance only — pointer + a keyboard-focus ring; the card's full
   look (rail, hover, grid skin) is the deferred look lane's. Token-only outline. */
.browse-card.openable { cursor: pointer; }
.browse-card.openable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The selected-assets shelf makes the file-manager grammar self-explanatory:
   one click primes an asset, Open is explicit for people who dislike double-click,
   and Actions rides the SAME selection-aware menu provider as right-click/⋯.
   A compact centered shelf avoids covering the rail and scales down to a phone-width
   bottom bar with safe-area breathing room. */
.asset-selection-shelf {
  position: fixed;
  inset-inline-start: 50%;
  inset-block-end: max(var(--sp4), env(safe-area-inset-bottom));
  z-index: var(--layer-notice);
  transform: translateX(-50%);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp3);
  inline-size: min(44rem, calc(100vw - 2 * var(--sp5)));
  min-block-size: 3.5rem;
  padding: var(--sp2);
  border: 1px solid color-mix(in oklab, var(--accent) 44%, var(--line));
  border-radius: 8px;
  background: color-mix(in oklab, var(--surface) 94%, var(--bg));
  box-shadow: 0 12px 32px color-mix(in oklab, var(--bg) 72%, transparent);
}
.asset-selection-shelf[hidden] { display: none; }
.asset-selection-clear,
.asset-selection-all,
.asset-selection-more,
.asset-selection-share,
.asset-selection-open {
  min-block-size: 2.5rem;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: transparent;
  color: var(--ink);
  font: 600 var(--fs-ui) / 1 var(--f-ui);
  cursor: pointer;
}
.asset-selection-clear {
  inline-size: 2.5rem;
  color: var(--ink-dim);
  font-size: var(--fs-base);
}
.asset-selection-copy {
  display: grid;
  gap: 2px;
  min-inline-size: 0;
}
.asset-selection-summary,
.asset-selection-hint {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.asset-selection-summary {
  color: var(--ink);
  font: 600 var(--fs-ui) / 1.3 var(--f-ui);
}
.asset-selection-hint {
  color: var(--ink-faint);
  font: var(--fs-note) / 1.3 var(--f-ui);
}
.asset-selection-actions { display: flex; gap: var(--sp2); }
.asset-selection-more,
.asset-selection-all,
.asset-selection-share,
.asset-selection-open { padding-inline: var(--sp3); }
.asset-selection-open {
  border-color: color-mix(in oklab, var(--accent) 68%, var(--line));
  background: var(--accent);
  color: var(--bg);
}
.asset-selection-clear:hover,
.asset-selection-all:hover,
.asset-selection-more:hover,
.asset-selection-share:hover { background: var(--wash); }
.asset-selection-open:hover {
  background: oklch(from var(--accent) calc(l + var(--hover-delta)) c h);
}
.asset-selection-clear:focus-visible,
.asset-selection-all:focus-visible,
.asset-selection-more:focus-visible,
.asset-selection-share:focus-visible,
.asset-selection-open:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (max-width: 40rem) {
  .asset-selection-shelf {
    inset-block-end: max(var(--sp2), env(safe-area-inset-bottom));
    inline-size: calc(100vw - 2 * var(--sp2));
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--sp2);
  }
  .asset-selection-clear,
  .asset-selection-all,
  .asset-selection-more,
  .asset-selection-share,
  .asset-selection-open {
    min-block-size: 2.75rem;
  }
  .asset-selection-hint,
  .asset-selection-all,
  .asset-selection-more { display: none; }
  /* One selected asset gets the direct Open action. A batch cannot open, so
     spend that same scarce mobile slot on the shared Actions menu instead of
     leaving the user with a selection they can only dismiss. */
  .asset-selection-shelf.is-multi .asset-selection-more:not([hidden]) {
    display: block;
  }
}

/* the scoped-project header (W2 lane-2) — FUNCTIONAL structure only: a row with a
   color swatch that consumes --project-accent (the recolor swatch must show the
   color it represents) and the project title. The full voice (typography, chrome,
   the recolor swatch grid) is the deferred look lane's, like the cards above. */
/* the scope-climb breadcrumb (brief §7): the ancestor trail above the list. §3
   theme discipline — cool-neutral chrome RECEDES: the separator + the current leaf
   read in the dim/faint inks, while the one climbable crumb (Home) is the lone
   interaction note in --accent (teal). No hardcoded hex — all token-borne. */
.breadcrumb {
  display: flex;
  align-items: center;
  gap: var(--sp2);
  min-inline-size: 0;
  overflow-x: auto;
  scrollbar-width: none;
  white-space: nowrap;
  font-family: var(--f-ui);
  font-size: var(--fs-base);
  line-height: 1.4;
}
.breadcrumb::-webkit-scrollbar { display: none; }
.crumb-sep { color: var(--ink-faint); user-select: none; }
.crumb-current { color: var(--ink); font-weight: 500; }
/* The hierarchy band stays typographically flat and quiet. Populated scopes move
   project utilities into the toolbar; an empty scope may keep their compact group
   in the optional trailing track because it has no toolbar of its own. */
.project-header.browse-orient {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp4);
  inline-size: 100%;
  margin-bottom: var(--sp3);
}
.browse-orient .project-title {
  font: inherit;
  font-weight: 500;
  color: inherit;
  line-height: inherit;
}
.project-identity-copy,
.project-identity {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.project-header .project-description,
.project-list .project-description,
.project-grid .project-description,
.preview-description {
  color: var(--ink-faint);
  font-family: var(--f-ui);
  font-size: var(--fs-note);
  font-weight: 400;
  line-height: 1.35;
}
.project-header .project-description {
  max-inline-size: min(34rem, 40vw);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.crumb-link {
  color: var(--ink-dim);
  text-decoration: none;
  cursor: pointer;
}
.crumb-link:hover { color: var(--ink); }

.project-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  /* NO base row spacing here: the `.browse-orient` variant owns the complete
     breadcrumb-and-controls band's --sp3 rhythm (design-audit pins this). */
}
.project-scope-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--sp1);
  justify-self: end;
}
.project-swatch {
  inline-size: 0.85rem;
  block-size: 0.85rem;
  border-radius: 50%;
  flex: none;
  background: var(--project-accent, var(--project-slate));
}
.project-swatch.no-accent { background: var(--project-slate); }
.project-title { font-weight: 600; }

/* Project people are internal access, not the external Share action. The compact
   labelled control lives beside the project identity so access is discoverable
   without turning the breadcrumb into another toolbar. */
.project-people {
  display: inline-flex;
  align-items: center;
  gap: var(--sp1);
  min-block-size: 2rem;
  padding: 0 var(--sp2);
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-dim);
  font: 600 var(--fs-chip)/1 var(--f-ui);
  cursor: pointer;
}
.project-people-label { display: none; }
.project-people svg { inline-size: 1rem; block-size: 1rem; }
.project-people-summary {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-variant-numeric: tabular-nums;
}
.project-people-summary[hidden] { display: none; }
.project-people-avatars { display: inline-flex; align-items: center; }
.project-people-avatar {
  display: inline-grid;
  place-items: center;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border: 1px solid var(--bg);
  border-radius: 50%;
  background: color-mix(in oklab, var(--accent) 18%, var(--surface));
  color: var(--ink);
  font: 650 0.48rem/1 var(--f-ui);
  letter-spacing: 0;
}
.project-people-avatar + .project-people-avatar { margin-inline-start: -0.35rem; }
.project-people-count { color: var(--ink); }
.project-people:hover { color: var(--ink); border-color: var(--line-strong); background: var(--wash); }
.project-people:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.project-people[data-loading="true"] { cursor: progress; }
.project-people[data-loading="true"] .project-people-icon { animation: project-people-pulse 900ms ease-in-out infinite alternate; }
@keyframes project-people-pulse { to { opacity: .35; } }
:root[data-motion="reduced"] .project-people[data-loading="true"] .project-people-icon {
  animation: none;
  opacity: .55;
}
.project-people[data-access-mode="restricted"]::after {
  content: "Restricted";
  color: var(--ink-faint);
  font-weight: 500;
}

/* The project toolset reaches its real content pressure point before the rest
   of Browse becomes a phone layout. Reflow the same controls at 680px so the
   segmented List/Grid control is never compressed into a clipped syllable;
   bottom sheets and record-style asset rows remain reserved for 560px. */
@media (max-width: 680px) {
  .browse-tools {
    max-inline-size: 100%;
    flex-wrap: wrap;
    align-items: center;
    overflow: visible;
    row-gap: var(--sp2);
  }
  .browse-tools > * { flex: none; }
  .browse-tools .browse-search.is-open {
    flex: 1 0 100%;
    margin-inline-end: 0;
    min-inline-size: 0;
  }
  .browse-tools .browse-search.is-open .browse-search-input {
    inline-size: 100%;
    min-block-size: 2.75rem;
  }
  .browse-search-toggle,
  .browse-sort-toggle,
  .browse-display-option { inline-size: 44px; block-size: 44px; flex-basis: 44px; }
  .browse-tools .browse-fields > summary,
  .browse-tools .browse-filters > summary,
  .browse-tools .browse-views > summary,
  .browse-tools .browse-group,
  .browse-tools .browse-group-dir,
  .browse-tools .browse-sort,
  .browse-tools .browse-lifecycle,
  .browse-tools .browse-folder-new,
  .browse-tools .browse-new,
  .browse-tools .browse-scope,
  .browse-tools .layout-toggle {
    min-block-size: 2.75rem;
  }
  .browse-tools .browse-add { --browse-add-size: 2.75rem; }
  .browse-tools .browse-fields > summary,
  .browse-tools .browse-filters > summary,
  .browse-tools .browse-views > summary {
    display: flex;
    align-items: center;
  }
  .browse-tools .layout-group { margin-inline-start: auto; }
}

@media (max-width: 560px) {
  .browse-orient { gap: var(--sp2); }
  .breadcrumb { padding-block: var(--sp1); font-size: 1rem; }
  .browse-toolbar { align-items: stretch; flex-direction: column; }
  .browse-collection-context { min-block-size: 2.75rem; }
  .browse-collection-identity { min-inline-size: 0; }
  .browse-collection-kicker { display: none; }
  .browse-collection-context .browse-views > summary {
    max-inline-size: calc(100vw - 6.5rem);
    min-block-size: 2.75rem;
    display: flex;
    align-items: center;
  }
  .browse-collection-more { min-block-size: 2.75rem; inline-size: 2.75rem; }
  .browse-collection-savebar {
    inset-block-end: var(--sp2);
    inline-size: calc(100% - 2 * var(--sp2));
    align-items: stretch;
    flex-wrap: wrap;
    gap: var(--sp2);
  }
  .browse-collection-savebar-copy { flex-basis: calc(100% - 6rem); }
  .browse-collection-savebar-error { order: 3; flex-basis: 100%; }
  .browse-collection-savebar-actions { margin-inline-start: auto; }
  .browse-facets {
    max-inline-size: 100%;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .browse-facets::-webkit-scrollbar { display: none; }
  .browse-collection-context .browse-collection-draft-save {
    min-block-size: 2.75rem;
    flex: none;
  }
  .home-project-controls .browse-sort,
  .home-project-controls .layout-toggle { min-block-size: 2.75rem; }
  .browse-sheet-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: var(--layer-drawer-scrim);
    inline-size: 100%;
    block-size: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: color-mix(in oklch, var(--ground) 68%, transparent);
    cursor: default;
  }
  .browse-fields-panel,
  .browse-filters-panel,
  .browse-views-panel {
    position: fixed;
    z-index: var(--layer-drawer);
    inset: auto 0 0;
    inline-size: auto;
    min-inline-size: 0;
    max-block-size: min(82dvh, 40rem);
    overflow: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    border-radius: 14px 14px 0 0;
    padding: var(--sp3) var(--sp3) calc(var(--sp3) + env(safe-area-inset-bottom));
    box-shadow: 0 -12px 36px oklch(0 0 0 / 0.48);
  }
  .browse-sheet-header {
    display: flex;
    position: sticky;
    inset-block-start: 0;
    z-index: 1;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp3);
    min-block-size: 2.75rem;
    margin-block-end: var(--sp2);
    padding-block-end: var(--sp2);
    color: var(--ink);
    background: var(--surface);
    border-bottom: 1px solid var(--line);
  }
  .browse-sheet-title { font: 600 1rem/1.2 var(--f-ui); }
  .browse-sheet-close {
    min-inline-size: 2.75rem;
    min-block-size: 2.75rem;
    padding-inline: var(--sp2);
    border: 0;
    border-radius: 5px;
    color: var(--accent);
    background: none;
    font: 600 var(--fs-chip)/1 var(--f-ui);
    cursor: pointer;
  }
  .browse-sheet-close:hover { background: var(--wash); }
  .browse-sheet-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
  .browse-field-opt,
  .browse-filter-rule { min-block-size: 2.75rem; }
  .browse-field-move,
  .browse-field-rename,
  .browse-field-retire,
  .browse-filter-remove,
  .browse-views-more { min-inline-size: 2.75rem; min-block-size: 2.75rem; }
  .browse-filter-field,
  .browse-filter-operator,
  .browse-filter-value,
  .browse-filter-file-size-unit,
  .browse-filter-add-button,
  .browse-field-add-name,
  .browse-field-add-type,
  .browse-field-add-btn,
  .browse-views-save-name,
  .browse-views-save-btn { min-block-size: 2.75rem; font-size: 1rem; }
  .browse-views-apply { min-block-size: 3.25rem; justify-content: center; }
  .browse-filter-add { grid-template-columns: 1fr; }
  .browse-filter-date-presets { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .browse-filter-field,
  .browse-filter-operator,
  .browse-filter-value-slot,
  .browse-filter-add-button { grid-column: 1; }
  .project-people { min-inline-size: 2.75rem; min-block-size: 2.75rem; justify-content: center; padding: 0 var(--sp1); }
  .project-people-label,
  .project-people[data-access-mode="restricted"]::after { display: none; }
  .project-people:has(.project-people-summary:not([hidden])) .project-people-icon { display: none; }
  .project-people-avatar { inline-size: 1.35rem; block-size: 1.35rem; }
  .project-people-avatar:nth-child(n + 2) { display: none; }
}

/* the ARCHIVED cue (look-lane) — both the scoped header (self-archived, STAYS in
   view) and a still-shown card carry `.archived` from the render (browse.ts
   170/233) and the optimistic Archive verb (cardmenu.ts), yet the class was
   visually SILENT: only the transient toast cued it. Two reads: (1) a uniform dim
   so the set-aside item recedes without disappearing, and (2) a quiet token-only
   "Archived" chip via ::after so the state is named, not just inferred from the
   fade. Pure look — no DOM/JS change. FUNCTIONAL voice only (Jesse owns the exact
   styling), same discipline as the recolor swatch / R28 rip; static, no motion. */
.browse-card.archived,
.project-header.archived,
/* a home project row optimistically archived from its context menu (cardmenu.ts):
   the row dims in place until the next navigation drops it (the home lists active
   projects). Same set-aside recede as a card/header — the chip placement on the
   --list-cols grid row is a look-lane follow-on (#18). */
.project-item.archived,
.project-tile.archived { opacity: var(--set-aside-opacity); }
.browse-card.archived::after,
.project-header.archived::after {
  content: "Archived";
  flex: none;                 /* in the flex header it's a non-growing trailing item */
  align-self: center;
  padding: 1px var(--sp1);
  border-radius: 3px;
  /* the trio's grey status, made visible: a grey-washed pill ringed in the
     --archived role (not the generic --wash) so "grey = archived" SHOWS and the
     role is genuinely consumed — revert the token and the pill loses its grey. */
  background: var(--archived-wash);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--archived) 45%, transparent);
  color: var(--ink-dim);
  font-family: var(--f-ui);
  font-size: var(--fs-chip);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}
/* the header is the flex row above — its `gap` already separates the chip; the
   card is block flow, so the chip needs its own inline separation. */
.browse-card.archived::after { display: inline-block; margin-left: var(--sp2); vertical-align: middle; }

/* the per-row / header ⋯ Actions affordance (A2 slice-3 DISCOVERABILITY): the W2/W3
   Rename/Recolor/Archive/Move verbs were reachable by right-click ONLY (invisible) —
   this is the file-manager kebab that opens the SAME menu. FUNCTIONAL voice + a11y
   only; Jesse owns the final feel. At rest it recedes to chrome ink; hover/focus
   lights the lone interaction note (--accent teal), never a second color (§3). */
.row-actions {
  flex: none;
  border: none;
  background: none;
  cursor: pointer;
  padding: 0 var(--sp1);
  border-radius: 4px;
  color: var(--ink-faint);
  font-size: var(--fs-base);
  line-height: 1;
}
.row-actions:hover { color: var(--accent); background: var(--accent-soft); }
.row-actions:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; color: var(--accent); }
/* IN A CARD: absolutely placed so it claims NO grid track — the --list-cols columns
   and the colhead stay column-aligned (an in-flow child would add a stray cell). It
   reveals on row hover / focus so it never sits over the last column's text at rest. */
.browse-card { position: relative; }
.browse-card > .row-actions {
  position: absolute;
  top: 50%;
  right: var(--sp2);
  transform: translateY(-50%);
  opacity: 0;
  pointer-events: none;
}
.browse-card:hover > .row-actions,
.browse-card:focus-within > .row-actions,
.browse-card > .row-actions:focus-visible { opacity: 1; pointer-events: auto; }
/* IN THE GRID gallery the card is tall — pin the kebab to the top-right corner. */
.browse-grid .browse-card > .row-actions { top: var(--sp2); right: var(--sp2); transform: none; }
/* IN THE SCOPE HEADER it belongs to the explicit trailing control group, never the
   breadcrumb reading stream. Keep it large enough to be a calm touch target. */
.project-scope-actions > .row-actions {
  display: inline-grid;
  place-items: center;
  inline-size: 2rem;
  block-size: 2rem;
  padding: 0;
}
@media (max-width: 560px) {
  .project-scope-actions > .row-actions { inline-size: 2.75rem; block-size: 2.75rem; }
}
/* ON A HOME PROJECT ROW (list .project-item / detail master) — the ROW-PRIMITIVE pilot.
   The li is now a `.row` and the kebab lives in its `.row-trail` slot (styles/row.css),
   which drives the opacity hover-reveal. A grid-table row can't let that trailing slot
   claim a --list-cols track (it would knock the rows out of alignment with the colhead),
   so — exactly like the asset card — pin `.row-trail` ABSOLUTELY over the trailing edge
   instead of the primitive's in-flow margin-left:auto. The li carries the data-ctx='project'
   host the kebab forwards into the project menu. */
.project-item { position: relative; }
.project-item > .row-trail {
  position: absolute;
  top: 50%;
  right: var(--sp2);
  transform: translateY(-50%);
}
/* pointer-events gating (the primitive reveals with opacity only): under a hover pointer
   the invisible-at-rest slot must not eat a click over the last cell's text, so it stays
   non-interactive until the row is hovered/focused — matching the opacity reveal. On a
   coarse/no-hover pointer the primitive keeps the slot VISIBLE, and pointer-events default
   to auto here, so the kebab stays tappable (touch can't hover-reveal). */
@media (hover: hover) {
  .project-item > .row-trail { pointer-events: none; }
  .project-item:hover > .row-trail,
  .project-item:focus-within > .row-trail { pointer-events: auto; }
}
/* ON A GRID TILE the tile is tall — pin the kebab to the top-right corner (matching
   the asset grid card), always shown (a stacked tile has no last-column text to clear). */
.project-tile { position: relative; }
.project-tile > .row-actions {
  position: absolute; top: var(--sp2); right: var(--sp2);
  /* TILE-KEBAB-LIFT (Jesse 2026-07-20): the has-poster tile lifts its full-tile link to
     z-index:1 (text above the art, ~line 1399) — without a HIGHER index here the link ate
     the kebab's clicks on every poster tile (the tell: right-click still worked, it rides
     the row socket). z-index:2 restores the click on poster and poster-less tiles alike. */
  z-index: 2;
  /* …and make it READ on any backdrop (poster frames + color bands both defeated the bare
     --ink-faint glyph): the ink-on-media glyph on a quiet scrim disc — the same two tokens
     the poster tile's name legibility already rides (one on-media language). */
  color: var(--ink-on-media);
  background: var(--scrim);
  border-radius: 6px;
  padding: 2px var(--sp1);
}
.project-tile > .row-actions:hover,
.project-tile > .row-actions:focus-visible {
  color: var(--accent);
  background: var(--scrim);
}

/* Recent uploads use the same trailing-action placement as project rows. Previously the
   bare kebab sat after the full-width row button, which made it wrap onto a stray second
   line on phones. It is an overlay affordance, not another row of content. */
.recent-item { position: relative; }
.recent-item > .row-actions {
  position: absolute;
  top: 50%;
  right: var(--sp2);
  transform: translateY(-50%);
}
@media (hover: hover) {
  .recent-item > .row-actions { opacity: 0; pointer-events: none; transition: opacity 0.12s ease; }
  .recent-item:hover > .row-actions,
  .recent-item:focus-within > .row-actions { opacity: 1; pointer-events: auto; }
}

/* ───────────────────────────────────────────────────────────────────────────
   The HOME / scope-state shell (browser-shell brief §1/§6 — the nav-shell's
   landing surface). browse.ts renders EVERY messaging state (no-scope home,
   empty-project scope, loading, true-empty welcome, couldn't-load) through ONE
   `.browse-state` box: an `.state-title` heading, a `.state-line` subline, an
   optional `.state-cta` (New project), and — on the populated home — a
   `.project-list` and a `.recent-list` of row links into each scope. These were
   structurally correct but VISUALLY SILENT (raw unstyled HTML stacked at the
   top-left) — the "doesn't feel like a real app / no evidence of real flow" gap
   Jesse filed the brief over. This is the FUNCTIONAL-voice look-lane pass: §3
   token discipline (cool-neutral chrome RECEDES, warm serif names content, teal
   --accent is the lone interaction note), no hardcoded hex. Jesse owns the exact
   visual taste (#18 feel-check); this just makes the states read deliberate.
   NB: the home's "New project" is a real action but NOT the app's single PRIMARY
   verb — that register (`button.primary`, accent fill) is reserved for the
   editor's render command. The brief's "amber = primary action" wants a
   dedicated primary token the palette doesn't carry yet → flagged for the
   look-lane, not invented here; the CTA stays the standard bordered register. */
/* HOME-FLASH (Jesse: "two incorrect frames … worse than NO feedback") — the
   delay-before-show gate the boot surface + the scoped skeleton already ride, done
   in CSS: the home's loading state stays INVISIBLE for its first ~150ms, so a fast
   settle paints the real home with ZERO intermediate frames; a genuinely slow load
   fades the feedback in (still honest). settle() drops aria-busy → this rule stops
   matching → the final copy is visible instantly regardless of timing. */
.browse-state[data-state="no-scope"][aria-busy="true"] {
  opacity: 0;
  animation: state-delay-reveal 0.18s ease 0.15s forwards;
}
@keyframes state-delay-reveal { to { opacity: 1; } }

.browse-state {
  max-width: var(--measure-prose);            /* a readable measure — copy + lists don't sprawl edge-to-edge */
  margin: var(--sp6) auto 0;
  padding: 0 var(--sp4);
  display: flex;
  flex-direction: column;
  gap: var(--sp3);
}
/* `display:flex` is author CSS and therefore outranks the browser's built-in
   `[hidden] { display:none }`. Loading gates use the hidden attribute as their
   source of truth, so pin it explicitly or a logically hidden fast-load state
   still flashes on screen. */
.browse-state[hidden] { display: none; }
/* The home (`state-no-scope`) is a real library surface, so it is left-anchored and
   fills the content measure (shell-bounded by C1 — CONTAINED, never sprawls) for
   ALL its states: loading, populated, empty-welcome, couldn't-load. Keying the
   geometry on `state-no-scope` (not only the populated class browse.ts adds once
   content paints) kills the loading→populated JUMP (Jesse: the "thin centered page
   then snap to the real layout" microflash) — the box never repositions, only its
   copy + list swap in place. The 52ch dead-centered message box stays for the
   SCOPED states (empty project, no-results) — this rule is home-only. */
.browse-state.browse-home--populated,
.browse-state.state-no-scope {
  max-width: none;
  margin: var(--sp4) 0 0;
}
/* EMPTY-SCOPE = a proper DROP-ZONE CARD (Jesse 2026-07-20 — Frame/Spooky parity). The old
   52ch left-stacked "Nothing here yet" column read weird; this fills the content area as a
   dashed, centered upload target: cloud glyph → title → line → the actions in a ROW. The
   armDropZone .drag-over state lights the border + a soft wash so a drag reads as "drop
   here" (the .drop-overlay copy rides on top). position:relative anchors that overlay. */
.browse-state.state-empty-scope {
  max-width: none;
  margin: var(--sp4) 0 0;
  min-height: 340px;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--sp2);
  padding: var(--sp6);
  border: 2px dashed var(--line);
  border-radius: 12px;
  position: relative;
}
.browse-state.state-empty-scope.drag-over {
  border-color: var(--accent);
  background: var(--accent-soft);
}
/* WORKSPACE-EMPTY-STATES-1 — the arm that CANNOT take a drop (a workspace scope) sheds
   the dashed drop-target border: the dashes are the visual promise "drop a file here", and
   this scope refuses drops. Same card, same centering, quiet solid edge — an honest
   welcome rather than a drop zone that ignores you. */
.browse-state.state-empty-scope.empty-scope--no-ingest {
  border-style: solid;
  border-width: 1px;
}
.empty-glyph { color: var(--ink-faint); line-height: 0; }
.empty-glyph svg { width: 44px; height: 44px; display: block; }
/* the actions ride a ROW (not the stacked left-anchored .state-cta default) */
.empty-actions { display: flex; gap: var(--sp2); margin-top: var(--sp2); }
.empty-actions .state-cta { align-self: auto; margin-top: 0; }
.state-title {
  margin: 0;
  font-family: var(--f-body);   /* warm serif = content/names */
  font-size: var(--fs-clock);
  font-weight: 600;
  color: var(--ink);
}
.state-line {
  margin: 0;
  font-family: var(--f-ui);
  font-size: var(--fs-control);
  color: var(--ink-dim);
}
/* a home SECTION sub-heading (Recent uploads · Recent projects) — the warm serif of
   .state-title one step down in size, with breathing room above so a second strip reads
   as its OWN labeled section, not a continuation of the table above (Jesse 2026-07-20). */
.browse-section-head {
  margin: var(--sp5) 0 var(--sp2);
  font-family: var(--f-body);
  font-size: var(--fs-ui);
  font-weight: 600;
  color: var(--ink);
}
/* Home's two display lenses travel as one restrained toolbar. Layout stays left;
   the recency question sits beside it instead of masquerading as a global setting.
   The label is explicit because Created and Activity answer genuinely different jobs. */
.home-project-controls {
  display: flex;
  align-items: center;
  gap: var(--sp2);
  inline-size: 100%;
}
.home-project-controls > .layout-group { margin-inline-start: auto; }
.project-search {
  flex: 1 1 18rem;
  max-inline-size: 28rem;
}
.project-search .browse-search-input { inline-size: 100%; }
.project-filter-empty {
  margin: var(--sp4) 0 0;
  color: var(--ink-dim);
  font: 500 var(--fs-control)/1.4 var(--f-ui);
}
.home-recency {
  display: inline-flex;
  align-items: center;
  gap: var(--sp2);
  color: var(--ink-dim);
  font: 500 var(--fs-chip)/1.2 var(--f-ui);
}
.home-recency .browse-sort:disabled {
  cursor: progress;
  opacity: var(--set-aside-opacity);
}
@media (max-width: 640px) {
  .home-project-controls {
    align-items: stretch;
    flex-wrap: wrap;
  }
  .project-search {
    flex-basis: 100%;
    max-inline-size: none;
  }
}
.state-cta { align-self: flex-start; margin-top: var(--sp2); }
.state-cta:hover { border-color: var(--accent); color: var(--accent); }
/* The home "New project" hero CTA wears the SAME codified --primary-action role
   as the toolbar New (.browse-new, DECISIONS #27(1)) — the create verb is amber
   wherever it appears, retiring the bordered/wayfinding-accent register for THIS
   primary verb. The OTHER state CTAs (Upload videos, Clear, secondary New folder/
   Cancel) keep the standard skin — only the create verb is the primary action, so
   no two amber verbs collide. Color ROLE only: the CTA keeps its own state sizing;
   hover/active ride channel A's one --hover-delta knob, exactly like .browse-new.
   The :hover/:active here out-specify .state-cta:hover (0,3,0 > 0,2,0). */
.state-cta.primary-action {
  color: var(--bg-deep);
  background: var(--primary-action);
  border-color: transparent;
  font-weight: 600;
}
.state-cta.primary-action:hover {
  color: var(--bg-deep);
  border-color: transparent;
  background: oklch(from var(--primary-action) calc(l + var(--hover-delta)) c h);
}
.state-cta.primary-action:active {
  background: oklch(from var(--primary-action) calc(l - var(--hover-delta)) c h);
}
/* The home HEAD row has one stable action edge. Identity remains the left-hand
   reading group while creation aligns with the browser's right-edge action cluster;
   it no longer drifts beside short titles or moves when organization names change. */
.browse-home-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp4);
  inline-size: 100%;
}
.browse-home-titles { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* First run is the empty library, not a tour. Give its single explanatory line a
   readable measure and optically center the outcome-specific action against the
   two-line title block. */
.browse-home--first-run .browse-home-head { align-items: center; }
.browse-home--first-run .state-line { max-width: var(--measure-prose); line-height: 1.5; }
.first-run-path {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 18rem));
  gap: var(--sp3);
  margin: var(--sp3) 0 0;
  padding: 0;
  list-style: none;
  counter-reset: first-run-step;
}
.first-run-step {
  counter-increment: first-run-step;
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  gap: 2px var(--sp2);
  align-items: center;
  color: var(--ink-dim);
  font: var(--fs-chip)/1.4 var(--f-ui);
}
.first-run-step::before {
  content: counter(first-run-step);
  grid-row: 1 / span 2;
  display: grid;
  place-items: center;
  inline-size: 28px;
  block-size: 28px;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--accent);
  font: 600 var(--fs-note)/1 var(--f-ui);
}
.first-run-step-title { color: var(--ink); font-weight: 600; }
.first-run-step-copy { color: var(--ink-dim); }
.first-run-growth-link {
  align-self: flex-start;
  margin-top: var(--sp2);
  color: var(--ink-dim);
  font: 500 var(--fs-chip)/1.3 var(--f-ui);
  text-decoration: underline;
  text-decoration-color: var(--line-strong);
  text-underline-offset: 3px;
}
.first-run-growth-link:hover { color: var(--accent); text-decoration-color: currentColor; }
.first-run-growth-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* The clear "+ New project" button — the codified amber create verb (same --primary-action
   role + --hover-delta knob as .state-cta.primary-action / .browse-new), sized as a real
   top-bar action. flex:none so it never shrinks against a long title. */
.browse-new-project {
  flex: none;
  align-self: flex-start;
  font: inherit;
  font-size: var(--fs-chip);
  font-weight: 600;
  color: var(--bg-deep);
  background: var(--primary-action);
  border: 1px solid transparent;
  border-radius: 5px;
  padding: 7px 14px;
  cursor: pointer;
  white-space: nowrap;
}
.browse-new-project:hover { background: oklch(from var(--primary-action) calc(l + var(--hover-delta)) c h); }
.browse-new-project:active { background: oklch(from var(--primary-action) calc(l - var(--hover-delta)) c h); }
.browse-new-project:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (max-width: 560px) {
  .browse-home-head {
    align-items: flex-start;
    flex-direction: column;
    gap: var(--sp3);
  }
  .first-run-path { grid-template-columns: 1fr; }
}
/* R-REC-3 — the couldn't-load message-box's "Try again": a quiet inline text-button
   (no chrome at rest, --accent on hover/focus), the SAME recede-ink grammar the rail
   note uses (.rail-retry), shared by the home AND Recents couldn't-load states. Left-
   anchored on its own line under the state copy, above the New-project CTA. */
.state-retry {
  align-self: flex-start; margin-top: var(--sp1);
  background: none; border: none; padding: 0;
  font: inherit; color: var(--accent); cursor: pointer;
}
.state-retry:hover, .state-retry:focus-visible { text-decoration: underline; }

/* Mutations confirm from a quiet, reusable corner toast rather than inserting
   celebratory copy into the object list. The action remains easy to reach without
   displacing rows or stealing the page's visual hierarchy. */
.browse-toast,
body[data-route="page"] > #toast.toast {
  position: fixed;
  inset-inline-start: auto;
  inset-inline-end: max(var(--sp4), env(safe-area-inset-right));
  inset-block-end: max(var(--sp4), env(safe-area-inset-bottom));
  transform: none;
  z-index: var(--layer-toast);
  display: inline-flex;
  align-items: center;
  gap: var(--sp3);
  max-inline-size: min(24rem, calc(100vw - 2 * var(--sp4)));
  padding: 8px 10px 8px 12px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: color-mix(in oklab, var(--surface) 94%, var(--bg-deep));
  color: var(--ink);
  box-shadow: 0 12px 36px -18px rgb(0 0 0 / .72);
  font: 500 var(--fs-chip)/1.3 var(--f-ui);
}
.browse-toast[hidden],
body[data-route="page"] > #toast.toast[hidden] { display: none; }
.browse-toast .toast-action,
body[data-route="page"] > #toast.toast .toast-action {
  min-block-size: 28px;
  padding-inline: 8px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-weight: 650;
  cursor: pointer;
}
.browse-toast .toast-action:hover,
body[data-route="page"] > #toast.toast .toast-action:hover { background: var(--accent-soft); }
.browse-toast .toast-action:focus-visible,
body[data-route="page"] > #toast.toast .toast-action:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The two home lists — the org's projects and its recent uploads — as clean
   file-browser row lists: no bullets, hairline-separated rows. */
.project-list,
.recent-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--line);
}
.project-item,
.recent-item { border-bottom: 1px solid var(--line); }

/* the project-color rail (the 0009 facet): a 3px left stripe in --card-accent on the
   PROJECT rows — a project's identity color lives on its own display. The gutter is
   always reserved (transparent default) so colored and colorless rows align down the
   edge; .no-accent keeps it clear, never a faked hue. RETIRED on .recent-item (Jesse
   2026-07-20): recent uploads are ASSETS, and a project's color shouldn't echo onto the
   files inside it — same ruling as the in-project asset rows/tiles above. */
.project-item { border-left: 3px solid var(--card-accent, var(--project-slate)); }
.project-item.no-accent { border-left-color: var(--project-slate); }

/* Each row is a full-width button that must read as a LIST ROW, not a boxed
   button — override the base bordered treatment (border/padding from the base
   `button` rule). The name carries warm serif (content); hover lifts the
   themed --wash; focus shows the same --accent ring the openable cards use. */
.project-link,
.recent-link {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  border-radius: 4px;
  padding: var(--sp2) var(--sp3);
  background: none;
  font-family: var(--f-body);
  font-size: var(--fs-base);
  color: var(--ink);
}
.project-link:hover,
.recent-link:hover { background: var(--wash); border-color: transparent; }
.project-link:focus-visible,
.recent-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
/* a recent upload is a level quieter than a project (a project is a place you
   go; an upload is a thing inside one) — its name reads in the dim ink. */
.recent-link { color: var(--ink-dim); }

/* The home strip's "see all" bridge into /recents — a quiet text link at the foot
   of the strip. Reads as an action (themed accent ink), not chrome; the arrow nudges
   on hover so it feels like a forward move into the dedicated surface. Left-aligned
   under the rows, padded to echo the row inset. */
.recent-seeall {
  display: inline-flex;
  align-items: center;
  /* the home box is a flex COLUMN (stretches its children) — pin to content width so
     this reads as a compact link, never a full-width clickable bar. */
  align-self: flex-start;
  gap: var(--sp1);
  margin-top: var(--sp2);
  padding: var(--sp1) var(--sp3);
  font-family: var(--f-body);
  font-size: var(--fs-ui);
  color: var(--accent);
  text-decoration: none;
  border-radius: 4px;
}
.recent-seeall:hover { background: var(--wash); }
.recent-seeall:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.recents-tools {
  display: flex;
  align-items: center;
  margin-block: var(--sp3);
}
.recents-search {
  inline-size: min(24rem, 100%);
}
.recents-search .browse-search-input {
  inline-size: 100%;
}
.recents-filter-empty {
  margin: var(--sp4) 0;
  padding: var(--sp3);
  border-block: 1px solid var(--line);
  color: var(--ink-dim);
  font: var(--fs-ui) / 1.5 var(--f-ui);
}
.recent-seeall-arrow { transition: transform 120ms ease; }
.recent-seeall:hover .recent-seeall-arrow { transform: translateX(2px); }
/* the arrow nudge snaps under the reduced-motion attribute tier (the stylesheet's ONLY
   motion key — never a @media prefers-reduced-motion fork, uiprefs invariant). */
:root[data-motion="reduced"] .recent-seeall-arrow { transition: none; }

/* BOTH home strips — the org's projects AND its recent uploads — ride the SAME
   list-as-table machinery as the in-project asset table (Slice-C view-engine): a
   `.browse-colhead` column header + each row flattened to grid CELLS under the shared
   --list-cols template (browse.ts computes each from its kind's ColumnDescriptor set),
   so the entry-point home reads as ONE library surface — the two strips never visually
   fork (the old feed-vs-table split is gone; sin #34: one renderer, never two). The
   colhead is a sibling presentation row; the per-row grid lives on the row button (the
   rail + accent gutter stay on the `-item` wrapper). Each list carries its OWN
   --list-cols template (projects: Assets·Updated; uploads: Duration·Uploaded), so its
   columns align down the page. */
.project-list .browse-colhead,
.project-list .project-link,
.recent-list .browse-colhead,
.recent-list .recent-link {
  display: grid;
  grid-template-columns: var(--list-cols);
  align-items: center;
  gap: var(--sp3);
}
/* the colhead reserves the SAME 3px rail gutter the rows carry + matches the row
   button's padding, so its column 1 starts exactly under the row names. Quiet
   uppercase micro-labels — the cool chrome recedes, the same register as the
   in-project table head. */
.project-list .browse-colhead,
.recent-list .browse-colhead {
  border-left: 3px solid transparent;
  padding: var(--sp2) var(--sp3);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  font-family: var(--f-ui);
  font-size: var(--fs-label);
  color: var(--ink-faint);
  border-bottom: 1px solid var(--ink-deco);
}
/* the NAME is the content hero (warm serif, truncating); its COLOR is inherited from
   the row button — full --ink for a project (a place you go), the quieter --ink-dim for
   an upload (a thing inside one) — so the hierarchy distinction survives the shared
   cadence. The meta cells recede to the cool chrome register (dim ink, UI face, tabular
   figures) — the same content/chrome split the in-project table draws. */
.project-list .project-name,
.recent-list .recent-name {
  font-family: var(--f-body);
  font-size: var(--fs-base);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.project-list .project-identity,
.recent-list .recent-identity {
  display: grid;
  align-items: center;
  gap: var(--sp2);
  min-width: 0;
  overflow: hidden;
}
.project-list .project-identity {
  grid-template-columns: 3rem minmax(0, 1fr);
}
.recent-list .recent-identity {
  grid-template-columns: 5rem minmax(0, 1fr);
}
.recent-list .recent-identity .card-thumb {
  inline-size: 5rem;
  aspect-ratio: 16 / 9;
  border-radius: 3px;
}
.project-list .project-description {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.project-list .project-count,
.project-list .project-created,
.project-list .project-updated,
.recent-list .recent-dur,
.recent-list .recent-uploaded {
  font-family: var(--f-ui);
  font-size: var(--fs-note);
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;   /* counts/ages align cleanly down the column */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* the active-sort mark (#39): the column the rows are ORDERED by lifts out of the quiet
   chrome register — full ink + a direction caret — so the order the rows arrive in is the
   order the header advertises. The caret is decoration (aria-hidden); the cell's `title`
   carries the read-aloud signal. Sits inside the list AND recent colheads (one indicator
   vocabulary), keyed by `.is-sorted` so a non-active column stays plain. */
.browse-colhead .is-sorted {
  color: var(--ink);
}
.browse-colhead .colhead-sort {
  margin-left: 0.3em;
  font-size: 0.85em;
  opacity: 0.8;
}
/* a click-to-sort header (#39): the sortable Name + Created cells are real <button>s, so
   strip the UA chrome and inherit the colhead's type register — they read as the SAME
   header labels, just pressable. The cursor + a hover/focus ink-lift advertise the
   affordance; the active (`.is-sorted`) one already carries full ink + the caret. Keyboard
   focus rides the app's global :focus-visible ring (a real button is reachable by Tab — the
   whole point of not faking it with a div). Inline-flex so the caret rides beside the
   truncating label without a second layout context. */
.browse-colhead button.colhead-sortable {
  display: inline-flex;
  align-items: baseline;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
}
.browse-colhead button.colhead-sortable:hover {
  color: var(--ink);
}

/* The Recents recency lanes (Slice-1) ride the SAME .browse-lane divider the browse
   gallery uses (sin #34: one lane renderer, never a fork). But here the lane is a plain
   list row, NOT a grid cell — so .browse-lane's `grid-column: 1 / -1` is inert and its
   label would hang flush at the ul edge, left of the row names. Re-inset it under the
   names: match the row button's left padding PLUS the 3px accent-rail gutter the
   `.recent-item` wrappers carry, so the lane label and the upload names share one left
   edge down the surface. */
.recent-list .browse-lane {
  padding-left: calc(var(--sp3) + 3px);
}

/* The home's project GRID arm (`?layout=grid`) gives durable containers more visual
   identity than a table row without making them impersonate 16:9 assets. The cover is
   square (album/campaign language); the title and metadata remain live text in a calm
   footer. Posterless projects get a stable id-derived motif colored by --card-accent,
   while a real poster replaces only the cover plane. */
.project-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: var(--sp3);
}
.project-tile {
  min-width: 0;
  aspect-ratio: 1;
}
/* One square object, not cover + database-row chin. The art is full-bleed; live title
   and metadata sit over its calm lower exposure behind a shared scrim. */
.project-grid .project-link {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 0;
  block-size: 100%;
  padding: 0 0 var(--sp3);
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
  overflow: hidden;            /* clip the hero band's color to the rounded frame */
}
.project-grid .project-link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  inset: 32% 0 0;
  background: var(--bg);
  mask-image: linear-gradient(to bottom, transparent, black 72%);
}
.project-grid .project-link:hover { border-color: color-mix(in oklab, var(--ink) 24%, var(--line)); }
.project-grid .project-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
/* Darkroom light, not software-gradient wallpaper: the project's chosen color controls
   the room while Proof amber warms a deterministic ray. Project-id geometry variables
   move the source and angle so repeated palette choices still have individual covers. */
.project-cover {
  display: block;
  overflow: hidden;
  isolation: isolate;
  background: color-mix(in oklab, var(--card-accent, var(--project-slate)) 34%, var(--bg));
}
.project-grid .project-tile-hero {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
}
.project-list .project-list-thumb {
  position: relative;
  inline-size: 3rem;
  aspect-ratio: 1;
  border: 1px solid var(--line);
  border-radius: 5px;
}
.project-cover::before,
.project-cover::after {
  content: "";
  position: absolute;
  pointer-events: none;
  transform-origin: 28% 24%;
}
.project-cover::before {
  inline-size: 128%;
  block-size: 112%;
  inset: -38% auto auto -38%;
  clip-path: polygon(42% 0, 61% 0, 100% 100%, 8% 100%);
  background: color-mix(in oklab, var(--card-accent, var(--project-slate)) 44%, var(--primary-action));
  filter: blur(8px);
  opacity: 0.34;
  translate: var(--cover-ray-x, 0) var(--cover-ray-y, 0);
  transform: rotate(var(--cover-ray-angle, -12deg));
}
.project-cover::after {
  inline-size: 140%;
  block-size: 64%;
  inset: auto -28% -28% auto;
  border-radius: 50%;
  background: color-mix(in oklab, var(--card-accent, var(--project-slate)) 70%, var(--accent));
  filter: blur(28px);
  opacity: 0.18;
  transform: rotate(-8deg);
}
[data-cover-variant="1"] .project-cover::before {
  inset: -34% -38% auto auto;
  transform: rotate(26deg);
}
[data-cover-variant="1"] .project-cover::after {
  inset: auto auto -26% -34%;
  transform: rotate(14deg);
}
[data-cover-variant="2"] .project-cover::before {
  inline-size: 148%;
  block-size: 104%;
  inset: -16% auto auto -62%;
  transform: rotate(-28deg);
}
[data-cover-variant="2"] .project-cover::after {
  inset: -22% -42% auto auto;
  transform: rotate(28deg);
}
[data-cover-variant="3"] .project-cover::before {
  inline-size: 118%;
  block-size: 142%;
  inset: -62% auto auto -8%;
  transform: rotate(62deg);
}
[data-cover-variant="3"] .project-cover::after {
  inset: auto -42% -32% auto;
  transform: rotate(-18deg);
}
[data-cover-variant="4"] .project-cover::before {
  inset: -44% -28% auto auto;
  transform: rotate(38deg);
}
[data-cover-variant="4"] .project-cover::after {
  inset: auto auto -32% -22%;
  transform: rotate(-24deg);
}
[data-cover-variant="5"] .project-cover::before {
  inline-size: 154%;
  block-size: 108%;
  inset: -30% auto auto -56%;
  transform: rotate(8deg);
}
[data-cover-variant="5"] .project-cover::after {
  inset: -18% -46% auto auto;
  transform: rotate(22deg);
}
.project-item.no-accent .project-cover,
.project-tile.no-accent .project-cover {
  background: color-mix(in oklab, var(--project-slate) 68%, var(--bg));
}
/* Live copy overlays the exposure rather than becoming part of generated art. */
.project-grid .project-name {
  position: relative;
  z-index: 2;
  font-family: var(--f-body);
  font-size: var(--fs-clock);
  line-height: 1.12;
  color: var(--ink-on-media);
  margin: 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.project-grid .project-identity {
  position: relative;
  z-index: 2;
  gap: 3px;
  margin: 0 var(--sp3);
}
.project-grid .project-identity .project-name { margin: 0; }
.project-grid .project-description {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  color: color-mix(in oklab, var(--ink-on-media) 76%, transparent);
}
/* the SAME meta cells the list arm renders — count · updated — here a quiet stacked run
   beneath the name (the cool chrome register: dim ink, UI face, tabular figures). */
.project-grid .project-tile-meta {
  position: relative;
  z-index: 2;
  display: flex;
  gap: var(--sp2);
  margin: var(--sp1) var(--sp3) 0;
  font-family: var(--f-ui);
  font-size: var(--fs-note);
  color: color-mix(in oklab, var(--ink-on-media) 68%, transparent);
  font-variant-numeric: tabular-nums;
}

/* A real representative image keeps its complete frame inside generated-art geometry.
   This legacy surface has no atmospheric duplicate, so its neutral backing supplies
   honest letterbox space for portrait, square, and ultra-wide media. */
:is(.project-grid, .project-list, .project-preview-hero) .project-tile-poster {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;            /* clip the scaled blur bleed to the tile box */
  pointer-events: none;        /* the tile's button owns the clicks, not the art */
  border-radius: inherit;
}
:is(.project-grid, .project-list, .project-preview-hero) .project-tile-poster-media {
  inline-size: 100%;
  block-size: 100%;
  object-fit: contain;
  background: var(--bg-deep);
  filter: none;
  transform: none;
}
.has-poster .project-cover::before,
.has-poster .project-cover::after { display: none; }

/* Folders are quiet structure inside the existing browser, not a permanent third
   navigation column. Their section precedes direct assets and collapses away when
   empty. List rows stay compact; Grid gives the folder a little more silhouette. */
.folder-section:has(ul:empty) { display: none; }
.folder-list,
.folder-grid { list-style: none; margin: 0 0 var(--sp3); padding: 0; }
.folder-list { border-block: 1px solid var(--line); }
.folder-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: var(--sp2);
}
.folder-item {
  position: relative;
  display: flex;
  align-items: stretch;
  min-width: 0;
  color: var(--ink);
}
.folder-list .folder-item + .folder-item { border-top: 1px solid var(--line); }
.folder-grid .folder-item {
  min-height: 5.5rem;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
}
.folder-item.drop-target {
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 2px var(--accent);
}
.browse-card.dragging { opacity: var(--set-aside-opacity); }
.browse-card.version-drop-target {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  background: color-mix(in oklab, var(--accent) 9%, var(--surface));
}
.browse-card.version-drop-target::after {
  content: "Stack as new version";
  position: absolute;
  inset: auto var(--sp2) var(--sp2) auto;
  z-index: 4;
  padding: .32rem .5rem;
  border: 1px solid color-mix(in oklab, var(--accent) 55%, var(--line));
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--f-ui);
  font-size: var(--fs-chip);
  font-weight: 650;
  line-height: 1;
  box-shadow: var(--lift-1);
  pointer-events: none;
}
.folder-open {
  display: flex;
  align-items: center;
  gap: var(--sp2);
  flex: 1;
  min-width: 0;
  min-height: 46px;
  padding: var(--sp2) calc(44px + var(--sp2)) var(--sp2) var(--sp3);
  border: 0;
  color: inherit;
  background: transparent;
  text-align: left;
}
.folder-open:hover { background: var(--wash); }
.folder-open:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.folder-glyph { inline-size: 1.35rem; block-size: 1.35rem; flex: none; color: var(--working); }
.folder-grid .folder-glyph { inline-size: 2rem; block-size: 2rem; }
.folder-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.folder-actions {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: var(--sp1);
  z-index: 4;
  display: grid;
  place-items: center;
  inline-size: 40px;
  block-size: 40px;
  padding: 0;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--ink-dim);
  cursor: pointer;
  transform: translateY(-50%);
}
.folder-actions:hover,
.folder-actions:focus-visible { color: var(--accent); background: var(--accent-soft); }
.folder-empty { padding: clamp(2.5rem, 10vh, 6rem) var(--sp4); text-align: center; }
.folder-empty-title { margin: 0; font: 600 var(--fs-control)/1.2 var(--f-body); color: var(--ink); }
.folder-empty-line { margin: var(--sp2) 0 0; color: var(--ink-dim); }

/* The home's project DETAIL arm — a master–detail surface mirroring the in-project
   .browse-detail layout, but over PROJECTS: the SAME .project-list table as the master
   column (sin #34 — one renderer) beside a sticky preview pane projecting the lead
   project. The pane's full voice (asset thumbs, field rows) is the look lane's; this is
   the functional structure. Token-only — no magic px past the shared shape, no hex. */
.project-detail { display: grid; gap: var(--sp5); align-items: start; }
.project-detail:has(.project-preview) { grid-template-columns: minmax(0, 1fr) minmax(0, 22rem); }
.project-preview {
  position: sticky;
  top: var(--sp4);
  display: flex;
  flex-direction: column;
  gap: var(--sp2);
  padding: var(--sp4);
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  overflow: hidden;            /* clip the hero band's color to the rounded frame */
}
.project-preview.empty { color: var(--ink-faint); }
/* the project's identity HERO band — full-bleed to the pane edge, painted from the SAME
   --card-accent the grid tile + list rail read (a project has no poster, so its color IS
   the hero). A colorless project (.no-accent) keeps the quiet --wash-soft face — never a
   faked hue (the rail's honest voice, identical to the grid hero one arm over). */
.project-preview-hero {
  block-size: 4rem;
  margin: calc(-1 * var(--sp4)) calc(-1 * var(--sp4)) 0;
  background: var(--card-accent, var(--wash-soft));
  position: relative;          /* anchor the absolute .project-tile-poster the hero now carries */
  overflow: hidden;            /* clip the poster's scaled blur bleed to the hero band */
}
.project-preview.no-accent .project-preview-hero { background: color-mix(in oklab, var(--project-slate) 55%, var(--bg)); }
/* Selected projects carry the same quiet wash in List/Grid and in the dormant Detail
   master. The project's accent rail remains the identity signal; selection only changes
   the field behind it, so a multi-range reads clearly without becoming a neon block. */
.project-item.selected,
.project-tile.selected { background: var(--wash); }
@media (max-width: 48rem) {
  .project-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .project-grid .project-name { font-size: var(--fs-base); }
  .folder-grid { grid-template-columns: 1fr; }
  .folder-open { min-height: 52px; }
  .folder-actions { inline-size: 44px; block-size: 44px; }
  .browse-folder-new { min-height: 44px; }
  /* A phone is not a narrow desktop table. Collapse the two home tables into compact
     two-line records: the identity gets the full first read, useful recency survives,
     and low-priority creation metadata yields. The persistent action remains in the
     trailing thumb target instead of wrapping onto its own line. */
  .project-list .browse-colhead,
  .recent-list .browse-colhead { display: none; }

  .project-list .project-link {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "name updated"
      "workspace count";
    row-gap: var(--sp1);
    padding-right: calc(44px + var(--sp2));
  }
  .project-list .project-identity { grid-area: name; }
  .project-list .project-workspace {
    grid-area: workspace;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--f-ui);
    font-size: var(--fs-note);
    color: var(--ink-dim);
  }
  .project-list .project-count { grid-area: count; }
  .project-list .project-created { display: none; }
  .project-list .project-updated { grid-area: updated; }
  .project-list .project-updated::before { content: "Updated "; color: var(--ink-faint); }

  .recent-list .recent-link {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "name name"
      "duration uploaded";
    row-gap: var(--sp1);
    padding-right: calc(44px + var(--sp2));
  }
  .recent-list .recent-identity {
    grid-area: name;
    grid-template-columns: 4rem minmax(0, 1fr);
  }
  .recent-list .recent-identity .card-thumb { inline-size: 4rem; }
  .recent-list .recent-dur { grid-area: duration; }
  .recent-list .recent-uploaded { grid-area: uploaded; }
  .recent-list .recent-uploaded::before { content: "Uploaded "; color: var(--ink-faint); }

  .row-actions { min-width: 44px; min-height: 44px; }

  /* narrow: stack the pane under the master list, drop the sticky follow (the
     .browse-detail precedent) */
  .project-detail:has(.project-preview) { grid-template-columns: 1fr; }
  .project-preview { position: static; }
}

@media (max-width: 24rem) {
  .project-grid { grid-template-columns: 1fr; }
}

/* §6 partial-failure notice (populated dimension): one home section loaded but
   the other's read broke. A quiet rust-register inline alert — failure speaks
   --rust across this app (no dedicated --error token yet; flagged for the
   look-lane). The loaded content stays put; this just keeps a swallowed read
   failure from reading as a clean, complete home. Token-only, static. */
.home-partial-notice {
  margin: var(--sp2) 0 0;
  padding: 6px 8px;
  border-radius: 4px;
  background: var(--rust-wash);
  font-family: var(--f-ui);
  font-size: var(--fs-note);
  color: var(--ink-dim);
}

/* the home's LOADING state (browse.ts sets aria-busy on the box, "Loading your
   home…") earns a quiet working cue: the title leads with a small spun glyph in
   the semantic --working amber. Reduced-motion swaps it for a static --working
   dot — the same a11y floor the card spinner uses (no @media fork; the
   data-motion attribute tier owns it). */
.browse-state[aria-busy="true"] .state-title::before {
  content: "";
  display: inline-block;
  width: 0.7em;
  height: 0.7em;
  margin-right: var(--sp2);
  border-radius: 50%;
  border: 2px solid var(--working-soft);
  border-top-color: var(--working);
  animation: card-spin 0.8s linear infinite;
}
:root[data-motion="reduced"] .browse-state[aria-busy="true"] .state-title::before { animation: none; border-color: var(--working); }
/* Thin imperative adapter styling retained for the public collection viewer.
   The authenticated /a route owns an equivalent colocated CSS Module. */
.asset-viewer-stage { min-width: 0; min-height: 0; display: grid; place-items: center; padding: clamp(var(--sp4), 4vw, var(--sp8)); overflow: hidden; background: var(--bg-deep); }
.asset-viewer-text { width: min(100%, 980px); max-height: 100%; box-sizing: border-box; margin: 0; padding: clamp(var(--sp4), 4vw, var(--sp7)); overflow: auto; border: 1px solid var(--line); border-radius: 7px; background: var(--bg); color: var(--ink); font: 400 0.88rem/1.65 var(--f-mono); overflow-wrap: anywhere; tab-size: 2; white-space: pre-wrap; }
.asset-viewer-text-note { width: min(100%, 980px); box-sizing: border-box; margin: 0 0 var(--sp2); color: var(--ink-dim); font: 500 0.78rem/1.4 var(--f-ui); }
.asset-viewer-archive { width: min(100%, 920px); max-height: 100%; min-height: 0; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden; border: 1px solid var(--line); border-radius: 7px; background: var(--bg); box-shadow: 0 18px 56px rgb(0 0 0 / 0.22); }
.asset-viewer-archive-head { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: var(--sp4); padding: var(--sp4); border-bottom: 1px solid var(--line); }
.asset-viewer-archive-head > div { min-width: 0; display: flex; align-items: center; gap: var(--sp3); }
.asset-viewer-archive-head .asset-viewer-filemark { width: 48px; flex: 0 0 48px; }
.asset-viewer-archive-head h2 { overflow: hidden; margin: 0; color: var(--ink); font: 600 0.95rem/1.25 var(--f-ui); text-overflow: ellipsis; white-space: nowrap; }
.asset-viewer-archive-head p { margin: var(--sp1) 0 0; color: var(--ink-faint); font: 400 0.75rem/1.25 var(--f-ui); }
.asset-viewer-archive-badge { flex: 0 0 auto; padding: var(--sp1) var(--sp2); border: 1px solid var(--line); border-radius: 999px; color: var(--ink-dim); font: 600 0.68rem/1.3 var(--f-ui); }
.asset-viewer-archive-list { min-height: 0; margin: 0; padding: 0; overflow-y: auto; overscroll-behavior: contain; list-style: none; }
.asset-viewer-archive-list li { min-width: 0; display: grid; grid-template-columns: 54px minmax(0, 1fr) auto; align-items: center; gap: var(--sp3); min-height: 48px; padding: var(--sp2) var(--sp4); border-bottom: 1px solid var(--line); }
.asset-viewer-archive-list li:last-child { border-bottom: 0; }
.asset-viewer-archive-kind { color: var(--ink-faint); font: 600 0.64rem/1 var(--f-ui); letter-spacing: var(--track-label); text-transform: uppercase; }
.asset-viewer-archive-path { min-width: 0; overflow: hidden; color: var(--ink-dim); font: 450 0.82rem/1.3 var(--f-ui); text-overflow: ellipsis; white-space: nowrap; }
.asset-viewer-archive-size { color: var(--ink-faint); font: 400 0.75rem/1 var(--f-mono); white-space: nowrap; }
.asset-viewer-archive-empty { display: block !important; padding-block: var(--sp6) !important; color: var(--ink-faint); font: 400 0.82rem/1.4 var(--f-ui); text-align: center; }
.asset-viewer-archive-note { margin: 0; padding: var(--sp3) var(--sp4); border-top: 1px solid var(--line); color: var(--ink-faint); font: 400 0.72rem/1.35 var(--f-ui); }
.asset-viewer-image { display: block; max-width: 100%; max-height: 100%; object-fit: contain; box-shadow: 0 18px 56px rgb(0 0 0 / 0.32); }
.asset-viewer-pdf { width: min(100%, 1180px); height: 100%; min-height: 360px; border: 1px solid var(--line); background: var(--surface); }
.asset-viewer-audio, .asset-viewer-empty { width: min(100%, 560px); display: grid; justify-items: center; gap: var(--sp4); text-align: center; }
.asset-viewer-audio audio { width: min(100%, 480px); }
.asset-viewer-filemark { width: 84px; aspect-ratio: 1; display: grid; place-items: center; border: 1px solid var(--line); border-radius: 7px; background: var(--lift-1); color: var(--ink-dim); font: 600 0.9rem/1 var(--f-tc); letter-spacing: var(--track-label); }
.asset-viewer-empty h2 { margin: 0; font: 500 1.25rem/1.25 var(--f-ui); }
.asset-viewer-empty p { max-width: 38ch; margin: calc(-1 * var(--sp2)) 0 0; color: var(--ink-dim); }
.asset-viewer-loading-line { color: var(--ink-dim); font: 500 0.9rem/1.4 var(--f-ui); }
@media (max-width: 640px) {
  .asset-viewer-stage { min-height: min(58dvh, 540px); padding: var(--sp3); }
  .asset-viewer-archive-head { align-items: flex-start; }
  .asset-viewer-archive-list li { grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp2); min-height: 52px; padding-inline: var(--sp3); }
  .asset-viewer-archive-kind { display: none; }
  .asset-viewer-archive-path { white-space: normal; overflow-wrap: anywhere; }
}
/* One focus treatment throughout the app. Important is intentional here:
   legacy surface rules must not add a second outline or shadow. Selection,
   playback, errors, and hover retain their own independent styling. */
:root { --focus-ring: var(--ink); }
:focus-visible {
  outline: 2px solid var(--focus-ring) !important;
  outline-offset: 2px !important;
}
/* Ordinary fields use their existing boundary instead of an outside rectangle. */
:is(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]), textarea, select):focus-visible {
  outline-offset: -2px !important;
  box-shadow: none !important;
}
/* Composite fields opt into one enclosing cue. Only the designated input
   transfers its cue; Tab onto an action button still identifies that button. */
[data-focus-field]:has([data-focus-input]:focus-visible) {
  outline: 2px solid var(--focus-ring) !important;
  outline-offset: -2px !important;
}
[data-focus-field] [data-focus-input]:focus-visible {
  outline: none !important;
  box-shadow: none !important;
}
/* Canvas pointer focus is retained for arrow navigation, not drawn as selection. */
#timeline.pointer-focus:focus-visible { outline: none !important; }
@media (forced-colors: active) {
  :root { --focus-ring: Highlight; }
}
