/* ══════════════════════════════════════════════════════════════════════════
   dv4's component layer, ported.

   `dv4-skin.html` at the repository root is the authority (docs/08). Every
   value here is the skin's, with the two corrections the skin's own record
   specifies:

     amber  #8f5c0f (5.15:1)  not  #b57614 (3.43:1)
     muted  fg3               not  fg4

   Light only: the product shipped light-only and this commits to the same.

   ── Scale ────────────────────────────────────────────────────────────────
   The root is dv4's root: `html { font-size: 100% }`. Every rem in this file
   is dv4's own value for the same place, so each surface renders at dv4's
   exact pixel size (14px body, 12px tabs and bylines, 21.6px page title).
   It shipped at 122%, then 112%, and both read bigger than dv4 side by side;
   parity per place is what the product owner chose (2026-08-31). A
   percentage rather than a px value so a reader who has raised their
   browser's font size keeps that gain.

   Tap floors stay in px: they are physical, not typographic, and must not
   move with the type scale.
   ══════════════════════════════════════════════════════════════════════════ */

/* IRANSansWeb(FaNum) — the exact face dv4 ships (dv4 myfont.scss), converted
   woff→woff2. ONE regular cut: anything ≥600 is browser-synthesized bold and
   500 renders as the regular glyph, which is precisely why dv4 never shouts.
   FaNum: ASCII digits render with Persian shapes. Proprietary (FontIran),
   licensed alongside dv4 — not OFL like the Vazirmatn it replaced.
   The URL is rewritten to a content-addressed one at startup (src/assets.rs);
   `/static/ec4f7d2095721e4264ba40573dfc1d61.woff2` never reaches a browser. */
@font-face {
  font-family: "IRANSans";
  src: url("/static/ec4f7d2095721e4264ba40573dfc1d61.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;          /* fallback text for 200ms beats no text */
}

/* ── Identifiers. FaNum draws every ASCII digit with a Persian shape, so a
   login such as `student7` read as «student۷» wherever it appeared — and a
   new student reading the printed slip tried to type ۷. Logins take the
   sheet's Latin monospace, in text and in the fields a person types one into
   (docs/05: identifiers are not copy). ─────────────────────────────────── */
/* `.input` sets the `font` shorthand, which resets the family, so the field
   rules need the extra class to win. */
.login, .input.login, .input[name="login"], .c-login { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .95em; }
.login { direction: ltr; unicode-bidi: isolate; }

/* ── Matching pairs: the author's rows and the candidate's controls. ─────── */
.matching-editor-rows, .matching-question { display: grid; gap: .5rem; }
.matching-editor-rows { margin-bottom: .35rem; }
.matching-editor-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; gap: .5rem; align-items: center; }
/* Row removal is a red text link like an attachment's «حذف»; adding a row is
   a quiet link — a bordered box on every row and a full-width grey bar read
   as one disabled control after another. */
.matching-editor-row .danger-button { border-color: transparent; padding: 0 .45rem; }
.matching-editor-row .danger-button:hover { background: color-mix(in srgb, var(--red) 6%, transparent); }
.matching-editor-add { display: inline-flex; align-items: center; min-block-size: var(--tap); padding: 0 .2rem; border: none; background: none; color: var(--blue); font-size: .78rem; font-weight: 600; cursor: pointer; }
.matching-editor-add:hover { text-decoration: underline; }
.matching-question label { display: grid; grid-template-columns: minmax(7.5rem, 1fr) minmax(10rem, 1fr); gap: .75rem; align-items: center; }
.quiz-hint { margin-top: .3rem; color: var(--fg4); font-size: .72rem; }

/* ── Operations: accounts. ───────────────────────────────────────────────── */
.ops-create { margin: 0 0 1rem; }
.people-list { border-top: 1px solid var(--bg2); }
.person-access { border-bottom: 1px solid var(--bg1); }
.person-access > summary { display: flex; align-items: center; gap: .6rem; min-height: 3.2rem; padding: .4rem .2rem; cursor: pointer; list-style: none; }
.person-access > summary::-webkit-details-marker { display: none; }
.person-access > summary:hover { background: var(--row-hover); }
.person-access > summary .chev { flex-shrink: 0; display: flex; color: var(--fg3); opacity: .7; transition: transform .15s; transform: rotate(90deg); }
.person-access[open] > summary .chev { transform: rotate(0); }
.person-name { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; }
.person-name strong { font-weight: 500; font-size: .82rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.person-name small { color: var(--fg4); font-size: .7rem; }
.person-access > summary .chips { flex-shrink: 0; }
.person-access.is-disabled > summary .person-name { opacity: .7; }
.person-body { padding: .2rem .2rem 1rem 0; }
.person-sec { margin-top: .9rem; }
.person-sec .form { margin-top: .5rem; gap: .6rem; }
.person-sec .danger-row { max-width: 34rem; margin-top: .5rem; }
.role-facts-reason { color: var(--fg3); }
.form-row-end { display: flex; align-items: end; }

/* ── The one-time credential sheet: hairline cards, no radius, the slip's
   password face; two columns when printed. ─────────────────────────────── */
.credential-sheet { display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); gap: .75rem; margin-top: 1rem; }
.credential-card { border: 1px solid var(--bg2); padding: .9rem 1rem; background: var(--content); }
.credential-card > strong { display: block; font-weight: 600; margin-bottom: .35rem; }
.credential-card dl { margin: 0; }
.credential-card dl div { display: flex; justify-content: space-between; align-items: baseline; gap: .75rem; padding: .35rem 0; border-top: 1px solid var(--bg1); }
.credential-card dt { color: var(--fg3); font-size: .72rem; }
.credential-card dd { margin: 0; }
.credential-foot { margin-top: 1.2rem; }
@media (max-width: 43rem) { .matching-editor-row, .matching-question label { grid-template-columns: 1fr; } .person-access > summary { flex-wrap: wrap; } }
@media print { .side, .top, .mobile-nav, .page-hd-actions, .crumb, .notice, .credential-foot { display: none !important; } .wrap, .body { display: block !important; margin: 0 !important; padding: 0 !important; } .credential-sheet { grid-template-columns: repeat(2, 1fr); } }

:root {
  color-scheme: light;

  /* Gruvbox Light Hard */
  --bg0: #f9f5d7;              /* page ground   */
  --bg1: #f2e8c4;              /* raised / row divider */
  --bg2: #e7ddbc;              /* hairline      */
  /* The surface must be visibly lighter than the ground or every boundary
     has to be drawn as a line. dv4's content plane sits a clear step above
     its cream page; #faf7e0 was 3 RGB points from bg0 and read as one flat
     wash. */
  --content: #fdfbf2;          /* surface       */
  --fg0: #282828;              /* emphasis      */
  --fg1: #3c3836;              /* body + titles */
  --fg3: #665c54;              /* muted on tinted fills / the cream ground */
  /* dv4's muted ink. 4.7:1 on the content surface — legal there and only
     there; on bg0/bg1 it drops under the 4.5:1 gate, where fg3 stays. */
  --fg4: #7c6f64;              /* muted on the content surface */
  --blue: #076678;             /* the primary: links, buttons, the current page */
  --accent: var(--blue);       /* the highlight: counts, chips, bars, progress. The
                                  same ink here; green and mocha split the two */
  --amber: #8f5c0f;            /* overdue / waiting */
  --aqua: #427b58;             /* affirmative   */
  --red: #9d0006;              /* negative      */

  /* The tint engine. Two triplets drive every fill in the system: one neutral
     scale for elevation, one accent scale for identity. No shadows. */
  --accent-rgb: 7, 102, 120;
  --neutral-rgb: 213, 196, 161;
  --accent-tint: rgba(7, 102, 120, .10);
  --accent-tint-hover: rgba(7, 102, 120, .16);
  --accent-edge: rgba(7, 102, 120, .22);
  --accent-edge-hover: rgba(7, 102, 120, .38);
  --row-hover: rgba(213, 196, 161, .24);   /* dv4's exact row hover */

  /* The one place dv4 allows depth. */
  --bar-bg: #3c3836;
  --bar-fg: #f9f5d7;

  --tap: 44px;                 /* floor, not a preference — docs/08 */
}

/* ══════════════════════════════════════════════════════════════════════════
   THEMES — dv4's four palettes (tabler_gruvbox_light_hard, tabler_blue_theme,
   tabler_green_theme, tabler_mocha_theme), each in a light and a dark cut.
   The `<html>` element carries `data-theme` (palette; absent = gruvbox) and
   `data-mode` (light|dark; absent = follow the device), stamped server-side
   from the theme cookie (src/theme.rs) so nothing ever flashes. Every rule
   above reads tokens, so a palette is only this block.

   Two inks per palette: `--blue` is the PRIMARY (links, buttons, the current
   page) and `--accent` the HIGHLIGHT (counts, chips, accent bars, progress).
   Gruvbox and blue draw both with one ink; green and mocha are dv4's two-tone
   pairs — green/amber, mocha/gold. Where dv4's own values fell under the 4.5:1
   text gate the audit enforces (docs/09) they are stepped darker or brighter
   here; the tints and neutrals are dv4's.
   ══════════════════════════════════════════════════════════════════════════ */
:root[data-mode="dark"] {
  color-scheme: dark;
  --bg0: #1d2021; --bg1: #3c3836; --bg2: #504945; --content: #282828;
  --fg0: #fbf1c7; --fg1: #ebdbb2; --fg3: #bdae93; --fg4: #a89984;
  --blue: #83a598; --accent: #83a598; --amber: #fabd2f; --aqua: #8ec07c; --red: #fb4934;
  --accent-rgb: 131, 165, 152; --neutral-rgb: 213, 196, 161;
  --accent-tint: rgba(131, 165, 152, 0.14); --accent-tint-hover: rgba(131, 165, 152, 0.22);
  --accent-edge: rgba(131, 165, 152, 0.3); --accent-edge-hover: rgba(131, 165, 152, 0.48);
  --row-hover: rgba(213, 196, 161, .10); --bar-bg: #16181a; --bar-fg: #fbf1c7;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-mode="light"]) {
  color-scheme: dark;
  --bg0: #1d2021; --bg1: #3c3836; --bg2: #504945; --content: #282828;
  --fg0: #fbf1c7; --fg1: #ebdbb2; --fg3: #bdae93; --fg4: #a89984;
  --blue: #83a598; --accent: #83a598; --amber: #fabd2f; --aqua: #8ec07c; --red: #fb4934;
  --accent-rgb: 131, 165, 152; --neutral-rgb: 213, 196, 161;
  --accent-tint: rgba(131, 165, 152, 0.14); --accent-tint-hover: rgba(131, 165, 152, 0.22);
  --accent-edge: rgba(131, 165, 152, 0.3); --accent-edge-hover: rgba(131, 165, 152, 0.48);
  --row-hover: rgba(213, 196, 161, .10); --bar-bg: #16181a; --bar-fg: #fbf1c7;
  }
}
:root[data-theme="blue"] {
  color-scheme: light;
  --bg0: #f4f6f8; --bg1: #e9edf1; --bg2: #d8dee5; --content: #ffffff;
  --fg0: #1a2230; --fg1: #2b3648; --fg3: #5a6575; --fg4: #6b7686;
  --blue: #1971c2; --accent: #1971c2; --amber: #b45309; --aqua: #0b7285; --red: #c92a2a;
  --accent-rgb: 25, 113, 194; --neutral-rgb: 196, 206, 220;
  --accent-tint: rgba(25, 113, 194, 0.1); --accent-tint-hover: rgba(25, 113, 194, 0.16);
  --accent-edge: rgba(25, 113, 194, 0.25); --accent-edge-hover: rgba(25, 113, 194, 0.4);
  --row-hover: rgba(25, 113, 194, .06); --bar-bg: #2b3648; --bar-fg: #eef2f7;
}
:root[data-theme="blue"][data-mode="dark"] {
  color-scheme: dark;
  --bg0: #0f1620; --bg1: #1c2733; --bg2: #2c3a4a; --content: #16202b;
  --fg0: #e9eef5; --fg1: #d4dce6; --fg3: #a3afbf; --fg4: #8a97a8;
  --blue: #4dabf7; --accent: #4dabf7; --amber: #ffd43b; --aqua: #38d9a9; --red: #ff6b6b;
  --accent-rgb: 77, 171, 247; --neutral-rgb: 138, 151, 168;
  --accent-tint: rgba(77, 171, 247, 0.14); --accent-tint-hover: rgba(77, 171, 247, 0.22);
  --accent-edge: rgba(77, 171, 247, 0.3); --accent-edge-hover: rgba(77, 171, 247, 0.48);
  --row-hover: rgba(77, 171, 247, .08); --bar-bg: #0f1620; --bar-fg: #e9eef5;
}
@media (prefers-color-scheme: dark) {
  :root[data-theme="blue"]:not([data-mode="light"]) {
  color-scheme: dark;
  --bg0: #0f1620; --bg1: #1c2733; --bg2: #2c3a4a; --content: #16202b;
  --fg0: #e9eef5; --fg1: #d4dce6; --fg3: #a3afbf; --fg4: #8a97a8;
  --blue: #4dabf7; --accent: #4dabf7; --amber: #ffd43b; --aqua: #38d9a9; --red: #ff6b6b;
  --accent-rgb: 77, 171, 247; --neutral-rgb: 138, 151, 168;
  --accent-tint: rgba(77, 171, 247, 0.14); --accent-tint-hover: rgba(77, 171, 247, 0.22);
  --accent-edge: rgba(77, 171, 247, 0.3); --accent-edge-hover: rgba(77, 171, 247, 0.48);
  --row-hover: rgba(77, 171, 247, .08); --bar-bg: #0f1620; --bar-fg: #e9eef5;
  }
}
:root[data-theme="green"] {
  color-scheme: light;
  --bg0: #e9f4ee; --bg1: #dcece3; --bg2: #cfe2d6; --content: #f3faf6;
  --fg0: #1b2420; --fg1: #2b3a32; --fg3: #5f6e65; --fg4: #667569;
  --blue: #237a36; --accent: #946800; --amber: #a05a00; --aqua: #0b7285; --red: #c92a2a;
  --accent-rgb: 232, 163, 26; --neutral-rgb: 200, 212, 204;
  --accent-tint: rgba(232, 163, 26, 0.12); --accent-tint-hover: rgba(232, 163, 26, 0.18);
  --accent-edge: rgba(232, 163, 26, 0.28); --accent-edge-hover: rgba(232, 163, 26, 0.42);
  --row-hover: rgba(232, 163, 26, .07); --bar-bg: #2b3a32; --bar-fg: #eef5f0;
}
:root[data-theme="green"][data-mode="dark"] {
  color-scheme: dark;
  --bg0: #0f1711; --bg1: #18241c; --bg2: #2a3a2f; --content: #14201a;
  --fg0: #e9f1ea; --fg1: #d2e0d5; --fg3: #9fb0a5; --fg4: #88988c;
  --blue: #51cf66; --accent: #ffcb47; --amber: #ffd43b; --aqua: #63e6be; --red: #ff6b6b;
  --accent-rgb: 252, 196, 25; --neutral-rgb: 138, 160, 146;
  --accent-tint: rgba(252, 196, 25, 0.14); --accent-tint-hover: rgba(252, 196, 25, 0.22);
  --accent-edge: rgba(252, 196, 25, 0.3); --accent-edge-hover: rgba(252, 196, 25, 0.48);
  --row-hover: rgba(252, 196, 25, .09); --bar-bg: #0f1711; --bar-fg: #e9f1ea;
}
@media (prefers-color-scheme: dark) {
  :root[data-theme="green"]:not([data-mode="light"]) {
  color-scheme: dark;
  --bg0: #0f1711; --bg1: #18241c; --bg2: #2a3a2f; --content: #14201a;
  --fg0: #e9f1ea; --fg1: #d2e0d5; --fg3: #9fb0a5; --fg4: #88988c;
  --blue: #51cf66; --accent: #ffcb47; --amber: #ffd43b; --aqua: #63e6be; --red: #ff6b6b;
  --accent-rgb: 252, 196, 25; --neutral-rgb: 138, 160, 146;
  --accent-tint: rgba(252, 196, 25, 0.14); --accent-tint-hover: rgba(252, 196, 25, 0.22);
  --accent-edge: rgba(252, 196, 25, 0.3); --accent-edge-hover: rgba(252, 196, 25, 0.48);
  --row-hover: rgba(252, 196, 25, .09); --bar-bg: #0f1711; --bar-fg: #e9f1ea;
  }
}
:root[data-theme="mocha"] {
  color-scheme: light;
  --bg0: #efe7d9; --bg1: #e6dccb; --bg2: #dccfb8; --content: #faf5ee;
  --fg0: #2c241c; --fg1: #3d342a; --fg3: #6f6353; --fg4: #7a6d5c;
  --blue: #5f4636; --accent: #8f6d12; --amber: #a05a00; --aqua: #4f7a5a; --red: #c92a2a;
  --accent-rgb: 205, 164, 53; --neutral-rgb: 214, 202, 178;
  --accent-tint: rgba(205, 164, 53, 0.12); --accent-tint-hover: rgba(205, 164, 53, 0.18);
  --accent-edge: rgba(205, 164, 53, 0.28); --accent-edge-hover: rgba(205, 164, 53, 0.42);
  --row-hover: rgba(205, 164, 53, .07); --bar-bg: #3d342a; --bar-fg: #f7f1e6;
}
:root[data-theme="mocha"][data-mode="dark"] {
  color-scheme: dark;
  --bg0: #17120d; --bg1: #241c14; --bg2: #382d20; --content: #1e1813;
  --fg0: #f0e7da; --fg1: #e2d6c4; --fg3: #bfae99; --fg4: #a89a88;
  --blue: #c99a6b; --accent: #ecc24f; --amber: #ffd43b; --aqua: #8ec07c; --red: #ff6b6b;
  --accent-rgb: 236, 194, 79; --neutral-rgb: 168, 154, 132;
  --accent-tint: rgba(236, 194, 79, 0.14); --accent-tint-hover: rgba(236, 194, 79, 0.22);
  --accent-edge: rgba(236, 194, 79, 0.3); --accent-edge-hover: rgba(236, 194, 79, 0.48);
  --row-hover: rgba(236, 194, 79, .09); --bar-bg: #17120d; --bar-fg: #f0e7da;
}
@media (prefers-color-scheme: dark) {
  :root[data-theme="mocha"]:not([data-mode="light"]) {
  color-scheme: dark;
  --bg0: #17120d; --bg1: #241c14; --bg2: #382d20; --content: #1e1813;
  --fg0: #f0e7da; --fg1: #e2d6c4; --fg3: #bfae99; --fg4: #a89a88;
  --blue: #c99a6b; --accent: #ecc24f; --amber: #ffd43b; --aqua: #8ec07c; --red: #ff6b6b;
  --accent-rgb: 236, 194, 79; --neutral-rgb: 168, 154, 132;
  --accent-tint: rgba(236, 194, 79, 0.14); --accent-tint-hover: rgba(236, 194, 79, 0.22);
  --accent-edge: rgba(236, 194, 79, 0.3); --accent-edge-hover: rgba(236, 194, 79, 0.48);
  --row-hover: rgba(236, 194, 79, .09); --bar-bg: #17120d; --bar-fg: #f0e7da;
  }
}

/* Dark cuts: the surface is what the page paints, so the few places that
   asked for a literal white or a light-only shadow follow the tokens. */
:root[data-mode="dark"] .sheet-page { background: var(--content); }
@media (prefers-color-scheme: dark) { :root:not([data-mode="light"]) .sheet-page { background: var(--content); } }
@media print { .sheet-page { background: #fff !important; } }

/* ── The settings page follows the class page's anatomy: `.manage-section`
   strips at 48rem with a 34rem form, an aside with the page's own table of
   contents. The palette picker is a row of swatches, each painted in its own
   palette so the choice can be seen before it is made. ─────────────────── */
.settings .manage-section:first-child { padding-top: 0; }
.settings .form { max-width: 34rem; }
.settings-notice { max-width: 48rem; margin-bottom: .6rem; }
.settings .form-row--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.theme-pick { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 .6rem; padding: 0; border: none; }
.theme-pick legend { padding: 0; margin-bottom: .4rem; font-size: .72rem; font-weight: 600; color: var(--fg3); }
.theme-swatch { display: inline-flex; align-items: center; gap: .5rem; min-height: var(--tap); padding: .3rem .7rem; border: 1px solid var(--bg2); background: var(--content); font-size: .8rem; cursor: pointer; transition: border-color .12s, background .12s; }
.theme-swatch:hover { border-color: var(--accent-edge-hover); }
.theme-swatch:has(input:checked) { border-color: var(--accent); background: var(--accent-tint); }
.theme-swatch input { accent-color: var(--accent); }
.theme-swatch-chip { display: inline-flex; gap: 2px; padding: 3px; border: 1px solid rgba(0, 0, 0, .12); background: var(--swatch-bg, #f9f5d7); }
.theme-swatch-chip i { display: block; width: 10px; height: 14px; }
.theme-swatch[data-palette="gruvbox"] { --swatch-bg: #f9f5d7; }
.theme-swatch[data-palette="gruvbox"] i:nth-child(1) { background: #fdfbf2; }
.theme-swatch[data-palette="gruvbox"] i:nth-child(2) { background: #076678; }
.theme-swatch[data-palette="gruvbox"] i:nth-child(3) { background: #8f5c0f; }
.theme-swatch[data-palette="blue"] { --swatch-bg: #f4f6f8; }
.theme-swatch[data-palette="blue"] i:nth-child(1) { background: #ffffff; }
.theme-swatch[data-palette="blue"] i:nth-child(2) { background: #1971c2; }
.theme-swatch[data-palette="blue"] i:nth-child(3) { background: #2b3648; }
.theme-swatch[data-palette="green"] { --swatch-bg: #e9f4ee; }
.theme-swatch[data-palette="green"] i:nth-child(1) { background: #f3faf6; }
.theme-swatch[data-palette="green"] i:nth-child(2) { background: #237a36; }
.theme-swatch[data-palette="green"] i:nth-child(3) { background: #e8a31a; }
.theme-swatch[data-palette="mocha"] { --swatch-bg: #efe7d9; }
.theme-swatch[data-palette="mocha"] i:nth-child(1) { background: #faf5ee; }
.theme-swatch[data-palette="mocha"] i:nth-child(2) { background: #5f4636; }
.theme-swatch[data-palette="mocha"] i:nth-child(3) { background: #cda435; }


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

html {
  font-size: 100%;             /* see the scale note above */
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg0);
  /* dv4's running text is a soft dark, not near-black; #282828 is kept for
     the few true-emphasis spots. */
  color: var(--fg1);
  font-family: "IRANSans", Tahoma, system-ui, sans-serif;
  font-size: .875rem;          /* 14px — dv4's body */
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* The browser's 700 default for headings and <strong> is the loudest voice in
   the file; dv4 titles are 600 and its "bold" numbers ~650. Weight above 600
   exists only where a class asks for it. */
h1, h2, h3 { font-weight: 600; }

h1, h2, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
svg { display: block; flex-shrink: 0; }
button { font: inherit; color: inherit; }

a:focus-visible,
button:focus-visible,
summary:focus-visible,
input:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

/* ══════════════════════════════════════════════════════════════════════════
   SHELL — skin section 01
   ══════════════════════════════════════════════════════════════════════════ */
.app {
  display: flex;
  min-height: 100vh;
  background: var(--bg0);
}

/* aside.navbar-vertical. The border is an inset shadow in dv4, so it costs no
   layout box and cannot be double-drawn where two panels meet. */
.side {
  flex: 0 0 14.5rem;
  /* Without this the sidebar is as wide as its longest course title: a flex
     item's automatic minimum size is its min-content, and `white-space: nowrap`
     on a truncating link makes that the whole untruncated string. The basis is
     then a suggestion the layout ignores. Found by rendering. */
  min-width: 0;
  /* Chrome lives on the ground; only the content region gets the surface.
     That single value difference is what draws the page's main boundary. */
  background: var(--bg0);
  box-shadow: inset 1px 0 0 0 var(--bg2);
  padding-bottom: .9rem;
}

.side-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  min-height: var(--tap);
  padding: .75rem 0;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--fg0);
}
.side-brand svg { color: var(--blue); }

/* .sidebar-section-label */
.side-label {
  padding: .5rem 1rem .2rem;
  font-size: .65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--fg3);
  border-top: 1px solid var(--bg2);
  margin-top: .4rem;
}
.side-label:first-of-type { border-top: none; margin-top: 0; }

/* .sidebar-group-btn. The accent bar is the only thing that marks the current
   page: one border-colour declaration, no pill, no shadow. */
.side-btn {
  display: flex;
  align-items: center;
  gap: .4rem;
  width: 100%;
  min-height: var(--tap);
  padding: .5rem 1rem;
  border: none;
  border-inline-start: 3px solid transparent;
  background: none;
  color: var(--fg1);
  font-size: .875rem;
  font-weight: 500;
  line-height: 1.4;
  text-align: start;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.side-btn:hover { background: rgba(var(--neutral-rgb), .30); color: var(--fg0); }
.side-btn.is-active {
  background: rgba(var(--accent-rgb), .08);
  color: var(--blue);
  font-weight: 600;
  border-inline-start-color: var(--blue);
}
.side-btn .ico { opacity: .7; display: flex; }
.side-btn.is-active .ico { opacity: 1; }
.side-btn .label { flex: 1 1 auto; min-width: 0; }
.side-btn .chev { opacity: .45; display: flex; transition: transform .12s; }

/* .sidebar-sub a — indented one step, never given its own icon. */
.side-sub-link {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  padding: .45rem 1rem .45rem 2.25rem;
  padding-inline: 2.25rem 1rem;
  border-inline-start: 3px solid transparent;
  font-size: .82rem;
  color: var(--fg3);
  transition: background .15s, color .15s, border-color .15s;
}
.side-sub-link:hover:not(.is-active) { background: rgba(var(--neutral-rgb), .25); color: var(--fg1); }
.side-sub-link.is-active {
  background: rgba(var(--accent-rgb), .09);
  color: var(--blue);
  font-weight: 600;
  border-inline-start-color: var(--blue);
}
/* `min-width: 0` is what makes the ellipsis work: a flex item's default
   minimum is its content, so without it a long course title pushes past the
   sidebar instead of truncating (docs/08). */
.side-sub-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* .page-wrapper. It carries the content surface itself: `.teacher-body` is a
   centred max-width column, and on a wide screen the area around it would
   otherwise show the cream ground as a stray column beside the sidebar. */
.wrap { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; background: var(--content); }

/* header.navbar */
.top {
  display: flex;
  align-items: center;
  gap: .25rem;
  min-height: 46px;
  padding: 0 1rem;
  background: var(--bg0);
  border-bottom: 1px solid var(--bg2);
}
.top .who {
  margin-inline-start: auto;
  font-size: .8rem;
  color: var(--fg3);
  padding: .35rem .2rem;
}

/* .cb-trigger-btn — dv4's fake search input in the header (command_bar.scss
   L200). A pill that opens the palette; the Ctrl+K chip sits one shade lighter
   than the button, which reads as inset. */
/* The pill is dv4-slim while the hit box keeps the floor: the extra height
   is a transparent border, the visible 1px edge an inset shadow at the
   padding box, and the fill is clipped to the padding box. */
.qa-trigger {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  min-height: var(--tap);
  padding: 0 .6rem;
  border: 5px solid transparent;
  border-radius: 11px;
  background: var(--bg1);
  background-clip: padding-box;
  box-shadow: inset 0 0 0 1px var(--bg2);
  color: var(--fg3);
  font-size: .8rem;
  cursor: pointer;
  transition: background .12s, color .12s;
}
.qa-trigger:hover { background: var(--bg2); background-clip: padding-box; color: var(--fg0); }
.qa kbd {
  padding: 0 .3rem;
  border: 1px solid var(--bg2);
  border-radius: 3px;
  background: var(--bg0);
  color: var(--fg3);
  font-family: ui-monospace, monospace;
  font-size: .65rem;
  line-height: 1.6;
}
/* Inside the trigger the shortcut is a HINT, not a control — dv4 sets it as
   faint inline text; a bordered chip read as loud as the label itself. */
.qa-trigger kbd {
  color: var(--fg3);
  opacity: .75;
  font-family: ui-monospace, monospace;
  font-size: .69rem;
}
.top .who-name { font-weight: 600; color: var(--fg1); }
.top a, .top .linkish {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0 .6rem;
  font-size: .8rem;
  color: var(--fg3);
  transition: color .12s;
}
.top a:hover, .top .linkish:hover { color: var(--blue); }
.inline-form { display: inline-flex; }
.linkish { background: none; border: none; cursor: pointer; }
/* Author styles beat the UA's [hidden]; without this reset an .icon-btn
   stays visible even while hidden. */
[hidden] { display: none !important; }
.top .nav-toggle { display: none; }
.nav-backdrop { display: none; }

/* .page-body */
.body { flex: 1 1 auto; padding: .9rem 1rem 1.4rem; background: var(--content); }

/* .row → .col-lg-9 + .col-lg-3.order-lg-1 — dv4's show pages. Main is written
   first and laid out first, so in RTL it stands beside the nav sidebar and the
   info aside lands on the far left: sidebar ‹ main ‹ aside, exactly dv4's
   order. A grid rather than Bootstrap's flex + order, so it stacks on a phone
   with no breakpoint to keep in sync (docs/08). */
.cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 16.5rem;
  align-items: start;
}
/* The gap belongs on the edge that faces the aside — main's inline END, since
   the aside is now the left-hand column. The hairline is the aside's
   `border-inline-start` (wikis.scss L1465): its right edge, the one boundary
   between content and metadata. */
.col-main { min-width: 0; padding-inline-end: 1rem; }
/* The aside column stretches to the row so `.aside-inner` has room to stick:
   under the grid's `align-items: start` the column was only as tall as its
   own content, and "sticky" had nowhere to go — no aside had ever actually
   stayed on screen (found by measuring, 2026-09-04). The hairline now runs
   the full height, which is dv4's boundary anyway. */
.col-aside { align-self: stretch; border-inline-start: 1px solid var(--bg2); }
.aside-inner { position: sticky; top: 1rem; }
/* dv4's edit sidebar holds the uploads; at 16.5rem the file rows drop their
   icon and let the actions share the name's line. */
.aside-inner .attachment-list > div { display: flex; flex-wrap: wrap; align-items: center; }
.aside-inner .attachment-list > div > span { flex: 1 1 100%; min-width: 0; }
.aside-inner .attachment-list .file-mark { display: none; }
.aside-inner .attachment-upload { flex-direction: column; align-items: stretch; }

@media (max-width: 62rem) {
  .cols { grid-template-columns: minmax(0, 1fr); }
  .col-main { padding-inline-end: 0; }
  .col-aside { border-inline-start: none; border-top: 1px solid var(--bg2); margin-top: 1rem; }
  .aside-inner { position: static; }
  .side { display: none; }
  /* The bottom nav carries the five primary destinations; everything else in
     the sidebar — course sub-links, the workspace switcher, the password
     link — opens as a drawer from the toggle. An overlay, so it shares the
     palette's licence for depth. */
  .top .nav-toggle { display: inline-flex; }
  body.nav-open .side {
    display: block;
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0;    /* the sidebar's own edge: the right, in RTL */
    z-index: 70;
    width: min(19rem, 85vw);
    overflow-y: auto;
    background: var(--bg0);
    box-shadow: 0 0 40px rgba(40, 40, 40, .25);
  }
  body.nav-open .nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 60;
    border: none;
    background: rgba(40, 40, 40, .45);
    cursor: pointer;
  }
  body.nav-open { overflow: hidden; }
}

/* ol.breadcrumb. The separator is `‹`, which the bidi algorithm mirrors in an
   RTL run — typing `›` would render backwards. */
.crumb {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
  margin-bottom: .6rem;
  font-size: .75rem;
  color: var(--fg4);
}
/* A breadcrumb is a link a student taps, so it takes the 44px floor like every
   other one. The skin leaves these at their line box — 23px, which the
   rendered-page audit flags on every screen. The row grows by ~20px, and that
   is the trade docs/08 already made everywhere else. */
.crumb li { display: inline-flex; align-items: center; }
.crumb a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  /* Height was already taken to the floor here; width was not, so a crumb of a
     short word — "خانه" — rendered 25px wide. The floor is both dimensions,
     and students tap this on phones and tablets. */
  min-inline-size: var(--tap);
  color: var(--fg4);
  transition: color .12s;
}
.crumb a:hover { color: var(--blue); }
.crumb li + li::before { content: "‹"; margin-inline-end: .35rem; opacity: .5; }

/* ── DASHBOARD HEADER — dv4's dashboards have NO display title: the
   breadcrumb strip IS the header, the current item is the page's h1 at strip
   size, and the strip's far end carries the date or the page's one primary
   action. Content pages (a lesson, a course) keep .page-hd; the ten root
   list pages use this and give the saved 150px back to their content. ───── */
/* dv4's dashboard breadcrumb is a BAND (tabler_rtl_enhancements.scss §21:
   `.row .breadcrumb { padding: .875rem 1rem; background: rgba(0,0,0,.02);
   border: 1px solid rgba(0,0,0,.06); font-size: .875rem }`) — the page's top
   edge, with the current item bold at body size. The skin file's stripped
   version shipped first and left the title smaller than the rows under it
   (2026-09-05). */
.crumb-bar {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-bottom: 1rem;
  padding: .55rem 1rem;
  border: 1px solid var(--bg2);
  background: rgba(var(--neutral-rgb), .10);
}
.crumb-bar .crumb { flex: 1 1 auto; margin-bottom: 0; font-size: .875rem; }
.crumb-bar .crumb a { color: var(--fg3); }
.crumb-bar .crumb li + li::before { opacity: .45; }
.crumb h1 {
  font-size: .875rem;
  font-weight: 600;
  line-height: inherit;
  color: var(--fg0);
}
.crumb-note { flex-shrink: 0; color: var(--fg3); font-size: .78rem; }
.crumb-bar .go { margin-top: 0; }
.crumb-bar .icon-btn { color: var(--fg3); }

/* ══════════════════════════════════════════════════════════════════════════
   PAGE HEADER — .wiki-flat-header (wikis.scss L921). The breadcrumb stands
   alone on its own row above; then one flex row: the title group (title with
   its byline directly beneath) takes the start side, and the action cluster is
   pushed to the far end — the top-LEFT corner in RTL. The primary action is
   the first child of the cluster; the unimportant ones are icon-only ghosts.
   ══════════════════════════════════════════════════════════════════════════ */
.page-hd {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  margin-bottom: .9rem;
}
.page-hd-main { flex: 1 1 auto; min-width: 0; }
/* dv4's article title is ~20px, not a poster headline; section titles below
   must never compete with it. */
.page-hd h1 { font-size: 1.35rem; line-height: 1.35; color: var(--fg1); }
/* .wiki-byline sits inside the title group, never a separate row. */
.page-hd .byline { margin-top: .3rem; }
.byline-author { font-weight: 600; color: var(--fg1); }
.page-hd-actions {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: .15rem;
  padding-top: .3rem;
}
.page-hd-actions form { display: inline-flex; }
.page-hd-actions .go { margin-top: 0; }

/* .btn-action — Tabler's transparent ghost square: an icon, a title, and a
   fill on hover. Nothing else. It carries the tap floor like everything. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap);
  min-height: var(--tap);
  border: none;
  background: none;
  color: var(--fg3);
  cursor: pointer;
  transition: background .12s, color .12s;
}
.icon-btn:hover { background: rgba(var(--neutral-rgb), .30); color: var(--fg0); }
.icon-btn.is-danger { color: var(--red); }
.icon-btn.is-danger:hover { background: color-mix(in srgb, var(--red) 8%, transparent); color: var(--red); }

/* ══════════════════════════════════════════════════════════════════════════
   TABS — .wiki-page-tabs. The underline sits ON the strip, not above it.
   ══════════════════════════════════════════════════════════════════════════ */
/* These are dv4's PRIMARY tabs — `.wiki-tab { font-size: 13px; padding: 8px
   14px; gap: 5px }` under a 2px strip with 20px beneath. The 12px / 5px 11px
   pair is its secondary scope row (`.tabs--sub` below); the product shipped
   the secondary values as its page tabs once, and they read cramped and
   faint beside the original (2026-09-05). */
.tabs {
  display: flex;
  align-items: stretch;
  overflow-x: auto;
  margin-bottom: 1.25rem;
  border-bottom: 2px solid var(--bg2);
}
.tab {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  min-height: var(--tap);      /* floor */
  padding: .5rem .875rem;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  background: none;
  color: var(--fg4);
  font-size: .8125rem;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.tab:hover { color: var(--fg1); border-bottom-color: var(--bg2); }
.tab.is-active { color: var(--blue); border-bottom-color: var(--blue); font-weight: 600; }
.tab.is-placeholder { color: var(--fg3); opacity: .55; cursor: not-allowed; }
/* Every tab carries dv4's 16px icon at full ink. */
.tab svg { inline-size: 16px; block-size: 16px; opacity: .85; }
.tab.is-active svg { opacity: 1; color: var(--blue); }

/* .wiki-page-tabs--sub — the quieter nested strip, for pickers inside a page
   (the attendance session switcher), one step down from the page tabs. */
.tabs--sub { border-bottom-width: 1px; margin-bottom: .8rem; }
.tabs--sub .tab { font-size: .78rem; margin-bottom: -1px; }

/* ══════════════════════════════════════════════════════════════════════════
   BADGES — a count is accent-tinted; a zero is dimmed, never hidden.
   ══════════════════════════════════════════════════════════════════════════ */
.badge-total, .badge-count, .badge-zero {
  flex-shrink: 0;
  border-radius: 3px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.badge-total {
  background: var(--accent-tint);
  color: var(--accent);
  font-size: .7rem;
  font-weight: 600;
  padding: .25em .55em;
}
.badge-count {
  background: var(--accent-tint);
  color: var(--accent);
  font-size: .72rem;
  font-weight: 600;
  padding: .2em .5em;
  min-width: 1.6rem;
}
.badge-zero {
  background: var(--bg2);
  color: var(--fg3);
  font-size: .7rem;
  padding: .25em .55em;
}
/* A count that is WAITING on the reader is amber — dv4's dashboards find the
   actionable rows by colour, not by reading. Blue counts are inventory. */
.badge-warn {
  background: color-mix(in srgb, var(--amber) 12%, transparent);
  color: var(--amber);
  font-size: .72rem;
  font-weight: 600;
  padding: .2em .5em;
  min-width: 1.6rem;
  flex-shrink: 0;
  border-radius: 3px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* ══════════════════════════════════════════════════════════════════════════
   SECTIONS — .dash-wf-section over .wiki-dir-list. dv4's Pipelines idiom, and
   the thing that makes a page read as dv4: a 3px accent bar over a .15 neutral
   tint, with a 1px hairline body under it and no radius anywhere.
   ══════════════════════════════════════════════════════════════════════════ */
.sect { margin-bottom: 1.4rem; }
.sect:last-child { margin-bottom: 0; }

.sect-hd {
  display: flex;
  align-items: center;
  gap: .4rem;
  min-height: var(--tap);      /* the skin's own record: a 42.2px header failed */
  padding: .45rem .65rem;
  border-inline-start: 3px solid var(--fg3);
  background: rgba(var(--neutral-rgb), .15);
  margin-bottom: 1px;
  user-select: none;
  transition: background .12s, border-color .12s;
}
.sect-hd::-webkit-details-marker { display: none; }
.sect-hd { list-style: none; }
a.sect-hd, summary.sect-hd { cursor: pointer; }
.sect-hd:hover {
  background: rgba(var(--neutral-rgb), .30);
  border-inline-start-color: var(--accent);
}
.sect-hd:hover .sect-title { color: var(--blue); }
.sect-hd:hover .grip { color: var(--fg3); }

.grip { color: var(--bg2); display: flex; transition: color .12s; }
.sect-icon { color: var(--fg3); display: flex; }
.sect-title {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .82rem;
  font-weight: 700;            /* dv4's .dash-ac-title — the one 700 on a page */
  color: var(--fg1);
  transition: color .12s;
}
/* The strip is dv4's: .45rem/.65rem padding, an icon, the count, and the
   far-end quiet link — a strip with no body beneath it is never rendered;
   an empty section shows its calm line instead. */
.task-more { border-bottom: 1px solid var(--bg1); }
.task-more .quiet-link { display: flex; justify-content: flex-start; padding-inline: .6rem; font-size: .74rem; }

/* The collapse is a real <details>: keyboard-operable for free, no JavaScript
   (ADR-0001). Closed points along the reading direction — left, in RTL. */
.sect-hd .chev { color: var(--fg3); display: flex; transition: transform .12s; transform: rotate(90deg); }
.sect[open] > .sect-hd .chev { transform: rotate(0deg); }

.sect-bd {
  border: 1px solid var(--bg2);
  border-block-start: none;
  padding: .6rem .65rem;
}
/* A section whose body is a row list: the rows bring their own padding. */
.sect-bd--flush { padding: 0; }
/* The strip's far-end annotation — dv4 hangs "همهٔ …" / "N مورد پنهان شد"
   off the header itself instead of spending a hero button on it. The link
   keeps the 44px floors; the strip is already that tall. */
.sect-hd .quiet-link { font-size: .74rem; }

/* ══════════════════════════════════════════════════════════════════════════
   DIRECTORY ROWS — .wiki-dir-list. Rows are divided by bg1, one step lighter
   than the section's own bg2 border, so the list reads as inside the section.
   ══════════════════════════════════════════════════════════════════════════ */
.dir { border: 1px solid var(--bg2); border-block-start: none; }
/* A directory with no section header above it keeps its own top edge. */
.dir--solo { border-block-start: 1px solid var(--bg2); }
.dir-item + .dir-item { border-top: 1px solid var(--bg1); }
.dir-link {
  display: flex;
  align-items: center;
  gap: .5rem;
  min-height: var(--tap);      /* floor */
  padding: .45rem .65rem .45rem 1.6rem;
  padding-inline: 1.6rem .65rem;
  color: var(--fg1);
  transition: background .1s, color .1s;
}
.dir-link:hover { background: var(--row-hover); color: var(--fg0); }
.dir-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .82rem;
  font-weight: 500;
}
/* The dot fills only when there is something behind the row. An empty lesson
   gets a hollow ring and an em-dash, never a zero — a zero is a measurement. */
.state-icon { color: var(--accent); opacity: .55; display: flex; }
.dir-link--empty { color: var(--fg3); }
.dir-link--empty .dir-name { opacity: .65; }
.dir-link--empty .state-icon { color: var(--bg2); opacity: 1; }
.state-zero { color: var(--bg2); font-size: .8rem; flex-shrink: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   PROGRESS + THE QUIET BUTTON
   dv4 has no course card, so this is built from dv4's parts: a .wf-header, a
   hairline body, a .byline stat, and .dash-grp-send as the call to action.
   Nothing is invented.
   ══════════════════════════════════════════════════════════════════════════ */
.prog {
  height: 5px;
  background: var(--bg1);
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: .5rem;
}
.prog > i { display: block; height: 100%; background: var(--accent); }

/* The secondary button. It sits on the surface with a firm accent edge, one
   step quieter than .btn-primary's fill — the tinted version it replaced was
   three RGB points from the ground and read as a disabled control beside a
   solid teal one. The fill arrives on hover. */
.go {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  min-height: var(--tap);      /* floor: this is the one thing a student taps */
  margin-top: .5rem;
  cursor: pointer;
  padding: .3rem .8rem;
  border: 1px solid var(--accent-edge-hover);
  border-radius: 3px;
  background: var(--content);
  color: var(--blue);
  font-size: .78rem;
  font-weight: 600;
  transition: background .1s, border-color .1s;
}
.go:hover { background: var(--accent-tint); border-color: var(--blue); }

/* ── COURSE TILES — the student home's courses, one strip, a grid of tiles
   on a 1px bg1 lattice inside the strip's flush body. A tile answers two
   questions and asks nothing else: how far along am I, and what do I open
   next. Both links carry the student's 44px floor; the progress bar is not a
   control. ─────────────────────────────────────────────────────────────── */
/* As many columns as fit at 20rem: one course fills the row, two or more
   share it. The hairlines are each tile's own outline meeting in the 1px gap,
   not a painted lattice — a lattice showed as a grey block wherever the grid
   had a cell and no tile (one course, or an odd third). `overflow` clips those
   outlines at the section's own border. */
.ctiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 1px;
  overflow: hidden;
}
.ctile {
  display: flex;
  flex-direction: column;
  gap: .35rem;
  min-height: 6.5rem;
  padding: .45rem .75rem .5rem;
  background: var(--content);
  outline: 1px solid var(--bg1);
  color: var(--fg1);
}
.ctile-hd { display: flex; align-items: center; gap: .5rem; min-width: 0; }
.ctile-title {
  flex: 1 1 auto;
  display: inline-flex;
  align-items: center;
  min-width: 0;
  min-block-size: var(--tap);
  overflow: hidden;
  font-size: .875rem;
  font-weight: 600;
  color: var(--fg0);
  transition: color .12s;
}
.ctile-title bdi { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctile-title:hover { color: var(--blue); }
.ctile .prog { margin: 0; }
.ctile-stat { display: inline-flex; align-items: baseline; gap: .25rem; }
/* The resume link: a faint verb, then the lesson's own name in the accent —
   the name is the information, the verb is grammar. */
.ctile-next {
  margin-top: auto;
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  min-block-size: var(--tap);
  min-inline-size: var(--tap);
  max-width: 100%;
  font-size: .8rem;
  font-weight: 600;
  color: var(--blue);
}
.ctile-next bdi { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctile-next:hover { text-decoration: underline; }
.ctile-verb { font-weight: 500; color: var(--fg4); }
@media (max-width: 45rem) { .ctiles { grid-template-columns: minmax(0, 1fr); } }

/* ══════════════════════════════════════════════════════════════════════════
   ASIDE — .dash-sidebar-section + .wiki-byline. One declaration of structure:
   a hairline between sections. No card, no fill.
   ══════════════════════════════════════════════════════════════════════════ */
.side-sec { padding: .6rem .75rem .65rem; border-bottom: 1px solid var(--bg1); }

/* ── The info column runs a step smaller than the page. dv4's aside is 12px
   metadata and 11px labels beside a 14px body — glance material, not reading
   material. Everything inside the aside steps one rung down the ladder and
   its rows tighten; only the tap floors stay where they are (44px for a
   student, 32px on the dense teacher desktop). ─────────────────────────── */
.aside-inner { font-size: .78rem; }
.aside-inner .side-sec { padding: .5rem .65rem .55rem; }
.aside-inner .side-sec-label { font-size: .65rem; margin-bottom: .35rem; }
.aside-inner .byline { font-size: .7rem; }
.aside-inner .byline-dot { margin: 0 6px; }
.aside-inner .stat-num { font-size: .78rem; }
.aside-inner .stat-lbl { font-size: .68rem; }
.aside-inner .chip { font-size: .68rem; }
.aside-inner .meta-row { margin-bottom: .4rem; }
.aside-inner .meta-label { margin-bottom: .15rem; }
.aside-inner .quiet-link, .aside-inner .summary-count { font-size: .72rem; }
.aside-inner .feed-link, .aside-inner .act-text, .aside-inner .feed-empty { font-size: .7rem; }
.aside-inner .section-help { font-size: .68rem; }
.aside-inner .attachment-list > div { padding: .4rem 0; }
.aside-inner .attachment-list small { font-size: .65rem; }
.aside-inner .upload-field { padding: .45rem .6rem; gap: .35rem; }
.aside-inner .upload-btn, .aside-inner .upload-names { font-size: .68rem; }
.aside-inner .btn-primary, .aside-inner .quiet-button, .aside-inner .danger-button {
  min-inline-size: var(--tap);   /* the width floor survives the small type */
  font-size: .7rem;
  padding: 0 .55rem;
}
/* The upload button hugs its label like every other form button — a
   full-column bar read as a banner, not an action. */
.aside-inner .attachment-upload .btn-primary,
.aside-inner .attachment-upload .quiet-button { align-self: flex-start; }
/* In a file row the two actions speak the same language: دریافت is a quiet
   link, so حذف is a red one — a lone bordered box beside a bare link read as
   two different systems. The element stays a <button> in a POST form. */
.attachment-list .danger-button { border-color: transparent; padding: 0 .45rem; }
.attachment-list .danger-button:hover { background: color-mix(in srgb, var(--red) 6%, transparent); }
.aside-inner .work-facts dt { font-size: .65rem; }
.aside-inner .work-facts dd { font-size: .78rem; }
.aside-inner .work-facts dl div { padding: .4rem 0; }
.aside-inner .input { font-size: .74rem; padding: .25rem .5rem; }
.side-sec:last-child { border-bottom: none; }

.side-sec-label {
  display: flex;
  align-items: center;
  gap: .3rem;
  margin-bottom: .45rem;
  font-size: .65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--fg4);
}

.byline {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .3rem 0;
  font-size: .75rem;
  line-height: 1;
  color: var(--fg4);
}
.byline-item { display: inline-flex; align-items: center; gap: .2rem; white-space: nowrap; }
/* The drawn mid-dot for inline text runs. Beside a Persian digit a typed "·"
   is indistinguishable from ۰ — "۲۴ · پاسخ" reads as ۲۴۰. Same rule as
   .byline-dot (docs/05), sized for running text. */
.dot-sep {
  display: inline-block;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--fg3);
  opacity: .55;
  margin: 0 .3rem;
  vertical-align: .18em;
}
/* The mid-dot is DRAWN, never typed: a "·" between two numbers is bidi-neutral
   and the algorithm, not the template, would pick which side it lands on. */
.byline-dot {
  display: inline-block;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--bg2);
  margin: 0 .5rem;
  flex-shrink: 0;
}
/* A byline promoted to a stat strip under the tabs — the ops dashboards'
   at-a-glance row, dv4's aside stat line brought into the column. */
.stat-row { margin: .2rem 0 .9rem; }

.stat-num {
  font-size: .88rem;
  font-weight: 600;
  color: var(--fg1);
  margin-inline-end: .2rem;
  font-variant-numeric: tabular-nums;
}
.stat-lbl { font-size: .72rem; color: var(--fg4); }

.feed-empty { font-size: .74rem; color: var(--fg4); padding: .2rem 0; }
/* Quick links in an aside stack, one per line, start-aligned. The width
   floor stays — zeroing it once squeezed a دریافت link to 42px. */
.side-sec .quiet-link { display: flex; justify-content: flex-start; }

/* A placeholder should be visible enough not to look finished, and quiet
   enough not to read as data (the skin's own rule). */
.placeholder {
  border: 1px dashed var(--bg2);
  padding: .5rem;
  text-align: center;
  color: var(--fg3);
  opacity: .75;
}

/* .show-activity-feed */
.feed { display: flex; flex-direction: column; }
.act-row {
  display: flex;
  gap: .5rem;
  align-items: flex-start;
  padding: .4rem 0;
  border-bottom: 1px solid var(--bg1);
}
.act-row:last-child { border-bottom: none; }
.act-icon {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  margin-top: 1px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.act-fwd { background: color-mix(in srgb, var(--aqua) 12%, transparent); color: var(--aqua); }
.act-bck { background: color-mix(in srgb, var(--red) 8%, transparent); color: var(--red); }
.act-body { flex: 1 1 auto; min-width: 0; }
/* The shortcut to the most urgent piece of work. It shipped at 23px — barely
   half the 44px floor — on the one surface a student opens first. */
.feed-link {
  display: flex;
  align-items: center;
  min-block-size: 44px;
  font-size: .74rem;
  font-weight: 500;
  color: var(--fg1);
  line-height: 1.35;
}
.feed-link:hover { color: var(--blue); text-decoration: underline; }
.act-states { display: block; font-size: .7rem; color: var(--fg3); }
.age-late { color: var(--amber); }   /* whisper-quiet amber, never a red field */

/* .show-meta-row + .ue-chips — aside metadata as quiet accent chips
   (article_unified_edit.scss L390-420). A label over a row of chips; the dim
   variant for dates and states that are facts, not links. */
.meta-row { margin-bottom: .55rem; }
.meta-row:last-child { margin-bottom: 0; }
.meta-label {
  display: block;
  margin-bottom: .2rem;
  color: var(--fg3);
  font-size: .65rem;
  font-weight: 600;
  letter-spacing: .07em;
}
.chips { display: flex; flex-wrap: wrap; gap: .2rem; }
.chip {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0 .35rem;
  background: rgba(var(--accent-rgb), .07);
  border: 1px solid rgba(var(--accent-rgb), .18);
  color: var(--accent);
  font-size: .72rem;
  line-height: 1.8;
}
.chip--dim { background: var(--bg1); border-color: var(--bg2); color: var(--fg3); }

/* ══════════════════════════════════════════════════════════════════════════
   EMPTY STATE — a 40px icon at .3, two lines, generous padding. dv4 never
   shows an empty card with a border round it.
   ══════════════════════════════════════════════════════════════════════════ */
.empty { text-align: center; color: var(--fg3); padding: 2rem 1rem; }
.empty svg { opacity: .3; margin: 0 auto .75rem; color: var(--fg3); }
.empty p { font-size: .82rem; font-weight: 500; color: var(--fg3); }
.empty p + p { margin-top: .3rem; font-size: .8rem; font-weight: 400; }

/* ══════════════════════════════════════════════════════════════════════════
   ERROR PAGE — the empty state, with a reason.
   Same centred column as sign-in, same 40px mark at .3 as `.empty`, no border
   and no shadow. An error is not a special visual mode; it is a page with
   nothing on it and one sentence explaining why.
   ══════════════════════════════════════════════════════════════════════════ */
.notice-page { max-width: 26rem; margin: 3rem auto; text-align: center; }
.notice-mark { margin: 1.5rem 0 .75rem; color: var(--fg3); opacity: .3; }
.notice-mark svg { margin: 0 auto; }
.notice-title { font-size: 1.35rem; font-weight: 600; color: var(--fg0); }
.notice-detail { margin-top: .6rem; color: var(--fg3); line-height: 1.75; }
.notice-actions {
  display: flex;
  justify-content: center;
  gap: .6rem;
  margin-top: 1.6rem;
}
/* `.btn-primary` is styled for a column form, where stretching reads as one
   full-width control. Here it sits inline, so it takes its own width and the
   44px floor rather than the column's. */
.notice-actions .btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: var(--tap);
  min-inline-size: var(--tap);
  padding-inline: 1.1rem;
  text-decoration: none;
}
.notice-code { margin-top: 1.75rem; color: var(--fg3); font-size: .76rem; }

/* ══════════════════════════════════════════════════════════════════════════
   SIGN-IN AND PASSWORD CHANGE
   ══════════════════════════════════════════════════════════════════════════ */
.signin { max-width: 24rem; margin: 3rem auto; }
.signin-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  margin-bottom: 1.5rem;
  color: var(--blue);
  font-size: 1.05rem;
  font-weight: 600;
}
.signin-brand span { color: var(--fg0); }
.signin-title { font-size: 1.35rem; font-weight: 600; margin-bottom: .9rem; }
.form { display: flex; flex-direction: column; gap: 1rem; margin-top: 1.1rem; }
/* In a column flex form a button stretches to the full column and reads as a
   disabled input, not an action. Buttons hug their label; the sign-in page,
   where one big target is the point, opts back in below. */
.form > .btn-primary, .form > .quiet-button { align-self: flex-start; }
.signin .form > .btn-primary { align-self: stretch; }
.field { display: flex; flex-direction: column; gap: .35rem; }
.field-label { font-size: .78rem; font-weight: 600; color: var(--fg3); }
.input {
  min-height: var(--tap);      /* floor */
  padding: .5rem .7rem;
  font: inherit;
  color: var(--fg0);
  background: var(--content);
  border: 1px solid var(--bg2);
  border-radius: 0;
  transition: border-color .12s;
}
.input:focus { border-color: var(--blue); outline: none; }
/* Four Persian words need room; a narrow box makes the reader lose their place. */
.input--words { font-size: 1.05rem; letter-spacing: .01em; }
/* A typed Jalali date (src/dates.rs). The field is LTR because a date and a
   clock are two number runs, and in an RTL box the bidi algorithm would lay
   «۱۴۰۵/۰۶/۱۵ ۰۸:۳۰» out as «۰۸:۳۰ ۱۴۰۵/۰۶/۱۵». It keeps the page's right
   alignment so it still sits in the form like every other control. */
.input--date { direction: ltr; text-align: right; font-variant-numeric: tabular-nums; }
.input--date::placeholder { color: var(--fg3); opacity: .7; }
.btn-primary {
  min-height: var(--tap);
  padding: 0 1.2rem;
  border: none;
  background: var(--blue);
  color: var(--bg0);
  font-weight: 600;   /* dv4's buttons are firm, not shouting */
  cursor: pointer;
  transition: background .12s;
}
.btn-primary:hover { background: color-mix(in srgb, var(--blue) 85%, black); }
.alert {
  padding: .55rem .75rem;
  border-inline-start: 3px solid var(--red);
  background: color-mix(in srgb, var(--red) 7%, transparent);
  color: var(--fg0);
  font-size: .82rem;
}
.hint { margin-top: 1rem; font-size: .78rem; color: var(--fg3); }

/* ══════════════════════════════════════════════════════════════════════════
   STUDENT CORE — action lists, reader, work, grades and conversation.
   These reuse dv4's hairlines and tints; hierarchy comes from spacing, not
   additional shadows or a second accent colour.
   ══════════════════════════════════════════════════════════════════════════ */
.skip-link {
  position: fixed;
  inset-block-start: .5rem;
  inset-inline-start: .5rem;
  z-index: 100;
  /* docs/09 floors every interactive element at 44px. Padding alone left this
     at 43.2 — close enough to look right and to be wrong, which is the whole
     reason a machine measures it. It is the first thing a keyboard user meets
     on every authenticated page. */
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  padding-inline: .7rem;
  background: var(--bar-bg);
  color: var(--bar-fg);
  transform: translateY(-150%);
}
.skip-link:focus { transform: translateY(0); }
.mobile-nav { display: none; }

.panel-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
}
.panel-head h1 { font-size: 1.35rem; line-height: 1.3; color: var(--fg1); }
.eyebrow { margin-bottom: .15rem; color: var(--fg3); font-size: .7rem; font-weight: 600; letter-spacing: .06em; }
.summary-count, .quiet-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  /* "دریافت" on an attachment row measured 41.2px wide — under the floor by
     less than 3px, which is exactly the size of miss no one finds by eye. */
  min-inline-size: var(--tap);
  color: var(--fg3);
  font-size: .78rem;
}
.quiet-link { color: var(--blue); }
.section-heading { margin: 1.7rem 0 .65rem; }
.section-heading h2, .section-title { font-size: .88rem; font-weight: 600; color: var(--fg1); }

/* A micro-label above a group of sections — the aside's label idiom, allowed
   into the main column so a group heading stops competing with the page. */
.list-label {
  display: flex;
  align-items: center;
  gap: .3rem;
  margin: 1.3rem 0 .5rem;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .08em;
  color: var(--fg3);
}

/* Inside a section body a row list drops its own top border — the strip
   above already draws that edge. */
.sect-bd .task-list, .sect-bd .teacher-list { border-top: none; }
.calm-state, .complete-note {
  display: flex;
  align-items: center;
  gap: .45rem;
  min-height: var(--tap);
  padding: .6rem .75rem;
  background: color-mix(in srgb, var(--aqua) 8%, transparent);
  color: var(--aqua);
}
/* dv4's item titles are 500 (.ac-item-title); a browser-default 700 <strong>
   on every row makes bold mean nothing. Row titles are 500 — enough to lead
   the row at list size — so real bold is left for what is really important:
   stat numbers, counts, micro-labels, and the marked/overdue states. */
.task-row strong, .task-copy strong, .teacher-list strong, .course-hub-row strong,
.conversation-main strong, .marking-row strong, .attachment-list strong,
.material-files strong, .roster-list strong,
.builder-section > summary strong, .lesson-edit > summary strong,
.report-row strong, .poll-row strong { font-weight: 500; font-size: .8rem; }

.task-list { border-top: 1px solid var(--bg2); }
.course-hub { border-top: 1px solid var(--bg2); }
.course-hub-row { display: grid; grid-template-columns: 2.2rem minmax(0, 1fr) auto; gap: .7rem; align-items: center; min-height: 3.4rem; padding: .45rem .6rem; border-bottom: 1px solid var(--bg1); transition: background .12s, transform .12s cubic-bezier(.2, .8, .2, 1); }
.course-hub-row:hover { background: var(--row-hover); }
.course-hub-row:active { transform: scale(.995); }
.course-hub-row > span:nth-child(2) { display: flex; flex-direction: column; min-width: 0; }
.course-hub-row strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.course-hub-row small { color: var(--fg4); font-size: .7rem; }
.course-hub-icon { display: flex; align-items: center; justify-content: center; color: var(--blue); }
.task-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(8rem, auto) auto;
  align-items: center;
  gap: .75rem;
  /* dv4's rows are ~40px; ours carry a second context line and the student
     tap floor, so 3rem (54px) is the honest floor, not 4.2rem of air. */
  min-height: 3rem;
  padding: .4rem .6rem;
  border-bottom: 1px solid var(--bg1);
  border-inline-start: 3px solid transparent;
  color: var(--fg1);
  transition: background .12s, border-color .12s, transform .12s cubic-bezier(.2, .8, .2, 1);
}
.task-row:hover { background: var(--row-hover); }
/* The home's rows carry a state dot as a fourth cell; without its own column
   the action label wrapped onto a second line. */
.action-panel .task-row { grid-template-columns: auto minmax(0, 1fr) minmax(8rem, auto) auto; }
.task-row:active { transform: scale(.995); }
.task-row.is-urgent { border-inline-start-color: var(--amber); }
.task-main, .task-copy, .conversation-main { display: flex; flex-direction: column; min-width: 0; }
.task-main strong, .task-copy strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.task-course, .task-copy small { color: var(--fg4); font-size: .72rem; }
.task-meta { display: flex; flex-direction: column; align-items: flex-start; gap: .25rem; color: var(--fg4); font-size: .72rem; }
.state-chip { padding: .12rem .35rem; background: var(--bg1); color: var(--fg3); font-size: .68rem; }
.copy-preview { display: flex; flex-wrap: wrap; gap: .35rem; margin: -.15rem 0 .35rem; }
.state-chip.is-done { background: color-mix(in srgb, var(--aqua) 10%, transparent); color: var(--aqua); }
.task-mark { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
.task-mark.is-late { background: var(--amber); }
.task-mark.is-returned { background: var(--red); }
.task-mark.is-marked { background: var(--aqua); }
.task-time { color: var(--fg4); font-size: .72rem; white-space: nowrap; }
.task-time.age-late { color: var(--amber); }
/* The row's verb is dv4's `.btn-ghost-secondary`: quiet grey at rest, so a
   list of twenty rows is not twenty blue buttons; the ROW is the target and
   its hover lights the verb — fill on the verb, ink to the full dark. */
.task-action { display: inline-flex; align-items: center; gap: .25rem; padding: .2rem .5rem; margin-inline-end: -.5rem; border-radius: 3px; color: var(--fg3); font-size: .74rem; font-weight: 500; white-space: nowrap; transition: background .12s, color .12s; }
.task-action svg { opacity: .7; }
.task-row:hover .task-action, .marking-row:hover .task-action, .material-files > a:hover .task-action { background: rgba(var(--neutral-rgb), .30); color: var(--fg0); }
.task-row:hover .task-action svg, .marking-row:hover .task-action svg { opacity: 1; }
/* The title darkens with the row, the way dv4's item titles do. */
.task-row:hover .task-copy strong, .task-row:hover .task-main strong, .marking-row:hover strong { color: var(--fg0); }

.reading-layout { max-width: 46rem; margin: 0 auto; }
.lesson-head { padding-bottom: 1rem; border-bottom: 1px solid var(--bg2); }
.lesson-meta { margin-top: .4rem; color: var(--fg4); font-size: .74rem; }
.lesson-body, .prose { padding: 1.4rem 0; font-size: .94rem; line-height: 1.9; }
.lesson-body p + p, .prose p + p { margin-top: 1rem; }
.lesson-complete { padding: 1rem 0; border-top: 1px solid var(--bg2); }
.lesson-nav { display: flex; align-items: center; justify-content: space-between; min-height: 4rem; border-top: 1px solid var(--bg2); }
/* One control whether it is an <a>, a <button> or a <summary>: the UA's grey
   buttonface made the <button> ones read as a different, heavier thing. */
.quiet-button { display: inline-flex; align-items: center; min-height: var(--tap); padding: 0 .7rem; border: 1px solid var(--bg2); background: none; color: var(--fg3); cursor: pointer; }
.quiet-button:hover { background: var(--row-hover); color: var(--fg1); }

.work-main { min-width: 0; }
.work-facts dl div { padding: .55rem 0; border-bottom: 1px solid var(--bg1); }
.work-facts dl div:last-child { border-bottom: none; }
.work-facts dt { color: var(--fg3); font-size: .68rem; }
.work-facts dd { margin: .12rem 0 0; font-weight: 600; }
.submission-form { padding: 1rem 0; border-top: 1px solid var(--bg2); }
.textarea { width: 100%; min-height: 12rem; margin-top: .35rem; resize: vertical; line-height: 1.7; }
.form-actions { display: flex; align-items: center; gap: .7rem; margin-top: .6rem; color: var(--fg3); font-size: .72rem; }
.btn-primary { display: inline-flex; align-items: center; justify-content: center; gap: .35rem; transition: background .12s, transform .12s cubic-bezier(.2, .8, .2, 1); }
.btn-primary:active, .go:active, .quiet-button:active { transform: scale(.97); }
.receipt, .feedback-box { margin-top: 1rem; padding: .8rem; border-inline-start: 3px solid var(--aqua); background: color-mix(in srgb, var(--aqua) 7%, transparent); }
.receipt h2, .feedback-box h2 { display: flex; align-items: center; gap: .35rem; font-size: .88rem; }
.receipt small, .feedback-box p { color: var(--fg3); }

/* A grade row leads with the teacher's words. The score is a chip on the
   title line — a fact, read once — and the feedback is body text, because
   that is the part a student re-reads. The bar that duplicated the score is
   gone; «۱۸ از ۲۰» already says how much of the whole it is. */
.grade-list { border-top: 1px solid var(--bg2); }
.grade-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 1rem; padding: .8rem .65rem; border-bottom: 1px solid var(--bg1); border-inline-start: 3px solid transparent; }
.grade-row.is-unread { border-inline-start-color: var(--blue); background: rgba(var(--accent-rgb), .04); }
.grade-main { min-width: 0; }
.grade-main h2 { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .6rem; margin-top: .15rem; font-size: .94rem; }
.grade-score { display: inline-flex; align-items: baseline; gap: .25rem; padding: 0 .45rem; border-radius: 3px; background: var(--accent-tint); color: var(--accent); font-size: .94rem; font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.6; }
.grade-score small { color: var(--accent); font-size: .68rem; font-weight: 500; }
.grade-feedback { margin-top: .4rem; color: var(--fg1); font-size: .875rem; line-height: 1.75; }
.grade-feedback--none { color: var(--fg4); font-size: .78rem; }

.message-grid { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(16rem, .9fr); gap: 1.5rem; }
.section-title { padding-bottom: .55rem; border-bottom: 1px solid var(--bg2); }
.conversation-row { display: grid; grid-template-columns: 2.2rem minmax(0, 1fr) auto; gap: .65rem; align-items: center; min-height: 4.6rem; padding: .55rem; border-bottom: 1px solid var(--bg1); transition: background .12s; }
.conversation-row:hover { background: var(--row-hover); }
.conversation-row.is-unread { border-inline-start: 3px solid var(--blue); }
.avatar { display: flex; align-items: center; justify-content: center; width: 2.2rem; height: 2.2rem; background: var(--accent-tint); color: var(--accent); border-radius: 50%; }
.conversation-main strong { color: var(--fg1); }
.conversation-main small, .conversation-main > span, .conversation-row time { overflow: hidden; color: var(--fg3); font-size: .68rem; text-overflow: ellipsis; white-space: nowrap; }
.announcement { padding: .75rem 0; border-bottom: 1px solid var(--bg1); }
.announcement h3 { margin: .2rem 0; font-size: .88rem; }
.announcement p { color: var(--fg3); font-size: .74rem; line-height: 1.65; }
.conversation-head { display: flex; align-items: center; gap: .7rem; padding-bottom: .75rem; border-bottom: 1px solid var(--bg2); }
.conversation-head h1 { font-size: 1rem; }
.conversation-head p { color: var(--fg3); font-size: .72rem; }
.thread { display: flex; flex-direction: column; gap: .5rem; max-width: 42rem; min-height: 18rem; padding: 1rem 0; }
/* Two voices, told apart by side and tint. A bubble stops at two-thirds of
   the thread so the empty side says who spoke; the student's own carry the
   accent tint. */
.bubble { align-self: flex-start; max-width: min(28rem, 68%); padding: .55rem .75rem .4rem; background: rgba(var(--neutral-rgb), .22); }
.bubble.mine { align-self: flex-end; background: var(--accent-tint); }
.bubble p { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.7; }
.bubble time { display: block; margin-top: .2rem; color: var(--fg3); font-size: .65rem; }
/* Reporting is for what the other party wrote, and it is a quiet line under
   the bubble — present on every one so a student never has to hunt for it, but
   never louder than the message. It keeps the tap floor. */
.message-report { margin-top: .1rem; }
.message-report > summary { display: inline-flex; align-items: center; min-block-size: var(--tap); list-style: none; color: var(--fg4); font-size: .7rem; cursor: pointer; }
.message-report > summary::-webkit-details-marker { display: none; }
.message-report > summary:hover { color: var(--red); }
.message-report form { display: grid; gap: .4rem; padding-bottom: .3rem; }
.composer { max-width: 42rem; padding-top: .75rem; border-top: 1px solid var(--bg2); }
.composer > div { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .5rem; align-items: stretch; }
.composer textarea { width: 100%; resize: vertical; }

@media (max-width: 62rem) {
  /* Room for the fixed bottom bar, plus the phone's own home indicator
     underneath it: without the inset the last control on a page — a
     receipt, the save button on the marking sheet — sits under the bar. */
  body { padding-bottom: calc(4.15rem + env(safe-area-inset-bottom, 0px)); }
  .top .who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .top a { display: none; }
  .body { padding-inline: .75rem; }
  .mobile-nav {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    z-index: 50;
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    min-height: 3.65rem;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    border-top: 1px solid var(--bg2);
    background: var(--content);
  }
  .mobile-nav a { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .12rem; min-height: var(--tap); color: var(--fg3); font-size: .65rem; }
  .mobile-nav a.is-active { color: var(--blue); font-weight: 600; }
  .message-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 38rem) {
  .task-row { grid-template-columns: minmax(0, 1fr) auto; }
  .task-meta { grid-column: 1; }
  .task-action { grid-column: 2; grid-row: 1 / span 2; }
  .grade-row { grid-template-columns: minmax(0, 1fr); }
  .grade-row form, .grade-row > .go { justify-self: start; }
  .page-hd { flex-direction: column; }
  .composer > div { grid-template-columns: minmax(0, 1fr); }
}

/* ══════════════════════════════════════════════════════════════════════════
   TEACHER WORKSPACE — dense authoring controls, same material language.
   Operational screens avoid decorative entrance motion: teachers revisit
   these controls dozens of times a day. Press feedback remains immediate.
   ══════════════════════════════════════════════════════════════════════════ */
.teacher-brand-tag { padding: .08rem .3rem; background: var(--accent-tint); color: var(--blue); font-size: .65rem; }
.teacher-body { max-width: 76rem; width: 100%; margin-inline: auto; }

/* ── TEACHER DENSITY — docs/08: ".dense hands density back on the teacher
   side." dv4 is a compact desktop tool: btn-sm ≈ 28px, form-control-sm ≈
   30px, 13px control labels, and none of the padding a phone needs.
   A teacher at a desktop gets that density here. One variable does most of
   the work — the tap floor drops to 32px, still above WCAG 2.2's 24px
   pointer minimum — and control type steps down the ladder. Touch widths are
   untouched: below 62rem none of this applies, so a teacher on a phone taps
   the same 44px targets a student does. The rendered audit asserts the two
   floors (e2e/src/audit.ts: DENSE_TAP_FLOOR). Students keep 44px everywhere;
   that departure from dv4 is docs/08's oldest deliberate one. ───────────── */
@media (min-width: 62rem) {
  .teacher-app { --tap: 32px; }
  .teacher-app .input { padding: .25rem .55rem; font-size: .8rem; }
  .teacher-app .btn-primary { padding: 0 .8rem; font-size: .78rem; }
  .teacher-app .go { padding: .15rem .6rem; font-size: .72rem; }
  .teacher-app .quiet-button { padding: 0 .55rem; font-size: .74rem; }
  .teacher-app .quiet-link, .teacher-app .summary-count { font-size: .74rem; }
  .teacher-app .tab { padding: .4rem .8rem; }
  .teacher-app .form { gap: .7rem; }
  .teacher-app .textarea { min-height: 8rem; }
  .teacher-app .upload-field { padding: .55rem .7rem; }
  .teacher-app .upload-field input[type=file]::file-selector-button { padding: .25rem .6rem; }
  .teacher-app .teacher-list > a { min-height: 3.2rem; }
  .teacher-app .qa-trigger { min-height: 38px; }
}
.teacher-columns { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 1.5rem; }
.teacher-columns h2,.manage-section h2 { font-size: 1rem; }
.teacher-list { border-top: 1px solid var(--bg2); }
.teacher-list > a, .teacher-list > div { display: flex; align-items: center; justify-content: space-between; gap: 1rem; min-height: 3.2rem; padding: .45rem .6rem; border-bottom: 1px solid var(--bg1); transition: background .12s,transform .12s cubic-bezier(.23,1,.32,1); }
.teacher-list > a:hover { background: var(--row-hover); }
.teacher-list > a:active { transform: scale(.995); }
.teacher-list > a > span:first-child, .teacher-list > div > span:first-child { display: flex; flex-direction: column; min-width: 0; }
.teacher-list strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.teacher-list small { color: var(--fg4); font-size: .7rem; }
.teacher-list--large { margin-top: 1.2rem; }
.quick-create { padding: .8rem; border: 1px solid var(--bg2); background: rgba(var(--neutral-rgb),.10); }
.quick-create > label { display: block; margin-bottom: .35rem; color: var(--fg3); font-size: .72rem; font-weight: 600; }
.quick-create > div,.inline-editor > div,.add-row { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: .5rem; }
/* The create panel is a <details> (de-facto API) whose summary doubles the
   header's create button for the no-JS path. Closed, it is a quiet line, not
   a bordered box with nothing in it; open, it becomes the tinted panel. */
.create-panel { padding: 0 .8rem; border: 1px solid transparent; }
.create-panel[open] { padding: .8rem; border-color: var(--bg2); background: rgba(var(--neutral-rgb),.10); }
.create-panel summary { min-height: var(--tap); display: flex; align-items: center; color: var(--blue); font-size: .78rem; font-weight: 600; cursor: pointer; }
.create-panel .form { max-width: 38rem; padding-top: .8rem; }
/* One line under a list of one or two: what this list is for, so a young
   workspace reads as young rather than broken. */
.list-hint { max-width: 40rem; margin-top: .8rem; color: var(--fg4); font-size: .74rem; line-height: 1.7; }
.inline-editor { max-width: 40rem; padding: 1rem 0; }
.builder { border-top: 1px solid var(--bg2); }
.builder-section { border-bottom: 1px solid var(--bg2); }
.builder-section > summary { display: grid; grid-template-columns: 2rem minmax(0,1fr) auto auto; gap: .6rem; align-items: center; min-height: 3.2rem; padding: .5rem; cursor: pointer; list-style: none; }
.builder-section > summary::-webkit-details-marker,.lesson-edit > summary::-webkit-details-marker { display:none; }
.builder-section > summary small { color: var(--fg3); font-size: .65rem; }
.builder-section > summary > svg { transition: transform .12s cubic-bezier(.23,1,.32,1); }
.builder-section[open] > summary > svg { transform: rotate(180deg); }
.builder-order { display: flex; align-items: center; justify-content: center; width: 1.7rem; height: 1.7rem; background: var(--bg1); color: var(--fg3); }
.builder-body { padding: .8rem; border-top: 1px solid var(--bg1); background: rgba(var(--neutral-rgb),.06); }
.compact-form { display: grid; grid-template-columns: minmax(10rem,1fr) 6rem auto auto; gap: .55rem; align-items: end; padding-bottom: .8rem; }
.compact-form label,.form-row label,.lesson-edit form > label { display: flex; flex-direction: column; gap: .25rem; color: var(--fg3); font-size: .68rem; }
.check-label { display: inline-flex!important; flex-direction: row!important; align-items: center; gap: .35rem!important; min-height: var(--tap); color: var(--fg3); font-size: .68rem; }
/* A floor as well as a cap: in a narrow grid cell a number input shrank to
   36px (F19), which is under the tap floor and too narrow to read a value. */
.input--short, .input[type="number"] { min-inline-size: 4.5rem; }
.input--short { max-width: 6rem; }
.lesson-edit-list { border-top: 1px solid var(--bg2); }
.lesson-edit { border-bottom: 1px solid var(--bg1); }
.lesson-edit > summary { display: grid; grid-template-columns: 1.5rem minmax(0,1fr) auto; gap: .5rem; align-items: center; min-height: var(--tap); padding: .4rem; cursor: pointer; }
.lesson-edit > summary small { color: var(--fg3); font-size: .68rem; }
.lesson-edit form { padding: .7rem; border-top: 1px solid var(--bg1); }
/* The two side columns hold a typed date and clock — «۱۴۰۵/۰۶/۱۰ ۲۰:۰۰» is
   ten rem at the teacher's 13px, and a clipped deadline is a wrong one. */
.form-row { display: grid; grid-template-columns: minmax(10rem,1fr) 10rem 10rem; gap: .6rem; }
.textarea--editor { min-height: 10rem; }
.is-deleted { opacity: .58; }
.add-row { margin-top: .7rem; }
.add-section { max-width: 40rem; padding-top: 1rem; }
.notice { max-width: 44rem; margin-top: 1rem; padding: .7rem .8rem; border-inline-start: 3px solid var(--amber); background: color-mix(in srgb, var(--amber) 7%, transparent); color: var(--fg3); }
.archive-course { display: flex; align-items: center; justify-content: space-between; gap: 1rem; max-width: 44rem; margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--bg2); }
.archive-course p { color: var(--fg3); font-size: .7rem; }
.danger-button { display: inline-flex; align-items: center; justify-content: center; min-height: var(--tap); padding: 0 .7rem; border: 1px solid color-mix(in srgb, var(--red) 25%, transparent); background: none; color: var(--red); cursor: pointer; transition: background .12s,transform .12s cubic-bezier(.23,1,.32,1); }
.danger-button:hover { background: color-mix(in srgb, var(--red) 6%, transparent); }
.danger-button:active { transform: scale(.97); }
.preview-banner { display: flex; align-items: center; justify-content: space-between; gap: .7rem; padding: .25rem .75rem; margin-bottom: 1rem; border-inline-start: 3px solid var(--amber); background: color-mix(in srgb, var(--amber) 7%, transparent); color: var(--fg3); font-size: .74rem; }
/* The only control on the preview, and the way back: it holds the tap floor
   like every other control (F15 — it was 17.8px tall). */
.preview-banner a { display: inline-flex; align-items: center; min-height: var(--tap); min-inline-size: var(--tap); padding: 0 .35rem; color: var(--blue); font-weight: 600; }
.preview-section { padding: 1rem 0; border-bottom: 1px solid var(--bg2); }
.preview-section h2 { font-size: 1.05rem; }
.preview-section article { padding: .8rem 0; }
.preview-section h3 { font-size: .88rem; margin-bottom: .35rem; }
.preview-section p { line-height: 1.8; }
.preview-section p + p { margin-top: .7rem; }
.manage-section { max-width: 48rem; padding: 1.2rem 0; border-bottom: 1px solid var(--bg2); scroll-margin-top: 1rem; }
.manage-section > .form { max-width: 34rem; }
.section-help { margin: .2rem 0 .7rem; color: var(--fg4); font-size: .72rem; }
.readonly-facts { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); max-width: 34rem; border-top: 1px solid var(--bg2); }
.readonly-facts > div { padding: .65rem; border-bottom: 1px solid var(--bg1); }
.readonly-facts dt { color: var(--fg3); font-size: .68rem; }
.readonly-facts dd { margin: .15rem 0 0; }
.check-list { display: flex; flex-direction: column; margin-bottom: .7rem; border-top: 1px solid var(--bg2); }
.check-list label { display: flex; align-items: center; gap: .5rem; min-height: var(--tap); padding: .35rem; border-bottom: 1px solid var(--bg1); }
.roster-list { margin-top: .8rem; border-top: 1px solid var(--bg2); }
.roster-list > div { display: flex; align-items: center; justify-content: space-between; gap: .7rem; min-height: 3.2rem; padding: .4rem; border-bottom: 1px solid var(--bg1); }
.roster-list > div > span { display: flex; flex-direction: column; }
.roster-list small { color: var(--fg3); font-size: .68rem; }
.roster-list button { min-height: var(--tap); color: var(--red); }
.staff-list { display: flex; flex-direction: column; border-top: 1px solid var(--bg2); }
.staff-list > span { display: flex; justify-content: space-between; align-items: center; min-height: var(--tap); border-bottom: 1px solid var(--bg1); }
.staff-list small { color: var(--fg3); }
.teacher-mobile { grid-template-columns:repeat(5,1fr); }
@media(max-width:62rem){.teacher-columns{grid-template-columns:minmax(0,1fr)}.compact-form{grid-template-columns:minmax(0,1fr) 5rem}.compact-form .check-label{grid-column:1}.form-row{grid-template-columns:minmax(0,1fr) 9rem 9rem}}
@media(max-width:38rem){.quick-create>div,.inline-editor>div,.add-row{grid-template-columns:minmax(0,1fr)}.form-row{grid-template-columns:minmax(0,1fr)}.compact-form{grid-template-columns:minmax(0,1fr)}.compact-form .input--short{max-width:none}.archive-course{align-items:stretch;flex-direction:column}.archive-course .danger-button{width:100%}}

/* Teacher operations: dense, stable surfaces with no decorative entrance motion. */
.assessment-settings { max-width: 48rem; padding: 1.1rem 0; }
.question-builder { border-top: 1px solid var(--bg2); }
.marking-list { border-top: 1px solid var(--bg2); }
.marking-row { display: grid; grid-template-columns: minmax(14rem,1fr) 6rem 10rem 5rem; gap: .7rem; align-items: center; min-height: 3.4rem; padding: .45rem .6rem; border-bottom: 1px solid var(--bg1); color: var(--fg1); transition: background .12s,transform .12s cubic-bezier(.23,1,.32,1); }
.marking-row:hover { background: var(--row-hover); }
.marking-row:active { transform: scale(.995); }
.marking-row > span:first-child { display: flex; flex-direction: column; min-width: 0; }
.marking-row small,.marking-row time,.marking-row > span:nth-child(2) { color: var(--fg4); font-size: .7rem; }
.queue-next { margin-top: 1rem; }
.roster-pager { display: flex; flex-wrap: wrap; gap: .4rem 1rem; align-items: baseline; margin-top: .8rem; color: var(--fg4); font-size: .74rem; line-height: 1.7; }
.mark-layout { display: grid; grid-template-columns: minmax(0,1fr) 20rem; gap: 1.5rem; align-items: start; }
.submission-paper { min-height: 28rem; padding: 1rem; border: 1px solid var(--bg2); background: rgba(var(--neutral-rgb),.06); }
.submission-paper p { white-space: pre-wrap; overflow-wrap: anywhere; }
.mark-panel { position: sticky; top: 1rem; padding-inline-start: 1rem; border-inline-start: 1px solid var(--bg2); }
.mark-panel .textarea { min-height: 10rem; }
.mark-panel .btn-primary { width: 100%; margin-top: .7rem; }
/* The table hugs its columns: a class with two assessments is a small table,
   not a wide one with an empty half. The scroll box is inline so its border
   ends where the columns end, and it scrolls only when the columns outgrow
   the page. */
.gradebook-scroll { display: inline-block; overflow: auto; max-width: 100%; border: 1px solid var(--bg2); }
.gradebook { width: max-content; border-collapse: collapse; font-size: .74rem; }
.gradebook th,.gradebook td { min-width: 6rem; padding: .55rem .65rem; border-bottom: 1px solid var(--bg1); border-inline-end: 1px solid var(--bg1); text-align: center; }
.gradebook th:first-child { position: sticky; inset-inline-start: 0; z-index: 1; min-width: 12rem; background: var(--content); text-align: start; }
/* The header tint is the strip's neutral, not bg1: the cream reads as amber
   here, and amber means "waiting on you" everywhere else. */
.gradebook thead th { position: sticky; top: 0; z-index: 2; background: rgba(var(--neutral-rgb), .15); color: var(--fg1); }
.gradebook tbody tr:last-child th, .gradebook tbody tr:last-child td { border-bottom: none; }
.gradebook thead th:first-child { z-index: 3; }
.gradebook th small { display: block; color: var(--fg3); font-weight: 400; }
.gradebook td.is-marked { color: var(--aqua); font-weight: 600; }
.gradebook td.is-pending { color: var(--amber); }
.gradebook td.is-returned { color: var(--red); }
.attendance-create { display: flex; align-items: end; gap: .6rem; max-width: 42rem; margin-bottom: 1rem; }
.attendance-create .form-row { flex: 1; }
.attendance-tabs a.is-active { color: var(--blue); border-bottom: 2px solid var(--blue); }
.attendance-list { border-top: 1px solid var(--bg2); }
.attendance-list form { display: grid; grid-template-columns: minmax(10rem,1fr) 8rem minmax(10rem,1fr) auto minmax(5rem,auto); gap: .55rem; align-items: center; min-height: 3.8rem; padding: .45rem; border-bottom: 1px solid var(--bg1); }
/* The fifth cell is the row's own word on its state («ذخیره شد»), written by
   app.js after a quiet save; empty and widthless on a page without script. */
.attendance-list form .save-state { min-height: 0; white-space: nowrap; }
/* Four numbers beside a name should sit beside it, not 900px away. */
.report-list { max-width: 52rem; border-top: 1px solid var(--bg2); }
.report-head,.report-row > summary { display: grid; grid-template-columns: minmax(12rem,1fr) repeat(4,6rem); gap: .6rem; align-items: center; min-height: 3.4rem; padding: .45rem .6rem; border-bottom: 1px solid var(--bg1); }
.report-head { min-height: 2.5rem; color: var(--fg3); font-size: .68rem; }
.report-row > summary { cursor: pointer; list-style: none; }
.report-row > summary::-webkit-details-marker { display: none; }
.report-row.is-risk > summary { border-inline-start: 3px solid var(--amber); }
.report-row .add-row { margin: .5rem .7rem .8rem; }
.quiz-sheet { display: flex; flex-direction: column; gap: 1rem; }
.quiz-question { padding: .8rem; border: 1px solid var(--bg2); }
.quiz-question legend { padding: 0 .35rem; font-weight: 600; line-height: 1.6; }
/* Only the number wears the square. The rule once matched every span, so an
   exam prompt wrapped in `.rich-inline` was squeezed into the same 1.6rem box
   and rendered one word per line. */
.quiz-question legend > span:first-child, .q-num { display: inline-flex; align-items: center; justify-content: center; min-width: 1.6rem; height: 1.6rem; margin-inline-end: .35rem; padding: 0 .3rem; background: var(--bg1); font-size: .78rem; vertical-align: middle; }
.quiz-question legend small { color: var(--fg3); font-size: .65rem; font-weight: 400; }
.rich-inline { display: inline; }
.rich-inline > p { display: inline; margin: 0; }
.rich-inline > p + p::before { content: " "; }
.quiz-question > label { display: flex; align-items: center; gap: .45rem; min-height: var(--tap); padding: .25rem .4rem; border-bottom: 1px solid var(--bg1); }
@media(max-width:62rem){.mark-layout{grid-template-columns:minmax(0,1fr)}.mark-panel{position:static;border-inline-start:0;padding-inline-start:0}.marking-row{grid-template-columns:minmax(0,1fr) auto}.marking-row time{grid-column:1}.marking-row .task-action{grid-column:2;grid-row:1/span 2}.attendance-list form{grid-template-columns:minmax(0,1fr) 7rem}.attendance-list form .save-state{grid-column:1/-1}.attendance-list form input{grid-column:1}.attendance-list form button{grid-column:2;grid-row:2}.report-head,.report-row>summary{grid-template-columns:minmax(9rem,1fr) repeat(4,4rem)}}

/* Rich authoring and private course materials. These once carried their own
   token family (--paper, --line, a green --accent) whose tokens were never
   declared, so they rendered their fallbacks: white rounded cards on a page
   that has neither white nor a radius. Re-drawn on the gruvbox tokens —
   hairlines, tints, the one blue — per docs/08. */
.rich-create-form, .course-metadata { max-width: 52rem; }
.course-metadata { margin-block: 1rem 1.5rem; }
.rich-editor { min-height: 13rem; }
/* The editor is a control, so it wears the control's hairline and nothing of
   TinyMCE's own design system: no radius, no shadow, and the focus ring is the
   same blue edge every .input takes. */
.tox.tox-tinymce { border: 1px solid var(--bg2); border-radius: 0; box-shadow: none; transition: border-color .12s; }
.tox.tox-tinymce.tox-edit-focus { border-color: var(--blue); }
.tox .tox-edit-area::before { display: none; }
/* The toolbar joins the page: surface fill, a hairline below, flat buttons at
   the page's 13px, the page's ink on every glyph. */
.tox .tox-toolbar-overlord, .tox:not(.tox-tinymce-inline) .tox-editor-header { background: var(--content) !important; box-shadow: none !important; border-bottom: 1px solid var(--bg2) !important; padding: 0 !important; }
.tox .tox-toolbar__primary { background: var(--content) !important; padding: 0 .2rem !important; }
.tox .tox-toolbar__group { padding: 0 .25rem !important; }
.tox .tox-tbtn { background: transparent; border-radius: 0; cursor: pointer; height: 30px; margin: 2px 0; color: var(--fg1); font-family: inherit; }
.tox .tox-tbtn:hover { background: var(--row-hover) !important; }
.tox .tox-tbtn--enabled, .tox .tox-tbtn--enabled:hover { background: var(--accent-tint) !important; color: var(--blue) !important; }
.tox .tox-tbtn svg { fill: var(--fg1) !important; }
.tox .tox-tbtn--enabled svg { fill: var(--blue) !important; }
.tox .tox-tbtn--select { width: auto; }
.tox .tox-tbtn__select-label { font-family: inherit; font-size: .78rem; }
/* The block picker is a dropdown of the page's own words, so it takes the
   page's face. */
.tox .tox-tbtn__select-label, .tox .tox-collection__item-label, .tox .tox-menu { font-family: "IRANSans", Tahoma, system-ui, sans-serif !important; }
.tox .tox-menu { border: 1px solid var(--bg2) !important; border-radius: 6px !important; background: var(--content) !important; box-shadow: 0 4px 12px rgba(40, 40, 40, .12) !important; }
.tox .tox-collection--list .tox-collection__item--active { background: var(--accent-tint) !important; color: var(--blue) !important; }
.tox .tox-dialog { border-radius: 6px !important; border: 1px solid var(--bg2) !important; font-family: "IRANSans", Tahoma, system-ui, sans-serif !important; }
.upload-field { display: grid; gap: .55rem; padding: .8rem; border: 1px dashed var(--bg2); background: rgba(var(--neutral-rgb), .10); cursor: pointer; }
.upload-field > span { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; font-weight: 600; }
.upload-field small { font-weight: 400; color: var(--fg3); }
/* The audit measures the file input itself (only checkbox/radio defer to
   their label), so the input carries the 44px floor directly. */
.upload-field input[type=file] { max-width: 100%; min-height: var(--tap); font: inherit; color: var(--fg3); }

/* With JS the native control — which speaks the browser's language and takes
   no styling — becomes an invisible layer over the whole dashed field, and a
   drawn Persian button + status line stand in for it. Without JS none of this
   applies and the native input stays visible. */
.upload-field--js { position: relative; }
.upload-field--js input[type=file] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
.upload-row { display: flex; align-items: center; gap: .6rem; min-height: var(--tap); }
.upload-btn {
  display: inline-flex;
  align-items: center;
  padding: .2rem .7rem;
  border: 1px solid var(--accent-edge);
  border-radius: 3px;
  background: var(--accent-tint);
  color: var(--blue);
  font-size: .74rem;
  font-weight: 600;
}
.upload-field--js:hover .upload-btn { background: var(--accent-tint-hover); border-color: var(--accent-edge-hover); }
.upload-names { color: var(--fg3); font-size: .74rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.upload-field input[type=file]::file-selector-button { margin-inline-end: .75rem; border: 1px solid var(--bg2); border-radius: 0; padding: .5rem .8rem; background: var(--content); color: var(--fg0); font: inherit; cursor: pointer; transition: border-color .12s; }
.upload-field input[type=file]::file-selector-button:hover { border-color: var(--accent-edge-hover); }
.attachment-upload { display: flex; align-items: end; gap: .75rem; margin-top: 1rem; }
.attachment-upload .upload-field { flex: 1; }
.attachment-list { display: grid; border-top: 1px solid var(--bg2); }
.attachment-list > div { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; align-items: center; gap: .75rem; padding: .6rem 0; border-bottom: 1px solid var(--bg1); }
.attachment-list > div.is-deleted { opacity: .58; }
.attachment-list strong, .material-files strong { display: block; overflow-wrap: anywhere; }
.attachment-list small, .material-files small { display: block; color: var(--fg3); margin-top: .15rem; }
.file-mark { display: grid; place-items: center; width: 2.1rem; height: 2.1rem; background: var(--accent-tint); color: var(--accent); }
.material-files { display: grid; margin-top: 1rem; border-top: 1px solid var(--bg2); }
.material-files > a { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: .75rem; padding: .55rem .4rem; border-bottom: 1px solid var(--bg1); color: inherit; transition: background .12s; }
.material-files > a:hover { background: var(--row-hover); }
.course-materials { margin-block: 1rem 1.5rem; }
.course-materials h2 { margin: 0; }
.rich-content { color: var(--fg1); line-height: 1.95; overflow-wrap: anywhere; }
.rich-content > :first-child { margin-top: 0; }
.rich-content > :last-child { margin-bottom: 0; }
.rich-content h2, .rich-content h3, .rich-content h4 { line-height: 1.45; margin-block: 1.25em .5em; }
.rich-content ul, .rich-content ol { padding-inline-start: 1.5rem; }
.rich-content ul { list-style: disc; }
.rich-content ol { list-style: decimal; }
.rich-content blockquote { margin-inline: 0; padding: .6rem 1rem; border-inline-start: 3px solid var(--blue); background: rgba(var(--neutral-rgb), .15); }
.rich-content table { display: block; max-width: 100%; overflow-x: auto; border-collapse: collapse; }
.rich-content td, .rich-content th { border: 1px solid var(--bg2); padding: .5rem .65rem; }
.rich-content a { color: var(--blue); }
.class-note { margin-top: 1rem; padding: .8rem 1rem; border-inline-start: 3px solid var(--blue); background: rgba(var(--neutral-rgb), .15); }
.class-note > strong { display: block; margin-bottom: .45rem; }
.preview-description { margin-block: 1rem 1.5rem; }
@media(max-width:40rem){.attachment-upload{align-items:stretch;flex-direction:column}.attachment-list>div{grid-template-columns:auto minmax(0,1fr) auto}.attachment-list>div form{grid-column:2/4}.upload-field>span{align-items:flex-start;flex-direction:column;gap:.25rem}}

/* Scheduled class sessions — hairline groups, not floating cards. The state
   chip keeps the badge's 3px radius; the cancelled state is a red colour on
   the chip, never a red field. */
.course-sessions { margin-block: 1rem 1.5rem; }
.course-sessions h2, .session-card h3 { margin: 0; }
.session-list { display: grid; border-top: 1px solid var(--bg2); }
.session-create { margin-bottom: 1rem; }
.session-create > summary, .session-edit > summary { width: max-content; cursor: pointer; list-style: none; }
.session-create > summary::-webkit-details-marker, .session-edit > summary::-webkit-details-marker { display: none; }
.session-form { margin-top: 1rem; max-width: 52rem; }
.session-card { padding: .8rem .65rem; border-bottom: 1px solid var(--bg1); }
.session-card.is-cancelled { opacity: .7; }
.session-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
.session-card-head > div { min-width: 0; }
.session-card-head h3 { margin-top: .3rem; font-size: .94rem; }
.session-card-head p { margin: .2rem 0 0; color: var(--fg3); font-size: .78rem; }
.session-state { display: inline-flex; padding: .18rem .5rem; border-radius: 3px; background: var(--accent-tint); color: var(--accent); font-size: .68rem; font-weight: 600; }
.session-state-1 { background: var(--bg1); color: var(--fg3); }
.session-state-2 { background: color-mix(in srgb, var(--red) 8%, transparent); color: var(--red); }
/* ── Live classes (docs/18) ───────────────────────────────────────────────
   "Live" is carried by ink and weight, never by motion: this system has none
   beyond the 120ms colour transitions, so a pulsing dot would be the only
   moving thing on any page. */
.session-state-live { background: var(--accent-tint); color: var(--accent); }
.live-dot { display: inline-block; inline-size: .45rem; block-size: .45rem; border-radius: 50%;
            background: currentColor; margin-inline-end: .35rem; }
.session-note { margin-top: .5rem; font-size: .74rem; color: var(--fg4); }
.session-live-bar { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
                    flex-wrap: wrap; margin-top: .65rem; padding: .55rem .65rem;
                    background: var(--accent-tint); border-inline-start: 3px solid var(--accent); }
.session-live-bar span { font-size: .74rem; color: var(--fg1); }
.quiet-form { display: contents; }
.live-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
            padding: .75rem .65rem; flex-wrap: wrap; }
.live-copy { display: flex; flex-direction: column; gap: .2rem; min-width: 0; }
.live-copy strong { font-size: .8rem; font-weight: 500; color: var(--fg1); }
.live-copy small { font-size: .7rem; color: var(--fg4); }
/* An endpoint is an identifier: Latin, isolated, never in Persian digits. */
.ident-line { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
              font-size: .72rem; color: var(--fg3); unicode-bidi: isolate; overflow-wrap: anywhere; }
@media (max-width: 40rem) { .live-row { flex-direction: column; align-items: stretch; } }

.session-facts { display: flex; flex-wrap: wrap; gap: .45rem 1rem; margin-top: .5rem; color: var(--fg3); font-size: .78rem; }
.session-facts a { display: inline-flex; align-items: center; min-block-size: var(--tap); color: var(--blue); }
.session-description { margin-top: .7rem; padding-top: .6rem; border-top: 1px solid var(--bg1); }
.session-edit { margin-top: .8rem; padding-top: .65rem; border-top: 1px solid var(--bg1); }
.danger-row { margin-top: .75rem; }
.lesson-files { margin: .75rem 0; padding: .65rem; background: rgba(var(--neutral-rgb), .10); }
@media(max-width:40rem){.session-card-head{align-items:stretch;flex-direction:column}.session-card-head>.quiet-button{width:100%;text-align:center}.session-form .form-row{grid-template-columns:1fr}}
@media(max-width:38rem){.attendance-create{align-items:stretch;flex-direction:column}.report-head{display:none}.report-row>summary{grid-template-columns:minmax(0,1fr) repeat(2,4rem)}.report-row>summary span:nth-of-type(n+3){display:none}.marking-row>span:nth-child(2){display:none}}

/* Configurable assignments, roster moderation, and class polls. */
.config-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.7rem;margin:1rem 0;padding:1rem;border:1px solid var(--bg2)}.config-grid legend{padding:0 .4rem;font-weight:700}.config-grid>label:not(.check-label){display:flex;flex-direction:column;gap:.3rem;color:var(--fg3);font-size:.69rem}.config-grid>.field{grid-column:1/-1}.assessment-resources{max-width:48rem;margin:1rem 0;padding-top:1rem;border-top:1px solid var(--bg2)}.submission-form>.upload-field{margin-block:1rem}.roster-list>div{align-items:flex-start}.roster-moderation{display:grid;grid-template-columns:7rem minmax(11rem,1fr) auto;gap:.45rem;align-items:center;width:min(100%,30rem)}.poll-list{display:grid;border-top:1px solid var(--bg2)}.poll-row{display:flex;align-items:center;justify-content:space-between;gap:1rem;min-height:3.8rem;padding:.55rem;border-bottom:1px solid var(--bg1);color:inherit}.poll-row>span:first-child{display:flex;min-width:0;flex-direction:column}.poll-row small{color:var(--fg3);font-size:.7rem}.poll-page{max-width:46rem}.poll-page>header{margin-bottom:1.2rem}.poll-options{display:grid;gap:.55rem;margin:1.25rem 0;padding:1rem;border:1px solid var(--bg2)}.poll-options legend{padding:0 .4rem;font-weight:700}.poll-options label{display:flex;align-items:center;gap:.65rem;min-height:var(--tap);padding:.45rem .65rem;border:1px solid var(--bg1);background:rgba(var(--neutral-rgb),.06);cursor:pointer}.poll-options input{inline-size:1.15rem;block-size:1.15rem;accent-color:var(--blue)}.poll-results{display:grid;gap:.35rem;margin-top:1.25rem}.poll-results h2{margin-bottom:.35rem;font-size:1rem}.poll-results>div{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:.7rem .8rem;border-bottom:1px solid var(--bg1);background:rgba(var(--neutral-rgb),.06)}.poll-results strong{font-variant-numeric:tabular-nums;color:var(--blue)}
.poll-description{max-width:46rem;margin-top:1rem}.poll-facts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));max-width:46rem;margin-top:1rem;border-top:1px solid var(--bg2)}.poll-facts>div{padding:.65rem;border-bottom:1px solid var(--bg1)}.poll-facts dt{color:var(--fg3);font-size:.68rem}.poll-facts dd{margin:.2rem 0 0}
@media(hover:hover) and (pointer:fine){.poll-row:hover,.poll-options label:hover{background:var(--row-hover)}}
@media(max-width:46rem){.config-grid{grid-template-columns:minmax(0,1fr)}.config-grid>.field{grid-column:auto}.roster-list>div{flex-direction:column}.roster-moderation{width:100%}.poll-facts{grid-template-columns:minmax(0,1fr)}}
@media(max-width:32rem){.roster-moderation{grid-template-columns:minmax(0,1fr)}.roster-moderation .quiet-button{width:100%}}

/* ══════════════════════════════════════════════════════════════════════════
   UNIFIED EDIT — dv4's article edit idiom (article_unified_edit.scss):
   editing gives weight only to what matters. Secondary settings sit in
   accordion rows whose labels are FAINTER than content ("muted/receding
   labels — chips are the dominant visual element"), previewed by chips, all
   closed by default. Ours ride a real <details>, so they need no JavaScript
   and stay keyboard-operable.
   ══════════════════════════════════════════════════════════════════════════ */
.acc { border-top: 1px solid var(--bg1); margin: 1rem 0; }
.acc-row { border-bottom: 1px solid var(--bg1); }
.acc-row > summary {
  display: flex;
  align-items: center;
  gap: .4rem;
  min-height: var(--tap);
  padding: .25rem .1rem;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.acc-row > summary::-webkit-details-marker { display: none; }
.acc-row > summary .chev {
  flex-shrink: 0;
  display: flex;
  color: var(--fg3);
  opacity: .7;
  transition: transform .15s;
  transform: rotate(90deg);        /* closed points along the reading direction */
}
.acc-row[open] > summary .chev { transform: rotate(0); }
.acc-title { flex-shrink: 0; font-size: .74rem; font-weight: 500; color: var(--fg3); }
.acc-row > summary:hover .acc-title { color: var(--fg1); }
.acc-row > summary .chips { flex: 1 1 auto; min-width: 0; overflow: hidden; }
.acc-body { padding: .2rem 0 1rem; }
.acc-body > .form { max-width: 34rem; }
.chip-none { font-size: .72rem; color: var(--fg3); font-style: italic; }

/* dv4's .ue-field: on the teacher side a control is page surface until it
   takes focus, so a form reads as a page, not a wall of boxes. */
.teacher-app .input { background: transparent; }
.teacher-app .input:focus { background: var(--content); }

/* ══════════════════════════════════════════════════════════════════════════
   QUICK ACCESS — dv4's command bar (command_bar.scss), on a native <dialog>.
   An overlay, so it shares the bulk bar's licence for depth: this radius and
   shadow are the palette's, and stop at its edge.
   ══════════════════════════════════════════════════════════════════════════ */
.qa {
  width: min(34rem, calc(100vw - 2rem));
  margin: 12vh auto 0;
  padding: 0;
  border: 1px solid var(--bg2);
  border-radius: 10px;
  background: var(--content);
  color: var(--fg0);
  box-shadow: 0 20px 60px rgba(40, 40, 40, .22), 0 4px 12px rgba(40, 40, 40, .12);
}
.qa::backdrop { background: rgba(40, 40, 40, .55); }
.qa-input-wrap {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: 0 .8rem;
  border-bottom: 1px solid var(--bg2);
  color: var(--fg3);
}
.qa-input-wrap input {
  flex: 1 1 auto;
  min-width: 0;
  padding: .8rem 0;
  border: none;
  background: none;
  color: var(--fg0);
  font: inherit;
  font-size: .88rem;
}
.qa-input-wrap input:focus { outline: none; }
.qa-input-wrap input::placeholder { color: var(--fg3); }
.qa-results { max-height: 19rem; overflow-y: auto; }
.qa-results:empty { display: none; }
#qa-defaults[hidden] { display: none; }
.qa-group {
  padding: .45rem .8rem .15rem;
  font-size: .65rem;
  font-weight: 600;
  letter-spacing: .06em;
  color: var(--fg3);
}
.qa-row {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  min-height: var(--tap);
  padding: .45rem .8rem;
  cursor: pointer;
  transition: background .1s;
}
.qa-row:hover, .qa-row.is-active { background: var(--bg1); }
.qa-row-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--fg0);
  font-size: .82rem;
  font-weight: 500;
}
.qa-row.is-active .qa-row-title { color: var(--blue); }
.qa-row-sub {
  margin-inline-start: auto;
  flex-shrink: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--fg3);
  font-size: .7rem;
}
.qa-empty { padding: 1.1rem .8rem; text-align: center; color: var(--fg3); font-size: .8rem; }
.qa-footer {
  display: flex;
  flex-wrap: wrap;
  gap: .3rem 1rem;
  padding: .45rem .8rem;
  border-top: 1px solid var(--bg2);
  color: var(--fg3);
  font-size: .68rem;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE PRINTED SHEET — ADR-0015. This page IS a credential in physical form.
   ══════════════════════════════════════════════════════════════════════════ */
.sheet-page { background: #fff; }
.sheet { max-width: 44rem; margin: 0 auto; padding: 1.5rem; }
.sheet-warn {
  display: flex;
  gap: .5rem;
  align-items: flex-start;
  border-inline-start: 3px solid var(--red);
  background: color-mix(in srgb, var(--red) 6%, transparent);
  padding: .6rem .8rem;
  margin-bottom: 1.2rem;
  font-size: .82rem;
  font-weight: 700;
}
.sheet-warn svg { color: var(--red); margin-top: .15rem; }
.sheet-title { font-size: 1.2rem; font-weight: 700; }
.sheet-meta { font-size: .78rem; color: var(--fg3); margin-bottom: 1rem; }
.sheet-table { width: 100%; border-collapse: collapse; }
.sheet-table th {
  text-align: start;
  font-size: .68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--fg3);
  padding: .4rem .5rem;
  border-bottom: 1px solid var(--fg3);
}
.sheet-table td { padding: .5rem; border-bottom: 1px solid var(--bg2); }
.c-login { font-family: ui-monospace, monospace; font-size: .82rem; }
/* The password is the thing a person reads aloud and types. Give it room. */
.c-pass { font-size: 1.05rem; font-weight: 700; letter-spacing: .01em; }
.sheet-foot { margin-top: 1rem; font-size: .78rem; color: var(--fg3); }

@media print {
  html { font-size: 100%; }
  .sheet { padding: 0; }
  .sheet-warn { background: none; }
  a[href]::after { content: none; }
}

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

/* ══════════════════════════════════════════════════════════════════════════
   OPERATIONS — the moderator/admin workspaces, in the same material language.
   Nothing here invents a mode: chips are .chip, counts are .teacher-stats,
   rows are hairlines, and the one overlay (the workspace menu) borrows the
   palette's licence for depth.
   ══════════════════════════════════════════════════════════════════════════ */

/* Context switcher: navigation only. Authority is always checked again on
   the destination route. */
.workspace-switcher { position: relative; margin: .45rem .65rem 1rem; }
.workspace-switcher summary {
  list-style: none; cursor: pointer; display: flex; align-items: center;
  justify-content: space-between; gap: .5rem; min-height: var(--tap); padding: .2rem .65rem;
  border: 1px solid var(--bg2); background: var(--bg1);
  color: var(--fg1); font-size: .78rem; font-weight: 600;
}
.workspace-switcher summary::-webkit-details-marker { display: none; }
.workspace-switcher summary small { color: var(--fg3); font-size: .65rem; font-weight: 500; }
/* An overlay, like the palette — the shadow stops at its edge. */
.workspace-menu {
  position: absolute; z-index: 30; inset-inline: 0; top: calc(100% + .3rem);
  display: grid; padding: .3rem; border: 1px solid var(--bg2); border-radius: 6px;
  background: var(--content); box-shadow: 0 4px 12px rgba(40, 40, 40, .12);
}
.workspace-menu a { min-height: var(--tap); display: flex; align-items: center; padding: 0 .55rem; color: var(--fg1); font-size: .78rem; }
.workspace-menu a:hover, .workspace-menu a.is-current { background: var(--accent-tint); color: var(--blue); }

.ops-context { color: var(--fg3); font-size: .74rem; margin-inline-end: auto; }

/* A one-line explainer under the page header — quiet, never a card. */
.page-note { max-width: 52rem; margin: -.4rem 0 .9rem; color: var(--fg3); font-size: .78rem; line-height: 1.7; }

/* The flagged-message queue: hairline rows, the quote carried on the amber
   accent bar — a report is a waiting item, not an alarm. */
.ops-list { display: grid; }
.ops-item { padding: .8rem 0; border-bottom: 1px solid var(--bg1); }
.ops-item:last-child { border-bottom: 0; }
.ops-item-head, .person-access-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
.ops-item-head strong { font-weight: 600; }
.ops-item .meta { color: var(--fg3); font-size: .74rem; margin-top: .15rem; }
.ops-item blockquote { margin: .65rem 0; padding: .55rem .8rem; border-inline-start: 3px solid var(--amber); background: rgba(var(--neutral-rgb), .15); }
.ops-item p strong { font-weight: 600; }
.inline-edit, .search-form { display: flex; align-items: center; gap: .55rem; }
.inline-edit .input, .search-form .input { flex: 1; }
.search-form { max-width: 34rem; margin-bottom: 1rem; }

/* People and access: one hairline article per person. */
.person-access { padding: .9rem 0; border-bottom: 1px solid var(--bg1); }
.person-access:last-child { border-bottom: 0; }
.person-access-head h2 { margin: 0; font-size: .88rem; }
.person-access-head p { margin: .2rem 0 0; color: var(--fg3); font-size: .72rem; }
.role-facts { display: flex; flex-wrap: wrap; gap: .2rem; margin: .55rem 0; }
.role-facts span {
  padding: 0 .35rem; background: var(--bg1); border: 1px solid var(--bg2);
  color: var(--fg3); font-size: .72rem; line-height: 1.8;
}
.role-form { display: grid; grid-template-columns: repeat(3,max-content) minmax(12rem,1fr) auto; gap: .7rem; align-items: center; }
.role-form label { display: inline-flex; align-items: center; gap: .3rem; min-height: var(--tap); font-size: .72rem; color: var(--fg1); }
.ops-term-form { display: grid; grid-template-columns: .6fr 1.5fr 1fr 1fr auto; gap: .7rem; align-items: end; margin-bottom: 1.2rem; }
.ops-term-form label { display: grid; gap: .3rem; font-size: .7rem; color: var(--fg3); }

/* The audit trail — micro-label header row, hairline body (docs/08 table idiom). */
.table-wrap { overflow-x: auto; }
.data-table { width: 100%; border-collapse: collapse; font-size: .74rem; }
.data-table th, .data-table td { padding: .55rem .65rem; text-align: start; border-bottom: 1px solid var(--bg1); vertical-align: top; }
.data-table th { color: var(--fg3); font-size: .65rem; font-weight: 600; letter-spacing: .06em; border-bottom-color: var(--bg2); }
.data-table td small { display: block; margin-top: .2rem; color: var(--fg3); }
@media (max-width: 850px) {
  .role-form, .ops-term-form { grid-template-columns: 1fr; align-items: stretch; }
}

/* The read-only support view announces itself the way overdue does — amber,
   whisper-quiet, never a warning field from another design system. */
.support-banner {
  display: flex; align-items: center; gap: .8rem; padding: .4rem 1rem;
  background: color-mix(in srgb, var(--amber) 8%, transparent); border-bottom: 1px solid var(--bg2);
  color: var(--fg1); font-size: .78rem;
}
.support-banner strong { color: var(--amber); font-weight: 600; }
.support-banner span { flex: 1; }
.support-banner form { margin: 0; }
.support-banner button {
  min-height: var(--tap); border: 1px solid var(--accent-edge); background: var(--accent-tint);
  color: var(--blue); font-size: .74rem; font-weight: 600; padding: 0 .7rem; cursor: pointer;
  transition: background .1s, border-color .1s;
}
.support-banner button:hover { background: var(--accent-tint-hover); border-color: var(--accent-edge-hover); }
.support-confirm { max-width: 46rem; }
.ops-mobile { grid-template-columns: repeat(4,1fr); }
.ops-mobile--moderator { grid-template-columns: repeat(2,1fr); }

/* ══════════════════════════════════════════════════════════════════════════
   FORMAL EXAMS — the same paper-and-hairline language; only the clock is
   allowed to stay put. During an attempt the page is the exam: no search in
   the bar, no bottom tabs, and the aside (clock + flagged questions) is
   sticky beside the paper on a desktop and rides the top on a phone.
   ══════════════════════════════════════════════════════════════════════════ */
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.mobile-nav--off { display: none !important; }
.exam-description { max-width: 52rem; margin-bottom: 1rem; }

/* Pre-flight: the numbers first, what they count beneath — the fact grid the
   assignment and poll pages already use. */
.exam-preflight { max-width: 52rem; }
.exam-preflight h2 { font-size: 1rem; margin-bottom: .6rem; }
.exam-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); border-top: 1px solid var(--bg2); margin: 0 0 1rem; }
.exam-facts > div { padding: .65rem .5rem .6rem; border-bottom: 1px solid var(--bg1); }
.exam-facts dd { margin: 0; color: var(--fg1); font-size: .9rem; }
.exam-facts dd .stat-num { font-size: 1.15rem; }
.exam-facts dt { margin-top: .1rem; color: var(--fg4); font-size: .7rem; }
.exam-preflight .notice { margin-top: 0; }
.exam-preflight .form { margin-top: 1rem; }

.exam-cols { align-items: start; }
.exam-timer { padding: .45rem .7rem; border-inline-start: 3px solid var(--accent); background: var(--accent-tint); transition: background .2s, border-color .2s; }
.exam-timer small { display: block; color: var(--fg3); font-size: .66rem; }
.exam-timer strong { display: block; margin-top: .12rem; font-size: 1.35rem; line-height: 1.2; font-variant-numeric: tabular-nums; color: var(--fg0); }
.exam-timer.is-urgent { border-color: var(--amber); background: color-mix(in srgb, var(--amber) 10%, transparent); }
.exam-timer.is-urgent strong { color: var(--amber); }
.exam-flags { display: flex; flex-wrap: wrap; gap: .3rem; }
.exam-flags .chip { display: inline-flex; align-items: center; justify-content: center; min-inline-size: var(--tap); min-block-size: var(--tap); font-size: .82rem; font-variant-numeric: tabular-nums; }
.exam-form { max-width: 52rem; }
.exam-form .quiz-sheet, .exam-review { display: grid; gap: 1rem; }
.exam-form .quiz-question { scroll-margin-top: 4.5rem; }
.exam-form .quiz-question[hidden] { display: none; }
.save-state { min-height: 1.4rem; color: var(--fg3); font-size: .72rem; }
/* In-place forms (app.js `data-swap`/`data-quiet`): the state line turns red
   when the write did not land; a field the browser refused is edged red. */
.is-failed .save-state { color: var(--red); }
form[aria-busy="true"] { cursor: progress; }
.upload-field.is-invalid { border-color: var(--red); }
.upload-field.is-invalid .upload-names { color: var(--red); }
.question-flag { color: var(--fg3); font-size: .72rem; }
/* The bar is sticky at the foot of the viewport; the sheet leaves room under
   its last question so nothing is ever hidden behind it. */
.exam-actions, .exam-page-nav { position: sticky; bottom: 0; z-index: 5; display: flex; align-items: center; justify-content: space-between; gap: .7rem; padding: .65rem 0; border-top: 1px solid var(--bg2); background: color-mix(in srgb, var(--content) 94%, transparent); }
.exam-form .quiz-sheet { padding-bottom: .5rem; }
.exam-page-nav[hidden] { display: none; }
.exam-files { display: grid; gap: .35rem; margin-bottom: .5rem; }
.exam-files .file-row { display: flex; align-items: center; gap: .55rem; padding: .35rem 0; border-bottom: 1px solid var(--bg1); }

/* The receipt and the review: the score is the grades page's chip, each
   objective item wears the activity feed's check or cross. */
.exam-receipt-score { margin-top: .4rem; }
.exam-receipt-score .grade-score { font-size: 1.1rem; }
.exam-review { max-width: 52rem; margin-top: 1.5rem; }
.exam-review h2 { font-size: 1rem; }
.exam-review .quiz-question h3 { display: flex; align-items: center; gap: .35rem; margin-top: 0; font-size: .88rem; }
.exam-review .quiz-question h3 .rich-inline { flex: 1 1 auto; min-width: 0; }
.exam-review .quiz-question p { margin-top: .35rem; font-size: .82rem; }
.review-mark { flex-shrink: 0; }
.review-item.is-right { border-inline-start: 3px solid var(--aqua); }
.review-item.is-wrong { border-inline-start: 3px solid var(--red); }

/* The teacher's page. Equal columns for rows whose fields are peers; the
   default .form-row (one wide, two narrow) is for a name beside two dates. */
.form-row--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.form-row--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.check-list--plain { border-top: none; margin: .4rem 0 0; }
.check-list--plain label { border-bottom: none; padding-inline: 0; min-height: var(--tap); font-size: .78rem; }
.readonly-facts--wide { max-width: 48rem; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.readonly-facts--wide > div:nth-child(n+7) { grid-column: 1 / -1; }
.readonly-facts dd .chips { margin-top: .2rem; }
.exam-question-row > summary { grid-template-columns: 1.5rem minmax(0, 1fr); }
.exam-question-name { display: flex; flex-direction: column; min-width: 0; }
.exam-question-name strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; font-size: .82rem; }
.exam-question-name small { color: var(--fg4); font-size: .68rem; }
.exam-question-body { padding: .5rem .7rem .8rem; }
/* The monitor: a candidate on the start side, the proctor's three moves as
   three one-line forms on the end side — minutes, reason, verb. */
.exam-monitor-row { display: grid; grid-template-columns: minmax(11rem, 1fr) minmax(0, 30rem); gap: .8rem; align-items: start; padding: .7rem 0; border-bottom: 1px solid var(--bg1); }
.exam-monitor-row small { display: block; color: var(--fg3); }
.exam-proctor { display: grid; gap: .35rem; }
.exam-proctor form, .exam-proctor-line { display: grid; grid-template-columns: 7rem minmax(0, 1fr) auto; align-items: center; gap: .35rem; }
.exam-proctor form:not(:has(.input--short)), .exam-proctor-line:not(:has(.input--short)) { grid-template-columns: minmax(0, 1fr) auto; }
.exam-proctor .input--short, .exam-proctor-line .input--short { max-width: none; }
.exam-proctor .quiet-button, .exam-proctor .danger-button, .exam-proctor-line .quiet-button { white-space: nowrap; }
@media (max-width: 40rem) { .form-row--two, .form-row--three, .readonly-facts--wide { grid-template-columns: minmax(0, 1fr); } .exam-monitor-row { grid-template-columns: minmax(0, 1fr); } .exam-proctor form, .exam-proctor-line { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 62rem) {
  /* The aside comes first and stays at the top: the clock is never off
     screen on a phone. */
  .exam-cols .exam-aside { order: -1; position: sticky; top: 0; z-index: 6; margin: 0 -.75rem .75rem; padding: 0 .75rem; border-top: none; border-bottom: 1px solid var(--bg2); background: var(--content); }
  .exam-aside .aside-inner { display: flex; align-items: center; gap: .5rem; }
  .exam-aside .side-sec { border-bottom: none; padding: .35rem 0; }
  .exam-aside .side-sec:first-child { flex: 0 0 auto; }
  .exam-aside .side-sec:last-child { flex: 1 1 auto; min-width: 0; }
  .exam-aside .side-sec-label { margin-bottom: .15rem; }
  .exam-timer { display: flex; align-items: baseline; gap: .5rem; padding: .3rem .6rem; }
  .exam-timer strong { margin-top: 0; font-size: 1.15rem; }
  .exam-form .quiz-question { scroll-margin-top: 5rem; }
}
