/* GuideStar Board - the parts of GuideStar Tutoring Helper's app.css the Board uses, in the same order.
   Made by stage 0 of docs/PLAN.md; each part is marked with where it came from. Edit freely from here on. */

/* ═══ the look: palettes, type packs, styles, glass, texture, the shell, type, cards (GuideStar app.css 1–1041) ═══ */
/* GuideStar Tutoring Helper — brand: Cormorant Garamond + Jost, ink on paper. */

@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,500;0,600;0,700;1,500&family=Jost:wght@300;400;500;600&display=swap');

/* ── Palettes ──────────────────────────────────────────────────────────
   Both are declared once here; the theme blocks below just point the
   working variables at one set or the other. Change a colour in one place. */
:root {
  /* light — ink on paper, straight off the business card.
     Toned down from the original near-white: the page was #fcfbf7 and cards
     were the same value, so a screenful was one flat sheet at 99% luminance
     with nothing to rest on. The page is now a shade darker than the cards,
     which both takes the glare off and lets a card read as a card. */
  --l-ink: #201e1a;
  --l-paper: #f1eee6;        /* page */
  --l-surface-lit: #faf8f3;  /* cards, masthead, dialogs — lifted off the page */
  --l-paper-2: #e7e3d8;      /* hovers, inputs, code */
  --l-muted: #6f6a61;
  --l-faint: #757066;
  --l-rule: #ddd8cb;
  --l-rule-strong: #c7c1b3;
  --l-accent: #7d6234;
  --l-accent-soft: #efe7d7;
  --l-accent-2: #4a6785;     /* measurements — progress, charts, scores */
  --l-green: #4f7a52;
  /* Darkened from #b07d2a, which sat at 3.41:1 on its own badge background
     — below AA, and had been since long before any of the glass work. */
  --l-amber: #946923;
  --l-red: #a4443c;
  --l-blue: #4a6785;
  --l-green-bg: #f3f8f3;  --l-green-br: #cdddce;
  --l-amber-bg: #fdf8ee;  --l-amber-br: #ecdcbe;
  --l-red-bg:   #fdf3f2;  --l-red-br:   #e8cfcd;
  --l-blue-bg:  #f3f6fa;  --l-blue-br:  #cfd9e4;
  /* An elevation scale, not one shadow. Every level is built the same way —
     a tight contact shadow plus a wider ambient one, both pulled in with a
     negative spread so they stay under the element instead of haloing out
     past it. Before this each component invented its own: cards carried a
     32px 50%-black cloud with no spread while buttons had a tight 8px one,
     which is why some things looked far heavier than others. */
  --l-shadow-1: 0 1px 1px rgb(32 30 26 / .04), 0 4px 10px -4px rgb(32 30 26 / .10);
  --l-shadow-2: 0 2px 3px -1px rgb(32 30 26 / .06), 0 10px 20px -8px rgb(32 30 26 / .14);
  --l-shadow-3: 0 4px 6px -2px rgb(32 30 26 / .08), 0 18px 36px -12px rgb(32 30 26 / .20);
  --l-shadow: var(--l-shadow-1);
  --l-logo-filter: none;
  --l-backdrop: rgba(32,30,26,.34);
  --l-surface: var(--l-surface-lit);

  /* dark — neutral graphite, not a dimmed version of the paper.
     Tinting the background warm turns it muddy brown, so the base stays a
     clean near-neutral and the brand warmth is carried entirely by the gold
     accent and the off-white text. */
  --d-ink: #eae8e3;
  --d-paper: #121216;        /* page */
  --d-surface: #2c2c32;      /* cards, masthead, dialogs — raised off the page */
  --d-paper-2: #37373e;      /* hovers, inputs, code */
  --d-muted: #adabac;        /* 4.7:1 on the card surface — clears AA */
  --d-faint: #918f97;
  --d-rule: #45454b;
  --d-rule-strong: #3b3b45;
  --d-accent: #dcb877;
  --d-accent-soft: #22222b;  /* highlight panels stay neutral; the accent shows
                                through the border and the text instead */
  --d-accent-2: #93b3d6;
  --d-green: #85c08d;
  --d-amber: #ddb46a;
  --d-red: #e0918a;
  --d-blue: #93b3d6;
  --d-green-bg: #182018;  --d-green-br: #2f4433;
  --d-amber-bg: #221e17;  --d-amber-br: #453a26;
  --d-red-bg:   #241a1a;  --d-red-br:   #4a302e;
  --d-blue-bg:  #181d24;  --d-blue-br:  #2d3b4c;
  /* Dark needs more alpha to register at all, but tighter — a wide soft
     shadow on a dark ground just muddies the page around the element. */
  --d-shadow-1: 0 1px 1px rgb(0 0 0 / .30), 0 4px 10px -4px rgb(0 0 0 / .45);
  --d-shadow-2: 0 2px 3px -1px rgb(0 0 0 / .35), 0 10px 20px -8px rgb(0 0 0 / .55);
  --d-shadow-3: 0 4px 6px -2px rgb(0 0 0 / .40), 0 18px 36px -12px rgb(0 0 0 / .65);
  --d-shadow: var(--d-shadow-1);
  --d-logo-filter: invert(1) hue-rotate(180deg) brightness(1.06) saturate(.7);
  --d-backdrop: rgba(0,0,0,.62);

  --serif: 'Cormorant Garamond', Georgia, serif;
  --sans: 'Jost', 'Segoe UI', system-ui, sans-serif;
  --radius: 3px;

  /* ── Working variables ────────────────────────────────────────────────
     Declared ONCE. light-dark() picks a side from `color-scheme`, so there
     is no second copy of this list to fall out of step — an earlier version
     kept the dark values in a duplicate block and a variable added to one
     and not the other made every card render light-on-light. */
  color-scheme: light dark;                    /* follow the OS by default */

  --ink:         light-dark(var(--l-ink),         var(--d-ink));
  --paper:       light-dark(var(--l-paper),       var(--d-paper));
  --surface:     light-dark(var(--l-surface),     var(--d-surface));
  --paper-2:     light-dark(var(--l-paper-2),     var(--d-paper-2));
  --muted:       light-dark(var(--l-muted),       var(--d-muted));
  --faint:       light-dark(var(--l-faint),       var(--d-faint));
  --rule:        light-dark(var(--l-rule),        var(--d-rule));
  --rule-strong: light-dark(var(--l-rule-strong), var(--d-rule-strong));
  --accent:      light-dark(var(--l-accent),      var(--d-accent));
  --accent-soft: light-dark(var(--l-accent-soft), var(--d-accent-soft));
  /* A second hue, with a job of its own. --accent means "you can act on this"
     — links, focus rings, today, the primary button. --accent-2 means "this is
     a measurement" — progress bars, score lines, the earnings chart. Splitting
     them is what lets a palette read as a palette instead of one tinted dot,
     and it stops a progress bar looking like something you can click. */
  --accent-2:    light-dark(var(--l-accent-2),    var(--d-accent-2));
  --backdrop:    light-dark(var(--l-backdrop),    var(--d-backdrop));

  --green:    light-dark(var(--l-green),    var(--d-green));
  --green-bg: light-dark(var(--l-green-bg), var(--d-green-bg));
  --green-br: light-dark(var(--l-green-br), var(--d-green-br));
  --amber:    light-dark(var(--l-amber),    var(--d-amber));
  --amber-bg: light-dark(var(--l-amber-bg), var(--d-amber-bg));
  --amber-br: light-dark(var(--l-amber-br), var(--d-amber-br));
  --red:      light-dark(var(--l-red),      var(--d-red));
  --red-bg:   light-dark(var(--l-red-bg),   var(--d-red-bg));
  --red-br:   light-dark(var(--l-red-br),   var(--d-red-br));
  --blue:     light-dark(var(--l-blue),     var(--d-blue));
  --blue-bg:  light-dark(var(--l-blue-bg),  var(--d-blue-bg));
  --blue-br:  light-dark(var(--l-blue-br),  var(--d-blue-br));

  /* A solid near-black button disappears on a dark page, so the primary
     action becomes gold there. Fields lift off the panel to stay findable. */
  --btn-bg:  light-dark(var(--l-ink),   var(--d-accent));
  --btn-fg:  light-dark(var(--l-paper), #16150f);
  /* A field is a hole, not a tile: it sits BELOW the surface it's cut into,
     which is the opposite of a card and is what stops a form reading as a
     stack of little panels. Mixed rather than set to the page colour flat,
     because plenty of fields sit directly on the page and would otherwise
     vanish into it. */
  --field-bg: light-dark(var(--l-paper),
                         color-mix(in srgb, var(--d-paper) 62%, var(--d-surface)));
  /* NOT light-dark(). That is a <color> function, so a shadow list inside it
     is invalid — and an invalid custom property takes the whole declaration
     with it, so `box-shadow: var(--field-inset)` computed to `none` and
     every input, select and textarea in the app rendered flat. Nothing
     announced it; the sunken look the rest of the design leans on simply
     was not there. Measured: CSS.supports('box-shadow', 'light-dark(inset
     0 1px 2px rgb(0 0 0 / .06), …)') is false, and getComputedStyle on any
     field returned boxShadow "none".
     Per-theme pair instead, selected with the other shadows below — which is
     what every other shadow token here already does. */
  --l-field-inset: inset 0 1px 2px rgb(0 0 0 / .06);
  --d-field-inset: inset 0 1px 3px rgb(0 0 0 / .32);
  --track:    light-dark(var(--l-rule),  var(--d-paper-2));
}

/* ── Type packs ────────────────────────────────────────────────────────
   A fourth appearance axis: which two families the whole app is set in.
   Everything already goes through --serif (headings, figures, the wordmark)
   and --sans (everything else), so a pack is nothing but a pair of stacks —
   no rule anywhere else has to know a pack exists.

   The webfonts these need are NOT in the @import at the top of this file.
   They are fetched by the boot script in base.html, and only when a pack is
   actually chosen, so the default set stays two families rather than seven.
   Each stack therefore ends in something already on the machine: if the
   request never lands, the app is set in a near neighbour instead of falling
   all the way back to Times. */

/* Vista-era Frutiger, which is where the glass style comes from in the first
   place. Segoe UI is already on every Windows machine, so on this one the
   pack costs no download at all. */
:root[data-font="aero"] {
  --serif: 'Segoe UI Semibold', 'Segoe UI', 'Source Sans 3', system-ui, sans-serif;
  --sans: 'Segoe UI', 'Source Sans 3', system-ui, sans-serif;
}

/* Angular Y2K. Chakra Petch rather than something wider like Orbitron: the
   headings here run to 34px and the figures to 30px, and a genuinely wide
   face at those sizes wraps the stat cards and the masthead. */
:root[data-font="techno"] {
  --serif: 'Chakra Petch', 'Rajdhani', 'Segoe UI', system-ui, sans-serif;
  --sans: 'Chakra Petch', 'Rajdhani', 'Segoe UI', system-ui, sans-serif;
  letter-spacing: .005em;
}

/* The media-player console: everything monospaced. Share Tech Mono rather
   than a true bitmap face like VT323 — the pixel fonts have a tiny x-height
   and this app is read at 13px for hours at a time. */
:root[data-font="terminal"] {
  --serif: 'Share Tech Mono', ui-monospace, Consolas, monospace;
  --sans: 'Share Tech Mono', ui-monospace, Consolas, monospace;
}
/* Monospace runs wide, so the two places with the least room to give get a
   little back rather than wrapping. */
:root[data-font="terminal"] h1 { font-size: 29px; letter-spacing: 0; }
:root[data-font="terminal"] .stat-figure { font-size: 26px; }


/* The toggle pins a side by switching color-scheme; every light-dark() above
   follows automatically. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

/* light-dark() is colours only, so these two non-colour properties still need
   a branch each. Keep this list at exactly these two. */
/* The top-edge highlight. Barely there on paper, where a raised card is
   already obvious from its shadow; a real catch of light on dark, where it
   is doing most of the work of separating a panel from the page. */
:root { --l-edge-light: rgb(255 255 255 / .55); --d-edge-light: rgb(255 255 255 / .07); }
:root { --shadow: var(--l-shadow); --logo-filter: var(--l-logo-filter);
        --edge-light: var(--l-edge-light); --field-inset: var(--l-field-inset);
        --shadow-1: var(--l-shadow-1); --shadow-2: var(--l-shadow-2); --shadow-3: var(--l-shadow-3); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --shadow: var(--d-shadow); --logo-filter: var(--d-logo-filter);
                                     --edge-light: var(--d-edge-light); --field-inset: var(--d-field-inset);
                                     --shadow-1: var(--d-shadow-1); --shadow-2: var(--d-shadow-2); --shadow-3: var(--d-shadow-3); }
}
:root[data-theme="dark"]  { --shadow: var(--d-shadow); --logo-filter: var(--d-logo-filter);
                            --edge-light: var(--d-edge-light); --field-inset: var(--d-field-inset);
                            --shadow-1: var(--d-shadow-1); --shadow-2: var(--d-shadow-2); --shadow-3: var(--d-shadow-3); }
:root[data-theme="light"] { --shadow: var(--l-shadow); --logo-filter: var(--l-logo-filter);
                            --edge-light: var(--l-edge-light); --field-inset: var(--l-field-inset);
                            --shadow-1: var(--l-shadow-1); --shadow-2: var(--l-shadow-2); --shadow-3: var(--l-shadow-3); }

/* ── Palettes ──────────────────────────────────────────────────────────
   Colour and brightness are separate choices. A palette re-points the --l-*
   and --d-* inputs above; light-dark() then picks a side exactly as before,
   so every palette works in light AND dark without a second set of rules.

   Only the neutrals and the accent move. Green/amber/red/blue carry meaning —
   paid, overdue, needs review — and a palette that recoloured those would be
   changing what the interface says, not how it looks. */
/* Each palette below is built from the reference swatches, not invented near-
   greys with one tinted dot. Where a supplied colour is dark enough to be a
   page or light enough to be a card, it IS the page or the card — Meadow's
   dark ground is literally Onyx, Blossom's is Space Indigo. The saturated ones
   become the two accents; anything too light to sit on white becomes a soft
   panel instead of being darkened past recognition. */

/* Ghost White · Antique White · Peach Fuzz · Periwinkle · Soft Periwinkle */
:root[data-palette="periwinkle"] {
  --l-ink:#221f36; --l-paper:#eeebfa; --l-surface-lit:#f8f7ff; --l-paper-2:#ffeedd;
  --l-muted:#645e83; --l-faint:#726b95; --l-rule:#ddd7f2; --l-rule-strong:#c3bbe4;
  --l-accent:#5a45cc; --l-accent-soft:#ffeedd; --l-accent-2:#a4601e;
  --d-ink:#eceaff; --d-paper:#17132b; --d-surface:#2f2a49; --d-paper-2:#3b335a;
  --d-muted:#b2aad6; --d-faint:#948db8; --d-rule:#4b426f; --d-rule-strong:#4d4180;
  --d-accent:#b8b8ff; --d-accent-soft:#1e1836; --d-accent-2:#ffd8be;
}
/* Honeydew · Light Cyan · Frosted Blue · Wisteria Blue · Medium Slate Blue */
:root[data-palette="frost"] {
  --l-ink:#152630; --l-paper:#dff1f6; --l-surface-lit:#e0faff; --l-paper-2:#eaf7ed;
  --l-muted:#4f6f7d; --l-faint:#58727d; --l-rule:#c6e2ea; --l-rule-strong:#a3ccd8;
  --l-accent:#5a54a8; --l-accent-soft:#eaf7ed; --l-accent-2:#17788a;
  --d-ink:#e2f4f9; --d-paper:#0b1d29; --d-surface:#1f3646; --d-paper-2:#234155;
  --d-muted:#a6c2d0; --d-faint:#8aa7b6; --d-rule:#2f5166; --d-rule-strong:#33627d;
  --d-accent:#9fe2ee; --d-accent-soft:#102434; --d-accent-2:#80a4d4;
}
/* Lavender · Amethyst Smoke · Light Green · Dark Slate Grey · Onyx */
:root[data-palette="meadow"] {
  --l-ink:#0e2119; --l-paper:#e5efe8; --l-surface-lit:#f4faf6; --l-paper-2:#d8e4ff;
  --l-muted:#4e6b62; --l-faint:#5d766d; --l-rule:#cbdfd3; --l-rule-strong:#a8c6b5;
  --l-accent:#35605a; --l-accent-soft:#d8e4ff; --l-accent-2:#8e5a8d;
  --d-ink:#e2f2e7; --d-paper:#00120b; --d-surface:#192f24; --d-paper-2:#1d3b30;
  --d-muted:#9dbcaa; --d-faint:#80a08e; --d-rule:#2a4b3c; --d-rule-strong:#2a5a44;
  --d-accent:#59ee99; --d-accent-soft:#0b291c; --d-accent-2:#c79bc6;
}
/* Ivory · Powder Blush · Bubblegum Pink · Dark Magenta · Space Indigo */
:root[data-palette="blossom"] {
  --l-ink:#17183b; --l-paper:#eef1e4; --l-surface-lit:#f8faf1; --l-paper-2:#ffe4d9;
  --l-muted:#5f5f7d; --l-faint:#6f6e8d; --l-rule:#ddddcb; --l-rule-strong:#c0c0ab;
  --l-accent:#a11692; --l-accent-soft:#ffe4d9; --l-accent-2:#c22a52;
  --d-ink:#efeaf6; --d-paper:#17183b; --d-surface:#2b2c5f; --d-paper-2:#343571;
  --d-muted:#aeacd2; --d-faint:#918fb8; --d-rule:#444577; --d-rule-strong:#43448c;
  --d-accent:#ff6b8e; --d-accent-soft:#1f2050; --d-accent-2:#ffb49a;
}
/* Canary Yellow · Lemon Lime · Green Yellow · Forest Green · Ebony */
:root[data-palette="citrus"] {
  --l-ink:#22301c; --l-paper:#eef2dd; --l-surface-lit:#f9fbee; --l-paper-2:#e3ebc4;
  --l-muted:#5c6a4c; --l-faint:#6a765a; --l-rule:#d8e0bd; --l-rule-strong:#bcc79b;
  --l-accent:#487d34; --l-accent-soft:#e9f2d4; --l-accent-2:#8a6d12;
  --d-ink:#eaf2dd; --d-paper:#171e11; --d-surface:#2c3623; --d-paper-2:#344128;
  --d-muted:#abb999; --d-faint:#8d9b7c; --d-rule:#425035; --d-rule-strong:#4c5f38;
  --d-accent:#afff3e; --d-accent-soft:#1d2615; --d-accent-2:#fffa70;
}
/* Grapefruit Pink · Cinnabar · Atomic Tangerine · Clay Soil · Deep Mocha */
:root[data-palette="ember"] {
  --l-ink:#33201c; --l-paper:#f7e9e3; --l-surface-lit:#fef8f4; --l-paper-2:#f2d9cd;
  --l-muted:#7a5a51; --l-faint:#886a60; --l-rule:#ecd2c5; --l-rule-strong:#d7b3a3;
  --l-accent:#b8392a; --l-accent-soft:#fae2d6; --l-accent-2:#4c3535;
  --d-ink:#f6e6df; --d-paper:#201513; --d-surface:#3b2d2b; --d-paper-2:#4a3834;
  --d-muted:#c3a79d; --d-faint:#a58a81; --d-rule:#5a4541; --d-rule-strong:#61443c;
  --d-accent:#ff783e; --d-accent-soft:#281a17; --d-accent-2:#ffa39f;
}

/* ── Styles ─────────────────────────────────────────────────────────────
   A third axis, and a different kind of one. A palette answers "which
   colours"; brightness answers "how dark". A style answers "what are these
   surfaces MADE of" — and that is a question about gradients, blur, rim
   light and corner radius, none of which a colour token can express. Every
   style reads its colour from whichever palette is set, so the two combine
   rather than competing: Glass over Harbour and Glass over Dusk are the
   same material in different light.

   Everything below is expressed as overrides of a handful of surface
   variables, so a style never has to restate a component's layout — only
   what it is made of. */

/* ── Glass ──────────────────────────────────────────────────────────────
   Frutiger Aero, in the specific sense: things are panes, not cards. Light
   comes from above and catches the top edge, the panel is faintly
   transparent so the page glows through it, and every corner is generously
   round. The gloss is a real top-half highlight rather than a border
   trick, because that is what actually reads as glass. */
:root[data-style="glass"] {
  --radius: 18px;
  /* Panes let a little of the page through and blur what's behind. Kept at
     82% rather than something more dramatic: below about 75% the text
     contrast measured on the card stops holding against a busy backdrop. */
  /* 86%, not lower. Measured against the strongest point of the aurora
     behind it: below 85% the secondary text on Frost and GuideStar in light
     mode drops under 4.5:1. Glass you can't read is just a window. */
  --pane: color-mix(in srgb, var(--surface) 86%, transparent);
  /* Glass gets its own light, not the hairline's. --edge-light is tuned to
     be almost invisible because it draws a single pixel; a specular sheen
     across the top third of a pane has to be several times stronger before
     it reads as a highlight at all rather than as a slightly paler edge. */
  --sheen: light-dark(rgb(255 255 255 / .78), rgb(255 255 255 / .16));
  --sheen-soft: light-dark(rgb(255 255 255 / .30), rgb(255 255 255 / .05));
  /* The Aqua signature is a hard stop, not a fade. A gloss that dissolves
     smoothly reads as "slightly lighter at the top"; one that ends in a
     crisp line halfway down reads as a curved, lacquered surface with a
     reflection on it. That single edge is most of the look. */
  --gloss: linear-gradient(180deg,
             var(--sheen) 0,
             var(--sheen-soft) 48%,
             transparent 48.5%,
             transparent 100%);
  /* Panels get the softer version — a full hard-stop across a whole card
     is too much lacquer; it belongs on things you press. */
  --gloss-soft: linear-gradient(180deg,
             var(--sheen) 0,
             var(--sheen-soft) 26%,
             transparent 58%),
           radial-gradient(120% 60% at 50% -12%,
             light-dark(rgb(255 255 255 / .55), rgb(255 255 255 / .10)),
             transparent 70%);
  /* Bevel: lit from above, shaded below, the way a moulded plastic control
     was drawn for about a decade. */
  --bevel: inset 0 1px 0 light-dark(rgb(255 255 255 / .95), rgb(255 255 255 / .34)),
           inset 0 -2px 3px light-dark(rgb(10 60 80 / .10), rgb(0 0 0 / .28));
  /* A thicker, luminous edge. In the reference the panels read as slabs of
     glass with a genuinely lit rim all the way round and a soft bloom
     spilling outside them — not a 1px hairline. Four layers: a bright top
     highlight, a full inner outline, a wide inner glow that fades toward
     the middle, and an outer bloom in the accent. */
  --rim:
    inset 0 1px 0 light-dark(rgb(255 255 255 / 1), rgb(255 255 255 / .42)),
    inset 0 -1px 0 light-dark(rgb(255 255 255 / .45), rgb(255 255 255 / .10)),
    inset 0 0 0 1px light-dark(rgb(255 255 255 / .70), rgb(255 255 255 / .16)),
    inset 0 0 26px -6px light-dark(rgb(255 255 255 / .70), rgb(255 255 255 / .12)),
    0 0 30px -12px color-mix(in srgb, var(--accent) 65%, transparent);
  --glass-blur: blur(18px) saturate(1.4);
  /* How strongly the wash behind the glass is tinted. A palette may raise it
     as far as its own text contrast allows — Aero is built for a vivid sky
     and has the headroom; the muted palettes do not and keep the default. */
  --aurora: 42%;
}

/* An aurora behind everything — two soft washes of the palette's own two
   accents. This is what stops a glass panel looking like a grey box: glass
   needs something behind it to be glass ABOUT. Fixed and non-interactive,
   and it sits under all content. */
:root[data-style="glass"] body::before {
  content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  /* Promoted to its own compositing layer. Without this the fixed aurora
     and the many backdrop-filtered panels above it share a layer, and
     Chromium repaints only the strip it thinks changed while scrolling —
     which is the horizontal tear across the page. Forcing the layer makes
     the backdrop a texture the compositor can just re-sample. */
  transform: translateZ(0);
  will-change: transform;
  background:
    radial-gradient(56% 42% at 10% -4%,
      color-mix(in srgb, var(--accent) calc(var(--aurora) * 1.00), transparent), transparent 68%),
    radial-gradient(50% 38% at 94% 4%,
      color-mix(in srgb, var(--accent-2) calc(var(--aurora) * 0.90), transparent), transparent 66%),
    radial-gradient(64% 44% at 74% 52%,
      color-mix(in srgb, var(--accent-2) calc(var(--aurora) * 0.48), transparent), transparent 70%),
    radial-gradient(80% 56% at 40% 106%,
      color-mix(in srgb, var(--accent) calc(var(--aurora) * 0.62), transparent), transparent 72%);
}

/* Bubbles. Real elements, one per sphere, because the point is that they
   drift independently — a single element carrying nine gradients can only
   move as one sheet, and reads as a texture sliding about rather than as
   bubbles rising. Also, the gradient version lived on body::after, which
   the grain texture had already claimed: whichever rule lost simply
   vanished, so picking Grain silently deleted the bubbles.

   Hidden entirely unless the Glass style is on, so every other style pays
   nothing for them. */
.aero-bubbles { display: none; }
:root[data-style="glass"] .aero-bubbles {
  display: block; position: fixed; inset: 0; z-index: -1;
  pointer-events: none; overflow: hidden;
  transform: translateZ(0);          /* same reason as the aurora above */
  opacity: var(--bubble-opacity, .55);
}
/* Each bubble is two layers. The span is WHERE it is: the rise animation
   owns its `transform`, and the cursor nudges it about on `translate` (which
   composes with the animation rather than replacing it). The ::before is
   its skin — the paint, and WHAT SHAPE it is: the cursor squashes it through
   --squish, a 2-D matrix set from app.js (bubblesInTheWater), and a slow
   keyframe on `scale` keeps it faintly wobbling even when nothing has
   touched it, the way a bubble rising through water never is quite round. */
:root[data-style="glass"] .aero-bubbles span {
  position: absolute;
  animation: aero-rise var(--dur, 34s) ease-in-out var(--delay, 0s) infinite;
  will-change: transform;
}
:root[data-style="glass"] .aero-bubbles span::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  /* Rim bright, body barely there, plus an off-centre specular dot. That
     dot is what makes a circle read as a sphere rather than a disc. */
  background:
    radial-gradient(circle at 32% 28%,
      light-dark(rgb(255 255 255 / .95), rgb(255 255 255 / .55)) 0 7%,
      transparent 8%),
    radial-gradient(circle at 68% 74%,
      light-dark(rgb(255 255 255 / .40), rgb(255 255 255 / .16)) 0 4%,
      transparent 5%),
    radial-gradient(circle at 50% 50%,
      transparent 58%,
      color-mix(in srgb, var(--accent) 45%, transparent) 76%,
      color-mix(in srgb, var(--accent) 18%, transparent) 88%,
      transparent 92%);
  box-shadow: 0 0 24px -6px color-mix(in srgb, var(--accent) 40%, transparent);
  transform: var(--squish, none);
  /* Smaller bubbles wobble quicker: the pace is tied to the rise, which is
     already quicker for the small ones. `alternate` with ease-in-out comes
     to rest at each end, so the reversal is a soft turn, not a bounce. */
  animation: aero-wobble calc(var(--dur, 34s) / 16) ease-in-out calc(var(--delay, 0s) / 3) infinite alternate;
  will-change: transform;
}
/* Each bubble gets its own size, lane and pace. Written out rather than
   generated so the spread is deliberate — a few big slow ones at the back,
   smaller quicker ones in front, none of them in step with each other. */
:root[data-style="glass"] .aero-bubbles span:nth-child(1) { width: 190px; height: 190px; left:  6%; top: 68%; --dur: 41s; --delay: -3s; }
:root[data-style="glass"] .aero-bubbles span:nth-child(2) { width: 110px; height: 110px; left: 22%; top: 84%; --dur: 33s; --delay: -12s; }
:root[data-style="glass"] .aero-bubbles span:nth-child(3) { width:  64px; height:  64px; left: 38%; top: 76%; --dur: 26s; --delay: -7s; }
:root[data-style="glass"] .aero-bubbles span:nth-child(4) { width: 148px; height: 148px; left: 55%; top: 90%; --dur: 46s; --delay: -19s; }
:root[data-style="glass"] .aero-bubbles span:nth-child(5) { width:  86px; height:  86px; left: 71%; top: 72%; --dur: 30s; --delay: -1s; }
:root[data-style="glass"] .aero-bubbles span:nth-child(6) { width: 220px; height: 220px; left: 83%; top: 88%; --dur: 52s; --delay: -25s; }
:root[data-style="glass"] .aero-bubbles span:nth-child(7) { width:  48px; height:  48px; left: 14%; top: 58%; --dur: 22s; --delay: -14s; }
:root[data-style="glass"] .aero-bubbles span:nth-child(8) { width: 128px; height: 128px; left: 46%; top: 64%; --dur: 38s; --delay: -30s; }
:root[data-style="glass"] .aero-bubbles span:nth-child(9) { width:  72px; height:  72px; left: 92%; top: 60%; --dur: 28s; --delay: -9s; }

/* Rising, with a lazy sideways wander so they don't travel in straight
   lines. Ends where it starts so the loop never jumps. */
@keyframes aero-rise {
  0%   { transform: translate3d(0, 0, 0) scale(1); opacity: 0; }
  8%   { opacity: 1; }
  50%  { transform: translate3d(26px, -46vh, 0) scale(1.06); }
  92%  { opacity: 1; }
  100% { transform: translate3d(-12px, -94vh, 0) scale(.94); opacity: 0; }
}
@keyframes aero-wobble {
  from { scale: 1.035 .965; }
  to   { scale: .965 1.035; }
}
@media (prefers-reduced-motion: reduce) {
  :root[data-style="glass"] .aero-bubbles span {
    animation: none;
    /* Still there, just still — the look survives without the motion. */
    transform: translate3d(0, -40vh, 0);
  }
  :root[data-style="glass"] .aero-bubbles span::before { animation: none; }
}
/* Bubbles switched off for the see-through window (Settings → Appearance →
   See-through), where they float over the desktop rather than over the
   page. data-bubbles="off" is only ever set on <html> in that window, so
   every other window keeps the Glass style's own. Not drawn at all rather
   than faded out: display: none stops the rise and the wobble outright,
   the animations are dropped as well so none is even computed, and
   bubblesInTheWater in app.js puts its loop to sleep while they are away.
   Same specificity as the Glass rules above, so it wins by coming after. */
:root[data-bubbles="off"] .aero-bubbles { display: none; }
:root[data-bubbles="off"] .aero-bubbles span,
:root[data-bubbles="off"] .aero-bubbles span::before { animation: none; will-change: auto; }

:root[data-style="glass"] .card,
:root[data-style="glass"] .stat,
:root[data-style="glass"] .ctxmenu,
:root[data-style="glass"] dialog,
:root[data-style="glass"] .cal {
  background-color: var(--pane);
  background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule));
  box-shadow: var(--rim), var(--shadow-2);
}
/* The card's own top hairline would double up with the rim, and two
   highlights a pixel apart looks like a rendering fault rather than light. */
/* Iridescence. Real glass — a jewel case, a MiniDisc shell, a soap film —
   splits light at its edges into faint rainbow bands, and it is that, not
   the transparency, that stops it reading as grey plastic. A conic gradient
   round the panel, masked to the border only so the interior stays clean
   and the text on it stays legible, at an opacity low enough to register as
   a sheen rather than as a rainbow. */
:root[data-style="glass"][data-iridescent="on"] .card::before,
:root[data-style="glass"][data-iridescent="on"] .stat::before {
  content: ''; position: absolute; inset: 0; z-index: 0;
  /* height and background must both be reset: the plain-style rule above
     makes this pseudo-element a 1px hairline pinned to the top edge, and
     inheriting that turned the ring into a stripe across the top. */
  height: auto; border-radius: inherit; pointer-events: none;
  padding: 1.5px;
  background: conic-gradient(from 210deg,
    #ff8fd0, #ffd08f, #b6ff9b, #8ff0ff, #a99bff, #ff8fd0);
  /* Mask to the ring: paint the whole box, then punch out everything but
     the border. Two masks intersected via xor. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  /* Not light-dark(): that is a <color> function, so in `opacity` the whole
     declaration is dropped and the ring has been painting at full strength
     rather than .42. Swapped per theme like --grain-strength instead. */
  opacity: var(--iris-ring-a);
  display: block;
}
/* The card's contents must sit above the ring. */
:root[data-style="glass"][data-iridescent="on"] .card > *,
:root[data-style="glass"][data-iridescent="on"] .stat > * { position: relative; z-index: 1; }
/* With iridescence off the hairline is unwanted here too — the glass rim
   already lights the top edge, and two highlights a pixel apart reads as a
   rendering fault rather than as light. */
:root[data-style="glass"]:not([data-iridescent="on"]) .card::before,
:root[data-style="glass"]:not([data-iridescent="on"]) .stat::before { display: none; }

:root[data-style="glass"] .masthead {
  /* A sticky element with a backdrop-filter has to re-read what is behind
     it on every scroll frame, and it is the one that tears first. Its own
     layer, so the compositor moves it rather than repainting it. */
  transform: translateZ(0);
  will-change: transform;
  background-color: color-mix(in srgb, var(--surface) 72%, transparent);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-bottom-color: color-mix(in srgb, var(--edge-light) 40%, var(--rule));
}

/* The settings search bar, which on glass was the one obvious flaw left: a
   sticky strip filled with an opaque, square-cornered var(--paper). Over a
   flat page that is invisible and correct — it is the page colour on the page
   colour, hiding whatever scrolls under it. Over the aurora it is a pale slab
   with hard corners pasted across the wash, which is the "weird square".

   So on this style it becomes what every other surface here already is: a
   translucent pane that blurs what passes behind it. The negative margin is
   what keeps the field and the buttons aligned with the cards below while the
   pane itself sits a little wider, the way a floating toolbar should. */
:root[data-style="glass"] .settings-bar {
  /* Sticky plus backdrop-filter is the combination that tears — the same one
     the masthead above had to be promoted for. */
  transform: translateZ(0);
  will-change: transform;
  padding: 10px 14px 12px;
  margin-left: -14px; margin-right: -14px;
  border-radius: var(--radius);
  background-color: var(--pane);
  background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--rim), var(--shadow-1);
}

/* Buttons are little panes too — same light, same curve. */
/* Buttons get their OWN gloss, weaker than a panel's. The panel sheen is
   78% white, which is right over pale translucent glass and completely
   wrong over a saturated fill — it bleached a deep teal button into a
   muddy two-tone band. An Aqua button's highlight sits nearer 40%: enough
   to look lacquered, not enough to lose the colour underneath. */
:root[data-style="glass"] .btn {
  border-radius: 999px;
  /* Translucent, like everything else made of glass here. A solid fill was
     the last thing on the page still reading as painted plastic. Measured
     across every palette with the button sitting over the strongest part of
     the aurora: at 68% the worst label contrast is 4.91, so this is as far
     as it goes without the text starting to suffer. */
  background-color: color-mix(in srgb, var(--btn-bg) 68%, transparent);
  border-color: color-mix(in srgb, var(--btn-bg) 68%, transparent);
  backdrop-filter: blur(10px) saturate(1.3);
  -webkit-backdrop-filter: blur(10px) saturate(1.3);
  background-image: linear-gradient(180deg,
    rgb(255 255 255 / .42) 0,
    rgb(255 255 255 / .16) 48%,
    rgb(255 255 255 / .02) 48.5%,
    rgb(255 255 255 / .10) 100%);
  box-shadow: var(--bevel), var(--shadow-1);
}
/* The nav's current page is the same lozenge, so the bar reads as one set
   of controls rather than one solid tab among glass ones. Solid, though, and
   under a lighter gloss than .btn's: translucent at 68% under a 38% sheen,
   its light words in the light theme measured 2.9-3.2:1 in every palette
   (lane L19, tests/test_appearance.py). */
:root[data-style="glass"] nav.main a.on {
  border-radius: 999px;
  background-color: var(--btn-bg);
  background-image: linear-gradient(180deg, rgb(255 255 255 / .26) 0, rgb(255 255 255 / .08) 48%, transparent 48.5%);
  box-shadow: var(--bevel);
}
/* A ghost button is mostly transparent already, so it takes the pale
   treatment the panels use rather than the button one. */
:root[data-style="glass"] .btn.ghost { background-image: var(--gloss); }

/* ── One rule for every tinted control ───────────────────────────────────
   Danger buttons and the coloured badges were the last things still
   painted in flat semantic colour, and the generic .btn rule above was
   actively making it worse: it put the solid --btn-bg fill behind a danger
   button that is meant to be transparent, which is why Pause came out as a
   dark olive lozenge with red text on it.

   They all become the same material: their own colour, heavily thinned,
   over blur, with the same gloss and pill as everything else. The colour
   still means what it meant — this only changes what it is made of. */
:root[data-style="glass"] .btn.danger {
  background-color: color-mix(in srgb, var(--red-bg) 82%, transparent);
  border-color: color-mix(in srgb, var(--red) 40%, var(--red-br));
  color: var(--red);
  box-shadow: var(--bevel), var(--shadow-1);
}
:root[data-style="glass"] .btn.danger:hover {
  background-color: var(--red-bg);
}

:root[data-style="glass"] .badge {
  border-radius: 999px;
  backdrop-filter: blur(8px) saturate(1.25);
  -webkit-backdrop-filter: blur(8px) saturate(1.25);
  background-image: linear-gradient(180deg,
    rgb(255 255 255 / .34) 0, rgb(255 255 255 / .10) 50%, transparent 50.5%);
  box-shadow: inset 0 1px 0 light-dark(rgb(255 255 255 / .85), rgb(255 255 255 / .22));
}
/* Built on each badge's OWN background colour rather than a thin wash of
   its foreground. Tinting with the strong colour looked right but measured
   badly — the dedicated -bg colours are pale for a reason, and thinning the
   foreground instead dropped every badge below AA. This keeps the existing
   colour relationship and only makes the material translucent. */
:root[data-style="glass"] .badge.green {
  background-color: color-mix(in srgb, var(--green-bg) 88%, transparent);
  border-color: color-mix(in srgb, var(--green) 40%, var(--green-br));
}
:root[data-style="glass"] .badge.amber {
  background-color: color-mix(in srgb, var(--amber-bg) 88%, transparent);
  border-color: color-mix(in srgb, var(--amber) 40%, var(--amber-br));
}
:root[data-style="glass"] .badge.red {
  background-color: color-mix(in srgb, var(--red-bg) 88%, transparent);
  border-color: color-mix(in srgb, var(--red) 40%, var(--red-br));
}
:root[data-style="glass"] .badge.blue {
  background-color: color-mix(in srgb, var(--blue-bg) 88%, transparent);
  border-color: color-mix(in srgb, var(--blue) 40%, var(--blue-br));
}
:root[data-style="glass"] .badge.ink {
  background-color: color-mix(in srgb, var(--btn-bg) 68%, transparent);
  border-color: color-mix(in srgb, var(--btn-bg) 68%, transparent);
}
/* The plain badge, which had no fill at all, gets a pane like the rest. */
:root[data-style="glass"] .badge:not(.green):not(.amber):not(.red):not(.blue):not(.ink) {
  background-color: color-mix(in srgb, var(--surface) 45%, transparent);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule-strong));
}
/* A press should look pressed: the gloss flips under, the bevel inverts. */
:root[data-style="glass"] .btn:active {
  background-image: linear-gradient(0deg, var(--sheen-soft) 0, transparent 55%);
  box-shadow: inset 0 2px 4px light-dark(rgb(10 60 80 / .22), rgb(0 0 0 / .40));
}
/* The primary button glows, because in 2007 everything important glowed. */
:root[data-style="glass"] .btn:not(.ghost):not(.danger) {
  box-shadow: var(--bevel), var(--shadow-1),
              0 0 18px -6px color-mix(in srgb, var(--accent) 70%, transparent);
}
:root[data-style="glass"] .btn.ghost {
  background-color: color-mix(in srgb, var(--surface) 42%, transparent);
  border-color: color-mix(in srgb, var(--edge-light) 60%, var(--rule-strong));
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}
:root[data-style="glass"] .badge,
:root[data-style="glass"] .pill { border-radius: 999px; }

/* Fields stay recessed — a sunken pane is still sunken — but pick up the
   curve so they don't read as square holes in round furniture. */
:root[data-style="glass"] input,
:root[data-style="glass"] select,
:root[data-style="glass"] textarea { border-radius: 10px; }

/* Lessons on the calendar get the same gloss, so the grid belongs to the
   same world as the chrome around it. */
:root[data-style="glass"] .ev {
  border-radius: 9px;
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--edge-light) 120%, transparent);
}

@media (prefers-reduced-transparency: reduce) {
  :root[data-style="glass"] .card,
  :root[data-style="glass"] .stat,
  :root[data-style="glass"] .ctxmenu,
  :root[data-style="glass"] dialog,
  :root[data-style="glass"] .cal,
  :root[data-style="glass"] .masthead {
    background-color: var(--surface);
    backdrop-filter: none; -webkit-backdrop-filter: none;
  }
  :root[data-style="glass"] body::before { opacity: .35; }
}

/* ── Texture ────────────────────────────────────────────────────────────
   A large flat fill of one colour is what makes a dark interface read as
   plastic. A fine grain over the whole page breaks that up: it is far too
   faint to see as dots, but the eye stops reading the background as a
   perfectly uniform sheet and the whole thing gains a bit of tooth.

   Fixed to the viewport rather than tiled per element, so it doesn't move
   independently of the page or stack up double on a card. pointer-events
   none so it never intercepts a click; aria-hidden isn't needed because a
   pseudo-element on <body> is not in the accessibility tree. */
:root[data-texture="grain"] body::after {
  content: ''; position: fixed; inset: 0; z-index: 9999; pointer-events: none;
  opacity: var(--grain-strength);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* Lighter touch on paper, where grain reads much more strongly than it does
   over a dark ground. */
:root { --iris-ring-a: .42; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --iris-ring-a: .30; }
}
:root[data-theme="dark"]  { --iris-ring-a: .30; }
:root[data-theme="light"] { --iris-ring-a: .42; }

:root { --grain-strength: .035; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --grain-strength: .055; }
}
:root[data-theme="dark"]  { --grain-strength: .055; }
:root[data-theme="light"] { --grain-strength: .035; }
@media (prefers-reduced-motion: reduce) { :root[data-texture="grain"] body::after { opacity: 0; } }

/* Sky · Grass · White — the 2007 desktop wallpaper, more or less. Made to
   sit under the Glass style, though it works plain too. */
:root[data-style="glass"][data-palette="aero"] {
  --bubble-opacity: .8;
  /* Measured: at 75% the worst text contrast through the glass is 4.62,
     still clear of 4.5. A sky is meant to be a colour, not a suggestion. */
  --aurora: 75%;
}
:root[data-palette="aero"] {
  --l-ink:#0b2b38; --l-paper:#dff2fb; --l-surface-lit:#f7fdff; --l-paper-2:#e2f7e4;
  --l-muted:#40707f; --l-faint:#4d7e8c; --l-rule:#c4e6f2; --l-rule-strong:#98cfe2;
  --l-accent:#0a7ea8; --l-accent-soft:#e2f7e4; --l-accent-2:#3f8f3f;
  --d-ink:#e2f6fd; --d-paper:#04161f; --d-surface:#123a4c; --d-paper-2:#17495e;
  --d-muted:#9ccadb; --d-faint:#7fb2c6; --d-rule:#1f5d76; --d-rule-strong:#2b7b9b;
  --d-accent:#5fd8ff; --d-accent-soft:#0a2532; --d-accent-2:#7ae08a;
}

/* ── Complementary palettes ────────────────────────────────────────────
   The six above are each built around a single hue, which is restful but
   goes flat across a whole screen: page, card, accent and text all sit in
   the same family, so nothing pulls the eye. These three are built the
   other way — the accent sits 90-150 degrees round the wheel from the
   ground it stands on, so there is a warm note against a cool field (or
   the reverse) on every page rather than only inside a chart. */

/* Deep Teal · Sea Glass · Warm Amber — cool ground, warm signal */
:root[data-palette="harbour"] {
  --l-ink:#10262b; --l-paper:#e3eff0; --l-surface-lit:#f5fbfb; --l-paper-2:#ffeedd;
  --l-muted:#4a6b70; --l-faint:#587a7f; --l-rule:#cfe3e4; --l-rule-strong:#a9c9cb;
  --l-accent:#186b74; --l-accent-soft:#ffeedd; --l-accent-2:#b3651a;
  --d-ink:#e4f3f4; --d-paper:#08191c; --d-surface:#16333a; --d-paper-2:#1d424a;
  --d-muted:#a3c4c8; --d-faint:#87acb1; --d-rule:#2b5b66; --d-rule-strong:#3a7885;
  --d-accent:#5fd3c4; --d-accent-soft:#0d2429; --d-accent-2:#ffb066;
}
/* Indigo Night · Coral · Cornflower — the contrast Blossom only hints at */
:root[data-palette="dusk"] {
  --l-ink:#1e1b33; --l-paper:#eceaf3; --l-surface-lit:#faf9ff; --l-paper-2:#ffe8e4;
  --l-muted:#5e5a7d; --l-faint:#6d6889; --l-rule:#dbd8e8; --l-rule-strong:#bfbad4;
  --l-accent:#c2413f; --l-accent-soft:#ffe8e4; --l-accent-2:#4d63b5;
  --d-ink:#ece9f7; --d-paper:#12111f; --d-surface:#282641; --d-paper-2:#33314f;
  --d-muted:#aca7c6; --d-faint:#8f8aad; --d-rule:#453f68; --d-rule-strong:#5a5382;
  --d-accent:#ff8f7a; --d-accent-soft:#1d1a2e; --d-accent-2:#9fb4ff;
}
/* Olive · Terracotta · Sage — earthy, and the only warm ground here */
:root[data-palette="sagebrush"] {
  --l-ink:#232619; --l-paper:#eceee2; --l-surface-lit:#f9faf3; --l-paper-2:#f7e3d5;
  --l-muted:#5e6450; --l-faint:#6d7360; --l-rule:#dcdfcd; --l-rule-strong:#bec2ab;
  --l-accent:#a1512a; --l-accent-soft:#f7e3d5; --l-accent-2:#4f6f52;
  --d-ink:#eef0e4; --d-paper:#14170f; --d-surface:#2b2f22; --d-paper-2:#373c2c;
  --d-muted:#b3b9a2; --d-faint:#959c84; --d-rule:#454b36; --d-rule-strong:#5c6349;
  --d-accent:#f0a06a; --d-accent-soft:#1c2015; --d-accent-2:#9ecf9c;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* The `hidden` attribute has to actually hide things. The UA rule for it is
   `[hidden] { display: none }` — one class's worth of specificity, which any
   `.btn { display: inline-flex }` or `.row { display: flex }` in this file
   silently beats. That is why "Clear done" sat on the dashboard permanently
   with nothing to clear, and why .ctxmenu needed its own patch below. Fixed
   once, here, rather than per component. */
[hidden] { display: none !important; }

/* Always reserve the scrollbar gutter. Without it a short page has no
   scrollbar and a long one does, so the whole masthead jumps 15px sideways
   every time you move between them. */
html { scrollbar-gutter: stable; }

/* The scrollbar is part of the window, not a browser detail. Left alone it
   is drawn in the OS default — a hard dark slab down the edge of a pale
   glass page. Both syntaxes: the standard one for Firefox and modern
   Chromium, the -webkit- pseudo-elements for the rest, because the two
   overlap but neither covers everything. */
html {
  scrollbar-color: color-mix(in srgb, var(--rule-strong) 80%, transparent) transparent;
  scrollbar-width: thin;
}
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--rule-strong) 70%, transparent);
  border-radius: 999px;
  /* Transparent border with background-clip is the trick that insets a
     thumb without a wrapper element. */
  border: 3px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--accent) 60%, var(--rule-strong));
  background-clip: content-box;
}
::-webkit-scrollbar-corner { background: transparent; }

/* Under glass the thumb becomes a little pane of its own. */
:root[data-style="glass"] ::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg,
      light-dark(rgb(255 255 255 / .75), rgb(255 255 255 / .30)),
      color-mix(in srgb, var(--accent) 45%, transparent));
  background-clip: content-box;
  box-shadow: inset 0 1px 0 light-dark(rgb(255 255 255 / .9), rgb(255 255 255 / .3));
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-weight: 400;
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--accent); }

/* ── Shell ─────────────────────────────────────────────────────────── */

.masthead {
  border-bottom: 1px solid var(--rule);
  background: var(--surface);
  position: sticky; top: 0; z-index: 50;
}

/* ── When the window has no title bar of its own ─────────────────────────
   With window-controls-overlay the OS draws only the three buttons, over
   the top-right of our page, and hands us the strip they sit in. These
   variables describe the space actually left: env(titlebar-area-width) is
   the page width minus the buttons. Outside that mode every one of them
   falls back to its second argument, so this costs nothing normally.

   -webkit-app-region: drag makes the masthead behave as the title bar it
   has replaced — you can pick the window up by it. Anything clickable
   inside has to opt back out with `no-drag`, or it becomes a drag handle
   and stops responding to clicks. */
@media (display-mode: window-controls-overlay) {
  .masthead {
    /* Sit in the overlay strip, and stop short of the system buttons. */
    padding-left: env(titlebar-area-x, 0px);
    width: env(titlebar-area-width, 100%);
    -webkit-app-region: drag;
    app-region: drag;
  }
  .masthead a, .masthead button, .masthead select, .masthead input {
    -webkit-app-region: no-drag;
    app-region: no-drag;
  }
  /* The page starts below the overlay strip rather than under it. */
  body { padding-top: env(titlebar-area-height, 0px); }
}
.masthead-inner {
  max-width: 1240px; margin: 0 auto; padding: 14px 28px;
  display: flex; align-items: center; gap: 28px;
}
.backbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; margin-right: -6px; padding: 0; cursor: pointer;
  background: transparent; border: 1px solid var(--rule-strong);
  border-radius: var(--radius); color: var(--muted);
  transition: background .12s, color .12s, border-color .12s;
}
.backbtn:hover { background: var(--paper-2); color: var(--ink); border-color: var(--muted); }
.backbtn svg { width: 16px; height: 16px; }
/* visibility, not display: the button keeps its place in the row either way,
   so a page with nowhere to go back to lays out identically to one that has
   somewhere. Hidden this way it is also out of the accessibility tree and
   can't be tabbed to, which display:none gave us and we shouldn't lose. */
.backbtn { visibility: hidden; }
:root[data-back="1"] .backbtn { visibility: visible; }

.brand { display: flex; align-items: center; gap: 11px; }
/* The mark is near-black line art on transparency, so it has to flip in dark. */
/* width:auto is load-bearing — without it the width="731" attribute in the
   markup becomes the used width and the mark renders enormous. The attribute
   is there for the aspect ratio, not the size; aspect-ratio states the same
   thing to CSS so the box is reserved before the PNG arrives, instead of the
   masthead reflowing the moment it does. */
.brand img { height: 30px; width: auto; aspect-ratio: 731 / 1185;
             filter: var(--logo-filter); }
.brand-text { line-height: 1; }
.brand-name {
  font-family: var(--serif); font-weight: 600; font-size: 20px;
  letter-spacing: .2em; text-transform: uppercase; padding-left: .2em;
}
/* --muted with a touch of ink, not --faint: at 8px --faint measured 3.6-4.5:1, and on Glass the aurora comes
   through the masthead - bare --muted still measured 4.37 on Aero's and Frost's vivid skies (lane L19,
   tests/test_appearance.py). */
.brand-sub {
  font-size: 8px; letter-spacing: .38em; text-transform: uppercase;
  color: color-mix(in srgb, var(--ink) 30%, var(--muted)); padding-left: .38em; margin-top: 3px;
}
nav.main { display: flex; gap: 4px; margin-left: auto; flex-wrap: wrap; }
/* --muted with a touch of ink, as .brand-sub: a page's link that is not the current one (People, beside
   Whiteboards - lane L3) measured 4.34-4.40:1 in bare --muted on Glass over the Aero, Frost and GuideStar skies. */
nav.main a {
  padding: 7px 13px; font-size: 12.5px; letter-spacing: .1em;
  text-transform: uppercase; color: color-mix(in srgb, var(--ink) 30%, var(--muted)); border-radius: var(--radius);
  transition: background .12s, color .12s;
}
nav.main a:hover { background: var(--paper-2); color: var(--ink); }
nav.main a.on { background: var(--btn-bg); color: var(--btn-fg); }

main { max-width: 1240px; margin: 0 auto; padding: 30px 28px 90px; }

/* ── Type ──────────────────────────────────────────────────────────── */

h1 { font-family: var(--serif); font-weight: 600; font-size: 34px; letter-spacing: -.01em; line-height: 1.1; }
h2 { font-family: var(--serif); font-weight: 600; font-size: 22px; letter-spacing: .01em; }
h3 { font-family: var(--serif); font-weight: 600; font-size: 17px; }
.eyebrow {
  font-size: 9.5px; letter-spacing: .3em; text-transform: uppercase;
  color: var(--faint); font-weight: 500;
}
.lede { color: var(--muted); font-size: 14px; }
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; margin-bottom: 26px; flex-wrap: wrap; }
.rule { height: 1px; background: var(--rule); margin: 22px 0; border: 0; }
.rule-dot { display: flex; align-items: center; gap: 7px; margin: 26px 0; }
.rule-dot::before, .rule-dot::after { content: ''; height: 1px; background: var(--rule); flex: 1; }
.rule-dot span { width: 4px; height: 4px; background: var(--rule-strong); transform: rotate(45deg); }
.mono { font-variant-numeric: tabular-nums; }

/* The sum row in "Owed to you". It is the figure the box exists to give, so
   it is the largest thing in it — the two rows above are its workings. Lining
   tabular figures, so the digits line up with those rows and sit on one
   baseline. */
.owed-total { padding-top: 13px; }
.owed-total > b { font-size: 19px; letter-spacing: -.01em; }
.owed-total .small b { font-size: 13px; }

/* ── Cards & grids ─────────────────────────────────────────────────── */

/* Raised, and made to look it. A panel only reads as sitting above the page
   if three things agree: it is lighter than the page, it casts a shadow, and
   its top edge catches a little light. The colours were carrying that on
   their own and only just — a card was 8-20% brighter than the page
   depending on palette, which is below the point at which the eye reads
   "separate object", so the whole screen collapsed into one flat field. */
.card {
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 20px;
  box-shadow: var(--shadow);
  position: relative;
}
/* The hairline along the top edge. One pixel of the light the panel would
   catch if it really were tilted up out of the page — the cheapest honest
   depth cue there is, and it costs no contrast anywhere else. */
.card::before, .stat::before {
  content: ''; position: absolute; inset: 0 0 auto; height: 1px;
  border-radius: var(--radius) var(--radius) 0 0;
  background: linear-gradient(to right,
    transparent, var(--edge-light) 18%, var(--edge-light) 82%, transparent);
  pointer-events: none;
}
.card + .card { margin-top: 16px; }
/* ...but inside a grid the gap already does the spacing, and a stray margin on
   every card except the first knocks that column out of alignment. */
.grid > .card + .card { margin-top: 0; }
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-bottom: 14px; }

/* Cards sitting side by side stretch to the tallest in the row, so pin their
   closing block to the bottom and the rules and buttons line up across all. */
.card.stacked { display: flex; flex-direction: column; }
.card.stacked .card-foot { margin-top: auto; }
.card.stacked .card-foot > .rule:first-child { margin-top: 22px; }
.grid { display: grid; gap: 16px; }
.g2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.g3 { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.g4 { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.split { display: grid; grid-template-columns: minmax(0,1.9fr) minmax(0,1fr); gap: 22px; align-items: start; }
@media (max-width: 940px) { .split { grid-template-columns: 1fr; } }

.stat {
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 16px 18px; background: var(--surface);
  box-shadow: var(--shadow); position: relative;
}
.stat .label { font-size: 9.5px; letter-spacing: .26em; text-transform: uppercase; color: var(--faint); }
/* The brand serif's figures are old-style by default — no spur on the "1",
   varying baselines on 3/5/7/9 — which reads fine in a sentence but not as a
   number you're meant to take in at a glance. Same fix as the day-label
   dates: lining, tabular figures, still in the serif that makes the tile. */
.stat .value {
  font-family: var(--serif); font-size: 30px; font-weight: 600; line-height: 1.15; margin-top: 5px;
  font-variant-numeric: lining-nums tabular-nums;
}
.stat .note { font-size: 12px; color: var(--muted); margin-top: 2px; font-variant-numeric: lining-nums tabular-nums; }
/* The featured tile takes its emphasis from a gold edge and gold figure,
   which works on paper and on graphite without tinting the fill brown. */
/* The featured tile in the SECOND accent, not the first. Every palette
   carries two — one for "you can act on this", one for "this is a
   measurement" — but the second was only ever reaching charts and progress
   bars, so on a page of stat tiles and lists it never appeared at all and
   the screen came out in a single hue. Money is a measurement, so this is
   the colour it was always meant to be, and it gives every palette a warm
   note against a cool field (or the reverse) on the pages you actually
   look at. The figure is 30px bold — large text — so the 3.0:1 threshold
   applies to it, and the smallest margin across ten palettes is 3.87. */
.stat.hi { background: var(--accent-soft); border-color: var(--accent-2); }
.stat.hi .value { color: var(--accent-2); }


/* ═══ tables, buttons and forms, drop-down lists, badges, progress (GuideStar app.css 1108–1454) ═══ */
/* ── Tables ────────────────────────────────────────────────────────── */

table { width: 100%; border-collapse: collapse; }
th {
  text-align: left; font-size: 9.5px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--faint); font-weight: 500; padding: 0 10px 8px; border-bottom: 1px solid var(--rule-strong);
}
td { padding: 11px 10px; border-bottom: 1px solid var(--rule); vertical-align: middle; font-size: 14px; }
tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--paper-2); }
td.r, th.r { text-align: right; }
.table-wrap { overflow-x: auto; }

/* ── Buttons & forms ───────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font-family: var(--sans); font-size: 12px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase;
  padding: 8px 15px; border: 1px solid var(--btn-bg); background: var(--btn-bg);
  color: var(--btn-fg); border-radius: var(--radius);
  /* Explicit, not `normal`. With normal line-height a button's height comes
     from the font's own metrics, so the same class rendered 32, 34 and 36px
     across this page depending on the family — most visibly on the type
     picker, where each button previews a different one. 18px reproduces the
     36px every other button already was, and makes it font-independent. */
  line-height: 18px;
  transition: opacity .12s, background .12s, color .12s;
}
.btn:hover { opacity: .84; color: var(--btn-fg); }
.btn.ghost { background: transparent; color: var(--ink); border-color: var(--rule-strong); }
.btn.ghost:hover { background: var(--paper-2); color: var(--ink); opacity: 1; }
.btn.sm { padding: 5px 10px; font-size: 10.5px; line-height: 16px; }
.btn.danger { background: transparent; color: var(--red); border-color: var(--red-br); }
.btn.danger:hover { background: var(--red-bg); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }

label { display: block; font-size: 9.5px; letter-spacing: .2em; text-transform: uppercase; color: var(--faint); margin-bottom: 5px; font-weight: 500; }
input, select, textarea {
  width: 100%; font-family: var(--sans); font-size: 14px; color: var(--ink);
  background: var(--field-bg); border: 1px solid var(--rule-strong);
  border-radius: var(--radius); padding: 8px 10px;
  box-shadow: var(--field-inset);
}
option, optgroup { background: var(--surface); color: var(--ink); }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); }
textarea { min-height: 84px; resize: vertical; line-height: 1.5; }
.field { margin-bottom: 14px; }
/* A field-shaped readout: the same sunken pane as an input, for a value the
   page is telling you rather than asking you for.

   It exists because hiding a control that cannot do anything also hid what
   it was reporting. With one model on the account there is nothing to pick
   — but you still need to know which model is writing your questions, and a
   missing row answers that with silence.

   Sunken, never raised: raised and glossy is this app's word for "press me".
   A softer edge than an input (--rule, not --rule-strong) and no focus ring
   is what separates "information" from "a field that won't let you type". */
.field-readout {
  width: 100%; font-size: 14px; color: var(--ink);
  background: var(--field-bg); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 8px 10px;
  box-shadow: var(--field-inset);
  display: flex; align-items: center; gap: 8px; cursor: default;
}
/* One line, exactly as tall as the input beside it: a readout that wraps
   makes its field taller than the rest of the row, and a row aligned to
   its bottom edge then lifts that one label above the others. What will
   not fit is in the tooltip, which is where the detail lives anyway. */
.field-readout.one-line {
  display: block; line-height: normal;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── Drop-down lists, drawn by the page ─────────────────────────────────
   A native <select> on Windows opens its list in a separate OS window, and
   that window is shown black until its first frame has been composited. On
   a page with blur behind every card and bubbles always in motion, that
   frame is late — so every menu on the curriculum path went black for a
   beat before its names arrived. Nothing about the page's own speed could
   fix it; the delay was the second window.

   `appearance: base-select` has the browser draw the list inside the page
   instead. No second window, so nothing to flash; and the list is ordinary
   CSS, so it can be made of the same material as the app's other menu
   (.ctxmenu) instead of whatever the OS decided — and it is glass under the
   glass style, which an OS popup can never be.

   Wrapped in @supports on purpose. A browser without it must keep its
   native list exactly as it was, and an unknown pseudo-element in a
   selector list would otherwise invalidate every rule it shares one with. */
@supports (appearance: base-select) {
  select, select::picker(select) { appearance: base-select; }

  /* The closed control becomes a flex box in this mode, with its items
     aligned `normal` — which for a fixed-height item means the TOP of the
     line. That put the arrow in the control's top corner. Centre them: the
     label and the arrow both sit on the control's midline. */
  select { align-items: center; }

  /* The arrow. A chevron from two borders rather than a glyph, so it is
     the same crisp shape in every type pack. The rotated square's box is
     centred by the rule above; the visible V is the lower half of that
     box, so it is lifted a little to sit on the midline optically. */
  select::picker-icon {
    content: ''; display: block; flex: none; align-self: center;
    width: 7px; height: 7px; margin-left: 8px;
    border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted);
    transform: translateY(-2px) rotate(45deg);
    transition: transform .18s;
  }
  select:open::picker-icon { transform: translateY(2px) rotate(225deg); }

  /* The list itself: a .ctxmenu, exactly. */
  select::picker(select) {
    padding: 5px; margin: 4px 0;
    background: var(--surface); border: 1px solid var(--rule-strong);
    border-radius: var(--radius); box-shadow: var(--shadow-3);
    max-height: min(60vh, 420px); overflow-y: auto; overscroll-behavior: contain;
    /* Opens by unfolding from the control, closes the same way back. Short,
       eased out, and off entirely for anyone who has asked for no motion —
       a menu that appears instantly is the right answer there, not one
       that appears slowly. */
    transform-origin: top center;
    transition: opacity .16s ease-out, transform .16s cubic-bezier(.2, .8, .3, 1.15),
                display .16s allow-discrete, overlay .16s allow-discrete;
  }
  select::picker(select):not(:popover-open) { opacity: 0; transform: translateY(-4px) scale(.98); }
  @starting-style {
    select::picker(select):popover-open { opacity: 0; transform: translateY(-4px) scale(.98); }
  }
  @media (prefers-reduced-motion: reduce) {
    select::picker(select) { transition: none; }
  }

  select option {
    display: flex; align-items: center; gap: 8px;
    padding: 6px 9px; border-radius: 5px;
    font-size: 13px; color: var(--ink); background: transparent;
    /* Reset what a group label sets on its optgroup (below), because the
       options inside inherit it. */
    text-transform: none; letter-spacing: normal; font-weight: 400;
    cursor: pointer;
  }
  select option:hover, select option:focus-visible { background: var(--paper-2); outline: none; }
  select option:checked { font-weight: 600; }
  /* The tick. Accent-coloured, and reserved for every row whether checked
     or not, so the names line up down the list. */
  select option::checkmark { color: var(--accent); width: 1em; flex: none; }
  /* A group's label (.menu-label, in the context menu). Chromium draws the
     label itself, inside the optgroup, from its `label` attribute — there is
     no legend and no ::before to style, and the label is reachable only
     through what the optgroup passes down. So the group carries the label's
     type, and each option resets it (above). Verified: neither
     `optgroup::before` nor `optgroup legend` matches anything. */
  select optgroup {
    padding: 6px 9px 2px; font-size: 10px; letter-spacing: .1em;
    text-transform: uppercase; color: var(--faint);
  }
  select optgroup option { margin: 0 -9px; }

  /* Glass: the list is a pane like the cards, not a painted box over them. */
  :root[data-style="glass"] select::picker(select) {
    background-color: var(--pane); background-image: var(--gloss-soft);
    backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
    border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule));
    box-shadow: var(--rim), var(--shadow-2);
  }
  @media (prefers-reduced-transparency: reduce) {
    :root[data-style="glass"] select::picker(select) {
      background-color: var(--surface);
      backdrop-filter: none; -webkit-backdrop-filter: none;
    }
  }
}
/* Sits under the minutes box rather than inside it — a number input's spinner
   already owns the right-hand edge. Hidden when empty so a blank field has no
   stray line under it. */
.dur-hint {
  display: block; margin-top: 5px;
  font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.dur-hint:empty { display: none; }
.form-row { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.inline-form { display: inline; }
.check { display: flex; align-items: center; gap: 8px; }
.check input { width: auto; }
.check label { margin: 0; text-transform: none; letter-spacing: .02em; font-size: 13.5px; color: var(--ink); }

/* ── Badges ────────────────────────────────────────────────────────── */

.badge {
  display: inline-block; font-size: 9.5px; letter-spacing: .14em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 100px; border: 1px solid var(--rule-strong);
  color: var(--muted); background: transparent; white-space: nowrap; font-weight: 500;
}
.badge.green { color: var(--green); border-color: var(--green-br); background: var(--green-bg); }
.badge.amber { color: var(--amber); border-color: var(--amber-br); background: var(--amber-bg); }
.badge.red   { color: var(--red);   border-color: var(--red-br);   background: var(--red-bg); }
.badge.blue  { color: var(--blue);  border-color: var(--blue-br);  background: var(--blue-bg); }
.badge.ink   { color: var(--btn-fg); background: var(--btn-bg); border-color: var(--btn-bg); }

/* A connection's state, in the schedule toolbar.

   These sit in a row with Export PNG and Re-sync, and the first attempt at
   telling them apart took the container away from the connected one — a bare
   dot and a word floating between two glossy pills. In a language where every
   control is a lozenge, a label with no lozenge does not read as "quiet", it
   reads as unfinished.

   The distinction the style already makes is depth: buttons are raised and
   lacquered, fields are sunken. "A sunken pane is still sunken" — so these
   keep the pill, the size and the rhythm of the row, and give up the gloss
   and the lift instead. Pressable things stand up; readouts are set into the
   surface. Which is also true of them: they report a state, and the click
   through to the setup page is a convenience, not the point.

   The dot carries the actual status, so the two chips stay a matching pair
   rather than one shouting and one whispering.

   The state classes are prefixed rather than named `ok` / `todo`: `.todo` is
   already the dashboard's to-do row, and at equal specificity the later rule
   wins — it was quietly handing this chip `display: flex`, 8px padding and a
   9px radius, which is exactly the mismatched pair this was meant to fix. */
.conn {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--sans); font-size: 9.5px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; white-space: nowrap;
  padding: 3px 10px; border-radius: 999px;
  /* A touch of ink into --muted. Bare --muted is 4.14:1 against this chip's
     own tinted ground at worst across the light palettes, and at 9.5px
     uppercase that is under the 4.5 line; no amount of thinning the tint
     fixes it, because --muted on bare paper only reaches 4.63 to begin with.
     15% is the smallest push that clears AA everywhere (4.87 light, 7.44
     dark) and it stays 5.7 contrast-points below full ink, so the chip is
     still visibly the quiet thing next to the buttons.

     Measured from the raw per-theme tokens rather than getComputedStyle: in
     Chromium, flipping data-theme re-resolves a light-dark() background but
     leaves an inherited light-dark() COLOUR reporting its old value, so
     reading colours back after a theme switch measures the wrong theme. */
  color: color-mix(in srgb, var(--ink) 15%, var(--muted));
  background-color: color-mix(in srgb, var(--ink) 5%, transparent);
  border: 1px solid var(--rule);
  text-decoration: none;
  transition: color .12s, border-color .12s, background-color .12s;
}
/* The yellow the unfinished chip is tinted with. Deliberately NOT --amber:
   that token is #946923, a dark brown-gold, chosen to be read as TEXT on a
   pale ground. Mixed into a pane it does not tint it yellow, it darkens it —
   the chip came out browner and heavier than the one beside it, which is the
   opposite of the tinted-glass it is meant to be. A glass tint has to be a
   light, saturated version of the hue, so that adding it to the pane moves
   the colour without moving the brightness. --amber stays as the text and
   the dot, where being dark is the whole point. */
:root { --amber-glass: #ffc83c; }

.conn-dot {
  flex: none; width: 6px; height: 6px; border-radius: 50%;
  /* A pinpoint of its own light, so at 6px it still reads as a lit indicator
     rather than as a full stop. */
  box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 18%, transparent);
}
.conn.conn-ready .conn-dot { color: var(--green); background: var(--green); }
.conn.conn-setup .conn-dot { color: var(--amber); background: var(--amber); }
/* Only the unfinished one tints its whole chip — enough to catch the eye in a
   row of grey, not enough to outshout the buttons. */
.conn.conn-setup {
  color: var(--amber);
  background-color: color-mix(in srgb, var(--amber-glass) 20%, transparent);
  border-color: color-mix(in srgb, var(--amber-glass) 60%, transparent);
}
.conn:hover      { color: var(--ink); border-color: var(--rule-strong); }
.conn.conn-setup:hover { color: var(--amber); border-color: var(--amber); }

/* Sunken, in the style that has a word for it. No gloss and no lift: the two
   things that mark a pressable control here.

   Properly glass, not just tinted — the same blur the ghost buttons and panes
   use, so the aurora moves behind these too. The highlight is on the BOTTOM
   inner edge rather than the top: a well catches its light on the far side,
   which is the same physics that puts the gloss on the top of a raised pill. */
:root[data-style="glass"] .conn {
  background-color: color-mix(in srgb, var(--ink) 6%, transparent);
  border-color: color-mix(in srgb, var(--rule-strong) 70%, transparent);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 2px light-dark(rgb(10 60 80 / .13), rgb(0 0 0 / .30)),
              inset 0 -1px 0 var(--sheen-soft, rgb(255 255 255 / .28));
}
/* Same pane as the chip beside it, tinted rather than filled. Not
   --amber-bg, which is an opaque cream swatch for flat panels and turned this
   into a painted sticker lying on the glass; and not --amber either, which is
   dark enough to weigh the pane down instead of colouring it. See
   --amber-glass above. */
:root[data-style="glass"] .conn.conn-setup {
  background-color: color-mix(in srgb, var(--amber-glass) 24%, transparent);
  border-color: color-mix(in srgb, var(--amber-glass) 62%, transparent);
}
:root[data-style="glass"] .conn.conn-setup:hover {
  background-color: color-mix(in srgb, var(--amber-glass) 34%, transparent);
}

/* A hair of space, not a rule: enough to break the run of pills without
   drawing a line across a toolbar that has no other lines in it. */
.conn-sep { display: inline-block; width: 7px; }
@media (prefers-reduced-motion: reduce) { .conn { transition: none; } }

.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 7px; vertical-align: 1px; }

/* ── Progress ──────────────────────────────────────────────────────── */

.bar { height: 5px; background: var(--track); border-radius: 100px; overflow: hidden; }
.bar > i { display: block; height: 100%; background: var(--accent-2); border-radius: 100px; transition: width .25s; }
.bar.tall { height: 7px; }

.topic-tree { font-size: 14px; }
.topic-group { border-top: 1px solid var(--rule); padding: 12px 0; }
.topic-group:first-child { border-top: 0; }
.topic-group > summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: 12px; }
.topic-group > summary::-webkit-details-marker { display: none; }
.topic-group > summary::before { content: '›'; color: var(--faint); font-size: 17px; transition: transform .15s; line-height: 1; }
.topic-group[open] > summary::before { transform: rotate(90deg); }
.topic-title { font-family: var(--serif); font-size: 16.5px; font-weight: 600; flex: 1; }
.topic-code { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }
.topic-list { margin: 10px 0 2px 22px; }
.topic-row {
  display: grid; grid-template-columns: 1fr auto auto; gap: 12px; align-items: center;
  padding: 6px 0; border-bottom: 1px dotted var(--rule);
}
/* A week of the teaching plan. Thirty-six of them on the page and all but
   three or four below the fold, so the ones out of view are not laid out
   until scrolled towards — the same arrangement as the Library's groups.
   The placeholder is a typical week's height; `auto` keeps the real height
   once a card has been rendered, so nothing jumps on the way back up.
   Anchors (#week-12, where "Move to" lands you) still work: the browser
   renders the target before scrolling to it. */
.week-card {
  content-visibility: auto;
  contain-intrinsic-size: auto 340px;
}
.topic-row:last-child { border-bottom: 0; }
.topic-row .name { font-size: 13.5px; }
.topic-row select { width: auto; font-size: 11.5px; padding: 3px 6px; color: var(--muted); }
.topic-meta { font-size: 11px; color: var(--faint); }


/* ═══ lists, flash, tabs, dialogs (GuideStar app.css 1577–1650) ═══ */
/* ── Lists ─────────────────────────────────────────────────────────── */

.list { list-style: none; }
.list li { padding: 11px 0; border-bottom: 1px solid var(--rule); display: flex; gap: 12px; align-items: center; }
.list li:last-child { border-bottom: 0; }
.list .grow { flex: 1; min-width: 0; }
.list .sub { font-size: 12px; color: var(--muted); }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.empty { text-align: center; padding: 34px 20px; color: var(--faint); font-size: 13.5px; }
.empty .serif { font-family: var(--serif); font-size: 18px; color: var(--muted); display: block; margin-bottom: 5px; }

/* ── Flash ─────────────────────────────────────────────────────────── */

.flash {
  border: 1px solid var(--rule-strong); background: var(--accent-soft);
  padding: 11px 15px; border-radius: var(--radius); margin-bottom: 20px; font-size: 13.5px;
  display: flex; align-items: flex-start; gap: 10px;
}
.flash.warn { background: var(--amber-bg); border-color: var(--amber-br); color: var(--amber); }
.flash button { background: none; border: 0; cursor: pointer; color: inherit; font-size: 16px; line-height: 1; opacity: .5; }

/* ── Tabs ──────────────────────────────────────────────────────────── */

.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--rule); margin-bottom: 24px; flex-wrap: wrap; }
.tabs a {
  padding: 9px 15px; font-size: 11.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs a.on { color: var(--ink); border-bottom-color: var(--ink); }

/* ── Dialog ────────────────────────────────────────────────────────── */

/* `margin: auto` is what centres a modal <dialog>, and the `*` reset above
   wipes it — without this every dialog pins to the top-left corner.
   --dx/--dy are the drag offset, so dragging never disturbs the centring. */
dialog {
  border: 1px solid var(--rule-strong); border-radius: var(--radius); padding: 0;
  background: var(--surface); color: var(--ink); box-shadow: var(--shadow);
  max-width: 620px; width: calc(100% - 40px);
  margin: auto;
  --dx: 0px; --dy: 0px;
  transform: translate(var(--dx), var(--dy));
}
dialog::backdrop { background: var(--backdrop); }
dialog .dlg-head { padding: 18px 22px 12px; border-bottom: 1px solid var(--rule); display: flex; justify-content: space-between; align-items: center; cursor: move; }
/* Buttons in the header stay clickable rather than becoming drag handles. */
dialog .dlg-head button, dialog .dlg-head a, dialog .dlg-head input, dialog .dlg-head select { cursor: pointer; }
dialog.dragging { user-select: none; }
dialog.dragging .dlg-head { cursor: grabbing; }
dialog .dlg-body { padding: 20px 22px; max-height: 68vh; overflow-y: auto; }
dialog .dlg-foot { padding: 14px 22px; border-top: 1px solid var(--rule); display: flex; justify-content: flex-end; gap: 10px; background: var(--paper-2); }


/* Held for one frame while the palette changes: matches everything, which
   forces the restyle, and kills transitions so the page doesn't animate
   through an intermediate colour on the way. */
:root.theme-swap, :root.theme-swap * {
  transition: none !important; animation: none !important;
}

/* Replaces the browser's confirm(). Narrower than a form dialog — it holds one
   question — and with no header bar, since there is nothing to title or drag. */
dialog.confirm { max-width: 420px; }
dialog.confirm .dlg-body { padding: 22px 22px 18px; }
.confirm-q {
  font-family: var(--serif); font-size: 18px; line-height: 1.35;
  font-weight: 400; margin: 0; color: var(--ink);
}
.confirm-note {
  margin: 10px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--muted);
  white-space: pre-line;
}


/* ═══ utility (GuideStar app.css 1872–1890) ═══ */
/* ── Utility ───────────────────────────────────────────────────────── */

.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.row.tight { gap: 6px; }
.right { margin-left: auto; }
.muted { color: var(--muted); }
.faint { color: var(--faint); }
.red { color: var(--red); }
.small { font-size: 12px; }
.tiny { font-size: 11px; }
.serif { font-family: var(--serif); }
.nowrap { white-space: nowrap; }
.mt { margin-top: 16px; }
.mt-lg { margin-top: 28px; }
.mb { margin-bottom: 16px; }
.pre { white-space: pre-wrap; }
.code { font-family: 'DM Mono', ui-monospace, Consolas, monospace; font-size: 12px; background: var(--paper-2); padding: 2px 6px; border-radius: 2px; }
.hide { display: none !important; }


/* ═══ the right-click menu (GuideStar app.css 2455–2511) ═══ */
/* Right-click menu on a lesson. Fixed-position so it isn't clipped by the
   calendar's own overflow. */
/* A fixed width, not a min-width: the menu is measured before it is moved into
   place, and a width that depends on where it sits would rewrap the labels and
   change its height, leaving the clamp that keeps it on screen working from a
   stale number. max-height covers the case where it is simply taller than the
   window — better a scrollable menu than a "Delete" you cannot reach. */
.ctxmenu {
  position: fixed; z-index: 200; width: 244px; padding: 5px;
  max-height: calc(100vh - 16px); overflow-y: auto; overscroll-behavior: contain;
  background: var(--surface); border: 1px solid var(--rule-strong);
  border-radius: var(--radius); box-shadow: var(--shadow-3);
}
.ctxmenu[hidden] { display: none; }
.menu-head {
  padding: 6px 9px 7px; font-size: 11.5px; color: var(--faint);
  border-bottom: 1px solid var(--rule); margin-bottom: 4px;
  overflow: hidden;
}
/* Two lines now — who and when, then what. Each truncates on its own rather
   than the pair sharing one nowrap, which would have hidden the subjects
   entirely the moment the first line filled the width. */
.menu-head, .menu-head i { display: block; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }
.menu-head i { margin-top: 1px; }
.menu-item {
  display: block; width: 100%; text-align: left;
  padding: 6px 9px; border: 0; border-radius: 5px; background: transparent;
  font: inherit; font-size: 13px; color: var(--ink);
  text-decoration: none; cursor: pointer;
}
.menu-item:hover { background: var(--paper-2); }
/* The shortcut, shown so the keyboard route is discoverable from the menu
   rather than being something you had to be told about. */
.menu-key {
  float: right; margin-left: 12px;
  font-size: 10.5px; font-style: normal; color: var(--faint);
  font-variant-numeric: tabular-nums;
}
/* A line that explains rather than does — no hover, no pointer. */
.menu-item.is-note { color: var(--faint); font-size: 12px; cursor: default; }
.menu-item.is-note:hover { background: transparent; }
.menu-head i { font-style: normal; color: var(--muted); }
.menu-item.danger { color: var(--red); }
.menu-rule { height: 1px; background: var(--rule); margin: 4px 0; }
.menu-label {
  padding: 4px 9px 2px; font-size: 10px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--faint);
}
/* Subjects in the menu carry their own colour, so picking one is picking the
   colour too — the tag and the tint can't drift apart. */
.subject-item { display: flex; align-items: center; gap: 9px; }
.subject-dot {
  flex: none; width: 10px; height: 10px; border-radius: 50%;
  border: 1px solid rgb(0 0 0 / .18);
}
.menu-item.on { background: var(--accent-soft); font-weight: 500; }
.menu-item.on::after { content: "✓"; margin-left: auto; color: var(--accent); }

/* ═══ the theme toggle (GuideStar app.css 2690–2714) ═══ */
/* ── Theme toggle ──────────────────────────────────────────────────── */

/* Sign out (templates/base.html, signed in) is the same small button beside it, and so are the Appearance panel's
   paint palette before it and the panel's own close button. */
.theme-toggle, .signout-btn, .look-btn, .ap-close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; cursor: pointer;
  background: transparent; border: 1px solid var(--rule-strong);
  border-radius: var(--radius); color: var(--muted); line-height: 1;
  transition: background .12s, color .12s, border-color .12s;
}
.theme-toggle:hover, .signout-btn:hover, .look-btn:hover, .ap-close:hover { background: var(--paper-2); color: var(--ink); border-color: var(--muted); }
.theme-toggle svg, .signout-btn svg, .look-btn svg, .ap-close svg { width: 15px; height: 15px; }
/* Its form takes no room of its own: the button sits in the nav's row like the rest. */
nav.main form.signout { display: contents; }
.theme-toggle .sun { display: none; }
.theme-toggle .moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .sun { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .moon { display: none; }
}
:root[data-theme="dark"] .theme-toggle .sun { display: block; }
:root[data-theme="dark"] .theme-toggle .moon { display: none; }
:root[data-theme="light"] .theme-toggle .sun { display: none; }
:root[data-theme="light"] .theme-toggle .moon { display: block; }

/* No per-theme component overrides live here any more — the variables above
   already carry both sides. */

/* ═══ the Appearance panel (lane L19, 30 Sep 2026: templates/appearance.html, js/appearance.js) ═══ */
/* A dialog, so on Glass it is already a pane like the cards (the glass rules above): these only lay it out. It
   opens under the masthead at the right, where its button is, and dims nothing - the page behind is what is
   being judged, so it stays in full view (the backdrop is clear, and a click on it closes the panel).

   Every word in it is --ink or --muted on the pane, and the chosen choice --btn-fg on an opaque --btn-bg: each
   of those measured at 4.5:1 or more in every palette, light and dark, glass and plain (tests/test_look.py). No
   --faint here: on the glass over the aurora it falls short. */
dialog.appearance {
  width: min(384px, calc(100% - 24px)); max-width: none;
  margin: 62px 16px auto auto;
  max-height: calc(100vh - 78px); max-height: calc(100dvh - 78px);
  overflow: hidden;
}
dialog.appearance[open] { display: flex; flex-direction: column; }
dialog.appearance::backdrop { background: transparent; }
/* A popped-out whiteboard has no masthead to sit under. */
body.bare dialog.appearance { margin-top: 12px; }
@media (max-width: 520px) { dialog.appearance { margin: 62px auto auto; } }

.ap-head { display: flex; align-items: center; justify-content: space-between; gap: 12px;
           padding: 16px 20px 12px; border-bottom: 1px solid var(--rule); }
.ap-head h2 { font-size: 22px; line-height: 1.2; }
.ap-body { overflow-y: auto; overscroll-behavior: contain; padding: 2px 20px 6px; }
.ap-group { padding: 12px 0 14px; border-bottom: 1px solid var(--rule); }
.ap-group:last-child { border-bottom: 0; }
/* The group names in --ink, not --muted: in the dark theme the pane's own sheen (--gloss-soft) lightens its top
   third, and --muted there measured 3.4-4.4:1. */
.ap-label {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 8px;
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink); font-weight: 500;
}
.ap-now { font-weight: 600; color: var(--ink); letter-spacing: .08em; }
.ap-note { font-size: 12px; line-height: 1.45; color: var(--muted); margin-top: 7px; }
.ap-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px;
           padding: 12px 20px 14px; border-top: 1px solid var(--rule); }
.ap-foot .ap-note { margin: 0; text-align: right; }

/* A row of choices, one of which is on: a sunken track (a field is a hole, as above) with the chosen one raised
   out of it in the primary button's colours. */
.ap-seg {
  display: flex; gap: 3px; padding: 3px;
  background: var(--field-bg); border: 1px solid var(--rule); border-radius: calc(var(--radius) + 3px);
  box-shadow: var(--field-inset);
}
.ap-seg button {
  flex: 1 1 0; min-width: 0; padding: 6px 8px; cursor: pointer;
  border: 0; border-radius: var(--radius); background: transparent;
  font: inherit; font-size: 13px; line-height: 18px; color: var(--ink); white-space: nowrap;
  transition: background .12s, color .12s;
}
.ap-seg button:hover { background: var(--paper-2); }
.ap-seg button[aria-checked="true"] { background: var(--btn-bg); color: var(--btn-fg); font-weight: 500; }
.ap-seg button:disabled { opacity: .45; cursor: not-allowed; background: transparent; }
.ap-seg button[aria-checked="true"]:disabled { background: var(--btn-bg); }
.ap-seg button:focus-visible, .ap-swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Glass: lozenges, and the chosen one lacquered like a button - but opaque, and with a lighter gloss than .btn's.
   Measured: .btn's own (68% fill, 42% sheen) puts the light words of the light theme's dark button at about
   3:1, too little for 13px. */
:root[data-style="glass"] .ap-seg,
:root[data-style="glass"] .ap-seg button { border-radius: 999px; }
:root[data-style="glass"] .ap-seg button[aria-checked="true"] {
  background-image: linear-gradient(180deg, rgb(255 255 255 / .26) 0, rgb(255 255 255 / .08) 48%, transparent 48.5%);
  box-shadow: var(--bevel);
}
/* Each type pack's button in its own pack (the stacks are app.css's "Type packs"; the head script fetches the
   few letters each needs as the panel opens). */
.ap-type [data-value=""] { font-family: 'Jost', 'Segoe UI', system-ui, sans-serif; }
.ap-type [data-value="aero"] { font-family: 'Segoe UI', 'Source Sans 3', system-ui, sans-serif; }
.ap-type [data-value="techno"] { font-family: 'Chakra Petch', 'Rajdhani', 'Segoe UI', system-ui, sans-serif; }
.ap-type [data-value="terminal"] { font-family: 'Share Tech Mono', ui-monospace, Consolas, monospace; }

/* The palettes, each painted in its own colours rather than named in words: its page, a card on it with a line
   of its ink, and its two accents. The colours come from app.css itself (app/look.py), light and dark, and
   light-dark() picks the side the page is on. */
.ap-swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); gap: 9px; }
.ap-swatch {
  position: relative; aspect-ratio: 1; min-width: 0; padding: 0; cursor: pointer; overflow: hidden;
  border: 1px solid var(--rule-strong); border-radius: 12px;
  background: light-dark(var(--sw-lp), var(--sw-dp));
  transition: transform .12s, box-shadow .12s;
}
.ap-swatch:hover { transform: translateY(-1px); }
.ap-swatch i { position: absolute; display: block; }
.ap-sw-card { left: 14%; top: 42%; width: 56%; height: 42%; border-radius: 3px;
              background: light-dark(var(--sw-ls), var(--sw-ds)); box-shadow: 0 1px 2px rgb(0 0 0 / .18); }
.ap-sw-card::after { content: ''; position: absolute; left: 18%; right: 26%; top: 40%; height: 2px; border-radius: 1px;
                     background: light-dark(var(--sw-li), var(--sw-di)); }
.ap-sw-a { right: 13%; top: 13%; width: 34%; height: 34%; border-radius: 50%; background: light-dark(var(--sw-la), var(--sw-da)); }
.ap-sw-b { right: 15%; bottom: 14%; width: 20%; height: 20%; border-radius: 50%; background: light-dark(var(--sw-lb), var(--sw-db)); }
.ap-swatch[aria-checked="true"] { border-color: var(--accent); box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent); }
/* Glass: little bubbles of colour, lit from above. */
:root[data-style="glass"] .ap-swatch { border-radius: 50%; }
:root[data-style="glass"] .ap-swatch::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background-image: var(--gloss); opacity: .55; box-shadow: var(--bevel);
}

/* Volume and the writing sound: a name, then its control. The label rule for forms (small capitals in --faint)
   is not wanted here. */
.ap-row { display: flex; align-items: center; gap: 12px; margin-top: 12px; }
.ap-row .ap-rowlabel { display: block; flex: none; width: 104px; margin: 0; font-size: 13px; letter-spacing: 0;
                       text-transform: none; font-weight: 400; color: var(--ink); }
.ap-row input[type="range"] { flex: 1; width: auto; min-width: 0; height: 22px; padding: 0; border: 0;
                              background: transparent; box-shadow: none; accent-color: var(--accent); }
.ap-row input[type="range"]:disabled { opacity: .45; }
.ap-value { flex: none; width: 38px; text-align: right; font-size: 12.5px; color: var(--muted);
            font-variant-numeric: tabular-nums; }
.ap-row .ap-seg { flex: 1; }


/* ═══ the progress tube (Redraw's progress) (GuideStar app.css 3456–3550) ═══ */

/* The tube. Nine pixels rather than six: below about eight a bubble is
   smaller than the rounding on the ends and the whole thing reads as noise
   on a line. */
.genprog-track {
  position: relative;
  height: 9px; border-radius: 999px; overflow: hidden;
  background-color: color-mix(in srgb, var(--ink) 9%, transparent);
  background-image: linear-gradient(180deg,
    color-mix(in srgb, var(--ink) 10%, transparent) 0,
    transparent 55%);
  box-shadow:
    inset 0 1px 2px light-dark(rgb(10 60 80 / .13), rgb(0 0 0 / .36)),
    inset 0 -1px 0 var(--sheen-soft, rgb(255 255 255 / .30));
}

/* The lit water. background-color as its own longhand, never folded into the
   shorthand, so a mistake in the image layer can never take the colour with
   it. */
.genprog-fill {
  position: relative;
  width: 0; height: 100%; border-radius: 999px;
  transition: width .18s linear;
  background-color: var(--gf-glow, var(--accent-2));
  background-image: linear-gradient(180deg,
    var(--sheen, rgb(255 255 255 / .70)) 0 16%,
    var(--sheen-soft, rgb(255 255 255 / .28)) 16% 30%,
    transparent 30% 74%,
    color-mix(in srgb, var(--ink) 14%, transparent) 100%);
}

/* Clips the bubbles to the water. Its own element so the fill keeps its
   rounded ends and the meniscus can still overhang them. */
.genprog-body {
  position: absolute; inset: 0; z-index: 1;
  border-radius: inherit; overflow: hidden; pointer-events: none;
}

/* The bubbles. Two fields on different clocks and different tile widths, so
   the pattern never visibly repeats and the near ones outrun the far ones —
   which is most of what makes it read as depth rather than wallpaper.

   Each bubble is a bright core with a faint dark rim, because a core alone
   disappears on a pale palette and a rim alone disappears on a dark one. */
.genprog-body::before,
.genprog-body::after {
  content: ''; position: absolute;
  top: -6px; bottom: -6px; left: -140px; width: 900px;
  will-change: transform;
}
.genprog-body::before {
  background-image:
    radial-gradient(circle 2.6px at 6px 62%,
      var(--sheen, rgb(255 255 255 / .82)) 0 42%,
      color-mix(in srgb, var(--ink) 16%, transparent) 62% 82%, transparent 86%),
    radial-gradient(circle 1.8px at 30px 34%,
      var(--sheen, rgb(255 255 255 / .82)) 0 46%,
      color-mix(in srgb, var(--ink) 14%, transparent) 66% 84%, transparent 88%),
    radial-gradient(circle 2.2px at 52px 74%,
      var(--sheen, rgb(255 255 255 / .82)) 0 44%,
      color-mix(in srgb, var(--ink) 15%, transparent) 64% 84%, transparent 88%);
  background-size: 71px 100%;          /* coprime with the finer field */
  animation: gp-stream 7.5s linear infinite;
}
.genprog-body::after {
  background-image:
    radial-gradient(circle 1.4px at 10px 40%,
      var(--sheen-soft, rgb(255 255 255 / .45)) 0 52%, transparent 60%),
    radial-gradient(circle 1.1px at 34px 70%,
      var(--sheen-soft, rgb(255 255 255 / .45)) 0 54%, transparent 62%);
  background-size: 47px 100%;
  opacity: .8;
  animation: gp-stream 11.5s linear infinite;
}

/* The meniscus: the brightest thing on the bar, sitting exactly on the true
   width. Whatever the bubbles are doing, the eye lands on this edge, and
   this edge never lies. */
.genprog-fill::after {
  content: ''; position: absolute; z-index: 2;
  top: -1px; bottom: -1px; right: -1px; width: 5px;
  border-radius: 999px; pointer-events: none;
  background: linear-gradient(90deg, transparent,
    var(--sheen, rgb(255 255 255 / .78)));
  opacity: .95;
}

.genprog-note { margin: 7px 0 0; }

/* Bubbles stream along the tube. One tile width per cycle, so it loops with
   no seam and never accumulates an offset over a long run. */
@keyframes gp-stream {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(71px, 0, 0); }
}

/* ═══ the Board (GuideStar app.css 5423–6622) ═══ */
/* ══ The Board ══════════════════════════════════════════════════════════
   The index (boards.html) and the editor (board.html, js/board.js). The
   editor's controls float over the page as small panes — the page is the
   thing, and it runs edge to edge under them. */

/* Notebook colours: a dot on the rail, the cards and the editor's chip. */
.nb-sky    { --nb: #3a8fd9; }
.nb-leaf   { --nb: #3aa05a; }
.nb-sun    { --nb: #e0a21b; }
.nb-rose   { --nb: #d9537a; }
.nb-violet { --nb: #7e5bd6; }
.nb-slate  { --nb: #5d7483; }
.nb-none   { --nb: color-mix(in srgb, var(--ink) 30%, transparent); }
.bi-dot { display: inline-block; flex: none; width: 9px; height: 9px; border-radius: 50%;
  background: var(--nb, var(--accent)); box-shadow: inset 0 1px 1px rgb(255 255 255 / .45); }

/* ── The index ── */
.board-index { display: grid; grid-template-columns: 236px 1fr; gap: 20px; align-items: start; }
.bi-rail { position: sticky; top: 96px; padding: 12px 10px; display: flex; flex-direction: column; gap: 4px; }
.bi-link { display: flex; align-items: center; gap: 9px; padding: 7px 10px; border-radius: 10px;
  color: var(--ink); font-size: 13.5px; transition: background .15s; }
.bi-link b { font-weight: 500; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bi-link span { margin-left: auto; font-size: 11.5px; color: color-mix(in srgb, var(--ink) 15%, var(--muted));
  font-variant-numeric: tabular-nums; }
.bi-link:hover { background: color-mix(in srgb, var(--accent) 9%, transparent); color: var(--ink); }
.bi-link.on { background: color-mix(in srgb, var(--accent) 16%, transparent); font-weight: 500; }
.bi-head { display: flex; align-items: center; justify-content: space-between; margin: 12px 10px 4px;
  font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase; color: color-mix(in srgb, var(--ink) 15%, var(--muted)); }
.bi-add { width: 24px; height: 24px; border-radius: 50%; border: 1px solid var(--rule-strong); background: transparent;
  color: var(--ink); font-size: 16px; line-height: 1; cursor: pointer; }
.bi-add:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bi-none { margin: 2px 10px 6px; }
.bi-tags { display: flex; flex-wrap: wrap; gap: 5px; padding: 2px 8px 4px; }
.bi-tag, .bi-cardtags a { font-size: 11.5px; padding: 2px 8px; border-radius: 999px; color: var(--ink);
  background: color-mix(in srgb, var(--accent) 8%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent); }
.bi-tag.on, .bi-tag:hover, .bi-cardtags a:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--ink); }
.bi-bottom { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--rule); }
.bi-main { min-width: 0; }
.bi-search { display: flex; gap: 10px; margin-bottom: 14px; }
.bi-search input { flex: 1; }
.bi-search select { width: auto; }
.bi-nbhead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 14px; }
.bi-nbhead h2 { margin: 0; font-size: 22px; }
.bi-nbhead .row { margin-left: auto; }
.bi-nbhead .bi-dot { width: 13px; height: 13px; }
.bi-colours { display: flex; gap: 5px; }
.bi-colour { width: 20px; height: 20px; border-radius: 50%; border: 2px solid transparent; background: var(--nb); cursor: pointer;
  box-shadow: inset 0 1px 2px rgb(255 255 255 / .5); }
.bi-colour.on { border-color: var(--ink); }
.bi-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(214px, 1fr)); gap: 16px; }
.bi-card { position: relative; display: flex; flex-direction: column; border-radius: 14px; overflow: hidden;
  background: var(--surface); border: 1px solid var(--rule); box-shadow: var(--shadow-1);
  transition: box-shadow .18s, transform .18s; }
.bi-card:hover { box-shadow: var(--shadow-2); transform: translateY(-2px); }
.bi-card.bi-binned { opacity: .72; }
.bi-thumb { position: relative; display: block; aspect-ratio: 3 / 2; background: #fbfaf6; border-bottom: 1px solid var(--rule); }
.bi-thumb.paper-slate { background: #1d272b; }
.bi-thumb.paper-glass { background: color-mix(in srgb, var(--paper) 60%, #dfeaf2); }
.bi-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top center; }
.bi-empty { position: absolute; inset: 0; display: grid; place-items: center; font-size: 12px; color: rgb(60 70 80 / .55); }
.bi-thumb.paper-slate .bi-empty { color: rgb(255 255 255 / .5); }
.bi-pin { position: absolute; top: 8px; left: 10px; color: #e0a21b; font-size: 15px; text-shadow: 0 1px 2px rgb(0 0 0 / .25); }
.bi-meta { padding: 10px 40px 12px 12px; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.bi-title { font-weight: 500; font-size: 14.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bi-when { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.bi-chip { display: inline-flex; align-items: center; gap: 5px; }
.bi-snippet { color: var(--ink); background: color-mix(in srgb, #ffd21f 22%, transparent); border-radius: 6px; padding: 3px 6px; }
.bi-cardtags { display: flex; flex-wrap: wrap; gap: 4px; }
.bi-more { position: absolute; right: 8px; bottom: 10px; width: 28px; height: 26px; border: 0; border-radius: 8px; background: transparent;
  color: var(--muted); font-size: 16px; cursor: pointer; }
.bi-more:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--ink); }
.bi-emptystate { grid-column: 1 / -1; padding: 40px 0; text-align: center; color: color-mix(in srgb, var(--ink) 15%, var(--muted)); }
:root[data-style="glass"] .bi-card { background-color: var(--pane); background-image: var(--gloss-soft);
  box-shadow: var(--rim), var(--shadow-1); border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); }
:root[data-style="glass"] .bi-card:hover { box-shadow: var(--rim), var(--shadow-2); }
@media (max-width: 760px) {
  .board-index { grid-template-columns: 1fr; }
  .bi-rail { position: static; }
}

/* ── The editor ── */
main.board-main { max-width: none; padding: 0; }
/* The page is a column: masthead, then the board taking what is left —
   whatever height the masthead settles at, and with nothing to scroll. */
html:has(.bd), body:has(.bd) { overflow: hidden; }
html:has(.bd) { scrollbar-gutter: auto; }
body:has(.bd) { display: flex; flex-direction: column; height: 100vh; height: 100dvh; }
body:has(.bd) > main.board-main { flex: 1; min-height: 0; width: 100%; display: flex; flex-direction: column; }
.bd { position: relative; display: flex; flex-direction: column; flex: 1; min-height: 0; }
.bd-head { position: relative; z-index: 5; display: flex; align-items: center; gap: 10px; padding: 7px 14px;
  border-bottom: 1px solid var(--rule); background: var(--surface); min-width: 0; }
:root[data-style="glass"] .bd-head { background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur); }
.bd-back { display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%; color: var(--ink); flex: none; }
.bd-back svg { width: 18px; height: 18px; }
.bd-back:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
input.bd-title { flex: 1 1 160px; min-width: 60px; max-width: 420px; border: 1px solid transparent; background: transparent;
  box-shadow: none; padding: 4px 8px; font-family: var(--serif, var(--sans)); font-size: 19px; color: var(--ink); border-radius: 8px; }
input.bd-title:hover { border-color: var(--rule); }
input.bd-title:focus { border-color: color-mix(in srgb, var(--accent) 50%, var(--rule)); background: color-mix(in srgb, var(--surface) 80%, transparent); }
.bd-chip { display: inline-flex; align-items: center; gap: 6px; flex: none; max-width: 180px; padding: 4px 10px;
  border-radius: 999px; border: 1px solid var(--rule); background: transparent; color: var(--ink); font: inherit; font-size: 12.5px; cursor: pointer; }
.bd-chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bd-chip:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--ink); }
.bd-lessonchip { border-color: color-mix(in srgb, var(--accent) 35%, var(--rule)); }
.bd-save { flex: none; font-size: 11.5px; color: color-mix(in srgb, var(--ink) 15%, var(--muted)); min-width: 5.5em; }
.bd-save[data-state="error"], .bd-save[data-state="conflict"] { color: var(--red); max-width: 22em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* In a small popped-out window the buttons ran off the edge (and, scrolled
   sideways, hid behind the window's own buttons): they wrap onto a second
   row instead, every one in sight, kept to the right. */
.bd-head { flex-wrap: wrap; row-gap: 6px; }
.bd-headacts { margin-left: auto; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px; flex: 0 1 auto; min-width: 0; }
.bd-headacts > * { flex: none; }
@media (max-width: 820px) { .bd-hbtn { padding: 4px 9px; font-size: 12px; } }
.bd-hbtn { display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border-radius: 999px;
  border: 1px solid var(--rule-strong); background: transparent; color: var(--ink); font: inherit; font-size: 12.5px; cursor: pointer;
  transition: background .15s, border-color .15s; }
.bd-hbtn svg { width: 14px; height: 14px; }
.bd-hbtn:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); border-color: color-mix(in srgb, var(--accent) 40%, var(--rule-strong)); }
.bd-hbtn[aria-pressed="true"] { background: color-mix(in srgb, var(--accent) 20%, transparent); border-color: var(--accent); }
/* The header's buttons as icons ("instead of text these could be icons, clear and nice", 29 Sep 2026): one round
   size, an 18 px icon, and each one's name (its aria-label, what a screen reader says) shown just under it on
   hover or keyboard focus - at once, in the app's own look, where a browser tooltip waits and looks foreign.
   Keep on top fills its pin while it is on. */
.bd-headacts .bd-hbtn { position: relative; width: 34px; height: 34px; padding: 0; justify-content: center; gap: 0; }
.bd-headacts .bd-hbtn svg { width: 18px; height: 18px; flex: none; }
.bd-headacts .bd-ontop[aria-pressed="true"] .bd-pinhead { fill: currentColor; }
.bd-headacts .bd-hbtn[aria-label]:hover::after,
.bd-headacts .bd-hbtn[aria-label]:focus-visible::after {
  content: attr(aria-label); position: absolute; top: calc(100% + 7px); left: 50%; transform: translateX(-50%);
  width: max-content; max-width: 260px; white-space: normal; text-align: center;
  padding: 5px 10px; border-radius: 9px; font-size: 12px; font-weight: 500; line-height: 1.35; letter-spacing: 0;
  text-transform: none; color: var(--ink); background: var(--surface); border: 1px solid var(--rule-strong);
  box-shadow: var(--shadow-2); pointer-events: none; z-index: 30; }
:root[data-style="glass"] .bd-headacts .bd-hbtn[aria-label]:hover::after,
:root[data-style="glass"] .bd-headacts .bd-hbtn[aria-label]:focus-visible::after {
  background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur); box-shadow: var(--rim), var(--shadow-2); }
/* (The last two open their names towards the page, not off the window's edge.) */
.bd-headacts .bd-hbtn:nth-last-child(-n+2)[aria-label]:hover::after,
.bd-headacts .bd-hbtn:nth-last-child(-n+2)[aria-label]:focus-visible::after { left: auto; right: 0; transform: none; }
.bd-morebtn { padding: 5px 10px; font-size: 15px; line-height: 1; }
.bd-hostonly { display: none; }
.bd-hostonly.bd-hostshown { display: inline-flex; }
.menu-item.bd-mon { font-weight: 500; }
.bd-pageitem { display: flex !important; align-items: center; gap: 9px; }
.bd-pageitem img, .bd-pageitem i { flex: none; width: 42px; height: 28px; border-radius: 4px; object-fit: cover; object-position: top;
  background: #fbfaf6; border: 1px solid var(--rule); }
.bd-pageitem span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.bd-stage { position: relative; flex: 1; min-height: 0; overflow: hidden; touch-action: none; user-select: none;
  -webkit-user-select: none; background: #fbfaf6; }
.bd-stage.paper-slate { background: #1d272b; }
/* See-through paper shows the page behind it - and on Glass the aurora, which at its strongest took the coloured
   inks for light paper down to 1.6-3.5:1, in every palette, the default too, and Aero's dark ink to 3.7:1
   (measured 30 Sep 2026, lane L19: tests/test_appearance.py). So a veil of the page's own colour lies behind the
   writing: the aurora still tints it, but can no longer wash an ink out. 85% on light paper, where the coloured
   inks are darkest and have least to spare (at 70% Aero's and Ember's orange still fell to 2.9); 60% on dark,
   whose inks kept over 5:1 even so. */
:root { --glass-veil: 85%; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --glass-veil: 60%; } }
:root[data-theme="dark"]  { --glass-veil: 60%; }
:root[data-theme="light"] { --glass-veil: 85%; }
.bd-stage.paper-glass { background: color-mix(in srgb, var(--paper) var(--glass-veil), transparent); }
.bd-layer { position: absolute; left: 0; top: 0; pointer-events: none; }
#bd-over { pointer-events: auto; touch-action: none; }
.bd-stage[data-tool="pen"] #bd-over, .bd-stage[data-tool="hl"] #bd-over, .bd-stage[data-tool="eraser"] #bd-over { cursor: none; }
.bd-stage[data-tool="lasso"] #bd-over, .bd-stage[data-tool="shape"] #bd-over { cursor: crosshair; }
.bd-stage[data-tool="text"] #bd-over { cursor: text; }
/* The Fill tool: a paint pot, pouring where it points (a white edge keeps it seen on slate). */
.bd-stage[data-tool="fill"] #bd-over { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23fff' stroke-width='3.6' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M10.9 3.8l7.6 7.6-6.3 6.3a1.8 1.8 0 0 1-2.6 0l-4.9-4.9a1.8 1.8 0 0 1 0-2.6z'/%3E%3Cpath d='M6.6 2.6l4.3 4.3'/%3E%3Cpath d='M21.3 19.2a1.75 1.75 0 1 1-3.5 0c0-1.3 1.3-2.1 1.75-3.7.45 1.6 1.75 2.4 1.75 3.7z'/%3E%3C/g%3E%3Cg fill='none' stroke='%231c1f24' stroke-width='1.6' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M10.9 3.8l7.6 7.6-6.3 6.3a1.8 1.8 0 0 1-2.6 0l-4.9-4.9a1.8 1.8 0 0 1 0-2.6z'/%3E%3Cpath d='M6.6 2.6l4.3 4.3M3.7 12.3h13.9'/%3E%3C/g%3E%3Cpath d='M21.3 19.2a1.75 1.75 0 1 1-3.5 0c0-1.3 1.3-2.1 1.75-3.7.45 1.6 1.75 2.4 1.75 3.7z' fill='%231c1f24'/%3E%3C/svg%3E") 19 20, crosshair; }
.bd-stage.bd-picking #bd-over { cursor: default; }       /* the mouse where it is a cursor: a pointer, not a nib */
.bd-stage.bd-grab #bd-over { cursor: grab; }
.bd-stage.bd-panning #bd-over { cursor: grabbing; }
/* Selecting: over a thing or inside the selection it moves; on a corner it
   resizes (board.js sets data-hover). Grabbing the page still wins. */
.bd-stage[data-hover="move"]:not(.bd-grab, .bd-panning) #bd-over { cursor: move; }
.bd-stage[data-hover="nwse"]:not(.bd-grab, .bd-panning) #bd-over { cursor: nwse-resize; }
.bd-stage[data-hover="nesw"]:not(.bd-grab, .bd-panning) #bd-over { cursor: nesw-resize; }
.bd-stage[data-hover="ew"]:not(.bd-grab, .bd-panning) #bd-over { cursor: ew-resize; }
.bd-stage[data-hover="ns"]:not(.bd-grab, .bd-panning) #bd-over { cursor: ns-resize; }
.bd-cursor { position: absolute; left: 0; top: 0; z-index: 3; pointer-events: none; width: var(--s); height: var(--s);
  margin: calc(var(--s) / -2) 0 0 calc(var(--s) / -2); border-radius: 50%; background: var(--c);
  box-shadow: 0 0 0 1px rgb(255 255 255 / .8), 0 0 0 2px rgb(0 0 0 / .18); }
.bd-cursor[hidden] { display: none; }
.bd-cursor[data-kind="hl"] { opacity: .5; }
.bd-cursor[data-kind="eraser"] { background: rgb(255 255 255 / .35); box-shadow: 0 0 0 1.5px rgb(40 50 60 / .6), 0 0 0 3px rgb(255 255 255 / .6); }
/* The ring that fills round a pen held still, until the radial menu opens
   (board.js: showHoldCue) — seen coming, a pause before writing can write on. */
.bd-holdcue { position: absolute; z-index: 8; width: 40px; height: 40px; margin: -20px 0 0 -20px; pointer-events: none; }
.bd-holdcue svg { display: block; width: 100%; height: 100%; transform: rotate(-90deg); overflow: visible; }
.bd-holdcue circle { fill: none; stroke: color-mix(in srgb, var(--accent) 80%, #fff); stroke-width: 3.5; stroke-linecap: round;
  stroke-dasharray: 100; stroke-dashoffset: 100; filter: drop-shadow(0 0 1.5px rgb(0 0 0 / .45));
  animation: bd-holdfill var(--fill, 400ms) linear forwards; }
@keyframes bd-holdfill { to { stroke-dashoffset: 0; } }
@media (prefers-reduced-motion: reduce) { .bd-holdcue circle { animation: none; stroke-dashoffset: 0; opacity: .75; } }
.bd-edit { position: absolute; inset: 0; pointer-events: none; z-index: 2; }
/* The page's own layers of elements (board.js: makeLayer), placed in page
   units and moved and zoomed with the page by one transform. They take no
   presses themselves; an element in one that wants them says so with
   pointer-events: auto. Under the page: beneath the page's canvas, seen
   where the page is cut away. Over it: above the ink, under the tools. */
.bd-world { position: absolute; left: 0; top: 0; width: 0; height: 0; transform-origin: 0 0; pointer-events: none; }
.bd-world > * { position: absolute; }
.bd-overlay { z-index: 1; }
.bd-textedit { position: absolute; left: 0; top: 0; pointer-events: auto; resize: none; overflow: hidden; margin: 0;
  padding: 0 2px; border: 1.5px dashed color-mix(in srgb, var(--accent) 70%, transparent) !important; border-radius: 4px;
  background: color-mix(in srgb, #fff 55%, transparent) !important; box-shadow: none !important; transform-origin: 0 0; min-height: 1.4em; }
/* Light words on dark paper (slate, or see-through paper in the dark theme — board.js
   sets bd-ondark by the rule its ink colours follow): a darkened box, not a white one. */
.bd-textedit.bd-ondark { background: rgb(0 0 0 / .35) !important; }

/* The floating panes: rail, options, selection bar, zoom, the reading. */
.bd-rail, .bd-props, .bd-selbar, .bd-zoom, .bd-hint, .bd-conflict, .bd-diagprog {
  position: absolute; z-index: 4; background: var(--surface); border: 1px solid var(--rule-strong);
  box-shadow: var(--shadow-2); }
:root[data-style="glass"] :is(.bd-rail, .bd-props, .bd-selbar, .bd-zoom, .bd-hint, .bd-conflict, .bd-diagprog) {
  background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); box-shadow: var(--rim), var(--shadow-2); }
/* The tools sit halfway down the left edge, where the hand finds them — or
   down the right, when chosen (data-side) — centred by their margins, and
   scrolling within themselves when the window is short. */
.bd-rail { left: 12px; top: 12px; bottom: 12px; margin-block: auto; height: fit-content; display: flex; flex-direction: column;
  gap: 3px; padding: 6px; border-radius: 22px; max-height: calc(100% - 24px); overflow-y: auto; scrollbar-width: none; }
.bd-stage[data-side="right"] .bd-rail { left: auto; right: 12px; }
.bd-rail::-webkit-scrollbar { display: none; }
.bd-railgap { height: 1px; margin: 5px 6px; background: var(--rule); flex: none; }
.bd-tool { position: relative; display: grid; place-items: center; width: 38px; height: 38px; flex: none; border-radius: 50%;
  border: 1px solid transparent; background: transparent; color: var(--ink); cursor: pointer; transition: background .15s, border-color .15s; }
.bd-tool svg { width: 21px; height: 21px; }
.bd-tletter { font-size: 15px; font-weight: 600; line-height: 1; }   /* a tool that came without an icon: its first letter */
.bd-tool:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bd-tool.on { background: color-mix(in srgb, var(--accent) 22%, transparent); border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .5); }
.bd-tool:disabled { opacity: .35; cursor: default; background: transparent; }
.bd-nib { display: block; width: var(--nibw, 5px); height: 18px; border-radius: 999px; background: var(--nib, var(--ink));
  box-shadow: inset 0 1px 1px rgb(255 255 255 / .45), 0 0 0 1px rgb(0 0 0 / .08); }
.bd-pen[data-style="fountain"] .bd-nib { border-radius: 999px 999px 40% 40%; }
.bd-pen[data-style="marker"] .bd-nib { border-radius: 3px; }
.bd-hlswatch { opacity: .75; }
/* The pen's recent settings, in its options: a short line in each one's
   colour, size and nib — lit from within when it is the one in hand. */
.bd-recent { display: flex; align-items: center; gap: 3px; flex-wrap: wrap; }
.bd-recent .bd-plabel { padding: 0 4px 0 2px; }
.bd-recentpen { display: grid; place-items: center; width: 38px; height: 28px; padding: 0; border: 1px solid transparent;
  border-radius: 999px; background: transparent; cursor: pointer; transition: background .15s, border-color .15s; }
.bd-recentpen svg { width: 30px; height: 20px; overflow: visible; }
.bd-recentpen path { fill: none; stroke: var(--nib); stroke-width: var(--nibw); stroke-linecap: round; stroke-linejoin: round; }
.bd-recentpen[data-style="marker"] path { stroke-linecap: square; stroke-linejoin: miter; }
.bd-recentpen[data-style="fine"] path { stroke-width: calc(var(--nibw) * .8); }
.bd-recentpen:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bd-recentpen.on { background: color-mix(in srgb, var(--accent) 20%, transparent); border-color: color-mix(in srgb, var(--accent) 50%, transparent);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .5); }

/* The chosen tool's options: a pane that opens beside the rail, level with
   the tool's button (board.js places it), its groups one above another. */
.bd-props { left: 64px; top: 12px; display: flex; flex-direction: column; align-items: flex-start; gap: 9px; padding: 9px 10px;
  border-radius: 20px; width: max-content; max-width: min(236px, calc(100% - 84px)); max-height: calc(100% - 16px);
  overflow-y: auto; scrollbar-width: none; }
.bd-props::-webkit-scrollbar { display: none; }
.bd-props[hidden] { display: none; }
.bd-props .bd-swatches, .bd-props .bd-sizes { flex-wrap: wrap; }
.bd-props .bd-kinds { border-radius: 16px; }
.bd-props .bd-plabel { line-height: 1.4; }
.bd-plabel { font-size: 12.5px; color: color-mix(in srgb, var(--ink) 15%, var(--muted)); padding: 4px 4px; }
.bd-seg { display: flex; padding: 2px; border-radius: 999px; background: color-mix(in srgb, var(--ink) 6%, transparent); }
.bd-seg button { border: 0; background: transparent; color: var(--ink); font: inherit; font-size: 12px; padding: 4px 10px;
  border-radius: 999px; cursor: pointer; white-space: nowrap; }
.bd-seg button:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bd-seg button.on { background: var(--surface); box-shadow: 0 1px 2px rgb(0 0 0 / .16); font-weight: 500; }
.bd-kinds { flex-wrap: wrap; }
.bd-swatches { display: flex; gap: 5px; align-items: center; }
.bd-swatch { position: relative; width: 22px; height: 22px; border-radius: 50%; padding: 0; cursor: pointer; flex: none;
  border: 2px solid var(--surface); background: var(--sw); box-shadow: 0 0 0 1px var(--rule-strong), inset 0 1px 2px rgb(255 255 255 / .35); }
.bd-swatch:hover { transform: scale(1.1); }
.bd-swatch.on { box-shadow: 0 0 0 2px var(--accent), inset 0 1px 2px rgb(255 255 255 / .35); }
/* The any-colour swatch is a <label> round the colour picker: the app-wide
   label rule's bottom margin lifted it above the rest of its row. */
.bd-custom { overflow: hidden; margin: 0; background: conic-gradient(#f44, #fd3, #4d4, #3bf, #a4f, #f44); }
.bd-custom input { position: absolute; inset: -6px; width: 40px; height: 40px; opacity: 0; cursor: pointer; }
/* Faces for text: the Text tool's choice, Font on the selection bar and its
   menu — each written in its own face (board.js sets it), the handwriting a
   little larger, as its letters are small for their size. */
.bd-fonts button { line-height: 18px; }
.bd-fonts [data-font="hand"], .bd-sbtn[data-face="hand"] { font-size: 15.5px; }
.bd-sbtn[data-act="font"] { line-height: 18px; }
.bd-fontitem { line-height: 20px; }
.bd-fontitem[data-face="hand"] { font-size: 17px; }
.bd-fontitem .menu-key { font-family: var(--sans); font-weight: 400; line-height: 20px; }
.bd-sizes { display: flex; gap: 2px; align-items: center; }
.bd-size { display: grid; place-items: center; width: 28px; height: 28px; border: 0; border-radius: 50%; background: transparent; cursor: pointer; }
.bd-size i { display: block; width: var(--d); height: var(--d); border-radius: 50%; background: var(--ink); opacity: .75; }
.bd-size:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bd-size.on { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.bd-size.on i { opacity: 1; }
/* Fill: the paint in the Fill tool's pot takes the colour chosen (board.js
   sets it); "Fill" leads the Shapes tool's None / Tint / Solid; and the
   selection bar's Fill menu ends with a row of colours. */
.bd-fillswatch { opacity: .8; }
.bd-seglabel { align-self: center; padding: 0 6px 0 8px; font-size: 11.5px; color: color-mix(in srgb, var(--ink) 15%, var(--muted)); }
.bd-fillrow { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 9px 5px; margin-top: 4px; border-top: 1px solid var(--rule); }
.bd-fillrow .bd-swatch { width: 20px; height: 20px; }

.bd-selbar { left: 0; top: 0; display: flex; align-items: center; gap: 4px; padding: 4px 6px; border-radius: 999px; white-space: nowrap; }
.bd-selbar[hidden] { display: none; }
/* Wider than the page beside the tools (a narrow, popped-out window): it
   wraps onto more rows rather than cover them (board.js sets its width). */
.bd-selbar.bd-wrap { flex-wrap: wrap; justify-content: center; border-radius: 16px; }
.bd-sbtn { border: 0; background: transparent; color: var(--ink); font: inherit; font-size: 12.5px; padding: 5px 10px; border-radius: 999px; cursor: pointer; }
.bd-sbtn:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.bd-sbtn[hidden] { display: none; }
.bd-sdanger { color: var(--red); }
.bd-ssep { width: 1px; height: 18px; background: var(--rule); margin: 0 2px; }
.bd-scolours { display: flex; gap: 4px; }
.bd-scolours .bd-swatch { width: 18px; height: 18px; }

.bd-zoom { right: 12px; bottom: 12px; display: flex; align-items: center; padding: 3px; border-radius: 999px; }
.bd-stage[data-side="right"] .bd-zoom { right: auto; left: 12px; }      /* out of the way of tools on the right */
.bd-zoom button { border: 0; background: transparent; color: var(--ink); font: inherit; font-size: 13px; min-width: 30px; height: 28px;
  padding: 0 8px; border-radius: 999px; cursor: pointer; font-variant-numeric: tabular-nums; }
.bd-zoom button:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }

.bd-read { position: absolute; left: 0; top: 0; z-index: 6; width: min(380px, calc(100% - 24px)); padding: 14px 16px 12px; }
.bd-read[hidden] { display: none; }
.bd-readx { position: absolute; top: 6px; right: 8px; width: 26px; height: 26px; border: 0; border-radius: 50%; background: transparent;
  color: var(--muted); font-size: 18px; cursor: pointer; }
.bd-readx:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--ink); }
.bd-readx + p { padding-right: 24px; }   /* a message's first line clears the × */
.bd-readtext { width: 100%; font-size: 15px; resize: vertical; margin: 6px 0 8px; }
.bd-readlatex { width: 100%; font-family: ui-monospace, Consolas, monospace; font-size: 13px; margin: 6px 0 10px; }
.bd-mathpreview svg { max-width: 100%; height: auto; color: var(--ink); }
.bd-mathpreview { min-height: 44px; padding: 10px; border-radius: 10px; margin-top: 6px; overflow-x: auto;
  background: color-mix(in srgb, var(--paper) 70%, transparent); border: 1px solid var(--rule); text-align: center; }
.bd-alts { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; margin-bottom: 8px; }
.bd-alt { border: 1px solid var(--rule-strong); background: transparent; color: var(--ink); font: inherit; font-size: 12.5px;
  padding: 2px 9px; border-radius: 999px; cursor: pointer; }
.bd-alt:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bd-readacts { flex-wrap: wrap; }

.bd-conflict { left: 50%; top: 14px; transform: translateX(-50%); display: flex; align-items: center; gap: 10px;
  padding: 8px 10px 8px 16px; border-radius: 999px; font-size: 13px; }
.bd-conflict[hidden] { display: none; }
.bd-hint { left: 50%; bottom: 16px; transform: translateX(-50%); padding: 7px 16px; border-radius: 999px; font-size: 13px;
  max-width: calc(100% - 200px); text-align: center; pointer-events: none; }
.bd-hint[hidden] { display: none; }
/* A picture being redrawn as a diagram (board/diagram.js): where it has got to, over the middle of the picture -
   the step in words, the app's bubble tube (.genprog-track) under it. Gone a moment after it ends. */
.bd-diagprog { width: 180px; padding: 7px 12px 9px; border-radius: 12px; transform: translateX(-50%);
  pointer-events: none; transition: opacity .5s ease .7s; }
.bd-diagprog-note { margin: 0 0 6px; font-size: 12.5px; line-height: 1.3; text-align: center; }
/* Cropping a picture (board/crop.js): the whole picture dimmed, the part kept bright inside a frame with eight
   handles, and Reset / Cancel / Done under it. The layer over the page takes every press while it is open. */
.bd-crop { position: absolute; inset: 0; z-index: 6; cursor: default; background: rgb(18 22 28 / .55); }
/* (The page still draws the picture under the veil; this dim copy on top keeps the hidden part just visible.) */
.bd-crop-full { position: absolute; opacity: .3; pointer-events: auto; user-select: none; -webkit-user-drag: none; }
.bd-crop-frame { position: absolute; overflow: hidden; cursor: move; touch-action: none;
  outline: 2px solid #fff; box-shadow: 0 0 0 1px rgb(0 0 0 / .45), 0 6px 22px rgb(0 0 0 / .25); }
.bd-crop-shown { position: absolute; max-width: none; pointer-events: none; user-select: none; -webkit-user-drag: none; }
.bd-crop-h { position: absolute; width: 14px; height: 14px; border-radius: 3px; background: #fff;
  box-shadow: 0 0 0 1px rgb(0 0 0 / .5); z-index: 1; }
.bd-crop-h[data-h="n"], .bd-crop-h[data-h="s"] { left: calc(50% - 7px); cursor: ns-resize; }
.bd-crop-h[data-h="e"], .bd-crop-h[data-h="w"] { top: calc(50% - 7px); cursor: ew-resize; }
.bd-crop-h[data-h="n"], .bd-crop-h[data-h="ne"], .bd-crop-h[data-h="nw"] { top: -1px; }
.bd-crop-h[data-h="s"], .bd-crop-h[data-h="se"], .bd-crop-h[data-h="sw"] { bottom: -1px; }
.bd-crop-h[data-h="w"], .bd-crop-h[data-h="nw"], .bd-crop-h[data-h="sw"] { left: -1px; }
.bd-crop-h[data-h="e"], .bd-crop-h[data-h="ne"], .bd-crop-h[data-h="se"] { right: -1px; }
.bd-crop-h[data-h="ne"], .bd-crop-h[data-h="sw"] { cursor: nesw-resize; }
.bd-crop-h[data-h="nw"], .bd-crop-h[data-h="se"] { cursor: nwse-resize; }
.bd-crop-bar { position: absolute; transform: translateX(-50%); display: flex; gap: 6px; padding: 5px;
  border-radius: 999px; background: var(--surface); border: 1px solid var(--rule-strong); box-shadow: var(--shadow-2); }
:root[data-style="glass"] .bd-crop-bar { background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur); box-shadow: var(--rim), var(--shadow-2); }
.bd-crop-bar button { font: inherit; font-size: 13px; padding: 5px 12px; border-radius: 999px; border: 1px solid var(--rule);
  background: transparent; color: var(--ink); cursor: pointer; }
.bd-crop-bar button.primary { background: var(--accent); color: #fff; border-color: transparent; }

/* …and a four-pointed star bouncing about over the picture meanwhile (board/diagram.js bubbleOver), like the old
   screensavers: the star's shape filled with the colour of the moment (--mark, new at each bounce - a snap, not a
   fade), flashing when it lands square in a corner, faded out at the end. */
.bd-diagmark { position: absolute; left: 0; top: 0; z-index: 5; pointer-events: none; will-change: transform;
  background-color: var(--mark, #2f7bff);
  --mark-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M50 2C53 36 64 47 98 50C64 53 53 64 50 98C47 64 36 53 2 50C36 47 47 36 50 2Z'/%3E%3C/svg%3E");
  -webkit-mask: var(--mark-shape) center / contain no-repeat;
  mask: var(--mark-shape) center / contain no-repeat;
  transition: opacity .5s; }
/* (A glow round it would be cut off by its own shape, so a corner makes it flash instead.) */
.bd-diagmark-corner { animation: mark-corner .2s steps(1) 8; }
@keyframes mark-corner { 0% { filter: brightness(1.7) saturate(1.3); } 50% { filter: none; } }
.bd-diagmark-gone { opacity: 0; }
.bd-diagprog-done { opacity: 0; }
.bd-diagprog-failed { transition-delay: 4s; opacity: 0; }
.bd-diagprog-failed .bd-diagprog-note { color: var(--red); }

/* The radial menu (board.js, showRadial): a glass disc round the pointer
   holding a ring of raised glass orbs — the tools, or what can be done
   with the selection — and, when there is one, a glass band beyond it
   with the tool's colours and sizes (or Colour's and Fill's choices). The
   middle is a sunken well naming what is pointed at. The choice pointed
   at is lit: full lacquer and a glow, a little larger. The box itself
   takes no presses, so its empty corners are the page's. It opens outward
   from the pointer — at once where motion is turned down. */
.bd-radial { position: absolute; z-index: 8; width: 340px; height: 340px; pointer-events: none; }
.bd-rdisc, .bd-rtrack, .bd-rhub, .bd-ritem { position: absolute; left: 50%; top: 50%; border-radius: 50%; pointer-events: auto; }
.bd-rdisc { width: 216px; height: 216px; margin: -108px 0 0 -108px; background: var(--surface);
  border: 1px solid var(--rule-strong); box-shadow: var(--shadow-3); }
.bd-rtrack { width: 330px; height: 330px; margin: -165px 0 0 -165px; background: var(--surface);
  border: 1px solid var(--rule-strong); box-shadow: var(--shadow-2);
  -webkit-mask: radial-gradient(circle, transparent 121px, #000 121.5px); mask: radial-gradient(circle, transparent 121px, #000 121.5px);
  opacity: 0; visibility: hidden; transition: opacity .15s, visibility .15s; }
.bd-radial.bd-rhasouter .bd-rtrack { opacity: 1; visibility: visible; }
/* A tool's own arc open (board.js: showSub): the colours and sizes of the tool in hand step aside for it. */
.bd-radial.bd-rsubopen .bd-rout { visibility: hidden; }
/* On glass, a breath of the palette's accent in the pane and at its edge:
   untinted, a light pane over light paper all but vanished. */
:root[data-style="glass"] :is(.bd-rdisc, .bd-rtrack) {
  background-color: color-mix(in srgb, var(--accent) 7%, var(--pane)); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--accent) 30%, var(--rule-strong)); box-shadow: var(--rim), var(--shadow-3); }
.bd-rhub { width: 72px; height: 72px; margin: -36px 0 0 -36px; display: grid; place-items: center; padding: 5px;
  text-align: center; font-size: 11.5px; line-height: 1.15; font-weight: 500; color: var(--ink); overflow-wrap: anywhere;
  cursor: pointer; background: color-mix(in srgb, var(--ink) 5%, transparent);
  box-shadow: inset 0 1px 2px rgb(10 60 80 / .13), inset 0 -1px 0 var(--sheen-soft, rgb(255 255 255 / .28)); }
.bd-ritem { width: var(--s); height: var(--s); margin: calc(var(--s) / -2) 0 0 calc(var(--s) / -2); translate: var(--x) var(--y);
  display: grid; place-items: center; padding: 0; font: inherit; color: var(--ink); cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--ink) 16%, transparent); background: color-mix(in srgb, var(--surface) 84%, transparent);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .55), 0 1px 3px rgb(0 0 0 / .14);
  transition: scale .12s, box-shadow .12s, border-color .12s; }
:root[data-style="glass"] .bd-ritem { background-image: var(--gloss-soft); box-shadow: var(--bevel), 0 1px 3px rgb(0 0 0 / .14); }
.bd-ritem svg { width: 21px; height: 21px; }
.bd-ritem.bd-ron { border-color: color-mix(in srgb, var(--accent) 70%, transparent);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .55), 0 0 0 2px color-mix(in srgb, var(--accent) 45%, transparent); }
.bd-ritem.bd-raim { scale: 1.14; border-color: color-mix(in srgb, var(--accent) 75%, transparent);
  background-color: color-mix(in srgb, var(--accent) 16%, var(--surface));
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .75), 0 0 0 3px color-mix(in srgb, var(--accent) 32%, transparent), 0 5px 14px rgb(0 0 0 / .2); }
/* On glass the lit orb keeps its fill: the light changes, not the colour. */
:root[data-style="glass"] .bd-ritem.bd-raim { background-color: color-mix(in srgb, var(--surface) 84%, transparent); background-image: var(--gloss);
  box-shadow: var(--bevel), 0 0 0 3px color-mix(in srgb, var(--accent) 34%, transparent),
    0 0 18px 2px color-mix(in srgb, var(--accent) 45%, transparent), 0 5px 14px rgb(0 0 0 / .18); }
.bd-ritem[aria-disabled="true"] { opacity: .38; cursor: default; }
.bd-ritem[aria-disabled="true"].bd-raim { scale: 1; }
.bd-rsw { display: block; width: 20px; height: 20px; border-radius: 50%; background: var(--sw);
  box-shadow: inset 0 1px 2px rgb(255 255 255 / .45), 0 0 0 1px rgb(0 0 0 / .16); }
.bd-rsw.bd-rtint { background: color-mix(in srgb, var(--sw) 32%, transparent); }
.bd-rsw.bd-rnone { background: linear-gradient(135deg, transparent 44%, var(--red) 44% 56%, transparent 56%), var(--surface); }
.bd-rsw.bd-rany { background: conic-gradient(#1f5fc4, #6a45bd, #c8352b, #c26508, #1b7f45, #1c1f24, #1f5fc4); }
.bd-rdot { display: block; width: var(--d); height: var(--d); border-radius: 50%; background: var(--sw, var(--ink)); }
.bd-rdot.bd-rhollow { background: transparent; border: 1.5px solid var(--ink); }
.bd-rword { font-weight: 600; line-height: 1; }
.bd-rglyph { font-size: 16px; font-weight: 600; line-height: 1; }
@keyframes bd-rpop { from { opacity: 0; scale: .8; } }
@keyframes bd-rfly { from { opacity: 0; translate: 0 0; scale: .5; } }
.bd-rdisc, .bd-rhub { animation: bd-rpop .16s cubic-bezier(.2, .8, .3, 1); }
.bd-ritem { animation: bd-rfly .2s cubic-bezier(.2, .8, .3, 1.12) backwards; }
.bd-rsub .bd-ritem { animation-duration: .15s; }
@media (prefers-reduced-motion: reduce) {
  .bd-radial :is(.bd-rdisc, .bd-rtrack, .bd-rhub, .bd-ritem) { animation: none; transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  :root[data-style="glass"] :is(.bd-rdisc, .bd-rtrack) { background-color: var(--surface); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* The right-click list (the app's .ctxmenu): what cannot be used now is
   greyed; the keys light the entry they are on; the colours sit in a row. */
#bd-menu:focus { outline: none; }
#bd-menu .menu-item:disabled { color: var(--faint); cursor: default; background: transparent; }
#bd-menu .menu-item:focus-visible { outline: none; background: var(--paper-2);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 60%, transparent); }
.bd-ctxrow { display: flex; align-items: center; gap: 6px; padding: 5px 9px; }
.bd-ctxrow > span { margin-right: auto; font-size: 13px; color: var(--ink); }
.bd-ctxrow .bd-swatch { width: 18px; height: 18px; }
.bd-ctxrow .bd-swatch:disabled { opacity: .35; cursor: default; transform: none; }
.bd-ctxrow .bd-swatch:focus-visible, .bd-fillrow .bd-swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Popped out: the page and nothing else, and a lighter header. */
body.bare main { padding: 0; }
.bd-pop .bd-head { padding: 5px 10px; gap: 8px; }
.bd-pop input.bd-title { font-size: 16px; }
@media (max-width: 640px) {
  .bd-hbtn { padding: 5px 9px; }
  #bd-paperbtn, #bd-exportbtn { display: none; }
  .bd-props { max-width: min(236px, calc(100% - 76px)); }
}
@media (prefers-reduced-motion: reduce) {
  .bi-card, .bi-card:hover { transition: none; transform: none; }
  .bd-tool, .bd-hbtn, .bd-swatch:hover, .bd-recentpen { transition: none; transform: none; }
}

/* ── Search (round 4) ────────────────────────────────────────────────────
   js/board/search.js. Search in the top bar (or Ctrl+F) opens a sunken
   field there: the words, "3 of 12", the one before and the next. Under
   it a glass pane lists what was found, page by page, the words marked;
   on the page each match glows softly in the page's top layer, the one
   gone to brighter. The app-wide input rule is undone for the field. */
.bd-find { position: relative; display: flex; align-items: center; flex: none; }
.bd-findbtn[hidden], .bd-findbox[hidden], .bd-findlist[hidden], .bd-findnote[hidden], .bd-findedge[hidden] { display: none; }
.bd-findbox { display: flex; align-items: center; gap: 1px; height: 30px; padding: 0 3px 0 10px; border-radius: 999px;
  background: var(--field-bg); border: 1px solid var(--rule-strong); box-shadow: var(--field-inset); color: var(--ink); }
.bd-findbox:focus-within { border-color: color-mix(in srgb, var(--accent) 70%, var(--rule-strong));
  box-shadow: var(--field-inset), 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent); }
.bd-findicon { display: grid; place-items: center; flex: none; color: color-mix(in srgb, var(--ink) 15%, var(--muted)); }
.bd-findicon svg { display: block; width: 14px; height: 14px; }
input.bd-findq { flex: 1 1 auto; width: 176px; min-width: 64px; height: 28px; margin: 0; padding: 0 4px 0 7px; border: 0; border-radius: 0;
  background: transparent; box-shadow: none; font-size: 13px; color: var(--ink); }
input.bd-findq:focus { border: 0; }
input.bd-findq::placeholder { color: color-mix(in srgb, var(--ink) 15%, var(--muted)); opacity: 1; }
input.bd-findq::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; display: none; }
.bd-findcount { flex: none; min-width: 3.4em; height: 24px; padding: 0 7px; border: 0; border-radius: 999px; background: transparent;
  font: inherit; font-size: 11.5px; font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; cursor: pointer;
  color: color-mix(in srgb, var(--ink) 15%, var(--muted)); }
.bd-findcount:empty { min-width: 0; padding: 0; }
.bd-findnav { display: grid; place-items: center; flex: none; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 50%;
  background: transparent; color: var(--ink); font: inherit; cursor: pointer; transition: background .15s, box-shadow .15s; }
.bd-findnav svg { display: block; width: 14px; height: 14px; }
.bd-findx { font-size: 17px; line-height: 1; }
.bd-findnav:disabled { opacity: .38; cursor: default; }
.bd-findcount:hover, .bd-findnav:not(:disabled):hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
:root[data-style="glass"] :is(.bd-findcount, .bd-findnav:not(:disabled)):hover { background-image: var(--gloss); box-shadow: var(--bevel); }
:is(.bd-findcount, .bd-findnav, .bd-findall, .bd-findhit):focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The list: a pane of the Board's glass under the field (search.js places
   it). Its words lie over the page, which shows through a glass pane —
   a dark pane over white paper, a light one over slate — and its first
   line sits where the gloss is brightest. Measured, the usual pane (86 %)
   and gloss left the quieter words at 2.7–4.4 : 1 in dark themes over
   paper; so this pane is thicker (93 %), its gloss the soft sheen only,
   and its quieter words (--bd-quiet) lean further to the ink. */
.bd-findlist { --bd-quiet: color-mix(in srgb, var(--ink) 55%, var(--muted));
  position: absolute; z-index: 7; left: 0; top: 0; width: min(420px, calc(100vw - 16px)); max-height: min(60vh, 470px);
  display: flex; flex-direction: column; overflow: hidden; border-radius: 16px;
  background: var(--surface); border: 1px solid var(--rule-strong); box-shadow: var(--shadow-3); }
:root[data-style="glass"] .bd-findlist { background-color: color-mix(in srgb, var(--surface) 93%, transparent);
  background-image: linear-gradient(180deg, var(--sheen-soft) 0, transparent 44%);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); box-shadow: var(--rim), var(--shadow-3); }
.bd-findtop { display: flex; align-items: center; gap: 8px; padding: 7px 8px 7px 14px; border-bottom: 1px solid var(--rule); flex: none; }
.bd-findsay { flex: 1; min-width: 0; font-size: 12.5px; color: var(--bd-quiet); }
.bd-findsay.bd-findbad { color: var(--red); }
.bd-findnote { margin: 0; padding: 6px 14px; font-size: 12px; line-height: 1.35; border-bottom: 1px solid var(--rule); flex: none;
  color: var(--bd-quiet); background: color-mix(in srgb, var(--accent) 6%, transparent); }
/* All whiteboards: a switch, its knob lit when on. */
.bd-findall { display: inline-flex; align-items: center; gap: 7px; flex: none; padding: 4px 11px 4px 5px; border-radius: 999px;
  border: 1px solid var(--rule-strong); background: transparent; color: var(--ink); font: inherit; font-size: 12px; cursor: pointer;
  transition: background .15s, border-color .15s; }
.bd-findall:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bd-findall i { position: relative; flex: none; width: 26px; height: 15px; border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 22%, transparent); box-shadow: inset 0 1px 2px rgb(0 0 0 / .22); transition: background .15s; }
.bd-findall i::after { content: ""; position: absolute; top: 2px; left: 2px; width: 11px; height: 11px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 2px rgb(0 0 0 / .35); transition: translate .15s; }
.bd-findall[aria-checked="true"] { border-color: color-mix(in srgb, var(--accent) 55%, var(--rule-strong)); }
.bd-findall[aria-checked="true"] i { background: var(--accent); }
.bd-findall[aria-checked="true"] i::after { translate: 11px 0; }
.bd-findhits { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 4px 5px 6px; touch-action: pan-y; }
.bd-findhits:empty { display: none; }
.bd-findgroup { padding: 9px 9px 3px; font-size: 11.5px; font-weight: 600; color: var(--bd-quiet);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* One match: its page and kind, then the words round it. */
.bd-findhit { display: grid; grid-template-columns: auto 1fr; align-items: baseline; column-gap: 8px; row-gap: 2px; width: 100%;
  margin: 1px 0; padding: 6px 9px 7px; border: 1px solid transparent; border-radius: 11px; background: transparent;
  font: inherit; text-align: left; color: var(--ink); cursor: pointer; transition: background .12s; }
.bd-findhit:hover { background: color-mix(in srgb, var(--accent) 9%, transparent); }
.bd-findhit.bd-findon { background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent); box-shadow: inset 0 1px 0 var(--sheen-soft, rgb(255 255 255 / .3)); }
.bd-findhit.bd-findon .bd-findkind { color: var(--ink); }
.bd-findwhere { font-size: 11.5px; font-weight: 600; white-space: nowrap; max-width: 14em; overflow: hidden; text-overflow: ellipsis; }
.bd-findwhere.bd-findhere { color: var(--ink); }
.bd-findwhere.bd-findhere::before { content: ""; display: inline-block; width: 6px; height: 6px; margin: 0 5px 1px 0; border-radius: 50%;
  background: var(--accent); vertical-align: middle; }
.bd-findkind { font-size: 11.5px; color: var(--bd-quiet); }
.bd-findsnip { grid-column: 1 / -1; font-size: 13px; line-height: 1.4; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
/* The words that matched: a highlighter's yellow, dark letters on it in either theme. */
.bd-findsnip mark { padding: 0 1px; border-radius: 3px; background: #ffe066; color: #1c1f24; }
.bd-findtex { font-family: ui-monospace, Consolas, monospace; font-size: 12px; }
.bd-findwhy { color: var(--bd-quiet); font-size: 12px; }

/* On the page: a soft glow round each match, the same size on the screen
   at any zoom (--bdk is 1 ÷ the zoom; the layer is scaled with the page);
   the one gone to glows up once as it is reached. */
.bd-findmarks { left: 0; top: 0; width: 0; height: 0; --bdk: 1; }
.bd-findmark { position: absolute; box-sizing: content-box; pointer-events: none;
  margin: calc(-3px * var(--bdk)) 0 0 calc(-3px * var(--bdk)); padding: calc(3px * var(--bdk)); border-radius: calc(6px * var(--bdk));
  background: color-mix(in srgb, #ffd21f 20%, transparent);
  box-shadow: 0 0 0 calc(1.5px * var(--bdk)) color-mix(in srgb, #f0b000 70%, transparent),
              0 0 calc(12px * var(--bdk)) calc(2px * var(--bdk)) color-mix(in srgb, #ffd21f 45%, transparent); }
.bd-findmark.bd-findcur { background: color-mix(in srgb, #ffd21f 30%, transparent);
  box-shadow: 0 0 0 calc(2.5px * var(--bdk)) #f0a500,
              0 0 calc(22px * var(--bdk)) calc(5px * var(--bdk)) color-mix(in srgb, #ffc400 65%, transparent);
  animation: bd-findglow .9s ease-in-out 2; }
@keyframes bd-findglow {
  50% { box-shadow: 0 0 0 calc(3.5px * var(--bdk)) #f0a500,
                    0 0 calc(34px * var(--bdk)) calc(10px * var(--bdk)) color-mix(in srgb, #ffc400 75%, transparent); }
}
/* A match somewhere in the page's handwriting, not yet placed: the whole page glows at its edge. */
.bd-findedge { position: absolute; inset: 0; z-index: 3; pointer-events: none;
  box-shadow: inset 0 0 0 3px color-mix(in srgb, #f0b000 75%, transparent), inset 0 0 40px 8px color-mix(in srgb, #ffd21f 40%, transparent); }
/* Narrow windows (a popped-out board): the field takes the room of the
   save note and the notebook's name while open, and gives way itself (its
   words box narrows) rather than push the top bar past the window. */
.bd-head:has(.bd-findbox:not([hidden])) .bd-headacts { flex: 0 1 auto; min-width: 0; }
.bd-head:has(.bd-findbox:not([hidden])) .bd-headacts > :not(.bd-find) { flex-shrink: 0; }
.bd-find:has(.bd-findbox:not([hidden])) { flex: 0 1 auto; min-width: 0; }
.bd-findbox { min-width: 0; }
@media (max-width: 760px) {
  .bd-head:has(.bd-findbox:not([hidden])) :is(.bd-save, #bd-where, .bd-lessonchip) { display: none; }
  input.bd-findq { width: 130px; min-width: 44px; }
}
@media (max-width: 640px) {
  .bd-findbtn span { display: none; }
  .bd-findbtn { padding-inline: 8px; }
  .bd-findicon { display: none; }
  .bd-findbox { padding-left: 4px; }
  .bd-findcount { min-width: 0; padding: 0 4px; }
}
/* The see-through window's own top: the field and the list are not handles for moving the window. */
:root[data-frame="own"] :is(.bd-findbox, .bd-findlist) { app-region: no-drag; -webkit-app-region: no-drag; }
@media (prefers-reduced-motion: reduce) {
  .bd-findmark.bd-findcur { animation: none; }
  .bd-findnav, .bd-findall, .bd-findall i, .bd-findall i::after, .bd-findhit { transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  :root[data-style="glass"] .bd-findlist { background-color: var(--surface); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* ── Pages (round 4) ─────────────────────────────────────────────────────
   js/board/pages.js. A whiteboard's pages: a slim glass column on the side
   away from the tools, above the zoom — each page a small picture with its
   number and name, the one shown lit — or, folded, a small pill halfway
   down that side: the page before, "2/5", the page after, and +. Also a
   popped-out window's "Back in the main window" in its top bar. */
.bd-pages, .bd-pagepill, .bd-pgtoast {
  position: absolute; z-index: 4; color: var(--ink); background: var(--surface);
  border: 1px solid var(--rule-strong); box-shadow: var(--shadow-2); }
:root[data-style="glass"] :is(.bd-pages, .bd-pagepill, .bd-pgtoast) {
  background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); box-shadow: var(--rim), var(--shadow-2); }
.bd-pages[hidden], .bd-pagepill[hidden], .bd-pgtoast[hidden] { display: none; }
:is(.bd-pages, .bd-pagepill) svg { display: block; width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; }
.bd-pages { right: 12px; top: 12px; bottom: 58px; width: 136px; display: flex; flex-direction: column; border-radius: 18px; overflow: hidden; }
.bd-stage[data-side="right"] .bd-pages { right: auto; left: 12px; }
.bd-pghead { display: flex; align-items: center; justify-content: space-between; flex: none; padding: 6px 6px 4px 12px; }
.bd-pgtitle { font-size: 12px; font-weight: 600; }
.bd-pgtitle b { margin-left: 3px; font-weight: 500; color: color-mix(in srgb, var(--ink) 15%, var(--muted)); font-variant-numeric: tabular-nums; }
.bd-pgfold { display: grid; place-items: center; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 50%;
  background: transparent; color: var(--ink); cursor: pointer; transition: background .15s; }
.bd-stage[data-side="right"] .bd-pgfold svg { transform: scaleX(-1); }   /* folds towards its own edge */
.bd-pglist { position: relative; flex: 1 1 auto; min-height: 0; margin: 0; padding: 3px 9px 4px; list-style: none;
  overflow-y: auto; overscroll-behavior: contain; touch-action: pan-y; scrollbar-width: thin; }
.bd-pg { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; margin: 0 0 9px; }
.bd-pgopen { grid-column: 1 / -1; position: relative; display: block; width: 100%; padding: 0; border: 0; border-radius: 9px;
  background: transparent; cursor: pointer; }
.bd-pgthumb { display: block; aspect-ratio: 3 / 2; overflow: hidden; border-radius: 9px; background: #fbfaf6;
  box-shadow: 0 0 0 1px var(--rule-strong), 0 1px 3px rgb(0 0 0 / .12); transition: box-shadow .15s; }
.bd-pgthumb.paper-slate { background: #1d272b; }
.bd-pgthumb.paper-glass { background: color-mix(in srgb, var(--paper) 60%, #dfeaf2); }
.bd-pgthumb img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top center; pointer-events: none; }
/* A page with nothing on it yet: dark letters on paper, light on slate. */
.bd-pgthumb em { display: grid; place-items: center; height: 100%; font-style: normal; font-size: 11px; color: #4f5963; }
.bd-pgthumb.paper-slate em { color: #c3ced3; }
.bd-pgnum { position: absolute; left: 5px; top: 5px; min-width: 19px; height: 19px; padding: 0 5px; border-radius: 999px;
  font-size: 11px; font-weight: 600; line-height: 19px; text-align: center; font-variant-numeric: tabular-nums;
  color: var(--ink); background: color-mix(in srgb, var(--surface) 92%, transparent); box-shadow: 0 0 0 1px var(--rule-strong); }
.bd-pgopen:hover .bd-pgthumb { box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 60%, var(--rule-strong)),
  0 0 0 4px color-mix(in srgb, var(--accent) 16%, transparent); }
/* The page shown: lit — a ring of the accent and a glow — its number too. */
.bd-pgon .bd-pgthumb { box-shadow: 0 0 0 2px var(--accent), 0 0 12px 1px color-mix(in srgb, var(--accent) 42%, transparent); }
.bd-pgon .bd-pgnum { background: color-mix(in srgb, var(--accent) 32%, var(--surface)); box-shadow: 0 0 0 1px var(--accent); }
.bd-pgname { min-width: 0; padding: 3px 2px 0 3px; font-size: 11.5px; line-height: 1.35; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bd-pgon .bd-pgname { font-weight: 600; }
.bd-pgmore { width: 26px; height: 20px; margin-top: 3px; padding: 0; border: 0; border-radius: 999px; background: transparent;
  color: var(--ink); font: inherit; font-size: 15px; line-height: 1; cursor: pointer; transition: background .15s; }
input.bd-pgrename { grid-column: 1; width: 100%; min-width: 0; height: 22px; margin: 3px 0 0; padding: 1px 5px; border-radius: 6px;
  font-size: 11.5px; color: var(--ink); background: var(--field-bg, var(--surface)); border: 1px solid var(--accent); box-shadow: none; }
/* Dragged: lifted and following the pointer; a line where it will go. */
.bd-pg.bd-pglift { z-index: 2; opacity: .94; }
.bd-pglift .bd-pgthumb { box-shadow: 0 0 0 2px var(--accent), 0 10px 22px rgb(0 0 0 / .28); }
.bd-pgmark { position: absolute; left: 9px; right: 9px; height: 3px; margin-top: -1px; border-radius: 2px; background: var(--accent);
  box-shadow: 0 0 6px color-mix(in srgb, var(--accent) 60%, transparent); pointer-events: none; }
.bd-pgmark[hidden] { display: none; }
.bd-pgadd { display: flex; align-items: center; justify-content: center; gap: 5px; flex: none; margin: 2px 9px 9px; padding: 5px 8px;
  border: 1px dashed color-mix(in srgb, var(--accent) 60%, var(--rule-strong)); border-radius: 999px; background: transparent;
  color: var(--ink); font: inherit; font-size: 12px; cursor: pointer; transition: background .15s, border-color .15s; }
.bd-pgadd svg { width: 14px; height: 14px; }
/* Folded: the pill. */
/* Bottom left (the owner: "move this to the left"), below the tools' rail
   and clear of the zoom, bottom right. */
.bd-pagepill { left: 12px; bottom: 16px; display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 5px; border-radius: 22px; }
.bd-pagepill button { display: grid; place-items: center; width: 32px; height: 30px; padding: 0; border: 0; border-radius: 999px;
  background: transparent; color: var(--ink); font: inherit; cursor: pointer; transition: background .15s; }
.bd-pagepill button:disabled { opacity: .35; cursor: default; }
.bd-pagepill .bd-pgnow { display: flex; flex-direction: column; align-items: center; height: auto; padding: 4px 0; line-height: 1.1; }
.bd-pgnow b { font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; }
.bd-pgnow span { font-size: 10.5px; color: color-mix(in srgb, var(--ink) 15%, var(--muted)); font-variant-numeric: tabular-nums; }
.bd-pgsep { width: 18px; height: 1px; margin: 2px 0; background: var(--rule); }
/* Lit by the light, not a new colour: the hover of every control here. */
:is(.bd-pgfold, .bd-pgmore, .bd-pgadd, .bd-pagepill button:not(:disabled), .bd-pgtoast button):hover {
  background: color-mix(in srgb, var(--accent) 14%, transparent); }
:root[data-style="glass"] :is(.bd-pgfold, .bd-pgmore, .bd-pgadd, .bd-pagepill button:not(:disabled), .bd-pgtoast button):hover {
  background-image: var(--gloss); box-shadow: var(--bevel); }
.bd-pgadd:hover { border-style: solid; }
:is(.bd-pgopen, .bd-pgfold, .bd-pgmore, .bd-pgadd, .bd-pagepill button, .bd-pgtoast button):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A word with a button, for a moment ("Page 3 is in the bin. Put it back"). */
.bd-pgtoast { left: 50%; bottom: 58px; transform: translateX(-50%); display: flex; align-items: center; gap: 10px;
  padding: 5px 6px 5px 15px; border-radius: 999px; font-size: 13px; white-space: nowrap; }
.bd-pgtoast button { padding: 4px 12px; border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--rule-strong)); border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--ink); font: inherit; font-size: 12.5px; font-weight: 500; cursor: pointer; }
/* A popped-out window's way back: the one lit button in its top bar, the
   label shortened as the window narrows (its name stays for the ear). */
.bd-hbtn.bd-popback { font-weight: 500; border-color: color-mix(in srgb, var(--accent) 60%, var(--rule-strong));
  background: color-mix(in srgb, var(--accent) 15%, transparent); }
.bd-hbtn.bd-popback:hover { background: color-mix(in srgb, var(--accent) 26%, transparent); }
:root[data-style="glass"] .bd-hbtn.bd-popback { background-image: var(--gloss-soft); box-shadow: var(--bevel); }
:root[data-style="glass"] .bd-hbtn.bd-popback:hover { background-image: var(--gloss); }
.bd-hbtn.bd-popback:disabled { opacity: .6; cursor: default; }
.bd-popback .bd-short { display: none; }
@media (max-width: 860px) {
  .bd-popback .bd-long { display: none; }
  .bd-popback .bd-short { display: inline; }
}
@media (max-width: 640px) {
  .bd-pages { width: 116px; }
  .bd-pglist { padding-inline: 7px; }
}
@media (max-width: 520px) {
  .bd-popback .bd-short { display: none; }
  .bd-hbtn.bd-popback { padding-inline: 8px; }
}
@media (prefers-reduced-motion: reduce) {
  .bd-pgthumb, .bd-pgfold, .bd-pgmore, .bd-pgadd, .bd-pagepill button { transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  :root[data-style="glass"] :is(.bd-pages, .bd-pagepill, .bd-pgtoast) { background-color: var(--surface); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* ── Drawing aids (round 4) ──────────────────────────────────────────────
   board.js (Shapes, snapping) and js/board/shapes-edit.js. The Shapes
   options: each kind a small drawing of itself in the ink chosen (--kc),
   in its group on an even grid of four columns — the group's name, then
   up to three — the one in hand lit like a chosen tool; the fill for new
   shapes on the same grid. ⋯ → Snap to the grid: Off / Gentle / Firm as
   one row. The radial menu's kinds: the one in hand lit, not only ringed.
   A selected shape's own handles show "grab" as the pointer. */
.bd-kindgrid { display: grid; grid-template-columns: auto repeat(3, 32px); gap: 3px 4px; align-items: center; }
.bd-kindgrid > i { display: block; }
.bd-kindlabel { padding-right: 6px; font-size: 11.5px; color: color-mix(in srgb, var(--ink) 30%, var(--muted)); }
.bd-kindbtn { display: grid; place-items: center; width: 32px; height: 30px; padding: 0; border: 1px solid transparent;
  border-radius: 10px; background: transparent; color: var(--ink); cursor: pointer; transition: background .15s, border-color .15s; }
.bd-kindbtn svg { width: 20px; height: 20px; overflow: visible; }
.bd-kindbtn path { fill: none; stroke: var(--kc, currentColor); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.bd-kindbtn path.bd-kx { stroke: var(--red); stroke-width: 1.6; }
.bd-kindbtn path.bd-ktint { fill: color-mix(in srgb, var(--kc, currentColor) 26%, transparent); }
.bd-kindbtn path.bd-ksolid { fill: var(--kc, currentColor); }
.bd-kindbtn:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bd-kindbtn.on { background: color-mix(in srgb, var(--accent) 22%, transparent); border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .5); }
.bd-kindbtn.bd-dim:not(.on) { opacity: .55; }
/* Dark, over light paper the glass pane turns mid-grey: the names in full
   ink, lifted, and each drawing a shade lighter and edged in shadow, so
   they keep 4.5:1 and 3:1 there too. */
:root[data-theme="dark"] .bd-kindlabel { color: var(--ink); text-shadow: 0 1px 2px rgb(0 0 0 / .5); }
:root[data-theme="dark"] .bd-kindgrid { --kc2: color-mix(in srgb, var(--kc, currentColor) 76%, #fff); }
:root[data-theme="dark"] .bd-kindbtn svg { filter: drop-shadow(0 0 1.2px rgb(0 0 0 / .75)); }
:root[data-theme="dark"] .bd-kindbtn path { stroke: var(--kc2); }
:root[data-theme="dark"] .bd-kindbtn path.bd-ksolid { fill: var(--kc2); }
:root[data-theme="dark"] .bd-kindbtn path.bd-kx { stroke: var(--red); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .bd-kindlabel { color: var(--ink); text-shadow: 0 1px 2px rgb(0 0 0 / .5); }
  :root:not([data-theme]) .bd-kindgrid { --kc2: color-mix(in srgb, var(--kc, currentColor) 76%, #fff); }
  :root:not([data-theme]) .bd-kindbtn svg { filter: drop-shadow(0 0 1.2px rgb(0 0 0 / .75)); }
  :root:not([data-theme]) .bd-kindbtn path { stroke: var(--kc2); }
  :root:not([data-theme]) .bd-kindbtn path.bd-ksolid { fill: var(--kc2); }
  :root:not([data-theme]) .bd-kindbtn path.bd-kx { stroke: var(--red); }
}
.bd-kindbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
:root[data-style="glass"] .bd-kindbtn.on { background-image: var(--gloss-soft); }
/* (Tight enough that Firm, lit and bold, stays inside the menu's row — it ran 3 px past it.) */
.bd-snaprow { gap: 2px; }
.bd-snaprow > span { white-space: nowrap; margin-right: 2px; }
.bd-snapopt { border: 1px solid transparent; background: transparent; color: var(--ink); font: inherit; font-size: 12px;
  padding: 2px 6px; border-radius: 999px; cursor: pointer; flex: none; }
.bd-snapopt:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bd-snapopt.on { background: color-mix(in srgb, var(--accent) 20%, transparent); border-color: color-mix(in srgb, var(--accent) 50%, transparent);
  font-weight: 600; }
.bd-snapopt:focus-visible { outline: none; box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 60%, transparent); }
.bd-ritem.bd-rkind.bd-ron { background-color: color-mix(in srgb, var(--accent) 24%, var(--surface)); }
.bd-changeitem { display: flex; align-items: center; gap: 9px; }
.bd-changeitem svg { width: 18px; height: 18px; flex: none; }
.bd-changeitem .menu-key { margin-left: auto; float: none; }
.bd-stage.bd-onhandle:not(.bd-grab, .bd-panning) #bd-over { cursor: grab; }
.bd-stage.bd-handling:not(.bd-grab, .bd-panning) #bd-over { cursor: grabbing; }
@media (prefers-reduced-motion: reduce) { .bd-kindbtn { transition: none; } }

/* ── Auto convert (round 4) ──────────────────────────────────────────────
   Writing turned into typed text and equations as it is written
   (js/board/convert.js): the switch in the header (lit while on), the fade
   from ink to type, the "Converted · Undo" chip, and the soft dotted marks
   under words whose spelling the reading put right — in the read box and
   on the page — with the choices for each. */
/* On: lit — a deeper tint of the accent and a soft glow round it, so it reads as on at a glance. */
.bd-autobtn[aria-pressed="true"] { background-color: color-mix(in srgb, var(--accent) 30%, var(--surface));
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .45), 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent), 0 0 9px color-mix(in srgb, var(--accent) 35%, transparent); }
:root[data-style="glass"] .bd-autobtn[aria-pressed="true"] { background-image: var(--gloss-soft);
  box-shadow: var(--bevel), 0 0 12px color-mix(in srgb, var(--accent) 45%, transparent); }
.bd-autobtn[aria-pressed="true"]:hover { background-color: color-mix(in srgb, var(--accent) 40%, var(--surface)); }
@media (max-width: 760px) {
  .bd-autolabel { display: none; }
  .bd-autobtn { padding-inline: 8px; }
}
/* The page as it was, fading off the new one (laid in the page's own layer, so it pans and zooms with it). */
.bd-morph { pointer-events: none; opacity: 1; transition: opacity .42s ease-in-out, filter .42s ease-in-out; }
.bd-morph.bd-morphgo { opacity: 0; filter: blur(1.2px); }
.bd-convchip { position: absolute; left: 50%; bottom: 58px; z-index: 5; transform: translateX(-50%); display: flex; align-items: center;
  gap: 4px; padding: 4px 5px 4px 14px; border-radius: 999px; font-size: 13px; color: var(--ink); white-space: nowrap;
  background: var(--surface); border: 1px solid var(--rule-strong); box-shadow: var(--shadow-2); animation: bd-convin .22s ease-out; }
:root[data-style="glass"] .bd-convchip { background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); box-shadow: var(--rim), var(--shadow-2); }
.bd-convsay { padding-right: 4px; }
.bd-convbtn { border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--rule-strong)); background: transparent; color: var(--ink);
  font: inherit; font-size: 12.5px; padding: 3px 11px; border-radius: 999px; cursor: pointer; transition: background .15s, box-shadow .15s; }
.bd-convbtn:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
:root[data-style="glass"] .bd-convbtn { background-image: var(--gloss-soft); box-shadow: var(--bevel); }
:root[data-style="glass"] .bd-convbtn:hover { box-shadow: var(--bevel), 0 0 9px color-mix(in srgb, var(--accent) 38%, transparent); }
.bd-convbtn:active, :root[data-style="glass"] .bd-convbtn:active { background-image: none; box-shadow: inset 0 1px 2px rgb(10 60 80 / .22); }
@keyframes bd-convin { from { opacity: 0; translate: 0 6px; } }
/* In the read box: a layer laid exactly over the text box (convert.js copies its
   font and padding) that draws only the marks — its own letters are clear. */
.bd-readmarks { position: absolute; z-index: 1; margin: 0; box-sizing: border-box; overflow: hidden; pointer-events: none;
  color: transparent; background: transparent; border-style: solid; border-color: transparent;
  white-space: pre-wrap; overflow-wrap: break-word; word-break: normal; }
.bd-mu { text-decoration: underline 1.5px color-mix(in srgb, var(--ink) 75%, transparent); text-underline-offset: 3px; }
.bd-mfix { text-decoration: underline dotted 2px var(--accent); text-underline-offset: 4px; text-decoration-skip-ink: none; }
.bd-mu .bd-mfix { text-underline-offset: 6px; }
.bd-fixes { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin: -2px 0 8px; }
.bd-fixword, .bd-fixopt { border: 1px solid var(--rule-strong); background: transparent; color: var(--ink); font: inherit;
  font-size: 12.5px; padding: 2px 9px; border-radius: 999px; cursor: pointer; transition: background .15s, box-shadow .15s; }
.bd-fixword { text-decoration: underline dotted 1.5px var(--accent); text-underline-offset: 3px; }
.bd-fixopt small { font-size: 11px; color: color-mix(in srgb, var(--ink) 40%, var(--muted)); }
.bd-fixword:hover, .bd-fixopt:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bd-fixopt.bd-fixon { font-weight: 500; background: color-mix(in srgb, var(--accent) 18%, transparent);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent); box-shadow: inset 0 1px 0 rgb(255 255 255 / .5); }
.bd-fixlink { border: 0; background: transparent; color: var(--ink); font: inherit; font-size: 12.5px; padding: 3px 7px;
  border-radius: 999px; cursor: pointer; text-decoration: underline 1px color-mix(in srgb, var(--accent) 65%, transparent); text-underline-offset: 3px; }
.bd-fixlink:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bd-fixrow { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin-bottom: 7px; }
.bd-fixwas { font-size: 12.5px; color: color-mix(in srgb, var(--ink) 40%, var(--muted)); padding-right: 2px; }
.bd-fixwas b { color: var(--ink); font-weight: 600; }
.bd-fixopts { display: flex; flex-wrap: wrap; gap: 5px; }
/* On the page: a button over each marked word, while its chip shows or the text is selected — for the
   keyboard only. It takes no presses, so a drag, a double-click, a right-click or the pen on the word
   does what it does anywhere on the page; a tap on the word shows its choices (convert.js). */
.bd-fixhit { pointer-events: none; padding: 0; margin: 0; border: 0; border-radius: 3px; background: transparent; }
.bd-fixhit:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The choices for words on the page: a small glass pane under the word. */
.bd-fixpop { position: absolute; z-index: 7; width: max-content; max-width: min(360px, calc(100% - 20px)); padding: 10px 12px 8px;
  border-radius: 16px; color: var(--ink); background: var(--surface); border: 1px solid var(--rule-strong); box-shadow: var(--shadow-2); }
:root[data-style="glass"] .bd-fixpop { background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); box-shadow: var(--rim), var(--shadow-2); }
.bd-fixhead { font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; color: color-mix(in srgb, var(--ink) 40%, var(--muted)); margin-bottom: 7px; }
.bd-fixfoot { display: flex; justify-content: flex-end; gap: 4px; border-top: 1px solid var(--rule); padding-top: 6px; }
@media (prefers-reduced-motion: reduce) {
  .bd-morph { display: none; }
  .bd-convchip { animation: none; }
  .bd-convbtn, .bd-fixword, .bd-fixopt { transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  :root[data-style="glass"] :is(.bd-convchip, .bd-fixpop) { background-color: var(--surface); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* ── The equation editor (round 4) ───────────────────────────────────────
   js/board/mathedit.js. A glass pane by the equation being edited: the
   equation as it looks in a sunken well (the visual field, or a picture of
   it offline), five tabs of symbols as raised keys big enough for a pen's
   tap, its LaTeX beneath, then the actions. In a short window the keys
   scroll rather than the pane run off the board. A reading as maths in the
   read box gets an Edit button on its picture. */
.bd-eqed { position: absolute; left: 0; top: 0; z-index: 7; display: flex; flex-direction: column; gap: 8px;
  width: min(560px, calc(100% - 16px)); max-height: calc(100% - 16px); padding: 10px 12px 12px; border-radius: 18px;
  background: var(--surface); border: 1px solid var(--rule-strong); box-shadow: var(--shadow-3); color: var(--ink);
  font-size: 13px; container-type: inline-size;
  /* Its quieter words: nearer the ink than the app's usual lift — measured on
     the sheen of dark glass that one read 3.8:1 (2.6:1 over white paper). */
  --bd-eqquiet: light-dark(color-mix(in srgb, var(--l-ink) 30%, var(--l-muted)), color-mix(in srgb, var(--d-ink) 80%, var(--d-muted))); }
.bd-eqed[hidden] { display: none; }
/* Fuller than the other panes (95%, not 86%): in the dark theme over white
   paper, 86% let the page wash its words out (title 4.4:1, note 2.6:1). */
:root[data-style="glass"] .bd-eqed { background-color: color-mix(in srgb, var(--surface) 95%, transparent); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); box-shadow: var(--rim), var(--shadow-3); }
@keyframes bd-eqin { from { opacity: 0; translate: 0 6px; } }
.bd-eqed:not([hidden]) { animation: bd-eqin .16s ease-out; }
.bd-eqhead { display: flex; align-items: center; gap: 10px; min-height: 30px; flex: none; }
.bd-eqtitle { font-weight: 600; font-size: 13.5px; white-space: nowrap; }
.bd-eqnote { flex: 1; min-width: 0; font-size: 12px; color: var(--bd-eqquiet);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bd-eqnote.bd-eqsays { color: var(--ink); }            /* something to say (a mistake, offline), not just the keys */
.bd-eqx { flex: none; margin-left: auto; width: 32px; height: 32px; padding: 0; border: 0; border-radius: 50%; background: transparent;
  color: var(--ink); font: inherit; font-size: 20px; line-height: 1; cursor: pointer; }
.bd-eqx:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
/* The well: sunken, lit on its lower inner edge. The field's own keyboard
   and menu buttons are hidden — the palette is the Board's keyboard. */
.bd-eqfield { flex: none; }
/* The stage's user-select: none reaches the field too, and MathLive takes that as "no pointer selection": a click
   in it left the caret at the end ("should be able to click around in the top box", 29 Sep 2026). */
.bd-eqfield math-field { -webkit-user-select: text; user-select: text; }
.bd-eqfield math-field, .bd-eqshow { display: block; width: 100%; box-sizing: border-box; min-height: 64px; border-radius: 12px;
  border: 1px solid var(--rule-strong); background: var(--field-bg); color: var(--ink); box-shadow: var(--field-inset); }
.bd-eqfield math-field { padding: 8px 12px; font-size: 26px; max-height: 190px; overflow: auto;
  --caret-color: var(--accent); --selection-color: var(--ink);
  --selection-background-color: color-mix(in srgb, var(--accent) 26%, transparent);
  --contains-highlight-background-color: color-mix(in srgb, var(--accent) 9%, transparent);
  --placeholder-color: color-mix(in srgb, var(--accent) 70%, var(--ink)); --smart-fence-color: var(--muted); }
.bd-eqfield math-field:focus-within { outline: none; border-color: var(--accent);
  box-shadow: var(--field-inset), 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
.bd-eqfield math-field::part(virtual-keyboard-toggle), .bd-eqfield math-field::part(menu-toggle) { display: none; }
.bd-eqfield.bd-eqvisual .bd-eqshow { display: none; }
.bd-eqshow { display: grid; place-items: center; padding: 10px; max-height: 150px; overflow: auto; text-align: center; font-size: 24px; }
.bd-eqshow svg { max-width: 100%; height: auto; color: var(--ink); }
.bd-eqfaint { font-size: 12.5px; color: var(--bd-eqquiet); }
.bd-eqtabs { display: flex; gap: 2px; padding: 3px; flex: none; border-radius: 999px; background: color-mix(in srgb, var(--ink) 6%, transparent);
  overflow-x: auto; scrollbar-width: none; }
.bd-eqtabs::-webkit-scrollbar { display: none; }
.bd-eqtab { flex: 1 0 auto; min-height: 36px; padding: 6px 12px; border: 1px solid transparent; border-radius: 999px; background: transparent;
  color: var(--ink); font: inherit; font-size: 12.5px; cursor: pointer; white-space: nowrap; }
.bd-eqtab:hover { background: color-mix(in srgb, var(--surface) 60%, transparent); }
.bd-eqtab[aria-selected="true"] { background: var(--surface); font-weight: 600; box-shadow: 0 1px 2px rgb(0 0 0 / .16); }
:root[data-style="glass"] .bd-eqtab[aria-selected="true"] { background-image: var(--gloss); box-shadow: var(--bevel), 0 1px 2px rgb(0 0 0 / .12); }
.bd-eqtab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The keys: raised and glossy, as things to press are; lit by the accent
   under the pointer, pushed in while pressed. */
.bd-eqpal { display: grid; grid-template-columns: repeat(auto-fill, minmax(50px, 1fr)); gap: 5px; flex: 0 1 auto;
  min-height: 50px; max-height: 204px; overflow-y: auto; padding: 2px; overscroll-behavior: contain; }
.bd-eqpal[data-tab="matrices"] { grid-template-columns: repeat(auto-fill, minmax(58px, 1fr)); }
.bd-eqkey { display: grid; place-items: center; min-height: 44px; padding: 3px; border-radius: 12px; cursor: pointer; color: var(--ink);
  font: inherit; font-size: 16px; border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
  background-color: color-mix(in srgb, var(--surface) 82%, transparent);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .55), 0 1px 2px rgb(0 0 0 / .1); transition: box-shadow .12s, border-color .12s; }
:root[data-style="glass"] .bd-eqkey { background-image: var(--gloss); box-shadow: var(--bevel), 0 1px 2px rgb(0 0 0 / .1); }
.bd-eqkey:hover { border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .75), 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent), 0 1px 3px rgb(0 0 0 / .12); }
:root[data-style="glass"] .bd-eqkey:hover { box-shadow: var(--bevel), 0 0 12px 1px color-mix(in srgb, var(--accent) 38%, transparent); }
.bd-eqkey:active, :root[data-style="glass"] .bd-eqkey:active { background-image: none; box-shadow: inset 0 2px 4px rgb(10 60 80 / .22); }
.bd-eqkey:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.bd-eqkey:disabled { opacity: .4; cursor: default; }
.bd-eqkey svg { display: block; max-width: 44px; max-height: 34px; overflow: visible; }
.bd-eqglyph { font-size: 17px; line-height: 1; }
.bd-eqword { font-size: 12px; font-weight: 600; line-height: 1.1; }
.bd-eqsrc { display: flex; align-items: flex-start; gap: 8px; flex: none; }
.bd-eqsrcname { flex: none; padding-top: 9px; font-size: 11.5px; color: var(--bd-eqquiet); }
.bd-eqed textarea.bd-eqtex { flex: 1; min-width: 0; min-height: 36px; height: 36px; margin: 0; padding: 7px 10px; resize: none; overflow-y: auto;
  font: 13px/1.5 ui-monospace, Consolas, monospace; letter-spacing: 0; }
.bd-eqacts { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; flex: none; }
.bd-eqacts .btn { min-height: 38px; padding: 8px 16px; font-size: 11.5px; }
.bd-eqacts .btn[hidden] { display: none; }
/* The glass button's 68% fill was measured over the aurora; over this light
   pane (and a dark one) its label fell to 4.36:1 — so fuller here. */
:root[data-style="glass"] .bd-eqacts .btn:not(.ghost) { background-color: color-mix(in srgb, var(--btn-bg) 90%, transparent);
  border-color: color-mix(in srgb, var(--btn-bg) 90%, transparent); }
@container (max-width: 500px) { .bd-eqacts .btn { padding: 8px 12px; } }
/* ∑ Equation in the Text tool's options. */
.bd-eqnew { border: 1px solid var(--rule-strong); background: transparent; color: var(--ink); font: inherit; font-size: 12.5px;
  min-height: 30px; padding: 5px 12px; border-radius: 999px; cursor: pointer; }
.bd-eqnew:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
:root[data-style="glass"] .bd-eqnew { background-image: var(--gloss); box-shadow: var(--bevel); }
/* The read box: Edit on the picture of a reading as maths (the picture itself opens the editor too). */
.bd-eqwrap { position: relative; }
.bd-read .bd-mathpreview.bd-eqtap { cursor: pointer; }
.bd-read .bd-mathpreview.bd-eqtap:hover { border-color: color-mix(in srgb, var(--accent) 50%, var(--rule)); }
.bd-eqpencil { position: absolute; top: 5px; right: 5px; min-height: 26px; padding: 3px 9px; border-radius: 999px; font: inherit; font-size: 12px;
  cursor: pointer; color: var(--ink); border: 1px solid var(--rule-strong); background: color-mix(in srgb, var(--surface) 88%, transparent);
  box-shadow: 0 1px 2px rgb(0 0 0 / .12); }
:root[data-style="glass"] .bd-eqpencil { background-image: var(--gloss); box-shadow: var(--bevel), 0 1px 2px rgb(0 0 0 / .12); }
.bd-eqpencil:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--rule-strong)); }
.bd-read.bd-eqaway { visibility: hidden; }            /* out of sight while the editor puts its reading right */
@media (prefers-reduced-motion: reduce) {
  .bd-eqed:not([hidden]) { animation: none; }
  .bd-eqkey { transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  :root[data-style="glass"] .bd-eqed { background-color: var(--surface); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* ── Brushes and opacity (round 4) ──────────────────────────────────────
   The pen's options open with its brushes: the three nibs, then Pencil,
   Watercolour, Calligraphy, Chalk and Neon, each a short stroke of itself
   on a chip of the paper it will be written on (board/brushes.js draws
   them). The chosen one is lit from within, and its name heads the grid.
   Then how strongly the pen — or the highlighter — lays its ink on: a
   slider drawn as the see-through window's clearness slider is. */
.bd-brushes { display: flex; flex-direction: column; gap: 5px; }
.bd-brushhead { display: flex; align-items: baseline; gap: 4px; }
.bd-brushhead .bd-plabel { padding: 0 2px; }
.bd-brushname { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.bd-brushgrid { display: grid; grid-template-columns: repeat(4, 50px); gap: 4px; }
.bd-brush { display: grid; place-items: center; width: 50px; height: 32px; padding: 0; margin: 0; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--ink) 16%, transparent); border-radius: 12px; background: transparent;
  transition: background .15s, border-color .15s, box-shadow .15s; }
.bd-brush img { display: block; width: 44px; height: 22px; border-radius: 8px; pointer-events: none; }
.bd-brush:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.bd-brush.on { background: color-mix(in srgb, var(--accent) 26%, transparent); border-color: color-mix(in srgb, var(--accent) 75%, transparent);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .5), 0 0 0 1px color-mix(in srgb, var(--accent) 50%, transparent); }
:root[data-style="glass"] .bd-brush.on { background-image: var(--gloss-soft); }
.bd-brush:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.bd-opacity { display: grid; grid-template-columns: auto minmax(0, 1fr) 2.9em; align-items: center; gap: 6px; width: 100%; }
.bd-opacity .bd-plabel { padding: 0 2px; }
.bd-opacity output { font-size: 12px; font-variant-numeric: tabular-nums; text-align: right; color: var(--ink); }
.bd-opacity input[type="range"] {
  -webkit-appearance: none; appearance: none; width: 100%; min-width: 0; height: 22px; margin: 0;
  background: transparent; cursor: pointer; --f: 1; --thumb: 16px;
  /* The app's text-box look (border, padding, inset shadow) is not for this. */
  border: 0; padding: 0; border-radius: 0; box-shadow: none; }
.bd-opacity input[type="range"]:focus { border: 0; box-shadow: none; }
.bd-opacity input[type="range"]::-webkit-slider-runnable-track {
  height: 6px; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent) 0 calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--f)),
                              color-mix(in srgb, var(--rule-strong) 70%, transparent) 0);
  box-shadow: inset 0 1px 2px rgb(0 0 0 / .12); }
.bd-opacity input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; width: var(--thumb); height: var(--thumb); margin-top: calc((6px - var(--thumb)) / 2);
  border-radius: 50%; border: 1px solid color-mix(in srgb, var(--accent) 75%, #000);
  background: radial-gradient(circle at 50% 30%, #fff 0 22%, transparent 60%), var(--accent);
  box-shadow: 0 1px 3px rgb(0 0 0 / .28), inset 0 1px 0 rgb(255 255 255 / .6); transition: transform .12s; }
.bd-opacity input[type="range"]:hover::-webkit-slider-thumb { transform: scale(1.08); }
.bd-opacity input[type="range"]:focus-visible { outline: none; }
.bd-opacity input[type="range"]:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 35%, transparent), 0 1px 3px rgb(0 0 0 / .28); }
/* The pen's recent settings: a brush's shows its picture; each at its strength. */
.bd-recentpen svg, .bd-recentimg { opacity: var(--op, 1); }
.bd-recentimg { display: block; width: 30px; height: 15px; border-radius: 5px; pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .bd-brush { transition: none; }
  .bd-opacity input[type="range"]::-webkit-slider-thumb { transition: none; }
}

/* ── Videos (round 4) ────────────────────────────────────────────────────
   A YouTube video on the page (board/video.js). Its player sits in a layer
   of its own beneath the page's canvas and is seen through the hole the
   video cuts in the page, so writing over it stays in sight; the layer is
   lifted above the page (bd-vup) only while the mouse uses YouTube's own
   controls. The bar across its top is smoked glass — white on a dark tint
   reads on any picture, in either theme and on every paper — and fades
   while the video plays until a pointer comes near. The pane that asks for
   a link is the Board's own glass. */
.bd-vidlayer.bd-vup { z-index: 1; }
.bd-video { overflow: hidden; background: #000; }
.bd-video > * { position: absolute; left: 0; top: 0; }
.bd-vposter { width: 100%; height: 100%; object-fit: cover; pointer-events: none; user-select: none; }
.bd-vframe { display: block; border: 0; margin: 0; transform-origin: 0 0; background: #000; pointer-events: none; }
.bd-video.bd-vraised .bd-vframe { pointer-events: auto; }
.bd-vveil { width: 100%; height: 100%; pointer-events: none; }
.bd-vveil[hidden] { display: none; }
.bd-verr { width: 100%; height: 100%; box-sizing: border-box; display: grid; place-items: center; padding: 6%; text-align: center;
  font: 500 14px/1.4 system-ui, "Segoe UI", sans-serif; color: #fff; background: rgb(10 14 20 / .84); }
.bd-vbar { display: flex; align-items: center; gap: 2px; height: 34px; padding: 0 5px; box-sizing: border-box; transform-origin: 0 0;
  pointer-events: auto; touch-action: none; user-select: none; -webkit-user-select: none; cursor: grab; color: #fff;
  background: linear-gradient(180deg, rgb(8 12 18 / .86), rgb(8 12 18 / .72));
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .16), 0 1px 0 rgb(0 0 0 / .25); transition: opacity .25s; }
/* On glass, the Aqua hard-stop sheen across its top half. */
:root[data-style="glass"] .bd-vbar {
  background: linear-gradient(180deg, rgb(255 255 255 / .17) 0, rgb(255 255 255 / .05) 48%, transparent 48.5%),
              linear-gradient(180deg, rgb(8 12 18 / .86), rgb(8 12 18 / .72));
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.bd-vbar[hidden] { display: none; }
.bd-vbar.bd-vquiet { opacity: 0; pointer-events: none; }
.bd-vbar.bd-vdragging { cursor: grabbing; }
.bd-vbar.bd-vlocked { cursor: default; }
.bd-vbtn { display: inline-flex; align-items: center; gap: 5px; flex: none; height: 26px; margin: 0; padding: 0 9px;
  border: 1px solid transparent; border-radius: 999px; background: transparent; color: #fff; font: inherit; font-size: 12.5px;
  font-weight: 500; line-height: 1; white-space: nowrap; cursor: pointer; transition: background .15s, border-color .15s; }
.bd-vbtn svg { width: 16px; height: 16px; flex: none; }
.bd-vbtn:hover { background: rgb(255 255 255 / .16); border-color: rgb(255 255 255 / .24); }
.bd-vbtn:active { background: rgb(255 255 255 / .26); }
.bd-vbtn:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }
.bd-vbtn[hidden] { display: none; }
.bd-vplay { width: 30px; padding: 0; justify-content: center; }
.bd-vtitle { flex: 1 1 auto; min-width: 0; padding: 0 6px; font-size: 13px; font-weight: 600; line-height: 34px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bd-vtime { flex: none; padding: 0 4px; font-size: 12px; font-variant-numeric: tabular-nums; color: rgb(255 255 255 / .88); }
.bd-vtime:empty { display: none; }
.bd-vmid :is([data-v="pop"], [data-v="open"]) span, .bd-vnarrow .bd-vbtn span { display: none; }
.bd-vmid :is([data-v="pop"], [data-v="open"]), .bd-vnarrow .bd-vbtn { padding: 0 6px; }
.bd-vtiny .bd-vtime { display: none; }
/* Locked in place: a padlock before its title, and no handle. */
.bd-vlocked .bd-vtitle::before { content: ''; display: inline-block; width: 11px; height: 12px; margin-right: 6px; vertical-align: -1px;
  background: currentColor; -webkit-mask: var(--bd-vlock) center / contain no-repeat; mask: var(--bd-vlock) center / contain no-repeat; }
.bd-vbar { --bd-vlock: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 24'%3E%3Crect x='2' y='10' width='18' height='13' rx='2.5'/%3E%3Cpath d='M6 10V7a5 5 0 0 1 10 0v3' fill='none' stroke='%23000' stroke-width='3'/%3E%3C/svg%3E"); }
/* Asking for a link: "Insert video…", or Edit on a video. */
.bd-vask { position: absolute; z-index: 6; width: min(340px, calc(100% - 16px)); box-sizing: border-box; padding: 12px 14px;
  border-radius: 18px; background: var(--surface); border: 1px solid var(--rule-strong); box-shadow: var(--shadow-2); }
:root[data-style="glass"] .bd-vask { background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); box-shadow: var(--rim), var(--shadow-2); }
.bd-vaskhead { margin: 0 0 8px; font-size: 13.5px; font-weight: 600; color: var(--ink); }
.bd-vask input.bd-vaskurl { display: block; width: 100%; margin: 0; }
.bd-vaskwhy { margin: 6px 0 0; font-size: 12.5px; color: var(--red); }
.bd-vaskwhy:empty { display: none; }
.bd-vaskacts { display: flex; gap: 6px; margin-top: 10px; }
@media (prefers-reduced-motion: reduce) {
  .bd-vbar, .bd-vbtn { transition: none; }
}

/* ── Text: underline, bold and italic, and reading it back (round 4) ──
   board.js (openEditor, placeEditor) and js/board/text-format.js. The text
   box shows the styles as the page draws them: its underline as thick and
   as far below the letters (--bd-ut, --bd-uo, set for the face and zoom),
   broken by letters' tails as the page's is. Its lines wrap only between
   words, as the page's do. */
.bd-textedit.bd-rich { white-space: pre-wrap; overflow-wrap: normal; word-break: normal; overflow: visible; cursor: text; outline: none; }
.bd-textedit .bd-b { font-weight: 700; }
.bd-textedit .bd-i { font-style: italic; }
.bd-textedit .bd-u { text-decoration-line: underline; text-decoration-color: currentColor; text-decoration-thickness: var(--bd-ut, 1px);
  text-underline-offset: var(--bd-uo, 2px); text-underline-position: auto; text-decoration-skip-ink: auto; }
/* The row of B, I and U above the text box: a small glass lozenge. The one
   that is on is lit, not only outlined. */
.bd-fmt { position: absolute; left: 0; top: 0; z-index: 3; display: flex; gap: 2px; padding: 3px; border-radius: 999px; pointer-events: auto;
  background: var(--surface); border: 1px solid var(--rule-strong); box-shadow: var(--shadow-2); }
:root[data-style="glass"] .bd-fmt { background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); box-shadow: var(--rim), var(--shadow-2); }
.bd-fmtbtn { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 28px; margin: 0; padding: 0;
  border: 1px solid transparent; border-radius: 999px; background: transparent; color: var(--ink); font: 600 15px/1 var(--sans);
  cursor: pointer; transition: background-color .15s, box-shadow .15s; }
.bd-fmtbtn:hover { background-color: color-mix(in srgb, var(--accent) 14%, transparent); }
.bd-fmtbtn.on { background-color: color-mix(in srgb, var(--accent) 26%, var(--surface)); border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  color: var(--ink); box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 35%, transparent); }
:root[data-style="glass"] .bd-fmtbtn.on { background-image: var(--gloss-soft); box-shadow: var(--bevel), 0 0 8px color-mix(in srgb, var(--accent) 35%, transparent); }
.bd-fmtbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.bd-fmt-b { font-weight: 800; }
.bd-fmt-i { font-family: Georgia, 'Times New Roman', serif; font-style: italic; font-weight: 600; }
.bd-fmt-u { text-decoration: underline; text-decoration-thickness: 1.5px; text-underline-offset: 2px; }
/* What was read, large enough to read at a glance before it goes on the page. */
.bd-read .bd-readtext { font-size: 17px; line-height: 1.45; }
@media (prefers-reduced-motion: reduce) {
  .bd-fmtbtn { transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  :root[data-style="glass"] .bd-fmt { background-color: var(--surface); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* ── Selecting and locking (round 4) ──────────────────────────────────────
   board.js (startCursor, the Cursor's options) and js/board/lock.js. The
   Cursor's "Select by": Box or Lasso, a lozenge with its label inside. The
   selection bar's Lock: a padlock beside the word. The mouse over a thing
   locked down: a small glass bead with a padlock at its top right corner,
   which takes no presses. */
.bd-selby { align-items: center; }
/* (Darker than a plain label: the pane is see-through, and over light paper in the dark theme the muted ink measured 3.75:1.) */
.bd-selby .bd-seglabel { padding: 0 4px 0 8px; color: color-mix(in srgb, var(--ink) 45%, var(--muted)); }
.bd-sbtn[data-act="x-lock"] { display: inline-flex; align-items: center; gap: 4px; }
.bd-sbtn[data-act="x-lock"][hidden] { display: none; }
.bd-sbtn[data-act="x-lock"] svg { flex: none; margin-left: -2px; }
.bd-lockmark { position: absolute; left: 0; top: 0; z-index: 3; width: 26px; height: 26px; margin: -13px 0 0 -13px;   /* (under the panes and bars, z-index 4) */
  display: grid; place-items: center; border-radius: 50%; pointer-events: none; color: var(--ink);
  background: var(--surface); border: 1px solid var(--rule-strong); box-shadow: var(--shadow-2); }
.bd-lockmark[hidden] { display: none; }
:root[data-style="glass"] .bd-lockmark { background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); box-shadow: var(--rim), var(--shadow-2); }
@media (prefers-reduced-transparency: reduce) {
  :root[data-style="glass"] .bd-lockmark { background-color: var(--surface); backdrop-filter: none; -webkit-backdrop-filter: none; }
}
/* ── The pen's buttons (round 4) ──
   js/board/penbuttons.js: ⋯ → "Pen buttons…", a small glass pane that asks
   for each button, shows what arrived, and offers what each does; its Test
   chips light as a button is pressed. Muted words are mixed darker than a
   plain label, as the pane is see-through (measured ≥ 4.5:1 on every paper). */
.bd-pb { position: absolute; z-index: 6; width: min(360px, calc(100% - 16px)); box-sizing: border-box; padding: 10px 14px 12px;
  border-radius: 18px; background: var(--surface); border: 1px solid var(--rule-strong); box-shadow: var(--shadow-2); color: var(--ink); }
:root[data-style="glass"] .bd-pb { background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); box-shadow: var(--rim), var(--shadow-2); }
.bd-pb { --pb-quiet: color-mix(in srgb, var(--ink) 45%, var(--muted)); }
.bd-pbhead { display: flex; align-items: center; justify-content: space-between; font-size: 13.5px; font-weight: 600; margin-bottom: 6px; }
.bd-pbx { width: 26px; height: 26px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--ink);
  font: inherit; font-size: 18px; line-height: 1; cursor: pointer; transition: background .15s; }
.bd-pbx:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.bd-pbnum { display: block; font-size: 11.5px; color: var(--pb-quiet); }
.bd-pbask { font-size: 15px; margin: 2px 0 1px; }
.bd-pbmore, .bd-pbhint, .bd-pbwhy, .bd-pbseen { font-size: 12.5px; font-weight: 400; color: var(--pb-quiet); }
.bd-pbsaw { margin: 10px 0 6px; font-size: 13px; }
.bd-pbwait { color: var(--pb-quiet); }
.bd-pblog { max-height: 7.5em; overflow: hidden; padding: 6px 8px; border-radius: 10px; font: 11.5px/1.35 ui-monospace, Consolas, monospace;
  background: color-mix(in srgb, var(--ink) 6%, transparent); color: var(--ink); }
.bd-pbnone { color: var(--pb-quiet); font-family: inherit; }
.bd-pbacts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.bd-pbrow { margin: 8px 0 10px; }
.bd-pbname { font-size: 13px; font-weight: 600; margin-bottom: 4px; }
.bd-pbwhy { margin-top: 3px; }
/* (The app's own select rule is for forms: this one is a lozenge in the pane. It stays a flex box, as the
   app's customisable select is, so its chevron sits at the right on the label's line.) */
.bd-pb select.bd-pbsel { display: flex; justify-content: space-between; width: 100%; box-sizing: border-box; margin: 0; padding: 5px 12px; min-height: 0; height: auto;
  font: inherit; font-size: 13px; color: var(--ink); background-color: var(--surface); border: 1px solid var(--rule-strong); border-radius: 999px; box-shadow: none; }
.bd-pb select.bd-pbsel:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--rule-strong)); }
.bd-pb select.bd-pbsel:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 70%, transparent); outline-offset: 1px; }
.bd-pbtest { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 4px; }
.bd-pbtest .bd-pblabel { font-size: 12.5px; font-weight: 600; margin-right: 2px; }
.bd-pbchip { padding: 3px 11px; border-radius: 999px; font-size: 12.5px; border: 1px solid var(--rule-strong); background: transparent;
  transition: background .15s, box-shadow .15s; }
.bd-pbchip.bd-lit { background: color-mix(in srgb, var(--accent) 34%, var(--surface)); border-color: color-mix(in srgb, var(--accent) 70%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
@media (prefers-reduced-motion: reduce) {
  .bd-pbx, .bd-pbchip { transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  :root[data-style="glass"] .bd-pb { background-color: var(--surface); backdrop-filter: none; -webkit-backdrop-filter: none; }
}


/* ═══ tactile presses (GuideStar app.css 6655–6684) ═══ */
/* ══ Tactile ("we want the UI to feel tactile and satisfying") ═══════════
   Pressed, a control sinks a touch and dims its shine; let go, it springs
   back just past where it was, like a real key (static/js/sounds.js adds
   the classes). The `scale` property, not transform, so a thing placed by
   a transform (the radial menu's choices) keeps its place. */
.gs-pressed { scale: .95; filter: brightness(.96); transition: scale 70ms ease-out, filter 70ms ease-out !important; }
.gs-sprung { animation: gs-spring 380ms cubic-bezier(.34, 1.8, .64, 1); }
@keyframes gs-spring { from { scale: .95; } to { scale: 1; } }
/* A right answer: the card lifts a moment with a green glow. */
.deck-card.deck-win { animation: gs-win 620ms cubic-bezier(.34, 1.56, .64, 1); }
@keyframes gs-win {
  0% { scale: 1; box-shadow: 0 0 0 0 color-mix(in srgb, var(--green, #3a9a5b) 45%, transparent); }
  30% { scale: 1.012; box-shadow: 0 0 0 7px color-mix(in srgb, var(--green, #3a9a5b) 22%, transparent); }
  100% { scale: 1; box-shadow: 0 0 0 14px transparent; }
}
.deck-card .opt.right { animation: gs-spring 420ms cubic-bezier(.34, 1.8, .64, 1); }
/* The progress bar springs forward rather than sliding. */
.deck-bar > i { transition: width 520ms cubic-bezier(.34, 1.45, .64, 1); }
/* Sparkles from a run of right answers (sounds.js: sparkle). */
.gs-spark { position: fixed; z-index: 9999; width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px; border-radius: 50%;
  pointer-events: none; background: hsl(var(--h) 90% 62%); box-shadow: 0 0 8px hsl(var(--h) 90% 62%);
  animation: gs-spark 820ms cubic-bezier(.2, .7, .3, 1) forwards; }
@keyframes gs-spark { from { transform: translate(0, 0) scale(1); opacity: 1; }
  to { transform: translate(var(--dx), var(--dy)) scale(.3); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .gs-pressed, .gs-sprung, .deck-card.deck-win, .deck-card .opt.right { animation: none !important; scale: 1 !important; }
  .deck-bar > i { transition: none; }
  .gs-spark { display: none; }
}


/* ═══ the writing sound's tuner (GuideStar app.css 6716–6735) ═══ */
/* The writing sound's tuning panel (sounds.js: tuner), over the Board, below its header bar. */
.gs-tuner { position: fixed; top: 120px; right: 16px; z-index: 2000; width: min(300px, calc(100vw - 32px));
  max-height: calc(100vh - 136px); overflow: auto;
  padding: 10px 14px 6px; border-radius: 14px; background: var(--surface); color: var(--ink);
  border: 1px solid var(--rule-strong); box-shadow: var(--shadow-3); }
:root[data-style="glass"] .gs-tuner { background: color-mix(in srgb, var(--surface) 72%, transparent);
  backdrop-filter: blur(18px) saturate(1.3); -webkit-backdrop-filter: blur(18px) saturate(1.3); }
.gs-tuner-head { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
.gs-tuner-head b { flex: 1; white-space: nowrap; }
.gs-tuner-head select { font: inherit; font-size: .84rem; width: auto; min-width: 0; padding: 3px 24px 3px 8px; height: auto; }
/* A form label here is a slider's row, not the app's small-caps field label. */
.gs-tuner .gs-tune { display: block; margin: 6px 0; text-transform: none; letter-spacing: normal; font-weight: 400; }
.gs-tune-name { display: flex; justify-content: space-between; font-size: .84rem; color: var(--ink); }
.gs-tune-name output { color: var(--muted); font-variant-numeric: tabular-nums; }
.gs-tune input[type="range"] { width: 100%; margin: 2px 0 0; accent-color: var(--accent); }
.gs-tune-ends { display: flex; justify-content: space-between; font-size: .7rem; color: var(--faint); }
.gs-tuner-foot { display: flex; gap: 8px; margin: 8px 0 4px; }
.gs-tune select { display: block; width: 100%; margin-top: 2px; font: inherit; font-size: .84rem; }
.gs-tune-note { display: block; margin-top: 3px; font-size: .72rem; color: var(--faint); line-height: 1.35; }


/* ═══ signing in (lane L3's first part, 30 Sep 2026: app/auth.py) ═══ */
/* ── The sign-in page (templates/signin.html) ────────────────────────
   One pane in the middle of the aurora, the star over it: the only thing on the page. The pane is the app's own
   .card, so it is glass under the glass style and a raised card under plain, in every palette and both themes. */
main.signin-main { display: flex; justify-content: center; padding-top: 8vh; }
.signin { width: 100%; max-width: 400px; padding: 30px 30px 28px; text-align: center; }
.signin-mark { display: block; height: 58px; width: auto; aspect-ratio: 731 / 1185; margin: 0 auto 14px;
  filter: var(--logo-filter); }
.signin h1 { margin: 2px 0 8px; }
/* Small words on glass: the lifted grey, which clears 4.5:1 over the aurora where --muted alone can fall under. */
.signin-lede { font-size: 13.5px; color: color-mix(in srgb, var(--ink) 15%, var(--muted)); }
.signin-note { margin-top: 14px; font-size: 13.5px; font-weight: 500; }
/* What went wrong: the words in ink, the red a mark beside them. Measured over every palette, theme and style:
   --red as the words fell to 3.4:1 in the dark theme on glass where the aurora is strongest (Aero), and lifting it
   30% toward ink still left 4.35 - while a mark needs only 3:1, which it clears everywhere. */
.signin-warn { display: flex; align-items: flex-start; justify-content: center; gap: 7px; text-align: left; }
.signin-warnmark { flex: none; width: 16px; height: 16px; margin-top: 2px; }
.signin-warnmark circle { fill: var(--red); }
.signin-warnmark path { fill: none; stroke: var(--surface); stroke-width: 1.9; stroke-linecap: round; }
.signin-form { display: flex; flex-direction: column; gap: 7px; margin-top: 22px; text-align: left; }
.signin-form label { margin: 0; color: color-mix(in srgb, var(--ink) 15%, var(--muted)); }
/* 16px: below that a phone zooms the page in to type. */
.signin-form input[type="password"] { font-size: 16px; padding: 10px 12px; }
.signin-go { justify-content: center; width: 100%; margin-top: 12px; padding-block: 10px; }
.signin-lede + .signin-go { margin-top: 22px; }             /* "Signed in": the link straight under its words */
/* The name a password manager files the password under: in the form, out of sight and out of the tab order. */
.signin-user { position: absolute; left: 0; top: 0; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

/* ── Signed out while a page is open (static/js/app.js) ──────────────
   At the top in the middle, over everything: the same kind of pane as the editor's "changed in another window",
   with its one way on. It stays until this browser is signed in again. */
.signedout { position: fixed; z-index: 300; left: 50%; top: 14px; transform: translateX(-50%);
  display: flex; align-items: center; gap: 12px; width: max-content; max-width: calc(100% - 24px);
  padding: 8px 8px 8px 18px; border-radius: 999px; font-size: 13px; line-height: 1.4; color: var(--ink);
  background: var(--surface); border: 1px solid var(--rule-strong); box-shadow: var(--shadow-3); }
.signedout .btn { flex: none; }
:root[data-style="glass"] .signedout {
  background-color: var(--pane); background-image: var(--gloss-soft);
  backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule)); box-shadow: var(--rim), var(--shadow-3); }
/* Two lines on a phone: a pill that tall reads as a blob, so it becomes a pane with corners. */
@media (max-width: 640px) { .signedout { border-radius: 18px; padding: 10px 10px 10px 14px; } }
@media (prefers-reduced-transparency: reduce) {
  :root[data-style="glass"] .signedout { background-color: var(--surface); backdrop-filter: none; -webkit-backdrop-filter: none; }
}



/* ═══ invite links and the AI's limits (lanes L3 and L4, 30 Sep 2026: services/people.py, gateway.py) ═══ */
/* ── Who came in by a link, in the masthead ─────────────────────────
   Their name and what their link lets them do, where the owner has People. A lozenge like the nav's, not a link. */
.who-chip { display: inline-flex; align-items: center; gap: 8px; padding: 4px 6px 4px 12px; border-radius: 999px;
  border: 1px solid var(--rule-strong); font-size: 12.5px; color: var(--ink); white-space: nowrap; }
.who-chip b { font-weight: 500; max-width: 14em; overflow: hidden; text-overflow: ellipsis; }
.who-chip span { padding: 1px 8px; border-radius: 999px; font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  background: color-mix(in srgb, var(--accent) 14%, transparent); }
:root[data-style="glass"] .who-chip { background-image: var(--gloss); box-shadow: var(--bevel);
  border-color: color-mix(in srgb, var(--edge-light) 55%, var(--rule-strong)); }

/* ── The editor, view only (board.js VIEW_ONLY) ──────────────────────
   Nothing to draw with, so no tools, no tool options and no selection bar - the page, its zoom and its pages. A
   label in the head says so, in words and with an eye. */
.bd[data-view="1"] :is(.bd-rail, .bd-props, .bd-selbar, .bd-pgmore, .bd-autobtn) { display: none !important; }
.bd[data-view="1"] input.bd-title { cursor: default; }
.bd[data-view="1"] input.bd-title:hover, .bd[data-view="1"] input.bd-title:focus { border-color: transparent; background: transparent; }
.bd[data-view="1"] .bd-stage { cursor: grab; }
.bd-viewonly { display: inline-flex; align-items: center; gap: 6px; flex: none; padding: 3px 11px 3px 9px; border-radius: 999px;
  font-size: 12px; font-weight: 500; color: var(--ink); border: 1px solid var(--rule-strong);
  background: color-mix(in srgb, var(--accent) 12%, transparent); }
.bd-viewonly svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linejoin: round; }
:root[data-style="glass"] .bd-viewonly { background-image: var(--gloss); box-shadow: var(--bevel); }

/* ── The People page (templates/people.html) ─────────────────────────
   A new link on the left, the links on the right, the AI underneath. Its small words are a grey lifted toward the
   ink: measured on glass over the aurora in every palette and both themes (30 Sep 2026), the sign-in page's 15%
   left labels at 3.8:1 in Citrus and Ember dark (35% still 4.3:1 under a lit role), so 45% on the panes - and 55%
   for words straight on the aurora, with no pane under them, which fell to 3.5:1 in Aero light. */
main.pp, .bi-guestlede { --soft: color-mix(in srgb, var(--ink) 45%, var(--muted));
  --on-aurora: color-mix(in srgb, var(--ink) 55%, var(--muted)); }
main.pp :is(label, legend, th, .pp-soft, .pp-sub, .pp-opt), main.pp .stat :is(.label, .note) { color: var(--soft); }
main.pp .pp-lede, p.bi-guestlede { color: var(--on-aurora); }
.pp-lede { margin: 4px 0 0; max-width: 62ch; }
.pp-layout { display: grid; grid-template-columns: minmax(300px, 380px) minmax(0, 1fr); gap: 18px; align-items: start; }
@media (max-width: 980px) { .pp-layout { grid-template-columns: 1fr; } }
.pp-layout > .card + .card { margin-top: 0; }
.pp h2 { margin-bottom: 14px; }
.pp-set { border: 0; min-width: 0; }
.pp-set legend { display: block; font-size: 9.5px; letter-spacing: .2em; text-transform: uppercase; margin-bottom: 7px; font-weight: 500; }
.pp-opt { text-transform: none; letter-spacing: 0; font-size: 11px; }
/* Three lozenges, one lit: what they may do, each with a line saying what that means. */
.pp-roles { display: flex; flex-direction: column; gap: 6px; }
.pp-role { position: relative; display: grid; grid-template-columns: auto 1fr; column-gap: 9px; align-items: baseline; margin: 0;
  padding: 8px 12px; border-radius: 14px; border: 1px solid var(--rule-strong); cursor: pointer;
  text-transform: none; letter-spacing: 0; font-size: 13.5px; color: var(--ink); transition: background .15s, border-color .15s; }
.pp-role b { font-weight: 500; color: var(--ink); }
.pp-role span { grid-column: 2; font-size: 12px; color: var(--soft); }
.pp-role input { width: auto; box-shadow: none; accent-color: var(--accent); }
.pp-role:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.pp-role:has(input:checked) { border-color: color-mix(in srgb, var(--accent) 60%, var(--rule-strong));
  background: color-mix(in srgb, var(--accent) 13%, transparent); }
.pp-role:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
:root[data-style="glass"] .pp-role:has(input:checked) { background-image: var(--gloss); box-shadow: var(--bevel); }
.pp-check { display: flex; align-items: center; gap: 8px; margin: 0; padding: 4px 2px; cursor: pointer;
  text-transform: none; letter-spacing: 0; font-size: 13.5px; font-weight: 400; color: var(--ink) !important; }
.pp-check input { width: auto; flex: none; box-shadow: none; accent-color: var(--accent); }
.pp-check span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pp-check em { font-style: normal; color: var(--soft); }
.pp-pick { margin-top: 6px; max-height: 250px; overflow-y: auto; padding: 4px 10px 6px; border-radius: 12px;
  border: 1px solid var(--rule); background: color-mix(in srgb, var(--field-bg) 55%, transparent); transition: opacity .15s; }
.pp-pick.pp-muted { opacity: .5; }
.pp-pickhead { margin: 8px 0 2px; font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase; color: var(--soft); }
.pp-pickhead span { text-transform: none; letter-spacing: 0; }
.pp-two { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.pp-error { display: flex; gap: 7px; margin: 0 0 12px; font-size: 13.5px; font-weight: 500; color: var(--ink); }
.pp-error::before { content: '!'; flex: none; display: grid; place-items: center; width: 16px; height: 16px; margin-top: 2px;
  border-radius: 50%; background: var(--red); color: var(--surface); font-size: 11px; font-weight: 700; }
.pp-made h3 { margin-bottom: 4px; }
.pp-copyrow { display: flex; gap: 8px; margin: 12px 0; }
.pp-copyrow input { flex: 1; min-width: 0; font-size: 13px; font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace; }
.pp-copyrow .btn { flex: none; }
/* The list. */
.pp-table td { font-size: 13.5px; vertical-align: top; }
.pp-table td.r, .pp-table th.r { white-space: nowrap; }
.pp-name { font-weight: 500; }
.pp-sub { font-size: 11.5px; margin-top: 2px; }
.pp-rolechip { display: inline-block; padding: 1px 9px; border-radius: 999px; font-size: 12px; white-space: nowrap;
  border: 1px solid var(--rule-strong); }
.pp-rolechip.pp-edit_ai { background: color-mix(in srgb, var(--accent) 13%, transparent);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--rule-strong)); }
.pp-opencell { max-width: 280px; }
.pp-opens { display: inline-flex; align-items: center; gap: 5px; margin: 0 8px 2px 0; }
.pp-nbmark { display: inline-block; width: 9px; height: 9px; border-radius: 2px; background: var(--accent-2); }
.pp-all { font-weight: 500; }
.pp-when { white-space: nowrap; }
.pp-aicell { white-space: nowrap; }
.pp-limit { width: 4.6em; padding: 2px 6px; font-size: 13px; text-align: right; }
.pp-acts { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }
.pp-none { padding: 18px 4px; font-size: 13.5px; color: var(--soft); }
.pp-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* The AI. */
.pp-ai { margin-top: 18px; }
.pp-ai .card-head { flex-wrap: wrap; }
.pp-ai .card-head h2 { margin-bottom: 0; }
.pp-ai .stat .value small { font-size: 15px; color: var(--soft); }
.pp-ai .stat .note .bar { margin-top: 8px; }
.pp-loghead { margin: 20px 0 8px; }
.pp-log td { padding-block: 7px; }
