/* ============================================================
   vibel — component layer
   ------------------------------------------------------------
   Built on the vibel design system. Every value here is a token
   from assets/css/tokens/*.css (copied verbatim from the DS);
   nothing is a raw hex, raw px, or a font the DS does not ship.

   Component skins are direct ports of the DS React components:
     .btn         ← Button        (primary / secondary / ghost)
     .iconbtn     ← IconButton
     .card        ← Card, .card--inset ← Card inset
     .badge       ← Badge
     .tag         ← Tag
     .tabs        ← Tabs
     .topbar      ← PlayerBar     (identity left, playback right)
     .track       ← TrackTile     (stations)
     .slider      ← Slider        (volume)
   Same tokens, same box-shadow stacks, same press behaviour.

   The signature interaction is the press: translateY(--press-shift)
   + scale(--press-scale) + inset shadow, on --ease-snap at --dur-fast.
   Every interactive surface here does it.
   ============================================================ */

@import url("tokens.css?v=4");

/* Keep native browser zoom available alongside reader preferences. */
html {
  touch-action: manipulation;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* ---------- 0. app-level tokens ----------
   Layout measures the DS does not define. Kept as multiples of the
   spacing scale so nothing is an arbitrary number. */
:root {
  /* The rail was a flat 280px at every width, and roughly 40% of that is
     ladder indent and the number gutter — so a note title got 168px and
     wrapped to two or three lines even on a 1920 screen with room to spare.
     It now grows with the viewport and stops before it starts eating the
     reading column. */
  --rail-w: clamp(23rem, 26vw, 30rem);                   /* 368 → 480px */
  --dock-h: calc(var(--space-8) + var(--space-3));        /* 76px, legacy */
  --bar-safe-top: env(safe-area-inset-top, 0px);
  --bar-content-h: var(--space-8);                        /* 64px phone */
  --bar-h: calc(var(--bar-content-h) + var(--bar-safe-top));
  --bar-logo-h: var(--space-6);                           /* 32px phone */
  --measure: 70ch;
  /* Two measures, not one. --measure-prose is the READING line length and is
     the only thing that must stay short: 85ch is the top of the comfortable
     range, and paragraphs are pinned to it however wide the window gets.
     --measure-wide bounds the card around it, so tables, figures and display
     maths — which were pinned at 100% of a 723px column and squeezed tall
     because of it — can use the space prose must not. */
  --measure-prose: 65ch;
  --measure-wide: min(124ch, 100%);
  --hair: 1px;
  --hair-2: 2px;
  --hair-3: 3px;
  /* sizes the DS components use internally but did not name */
  --text-2xs: 11px;      /* Badge label */
  --text-tag: 13px;      /* Tag label */
  --tile-art: var(--control-h-md);
  --eq-h: 14px;          /* TrackTile equaliser height */
  --embed-w: 340px;      /* the offscreen SoundCloud widget */
  --embed-h: 190px;
  /* the DS TrackTile "playing" fill, verbatim, given a name */
  --surface-playing: linear-gradient(180deg, #FFF3EC, #FFE8DC);
  --tile-shadow: inset 0 var(--hair) 0 rgba(255,255,255,.4), 0 var(--hair) var(--hair-3) rgba(41,31,21,.25);
  /* the colours vibel.css owns (the palette itself lives in tokens/):
     glass, sheen, scrim, hairlines — everything the theme has to flip */
  --under-hair: 0 var(--hair) 0 rgba(41,31,21,.15);
  --rim-faint: inset 0 var(--hair) 0 rgba(255,255,255,.4);
  --bar-glass: linear-gradient(180deg, rgba(255,255,255,.62) 0%, rgba(255,255,255,.46) 55%, rgba(244,240,234,.38) 100%);
  --bar-glass-lift: linear-gradient(180deg, rgba(255,255,255,.92) 0%, rgba(255,255,255,.84) 55%, rgba(244,240,234,.78) 100%);
  --bar-hairline: rgba(0,0,0,.10);
  --bar-rims: inset 0 var(--hair) 0 rgba(255,255,255,.70), inset 0 calc(var(--hair) * -1) 0 rgba(41,31,21,.04);
  --bar-rims-lift: inset 0 var(--hair) 0 rgba(255,255,255,.85), inset 0 calc(var(--hair) * -1) 0 rgba(41,31,21,.05), 0 var(--space-1) var(--space-5) calc(var(--space-4) * -1) rgba(41,31,21,.45);
  --sheen-wash: radial-gradient(120% 180% at 12% -40%, rgba(255,255,255,.55), rgba(255,255,255,0) 60%), radial-gradient(90% 140% at 88% -30%, rgba(255,122,69,.10), rgba(255,122,69,0) 55%);
  --scrim-bg: rgba(41,31,21,.4);
  --press-tint: rgba(41,31,21,.06);
  --figure-plate: var(--white);
  /* The image viewer is deliberately dark in both themes: it is a neutral
     inspection surface, not another reading card. These tokens keep every
     translucent layer and control edge named and theme-independent. */
  --viewer-backdrop: rgba(0,0,0,.96);
  --viewer-chrome: rgba(18,13,10,.86);
  --viewer-chrome-hover: rgba(42,30,22,.94);
  --viewer-border: rgba(255,255,255,.22);
  --bp-tablet: 940px;
  --bp-phone: 640px;
}

/* The player bar grows with the canvas instead of staying phone-sized on a
   wide display. A short laptop gets the compact override at the end, so width
   alone can never consume too much of the reading viewport. */
@media (min-width: 37.5rem) {
  :root {
    --bar-content-h: calc(var(--space-8) + var(--space-1)); /* 68px */
    --bar-logo-h: calc(var(--space-6) + var(--hair-2));     /* 34px */
  }
}
@media (min-width: 941px) {
  :root {
    --bar-content-h: calc(var(--space-8) + var(--space-2)); /* 72px */
    --bar-logo-h: calc(var(--space-6) + var(--space-1));    /* 36px */
  }
}
@media (min-width: 90rem) {
  :root {
    --bar-content-h: calc(var(--space-8) + var(--space-3)); /* 76px */
    --bar-logo-h: calc(var(--space-6) + var(--space-1) + var(--hair-2)); /* 38px */
  }
}
@media (min-width: 120rem) {
  :root {
    --bar-content-h: calc(var(--space-8) + var(--space-4)); /* 80px */
    --bar-logo-h: calc(var(--space-6) + var(--space-2));    /* 40px */
  }
}
@media (min-width: 941px) and (max-height: 43.75rem) {
  :root {
    --bar-content-h: calc(var(--space-8) + var(--space-1)); /* 68px */
    --bar-logo-h: calc(var(--space-6) + var(--hair-2));     /* 34px */
  }
}

/* Dark: glass over a dark room is mostly tint, so the stops keep their
   alphas but trade cream for umber; the specular rims fall to a tenth
   (bright chrome on dark glass reads as a scratch, not a highlight); the
   playing tile glows ember instead of blush; and figures keep a paper
   plate — a printed diagram, photographed in a dark album. */
:root[data-theme="dark"] {
  --surface-playing: linear-gradient(180deg, #33231A, #2A1D15);
  --tile-shadow: inset 0 var(--hair) 0 rgba(255,255,255,.07), 0 var(--hair) var(--hair-3) rgba(0,0,0,.5);
  --under-hair: 0 var(--hair) 0 rgba(0,0,0,.5);
  --rim-faint: inset 0 var(--hair) 0 rgba(255,255,255,.07);
  --bar-glass: linear-gradient(180deg, rgba(0,0,0,.62) 0%, rgba(0,0,0,.50) 55%, rgba(0,0,0,.42) 100%);
  --bar-glass-lift: linear-gradient(180deg, rgba(0,0,0,.92) 0%, rgba(0,0,0,.86) 55%, rgba(0,0,0,.80) 100%);
  --bar-hairline: rgba(255,255,255,.12);
  --bar-rims: inset 0 var(--hair) 0 rgba(255,255,255,.10), inset 0 calc(var(--hair) * -1) 0 rgba(0,0,0,.35);
  --bar-rims-lift: inset 0 var(--hair) 0 rgba(255,255,255,.14), inset 0 calc(var(--hair) * -1) 0 rgba(0,0,0,.4), 0 var(--space-1) var(--space-5) calc(var(--space-4) * -1) rgba(0,0,0,.7);
  --sheen-wash: radial-gradient(120% 180% at 12% -40%, rgba(255,255,255,.10), rgba(255,255,255,0) 60%), radial-gradient(90% 140% at 88% -30%, rgba(255,122,69,.14), rgba(255,122,69,0) 55%);
  --scrim-bg: rgba(0,0,0,.72);
  --press-tint: rgba(255,255,255,.07);
  --figure-plate: #F2EADD;
}

/* ---------- 1. reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
/* base.css sets body font/colour/background; we add layout hygiene */
body { overflow-x: clip; }
button, input, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; padding: 0; }
/* Kill the browser's own tap-highlight flash: it ignores border-radius and
   paints a square-cornered box over rounded buttons/pills on any touch
   input, in a colour this theme never chose. The real, accessible focus
   ring is the rule directly below — this only removes the redundant,
   unthemed one that WebKit/Blink add on their own. */
a, button, [role="button"], [tabindex] { -webkit-tap-highlight-color: transparent; }
:focus-visible { outline: var(--hair-2) solid var(--text-accent); outline-offset: var(--hair-2); border-radius: var(--radius-sm); }

/* ---------- 2. typographic apparatus ---------- */

/* mono eyebrow — the only place ALL CAPS is allowed */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.eyebrow--accent { color: var(--text-accent); }

/* display heading — Bricolage at display weight, tight tracking */
.display {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-display);
  line-height: var(--leading-tight);
  color: var(--text-heading);
  margin: 0;
}

/* ---------- 3. DS component ports ---------- */

/* Button */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  height: var(--control-h-md); padding: 0 var(--space-5);
  font-family: var(--font-body); font-weight: 600; font-size: var(--text-md);
  border: 0; border-radius: var(--radius-md); cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-snap), box-shadow var(--dur-fast) var(--ease-snap), filter var(--dur-fast) var(--ease-snap);
}
.btn--sm { height: var(--control-h-sm); padding: 0 var(--space-4); font-size: var(--text-sm); }
.btn--lg { height: var(--control-h-lg); padding: 0 calc(var(--space-5) + var(--space-1)); font-size: var(--text-lg); }
.btn--primary {
  background: var(--gloss-cap), linear-gradient(180deg, var(--tape-orange-hi), var(--tape-orange));
  color: var(--text-on-accent); box-shadow: var(--shadow-accent-raise);
}
.btn--secondary {
  background: var(--surface-raised); color: var(--ink-1);
  box-shadow: var(--shadow-raise-sm); border: var(--hair) solid var(--border-soft);
}
.btn--ghost { background: transparent; color: var(--ink-2); box-shadow: none; }
@media (hover:hover) and (pointer:fine) { .btn:hover { filter: brightness(1.03); } }
.btn:active, .btn[aria-pressed="true"] { transform: translateY(var(--press-shift)) scale(var(--press-scale)); }
.btn--primary:active { background: var(--tape-orange-lo); box-shadow: var(--shadow-accent-pressed); }
.btn--secondary:active { background: var(--paper-3); box-shadow: var(--shadow-pressed); }
.btn--ghost:active { background: var(--paper-3); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* IconButton */
.iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--control-h-md); height: var(--control-h-md); flex: none;
  border: 0; border-radius: var(--radius-md); cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-snap), box-shadow var(--dur-fast) var(--ease-snap), filter var(--dur-fast) var(--ease-snap);
}
.iconbtn--sm { width: var(--control-h-sm); height: var(--control-h-sm); }
.iconbtn--lg { width: var(--control-h-lg); height: var(--control-h-lg); }
.iconbtn--secondary { background: var(--surface-raised); color: var(--ink-2); box-shadow: var(--shadow-raise-sm); border: var(--hair) solid var(--border-soft); }
.iconbtn--ghost { background: transparent; color: var(--ink-2); }
@media (hover:hover) and (pointer:fine) { .iconbtn:hover { filter: brightness(1.03); } }
.iconbtn:active { transform: translateY(var(--press-shift)) scale(0.96); }
.iconbtn--secondary:active { background: var(--paper-3); box-shadow: var(--shadow-pressed); }
.iconbtn--ghost:active { background: var(--paper-3); }
.iconbtn svg { width: var(--space-4); height: var(--space-4); }

/* Card */
/* Reading canvases are flat: the same black (or white) as the floor, framed
   by a hairline, no shadow. On a black floor a bevelled black card was
   invisible, and a cast shadow has nowhere darker to go — so the frame does
   all the work, and the hardware (buttons, knobs, the bar) is the only
   thing left on the page that is still allowed to look lit. */
.card {
  background: var(--surface-card); border-radius: var(--radius-lg); padding: var(--space-5);
  border: var(--hair) solid var(--border-strong); box-shadow: none;
}
.card--inset {
  background: var(--surface-inset); border-color: var(--border-strong); box-shadow: none;
}

/* Badge */
.badge {
  display: inline-flex; align-items: center; height: calc(var(--space-5) - var(--hair-2)); padding: 0 calc(var(--space-2) + var(--hair));
  border-radius: var(--radius-pill); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--weight-bold); letter-spacing: 0.04em;
  box-shadow: var(--bevel-hi-soft), var(--under-hair);
  background: var(--paper-3); color: var(--ink-2);
}
.badge--orange { background: var(--tape-orange); color: var(--text-on-accent); }
.badge--moss { background: var(--moss); color: var(--text-on-deep); }
.badge--haze { background: var(--haze); color: var(--text-on-deep); }
/* --butter is the declared --warning colour. Reading progress is not a
   warning, so the two badges are now one treatment and progress leads with a
   filled dot rather than with alarm. */
.badge--lead { font-weight: var(--weight-bold); color: var(--ink-1); }
.badge--lead::before {
  content: ""; width: var(--space-2); height: var(--space-2); border-radius: 50%;
  background: var(--text-accent); flex: none;
}
.badge--lead[data-complete="true"]::before { background: var(--moss); }

/* Tag */
.tag {
  display: inline-flex; align-items: center; height: var(--space-6); padding: 0 calc(var(--space-3) + var(--hair-2));
  border-radius: var(--radius-pill); border: var(--hair) solid var(--border-soft);
  background: var(--surface-raised); color: var(--ink-2);
  font-family: var(--font-body); font-weight: 600; font-size: var(--text-tag); cursor: pointer;
  box-shadow: var(--shadow-raise-sm);
  transition: all var(--dur-fast) var(--ease-snap);
}
.tag:active { transform: translateY(var(--press-shift)); background: var(--paper-3); box-shadow: var(--shadow-pressed); }
.tag[aria-pressed="true"], .tag--selected {
  border-color: var(--tape-orange-lo);
  background: var(--gloss-cap), linear-gradient(180deg, var(--tape-orange-hi), var(--tape-orange));
  color: var(--text-on-accent); box-shadow: var(--shadow-accent-raise);
}

/* Tabs */
.tabs {
  display: inline-flex; gap: var(--space-1); padding: var(--space-1);
  background: var(--surface-inset); border: var(--hair) solid var(--border-strong);
  border-radius: var(--radius-md); box-shadow: var(--shadow-inset-well);
}
.tabs__tab {
  height: calc(var(--space-6) + var(--space-1)); padding: 0 var(--space-4);
  border: 0; border-radius: calc(var(--radius-md) - var(--space-1)); cursor: pointer;
  font-family: var(--font-body); font-size: var(--text-sm); font-weight: 600;
  background: transparent; color: var(--ink-3);
  transition: all var(--dur-fast) var(--ease-snap);
}
.tabs__tab[aria-selected="true"] {
  background: var(--gloss-cap), var(--surface-raised); color: var(--ink-1); box-shadow: var(--shadow-raise-sm);
}

/* Slider */
.slider { position: relative; height: var(--control-h-md); display: flex; align-items: center; width: 100%; }
.slider__track {
  position: absolute; left: 0; right: 0; height: calc(var(--space-2) + var(--hair-2)); border-radius: var(--radius-pill);
  background: var(--surface-inset); border: var(--hair) solid var(--border-strong); box-shadow: var(--shadow-inset-well);
}
.slider__fill {
  position: absolute; left: 0; height: calc(var(--space-2) + var(--hair-2)); border-radius: var(--radius-pill);
  background: var(--gloss-cap), linear-gradient(180deg, var(--tape-orange-hi), var(--tape-orange));
  box-shadow: var(--rim-faint);
  pointer-events: none;
}
.slider__knob {
  position: absolute; width: var(--space-5); height: var(--space-5); border-radius: 50%;
  background: var(--metal-knob); border: var(--hair) solid var(--border-soft); box-shadow: var(--shadow-raise-sm);
  transform: translateX(-50%); pointer-events: none;
  transition: box-shadow var(--dur-fast) var(--ease-snap);
}
.slider input[type="range"] { position: absolute; inset: 0; width: 100%; opacity: 0; cursor: pointer; margin: 0; }
.slider input:active + .slider__knob { box-shadow: var(--shadow-pressed); }

/* ---------- 4. shell ---------- */

.shell {
  display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr);
  min-height: 100vh; padding-top: var(--bar-h); padding-bottom: var(--space-5);
}
/* Collapsed on desktop: the rail's whole track goes back to the reading
   column. Scoped to the wide breakpoint deliberately — below 940px the rail is
   an off-canvas drawer with its own open state, and an unscoped display:none
   here would follow a desktop collapse down to a phone and leave the drawer
   impossible to open. The attribute is desktop-only in JS as well. */
@media (min-width: 941px) {
  .shell[data-rail="hidden"] { grid-template-columns: minmax(0, 1fr); }
  .shell[data-rail="hidden"] > .rail { display: none; }
}

/* ---------- 5. rail ---------- */
/* Deliberately a raised card standing on the paper, not a full-height slab.
   That is what makes it read as hardware on a desk. */

.rail {
  position: sticky; top: var(--bar-h); align-self: start; height: calc(100vh - var(--bar-h));
  display: flex; flex-direction: column;
  /* Symmetric inset. The right side used to be 0, which put the card's edge
     hard against the reading column and left rail text 9px from it. */
  padding: var(--space-4) var(--space-4) var(--space-5) var(--space-4);
}
.rail__card {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
  padding: 0; overflow: hidden;
}
/* identity lives in the top bar; the rail is purely the tree */

.wordmark {
  display: inline-flex; align-items: center; flex: none;
  line-height: 0; text-decoration: none;
}
.wordmark__logo {
  display: block; width: auto;
  height: var(--bar-logo-h);
}
.wordmark__logo--reversed { display: none; }
:root[data-theme="dark"] .wordmark__logo--primary { display: none; }
:root[data-theme="dark"] .wordmark__logo--reversed { display: block; }
.rail__tagline { margin: var(--space-2) 0 0; }

.rail__scroll { flex: 1; overflow-y: auto; padding: var(--space-3) 0 var(--space-4); scrollbar-width: thin; }
.rail__group + .rail__group { margin-top: var(--space-4); }
.rail__grouphead { padding: var(--space-2) var(--space-5) var(--space-2); }

/* One row, one line. Number in a fixed gutter, title in the middle, duration
   trailing — three stacked lines cost 73px each and put four notes on a
   phone screen. Every row shares this inset so the column has one left edge. */
.entry {
  display: grid;
  grid-template-columns: var(--tree-num-w) minmax(0, 1fr);
  gap: 0 var(--space-2); align-items: baseline;
  width: 100%; margin: 0;
  padding: var(--space-3) var(--space-4) var(--space-3) 0;
  border-radius: var(--radius-sm); border: var(--hair) solid transparent;
  color: var(--ink-2); text-decoration: none;
  transition: background var(--dur-fast) var(--ease-snap), color var(--dur-fast) var(--ease-snap);
}
@media (hover:hover) and (pointer:fine) { .entry:hover { background: var(--paper-2); color: var(--ink-1); text-decoration: none; } }
.entry__num {
  font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.entry__title {
  font-size: var(--text-md); font-weight: 600; line-height: var(--leading-snug); color: var(--ink-1);
  text-wrap: pretty;
}
/* Current note: a flat tint and an accent number. There used to be an orange
   bar in the rule column as well, on the same row as the tint — two marks for
   one state, and the tint alone already reads. (Before that it was a raised
   card inset 28px further than every other row, which broke the left edge and
   stranded its own chevron outside it.) */
.entry[aria-current="page"] { background: var(--paper-2); color: var(--ink-1); }
.entry[aria-current="page"] .entry__num { color: var(--text-accent); font-weight: var(--weight-bold); }
.entry[data-read="true"] .entry__num::after { content: "\00b7"; color: var(--moss); margin-left: var(--hair-2); }

/* ---------- 5b. rail tree ----------
   Section, optional phase, note, and module share one compact ladder. Text
   starts at 28 (section), 62 (note title)
   and 62 (module, aligned under the title it belongs to) — never stepping
   back. Before this, modules started 24px LEFT of their own note title, so
   the deepest level read as the shallowest.

   Depth past that is carried by the guide rule, not by more indent: at rail
   width every extra step of indent costs a wrapped line. */

:root {
  /* where the note row puts its own parts, so the module list can align to
     the title rather than to a guessed constant */
  /* The number gutter, named once. A two-digit mono numeral at 11px is about
     13px wide, so 24 was eight pixels of nothing on every row — and it was
     spelled twice, here and in .entry's grid, where the two could drift and
     take the module links out of alignment under their titles. */
  --tree-num-w: var(--space-4);                                            /* 16 */
  --tree-entry-x: calc(var(--space-5) + var(--space-4) + var(--space-2));  /* 48 — entry left edge */
  --tree-title-x: calc(var(--tree-num-w) + var(--space-2));                /* +24 — past the number */
}

.tree__domain + .tree__domain {
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: var(--hair) solid var(--border-soft);
}
.tree__domainhead {
  margin: 0 0 var(--space-2);
  padding: 0 var(--space-3);
  color: var(--text-accent);
  font-size: var(--text-xs);
  letter-spacing: .16em;
}
.tree__sec + .tree__sec { margin-top: var(--space-5); }
/* An optional phase sits between a section and its notes without adding
   another rail indent; the existing section and note ladder stays intact. */
.tree__phase + .tree__phase {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: var(--hair) solid var(--border-soft);
}
.tree__phasehead {
  margin: 0 0 var(--space-3);
  padding: 0 var(--space-3);
  color: var(--text-accent);
  font-size: var(--text-2xs);
  letter-spacing: .14em;
}
/* the same height transition the note branches use, so one tree does not
   speak two motion languages */
.tree__kids {
  display: grid; grid-template-rows: 1fr;
  transition: grid-template-rows var(--dur-med) var(--ease-smooth);
}
.tree__kids[data-open="false"] { grid-template-rows: 0fr; }
.tree__kidsin { overflow: hidden; min-height: 0; }
.tnode { display: flex; align-items: center; gap: var(--space-2); }

/* L1 — a quiet label that presides over its group, not a peer of the notes */
.tnode--sec {
  width: 100%; margin: 0;
  min-height: var(--control-h-md);
  padding: var(--space-2) var(--space-4) var(--space-2) var(--space-3);
  background: none; border: 0; border-radius: var(--radius-sm);
  color: inherit; text-align: left; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-snap);
}
@media (hover:hover) and (pointer:fine) { .tnode--sec:hover { background: var(--paper-2); } }
.tnode--sec .eyebrow { font-size: var(--text-2xs); letter-spacing: .14em; color: var(--ink-3); }

/* twist: one chevron, rotated by state */
.twist {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--space-4); height: var(--space-4); flex: none; padding: 0;
  background: none; border: 0; color: var(--ink-4); cursor: pointer;
}
.twist__ico {
  width: var(--space-3); height: var(--space-3);
  fill: none; stroke: currentColor; stroke-width: 2.5;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform var(--dur-med) var(--ease-snap);
}
[aria-expanded="true"] > .twist__ico,
[aria-expanded="true"] .twist > .twist__ico { transform: rotate(90deg); }
@media (hover:hover) and (pointer:fine) { .twist--btn:hover { color: var(--text-accent); } }
.twist--btn:focus-visible {
  outline: var(--hair-2) solid var(--text-accent); outline-offset: var(--hair);
  border-radius: var(--radius-sm);
}
/* glyph stays 12px; the hit box is a centred 44x44 pseudo-element */
.twist--btn { position: relative; }
.twist--btn::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: var(--control-h-md); height: var(--control-h-md);
  transform: translate(-50%, -50%);
}

/* L2 — the loudest thing in the tree, because it is what you tap */
.tnode--note { align-items: flex-start; padding-left: var(--space-5); gap: 0; }
.tnode--note .twist--btn { margin-top: var(--space-3); margin-right: var(--space-2); }
.tnode--note .entry { flex: 1; min-width: 0; }

/* L3 — legible but receding, aligned under the title that owns it */
/* Branches open by animating grid-template-rows 0fr → 1fr, which is the one
   way to transition to a height the content decides. visibility flips at the
   end of the close so the links leave the tab order without cutting the
   animation short. */
.tree__mods {
  display: grid; grid-template-rows: 0fr;
  margin-left: var(--tree-entry-x);
  visibility: hidden; opacity: 0;
  transition:
    grid-template-rows var(--dur-med) var(--ease-smooth),
    opacity var(--dur-fast) var(--ease-smooth),
    visibility 0s linear var(--dur-med);
}
.tree__mods[data-open="true"] {
  grid-template-rows: 1fr; visibility: visible; opacity: 1;
  transition:
    grid-template-rows var(--dur-med) var(--ease-smooth),
    opacity var(--dur-med) var(--ease-smooth),
    visibility 0s;
}
.tree__modsin {
  overflow: hidden; min-height: 0;
  padding-left: var(--tree-title-x);
  border-left: var(--hair) solid var(--border-soft);
  transition: border-color var(--dur-med) var(--ease-smooth);
}
.tree__note[data-current="true"] .tree__modsin { border-left-color: var(--tape-orange); }

.mod {
  display: flex; align-items: baseline; gap: var(--space-2);
  min-height: var(--control-h-md);
  padding: var(--space-2) var(--space-4) var(--space-2) 0;
  border-radius: var(--radius-sm);
  font-size: var(--read-sm); line-height: var(--leading-snug);
  font-weight: 400; color: var(--ink-3); text-decoration: none;
  transition: background var(--dur-fast) var(--ease-snap), color var(--dur-fast) var(--ease-snap);
}
@media (hover:hover) and (pointer:fine) { .mod:hover { background: var(--paper-2); color: var(--ink-1); text-decoration: none; } }
/* the dot marks the module you are inside — so only that one gets a dot.
   A dot on every row is a bullet, and the guide rule is already the list. */
.mod__tick {
  width: var(--space-1); height: var(--space-1); border-radius: 50%; flex: none;
  margin-left: calc((var(--space-1) + var(--space-2)) * -1);
  background: transparent;
  transition: background var(--dur-fast) var(--ease-snap), box-shadow var(--dur-fast) var(--ease-snap);
}
.mod__label { min-width: 0; }
.mod[data-current="true"] { color: var(--ink-1); font-weight: 600; }
.mod[data-current="true"] .mod__tick {
  background: var(--tape-orange); box-shadow: 0 0 var(--space-2) var(--tape-orange);
}
.mod--note { color: var(--ink-3); font-style: italic; margin: 0; min-height: 0; }
.tree__modsin > :last-child { margin-bottom: var(--space-2); }

/* the heading a module link just jumped to */
.doc__body [data-flash="true"] { animation: flash var(--dur-slow) var(--ease-smooth) 2 alternate; }
@keyframes flash { from { background: transparent; } to { background: var(--paper-2); } }
@media (prefers-reduced-motion: reduce) {
  /* the pill still gets out of the way, it just stops being a fade */
  .foldpill { transition: none; }
  .twist__ico { transition: none; }
  .navbtn__ico { transition: opacity 100ms linear; transform: none !important; }
  .rail { transition: none; }
  .doc__body [data-flash="true"] { animation: none; }
}

/* The panel arrives first and its groups settle in just behind it. The
   stagger is small on purpose — it should read as the content catching up
   with the panel, not as a sequence you have to wait through. */
@media (prefers-reduced-motion: no-preference) {
  .rail[data-open="true"] .tree__domain {
    animation: railSettle 300ms var(--ease-smooth) both;
  }
  .rail[data-open="true"] .tree__domain:nth-child(1) { animation-delay: 60ms; }
  .rail[data-open="true"] .tree__domain:nth-child(2) { animation-delay: 110ms; }
  .rail[data-open="true"] .tree__domain:nth-child(n+3) { animation-delay: 155ms; }
}
@keyframes railSettle {
  from { opacity: 0; transform: translate3d(calc(var(--space-3) * -1), 0, 0); }
  to   { opacity: 1; transform: none; }
}

/* ---------- 6. reading column ---------- */

.main { min-width: 0; }
.page { max-width: var(--measure); margin: 0 auto; padding: var(--space-7) var(--space-6) var(--space-8); }
.page--wide { max-width: var(--measure-wide); }

.runninghead {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-4);
  padding-bottom: var(--space-3); border-bottom: var(--hair) solid var(--border-soft); margin-bottom: var(--space-6);
}

.doc__title { font-size: clamp(var(--text-2xl), 4vw, var(--text-3xl)); margin: 0 0 var(--space-3); text-wrap: balance; }
.doc__standfirst { font-size: var(--read-lg); line-height: var(--read-leading-snug); color: var(--ink-2); margin: 0 0 var(--space-6); font-weight: 500; text-wrap: pretty; }

/* the note body: the reading scale lives here, not on :root, so the
   controls around it keep the control scale */
.doc__body { padding: var(--space-6); font-size: var(--read-body); line-height: var(--read-leading); color: var(--ink-2); }
/* The card is now much wider than a line of text should ever be, so the
   reading measure moves off the container and onto the prose itself. Anything
   NOT listed here — .tablewrap, .figure, .tex-block, .toc — deliberately keeps
   the full card width, which is the whole point: those were pinned at 100% of
   a 723px column and squeezed tall for it. Descendant selectors, because the
   content nests as details.sec > .sec__body > p. */
.doc__body p,
.doc__body ul,
.doc__body ol,
.doc__body h2,
.doc__body h3,
.doc__body h4,
.doc__body blockquote,
.doc__body .aside,
.doc__body .figref,
.doc__body .details__body { max-width: var(--measure-prose); }
.doc__standfirst { max-width: var(--measure-prose); }
.doc__body h2 {
  font-size: var(--read-h2); margin: var(--space-7) 0 var(--space-3);
}
.doc__body h2:first-child { margin-top: 0; }
/* h3 used to be 12px orange mono uppercase — pixel-identical to the eyebrow
   labels that are pure chrome. Two different jobs wearing one appearance, so
   neither read as what it was. It is a heading, so it looks like one; the
   accent survives as a marker rather than as the whole treatment. */
.doc__body h3 {
  font-family: var(--font-display); font-size: var(--read-h3); font-weight: var(--weight-bold);
  letter-spacing: -0.01em; color: var(--ink-1); line-height: var(--leading-snug);
  margin: var(--space-6) 0 var(--space-3);
}
/* h4 used to be 19px — BIGGER than the 16px h3 above it. Now body size,
   bold, in the display face: the last rank of heading, not the first. */
.doc__body h4 { font-size: var(--read-h4); font-weight: var(--weight-bold); line-height: var(--leading-snug); margin: var(--space-5) 0 var(--space-2); }
.doc__body p { margin: 0 0 var(--space-4); text-wrap: pretty; }
.doc__body a { color: var(--link); font-weight: 600; }
.doc__body strong { color: var(--ink-1); font-weight: var(--weight-bold); }
.doc__body ul, .doc__body ol { margin: 0 0 var(--space-4); padding-left: 0; list-style: none; }
.doc__body li { margin-bottom: var(--space-2); }
.doc__body ul > li { position: relative; padding-left: var(--space-5); }
/* bullets are plain accent dots — the gloss cap and shadow they had read as
   a smudge on black, and a bullet's only job is to mark the line */
.doc__body ul > li::before {
  content: ""; position: absolute; left: var(--space-1); top: 0.62em;
  width: var(--space-2); height: var(--space-2); border-radius: 50%;
  background: var(--tape-orange);
}
.doc__body ol { counter-reset: li; }
.doc__body ol > li { position: relative; padding-left: var(--space-6); counter-increment: li; }
.doc__body ol > li::before {
  content: counter(li) "."; position: absolute; left: 0; top: 0;
  min-width: var(--space-5); text-align: right;
  font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--weight-bold);
  line-height: inherit; color: var(--text-accent);
}
.doc__body pre {
  margin: var(--space-5) 0; padding: var(--space-4) var(--space-5);
  background: var(--surface-inset); border: var(--hair) solid var(--border-strong); border-radius: var(--radius-md);
  box-shadow: none; overflow-x: auto;
  font-family: var(--font-mono); font-size: var(--read-sm); line-height: 1.65;
}
.codeframe { position: relative; margin: var(--space-5) 0; }
.codeframe > pre { margin: 0; padding-top: var(--space-6); }
.codeframe__lang {
  position: absolute; z-index: 1; top: var(--space-2); right: var(--space-3);
  font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--weight-bold);
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3);
}
.diagram {
  margin: var(--space-5) 0; padding: var(--space-4);
  overflow-x: auto; background: var(--paper-1);
  border: var(--hair) solid var(--border-strong); border-radius: var(--radius-md);
  box-shadow: none;
}
.diagram > pre.mermaid {
  min-width: min-content; margin: 0; padding: 0;
  background: transparent; border: 0; box-shadow: none; text-align: center;
}
.diagram > pre.mermaid--raw {
  padding: var(--space-4); text-align: left;
  background: var(--surface-inset); border: var(--hair) solid var(--border-soft);
}
.diagram svg { display: block; max-width: 100%; height: auto; margin: 0 auto; }
.doc__body :not(pre) > code {
  font-family: var(--font-mono); font-size: 0.9em; padding: 0 var(--space-1);
  background: var(--paper-2); border-radius: var(--radius-sm); color: var(--ink-1);
}

/* callouts → framed canvases with a flat colour tab. One step under body
   size, never under the reading floor: the Goal / Depth / Scope box at the
   top of every note is read as carefully as the prose. */
.aside {
  margin: var(--space-5) 0; padding: var(--space-4) var(--space-4) var(--space-4) var(--space-5);
  position: relative; border-radius: var(--radius-md);
  background: var(--surface-inset); border: var(--hair) solid var(--border-strong); box-shadow: none;
  font-size: calc(var(--read-body) - var(--hair)); line-height: var(--read-leading); color: var(--ink-2);
}
.aside::before {
  content: ""; position: absolute; left: 0; top: var(--space-4); bottom: var(--space-4);
  width: var(--hair-3); border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
  background: var(--haze);
}
.aside--warn::before { background: var(--butter); }
.aside__icon { float: left; margin: 0 var(--space-2) 0 0; line-height: 1.5; }
.aside p:last-child { margin-bottom: 0; }

/* quotes → the one sentence a module turns on, set apart rather than indented */
.doc__body blockquote.quote {
  margin: var(--space-5) 0; padding: var(--space-3) 0 var(--space-3) var(--space-5);
  border-left: var(--hair-3) solid var(--text-accent); color: var(--ink-1);
  font-size: var(--read-lg); line-height: var(--read-leading-snug); font-weight: 500; text-wrap: pretty;
}
.doc__body blockquote.quote p:last-child { margin-bottom: 0; }

/* maths */
.tex { white-space: nowrap; }
.tex--raw { white-space: normal; word-break: break-word; font-family: var(--font-mono); font-size: 1em; color: var(--text-accent); }
.tex-block {
  margin: var(--space-5) 0; padding: var(--space-4) var(--space-3);
  background: var(--white); border: var(--hair) solid var(--border-strong); border-radius: var(--radius-md);
  box-shadow: none; overflow-x: auto; overflow-y: hidden;
}
.tex-block .katex-display { margin: 0; }
.katex { font-size: 1.05em; }

/* tables → framed canvases. The header row used to be a brushed-metal
   faceplate with 12px mono caps; it is now a plain bold row at reading
   size, because column names are read, not pressed. */
.tablewrap {
  margin: var(--space-5) 0; overflow-x: auto;
  background: var(--white); border: var(--hair) solid var(--border-strong); border-radius: var(--radius-md);
  box-shadow: none;
}
.tablewrap table { width: 100%; border-collapse: collapse; font-size: var(--read-sm); line-height: 1.5; }
.tablewrap th {
  font-family: var(--font-body); font-size: var(--read-sm); font-weight: var(--weight-bold);
  color: var(--ink-1); text-align: left; padding: var(--space-3) var(--space-4);
  background: transparent; border-bottom: var(--hair) solid var(--border-strong); white-space: nowrap;
}
.tablewrap td { padding: var(--space-3) var(--space-4); border-bottom: var(--hair) solid var(--border-soft); vertical-align: top; color: var(--ink-2); }
.tablewrap tr:last-child td { border-bottom: 0; }
.tablewrap td:first-child { color: var(--ink-1); font-weight: 500; }
@media (hover:hover) and (pointer:fine) { .tablewrap tr:hover td { background: var(--paper-2); } }
.tablewrap table.cols-3 td:nth-child(1) { width: 21%; }
.tablewrap table.cols-3 td:nth-child(2) { width: 33%; }
.tablewrap table.cols-3 td:nth-child(3) { width: 46%; }
.tablewrap table.cols-4 td:nth-child(1), .tablewrap table.cols-4 td:nth-child(2) { width: 18%; }
.tablewrap table.cols-5 { min-width: 34rem; }

/* disclosures → a raised key that pops open a well */
details { margin: var(--space-5) 0; }
details > summary {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: var(--space-2);
  height: var(--control-h-sm); padding: 0 var(--space-4);
  font-family: var(--font-body); font-weight: 600; font-size: var(--read-sm); color: var(--ink-1);
  background: var(--surface-raised); border: var(--hair) solid var(--border-soft); border-radius: var(--radius-md);
  box-shadow: var(--shadow-raise-sm);
  transition: transform var(--dur-fast) var(--ease-snap), box-shadow var(--dur-fast) var(--ease-snap);
}
details > summary::-webkit-details-marker { display: none; }
details > summary::before { content: "+"; color: var(--text-accent); font-weight: var(--weight-bold); }
details[open] > summary::before { content: "–"; }
details > summary:hover { filter: brightness(1.03); }
details > summary:active, details[open] > summary { transform: translateY(var(--press-shift)); background: var(--paper-3); box-shadow: var(--shadow-pressed); }
details > .details__body {
  margin-top: var(--space-3); padding: var(--space-4) var(--space-5);
  background: var(--surface-inset); border: var(--hair) solid var(--border-strong); border-radius: var(--radius-md); box-shadow: none;
}
details > .details__body > :last-child { margin-bottom: 0; }

/* captions are read: reading size, sentence case, the body face */
.figref {
  font-family: var(--font-body); font-size: var(--read-sm); line-height: var(--leading-snug); color: var(--ink-3);
  padding: var(--space-2) 0 var(--space-2) var(--space-3); margin: var(--space-4) 0;
  border-left: var(--hair-2) solid var(--border-strong);
}
.rule { border: 0; border-top: var(--hair) solid var(--border-soft); margin: var(--space-6) 0; }
.loading { color: var(--text-muted); font-style: italic; }

/* mark-as-read → secondary Button that becomes moss when done */
.finish { width: 100%; margin-top: var(--space-6); justify-content: flex-start; }
.finish__box {
  width: var(--space-4); height: var(--space-4); border-radius: var(--space-1); flex: none; position: relative;
  background: var(--surface-inset); border: var(--hair) solid var(--border-strong); box-shadow: var(--shadow-inset-well);
  transition: all var(--dur-fast) var(--ease-snap);
}
.finish[data-done="true"] .finish__box {
  background: var(--gloss-cap), linear-gradient(180deg, var(--moss-hi), var(--moss)); border-color: var(--moss-lo);
  box-shadow: 0 var(--hair) 0 var(--moss-lo);
}
.finish[data-done="true"] .finish__box::after {
  content: ""; position: absolute; left: calc(var(--space-1) + var(--hair)); top: var(--hair); width: var(--space-1); height: var(--space-2);
  border: solid var(--text-on-deep); border-width: 0 var(--hair-2) var(--hair-2) 0; transform: rotate(43deg);
}

/* pager → two secondary buttons */
.pager { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); margin-top: var(--space-6); }
.pager__link { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-1); height: auto; padding: var(--space-4) var(--space-5); text-align: left; text-decoration: none; }
.pager__link--next { align-items: flex-end; text-align: right; }
.pager__title { font-family: var(--font-display); font-weight: 600; font-size: var(--text-md); line-height: var(--leading-snug); color: var(--ink-1); }
.pager__link[data-empty="true"] { opacity: 0.4; pointer-events: none; }
@media (hover:hover) and (pointer:fine) { .pager__link:hover { text-decoration: none; } }

/* ---------- 7. home ---------- */

.home { padding: var(--space-7) var(--space-6) var(--space-8); max-width: min(76rem, 100%); margin: 0 auto; }
.home__lede { font-size: clamp(var(--text-3xl), 5.2vw, var(--text-4xl)); margin: 0 0 var(--space-4); max-width: 16ch; text-wrap: balance; }
.home__lede em { font-style: normal; color: var(--text-accent); }
.home__sub { font-size: var(--text-lg); color: var(--text-muted); max-width: 46ch; margin: 0 0 var(--space-6); font-weight: 500; text-wrap: pretty; }
.home__stats { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-bottom: var(--space-6); }

.contents { display: flex; flex-direction: column; gap: var(--space-8); }
.contents__domain { display: flex; flex-direction: column; gap: var(--space-4); }
.contents__domain + .contents__domain {
  padding-top: var(--space-7);
  border-top: var(--hair) solid var(--border-soft);
}
.contents__domainhead {
  margin: 0;
  font-size: clamp(var(--text-xl), 3vw, var(--text-2xl));
  color: var(--ink-1);
}
.contents__sections { display: flex; flex-direction: column; gap: var(--space-6); }
.contents__group { display: flex; flex-direction: column; gap: var(--space-3); }
.contents__grouphead { margin: 0 0 var(--space-1); font-size: var(--text-2xs); }
/* Optional curriculum phase; it groups existing sections without becoming a
   tile or consuming horizontal space in the reading index. */
.contents__phase { display: flex; flex-direction: column; gap: var(--space-5); }
.contents__phase + .contents__phase {
  padding-top: var(--space-5);
  border-top: var(--hair) solid var(--border-soft);
}
.contents__phasehead {
  margin: 0;
  color: var(--text-accent);
  font-size: var(--text-xs);
  letter-spacing: .14em;
}
.pager__link--done { justify-content: flex-start; }
/* each note is a TrackTile */
.contents__row {
  display: grid; grid-template-columns: var(--control-h-md) 1fr auto; gap: var(--space-4); align-items: center;
  padding: var(--space-3) var(--space-4); text-decoration: none;
  background: var(--surface-raised); border: var(--hair) solid var(--border-soft); border-radius: var(--radius-md);
  box-shadow: var(--shadow-raise-sm);
  transition: transform var(--dur-fast) var(--ease-snap), box-shadow var(--dur-fast) var(--ease-snap), filter var(--dur-fast) var(--ease-snap);
}
@media (hover:hover) and (pointer:fine) { .contents__row:hover { filter: brightness(1.03); text-decoration: none; } }
.contents__row:active { transform: translateY(var(--press-shift)) scale(0.99); box-shadow: var(--shadow-pressed); }
.contents__num {
  width: var(--control-h-md); height: var(--control-h-md); border-radius: calc(var(--radius-md) - var(--space-1));
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: var(--read-sm); font-weight: var(--weight-bold); color: var(--text-on-deep);
  background: linear-gradient(135deg, var(--haze-hi), var(--haze-lo));
  box-shadow: var(--tile-shadow);
}
.contents__row[data-read="true"] .contents__num { background: linear-gradient(135deg, var(--moss-hi), var(--moss-lo)); }
.contents__title { display: block; font-family: var(--font-display); font-weight: 600; font-size: var(--read-body); line-height: var(--leading-snug); color: var(--ink-1); }
.contents__blurb { display: block; font-size: var(--read-sm); color: var(--text-muted); margin-top: var(--hair-2); max-width: 60ch; }
.contents__time { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--ink-3); white-space: nowrap; }

/* the filter host must not take up space or catch a tap */
.glassdefs { position: absolute; width: 0; height: 0; overflow: hidden; pointer-events: none; }

/* ---------- 8. the top bar ---------- */
/* Was a bottom dock. Moving it up hands the bottom edge back to reading and
   gives the menu button a fixed home that the drawer can never cover. */

/* Liquid glass. The look is layered optics rather than one blur: a
   translucent gradient body, a specular rim along the top edge, a soft
   caustic sheen, and a lift shadow that only appears once there is content
   underneath to lift away from. Everything keys off two data attributes the
   scroll handler sets, so the CSS stays declarative. */
.topbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 80;
  height: var(--bar-h);
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  /* symmetric optical margins, and viewport-fit=cover is declared in the
     document so the notch has to be honoured rather than ignored */
  padding-left: max(var(--space-2), env(safe-area-inset-left));
  padding-right: max(var(--space-2), env(safe-area-inset-right));
  padding-top: var(--bar-safe-top);
  isolation: isolate;

  background: var(--bar-glass);
  backdrop-filter: blur(16px) saturate(180%);
  -webkit-backdrop-filter: blur(16px) saturate(180%);
  border-bottom: var(--hair) solid var(--bar-hairline);

  /* specular rim above, a hairline of shade below */
  box-shadow: var(--bar-rims);

  transition:
    background var(--dur-slow) var(--ease-smooth),
    box-shadow var(--dur-slow) var(--ease-smooth),
    height var(--dur-med) var(--ease-smooth);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .topbar { background: var(--paper-1); }
}

/* Lifted: once the page has scrolled there is something behind the glass, so
   it thickens and casts. At the very top it stays almost clear and the bar
   reads as part of the paper. */
.topbar[data-lift="true"] {
  background: var(--bar-glass-lift);
  backdrop-filter: blur(22px) saturate(200%);
  -webkit-backdrop-filter: blur(22px) saturate(200%);
  box-shadow: var(--bar-rims-lift);
}

/* the caustic. A turbulence displacement on a decorative layer only — never
   on the backdrop, where an unsupported filter would take the blur down with
   it. If the filter does not resolve, this is simply a soft highlight. */
.topbar__sheen {
  position: absolute; inset: 0; pointer-events: none; z-index: -1;
  background: var(--sheen-wash);
  opacity: .9;
  will-change: transform, opacity;
  transition: opacity var(--dur-slow) var(--ease-smooth);
}
@supports (filter: url("#x")) {
  .topbar__sheen { filter: url(#glass-caustic); }
}
/* the sheen drifts only while something is actually playing — motion tied to
   product state, not decoration running on an idle page */
body[data-playing="true"] .topbar__sheen {
  animation: caustic 14s var(--ease-smooth) infinite alternate;
}
@keyframes caustic {
  from { transform: translate3d(-2%, 0, 0) scale(1.04); opacity: .75; }
  to   { transform: translate3d(2%, 0, 0)  scale(1.10); opacity: 1; }
}

.topbar__progress {
  position: absolute; left: 0; bottom: 0; height: var(--hair-2); width: 0;
  background: linear-gradient(90deg, var(--tape-orange-hi), var(--tape-orange));
  box-shadow: 0 0 var(--space-2) rgba(242,87,31,.55);
  transition: width var(--dur-med) linear;
}
/* width is driven by scroll position in app.js */

/* The identity side is the anchor and never shrinks, so the wordmark keeps
   the same x at every width. Previously it was the shrinkable one while
   --end was flex:none, so on a phone it was squeezed under its content and
   the wordmark overflowed its own track into the now-playing text. The
   squeeze belongs on --end, whose station line is built to ellipsize. */
.topbar__side { display: flex; align-items: center; gap: var(--space-2); flex: none; }
.topbar__side--end { gap: var(--space-2); flex: 0 1 auto; min-width: 0; }

.topbar__now { min-width: 0; text-align: right; }
.topbar__station {
  font-weight: 600; font-size: var(--text-xs); color: var(--ink-1);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 14rem;
}
.topbar__source { font-size: var(--text-2xs); color: var(--text-muted); display: none; }
/* on a phone the name alone, truncated by whatever the track leaves it; the
   source line only once there is room for it */
.topbar__station { max-width: 100%; }
@media (min-width: 30rem) {
  .topbar__source { display: block; }
  .topbar__station { max-width: 14rem; }
}

/* menu <-> close: one button, two glyphs, so focus is never lost mid-gesture */
.navbtn {
  position: relative; flex: none;
  width: var(--control-h-md); height: var(--control-h-md);
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; border-radius: var(--radius-sm);
  color: var(--ink-2); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-snap);
}
.navbtn:active { background: var(--press-tint); }
.navbtn:focus-visible { outline: var(--hair-2) solid var(--text-accent); outline-offset: calc(var(--hair) * -1); }
.navbtn__ico {
  position: absolute; width: var(--space-5); height: var(--space-5);
  stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round;
  transition: opacity var(--dur-fast) var(--ease-snap), transform var(--dur-med) var(--ease-snap);
}
.navbtn__ico--menu  { opacity: 1; transform: rotate(0) scale(1); }
.navbtn__ico--close { opacity: 0; transform: rotate(-90deg) scale(0.9); }
/* the 80ms delay on the incoming glyph is what makes this read as one
   hand-off rather than two fades running at each other */
.navbtn[aria-expanded="true"] .navbtn__ico--menu  { opacity: 0; transform: rotate(90deg) scale(0.9); }
.navbtn[aria-expanded="true"] .navbtn__ico--close { opacity: 1; transform: rotate(0) scale(1); transition-delay: 50ms; }

.topbar .player__btn { width: var(--control-h-md); height: var(--control-h-md); }
.topbar__tuner { flex: none; }

/* theme switch: one knob, two glyphs — the glyph names where pressing takes
   you, matching the aria-label (moon in the light theme, sun in the dark).
   Below 37.5rem the bar has no room for it (the station text is already
   ellipsizing), so the switch lives in the drawer's foot instead; exactly
   one instance is visible at any width. */
.themebtn { position: relative; }
.themebtn svg {
  position: absolute; top: 50%; left: 50%;
  margin: calc(var(--space-4) / -2) 0 0 calc(var(--space-4) / -2);
  transition: opacity var(--dur-med) var(--ease-snap), transform var(--dur-med) var(--ease-snap);
}
.themebtn__moon { opacity: 1; transform: rotate(0deg) scale(1); }
.themebtn__sun { opacity: 0; transform: rotate(-90deg) scale(.4); }
:root[data-theme="dark"] .themebtn__moon { opacity: 0; transform: rotate(90deg) scale(.4); }
:root[data-theme="dark"] .themebtn__sun { opacity: 1; transform: rotate(0deg) scale(1); }
.topbar .themebtn { display: none; }
@media (min-width: 37.5rem) { .topbar .themebtn { display: flex; } }

/* The reader HUD gives progress a stable home beside the global fold controls.
   The percentage remains visible while the fold control defocuses on a
   downward scroll, so progress is available precisely while it is changing. */
.readerhud {
  position: fixed; z-index: 60;
  right: max(var(--space-4), env(safe-area-inset-right));
  bottom: max(var(--space-5), calc(env(safe-area-inset-bottom) + var(--space-3)));
  display: flex; align-items: flex-end; gap: var(--space-2);
}
.readerhud[hidden] { display: none; }
.readpct {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: calc(var(--control-h-md) + var(--space-2));
  height: var(--control-h-sm);
  padding: 0 var(--space-2);
  border: var(--hair) solid var(--border-soft);
  border-radius: var(--radius-pill);
  background: var(--surface-raised);
  box-shadow: var(--shadow-raise-sm);
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  pointer-events: none;
}

/* Global fold controls. The active half reads as a latched hardware button:
   pressed-in shadow plus the accent tint.

   ONE home at every width — a fixed pill in the bottom-right, stacked
   vertically. It used to be two loose knobs in the bar above 600px and a
   horizontal pill below, which put the same setting in two different places
   depending on the screen. It cannot live in the bar and be fixed: the bar
   carries a transform and a backdrop-filter, either of which makes it the
   containing block for a fixed child, so the pill would retract off-screen
   with it. Deliberately BELOW the scrim (64), so an open drawer or stations
   sheet covers it rather than competing with it, and lifted clear of the
   home indicator. */
.foldpill {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--hair-2); padding: var(--hair-3);
  border-radius: var(--radius-pill);
  background: var(--surface-raised);
  border: var(--hair) solid var(--border-soft);
  box-shadow: var(--shadow-raise-lg);

  /* Only this control retracts on the reading gesture. It defocuses in place
     rather than sliding, then returns when the reader scrolls upward. */
  opacity: 1; visibility: visible; filter: blur(0);
  transition:
    opacity var(--dur-med) var(--ease-smooth),
    filter var(--dur-med) var(--ease-smooth),
    visibility 0s linear 0s;
}
.foldpill[hidden] { display: none; }
.foldpill[data-hidden="true"] {
  opacity: 0; visibility: hidden; filter: blur(var(--space-2));
  transition:
    opacity var(--dur-fast) var(--ease-smooth),
    filter var(--dur-fast) var(--ease-smooth),
    visibility 0s linear var(--dur-fast);
}
/* the knobs are segments of one control: no metal face, no lift of their own */
.foldpill .foldbtn {
  width: calc(var(--control-h-md) + var(--hair-2));
  height: calc(var(--control-h-md) + var(--hair-2));
  border-radius: var(--radius-pill);
  background: transparent; box-shadow: none;
}
/* the hairline between the halves lies across the stack, not along it */
.foldsep {
  flex: none;
  width: var(--space-5); height: var(--hair); background: var(--border-soft);
}
.foldbtn svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.foldpill .foldbtn svg { width: var(--space-5); height: var(--space-5); }
/* the in-note pair latches from the same [data-tree] paint, so both agree */
.foldbtn[aria-pressed="true"] {
  color: var(--text-accent);
  background: var(--paper-3);
  box-shadow: var(--shadow-pressed);
}
/* (the reading tail the pill needs is set with the other layout rules at the
   foot of this file — it has to land after the 940px block, whose padding
   shorthand would otherwise reset the bottom) */

.rail__foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-top: var(--hair) solid var(--border-soft);
}
.rail__footlabel {
  font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--weight-bold);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted);
}
@media (min-width: 37.5rem) { .rail__foot { display: none; } }
body[data-playing="true"] .topbar .player__btn--primary {
  box-shadow: 0 0 0 var(--hair-2) rgba(242,87,31,.22), 0 var(--hair-2) var(--space-2) rgba(242,87,31,.32);
}

/* controls the bar has no room for, parked in the stations sheet */
.tuner__transport {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-bottom: var(--hair) solid var(--border-soft);
}
.tuner__transport .player__vol { flex: 1; }
/* in the bar it is a naked cluster, not a panel */
.topbar .tuner__transport {
  padding: 0; border-bottom: 0; gap: var(--space-2); flex: none;
}
.topbar .tuner__transport .player__vol { flex: none; width: calc(var(--space-8) + var(--space-5)); }
.topbar .player__art { width: var(--control-h-sm); height: var(--control-h-sm); }

/* a control that cannot act on this source says so instead of moving */
.player__vol[data-na="true"] { opacity: .38; }
.player__vol[data-na="true"] input { cursor: not-allowed; }

/* a station that failed to load is not selectable */
.track[data-dead="true"] { opacity: .5; cursor: not-allowed; }


/* the tape running underneath, kept from the dock */
.topbar__progress {
  position: absolute; left: 0; bottom: 0; height: var(--hair-2); width: 0;
  background: linear-gradient(90deg, var(--tape-orange-hi), var(--tape-orange));
  transition: width var(--dur-med) linear;
}
body[data-playing="true"] .topbar__progress { width: 100%; transition: width 30s linear; }

.player__art {
  width: var(--control-h-md); height: var(--control-h-md); border-radius: calc(var(--radius-md) - var(--space-1)); flex: none;
  background: linear-gradient(135deg, var(--haze-hi), var(--haze-lo));
  box-shadow: var(--tile-shadow);
  display: flex; align-items: flex-end; justify-content: center; gap: var(--hair-2); padding: 0 var(--space-2) var(--space-2);
  overflow: hidden;
}
.player__art i { display: block; width: var(--hair-3); height: var(--hair-3); border-radius: var(--hair); background: rgba(255,255,255,.85); transition: height 0.11s linear; }


/* round hardware buttons: gloss orange primary, metal knob secondary */
.player__btn {
  width: var(--control-h-md); height: var(--control-h-md); border-radius: 50%; border: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center; flex: none;
  background: var(--metal-knob); color: var(--ink-2); box-shadow: var(--shadow-raise-sm);
  transition: transform var(--dur-fast) var(--ease-snap), box-shadow var(--dur-fast) var(--ease-snap), filter var(--dur-fast) var(--ease-snap);
}
.player__btn--primary {
  width: calc(var(--control-h-md) + var(--hair-2)); height: calc(var(--control-h-md) + var(--hair-2));
  background: var(--gloss-cap), linear-gradient(180deg, var(--tape-orange-hi), var(--tape-orange));
  color: var(--text-on-accent); box-shadow: var(--shadow-accent-raise);
}
@media (hover:hover) and (pointer:fine) { .player__btn:hover { filter: brightness(1.03); } }
.player__btn:active { transform: translateY(var(--press-shift)) scale(var(--press-scale)); box-shadow: var(--shadow-pressed); }
.player__btn--primary:active { background: var(--tape-orange-lo); box-shadow: var(--shadow-accent-pressed); }
.player__btn svg { width: var(--space-4); height: var(--space-4); fill: currentColor; }
.player__btn--primary svg { width: calc(var(--space-4) + var(--space-1)); height: calc(var(--space-4) + var(--space-1)); }
.ico-pause { display: none; }
body[data-playing="true"] .ico-play { display: none; }
body[data-playing="true"] .ico-pause { display: block; }

.player__vol { width: auto; }

/* ---------- 9. stations sheet ---------- */
/* a raised sheet (radius-xl) that rises from the player */

.tuner {
  position: fixed; right: var(--space-3); top: calc(var(--bar-h) + var(--space-2)); z-index: 79;
  width: min(32rem, calc(100vw - var(--space-5)));
  max-height: min(40rem, calc(100vh - var(--bar-h) - var(--space-7)));
  overflow-y: auto; scrollbar-width: thin;
  background: var(--surface-card); border: var(--hair) solid var(--border-soft); border-radius: var(--radius-xl);
  box-shadow: var(--shadow-raise-lg);
  transform: translateY(calc(var(--space-2) * -1)) scale(0.98); opacity: 0; visibility: hidden;
  transition:
    transform var(--dur-med) var(--ease-smooth),
    opacity var(--dur-med) var(--ease-smooth),
    visibility 0s linear var(--dur-med);
}
.tuner[data-open="true"] {
  transform: none; opacity: 1; visibility: visible;
  transition:
    transform var(--dur-med) var(--ease-smooth),
    opacity var(--dur-med) var(--ease-smooth),
    visibility 0s;
}
.tuner__head {
  position: sticky; top: 0; z-index: 1;
  display: flex; justify-content: space-between; align-items: center;
  padding: var(--space-4) var(--space-5) var(--space-3);
  background: var(--surface-card); border-bottom: var(--hair) solid var(--border-soft);
}
.tuner__title { font-family: var(--font-display); font-weight: var(--weight-display); font-size: var(--text-lg); letter-spacing: var(--tracking-display); }
.tuner__body { padding: 0 var(--space-5) var(--space-5); display: flex; flex-direction: column; }
.tuner__tabs { align-self: flex-start; }
.tuner__list { display: flex; flex-direction: column; gap: var(--space-2); }
.tuner__pane[hidden] { display: none; }

/* Tabs and the paste field stay put while the list scrolls under them. With
   a library this size, controls that scroll away are controls you have to go
   hunting for. */
.tuner__sticky {
  position: sticky; top: 0; z-index: 2;
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-4) 0 var(--space-3);
  background: var(--surface-card);
  border-bottom: var(--hair) solid var(--border-soft);
}
.tuner__find { display: flex; gap: var(--space-2); align-items: center; }
.tuner__find[hidden] { display: none; }
.tuner__input {
  flex: 1; min-width: 0; height: var(--control-h-md);
  padding: 0 var(--space-3); border-radius: var(--radius-md);
  border: var(--hair) solid var(--border-strong);
  background: var(--surface-inset); box-shadow: var(--shadow-inset-well);
  font-family: var(--font-body); font-size: var(--text-sm); color: var(--ink-1);
}
.tuner__input::placeholder { color: var(--ink-4); }
.tuner__input:focus-visible { outline: var(--hair-2) solid var(--text-accent); outline-offset: var(--hair-2); }
.tuner__addbtn { flex: none; min-width: calc(var(--space-8) + var(--space-4)); }
.tuner__addbtn[aria-busy="true"] { opacity: .7; }

.tuner__msg { margin: 0; font-size: var(--text-tag); line-height: var(--leading-body); color: var(--text-muted); }
.tuner__msg[data-kind="err"] { color: var(--danger); }
.tuner__msg[data-kind="ok"] { color: var(--text-accent); }
.tuner__msg[hidden] { display: none; }
.tuner__anyway {
  border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; font-weight: 600; color: var(--text-accent); text-decoration: underline;
}

.tuner__grouphead {
  padding: var(--space-4) 0 var(--space-2);
  font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--weight-bold);
  letter-spacing: 0.10em; text-transform: uppercase; color: var(--ink-4);
}
.tuner__empty {
  margin: 0; padding: var(--space-3) 0;
  font-size: var(--text-tag); line-height: var(--leading-body); color: var(--text-muted);
}

/* a row is the tile plus its remove button, so the button is a real sibling
   rather than something nested inside the tile's own click target */
.track__row { display: flex; align-items: stretch; gap: var(--space-2); }
.track__row[hidden] { display: none; }
.track__row .track { flex: 1; min-width: 0; }
.track__kill {
  flex: none; width: var(--control-h-md); height: auto; min-height: var(--control-h-md);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  border: var(--hair) solid var(--border-soft); border-radius: var(--radius-md);
  background: var(--surface-raised); color: var(--ink-3);
  transition: color var(--dur-fast) var(--ease-snap), background var(--dur-fast) var(--ease-snap), box-shadow var(--dur-fast) var(--ease-snap);
}
.track__kill svg { width: var(--space-3); height: var(--space-3); fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
@media (hover: hover) and (pointer: fine) {
  .track__kill:hover { color: var(--danger); background: var(--paper-2); }
}
.track__kill:active { transform: translateY(var(--press-shift)); box-shadow: var(--shadow-pressed); }
.track__kill:focus-visible { outline: var(--hair-2) solid var(--text-accent); outline-offset: var(--hair); }

/* real cover art where SoundCloud gave us one; the gradient tile stays as
   the fallback underneath, so a broken image is just the old look */
.track__cover {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; border-radius: inherit;
}
.track[aria-current="true"] .track__cover { opacity: .35; }

/* TrackTile, verbatim */
.track {
  display: flex; align-items: center; gap: var(--space-3); width: 100%; padding: var(--space-2) var(--space-3) var(--space-2) var(--space-2);
  border: var(--hair) solid var(--border-soft); border-radius: var(--radius-md);
  background: var(--surface-raised); box-shadow: var(--shadow-raise-sm);
  cursor: pointer; text-align: left; font-family: var(--font-body);
  transition: transform var(--dur-fast) var(--ease-snap), box-shadow var(--dur-fast) var(--ease-snap), filter var(--dur-fast) var(--ease-snap);
}
@media (hover:hover) and (pointer:fine) { .track:hover { filter: brightness(1.03); } }
.track:active { transform: translateY(var(--press-shift)); box-shadow: var(--shadow-pressed); }
.track[aria-current="true"] {
  border-color: var(--tape-orange-lo);
  background: var(--surface-playing);
  box-shadow: var(--shadow-raise-md);
}
.track__art {
  position: relative; overflow: hidden;
  width: var(--control-h-md); height: var(--control-h-md); border-radius: calc(var(--radius-md) - var(--space-1)); flex: none;
  display: flex; align-items: center; justify-content: center; color: rgba(255,255,255,.85);
  box-shadow: var(--tile-shadow);
}
.track__art--soundcloud { background: linear-gradient(135deg, var(--butter-hi), var(--tape-orange)); }
.track__art--synth { background: linear-gradient(135deg, var(--haze-hi), var(--haze-lo)); }
.track__art svg { position: relative; width: var(--space-4); height: var(--space-4); fill: currentColor; }
.track__eq { position: relative; display: none; gap: var(--hair-2); align-items: flex-end; height: var(--eq-h); }
.track__eq i { display: block; width: var(--hair-3); border-radius: var(--hair-2); background: currentColor; }
.track[aria-current="true"] .track__eq { display: flex; }
.track[aria-current="true"] .track__art svg { display: none; }
.track__meta { flex: 1; min-width: 0; }
.track__name { display: block; font-weight: 600; font-size: var(--text-sm); color: var(--ink-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.track__desc { display: block; font-size: var(--text-xs); color: var(--text-muted); }
.track__tag { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--ink-3); white-space: nowrap; }
.track[aria-current="true"] .track__tag { color: var(--text-accent); }
.track[data-dead="true"] { opacity: 0.45; }
.track[data-dead="true"] .track__tag { color: var(--danger); }

/* the widget host. Mounted once, parked offscreen, never unmounted — this is
   what lets playback survive navigation. */
.audiohost { position: fixed; width: var(--embed-w); height: var(--embed-h); left: -9999px; top: 0; pointer-events: none; opacity: 0; }
.audiohost iframe { width: 100%; height: 100%; border: 0; display: block; }

/* ---------- 9b. touch ----------
   Hover is guarded above, so a phone gets no feedback from it at all — which
   is the point: a latched hover state reads as selection and competes with
   the real current-note marker. These pressed states replace it. */

.entry:active, .mod:active, .tnode--sec:active, .contents__row:active, .track:active {
  background: var(--paper-3);
}
.twist--btn:active { color: var(--text-accent); }
.entry, .mod, .tnode--sec, .twist--btn, .contents__row, .track, .btn, .iconbtn, .player__btn {
  -webkit-tap-highlight-color: transparent;
}

/* ---------- 10. mobile ---------- */

/* The menu slot is reserved at EVERY width. On desktop the rail is always
   open so the button has nothing to toggle — but removing it moved the
   wordmark 52px sideways across the breakpoint, so the brand mark landed in
   two different places depending on window width. The box stays; only the
   glyph and the interactivity go. */
.navbtn { display: inline-flex; }
/* The menu button used to be switched off above 941px because the rail was
   always open and it had nothing to toggle. It now collapses the rail on
   desktop, so it stays live at every width. */
.scrim {
  position: fixed; inset: var(--bar-h) 0 0; z-index: 64; background: var(--scrim-bg);   /* the DS scrim, themed */
  opacity: 0; visibility: hidden; transition: opacity var(--dur-med) var(--ease-smooth), visibility var(--dur-med);
}
.scrim[data-open="true"] { opacity: 1; visibility: visible; }

/* ---------- heading tree ----------
   # / ## / ### become nested collapsible sections, and the lesson map is
   the same outline as a linked tree. Section summaries must read as
   headings, not keys, so the global details skin is undone here. */
.doc__body details.sec { margin: 0; }
.doc__body .sec > summary {
  display: flex; align-items: baseline; gap: var(--space-2);
  height: auto; padding: 0; background: transparent; border: 0; box-shadow: none;
}
@media (hover:hover) and (pointer:fine) { .doc__body .sec > summary:hover { filter: none; } }
@media (hover:hover) and (pointer:fine) { .doc__body .sec > summary:hover > * { color: var(--link); } }
.doc__body .sec[open] > summary, .doc__body .sec > summary:active {
  transform: none; background: transparent; box-shadow: none;
}
.doc__body .sec--1 > summary { margin: var(--space-7) 0 var(--space-3); }
.doc__body .sec--2 > summary { margin: var(--space-6) 0 var(--space-3); }
.doc__body .sec--3 > summary { margin: var(--space-5) 0 var(--space-2); }
.doc__body .sec > summary > h2, .doc__body .sec > summary > h3, .doc__body .sec > summary > h4 { margin: 0; }
/* Must clear the fixed top bar, not just sit off the viewport edge —
   scrollIntoView and native hash jumps both honour this, which is what the
   lesson map's own links use. */
.doc__body h2[id], .doc__body h3[id], .doc__body h4[id] { scroll-margin-top: calc(var(--bar-h) + var(--space-5)); }

/* the lesson-map tree */
.toc { margin: var(--space-2) 0 0; }
.toc details { margin: 0; }
.toc > .toc__leaf { padding: var(--space-1) 0 var(--space-1) calc(var(--space-4) + var(--space-2)); }
.toc summary {
  display: flex; align-items: baseline; gap: var(--space-2);
  height: auto; padding: var(--space-1) 0; background: transparent; border: 0; box-shadow: none;
  font-size: var(--text-md); font-weight: 600; color: var(--ink-1);
}
@media (hover:hover) and (pointer:fine) { .toc summary:hover { filter: none; color: var(--link); } }
.toc details[open] > summary, .toc summary:active { transform: none; background: transparent; box-shadow: none; }
.doc__body .toc ul { margin: 0; padding-left: calc(var(--space-4) + var(--space-2)); }
.doc__body .toc ul ul { padding-left: var(--space-4); }
.doc__body .toc li { margin: 0; padding-left: 0; }
.doc__body .toc ul > li::before { content: none; }
.doc__body .toc a.toc__link {
  display: inline-block; padding: var(--space-1) 0;
  font-size: var(--text-md); font-weight: 500; color: var(--ink-2);
}
@media (hover:hover) and (pointer:fine) { .doc__body .toc a.toc__link:hover { color: var(--link); } }

/* figures: real images for local assets, caption in the figref voice */
.figure { margin: var(--space-5) 0; }
.figure__open {
  position: relative; display: block; width: 100%; border-radius: var(--radius-md);
  cursor: zoom-in; text-align: left;
}
.figure__img {
  display: block; width: 100%; height: auto;
  /* Reserve the box before a lazy image arrives. Without this a figure is
     2px tall until it loads and then jumps to full height, which shifts
     everything below it — including a heading someone is being scrolled to.
     "auto" first means the real ratio wins once the image is decoded, so a
     figure that is not 7:4 is not distorted. */
  aspect-ratio: auto 7 / 4;
  /* Always a paper plate: invisible on the light card, and in the dark it
     keeps the figures' black strokes legible — a printed diagram rather
     than an inverted one. */
  padding: var(--space-2);
  background: var(--figure-plate); border: var(--hair) solid var(--border-strong); border-radius: var(--radius-md);
  box-shadow: none;
}
.figure__expand {
  position: absolute; top: var(--space-3); right: var(--space-3);
  width: var(--control-h-sm); height: var(--control-h-sm);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-on-deep); background: var(--viewer-chrome);
  border: var(--hair) solid var(--viewer-border); border-radius: var(--radius-pill);
  box-shadow: var(--shadow-raise-sm);
  opacity: .84;
  transition: opacity var(--dur-fast) var(--ease-snap), transform var(--dur-fast) var(--ease-snap), background var(--dur-fast) var(--ease-snap);
}
.figure__expand svg {
  width: var(--space-4); height: var(--space-4); fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.figure__open:focus-visible { outline-offset: var(--space-1); }
@media (hover:hover) and (pointer:fine) {
  .figure__open:hover .figure__expand { opacity: 1; transform: scale(1.04); background: var(--viewer-chrome-hover); }
}
.figure > .figref { margin: var(--space-2) 0 0; }
.diagram__open {
  height: auto; min-height: var(--control-h-md); padding: 0;
  border: 0; background: transparent; box-shadow: none; color: inherit;
  white-space: normal;
}
.diagram__open > svg { width: 100%; }
.diagram__open .figure__expand { top: 4px; right: 4px; }
.diagram__open:hover, .diagram__open:active { background: transparent; box-shadow: none; transform: none; }

/* ---------- full-screen image viewer ----------
   The page viewport stays fixed at 1:1; only this image transforms. That
   keeps the app chrome stable while still giving diagrams a true inspection
   surface on touch and pointer devices. */
body.imageviewer-open { overflow: hidden; }
.imageviewer {
  position: fixed; inset: 0; width: 100vw; height: 100dvh;
  max-width: none; max-height: none; margin: 0; padding: 0;
  overflow: hidden; border: 0; background: var(--viewer-backdrop); color: var(--text-on-deep);
}
.imageviewer:not([open]) { display: none; }
.imageviewer::backdrop { background: var(--viewer-backdrop); }
.imageviewer__stage {
  position: absolute; inset: 0; z-index: 0;
  display: flex; align-items: center; justify-content: center;
  padding: calc(env(safe-area-inset-top) + var(--space-8)) var(--space-4)
           calc(env(safe-area-inset-bottom) + var(--space-8));
  overflow: hidden; overscroll-behavior: contain; touch-action: none;
  cursor: zoom-in; user-select: none; -webkit-user-select: none;
}
.imageviewer__stage[data-zoomed="true"] { cursor: grab; }
.imageviewer__stage[data-gesturing="true"] { cursor: grabbing; }
.imageviewer__image {
  display: block; max-width: 100%; max-height: 100%; width: auto; height: auto;
  padding: var(--space-2); object-fit: contain; pointer-events: none;
  background: var(--figure-plate); border: var(--hair) solid var(--viewer-border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-raise-lg); transform-origin: center center;
  will-change: transform; transition: transform var(--dur-fast) var(--ease-smooth);
}
.imageviewer__stage[data-gesturing="true"] .imageviewer__image { transition: none; }
.imageviewer__control, .imageviewer__zoom {
  flex: none; height: var(--control-h-md);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-on-deep); background: transparent; border-radius: var(--radius-pill);
  transition: background var(--dur-fast) var(--ease-snap), transform var(--dur-fast) var(--ease-snap);
}
.imageviewer__control { width: var(--control-h-md); }
.imageviewer__zoom {
  min-width: calc(var(--control-h-md) + var(--space-3)); padding: 0 var(--space-2);
  font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--weight-bold);
}
.imageviewer__control svg {
  width: var(--space-5); height: var(--space-5); fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.imageviewer__control:active, .imageviewer__zoom:active { transform: scale(var(--press-scale)); }
.imageviewer__control:focus-visible, .imageviewer__zoom:focus-visible {
  outline-color: var(--tape-orange-hi); outline-offset: calc(var(--hair-2) * -1);
}
@media (hover:hover) and (pointer:fine) {
  .imageviewer__control:hover, .imageviewer__zoom:hover { background: var(--viewer-chrome-hover); }
}
.imageviewer__close {
  position: absolute; z-index: 2;
  top: max(var(--space-4), env(safe-area-inset-top));
  right: max(var(--space-4), env(safe-area-inset-right));
  background: var(--viewer-chrome); border: var(--hair) solid var(--viewer-border);
  box-shadow: var(--shadow-raise-md);
}
.imageviewer__toolbar {
  position: absolute; z-index: 2; left: 50%;
  bottom: max(var(--space-4), calc(env(safe-area-inset-bottom) + var(--space-3)));
  transform: translateX(-50%);
  display: flex; align-items: center; gap: var(--hair-2); padding: var(--hair-3);
  max-width: calc(100vw - var(--space-5));
  border: var(--hair) solid var(--viewer-border); border-radius: var(--radius-pill);
  background: var(--viewer-chrome); box-shadow: var(--shadow-raise-lg);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
}
.imageviewer__divider { width: var(--hair); height: var(--space-5); background: var(--viewer-border); }
.imageviewer__fullscreen { position: relative; }
.imageviewer__fullscreen .imageviewer__exit { display: none; }
.imageviewer__fullscreen[aria-pressed="true"] .imageviewer__enter { display: none; }
.imageviewer__fullscreen[aria-pressed="true"] .imageviewer__exit { display: block; }
.imageviewer__help {
  position: absolute; z-index: 1;
  top: max(var(--space-4), env(safe-area-inset-top));
  left: max(var(--space-4), env(safe-area-inset-left));
  max-width: calc(100vw - calc(var(--control-h-md) + var(--space-8)));
  margin: 0; padding: var(--space-2) var(--space-3);
  color: var(--text-on-deep); background: var(--viewer-chrome);
  border: var(--hair) solid var(--viewer-border); border-radius: var(--radius-pill);
  font-family: var(--font-mono); font-size: var(--text-2xs); line-height: var(--leading-snug);
}
.imageviewer__help-pointer { display: none; }
@media (hover:hover) and (pointer:fine) {
  .imageviewer__help-touch { display: none; }
  .imageviewer__help-pointer { display: inline; }
}

/* outline controls between standfirst and body */
.toc__tools {
  display: flex; align-items: center; gap: var(--space-2);
  margin: 0 0 var(--space-3); padding-bottom: var(--space-3);
  border-bottom: var(--hair) solid var(--border-soft);
}
.toc__tools .eyebrow { margin-right: auto; }
/* the in-note pair is icon-only — the words moved to aria-label/title so the
   row reads as one quiet line, and it matches the floating pill's two icons */
.toc__tools .foldbtn { flex: none; }
.toc__tools--loose { margin-bottom: var(--space-4); }


@media (max-width: 940px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  .rail {
    position: fixed; top: var(--bar-h); left: 0; bottom: 0; z-index: 65;
    height: calc(100dvh - var(--bar-h));
    /* Full width on a phone. At 88vw the drawer left a dead strip down the
       right edge while every note title wrapped to two lines beside it —
       space withheld from the only content the drawer has. min() keeps a
       tablet from getting an absurd 900px-wide tree. Closing is unaffected:
       the menu button is already an X in the bar above, and Escape works. */
    width: min(100%, 28rem);
    padding: var(--space-3) var(--space-3) max(var(--space-3), env(safe-area-inset-bottom))
             max(var(--space-3), env(safe-area-inset-left));
    transform: translateX(-104%); transition: transform var(--dur-med) var(--ease-smooth);
  }
  .rail[data-open="true"] { transform: none; }
  .page, .home { padding: var(--space-5) var(--space-4) var(--space-7); }
  .doc__body { padding: var(--space-4); }
  .contents__row { grid-template-columns: var(--control-h-md) 1fr; }
  .contents__time { display: none; }
  .pager { grid-template-columns: 1fr; }
  .pager__link--next { align-items: flex-start; text-align: left; }
  .tuner { right: var(--space-2); left: var(--space-2); width: auto; }
  .audiohost[data-visible="true"] { right: var(--space-2); left: var(--space-2); width: auto; }
}
@media (max-width: 640px) {
  .tablewrap table { font-size: var(--text-tag); }
  .tablewrap th, .tablewrap td { padding: var(--space-2) var(--space-3); }
}

/* The fold pill floats over the bottom-right of the reading column, which is
   exactly where the pager's "next note" link ends. Give the note enough tail
   to scroll that clear, rather than leaving a tap target sitting under the
   pill. The pill is the same size at every width, so this is unconditional —
   it used to be phone-only, back when the pill was too. It has to come after
   the 940px block above, whose padding shorthand would otherwise reset the
   bottom. */
.page { padding-bottom: calc(var(--space-8) * 2); }
}

/* ---------- 11. entrance ---------- */
.page, .home { animation: rise var(--dur-med) var(--ease-smooth) both; }
/* where the browser cross-fades between views, the rise would be a second
   animation playing over the first */
html[data-vt="true"] .page, html[data-vt="true"] .home { animation: none; }
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation-duration: 150ms; animation-timing-function: var(--ease-smooth); }
  ::view-transition-new(root) { animation-duration: 240ms; animation-timing-function: var(--ease-smooth); }
}
@keyframes rise { from { opacity: 0; transform: translateY(var(--space-1)); } to { opacity: 1; transform: none; } }

.sronly {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.skip { position: absolute; left: -9999px; top: var(--space-2); z-index: 200; }
.skip:focus { left: var(--space-2); }
