/* ============================================================================
   SAT Freshman -- the interface (spec/rebuild-v2.md 2.1, 2.3, Shell 2.13)

   Rules
   1  Functionality first. Every control a student acts on has a visible text
      label; legends carry counts, numbers carry units; a control that changes
      how practice behaves has a one-line helper under it. An empty state shows
      a dimmed sample row, says what will appear, and offers the smallest action
      that fills it, with its cost (UI.empty).
   2  One primary per region (a page header, a card, a sheet, a player, an
      action bar): the filled orange .pill. Secondary is .btn (a white face with
      a fine edge, a light top edge and a contact shadow, --ctl tall, radius 10,
      Geist 600 14px, ink). Tertiary is .quiet. Hierarchy comes from size and
      position.
   3  Depth, not lines. The page is warm (--page), cards are white (--raised)
      with a soft see-through edge inside --shadow-1; a clickable card steps to
      --shadow-2 on hover; the page's one hero (.card.hero) sits at --shadow-3
      on --hero-wash. Menus, popovers, tips and toasts --shadow-3/4, sheets and
      the palette --shadow-5. Shadows are warm-tinted, never grey or blue.
      Hairlines (1px --line) only split rows inside a surface. Radius 12
      (--radius-card), padding 20 (--card-pad; 16 on a phone), 32 between groups.
   4  Orange is the only accent. It marks what is yours or live: the selection,
      the key, the recommended action, your row, open-mistake dots, goal fills.
      Orange fills carry --ink-deep text (white on #F18508 is 2.4:1). Status is
      never colour alone: every state has a glyph and text. Destructive actions
      are graphite behind a confirm sheet, never red. Gradients: three only (the
      pill's faint top sheen, the amber fade under trend lines, --hero-wash).
      The pill's shadow is a warm contact shadow under 4px blur: no halo, no glow.
   5  One source per fact; each shows its working (UI.fact).
   6  The session and the test window have no chrome, and the test window stays
      flat (.rn-exam: no sheen, no shadows). Floats are menus, popovers, tips,
      toasts, sheets and the bars a list docks.
   7  Nothing a student does is lost.
   8  Four states per card: loading (cached copy, else static skeletons at final
      size, no shimmer), error (inside the card, with Retry), new student (the
      frame and its empty-state action stay), lots of history (paging, collapse).
   9  Phone at 390px: 16px gutters, no horizontal page scroll, 44px touch targets
      (--touch), sheets become bottom sheets, nothing hidden that desktop shows.
   10 Motion: one short page entry per visit (blocks rise 8px, at most 6, 30ms
      apart, never on back or refresh, the primary never delayed); hero numbers
      count or roll on a new score; the gauge sweeps on a fresh test; nothing
      else on load. Presses go down fast and come back slower; no hover lifts.
      Nothing loops but a spinner while it waits. prefers-reduced-motion and
      html[data-motion="reduce"] turn the movement off.
   11 Type: Geist for UI; Geist Mono (.mono) for clocks, codes and ids;
      Source Serif 4 for question text at --q-size / --q-opt.
   12 Copy: no emoji. "Question 7 of 22". Thousands separators. 1:12, 42h 10m.
   13 Speed is measured (Check -> verdict 100ms, Next -> stem 50ms, CLS 0.02).
   14 One question, one row (UI.questionActions). 15 Readable without sight.

   Look: html[data-theme="dark"|"light"] (light is the default; head.js resolves
   System), html[data-text="small"|"large"] (question text only),
   html[data-motion="reduce"]. Breakpoints: phone < 760px, rail < 1100px.

   Question states (2.3, R20, R35, R52, SR25): an element that shows one sets
   data-status and reads these tokens; it never paints a state by itself.
     --st-fill  solid fill (SVG points, legend swatches)
     --st-bg    background: --st-fill, or the split / slash of mixed and voided; correct and repaired cells are
                the amber wash (--wash-2: a wall of solid amber outshouted the primary), their --st-fill stays amber
     --st-edge, --st-edge-style   1.5px edge colour and style (transparent: none)
     --st-fg    the number   --st-halo  text-shadow for the number (mixed)
     --st-glyph the corner glyph (content: var(--st-glyph)), --st-mark its colour
     --st-badge a second glyph (fixed since), drawn in --amber-deep
     --st-flag  the flag notch colour on this fill
   Values: correct incorrect mixed repaired fixed unanswered blank skipped
   voided removed repeat not_reached answered, and the type states unmet met
   ready. Overlays: data-flag (notch, top-right, 9px), data-saved (bookmark),
   data-note (7px glyph, bottom-left), aria-current (--ring), data-filtered
   (30% opacity; the component also takes it out of Tab), an open mistake is
   an orange dot, being fixed is --hatch.
   Difficulty: data-tier="easy|medium|hard" gives --lvl (tint), --lvl-ink,
   --lvl-letter (E M H) and --lvl-n (bars filled, 1-3). Bars are ink on --mat.
   Mastery: a bar (--m-track, --m-fill) plus UI.masteryWord and accuracy.

   Print: the shell, overlays, controls and every button are hidden, dark
   prints light, state fills print exactly, rows never split. .no-print hides,
   .print-only shows only on paper, .page-break starts a page, .keep never
   splits, button.print-keep stays.

   Components, in this order; each group opens with the markup it expects:
     shell     .side (.rail .open) .scrim .pbar .tabs .tabs-grip .ibtn .nextup .side-day
               .now-strip .sections
     controls  .pill .btn .split-btn .spin .verb .seg .tablist .switch .pref .chips .check
               .field .counter .otp .dd .filters .selbar .score-slider .choices, range, checkbox
     data      .card (.hero .go .line) .ledge .hero-num .card-head .card-err .skel .stat .stats .meter .badge .kbd .table
               .table-wrap .thumbs .empty .pts [data-fact] .ring .avatar
     marks     .cell .cell-grid .sw .legend .strip .atlas .pips .dbars
     charts    .chart .chart-hits .ch-* .gauge .scale .cal .heat .spread .q-* .spark .sb-* .ds-*
     overlays  dialog.sheet dialog.more .menu .popover [role=option] .tip .toasts .banner
               dialog.palette .calc
     kit       .checks .filters-sec .date-form .skill-row .countdown .fact-pop .avatar.mark
   Where a page still defines one of these names itself (.seg, .cell, .tip, .legend,
   .stat, .card), the shared rule is written so the page's own rule wins until the
   page is rebuilt.
   ========================================================================== */

/* Geist and Geist Mono, variable, self-hosted (OFL, assets/fonts/OFL.txt): Latin and Cyrillic */
@font-face { font-family: "Geist"; src: url("/assets/fonts/Geist.woff2") format("woff2"); font-weight: 100 900; font-display: swap; }
@font-face { font-family: "Geist Mono"; src: url("/assets/fonts/GeistMono.woff2") format("woff2"); font-weight: 100 900; font-display: swap; }
/* 1. the Marks font: the marks Geist lacks (✓ ✕ ✗ ⋯ ▾ ▴ ▸ ⌄ ⓘ ↻ ↺ ⊘ ⊖ ◐ ★ ✎ ⚑ ■ □ ⌫ ⌇ ⠿ ⁻), drawn the same on
   every device. Goes beside Geist's @font-face. One weight serves all (100 900), so 9px/700 cells get no
   faux bold. block, not swap: it is 2 KB, and a mark that swaps shape mid-load is the bug being fixed. */
@font-face {
  font-family: "Marks"; src: url("/assets/fonts/Marks.woff2") format("woff2"); font-weight: 100 900; font-display: block;
  unicode-range: U+00AB, U+00B7, U+00BB, U+2039-203A, U+207B, U+21BA-21BB, U+22EF, U+2296, U+2298, U+2304, U+2307,
    U+232B, U+24D8, U+25A0-25A1, U+25B4, U+25B8, U+25BE, U+25CB, U+25CF-25D0, U+2605, U+2691, U+270E, U+2713,
    U+2715, U+2717, U+283F;
}

:root {
  color-scheme: light;

  --paper:      #FFFFFF;
  --ink:        #3F3F3F;     /* the brand graphite */
  --ink-deep:   #1E1E1E;     /* the label on orange: 6.4:1 where graphite is 4.1 */
  --amber:      #F18508;     /* the brand orange, for fills */
  --amber-deep: #D97705;     /* 3.2:1 on paper, enough to carry an edge alone */
  --wash:       #FEF4E7;     /* orange at reading strength */
  --wash-2:     #FDEBD3;
  --tile:       #F6F4F0;
  --tile-2:     #EFECE6;
  --mat:        #E9E5DF;
  --line:       #E4E1DC;
  --soft:       #6F6A62;     /* 4.55:1 on --tile-2, 4.88:1 on --tile: small text passes AA (#8A847C was 3.1-3.7:1) */
  --faint:      #B7B1A8;

  --on-ink:     #FFFFFF;     /* a number or glyph on an --ink fill */
  --knob:       #FFFFFF;     /* a switch's knob, light in both themes */
  --track:      rgba(63, 63, 63, .1);
  --backdrop:   rgba(40, 38, 34, .32);
  --ring:       0 0 0 2px var(--paper), 0 0 0 4px var(--ink);   /* current: 2px ink, 2px paper gap */

  /* surfaces (premium A1): a warm page, white cards on it, white floats. --paper stays white: the test
     window, fields and keycaps keep it. Dark lifts each level a step lighter. */
  --page:    #F7F5F1;      /* the canvas: body, sidebar, sticky bars */
  --raised:  #FFFFFF;      /* cards, stats, the hero, the sidebar Now card (shadow 1 to 3) */
  --float:   #FFFFFF;      /* menus, popovers, toasts, sheets, the palette (shadow 3 to 5) */
  --face:    #FFFFFF;      /* the face of .btn, chips and the seg thumb */
  --ink-strong: #1E1E1E;   /* headings and the one hero number; body text stays --ink */
  --ink-2:   #55514B;      /* secondary values, between --ink and --soft */
  --fs-hero: clamp(56px, 2.5vw + 28px, 64px);   /* the one number a page is about (.hero-num) */
  /* the amber ramp, light to deep; 500 is the brand fill */
  --amber-100: var(--wash);  --amber-200: var(--wash-2);  --amber-300: #F8BE7A;  --amber-400: #F5A142;
  --amber-500: var(--amber); --amber-600: var(--amber-deep); --amber-700: #A85B03;

  /* elevation: warm-tinted layers (offsets 1:2, alpha falling as they widen) behind a soft see-through edge,
     so one token works on the page, on a card and in dark. 1 card, 2 a clickable card on hover, 3 the hero,
     menus, popovers, 4 toasts and docked bars, 5 sheets and the palette */
  --sh:      60 52 44;       /* the warm shadow tint; under orange it is 140 70 0 */
  --edge:    0 0 0 1px rgb(var(--sh) / .09);
  --shadow-1: var(--edge), 0 1px 2px rgb(var(--sh) / .06), 0 2px 4px -2px rgb(var(--sh) / .06);
  --shadow-2: var(--edge), 0 1px 2px rgb(var(--sh) / .06), 0 2px 4px rgb(var(--sh) / .05), 0 6px 12px -4px rgb(var(--sh) / .08);
  --shadow-3: var(--edge), 0 1px 2px rgb(var(--sh) / .05), 0 3px 6px rgb(var(--sh) / .05), 0 8px 16px -4px rgb(var(--sh) / .07), 0 16px 24px -8px rgb(var(--sh) / .08);
  --shadow-4: var(--edge), 0 1px 2px rgb(var(--sh) / .05), 0 4px 8px rgb(var(--sh) / .05), 0 12px 24px -6px rgb(var(--sh) / .08), 0 24px 40px -12px rgb(var(--sh) / .10);
  --shadow-5: var(--edge), 0 1px 2px rgb(var(--sh) / .04), 0 4px 8px rgb(var(--sh) / .04), 0 16px 32px -8px rgb(var(--sh) / .10), 0 32px 64px -16px rgb(var(--sh) / .18);
  --shadow-float: var(--shadow-3);    /* the old names, kept */
  --shadow-pop:   var(--shadow-5);
  --shadow-scroll: 0 1px 0 rgb(var(--sh) / .07), 0 6px 12px -8px rgb(var(--sh) / .22);   /* a sticky bar while content slides under it */
  /* the faint warm corner tint behind the one hero (.card.hero) */
  --hero-wash: radial-gradient(120% 90% at 100% 0, rgb(241 133 8 / .07), transparent 60%);
  /* controls: the .btn and chip face (fine edge, contact shadow, a lip at the foot), a field's well, the
     hover band on a clickable row, the one selected look, and disabled as a solid grey */
  --edge-ctl:   0 0 0 1px rgb(var(--sh) / .14);
  --shadow-btn: var(--edge-ctl), inset 0 -1px 0 rgb(var(--sh) / .05), 0 1px 2px -1px rgb(var(--sh) / .18);
  --shadow-btn-down: var(--edge-ctl), inset 0 1px 2px rgb(var(--sh) / .16);
  --face-hover: #F7F6F3;
  --seg-thumb: var(--edge-ctl), 0 1px 3px rgb(var(--sh) / .12), 0 1px 2px -1px rgb(var(--sh) / .12);   /* a raised white thumb; no orange (it stays on the chosen row and Start) */
  --shadow-field: inset 0 0 0 1px rgb(var(--sh) / .18), inset 0 1px 2px rgb(var(--sh) / .07);
  --halo:    0 0 0 4px rgb(241 133 8 / .2);   /* the focused field's soft orange halo */
  --hover:   rgb(var(--sh) / .06);
  --sel-bg:  var(--wash-2);
  --sel-edge: inset 0 0 0 1.5px var(--amber);
  --off-bg:  #EFECE6;
  --off-fg:  #8C867D;
  /* the orange pill (REFERENCES R1): a faint top sheen, a darker edge lit at the top, a warm contact shadow */
  --pill-sheen: .2;
  --pill-edge:  color-mix(in srgb, var(--amber), #000 18%);
  --pill-edge-top: color-mix(in srgb, var(--amber), #000 5%);
  --pill-hover: color-mix(in srgb, var(--amber), #fff 9%);
  --pill-shadow: inset 0 1px 0 rgb(255 255 255 / .22), inset 0 -3px 6px -3px rgb(120 55 0 / .28), 0 1px 1px rgb(140 70 0 / .14), 0 2px 3px rgb(140 70 0 / .14);
  --pill-down:   inset 0 1px 3px rgb(120 55 0 / .35), 0 1px 1px rgb(140 70 0 / .12);
  /* tooltips: a dark chip with white text in both themes */
  --tip-bg:  #2A2723;  --tip-fg: #FFFFFF;  --tip-soft: rgb(255 255 255 / .68);
  /* a drawn check, for masks and for the checkbox (stroke in --ink-deep, the same in both themes) */
  --check-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-7' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --check-ink: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-7' fill='none' stroke='%231E1E1E' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  /* UI.ICON.caret_down (Phosphor caret-down) as a mask, 1.4px at 16px: the drawn chevron of a value trigger */
  --caret-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpolyline points='208 96 128 176 48 96' fill='none' stroke='black' stroke-width='22.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --hatch:      repeating-linear-gradient(-45deg, var(--amber) 0 1.5px, transparent 1.5px 5px);

  /* difficulty tints, ascending; each carries its own text colour */
  --lvl-easy: #FDEBD3;  --lvl-easy-ink: var(--ink-deep);
  --lvl-med:  #F8BE7A;  --lvl-med-ink:  var(--ink-deep);
  --lvl-hard: #F18508;  --lvl-hard-ink: var(--ink-deep);

  /* mastery bar */
  --m-track: var(--track);
  --m-fill:  var(--ink);

  /* question text, by prefs.text */
  --q-size: 20px;
  --q-opt:  18.5px;

  --font:  "Geist", "Marks", "Helvetica Neue", system-ui, sans-serif;
  --mono:  "Geist Mono", "Marks", ui-monospace, SFMono-Regular, Menlo, monospace;
  --serif: "Source Serif 4", "Marks", Georgia, "Times New Roman", serif;

  --radius:      10px;       /* .btn, fields, tiles */
  --radius-card: 12px;
  --card-pad:    20px;       /* 16 on a phone (BUILD rule 8) */
  --card-gap:    32px;       /* between cards in a grid or a column: groups get air (20 on a phone) */
  --row-h:       40px;       /* a list row; rows sit inside one card with hairlines */
  --touch:       44px;
  --ctl:         36px;       /* .btn, .seg, .chips, tabs, menu rows; --touch on a phone or a coarse pointer */

  /* what you sit on, and a track or button one step darker; cards, menus and sheets reset both (surfaces) */
  --surface: var(--page);
  --well:    var(--mat);

  --ease:   cubic-bezier(.23, 1, .32, 1);
  --max:    1120px;
  --gutter: clamp(16px, 4vw, 40px);
  --bar:    64px;
  --side-w: 248px;
  --rail-w: 68px;

  /* the has-note glyph: three text lines in currentColor, used as a background */
  --note-glyph: linear-gradient(currentColor 0 0) 0 0 / 100% 1.5px no-repeat,
                linear-gradient(currentColor 0 0) 0 50% / 100% 1.5px no-repeat,
                linear-gradient(currentColor 0 0) 0 100% / 62% 1.5px no-repeat;
}
:root[data-text="small"] { --q-size: 17px; --q-opt: 15.5px; }
:root[data-text="large"] { --q-size: 23px; --q-opt: 21px; }
@media (max-width: 759px) { :root { --card-pad: 16px; --card-gap: 20px; } }
@media (max-width: 759px), (pointer: coarse) { :root { --ctl: var(--touch); } }

/* dark (Shell 2.13); screen only, so paper always prints light */
@media screen {
  :root[data-theme="dark"] {
    color-scheme: dark;

    --paper:      #1A1917;
    --ink:        #ECE8E1;
    --ink-deep:   #1E1E1E;   /* stays: it is the label on orange */
    --amber:      #F18508;
    --amber-deep: #F59B32;
    --wash:       rgba(241, 133, 8, .13);
    --wash-2:     rgba(241, 133, 8, .22);
    --tile:       #242220;
    --tile-2:     #2D2A27;
    --mat:        #3A3631;
    --line:       #34312C;
    --soft:       #A39C92;
    --faint:      #6E685F;

    --on-ink:     #1A1917;
    --track:      rgba(236, 232, 225, .14);
    --backdrop:   rgba(0, 0, 0, .55);

    /* premium A1: higher is lighter; a soft white edge and a 1px top highlight instead of black outlines */
    --page:    #1A1917;
    --raised:  #242220;
    --float:   #2C2926;
    --face:    #312E2A;
    --ink-strong: #F5F2EC;
    --ink-2:   #CFC9C0;
    --amber-700: #FFC37A;
    --sh:      0 0 0;
    --edge:    0 0 0 1px rgb(255 255 255 / .07);
    --hi:      inset 0 1px 0 rgb(255 255 255 / .05);
    --shadow-1: var(--edge), var(--hi), 0 1px 2px rgb(0 0 0 / .3), 0 2px 4px -2px rgb(0 0 0 / .3);
    --shadow-2: var(--edge), var(--hi), 0 1px 2px rgb(0 0 0 / .3), 0 2px 4px rgb(0 0 0 / .25), 0 6px 12px -4px rgb(0 0 0 / .35);
    --shadow-3: var(--edge), var(--hi), 0 1px 2px rgb(0 0 0 / .3), 0 3px 6px rgb(0 0 0 / .25), 0 8px 16px -4px rgb(0 0 0 / .35), 0 16px 24px -8px rgb(0 0 0 / .4);
    --shadow-4: var(--edge), var(--hi), 0 1px 2px rgb(0 0 0 / .3), 0 4px 8px rgb(0 0 0 / .25), 0 12px 24px -6px rgb(0 0 0 / .4), 0 24px 40px -12px rgb(0 0 0 / .45);
    --shadow-5: var(--edge), var(--hi), 0 1px 2px rgb(0 0 0 / .3), 0 4px 8px rgb(0 0 0 / .25), 0 16px 32px -8px rgb(0 0 0 / .45), 0 32px 64px -16px rgb(0 0 0 / .6);
    --shadow-scroll: 0 1px 0 rgb(255 255 255 / .06), 0 6px 12px -8px rgb(0 0 0 / .6);
    --hero-wash: radial-gradient(120% 90% at 100% 0, rgb(241 133 8 / .08), transparent 60%);
    --edge-ctl:   0 0 0 1px rgb(255 255 255 / .09);
    --shadow-btn: var(--edge-ctl), inset 0 1px 0 rgb(255 255 255 / .07), 0 1px 2px rgb(0 0 0 / .4);
    --shadow-btn-down: var(--edge-ctl), inset 0 1px 2px rgb(0 0 0 / .4);
    --face-hover: #393530;
    --seg-thumb: var(--edge-ctl), inset 0 1px 0 rgb(255 255 255 / .06), 0 1px 2px rgb(0 0 0 / .45);
    --shadow-field: inset 0 0 0 1px rgb(255 255 255 / .1), inset 0 1px 2px rgb(0 0 0 / .4);
    --halo:    0 0 0 4px rgb(241 133 8 / .22);
    --hover:   rgb(255 255 255 / .05);
    --off-bg:  #2A2724;
    --off-fg:  #7A746B;
    --pill-sheen: .14;
    --pill-edge:  color-mix(in srgb, var(--amber), #000 40%);
    --pill-edge-top: color-mix(in srgb, var(--amber), #000 22%);
    --pill-shadow: inset 0 1px 0 rgb(255 255 255 / .2), inset 0 -3px 6px -3px rgb(120 55 0 / .3), 0 1px 1px rgb(0 0 0 / .4), 0 2px 3px rgb(0 0 0 / .35);
    --pill-down:   inset 0 1px 3px rgb(80 35 0 / .45), 0 1px 1px rgb(0 0 0 / .35);
    --tip-bg:  #46413B;  --tip-fg: #F5F2EC;  --tip-soft: rgb(245 242 236 / .7);

    /* dark ramps up in brightness; the two darker tints take light text */
    --lvl-easy: #4A3219;  --lvl-easy-ink: var(--ink);
    --lvl-med:  #8A500B;  --lvl-med-ink:  var(--ink);
    --lvl-hard: #F18508;  --lvl-hard-ink: var(--ink-deep);
  }
}

/* ------------------------------------------------------------------ question states (2.3) */

[data-status] {
  --st-fill: var(--paper); --st-bg: var(--st-fill);
  --st-edge: transparent;  --st-edge-style: solid;
  --st-fg: var(--ink);     --st-halo: none;
  --st-glyph: "";          --st-mark: var(--ink);  --st-badge: "";
  --st-flag: var(--amber);
}
/* right on the first try: check, top-right */
[data-status="correct"]     { --st-fill: var(--amber); --st-bg: var(--wash-2); --st-fg: var(--ink); --st-mark: var(--ink); --st-glyph: "\2713"; --st-flag: var(--amber-deep); }
/* wrong: cross */
[data-status="incorrect"]   { --st-fill: var(--ink); --st-fg: var(--on-ink); --st-mark: var(--on-ink); --st-glyph: "\2715"; }
/* right on try 2 or 3 in retry mode: a diagonal split, tilde */
[data-status="mixed"]       { --st-fill: var(--amber); --st-edge: color-mix(in srgb, var(--amber) 45%, transparent); --st-glyph: "~";
                              --st-bg: linear-gradient(135deg, var(--wash-2) 50%, var(--paper) 50%);
                              --st-halo: 0 0 2px var(--paper), 0 0 2px var(--paper); }
/* right on the repair after a miss: an ink cycle corner */
[data-status="repaired"]    { --st-fill: var(--amber); --st-bg: var(--wash-2); --st-fg: var(--ink); --st-mark: var(--ink); --st-glyph: "\21BB"; --st-flag: var(--amber-deep); }
/* wrong then, the mistake has closed since: cross plus a small amber check */
[data-status="fixed"]       { --st-edge: var(--ink); --st-glyph: "\2715"; --st-badge: "\2713"; }
/* not answered: never Incorrect (R35); en dash */
[data-status="unanswered"],
[data-status="blank"]       { --st-edge: var(--faint); --st-edge-style: dashed; --st-fg: var(--soft); --st-mark: var(--soft); --st-glyph: "\2013"; }
/* skipped, can still be answered before Finish: guillemet */
[data-status="skipped"]     { --st-edge: var(--soft); --st-edge-style: dashed; --st-glyph: "\BB"; }
/* a contest question voided by leaving or copying: a diagonal ink slash; a faint edge keeps it a box */
[data-status="voided"]      { --st-fg: var(--soft); --st-glyph: "\2298"; --st-edge: var(--faint);
                              --st-bg: linear-gradient(to top right, transparent calc(50% - .75px), var(--ink) calc(50% - .75px) calc(50% + .75px), transparent calc(50% + .75px)), var(--paper); }
/* taken out of scoring by a key correction (SR25): dotted ink edge, circled minus */
[data-status="removed"]     { --st-edge: var(--ink); --st-edge-style: dotted; --st-fg: var(--soft); --st-glyph: "\2296"; }
/* an exact re-attempt, left out of mastery: outline only, equals */
[data-status="repeat"]      { --st-fill: transparent; --st-edge: var(--ink); --st-glyph: "="; }
/* a set ended with this on screen, left out of counts: dotted faint edge, middle dot */
[data-status="not_reached"] { --st-edge: var(--faint); --st-edge-style: dotted; --st-fg: var(--soft); --st-mark: var(--soft); --st-glyph: "\B7"; }
/* answered, no verdict shown (exam, end mode) */
[data-status="answered"]    { --st-fill: var(--ink); --st-fg: var(--on-ink); --st-mark: var(--on-ink); --st-glyph: "\2013"; }
/* question types (R52): unmet dashed, met --tile-2, ready ink with a check */
[data-status="unmet"]       { --st-edge: var(--faint); --st-edge-style: dashed; --st-fg: var(--soft); --st-mark: var(--soft); --st-glyph: "\25CB"; }
[data-status="met"]         { --st-fill: var(--tile-2); --st-glyph: "\25CF"; }
[data-status="ready"]       { --st-fill: var(--ink); --st-fg: var(--on-ink); --st-mark: var(--on-ink); --st-glyph: "\2713"; }

/* outside the active filter */
[data-filtered] { opacity: .3; }

/* difficulty: tint, its text, the corner letter and the bars filled */
[data-tier="easy"]   { --lvl: var(--lvl-easy); --lvl-ink: var(--lvl-easy-ink); --lvl-letter: "E"; --lvl-n: 1; }
[data-tier="medium"] { --lvl: var(--lvl-med);  --lvl-ink: var(--lvl-med-ink);  --lvl-letter: "M"; --lvl-n: 2; }
[data-tier="hard"]   { --lvl: var(--lvl-hard); --lvl-ink: var(--lvl-hard-ink); --lvl-letter: "H"; --lvl-n: 3; }

/* ------------------------------------------------------------------ base */

*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%; text-size-adjust: 100%;
  accent-color: var(--amber);                       /* native radios and progress; checkboxes are drawn (controls) */
  scroll-padding-top: calc(var(--bar) + 8px);       /* anchors land below a sticky bar */
  scrollbar-color: var(--mat) transparent; scrollbar-width: thin;   /* thin warm scrollbars; scrollbar-color inherits */
}
/* from 760px there is no top bar: an anchor lands 16px down, or under the section bar where a page has one */
@media (min-width: 760px) {
  html { scroll-padding-top: 16px; }
  html:has(main > .sections:not([hidden])) { scroll-padding-top: 72px; }
}
html, body { min-height: 100%; }
body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font: 400 14.5px/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, pre, ol, ul { margin: 0; }
h1, h2, h3, h4, h5, h6 { text-wrap: balance; }
p { text-wrap: pretty; }
ol, ul { padding: 0; list-style: none; }
img, video { max-width: 100%; height: auto; }
button, input, select, textarea { font: inherit; color: inherit; letter-spacing: inherit; }
button { cursor: pointer; -webkit-tap-highlight-color: transparent; }
/* phone taps: no grey flash on anything tappable, no text selection or callout on controls */
a, label, summary, [role="button"], [role="tab"], [role="option"], [role^="menuitem"] { -webkit-tap-highlight-color: transparent; }
:where(.pill, .btn, .ibtn, .x, .quiet, .chips > *, .seg > *, .choice-row, [role="tab"], .side-item, .split-btn) {
  -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}
input, textarea { caret-color: var(--amber); }
button:disabled { cursor: default; }
textarea { resize: vertical; }
fieldset { margin: 0; padding: 0; border: 0; min-width: 0; }
legend { padding: 0; }
table { border-collapse: collapse; font-variant-numeric: tabular-nums; }
th { text-align: left; font-weight: 600; }
hr { height: 1px; margin: 0; border: 0; background: var(--line); }
kbd, code, samp, pre { font-family: var(--mono); font-size: .92em; }
/* a highlighter stroke over the lower part of the line; runner.css's mark.hl resets the background */
mark { background: linear-gradient(transparent 45%, var(--wash-2) 45%); color: inherit; border-radius: 2px;
  -webkit-box-decoration-break: clone; box-decoration-break: clone; }
summary { cursor: pointer; }
a { color: inherit; }
a:not([class]) {
  text-decoration-thickness: 1px; text-underline-offset: 3px; text-decoration-color: var(--faint);
  transition: text-decoration-color .16s var(--ease);
}
@media (hover: hover) and (pointer: fine) { a:not([class]):hover { text-decoration-color: var(--amber); } }
::selection { background: color-mix(in srgb, var(--amber) 32%, transparent); }
dialog { background: var(--float); color: var(--ink); }
dialog::backdrop { background: var(--backdrop); }
[hidden] { display: none !important; }

/* heard, not seen */
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* a fraction in the line: numerator over a bar over denominator (UI.fracText); the slash and brackets stay in the text,
   visually hidden, so highlights' offsets and screen readers are unchanged */
.frac { position: relative; display: inline-flex; flex-direction: column; align-items: stretch; vertical-align: middle;
  margin: 0 .12em; font-size: .9em; line-height: 1.2; text-align: center; white-space: nowrap; text-indent: 0; }
.frac > .fn { padding: 0 .2em .08em; border-bottom: max(1px, .065em) solid currentColor; }
.frac > .fd { padding: .1em .2em 0; }
.frac .frac { font-size: 1em; }
.fh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* "Skip to content": the first Tab stop, seen only when focused */
.skip {
  position: fixed; left: 12px; top: 12px; z-index: 100;
  padding: 10px 16px; border-radius: var(--radius);
  background: var(--ink); color: var(--on-ink); font: 600 14px var(--font); text-decoration: none;
  transform: translateY(calc(-100% - 24px));
}
.skip:focus { transform: none; }
/* focus (keyboard only): a thin amber-deep ring 2px out that follows the control's corners and leaves its own
   shadows under it, never ink. Text links get no box: the underline turns amber-deep and 2px, on a faint wash */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--amber-deep); outline-offset: 2px;
}
:is(a:not([class]), .link, .quiet, .fold-s, .why-q.word):focus-visible {
  outline: none; border-radius: 4px; background-color: var(--wash); background-clip: content-box;
  text-decoration: underline 2px var(--amber-deep); text-underline-offset: 4px;
}
main[tabindex="-1"]:focus { outline: none; }     /* the skip link's target */
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
/* an icon inside running text sits on the baseline like a letter (flex rows ignore this). Icon sizes: 16 beside
   12.5-14px text (.btn.sm, .quiet), 18 in .btn, .pill, menus and sidebar rows, 20 in .ibtn, 24 in the phone tabs */
:where(p, li, .meta, .help, .k, .note, .link) > svg { vertical-align: -.125em; }
@media screen { .print-only { display: none !important; } }

/* surfaces: the page (and the sidebar on it) is --page with --mat wells; a card, a panel, a stat and the Now card
   are --raised (white) with --tile-2 wells; anything that floats is --float. Dark steps each level lighter, so its
   wells are --mat inside cards and floats. Components read --surface and --well instead of patching themselves. */
:where(.card, .panel, .stat, .nextup, .side-day, .now-strip > *) { --surface: var(--raised); --well: var(--tile-2); }
:where(dialog, .popover, .menu) { --surface: var(--float); --well: var(--tile-2); }
@media screen {
  :root[data-theme="dark"] { --well: var(--tile-2); }
  :root[data-theme="dark"] :where(.card, .panel, .stat, .nextup, .side-day, .now-strip > *, dialog, .popover, .menu) { --well: var(--mat); }
}

/* .twocol: the task in main, context in aside.twocol-side. From 1280px wide the aside is a 300px column, sticky
   under the top (--twocol-top, 16px; a page with a sticky bar sets it; --twocol-bottom clears a fixed bottom bar)
   and scrolling inside itself; below that it
   follows main, or leads it with .twocol.side-first. (.split is the Check button's, runner.css.) */
.twocol { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--card-gap); align-items: start; }
.twocol > * { min-width: 0; }
.twocol-side { display: grid; gap: var(--card-gap); align-content: start; }
.twocol.side-first > .twocol-side { order: -1; }
@media (min-width: 1280px) {
  .twocol { grid-template-columns: minmax(0, 1fr) 300px; }
  .twocol.side-first > .twocol-side { order: 0; }
  .twocol-side {
    position: sticky; top: var(--twocol-top, 16px); max-height: calc(100vh - var(--twocol-top, 16px) - var(--twocol-bottom, 16px));
    overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin;
    padding: 8px; margin: -8px;            /* room for focus rings and card shadows at its edges */
  }
}

/* .reveal: a row's secondary actions and metas, shown while its parent is hovered or holds focus, on a fine
   pointer; always shown on a coarse one. Opacity only, so nothing shifts and its buttons stay in the tab order. */
@media (hover: hover) and (pointer: fine) {
  .reveal { opacity: 0; transition: opacity .16s var(--ease); }
  :hover > .reveal, :focus-within > .reveal, .reveal:focus-within,
  .reveal-host:hover .reveal, .reveal-host:focus-within .reveal { opacity: 1; }
}

/* scroll edges fade where there is more to scroll: UI.edgeFade(el[, x]) sets .fade-s (more before) and .fade-e
   (more after); .fade-x runs them across instead of down */
.fade-s:not(.fade-x) { -webkit-mask-image: linear-gradient(to bottom, transparent, #000 16px); mask-image: linear-gradient(to bottom, transparent, #000 16px); }
.fade-e:not(.fade-x) { -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 16px), transparent); mask-image: linear-gradient(to bottom, #000 calc(100% - 16px), transparent); }
.fade-s.fade-e:not(.fade-x) { -webkit-mask-image: linear-gradient(to bottom, transparent, #000 16px, #000 calc(100% - 16px), transparent); mask-image: linear-gradient(to bottom, transparent, #000 16px, #000 calc(100% - 16px), transparent); }
.fade-x.fade-s { -webkit-mask-image: linear-gradient(to right, transparent, #000 24px); mask-image: linear-gradient(to right, transparent, #000 24px); }
.fade-x.fade-e { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); }
.fade-x.fade-s.fade-e { -webkit-mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent); mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent); }

/* .choice-row: a row-shaped toggle (a deck, a filter row, a tile choice): --row-h tall, 8px corners, the label left
   and a count or meta right. At rest no fill, a hairline (a grey well read as disabled); hover the --hover band;
   pressed, checked or current is the chips' look (wash-2, the amber inset) */
.choice-row {
  appearance: none; border: 0; cursor: pointer; width: 100%; text-align: left; text-decoration: none; position: relative;
  display: flex; align-items: center; gap: 10px; min-height: var(--row-h); padding: 6px 12px; border-radius: 8px;
  background: none; box-shadow: inset 0 0 0 1px var(--line); color: var(--ink); font: 500 14px/1.3 var(--font);
  transition: background-color .16s var(--ease), box-shadow .16s var(--ease);
}
.choice-row > :is(.n, .meta, .choice-meta) { margin-left: auto; font: 500 12.5px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; white-space: nowrap; }
.choice-row svg { width: 18px; height: 18px; flex: none; }
.choice-row[aria-pressed="true"], .choice-row[aria-checked="true"], .choice-row[aria-current="true"], label.choice-row:has(input:checked) {
  background: var(--sel-bg); box-shadow: var(--sel-edge); font-weight: 600;
}
.choice-row:disabled, .choice-row[aria-disabled="true"], label.choice-row:has(input:disabled) { color: var(--faint); cursor: default; }
label.choice-row > input { position: absolute; opacity: 0; pointer-events: none; }
label.choice-row:has(input:focus-visible) { outline: 2px solid var(--amber-deep); outline-offset: 2px; }
@media (pointer: coarse) { .choice-row { min-height: var(--touch); } }
@media (hover: hover) and (pointer: fine) {
  .choice-row:not(:disabled):not([aria-disabled="true"]):not(:has(input:disabled)):not([aria-pressed="true"]):not([aria-checked="true"]):not([aria-current="true"]):not(:has(input:checked)):hover { background: var(--hover); }
}

/* ------------------------------------------------------------------ shell */

.bar {
  position: sticky; top: 0; z-index: 30;
  background: var(--page);
  padding-top: env(safe-area-inset-top, 0px);
  transition: box-shadow .2s var(--ease);
}
.bar.scrolled { box-shadow: var(--shadow-scroll); }
.bar-in {
  max-width: var(--max); margin: 0 auto; height: var(--bar);
  padding: 0 var(--gutter);
  display: flex; align-items: center; gap: 30px;
}
.brand { display: flex; align-items: center; flex: none; }
.brand svg { width: 26px; height: 26px; display: block; }
.nav { display: flex; gap: 4px; }
.nav a {
  position: relative; padding: 8px 10px; border-radius: 8px;
  font: 500 14.5px var(--font); color: var(--soft); text-decoration: none;
  transition: color .16s var(--ease), background-color .16s var(--ease);
}
.nav a[aria-current="page"] { color: var(--ink); font-weight: 600; }
.nav a[aria-current="page"]::after {
  content: ""; position: absolute; left: 10px; right: 10px; bottom: 1px;
  height: 2px; border-radius: 2px; background: var(--amber);
}
@media (hover: hover) and (pointer: fine) { .nav a:hover { color: var(--ink); } }

.bar-end { margin-left: auto; display: flex; align-items: center; gap: 14px; }
.streak {
  display: inline-flex; align-items: center; gap: 5px;
  font: 500 13px var(--font); font-variant-numeric: tabular-nums; color: var(--soft);
  text-decoration: none;
}
button.streak { appearance: none; border: 0; background: none; padding: 0; cursor: pointer; }
.streak svg { width: 16px; height: 16px; }
.streak.on { color: var(--ink); }
.streak.on .flame { fill: var(--amber); stroke: var(--amber-deep); }
.avatar {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: var(--well); color: var(--ink); text-decoration: none;
  font: 600 13px var(--font);
  transition: background-color .16s var(--ease);
}
@media (hover: hover) and (pointer: fine) { .avatar:hover { background: color-mix(in srgb, var(--well), var(--ink) 7%); } }
/* UI.avatar (U22): an initial, a topic glyph, the living mark (an svg) or this device's photo;
   .lg 40px, .xl 72px (Me); data-ring is an earned glyph: a 2px orange ring and a small check */
.avatar.lg { width: 40px; height: 40px; font-size: 15px; }
.avatar.xl { width: 72px; height: 72px; font-size: 26px; }
.avatar > img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }
.avatar[data-ring] { position: relative; box-shadow: 0 0 0 2px var(--amber); }
.avatar[data-ring]::after {
  content: "\2713"; position: absolute; right: -3px; bottom: -3px; width: 14px; height: 14px; border-radius: 50%;
  display: grid; place-items: center; background: var(--amber); color: var(--ink-deep);
  font: 700 9px/1 var(--font); box-shadow: 0 0 0 2px var(--paper);
}
.avatar.xl[data-ring]::after { width: 22px; height: 22px; font-size: 13px; }

.tabs { display: none; }

.page {
  max-width: var(--max); margin: 0 auto;
  padding: 32px var(--gutter) 64px;          /* page top 32 (BUILD rule 8); 20 on a phone */
}

@media (max-width: 759px) {
  .nav { display: none; }
  .bar-in { gap: 12px; }
  .tabs {
    display: grid; grid-template-columns: repeat(5, 1fr);
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
    /* the one blur besides a sheet's backdrop: content slides under the tab bar */
    background: color-mix(in srgb, var(--page) 86%, transparent); box-shadow: 0 -1px 0 var(--line);
    -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
  }
  .tabs a {
    position: relative;
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    padding: 6px 0 4px; text-decoration: none;
    font: 500 11.5px var(--font); color: var(--soft);
  }
  .tabs svg { width: 24px; height: 24px; }
  .tabs a[aria-current="page"] { color: var(--ink); font-weight: 600; }
  .tabs a[aria-current="page"]::before {
    content: ""; position: absolute; top: -6px; width: 18px; height: 2px;
    border-radius: 2px; background: var(--amber);
  }
  .tabs { min-height: calc(64px + env(safe-area-inset-bottom, 0px)); }
  /* hold a tab for its shortcut menu (U26): no callout, no text selection */
  .tabs a { justify-content: center; min-height: 52px; -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
  .tabs a .dot { position: absolute; top: 4px; left: calc(50% + 7px); box-shadow: 0 0 0 2px var(--page); }   /* graphite: see .side-meta .dot */
  .page { padding-top: 20px; padding-bottom: calc(96px + env(safe-area-inset-bottom, 0px)); }
}

/* the grip: a 44x24 button centred on the tab bar's top edge; it opens the menu sheet (U26) */
.tabs-grip {
  position: absolute; top: -12px; left: 50%; width: 44px; height: 24px; margin-left: -22px;
  padding: 0; border: 0; border-radius: 12px 12px 0 0; cursor: pointer;
  background: var(--page); box-shadow: 0 -1px 0 var(--line);
}
.tabs-grip::before {
  content: ""; position: absolute; left: 50%; top: 7px; width: 28px; height: 4px; margin-left: -14px;
  border-radius: 2px; background: var(--faint);
}
/* "Hold a tab for shortcuts", once, above the tabs; centred by margins, since rise animates transform */
.tabs-hint {
  position: fixed; z-index: 31; left: 0; right: 0; width: max-content; max-width: calc(100% - 32px); margin-inline: auto;
  bottom: calc(64px + 18px + env(safe-area-inset-bottom, 0px));
  display: flex; align-items: center; gap: 4px; padding: 5px 5px 5px 12px; border-radius: 10px;
  background: var(--ink); color: var(--on-ink); font: 500 13px var(--font); white-space: nowrap;
  box-shadow: var(--shadow-4); animation: rise .3s var(--ease) both;
}
.tabs-hint .x { width: 30px; height: 30px; color: inherit; opacity: .75; }

/* ------------------------------------------------------------------ the v2 shell (Shell 2.1-2.4, U1, U26)

   F2 renders it; these classes are the contract.
   body.has-side | body.has-rail   pages are inset by the sidebar (248px) or the rail (68px)
   <nav class="side [rail] [open]">, in this order:
     .side-brand   a.side-mark (svg 26 + .side-word) and button.ibtn.side-toggle
     button.side-search   svg, <span class="side-text">Search</span>, .kbd
     .nextup              Up next (U1); a.side-play is its rail form, a 44px orange play button (.own: white)
     button.side-quick    svg, .side-text "Quick", .kbd
     .side-nav > .side-group > .side-label, a.side-item (svg 20, .side-text, .side-meta)
     .side-day            the Today card; its .ring is the rail form
     .side-foot           a|button (svg, .side-text): Help & FAQ, Send feedback
     button.side-user     .avatar, .side-name, .side-sub, a chevron svg (hidden; aria-label on the rail)
   .side-meta[data-rail="count"|"dot"] says what a meta becomes on the rail: an orange count
   bubble or a 7px dot; a meta without it is hidden there (its tooltip carries it). A
   .side-item[data-ring] with style="--p: .4" draws the Today progress ring on the rail.
   .rail: icons only, text kept for screen readers. .open: the full sidebar over a .scrim
   (760-1099px wide, and the rail peek); drop .rail while it is open.
   Layout (redesign 2026-10-19): Search and a square Quick share a row, the group labels are
   for screen readers, Up next and the Today card sit together above the user row, Help and
   Feedback are icons beside it. Height tiers, no script: from 700px tall the Now block shows
   (body.no-now hides it where the page carries it); below 700px it moves to the Now strip;
   below 760px rows come down to 32px. The nav region scrolls when nothing else fits. The rail: 44px targets from
   880px tall, 36 below (Help and Feedback leave for the account menu); its nav scrolls. */

body.has-side { padding-left: var(--side-w); }
body.has-rail { padding-left: var(--rail-w); }

.side {
  --row: 34px; --lab: 0px; --gap: 8px;
  position: fixed; inset: 0 auto 0 0; z-index: 35;
  width: var(--side-w); padding: 0 12px 12px;
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto minmax(0, 1fr) auto auto auto;
  grid-template-areas: "brand brand" "search quick" "nav nav" "next next" "day day" "user foot";
  background: var(--page); border-right: 1px solid var(--line);   /* the sidebar sits on the canvas; its Now card is the one raised thing */
}
.side > * { min-width: 0; margin-bottom: var(--gap); }
.side > .side-user, .side > .side-brand { margin-bottom: 0; }
.side > .side-brand { grid-area: brand; }  .side > .side-search { grid-area: search; }
.side > .nextup, .side > .side-play { grid-area: next; }  .side > .side-quick { grid-area: quick; }
.side > .side-nav { grid-area: nav; }  .side > .side-day { grid-area: day; }
.side > .side-foot { grid-area: foot; }  .side > .side-user { grid-area: user; }

.side-brand { display: flex; align-items: center; gap: 8px; height: 52px; }
.side-mark { display: flex; align-items: center; gap: 10px; min-width: 0; color: var(--ink); text-decoration: none; border-radius: 8px; }
.side-mark svg { width: 26px; height: 26px; flex: none; display: block; }
.side-word { font: 800 13px/1.05 var(--font); letter-spacing: .06em; text-transform: uppercase; }
.side-toggle { margin-left: auto; }

.side-search, .side-quick {
  appearance: none; border: 0; cursor: pointer; width: 100%; min-width: 0;
  display: flex; align-items: center; gap: 10px;
  height: 36px; padding: 0 8px 0 11px; border-radius: 10px;
  font: 500 14px var(--font); text-align: left; text-decoration: none;
  transition: background-color .16s var(--ease), box-shadow .16s var(--ease);
}
.side-search { background: var(--well); color: var(--soft); border-radius: 8px; }
.side-quick  { background: var(--wash); color: var(--ink); }
.side-search svg, .side-quick svg { width: 18px; height: 18px; flex: none; }
.side-search .kbd { margin-left: auto; }
.side-quick { width: 36px; padding: 0; justify-content: center; margin-left: 8px; }
.side-quick .kbd { display: none; }
@media (hover: hover) and (pointer: fine) {
  .side-search:hover { background: color-mix(in srgb, var(--well), var(--ink) 6%); color: var(--ink); }
  .side-quick:hover { background: var(--wash-2); }
}

.side-play {
  display: none; place-items: center; justify-self: center;
  width: var(--rt, 44px); height: var(--rt, 44px); border-radius: 50%;
  background: var(--amber); color: var(--ink-deep); text-decoration: none;
}
.side-play svg { width: 18px; height: 18px; }
/* UI.shell({ ownPrimary }): the page's hero has the orange Start, so the rail's play is a white .btn face */
.side-play.own { background: var(--face); color: var(--ink); box-shadow: var(--shadow-btn); }

.side-nav { overflow-y: auto; overscroll-behavior: contain; margin: 0 -12px var(--gap); padding: 0 12px; scrollbar-width: thin; }
.side-group + .side-group { margin-top: 12px; }
/* "Study" and "Progress" are for screen readers: the 12px gap splits the groups for the eye */
.side-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.side-item {
  position: relative;
  display: grid; grid-template-columns: 20px minmax(0, 1fr) auto; align-items: center; gap: 10px;
  height: var(--row); padding: 0 10px; border-radius: 8px;
  font: 500 14px var(--font); color: var(--ink); text-decoration: none;
  transition: background-color .16s var(--ease);
}
.side-item svg { width: 18px; height: 18px; color: var(--soft); }
.side-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* current: the wash and 600, no side stripe (the user: no stripes on rows); the icon's filled twin is icons.css's
   (.side-item[aria-current] svg) */
.side-item[aria-current="page"] { font-weight: 600; }
.side-item[aria-current="page"] svg { color: var(--ink); }
@media (hover: hover) and (pointer: fine) { .side-item:not([aria-current="page"]):hover { background: var(--tile-2); } }
/* a fixed-width slot, so a badge never moves the label */
.side-meta {
  min-width: 40px; justify-self: end;
  display: inline-flex; align-items: center; justify-content: flex-end; gap: 5px;
  font: 500 12.5px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); white-space: nowrap;
}
.side-item[aria-current="page"] .side-meta { color: var(--ink); }
/* the chrome's dots are graphite, not orange: one orange per screen is the page's primary and the selection.
   They say: a new report (Results), today's plan or Up next waiting (Today tab), a test to resume or a test day
   (Tests tab), reviews due today (the phone bar's chip), today's challenge not started (the phone avatar),
   progress not saved to an email (the account row) */
:is(.side-meta, .side-sub, .tabs a, .pbar-chip, .pbar-me) .dot { background: var(--ink-2); }

.side-foot { display: flex; align-items: center; margin-bottom: 0; }
.side-foot > a, .side-foot > button {
  appearance: none; border: 0; background: none; cursor: pointer; position: relative;
  display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border-radius: 8px;
  color: var(--soft); text-decoration: none;
  transition: background-color .16s var(--ease), color .16s var(--ease);
}
.side-foot .side-text, .side-quick .side-text {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.side-foot svg { width: 18px; height: 18px; flex: none; }
.side-user {
  appearance: none; border: 0; background: none; cursor: pointer; width: 100%;
  display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 10px; align-items: center;
  min-height: 48px; padding: 6px 8px; border-radius: 10px; text-align: left; color: var(--ink);
  transition: background-color .16s var(--ease);
}
.side-user .avatar { grid-row: 1 / 3; background: var(--paper); box-shadow: inset 0 0 0 1px var(--line); }
.side-name { font: 600 14px/1.25 var(--font); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* name and sub line stay one line each: with no chevron the column beside Help and Feedback is ~100px, room for
   "Student 8B3A" and "Not saved yet"; a longer name or a masked email ends in an ellipsis */
.side-sub { display: block; min-width: 0; font: 500 12.5px/1.3 var(--font); font-variant-numeric: tabular-nums; color: var(--soft);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side-sub > :is(svg, i, .dot) { display: inline-block; vertical-align: -1px; margin-right: 5px; }
.side-user > svg { display: none; }   /* the chevron: the row is the menu's button (its hover band says so); kept in the DOM */
.side-user[aria-expanded="true"] { background: var(--tile-2); }
@media (hover: hover) and (pointer: fine) {
  .side-foot > a:hover, .side-foot > button:hover { background: var(--tile-2); color: var(--ink); }
  .side-user:hover { background: var(--tile-2); }
}

/* text that leaves the eye but stays for screen readers (rail, square Quick, icon rows) */
.side.rail .side-text, .side.rail .side-label, .side.rail .side-word {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* the Now block: Up next and the Today card, above the user row; where the page carries both, it leaves */
.no-now .side:not(.rail) > :is(.nextup, .side-day) { display: none; }
/* under 700px tall there is no room: both move to the Now strip in main */
@media (max-height: 699px) { .side .nextup, .side .side-day { display: none; } }
/* Short: rows come down to 32px */
@media (max-height: 759px) {
  .side { --row: 32px; --gap: 6px; }
  .side-brand { height: 44px; }
  .side-user { min-height: 44px; padding-block: 4px; }
  .side-user .avatar { width: 30px; height: 30px; }
}

/* the rail */
.side.rail {
  --rt: 44px; --gap: 6px;
  width: var(--rail-w);
  grid-template-columns: var(--rt); justify-content: center;
  grid-template-rows: auto auto auto auto minmax(0, 1fr) auto auto auto;
  grid-template-areas: "brand" "search" "next" "quick" "nav" "day" "foot" "user";
}
.side.rail .side-brand { height: 64px; justify-content: center; }
.side.rail .side-toggle { display: none; }                      /* the mark is the expand toggle (U26) */
.side.rail .side-search, .side.rail .side-quick { width: var(--rt); height: var(--rt); margin-left: 0; padding: 0; justify-content: center; }
.side.rail .side-search .kbd, .side.rail .side-quick .kbd { display: none; }
.side.rail .nextup { display: none; }
.side.rail .side-play { display: grid; }
.side.rail .side-group + .side-group { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--line); }
.side.rail .side-item svg { width: 20px; height: 20px; }
.side.rail .side-item { grid-template-columns: 1fr; justify-items: center; width: var(--rt); height: var(--rt); padding: 0; margin: 0 auto 2px; }
.side.rail .side-meta { display: none; position: absolute; top: 5px; right: 5px; min-width: 0; }
.side.rail .side-meta[data-rail="dot"] {
  display: block; width: 7px; height: 7px; border-radius: 50%; background: var(--ink-2);
  font-size: 0; overflow: hidden; box-shadow: 0 0 0 2px var(--page);
}
.side.rail .side-meta[data-rail="dot"] > * { display: none; }
.side.rail .side-meta[data-rail="count"] {
  display: inline-grid; place-items: center; top: 1px; right: -2px; min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 9px; background: var(--ink); color: var(--on-ink); font: 600 10.5px/1 var(--font); font-variant-numeric: tabular-nums;   /* a count, not an alert: graphite */
  box-shadow: 0 0 0 2px var(--page);
}
.side.rail .side-meta[data-rail="count"] .dot { display: none; }
.side.rail .side-item[data-ring]::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 34px; height: 34px; margin: -17px 0 0 -17px;
  border-radius: 50%; background: conic-gradient(var(--amber) calc(var(--p, 0) * 1turn), var(--mat) 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 1.5px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 1.5px));
}
.side.rail .side-day { display: grid; justify-items: center; padding: 0; background: none; box-shadow: none; }
.side.rail .side-day > :not(.ring) { display: none; }
.side.rail .side-day .ring { display: inline-grid; }
.side.rail .side-foot { display: grid; justify-items: center; margin-bottom: var(--gap); }
.side.rail .side-foot > a, .side.rail .side-foot > button { position: relative; width: var(--rt); height: var(--rt); padding: 0; justify-content: center; }
.side.rail .side-foot .side-text {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.side.rail .side-user { grid-template-columns: 1fr; justify-items: center; width: var(--rt); min-height: var(--rt); padding: 0; }
.side.rail .side-user > :not(.avatar) { display: none; }
.side.rail .side-user .avatar { grid-row: auto; }
@media (max-height: 879px) {
  .side.rail { --rt: 36px; --gap: 4px; }
  .side.rail .side-brand { height: 52px; }
  .side.rail .side-foot { display: none; }
}

/* the sidebar opened over the page (760-1099px wide, and the rail peek) */
.side.open { z-index: 45; box-shadow: var(--shadow-5); animation: side-in .2s var(--ease) both; }
.scrim { position: fixed; inset: 0; z-index: 44; background: var(--backdrop); animation: fade .2s var(--ease) both; }

/* phone: a 56px top bar (.pbar) and the five tabs */
.pbar { display: none; }
.pbar-mark { display: grid; place-items: center; width: 40px; height: 40px; margin-left: -7px; flex: none; color: var(--ink); border-radius: 10px; }
.pbar-mark svg { width: 26px; height: 26px; }
.pbar-grow { flex: 1 1 auto; min-width: 0; }
.pbar-chip {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; flex: none;
  min-width: 48px; height: 36px; padding: 0 11px; border-radius: 10px;
  background: var(--well); color: var(--ink); text-decoration: none;   /* neutral: the page's Start is the orange */
  font: 600 13px var(--font); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.pbar-chip[aria-current="page"] { background: var(--sel-bg); box-shadow: var(--sel-edge); }
.pbar-me { position: relative; }
.pbar-me .avatar { width: 32px; height: 32px; }
.pbar-me > .dot { position: absolute; top: 4px; right: 4px; box-shadow: 0 0 0 2px var(--page); }
@media (max-width: 759px) {
  .side, .scrim { display: none; }
  body.has-side, body.has-rail { padding-left: 0; padding-top: calc(56px + env(safe-area-inset-top, 0px)); }
  .pbar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 30;
    display: flex; align-items: center; gap: 8px;
    height: calc(56px + env(safe-area-inset-top, 0px)); padding: env(safe-area-inset-top, 0px) 16px 0;
    background: var(--page); transition: box-shadow .2s var(--ease);
  }
  .pbar.scrolled { box-shadow: var(--shadow-scroll); }
}
@media (max-width: 379px) { .pbar { gap: 4px; } }

/* a square icon button: --ib sets its size (36px; the phone bar uses 40). A ghost until hovered (quiet menus: a
   More button is never a grey square at rest); pressed, or holding its menu or dialog open, it keeps the well (an open
   fold's arrow does not: its turned caret says it); a press sinks it */
.ibtn {
  appearance: none; border: 0; background: none; cursor: pointer; flex: none; padding: 0;
  display: inline-grid; place-items: center; width: var(--ib, 36px); height: var(--ib, 36px);
  border-radius: 10px; color: var(--soft); text-decoration: none;
  transition: background-color .14s var(--ease), color .14s var(--ease), transform .25s cubic-bezier(.3, .7, .4, 1);
}
.ibtn svg { width: 20px; height: 20px; }
.ibtn[aria-pressed="true"], .ibtn[aria-haspopup][aria-expanded="true"] { background: var(--well); color: var(--ink); }
.ibtn[aria-expanded="true"] { color: var(--ink); }
.ibtn:not(:disabled):active { transform: scale(.94); transition-duration: .05s; }
.ibtn:disabled { color: var(--faint); cursor: default; }
.pbar .ibtn { --ib: 40px; color: var(--ink); }
@media (hover: hover) and (pointer: fine) { .ibtn:not(:disabled):hover { background: var(--well); color: var(--ink); } }

/* Up next (U1): the sidebar block, the Now strip cell and the menu sheet's first row.
   .k, .nextup-t title, .nextup-m "3 q · 4 min · ≈ 6 pts", .nextup-r reason,
   .nextup-go (a .pill Start, a .btn with UI.shell({ ownPrimary }), with a .kbd "G N", then an .ibtn › for five more) */
.nextup {
  display: grid; gap: 4px; padding: 12px; border-radius: 12px;
  background: var(--raised); box-shadow: var(--shadow-1);
}
.nextup-t { font: 600 14.5px/1.3 var(--font); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.nextup-m { font: 500 12.5px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; }
.nextup-r { font: 400 13px/1.4 var(--font); color: var(--soft); }
.nextup-go { display: flex; gap: 6px; margin-top: 6px; }
.nextup-go :is(.pill, .btn) { flex: 1 1 auto; height: 34px; padding: 0 14px; font-size: 14px; }
.side .nextup { position: relative; }
.now-fold { --ib: 24px; position: absolute; top: 6px; right: 6px; }
.now-fold svg { width: 16px; height: 16px; transform: rotate(90deg); transition: transform .2s var(--ease); }
.side.now-folded .now-fold { position: static; }
.side.now-folded .now-fold svg { transform: rotate(-90deg); }
.side.now-folded > .nextup { grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; column-gap: 6px; padding: 6px 6px 6px 12px; }
.side.now-folded > .nextup > .k { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.side.now-folded > .nextup > .nextup-t { -webkit-line-clamp: 1; font-size: 14px; }
.side.now-folded > .nextup > .nextup-go { margin: 0; }
.side.now-folded > .nextup > .nextup-go :is(.pill, .btn) { flex: none; padding: 0 12px; }
.side.now-folded > .nextup > .nextup-go .ibtn { display: none; }
.side.now-folded > .nextup:not([hidden]) ~ .side-day { display: none; }

/* the Today card: the sidebar's, the Now strip's and the menu sheet's.
   .side-day-head (.k TODAY, the streak), .meter rows, .side-day-foot "Exam in 45 days · Nov 7" */
.side-day {
  display: grid; gap: 8px; padding: 12px; border-radius: 12px;
  background: var(--raised); box-shadow: var(--shadow-1);
  color: var(--ink); text-decoration: none; font: 500 13px/1.35 var(--font);
}
/* the sidebar's Now card: Up next over the Today card reads as one white raised card. They are two grid items,
   so Up next keeps only its edge (which draws the hairline between them, since it paints above) and the Today
   part carries the shadow; either one alone (folded, hidden) keeps its full corners */
.side:not(.rail):not(.now-folded) > .nextup:not([hidden]):has(~ .side-day:not([hidden])) {
  margin-bottom: 0; border-radius: 12px 12px 0 0; box-shadow: var(--edge);
}
.side:not(.rail):not(.now-folded) > .nextup:not([hidden]) ~ .side-day:not([hidden]) { border-radius: 0 0 12px 12px; }
.side-day-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.side-day-foot { font: 500 12.5px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; }
.side-day .ring { display: none; }

/* the Now strip (U26): the first row of main whenever the sidebar is not showing Up next in
   full. One 44px line, "Up next · title · 8 min · Start ⋯"; the reason is the title's tooltip. */
.now-strip { display: grid; grid-template-columns: minmax(0, 1fr); margin: 0 0 16px; }
.now-strip > .nextup {
  display: flex; align-items: center; gap: 10px; min-height: 44px; padding: 5px 6px 5px 16px;
  border-radius: var(--radius-card); background: var(--raised); box-shadow: var(--shadow-1);
}
.now-strip > .nextup > .k { flex: none; }
.now-strip > .nextup > .nextup-t { display: block; flex: 0 1 auto; min-width: 0; white-space: nowrap; text-overflow: ellipsis; }
.now-strip > .nextup > .nextup-m { flex: none; white-space: nowrap; }
.now-strip > .nextup > .nextup-t::before, .now-strip > .nextup > .nextup-m::before { content: '\00b7'; margin-right: 10px; color: var(--soft); }
.now-strip > .nextup > .nextup-r { display: none; }
.now-strip > .nextup > .nextup-go { flex: none; margin: 0 0 0 auto; }
.now-strip .nextup-go :is(.pill, .btn) { flex: 0 1 auto; min-width: 0; padding: 0 18px; }
@media (max-width: 759px), (pointer: coarse) { .now-strip > .nextup { padding-block: 0; } }
@media (max-width: 559px) { .now-strip > .nextup > .nextup-m { display: none; } }
/* the "more up next" list: every row the same, Start the same compact size, Not today beside it */
.mup-row { display: grid; gap: 2px; padding-top: 10px; border-top: 1px solid var(--line); }
.mup-acts { display: flex; align-items: center; gap: 12px; margin-top: 8px; }
.mup-acts .pill { height: 34px; min-width: 88px; padding: 0 16px; border-radius: 10px; font-size: 14px; }
@media (min-width: 1100px) and (min-height: 700px) {
  body.has-side .now-strip { display: none; }
  body.has-side:not(.no-now) .sections-next { display: none; }
  body.has-side:not(.no-now) .sections:not(:has(> a, > button)) { display: none; }
}

/* the section bar (U26): UI.sections, sticky at the top of main. Chips are a|button with
   aria-current="true" on the one in view; .sections-next ("Up next: ... · Start") shows
   once the Now strip has scrolled away (.stuck). Desktop wraps; the phone scrolls sideways. */
.sections {
  position: sticky; top: 0; z-index: 20;
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  min-height: 36px; padding: 8px 0; margin: 0 0 16px;
  background: var(--page);
}
/* scroll shadows: a sticky bar gets a soft shadow only while content slides under it (.scrolled, .stuck) */
.sections.stuck, :where(.scrolled) { box-shadow: var(--shadow-scroll); }
.sections > a, .sections > button {
  appearance: none; border: 0; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; height: 28px; padding: 0 10px; border-radius: 8px;
  background: var(--face); box-shadow: var(--edge-ctl); color: var(--ink); text-decoration: none;
  font: 500 13px var(--font); white-space: nowrap; scroll-snap-align: start;
  transition: background-color .16s var(--ease), box-shadow .16s var(--ease);
}
.sections > [aria-current="true"] { background: var(--sel-bg); box-shadow: var(--sel-edge); font-weight: 600; }
@media (hover: hover) and (pointer: fine) { .sections > a:not([aria-current="true"]):hover, .sections > button:not([aria-current="true"]):hover { background: var(--face-hover); } }
.sections-next { display: none; align-items: center; gap: 10px; margin-left: auto; min-width: 0; font: 500 13px var(--font); color: var(--soft); }
.sections-next b { color: var(--ink); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sections.stuck .sections-next { display: inline-flex; }
@media (max-width: 759px) {
  .sections {
    top: calc(56px + env(safe-area-inset-top, 0px));
    flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none;
    scroll-snap-type: x proximity; scroll-padding-inline: var(--gutter);
    margin-inline: calc(-1 * var(--gutter)); padding: 0 var(--gutter);
  }
  .sections::-webkit-scrollbar { display: none; }
  /* 44px to tap, 32px to see */
  .sections > a, .sections > button { position: relative; height: 44px; background: none; }
  .sections > a::before, .sections > button::before {
    content: ""; position: absolute; inset: 6px 0; z-index: -1; border-radius: 8px; background: var(--face); box-shadow: var(--edge-ctl);
  }
  .sections > a, .sections > button, .sections > [aria-current="true"] { box-shadow: none; }
  .sections > [aria-current="true"]::before { background: var(--sel-bg); box-shadow: var(--sel-edge); }
  .sections-next { flex: none; }
}

/* ------------------------------------------------------------------ type */

/* the scale (redesign 2026-10-19, premium A1): hero 56-64 (--fs-hero, one number per page), gauge 44, figure 28,
   page 22, section 16, body 14.5, control 14, meta 12.5, micro 11.5; weights 400/500/600. Headings and the hero
   number in --ink-strong, body in --ink, secondary values --ink-2, meta --soft. Words are never mono or uppercase. */
.k { font: 500 12.5px/1.3 var(--font); color: var(--soft); }
.num { font-weight: 600; letter-spacing: -.03em; line-height: 1; font-variant-numeric: tabular-nums; }
.meta { font: 500 12.5px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; }
.h1 { font: 600 22px/1.2 var(--font); letter-spacing: -.02em; text-wrap: balance; color: var(--ink-strong); }
.h2 { font: 600 16px/1.3 var(--font); letter-spacing: -.01em; color: var(--ink-strong); }
.h3 { font: 600 14.5px/1.35 var(--font); letter-spacing: -.005em; color: var(--ink-strong); }
/* the hero number: <span class="hero-num">1340<small>of 1600</small></span>; the unit sits small on its baseline.
   Proportional figures (a lone number); UI.roll sets tabular ones itself while it rolls */
.hero-num { display: inline-flex; align-items: baseline; gap: 8px; font: 600 var(--fs-hero)/1 var(--font); letter-spacing: -.035em; color: var(--ink-strong); }
.hero-num small { font: 500 15px/1.2 var(--font); letter-spacing: 0; color: var(--soft); }
/* a form label, and the one-line helper under a control that changes how practice behaves (rule 1) */
.label { display: block; margin-bottom: 6px; font: 600 14px/1.3 var(--font); color: var(--ink); }
.help { margin-top: 6px; font: 400 12.5px/1.45 var(--font); color: var(--soft); text-wrap: pretty; }

/* ------------------------------------------------------------------ controls */

/* the orange pill (REFERENCES R1, BUILD 4): the brand fill under a faint top sheen, a darker edge lit at the top,
   a warm contact shadow (blur under 4px, no glow). Hover brightens the fill about 9% and never lifts; a press sinks
   1px with an inner shadow, down in 50ms and back on a slow release (Comeau). Busy and done below. */
.pill {
  appearance: none; cursor: pointer; text-decoration: none; position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 42px; padding: 0 22px; border-radius: 10px;   /* the one primary button: compact, softly cornered. No 999px pills, nothing taller than 48px (tools/check-ui.py) */
  border: 1px solid var(--pill-edge); border-top-color: var(--pill-edge-top);
  background-color: var(--amber); color: var(--ink-deep);
  background-image: linear-gradient(rgb(255 255 255 / var(--pill-sheen)), rgb(255 255 255 / 0) 55%);
  box-shadow: var(--pill-shadow);
  font: 600 14px var(--font); letter-spacing: -.005em;
  transition: transform .25s cubic-bezier(.3, .7, .4, 1), background-color .14s var(--ease), box-shadow .14s var(--ease), opacity .2s var(--ease);
  user-select: none; -webkit-user-select: none;
}
.pill.wide { width: 100%; }
/* a card's or a sheet's primary: the same pill at control height */
/* the same button, a size down */
.pill.sm { height: 34px; padding: 0 18px; border-radius: 10px; font-size: 14px; gap: 7px; }
.pill svg { width: 18px; height: 18px; flex: none; }
.pill .kbd { background: rgba(30, 30, 30, .12); box-shadow: none; color: var(--ink-deep); }
@media (hover: hover) and (pointer: fine) {
  .pill:not(:disabled):hover { background-color: var(--pill-hover); }
}
.pill:not(:disabled):active { transform: translateY(1px) scale(.98); box-shadow: var(--pill-down); transition-duration: .05s; }
/* disabled is a solid grey, not a see-through orange */
.pill:disabled {
  cursor: default; opacity: 1; color: var(--off-fg); background-color: var(--off-bg); background-image: none;
  border-color: transparent; box-shadow: none;
}
[aria-busy="true"] { pointer-events: none; }       /* a second click would start a second sitting */

/* busy (aria-busy) on a .pill or .btn: nothing for 300ms (a quick save never flashes), then the label fades and a
   small spinner turns in its place, so the width holds. A .spin inside the button (kit's saving) is used instead. */
:is(.pill, .btn:not(.dd))[aria-busy="true"] { cursor: progress; }
:is(.pill, .btn:not(.dd))[aria-busy="true"]:not(:has(.spin)) { animation: busy-label .12s .3s forwards; }
:is(.pill, .btn:not(.dd))[aria-busy="true"]:not(:has(.spin)) > * { animation: busy-fade .12s .3s forwards; }
:is(.pill, .btn:not(.dd))[aria-busy="true"]:not(:has(.spin))::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 16px; height: 16px; margin: -8px 0 0 -8px;
  border-radius: 50%; border: 2px solid currentColor; border-right-color: transparent;
  color: var(--ink-deep); opacity: 0; animation: busy-in .12s .3s forwards, spin .7s linear infinite;
}
.btn:not(.dd)[aria-busy="true"]::after { color: var(--ink); }
/* a spinner to put inside any button or line: <span class="spin" aria-hidden="true"></span> */
.spin {
  display: inline-block; flex: none; width: 14px; height: 14px; border-radius: 50%; vertical-align: -2px;
  border: 1.75px solid currentColor; border-right-color: transparent; animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(1turn); } }
@keyframes busy-in { to { opacity: 1; } }
@keyframes busy-fade { to { opacity: 0; } }
@keyframes busy-label { to { color: transparent; } }
/* done: a check draws in before the label (Saved, Ready); the class goes back off when the moment ends */
.pill.done::after {
  content: ""; order: -1; flex: none; width: 16px; height: 16px; background: currentColor;
  -webkit-mask: var(--check-mask) center / contain no-repeat; mask: var(--check-mask) center / contain no-repeat;
  animation: check-draw .24s var(--ease) both;
}
@keyframes check-draw { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0); } }
/* a spinner never strobes under reduced motion: it stands still */
@media (prefers-reduced-motion: reduce) {
  .spin { animation: none; }
  :is(.pill, .btn:not(.dd))[aria-busy="true"]:not(:has(.spin))::after { animation: busy-in 0s .3s forwards; }
}
:root[data-motion="reduce"] .spin { animation: none; }
:root[data-motion="reduce"] :is(.pill, .btn:not(.dd))[aria-busy="true"]:not(:has(.spin))::after { animation: busy-in 0s .3s forwards; }

/* secondary (rule 2, BUILD 10): a white face with a fine edge, a light top edge (dark) or a lip at the foot (light)
   and a contact shadow; --ctl tall (36, 44 on a phone), radius 10, Geist 600 14px, ink. Hover steps the face, a
   press sinks it; pressed or open (aria-pressed, aria-expanded) stays the orange selected look. .sm 34px like
   .pill.sm; .icon square; .ink is the graphite confirm of a destructive action (never red). */
.btn {
  appearance: none; border: 0; cursor: pointer; text-decoration: none; position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--ctl); padding: 0 16px; border-radius: var(--radius);
  background: var(--face); color: var(--ink); box-shadow: var(--shadow-btn);
  font: 600 14px/1.2 var(--font); white-space: nowrap;
  transition: background-color .14s var(--ease), box-shadow .14s var(--ease), transform .25s cubic-bezier(.3, .7, .4, 1), opacity .2s var(--ease);
  user-select: none; -webkit-user-select: none;
}
.btn svg { width: 18px; height: 18px; flex: none; }
.btn .n { font: 500 12.5px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; }
.btn.sm { min-height: 34px; padding: 0 12px; border-radius: 8px; font-size: 14px; gap: 6px; }
.btn.sm svg { width: 16px; height: 16px; }
.btn.icon { width: var(--ctl); padding: 0; }
.btn.sm.icon { width: 34px; }
/* small controls keep a 44px hit area on a coarse pointer, with no layout shift */
@media (pointer: coarse) {
  .btn.sm, .pill.sm, .ibtn, .x, .quiet { position: relative; }
  .btn.sm::before, .pill.sm::before, .ibtn::before, .x::before, .quiet::before { content: ""; position: absolute; left: 50%; top: 50%; width: max(100%, 44px); height: max(100%, 44px); transform: translate(-50%, -50%); }
}
.btn.ink {
  background: var(--ink); color: var(--on-ink);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .18), inset 0 -1px 0 rgb(0 0 0 / .2), 0 1px 2px rgb(0 0 0 / .14), 0 2px 4px -2px rgb(0 0 0 / .18);
}
.btn.ink .n { color: inherit; opacity: .75; }
.btn:not(:disabled):not([aria-disabled="true"]):active { transform: scale(.97); box-shadow: var(--shadow-btn-down); transition-duration: .05s; }
.btn.ink:not(:disabled):active { box-shadow: inset 0 1px 2px rgb(0 0 0 / .35); }
.btn:disabled, .btn[aria-disabled="true"] {
  cursor: default; transform: none; color: var(--off-fg); background: var(--off-bg); box-shadow: none;
}
.btn[aria-pressed="true"], .btn[aria-expanded="true"] { background: var(--sel-bg); box-shadow: var(--sel-edge); }
@media (hover: hover) and (pointer: fine) {
  .btn:not(:disabled):not([aria-disabled="true"]):not(.ink):not(.on):not([aria-pressed="true"]):not([aria-expanded="true"]):hover { background: var(--face-hover); }
  .btn.ink:not(:disabled):hover { background: color-mix(in srgb, var(--ink), var(--paper) 14%); }
}

/* the study verbs (G2, UI.verb): glyph, label, and on coarse pointers or the first uses a
   second line .verb-sub ("same kind, new numbers") inside the 44px target */
.verb:has(.verb-sub) {
  display: inline-grid; grid-template-columns: auto auto; grid-template-rows: auto auto;
  align-content: center; justify-content: start; column-gap: 8px; row-gap: 1px;
  min-height: var(--touch); padding-block: 5px; text-align: left; line-height: 1.15;
}
.verb:has(.verb-sub) > svg { grid-row: 1 / 3; align-self: center; }
.verb-sub { grid-column: 2; font: 400 12px/1.2 var(--font); color: var(--soft); white-space: nowrap; }
.pill .verb-sub { color: var(--ink-deep); opacity: .78; }
.btn.ink .verb-sub { color: inherit; opacity: .75; }

.quiet {
  appearance: none; border: 0; background: none; cursor: pointer; text-decoration: none;
  display: inline-flex; align-items: center; gap: 8px;
  /* a tall tap area that reaches only 4px sideways: 12px made neighbours in a row share taps ("Review" over "Fix" on
     every report row, "Options" over "Race friends"); padding and margin cancel, so nothing moves */
  padding: 9px 4px; margin: -9px -4px; border-radius: 8px;
  font: 600 14.5px var(--font); color: var(--ink);
  transition: background-color .16s var(--ease), color .16s var(--ease);
}
/* the hover underlines instead of painting: the padding reaches 9px past the text, and a painted hover covered
   neighbours. Never a fill or a shadow; a press only dims the ink; focus is the link look (base) */
@media (hover: hover) and (pointer: fine) { .quiet:hover { background: none; text-decoration: underline; text-decoration-thickness: 1.5px; text-underline-offset: 4px; text-decoration-color: var(--amber); } }
.quiet:not(:disabled):active { color: var(--soft); }
.quiet:disabled { color: var(--faint); cursor: default; background: none; }
.quiet svg { width: 16px; height: 16px; }

.link {
  color: var(--ink); text-decoration: underline; text-decoration-color: var(--faint);
  text-decoration-thickness: 1px; text-underline-offset: 3px;
  transition: text-decoration-color .16s var(--ease);
}
@media (hover: hover) and (pointer: fine) { .link:hover { text-decoration-color: var(--amber); } }

/* a choice among a few, drawn as the values themselves */
.choices { display: flex; flex-wrap: wrap; }
.choices button {
  appearance: none; border: 0; background: none; cursor: pointer;
  position: relative; min-width: 36px; height: 38px; padding: 0 9px;
  font: 600 15px var(--font); font-variant-numeric: tabular-nums; color: var(--soft);
  transition: color .16s var(--ease);
}
/* three looks: picked (ink, orange line), open to pick (soft), not open (faint): an open option drawn --faint read as
   disabled, so a disabled one looked no different (the user: "why cant i click our mistakes") */
.choices button:first-child { padding-left: 0; min-width: 27px; }
.choices button:first-child[aria-pressed="true"]::after { left: 0; }
.choices button:disabled { color: var(--faint); cursor: default; }
.choices button[aria-pressed="true"] { color: var(--ink); }
.choices button[aria-pressed="true"]::after {
  content: ""; position: absolute; left: 9px; right: 9px; bottom: 4px;
  height: 2px; border-radius: 2px; background: var(--amber);
}
@media (hover: hover) and (pointer: fine) { .choices button:not(:disabled):hover { color: var(--ink); } }

/* a field: white (dark: the page tone, a step down) with a fine edge and a faint inner shadow, so it sits in the
   surface while buttons stand on it; focus is an amber-deep edge with a soft orange halo (DEMO C4; never an ink box) */
.field {
  appearance: none; width: 100%; height: 48px; padding: 0 14px;
  border: 0; border-radius: 10px; background: var(--paper);
  font: 500 16px var(--font); color: var(--ink);
  box-shadow: var(--shadow-field);
  transition: box-shadow .14s var(--ease), background-color .14s var(--ease);
}
.field:focus { outline: none; box-shadow: inset 0 0 0 1.5px var(--amber-deep), var(--halo); background: var(--paper); }
.field::placeholder { color: var(--faint); }
.field.sm { height: var(--ctl); font-size: 16px; }
textarea.field { height: auto; min-height: 104px; padding: 12px 14px; line-height: 1.5; }
select.field {
  padding-right: 38px; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--soft) 50%), linear-gradient(135deg, var(--soft) 50%, transparent 50%);
  background-position: calc(100% - 20px) 50%, calc(100% - 15px) 50%;
  background-size: 5px 5px; background-repeat: no-repeat;
}
input.field[type="number"], input.field[inputmode="numeric"] { font-variant-numeric: tabular-nums; }
.field:disabled { cursor: not-allowed; color: var(--off-fg); background: var(--off-bg); box-shadow: none; }
/* a wrong value: an ink edge and the reason under it, never red */
.field[aria-invalid="true"] { box-shadow: inset 0 0 0 1.5px var(--ink); background: var(--paper); }
.field[aria-invalid="true"]:focus { box-shadow: inset 0 0 0 1.5px var(--ink), var(--halo); }
.field-err { display: flex; align-items: baseline; gap: 8px; margin-top: 6px; font: 500 13px/1.4 var(--font); color: var(--ink); }
.field-err::before { content: ""; flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--ink); transform: translateY(-1px); }
/* the characters left under a field (UI.counter): "12/200", shown from .near (90%) and .over past it; before
   that it keeps its line (nothing moves when it shows) and stays in the field's description */
.counter:not(.near, .over) { visibility: hidden; }
.counter { display: block; margin-top: 4px; text-align: right; font: 500 12.5px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; }
.counter.near { color: var(--ink); }
.counter.over { color: var(--ink); font-weight: 600; }
.counter.over::before { content: "\2715  "; }

/* the drawn checkbox, for every native checkbox in the app: 18px, 5px corners, a fine edge on white; checked fills
   amber and the check (an SVG in --ink-deep, the same in both themes) grows in; indeterminate (UI.triCheck) is a
   bar. :where() keeps it at zero specificity, so a page's own checkbox rule still wins. Radios stay native. */
:where(input[type="checkbox"]) {
  -webkit-appearance: none; appearance: none; flex: none; margin: 0; cursor: pointer; vertical-align: -4px;
  width: 18px; height: 18px; border-radius: 5px;
  background: var(--paper) no-repeat center / 0 0;
  box-shadow: inset 0 0 0 1.5px var(--off-fg), inset 0 1px 2px rgb(var(--sh) / .08);
  transition: background-color .14s var(--ease), box-shadow .14s var(--ease), background-size .16s var(--ease);
}
:where(input[type="checkbox"]):checked { background-color: var(--amber); background-image: var(--check-ink); background-size: 13px 13px; box-shadow: inset 0 0 0 1px var(--pill-edge); }
:where(input[type="checkbox"]):indeterminate {
  background-color: var(--amber); background-image: linear-gradient(var(--ink-deep) 0 0); background-size: 10px 2px;
  box-shadow: inset 0 0 0 1px var(--pill-edge);
}
:where(input[type="checkbox"]):disabled { cursor: default; background-color: var(--off-bg); box-shadow: inset 0 0 0 1.5px var(--line); }
:where(input[type="checkbox"]):disabled:is(:checked, :indeterminate) { background-color: color-mix(in srgb, var(--amber) 40%, var(--off-bg)); box-shadow: none; }
@media (hover: hover) and (pointer: fine) {
  :where(input[type="checkbox"]):not(:disabled):not(:checked):not(:indeterminate):hover { box-shadow: inset 0 0 0 1.5px var(--soft), inset 0 1px 2px rgb(var(--sh) / .08); }
}

/* a checkbox or radio with its label */
.check { display: inline-flex; align-items: center; gap: 10px; min-height: var(--ctl); cursor: pointer; font: 500 14.5px/1.35 var(--font); }
.check input { width: 18px; height: 18px; margin: 0; flex: none; cursor: pointer; }
.check .n { font: 500 12.5px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; }
.check:has(input:disabled) { color: var(--faint); cursor: default; }

/* six code boxes, grouped 3 + 3 (UI.otp) */
.otp { display: flex; justify-content: center; gap: 8px; }
.otp input {
  appearance: none; flex: none; width: min(48px, calc((100% - 50px) / 6)); height: 56px; padding: 0;   /* 100%: the row's width (5 gaps of 8, the 10px middle) */
  border: 1.5px solid transparent; border-radius: var(--radius); background: var(--paper); box-shadow: var(--shadow-field);
  font: 500 24px var(--mono); text-align: center; color: var(--ink); caret-color: var(--amber);
  transition: border-color .14s var(--ease), background-color .14s var(--ease), box-shadow .14s var(--ease);
}
.otp input:nth-child(3) { margin-right: 10px; }
.otp input:focus { outline: none; border-color: var(--amber-deep); background: var(--paper); box-shadow: var(--halo); }
.otp[aria-invalid="true"] input { border-color: var(--ink); border-style: dashed; }
.otp input:disabled { color: var(--off-fg); background: var(--off-bg); box-shadow: none; }

/* a slider: orange up to the value (--p, set by the page), a light track after */
input[type="range"] {
  -webkit-appearance: none; appearance: none; width: 100%; height: 28px; margin: 0;
  background: none; cursor: pointer; --p: 50%;
}
/* the track sits in (a faint inner shadow), the thumb stands on it (a warm contact shadow) and grows while dragged */
input[type="range"]::-webkit-slider-runnable-track {
  height: 6px; border-radius: 3px; background: linear-gradient(to right, var(--amber) var(--p), var(--track) var(--p));
  box-shadow: inset 0 1px 1px rgb(var(--sh) / .1);
}
input[type="range"]::-moz-range-track { height: 6px; border-radius: 3px; background: var(--track); box-shadow: inset 0 1px 1px rgb(var(--sh) / .1); }
input[type="range"]::-moz-range-progress { height: 6px; border-radius: 3px; background: var(--amber); }
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 22px; height: 22px; margin-top: -8px; border-radius: 50%;
  background: var(--knob); box-shadow: 0 0 0 2px var(--amber), 0 1px 2px rgb(140 70 0 / .2), 0 2px 4px rgb(var(--sh) / .14);
  transition: transform .14s var(--ease), box-shadow .14s var(--ease);
}
input[type="range"]::-moz-range-thumb {
  width: 22px; height: 22px; border: 0; border-radius: 50%;
  background: var(--knob); box-shadow: 0 0 0 2px var(--amber), 0 1px 2px rgb(140 70 0 / .2), 0 2px 4px rgb(var(--sh) / .14);
  transition: transform .14s var(--ease), box-shadow .14s var(--ease);
}
input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.12); }
input[type="range"]:active::-moz-range-thumb { transform: scale(1.12); }
input[type="range"]:focus-visible { outline: none; }
input[type="range"]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 2px var(--amber), 0 0 0 6px var(--wash-2); }
input[type="range"]:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 2px var(--amber), 0 0 0 6px var(--wash-2); }

/* a score slider (UI.scoreSlider): the range, a number field, and a bubble over the thumb at --p */
.score-slider { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 96px; align-items: center; gap: 16px; padding-top: 30px; }
.score-slider .field { height: var(--ctl); text-align: center; font: 600 16px var(--font); font-variant-numeric: tabular-nums; }
.score-slider .bubble {
  position: absolute; top: 0; left: calc(11px + var(--p, .5) * (100% - 96px - 16px - 22px)); transform: translateX(-50%);
  padding: 3px 8px; border-radius: 6px; background: var(--ink); color: var(--on-ink);
  font: 600 12.5px var(--font); font-variant-numeric: tabular-nums; white-space: nowrap; pointer-events: none;
}

/* a segmented choice (UI.seg): role=radiogroup, or role=group with aria-pressed for multi.
   The track sits in (a faint inner shadow); the chosen one is a raised white thumb (a .btn's fine edge, no orange) in 600;
   an option can carry a count (.n); a disabled one keeps its label and says why in a .help line under it. */
.seg:where([role]) {
  display: inline-flex; flex-wrap: wrap; gap: 2px; padding: 3px; max-width: 100%;
  border-radius: 10px; background: var(--well); box-shadow: inset 0 1px 2px rgb(var(--sh) / .08);
}
.seg:where([role]).full { display: flex; }
.seg:where([role]).full > * { flex: 1 1 0; }
.seg:where([role]) > button, .seg:where([role]) > label {
  appearance: none; border: 0; background: none; cursor: pointer; position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: calc(var(--ctl) - 4px); padding: 0 12px; border-radius: 8px;
  font: 500 14px var(--font); color: var(--ink); white-space: nowrap;
  transition: background-color .14s var(--ease), box-shadow .14s var(--ease), transform .25s cubic-bezier(.3, .7, .4, 1);
}
.seg:where([role]) > button:not(:disabled):active { transform: scale(.97); transition-duration: .05s; }
.seg:where([role]) > label > input { position: absolute; opacity: 0; pointer-events: none; }
.seg:where([role]) > [aria-checked="true"], .seg:where([role]) > [aria-pressed="true"], .seg:where([role]) > label:has(input:checked) {
  background: var(--thumb, var(--face)); box-shadow: var(--seg-thumb); font-weight: 600;
}
/* dark: --face is darker than a card's or a sheet's --mat well, so the thumb takes the well a step lighter (raised,
   not sunk); declared on the .seg, so it follows the well it sits in */
@media screen { :root[data-theme="dark"] :is(.seg, .wtabs) { --thumb: color-mix(in srgb, var(--well), #fff 8%); } }   /* .wtabs: Words' own tabs read it too */
.seg:where([role]) > label:has(input:focus-visible) { outline: 2px solid var(--amber-deep); outline-offset: 2px; }
/* options sit 2px apart: the ring goes inside, never over the next one */
.seg:where([role]) > button:focus-visible { outline-offset: -2px; }
.seg:where([role]) .n { font: 500 12.5px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; }
.seg:where([role]) > :disabled, .seg:where([role]) > [aria-disabled="true"] { color: var(--faint); cursor: not-allowed; }
@media (hover: hover) and (pointer: fine) {
  .seg:where([role]) > :not([aria-checked="true"]):not([aria-pressed="true"]):not(:disabled):hover { background: color-mix(in srgb, var(--well), var(--ink) 6%); }
}

/* tabs inside a page or card (UI.tabs): role=tablist of role=tab, each with an optional count */
.tablist {
  display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain;
  box-shadow: inset 0 -1px 0 var(--line);
}
.tablist::-webkit-scrollbar { display: none; }
.tablist [role="tab"] {
  appearance: none; border: 0; background: none; cursor: pointer; flex: none; position: relative;
  display: inline-flex; align-items: center; gap: 7px; min-height: var(--ctl); padding: 0 12px;
  font: 500 14px var(--font); color: var(--soft); white-space: nowrap; text-decoration: none;
  transition: color .16s var(--ease);
}
.tablist [role="tab"][aria-selected="true"] { color: var(--ink); font-weight: 600; }
.tablist [role="tab"][aria-selected="true"]::after {
  content: ""; position: absolute; left: 12px; right: 12px; bottom: 0; height: 2px; border-radius: 2px; background: var(--amber);
  box-shadow: 0 1px 2px rgb(140 70 0 / .25);   /* the underline stands a hair above the line */
}
.tablist [role="tab"]:focus-visible { outline-offset: -2px; }
.tablist .n {
  min-width: 20px; height: 20px; padding: 0 6px; border-radius: 6px; display: inline-grid; place-items: center;
  background: var(--well); color: var(--ink); font: 500 11.5px var(--font); font-variant-numeric: tabular-nums;
}
.tablist [aria-selected="true"] .n { background: var(--wash-2); }
.tablist [role="tab"]:disabled { color: var(--faint); cursor: default; }
@media (hover: hover) and (pointer: fine) { .tablist [role="tab"]:not(:disabled):hover { color: var(--ink); } }
/* .inline: tabs on a title row that draws its own hairline */
.tablist.inline { box-shadow: none; }
/* .sm: tighter tabs for a narrow column (the 300px side column holds four) */
.tablist.sm [role="tab"] { padding: 0 8px; gap: 5px; }
.tablist.sm [role="tab"][aria-selected="true"]::after, .tablist.sm .tab-ind::after { left: 8px; right: 8px; }
/* a phone wraps the tabs rather than hiding the last ones off the edge (rule 9); each row keeps its line */
@media (max-width: 759px) {
  .tablist { flex-wrap: wrap; overflow: visible; gap: 0; box-shadow: none; }
  .tablist [role="tab"] { flex: 1 0 auto; justify-content: center; box-shadow: inset 0 -1px 0 var(--line); }
}

/* an on/off setting (UI.switch): button.switch role=switch aria-checked; the knob carries a
   check when on, so the state is never colour alone. Its label sits beside it. */
.switch {
  appearance: none; border: 0; padding: 0; cursor: pointer; flex: none; position: relative;
  width: 42px; height: 26px; border-radius: 13px; background: var(--track);
  box-shadow: inset 0 1px 2px rgb(var(--sh) / .12);   /* the track sits in; the knob stands on it */
  transition: background-color .14s var(--ease), box-shadow .14s var(--ease);
}
/* the knob carries a drawn check when on; it stretches while pressed (toward the side it will travel from) */
.switch::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 10px;
  background: var(--knob) no-repeat center / 0 0;
  box-shadow: 0 1px 2px rgb(var(--sh) / .22), 0 2px 4px -1px rgb(var(--sh) / .14);
  transition: transform .25s cubic-bezier(.3, .7, .4, 1), width .14s var(--ease), background-size .16s var(--ease);
}
.switch[aria-checked="true"] { background: var(--amber); box-shadow: inset 0 1px 2px rgb(120 55 0 / .25); }
.switch[aria-checked="true"]::after { background-image: var(--check-ink); background-size: 12px 12px; transform: translateX(16px); }
.switch:not(:disabled):active::after { width: 24px; }
.switch[aria-checked="true"]:not(:disabled):active::after { transform: translateX(12px); }
.switch:disabled { cursor: default; background: var(--off-bg); box-shadow: inset 0 0 0 1px var(--line); }
.switch[aria-checked="true"]:disabled { background: color-mix(in srgb, var(--amber) 40%, var(--off-bg)); box-shadow: none; }
.switch:disabled::after { box-shadow: 0 0 0 1px var(--line); }
@media (pointer: coarse) { .switch::before { content: ""; position: absolute; inset: -9px -2px; } }

/* a settings row (UI.prefRow): .pref-k label, a .help under it, the control at the right,
   and .pref-state ("Saved", or why a save failed) beside the control. On a phone a wide
   control drops under its label; a switch stays at the right. */
.pref {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 2px 20px;
  padding: 12px 0; border-top: 1px solid var(--line);
}
.pref-k { font: 600 14.5px/1.35 var(--font); color: var(--ink); }
.pref > .help { grid-column: 1; margin-top: 0; }
.pref-ctl { grid-column: 2; grid-row: 1 / span 2; display: flex; align-items: center; gap: 12px; justify-self: end; }
.pref-state { font: 500 12.5px var(--font); color: var(--soft); white-space: nowrap; }
.pref-state:empty { position: absolute; }   /* out of the row's flex until it says something: no 12px gap (stays a live region) */
.pref-ctl .dd-k { display: none; }          /* the row's .pref-k already says it ("Test timing  Official" read twice) */
.pref-ctl .dd .dd-v { color: var(--ink); }  /* the value is then the button's text */
.pref-state.err { color: var(--ink); }
/* a row whose control is a switch (with or without its "Saved") keeps it at the right */
@media (max-width: 759px) {
  :is(.pref:not(:has(> .pref-ctl > .switch)), .pref:has(> .pref-ctl > :not(.switch):not(.pref-state))) { grid-template-columns: minmax(0, 1fr); }
  :is(.pref:not(:has(> .pref-ctl > .switch)), .pref:has(> .pref-ctl > :not(.switch):not(.pref-state))) .pref-ctl { grid-column: 1; grid-row: auto; justify-self: stretch; flex-wrap: wrap; margin-top: 8px; }
}

/* radio chips (UI.chips): button[aria-pressed] or label > input; each may carry a count. A chip is a small white
   key with a fine edge (the .btn face); chosen is the one selected look; a press sinks it */
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chips > button, .chips > label, .chips > a {
  appearance: none; border: 0; cursor: pointer; text-decoration: none; position: relative;
  display: inline-flex; align-items: center; gap: 6px; min-height: var(--ctl); padding: 0 12px; border-radius: 8px;
  background: var(--face); box-shadow: var(--shadow-btn); color: var(--ink); font: 500 14px var(--font); white-space: nowrap;
  transition: background-color .14s var(--ease), box-shadow .14s var(--ease), transform .25s cubic-bezier(.3, .7, .4, 1);
}
.chips > :not(:disabled):not([aria-disabled="true"]):active { transform: scale(.97); transition-duration: .05s; }
.chips > label > input { position: absolute; opacity: 0; pointer-events: none; }
.chips > label:has(input:focus-visible) { outline: 2px solid var(--amber-deep); outline-offset: 2px; }
.chips svg { width: 18px; height: 18px; flex: none; }
.chips .n { font: 500 12.5px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; }
.chips > [aria-pressed="true"], .chips > [aria-checked="true"], .chips > [aria-current="true"], .chips > label:has(input:checked), button.chip[aria-pressed="true"] {
  background: var(--sel-bg); box-shadow: var(--sel-edge); font-weight: 600;
}
.chips > :is([aria-pressed="true"], [aria-checked="true"], [aria-current="true"]) .n, .chips > label:has(input:checked) .n { color: var(--ink); }
.chips > :disabled, .chips > [aria-disabled="true"], .chips > label:has(input:disabled) { color: var(--off-fg); background: var(--off-bg); box-shadow: none; cursor: default; }
@media (max-width: 759px), (pointer: coarse) { .chips > button, .chips > label, .chips > a { min-height: var(--touch); } }
@media (hover: hover) and (pointer: fine) {
  .chips > :not([aria-pressed="true"]):not([aria-checked="true"]):not([aria-current="true"]):not(:has(input:checked)):not(:disabled):not([aria-disabled="true"]):hover { background: var(--face-hover); }
}

/* a dropdown trigger (UI.dropdown): a .btn.dd with its value and a chevron; .on when it
   narrows something; the list is a .popover[role=listbox] (a bottom sheet on a phone).
   The chevron is the drawn caret_down (--caret-mask), turning over while open, on every .dd and on a text
   .btn that opens a menu (UI.menu.bind sets aria-haspopup="menu"). None on a trigger that already draws an
   svg (UI.icon('caret_down'), an icon), a .btn.icon, a split button's part or a busy button. */
.dd::after,
.btn[aria-haspopup="menu"]:not(.icon, .dd, [aria-busy="true"]):not(.split-btn > *):not(:has(svg))::after {
  content: ""; width: 16px; height: 16px; flex: none; background: currentColor;
  -webkit-mask: var(--caret-mask) center / contain no-repeat; mask: var(--caret-mask) center / contain no-repeat;
  transition: rotate 150ms var(--ease);
}
.dd[aria-expanded="true"]::after, .btn[aria-haspopup="menu"][aria-expanded="true"]::after { rotate: 180deg; }
.dd:has(svg > g.ph-turn)::after { content: none; }
.dd .dd-v { font-weight: 500; color: var(--soft); }
.dd.on { background: var(--sel-bg); box-shadow: var(--sel-edge); }
.dd.on .dd-v { color: var(--ink); }

/* UI.filterBar({pop}): the filters in one popover, a dropdown a row, the value against the chevron */
.filters-pop { display: grid; gap: 8px; min-width: 240px; justify-items: stretch; }
.filters-pop > .dd { justify-content: flex-start; }
.filters-pop > .dd .dd-v { margin-left: auto; }

/* a filter bar (UI.filterBar): its dropdowns and chips in a row, a .quiet Clear, and on a
   phone a "Filters · n" .filters-open button whose sheet holds the same controls */
.filters { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.filters-open { display: none; }
@media (max-width: 759px) {
  .filters > :not(.filters-open):not(.filters-keep) { display: none; }
  .filters > .filters-open { display: inline-flex; }
}

/* a selection bar (SR15): sticky at the foot of the view while cells are selected; one primary */
.selbar {
  position: sticky; bottom: 16px; z-index: 25;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px;
  margin: 12px 0; padding: 8px 8px 8px 16px; border-radius: var(--radius-card);
  background: var(--float); box-shadow: var(--shadow-4);
  animation: rise .24s var(--ease) both;
}
.selbar-n { margin-right: auto; font: 600 14.5px var(--font); font-variant-numeric: tabular-nums; }
@media (max-width: 759px) { .selbar { bottom: calc(76px + env(safe-area-inset-bottom, 0px)); } .selbar-n { flex: 1 0 100%; } }

/* ------------------------------------------------------------------ cards and data */

/* a card (rule 3): white (--raised) on the warm page with the soft edge inside --shadow-1, radius 12, padding 20
   (16 on a phone). .line is the same card flat (its edge only), for a card inside a surface. .go marks a card that
   is one link or button (a or button.card are too): it steps to --shadow-2 on hover and sinks a hair on a press.
   .hero is the page's one hero: --shadow-3 over --hero-wash. The four states (rule 8): .skel shapes at final size
   while loading, .card-err inside it on failure, UI.empty when new. */
.card { position: relative; min-width: 0; padding: var(--card-pad); border-radius: var(--radius-card); background: var(--raised); box-shadow: var(--shadow-1); }
/* an aside that is itself a card (Words' Study glance): .card's relative must not undo the aside's sticky, and the
   aside's -8px shadow room would push the card past the content edge */
@media (min-width: 1280px) { .twocol-side.card { position: sticky; margin: 0; } }
.card.line { background: var(--raised); box-shadow: var(--edge); }
.card.hero { background: var(--hero-wash), var(--raised); box-shadow: var(--shadow-3); }
a.card { display: block; color: inherit; text-decoration: none; }
button.card { appearance: none; border: 0; display: block; width: 100%; text-align: inherit; font: inherit; color: inherit; }
:is(a, button).card, .card.go { cursor: pointer; transition: box-shadow .14s var(--ease), transform .25s cubic-bezier(.3, .7, .4, 1); }
@media (hover: hover) and (pointer: fine) { :is(a, button).card:hover, .card.go:hover { box-shadow: var(--shadow-2); } }
:is(a, button).card:active, .card.go:active { transform: scale(.995); transition-duration: .05s; }
/* a ledge (Brilliant), opt-in for selectable tiles only, never a .pill: the tile stands on a 3px edge of its own
   and presses down onto it; chosen keeps the selected look on an amber edge */
.ledge { box-shadow: var(--edge), 0 3px 0 0 var(--mat); transition: transform .1s var(--ease), box-shadow .1s var(--ease); }
.ledge:active { transform: translateY(3px); box-shadow: var(--edge), 0 0 0 0 var(--mat); }
.ledge:is([aria-pressed="true"], [aria-checked="true"], [aria-selected="true"]) { background: var(--sel-bg); box-shadow: var(--sel-edge), 0 3px 0 0 var(--amber-deep); }
.card-head { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; margin-bottom: 12px; }   /* centred, so the ? / i beside a title and the fold button line up with it */
.card-head > h2, .card-head > h3, .card-head > .h2 { font: 600 16px/1.3 var(--font); letter-spacing: -.01em; color: var(--ink-strong); }
/* a kicker left in a card head reads as meta after the title, never stacked above it */
.card-head > .k { order: 1; flex: 0 1 auto; margin: 0; }
.card-head > .card-act { order: 2; }
.card-head .meta { font-size: 12.5px; }
.card-act { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* collapsing (U26): .card[data-collapsed] keeps its head and a one-line .card-sum */
.card-fold svg { transition: transform .2s var(--ease); transform: rotate(90deg); }
.card[data-collapsed] .card-fold svg { transform: none; }
.card[data-collapsed] > :not(.card-head):not(.card-sum) { display: none; }
.card[data-collapsed] > .card-head { margin-bottom: 0; }
.card-sum { display: none; margin-top: 4px; font: 400 12.5px/1.45 var(--font); color: var(--soft); }
.card[data-collapsed] > .card-sum { display: block; }
.card-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 12px; }

/* a failing card: the reason and Retry inside it; the rest of the page still renders */
.card-err {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
  padding: 12px 16px; border-radius: var(--radius); background: var(--well);
  font: 500 14px/1.4 var(--font); color: var(--ink);
}
.card-err::before { content: ""; flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--ink); }
.card-err > span { flex: 1 1 200px; min-width: 0; }

/* a static skeleton at the final size (no shimmer, nothing loops): .line, .num, .circle,
   or a block sized by the caller; darker on a tile */
.skel { display: block; min-height: 12px; border-radius: 8px; background: var(--well); }
.skel.line { height: 12px; margin: 8px 0; border-radius: 6px; }
.skel.line.lg { height: 18px; }
.skel.num { width: 88px; height: 34px; }
.skel.circle { border-radius: 50%; }
.skel.pill-s { width: 140px; height: 34px; border-radius: 10px; }

/* a stat tile (UI.tile): .stat-k label, .stat-v value (Geist, figure step), .stat-sub sub-line, and
   optionally .stat-delta ("▲ 84") and a 24px .stat-spark. A button or link tile is a
   filter or a way in: aria-pressed marks the one applied. Missing data reads an en dash. Inside a card a
   stat has no fill of its own: the card is the surface, stats sit in it as a row. */
.stat { position: relative; min-width: 0; padding: 12px 16px; border-radius: var(--radius); background: var(--raised); box-shadow: var(--shadow-1); color: var(--ink); text-align: left; }
:where(.card, .panel) .stat:not(button):not(a) { background: none; box-shadow: none; padding: 0; }   /* a link tile nets 0 too (8px out, -8px margin) */
/* a link or filter stat in a card keeps the same left edge: its padding reaches out over the gap */
:where(.card, .panel) :is(a.stat, button.stat):not([aria-pressed="true"]):not([aria-current="true"]) { background: none; box-shadow: none; }
:where(.card, .panel) :is(a.stat, button.stat) { padding: 8px; margin: -8px; width: calc(100% + 16px); }
button.stat, a.stat { appearance: none; border: 0; width: 100%; font: inherit; cursor: pointer; text-decoration: none; display: block; transition: background-color .16s var(--ease), box-shadow .16s var(--ease); }
/* a button centres its content when a grid row stretches it: stack from the top like the others */
button.stat { display: flex; flex-direction: column; justify-content: flex-start; }
button.stat[aria-pressed="true"], a.stat[aria-current="true"] { background: var(--sel-bg); box-shadow: var(--sel-edge); }
button.stat:disabled { cursor: default; }
/* hover: a stat on the page steps up like a clickable card; inside a card it gets the row band */
@media (hover: hover) and (pointer: fine) {
  button.stat:not(:disabled):not([aria-pressed="true"]):hover, a.stat:not([aria-current="true"]):hover { box-shadow: var(--shadow-2); }
  :where(.card, .panel) :is(a.stat, button.stat):not([aria-pressed="true"]):not([aria-current="true"]):not(:disabled):hover { background: var(--hover); box-shadow: none; }
}
.stat-k { display: block; font: 500 12.5px/1.3 var(--font); color: var(--soft); }
.stat-v { display: block; margin-top: 4px; font: 600 28px/1.05 var(--font); letter-spacing: -.02em; font-variant-numeric: tabular-nums; color: var(--ink-strong); }
.stat-v small { font-size: 14px; font-weight: 500; letter-spacing: 0; color: var(--soft); }
.stat-sub { display: block; margin-top: 4px; font: 400 12.5px/1.45 var(--font); color: var(--soft); }
.stat-delta { display: inline-block; margin-top: 4px; font: 500 12.5px var(--font); color: var(--ink); font-variant-numeric: tabular-nums; }
.stat-spark { display: block; width: 100%; height: 24px; margin-top: 8px; overflow: visible; color: var(--ink); }
/* a number counting up (UI.tile): the real figure stays in the text, unseen, under the moving one */
.counting { position: relative; color: transparent; }
.counting::after { content: attr(data-n); content: attr(data-n) / ""; position: absolute; left: 0; top: 0; color: var(--ink-strong); }
.stats { display: grid; grid-template-columns: repeat(var(--cols, 4), minmax(0, 1fr)); gap: 16px; }
@media (max-width: 759px) { .stats { grid-template-columns: repeat(var(--cols-phone, 2), minmax(0, 1fr)); gap: 8px; } }

/* a meter (UI.meter): .meter-k label, .meter-n "18 of 45 min", and .meter-bar > i filled to
   --p (0 to 1) in orange (a goal fill); .ink fills graphite; a .meter-bar > b tick sits at --t */
.meter { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; gap: 6px 12px; }
.meter-k { font: 500 14px/1.3 var(--font); color: var(--ink); min-width: 0; }
.meter-n { font: 500 12.5px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; white-space: nowrap; }
.meter-bar { grid-column: 1 / -1; position: relative; height: 6px; border-radius: 3px; background: var(--track); box-shadow: inset 0 1px 1px rgb(var(--sh) / .08); }
.meter.ticks .meter-bar { box-shadow: none; }
.meter-bar > i { display: block; height: 100%; width: calc(min(var(--p, 0), 1) * 100%); border-radius: inherit; background: var(--amber); }
.meter.ink .meter-bar > i { background: var(--ink); }
.meter.lg .meter-bar { height: 8px; border-radius: 4px; }
.meter-bar > b { position: absolute; top: -3px; bottom: -3px; left: calc(var(--t, 0) * 100%); width: 2px; margin-left: -1px; border-radius: 1px; background: var(--ink); }
.meter.done .meter-n::after { content: " \2713"; color: var(--ink); }

/* a small label: --wash-2 and ink by default; .n a count in orange; .ink graphite; .soft a
   neutral tag ("Example", the sidebar's "Resume", "Logged by you · Bluebook"); .line outlined */
.badge {
  display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 7px; border-radius: 6px;
  background: var(--wash-2); color: var(--ink); font: 500 11.5px/1 var(--font); white-space: nowrap; vertical-align: middle;
}
.badge svg { width: 12px; height: 12px; }
.badge.n { justify-content: center; min-width: 20px; padding: 0 6px; background: var(--amber); color: var(--ink-deep); font: 600 11.5px/1 var(--font); font-variant-numeric: tabular-nums; }
.badge.ink { background: var(--ink); color: var(--on-ink); }
.badge.soft { background: var(--well); color: var(--soft); }
.badge.line { background: none; box-shadow: inset 0 0 0 1px var(--line); color: var(--soft); }
.badge.lg { height: 24px; padding: 0 8px; font-size: 12.5px; }

/* a key cap: <kbd class="kbd">G</kbd><kbd class="kbd">N</kbd> */
.kbd, kbd {
  display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding: 0 5px; border-radius: 5px;
  background: var(--paper); box-shadow: inset 0 0 0 1px var(--line), inset 0 -1.5px 0 var(--line);
  font: 500 11px/1 var(--font); font-variant-numeric: tabular-nums; color: var(--soft); white-space: nowrap; vertical-align: middle;   /* Geist, not mono: a key, not code */
}
.kbd + .kbd, kbd + kbd { margin-left: 3px; }
kbd kbd { box-shadow: none; padding: 0; min-width: 0; background: none; }
.kbd.lg { min-width: 28px; height: 28px; font-size: 13px; color: var(--ink); }
/* a touch screen has no keys: hints leave; a key that is the content stays (the ? sheet's table, Help's list, prose) */
@media (hover: none) and (pointer: coarse) { .kbd:not(:where(td, p, li, .key-row) > .kbd) { display: none; } }

/* a table (UI.table): numbers in .n cells (tabular Geist, right), sortable headers are buttons
   inside th[aria-sort], your row is tr.you (orange marks yours). A wide table sits in a
   .table-wrap that scrolls on its own (.more-l / .more-r while columns are off-screen),
   with .stick (and .stick-2 at --stick-1) columns pinned; the page never scrolls sideways. */
.table { width: 100%; border-collapse: collapse; font: 400 14px/1.4 var(--font); font-variant-numeric: tabular-nums; }
.table th {
  padding: 8px 12px; border-bottom: 1px solid var(--line); vertical-align: bottom; white-space: nowrap;
  font: 500 12.5px/1.3 var(--font); color: var(--soft); text-align: left;
}
.table td { height: var(--row-h); padding: 8px 12px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.table :is(th, td).n { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.table th[aria-sort] { color: var(--ink); }
.table th button {
  appearance: none; border: 0; background: none; padding: 0; margin: 0; cursor: pointer;
  display: inline-flex; align-items: center; gap: 4px; font: inherit; color: inherit; text-align: inherit;
}
.table th button::after { content: "\2195"; font-size: 11px; color: var(--faint); }
.table th[aria-sort="ascending"] button::after { content: "\25B2"; font-size: 8px; color: var(--ink); }
.table th[aria-sort="descending"] button::after { content: "\25BC"; font-size: 8px; color: var(--ink); }
.table th.n button { flex-direction: row-reverse; }
/* your row: the wash across the row and 600, the same height as every row, no side stripe; a .rank cell in it
   turns amber-deep */
.table tr.you > * { background: var(--wash); font-weight: 600; }
.table tr.you .rank { color: var(--amber-deep); }
.table tbody tr[aria-selected="true"] > * { background: var(--wash-2); }
.table.compact td { height: auto; padding-block: 6px; }
.table caption { caption-side: top; text-align: left; padding-bottom: 8px; font: 500 12.5px var(--font); color: var(--soft); }
/* the hover band: a warm tint over whatever the table sits on (opaque, so pinned cells stay solid) */
@media (hover: hover) and (pointer: fine) { .table.hover tbody tr:not(.you):not([aria-selected="true"]):hover > * { background: linear-gradient(var(--hover), var(--hover)), var(--surface); } }
.table-wrap { overflow-x: auto; overscroll-behavior-x: contain; max-width: 100%; scrollbar-width: thin; }
.table-wrap:focus-visible { outline-offset: -2px; }
.table .stick { position: sticky; left: 0; z-index: 1; background: var(--surface); }
.table .stick-2 { position: sticky; left: var(--stick-1, 64px); z-index: 1; background: var(--surface); }
.table-wrap.more-l .table .stick-last { box-shadow: 8px 0 8px -8px rgba(30, 30, 30, .28); }
.table-wrap.more-r { -webkit-mask-image: linear-gradient(to left, transparent, #000 28px); mask-image: linear-gradient(to left, transparent, #000 28px); }

/* image thumbnails (UI.images): 64px squares with a remove x, an add tile, and a drop target */
.thumbs { display: flex; flex-wrap: wrap; gap: 8px; }
.thumbs > figure, .thumbs > li { position: relative; width: 64px; height: 64px; margin: 0; flex: none; border-radius: 8px; background: var(--well); }
.thumbs img, .thumbs video { display: block; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
.thumbs .x {
  position: absolute; top: -7px; right: -7px; width: 24px; height: 24px; border-radius: 8px;
  background: var(--ink); color: var(--on-ink); box-shadow: 0 0 0 2px var(--paper);
}
.thumbs .x svg { width: 12px; height: 12px; }
.thumbs .x::before { content: ""; position: absolute; inset: -10px; }
.thumbs-add {
  appearance: none; border: 1.5px dashed var(--faint); background: none; cursor: pointer; flex: none;
  display: grid; place-items: center; align-content: center; gap: 2px; width: 64px; height: 64px; border-radius: 8px;
  font: 500 11px/1.2 var(--font); color: var(--soft); text-align: center;
}
.thumbs-add svg { width: 18px; height: 18px; }
.thumbs.drag, .drop.drag { background: var(--wash); box-shadow: 0 0 0 1.5px var(--amber); border-radius: 10px; }

/* an empty state (G12, UI.empty): one dimmed .empty-sample row of exactly what will
   appear (aria-hidden, a .badge.soft "Example" on its own line above it), .empty-text, and the smallest
   action that fills it, with its cost */
.empty { padding: 16px 0; color: var(--soft); font-size: 14.5px; }
.empty:has(> .empty-text) { display: grid; justify-items: start; gap: 12px; padding: 4px 0; }
.empty-sample { position: relative; width: 100%; opacity: .55; pointer-events: none; user-select: none; -webkit-user-select: none; }
.empty-sample > .badge { display: flex; width: max-content; margin-bottom: 8px; }   /* first child: a label row, never over the sample */
.empty-text { max-width: 60ch; font: 400 14.5px/1.5 var(--font); color: var(--soft); text-wrap: pretty; }

/* a number that opens its working (G6, UI.fact): a persistent dotted underline and a small
   circled i on every pointer type; a 44px hit area on coarse pointers, no layout shift */
[data-fact] {
  position: relative; cursor: pointer;
  text-decoration: underline dotted var(--soft) 1px; text-underline-offset: 3px;
}
[data-fact]::after {
  content: "\24D8"; content: "\24D8" / "";
  display: inline-block; margin-left: 3px; vertical-align: 1px;
  font: 400 11px/1 var(--font); letter-spacing: 0; color: var(--soft); text-decoration: none;
}
button[data-fact] { appearance: none; border: 0; background: none; padding: 0; text-align: inherit; }   /* no font or colour here: the base button rule inherits them, and a class's own type (.pts) must win */
@media (pointer: coarse) {
  [data-fact]::before { content: ""; position: absolute; left: 50%; top: 50%; width: max(100%, 44px); height: max(100%, 44px); transform: translate(-50%, -50%); }
}
/* points (G5, UI.pts): "≈ +6 pts", meta 12.5 tabular, --soft, never orange; opens its fact */
.pts { font: 500 12.5px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* a progress ring: --p (0 to 1) of an orange arc on a 6px track (--ring-w), the figure inside (the rail's Today) */
.ring {
  --rs: 36px; position: relative; display: inline-grid; place-items: center; flex: none;
  width: var(--rs); height: var(--rs); border-radius: 50%;
  font: 600 13px var(--font); color: var(--ink); font-variant-numeric: tabular-nums;
}
.ring::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(var(--amber) calc(min(var(--p, 0), 1) * 1turn), var(--track) 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring-w, 6px)), #000 calc(100% - var(--ring-w, 6px) + .5px));
          mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring-w, 6px)), #000 calc(100% - var(--ring-w, 6px) + .5px));
}

/* ------------------------------------------------------------------ status marks (2.3, R20, R35)

   One vocabulary, always a glyph and a legend with counts. Each mark sets data-status and
   reads the tokens above.
   UI.cell   <button class="cell" data-status="incorrect" [data-flag] [data-note] [data-open]
               [data-fixing] [data-over] [data-tier data-level] aria-current aria-label="…">
               <span class="n">7</span></button>
             40px (.sm 28, .lg 48; 44 in a .cell-grid on a phone). Corners: the status glyph
             top-right (bottom-right while flagged), the flag notch top-right, the note glyph
             bottom-left, an open mistake's orange dot top-left, Fixed-since's amber check
             beside the glyph, being fixed as a hatch, current as the ring.
             A grid with data-colour="difficulty" tints its cells by tier, E / M / H plus
             a measured level ("M4"). A fixed set's future slot is data-status="future".
   .cell-grid  --cols (auto-fill by default), --cell, --cell-gap
   UI.legend <ul class="legend"><li><i class="sw" data-status="correct"></i>Correct <b>5</b></li>
             …; an overlay's swatch is <i class="sw" data-flag|data-note|data-open|data-saved|
             data-current|data-fixing|data-over|data-tier>; a filtering legend puts a
             button[aria-pressed] in each li.
   UI.strip  <div class="strip"> of [data-status] squares (16px, the glyph centred), .fit to
             keep one line; an <i class="strip-mark"> is an orange marker between two.
   UI.pips   <span class="pips"><i class="on"></i><i></i></span> (clean answers given / owed)
   UI.dbars  <span class="dbars" data-tier="medium">Medium</span>: the bars and the word
   .heat     the activity grid: 7 rows of 12px day cells, data-h 0 to 4, data-rest, data-spare */

[data-status="future"] { --st-edge: var(--faint); --st-edge-style: dashed; --st-fg: var(--faint); --st-glyph: ""; }

.cell:where([data-status]) {
  position: relative; flex: none;
  display: inline-grid; place-items: center;
  width: var(--cell, 40px); height: var(--cell, 40px); padding: 0;
  border: 1.5px var(--st-edge-style) var(--st-edge); border-radius: 8px;
  background: var(--st-bg); color: var(--st-fg); text-shadow: var(--st-halo);
  font: 600 14px/1 var(--font); font-variant-numeric: tabular-nums; text-decoration: none;
  appearance: none; -webkit-tap-highlight-color: transparent;
}
:is(button, a).cell[data-status] { cursor: pointer; transition: transform .12s var(--ease), box-shadow .16s var(--ease); }
:is(button, a).cell[data-status]:active { transform: scale(.94); }
.cell[data-status]:disabled { cursor: default; transform: none; }
@media (hover: hover) and (pointer: fine) { :is(button, a).cell[data-status]:not(:disabled):hover { transform: translateY(-1px); } }
.cell[data-status]::after {
  content: var(--st-glyph); position: absolute; top: 3px; right: 4px;
  font: 700 9px/1 var(--font); color: var(--st-mark);
}
.cell[data-flag]::before {
  content: ""; position: absolute; top: 1px; right: 1px; width: 9px; height: 9px;
  background: var(--st-flag); clip-path: polygon(0 0, 100% 0, 100% 100%); border-top-right-radius: 5px;
}
.cell[data-flag]::after { top: auto; bottom: 3px; }
/* a cell with no number (a timeline, a type map): its glyph centred, not a 9px corner on a blank tile */
.cell[data-status]:has(> .n:empty)::after { inset: 0; display: grid; place-items: center; font-size: 12px; line-height: 1; }
.cell[data-status] > .n::after {
  content: var(--st-badge); position: absolute; top: 3px; right: 13px;
  font: 700 9px/1 var(--font); color: var(--amber-deep); text-shadow: none;
}
.cell[data-flag] > .n::after { top: auto; bottom: 3px; }
.cell[data-note] > .n::before {
  content: ""; position: absolute; left: 4px; bottom: 4px; width: 7px; height: 6px;
  color: var(--st-mark); background: var(--note-glyph);
}
.cell[data-open] > .n::after {
  content: ""; top: 4px; right: auto; bottom: auto; left: 4px; width: 6px; height: 6px;
  border-radius: 50%; background: var(--amber); box-shadow: 0 0 0 1.5px var(--paper);
}
.cell[data-fixing] { background: var(--hatch), var(--st-bg); }
.cell[data-over] { box-shadow: 0 0 0 1.5px var(--amber-deep); }
.cell[data-status][aria-current]:not([aria-current="false"]) { box-shadow: var(--ring); }
[data-colour="difficulty"] .cell[data-tier] { background: var(--lvl); color: var(--lvl-ink); border-color: transparent; text-shadow: none; }
[data-colour="difficulty"] .cell[data-tier]::after { content: var(--lvl-letter) attr(data-level); color: var(--lvl-ink); text-shadow: none; }
.cell.sm { --cell: 28px; border-radius: 6px; font-size: 12px; }
.cell.sm::after { top: 2px; right: 2px; font-size: 8px; }
.cell.sm[data-flag]::before { width: 7px; height: 7px; }
.cell.sm > .n::after { right: 10px; font-size: 8px; }
.cell.lg { --cell: 48px; font-size: 15px; }
.cell-grid { display: grid; grid-template-columns: repeat(var(--cols, auto-fill), var(--cell, 40px)); gap: var(--cell-gap, 6px); }
@media (max-width: 759px) { .cell-grid { --cell: var(--touch); } }

/* swatches, strip squares and type-map cells: the same tokens, the glyph centred */
.sw, .strip > [data-status], .atlas > [data-status] {
  position: relative; flex: none;
  display: inline-grid; place-items: center;
  width: var(--sw, 16px); height: var(--sw, 16px); padding: 0;
  border: 1.5px var(--st-edge-style, solid) var(--st-edge, transparent); border-radius: 4px;
  background: var(--o-dot, none), var(--o-hatch, none), var(--st-bg, var(--tile-2)); color: var(--st-mark, var(--ink));
  font: 700 9px/1 var(--font); text-decoration: none; appearance: none;
}
.sw::after, .strip > [data-status]::after, .atlas > [data-status]::after { content: var(--st-glyph, ""); }
:is(.sw, .strip > *, .atlas > *)[data-fixing] { --o-hatch: var(--hatch); }
:is(.strip, .atlas) > [data-open] { --o-dot: radial-gradient(circle at calc(100% - 4px) 4px, var(--amber) 2.5px, var(--paper) 2.6px 3.6px, transparent 3.7px); }
.atlas > [data-status="met"][data-open] { --o-dot: none; --st-mark: var(--amber); font-size: 16px; }   /* met with an open mistake: the dot itself is orange */
.sw[data-flag]::before, .strip > [data-flag]::before {
  content: ""; position: absolute; top: 0; right: 0; width: 6px; height: 6px;
  background: var(--st-flag, var(--amber)); clip-path: polygon(0 0, 100% 0, 100% 100%); border-top-right-radius: 2px;
}
.sw[data-note]:not([data-status])::after { content: ""; width: 8px; height: 7px; background: var(--note-glyph); }
.sw[data-open]:not([data-status]) { background: none; }
.sw[data-open]:not([data-status])::after { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--amber); }
.sw[data-saved]:not([data-status])::after { content: ""; width: 7px; height: 9px; background: currentColor; clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 72%, 0 100%); }
.sw[data-current], .strip > [aria-current]:not([aria-current="false"]) { box-shadow: 0 0 0 1.5px var(--paper), 0 0 0 3px var(--ink); }
.sw[data-current]:not([data-status]) { background: var(--paper); }
.sw[data-over] { box-shadow: 0 0 0 1.5px var(--amber-deep); }
.sw[data-tier]:not([data-status]) { background: var(--lvl); color: var(--lvl-ink); }
.sw[data-tier]:not([data-status])::after { content: var(--lvl-letter); }
.sw.lg { --sw: 20px; font-size: 10px; }

.strip { display: flex; flex-wrap: wrap; align-items: center; gap: 3px; }
.strip.fit { flex-wrap: nowrap; gap: 2px; min-width: 0; }      /* min-width: a grid or flex parent may shrink it to fit */
.strip.fit > [data-status] { flex: 1 1 0; width: auto; min-width: 0; max-width: 24px; overflow: hidden; }
.strip > button[data-status], .strip > a[data-status] { cursor: pointer; transition: transform .12s var(--ease); }
@media (hover: hover) and (pointer: fine) { .strip > button[data-status]:hover, .strip > a[data-status]:hover { transform: translateY(-1px); } }
/* the type map (UI.atlas; U8, P14): a skill's question types as 16px cells (.sm 14px on Me),
   largest weight first; unmet, met and ready, with an open mistake's dot, being fixed's
   hatch, data-due (a small clock: a check-in is due) and aria-selected (a 2px ink ring).
   .atlas-row lays out the skill, its cells and "8 / 23 ready". On a phone rows wrap at 18
   cells (20 at 14px), so nothing scrolls sideways. */
.atlas { display: flex; flex-wrap: wrap; gap: 3px; }
.atlas.sm > [data-status] { --sw: 14px; font-size: 8px; }
.atlas > :is(button, a)[data-status] { cursor: pointer; }
.atlas > [data-due]::before {
  content: ""; position: absolute; right: -3px; bottom: -3px; width: 8px; height: 8px; border-radius: 50%;
  background: linear-gradient(var(--ink) 0 0) 50% 1.5px / 1px 3px no-repeat, var(--paper); box-shadow: inset 0 0 0 1.25px var(--ink);
}
.atlas > [aria-selected="true"] { box-shadow: 0 0 0 1.5px var(--paper), 0 0 0 3.5px var(--ink); }
.atlas-row { display: grid; grid-template-columns: minmax(140px, 220px) minmax(0, 1fr) auto; align-items: center; gap: 6px 14px; padding: 7px 0; border-top: 1px solid var(--line); }
.atlas-row > :first-child { font: 500 13.5px/1.3 var(--font); min-width: 0; }
.atlas-row > :last-child { font: 500 12px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); white-space: nowrap; }
@media (max-width: 759px) {
  .atlas { max-width: calc(18 * 16px + 17 * 3px); }
  .atlas.sm { max-width: calc(20 * 14px + 19 * 3px); }
  .atlas-row { grid-template-columns: minmax(0, 1fr) auto; }
  .atlas-row > .atlas { grid-column: 1 / -1; grid-row: 2; }
}

.strip-mark { flex: none; align-self: stretch; width: 2px; margin: -3px 1px; border-radius: 1px; background: var(--amber); }

.legend {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; margin: 0; padding: 0; list-style: none;
  font: 500 12.5px/1.3 var(--font); color: var(--soft);
}
.legend > li, .legend > span, .legend > button, .legend li > button { display: inline-flex; align-items: center; gap: 7px; min-height: 20px; }
.legend b { font: 500 12.5px var(--font); color: var(--ink); font-variant-numeric: tabular-nums; }
.legend button {
  appearance: none; border: 0; background: none; cursor: pointer; font: inherit; color: inherit;
  padding: 4px 8px; margin: -4px -8px; border-radius: 8px; transition: background-color .16s var(--ease), color .16s var(--ease);
}
.legend button[aria-pressed="true"] { background: var(--sel-bg); color: var(--ink); box-shadow: var(--sel-edge); }
.legend button:disabled { color: var(--off-fg); cursor: default; }
@media (pointer: coarse) { .legend button { padding: 12px 8px; margin: -12px -8px; } }
@media (hover: hover) and (pointer: fine) { .legend button:not(:disabled):not([aria-pressed="true"]):hover { background: var(--hover); color: var(--ink); } }

/* owed pips: filled for each clean answer given */
.pips { display: inline-flex; align-items: center; gap: 4px; vertical-align: middle; }
.pips > i { display: block; flex: none; width: 9px; height: 9px; border-radius: 50%; box-shadow: inset 0 0 0 1.5px var(--ink); }
.pips > i.on { background: var(--amber); box-shadow: inset 0 0 0 1.5px var(--amber-deep); }
.pips.lg > i { width: 11px; height: 11px; }

/* difficulty: three ascending bars (ink on --mat) drawn before the word */
.dbars {
  --b1: var(--ink); --b2: var(--ink); --b3: var(--ink);
  display: inline-flex; align-items: center; gap: 6px; vertical-align: middle;
  font: 500 12.5px/1 var(--font); color: var(--ink); white-space: nowrap;
}
.dbars::before {
  content: ""; flex: none; width: 14px; height: 12px;
  background: linear-gradient(var(--b1) 0 0) 0 100% / 4px 5px no-repeat,
              linear-gradient(var(--b2) 0 0) 5px 100% / 4px 8.5px no-repeat,
              linear-gradient(var(--b3) 0 0) 10px 100% / 4px 12px no-repeat;
}
.dbars[data-tier="easy"]   { --b2: var(--mat); --b3: var(--mat); }
.dbars[data-tier="medium"] { --b3: var(--mat); }
.dbars.bare { gap: 0; font-size: 0; }

/* ------------------------------------------------------------------ charts (charts.js)

   <figure class="chart NAME">: the drawing is role=img with the summary, a hidden
   table.sr holds the numbers, and .chart-hits lays real buttons over the marks. SVG
   charts are drawn at the host's width, so these sizes are CSS pixels. Marks read
   the tokens, so dark and print follow; --surface is the colour a mark's ring and a
   hollow fill take (paper, or --tile inside a card).
   NAME: band-chart (UI.bandChart / UI.trend) · gauge · scale · bar-chart · cal
   (UI.calendar, .plan, .compact) · heatmap · line-chart · spread · quad-chart ·
   stackbar · clock-lane · dot-strip; .spark is inline. */

figure.chart { --surface: var(--page); position: relative; margin: 0; min-width: 0; }
:where(.card, .panel, .stat) figure.chart { --surface: var(--raised); }
:where(dialog, .popover) figure.chart { --surface: var(--float); }
.chart-plot { position: relative; min-width: 0; }
.chart-plot > svg { display: block; width: 100%; height: auto; overflow: visible; }
figure.chart svg text { font: 500 11.5px var(--font); fill: var(--soft); font-variant-numeric: tabular-nums; }
figure.chart svg text.ch-lab { font: 500 11px var(--font); }
/* a label crossing a line or a band keeps a halo of the surface under it */
figure.chart svg text:is(.ch-lab, .ch-gain, .ch-end, .q-lab, .q-name) { paint-order: stroke; stroke: var(--surface); stroke-width: 3px; stroke-linejoin: round; }
figure.chart svg text.ch-gain { font: 600 11.5px var(--font); fill: var(--ink); }
figure.chart svg text.ch-end { font: 600 11.5px var(--font); fill: var(--ink); }
figure.chart svg text.ch-glyph { font: 700 9px var(--font); fill: var(--soft); }
figure.chart svg text.ch-xtick.on { fill: var(--ink); font-weight: 600; }
.chart-ctl { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-bottom: 12px; }
.card-head .chart-ctl { margin: 0; }
.chart-ctl > .help { display: none; }
.chart-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; margin-top: 10px; }
.chart-foot:empty, .chart-ctl:empty { display: none; }
.chart-note { margin: 8px 0 0; font: 500 12.5px/1.45 var(--font); color: var(--soft); }
.chart-foot > .chart-note { margin: 0; flex-basis: 100%; }
.chart-key { gap: 6px 14px; }
.chart-key > li.off { opacity: .45; }
.ch-key { flex: none; overflow: visible; }
.ch-key-t { display: inline-grid; place-items: center; width: 18px; font: 700 12px var(--font); color: var(--ink); }
.chart-compare { margin-left: auto; margin-right: 0; }
.chart-compare[aria-pressed="true"] { color: var(--ink); box-shadow: var(--sel-edge); background: var(--sel-bg); }

/* the controls laid over the drawing: invisible until hovered or focused */
.chart-hits { position: absolute; inset: 0; pointer-events: none; }
.chart-hit {
  position: absolute; pointer-events: auto; appearance: none; margin: 0; padding: 0; border: 0; border-radius: 6px;
  background: transparent; cursor: default; -webkit-tap-highlight-color: transparent;
}
.chart-hit.go { cursor: pointer; }
.chart-hit[aria-pressed="true"] { box-shadow: inset 0 0 0 2px var(--amber); background: color-mix(in srgb, var(--amber) 10%, transparent); }
@media (hover: hover) and (pointer: fine) {
  .chart-hit:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); }
  .band-chart .chart-hit:not([aria-pressed="true"]):hover { background: transparent; }   /* it dims the other sittings: no column too */
}
.chart-hit:focus-visible { outline-offset: 0; }
.chart-pop-t { margin: 0 0 10px; font: 500 13.5px/1.45 var(--font); }
.chart-empty {
  position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; gap: 10px;
  padding: 12px; text-align: center; font: 500 13.5px/1.4 var(--font); color: var(--soft); pointer-events: none;
}
.chart-empty > * { pointer-events: auto; }

/* SVG marks */
.ch-grid line, .ch-grid-l { stroke: var(--line); stroke-width: 1; }
.ch-base { stroke: var(--faint); stroke-width: 1; }
.ch-pace-t { stroke: var(--faint); stroke-width: 1; }
.ch-zone { fill: color-mix(in srgb, var(--amber) 18%, transparent); stroke: color-mix(in srgb, var(--amber) 45%, transparent); stroke-width: 1; vector-effect: non-scaling-stroke; }   /* a hairline edge */
@media screen { :root[data-theme="dark"] .ch-zone { fill: color-mix(in srgb, var(--amber) 10%, transparent); stroke: color-mix(in srgb, var(--amber) 30%, transparent); } }   /* 18% read as a brown slab */
.ch-cone { fill: var(--mat); opacity: .8; }
.ch-cone-mid { stroke: var(--ink); stroke-width: 1; stroke-dasharray: 3 3; opacity: .55; fill: none; }
.ch-pace { stroke: var(--ink); stroke-width: 1.5; stroke-dasharray: 6 4; opacity: .7; fill: none; }
.ch-target { stroke: var(--amber-deep); stroke-width: 1.5; stroke-dasharray: 5 4; fill: none; }
.ch-exam { stroke: var(--amber); stroke-width: 1.5; }
.ch-now { stroke: var(--faint); stroke-width: 1; }
.ch-cross { stroke: var(--ink); stroke-width: 1; opacity: .35; }
.ch-trend { stroke: var(--ink); stroke-width: 2; fill: none; stroke-linejoin: round; stroke-linecap: round; }
.ch-band.sec  { fill: color-mix(in srgb, var(--ink) 24%, transparent); }
.ch-band.mod  { fill: color-mix(in srgb, var(--ink) 12%, transparent); }
.ch-band.diag { fill: none; stroke: var(--ink); stroke-width: 1.25; }
.ch-band.now  { fill: color-mix(in srgb, var(--amber) 50%, transparent); stroke: none; }
.ch-mid { fill: var(--ink); stroke: var(--surface); stroke-width: 2; }
.ch-mid.now { fill: var(--amber); }
.ch-dia { fill: var(--surface); stroke: var(--ink); stroke-width: 1.5; stroke-linejoin: round; }
.ch-bracket { fill: none; stroke: var(--ink); stroke-width: 1.25; }
.ch-aim { fill: var(--surface); stroke: var(--ink); stroke-width: 1.5; }
.ch-aim.on { fill: var(--ink); }
.ch-lane { fill: color-mix(in srgb, var(--ink) 4%, transparent); }
.ch-rw { fill: none; stroke-width: 5; stroke-linejoin: round; stroke-linecap: round; }
.ch-rw-pt { fill: var(--surface); stroke: var(--ink); stroke-width: 1.5; }
.ch-ghost { fill: none; stroke: var(--faint); stroke-width: 1.5; stroke-dasharray: 3 3; }
.ch-hatch-bg { fill: color-mix(in srgb, var(--ink) 16%, transparent); }
.ch-hatch-ln { stroke: var(--ink); stroke-width: 1.6; }
.ch-hatch-bg.rw { fill: color-mix(in srgb, var(--ink) 10%, transparent); }
.ch-f-ink   { fill: var(--ink); }
.ch-f-soft  { fill: color-mix(in srgb, var(--ink) 30%, transparent); }
.ch-f-amber { fill: var(--amber); }
.ch-f-mat   { fill: var(--mat); }
.ch-st { fill: var(--st-fill); stroke: var(--st-edge); stroke-width: 1.5; }
.ch-st:is([data-status="blank"], [data-status="unanswered"], [data-status="skipped"]) { stroke-dasharray: 3 2; }
.ch-st[data-status="not_reached"] { stroke-dasharray: 1 2; }
.ch-plan { fill: color-mix(in srgb, var(--mat) 40%, transparent); stroke: var(--mat); stroke-width: 1.5; }
.ch-zero { stroke: var(--faint); stroke-width: 2; stroke-linecap: round; }
.ch-budget { stroke: var(--ink); stroke-width: 2; stroke-linecap: round; }
.ch-today { fill: none; stroke: var(--ink); stroke-width: 1.5; }
.ch-line { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ch-line.dashed { stroke-dasharray: 5 4; opacity: .75; }
.ch-line.soft { opacity: .5; }
.ch-line.you { stroke: var(--amber); }
/* the amber fade under a trend line (gradient 2 of 3): <path class="ch-area" fill="url(#id)"> closed down to the
   baseline, its <linearGradient class="ch-fade" id x1=0 y1=0 x2=0 y2=1> holding two <stop>s (offset 0 and 1);
   without the gradient it falls back to a flat 10% tint */
.ch-area { stroke: none; pointer-events: none; }
.ch-area:not([fill]) { fill: color-mix(in srgb, var(--amber) 10%, transparent); }
.ch-fade > stop:first-child { stop-color: var(--amber); stop-opacity: .14; }
.ch-fade > stop:last-child { stop-color: var(--amber); stop-opacity: 0; }
.ch-dot { fill: var(--ink); stroke: var(--surface); stroke-width: 2; }
.ch-dot.thin { stroke-width: 1.5; }
.ch-dot.you { fill: var(--amber); }
.ch-star { fill: var(--amber); stroke: var(--ink-deep); stroke-width: .8; stroke-linejoin: round; }
.ch-stripe { fill: color-mix(in srgb, var(--ink) 5%, transparent); }
.ch-stripe.alt { fill: none; }
.ch-ahead { fill: var(--wash-2); }
.ch-flag { fill: var(--amber); }
.ch-rw-sw { stroke: none; }

/* UI.gauge: a 240-degree arc, the band's numbers in its centre */
.gauge-plot { max-width: 440px; margin: 0 auto; container-type: inline-size; }
.gauge-plot > svg { overflow: visible; }
.gauge-c {
  position: absolute; left: 50%; top: 60%; transform: translate(-50%, -50%);
  display: grid; justify-items: center; gap: 4px; text-align: center; pointer-events: none;
}
.gauge-v { font: 600 44px/1 var(--font); font-size: clamp(34px, 12.8cqw, 44px); letter-spacing: -.03em; white-space: nowrap; color: var(--ink-strong); }
.gauge-s { font: 500 12.5px var(--font); color: var(--soft); white-space: nowrap; }
@media (max-width: 759px) { .gauge-v { font-size: clamp(34px, 12.8cqw, 44px); } }
.gauge-key { display: flex; justify-content: center; margin-top: 8px; }
figure.gauge .chart-note { text-align: center; }
/* the gauge as an instrument: a groove as wide as the band (a light lip under it), the amber band standing on it,
   a knob needle with a small shadow */
.g-track { fill: none; stroke: var(--tile-2); stroke-width: 16; stroke-linecap: round; filter: drop-shadow(0 1px 0 rgb(255 255 255 / .8)); }
:where(dialog, .popover) .g-track { stroke: var(--mat); }
@media screen { :root[data-theme="dark"] .g-track { stroke: var(--mat); filter: drop-shadow(0 1px 0 rgb(255 255 255 / .05)); } }
.g-tick { stroke: var(--faint); stroke-width: 1.5; }
figure.chart svg text.g-lab { font-size: 11px; }
figure.chart svg text.g-tl { font: 600 11.5px var(--font); fill: var(--ink); }
.g-prev { fill: color-mix(in srgb, var(--ink) 18%, transparent); }
.g-out { fill: none; stroke: var(--ink); stroke-width: 1.5; }
.g-out.dotted { stroke-dasharray: 1.5 3; stroke-linecap: round; }
.g-out.dashed { stroke-dasharray: 5 3; }
.g-band { fill: var(--amber); filter: drop-shadow(0 1px 1px rgb(140 70 0 / .25)); }
.g-mid { stroke: var(--ink-strong); stroke-width: 5; stroke-linecap: round; filter: drop-shadow(0 1px 1.5px rgb(var(--sh) / .35)); }
.g-target { stroke: var(--ink); stroke-width: 1.5; stroke-dasharray: 3 2; fill: none; }

/* UI.scale: a 200-800 (or 400-1600) track, the band, the ticks and their labels */
.scale-vis { position: relative; padding: 0 1px; }
.scale-track { position: relative; height: 8px; margin: 3px 0; border-radius: 4px; background: var(--track); box-shadow: inset 0 1px 1px rgb(var(--sh) / .08); }
.scale.sm .scale-track { height: 6px; }
.scale-track > i { position: absolute; display: block; }
.scale-band { top: 0; bottom: 0; border-radius: 4px; background: color-mix(in srgb, var(--ink) 30%, transparent); transform-origin: left center; }
.scale-band.grow { animation: grow .6s var(--ease) both; }
.scale-band > b { position: absolute; left: 50%; top: 50%; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%; background: var(--ink); box-shadow: 0 0 0 2px var(--surface); }
.scale-prev { top: -3px; bottom: -3px; border-radius: 6px; box-shadow: inset 0 0 0 1.5px var(--faint); }
.scale-est { top: 50%; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%; box-shadow: inset 0 0 0 1.5px var(--faint); background: var(--surface); }
.scale-tick { top: -4px; bottom: -4px; width: 2px; margin-left: -1px; border-radius: 1px; background: var(--ink); }
.scale-tick.target { background: var(--amber); }
.scale-tick.start { background: var(--faint); }
.scale-dot { top: 50%; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%; background: var(--ink); box-shadow: 0 0 0 2px var(--surface); }
.scale-row { position: relative; height: 17px; font: 500 11px/17px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }
.scale-l { position: absolute; top: 0; transform: translateX(-50%); white-space: nowrap; }
.scale-l.l { transform: none; }
.scale-l.r { transform: translateX(-100%); }
.scale-l.target, .scale-l.band { color: var(--ink); }
.scale-l.band { font: 600 12px/17px var(--font); font-variant-numeric: tabular-nums; }
.scale-l.end { transform: none; }
.scale-l.end.lo { left: 0; }
.scale-l.end.hi { right: 0; }
.scale-hist { display: block; width: 100%; height: 32px; margin-bottom: 2px; }
.sh-band { fill: var(--wash-2); }
.sh-bar { fill: color-mix(in srgb, var(--ink) 35%, transparent); }
.sh-avg { fill: var(--ink); }
.sh-target { fill: var(--amber); }

/* UI.calendar: the heat look (Today; 40px cells, 34 on a phone, 32 compact) and the plan
   look (Plan; 64px tall, 46 on a phone). Status is the marker's shape plus aria-label. */
.cal-head { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; }
.cal-title { font: 600 15px var(--font); min-width: 9.5em; text-align: center; }
.cal-nav { font-size: 20px; line-height: 1; }
.cal-jump { margin-left: auto; }
.cal-metric { margin-bottom: 10px; }
.cal-metric > .help { display: none; }
.cal-grid { --c: 40px; display: grid; grid-template-columns: repeat(7, var(--c)); gap: 4px; }
.cal-row, .cal-dow { display: contents; }
.cal-dow > span { font: 500 11px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); text-align: center; }
.cal-d {
  position: relative; display: grid; place-items: center; width: 100%; height: var(--c); padding: 0;
  border: 0; border-radius: 6px; background: var(--h0, var(--well)); color: var(--ink);
  font: 500 13px var(--font); font-variant-numeric: tabular-nums; appearance: none; cursor: pointer;
}
.cal-d.blank { background: none; cursor: default; color: var(--faint); }
.cal-d[data-h="1"] { background: var(--wash); }
.cal-d[data-h="2"] { background: var(--wash-2); }
.cal-d[data-h="3"] { background: var(--amber); color: var(--ink-deep); }
.cal-d[data-hollow] { background: none; box-shadow: inset 0 0 0 1.5px var(--amber-deep); }
.cal-d[data-today] .cal-n { font-weight: 700; text-decoration: underline 1.5px; text-underline-offset: 3px; }   /* today: its number, no box */
/* the picked day is one thin ink ring: orange is the SAT's, as the legend says */
.cal-d[aria-selected="true"] { outline: 1.5px solid var(--ink); outline-offset: 1px; }
.cal-d:focus-visible { outline-offset: -2px; }
.cal-d[data-exam] { box-shadow: inset 0 0 0 1.5px var(--amber); }
.cal-d[data-spare]::after { content: ""; position: absolute; inset: 5px; border-radius: 50%; box-shadow: inset 0 0 0 1.5px var(--amber-deep); pointer-events: none; }
.cal-sat { position: absolute; bottom: 2px; font: 600 9px var(--font); font-variant-numeric: tabular-nums; color: var(--amber-deep); }
.cal-mark { position: absolute; bottom: 4px; left: 50%; width: 5px; height: 5px; margin-left: -2.5px; border-radius: 50%; background: currentColor; }
.cal-d[data-kind="test"] .cal-mark { width: 6px; height: 4px; margin-left: -3px; border-radius: 1px; }
.cal-d[data-kind="rest"] .cal-mark { width: 6px; height: 1.5px; margin-left: -3px; border-radius: 1px; }
.cal-d[data-state="skipped"] .cal-mark { background: linear-gradient(to top right, transparent 43%, currentColor 43% 57%, transparent 57%); box-shadow: inset 0 0 0 1px currentColor; }
.cal-shield { position: absolute; top: 4px; right: 4px; width: 7px; height: 8px; background: var(--soft); clip-path: polygon(50% 0, 100% 18%, 100% 55%, 50% 100%, 0 55%, 0 18%); }
.cal-meta { position: absolute; top: 2px; left: 0; right: 0; font: 500 9px/1 var(--font); font-variant-numeric: tabular-nums; color: var(--soft); white-space: nowrap; text-align: center; overflow: hidden; }
.cal:not(.plan) .cal-d:has(.cal-meta) .cal-n { margin-top: 5px; }
.cal-d[data-h="3"] .cal-meta { color: var(--ink-deep); }
@media (max-width: 759px) { .cal-grid { --c: 34px; } }
.cal.compact .cal-grid { --c: 32px; }
.cal.compact .cal-d { font-size: 11.5px; }
/* the plan look */
.cal.plan .cal-grid { --c: 64px; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.cal.plan.compact .cal-grid { --c: 32px; }
.cal.plan .cal-d { background: var(--paper); border-radius: 8px; box-shadow: inset 0 0 0 1px var(--line); }
.cal.plan .cal-d.blank { background: none; box-shadow: none; }
.cal.plan .cal-n { position: absolute; top: 5px; left: 7px; font: 500 12px var(--font); font-variant-numeric: tabular-nums; }
.cal.plan.compact .cal-n { position: static; }
.cal.plan .cal-mark { position: static; width: 14px; height: 14px; margin: 0; border-radius: 50%; background: none; box-shadow: inset 0 0 0 1.5px var(--ink); color: var(--ink); }
.cal.plan.compact .cal-mark { position: absolute; bottom: 3px; left: 50%; width: 6px; height: 6px; margin-left: -3px; }
.cal.plan .cal-d[data-kind="test"] .cal-mark { width: 16px; height: 12px; border-radius: 2px; }
.cal.plan.compact .cal-d[data-kind="test"] .cal-mark { width: 7px; height: 5px; margin-left: -3.5px; }
.cal.plan .cal-d[data-kind="rest"] .cal-mark { width: 14px; height: 2px; box-shadow: none; background: var(--faint); border-radius: 1px; }
.cal.plan .cal-d[data-state="done"] .cal-mark { background: var(--ink); }
.cal.plan .cal-d[data-state="partial"] .cal-mark { background: linear-gradient(to top, var(--ink) 50%, transparent 50%); }
.cal.plan .cal-d[data-state="missed"] .cal-mark { background: linear-gradient(var(--ink) 0 0) 50% 50% / 60% 1.5px no-repeat; }
.cal.plan .cal-d[data-state="skipped"] .cal-mark { background: linear-gradient(to top right, transparent 44%, var(--ink) 44% 56%, transparent 56%); }
.cal.plan .cal-d[data-state="madeup"] .cal-mark { background: radial-gradient(circle, var(--surface) 0 2px, var(--ink) 2.5px); }
.cal.plan .cal-d[data-kind="off"] { background: repeating-linear-gradient(-45deg, var(--mat) 0 1.5px, transparent 1.5px 6px), var(--tile); }
.cal.plan .cal-d[data-exam] { background: var(--amber); color: var(--ink-deep); box-shadow: none; }
.cal.plan .cal-d[data-exam] .cal-sat { position: static; font-size: 12px; color: var(--ink-deep); }
.cal.plan .cal-d[data-kind="eve"] .cal-meta { position: static; font-size: 10px; }
.cal.plan .cal-meta { top: auto; left: auto; font-size: 10px; right: 6px; bottom: 4px; text-align: right; }
.cal.plan .cal-d[data-today] { box-shadow: inset 0 0 0 1px var(--ink); }   /* with the marked number: the cell's own hairline, in ink */
.cal.plan .cal-d[aria-selected="true"] { outline: 0; background: var(--sel-bg); box-shadow: var(--sel-edge); }
.cal-own { position: absolute; top: 7px; right: 7px; width: 7px; height: 4px; border: solid var(--ink); border-width: 0 0 1.5px 1.5px; transform: rotate(-45deg); }
.cal-phase { position: absolute; top: 5px; right: 6px; font: 600 9px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }
.cal-own + .cal-phase { right: 18px; }
.cal.compact .cal-d[data-phase]::before { content: ""; position: absolute; left: 3px; right: 3px; bottom: -3px; height: 3px; border-radius: 2px; background: var(--mat); }
.cal.compact .cal-d[data-phase="now"]::before { background: var(--amber); }
@media (max-width: 759px) {
  .cal.plan .cal-grid { --c: 46px; gap: 3px; }
  .cal.plan .cal-n { top: 3px; left: 4px; font-size: 11px; }
  .cal.plan .cal-meta { font-size: 9.5px; right: 3px; bottom: 2px; }
  .cal.plan .cal-mark { width: 11px; height: 11px; }
  .cal.plan .cal-phase { display: none; }
}
.cal-key { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; margin-top: 12px; font: 500 11px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }
.cal-scale { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; }
.cal:not(.plan) .cal-scale { gap: 3px; }
.cal:not(.plan) .cal-scale > span:first-child { margin-right: 4px; }
.cal:not(.plan) .cal-scale > span:last-child { margin-left: 4px; }
.cal-k { display: inline-flex; align-items: center; gap: 6px; }
.cal-sw { position: relative; display: inline-block; flex: none; width: 12px; height: 12px; border-radius: 3px; background: var(--h0, var(--well)); }
.cal-sw[data-h="1"] { background: var(--wash); box-shadow: none; }
.cal-sw[data-h="2"] { background: var(--wash-2); box-shadow: none; }
.cal-sw[data-h="3"] { background: var(--amber); box-shadow: none; }
.cal-sw:is(.practice, .test, .skipped, .done, .partial, .missed, .madeup, .rest) { background: none; box-shadow: none; }
.cal-sw.practice::after, .cal-sw.skipped::after { content: ""; position: absolute; inset: 3.5px; border-radius: 50%; background: var(--ink); }
.cal-sw.test::after { content: ""; position: absolute; left: 3px; right: 3px; top: 4px; bottom: 4px; border-radius: 1px; background: var(--ink); }
.cal-sw.skipped::after { background: linear-gradient(to top right, transparent 40%, var(--ink) 40% 60%, transparent 60%); box-shadow: inset 0 0 0 1px var(--ink); }
.cal-sw.rest { background: linear-gradient(var(--faint) 0 0) 50% 50% / 70% 2px no-repeat; }
.cal-sw.off { background: repeating-linear-gradient(-45deg, var(--mat) 0 1.5px, transparent 1.5px 4px), var(--tile); box-shadow: inset 0 0 0 1px var(--line); }
.cal-sw.done { border-radius: 50%; background: var(--ink); }
.cal-sw.partial { border-radius: 50%; box-shadow: inset 0 0 0 1.5px var(--ink); background: linear-gradient(to top, var(--ink) 50%, transparent 50%); }
.cal-sw.missed { border-radius: 50%; box-shadow: inset 0 0 0 1.5px var(--ink); background: linear-gradient(var(--ink) 0 0) 50% 50% / 60% 1.5px no-repeat; }
.cal-sw.madeup { border-radius: 50%; background: radial-gradient(circle, var(--surface, var(--paper)) 0 2px, var(--ink) 2.5px); }
.cal.plan .cal-sw.skipped { border-radius: 50%; }
.cal.plan .cal-sw.skipped::after { inset: 0; border-radius: 50%; }
.cal.plan .cal-sw.practice { border-radius: 50%; box-shadow: inset 0 0 0 1.5px var(--ink); }
.cal.plan .cal-sw.practice::after, .cal.plan .cal-sw.test::after { content: none; }
.cal.plan .cal-sw.test { box-shadow: inset 0 0 0 1.5px var(--ink); }
.cal-sw.sat { background: none; box-shadow: inset 0 0 0 1.5px var(--amber); }
.cal.plan .cal-sw.sat { background: var(--amber); box-shadow: none; }
.cal-sw.shield { width: 9px; background: var(--soft); clip-path: polygon(50% 0, 100% 18%, 100% 55%, 50% 100%, 0 55%, 0 18%); border-radius: 0; box-shadow: none; }
.cal-sw.spare { background: none; border-radius: 50%; box-shadow: inset 0 0 0 1.5px var(--amber-deep); }

/* UI.heatmap: 7 weekday rows by week columns; orange by questions answered, and the
   tooltip and aria-label carry the numbers, so the heat is never the only signal */
.heat {
  --hc: 12px; --h0: var(--well);
  display: grid; grid-template-columns: 12px repeat(var(--weeks, 26), minmax(0, var(--hc))); grid-template-rows: 14px repeat(7, auto);
  gap: 3px; width: 100%; max-width: max-content;   /* 52 weeks shrink to the card instead of running past it */
}
.heat-r { display: contents; }
.heat-m { font: 500 10.5px/14px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); white-space: nowrap; }
.heat-d { font: 500 10px/1 var(--font); font-variant-numeric: tabular-nums; color: var(--soft); align-self: center; }
.heat-c, .heat-scale > i {
  display: block; width: 100%; max-width: var(--hc, 12px); aspect-ratio: 1; padding: 0; border: 0; border-radius: 3px;
  background: var(--h0, var(--well)); appearance: none;
}
button.heat-c { cursor: pointer; }
.heat-c.pad { visibility: hidden; }
:is(.heat, .heat-scale, .heat-key) [data-h="1"] { background: color-mix(in srgb, var(--amber) 28%, var(--h0, var(--well))); }
:is(.heat, .heat-scale, .heat-key) [data-h="2"] { background: color-mix(in srgb, var(--amber) 52%, var(--h0, var(--well))); }
:is(.heat, .heat-scale, .heat-key) [data-h="3"] { background: color-mix(in srgb, var(--amber) 76%, var(--h0, var(--well))); }
:is(.heat, .heat-scale, .heat-key) [data-h="4"] { background: var(--amber); }
.heat-c[data-rest] { background: linear-gradient(var(--faint) 0 0) 50% 50% / 50% 1.5px no-repeat, var(--h0, var(--well)); }
.heat-c[data-spare] { border-radius: 50%; box-shadow: inset 0 0 0 1.5px var(--amber-deep); }
.heat-c[data-spare]:not([data-h]) { background: none; }
.heat-c[aria-current="date"] { box-shadow: 0 0 0 1.5px var(--ink); }
.heat-c:focus-visible { outline-offset: 1px; border-radius: 3px; }
@media (max-width: 759px) {
  .heat { grid-template-columns: 12px repeat(var(--weeks, 20), minmax(0, 1fr)); grid-template-rows: 14px repeat(7, auto); gap: 2px; width: 100%; max-width: calc(var(--weeks, 20) * 17px + 12px); }
  .heat-c { width: auto; height: auto; aspect-ratio: 1; border-radius: 2px; }
  .heat-key .heat-c { width: 11px; height: 11px; }
}
.heat-key { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; margin-top: 10px; font: 500 11px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }
.heat-k { display: inline-flex; align-items: center; gap: 6px; }
.heat-k > .heat-c { --hc: 11px; }
.heat-scale { display: inline-flex; align-items: center; gap: 3px; font: 500 11px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }
.heat-scale > span:first-child { margin-right: 4px; } .heat-scale > span:last-child { margin-left: 4px; }
.heat-stats { margin-top: 8px; font: 500 12.5px var(--font); font-variant-numeric: tabular-nums; color: var(--ink); }

/* a trend host of an older page (the svg fills the width) */
.trend { position: relative; min-width: 0; }
.trend > svg { display: block; width: 100%; height: auto; overflow: visible; font: 500 11.5px var(--font); font-variant-numeric: tabular-nums; }
.trend text { fill: var(--soft); }

/* UI.spread: one thin bar per option; the key is orange, your wrong pick ink */
.spread-vis { display: grid; gap: 10px; }
.spread-row { display: grid; grid-template-columns: auto minmax(0, 1fr) 44px; align-items: center; gap: 3px 10px; }
.spread-l { font: 600 14px var(--font); min-width: 1em; }
.spread-main { display: grid; gap: 4px; min-width: 0; }
.spread-t { font: 500 13.5px/1.35 var(--font); }
.spread-bar { display: block; height: 6px; border-radius: 3px; background: var(--track); overflow: hidden; }
.spread-bar > i { display: block; height: 100%; border-radius: 3px; background: color-mix(in srgb, var(--ink) 35%, transparent); }
.spread-row[data-key] .spread-bar > i { background: var(--amber); }
.spread-row[data-pick]:not([data-key]) .spread-bar > i { background: var(--ink); }
.spread-v { font: 500 13px var(--font); text-align: right; font-variant-numeric: tabular-nums; }
.spread-why { grid-column: 2 / -1; display: flex; flex-wrap: wrap; gap: 2px 10px; font: 500 12.5px/1.35 var(--font); color: var(--soft); }
.spread-tag { font-weight: 600; color: var(--ink); }

/* UI.quadrant: four named zones with counts, the reference lines, glyph dots */
.q-zone { fill: transparent; }
.q-zone.fast_right { fill: color-mix(in srgb, var(--amber) 8%, transparent); }
.q-zone.slow_wrong { fill: color-mix(in srgb, var(--ink) 5%, transparent); }
figure.chart svg text.q-name { font: 600 11px var(--font); fill: var(--ink); }
figure.chart svg text.q-lab { font: 500 11.5px var(--font); fill: var(--ink); }
.q-ref { stroke: var(--ink); stroke-width: 1; opacity: .35; }
.q-halo { fill: var(--surface); stroke: var(--line); stroke-width: 1; }
.quad-ctl > .help { display: none; }
.quad-list { display: grid; }
.quad-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 6px 12px; padding: 10px 0; border-top: 1px solid var(--line); }
.quad-n { font: 600 14px var(--font); }
.quad-row > .mono { font-size: 12.5px; color: var(--soft); }
.quad-chip { justify-self: start; padding: 2px 7px; border-radius: 6px; background: var(--well); font: 500 12.5px var(--font); }
.quad-chip[data-q="fast_right"] { background: var(--wash); }
.quad-acts { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 8px 16px; width: 100%; }
.quad-act { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 0; border-top: 1px solid var(--line); }
.quad-an { display: inline-flex; align-items: baseline; gap: 6px; font: 600 13px var(--font); }
.quad-an > b { font: 500 12.5px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }

/* UI.spark: a small trend line, its last point orange */
.spark { position: relative; display: inline-block; vertical-align: middle; line-height: 0; }
.spark > svg { overflow: visible; }
.spark-l { fill: none; stroke: var(--ink); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.spark-d { fill: var(--amber); }

/* UI.stackBar: segments in ink shades, the largest orange; 2px gaps between them */
.sb-wrap { position: relative; }
.sb-bar { display: flex; gap: 2px; height: 30px; }
.sb-seg {
  --sb: var(--ink); flex: 1 1 0; min-width: 3px; display: flex; align-items: center; padding: 0; overflow: hidden;   /* no padding: a padded segment can't be narrower than the padding, which bent the proportions and the hit boxes */
  background: var(--sb); color: var(--on-ink); font: 600 12px/1 var(--font); white-space: nowrap;
}
.sb-seg:first-child { border-radius: 6px 0 0 6px; }
.sb-seg:last-child { border-radius: 0 6px 6px 0; }
.sb-seg:only-child { border-radius: 6px; }
:is(.sb-seg, .sb-sw, .sb-shade)[data-shade="1"] { --sb: color-mix(in srgb, var(--ink) 72%, var(--surface, var(--paper))); }
:is(.sb-seg, .sb-sw, .sb-shade)[data-shade="2"] { --sb: color-mix(in srgb, var(--ink) 48%, var(--surface, var(--paper))); color: var(--ink); }
:is(.sb-seg, .sb-sw, .sb-shade)[data-shade="3"] { --sb: color-mix(in srgb, var(--ink) 30%, var(--surface, var(--paper))); color: var(--ink); }
:is(.sb-seg, .sb-sw, .sb-shade)[data-shade="4"] { --sb: color-mix(in srgb, var(--ink) 16%, var(--surface, var(--paper))); color: var(--ink); }
.sb-shade { fill: var(--sb, var(--ink)); }
:is(.sb-seg, .sb-sw).top { --sb: var(--amber); color: var(--ink-deep); }
:is(.sb-seg, .sb-sw).hatch { background: repeating-linear-gradient(-45deg, var(--ink) 0 1.5px, transparent 1.5px 5px), color-mix(in srgb, var(--ink) 12%, var(--surface, var(--paper))); color: var(--ink); }
.sb-seg.empty { background: var(--track); }
/* .sb-warm: shades of orange then grey, so the legend's swatches read as colour codes */
.sb-warm :is(.sb-seg, .sb-sw)[data-shade="0"] { --sb: var(--amber); color: var(--ink-deep); }
.sb-warm :is(.sb-seg, .sb-sw)[data-shade="1"] { --sb: color-mix(in srgb, var(--amber) 55%, var(--surface, var(--paper))); color: var(--ink-deep); }
.sb-warm :is(.sb-seg, .sb-sw)[data-shade="2"] { --sb: color-mix(in srgb, var(--ink) 72%, var(--surface, var(--paper))); color: var(--on-ink); }
.sb-warm :is(.sb-seg, .sb-sw)[data-shade="3"] { --sb: color-mix(in srgb, var(--ink) 30%, var(--surface, var(--paper))); color: var(--ink); }
/* dark: the ink shades are light, so they step down from a mid tone instead of starting at near-white (the brightest block on the page) */
@media screen {
  :root[data-theme="dark"] :is(.sb-seg, .sb-sw, .sb-shade)[data-shade] { color: var(--ink); }
  :root[data-theme="dark"] :is(.sb-seg, .sb-sw, .sb-shade)[data-shade="0"] { --sb: color-mix(in srgb, var(--ink) 46%, var(--paper)); }
  :root[data-theme="dark"] :is(.sb-seg, .sb-sw, .sb-shade)[data-shade="1"] { --sb: color-mix(in srgb, var(--ink) 34%, var(--paper)); }
  :root[data-theme="dark"] :is(.sb-seg, .sb-sw, .sb-shade)[data-shade="2"] { --sb: color-mix(in srgb, var(--ink) 25%, var(--paper)); }
  :root[data-theme="dark"] :is(.sb-seg, .sb-sw, .sb-shade)[data-shade="3"] { --sb: color-mix(in srgb, var(--ink) 18%, var(--paper)); }
  :root[data-theme="dark"] :is(.sb-seg, .sb-sw, .sb-shade)[data-shade="4"] { --sb: color-mix(in srgb, var(--ink) 12%, var(--paper)); }
}
.sb-l { padding: 0 8px; }
.sb-l.hide { visibility: hidden; }
@media (max-width: 759px) { .sb-l { visibility: hidden; } }
.sb-hits { position: absolute; inset: 0; display: flex; gap: 2px; }
.sb-hit { flex: 1 1 0; min-width: 3px; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; cursor: pointer; appearance: none; }
@media (hover: hover) and (pointer: fine) { .sb-hit:hover { box-shadow: inset 0 0 0 2px var(--ink); } }
.sb-key { margin-top: 10px; }
/* the warm bars' legend: the full names, wrapping to a second line when the card is narrow (the user: "Cl… Tr…" was cut) */
.sb-warm .sb-key { flex-wrap: wrap; gap: 6px 14px; font-size: 12px; }
.sb-warm .sb-key > li { min-width: 0; gap: 6px; white-space: nowrap; }
.sb-sw { --sb: var(--ink); display: inline-block; width: 12px; height: 12px; border-radius: 3px; background: var(--sb); flex: none; }
.sb-key > li.off .sb-sw { background: var(--track); }
.sb-n { font: 500 12px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }

/* UI.clockLane and UI.dotStrip */
.ds-dot { fill: color-mix(in srgb, var(--ink) 30%, transparent); }
.ds-dot.in { fill: var(--ink); }
.ds-you { fill: var(--amber); }

/* ------------------------------------------------------------------ pieces */

.panel { background: var(--raised); box-shadow: var(--edge); border-radius: 10px; }
.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--amber); flex: none; }
.dots { display: inline-flex; gap: 4px; }
.dots i { display: block; width: 7px; height: 7px; border-radius: 50%; background: var(--amber); }
.dots i.in { animation: pin .5s var(--ease) both; }
.dots i.out { animation: out .38s var(--ease) forwards; }

/* mastery: a track and an ink fill, with UI.masteryWord and accuracy beside it (R37) */
.mbar { position: relative; height: 3px; border-radius: 3px; overflow: hidden; background: var(--m-track); }
.mbar > i {
  position: absolute; inset: 0; background: var(--m-fill); border-radius: 3px;
  transform-origin: left center; transform: scaleX(var(--m, 0));
  transition: transform .2s var(--ease);
}

/* the topic glyphs, drawn in whatever ground they sit on */

/* ------------------------------------------------------------------ icons (premium build A3, 2026-10-19)
   For ui.css. Pairs with build/icons.js (UI.ICON in Phosphor), build/glyphs.js (topic glyphs, spots) and
   web/assets/fonts/Marks.woff2. Merge notes are in BUILD.md "## A3 icons".
   Reduced motion: the global rule at the end of ui.css already zeroes these transitions and animations. */

/* 2. one stroke, held in screen pixels: 1.4px at 16-24, 1.25px at 14 and below, a hair lighter on dark.
   g.ph is the wrapper every UI.ICON entry carries; its stroke-width="16" attribute is only a no-CSS fallback. */
:root { --i-sw: 1.4px; }
@media screen { :root[data-theme="dark"] { --i-sw: 1.3px; } }
svg:is([width="12"], [width="13"], [width="14"]), :is(.badge, .thumbs, .meter, thead) svg { --i-sw: 1.25px; }
g.ph { stroke-width: var(--i-sw, 1.4px); }
g.ph *, svg.glyph *, svg.spot * { vector-effect: non-scaling-stroke; }

/* 3. twins: an entry with a filled twin holds <g class="ph ph-f" opacity="0">fill</g><g class="ph ph-o">outline</g>.
   The fill shows for "you are here" and "on": the current nav item (sidebar, phone tabs, menu rows), a pressed
   toggle, a checked menu item (Dark mode), a selected tab. The svg must be the control's child, or sit in a
   page helper's span.ic. Nav fills stay in ink; the orange bar beside them stays the one orange mark. */
g.ph-f { opacity: var(--i-on, 0); transition: opacity var(--i-t, 110ms) var(--ease); }
g.ph-o { opacity: calc(1 - var(--i-on, 0)); transition: opacity var(--i-t, 110ms) var(--ease); }
/* in over 150ms, back over 110ms (exits about 75% of entries); the test window swaps them at once */
body[data-page="test"] :is(g.ph-f, g.ph-o) { transition: none; }
:is([aria-current="page"], [aria-current="true"], [aria-pressed="true"], [aria-checked="true"], [role="tab"][aria-selected="true"]) > svg,
:is([aria-current="page"], [aria-current="true"], [aria-pressed="true"], [aria-checked="true"], [role="tab"][aria-selected="true"]) > .ic > svg {
  --i-on: 1; --i-t: 150ms;
}
/* status toggles (Mark for review: flag, Save: bookmark, Star a word: star): an amber fill with the outline
   kept as its amber-deep edge (orange alone on paper is 2.4:1, the deep edge 3.2:1). Replaces the CSS fill
   hacks at runner.css .rn-mark / .rn-markfoot [aria-pressed="true"] svg. */
g.ph-amber { color: var(--amber); }
g.ph-amber + g.ph-o { opacity: 1; }
[aria-pressed="true"] > svg > g.ph-amber + g.ph-o, [aria-pressed="true"] > .ic > svg > g.ph-amber + g.ph-o { color: var(--amber-deep); }

/* 4. disclosure: UI.icon('caret_down', 16) turns over when its control opens (replaces ▾ ▴ ▸ ⌄ as text) */
svg:has(> g.ph-turn) { transition: rotate 150ms var(--ease); }
[aria-expanded="true"] > svg:has(> g.ph-turn) { rotate: 180deg; }

/* 5. topic glyphs (build/glyphs.js, viewBox -12 -12 24 24): replaces ui.css's .glyph, .stk, .stki, .stkl,
   .stkd rules. Two tiers: primary in currentColor, secondary (axes, risers, pans) at full width in --faint.
   No --gb sticker fill any more (it went from .glyph, .q-skill .glyph, .toast > .glyph and the palette's).
   Smallest size 20px (.q-skill .glyph). */
.glyph { width: 22px; height: 22px; flex: none; color: var(--soft); }
.stk, .stkl, .stki, .stkb { fill: none; stroke: currentColor; stroke-width: var(--i-sw, 1.4px); stroke-linecap: round; stroke-linejoin: round; }
.stki { stroke: var(--faint); }
.stkb { stroke-width: calc(var(--i-sw, 1.4px) * 2); }
.stkd { fill: currentColor; }

/* 6. spot drawings (build/glyphs.js SPOT): left-aligned above an empty state's line */
.spot { display: block; width: 72px; height: 72px; flex: none; }
@media (max-width: 759px) { .spot { width: 56px; height: 56px; } }
.spot .sp-o { fill: none; stroke: var(--faint); stroke-width: var(--i-sw, 1.4px); stroke-linejoin: round; }
.spot .sp-on { fill: var(--amber); }
/* done: each mastered corner fills in turn, 240ms each, 40ms apart (it is drawn after the student's action) */
.spot .sp-in { transform-box: view-box; transform-origin: 50px 50px; animation: sp-in 240ms var(--ease) calc(var(--i, 0) * 40ms) both; }
@keyframes sp-in { from { opacity: 0; scale: .6; } }
@media print { .spot { display: none; } }


.chip {
  display: inline-flex; align-items: center; gap: 8px;
  height: 36px; padding: 0 12px 0 9px; border-radius: 8px;
  background: var(--well); color: var(--ink);
  font: 600 14px var(--font); white-space: nowrap;
}
.chip .glyph { width: 20px; height: 20px; color: var(--ink); }

.note { font: 500 12.5px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }
.error { font: 500 13px var(--font); color: var(--ink); }
.error::before { content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 8px; border-radius: 50%; background: var(--ink); vertical-align: 1px; }

/* ------------------------------------------------------------------ a question */

.q-skill {
  display: flex; align-items: center; gap: 9px; margin-bottom: 16px;
  font: 500 12.5px var(--font); font-variant-numeric: tabular-nums; color: var(--soft);
}
.q-skill .glyph { width: 20px; height: 20px; }
.q-skill .again { display: inline-flex; align-items: center; gap: 5px; color: var(--ink); }
.q-skill .again::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--amber); }
.q-fig { margin: 4px 0 18px; display: flex; justify-content: center; }
.q-fig svg { max-width: 100%; height: auto; display: block; }
/* width 0 + min-width 100%: a wide table scrolls in place and never widens a grid or flex parent */
.q-table { margin: 4px 0 18px; overflow-x: auto; width: 0; min-width: 100%; }
.q-table table { border-collapse: collapse; font: 400 16px/1.35 var(--serif); margin: 0 auto; }
.q-table th, .q-table td { padding: 7px 14px; border: 1px solid var(--line); text-align: center; font-variant-numeric: tabular-nums; }
.q-table th { font-weight: 600; background: var(--tile); }
.q-stem { font: 400 var(--q-size)/1.58 var(--serif); color: var(--ink); white-space: pre-line; text-wrap: pretty; }
.q-passage { font: 400 var(--q-size)/1.58 var(--serif); color: var(--ink); white-space: pre-line; text-wrap: pretty; margin: 0 0 12px; }

.opts { margin-top: 16px; display: grid; gap: 8px; }
.opt {
  appearance: none; border: 0; width: 100%; cursor: pointer; text-align: left;
  display: grid; grid-template-columns: 30px minmax(0, 1fr); align-items: center; gap: 16px;
  padding: 13px 18px; border-radius: 10px;
  background: var(--tile); color: var(--ink);
  font: 400 var(--q-opt)/1.4 var(--serif);
  transition: background-color .16s var(--ease), box-shadow .16s var(--ease), opacity .22s var(--ease), transform .12s var(--ease);
}
.opt .l {
  width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
  box-shadow: inset 0 0 0 1.5px var(--ink);
  font: 600 13px var(--font); color: var(--ink);
  transition: background-color .16s var(--ease), box-shadow .16s var(--ease), color .16s var(--ease);
}
@media (hover: hover) and (pointer: fine) { .opts:not(.done) .opt:hover { background: var(--tile-2); } }
.opts:not(.done) .opt:active { transform: scale(.992); }
.opts.done .opt { cursor: default; }
.opts.done .opt:not(.ok):not(.no) { opacity: .42; }
/* picked is the orange edge on paper; right (after Check) is the orange fill: a pick never looks like a verdict */
.opt.picked { background: var(--paper); box-shadow: inset 0 0 0 1.5px var(--amber); }
.opt.ok { background: var(--wash); box-shadow: inset 0 0 0 1.5px var(--amber); }
.opt.ok .l { background: var(--amber); box-shadow: none; color: var(--ink-deep); }
.opt.no .t { text-decoration: line-through; text-decoration-thickness: 1px; color: var(--soft); }
.opt.no .l { box-shadow: inset 0 0 0 1.5px var(--faint); color: var(--soft); }

.grid-in { margin-top: 16px; display: flex; gap: 10px; align-items: stretch; flex-wrap: wrap; }
.grid-in .field { flex: 1 1 220px; height: 56px; font: 400 20px var(--serif); letter-spacing: .02em; }
.grid-in.ok .field { background: var(--wash); box-shadow: inset 0 0 0 1.5px var(--amber); }
.grid-in.no .field { color: var(--soft); text-decoration: line-through; text-decoration-thickness: 1px; }
.grid-key { margin-top: 12px; font: 400 18px var(--serif); }
.grid-key b { font-weight: 600; background: var(--wash); box-shadow: inset 0 0 0 1.5px var(--amber); padding: 2px 10px; border-radius: 8px; }
.preview { margin-top: 8px; min-height: 18px; font: 500 12.5px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }

.verdict { margin-top: 16px; display: grid; gap: 12px; animation: rise .2s var(--ease) both; }
.mistake { display: flex; align-items: baseline; gap: 11px; font: 600 16px/1.4 var(--font); color: var(--ink); }
.mistake::before { content: ""; flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--amber); transform: translateY(-1px); }
.why { font: 400 16px/1.6 var(--serif); color: var(--ink); white-space: pre-line; }
.why-toggle { font: 600 14px var(--font); }

/* ------------------------------------------------------------------ sheets */

/* UI.sheet: <dialog class="sheet" data-size="s|m|l|full"> holding .sheet-head (an h2 and
   the .x), .sheet-body (it scrolls) and .sheet-foot (the actions, the primary last).
   A plain .sheet-in wrapper still works. Under 760px every sheet is a bottom sheet with
   a drag handle, full width, radius 16 16 0 0, at most 90% of the height. */
dialog.sheet {
  border: 0; padding: 0; border-radius: 12px; max-width: min(640px, calc(100vw - 32px));
  width: 100%; max-height: calc(100dvh - 48px); background: var(--float); color: var(--ink);
  box-shadow: var(--shadow-5);
}
dialog.sheet[open] { display: flex; flex-direction: column; }
/* the one blur besides the phone tab bar: the page behind an open sheet */
:is(dialog.sheet, dialog.more)::backdrop { -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); }
dialog.sheet[data-size="s"] { max-width: min(440px, calc(100vw - 32px)); }
dialog.sheet[data-size="l"] { max-width: min(880px, calc(100vw - 32px)); }
dialog.sheet[data-size="full"] { max-width: min(1120px, calc(100vw - 32px)); height: calc(100dvh - 48px); }
/* UI.filterBar's sheet: one column of checks and chips, so 560 (at 640 the topic list left a wide empty side) */
@media (min-width: 760px) { dialog.sheet.filters-sheet { max-width: min(560px, calc(100vw - 32px)); } }
.sheet-in { padding: 16px 20px 20px; }
.sheet-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
dialog.sheet > .sheet-head { flex: none; gap: 12px; margin: 0; padding: 12px 12px 8px 20px; }
dialog.sheet > .sheet-head > :is(h2, h3, .h2) { font: 600 16px/1.3 var(--font); letter-spacing: -.01em; min-width: 0; color: var(--ink-strong); }
.sheet-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; padding: 0 20px 16px; }
.sheet-body > p + p { margin-top: 10px; }
.sheet-foot {
  flex: none; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px;
  padding: 12px 20px; border-top: 1px solid var(--line);
}
.sheet-foot > .grow { margin-right: auto; }
@media (max-width: 759px) {
  dialog.sheet, dialog.sheet[data-size] {
    margin: auto 0 0; width: 100%; max-width: 100%; height: fit-content; max-height: 90dvh;
    border-radius: 16px 16px 0 0; padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  dialog.sheet::before { content: ""; flex: none; align-self: center; width: 36px; height: 4px; margin-top: 8px; border-radius: 2px; background: var(--mat); }
  .sheet-in { padding: 14px 16px 20px; }
  dialog.sheet > .sheet-head { padding: 6px 8px 8px 16px; }
  .sheet-body { padding: 4px 16px 18px; }
  .sheet-foot { padding: 12px 16px; }
  .sheet-foot > :is(.btn, .pill) { flex: 1 1 auto; }
}
.x {
  appearance: none; border: 0; background: none; cursor: pointer; flex: none;
  width: 36px; height: 36px; border-radius: 8px; display: grid; place-items: center; color: var(--soft);
  transition: color .14s var(--ease), background-color .14s var(--ease), transform .25s cubic-bezier(.3, .7, .4, 1);
}
@media (hover: hover) and (pointer: fine) { .x:hover { color: var(--ink); background: var(--well); } }
.x:active { transform: scale(.94); transition-duration: .05s; }

/* the phone's menu sheet (Shell 2.4, U26): <dialog class="more"> with .more-body (Up next,
   "Since you were last here", the Today card, the account header .more-head, .more-group
   of 52px .more-row) and .more-foot, always on screen: equal cells for Review, Words,
   Compete and Quick (22px icon, 11px label, an 11px DM Mono .more-meta). */
dialog.more {
  border: 0; padding: 0; margin: auto 0 0; width: 100%; max-width: 100%; max-height: 85dvh;
  border-radius: 16px 16px 0 0; background: var(--float); color: var(--ink); box-shadow: var(--shadow-5);
  overflow: hidden;
}
dialog.more[open] { display: flex; flex-direction: column; }
dialog.more::backdrop { background: var(--backdrop); }
.more-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; display: grid; align-content: start; gap: 12px; padding: 4px 16px 14px; }
.more-body > .side-day, .more-body > .nextup { background: var(--tile); box-shadow: none; }
.more-head {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; column-gap: 12px; row-gap: 1px;
  padding: 10px 4px; border-top: 1px solid var(--line); color: var(--ink); text-decoration: none;
}
.more-head .avatar { grid-row: 1 / 4; width: 40px; height: 40px; }
.more-group { display: grid; }
.more-group + .more-group { border-top: 1px solid var(--line); padding-top: 6px; }
.more-row {
  appearance: none; border: 0; background: none; cursor: pointer; width: 100%;
  display: flex; align-items: center; gap: 14px; min-height: 52px; padding: 0 4px; border-radius: 10px;
  font: 500 15px var(--font); color: var(--ink); text-decoration: none; text-align: left;
}
.more-row > svg { width: 22px; height: 22px; flex: none; color: var(--soft); }
.more-row > .switch, .more-row > .more-meta, .more-row > .badge { margin-left: auto; }
.more-meta { font: 500 11px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; white-space: nowrap; display: inline-flex; align-items: center; gap: 4px; }
.more-row > .more-meta { font-size: 12.5px; }
.more-foot {
  flex: none; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
  border-top: 1px solid var(--line); padding-bottom: env(safe-area-inset-bottom, 0px); background: var(--float);
}
.more-foot > a, .more-foot > button {
  appearance: none; border: 0; background: none; cursor: pointer; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  height: 64px; padding: 0 4px; font: 500 11px/1.2 var(--font); color: var(--ink); text-decoration: none;
}
.more-foot svg { width: 22px; height: 22px; }
.more-foot [aria-current="page"] { background: var(--wash); font-weight: 600; }

/* a menu (UI.menu): role=menu of role=menuitem | menuitemcheckbox | menuitemradio, a
   non-focusable .menu-head, role=group with a .menu-label, role=separator. Each item:
   an svg, the label (a .menu-sub under it for two lines), then a .kbd or .menu-meta. */
.menu {
  position: fixed; inset: auto; margin: 0; z-index: 60;
  min-width: 220px; max-width: min(320px, calc(100vw - 16px)); max-height: min(72dvh, 600px); overflow-y: auto;
  padding: 6px; border: 0; border-radius: 12px; scrollbar-width: thin;
  background: var(--float); color: var(--ink); box-shadow: var(--shadow-3);
}
.menu.wide { min-width: 300px; }
.menu [role^="menuitem"] {
  appearance: none; border: 0; background: none; cursor: pointer; width: 100%;
  display: flex; align-items: center; gap: 10px; min-height: var(--ctl); padding: 6px 10px; border-radius: 8px;
  font: 500 14px/1.3 var(--font); color: var(--ink); text-align: left; text-decoration: none;
}
.menu [role^="menuitem"] > svg { width: 18px; height: 18px; flex: none; color: var(--soft); }
/* where some items carry an icon, an item without one lines its label up with theirs */
.menu:has([role^="menuitem"] > svg) [role^="menuitem"]:not(:has(> svg)) { padding-left: 38px; }
.menu [role^="menuitem"]:focus-visible { outline-offset: -2px; background: var(--hover); }
.menu [role^="menuitem"][aria-disabled="true"] { color: var(--faint); cursor: default; }
.menu [role^="menuitem"] > .kbd:not(.kbd + .kbd), .menu .menu-meta { margin-left: auto; }
.menu-meta { font: 500 12px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; white-space: nowrap; }
.menu-sub { display: block; margin-top: 1px; font: 400 12.5px/1.35 var(--font); color: var(--soft); }
/* the drawn checkbox's look, on the item's ::after (a radio is round) */
.menu :is([role="menuitemcheckbox"], [role="menuitemradio"])::after {
  content: ""; flex: none; margin-left: auto; width: 18px; height: 18px; border-radius: 5px;
  background: no-repeat center / 0 0; box-shadow: inset 0 0 0 1.5px var(--off-fg);
  transition: background-color .14s var(--ease), background-size .16s var(--ease);
}
.menu [role="menuitemradio"]::after { border-radius: 50%; }
/* a radio list draws only the chosen one's mark; the others keep its 18px slot, so labels stay lined up */
.menu [role="menuitemradio"]:not([aria-checked="true"])::after { box-shadow: none; }
.menu :is([role="menuitemcheckbox"], [role="menuitemradio"])[aria-checked="true"]::after {
  background-color: var(--amber); background-image: var(--check-ink); background-size: 13px 13px; box-shadow: inset 0 0 0 1px var(--pill-edge);
}
.menu-head {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; column-gap: 12px; row-gap: 1px;
  padding: 10px 10px 12px; margin-bottom: 6px; border-bottom: 1px solid var(--line);
}
.menu-head .avatar { grid-row: 1 / 4; width: 40px; height: 40px; }
.menu-label { padding: 10px 10px 4px; font: 500 12.5px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }
.menu [role="separator"], .menu > hr { height: 1px; margin: 6px 4px; border: 0; background: var(--line); }
@media (hover: hover) and (pointer: fine) { .menu [role^="menuitem"]:not([aria-disabled="true"]):hover { background: var(--hover); } }

/* a popover (UI.pop): anchored, or a listbox of role=option (aria-selected is the value;
   .active the keyboard's place). Options: the label, an .opt-meta at the right and an
   .opt-sub line. Under 760px it becomes a bottom sheet unless it is .anchored. */
.popover {
  position: fixed; inset: auto; margin: 0; z-index: 55;
  width: max-content; max-width: min(380px, calc(100vw - 16px)); max-height: min(72dvh, 600px); overflow-y: auto;
  padding: 12px 16px; border: 0; border-radius: 12px; scrollbar-width: thin;
  background: var(--float); color: var(--ink); box-shadow: var(--shadow-3);
  font: 400 14px/1.45 var(--font);
}
.popover::backdrop { background: none; }
.popover.list, .popover[role="listbox"] { padding: 6px; }
.popover-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; font: 600 15px/1.3 var(--font); }
.popover-head .x { width: 32px; height: 32px; margin: -6px -8px -6px 0; }
.popover dl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 6px 14px; }
.popover dt { font: 500 12.5px var(--font); color: var(--soft); }
.popover dd { font: 500 13.5px var(--font); }
[role="option"] {
  display: flex; align-items: center; gap: 10px; min-height: var(--ctl); padding: 7px 10px; border-radius: 8px;
  cursor: pointer; font: 500 14px/1.3 var(--font); color: var(--ink);
}
[role="option"] > svg { width: 18px; height: 18px; flex: none; color: var(--soft); }
[role="option"][aria-selected="true"] { background: var(--wash); font-weight: 600; }
/* the chosen value's check: a drawn mark at the row's end that never wraps (listboxes are one line per option) */
[role="listbox"] [role="option"][aria-selected="true"]::after {
  content: ""; flex: none; align-self: center; width: 14px; height: 14px; margin-left: auto; background: var(--ink);
  -webkit-mask: var(--check-mask) center / contain no-repeat; mask: var(--check-mask) center / contain no-repeat;
}
[role="listbox"] [role="option"]:has(.opt-meta)[aria-selected="true"]::after { margin-left: 8px; }
[role="option"].active, [role="option"]:focus-visible { outline: 2px solid var(--amber-deep); outline-offset: -2px; }
[role="option"][aria-disabled="true"] { color: var(--faint); cursor: default; }
.opt-main { min-width: 0; flex: 1 1 auto; }
.opt-meta { margin-left: auto; font: 500 12px var(--font); color: var(--soft); font-variant-numeric: tabular-nums; white-space: nowrap; }
.opt-sub { display: block; margin-top: 2px; font: 400 12.5px/1.35 var(--font); color: var(--soft); }
@media (hover: hover) and (pointer: fine) { [role="option"]:not([aria-selected="true"]):not([aria-disabled="true"]):hover { background: var(--hover); } }
@media (max-width: 759px) {
  .popover:not(.anchored) {
    left: 0 !important; right: 0 !important; top: auto !important; bottom: 0 !important;
    width: 100%; max-width: none; max-height: 85dvh;
    padding: 22px 16px calc(16px + env(safe-area-inset-bottom, 0px)); border-radius: 16px 16px 0 0;
  }
  .popover:not(.anchored)::before {
    content: ""; position: absolute; top: 8px; left: 50%; width: 36px; height: 4px; margin-left: -18px; border-radius: 2px; background: var(--mat);
  }
}

/* a tooltip (UI.tooltip): role=tooltip, on hover and on focus, never the only way to a fact. A dark chip with
   white text in both themes; inside it --ink and --soft are redefined, so marked-up content stays readable */
.tip {
  position: fixed; inset: auto; margin: 0; z-index: 65; pointer-events: none;
  max-width: min(280px, calc(100vw - 16px)); padding: 6px 10px; border: 0; border-radius: 6px;
  --ink: var(--tip-fg); --soft: var(--tip-soft); --line: rgb(255 255 255 / .16);
  background: var(--tip-bg); color: var(--tip-fg); font: 500 12.5px/1.4 var(--font);
  box-shadow: var(--shadow-2);
}
.tip .m, .tip .meta { font: 500 12px var(--font); font-variant-numeric: tabular-nums; color: var(--tip-soft); }
.tip .kbd { margin-left: 4px; background: rgb(255 255 255 / .14); box-shadow: none; color: inherit; }

/* kit.js puts the toasts in the top layer (popover="manual"), so they show over an open
   sheet: undo the UA's [popover] box first, and let the .toasts rules below place it */
.toasts:where([popover]) { top: auto; right: auto; margin: 0; padding: 0; border: 0; background: none; color: inherit; overflow: visible; height: auto; }
/* toasts (UI.toast): <div class="toasts" aria-live="polite"> holding up to 3 .toast
   (.toast-t text, an .act button such as Undo, the .x). Bottom-left on desktop, clear of
   the sidebar; bottom-centre above the tabs on a phone. */
.toasts {
  position: fixed; z-index: 70; left: 20px; bottom: 20px; width: min(420px, calc(100vw - 40px));
  pointer-events: none;
}
body.has-side .toasts { left: calc(var(--side-w) + 20px); }
body.has-rail .toasts { left: calc(var(--rail-w) + 20px); }
.toast {
  pointer-events: auto; display: flex; align-items: center; gap: 10px;
  min-height: 44px; padding: 6px 6px 6px 14px; border-radius: 12px;
  background: var(--float); color: var(--ink); font: 500 14px/1.4 var(--font); box-shadow: var(--shadow-4);
}
.toast > svg, .toast > .glyph { width: 22px; height: 22px; flex: none; color: inherit; }
.toast-t { flex: 1 1 auto; min-width: 0; }
.toast .act {
  appearance: none; border: 0; background: none; cursor: pointer; flex: none; color: inherit; text-decoration: underline;
  padding: 8px 10px; border-radius: 8px; font: 600 14px var(--font);
  text-decoration-color: var(--amber); text-decoration-thickness: 2px; text-underline-offset: 4px;
}
.toast .act .kbd { margin-left: 6px; background: none; color: inherit; box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 40%, transparent); }
.toast .x { width: 32px; height: 32px; color: var(--soft); }
@media (hover: hover) and (pointer: fine) {
  .toast .act:hover, .toast .x:hover { background: color-mix(in srgb, currentColor 14%, transparent); }
}
@media (max-width: 759px) {
  .toasts, body.has-side .toasts, body.has-rail .toasts {
    left: 16px; right: 16px; width: auto; align-items: center;
    bottom: calc(64px + 16px + env(safe-area-inset-bottom, 0px));
  }
  .toast { width: 100%; }
}

/* a banner (UI.banner) at the top of main: --wash for a notice, .sys --tile for the
   system's own (offline, storage blocked); at most one shows */
.banner {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
  margin: 0 0 12px; padding: 8px 8px 8px 16px; border-radius: 12px;
  background: var(--wash); color: var(--ink); font: 500 14.5px/1.45 var(--font);
  animation: rise .24s var(--ease) both;
}
.banner.sys { background: var(--raised); box-shadow: var(--edge); }
/* at most one banner: a page's own (.banner without data-kind, e.g. Plan's) wins, the shell's notice waits */
main:has(.banner:not([data-kind], [hidden])) .banner[data-kind]:not(.sys) { display: none; }
.banner > svg { width: 20px; height: 20px; flex: none; }
.banner-t { flex: 1 1 220px; min-width: 0; }   /* 220: beside its icon on a 390px phone, never a line of its own */
.banner-act { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* a quiet action beside a button keeps its tap area to itself (Dismiss's -12px margin ran under Sign in) */
.banner-act .quiet { margin: 0; padding: 6px 8px; }
.banner > .x { width: 34px; height: 34px; margin: -4px -2px -4px 0; }

/* the command palette (UI.palette): <dialog class="palette"> with .palette-q (an svg and the
   input), .palette-list of .palette-group labels and role=option rows (a glyph, the
   label, .opt-meta, a .kbd), .palette-sub for a row's Tab actions, .palette-empty and
   .palette-foot. 640px, 12vh from the top; on a phone full screen, the input pinned above
   the keyboard (the page sets --kb from visualViewport). */
dialog.palette {
  border: 0; padding: 0; margin: 12vh auto auto; width: min(640px, calc(100vw - 32px)); max-width: none;
  max-height: 76vh; border-radius: 12px; overflow: hidden;
  background: var(--float); color: var(--ink); box-shadow: var(--shadow-5);
}
dialog.palette[open] { display: flex; flex-direction: column; animation: pop .16s var(--ease) both; }
.palette-q { flex: none; display: flex; align-items: center; gap: 10px; height: 48px; padding: 0 12px 0 16px; border-bottom: 1px solid var(--line); }
.palette-q > svg { width: 18px; height: 18px; flex: none; color: var(--soft); }
.palette-q input { flex: 1 1 auto; min-width: 0; height: 100%; border: 0; background: none; outline: none; font: 400 16px var(--font); color: var(--ink); }
.palette-q input::placeholder { color: var(--faint); }
.palette-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; padding: 6px; }
.palette-group { padding: 10px 10px 4px; font: 500 12.5px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }
.palette [role="option"] { min-height: var(--ctl); }
.palette [role="option"] > .glyph { color: var(--ink); }
.palette [role="option"][aria-selected="true"] { background: var(--wash); font-weight: 500; }
.palette [role="option"][aria-selected="true"]::after { content: none; }
/* the letters that matched: 600, underlined in amber */
.palette [role="option"] b { font-weight: 600; text-decoration: underline 1.5px var(--amber); text-underline-offset: 3px; }
.palette [role="option"] .mbar { width: 48px; flex: none; }
.palette-sub { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 10px 10px 42px; }
.palette-empty { padding: 28px 18px; font: 400 14.5px/1.5 var(--font); color: var(--soft); }
.palette-foot { flex: none; display: flex; flex-wrap: wrap; gap: 4px 16px; padding: 8px 16px; border-top: 1px solid var(--line); font: 500 11.5px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }
@media (max-width: 759px) {
  dialog.palette { margin: 0; width: 100%; height: 100dvh; max-height: 100dvh; border-radius: 0; padding-bottom: var(--kb, 0px); }
  .palette-q { order: 2; border-bottom: 0; border-top: 1px solid var(--line); }
  .palette-foot { display: none; }
}
.ref { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px 16px; }
.ref figure { display: flex; align-items: center; gap: 10px; }
.ref svg { flex: none; color: var(--ink); }
.ref figcaption { font: 400 15px/1.45 var(--serif); }
.ref figcaption small { display: block; font: 500 11.5px var(--font); font-variant-numeric: tabular-nums; color: var(--soft); }
.ref-facts { margin-top: 16px; font: 400 15px/1.6 var(--serif); }

/* the calculator floats, and can be dragged anywhere on the page */
.calc {
  position: fixed; z-index: 40; right: 20px; top: 84px;
  width: min(420px, calc(100vw - 24px)); height: min(520px, calc(100vh - 110px));
  display: flex; flex-direction: column;
  background: var(--float); border-radius: 12px; overflow: hidden;
  box-shadow: var(--shadow-5);
}
.calc-bar {
  display: flex; align-items: center; justify-content: space-between;
  height: 42px; padding: 0 6px 0 14px; cursor: grab; touch-action: none;
  background: var(--tile); font: 500 12.5px var(--font); font-variant-numeric: tabular-nums; color: var(--soft);
}
.calc-bar:active { cursor: grabbing; }
.calc-host { flex: 1; min-height: 0; }
.calc-note { padding: 18px; color: var(--soft); font-size: 14px; }

/* ------------------------------------------------------------------ kit pieces (kit.js)

   Markup kit.js makes that has no group above. */

/* a focused sheet, popover or listbox shows its focus on the item inside it, not the box */
dialog.sheet:focus-visible, .popover:focus-visible, .menu:focus-visible { outline: none; }

/* the checkbox list of a multi dropdown and the filters sheet: domain rows are tri-state,
   their skills indented under them; Select all and Clear at the foot */
.checks { display: grid; min-width: min(280px, calc(100vw - 48px)); }
.checks > .check { min-height: 36px; }
.checks > .check.sub { padding-left: 28px; }
.checks > .check .n { margin-left: auto; padding-left: 12px; }
.checks-foot { display: flex; gap: 20px; margin-top: 8px; padding: 10px 12px 2px; border-top: 1px solid var(--line); }
.dd-pop { padding: 10px 12px; }
.filters-search { flex: 1 1 200px; max-width: 320px; }
.filters-sec + .filters-sec { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line); }
.date-form { display: grid; gap: 6px; min-width: 220px; }
.date-form .pill { margin-top: 8px; justify-self: start; }
.date-range { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px; }
.exam-dates { max-width: min(460px, calc(100vw - 16px)); }
.table .empty-cell { padding: 22px 12px; color: var(--soft); text-align: left; }

/* a skill row (UI.skillRow): the glyph and name, the mastery bar with its word and accuracy
   (R37), then the open-mistake dots and Practise. In Today's points card (.gain) it adds the
   figure, a 0-100 track (an ink tick at your accuracy, an amber tick at 80%) and two meta lines. */
.skill-row {
  /* the side is a share of the row, not its content, so the bars of a list of rows line up */
  display: grid; grid-template-columns: minmax(150px, 1.2fr) minmax(160px, 1.4fr) minmax(max-content, 1fr); align-items: center; gap: 6px 18px;
  padding: 12px 0; border-top: 1px solid var(--line);
}
.skill-name { display: inline-flex; align-items: center; gap: 10px; min-width: 0; font: 600 14.5px/1.3 var(--font); color: var(--ink); text-decoration: none; }
.skill-name .glyph { width: 20px; height: 20px; color: var(--ink); }
@media (hover: hover) and (pointer: fine) { a.skill-name:hover span { text-decoration: underline; text-decoration-color: var(--amber); text-underline-offset: 3px; } }
.skill-m { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: baseline; gap: 6px 10px; }
.skill-m .mbar { grid-column: 1 / -1; }
.skill-word { font: 600 13px var(--font); }
.skill-side { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px 12px; }
.skill-open { display: inline-flex; align-items: center; gap: 6px; }
/* every column but the two fluid ones is fixed, so the bars start at the same x on every row (the side column was `auto`, so its content moved them) */
.skill-row.gain { grid-template-columns: minmax(170px, 1fr) 84px minmax(130px, 1.1fr) 96px; min-height: 60px; }
.skill-play { width: 34px; height: 34px; min-height: 0; padding: 0; border-radius: 8px; flex: none; }
.skill-play svg { width: 16px; height: 16px; }
/* one line per name and the side on one line, so every row is the same height and its bars sit at the same y */
.skill-row.gain .skill-name > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.skill-row.gain .skill-side { flex-wrap: nowrap; }
.skill-row.gain .skill-m { grid-column: 1; }
.skill-gain { grid-column: 2; grid-row: 1 / span 2; font: 600 18px var(--font); font-variant-numeric: tabular-nums; white-space: nowrap; }
.skill-gain.small { font: 500 13px var(--font); color: var(--soft); }
.skill-m .meta { white-space: nowrap; }
.skill-track-wrap { grid-column: 3; display: grid; gap: 5px; }
.skill-track { position: relative; height: 4px; border-radius: 2px; background: var(--track); }
.skill-track > i { position: absolute; top: -4px; bottom: -4px; left: calc(var(--t, 0) * 100%); width: 2px; margin-left: -1px; border-radius: 1px; background: var(--ink); }
.skill-track > i.goal { background: var(--amber); }
.skill-track-wrap .meta, .skill-meta, .skill-tiers { font-size: 11px; }
.skill-row.gain .skill-meta, .skill-row.gain .skill-tiers { grid-column: 3; }
.skill-row.gain .skill-side { grid-column: 4; grid-row: 1 / span 3; }
@media (max-width: 759px) {
  .skill-row, .skill-row.gain { grid-template-columns: minmax(0, 1fr) auto; }
  .skill-row > * { grid-column: 1 / -1 !important; grid-row: auto !important; }
  .skill-row.gain > .skill-name { grid-column: 1 !important; }
  .skill-row.gain > .skill-gain { grid-column: 2 !important; grid-row: 1 !important; }
  .skill-row .skill-side { justify-content: flex-start; }
}
/* the same one-column row inside a narrow right column (.twocol-side is a size container) */
.twocol-side { container-type: inline-size; }
@container (max-width: 520px) {
  .skill-row, .skill-row.gain { grid-template-columns: minmax(0, 1fr) auto; }
  .skill-row > * { grid-column: 1 / -1 !important; grid-row: auto !important; }
  .skill-row.gain > .skill-name { grid-column: 1 !important; }
  .skill-row.gain > .skill-gain { grid-column: 2 !important; grid-row: 1 !important; }
  .skill-row .skill-side { justify-content: flex-start; }
}

/* a countdown in boxes (UI.countdown {boxes}): DD HH MM SS, each labelled */
.countdown { display: flex; gap: 10px; }
.countdown > span { display: grid; justify-items: center; gap: 4px; min-width: 52px; padding: 8px 6px; border-radius: var(--radius); background: var(--well); }
.countdown .num { font: 500 28px/1 var(--mono); letter-spacing: -.02em; }

/* a number's working (UI.fact): the value, How it's worked out, Your numbers, Left out,
   What moves it, and where else the same number shows */
.fact-pop { width: min(380px, calc(100vw - 16px)); }
.fact-v { font: 500 24px/1.1 var(--font); font-variant-numeric: tabular-nums; margin-bottom: 4px; }
.fact-sec { margin-top: 14px; display: grid; gap: 5px; justify-items: start; }
.fact-sec ul { display: grid; gap: 3px; font-size: 13.5px; }
.fact-sec li::before { content: "\00B7  "; color: var(--soft); }
.fact-same { margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--line); }

/* the living mark as an avatar (U22): the four quadrants fill orange with each domain's mastery */
.avatar.mark { background: none; }
.avatar > svg.mark-live { width: 100%; height: 100%; display: block; }
/* a check-in due swatch for legends: the atlas's small clock */
.sw[data-due]:not([data-status]) { background: var(--paper); border-radius: 50%; box-shadow: inset 0 0 0 1.25px var(--ink); }
.sw[data-due]:not([data-status])::after { content: ""; width: 1px; height: 5px; margin-top: -3px; background: var(--ink); }

/* ------------------------------------------------------------------ kit pieces, polish round (2026-10-19)
   UI.why (.why-q, .why-pop), UI.saving ([data-saving]), UI.meter ticks (.meter.ticks), UI.roll (.roll),
   the heatmap readout (.heat-readout), the sliding marks of UI.seg (.seg-ind) and UI.tabs (.tab-ind).
   Every rule here wins on specificity, so the block can sit apart from the components it extends. */

/* 1. UI.why: the one "?" that explains (a word with .word). 20px, 8px corners, never round */
.why-q {
  appearance: none; border: 0; padding: 0 5px; margin: 0 0 0 4px; position: relative; flex: none;
  display: inline-grid; place-items: center; min-width: 20px; height: 20px; border-radius: 8px;
  background: none; box-shadow: inset 0 0 0 1px var(--line); cursor: pointer; vertical-align: 2px;
  font: 600 12.5px/1 var(--font); letter-spacing: 0; text-transform: none; color: var(--soft);
  transition: background-color .16s var(--ease), color .16s var(--ease);
}
.why-q.word {
  padding: 0 4px; box-shadow: none; font-weight: 500;
  text-decoration: underline dotted var(--faint); text-underline-offset: 3px;
}
.why-q[aria-expanded="true"] { background: var(--well); color: var(--ink); }
@media (hover: hover) and (pointer: fine) { .why-q:hover { background: var(--well); color: var(--ink); } }
.why-pop { max-width: 320px; }
.why-pop p { margin: 0; font: 400 14.5px/1.5 var(--font); color: var(--ink); text-wrap: pretty; }
.why-pop > * + * { margin-top: 8px; }
.why-pop .meta { color: var(--soft); }
/* in the touch block (~2485): a centred 44px hit area, no layout shift */
@media (max-width: 759px), (pointer: coarse) {
  .why-q::before { content: ""; position: absolute; left: 50%; top: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); }
}

/* 2. UI.saving: the label swaps in place (busy, done with a check, fail); the width holds */
[data-saving] > .saving-l, [data-saving] > svg { animation: saving-in .16s var(--ease); }
[data-saving] > svg { width: 16px; height: 16px; flex: none; }
@keyframes saving-in { from { opacity: 0; transform: translateY(3px); } }

/* 3. UI.meter({ticks: n}): the bar as n blocks, the done ones amber (graphite with .ink) */
.meter.ticks .meter-bar { display: flex; gap: 3px; background: none; }
.meter.ticks .meter-bar > i {
  flex: 1 1 0; min-width: 0; width: auto; border-radius: 2px; background: var(--track);
  transition: background-color .16s var(--ease);
}
.meter.ticks .meter-bar > i[data-on] { background: var(--amber); }
.meter.ink.ticks .meter-bar > i[data-on] { background: var(--ink); }
/* tickStates (a state per block, data-s): done lit as above, partial half lit, today outlined, missed a faint
   mark, off empty (an outline at the track's tone) */
.meter.ticks .meter-bar > i[data-s="partial"] { background: linear-gradient(90deg, var(--amber) 50%, var(--track) 50%); }
.meter.ink.ticks .meter-bar > i[data-s="partial"] { background: linear-gradient(90deg, var(--ink) 50%, var(--track) 50%); }
.meter.ticks .meter-bar > i[data-s="today"] { background: none; box-shadow: inset 0 0 0 1px var(--amber-deep); }
.meter.ink.ticks .meter-bar > i[data-s="today"] { box-shadow: inset 0 0 0 1px var(--ink); }
.meter.ticks .meter-bar > i[data-s="missed"] { background: var(--faint); opacity: .55; }
.meter.ticks .meter-bar > i[data-s="off"] { background: none; box-shadow: inset 0 0 0 1px var(--track); }

/* 4. UI.roll: the moving copy sits over the real text (kept, transparent, for screen readers).
   One 1.15em column per changed digit with an edge mask; tabular figures so nothing jumps */
.roll-w { position: relative; display: inline-block; font-variant-numeric: tabular-nums; font-kerning: none; }
.roll-w.rolling > .roll-t { opacity: 0; }
.roll {
  position: absolute;   /* kit.js checks this: without it UI.roll only sets the text. Keep it */
  left: 0; top: 50%; transform: translateY(-50%);
  display: flex; align-items: flex-start; white-space: pre; pointer-events: none;
}
.roll > span, .roll-s > span { display: block; height: 1.15em; line-height: 1.15em; }
.roll-c { overflow: hidden; mask-image: linear-gradient(transparent, #000 18%, #000 82%, transparent); }
.roll-s { display: block; }
/* in the print block (~2533) */
@media print {
  .roll { display: none; }
  .roll-w.rolling > .roll-t { opacity: 1; }
}

/* 5. heatmap readout: one fixed line under the grid (hovered or focused day, else the latest
   active day). It names the weekday, so the M / W / F labels go to screen readers only
   (INSPO-VISUAL row 14); their 12px column shrinks to 0 */
.heat-readout {
  min-height: 18px; margin-top: 8px; font: 500 12.5px/18px var(--font); font-variant-numeric: tabular-nums;
  color: var(--soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.heatmap:has(.heat-readout) .heat { grid-template-columns: 0 repeat(var(--weeks, 26), minmax(0, var(--hc))); }
.heatmap:has(.heat-readout) .heat-d { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
@media (max-width: 759px) {
  .heat-readout { min-height: 36px; white-space: normal; }
  .heatmap:has(.heat-readout) .heat { grid-template-columns: 0 repeat(var(--weeks, 20), minmax(0, 1fr)); max-width: calc(var(--weeks, 20) * 17px); }
}

/* 6. UI.seg: one raised white thumb (no amber ring) slides under the options (kit.js .seg-ind).
   [data-ind] is set only while the options sit on one line; otherwise the static mark shows */
.seg:has(> .seg-ind) { position: relative; isolation: isolate; }
.seg:has(> .seg-ind) > :not(.seg-ind) { z-index: 1; }
.seg-ind {
  --ind: 1;   /* kit.js reads this sentinel: without it there is no slider. Keep it */
  position: absolute; left: 0; top: 0; z-index: 0; display: none; pointer-events: none;
  border-radius: 8px; background: var(--thumb, var(--face)); box-shadow: var(--seg-thumb);
  transition: transform .2s var(--ease), width .2s var(--ease), height .2s var(--ease);
}
.seg[data-ind] > .seg-ind { display: block; }
.seg[data-ind] > [aria-checked="true"] { background: none; box-shadow: none; }

/* 7. UI.tabs: one amber 2px underline slides between tabs (kit.js .tab-ind), inset 12px like the
   static ::after it replaces while the tabs sit on one line */
.tablist:has(> .tab-ind) { position: relative; }
.tab-ind {
  --ind: 1;   /* kit.js reads this sentinel: without it there is no slider. Keep it */
  position: absolute; left: 0; bottom: 0; height: 2px; display: none; pointer-events: none;
  transition: transform .2s var(--ease), width .2s var(--ease);
}
.tab-ind::after { content: ""; position: absolute; left: 12px; right: 12px; bottom: 0; height: 2px; border-radius: 2px; background: var(--amber); box-shadow: 0 1px 2px rgb(140 70 0 / .25); }
.tablist[data-ind] > .tab-ind { display: block; }
.tablist[data-ind] [role="tab"][aria-selected="true"]::after { visibility: hidden; }

/* ------------------------------------------------------------------ motion */

@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes pin  { from { transform: scale(0); } 60% { transform: scale(1.35); } to { transform: scale(1); } }
@keyframes out  { to { transform: scale(0); opacity: 0; } }
@keyframes grow { from { transform: scaleX(0); } to { transform: none; } }
@keyframes fade { from { opacity: 0; } }
@keyframes pop  { from { opacity: 0; transform: translateY(4px) scale(.98); } }
@keyframes sheet-in { from { opacity: 0; transform: translateY(12px); } }
@keyframes sheet-up { from { transform: translateY(100%); } }
@keyframes side-in  { from { transform: translateX(-24px); opacity: 0; } }
/* .rise, .rise-2, .rise-3 are kept as classes but no longer animate: nothing moves on load for its own sake */

/* page changes: main fades (out 100, in 200: motion A2 below); the chrome is pinned above it, the rest swaps at once (G9).
   Names go on the positioned chrome itself (.bar, .pbar, the tabs, .side), never on a static
   wrapper: a name makes a stacking context, and a static #bar would then paint under main. */
@view-transition { navigation: auto; }
main  { view-transition-name: main; }
.bar  { view-transition-name: bar; }
.pbar { view-transition-name: pbar; }
#tabs { view-transition-name: tabs; }
.side { view-transition-name: side; }
::view-transition-group(root), ::view-transition-old(root), ::view-transition-new(root),
::view-transition-group(bar),  ::view-transition-old(bar),  ::view-transition-new(bar),
::view-transition-group(pbar), ::view-transition-old(pbar), ::view-transition-new(pbar),
::view-transition-group(tabs), ::view-transition-old(tabs), ::view-transition-new(tabs),
::view-transition-group(side), ::view-transition-old(side), ::view-transition-new(side) { animation: none; }

@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
:root[data-motion="reduce"] *, :root[data-motion="reduce"] *::before, :root[data-motion="reduce"] *::after {
  animation-duration: .01ms !important; transition-duration: .01ms !important;
}
:root[data-motion="reduce"]::view-transition-group(*),
:root[data-motion="reduce"]::view-transition-old(*),
:root[data-motion="reduce"]::view-transition-new(*) { animation: none !important; }

/* ------------------------------------------------------------------ motion (premium A2, 2026-10-19)
   From premium/build/motion.css (MOTION.md, BUILD rules 6 and 7). It sits after the old motion block and
   before touch targets and print, so it comes after every rule it overrides; the old rules each part names
   are gone. kit.js and ui.js read these tokens (and fall back to the same numbers). New names: m-*, data-m-*,
   nav-mark, tab-mark, focus-*, has-focus. Order: 1 tokens, 2 reduced, 3 the test window, 4 view transitions,
   5 surfaces, 6 toast stack, 7 folds, 8 meters, 9 keycaps, 10 clip indicator, section chips, goal ring,
   11 focus chrome, 12 the reduced net (the old block above stays until the literal durations are tokens). */

/* ------------------------------------------------------------------ 1. tokens (MOTION 2)
   Exits run at about 75% of their entry. A1 may define the same speeds and springs (BUILD rule 11):
   these are MOTION.md's exact values, so one copy can go. */
:root {
  --m-press: 100ms;  --m-fast: 140ms;   --m-base: 200ms;   --m-slow: 260ms;
  --m-reveal: 480ms; --m-moment: 600ms; --m-tip: 400ms;    --m-step: 30ms;
  --m-pop: 160ms;    --m-pop-out: 120ms;                  /* popover, menu (S3, S4) */
  --m-sheet: 260ms;  --m-sheet-out: 195ms;                /* desktop sheet (S1) */
  --m-drawer: 500ms; --m-drawer-out: 375ms;               /* phone sheet (S2, Vaul) */
  --m-toast: 400ms;  --m-toast-out: 300ms;                /* toast stack (S6, Sonner) */
  --m-fold: 260ms;   --m-fold-out: 195ms;                 /* details.fold, card collapse */
  --m-roll: 600ms;                                        /* UI.roll (NumberFlow's no-overshoot roll, app pace) */
  --m-meter: 240ms;                                       /* a meter's fill on change (D3) */
  --m-press-in: 50ms; --m-release: 250ms;                 /* C1 press (Comeau), A1's values: .05s and .25s --ease-release */

  --m-rise: 8px; --m-nudge: 4px;                          /* block entry; small entry and every exit */
  --m-pop-from: .96; --m-sheet-from: .98; --m-press-s: .97; --m-press-l: .985;

  --ease-move:    cubic-bezier(.77, 0, .175, 1);          /* on screen, A to B (nav mark, FLIP, rail) */
  --ease-quad:    cubic-bezier(.25, .46, .45, .94);
  --ease-drawer:  cubic-bezier(.32, .72, 0, 1);           /* phone sheets (Vaul) */
  --ease-release: cubic-bezier(.3, .7, .4, 1);            /* a press letting go (Comeau) */

  /* the three springs: each is right only at its own duration (MOTION 2). Without linear() they are --ease */
  --spring: var(--ease);      --spring-d: 370ms;          /* indicators, phone sheet, folds, roll */
  --spring-soft: var(--ease); --spring-soft-d: 420ms;     /* press release, seg thumb, tab underline */
  --spring-pop: var(--ease);  --spring-pop-d: 570ms;      /* rewards only */
}
@supports (animation-timing-function: linear(0, 1)) {
  :root {
    --spring: linear(0, 0.004 1.3%, 0.02 2.8%, 0.048 4.5%, 0.085 6.3%, 0.16 9.3%, 0.45 19.8%, 0.54 23.5%, 0.615 27%, 0.681 30.5%, 0.737 34%, 0.787 37.8%, 0.829 41.5%, 0.871 46.3%, 0.905 51.2%, 0.932 56.8%, 0.954 63%, 0.981 76.8%, 1);
    --spring-soft: linear(0, 0.006 1.5%, 0.026 3.3%, 0.057 5%, 0.103 7%, 0.201 10.5%, 0.525 21%, 0.62 24.5%, 0.704 28%, 0.771 31.3%, 0.827 34.5%, 0.877 38%, 0.917 41.5%, 0.951 45.5%, 0.979 50%, 0.998 55%, 1.009 60.5%, 1.015 73.8%, 1);
    --spring-pop: linear(0, 0.008 1.5%, 0.032 3%, 0.067 4.5%, 0.12 6.3%, 0.232 9.3%, 0.504 15.8%, 0.613 18.5%, 0.721 21.5%, 0.806 24.3%, 0.879 27%, 0.937 29.8%, 0.987 32.8%, 1.023 35.8%, 1.058 41%, 1.068 47.3%, 1.06 52.5%, 1.021 67.3%, 1.004 76.3%, 0.996 89%, 1);
  }
}

/* ------------------------------------------------------------------ 2. reduced motion
   The OS setting or the in-app switch: no travel, no scale, springs become --ease, every speed at
   most 120ms. Fades and fills that explain a state stay (kit.js plays its entries and exits as 120ms
   fades). */
@media (prefers-reduced-motion: reduce) {
  :root {
    --m-rise: 0px; --m-nudge: 0px; --m-pop-from: 1; --m-sheet-from: 1; --m-press-s: 1; --m-press-l: 1;
    --spring: var(--ease); --spring-soft: var(--ease); --spring-pop: var(--ease);
    --m-base: 120ms; --m-slow: 120ms; --m-reveal: 120ms; --m-moment: 120ms; --m-pop: 120ms; --m-sheet: 120ms;
    --m-drawer: 120ms; --m-drawer-out: 120ms; --m-toast: 120ms; --m-toast-out: 120ms; --m-fold: 120ms; --m-fold-out: 120ms;
    --m-roll: 120ms; --m-meter: 120ms; --m-release: 120ms; --spring-d: 120ms; --spring-soft-d: 120ms; --spring-pop-d: 120ms;
  }
}
:root[data-motion="reduce"] {
  --m-rise: 0px; --m-nudge: 0px; --m-pop-from: 1; --m-sheet-from: 1; --m-press-s: 1; --m-press-l: 1;
  --spring: var(--ease); --spring-soft: var(--ease); --spring-pop: var(--ease);
  --m-base: 120ms; --m-slow: 120ms; --m-reveal: 120ms; --m-moment: 120ms; --m-pop: 120ms; --m-sheet: 120ms;
  --m-drawer: 120ms; --m-drawer-out: 120ms; --m-toast: 120ms; --m-toast-out: 120ms; --m-fold: 120ms; --m-fold-out: 120ms;
  --m-roll: 120ms; --m-meter: 120ms; --m-release: 120ms; --spring-d: 120ms; --spring-soft-d: 120ms; --spring-pop-d: 120ms;
}

/* ------------------------------------------------------------------ 3. the test window (MOTION 3.9)
   Bluebook moves nothing new: every token is 0 under /test/ (kit.js also stops there, UI.motion.calm).
   The navigator popover keeps a 120ms fade in and goes at once, as Bluebook's does. */
body[data-page="test"] {
  --m-press: 0ms; --m-fast: 0ms; --m-base: 0ms; --m-slow: 0ms; --m-reveal: 0ms; --m-moment: 0ms; --m-pop: 0ms; --m-pop-out: 0ms;
  --m-sheet: 0ms; --m-sheet-out: 0ms; --m-drawer: 0ms; --m-drawer-out: 0ms; --m-toast: 0ms; --m-toast-out: 0ms;
  --m-fold: 0ms; --m-fold-out: 0ms; --m-roll: 0ms; --m-meter: 0ms; --m-press-in: 0ms; --m-release: 0ms;
  --m-rise: 0px; --m-nudge: 0px; --m-pop-from: 1; --m-sheet-from: 1;
}
body[data-page="test"] .popover { animation: fade 120ms var(--ease); }

/* ------------------------------------------------------------------ 4. view transitions (MOTION 3.1)
   Replaces ui.css's "::view-transition-group(main), -old(main), -new(main) { animation-duration: 120ms }".
   The overlay never catches a click (it froze the page for 120ms); main leaves in 100 and arrives in
   200 (the page entry, kit.js, gives the arrival its rise). */
::view-transition { pointer-events: none; }
::view-transition-group(main) { animation-duration: var(--m-base); }
::view-transition-old(main) { animation-duration: 100ms; }
::view-transition-new(main) { animation-duration: var(--m-base); }

/* N2: the current nav item's wash lives on one .nav-mark span inside it (ui.js navItem), named, so a page
   change slides and resizes it from the old item to the new (no side stripe: A1 took it out). In flight the
   light wash multiplies over the labels so they stay readable; dark's wash is see-through already. The phone
   tabs' top bar does the same as .tab-mark. Replaces the fill of ".side-item[aria-current="page"]". */
.side-item:has(> .nav-mark) { background: none; isolation: isolate; }
.side-item > .nav-mark {
  position: absolute; inset: 0; z-index: -1; border-radius: inherit;
  background: var(--wash); pointer-events: none; view-transition-name: nav-mark;
}
::view-transition-group(nav-mark) { mix-blend-mode: multiply; }
:root[data-theme="dark"]::view-transition-group(nav-mark) { mix-blend-mode: normal; }
.tab-mark { display: none; }
@media (max-width: 759px) {
  .tabs a > .tab-mark {
    display: block; position: absolute; top: -6px; left: 50%; width: 18px; height: 2px; margin-left: -9px;
    border-radius: 2px; background: var(--amber); pointer-events: none; view-transition-name: tab-mark;
  }
  .tabs a:has(> .tab-mark)::before { content: none; }
}
::view-transition-group(nav-mark), ::view-transition-group(tab-mark) {
  animation-duration: var(--m-slow); animation-timing-function: var(--ease-move);
}

/* B2: the rail collapse and expand (ui.js toggle, html.vt-rail for the run). The side and main groups
   are re-enabled for this run only (ui.css pins them with animation: none for page changes): the side
   morphs 248 to 68 and back while its labels fade (old 100 out, new 160 in); main's new picture is drawn
   at its final size from the left edge and clipped, so the content slides with the edge and never
   stretches. Width and padding never animate. */
html.vt-rail::view-transition-group(side), html.vt-rail::view-transition-group(main) {
  animation: revert; animation-duration: var(--m-slow); animation-timing-function: var(--ease-move); overflow: clip;
}
html.vt-rail::view-transition-old(side), html.vt-rail::view-transition-new(side) {
  animation: revert; height: 100%; width: auto; object-fit: none; object-position: left top;
}
html.vt-rail::view-transition-old(side) { animation-duration: 100ms; }
html.vt-rail::view-transition-new(side) { animation-duration: 160ms; }
html.vt-rail::view-transition-old(main) { animation: none; opacity: 0; }
html.vt-rail::view-transition-new(main) { animation: none; height: 100%; width: auto; object-fit: none; object-position: left top; }
html.vt-rail::view-transition-group(nav-mark) { animation-duration: var(--m-slow); }

/* B1: the Now card folds to one line (ui.js paintSide, html.vt-fold): the card morphs, Today leaves in
   140, the nav slides up; names m-nextup, m-day, m-nav exist only for this run */
html.vt-fold::view-transition-group(m-nextup), html.vt-fold::view-transition-group(m-nav),
html.vt-fold::view-transition-group(m-day), html.vt-fold::view-transition-group(nav-mark) {
  animation-duration: var(--m-slow); animation-timing-function: var(--ease-move);
}
html.vt-fold::view-transition-old(m-day) { animation-duration: var(--m-fast); }
html.vt-fold::view-transition-old(m-nextup), html.vt-fold::view-transition-new(m-nextup) { height: 100%; object-fit: none; object-position: left top; }
html.vt-fold::view-transition-group(m-nextup) { overflow: clip; }

/* ------------------------------------------------------------------ 5. surfaces (MOTION 3.5)
   Popovers, menus and tooltips: kit.js plays their entry (scale .96 from the trigger side, which
   place() writes as transform-origin; a phone popover rises from the bottom edge) and their exit
   (120, toward the trigger) with the Web Animations API, so the old keyframes go.
   Replaces: ".menu { animation: pop .15s }", ".popover { animation: pop .15s }", the phone
   ".popover:not(.anchored) { animation: sheet-up .24s }" and ".tip { animation: fade .12s }". */
.popover, .menu, .tip { animation: none; }
@media (max-width: 759px) { .popover:not(.anchored) { animation: none; } }
/* a menu pick shows on its row while the menu fades (DISCOVER H2) */
.menu [data-m-picked] { background: var(--wash); }

/* sheets (UI.sheet, the phone menu sheet): enter 260 from 8px below at .98, leave in 195 fading 4px down;
   the backdrop fades with them. The exit is a transition on display and overlay, so the closed dialog
   stays in the top layer while it leaves (Chromium, Safari 26; elsewhere it goes at once) and kit.js
   removes the node after. A key closes at once ([data-m-instant]).
   Replaces: "dialog.sheet[open] { animation: sheet-in .2s }", "dialog.sheet[open]::backdrop { animation:
   fade .2s }", the phone "dialog.sheet[open] { animation-name: sheet-up }" and
   "dialog.more[open] { animation: sheet-up .26s }". */
dialog.sheet, dialog.more {
  opacity: 0; transform: translateY(var(--m-nudge));
  transition: opacity var(--m-sheet-out) var(--ease), transform var(--m-sheet-out) var(--ease),
    overlay var(--m-sheet-out) allow-discrete, display var(--m-sheet-out) allow-discrete;
}
dialog.sheet[open], dialog.more[open] {
  animation: none; opacity: 1; transform: translateY(var(--m-dy, 0px)); transition-duration: var(--m-sheet);
}
@starting-style {
  dialog.sheet[open], dialog.more[open] { opacity: 0; transform: translateY(var(--m-rise)) scale(var(--m-sheet-from)); }
}
dialog.sheet::backdrop, dialog.more::backdrop {
  opacity: 0;
  transition: opacity var(--m-fast) var(--ease), overlay var(--m-sheet-out) allow-discrete, display var(--m-sheet-out) allow-discrete;
}
dialog.sheet[open]::backdrop, dialog.more[open]::backdrop { animation: none; opacity: var(--m-bd, 1); transition-duration: var(--m-base); }
@starting-style { dialog.sheet[open]::backdrop, dialog.more[open]::backdrop { opacity: 0; } }
dialog:is(.sheet, .more):is([data-m-instant], [data-m-drag]), dialog:is(.sheet, .more):is([data-m-instant], [data-m-drag])::backdrop { transition: none; }

/* S2 phone: the sheet rises from the bottom edge on Vaul's curve (500) and drops back from wherever the
   finger left it (375). While dragged it follows --m-dy 1:1 and the backdrop thins with --m-bd (kit.js) */
@media (max-width: 759px) {
  dialog.sheet, dialog.more {
    opacity: 1; transform: translateY(100%);
    transition-timing-function: var(--ease-drawer); transition-duration: var(--m-drawer-out);
  }
  dialog.sheet[open], dialog.more[open] { animation: none; transform: translateY(var(--m-dy, 0px)); transition-duration: var(--m-drawer); }
  @starting-style { dialog.sheet[open], dialog.more[open] { opacity: 1; transform: translateY(100%); } }
  dialog.sheet[open]::backdrop, dialog.more[open]::backdrop { transition-duration: var(--m-drawer); transition-timing-function: var(--ease-drawer); }
}

/* ------------------------------------------------------------------ 6. toast stack (S6, Sonner; DISCOVER-BROWSE 5)
   The newest toast is in front and in the flow; each older one sits 14px higher at 5% smaller with its
   content hidden; hover or focus fans them out 14px apart (kit.js sets --m-y and --m-s, data-m-i,
   data-m-back, and [data-m-open] on .toasts) and holds every timer. A toast enters from below (400,
   ease) and the front one leaves downward (300). Replaces: ".toasts { display: flex; flex-direction:
   column; gap: 8px }" (the display and gap) and ".toast { animation: rise .24s }". */
.toasts { display: block; }
.toast {
  position: absolute; left: 0; right: 0; bottom: 0; animation: none;
  transform: translateY(var(--m-y, 0px)) scale(var(--m-s, 1)); transform-origin: 50% 0;
  transition: transform var(--m-toast) ease, opacity var(--m-toast) ease;
}
.toast[data-m-i="0"] { position: relative; }
.toast > * { transition: opacity var(--m-toast) ease; }
.toasts:not([data-m-open]) .toast[data-m-back] > * { opacity: 0; }
/* the fanned stack keeps its hover across the 14px gaps */
.toasts[data-m-open] .toast[data-m-back]::before { content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 14px; }
/* an Undo toast's timer, running out as a 2px line (kit.js plays and holds it) */
.toast > .m-cd {
  position: absolute; left: 12px; right: 12px; bottom: 0; height: 2px; border-radius: 2px;
  background: color-mix(in srgb, var(--ink) 30%, transparent); transform-origin: left; pointer-events: none;   /* graphite: Undo's underline is the toast's one orange */
}

/* ------------------------------------------------------------------ 7. folds (SUGGESTIONS "Folds open smoothly")
   details.fold opens in height and fade, closes in 195, where CSS can size to auto (interpolate-size,
   ::details-content: Chromium 131+). Elsewhere ui.js animates the height and this still fades. A key
   opens it at once. The card collapse (.card[data-collapsed]) is ui.js's: it grows and its content
   rises 4px; its close is instant. */
details.fold { interpolate-size: allow-keywords; }
details.fold::details-content {
  block-size: 0; opacity: 0; overflow-y: clip;
  transition: block-size var(--m-fold-out) var(--ease), opacity var(--m-fold-out) var(--ease), content-visibility var(--m-fold-out) allow-discrete;
}
details.fold[open]::details-content { block-size: auto; opacity: 1; transition-duration: var(--m-fold); }
:root[data-input="key"] details.fold::details-content { transition: none; }

/* ------------------------------------------------------------------ 8. meters (D3)
   The fill is clipped to --p instead of sized, so a change slides it (240) with crisp rounded ends and
   no reflow; the first paint has its value already, so nothing moves on load. New ticks light left to
   right 20ms apart (kit.js sets their delays). Replaces ".meter-bar > i { width: calc(...);
   transition: width .2s }" for the plain bar (ticks keep their own blocks). */
.meter:not(.ticks) > .meter-bar > i {
  width: 100%;
  clip-path: inset(0 calc((1 - min(var(--p, 0), 1)) * 100%) 0 0 round 100px);   /* round ends at any height (4, 6, 8px) */
  transition: clip-path var(--m-meter) var(--ease);
}
.meter.ticks > .meter-bar > i { transition: background-color var(--m-meter) var(--ease); }

/* ------------------------------------------------------------------ 9. keycaps flash when their shortcut fires (ui.js keys) */
@keyframes m-kflash { from { background: var(--wash-2); color: var(--ink); transform: scale(.92); } }
:is(.kbd, kbd).m-kflash { animation: m-kflash 220ms var(--ease); }

/* ------------------------------------------------------------------ 10. the clip indicator (UI.seg / UI.tabs {mark: 'clip'})
   Paco's exclusion tabs: a copy of the row in the active colours, clipped to the picked item; the clip
   slides (300 ease), so the text turns dark exactly under the moving edge. Opt-in; the sliding thumb
   and underline stay the default. */
[data-m-clip] { position: relative; }
[data-m-clip] > :is(.seg-ind, .tab-ind) { visibility: hidden; }
.m-clip {
  position: absolute; left: 0; top: 0; height: 100%; z-index: 2; margin: 0; border: 0; overflow: hidden;
  background: none; box-shadow: none; pointer-events: none; transition: clip-path 300ms ease;
}
.m-clip > * { background: var(--amber) !important; color: var(--ink-deep) !important; box-shadow: none !important; }   /* a copy: it must wear the active look */
.m-clip > *::after { visibility: hidden; }
:not([data-ind]) > .m-clip { display: none; }

/* the section bar's chips wait until the page's header has scrolled away, then fade in as the bar sticks
   (ui.js paintBarNext sets [data-m-wait]; UI.shell({ sectionsAlways: true }) opts out). Waiting, it is
   clear and takes no room, but keeps its landmark and its chips stay in the Tab order: one that gets focus
   brings the bar out. Merge: this transition replaces ".sections { transition: box-shadow .2s }". */
.sections { transition: box-shadow var(--m-base) var(--ease), opacity var(--m-base) var(--ease); }
.sections[data-m-wait] { opacity: 0; pointer-events: none; }

/* the sidebar's Today card says the goal as a small ring beside "Today", minutes inside (ui.js todayCard,
   compact; A1's hand-off). ".side-day .ring { display: none }" keeps hiding the rail's own ring there. */
.side-day-goal { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.side-day-head .ring.side-goal {
  display: inline-grid; --rs: 30px; --ring-w: 3.5px; padding: 0; border: 0; background: none; cursor: pointer;
  font: 600 11px var(--font); color: var(--ink-strong, var(--ink)); text-decoration: none;
}

/* ------------------------------------------------------------------ 11. the focus chrome (UI.shell({ page, chrome: 'focus' }))
   Sign in, Welcome and Pair: a slim brand bar instead of the sidebar, rail, tabs and Now strip. The
   mark and wordmark go home; Help and the account menu sit at the right. body.has-focus lets the page
   centre its card. The bar's line shows only while content slides under it (.scrolled). */
body.has-focus #bar {
  position: sticky; top: 0; z-index: 30; background: var(--page);
  padding-top: env(safe-area-inset-top, 0px); transition: box-shadow var(--m-fast) var(--ease);
}
body.has-focus #bar.scrolled { box-shadow: 0 1px 0 var(--line); }
body.has-focus #tabs { display: none; }
.focus-bar { display: flex; align-items: center; gap: 6px; height: 56px; max-width: var(--max); margin: 0 auto; padding: 0 var(--gutter); }
.focus-brand {
  display: inline-flex; align-items: center; gap: 10px; min-height: 40px; padding-right: 6px; border-radius: 8px;
  color: var(--ink); text-decoration: none; font: 600 15px/1.1 var(--font); letter-spacing: -.01em;
}
.focus-brand svg { width: 26px; height: 26px; flex: none; display: block; }
.focus-grow { flex: 1 1 auto; }
.focus-user { width: auto; min-height: 40px; padding: 3px 6px 3px 3px; grid-template-columns: auto auto; column-gap: 6px; }
.focus-user :is(.side-name, .side-sub) { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.focus-user > svg { grid-column: 2; transform: rotate(90deg); }
@media (max-width: 759px) { .focus-word { display: none; } .focus-bar { height: 52px; } body.has-focus .page { padding-bottom: 32px; } }   /* no tab bar to clear */

/* ------------------------------------------------------------------ 12. the reduced net (MOTION 4.2 step 8): read before merging
   ui.css's reduced block sets "transition-duration: .01ms !important" on everything. That makes every
   CSS transition above instant under reduced motion (sheets, folds, the toast stack, meters), while
   kit.js still plays its entries and exits as 120ms fades. Keep it until ui.css's literal durations
   (.12s, .16s, .2s...) are tokens; then narrow it to animations only, so the 120ms tokens in section 2
   govern transitions, by replacing both of its rules with:

   @media (prefers-reduced-motion: reduce) {
     *, *::before, *::after, ::backdrop { animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
   }
   :root[data-motion="reduce"] *, :root[data-motion="reduce"] *::before, :root[data-motion="reduce"] *::after,
   :root[data-motion="reduce"] ::backdrop { animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
*/

/* ------------------------------------------------------------------ touch targets (rule 9)
   On a phone or a coarse pointer every control is at least --touch (44px) each way. Controls
   with room grow to it; small chrome gets a centred 44px hit area with no layout shift. Dense
   grids (UI.strip, UI.atlas, UI.heatmap) sit closer than 44px, so UI.nearTap gives their gaps,
   and a 14px band above and below, to the nearest cell. */
@media (max-width: 759px), (pointer: coarse) {
  .quiet { min-height: var(--touch); }
  .seg:where([role]) > button, .seg:where([role]) > label { min-height: var(--touch); }
  .ibtn { width: max(var(--ib, 36px), var(--touch)); height: max(var(--ib, 36px), var(--touch)); }
  .pbar-mark { width: var(--touch); height: var(--touch); margin-left: -9px; }
  .choices button { min-width: var(--touch); height: var(--touch); }
  .legend li > button, .legend > button { min-height: var(--touch); padding-block: 0; margin-block: -12px; }
  .nextup-go :is(.pill, .btn) { height: var(--touch); }
  .table th:has(> button) { padding-block: 0; }
  .table th button { min-width: var(--touch); min-height: var(--touch); }
  .table.hover tbody td { height: var(--touch); }
  .sb-hits { inset: calc((30px - var(--touch)) / 2) 0; }
  input[type="range"] { height: var(--touch); }
  :root { --cell: var(--touch); }
  .cell.sm { --cell: var(--touch); }
  .cal:not(.plan) .cal-grid, .cal.compact .cal-grid, .cal.plan.compact .cal-grid { --c: var(--touch); grid-template-columns: repeat(7, minmax(0, var(--c))); gap: 2px; }
  .near-tap:is(.strip, .atlas) { padding-block: 14px; margin-block: -14px; }
  .pbar-chip, .pbar-me, .brand, .streak, .bar-end > .avatar, .side-mark { position: relative; }
  :is(.pbar-chip, .pbar-me, .brand, .streak, .bar-end > .avatar, .side-mark)::before {
    content: ""; position: absolute; left: 50%; top: 50%; width: max(100%, 44px); height: max(100%, 44px); transform: translate(-50%, -50%);
  }
}

/* ------------------------------------------------------------------ print */

@page { margin: 14mm 12mm; }                     /* fits A4 and Letter */
@media print {
  /* chrome, overlays, controls and every button: paper carries content only */
  .bar, .nav, .tabs, .side, .rail, .pbar, .scrim, .skip, .more, .palette,
  .menu, .popover, .tip, .toast, .toasts, .banner, .calc, dialog,
  .seg, .switch, .chips, .counter, .otp, input[type="range"],
  .sections, .now-strip, .nextup, .filters, .selbar, .tabs-grip, .tabs-hint, .card-fold, .thumbs-add,
  button:not(.print-keep), .pill, .btn, .ibtn, .quiet, .x, .no-print { display: none !important; }

  body, body.has-side, body.has-rail { background: none; padding: 0 !important; }
  main, .page { max-width: none !important; margin: 0 !important; padding: 0 !important; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
  /* the right column, hover-revealed parts and scroll fades print in full */
  .twocol { display: block; }
  .twocol-side { position: static; max-height: none; overflow: visible; padding: 0; margin: 12px 0 0; }
  .reveal { opacity: 1 !important; }
  .fade-s, .fade-e { -webkit-mask-image: none !important; mask-image: none !important; }

  /* a card keeps its edge on paper (a line, not its shadow); a wide table prints whole */
  .card, .stat { border: 1px solid var(--line); }
  .card, .stat, .panel, .nextup, .side-day { box-shadow: none !important; background-image: none !important; }
  .card[data-collapsed] > :not(.card-sum) { display: revert !important; }
  .card > .card-sum { display: none !important; }
  .table-wrap { overflow: visible !important; -webkit-mask-image: none !important; mask-image: none !important; }
  .table :is(.stick, .stick-2) { position: static; }
  [data-fact] { text-decoration: none; }
  [data-fact]::after { content: none; }
  .counting { color: inherit; }
  .counting::after { content: none; }

  /* a state is its fill: print it even with background graphics off */
  [data-status], [data-status] *, [data-tier], .mbar, .mbar > i, .dot, .dots i, mark,
  .sw, .pips > i, .dbars, .meter-bar, .meter-bar > i, .heat-c, .heat-scale > i, .ring, .badge,
  .chart svg *, .cal-d, .cal-d *, .cal-sw, .sb-seg, .sb-sw, .spread-bar, .spread-bar > i, .scale-track, .scale-track > i, .scale-track b {
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }

  h1, h2, h3, h4, .card-head { break-after: avoid; }
  tr, li, figure, svg, img, .keep { break-inside: avoid; }
  thead { display: table-header-group; }
  p { orphans: 3; widows: 3; }
  .page-break { break-before: page; }
}

/* radicals and powers in the line (question.js richNodes): a stretchy radical sign with a bar over its radicand, a raised exponent */
.rad { display: inline-flex; align-items: stretch; vertical-align: middle; white-space: nowrap; margin: 0 .04em; }
.rad > .ri { align-self: flex-start; font-size: .62em; line-height: 1; margin-right: -.18em; padding-top: .1em; }
.rad > .rs { flex: none; width: .72em; margin-right: -.04em; background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 20' preserveAspectRatio='none'%3E%3Cpath d='M0.4 11.4 L2 10.4 L4.9 19 L9.9 0.9' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E") no-repeat center / 100% 100%;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 20' preserveAspectRatio='none'%3E%3Cpath d='M0.4 11.4 L2 10.4 L4.9 19 L9.9 0.9' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E") no-repeat center / 100% 100%; }
.rad > .rr { border-top: max(1px, .065em) solid currentColor; padding: .06em .14em 0 .06em; }
.sp { font-size: .72em; vertical-align: .55em; line-height: 0; }
.sp .frac { font-size: .92em; }
.sp.spf { vertical-align: .95em; }

/* fold (ui.js): extra detail behind a small toggle, closed until tapped */
details.fold { min-width: 0; }
.fold-s { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; list-style: none; cursor: pointer;
  font: 500 12.5px/1.4 var(--font); color: var(--soft); border-radius: 6px; padding: 2px 4px 2px 0; }
.fold-s::-webkit-details-marker { display: none; }
.fold-s::before { content: ""; flex: none; width: 5px; height: 5px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg); margin: 0 3px 0 2px; transition: transform .15s var(--ease); }
details.fold[open] > .fold-s::before { transform: rotate(45deg); }
@media (hover: hover) and (pointer: fine) { .fold-s:hover { color: var(--ink); } }
@media (prefers-reduced-motion: reduce) { .fold-s::before { transition: none; } }

/* ------------------------------------------------------------------ premium A1 pieces (2026-10-19) */

/* a split button: one shape, the main action and its menu.
   <div class="split-btn"><button class="pill">Start</button><button class="pill" aria-haspopup="menu"
   aria-expanded="false" aria-label="More ways to start">(chevron svg)</button></div>
   Parts may be .pill, .pill.sm, .btn or .btn.sm. The parts share one edge (the divider); the chevron turns while open */
.split-btn { display: inline-flex; align-items: stretch; vertical-align: middle; isolation: isolate; }
.split-btn > :not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.split-btn > :not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: -1px; }
.split-btn > [aria-haspopup]:not(:first-child) { min-width: 36px; padding: 0 9px; }
.split-btn > :focus-visible { z-index: 1; }
.split-btn > [aria-haspopup] > svg { transition: transform .16s var(--ease); }
/* a drawn caret (g.ph-turn) turns itself (icons block 4); only an older chevron needs the turn here */
.split-btn > [aria-haspopup][aria-expanded="true"] > svg:not(:has(> g.ph-turn)) { transform: rotate(180deg); }

/* a way forward (Start, Continue, Next): .go on a .pill, .btn or .quiet nudges its trailing arrow 3px on hover.
   Never an arrow in a circle */
:is(.pill, .btn, .quiet).go > svg:last-child { transition: transform .15s cubic-bezier(.215, .61, .355, 1); }
@media (hover: hover) and (pointer: fine) { :is(.pill, .btn, .quiet).go:not(:disabled):hover > svg:last-child { transform: translateX(3px); } }

/* the test window stays flat (BUILD, Not doing): no sheen, no shadow on its buttons; presses still answer */
.rn-exam .pill { background-image: none; border-color: transparent; box-shadow: none; }
.rn-exam .pill:not(:disabled):active { box-shadow: none; }
.rn-exam .btn:not([aria-pressed="true"]):not([aria-expanded="true"]):not(:disabled) { box-shadow: var(--edge-ctl); }

/* ------------------------------------------------------------------ premium A1 (2026-10-19)
   Surfaces and controls, per premium/BUILD.md rules 1-11 and 13. What changed:
   - tokens: --page (warm canvas) under --raised cards and --float floats, --face (button face), --ink-strong,
     --ink-2, --fs-hero, the amber ramp --amber-100..700, warm --shadow-1..5 behind a see-through --edge
     (--shadow-float and --shadow-pop now alias 3 and 5), --shadow-scroll, --hero-wash, --edge-ctl,
     --shadow-btn(-down), --shadow-field, --halo, --hover, --sel-bg/--sel-edge, --off-bg/--off-fg, --pill-*,
     --seg-thumb, --tip-*, --check-mask and --check-ink; a dark set where each level is lighter with a top highlight
   - spacing: --card-pad 20 (16 phone), --card-gap 32 (20 phone), .page top 32 (20 phone), .stats gap 16
   - surfaces: body, sidebar and sticky bars on --page; .card, .stat, .panel, the Now card white and raised;
     .card.hero, .card.go, a.card, button.card, .card.line flat; .ledge opt-in; wells follow --surface
   - type: headings, card and sheet titles, .stat-v in --ink-strong; .hero-num at --fs-hero
   - controls: the orange .pill recipe (sheen, lit edge, contact shadow, brighter hover, 1px press, busy spinner
     after 300ms at the same width, .done check, solid grey disabled); .btn white face; .quiet amber underline;
     .ibtn ghost with a press; chips as small keys; .split-btn; .go arrow nudge; the drawn checkbox (every native
     one), menu checkboxes and the switch knob check; fields white with a halo on focus; switch knob stretch;
     range well and thumb; .seg recessed track and raised thumb; tab underline; .spin
   - states: focus is a thin amber-deep ring (links: an amber-deep underline on a wash, no box); one selected look;
     disabled solid; a warm hover band (tables, menus, options, legends); your row without a side stripe;
     the nav's current item without its stripe; ::selection amber, mark as a highlighter, amber caret, matched
     letters underlined in the palette; tooltips as dark chips; thin warm scrollbars; scroll shadows on
     .bar, .pbar, .sections.stuck and any .scrolled; backdrop blur under sheets and the phone tab bar;
     no tap flash and no text selection on controls
   - data with weight: the gauge's groove as wide as its band with a lip, the band's contact shadow, a knob needle;
     .ch-area and .ch-fade for the amber fade under a trend line (charts.js draws the path); zone hairlines;
     6px meters and a 6px ring track (--ring-w); inner shadows on meter and scale tracks
   - the test window (.rn-exam) keeps flat buttons */

/* runner.css's split (the Twin verb in a practice question): its caret pill keeps the pill's depth (runner's
   divider rule would flatten it), and the two pill borders merge into one divider */
.split.primary > .pill.split-caret { box-shadow: var(--pill-shadow); margin-left: -1px; }
/* .quiet's wash sits on its text only (content-box); these radii round that inner box by 4px */
.quiet:focus-visible { border-radius: 8px / 13px; }

/* AP item figures ([image: path] in question.js): many are transparent PNGs drawn in black, so they get a white
   backing that keeps them readable in the dark theme, and never overflow a phone screen */
.q-img { display: block; max-width: 100%; height: auto; margin: 8px 0; background: #fff; border-radius: 4px; padding: 4px; }
