/* ui.css — the design system of the new Band9 Studio frontend.
 *
 * This is the prototype (design-edu/band9-prototip.html) turned into something a
 * server can ship: the same three themes, the same components, plus the parts a
 * prototype never needs — empty states, errors, forms, a phone layout, print.
 *
 * NOTHING IN public/ IS TOUCHED. The old frontend keeps its own styles.css and
 * its own palette. These two designs share a server and a database, not a
 * stylesheet, and either can be served without the other.
 *
 * WHY PLAIN CSS AND CUSTOM PROPERTIES, no build step: the target is a mid-range
 * Android WebView. No color-mix(), no :has(), no nesting, no container queries —
 * every rule here works in a 2021 browser.
 *
 * A THEME IS A SET OF TOKENS AND NOTHING ELSE. Three of them ship: quyosh (warm
 * paper, black rail), binafsha (white, purple), tungi (night). A screen may
 * never name a colour — it names a token, and the theme decides. The handful of
 * per-theme overrides further down are the exceptions that earn their place, and
 * each says why.
 */

@import url("/assets/fonts/faces.css");

/* ============================================================ base */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--fb);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: inherit; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
img, svg { max-width: 100%; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--accent-soft); color: var(--ink); }

/* Screen-reader-only, but focusable — the skip link. */
.vh {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.vh:focus {
  position: fixed; top: 10px; left: 10px; width: auto; height: auto;
  clip: auto; margin: 0; padding: 10px 16px; z-index: 90;
  background: var(--card); color: var(--ink); border-radius: var(--rs);
  box-shadow: 0 6px 24px rgba(0,0,0,.18);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* ============================================================ themes */
/* QUYOSH — warm paper, a black floating rail, orange accent. The daylight
 * classroom theme, and the one nearest the app that exists today. */
:root, :root[data-theme="quyosh"] {
  --bg: #f6f1e8; --side: #1b1c20; --side-ink: #f4f2ec; --side-mut: #9a9aa4; --side-line: #303138;
  --act-bg: #33343c; --act-ink: #ffffff;
  --card: #fffdf8; --card2: #f0ead9; --line: #e5ddc9; --line2: #d8cfb6;
  --ink: #1c1d21; --ink2: #45443f; --mut: #71706a;
  --accent: #ee5a2e; --on-accent: #ffffff; --accent-soft: #fde3d8;
  --low: #c22f1d; --mid: #96660a; --good: #1c1d21; --high: #1e7a3f;
  --low-soft: #fbe3de; --mid-soft: #f8ecd2; --high-soft: #ddf1e2;
  --t1: #ffd166; --t1i: #3d2e04; --t2: #ddcdf9; --t2i: #2c1f52;
  --t3: #c3e2f8; --t3i: #10395a; --t4: #cfeeda; --t4i: #114a26;
  /* The four criteria lines, colour-blind separable on this ground. */
  --c1: #2563cf; --c2: #b45f06; --c3: #9333b8; --c4: #15803c;
  --grid: #e5ddc9; --axis: #cfc6ae;
  --r: 22px; --rs: 12px; --pill: 999px;
  --fd: "Bricolage Grotesque", "Avenir Next", "Trebuchet MS", sans-serif;
  --fb: "Plus Jakarta Sans", "Avenir Next", ui-sans-serif, system-ui, sans-serif;
  --shadow: 0 1px 2px rgba(40,30,10,.05);
  --shadow-up: 0 10px 30px rgba(40,30,10,.10);
  color-scheme: light;
}

/* BINAFSHA — white, tight radii, purple accent. The theme for a projector and
 * for anyone who finds the warm ground busy. */
:root[data-theme="binafsha"] {
  --bg: #f6f6fa; --side: #ffffff; --side-ink: #17181d; --side-mut: #7c7f8d; --side-line: #e9e9f1;
  --act-bg: #efecfd; --act-ink: #5241cc;
  --card: #ffffff; --card2: #f1f1f7; --line: #e8e8f0; --line2: #d9d9e5;
  --ink: #17181d; --ink2: #42444e; --mut: #74767f;
  --accent: #6553e0; --on-accent: #ffffff; --accent-soft: #efecfd;
  --low: #cb2e2e; --mid: #9c6205; --good: #17181d; --high: #14824a;
  --low-soft: #fbe6e6; --mid-soft: #f8eed6; --high-soft: #ddf3e6;
  --t1: #ffedbe; --t1i: #6b4c05; --t2: #e9e3fd; --t2i: #4534b3;
  --t3: #ddeffd; --t3i: #155081; --t4: #def4e6; --t4i: #14653a;
  --c1: #2563cf; --c2: #b45f06; --c3: #9333b8; --c4: #15803c;
  --grid: #ececf2; --axis: #d5d5df;
  --r: 14px; --rs: 10px; --pill: 10px;
  --fd: "Figtree", "Avenir Next", ui-sans-serif, system-ui, sans-serif;
  --fb: "Figtree", "Avenir Next", ui-sans-serif, system-ui, sans-serif;
  --shadow: 0 1px 3px rgba(23,24,40,.06);
  --shadow-up: 0 10px 30px rgba(23,24,40,.12);
  color-scheme: light;
}

/* TUNGI — night. Evening study, and the theme the phone's own dark mode maps
 * onto. The chart line colours change here, not out of taste: on #171e28 the
 * light set drops below the lightness band where four lines stay separable. */
:root[data-theme="tungi"] {
  --bg: #0e141b; --side: #0a0f15; --side-ink: #e9edf3; --side-mut: #7f8ba0; --side-line: #1c2430;
  --act-bg: #14323a; --act-ink: #5ad0dc;
  --card: #171e28; --card2: #202a37; --line: #26303d; --line2: #33404f;
  --ink: #e9edf3; --ink2: #c3ccd9; --mut: #93a0b4;
  --accent: #5ad0dc; --on-accent: #06262c; --accent-soft: #143038;
  --low: #ff8272; --mid: #e3b34f; --good: #e9edf3; --high: #66d692;
  --low-soft: #3a1f1c; --mid-soft: #362b10; --high-soft: #14301f;
  --t1: #33290e; --t1i: #ffd166; --t2: #261e42; --t2i: #cbb8f5;
  --t3: #10293c; --t3i: #8fc7ee; --t4: #143020; --t4i: #9adfae;
  --c1: #4f88e0; --c2: #c5811f; --c3: #b164da; --c4: #2fa25f;
  --grid: #26303d; --axis: #33404f;
  --r: 16px; --rs: 10px; --pill: 999px;
  --fd: "Lora", "Iowan Old Style", Georgia, serif;
  --fb: "Inter", ui-sans-serif, system-ui, sans-serif;
  --shadow: none;
  --shadow-up: 0 10px 30px rgba(0,0,0,.5);
  color-scheme: dark;
}

/* ============================================================ shell */
.shell { display: flex; min-height: 100vh; min-height: 100dvh; }

/* -------- sidebar */
.side {
  width: 236px; flex-shrink: 0;
  background: var(--side); color: var(--side-ink);
  display: flex; flex-direction: column;
  padding: 22px 14px 18px;
  transition: width .25s ease, padding .25s ease, transform .25s ease;
  overflow: hidden;
  position: sticky; top: 0; height: 100vh; height: 100dvh;
  z-index: 40;
}
:root[data-theme="quyosh"] .side { margin: 14px; border-radius: 24px; height: calc(100vh - 28px); height: calc(100dvh - 28px); }
:root[data-theme="binafsha"] .side { border-right: 1px solid var(--side-line); }
:root[data-theme="tungi"] .side { border-right: 1px solid var(--side-line); }

.logo { display: flex; align-items: center; gap: 10px; padding: 0 10px 22px; text-decoration: none; }
.logo svg { flex-shrink: 0; }
.logo b { font-family: var(--fd); font-size: 17px; font-weight: 700; line-height: 1.1; display: block; }
.logo small { font-size: 10px; letter-spacing: .08em; color: var(--side-mut); font-weight: 600; }

.tgl {
  margin-left: auto; border: 0; background: transparent; color: var(--side-mut);
  width: 28px; height: 28px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  transition: transform .25s, color .15s, background .15s;
}
.tgl:hover { color: var(--side-ink); background: var(--act-bg); }

.ngroup { font-size: 10.5px; font-weight: 700; letter-spacing: .1em; color: var(--side-mut); padding: 10px 12px 6px; }
.nav { display: flex; flex-direction: column; gap: 3px; }
.nav a {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; border-radius: var(--rs);
  color: var(--side-mut); font-size: 14px; font-weight: 600; text-decoration: none;
  transition: background .15s, color .15s;
}
.nav a:hover { color: var(--side-ink); }
.nav a.active { background: var(--act-bg); color: var(--act-ink); }
.nav a svg { flex-shrink: 0; }
.nav .cnt {
  margin-left: auto; font-size: 11px; font-weight: 800;
  background: var(--accent); color: var(--on-accent);
  border-radius: 999px; padding: 2px 8px;
}
.sfoot { margin-top: auto; display: flex; flex-direction: column; gap: 3px; }
/* THE USER BLOCK IS A LINK NOW, AND IT SITS INSIDE `.sfoot nav`. That container
 * carries both classes, so `.nav a` (0,1,1) outranks a bare `.user` (0,1,0) and
 * would quietly hand this block the nav's padding, its 12px gap and — the one
 * you would actually see — its --side-mut ink, greying out the student's own
 * name. Every declaration below that undoes one of those is written as
 * `a.user`, which is (0,1,1) and later, so it wins on order. */
a.user, .user { display: flex; align-items: center; gap: 10px; margin-top: 12px; padding: 12px 10px 0; border-top: 1px solid var(--side-line); }
a.user { color: var(--side-ink); text-decoration: none; border-radius: 0; }
.user b { font-size: 13px; display: block; }
.user small { font-size: 11px; color: var(--side-mut); }
/* The rail's own links go light on hover; this one is already light, so it says
   so with its ground instead — and the avatar is what the eye is on. */
a.user:hover { color: var(--side-ink); }
a.user:hover .ava { background: var(--act-ink); color: var(--act-bg); }
a.user.active .ava { background: var(--act-ink); color: var(--act-bg); }
/* An exam strips the href and marks it disabled; it must stop looking pressable. */
a.user[aria-disabled="true"] { cursor: default; }
a.user[aria-disabled="true"]:hover .ava { background: var(--act-bg); color: var(--act-ink); }
.ava {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--act-bg); color: var(--act-ink);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700; flex-shrink: 0;
}

/* The purse: one quiet line under the user block, in the rail's own palette.
 * It lives HERE rather than with the credit card at the foot of this file for
 * the reason the phone block below spells out — .side-min has to be undone at
 * 780px selector by selector, and a rule appended after that block would win
 * against its own undo and fold the drawer's copy of this line at 250px wide.
 *
 * .low CARRIES ITS OWN GROUND, and that is the one thing here colour alone
 * could not do. The rail is near-black in quyosh, white in binafsha and
 * near-black again in tungi, while --mid — the warning ink — is a dark amber
 * built for a card: on quyosh's black rail it is barely legible. The warn PAIR
 * (--mid-soft ground, --mid ink) is the one .notice-warn already uses and reads
 * in all three themes because it brings its own ground with it. */
.purse {
  display: flex; align-items: baseline; gap: 5px;
  margin-top: 8px; padding: 6px 10px;
  font-size: 11.5px; font-weight: 600; color: var(--side-mut);
}
.purse b { font-size: 14px; color: var(--side-ink); }
.purse.low { background: var(--mid-soft); color: var(--mid); border-radius: var(--rs); }
.purse.low b { color: var(--mid); }

/* Collapsed: an icon rail. Set by the student's own toggle, and by the exam —
 * which collapses the rail rather than fading it, because a faded panel is
 * still a panel taking the room and now unreadable as well. */
.side-min .side { width: 74px; padding-left: 11px; padding-right: 11px; }
.side-min .logo { flex-direction: column; gap: 12px; padding: 0 0 14px; align-items: center; }
.side-min .logo > div { display: none; }
.side-min .tgl { margin-left: 0; transform: rotate(180deg); }
.side-min .ngroup { height: 1px; padding: 0; margin: 10px 8px; background: var(--side-line); overflow: hidden; font-size: 0; }
.side-min .nav a { font-size: 0; gap: 0; justify-content: center; padding: 11px 0; }
.side-min .nav a .cnt { display: none; }

.side-min .user { justify-content: center; padding-left: 0; padding-right: 0; }
.side-min .user > div { display: none; }
/* 74px has room for the number and nothing else. The word goes to font-size:0
   rather than display:none — the same trick .side-min .nav a uses two rules
   above, and for the same reason: the label leaves the picture but stays in the
   accessibility tree, so a folded rail still reads «82 kredit» aloud instead of
   a bare number. The sentence is on the title attribute for everyone else. */
.side-min .purse { justify-content: center; gap: 0; padding-left: 0; padding-right: 0; }
.side-min .purse span { font-size: 0; }

/* -------- content */
.content {
  flex-grow: 1; min-width: 0;
  /* Capped, because past about 1300px the essay table and the marked essay stop
     being readable — a line of prose the width of a cinema is not a feature.
     CENTRED, because the cap has to leave the surplus SOMEWHERE and all of it
     against one edge reads as a bug rather than as a measure: on a wide screen
     with the rail folded to 74px it left ~360px of nothing down the right. The
     margin is the same decision either way; this is the half that looks like
     one was made. */
  max-width: 1320px;
  margin-inline: auto;
  padding: 26px 30px 40px;
  display: flex; flex-direction: column; gap: 18px;
}
.content > * { min-width: 0; }

/* The phone's top bar. Hidden on a desktop, where the rail is always visible. */
.topbar { display: none; }

/* ============================================================ page furniture */
.htop { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.htop .date { font-size: 13px; color: var(--mut); }
.htop h1, .htop h2 {
  margin: 2px 0 0; font-family: var(--fd); font-size: 27px; font-weight: 700;
  color: var(--ink); line-height: 1.2; text-wrap: balance;
}
.htop .back { display: inline-flex; align-items: center; gap: 6px; }

.seg { display: flex; background: var(--card2); border-radius: 10px; padding: 3px; }
.seg button, .seg span {
  font-size: 12.5px; font-weight: 700; padding: 5px 12px; border-radius: 8px;
  color: var(--mut); border: 0; background: transparent;
}
.seg .on { background: var(--card); color: var(--ink); box-shadow: var(--shadow); }

/* ============================================================ primitives */
.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  padding: 20px 24px; box-shadow: var(--shadow);
}
.card h3 { margin: 0; font-family: var(--fd); font-size: 18px; font-weight: 700; color: var(--ink); }
.card.col { display: flex; flex-direction: column; gap: 12px; }
.cardhead { display: flex; align-items: center; justify-content: space-between; gap: 12px; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--accent); color: var(--on-accent);
  font-size: 14px; font-weight: 700; padding: 11px 20px;
  border-radius: var(--pill); border: 0;
  transition: filter .15s, opacity .15s;
  text-decoration: none;
}
.btn:hover { filter: brightness(1.06); }
.btn.ghost { background: transparent; color: var(--ink); border: 1.5px solid var(--line2); }
.btn.danger { background: transparent; color: var(--low); border: 1.5px solid var(--low); }
.btn.sm { padding: 8px 14px; font-size: 13px; }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; filter: none; }

.link {
  font-size: 13px; font-weight: 600; color: var(--mut);
  text-decoration: underline; text-underline-offset: 3px;
  cursor: pointer; background: none; border: 0; padding: 0;
}
.link:hover { color: var(--ink); }

.chips { display: flex; gap: 8px; flex-wrap: wrap; }
.chip {
  font-size: 12.5px; font-weight: 700; background: var(--card2); color: var(--ink2);
  border: 0; border-radius: 999px; padding: 7px 14px;
  transition: background .15s, color .15s;
}
.chip:hover { color: var(--ink); }
.chip.on { background: var(--ink); color: var(--card); }
/* On the night ground an inverted chip would be a white slab; the accent reads
 * as "chosen" there without shouting. */
:root[data-theme="tungi"] .chip.on { background: var(--accent); color: var(--on-accent); }

.tag {
  font-size: 11px; font-weight: 800; background: var(--card2); color: var(--ink2);
  border-radius: 6px; padding: 4px 8px; flex-shrink: 0;
}
.eyebrow { font-size: 11px; font-weight: 800; letter-spacing: .1em; color: var(--accent); }
.rm { font-size: 12px; color: var(--mut); }
.mut { color: var(--mut); }
.num { font-family: var(--fd); font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }

/* ============================================================ grids */
.grid2 { display: grid; grid-template-columns: 2fr 1fr; gap: 16px; align-items: start; }
.grid3 { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 16px; }
.grid4 { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 14px; }
.gridhalf { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 16px; align-items: start; }
.split { display: grid; grid-template-columns: 1fr 360px; gap: 16px; align-items: start; }
.split-l { display: grid; grid-template-columns: 320px 1fr; gap: 16px; align-items: start; }
/* THE TASK 1 SHEET IS HALF FIGURE, HALF PAPER. A student describing a chart
 * looks from one to the other every sentence, and a 320px column made them
 * squint at the thing they were being asked to describe. Its own modifier
 * rather than changing .split-l, which the vocabulary screens also use. */
.split-l.half { grid-template-columns: 1fr 1fr; }

/* ============================================================ hero */
.hero { display: flex; overflow: hidden; padding: 0; }
.hero-l { flex-grow: 1; padding: 24px 28px; display: flex; flex-direction: column; gap: 10px; }
.hero h2 { margin: 0; font-family: var(--fd); font-size: 25px; font-weight: 700; color: var(--ink); }
.hero p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--ink2); max-width: 520px; }
.hero-act { display: flex; align-items: center; gap: 18px; margin-top: 6px; flex-wrap: wrap; }
.hero-r {
  width: 300px; flex-shrink: 0; border-left: 1px solid var(--line);
  background: repeating-linear-gradient(var(--card) 0 33px, var(--line) 33px 34px);
  position: relative; display: flex; flex-direction: column; justify-content: flex-end;
  padding: 20px 22px 20px 34px;
}
.hero-r::before { content: ""; position: absolute; top: 0; bottom: 0; left: 22px; width: 1px; background: var(--accent-soft); }
.hero-r small { font-size: 10px; font-weight: 800; letter-spacing: .08em; color: var(--mut); }
.hero-r em { font-family: var(--fd); font-style: italic; font-size: 14px; line-height: 33px; color: var(--ink2); }
/* Quyosh alone paints the hero: on warm paper a white card next to a black rail
 * is the only flat thing on the page, and the amber lifts it. */
:root[data-theme="quyosh"] .hero { background: var(--t1); border-color: var(--t1); }
:root[data-theme="quyosh"] .hero .eyebrow { color: #a33c12; }
:root[data-theme="quyosh"] .hero-l * { color: var(--t1i); }
:root[data-theme="quyosh"] .hero-l .btn { background: #1c1d21; color: #ffedbe; }
:root[data-theme="quyosh"] .hero-l .link { color: #6a5416; }

/* ============================================================ stats */
.stat { border-radius: var(--r); padding: 18px 22px; display: flex; flex-direction: column; gap: 7px; border: 1px solid transparent; }
.stat .lbl { font-size: 13px; font-weight: 600; }
.stat .num { font-size: 40px; }
.stat .num small { font-size: 16px; font-weight: 600; }
.stat .sub { font-size: 12.5px; font-weight: 600; display: flex; align-items: center; gap: 6px; }
.stat.t1 { background: var(--t1); color: var(--t1i); } .stat.t1 * { color: var(--t1i); }
.stat.t2 { background: var(--t2); color: var(--t2i); } .stat.t2 * { color: var(--t2i); }
.stat.t3 { background: var(--t3); color: var(--t3i); } .stat.t3 * { color: var(--t3i); }
.stat.t4 { background: var(--t4); color: var(--t4i); } .stat.t4 * { color: var(--t4i); }
.stat.plain { background: var(--card); border-color: var(--line); box-shadow: var(--shadow); }

.track { height: 7px; border-radius: 999px; background: rgba(0,0,0,.14); overflow: hidden; }
:root[data-theme="tungi"] .track { background: rgba(255,255,255,.14); }
.track i { display: block; height: 100%; border-radius: 999px; background: currentColor; }

/* ============================================================ lists */
.row {
  display: flex; align-items: center; gap: 16px; padding: 12px 0;
  border-bottom: 1px solid var(--line); cursor: pointer;
  background: none; border-left: 0; border-right: 0; border-top: 0;
  width: 100%; text-align: left;
}
.row:last-child { border-bottom: 0; padding-bottom: 0; }
.row:hover .rt { color: var(--accent); }
.row > div { min-width: 0; }
.bnd { font-family: var(--fd); font-size: 20px; font-weight: 700; width: 44px; flex-shrink: 0; font-variant-numeric: tabular-nums; }
.rt { font-size: 14px; font-weight: 600; color: var(--ink); transition: color .12s; }
.chev { margin-left: auto; color: var(--mut); flex-shrink: 0; }
.clip { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.qrow {
  display: flex; align-items: center; gap: 14px; padding: 12px 0;
  border-top: 1px solid var(--line); width: 100%; text-align: left;
  background: none; border-left: 0; border-right: 0; border-bottom: 0; cursor: pointer;
}
.qrow:hover .qq { color: var(--accent); }
.qq { font-size: 14px; color: var(--ink); flex-grow: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: color .12s; }
.qt { font-size: 12px; color: var(--mut); flex-shrink: 0; }
.qs { font-size: 12.5px; font-weight: 700; flex-shrink: 0; width: 100px; text-align: right; font-variant-numeric: tabular-nums; }

.thead, .trow { display: grid; grid-template-columns: 70px 1fr 64px 56px 48px; gap: 12px; align-items: center; }
.thead { padding: 6px 0; border-bottom: 1px solid var(--line2); }
.thead span { font-size: 11px; font-weight: 800; letter-spacing: .06em; color: var(--mut); }
.trow { padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 13.5px; cursor: pointer; text-align: left; background: none; border-left: 0; border-right: 0; border-top: 0; width: 100%; }
.trow:last-child { border-bottom: 0; }
.trow .b { font-family: var(--fd); font-size: 16px; font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }
.right { text-align: right; }

/* ============================================================ mode cards */
.mode {
  border-radius: var(--r); padding: 18px 20px;
  display: flex; flex-direction: column; gap: 8px;
  border: 1px solid transparent; cursor: pointer; text-align: left;
  transition: transform .15s;
}
.mode:hover { transform: translateY(-2px); }
.mode .mh { display: flex; align-items: center; justify-content: space-between; }
.mode h4 { margin: 0; font-family: var(--fd); font-size: 16.5px; font-weight: 700; }
.mode p { margin: 0; font-size: 12.5px; line-height: 1.5; opacity: .85; }
.mode .min { font-size: 11px; font-weight: 800; border-radius: 999px; padding: 3px 9px; background: rgba(0,0,0,.14); }
:root[data-theme="tungi"] .mode .min { background: rgba(255,255,255,.12); }
.mode.t1 { background: var(--t1); color: var(--t1i); }
.mode.t2 { background: var(--t2); color: var(--t2i); }
.mode.t3 { background: var(--t3); color: var(--t3i); }
.mode.t4 { background: var(--t4); color: var(--t4i); }

.exam {
  display: flex; align-items: center; gap: 18px;
  background: var(--side); color: var(--side-ink);
  border-radius: var(--r); padding: 18px 24px; flex-wrap: wrap;
}
.exam h4 { margin: 0; font-family: var(--fd); font-size: 17px; font-weight: 700; }
.exam p { margin: 2px 0 0; font-size: 13px; color: var(--side-mut); }
.exam .btn { margin-left: auto; background: var(--side-ink); color: var(--side); flex-shrink: 0; }

/* ============================================================ vocabulary */
.topic { display: flex; flex-direction: column; gap: 8px; }
.topic .th { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.topic h4 { margin: 0; font-family: var(--fd); font-size: 16.5px; font-weight: 700; color: var(--ink); }
.st { font-size: 12px; font-weight: 700; }
.due { font-size: 11px; font-weight: 700; background: var(--mid-soft); color: var(--mid); border-radius: 999px; padding: 3px 9px; }
.vtrack { height: 6px; border-radius: 999px; background: var(--card2); overflow: hidden; }
.vtrack i { display: block; height: 100%; }

.flash { display: flex; flex-direction: column; gap: 12px; }
.flash .ph { font-family: var(--fd); font-size: 22px; font-weight: 700; color: var(--ink); margin: 0; }
.flash .tr { font-size: 13.5px; color: var(--mut); }
.weak { font-size: 13.5px; line-height: 1.55; color: var(--mut); text-decoration: line-through; }
.strong { font-size: 13.5px; line-height: 1.55; color: var(--ink); background: var(--high-soft); border-radius: 8px; padding: 9px 11px; }
:root[data-theme="tungi"] .strong { color: var(--high); }

/* ============================================================ charts */
.legend { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.legend span { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 600; color: var(--ink2); }
.legend i { width: 10px; height: 10px; border-radius: 3px; display: block; }
.chart { width: 100%; height: auto; display: block; overflow: visible; }
.chart .grid { stroke: var(--grid); }
.chart .axis { stroke: var(--axis); }
.chart .lbl { font-size: 11px; fill: var(--mut); }
.chart .val { font-size: 12px; font-weight: 600; fill: var(--ink); }
.chart .dot { stroke: var(--card); stroke-width: 2; }

.ebar { height: 6px; border-radius: 999px; background: var(--card2); overflow: hidden; }
.ebar i { display: block; height: 100%; background: var(--low); }

/* ============================================================ report */
.rephead { display: flex; align-items: center; gap: 26px; flex-wrap: wrap; }
.ring { position: relative; width: 120px; height: 120px; flex-shrink: 0; }
.ring .v { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.ring .v b { font-family: var(--fd); font-size: 34px; font-weight: 700; color: var(--ink); line-height: 1; }
.ring .v small { font-size: 10.5px; font-weight: 700; letter-spacing: .06em; color: var(--mut); }
.crits { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; flex-grow: 1; }
.crit { display: flex; flex-direction: column; gap: 6px; background: var(--card2); border-radius: var(--rs); padding: 12px 14px; }
.crit small { font-size: 11px; font-weight: 700; color: var(--mut); letter-spacing: .02em; }
.crit b { font-family: var(--fd); font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }

.essay { font-family: var(--fd); font-size: 15px; line-height: 2; color: var(--ink); margin: 0; }
.mark-red { border-bottom: 2px solid var(--low); background: var(--low-soft); }
.mark-amb { border-bottom: 2px solid var(--mid); background: var(--mid-soft); }
.mark-grn { background: var(--high-soft); }
.essay sup { font-weight: 700; font-size: 11px; }

.fix { display: flex; flex-direction: column; gap: 4px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.fix:last-child { border-bottom: 0; }
.fix .no { font-size: 12px; font-weight: 800; color: var(--low); }
.fix .was { font-size: 13.5px; color: var(--mut); text-decoration: line-through; }
.fix .now { font-size: 13.5px; color: var(--ink); font-weight: 600; }

details { border-bottom: 1px solid var(--line); }
details:last-child { border-bottom: 0; }
summary {
  display: flex; align-items: center; gap: 10px; padding: 13px 2px;
  font-size: 14px; font-weight: 700; color: var(--ink); cursor: pointer; list-style: none;
}
summary::-webkit-details-marker { display: none; }
summary .n { font-size: 12.5px; font-weight: 600; color: var(--mut); margin-left: auto; }
summary .caret { transition: transform .15s; color: var(--mut); flex-shrink: 0; }
details[open] summary .caret { transform: rotate(90deg); }
details .body { padding: 0 2px 14px; font-size: 13.5px; line-height: 1.6; color: var(--ink2); max-width: 640px; }

/* ============================================================ writing surface */
/* The page itself is the paper. No white box floating on a coloured ground, no
 * frame around the ruling — the student writes on the background they are
 * already looking at. This is the whole point of the surface. */
.qcard { display: flex; align-items: center; gap: 16px; background: var(--card2); border: 0; box-shadow: none; }
.qtext { font-size: 14.5px; line-height: 1.5; color: var(--ink); font-weight: 500; flex-grow: 1; }
.meta { display: flex; align-items: center; gap: 10px; flex-shrink: 0; flex-wrap: wrap; }
.mchip {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 13px; font-weight: 700; background: var(--card2); color: var(--ink);
  border-radius: 999px; padding: 7px 13px; font-variant-numeric: tabular-nums;
}
.mchip small { font-weight: 600; color: var(--mut); font-size: 11.5px; }
.mchip.warn { background: var(--mid-soft); color: var(--mid); }
.mchip.over { background: var(--low-soft); color: var(--low); }
.saved { font-size: 12px; font-weight: 700; color: var(--high); }

.wsheet { position: relative; padding: 4px 2px 0 28px; flex-grow: 1; }
.wsheet::before { content: ""; position: absolute; top: 0; bottom: 0; left: 12px; width: 1px; background: var(--accent-soft); }
.wsheet textarea {
  display: block; width: 100%; min-height: 380px;
  border: 0; resize: none; outline: none; padding: 0 2px;
  background: repeating-linear-gradient(transparent 0 32px, var(--line2) 32px 33px);
  background-attachment: local;
  line-height: 33px; font-size: 15.5px; color: var(--ink); font-family: var(--fb);
}
.wsheet textarea:focus-visible { outline: none; }

.wbar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.wfont { display: flex; background: var(--card2); border-radius: 10px; padding: 3px; }
.wfont button { border: 0; background: transparent; color: var(--mut); font-size: 12.5px; font-weight: 700; padding: 6px 12px; border-radius: 8px; }
.wfont button.on { background: var(--card); color: var(--ink); box-shadow: var(--shadow); }

/* The Task 1 figure's own styles are at the FOOT of this file now, under
 * «the Task 1 figure, in five». What stood here dressed one horizontal bar per
 * number, which is what every Task 1 question used to be drawn as whatever it
 * actually was; nothing emits that markup any more. */
.figimg { width: 100%; border-radius: var(--rs); display: block; border: 1px solid var(--line); }

/* ============================================================ waiting */
.waitwrap { max-width: 620px; width: 100%; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }
.spin {
  width: 22px; height: 22px; border-radius: 50%;
  border: 3px solid var(--card2); border-top-color: var(--accent);
  animation: b9spin 1s linear infinite; flex-shrink: 0;
}
@keyframes b9spin { to { transform: rotate(360deg); } }
.step { display: flex; align-items: center; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--line); font-size: 14px; font-weight: 600; color: var(--ink); }
.step:last-child { border-bottom: 0; }
.step.done { color: var(--mut); font-weight: 500; }
.step.todo { color: var(--mut); font-weight: 500; opacity: .65; }
.tick { width: 20px; height: 20px; border-radius: 50%; background: var(--high-soft); color: var(--high); display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.hollow { width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--line2); flex-shrink: 0; }
.dim { opacity: .55; }

/* ============================================================ exam */
/* The same paper, stripped bare. The rail collapses (see .side-min) rather than
 * dimming, so the only things left are the clock, the question and the sheet. */
.examstage {
  display: flex; flex-direction: column; gap: 20px;
  max-width: 920px; width: 100%; margin: 0 auto; flex-grow: 1; padding-top: 8px;
}
.examstage .etimer { text-align: center; display: flex; flex-direction: column; gap: 4px; }
.examstage .etimer b { font-family: var(--fd); font-size: 52px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; color: var(--ink); }
.examstage .etimer b.low { color: var(--low); }
.examstage .etimer small { font-size: 12.5px; font-weight: 600; color: var(--mut); }
.examstage .eq { background: var(--card2); border-radius: var(--rs); padding: 14px 18px; font-size: 14.5px; line-height: 1.55; font-weight: 500; color: var(--ink); }
.examstage .efoot { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.examstage .efoot .note { font-size: 12.5px; color: var(--mut); }
.examstage .efoot .btn { margin-left: auto; background: transparent; color: var(--ink); border: 1.5px solid var(--line2); }

/* ============================================================ settings + forms */
.optrow { display: flex; gap: 10px; flex-wrap: wrap; }
.opt {
  flex: 1 1 140px; border: 1.5px solid var(--line); border-radius: var(--rs);
  background: var(--card); color: var(--ink); padding: 12px 14px; text-align: left;
  display: flex; flex-direction: column; gap: 5px; transition: border-color .15s;
}
.opt small { font-size: 12px; font-weight: 700; color: var(--mut); }
.opt span { font-size: 14px; line-height: 1.4; }
.opt.on { border-color: var(--accent); }
.opt.on small { color: var(--accent); }

.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-size: 12.5px; font-weight: 700; color: var(--mut); }
.field input, .field textarea, .field select {
  background: var(--card); color: var(--ink);
  border: 1.5px solid var(--line2); border-radius: var(--rs);
  padding: 11px 14px; font-size: 15px; width: 100%;
}
.field input:focus, .field textarea:focus { border-color: var(--accent); }
.field textarea { resize: vertical; min-height: 96px; line-height: 1.55; }

/* ============================================================ states */
.notice {
  border-radius: var(--rs); padding: 12px 16px; font-size: 13.5px; line-height: 1.55;
  display: flex; align-items: flex-start; gap: 10px;
}
.notice-info { background: var(--card2); color: var(--ink2); }
.notice-warn { background: var(--mid-soft); color: var(--mid); }
.notice-error { background: var(--low-soft); color: var(--low); }
.notice-ok { background: var(--high-soft); color: var(--high); }

.empty {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 44px 24px; text-align: center; color: var(--mut);
}
.empty h3 { font-family: var(--fd); font-size: 17px; font-weight: 700; color: var(--ink); margin: 0; }
.empty p { margin: 0; font-size: 13.5px; max-width: 380px; line-height: 1.55; }

.skel { background: var(--card2); border-radius: var(--rs); animation: b9pulse 1.4s ease-in-out infinite; }
@keyframes b9pulse { 0%,100% { opacity: 1; } 50% { opacity: .55; } }

/* ============================================================ phone + tablet */
/* Below 1020px the rail is an icon strip; below 780px it leaves the flow
 * entirely and slides in over the content, with a top bar to summon it. The
 * writing surface never gets a drawer on top of it — the exam collapses the
 * rail on every width. */
@media (max-width: 1200px) {
  .split { grid-template-columns: 1fr 320px; }
  .grid4 { grid-template-columns: repeat(2, minmax(0,1fr)); }
}
@media (max-width: 1020px) {
  .content { padding: 22px 22px 36px; }
  .side { width: 74px; padding-left: 11px; padding-right: 11px; }
  .side .logo { flex-direction: column; gap: 12px; padding: 0 0 14px; align-items: center; }
  .side .logo > div { display: none; }
  .side .tgl { display: none; }
  .side .ngroup { height: 1px; padding: 0; margin: 10px 8px; background: var(--side-line); overflow: hidden; font-size: 0; }
  .side .nav a { font-size: 0; gap: 0; justify-content: center; padding: 11px 0; }
  .side .nav a .cnt { display: none; }
  .side .user { justify-content: center; padding-left: 0; padding-right: 0; }
  .side .user > div { display: none; }
  .side .purse { justify-content: center; gap: 0; padding-left: 0; padding-right: 0; }
  .side .purse span { font-size: 0; }
  .grid2, .split, .split-l { grid-template-columns: minmax(0,1fr); }
  .crits { grid-template-columns: repeat(2, minmax(0,1fr)); }
}
@media (max-width: 780px) {
  .shell { flex-direction: column; }
  .topbar {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 16px; background: var(--side); color: var(--side-ink);
    position: sticky; top: 0; z-index: 45;
  }
  .topbar .burger { border: 0; background: transparent; color: var(--side-ink); width: 36px; height: 36px; border-radius: 10px; display: flex; align-items: center; justify-content: center; }
  .topbar b { font-family: var(--fd); font-size: 15px; font-weight: 700; }
  .topbar .ava { margin-left: auto; }
  /* .side-min IS UNDONE HERE, SELECTOR BY SELECTOR, and it has to be named to do
     it: a media query adds no specificity, so `.side-min .side` (0,2,0) at the
     top of this file outranks a bare `.side` (0,1,0) however far down it sits.
     Left alone, a student who had folded the rail on a laptop — or who is in the
     exam, which folds it too — opened the phone drawer at 74px wide with the
     full-size labels this block puts back inside it. The other .side-min rules
     are matched by equally specific ones below and win on order alone; this one
     is the only pair the file does not already balance. */
  .side, .side-min .side {
    position: fixed; top: 0; left: 0; bottom: 0; height: 100vh; height: 100dvh;
    width: 250px; padding: 22px 14px 18px;
    transform: translateX(-100%); box-shadow: var(--shadow-up);
  }
  :root[data-theme="quyosh"] .side { margin: 0; border-radius: 0; height: 100vh; height: 100dvh; }
  .side .logo { flex-direction: row; gap: 10px; padding: 0 10px 22px; align-items: center; }
  .side .logo > div { display: block; }
  .side .ngroup { height: auto; padding: 10px 12px 6px; margin: 0; background: none; font-size: 10.5px; }
  .side .nav a { font-size: 14px; gap: 12px; justify-content: flex-start; padding: 10px 12px; }
  .side .nav a .cnt { display: inline; }
  .side .user { justify-content: flex-start; padding-left: 10px; padding-right: 10px; }
  .side .user > div { display: block; }
  .side .purse { justify-content: flex-start; gap: 5px; padding-left: 10px; padding-right: 10px; }
  .side .purse span { font-size: inherit; }
  .drawer-on .side { transform: translateX(0); }
  .scrim { position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 39; opacity: 0; pointer-events: none; transition: opacity .2s; }
  .drawer-on .scrim { opacity: 1; pointer-events: auto; }
  .content { padding: 18px 16px 32px; }
  .htop h1, .htop h2 { font-size: 22px; }
  .hero { flex-direction: column; }
  .hero-r { width: auto; border-left: 0; border-top: 1px solid var(--line); }
  .grid3, .grid4, .gridhalf { grid-template-columns: minmax(0,1fr); }
  .crits { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .thead, .trow { grid-template-columns: 56px 1fr 44px; }
  .thead span:nth-child(3), .thead span:nth-child(4),
  .trow > *:nth-child(3), .trow > *:nth-child(4) { display: none; }
  .qs { width: auto; }
  .examstage .etimer b { font-size: 40px; }
}
.scrim { display: none; }
@media (max-width: 780px) { .scrim { display: block; } }

/* ============================================================ print */
/* Only the report is ever printed, and a teacher printing it wants the essay and
 * the bands — not the rail, not the buttons. */
@media print {
  .side, .topbar, .scrim, .btn, .link, .wbar { display: none !important; }
  .shell { display: block; }
  .content { padding: 0; }
  .card { border-color: #ddd; box-shadow: none; break-inside: avoid; }
  body { background: #fff; color: #000; }
  details { break-inside: avoid; }
  details:not([open]) .body { display: block; }
}

/* ============================================================ drills
 * Four things the vocabulary above had no name for, added by the drill screen
 * (screens/mashq.js) because the report will want all four as well.
 *
 * .col — a screen that swaps its whole body between states (the drill: write →
 *   wait → answer) needs ONE element to replace, and .content's own 18px gap
 *   only reaches its direct children. Lower specificity than .card.col, so a
 *   card keeps its own tighter 12px.
 * .cross — .tick is the only marked state the waiting screen ever needed. A
 *   drill row says «this move is missing», which is not «not done yet» and must
 *   not be drawn as a dimmed step.
 * .mchip.met — the counter has a warn and an over state and no positive one.
 *   The drill's target is met, not exceeded, and that is worth saying in colour.
 * .subh / .prose — several short sections stack inside one card here (structure,
 *   what worked, the fix). .card h3 is the wrong size for the second one down,
 *   and .rm is a caption, not a paragraph of the model's own prose. */
.col { display: flex; flex-direction: column; gap: 16px; }

.cross {
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--low-soft); color: var(--low);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 800; line-height: 1; flex-shrink: 0;
}

.mchip.met { background: var(--high-soft); color: var(--high); }

.subh { margin: 6px 0 0; font-family: var(--fd); font-size: 14px; font-weight: 700; color: var(--ink); }
.prose { margin: 0; font-size: 13.5px; line-height: 1.6; color: var(--ink2); max-width: 640px; }

/* ============================================================ writing hub */
/* WHY .stack EXISTS. .content is a flex column with an 18px gap, so cards laid
 * out as its direct children are spaced for free — but a screen that needs ONE
 * stable node to hang its click delegation on (the router hands every screen a
 * mount that outlives it, so a listener on the mount stacks up one per visit)
 * loses that gap the moment it wraps its cards in that node. This restores the
 * page's rhythm through the wrapper, and it is generic on purpose. */
/* A column flex container, and its children need the same escape hatch
 * `.content > *` gives its own: a flex item's `min-width` is `auto`, which
 * resolves to the item's MIN-CONTENT width — and a card holding one line of
 * `white-space: nowrap` question text has a min-content width of the whole
 * sentence. The card then refuses to shrink, pushes past the column, and the
 * page grows a horizontal scrollbar that no amount of `text-overflow: ellipsis`
 * inside it can undo, because the ellipsis never has a narrow box to work in.
 * Measured on the dashboard: a 901px column holding a 1225px card. */
.stack { display: flex; flex-direction: column; gap: 18px; }
.stack > * { min-width: 0; }

/* Same reason, one level in: .row is a flex container AND a flex item, so it
 * needs the hatch on both sides of itself. */
.row { min-width: 0; }

/* AND EVERY GRID ITEM, which is where this actually bit. A grid item's
 * `min-width` is `auto` exactly like a flex item's, so a `2fr` track holding a
 * card with one line of nowrap text is floored at that sentence's width — the
 * track stops being 2fr and the whole page grows a horizontal scrollbar. The
 * dashboard's «Soʻnggi hisobotlar» card measured 1225px inside an 841px track.
 * Declared once here rather than per screen: every grid in this file lays out
 * cards that may hold a question, an essay title or a phrase. */
.grid2 > *, .grid3 > *, .grid4 > *, .gridhalf > *,
.split > *, .split-l > * { min-width: 0; }

/* WHY A THUMBNAIL ON A TASK 1 ROW. Two Task 1 prompts whose text differs by
 * four words hand the student a bar chart and a pie, and the row clips the
 * text at one line anyway — so without the picture every Task 1 row reads
 * "The chart below shows…" and the bank stops being browsable.
 *
 * figure() from core/figures.js draws the real thing at full size; here the
 * SAME renderer draws its thumb mode — the shape and nothing else. Text is not
 * shrunk, it is DROPPED: at this size a label is unreadable, and an unreadable
 * label is worse than none. The kind name printed in the row (.qt) is what
 * names the figure in words; the picture is what words cannot give you. The
 * box for those is at the foot of this file with the rest of the figure. */
.qthumb {
  width: 104px; flex-shrink: 0;
  display: flex; flex-direction: column; justify-content: center; gap: 3px;
  padding: 7px 9px; border-radius: var(--rs);
  background: var(--card2); overflow: hidden;
}
/* The one word a thumbnail does carry: how many stages a process has, because
 * a chain of pips cannot say «eight» and that is the fact worth 104px. */
.qthumb > b { font-size: 9px; font-weight: 700; color: var(--mut); }

@media (max-width: 780px) {
  /* At 375px the row cannot carry five things. The two that go are the two
     already stated above the list: the segmented control says which half, so
     the T1/T2 tag is a repeat, and the topic chips say which topic. What
     survives is the picture, the question and whether it is written. */
  .qrow .tag, .qrow .qt { display: none; }
  .qthumb { width: 72px; padding: 5px 7px; }
}

/* ====================================================== vocabulary, part two */
/* The prototype drew ONE flashcard and six topic tiles. §4.8 puts that same card
 * on six screens, so what follows is only the parts a single example never had
 * to show — the tag row, the detail lines, the model paragraph behind a fold —
 * plus the four exercises, which the prototype does not contain at all.
 * Everything above in the vocabulary block is reused exactly as it stands. */

/* Every vocabulary paint replaces exactly ONE element, because that is where the
 * delegated listeners hang: on the router's mount they would outlive the screen
 * and still be firing three screens later. That wrapper therefore has to
 * reproduce the content column's own spacing, which it no longer inherits. */
.flow { display: flex; flex-direction: column; gap: 18px; }

/* The tile navigates, so it is a <button> — which inherits neither of these. */
.topic { text-align: left; width: 100%; }

.flash .vtop { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.flash .tr, .flash .gloss { margin: 0; }
.flash .gloss { font-size: 13px; line-height: 1.6; color: var(--ink2); }
.vgap { border-top: 1px solid var(--line); padding-top: 12px; display: flex; flex-direction: column; gap: 8px; }
/* The band pair is a label, not a heading: accent ink would make the one thing
 * on the card that is not the student's business the loudest thing on it. */
.vgap .eyebrow { color: var(--mut); }

.vrow { margin: 0; font-size: 13px; line-height: 1.75; color: var(--ink2); }
.vrow .lbl { font-size: 11px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--mut); margin-right: 8px; }
.vrow b { color: var(--ink); font-weight: 600; }
.vrow i { font-style: normal; font-size: 12px; color: var(--mut); }
.vdot { color: var(--line2); padding: 0 7px; }
.vico { color: var(--accent); flex-shrink: 0; }

/* The gap a student types into. A rule with nothing on it says nothing out loud,
 * so the word is there for a screen reader and hidden from everyone else. */
.blank { display: inline-block; min-width: 110px; border-bottom: 2px solid var(--accent); }
.filled { font-weight: 700; }
.filled.ok { color: var(--high); }
.filled.no { color: var(--low); }
/* The four options, after the tap that answered them. */
.opt.ok { border-color: var(--high); background: var(--high-soft); }
.opt.no { border-color: var(--low); background: var(--low-soft); }
.opt.off { opacity: .45; }
.opt[disabled] { cursor: default; }
/* The student's own sentence, quoted back above the marking. */
.said { margin: 0; font-size: 14px; line-height: 1.6; color: var(--ink); background: var(--card2); border-radius: var(--rs); padding: 11px 14px; }

@media (max-width: 780px) {
  /* Two cards side by side stop fitting their own sentences at 375px. */
  .gridhalf { grid-template-columns: minmax(0,1fr); }
}

/* ============================================================ report, added */
/* THE MARKED ESSAY IS A BLOCK OF PARAGRAPHS, NOT ONE PARAGRAPH. .essay above
 * was written for the prototype, where the text was a single <p> of sample
 * prose. buildMarked() emits one <p> per blank line, because an essay whose
 * five paragraphs run together is not the essay the examiner read — and the
 * paragraphing is itself part of what Coherence is marked on. */
.essay p { margin: 0 0 14px; }
.essay p:last-child { margin-bottom: 0; }

/* A HIGHLIGHT IS NOT A BUTTON, but the ones carrying a card behind them are
 * reachable from the keyboard, so they need a hand and a visible focus ring.
 * The colour itself comes from .mark-red/.mark-amb/.mark-grn. */
.mark { border-radius: 3px; padding: 0 1px; }
.mark[data-ref] { cursor: pointer; }

/* PRINTING IS ALWAYS PAPER, whatever theme the screen was on. Without this a
 * report printed from tungi came out as pale ink on white: the band pigments
 * are theme tokens, and the night set is tuned for a dark ground — --low on
 * paper was a light red nobody would call a warning. The daylight tokens are
 * restated here, band colours included. The attribute selectors are needed
 * because :root[data-theme="…"] outranks a bare :root. */
@media print {
  :root,
  :root[data-theme="quyosh"],
  :root[data-theme="binafsha"],
  :root[data-theme="tungi"] {
    --bg: #fff; --card: #fff; --card2: #f2f2f0; --line: #dcdcd8; --line2: #c6c6c2;
    --ink: #111; --ink2: #333; --mut: #555;
    --accent: #b8431f; --on-accent: #fff; --accent-soft: #f4e2dc;
    --low: #a8261a; --mid: #7a5208; --good: #111; --high: #16632f;
    --low-soft: #f7e5e1; --mid-soft: #f7eeda; --high-soft: #e2f1e7;
    --shadow: none; --shadow-up: none;
  }
  body { font-size: 11pt; }
  /* Sections are printed whole or on the next page — a criterion split across
   * the fold is a criterion somebody misses. */
  .card, .fix, .grid2 > * { break-inside: avoid; }
}

/* ============================================================ the writing flow
 * Added for screens/yoz.js, screens/imtihon.js and screens/kutish.js. Everything
 * above was already in the vocabulary; these are the pieces those three screens
 * needed and could not compose.
 */

/* Each screen mounts ONE wrapper into .content and paints inside it, so that the
 * listeners it hangs there die with the paint instead of stacking up one set per
 * visit. That wrapper has to carry .content's own column rhythm. */
.col-page { display: flex; flex-direction: column; gap: 18px; min-width: 0; flex-grow: 1; }

/* THE WRITING TYPEFACE IS THE STUDENT'S OWN CHOICE and affects the sheet alone —
 * never the interface, which would be a different product every time somebody
 * changed a preference. --fw is unset by default, so the sheet falls back to the
 * theme's body face. No monospace face is bundled: a 40 kB download for a
 * preference nobody reads back is not worth the second on a slow connection. */
.wsheet textarea { font-family: var(--fw, var(--fb)); }
:root[data-wfont="serif"] { --fw: "Lora", "Iowan Old Style", Georgia, serif; }
:root[data-wfont="mono"] { --fw: ui-monospace, "SF Mono", "Roboto Mono", Menlo, Consolas, monospace; }

/* The Task 1 figure. Above 1020px it is a sticky column beside the paper, as the
 * prototype draws it; below that the grid is one column, the chart is a fold
 * over the paper, and the fold's own button is the only control that appears. */
.figcard { align-self: start; }
.figbox { overflow: auto; max-width: 100%; }
.figzoom { transform-origin: 0 0; }
@media (min-width: 1021px) {
  .figcard { position: sticky; top: 8px; max-height: calc(100vh - 44px); overflow: auto; }
  .figtgl { display: none; }
}

/* Where a photographed chart lands. The dashed edge is the whole affordance — a
 * button alone does not read as "you can drop a file here". */
.drop { display: flex; flex-direction: column; align-items: center; gap: 10px;
  border: 1.5px dashed var(--line2); border-radius: var(--rs); padding: 18px 14px; text-align: center; }
.drop.on { border-color: var(--accent); background: var(--accent-soft); }
.drop p { margin: 0; }

/* The exam footer gives its first control margin-left:auto so the row reads
 * "note | actions". With three controls each would claim its own share of the
 * free space and they would drift apart, so the group is one element and only it
 * is pushed. .go names the primary because .efoot .btn is a ghost by default,
 * and Finish is the one irreversible control in the room. */
.examstage .efoot .eacts { margin-left: auto; display: flex; gap: 10px; flex-wrap: wrap; }
.examstage .efoot .eacts .btn { margin-left: 0; }
.examstage .efoot .btn.go { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
/* Five minutes left is a warning; one minute is not the same warning. */
.examstage .etimer b.warn { color: var(--mid); }

/* ============================================================ settings
 * Four things .opt and .seg could not carry on their own, and one of them
 * breaks this file's own rule about colour on purpose.
 *
 * .sw — THE ONLY LITERAL COLOURS OUTSIDE THE THEME BLOCKS, and they have to be.
 *   A swatch of the night theme has to paint night's ink while the page is
 *   still in daylight, so it cannot reach for var(--accent): every swatch would
 *   come out the same three colours and the switcher would show nothing. The
 *   three per theme are the ones a student actually recognises it by — its
 *   accent, its paper and its rail — and they are lifted from the prototype's
 *   own theme switcher. Keep them in step with the token blocks at the top.
 * .oh — .opt is a column and the swatch belongs ON the label's line, not under
 *   it: the eye pairs the colour with the name, and a swatch on its own row
 *   reads as a fourth thing to decide about.
 * .face-* — the three writing faces, previewed in themselves. The stacks are
 *   written once here and reused by [data-wfont] below, so the sample on the
 *   settings card and the paper on the sheet can never be different fonts.
 * [data-wfont] — where the preference finally lands. The sheet is the only
 *   place it applies: a student choosing a face for their own writing has not
 *   asked for the interface to change with it. */
.sw { display: flex; gap: 3px; flex-shrink: 0; }
.sw i {
  width: 10px; height: 10px; border-radius: 3px; display: block;
  /* An inset ring rather than a border: white on white paper is invisible
     otherwise, and the ring is the same grey against every ground. */
  box-shadow: inset 0 0 0 1px rgba(128,128,128,.4);
}
.sw-quyosh i:nth-child(1) { background: #ffd166; }
.sw-quyosh i:nth-child(2) { background: #ee5a2e; }
.sw-quyosh i:nth-child(3) { background: #1b1c20; }
.sw-binafsha i:nth-child(1) { background: #6553e0; }
.sw-binafsha i:nth-child(2) { background: #ffffff; }
.sw-binafsha i:nth-child(3) { background: #17181d; }
.sw-tungi i:nth-child(1) { background: #5ad0dc; }
.sw-tungi i:nth-child(2) { background: #171e28; }
.sw-tungi i:nth-child(3) { background: #0e141b; }

.opt .oh { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

.face-sans { font-family: var(--fb); }
.face-serif { font-family: "Lora", "Iowan Old Style", Georgia, serif; }
.face-mono { font-family: ui-monospace, SFMono-Regular, Menlo, "Liberation Mono", monospace; font-size: 12.5px; }

:root[data-wfont="serif"] .wsheet textarea { font-family: "Lora", "Iowan Old Style", Georgia, serif; }
:root[data-wfont="mono"] .wsheet textarea {
  font-family: ui-monospace, SFMono-Regular, Menlo, "Liberation Mono", monospace;
  /* Mono runs wide. A whole 15.5px monospace line overshoots the ruling on a
     phone, and the ruled sheet is measured in whole lines. */
  font-size: 14px;
}

/* The «who marked this» line stands on the page rather than in a fifth card: it
 * is a footnote to the four decisions, not another one. It starts hidden until
 * /api/config answers — which works because of the global [hidden] rule at the
 * foot of this file, not in spite of the display below. */
.about { display: flex; flex-direction: column; gap: 8px; }
.about p { margin: 0; }

/* .opt span sets its own font-size (0,1,1) and would beat .face-mono (0,1,0) on
 * specificity alone, whatever the order here — so the one face that needs a
 * smaller size to fit the sample sentence has to say so from inside .opt. */
.opt .face-mono { font-size: 12.5px; }

/* ============================================================ kirish */
/* THE SIGN-IN PAGE IS THE ONLY PAGE OF THIS FRONTEND WITH NO SHELL. Everything
 * above assumes .content inside .shell — a column with a rail beside it — so a
 * card centred on the bare ground has nothing to compose from and gets the
 * handful of rules below. They live here rather than in a <style> block on
 * kirish.html because a second place to look for a colour is how two designs
 * start drifting apart. .card, .field, .btn, .chip, .seg and .notice are used
 * exactly as they stand. */
.auth {
  min-height: 100vh; min-height: 100dvh;
  display: flex; align-items: center; justify-content: center;
  padding: 28px 18px;
}
.authcard { width: 100%; max-width: 400px; display: flex; flex-direction: column; gap: 20px; }
.authcard section { display: flex; flex-direction: column; gap: 14px; }
.authcard form { display: flex; flex-direction: column; gap: 14px; }
.authcard h1 { margin: 0; font-family: var(--fd); font-size: 26px; font-weight: 700; line-height: 1.2; color: var(--ink); }
.authcard p { margin: 0; }
/* Full width: a phone held in one hand should not have to aim at the one
   control the whole page exists for. */
.authcard .btn { width: 100%; }
.authsub { font-size: 13.5px; line-height: 1.55; color: var(--mut); }

.authlogo { display: flex; align-items: center; gap: 10px; }
.authlogo b { display: block; font-family: var(--fd); font-size: 17px; font-weight: 700; line-height: 1.1; }
.authlogo small { font-size: 10px; font-weight: 600; letter-spacing: .08em; color: var(--mut); }

.authfoot {
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
  border-top: 1px solid var(--line); padding-top: 16px;
}
/* The pip IS the label for a student who cannot yet read either language on the
   screen — which, on a shared classroom machine, is exactly who needs this
   control before they can sign in. */
.swatch { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 7px; vertical-align: middle; }

/* [hidden] IS NOT A SUGGESTION, and it needs saying globally rather than on the
 * sign-in page alone. .notice and .card.col both set display, and a class rule
 * outranks the browser's own [hidden] { display: none } — so a panel switched
 * off with `el.hidden = true` stayed on screen. Every screen here hides
 * something that way. */
[hidden] { display: none !important; }

/* ============================================================ kredit */
/* The settings card. .row and .trow were the two candidates and neither fits:
 * .row is a tappable list item with a hover colour and these lines go nowhere,
 * and .trow is a five-column grid built for the essay table. The rail's own
 * line lives with the sidebar, next to the user block it sits under.
 *
 * The ledger is deliberately two lines per movement rather than four columns:
 * at 375 px in Uzbek «Boshlangʻich kredit» alone is most of the width, and a
 * four-column row either wraps into rubble or hides the number that matters.
 * What moved sits on the first line with the amount; when, and what was left
 * afterwards, sit under it. */
.krbal { display: flex; align-items: baseline; gap: 7px; }
.krbal b { font-size: 40px; color: var(--ink); }
.krbal.low b { color: var(--mid); }
.krbal span { font-size: 13px; color: var(--mut); }

.prices { display: flex; flex-direction: column; }
.prow {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 13.5px; color: var(--ink2);
}
.prow:last-child { border-bottom: 0; }
.prow b { font-weight: 700; color: var(--ink); flex-shrink: 0; font-variant-numeric: tabular-nums; }

.led { display: flex; flex-direction: column; }
.ledrow {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.ledrow:last-child { border-bottom: 0; }
.ledrow > div { min-width: 0; }
.ledrow b { display: block; font-size: 13.5px; font-weight: 600; color: var(--ink); }
.ledrow small { font-size: 12px; color: var(--mut); font-variant-numeric: tabular-nums; }
/* The sign is the whole message in this column, so it is never dropped and the
   two directions are never the same colour. Spent is plain ink rather than red:
   a check the student meant to buy is not an error. */
.ledrow .n { font-size: 13.5px; font-weight: 700; flex-shrink: 0; font-variant-numeric: tabular-nums; }
.ledrow .n.up { color: var(--high); }
.ledrow .n.dn { color: var(--ink2); }

/* ============================================================ vocabulary, the phrase */
/* screens/lugat.js — THE TAUGHT PHRASE INSIDE THE ENGLISH IT LIVES IN.
 *
 * Three fields of a card arrive from vocabulary.json with the phrase already
 * wrapped in <em>, and lugat.js's rich() escapes the field and then re-admits
 * exactly that tag: the model paragraph behind the fold (.body), the second
 * usage (.vrow), and the 7.5 line of the 5.5 → 7.5 block (.strong). That <em>
 * is not an aside — it is the one thing the whole card is teaching — and on its
 * own it was invisible. None of the three body faces ships an italic (see
 * fonts/faces.css: --fb is normal-only in all three themes), so the browser
 * synthesises a slant, and a synthesised slant at 13px is lost inside forty
 * words of English on a phone.
 *
 * WEIGHT AND INK, AND NOTHING ELSE. Not --accent: the 7.5 line already sits on
 * a --high-soft ground, and an orange run on a green ground makes the card
 * argue with itself over the same second of attention. No underline either —
 * every underlined run in this design is a .link, and this one is not tappable.
 * In the paragraph and the second usage the phrase rises out of --ink2 to --ink
 * at 700, which is the whole signal there; on the 7.5 line the ink is already
 * full strength in daylight and the weight carries it alone, while at night —
 * where .strong is drawn in --high — the phrase steps up to --ink and reads as
 * the brighter run. The slant is kept, not replaced: it is what the data means.
 *
 * SCOPED TO .flash, which is the card renderer and only the card renderer, so
 * all six vocabulary screens get it from one rule and nothing else does:
 * `details .body` is shared with the report screen, and hisobot.js's markdown
 * makes <em> of its own that this must not reach. */
.flash .body em,
.flash .vrow em,
.flash .strong em { font-style: italic; font-weight: 700; color: var(--ink); }

/* ============================================================ hisobot
 * Three things the report screen (screens/hisobot.js) asked for that the system
 * above had no name for. Nothing else uses any of them.
 *
 * button.crit RATHER THAN A NEW CLASS. The four criteria tiles are now buttons
 * onto the criterion's own section further down the page — except a tile whose
 * criterion the grader never returned, which has nothing to open and stays a
 * div. The two have to be the same tile to the eye, so this resets what a
 * <button> brings with it and changes nothing else about it. font and color are
 * already inherit for every button at the top of this file. */
button.crit {
  width: 100%; border: 0; text-align: left;
  -webkit-appearance: none; appearance: none;
  transition: background .15s;
}
/* --line is the next surface up from --card2 in all three themes: a shift the
   thumb can see without the tile changing what it is. */
button.crit:hover { background: var(--line); }

/* The criterion's own row inside the fold — what a tile lands on. scroll-margin
   rather than an offset worked out in JS: block:"start" would otherwise tuck the
   heading under the phone's sticky topbar, and a second scroll to correct for it
   would fight the smooth one it follows. Above 780px there is no topbar and this
   is simply air above the row. */
.critrow { scroll-margin-top: 72px; transition: background .25s; }

/* BACK TO THE TOP, on the longest page in the app. Fixed to the corner, above
   the page and below the drawer's scrim (39) on purpose — an open menu covers
   this like it covers everything else. env() keeps it off the phone's home
   indicator, and the plain `bottom` before it is the fallback for a browser that
   does not know the function; right:16px puts it on the same line as .content's
   own padding at phone width. 44px is the smallest square a thumb finds. */
.totop {
  position: fixed; right: 16px; bottom: 16px;
  bottom: calc(16px + env(safe-area-inset-bottom));
  width: 44px; height: 44px; border-radius: var(--pill);
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line2); background: var(--card); color: var(--ink);
  box-shadow: var(--shadow-up); z-index: 38;
  transition: background .15s, color .15s;
}
.totop:hover { background: var(--card2); color: var(--accent); }
/* [hidden] is a UA rule and EVERY author rule outranks it, so the display:flex
   above would show this button while the screen believes it is hidden. */
.totop[hidden] { display: none; }
/* The print block near the top hides .btn and .link by name; this is neither. */
@media print { .totop { display: none !important; } }

/* ============================================================ the ruled sheet
 * screens/yoz.js, screens/imtihon.js and screens/mashq.js all paint .wsheet.
 * These rules replace the handful of properties of it that were wrong: the
 * ruling did not travel with the writing, the paper was a 380px slot in the
 * middle of an empty screen, and the size of the writing was not the student's
 * to choose.
 *
 * WHY THE RULING DID NOT MOVE. The rules are a repeating gradient painted on the
 * textarea itself, and `background-attachment: local` is what was meant to carry
 * them along with the text. Three things can break that and only one of them was
 * true here:
 *
 *   1. the gradient's period and the line box disagree, so the writing drifts
 *      off the rules a fraction of a pixel per line. NOT the cause: a 33px
 *      period against a 33px line box. It becomes the cause the moment the size
 *      is adjustable, which is why the two lengths below are ONE token used
 *      twice and can no longer be changed apart.
 *   2. the element painting the ruling is not the element that scrolls. NOT the
 *      cause either, and worth stating because it reads like the obvious one:
 *      `min-height: 380px` with no height of its own is a box that stops at
 *      380px, so an essay past eleven lines really was scrolling INSIDE the
 *      textarea, and the ruling really was that textarea's own background.
 *   3. `background-attachment: local` on a <textarea> is not honoured. That is
 *      what is left, and it is not a thing a sheet of paper may rest on: Gecko
 *      ignores `local` on form controls, and iOS repaints the background at the
 *      fixed origin through a momentum scroll. On the engines that do honour it
 *      the sheet looked right — which is exactly why it shipped.
 *
 * So the fix removes the question instead of answering it. core/paper.js's
 * fitSheet() grows the textarea to its own content on every keystroke: a box
 * with nothing to overflow has no scrolling mechanism, the PAGE becomes the only
 * thing that scrolls, and the ruling cannot come adrift from the writing because
 * the two are one box moving as a unit. No engine gets a say. `local` is kept
 * below for the single case that still caps the box — watchKeyboard()'s
 * max-height while the iOS keyboard is up, where a sheet running on behind the
 * keyboard would be the worse bug.
 *
 * AND THE ROOM. The same growth is what gives the writing the screen: the sheet
 * opens at the height of the viewport it is in rather than at 380px, and from
 * there it is as tall as the essay. A 250-word Task 2 is then a page of the
 * student's own words instead of a slot with a window into them. .content's
 * 1320px cap is deliberately NOT touched — every screen in the app is composed
 * against it, and on Task 2 the sheet already spans that whole column. The
 * surplus this screen was wasting was vertical, not horizontal. */

/* THE LADDER. Each step is a pair, and the pair is the point: --wline is both
 * the line box and the gradient's period, so the writing sits on the rules at
 * every size by construction. Whole pixels only — a fractional line box is
 * rounded by the layout and a gradient is not.
 * KEEP IN STEP WITH core/store.js: WRITE_STEPS is how many there are and
 * WRITE_STEP_BASE is which one is the default (1 — the sheet as it always was).
 * The bare :root is what an unset attribute falls back to, so a page that opens
 * before anything has read the preference is the old sheet and not a surprise. */
:root { --wfs: 15.5px; --wline: 33px; }
:root[data-wsize="0"] { --wfs: 14px;   --wline: 30px; }
:root[data-wsize="1"] { --wfs: 15.5px; --wline: 33px; }
:root[data-wsize="2"] { --wfs: 17px;   --wline: 36px; }
:root[data-wsize="3"] { --wfs: 19px;   --wline: 40px; }
:root[data-wsize="4"] { --wfs: 22px;   --wline: 46px; }

/* No flex-grow. It stretched the wrapper to the bottom of the column while the
 * textarea inside it stopped at 380px, which is where the empty screen and the
 * accent margin rule running on past the last line both came from. The paper's
 * own min-height fills the screen now, and the wrapper ends where the paper
 * does. */
.wsheet { flex-grow: 0; }

.wsheet textarea {
  font-size: var(--wfs);
  line-height: var(--wline);
  background: repeating-linear-gradient(transparent 0 calc(var(--wline) - 1px),
    var(--line2) calc(var(--wline) - 1px) var(--wline));
  /* Kept for the iOS keyboard case above, and inert everywhere else: a box that
     never overflows scrolls the same background whatever this says. */
  background-attachment: local;
  /* THE FLOOR, THREE TIMES, EACH A FALLBACK FOR THE ONE BELOW IT. A browser
     without max() keeps 380px; one without dvh keeps the vh line. 340px is what
     the rest of the page costs on both screens that use this — on /write/task1
     the heading, the question strip, the action bar and .content's own padding;
     on /exam the clock, the question and the footer. So the sheet opens at one
     screenful and grows from there.
     mashq.js sets its own min-height inline, in whole lines, and an inline style
     outranks all three: the drill sheet keeps the height its brief deserves. */
  min-height: 380px;
  min-height: max(380px, calc(100vh - 340px));
  min-height: max(380px, calc(100dvh - 340px));
}

/* Mono runs wide, so it has always been set a size smaller than the sheet's —
 * as a constant, which would have pinned every step of the ladder to 14px. The
 * proportion is what was meant, and .9 is the proportion it already had. */
:root[data-wfont="mono"] .wsheet textarea { font-size: calc(var(--wfs) * .9); }

/* A−/A/A+ beside the typeface switcher in .wbar: both are preferences of the
 * paper and neither touches the interface. Built on .seg so it is the same
 * control as the chart's zoom two cards up, and like that one it does not
 * disable itself at the ends — the step simply clamps, and the writing not
 * getting any bigger is the plainest possible way of saying so. The letters are
 * sized like what they do. */
.wsize button { padding: 5px 10px; }
.wsize button:first-child { font-size: 11.5px; }
.wsize button:last-child { font-size: 15px; }

/* VERIFIED BY ARITHMETIC, at both ends of the ladder and on a phone.
 * A 360px phone: .content's 16px padding, .wsheet's 28px margin gutter and the
 * textarea's 2px leave 294px of text.
 *   step 0 (14px/30px) — about 42 characters a line, so a 250-word Task 2 is
 *     ~36 lines, 1080px of paper, scrolled as a page.
 *   step 4 (22px/46px) — about 27 characters a line, ~56 lines, 2576px.
 *   In both the line box and the rule are the same var, so the last line of the
 *   essay sits on its rule exactly as the first one does.
 * The floor on a 640px-tall phone is max(380px, 300px) = 380px: twelve rules at
 * step 0, eight at step 4 — never a slot, and never a page of empty ruling
 * either. At 1320px, the widest .content ever is, the line is long (~175
 * characters at step 0); that is the width the sheet was asked to use, and a
 * student who wants a shorter line has a bigger size to reach for. */

/* ======================= bosh + sozlamalar: the details the centre still wants
 * TWO PLACES, ONE SUBJECT, AND THE WEIGHT BETWEEN THEM IS THE DESIGN. The
 * dashboard carries the ASK and the settings card carries the ANSWER. A student
 * who came here to write has to be able to read past the ask in one glance; a
 * student who came here to fill it in has to find real fields. Those are
 * different jobs and they get different amounts of ink.
 *
 * .askline IS A LINE. No ground, no border, no icon, no accent — anything with a
 * surface of its own would sit under the hero card competing with the day's
 * advice, and the advice is the entire reason that screen exists. It is muted
 * text at the size of a caption with two links in it, and it wraps on a phone so
 * that the links fall under the sentence instead of squeezing it to three words
 * a line. */
.askline {
  margin: 0; display: flex; align-items: baseline; gap: 4px 14px; flex-wrap: wrap;
  font-size: 12.5px; line-height: 1.55; color: var(--mut);
}
.askline .link { font-size: 12.5px; }

/* The three details as the account card lists them. The label column is fixed
 * so the three values line up as a column rather than starting at three
 * different places, and wide enough for «E-pochta» in both languages.
 *
 * A VALUE THAT IS NOT THERE IS STILL A ROW. It prints «koʻrsatilmagan» in the
 * muted ink, never an empty space: a blank reads as something that failed to
 * load, and nobody decides to fix a thing the page would not admit was empty.
 * It is italic and grey and nothing else — no warning colour, because a missing
 * phone number is not an error, it is a question that has not been asked yet.
 *
 * Under 560px the label goes above its value: at that width a fixed column and
 * a long e-mail address leave about eleven characters for the address. */
.idlist { display: flex; flex-direction: column; gap: 8px; }
.idrow { display: flex; gap: 12px; align-items: baseline; font-size: 13.5px; }
.idrow > b { flex: 0 0 92px; font-size: 12px; font-weight: 700; color: var(--mut); }
.idrow > span { min-width: 0; color: var(--ink); overflow-wrap: anywhere; }
.idrow > span.none { color: var(--mut); font-style: italic; }
@media (max-width: 560px) {
  .idrow { flex-direction: column; gap: 2px; }
  .idrow > b { flex: none; }
}

/* ============================================================ royxat */
/* THE SIGN-UP PAGE, and only what the sign-in page did not already need. It is
 * the same .auth ground, the same .card.authcard, the same .field, .btn and
 * .authfoot — three things are new and each earns its four lines below.
 *
 * Nothing here is scoped to an id: the day the profile screen wants Google's
 * button, it should get this one rather than a second one that has drifted. */

/* GOOGLE'S BUTTON IS NOT .btn, deliberately. .btn is the accent, and there is
 * exactly one accent-coloured control on this page — the one that opens the
 * account. This is a neutral surface carrying the four-colour mark: 44px tall,
 * which is both Google's own minimum and the smallest target a thumb finds on a
 * moving bus, and full width like everything else in this card so that a phone
 * held in one hand does not have to aim.
 *
 * It takes its ground from --card and its rule from --line2, so it is legible
 * in all three themes without naming a colour. The mark itself keeps Google's
 * four colours in every theme, because it is a trademark and not an icon. */
.gwrap { display: flex; flex-direction: column; gap: 14px; }
.gbtn {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 44px; padding: 10px 18px; width: 100%;
  background: var(--card); color: var(--ink);
  border: 1.5px solid var(--line2); border-radius: var(--pill);
  font-family: var(--fb); font-size: 14px; font-weight: 700; line-height: 1.2;
  text-align: center; text-decoration: none;
  transition: background .15s, border-color .15s;
}
.gbtn:hover { background: var(--card2); border-color: var(--mut); }
.gbtn svg { flex-shrink: 0; }

/* «yoki» is a hairline with a word sitting in it rather than a word on its own
 * line: the two ways in are alternatives, and a rule that runs the full width
 * says so in a way that a centred «or» floating in space does not. Drawn with
 * the same --line the writing sheet rules its paper with. */
.ordiv {
  display: flex; align-items: center; gap: 12px;
  margin: 0; font-size: 12px; font-weight: 600; color: var(--mut);
}
.ordiv::before, .ordiv::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--line); }

/* THE OPTIONAL THREE. `details` further up is the report's fold — it carries a
 * bottom rule because those come in stacks — and this one is the quiet end of a
 * form, so it takes that rule back and sits on a top one instead. The summary
 * is muted rather than ink: it is an offer, not a heading, and it must not look
 * like the next thing that has to be filled in. */
.rxmore { border-bottom: 0; border-top: 1px solid var(--line); }
.rxmore > summary { padding: 12px 0; font-size: 13px; color: var(--mut); }
.rxmore > summary:hover { color: var(--ink); }
.rxbody { display: flex; flex-direction: column; gap: 14px; padding: 2px 0 14px; }

/* 360px, MEASURED, and it is the width this page is mostly read at. Above the
 * query below, .auth gives 18px a side and .card 24px, which leaves 274px of
 * card content — and the widest thing in it is Google's button: an 18px mark, a
 * 10px gap and «Google bilan davom etish» at 14/700, about 200px, inside the
 * 238px that button's own padding leaves. It fits, with nothing to spare.
 *
 * So the ground gives up its side padding at 360 and below rather than at some
 * width further down: 306px of card content instead of 274px is the difference
 * between «+998 90 123 45 67» sitting comfortably in the phone field and the
 * two theme chips wrapping onto a third row of the foot. The card keeps a
 * padding of its own — a form flush against the screen edge reads as a page
 * that has broken, not as a page that is using its width.
 *
 * The sign-in page shares .auth and .authcard and gets the same relief, which
 * is the point of them being shared classes. */
@media (max-width: 360px) {
  .auth { padding: 20px 10px; }
  .card.authcard { padding: 18px 16px; }
}

/* ============================================================ the ground switch
 * THE ONE CONTROL ON THE TWO DOORS — kirish.html and royxat.html, and nowhere
 * else. Those pages used to carry four coloured theme chips at the foot of the
 * card; the owner asked for «oddiy tepada dark mode light mode switch», so the
 * chips are gone and this is what replaced them. Sozlamalar keeps the full
 * three-theme choice and nothing about it changes.
 *
 * WHAT LIGHT AND DARK MEAN HERE IS THE TWO GROUNDS. quyosh is the light one and
 * tungi the dark one; binafsha is a light ground and shows as light. The switch
 * reports the ground that is ON THE SCREEN rather than the name in storage, and
 * it writes nothing until it is pressed — so a student who chose binafsha in
 * Sozlamalar can walk through this page and still have binafsha on the other
 * side of it. Only an actual press costs them that choice, and a press is a
 * student asking for the other ground.
 *
 * THE KNOB IS MOVED BY :root[data-theme] AND NOT BY A CLASS THE SCRIPT ADDS.
 * tema.js writes that attribute in the <head>, blocking, before the first pixel
 * — that is the whole reason the file exists — so the switch arrives already
 * thrown instead of showing a frame on the left and then sliding across once a
 * deferred module has loaded. The script's only job here is aria-checked, which
 * nobody sees.
 *
 * IT IS PAGE FURNITURE, NOT PART OF THE CARD, and that is why it gets a row of
 * its own rather than a corner. position:absolute would be slid under by a
 * sign-up card taller than the phone, and position:fixed would ride on top of
 * that card the whole way down it. So the page becomes a column — bar, then
 * ground — and .auth hands back the 100dvh it uses to centre itself when it is
 * the only thing on the page, because the bar is now the other half of that
 * height. flex:1 0 auto and not 1 1 auto: it may grow to keep a short card
 * centred, and may never shrink below a tall one. */
.authpage { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; }
.authpage .auth { flex: 1 0 auto; min-height: 0; }

/* 13 + the button's own 5 = the 18px .auth gives the card, so the track lines up
 * with the card's right edge on every phone narrow enough for the card to fill
 * the width. */
/* STICKY, NOT FIXED, AND NOT ABSOLUTE — the third option the note above this
 * block never considered. absolute is slid under by a sign-up card taller than
 * the phone and fixed rides on top of that card the whole way down it; sticky is
 * neither. The row keeps the height it always had, so .authpage's column and
 * .auth's `flex: 1 0 auto` still centre a short sign-in card exactly as before —
 * and the switch simply stops scrolling away after 56px, which is what «always
 * in the corner» asks of it. The band is opaque because the card now passes
 * underneath. */
.groundbar {
  position: sticky; top: 0; z-index: 5; background: var(--bg);
  display: flex; justify-content: flex-end; padding: 12px 13px 0;
}

/* 44px of target around a 62×34 track — Google's button up the page takes the
 * same minimum, for the same reason: a thumb on a moving bus. */
.ground {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 5px;
  background: none; border: 0; border-radius: 999px;
}
.groundtrack {
  position: relative; display: flex; align-items: center;
  width: 62px; height: 34px; padding: 3px;
  background: var(--card2); border: 1px solid var(--line); border-radius: 999px;
}
.ground:hover .groundtrack { border-color: var(--line2); }

/* Ground and knob are .seg's two colours, because this switch sits six lines
 * above .seg's language pair and the two should read as one family. */
.groundknob {
  position: absolute; top: 3px; left: 3px; width: 26px; height: 26px;
  border-radius: 50%; background: var(--card); box-shadow: var(--shadow);
  transition: transform .18s;
}

/* 28px is arithmetic, not taste: 62 less two 1px rules and two 3px pads is a
 * 54px content box, less the 26px knob. Get it wrong and the knob sits
 * off-centre at one end of its own track.
 *
 * The hairline is tungi's alone. That theme has no --shadow at all and its
 * --card is DARKER than its --card2, so the knob that reads as raised on the two
 * light grounds would read as a hole punched in the track. .seg gets away
 * without one because it also swaps its label's colour — which this does too —
 * but a 26px circle with neither shadow nor edge is a circle a thumb cannot
 * find. */
:root[data-theme="tungi"] .groundknob {
  transform: translateX(28px);
  border: 1px solid var(--line2);
}

/* position:relative is load-bearing: the knob is positioned and the icons are
 * not, so without it the knob paints over both of them. */
.groundhalf {
  position: relative; flex: 1 1 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--mut);
}
/* Whichever icon the knob is standing on is the ground that is on. */
.gsun { color: var(--ink); }
:root[data-theme="tungi"] .gsun { color: var(--mut); }
:root[data-theme="tungi"] .gmoon { color: var(--ink); }

/* 5 + the button's own 5 = the 10px .auth keeps below 360 — see the query
 * above. The switch is 72px wide with its padding and sits alone on its row, so
 * there is nothing here for it to crowd at any width. */
@media (max-width: 360px) {
  .groundbar { padding: 10px 5px 0; }
}

/* ------------------------------------------------ the same switch, in the corner
 * The two doors pin it with a row because they already had one. Every other
 * screen has no bar to put it in, so it is fixed to the top-right — the one
 * corner that is free at every width on every screen. The other three were each
 * ruled out by something already there:
 *   top-left     .vh:focus, the skip link, fixed at 10,10 with z-index 90. It
 *                is 175px wide and swallows this switch whole the moment a
 *                keyboard user presses Tab — and above 780px the rail owns the
 *                corner anyway.
 *   bottom-right #send, «Tekshirishga yuborish · N kredit», on both sheets, and
 *                .totop on the report — pixel for pixel the same 44×44 slot.
 *   bottom-left  the rail's .user and #purse at every desktop width, and the
 *                exam's finish button on a phone.
 * Bottom is where this product puts the buttons that spend money, which is the
 * other half of the argument: a control pressed twice a year does not belong
 * where the thumb lands by default.
 *
 * z-index 46 against the whole stack — .vh:focus 90, .topbar 45, .side 40,
 * .scrim 39, .totop 38. It clears the phone's bar, which it sits inside, and
 * loses to the skip link, which is correct and is in the other corner anyway. */
#btnGround {
  position: fixed; right: 12px;
  top: 6px; top: calc(6px + env(safe-area-inset-top));
  z-index: 46;
}
/* A fixed control is none of the names the print block hides, and it would
   otherwise appear on the teacher's printed copy of a report. */
@media print { #btnGround { display: none !important; } }

/* IT IS OPAQUE, AND FOR THE SAME REASON .totop IS. Both are controls that float
 * over a page rather than sitting in it, and a transparent island of icons over
 * somebody's own prose reads as a rendering fault. This is that component's
 * pill, borrowed whole.
 *
 * IT IS WRITTEN HERE, ABOVE THE TWO BLOCKS THAT UNDO IT, and that is not taste:
 * the door's row and the phone's bar both undo it with another #btnGround rule,
 * and three rules of equal specificity are decided by source order alone. Put
 * this one last and the white pill turns up on the sign-up page and stuck to the
 * near-black phone bar — which is exactly what it did the first time. */
#btnGround {
  background: var(--card); border: 1px solid var(--line2);
  box-shadow: var(--shadow-up);
}
/* THE TRACK KEEPS ITS --card2 AND ONLY LOSES ITS RULE. Blanking it was the
 * mistake: .groundknob is var(--card) and this pill is var(--card) too, so with
 * nothing between them the control became a flat lozenge with a sun at one end
 * and a moon at the other and no knob at all — measured on /home at 1000px, knob
 * and pill both rgb(255,253,248), separated by a 5%-alpha shadow that does not
 * register at 1x. tungi escaped it only because its knob carries a hairline.
 * --card2 inside --card is the pairing .seg uses and the one the four other
 * copies of this component already have. */
#btnGround .groundtrack { background: var(--card2); border-color: transparent; }
#btnGround:hover .groundtrack { border-color: transparent; }
#btnGround:hover { border-color: var(--accent); }

/* The doors keep their row: there the button is in flow and must not be pulled
   out of it. Written after the rule above, and more specific than it. */
.groundbar #btnGround {
  position: static; z-index: auto;
  background: none; border: 0; box-shadow: none;
}
.groundbar #btnGround .groundtrack { background: var(--card2); border-color: var(--line); }
.groundbar #btnGround:hover .groundtrack { border-color: var(--line2); }

/* ON A PHONE IT SITS INSIDE .topbar, over var(--side), so the page palette the
 * track is built from is the wrong one. The avatar moves left to make the room:
 * 72px of button, 12px of inset and 12px of air — the same 96 the page column
 * takes below. At 88 the gap between the avatar and the switch measured 2px,
 * which is two controls touching rather than two controls side by side.
 *
 * EVERY RULE IN HERE IS SCOPED TO .shell, AND THAT IS LOAD-BEARING. #btnGround
 * alone is (1,0,0) and beats the component's own class rules, so these reached
 * /signin and /signup too — documents with no .topbar and no rail, where the
 * knob became --side-ink on a --card2 track: 1.05:1 on quyosh, i.e. the moving
 * part of the switch simply gone, and a black dot on binafsha. The doors' own
 * reset below covered the button and the track and stopped short of the knob and
 * the glyphs. Scoping is the smaller fix than chasing each leak with another
 * .groundbar override, and it stops the next one too. */
@media (max-width: 780px) {
  .topbar { padding-right: 96px; }
  /* AND IT LOSES THE PILL. The opaque backing above exists because the switch
     floats over a page; here it floats over an opaque bar, and a --card pill on
     a near-black bar is a white lozenge stuck to the chrome. Inside the bar the
     track is the whole control again. */
  .shell #btnGround { background: none; border-color: transparent; box-shadow: none; }
  .shell #btnGround .groundtrack { background: rgba(255, 255, 255, .10); border-color: rgba(255, 255, 255, .20); }
  /* BINAFSHA'S BAR IS WHITE — --side is #ffffff on that theme alone — so the
     white-alpha track above vanishes into it and leaves a lone dark knob with an
     unattached moon beside it. Same idea, the other ink. */
  :root[data-theme="binafsha"] .shell #btnGround .groundtrack {
    background: rgba(23, 24, 29, .06); border-color: rgba(23, 24, 29, .14);
  }
  .shell #btnGround .groundknob { background: var(--side-ink); box-shadow: none; }
  /* THE ACTIVE GLYPH IS THE ONE UNDER THE KNOB, so it takes the BAR's colour and
     not the bar's ink: the knob is --side-ink, and an --side-ink sun on it is
     white on white. The idle glyph stays muted, on the track. */
  .shell #btnGround .groundhalf { color: var(--side-mut); }
  .shell #btnGround .gsun { color: var(--side); }
  :root[data-theme="tungi"] .shell #btnGround .gsun { color: var(--side-mut); }
  :root[data-theme="tungi"] .shell #btnGround .gmoon { color: var(--side); }
}

/* AND THE PAGE ENDS BEFORE IT, BETWEEN THE PHONE'S BAR AND THE WIDTH WHERE THE
 * MARGIN APPEARS ON ITS OWN. Padding .htop alone was not enough and the way it
 * failed is worth writing down: at rest nothing was under the switch, but the
 * page SCROLLS and the control does not. Measured at 1440 on the Task 2 sheet,
 * the paper's top-right corner passed under it and the last 51px of every line
 * in the top 44px of the essay went beneath a button — text a student could see
 * but not click into. The vocabulary screen put «Takrorlash» there.
 *
 * So the whole column stops 96px short: 72 of switch, 12 of inset, 12 of air —
 * enough that a focus ring on the switch and one on whatever the column ends
 * with are not drawn into each other.
 * Yes it is only on the right, and that is the point — the rail is chrome down
 * the left and this is chrome down the right. A margin with a control standing
 * in it reads as a decision; the same margin empty reads as a bug, which is what
 * the centring rule near the top of this file exists to fix.
 *
 * Above 1799 it is not needed: .content is capped at 1320 and centred, so each
 * margin is (W - 264 - 1320) / 2 with the rail unfolded, and that reaches the
 * same 96px at W = 1776. The breakpoint is 1800 and not 1776 because a media
 * query is measured against the viewport WITH its scrollbar and the margin is
 * not — 15px of difference, at the one width where the two rules hand over to
 * each other. Measured at 1775: the query had already switched off while
 * document.documentElement.clientWidth was 1760. Handing over at 1800 leaves
 * 100px of margin on the first width where the padding is gone, instead of the
 * half-pixel the arithmetic gives at 1768. */
@media (min-width: 781px) and (max-width: 1799px) {
  .content { padding-right: 96px; }
}

/* ================================================= the Task 1 figure, in five
 * A bar chart, a line graph, a pie, a table and a process diagram. Everything
 * below dresses core/figures.js, which decides WHICH of the five from the
 * question's own chart.type — the prompt names the form and the picture obeys
 * it, because in Task 1 the picture is the thing the student has to describe.
 *
 * --s1..--s6 ARE THE SERIES PALETTE AND THEY ARE NOT --c1..--c4. The four
 * criterion colours mean Task Response, Coherence, Lexical Resource and Grammar
 * on every report in this product; a Task 1 category wearing --c3 would read as
 * Lexical Resource to anybody who has looked at their own progress chart. These
 * six are their own tokens, six categorical slots handed out in order and never
 * cycled, and both sets clear the lightness band, the chroma floor, colour-blind
 * separation and contrast against the card each theme paints under them.
 *
 * They are declared here rather than up in the theme blocks so that this whole
 * feature is one block of this file. The selectors carry the same specificity as
 * the blocks at the top and come after them, so the values resolve exactly as
 * they would there. */
:root, :root[data-theme="quyosh"], :root[data-theme="binafsha"] {
  --s1: #2563cf; --s2: #b45f06; --s3: #9333b8; --s4: #15803c; --s5: #0891b2; --s6: #be123c;
}
/* The night set is not the daylight set dimmed. On #171e28 the light six drop
 * below the lightness band where six categories stay separable. */
:root[data-theme="tungi"] {
  --s1: #4f88e0; --s2: #c5811f; --s3: #b164da; --s4: #3fae62; --s5: #2f9fc4; --s6: #d8556f;
}

.fig { margin: 0; display: flex; flex-direction: column; gap: 10px; max-width: 520px; }
/* TWO CLASSES ON PURPOSE. Every caller puts the figure inside a .card, and
 * `.card h3` further up sets 18px with one class and one element — the same
 * specificity a lone .fig-h has, but it is declared first, so a chart title
 * would silently come out at heading size. */
.fig .fig-h { margin: 0; font-family: var(--fd); font-size: 15px; font-weight: 700; color: var(--ink); line-height: 1.3; }
.fig-u { margin: 0; margin-top: -4px; font-size: 12px; color: var(--mut); }
.fig-note { margin: 0; font-size: 12px; color: var(--mut); }
.fig-key { gap: 8px 14px; }
.fig-key b { font-size: 12.5px; color: var(--ink); font-variant-numeric: tabular-nums; }

/* THE PLOT SCALES, ITS TEXT WITH IT — which is why figures.js keeps the viewBox
 * only 360 units wide. In the 320px sticky column beside the writing sheet this
 * lands at about 270px, where a 12-unit label is 9px; the max-width above stops
 * the same label growing past ~17px on the exam screen. Nothing in here is
 * sized in JS. */
.fig-svg { width: 100%; height: auto; display: block; overflow: visible; }
.fig-grid { stroke: var(--grid); stroke-width: 1; }
.fig-axis { stroke: var(--axis); stroke-width: 1; }
.fig-tick { stroke: var(--axis); stroke-width: 1; }
.fig-lbl { font-size: 12px; fill: var(--mut); font-variant-numeric: tabular-nums; }
.fig-cat { font-size: 12px; fill: var(--ink2); }
.fig-val { font-size: 12px; font-weight: 600; fill: var(--ink); font-variant-numeric: tabular-nums; }
.fig-dot { stroke: var(--card); stroke-width: 1.5; }
/* The gap between slices is the CARD showing through, not a grey rule — on the
 * night ground a grey rule between two dark slices reads as a seventh colour. */
.fig-slice { stroke: var(--card); stroke-width: 2.5; }

/* The table is a table. Nine questions in the bank are one, and «the table
 * shows» is the sentence those nine want out of the student. Tabular figures so
 * a column of 40.7 / 42.4 / 44.5 lines up as a column. */
.fig-scroll { overflow-x: auto; max-width: 100%; }
.fig-tbl { border-collapse: collapse; width: 100%; font-size: 13px; }
.fig-tbl caption { caption-side: bottom; padding-top: 8px; font-size: 12px; color: var(--mut); text-align: left; }
.fig-tbl th, .fig-tbl td { padding: 7px 10px; border-bottom: 1px solid var(--line); text-align: right; white-space: nowrap; }
.fig-tbl thead th { font-size: 12px; font-weight: 700; color: var(--mut); border-bottom: 1px solid var(--line2); }
.fig-tbl tbody th { text-align: left; font-weight: 600; color: var(--ink); }
.fig-tbl td { color: var(--ink); font-variant-numeric: tabular-nums; }
.fig-tbl tbody tr:last-child th, .fig-tbl tbody tr:last-child td { border-bottom: 0; }

/* The process, numbered, with the direction between the steps drawn rather than
 * implied: the line and the arrowhead are what make it a sequence instead of a
 * list, and the sequence is the whole thing being described. */
.fig-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 16px; }
.fig-step { position: relative; display: flex; align-items: flex-start; gap: 12px; }
.fig-step-n {
  flex-shrink: 0; width: 26px; height: 26px; border-radius: 50%;
  background: var(--card2); color: var(--ink); font-size: 12.5px; font-weight: 700;
  font-variant-numeric: tabular-nums;
  display: inline-flex; align-items: center; justify-content: center;
}
.fig-step-b { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fig-step-b b { font-size: 13.5px; font-weight: 700; color: var(--ink); line-height: 1.35; }
.fig-step-b span { font-size: 12.5px; color: var(--ink2); line-height: 1.45; }
.fig-step + .fig-step::before {
  content: ""; position: absolute; left: 12px; top: -16px; width: 2px; height: 11px; background: var(--axis);
}
.fig-step + .fig-step::after {
  content: ""; position: absolute; left: 8px; top: -6px;
  border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 6px solid var(--axis);
}

/* Every figure carries the grader's own description of itself, folded away. It
 * is the text alternative for a student who cannot see the picture, and the
 * place every number lives when the picture is too narrow to print them all.
 *
 * The generic `details`/`summary` further up dress the report's accordion — a
 * rule under the fold, 13px of padding, 14px bold. This fold is a footnote
 * under a picture, so it takes back the three of those that would make it look
 * like a section of the page rather than a note about the figure. */
.fig-txt p { margin: 8px 0 0; white-space: pre-line; line-height: 1.5; }
.fig-prose { margin: 0; font-size: 12.5px; color: var(--ink2); white-space: pre-line; line-height: 1.5; }

/* 104px on a bank row. No text: the row already says «line graph» in words, and
 * what the words cannot give you is the shape — four columns, three rising
 * lines, a whole cut into six, a grid, a chain of stages. */
.qthumb .fig-thumb { width: 100%; height: auto; display: block; }
.qthumb .fig-slice { stroke: var(--card2); stroke-width: 2; }
.fig-cell { fill: var(--line); }
.fig-cell.on { fill: var(--line2); }

/* ------------------------------------------------ the question, on a phone */
/* .qcard is a row: tag, question, the two links. At 390px that row gives the
 * QUESTION 85px of the 358 available — an eight-character column 566px tall —
 * while the tag takes 107 and the links 159. The one thing the screen exists
 * for ends up the narrowest thing on it. Measured, not guessed.
 *
 * Below 700px it stacks: the question gets the full width and reads first,
 * with the tag above it and the links under it where a thumb can reach them. */
@media (max-width: 700px) {
  .qcard { flex-direction: column; align-items: flex-start; gap: 10px; }
  .qcard .qtext { width: 100%; order: 2; }
  .qcard .tag { order: 1; }
  .qcard .meta, .qcard .qacts { order: 3; width: 100%; }
}

/* ==================================================== the profile screen
 * Built from parts that already exist — .card, .crits, .optrow/.opt, .idlist,
 * .field — so there is almost nothing here. What there is, is the three places
 * that page needs a shape the design system has not had a use for before. */

/* The name, once, big, with the mark beside it. */
.prof-head { display: flex; align-items: center; gap: 14px; }
.prof-head b { font-family: var(--fd); font-size: 20px; font-weight: 700; display: block; }

/* THREE TILES, NOT FOUR. .crits is a four-column grid because a report has four
   criteria; this page has three things to say and a fourth empty cell would read
   as a number that failed to load. */
.prof-figs { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* TWO, NOT ONE, on a phone. Three full-width tiles is most of a screen spent on
   three short facts; two columns is what .crits itself drops to, and the third
   wrapping alone under them reads as the door it is. */
@media (max-width: 700px) { .prof-figs { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* The tile that is a door rather than a figure: its "value" is the chart glyph,
   so it takes the ink of a number without pretending to be one. */
.prof-more { color: var(--mut); display: flex; align-items: center; }

/* The picker is .opt, with the avatar itself as the swatch — which is why the
   preview is a real .ava: it shows the mark on --act-bg, the ground it will
   actually be worn on, in whichever theme the student is in. */
.prof-ava { flex: 0 1 116px; align-items: center; text-align: center; gap: 8px; }
.prof-ava span { font-size: 13px; }
.prof-ava.on span { color: var(--accent); }
