@import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,700;12..96,800&family=Space+Grotesk:wght@400;500;700&display=swap');

/*
 * app.css — one set of design tokens and the shared components.
 * specs/2026-09-16-app-design-pass.md §19–§21 (P3).
 *
 * Every page links this file before its own <style>, so the tokens are in place
 * at first paint and a page's own rules still win a tie. Nothing here is private:
 * styles only, no book text, no peg word, no encoding.
 *
 * Colour: one palette, dark by default, light under prefers-color-scheme. Each
 * status colour comes in two: a FILL (a background big enough to carry dark or
 * white text) and a TEXT shade (readable on the page and on that status's tint).
 * Every pair is measured at >= 4.5:1 (>= 3:1 for large text) by
 * system/tests/test_app_design.py against the composited background.
 */

:root {
  /* ---- colour: dark (default) ---- */
  /* Sticker, dark: only the CHROME inverts. The tangerine is the same tangerine in both
     themes -- a sticker is the same sticker in any light. specs/2026-09-17-sticker-part-1-tokens.md */
  --bg:#16140f; --surface:#201d16; --surface-2:#2a261d; --raise:#352f24;
  --ink:#f3ecdc; --ink-dim:#d9d1c0; --muted:#a89e8a; --line:rgba(243,236,220,.15);
  --accent:#ff5a1f; --accent-ink:#14120f; --accent-text:#ff8c52;
  /* The answer word inside the card. Cobalt on cream, lifted to periwinkle on a dark
     panel where cobalt falls under 4.5:1. */
  --answer:#8fa6ff;
  --good:#2ddb8b; --good-ink:#14120f; --good-text:#5fe4a8; --good-tint:color-mix(in oklab,var(--good) 18%, var(--surface));
  --slow:#ffd52e; --slow-ink:#14120f; --slow-text:#ffd52e; --slow-tint:color-mix(in oklab,var(--slow) 18%, var(--surface));
  --bad:#ff4d6a; --bad-ink:#14120f; --bad-text:#ff8a9c; --bad-tint:color-mix(in oklab,var(--bad) 18%, var(--surface));
  --bad-fill:#e02a48;                       /* a destructive button under white text */
  --bad-fill-ink:#fff;
  --shadow:0 18px 50px -18px rgba(0,0,0,.7);
  --shadow-1:0 2px 6px -2px rgba(0,0,0,.5);
  --shadow-2:0 10px 26px -12px rgba(0,0,0,.6);

  /* ---- type ---- */
  /* Two chosen faces, each with a full system fallback: the app renders immediately in the
     fallback and re-renders when the webfont lands, so it is never blocked on the network. */
  --numeral:"Bricolage Grotesque",-apple-system,BlinkMacSystemFont,system-ui,"Segoe UI",sans-serif;
  --display:"Bricolage Grotesque",-apple-system,BlinkMacSystemFont,system-ui,"Segoe UI",sans-serif;
  --ui:"Space Grotesk",-apple-system,BlinkMacSystemFont,"SF Pro Text",system-ui,"Segoe UI",sans-serif;
  --read:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
  --fs-12:12px; --fs-14:14px; --fs-16:16px; --fs-18:18px; --fs-20:20px;
  --fs-24:24px; --fs-30:30px; --fs-36:36px; --fs-48:48px;
  --track-caps:.12em;                       /* positive tracking for all-caps labels */

  /* ---- space, corners, targets ---- */
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px; --s-5:24px; --s-6:32px; --s-7:48px; --s-8:64px;
  --r-1:8px; --r-2:12px; --r-3:16px; --r:20px; --r-pill:999px;
  --tap:44px;                               /* the touch minimum, used by every control */

  /* Sticker's form language, part 2: a hard outline and a flat offset shadow. No blur
     anywhere -- that is the whole difference between this and a conventional card.
     specs/2026-09-17-sticker-part-2-form.md */
  --ol-w:3px; --ol:var(--ink);
  --glyph:42px;                             /* the Home tiles' icon block */
  --pop:5px 5px 0 var(--ink);
  --pop-sm:4px 4px 0 var(--ink);

  /* The calendar's ramp (Progress, §17): ONE hue in five steps, never a rainbow and never
     green = good / red = bad. Step 0 is an empty outline, so the ends differ by shape too. */
  --heat-1:color-mix(in oklab, var(--accent) 30%, var(--surface-2));
  --heat-2:color-mix(in oklab, var(--accent) 55%, var(--surface-2));
  --heat-3:color-mix(in oklab, var(--accent) 78%, var(--surface-2));
  --heat-4:var(--accent);
}

@media (prefers-color-scheme: light) {
  :root {
    /* Sticker, light: cream ground, ink outline, the same tangerine. */
    --bg:#f3ecdc; --surface:#f9f5ea; --surface-2:#efe7d4; --raise:#e8dfc9;
    --ink:#14120f; --ink-dim:#3a352c; --muted:#6b6254; --line:rgba(20,18,15,.20);
    --accent:#ff5a1f; --accent-ink:#14120f; --accent-text:#b83616;
    --answer:#2b4fd8;
    --good:#2ddb8b; --good-ink:#14120f; --good-text:#0d6d40;
    --slow:#ffd52e; --slow-ink:#14120f; --slow-text:#6f4a00;
    --bad:#e02a48; --bad-ink:#fff; --bad-text:#a8112e;
    --bad-fill:#c51f3d; --bad-fill-ink:#fff;
    --shadow:0 14px 40px -18px rgba(20,24,40,.35);
    --shadow-1:0 1px 3px rgba(20,24,40,.12);
    --shadow-2:0 10px 26px -14px rgba(20,24,40,.30);
  }
}

/* ------------------------------------------------------------------ reset -- */

* { box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
[hidden] { display:none !important; }
html, body { margin:0; }
:focus-visible { outline:2.5px solid var(--accent); outline-offset:3px; border-radius:6px; }

/* Never a tap target under the minimum: every control declares it once, here. */
button, [role="button"], [role="menuitem"] { min-height:var(--tap); }

/* ------------------------------------------------------- header and nav -- */

.top { padding:calc(14px + env(safe-area-inset-top)) 2px 10px; }
.app-nav { display:none; }
header.top[data-title] { display:flex; align-items:center; gap:var(--s-2); }
.app-lead { display:flex; align-items:center; gap:var(--s-1); margin-right:auto; min-width:0; }
.app-back { flex:none; display:inline-flex; align-items:center; gap:var(--s-1); min-height:var(--tap); min-width:var(--tap); margin-block:-6px;
  padding:0 var(--s-2) 0 2px; color:var(--muted); text-decoration:none; font:600 var(--fs-14) var(--ui); white-space:nowrap;
  -webkit-tap-highlight-color:transparent; }
.app-back span { font-size:var(--fs-20); line-height:1; margin-top:-2px; }
.app-back:active { color:var(--ink); }
.app-title { margin:0; min-width:0; font:700 var(--fs-18)/1.2 var(--ui); letter-spacing:-.2px; color:var(--ink);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.app-crumb, .app-crumb-sep { display:none; }
.app-gear { appearance:none; flex:none; width:var(--tap); height:var(--tap); margin-block:-6px; border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface); color:var(--ink); font-size:var(--fs-18); line-height:1; cursor:pointer; }
a.app-chip { flex:none; display:inline-flex; flex-direction:column; align-items:center; justify-content:center; min-height:var(--tap);
  padding:0 14px; border-radius:var(--r-pill); text-decoration:none; background:var(--ink); border:2px solid var(--ol);
  color:var(--bg); font:800 var(--fs-14) var(--display); font-variant-numeric:tabular-nums; white-space:nowrap; }
a.app-chip small { display:block; color:var(--bg); font-weight:600; font-size:var(--fs-12); line-height:1.1; }
a.app-chip.cold { background:var(--surface); color:var(--ink); }

@media (min-width: 900px) {
  .app-nav { display:flex; position:fixed; top:0; left:0; right:0; z-index:40; height:56px; align-items:stretch; gap:var(--s-1);
    padding:0 max(20px, calc(50vw - 560px)); border-bottom:1px solid var(--line);
    background:color-mix(in oklab, var(--bg) 92%, transparent); -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px);
    font:500 var(--fs-14)/1 var(--ui); }
  .app-nav > a { position:relative; display:flex; align-items:center; padding:0 var(--s-3); color:var(--muted); text-decoration:none; white-space:nowrap; }
  .app-nav > a:hover { color:var(--ink); }
  .app-nav > a[aria-current="page"] { color:var(--ink); font-weight:700; }
  .app-nav-bar { position:absolute; left:var(--s-3); right:var(--s-3); bottom:0; height:3px; border-radius:3px 3px 0 0; background:var(--accent); }
  .app-nav-end { margin-left:auto; display:flex; align-items:center; gap:var(--s-2); }
  .app-nav-end a.app-chip { min-height:40px; }
  .app-nav-end .app-gear { margin:0; }
  body.has-nav .app, body.has-nav .wrap { padding-top:56px; }
  header.top[data-title] > .app-gear, .app-back { display:none; }
  .app-crumb { display:inline; color:var(--muted); font-weight:600; text-decoration:none; }
  .app-crumb:hover { color:var(--ink); text-decoration:underline; }
  .app-crumb-sep { display:inline; color:var(--muted); margin:0 var(--s-2); font-weight:400; }
}

/* ------------------------------------------------- sync status (§15, §18) -- */
/* A status line, not a decorative pill: it says what happened, it can be tapped
   to sync now, and its mark differs by SHAPE as well as colour, so the state
   survives greyscale and colour blindness. */

/* The 44 px target is bought with a negative margin, like the gear's, so the header
   stays the height it was (test_app_home_nav guards it). */
.app-sync { appearance:none; display:inline-flex; align-items:center; gap:7px; min-height:var(--tap); max-width:100%;
  margin-block:-6px; padding:0 var(--s-3); border:1px solid var(--line); border-radius:var(--r-pill);
  background:var(--surface); color:var(--ink-dim); font:600 var(--fs-12) var(--ui); cursor:pointer; white-space:nowrap; }
.app-sync .mark { flex:none; width:11px; height:11px; border:2px solid var(--muted); background:transparent; }
.app-sync .lab { overflow:hidden; text-overflow:ellipsis; }
.app-sync .full { display:none; }
/* ok — a filled disc */
.app-sync[data-state="ok"] .mark { border-radius:50%; border-color:var(--good-text); background:var(--good-text); }
/* syncing — an open ring */
.app-sync[data-state="syncing"] .mark { border-radius:50%; border-color:var(--accent-text); background:transparent; animation:app-pulse 1s infinite; }
/* on this device / retrying — a half-filled disc */
.app-sync[data-state="local"] .mark, .app-sync[data-state="conflict"] .mark, .app-sync[data-state="unsaved"] .mark {
  border-radius:50%; border-color:var(--slow-text); background:linear-gradient(to right, var(--slow-text) 50%, transparent 50%); }
/* offline — a square */
.app-sync[data-state="offline"] .mark { border-radius:2px; border-color:var(--muted); background:transparent; }
/* no key — a dashed square */
.app-sync[data-state="nokey"] .mark { border-radius:2px; border-style:dashed; border-color:var(--muted); background:transparent; }
/* refused key — a triangle */
.app-sync[data-state="badkey"] .mark { width:0; height:0; border:0; border-left:6px solid transparent; border-right:6px solid transparent;
  border-bottom:11px solid var(--bad-text); background:transparent; }
@keyframes app-pulse { 50% { opacity:.3; } }
@media (prefers-reduced-motion: reduce) { .app-sync[data-state="syncing"] .mark { animation:none; } }
@media (min-width: 560px) { .app-sync .full { display:inline; } .app-sync .short { display:none; } }

/* ------------------------------------------------------ the key and Settings -- */

.app-keyhelp { color:var(--muted); font-size:var(--fs-12); line-height:1.5; }
.app-keyhelp p { margin:0 0 var(--s-1); }
.app-keyhelp ul { margin:0; padding-left:18px; }
.app-keyhelp li { margin:2px 0; }
.app-keyhelp code { font-family:var(--numeral); font-size:var(--fs-12); color:var(--ink-dim); }
.app-keyform { display:flex; flex-direction:column; gap:10px; }
.app-keyform label, .app-settings label { color:var(--muted); font-size:var(--fs-12); letter-spacing:var(--track-caps); text-transform:uppercase; }
.app-keyform .kf-row, .app-settings .kf-row { display:flex; gap:var(--s-2); }
.app-keyform input, .app-settings input { flex:1; min-width:0; min-height:var(--tap); font:var(--fs-14) var(--numeral);
  background:var(--surface-2); color:var(--ink); border:1px solid var(--line); border-radius:var(--r-2); padding:12px 14px; }
.app-keyform button { appearance:none; border:0; border-radius:var(--r-2); padding:0 18px; min-height:var(--tap); font:700 var(--fs-14) var(--ui);
  background:var(--accent); color:var(--accent-ink); cursor:pointer; }
.app-keyform .kf-msg { margin:0; color:var(--ink-dim); font-size:var(--fs-14); line-height:1.5; }
.app-settings { border:1px solid var(--line); background:var(--surface); color:var(--ink); border-radius:18px; padding:0;
  width:min(460px, 94vw); max-height:92dvh; box-shadow:var(--shadow); font-family:var(--ui); }
.app-settings::backdrop { background:rgba(0,0,0,.5); }
.as-sheet { padding:var(--s-5) 20px; display:flex; flex-direction:column; gap:var(--s-3); }
.as-sheet h2 { margin:0; font-size:var(--fs-18); }
.as-sheet h3 { margin:0; font-size:var(--fs-14); }
.app-settings button { appearance:none; cursor:pointer; min-height:var(--tap); border-radius:var(--r-2); font:700 var(--fs-14) var(--ui); padding:0 14px; }
.as-primary { border:0; background:var(--accent); color:var(--accent-ink); }
.as-small, .as-ghost { border:1px solid var(--line); background:var(--surface-2); color:var(--ink); }
.as-small { font-weight:600 !important; font-size:var(--fs-12) !important; }
.as-danger { border:0; background:var(--bad-fill); color:var(--bad-fill-ink); }
.as-row { display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap; }
.as-status { flex:1; min-width:0; color:var(--ink-dim); font-size:var(--fs-12); }
.as-status::before { content:"● "; color:var(--muted); }
.as-status.ok::before { color:var(--good-text); }
.as-msg { margin:0; font-size:var(--fs-14); line-height:1.5; color:var(--ink); background:var(--surface-2); border-radius:10px; padding:10px var(--s-3); }
.as-reset { margin-top:28px; padding-top:var(--s-4); border-top:1px solid var(--line); display:flex; flex-direction:column; gap:var(--s-2); }
.as-reset p, .app-settings p.as-note { margin:0; color:var(--ink-dim); font-size:var(--fs-12); line-height:1.5; }
.as-confirm { display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap; font-size:var(--fs-14); }
.as-confirm span { flex:1 1 100%; }
.as-close { align-self:stretch; margin-top:var(--s-2); }
.as-big { display:flex; gap:var(--s-2); flex-wrap:wrap; }
.as-big button[aria-pressed="true"] { background:var(--accent); color:var(--accent-ink); border-color:transparent; }

/* -------------------------------------------- the drill clock and its panel -- */

.app-hide, .app-drillbar[hidden], .app-decide [hidden], .app-offer[hidden] { display:none !important; }
.app-drillbar { display:flex; align-items:center; gap:var(--s-2); padding:6px 2px 2px; }
.app-drillbar .db-q { font:800 var(--fs-20)/1 var(--display); letter-spacing:-.4px; font-variant-numeric:tabular-nums;
  color:var(--ink-dim); min-width:4.4ch; }
.app-drillbar .db-q small { font-size:var(--fs-12); color:var(--muted); margin-left:3px; letter-spacing:0; }
.app-drillbar .db-total { margin-right:auto; color:var(--ink-dim); font:700 var(--fs-12) var(--ui); font-variant-numeric:tabular-nums; }
.app-drillbar .db-hint { color:var(--ink-dim); font:600 var(--fs-12) var(--ui); }
.app-drillbar button { appearance:none; border:2px solid var(--ol); background:var(--surface); color:var(--ink);
  font:700 var(--fs-14) var(--ui); border-radius:var(--r-2); padding:0 var(--s-4); min-height:var(--tap); cursor:pointer; }
.app-drillbar button[aria-pressed="true"] { background:var(--accent); color:var(--accent-ink); border-color:var(--ol); }
/* The glyph is drawn, not written, so the button's accessible name stays "Pause". */
.app-drillbar button[data-icon]::before { content:attr(data-icon) " "; font-weight:400; }
/* End is quiet and lives at the far left, a bar's width away from Pause
   (specs/2026-09-16-app-design-pass.md §11: never next to it). */
.app-drillbar .db-end { order:-1; border-color:transparent; background:transparent; color:var(--muted); font-weight:600; padding:0 10px; }
.app-drillbar .db-end:active { color:var(--ink); }
/* A press that has not yet lasted 0.5 s fills from the left; it only pauses when it gets there. */
.app-drillbar .db-pause.holding { background-image:linear-gradient(to right, var(--accent), var(--accent));
  background-repeat:no-repeat; background-size:0 100%; animation:db-hold .5s linear forwards; }
@keyframes db-hold { to { background-size:100% 100%; } }

/* Paused: the panel owns the screen and carries every control — one Resume
   (his: there must not be two), then Restart and End session. The bar's own
   Pause and End step aside while it is up (§11, and his 2026-09-16 words). */
.app-paused { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; min-height:40vh;
  margin:var(--s-2) 0 14px; padding:var(--s-5); text-align:center; color:var(--ink-dim); font-size:var(--fs-14); line-height:1.5;
  border:1px dashed var(--line); border-radius:var(--r); }
.app-paused b { font:600 var(--fs-30) var(--numeral); letter-spacing:-1px; color:var(--ink); }
.app-paused .pp-on { font-family:var(--numeral); font-variant-numeric:tabular-nums; color:var(--ink-dim); }
.app-paused .pp-resume { appearance:none; margin-top:auto; width:100%; max-width:320px; min-height:56px; border:0; border-radius:var(--r-3);
  background:var(--accent); color:var(--accent-ink); font:700 var(--fs-16) var(--ui); cursor:pointer; }
.app-paused .pp-resume::before { content:"▶ "; }
.app-paused .pp-more { display:flex; gap:var(--s-2); flex-wrap:wrap; justify-content:center; width:100%; max-width:320px; }
.app-paused .pp-more button { appearance:none; flex:1 1 0; min-width:120px; min-height:var(--tap); padding:0 var(--s-3);
  border:1px solid var(--line); border-radius:var(--r-2); background:var(--surface); color:var(--ink);
  font:600 var(--fs-14) var(--ui); cursor:pointer; }
.app-paused .pp-ask { display:flex; align-items:center; justify-content:center; gap:var(--s-2); flex-wrap:wrap; width:100%; max-width:320px;
  font-size:var(--fs-14); color:var(--ink); }
.app-paused .pp-ask span { flex:1 1 100%; }
.app-paused .pp-ask button { appearance:none; flex:1 1 0; min-height:var(--tap); padding:0 var(--s-4); border-radius:var(--r-2);
  border:1px solid var(--line); background:var(--surface); color:var(--ink); font:700 var(--fs-14) var(--ui); cursor:pointer; }
.app-paused .pp-ask .pp-go { border:0; background:var(--bad-fill); color:var(--bad-fill-ink); }

/* The row is held open for the whole session, empty or not: if it appeared and vanished the
   tap surface under his thumb would jump 50 px every four seconds. */
.app-undo { display:flex; align-items:center; gap:var(--s-2); padding:0 2px; min-height:0; }
.app-undo.armed { min-height:52px; padding-top:6px; }
.app-undo button { appearance:none; min-height:var(--tap); padding:0 14px; border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface-2); color:var(--ink); font:700 var(--fs-14) var(--ui); cursor:pointer; }
.app-undo .ub-alt { border-color:color-mix(in oklab, var(--accent) 45%, transparent); color:var(--ink); }

/* ---------------------------------------------------- voice answers (§2) -- */
/* A line, not a piece of chrome: a dot for whether the microphone is on, and one
   plain sentence for what it heard or why it could not hear. Tapping it listens
   again. It never replaces a control — every grade button stays where it was.
   The dot differs by SHAPE as well as colour (§18): filled while listening, a
   half disc for a mishearing, an open ring when the engine gave up, dashed when
   the microphone is off. */

.app-voice { display:flex; align-items:center; justify-content:center; gap:10px; width:100%;
  min-height:var(--tap); margin:var(--s-2) 0 0; padding:var(--s-2) var(--s-3); border:1px solid var(--line);
  border-radius:var(--r-2); background:var(--surface); color:var(--ink-dim);
  font:600 var(--fs-14)/1.4 var(--ui); text-align:center; cursor:pointer; }
.app-voice .vb-dot { flex:none; width:12px; height:12px; border-radius:50%;
  border:2px solid var(--muted); background:transparent; }
.app-voice .vb-text { min-width:0; overflow-wrap:anywhere; }
.app-voice[data-state="listening"] { color:var(--ink); border-color:color-mix(in oklab,var(--bad) 45%, var(--line)); }
.app-voice[data-state="listening"] .vb-dot { border-color:var(--bad-text); background:var(--bad-text); animation:app-listen 1.1s ease-in-out infinite; }
.app-voice[data-state="heard"] { color:var(--ink); }
.app-voice[data-state="heard"] .vb-dot { border-color:var(--slow-text);
  background:linear-gradient(to right, var(--slow-text) 50%, transparent 50%); }
.app-voice[data-state="ok"] .vb-dot { border-color:var(--good-text); background:var(--good-text); }
.app-voice[data-state="silent"] .vb-dot, .app-voice[data-state="error"] .vb-dot { border-color:var(--ink-dim); }
.app-voice[data-state="off"] .vb-dot { border-style:dashed; }
@keyframes app-listen { 50% { transform:scale(1.35); } }
@media (prefers-reduced-motion: reduce) { .app-voice[data-state="listening"] .vb-dot { animation:none; } }

/* ---------------------------------------------- Save / Discard, the offer -- */

.app-decide { display:flex; flex-direction:column; gap:var(--s-3); }
.app-decide .dc-head { display:flex; align-items:flex-start; gap:var(--s-3); }
.app-decide .dc-sum { margin:0; flex:1; min-width:0; color:var(--ink-dim); font-size:var(--fs-14); line-height:1.5; }
.app-decide .dc-sum b { color:var(--ink); font-family:var(--numeral); font-variant-numeric:tabular-nums; }
/* Discard is a quiet text button, top right, a screen away from Save — and it asks once. */
.app-decide .dc-discard { appearance:none; flex:none; min-height:var(--tap); padding:0 var(--s-3); border:0; border-radius:var(--r-2);
  background:transparent; color:var(--ink-dim); font:600 var(--fs-14) var(--ui); text-decoration:underline; cursor:pointer; }
.app-decide .dc-ask { display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap; font-size:var(--fs-14); color:var(--ink); }
.app-decide .dc-ask span { flex:1 1 100%; }
.app-decide .dc-ask button { appearance:none; min-height:var(--tap); padding:0 var(--s-4); border-radius:var(--r-2); cursor:pointer;
  font:700 var(--fs-14) var(--ui); border:1px solid var(--line); background:var(--surface-2); color:var(--ink); }
.app-decide .dc-ask .dc-go { border:0; background:var(--bad-fill); color:var(--bad-fill-ink); }
.app-decide .dc-save { width:100%; }
.app-decide .dc-chip { display:inline-block; white-space:nowrap; font-family:var(--numeral); color:var(--ink);
  background:var(--surface-2); border:1px solid var(--line); border-radius:var(--r-1); padding:3px var(--s-2); font-size:var(--fs-12); }
.app-decide .dc-note { margin:0; text-align:center; color:var(--ink-dim); font-size:var(--fs-14); }
/* The unfinished session is offered in place, at the top of the setup screen — never a dialog. */
.app-offer { display:flex; flex-direction:column; gap:10px; margin:0 0 var(--s-4); padding:14px var(--s-4); border-radius:var(--r-3);
  border:1px solid color-mix(in oklab, var(--accent) 40%, var(--line)); background:var(--surface);
  color:var(--ink); font-family:var(--ui); }
.app-offer h2 { margin:0; font-size:var(--fs-16); }
.app-offer p { margin:0; color:var(--ink-dim); font-size:var(--fs-14); line-height:1.5; }
.app-offer p.of-sub { color:var(--ink-dim); font-size:var(--fs-12); }
.app-offer .of-row { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.app-offer .of-ask { grid-column:1 / -1; display:flex; align-items:center; flex-wrap:wrap; gap:10px; font-weight:600; }
.app-offer .of-ask button { flex:1 1 auto; min-height:var(--tap); }
.app-offer button { appearance:none; min-height:var(--tap); border-radius:var(--r-2); cursor:pointer; font:700 var(--fs-14) var(--ui);
  border:1px solid var(--line); background:var(--surface-2); color:var(--ink); }
.app-offer button.of-save { border:0; background:var(--accent); color:var(--accent-ink); }
.app-offer .dc-go { border:0; background:var(--bad-fill); color:var(--bad-fill-ink); }

/* ------------------------------------------------------ shared components -- */
/* Used the same way by four or more pages. A page's own <style> comes after this
   file, so a page-specific override still wins. */

.view { flex:1; display:none; flex-direction:column; min-height:0; }
.view.active { display:flex; }

.field { display:flex; flex-direction:column; gap:9px; }
.field > label { color:var(--ink-dim); font-weight:800; font-family:var(--display); font-size:var(--fs-12); letter-spacing:var(--track-caps); text-transform:uppercase; padding-left:2px; }

.seg { display:flex; background:var(--surface-2); border:2px solid var(--ol); border-radius:13px; padding:var(--s-1); gap:var(--s-1); }
.seg button { flex:1; appearance:none; border:0; background:transparent; color:var(--ink-dim); font-family:var(--ui);
  font-size:var(--fs-14); font-weight:600; padding:10px 6px; min-height:var(--tap); border-radius:10px; cursor:pointer; }
.seg.grid2 { display:grid; grid-template-columns:1fr 1fr; }
.seg.grid3 { display:grid; grid-template-columns:repeat(3,1fr); }
.seg button[aria-pressed="true"] { background:var(--accent); color:var(--accent-ink); box-shadow:none; font-weight:800; }
.seg button.num { font-family:var(--numeral); letter-spacing:-.4px; }

.btn { appearance:none; border:0; cursor:pointer; font-family:var(--ui); font-weight:700; border-radius:15px;
  padding:17px; min-height:var(--tap); font-size:var(--fs-16); display:flex; align-items:center; justify-content:center; gap:9px; width:100%; }
.btn:active { transform:translate(2px,2px); }
.btn.primary { background:var(--accent); color:var(--accent-ink); border:var(--ol-w) solid var(--ol); box-shadow:var(--pop); }
.btn.ghost { background:var(--surface); color:var(--ink); border:var(--ol-w) solid var(--ol); box-shadow:var(--pop); }
/* The press takes the offset away instead of scaling: the sticker is pushed flat. */
.btn.primary:active, .btn.ghost:active { box-shadow:none; transform:translate(4px,4px); }
.btn[disabled] { opacity:.45; pointer-events:none; }

.progress-rail { height:4px; background:var(--surface-2); border-radius:99px; overflow:hidden; margin:2px 2px 0; }
.progress-fill { height:100%; width:0; background:var(--accent); border-radius:99px; transition:width .35s cubic-bezier(.2,.7,.2,1); }

/* Grade buttons: the word, its key, and a symbol — never colour alone (§14, §18). */
.grade { display:grid; grid-template-columns:1fr 1fr; gap:var(--s-3); padding:14px 0 calc(14px + env(safe-area-inset-bottom)); }
.grade button { appearance:none; border:0; cursor:pointer; font-family:var(--ui); font-weight:800; font-size:var(--fs-16); border-radius:var(--r-3);
  padding:19px 10px; min-height:56px; display:flex; flex-direction:column; align-items:center; gap:3px; }
.grade button small { font-weight:600; font-size:var(--fs-12); }

/* Dimmed, never dissolved: the outline and the offset stay so the row keeps its shape
   before the reveal. Washing them out made the busiest control on screen look broken. */
.grade button[disabled] { opacity:.62; filter:saturate(.75); pointer-events:none; }
.grade button { border:var(--ol-w) solid var(--ol); box-shadow:var(--pop-sm); }
.grade button:active { box-shadow:none; transform:translate(3px,3px); }
.grade .miss { background:var(--surface); color:var(--bad-text); }
.grade .slow { background:var(--slow); color:var(--slow-ink); }
.grade .got, .grade .inst { background:var(--good); color:var(--good-ink); }

.foot { text-align:center; color:var(--muted); font-size:var(--fs-12); padding:6px 0 calc(10px + env(safe-area-inset-bottom)); }

/* "Review these": two lines an item — the key and word first, the image left
   aligned under it, full width. Never a centred or right-aligned paragraph (§21). */
.missed-list { display:flex; flex-direction:column; gap:var(--s-2); }
.missed-head { color:var(--muted); font-size:var(--fs-12); letter-spacing:var(--track-caps); text-transform:uppercase; }
.missed-row { display:flex; flex-direction:column; gap:3px; background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-2); padding:11px 14px; text-align:left; }
.missed-row .hd { display:flex; align-items:baseline; gap:10px; }
.missed-row .k { font-family:var(--numeral); font-size:var(--fs-20); color:var(--bad-text); }
.missed-row .w { font-family:var(--numeral); font-size:var(--fs-16); color:var(--ink); }
.missed-row .i { color:var(--ink-dim); font-size:var(--fs-14); line-height:1.4; text-align:left; }
.allclear { text-align:center; color:var(--good-text); font-weight:600; font-size:var(--fs-16); }


/* --- My Memory System: reveal-on-tap and the inline editor ------------------
   specs/2026-09-18-edit-my-memory-system-in-the-app.md step 3. The description
   is hidden until tapped so a glance cannot spoil recall, and so browsing the
   system doubles as retrieval practice. */
.reveal{display:block;width:100%;text-align:left;font:inherit;color:var(--muted);
  background:var(--raise);border:1px dashed var(--line);border-radius:12px;
  padding:14px 16px;cursor:pointer}
.reveal:hover{border-color:var(--accent);color:var(--ink-dim)}
.reveal.empty{cursor:default;font-style:italic}
.revealed{padding:2px 0 4px;white-space:pre-line}
.editrow{display:flex;gap:8px;margin-top:12px}
.editrow button{font:600 14px/1 var(--ui);padding:10px 14px;border-radius:10px;
  border:1px solid var(--line);background:var(--accent);color:var(--accent-ink);cursor:pointer}
.editrow button.ghost{background:transparent;color:var(--ink-dim)}
#editForm label{display:block;margin:12px 0 0;font-size:12px;text-transform:uppercase;
  letter-spacing:.07em;color:var(--muted)}
#editForm input,#editForm textarea{display:block;width:100%;margin-top:5px;font:15px/1.5 var(--ui);
  color:var(--ink);background:var(--surface);border:1px solid var(--line);border-radius:10px;padding:10px 12px}
#editForm textarea{resize:vertical}
.warnbox{margin-top:12px;padding:11px 13px;border-radius:10px;font-size:13.5px;
  background:color-mix(in srgb,var(--bad) 12%,transparent);
  border:1px solid color-mix(in srgb,var(--bad) 40%,transparent);color:var(--ink)}
.editmsg{margin-top:9px;font-size:13px;color:var(--muted)}
