/* ==========================================================================
   OmniTaught beta app. OmniPsyche brand family (navy, coral, cream).
   Calm, mobile-first. See DESIGN.md: The Clinician's Field Notebook.
   ========================================================================== */

:root {
  /* Light theme = OmniPsyche.org brand palette (see site tokens.css):
     cream paper, navy ink, coral accent. Coral is the darkened accessible
     variant (#BE4A22) because it doubles as small text and as a fill
     under white text; brand coral #f0744f fails AA in both roles. */
  --paper: #fbf7f2;
  --card: #ffffff;
  --ink: #1a2f4f;
  --ink-soft: #4a5c79;
  --ink-faint: #5f7089;  /* faint but still AA at footnote size */
  --line: #e7ddd1;
  --accent: #BE4A22;
  --accent-soft: #faeae2;
  --btn-ink: #ffffff;    /* text on accent fills; flips dark in night mode */
  --gold: #d9a13f;
  --gold-text: #8a6a1a;  /* gold as TEXT on cream needs to be darker than gold as fill */
  --on-gold: #33260a;    /* text on a gold fill, both themes */

  /* DRS palette (muted, readable) */
  --c-low: #6a9e63;
  --c-low-bg: #e7f0e2;
  --c-med: #b9975a;
  --c-med-bg: #f3ead7;
  --c-high: #c07f45;
  --c-high-bg: #f6e6d4;
  --c-highest: #b35d5d;
  --c-highest-bg: #f5dfdc;
  --c-var: #5589a8;
  --c-var-bg: #e0ecf3;
  --c-face: #7d7a96;
  --c-face-bg: #e9e7f0;

  --radius: 14px;
  --shadow: 0 1px 3px rgba(29, 42, 58, .08), 0 8px 24px rgba(29, 42, 58, .07);
  --font-display: Georgia, 'Iowan Old Style', 'Times New Roman', serif;
  --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Night mode: same brand, darker paper. Coral stays the accent (The One
       Accent Rule); gold stays gold. Accent fills carry navy ink because
       night coral cannot hold white text at AA. */
    --paper: #0e1a2c;
    --card: #16273f;
    --ink: #e9edf4;
    --ink-soft: #aebbd1;
    --ink-faint: #93a6c4;
    --line: #2b3d5c;
    --accent: #ff8f6b;
    --accent-soft: #45281c;
    --btn-ink: #13233b;
    --gold: #e3b768;
    --gold-text: #e3b768;
    --c-low: #7fb475; --c-low-bg: #22331f;
    --c-med: #c9a86a; --c-med-bg: #383021;
    --c-high: #d1935c; --c-high-bg: #3d2d1e;
    --c-highest: #c97b7b; --c-highest-bg: #3d2424;
    --c-var: #6fa3c2; --c-var-bg: #1f3040;
    --c-face: #9a96b5; --c-face-bg: #2b2a3d;
    --shadow: 0 1px 3px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25);
  }
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.55;
}

/* ---------- layout ---------- */
#app { max-width: 780px; margin: 0 auto; padding: 16px 16px 96px; }
@media (min-width: 900px) { #app { padding-top: 28px; } }

header.topbar {
  display: flex; align-items: baseline; justify-content: space-between;
  max-width: 780px; margin: 0 auto; padding: 14px 16px 0;
}
.brand { font-family: var(--font-display); font-size: 1.05rem; letter-spacing: .02em; color: var(--ink-soft); text-decoration: none; }
.brand b { color: var(--ink); }
.streak { font-size: .85rem; color: var(--gold-text); font-weight: 600; }

/* bottom tab bar */
/* The bottom bar is how people move around the app, and it was reading as a
   pale strip that blended into the page. It now sits on its own surface with a
   real shadow above it, and the tab you are on is unmistakable. */
nav.tabs {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 40;
  display: flex; justify-content: center; gap: 2px;
  background: color-mix(in srgb, var(--card) 97%, transparent);
  backdrop-filter: blur(14px);
  border-top: 1.5px solid var(--line);
  box-shadow: 0 -2px 6px rgba(26,47,79,.06), 0 -12px 30px rgba(26,47,79,.10);
  padding: 7px 8px calc(7px + env(safe-area-inset-bottom));
}
nav.tabs a {
  flex: 0 1 112px; text-align: center; text-decoration: none;
  color: var(--ink-soft); font-size: .72rem; font-weight: 700;
  padding: 7px 4px 5px; border-radius: 12px; letter-spacing: .03em;
  transition: color .12s ease, background .12s ease;
}
nav.tabs a .ico { display: block; font-size: 1.3rem; line-height: 1.2; }
nav.tabs a:hover { color: var(--ink); }
nav.tabs a.active {
  color: var(--accent); background: var(--accent-soft);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent) 35%, transparent);
}
/* The Genie is the one paid-feeling thing in here, so it wears the gold. */
nav.tabs a.tab-genie { color: var(--gold-text); }
nav.tabs a.tab-genie .ico { color: var(--gold); }
nav.tabs a.tab-genie.active {
  color: var(--on-gold); background: var(--gold);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--gold) 55%, transparent);
}
nav.tabs a.tab-genie.active .ico { color: var(--on-gold); }
/* Due work is gold (something waiting for you), never the caution rose,
   which is reserved for safety and referral limits. */
nav.tabs a .badge {
  display: inline-block; min-width: 18px; padding: 0 4px; margin-left: 3px;
  background: var(--gold); color: var(--on-gold); border-radius: 9px;
  font-size: .7rem; line-height: 18px; font-weight: 800;
}
/* The weekly pause dims the routes it blocks; the Beta tab stays live. */
nav.tabs.locked a:not([data-tab="beta"]) { opacity: .45; }

/* ---------- typography ---------- */
h1 { font-family: var(--font-display); font-weight: 600; font-size: 1.7rem; line-height: 1.2; margin: .4em 0 .3em; }
h2 { font-family: var(--font-display); font-weight: 600; font-size: 1.25rem; margin: 1.4em 0 .4em; }
h3 { font-size: .95rem; margin: 1.2em 0 .35em; }
p { margin: .5em 0; color: var(--ink-soft); }
p.lede { font-size: 1.05rem; color: var(--ink); }
.dim { color: var(--ink-faint); font-size: .85rem; }
.eyebrow {
  font-size: .72rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--accent); margin: 1.6em 0 .2em;
}

/* ---------- cards & buttons ---------- */
.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px; margin: 12px 0; box-shadow: var(--shadow);
}
.card.tappable { cursor: pointer; transition: transform .06s ease; }
.card.tappable:active { transform: scale(.985); }

.btn {
  display: inline-block; border: 0; cursor: pointer; text-decoration: none; text-align: center;
  font: 600 1rem var(--font-ui); border-radius: 12px; padding: 12px 22px;
  background: var(--accent); color: var(--btn-ink);
}
.btn.block { display: block; width: 100%; }
.btn.ghost { background: transparent; color: var(--accent); border: 1.5px solid var(--accent); }
.btn.subtle { background: var(--accent-soft); color: var(--accent); }
.btn:disabled { opacity: .45; cursor: default; }
.btn-row { display: flex; gap: 10px; margin: 14px 0; }
.btn-row .btn { flex: 1; }

/* progress ring / bars */
.ring { width: 78px; height: 78px; }
.ring circle { fill: none; stroke-width: 7; }
.ring .track { stroke: var(--line); }
.ring .fill { stroke: var(--accent); stroke-linecap: round; transform: rotate(-90deg); transform-origin: center; transition: stroke-dashoffset .5s ease; }
.progressbar { height: 6px; background: var(--line); border-radius: 3px; overflow: hidden; }
.progressbar > div { height: 100%; background: var(--accent); border-radius: 3px; transition: width .25s ease; }

/* Free for life, on the home screen.

   Quiet by design. The beta asks one thing and the person should be able to
   see where they are on it without hunting, but it is not the reason they
   opened the app, so it does not get an accent fill, a button, or a heading.
   A line, a thin bar, a sentence. Gold only once it matters: while the beta is
   winding down, or on the single screen that says it has been earned. */
.lifecard { border-left: 3px solid var(--line); }
.lifecard .lifeline { margin: 0 0 8px; font-size: .95rem; }
.lifecard .lifeline b { color: var(--ink); }
.lifebar { height: 5px; background: var(--line); border-radius: 3px; overflow: hidden; margin-bottom: 8px; }
.lifebar > i { display: block; height: 100%; background: var(--gold); border-radius: 3px; transition: width .3s ease; }
.lifecard.urgent { border-left-color: var(--gold); }
.lifecard.done { border-left-color: var(--gold); }
.lifecard.done .eyebrow { color: var(--gold-text); }

/* ---------- home ---------- */
.hero { display: flex; gap: 16px; align-items: center; }
.hero .stats { flex: 1; }
.statline { display: flex; gap: 18px; margin-top: 6px; }
.statline b { font-size: 1.25rem; display: block; line-height: 1.1; }
.statline span { font-size: .72rem; color: var(--ink-faint); letter-spacing: .05em; text-transform: uppercase; }

.resume-card { border-left: 4px solid var(--accent); }
.more-progress { margin: 12px 0; }
.more-progress summary {
  cursor: pointer; list-style: none; font-size: .85rem; font-weight: 700;
  color: var(--accent); padding: 6px 2px;
}
.more-progress summary::before { content: "▸ "; }
.more-progress[open] summary::before { content: "▾ "; }
.more-progress summary::-webkit-details-marker { display: none; }
.resume-card .dim { margin-bottom: 2px; }

/* ---------- path ---------- */
.phase-label { margin-top: 26px; }
.unit-row {
  display: flex; align-items: center; gap: 14px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 13px 16px; margin: 8px 0; text-decoration: none; color: var(--ink);
  box-shadow: var(--shadow);
}
.unit-row:active { transform: scale(.99); }
.unit-row .state {
  width: 34px; height: 34px; flex: 0 0 34px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: .95rem; font-weight: 700;
  background: var(--line); color: var(--ink-faint);
}
.unit-row.done .state { background: var(--accent); color: var(--btn-ink); }
.unit-row.next .state { background: var(--gold); color: var(--on-gold); }
.unit-row.next { border-color: var(--gold); }
.unit-row .u-title { font-weight: 600; font-size: .98rem; }
.unit-row .u-sub { font-size: .78rem; color: var(--ink-faint); }
.unit-row .score { margin-left: auto; font-size: .8rem; color: var(--accent); font-weight: 700; white-space: nowrap; }

/* ---------- the table ---------- */

.legend { display: flex; flex-wrap: wrap; gap: 8px 14px; margin: 10px 0 4px; font-size: .74rem; color: var(--ink-soft); }
.legend .sw { display: inline-block; width: 11px; height: 11px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }

.searchbar {
  width: 100%; padding: 11px 14px; font: 1rem var(--font-ui);
  border: 1.5px solid var(--line); border-radius: 12px; background: var(--card); color: var(--ink);
}
.searchbar:focus { outline: 2px solid var(--accent); border-color: transparent; }
.filter-row { display: flex; gap: 6px; flex-wrap: wrap; margin: 10px 0; }
.chip {
  border: 1.5px solid var(--line); background: var(--card); color: var(--ink-soft);
  border-radius: 20px; padding: 5px 12px; font: 600 .78rem var(--font-ui); cursor: pointer;
}
.chip.on { background: var(--accent); border-color: var(--accent); color: var(--btn-ink); }


/* ---------- element detail sheet ---------- */
.sheet-veil {
  position: fixed; inset: 0; background: rgba(15, 22, 32, .45); z-index: 60;
  opacity: 0; transition: opacity .18s ease; pointer-events: none;
}
.sheet-veil.open { opacity: 1; pointer-events: auto; }
.sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 61;
  max-width: 720px; margin: 0 auto; max-height: 88dvh; overflow-y: auto;
  background: var(--card); border-radius: 20px 20px 0 0;
  padding: 10px 20px calc(24px + env(safe-area-inset-bottom));
  transform: translateY(102%); transition: transform .22s ease;
  box-shadow: 0 -8px 40px rgba(0,0,0,.25);
}
.sheet.open { transform: translateY(0); }
.grabber { width: 40px; height: 4px; border-radius: 2px; background: var(--line); margin: 6px auto 12px; }
.sheet-back {
  display: inline-block; border: 0; cursor: pointer; margin: 0 0 10px;
  background: var(--accent-soft); color: var(--accent);
  border-radius: 10px; padding: 7px 14px; font: 700 .85rem var(--font-ui);
}
.sheet-back:active { transform: scale(.97); }

.elcell {
  border-radius: 12px; padding: 14px 16px; margin: 8px 0 14px;
  display: grid; grid-template-columns: 1fr auto; gap: 2px 12px;
  border: 1.5px solid var(--line);
}
.elcell .big { font-size: 2rem; font-weight: 800; line-height: 1; display: flex; align-items: center; gap: 8px; }
.elcell .big .dot { width: 10px; height: 10px; }
.elcell .elname { font-family: var(--font-display); font-size: 1.1rem; }
.elcell .kv { font-size: .78rem; color: var(--ink-soft); display: flex; justify-content: space-between; gap: 10px; }
.elcell .kv b { color: var(--ink); font-weight: 700; }
.elmeta { grid-column: 1 / -1; display: grid; grid-template-columns: 1fr 1fr; gap: 3px 14px; margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line); }

.tagrow { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; }
.eltag {
  background: var(--accent-soft); color: var(--accent); border: 0; cursor: pointer;
  border-radius: 8px; padding: 4px 10px; font: 700 .8rem var(--font-ui);
}
.callout {
  border-left: 4px solid var(--accent); background: var(--accent-soft);
  border-radius: 0 10px 10px 0; padding: 10px 14px; margin: 12px 0;
}
.callout.gold { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 12%, var(--card)); }
.callout.red { border-color: var(--c-highest); background: var(--c-highest-bg); }
.callout.red .label { color: var(--c-highest); }
ol.asknext { margin: .4em 0 .2em; padding-left: 22px; color: var(--ink); }
ol.asknext li { margin: .45em 0; line-height: 1.45; }

/* tap-to-define terms */
.term {
  border-bottom: 1.5px dotted var(--accent);
  cursor: pointer;
  border-radius: 2px;
}
.term:active, .term:hover { background: var(--accent-soft); }
.callout p { margin: .25em 0; color: var(--ink); font-size: .92rem; }
.callout .label { font-size: .72rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--accent); }
.callout.gold .label { color: var(--gold-text); }

ul.looks { margin: .4em 0; padding-left: 20px; color: var(--ink-soft); }
ul.looks li { margin: .3em 0; }

/* inline element chip used in lessons */
.elref {
  display: inline-flex; align-items: baseline; gap: 5px; cursor: pointer;
  background: var(--card); border: 1.5px solid var(--line); border-radius: 9px;
  padding: 2px 9px; font-weight: 700; font-size: .88rem; color: var(--ink);
}
.elref small { font-weight: 600; color: var(--ink-faint); }
.elgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; margin: 10px 0; }
.elgrid .elref { justify-content: flex-start; padding: 8px 10px; }

/* ---------- lesson ---------- */
.lesson-head .dim { margin-bottom: 0; }
.lesson-body h2 { margin-top: 1.5em; }
.lesson-nav { display: flex; justify-content: space-between; gap: 10px; margin-top: 26px; }

/* ---------- quiz ---------- */
.quiz-top { display: flex; align-items: center; gap: 12px; margin: 10px 0 18px; }
.quiz-top .progressbar { flex: 1; }
.q-stem { font-size: 1.12rem; font-weight: 600; color: var(--ink); margin: 14px 0 16px; line-height: 1.45; }
.q-stem .scenario { font-weight: 400; font-style: italic; color: var(--ink-soft); display: block; margin-bottom: 10px; }
.opt {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: var(--card); border: 1.5px solid var(--line); border-radius: 12px;
  padding: 13px 16px; margin: 8px 0; font: 500 .98rem/1.4 var(--font-ui); color: var(--ink);
}
.opt.sel { border-color: var(--accent); }
.opt.correct { border-color: var(--c-low); background: var(--c-low-bg); }
.opt.wrong { border-color: var(--c-highest); background: var(--c-highest-bg); }
.feedback { border-radius: 12px; padding: 12px 16px; margin: 12px 0; font-size: .92rem; }
.feedback.good { background: var(--c-low-bg); color: var(--ink); }
.feedback.bad { background: var(--c-highest-bg); color: var(--ink); }
.feedback b { display: block; margin-bottom: 2px; }

.result-hero { text-align: center; padding: 26px 0 8px; }
.result-hero .big-score { font-family: var(--font-display); font-size: 3rem; font-weight: 700; }

/* ---------- review (flashcards) ---------- */
.flashcard {
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
  box-shadow: var(--shadow); padding: 30px 22px; min-height: 240px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center;
  cursor: pointer;
}
.flashcard .fc-sym { font-size: 2.4rem; font-weight: 800; }
.flashcard .fc-prompt { color: var(--ink-faint); font-size: .8rem; letter-spacing: .08em; text-transform: uppercase; margin-top: 14px; }
.flashcard .fc-answer { margin-top: 12px; }
.box-meter { display: flex; gap: 3px; justify-content: center; margin-top: 14px; }
.box-meter i { width: 16px; height: 5px; border-radius: 2px; background: var(--line); }
.box-meter i.on { background: var(--gold); }

/* misc */
.divider { border: 0; border-top: 1px solid var(--line); margin: 22px 0; }
.center { text-align: center; }
.spaced { margin-top: 20px; }
a.plain { color: var(--accent); font-weight: 600; text-decoration: none; }
.footnote { font-size: .74rem; color: var(--ink-faint); margin-top: 30px; }

/* Consent sits ABOVE the submit button and is never pre-ticked: agreement
   has to be a thing the person did, not a default they failed to undo. */
.agree {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 16px 0 4px; font-size: .84rem; line-height: 1.5; color: var(--ink-dim);
  cursor: pointer;
}
.agree input {
  flex: 0 0 auto; width: 20px; height: 20px; margin: 0; cursor: pointer;
  accent-color: var(--gold);
}
.agree a { color: var(--ink); }
.agree:has(input:focus-visible) { outline: 2px solid var(--gold); outline-offset: 4px; border-radius: 6px; }

/* ---------- math drills ---------- */
.drill-card { text-align: center; padding: 22px 16px 18px; }
.drill-q { font-family: var(--font-display); font-size: 2.2rem; font-weight: 700; letter-spacing: .02em; }
.drill-entry {
  margin: 14px auto 2px; min-height: 52px; max-width: 240px;
  border-bottom: 3px solid var(--accent); font-size: 2rem; font-weight: 700;
  display: flex; align-items: center; justify-content: center; color: var(--ink);
}
.numpad {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
  max-width: 320px; margin: 14px auto 0;
}
.numpad .key {
  font: 700 1.5rem var(--font-ui); padding: 14px 0; border-radius: 14px;
  border: 1.5px solid var(--line); background: var(--card); color: var(--ink); cursor: pointer;
  box-shadow: var(--shadow);
}
.numpad .key:active { transform: scale(.96); background: var(--accent-soft); }

/* ---------- times table heatmap ---------- */
.mult-grid {
  display: grid; grid-template-columns: repeat(13, 1fr); gap: 2px;
  max-width: 560px;
}
.mg-cell {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  font-size: .58rem; font-weight: 600; border-radius: 4px; color: var(--ink-faint);
  background: var(--card); border: 1px solid var(--line);
}
.mg-head { background: transparent; border: 0; color: var(--ink-soft); font-weight: 800; }
.mg-l1 { background: color-mix(in srgb, var(--gold) 16%, var(--card)); color: var(--ink-soft); }
.mg-l2 { background: color-mix(in srgb, var(--gold) 32%, var(--card)); color: var(--ink); }
.mg-l3 { background: color-mix(in srgb, var(--gold) 50%, var(--card)); color: var(--ink); }
.mg-l4 { background: color-mix(in srgb, var(--gold) 70%, var(--card)); color: #fff; }
.mg-l5 { background: var(--gold); color: #fff; border-color: var(--gold); }
@media (min-width: 700px) { .mg-cell { font-size: .78rem; } }

/* ---------- OmniTaught beta additions ---------- */
.beta-band {
  /* Site brand band: navy gradient, always leading with navy */
  background: #1a2f4f; background: linear-gradient(160deg, #1a2f4f 0%, #13233b 78%); color: #ffd9a8;
  font-size: .7rem; text-align: center; padding: 6px 12px; font-weight: 600;
}
.beta-band b { color: #fff; }
.topic { display: flex; gap: 12px; align-items: center; }
.tic { width: 46px; height: 46px; flex: 0 0 46px; border-radius: 13px; display: flex; align-items: center; justify-content: center; font-size: 1.05rem; font-weight: 800; color: #fff; }
.tmeta { font-size: .74rem; color: var(--ink-faint); }
.tprog { height: 5px; background: var(--line); border-radius: 3px; margin-top: 7px; overflow: hidden; }
.tprog i { display: block; height: 100%; background: var(--gold); border-radius: 3px; }
.soonpill { font-size: .7rem; font-weight: 800; padding: 3px 8px; border-radius: 9px; background: var(--line); color: var(--ink-soft); }
.stars-in { display: flex; gap: 8px; margin-top: 8px; }
.stars-in button { font-size: 1.7rem; background: none; border: 0; cursor: pointer; color: var(--line); padding: 0; }
.stars-in button.on { color: var(--gold); }
textarea.fbtext {
  width: 100%; border: 1.5px solid var(--line); border-radius: 12px; background: var(--paper);
  padding: 10px 12px; font: inherit; font-size: .88rem; min-height: 66px; resize: vertical;
  color: var(--ink); margin-top: 8px;
}
textarea.fbtext:focus { outline: 2px solid var(--accent); border-color: transparent; }

/* ---------- manual theme toggle (overrides system preference) ---------- */
:root[data-theme="light"] {
  --paper: #fbf7f2; --card: #ffffff; --ink: #1a2f4f; --ink-soft: #4a5c79; --ink-faint: #5f7089;
  --line: #e7ddd1; --accent: #BE4A22; --accent-soft: #faeae2; --btn-ink: #ffffff;
  --gold: #d9a13f; --gold-text: #8a6a1a; --on-gold: #33260a;
  --c-low: #6a9e63; --c-low-bg: #e7f0e2; --c-med: #b9975a; --c-med-bg: #f3ead7;
  --c-high: #c07f45; --c-high-bg: #f6e6d4; --c-highest: #b35d5d; --c-highest-bg: #f5dfdc;
  --c-var: #5589a8; --c-var-bg: #e0ecf3; --c-face: #7d7a96; --c-face-bg: #e9e7f0;
  --shadow: 0 1px 3px rgba(29, 42, 58, .08), 0 8px 24px rgba(29, 42, 58, .07);
}
:root[data-theme="dark"] {
  --paper: #0e1a2c; --card: #16273f; --ink: #e9edf4; --ink-soft: #aebbd1; --ink-faint: #93a6c4;
  --line: #2b3d5c; --accent: #ff8f6b; --accent-soft: #45281c; --btn-ink: #13233b;
  --gold: #e3b768; --gold-text: #e3b768; --on-gold: #33260a;
  --c-low: #7fb475; --c-low-bg: #22331f; --c-med: #c9a86a; --c-med-bg: #383021;
  --c-high: #d1935c; --c-high-bg: #3d2d1e; --c-highest: #c97b7b; --c-highest-bg: #3d2424;
  --c-var: #6fa3c2; --c-var-bg: #1f3040; --c-face: #9a96b5; --c-face-bg: #2b2a3d;
  --shadow: 0 1px 3px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .25);
}
.theme-btn {
  border: 1.5px solid var(--line); background: var(--card); color: var(--ink-soft);
  border-radius: 50%; width: 44px; height: 44px; cursor: pointer; font-size: 1.1rem;
  line-height: 1; margin-left: 10px; flex: 0 0 auto;
}
header.topbar { align-items: center; }
header.topbar .right { display: flex; align-items: center; }

/* OmniPsyche affiliation mark */
.affil { text-align: center; margin: 26px 0 4px; }
.affil a {
  display: inline-flex; align-items: center; gap: 8px; text-decoration: none;
  color: var(--ink-faint); font-size: .78rem; font-weight: 600;
}
.affil-mark {
  width: 20px; height: 22px; flex: 0 0 auto; opacity: .85; background: currentColor;
  -webkit-mask: url("head.png") center / contain no-repeat;
  mask: url("head.png") center / contain no-repeat;
}
.affil b { color: var(--ink-soft); }
.affil a:hover { color: var(--accent); }

/* ==========================================================================
   Accessibility and hardening foundation (2026-08-02 critique fixes)
   ========================================================================== */

/* The UA default is restored with force: two rules above (.btn-row, .badge)
   were silently defeating [hidden], which let flashcards be graded unseen
   and pinned a permanent "0" badge to the Review tab. */
[hidden] { display: none !important; }

/* One designed focus treatment for everything focusable, both themes. */
:where(a, button, [tabindex], summary, input, textarea, select):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Cards that are now real links and buttons keep looking like cards. */
a.card { display: block; color: inherit; text-decoration: none; }
a.card.topic { display: flex; }
button.card { width: 100%; text-align: left; font: inherit; color: inherit; cursor: pointer; }
button.card:disabled { cursor: default; opacity: .75; }

/* Screen-reader-only announcements (quiz feedback, route changes). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Correct and wrong never rely on color alone. */
.opt.correct::before { content: "\2713  "; font-weight: 800; }
.opt.wrong::before { content: "\2715  "; font-weight: 800; }

/* During a quiz the tab bar leaves: no mis-tap exits, and Next is never
   hidden behind it. */
body.quizmode nav.tabs { display: none; }
body.quizmode #app { padding-bottom: 28px; }

/* The quiz back arrow becomes a real target (was 14x25). */
.quiz-top .plain { font-size: 1.25rem; padding: 10px 14px; margin: -10px -8px; }

/* Reference chips reach a humane target size. */
.elref { padding: 8px 12px; min-height: 32px; }

/* The weekly pause responds to a blocked tap by pointing at itself. */
@keyframes lockpulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold) 60%, transparent); }
  100% { box-shadow: 0 0 0 16px transparent; }
}
.lock-pulse { animation: lockpulse .7s ease 2; }

/* Lesson lists: list-shaped content gets a list block instead of prose. */
ul.lesson-list, ol.lesson-list { margin: .5em 0; padding-left: 22px; color: var(--ink-soft); }
ul.lesson-list li, ol.lesson-list li { margin: .35em 0; line-height: 1.5; }

/* Reading measure: lesson prose caps near 70ch on wide screens. */
#app p:not(.center) { max-width: 70ch; }

/* Motion is a courtesy, not a requirement. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Exam mode clears ALL chrome: the beta band leaves with the tab bar. */
body.quizmode .beta-band { display: none; }

/* Stat labels stay on one line so the row reads as a unit. */
.statline span { white-space: nowrap; font-size: .68rem; }

/* Community votes on Wish Library courses. */
.lib-row { align-items: center; }
.lib-pinned { border-color: var(--gold); }
.votebtn {
  flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 1px;
  min-width: 46px; padding: 6px 8px; cursor: pointer;
  background: var(--card); color: var(--ink-soft);
  border: 1.5px solid var(--line); border-radius: 10px;
  font: 700 .7rem var(--font-ui); line-height: 1.1;
}
.votebtn .votenum { font-size: .82rem; font-weight: 800; }
.votebtn:hover { border-color: var(--accent); color: var(--accent); }
.votebtn.on { border-color: var(--gold); color: var(--gold-text); background: color-mix(in srgb, var(--gold) 12%, var(--card)); }
.votebtn:disabled { opacity: .5; cursor: default; }
.libadd { flex: 0 0 auto; padding: 8px 14px; font-size: .82rem; }
.dupflag { font-size: .64rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--gold-text); }

/* ---------- admin console ----------
   Deliberately plainer than the learner-facing app. This is a tool, and the
   destructive actions are marked in red so a misclick is less likely. */
.adm-tabs { display: flex; gap: 6px; flex-wrap: wrap; margin: 14px 0 16px; }
/* --ink-dim and --bg were never defined, so the selected tab painted a
   near-white fill and then failed to set a text colour, leaving light text on
   a light pill. --btn-ink is the token that already means "text on a filled
   button" and flips correctly between light and dark. */
.adm-tab {
  font-size: .82rem; padding: 7px 13px; border-radius: 999px; cursor: pointer;
  background: transparent; color: var(--ink-soft); border: 1.5px solid var(--line);
}
.adm-tab:hover { color: var(--ink); }
.adm-tab.on { background: var(--ink); color: var(--btn-ink); border-color: var(--ink); }
.adm-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.adm-list { border-top: 1px solid var(--line); }
.adm-row {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 11px 2px; border-bottom: 1px solid var(--line);
}
.adm-row .grow { flex: 1; min-width: 0; }
/* A problem report is the one row in this feed waiting on a reply, so it is
   the one row that gets marked. */
.adm-row.adm-problem { border-left: 3px solid var(--accent); }
.adm-row b { font-size: .95rem; }
.adm-row .meta { color: var(--ink-dim); font-size: .78rem; margin-top: 2px; line-height: 1.5; }
.adm-row .acts { display: flex; gap: 6px; flex-wrap: wrap; }
.adm-row button {
  font-size: .76rem; padding: 6px 10px; border-radius: 6px; cursor: pointer;
  background: transparent; color: var(--ink); border: 1.5px solid var(--line);
}
.adm-row button:hover { border-color: var(--gold); }
.adm-row button.danger { color: #c0392b; border-color: #c0392b; }
.adm-row button.danger:hover { background: rgba(192, 57, 43, .1); }
.pill.on { color: var(--gold); border-color: var(--gold); font-weight: 600; }
@media (max-width: 600px) {
  .adm-row { flex-direction: column; gap: 8px; }
  .adm-row .acts { width: 100%; }
}

/* The console on a real monitor.
   Everywhere else in this app the 780px column is the point: it is a reading
   width and the content is prose. The console is a tool with tables, mail
   lists, and side by side panes, and squeezing it into a phone column on a
   27 inch screen wastes the screen. The paragraph cap inside it is lifted
   too, because a console paragraph is a label, not an essay. */
@media (min-width: 1100px) {
  body.adm-wide #app,
  body.adm-wide header.topbar { max-width: min(1560px, 94vw); }
  body.adm-wide #app p:not(.center) { max-width: none; }
}

/* ---------- element table ----------
   A course can carry a reference dataset and render it as a grid. The sheet,
   legend, and swatch styles above are shared with the clinical reference
   layer; only the grid itself is new. */
.table-wrap {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  margin: 8px -16px; padding: 4px 16px 14px;
}
.bte-grid {
  display: grid;
  grid-template-columns: 26px repeat(18, minmax(58px, 1fr));
  gap: 4px;
  /* 26 + 18 columns at 58 + 19 gaps at 4 is about 1146, so this is the width
     below which the grid must scroll rather than crush the cells. */
  min-width: 1160px;
}

/* Wide screens: break the grid out of the 780px reading column so all
   eighteen columns fit and the scrollbar disappears. 390px is half the
   column, so these margins widen it symmetrically toward the viewport edge,
   less a gutter. The breakpoint is where the result first clears the 1146px
   the grid actually needs.

   overflow-x stays auto rather than visible: if a browser's vw includes the
   scrollbar and the result lands a few pixels over, the table scrolls inside
   itself instead of the whole page scrolling sideways. */
@media (min-width: 1240px) {
  .table-wrap {
    margin-left: calc(390px - 50vw + 24px);
    margin-right: calc(390px - 50vw + 24px);
    padding-left: 0;
    padding-right: 0;
  }
  /* Centred and capped, so an ultrawide monitor gets a readable table rather
     than eighteen enormous cells. */
  .bte-grid {
    max-width: 1600px;
    margin: 0 auto;
  }
}
.rowlab {
  display: flex; align-items: center; justify-content: center;
  font-size: .68rem; font-weight: 700; color: var(--ink-faint);
}
.cell {
  border-radius: 8px; padding: 6px 6px 5px; cursor: pointer; position: relative;
  border: 1px solid transparent; min-height: 58px;
  display: flex; flex-direction: column; justify-content: space-between;
}
.cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cell .sym { font-weight: 800; font-size: .95rem; line-height: 1; }
.cell .nm { font-size: .58rem; line-height: 1.15; opacity: .85; margin-top: 2px; }
.cell .drs { font-size: .6rem; font-weight: 700; opacity: .7; align-self: flex-end; }
.cell .dots { position: absolute; top: 5px; right: 5px; display: flex; gap: 2px; }
.cell .mastery {
  position: absolute; bottom: 4px; left: 6px; width: 18px; height: 3px;
  border-radius: 2px; background: var(--line);
}
.cell .mastery > i { display: block; height: 100%; border-radius: 2px; background: var(--gold); }
.cell.blank { background: transparent; border: 0; cursor: default; }

.dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; }
.dot.r { background: #d05a5a; }
.dot.b { background: #4f83b6; }

.cell.c-low     { background: var(--c-low-bg);     border-color: color-mix(in srgb, var(--c-low) 45%, transparent); color: var(--ink); }
.cell.c-medium  { background: var(--c-med-bg);     border-color: color-mix(in srgb, var(--c-med) 45%, transparent); }
.cell.c-high    { background: var(--c-high-bg);    border-color: color-mix(in srgb, var(--c-high) 45%, transparent); }
.cell.c-highest { background: var(--c-highest-bg); border-color: color-mix(in srgb, var(--c-highest) 50%, transparent); }
.cell.c-variable{ background: var(--c-var-bg);     border-color: color-mix(in srgb, var(--c-var) 45%, transparent); }
.cell.c-face    { background: var(--c-face-bg);    border-color: color-mix(in srgb, var(--c-face) 45%, transparent); }

.sw.c-low     { background: var(--c-low-bg);     border: 1px solid var(--c-low); }
.sw.c-medium  { background: var(--c-med-bg);     border: 1px solid var(--c-med); }
.sw.c-high    { background: var(--c-high-bg);    border: 1px solid var(--c-high); }
.sw.c-highest { background: var(--c-highest-bg); border: 1px solid var(--c-highest); }
.sw.c-variable{ background: var(--c-var-bg);     border: 1px solid var(--c-var); }
.sw.c-face    { background: var(--c-face-bg);    border: 1px solid var(--c-face); }

.hitlist .card { display: flex; align-items: center; gap: 12px; padding: 10px 14px; }
.hit-sym { font-weight: 800; font-size: 1.05rem; width: 44px; }

/* The invite card. Marked with the gold edge the beta offer uses elsewhere,
   because it is an ask rather than a feature. */
.invite-card { border-left: 3px solid var(--gold); }

/* ---------- My Words ----------
   A define zone is any block where double-clicking a word is meant to do
   something. Selection has to stay on (that is the whole mechanism), but on
   iOS the same gesture raises Safari's own Copy | Look Up | Share callout, and
   two menus stacked over one word on a phone screen is a coin toss about which
   one gets tapped. The callout goes. The cost is real and deliberate: copying
   text out of a lesson body on iOS stops working. Everywhere else in the app
   still copies normally, since nothing else carries this attribute. */
[data-define] {
  /* No selection, deliberately. Android Chrome raises a Define / Copy / Share
     / Select all bar the instant text is selected, it lands on top of our own
     popover, and there is no property that hides it: -webkit-touch-callout is
     iOS only. Since the word is now worked out from the tap coordinates
     rather than from a selection, none is needed, and without one neither
     platform has anything to put a bar around.

     The cost is real: lesson text can no longer be selected or copied by
     hand, on any platform. Copy lives on the popover instead. Nothing else in
     the app carries this attribute, so everything else still selects. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  /* Tells the browser this element wants no double-tap-to-zoom, so the second
     tap of a double tap arrives as an event instead of a zoom gesture. */
  touch-action: manipulation;
}
/* Our own highlight on the tapped word, painted through the Custom Highlight
   API so the DOM is never touched. The glossary linker has already rewritten
   this text once; wrapping it again to show a highlight is how you break it. */
::highlight(defword) {
  background: color-mix(in srgb, var(--accent) 32%, transparent);
  color: var(--ink);
}

.defpop {
  position: absolute; z-index: 200; width: min(310px, calc(100vw - 20px));
  background: var(--card); color: var(--ink);
  border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, .28);
  padding: 11px 13px 10px;
  font-size: .9rem; line-height: 1.45;
  /* The popover is chrome over the text, not part of it, so double-tapping
     inside it must not start another lookup. */
  -webkit-user-select: none; user-select: none;
}
.defpop-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.defpop-head b { font-size: 1.02rem; font-family: var(--font-display); }
.defpop-x {
  border: 0; background: transparent; color: var(--ink-dim); cursor: pointer;
  font-size: 1.15rem; line-height: 1; padding: 0 2px;
}
.defpop-head b { flex: 1 1 auto; }
.defpop-copy {
  flex: 0 0 auto; border: 1px solid var(--line); background: transparent;
  color: var(--ink-faint); cursor: pointer; border-radius: 7px;
  font-size: .72rem; font-weight: 600; padding: 3px 8px; line-height: 1.3;
}
.defpop-copy:hover { color: var(--ink); border-color: var(--accent); }
.defpop-x:hover { color: var(--ink); }
.defpop-def { margin: 5px 0 0; }
.defpop-def i, .defpop-senses i { color: var(--ink-dim); font-style: italic; margin-right: 4px; }
.defpop-base { margin: 4px 0 0; font-size: .78rem; }
.defpop-senses { margin: 5px 0 0; display: flex; flex-direction: column; gap: 6px; }
.defpop-senses label { display: flex; gap: 7px; align-items: flex-start; cursor: pointer; }
.defpop-senses input { margin-top: 4px; flex: 0 0 auto; }
.defpop-acts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.defpop-acts:empty { display: none; }
.defpop-btn {
  flex: 1 1 auto; text-align: center; text-decoration: none;
  font-size: .8rem; font-weight: 600; padding: 7px 11px; border-radius: 8px;
  cursor: pointer; background: transparent; color: var(--ink);
  border: 1.5px solid var(--line);
}
.defpop-btn:hover { border-color: var(--accent); }
.defpop-btn.primary { background: var(--accent); color: var(--btn-ink); border-color: var(--accent); }

/* The word list. Built on .adm-row because it is the same shape of thing: a
   record with a couple of actions on it. */
.wordrow .worddef { margin-top: 3px; }
.wordrow .wordctx {
  margin-top: 4px; padding-left: 9px; border-left: 2px solid var(--line); font-style: italic;
}
/* The sentence a word was met in, under its answer during review. */
.fc-ctx {
  margin-top: 9px; padding-left: 10px; border-left: 2px solid var(--line);
  font-style: italic; font-size: .86rem; text-align: left;
}

/* ---------- admin mail client ---------- */
.mailbadge {
  display: inline-block; margin-left: 6px; min-width: 17px; padding: 0 5px;
  border-radius: 999px; background: var(--accent); color: var(--btn-ink);
  font-size: .68rem; font-weight: 800; line-height: 17px; text-align: center;
}
.adm-tab.on .mailbadge { background: var(--btn-ink); color: var(--ink); }
.mailrow { cursor: pointer; }
.mailrow:hover { background: color-mix(in srgb, var(--ink) 5%, transparent); }
.mailrow.unread b { font-weight: 800; }
.mailrow.unread { border-left: 3px solid var(--accent); padding-left: 8px; }
.mailrow.open { background: color-mix(in srgb, var(--ink) 9%, transparent); }
/* The row's own buttons sit at its right edge and stay out of the way until
   the row is under the cursor, so a full list reads as mail rather than as a
   wall of controls. Touch has no hover, so there they are simply always on. */
.mailrow .acts { flex: 0 0 auto; align-self: center; }
@media (hover: hover) and (min-width: 601px) {
  .mailrow .acts { opacity: 0; transition: opacity .12s; }
  .mailrow:hover .acts,
  .mailrow:focus-within .acts { opacity: 1; }
}

/* Reading pane.
   Narrow: the message opens above the list, which is where it has always
   opened and where a thumb expects it. Wide: list on the left, message on
   the right, and the message stays put while the list scrolls under it. */
.mailsplit { display: flex; flex-direction: column; }
.mailsplit .mailread { order: -1; }
.mailsplit .mailread:empty { display: none; }
/* A mail row is a name, a subject, a preview, and now four buttons, and in a
   420px column the default side-by-side .adm-row gave the buttons most of the
   width and left the sender wrapping one word per line. The buttons go under
   the text instead, which is the same thing the phone layout already does. */
.mailsplit .adm-row { flex-direction: column; align-items: stretch; gap: 8px; }
.mailsplit .adm-row .acts { width: 100%; }

/* An open message now carries five actions, and .btn-row stretches its
   children to fill one line with no wrap. On a phone that squeezed five
   full-size buttons into 358px and the labels ran off the right edge. Here
   they take their natural width, wrap when they must, and sit at toolbar
   scale rather than at thumb-sized-primary-action scale. */
.mailread .btn-row { flex-wrap: wrap; }
.mailread .btn-row .btn { flex: 0 1 auto; padding: 9px 17px; font-size: .86rem; }
@media (min-width: 1100px) {
  /* A real two pane mail client: the page itself does not scroll, and each
     column scrolls inside its own box.

     This was a sticky pane with max-height: calc(100dvh - 28px), which is the
     right height only if the pane starts at the top of the window. It does
     not. It starts under the console heading, the tab strip, the box chips
     and the address line, about 355px down, so a full height pane hung 327px
     below the fold and you had to scroll the page to reach the end of a
     message. Sticky then dragged the pane along as you scrolled, which is why
     it felt like the message was moving while you read it.

     --mail-top is where the split actually begins, measured by the script,
     because no CSS expression can know how tall the chrome above it is. */
  .mailsplit {
    display: grid; grid-template-columns: minmax(320px, 460px) 1fr;
    gap: 22px; align-items: stretch;
    /* The floor is a backstop for a genuinely tiny window, not a target. At
       340 it was itself the reason a 1366x768 laptop still scrolled: the space
       available was 319 and the floor insisted on 340, so the page overflowed
       by the difference. 260 clears every laptop size worth designing for and
       still refuses to shrink the pane into uselessness. */
    height: max(260px, calc(100dvh - var(--mail-top, 380px)));
  }
  /* min-height: 0 on both, or a grid child refuses to shrink below its
     content and overflows the row instead of scrolling inside it. */
  .mailsplit .adm-list { min-width: 0; min-height: 0; overflow-y: auto; }
  .mailsplit .mailread {
    order: 0; min-height: 0; overflow-y: auto;
    position: static; max-height: none;
  }
  /* An empty pane still has to hold the column open, or the list snaps to
     full width the moment you close a message and jumps back when you open
     the next one. */
  .mailsplit .mailread:empty { display: block; }
  /* The pane's own card carries the padding, so the scrollbar sits at the
     edge of the column rather than inside the card. */
  .mailsplit .mailread > .card { margin-top: 0; }
}
/* Mail bodies are pre-wrapped: senders rely on their own line breaks, and a
   quoted reply is unreadable if the indentation collapses. */
pre.mailbody {
  white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere;
  font: 400 .92rem/1.55 var(--font-ui); color: var(--ink);
  background: transparent; margin: .6em 0; padding: 0; max-width: 100%;
}
.btn.tiny { padding: 4px 10px; font-size: .74rem; align-self: flex-start; flex: 0 0 auto; }

/* Inline commands in lesson text. Meant to be read as something you type,
   and deliberately left out of the tap-to-define pass. */
code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: .88em;
  background: color-mix(in srgb, var(--ink) 7%, transparent);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 1px 5px;
  overflow-wrap: anywhere;
}

/* Report-a-problem dialog. Its own class: the existing .sheet is a bottom
   sheet with different open/close mechanics and reusing it broke both. */
.flagdlg {
  position: fixed; inset: 0; z-index: 9000;
  background: rgba(15, 22, 34, .55);
  display: grid; place-items: center; padding: 18px;
  animation: flagfade .16s ease-out;
}
@keyframes flagfade { from { opacity: 0 } to { opacity: 1 } }
.flagdlg-in {
  background: var(--card); color: var(--ink);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 20px; width: 100%; max-width: 420px;
  max-height: 86vh; overflow-y: auto;
}
.flagdlg-in h2 { font-size: 1.15rem; }
.flagdlg-in .btn.ghost { background: transparent; border: 1px solid var(--line); color: var(--ink); }
.flagdlg-in .btn.ghost:hover { border-color: var(--accent); color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .flagdlg { animation: none } }

/* The feedback invitation.

   It glows only after three finished units and only until the person has sent
   something once. It is a card in the normal flow of the home screen, never a
   modal and never a blocker: the whole point of the redesign on 4 August 2026
   is that feedback is wanted, not required. Once given, the glow never returns
   but the card stays, because people should be able to write in whenever. */
.fbglow { animation: fbglow 2.6s ease-in-out infinite; }
@keyframes fbglow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(212, 165, 74, 0); }
  50%      { box-shadow: 0 0 0 4px rgba(212, 165, 74, .22); }
}
@media (prefers-reduced-motion: reduce) {
  .fbglow { animation: none; box-shadow: 0 0 0 3px rgba(212, 165, 74, .25); }
}

/* ---------- progress view ----------
   A twelve week grid of the days a unit was finished, plus a mastery bar per
   course. Built from data the app already kept (each unit stores the day it
   was first passed), so nothing new is tracked to draw it. */
.pg-grid { display: flex; gap: 4px; overflow-x: auto; padding-bottom: 4px; }
.pg-week { display: grid; grid-template-rows: repeat(7, 1fr); gap: 4px; flex: 1 0 auto; }
.pg-cell {
  display: block; width: 100%; aspect-ratio: 1; min-width: 11px;
  border-radius: 3px; background: var(--line);
}
.pg-cell.l1 { background: color-mix(in srgb, var(--gold) 32%, var(--line)); }
.pg-cell.l2 { background: color-mix(in srgb, var(--gold) 58%, var(--line)); }
.pg-cell.l3 { background: color-mix(in srgb, var(--gold) 80%, var(--line)); }
.pg-cell.l4 { background: var(--gold); }
.pg-cell.pg-future { opacity: .35; }
.pg-key { display: flex; align-items: center; gap: 5px; margin-top: 10px; font-size: .74rem; color: var(--ink-faint); }
.pg-key .pg-cell { width: 11px; min-width: 11px; }
.pg-course { display: block; text-decoration: none; color: inherit; padding: 10px 0; border-top: 1px solid var(--line); }
.pg-course:first-of-type { border-top: 0; }
.pg-course-top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-size: .95rem; }
.pg-course-top span { font-size: .8rem; color: var(--ink-faint); white-space: nowrap; }
.pg-bar { display: block; height: 7px; border-radius: 4px; background: var(--line); overflow: hidden; margin-top: 6px; }
.pg-bar > i { display: block; height: 100%; background: var(--gold); border-radius: 4px; }

/* ==========================================================================
   Charts (admin Genie health panel)

   The two series hues are NOT brand colors. Coral and gold already mean
   "button" and "streak" everywhere else in this app, and a chart that borrows
   them makes the reader ask whether a bar is clickable. These are picked for
   one job only, telling marks apart, and were validated against the actual
   card surfaces this panel renders on (#ffffff light, #16273f night): worst
   pair separation 9.2 light and 9.4 dark on the colorblind check, against a
   floor of 8. Do not swap them for brand colors without re-running that.

   Chart chrome deliberately reuses the app's own tokens (--line, --ink-faint,
   --card) so the plots sit inside the console rather than on top of it.
   ========================================================================== */
.viz { --s1: #2a78d6; --s2: #eb6834; --viz-surface: var(--card); }
@media (prefers-color-scheme: dark) {
  .viz { --s1: #3987e5; --s2: #d95926; }
}
.viz { position: relative; margin: 10px 0 2px; }
/* The SVG carries explicit width and height attributes matching the container
   it was measured against, so it renders at scale 1 and an 11px label is 11
   real pixels. max-width is the guard for the case the container shrank
   between the measurement and the paint: it scales down rather than
   overflowing, and the resize handler redraws it properly a moment later. */
.viz svg { display: block; max-width: 100%; height: auto; overflow: visible; }
/* Hairline, solid, one step off the surface. Never dashed: a dashed grid
   reads as "projection" when it is only a ruler. */
.viz .vx-grid { stroke: var(--line); stroke-width: 1; }
.viz .vx-base { stroke: var(--line); stroke-width: 1; }
.viz text { font-family: var(--font-ui); fill: var(--ink-faint); font-size: 11px; }
.viz .vx-tick { font-variant-numeric: tabular-nums; }
.viz .vx-val { fill: var(--ink-soft); font-size: 11px; font-weight: 600; }
/* The 2px separation between touching marks is drawn in the surface color,
   never as a stroke around the mark. */
.viz .vx-bar { stroke: var(--viz-surface); stroke-width: 2; }
.viz .vx-hit { fill: transparent; cursor: default; }
.viz .vx-hit:hover ~ .vx-bar, .viz .vx-bar:hover { filter: brightness(1.08); }

.viz-tip {
  position: absolute; z-index: 5; pointer-events: none; opacity: 0;
  transform: translate(-50%, -100%); transition: opacity .12s ease;
  background: var(--ink); color: var(--paper); border-radius: 8px;
  padding: 7px 9px; font-size: .78rem; line-height: 1.35; white-space: nowrap;
  box-shadow: 0 2px 10px rgba(0,0,0,.22);
}
.viz-tip.on { opacity: 1; }
.viz-tip b { font-variant-numeric: tabular-nums; }

.viz-legend { display: flex; flex-wrap: wrap; gap: 12px; margin: 8px 0 0; font-size: .78rem; color: var(--ink-soft); }
.viz-legend i { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }

.viz-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.viz-table { width: 100%; border-collapse: collapse; font-size: .8rem; margin-top: 8px; }
.viz-table th, .viz-table td { text-align: left; padding: 5px 8px 5px 0; border-bottom: 1px solid var(--line); }
.viz-table td.n, .viz-table th.n { text-align: right; font-variant-numeric: tabular-nums; }
.viz-none { color: var(--ink-faint); font-size: .85rem; padding: 14px 0; }

/* Stat tiles. Proportional figures on purpose: tabular-nums makes a big
   standalone number look loose, and these are display sized. */
.viz-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; }
.viz-tile { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 11px 13px; }
.viz-tile .k { font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-faint); }
.viz-tile .v { font-family: var(--font-ui); font-size: 1.55rem; font-weight: 600; color: var(--ink); margin: 3px 0 1px; }
.viz-tile .s { font-size: .74rem; color: var(--ink-faint); line-height: 1.35; }
.viz-hero .v { font-size: 2.5rem; line-height: 1.05; }

.viz-meter { display: block; height: 7px; border-radius: 4px; background: var(--line); overflow: hidden; margin-top: 5px; }
.viz-meter > i { display: block; height: 100%; border-radius: 4px; background: var(--s1); }

/* The sending-address picker at the top of the Mail tab. Inline with its label
   and the reply note, because it is a status line that happens to be editable,
   not a form. */
.mailfrom { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin: 0 0 6px; }
.mailfrom label { font-weight: 600; color: var(--ink-soft); }
.mailfrom select.input {
  width: auto; min-width: 0; flex: 0 1 auto;
  padding: 5px 26px 5px 9px; font-size: .86rem; margin: 0;
}

/* The admin console's own heading, compacted on wide screens.

   The Mail tab is a two pane client that has to fit the window, and on a
   1366x768 laptop (the most common Windows screen there is) the console's
   heading, subtitle and tab strip were eating 362px before the mail even
   started. That left 188px for the mail itself, so the page scrolled no matter
   what the split did.

   Compacted rather than deleted: on one line you still see where you are and
   who you are signed in as. Narrow screens keep the roomy stacked version,
   where vertical space is cheap because the layout scrolls anyway. */
@media (min-width: 1100px) {
  .adm-head {
    display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px;
    margin: 0 0 8px;
  }
  .adm-head .eyebrow { margin: 0; }
  .adm-head h1 { font-size: 1.15rem; margin: 0; }
  .adm-head p.dim { margin: 0; }
  #app { padding-top: 14px; }
  .adm-tabs { margin-bottom: 8px; }
  /* An empty status line still occupied its margins, which is 8px of a budget
     measured in tens. */
  #mailFromMsg:empty { display: none; }
}

/* The subject field. A textarea wearing an input's clothes: it wraps and grows
   so a long subject is readable in full, but it is one line until it needs to
   be two, and Enter does not belong to it. */
.subjfield {
  resize: none; overflow-y: auto; min-height: 0;
  font: inherit; line-height: 1.4;
}

/* Form fields.

   `.input` and `.input.block` are used on 22 fields across the admin console,
   and until 7 August 2026 NEITHER CLASS EXISTED IN THIS FILE. Every one of
   them was rendering as an unstyled browser-default input: 177px wide,
   whatever the container. That is why a subject line could not be read while
   it was being typed, and it was never a subject-specific problem. The field
   was simply never told to fill its box.

   Matched to textarea.fbtext, which was styled, so a form made of both stops
   looking like two different forms. */
.input {
  border: 1.5px solid var(--line); border-radius: 12px; background: var(--paper);
  padding: 10px 12px; font: inherit; font-size: .92rem; color: var(--ink);
  max-width: 100%;
}
.input:focus { outline: 2px solid var(--accent); border-color: transparent; }
.input.block { display: block; width: 100%; margin-top: 6px; }
