/* NAKS EIKEN.
   Two visual worlds on purpose:
     · the NAKS shell (login, home, results) — the school's navy and yellow;
     · the EXAM shell — made to look like the real 英検 S-CBT screen.
   The exam does NOT follow the system dark theme. The real thing is light, and
   a student who practises on a dark screen would meet a different exam on the
   day. Recognition is the product. */

:root{
  --navy:#143143; --navy2:#1F4A63; --yellow:#FEF200; --paleYellow:#FFF9C4;
  --paleNavy:#DEE7ED;
  --ground:#EEF2F5; --surface:#FFFFFF; --surface2:#F5F8FA;
  --ink:#12222c; --ink-soft:#5a6b76; --ink-faint:#8b9aa4;
  --line:#d8e0e6;
  --ok:#1F7A45; --okbg:#E2EFDA; --bad:#C00000; --badbg:#FCE4E4;
  /* 🔴 THE TRADEMARK FOOTER'S OWN INK — 2 Oct 2026. It used to be written as
   * two literals inside `.legal-foot` (#697280 for the notice, #8a939e for the
   * fold line) and measured 4.32:1 on the light ground, 3.82:1 on the dark one
   * and 2.76:1 for the fold line — three failures of the same 4.5 gate every
   * other colour on this site has to clear. A copyright notice nobody can read
   * is not a notice, and this one names 公益財団法人日本英語検定協会.
   *
   * It is a token in all three blocks precisely because the literal version
   * had no dark value at all: one colour was being asked to work on #EEF2F5
   * and on #0C141A, and 3.82 is what that costs. Measured: 5.61 light,
   * 8.39 dark. The footer on the board and the panel keeps its own pale ink on
   * the sky-deep band, which already passes at 5.87. */
  --legal-ink:#4A6373;
  --radius:10px; --tap:44px;
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --navy:#8FB4CC; --navy2:#A9C8DC; --paleNavy:#1B2A36;
    --ground:#0C141A; --surface:#141F27; --surface2:#1A2731;
    --ink:#E8EEF2; --ink-soft:#9FB0BC; --ink-faint:#778894;
    --line:#26343F;
    --ok:#5FCB8C; --okbg:#12291D; --bad:#F08880; --badbg:#2E1614;
    --paleYellow:#2A2711;
    --legal-ink:#A3B0BB;
  }
}
:root[data-theme="dark"]{
  --navy:#8FB4CC; --navy2:#A9C8DC; --paleNavy:#1B2A36;
  --ground:#0C141A; --surface:#141F27; --surface2:#1A2731;
  --ink:#E8EEF2; --ink-soft:#9FB0BC; --ink-faint:#778894;
  --line:#26343F;
  --ok:#5FCB8C; --okbg:#12291D; --bad:#F08880; --badbg:#2E1614;
  --paleYellow:#2A2711;
  --legal-ink:#A3B0BB;
}

*{box-sizing:border-box}
html{height:100%}
body{
  margin:0; height:100vh; height:100dvh;
  display:flex; flex-direction:column; overflow:hidden;
  background:var(--ground); color:var(--ink);
  font:16px/1.55 -apple-system,"Segoe UI",Roboto,"Noto Sans","Noto Sans JP","Noto Sans Bengali",sans-serif;
  -webkit-text-size-adjust:100%;
}
/* ══ WHO DECIDES WHAT A BOX LOOKS LIKE ════════════════════════════════════
 *
 * 9 Sep 2026. Razib typed a time into "What times" on the slots screen and
 * could not read it: a WHITE box with WHITE letters in it. The rest of the
 * page was correctly dark.
 *
 * `color: inherit` on line below gave the control the dark theme's pale ink.
 * Its BACKGROUND was never set by us at all, so the browser drew its own —
 * and a browser that has not been told which scheme this page uses draws form
 * controls light. Pale ink on a light box is an invisible field.
 *
 * `color-scheme` is the line that settles it. It tells the browser which
 * scheme the page is really in, so native controls, scrollbars and the caret
 * follow — and it also stops Chrome on Android applying its own "force dark"
 * inversion, which is a browser redrawing our page for us. That is the second
 * time in two days that a browser has redecided what a screen says (the first
 * rewrote the exam questions through machine translation), and the answer is
 * the same both times: SAY WHAT YOU MEAN, and leave the browser nothing to
 * guess.
 *
 * The EXAM shell is deliberately light in every scheme — the real 英検 screen
 * is light and recognition is the product — so it declares `light` for itself
 * further down, which also exempts it from force-dark.
 */
:root{color-scheme:light}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){color-scheme:dark}}
:root[data-theme="dark"]{color-scheme:dark}
:root[data-theme="light"]{color-scheme:light}

button,input,textarea,select{font:inherit;color:inherit}

/* EVERY field states its own colours. Not `input[type=text]` — an attribute
   selector matches the attribute, so an `<input>` with no type, or a select,
   or a textarea, quietly fell through to the browser's own idea of a box. */
input,select,textarea{
  background:var(--surface);color:var(--ink);
  border:1.5px solid var(--line);border-radius:8px;
}
input::placeholder,textarea::placeholder{color:var(--ink-faint);opacity:1}
select{padding:0 10px;min-height:var(--tap)}
/* THE LIST THAT DROPS DOWN when a select is tapped is drawn by the operating
   system, not by the page, and it inherits nothing. Unstated, Android can draw
   it white-on-white — the same invisible field, one tap further in, where it
   is harder to notice and just as impossible to use. */
option,optgroup{background:var(--surface);color:var(--ink)}
button{cursor:pointer}
:focus-visible{outline:3px solid #1F4A63;outline-offset:2px;border-radius:6px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
.hide{display:none!important}

/* ══ NAKS shell ═══════════════════════════════════════════════════════════ */
.shell{flex:1 1 auto;display:flex;flex-direction:column;min-height:0}
.top{
  flex:0 0 auto;display:flex;align-items:center;gap:12px;
  padding:8px 14px;background:#143143;color:#fff;border-bottom:3px solid var(--yellow);
}
:root[data-theme="dark"] .top,
:root:not([data-theme="light"]) .top{background:#0F2531}
@media (prefers-color-scheme:light){:root:not([data-theme="dark"]) .top{background:#143143}}
/* The crest in the bar, on every screen of the app. It is 180px of artwork
   drawn at 30, which is the size the rest of the site draws it at too; giving
   it explicit width AND height stops the bar jumping while it loads. */
.top .crest{width:30px;height:30px;flex:0 0 auto;border-radius:50%;
  box-shadow:0 1px 3px rgba(0,0,0,.28)}
.top .brand{font-weight:700;font-size:15px;white-space:nowrap}
.top .sub{font-size:11.5px;opacity:.75;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.top .spacer{flex:1}
.ghost{background:rgba(255,255,255,.14);border:0;color:#fff;border-radius:8px;
  min-height:36px;padding:0 12px;font-size:13.5px}
.ghost:hover{background:rgba(255,255,255,.24)}
.main{flex:1 1 auto;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:18px}
.wrap{max-width:820px;margin:0 auto}

.btn{min-height:var(--tap);padding:0 20px;border-radius:var(--radius);
  border:1px solid var(--line);background:var(--surface);color:var(--ink);
  font-weight:600;font-size:15px;white-space:nowrap}
.btn:hover{background:var(--surface2)}
.btn.primary{background:var(--navy2);border-color:var(--navy2);color:#fff}
/* Dark text on the primary button belongs to DARK MODE ONLY, where --navy2 is a
   pale blue. The second selector was written without its media query, so it
   matched in light mode too — where --navy2 is deep navy — and every primary
   button on the site rendered near-black text on near-black navy. It is the
   button that says "Start the test". Found in a guide screenshot. */
:root[data-theme="dark"] .btn.primary{color:#0C141A}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .btn.primary{color:#0C141A}
}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn.wide{width:100%}

.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);
  padding:18px 20px;margin-bottom:14px}
.centre{max-width:440px;margin:6vh auto;padding:0 4px}
.logo{width:74px;height:74px;border-radius:50%;display:block;margin:0 auto 14px}
h1.t{font-size:22px;text-align:center;margin:0 0 4px}
p.lede{text-align:center;color:var(--ink-soft);margin:0 0 22px;font-size:14.5px}
label{display:block;font-size:13px;font-weight:600;margin:0 0 5px;color:var(--ink-soft)}
input[type=text],input[type=password],input[type=date]{
  width:100%;min-height:var(--tap);padding:0 13px;border-radius:8px;
  border:1.5px solid var(--line);background:var(--surface);margin-bottom:14px}
.msg{border-radius:8px;padding:11px 14px;margin-bottom:14px;font-size:14px}
.msg.err{background:var(--badbg);color:var(--bad);border:1px solid var(--bad)}
.msg.ok{background:var(--okbg);color:var(--ok);border:1px solid var(--ok)}
.msg.info{background:var(--surface2);border:1px solid var(--line);color:var(--ink-soft)}
.rule{height:1px;background:var(--line);margin:18px 0}
/* Which paper this interview belongs to. A candidate holding three of them
   must never have to work out which card is which. */
.speak-card .ttl{font-weight:700;font-size:13px;letter-spacing:.04em;
  text-transform:uppercase;color:var(--ink-soft);margin:0 0 8px}
.small{font-size:12.5px;color:var(--ink-faint);line-height:1.6}
.kv{display:flex;justify-content:space-between;gap:12px;padding:7px 0;
  border-bottom:1px solid var(--line);font-size:14.5px}
.kv:last-child{border-bottom:0}
.kv b{font-variant-numeric:tabular-nums}

/* ══ EXAM shell — the S-CBT look ══════════════════════════════════════════
   Colours sampled from Razib's screenshots of the real 筆記試験 screen.
   Fixed light palette; see the note at the top of this file.               */
.exam{
  flex:1 1 auto;display:flex;flex-direction:column;min-height:0;
  /* Fixed light, in every system theme — and DECLARED, so no browser
     force-darkens the paper a candidate is reading. */
  color-scheme:light;
  background:#fff;color:#1a1a1a;
  font-family:"Hiragino Kaku Gothic ProN","Yu Gothic","Noto Sans JP",
              -apple-system,"Segoe UI",Roboto,sans-serif;
}
.exbar{
  flex:0 0 auto;display:flex;align-items:center;gap:14px;
  padding:8px 14px;background:#f1f1f1;border-bottom:1px solid #c4c4c4;
}
.exbar-l{display:flex;align-items:baseline;gap:12px;min-width:0}
.exbar-l b{font-size:19px;font-weight:700;white-space:nowrap}
.exbar-l span{font-size:15px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.exbar-l small{font-size:11px;font-weight:400;color:#555}
.exclock{
  margin-left:auto;display:flex;align-items:center;gap:10px;
  background:#4d4d4f;color:#fff;border-radius:4px;padding:6px 14px;white-space:nowrap;
}
.exclock-k{font-size:13px}
.exclock b{font-size:25px;font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:.02em}
.exclock.warn{background:#b00000;animation:exPulse 1.4s ease-in-out infinite}
/* PREVIEW. The number is real — it is what the candidate's clock will start
   from — but it is standing still, and it must not be mistaken for a running
   one. Hollow, dashed, and no pulse: it reads as a label, not a countdown. */
.exclock.preview{background:transparent;color:#4d4d4f;border:1px dashed #9a9a9c;animation:none}
@keyframes exPulse{50%{opacity:.7}}

.exbody{flex:1 1 auto;display:flex;min-height:0;position:relative}
.expaper{flex:1 1 auto;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:20px 26px 60px}
.expaper>.exin{max-width:760px}

/* the collapse handle, as on the real screen */
.exhandle{
  position:absolute;top:50%;right:352px;transform:translateY(-50%);z-index:6;
  width:26px;height:44px;border:1px solid #9aa39a;border-right:0;border-radius:6px 0 0 6px;
  background:#dfe6d7;color:#333;font-size:12px;line-height:1;padding:0;
}
.exam.panel-closed .exhandle{right:0;border-radius:6px 0 0 6px}

/* ── the 「あなたの解答」 panel ───────────────────────────────────────── */
.expanel{
  flex:0 0 352px;display:flex;flex-direction:column;min-height:0;
  background:#ced8c6;border-left:1px solid #9aa39a;
}
.exam.panel-closed .expanel{display:none}
.exph{
  flex:0 0 auto;padding:8px 12px;background:#bcc8b2;border-bottom:1px solid #9aa39a;
  font-size:14px;font-weight:700;color:#22331d;display:flex;align-items:baseline;gap:8px;
}
.exph>span{display:flex;align-items:baseline;gap:8px}
.exph small{font-weight:400;font-size:11px;color:#41573a}
.exclose{display:none;margin-left:auto;background:none;border:0;color:#22331d;
  font-size:18px;line-height:1;padding:0 2px;min-width:32px;min-height:32px}
@media (max-width:900px){ .exclose{display:block} .exph{align-items:center} }
.expl{flex:1 1 auto;overflow-y:auto;padding:8px}
.expf{flex:0 0 auto;padding:8px;border-top:1px solid #9aa39a}
.exfinish{
  width:100%;min-height:42px;border:1px solid #b3a300;border-radius:3px;
  background:#ffff00;color:#c00000;font-weight:700;font-size:14px;
}
.exfinish:hover{filter:brightness(.95)}
.exfinish:disabled{opacity:.5;cursor:not-allowed}

.exgroup{display:flex;gap:6px;margin-bottom:8px}
.exgnum{
  flex:0 0 26px;height:26px;border-radius:50%;background:#8a9b81;color:#fff;
  font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;
}
.exrows{flex:1 1 auto;background:#dfe6d7;border:1px solid #b6c0ac;border-radius:3px;padding:3px}
.exrow{display:flex;align-items:center;gap:4px;padding:1px 2px}
.exrow-n{flex:0 0 42px;font-size:12.5px;text-decoration:underline;color:#1a1a1a;
  background:none;border:0;padding:0;text-align:left}
.exrow-f{flex:0 0 16px;height:16px;border:1px solid #7e8a76;background:#fff;border-radius:2px;padding:0}
.exrow-f[aria-pressed="true"]{background:#4d4d4f;border-color:#4d4d4f}
.exrow-o{
  flex:1 1 0;min-width:0;height:22px;border:1px solid #9aa39a;background:#f4f4f4;
  border-radius:2px;font-size:12.5px;font-variant-numeric:tabular-nums;padding:0;
}
.exrow-o[aria-pressed="true"]{background:#6b6b6b;border-color:#5a5a5a;color:#fff;font-weight:700}
.exdone{padding:6px 4px 2px;font-size:12px;color:#33452c}

/* ── the paper itself ─────────────────────────────────────────────────── */
.exhead{
  background:#e7e7e7;border:1px solid #c4c4c4;border-radius:3px;
  padding:7px 12px;margin:0 0 14px;font-weight:700;font-size:14.5px;
  position:sticky;top:-20px;z-index:2;
}
.expassage{
  border:1px solid #c9c9c9;background:#fafafa;padding:13px 15px;margin:0 0 14px;
  white-space:pre-wrap;line-height:1.85;border-radius:2px;
}
.expassage h4{margin:0 0 8px;font-size:15px}
.exq{margin:0 0 26px}
/* 準1級 / 1級 第3部 — the printed Situation. It is a box, not a paragraph,
   because a candidate has ten seconds to find it and read it; prose that looks
   like the rest of the page costs two of those seconds. The constraint in it
   ("no car", "under fifty dollars") is the whole question, so it sits above
   the options and never scrolls out from under them. */
.exsit{border:1.5px solid var(--line);border-left:5px solid var(--navy2);
  border-radius:10px;padding:12px 14px;margin:0 0 14px;background:var(--surface2)}
.exsit-h{font-size:12px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  color:var(--navy2);margin:0 0 2px}
.exsit-h + .exsit-b{margin-bottom:8px}
.exsit-b{font-size:15.5px;line-height:1.85;white-space:pre-wrap}
/* --ink-faint measured 2.71:1 here, under the 4.5 a candidate is owed. This
   line tells them a clock they cannot see is running; it is not decoration. */
.exsit-t{font-size:12.5px;line-height:1.6;color:var(--ink-soft);margin-top:8px}
.exqh{display:flex;gap:9px;align-items:flex-start;margin-bottom:10px}
.exqn{flex:0 0 auto;font-weight:700;font-size:15.5px}
.exqt{flex:1 1 auto;white-space:pre-wrap;line-height:1.9;font-size:15.5px}
.exopts{display:flex;flex-direction:column;gap:7px;margin-left:30px}
.exopt{
  display:flex;align-items:center;gap:12px;text-align:left;width:100%;max-width:640px;
  background:none;border:0;padding:0;
}
/* 🔴 THE OPTION NUMBERS WERE INVISIBLE IN DARK MODE. Found 14 Sep 2026 while
   measuring the new Situation box, on every grade and every paper at once.
   The box is deliberately paper-white (`#efefef`) to look like the S-CBT
   answer sheet — a FIXED colour. The digit inside it had no colour of its own,
   so it inherited `--ink`, which flips to near-white after dark. Near-white on
   #efefef measures 1.02:1. A candidate sitting in the evening saw four blank
   grey boxes and could only tell which one they had already pressed.
   Nothing catches this: the guide screenshots were all taken in light mode.
   A FIXED BACKGROUND NEEDS A FIXED FOREGROUND — state both or neither. */
.exopt .n{
  flex:0 0 62px;height:34px;border:1px solid #9a9a9a;background:#efefef;color:#12222c;
  border-radius:2px;
  display:flex;align-items:center;justify-content:center;font-size:15px;
}
.exopt[aria-pressed="true"] .n{background:#6b6b6b;border-color:#5a5a5a;color:#fff;font-weight:700}
.exopt .l{flex:1 1 auto;font-size:15.5px;line-height:1.6}
.exopt:hover .n{border-color:#4d4d4f}
.exflag{
  display:inline-flex;align-items:center;gap:7px;margin:12px 0 0 30px;
  font-size:13px;color:#333;background:none;border:0;padding:4px 0;
}
.exflag i{width:14px;height:14px;border:1px solid #777;background:#fff;display:inline-block;border-radius:2px}
.exflag[aria-pressed="true"] i{background:#4d4d4f;border-color:#4d4d4f}
.exhr{height:1px;background:#dcdcdc;margin:0 0 22px}

/* ── writing: the photograph is the main road ─────────────────────────── */
.exwrite{border:1px solid #c4c4c4;border-radius:3px;padding:0;margin:0 0 22px;overflow:hidden}
.exwrite-h{background:#e7e7e7;border-bottom:1px solid #c4c4c4;padding:7px 12px;
  font-weight:700;font-size:14.5px;display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap}
.exwrite-h span{font-weight:400;font-size:13px;color:#444}
.exwrite-b{padding:14px}
/* The per-大問 confirmation, under its own strip. `.msg` already carries the
   colours; this only places it and stops it being the last thing with a margin
   inside the block. */
.exwrite-b .exslotsum{margin:12px 0 0}
.exprompt{background:#fffdf0;border:1px solid #e2dcb8;padding:13px 15px;
  white-space:pre-wrap;line-height:1.85;margin:0 0 14px;border-radius:2px}
.excam{
  width:100%;min-height:76px;border:2px solid #4d4d4f;border-radius:4px;background:#4d4d4f;
  color:#fff;font-size:17px;font-weight:700;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:3px;padding:12px;
}
.excam small{font-weight:400;font-size:12.5px;opacity:.9}
.excam:hover{filter:brightness(1.1)}
.exshots{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.exshot{position:relative;width:92px;height:92px;border:1px solid #b6b6b6;border-radius:3px;overflow:hidden;background:#f4f4f4}
.exshot img{width:100%;height:100%;object-fit:cover;display:block}
.exshot .st{position:absolute;left:0;right:0;bottom:0;background:rgba(0,0,0,.62);color:#fff;
  font-size:10.5px;text-align:center;padding:2px}
.exshot.bad{border-color:#c00000}
/* A photograph the server will not take, however many times it is sent. It is
   marked apart from `.bad` on purpose: `.bad` invites a tap and this must not. */
.exshot.toobig{border-color:#c00000;border-width:2px}
.exshot.toobig .st{background:#c00000;color:#fff}
.extype{margin-top:14px;border-top:1px dashed #cfcfcf;padding-top:12px}
.extype summary{font-size:13.5px;color:#0b57a4;cursor:pointer}
/* THE BOX THE CHILD WRITES THEIR 英作文 IN. It had no background and no colour
   of its own, so on a phone forcing dark it could come out dark text on a dark
   field — in the one place on the whole site where not seeing what you typed
   costs marks. White paper, black ink, stated outright. */
.extype textarea{width:100%;min-height:150px;margin-top:10px;padding:12px;
  background:#fff;color:#1a1a1a;
  border:1px solid #b6b6b6;border-radius:3px;line-height:1.8;resize:vertical}
#shellExam input,#shellExam select,#shellExam textarea{
  background:#fff;color:#1a1a1a;border-color:#b6b6b6}
.excount{display:flex;justify-content:space-between;font-size:12.5px;color:#555;margin-top:6px}
.excount b{font-variant-numeric:tabular-nums}
.excount.inrange b{color:#1F7A45}
.excount.out b{color:#c00000}
.exnote{font-size:12.5px;color:#555;line-height:1.75;margin:10px 0 0}
.exlink{color:#0b57a4;text-decoration:underline;background:none;border:0;padding:0;font-size:13.5px}

/* ── listening ────────────────────────────────────────────────────────── */
.exaudio{border:1px solid #c4c4c4;background:#fafafa;border-radius:3px;padding:18px;
  text-align:center;margin:0 0 20px}
.exaudio .big{font-size:16px;font-weight:700;margin-bottom:5px}
.exaudio .note{font-size:13px;color:#555;margin-bottom:12px}
.exaudio .bar{height:7px;background:#dcdcdc;border-radius:4px;overflow:hidden;margin-top:12px}
.exaudio .bar i{display:block;height:100%;background:#4d4d4f;width:0}
.exbtn{min-height:40px;padding:0 22px;border:1px solid #4d4d4f;background:#4d4d4f;color:#fff;
  border-radius:3px;font-size:15px;font-weight:700}
.exbtn.plain{background:#fff;color:#1a1a1a;border-color:#9a9a9a}
.exbtn:disabled{opacity:.5;cursor:not-allowed}
.exmsg{border:1px solid #c00000;background:#fdecec;color:#a00000;padding:11px 13px;
  border-radius:3px;font-size:13.5px;margin-top:12px;text-align:left;line-height:1.7}
.exinfo{border:1px solid #b6c0ac;background:#eef3e9;color:#33452c;padding:11px 13px;
  border-radius:3px;font-size:13.5px;line-height:1.75;margin:0 0 16px}

/* ── the upload screen ────────────────────────────────────────────────── */
.exupload{max-width:620px;margin:4vh auto;padding:0 6px;text-align:center}
.exupload h2{font-size:21px;margin:0 0 6px}
.exupload .sub{color:#555;font-size:14px;margin:0 0 20px}

/* ── narrow screens: the panel slides over, as it must on a phone ─────── */
@media (max-width:900px){
  .exbar{padding:6px 10px;gap:8px}
  .exbar-l b{font-size:15px}
  .exbar-l span{font-size:12px}
  .exbar-l small{display:none}
  .exclock{padding:4px 10px;gap:6px}
  .exclock-k{display:none}
  .exclock b{font-size:21px}
  .expaper{padding:14px 14px 50px}
  .exopts{margin-left:0}
  .exflag{margin-left:0}
  .exopt .n{flex:0 0 48px;height:32px}
  .exopts.wide:not(.txt) .exopt .n{flex:1 1 auto}
  .exopts.wide.txt .exopt .n{flex:0 0 44px;height:34px}
  .expanel{position:absolute;inset:0 0 0 auto;width:min(340px,88vw);z-index:20;
    box-shadow:-8px 0 26px rgba(0,0,0,.25)}
  .exhandle{right:0;border-radius:6px 0 0 6px}
  .exam:not(.panel-open) .expanel{display:none}
  .exam:not(.panel-open) .exscrim{display:none}
  .exscrim{position:absolute;inset:0;background:rgba(0,0,0,.35);z-index:15}
}
@media (min-width:901px){
  .exscrim{display:none}
}

/* The finish button repeated at the foot of the paper. Only on narrow screens,
   where the answer panel is a slide-over and a student might never open it. */
.exinline{display:none;margin:26px 0 10px}
@media (max-width:900px){ .exinline{display:block} }

/* ══ listening, S-CBT style ═══════════════════════════════════════════════ */
.exband{display:flex;gap:12px;align-items:flex-start;background:#e7e7e7;
  border:1px solid #c4c4c4;border-radius:3px;padding:12px 14px;margin:0 0 18px}
.exbadge{flex:0 0 34px;height:34px;border-radius:50%;background:#8a8a8a;color:#fff;
  font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center}
.exbandt{flex:1 1 auto;font-size:14.5px;line-height:1.75}
.explays{margin-top:4px;font-size:13px;color:#444}

.exexample{border:1px solid #c9c9c9;background:#fafafa;border-radius:3px;
  padding:10px 12px;margin:0 0 18px;max-width:420px}
.exexl{font-size:13px;font-weight:700;margin-bottom:6px}
.exexample img{width:100%;height:auto;display:block}

.exq-l .exqno{font-size:17px;font-weight:700;margin-bottom:12px}
.exfig{border:1px solid #c9c9c9;background:#fff;border-radius:3px;padding:8px;
  margin:0 0 16px;max-width:420px}
.exfig img{width:100%;height:auto;display:block}
/* Listening options come in two shapes and they must not share one rule.
   第1部 answers are 1 2 3 with nothing written beside them — the numbered box IS
   the button, so it takes the whole row. 第2部 and 第3部 answers have text, and
   there the number must stay a small box with the words beside it.

   The first version tried to serve both with `flex:0 0 100%` on the number plus a
   `:not(:has(.l))` correction. The correction only covered the picture-only case,
   so on a phone every 第2部 answer was a full-width numbered box with its text
   pushed off the right-hand edge of the screen: Razib could see 1 2 3 4 and not one
   word of what they meant. The kind is now a class the JS sets, not something the
   stylesheet infers — and it is one rule each, so they cannot fight. */
.exopts.wide{margin-left:0;gap:9px;max-width:620px}
.exopts.wide .exopt{max-width:none}
.exopts.wide .exopt .l{display:block}

/* 第1部 — picture questions: the number fills the row */
.exopts.wide:not(.txt) .exopt .n{flex:1 1 auto;height:40px;font-size:16px}

/* 第2部・第3部 — the answer is words. The number never grows. */
.exopts.wide.txt .exopt{align-items:flex-start;gap:10px}
.exopts.wide.txt .exopt .n{flex:0 0 54px;height:38px;font-size:15px}
.exopts.wide.txt .exopt .l{flex:1 1 auto;min-width:0;padding-top:8px;
  overflow-wrap:anywhere}

.exnav{display:flex;gap:10px;justify-content:flex-end;margin:22px 0 0;max-width:620px}

/* the audio control, at the head of the answer panel as on the real screen */
.exaudio-host{flex:0 0 auto;padding:8px;border-bottom:1px solid #9aa39a;background:#c5d0bc}
.exstatus{background:#4d4d4f;color:#fff;border-radius:3px;padding:6px 10px;font-size:13px;
  margin-bottom:7px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.explay{font-size:13px}
.exbar2{height:7px;background:#eee;border:1px solid #9aa39a;border-radius:3px;overflow:hidden;margin-top:7px}
.exbar2 i{display:block;height:100%;background:#4d4d4f;width:0}
.exvol{display:flex;align-items:center;gap:8px;margin-top:7px;font-size:12px;color:#22331d}
.exvol input{flex:1 1 auto;min-width:0}

/* panel: two columns for listening, dimmed parts, one answer cell */
.exrows.two{display:grid;grid-template-columns:1fr 1fr;gap:2px 6px}
.exgroup.dim{opacity:.5}
.exgroup.dim .exrow-n{cursor:default;text-decoration:none}
.exrow-a{flex:0 0 30px;height:24px;border:1px solid #7e8a76;background:#fff;border-radius:2px;
  font-size:13px;font-weight:700;font-variant-numeric:tabular-nums;padding:0}
.exrow-a[aria-pressed="true"]{background:#6b6b6b;border-color:#5a5a5a;color:#fff}

@media (max-width:900px){
  .exfig,.exexample{max-width:100%}
  .exnav{max-width:none}
}

/* the English line under an official Japanese instruction */
.exbanden{font-size:13px;color:#555;margin-top:3px;line-height:1.6}
.explay small{display:block;font-weight:400;font-size:11px;color:#7a5a00;margin-top:2px}
.exflag span{line-height:1.4}

/* on a phone the audio control sits in the paper, above the instruction band */
#exAudioPaper{border:1px solid #9aa39a;border-radius:3px;margin:0 0 16px;background:#dfe6d7}
@media (min-width:901px){ #exAudioPaper{display:none!important} }

/* ══ the staff panel ══════════════════════════════════════════════════════
   Razib's verdict on the first version: "this is not right at all" — and he was right.
   Everything staff-facing was stacked in one column on the student's home
   screen, so sixteen speaking tests meant sixteen open marking forms and a
   page you scrolled for a minute to reach the papers.

   So: a left rail, one section at a time, and rows instead of cards. A row is
   one line — name, what it is, its state, and the buttons that act on it —
   because a list you can scan is the whole point of a panel. Anything that
   needs a form opens INSIDE its own row, one at a time, and closes again.

   On a phone the rail becomes a scrolling strip of tabs across the top: the
   same sections, still one at a time. */
.main:has(.adm){padding:0}
.wrap:has(.adm){max-width:none}
/* 🔴 THE RAIL GREW — 17 Sep 2026. Razib: *"make this left panel wider, font
   bigger … catagoriy font more biger and simbol more biger like locks like
   app"*. It was 212px of 14px text with 14px icons, which reads as a dense
   list of settings; a panel somebody works in all day should read like an app.
   Every number below went up together — width, item, Japanese gloss, band
   heading, icon and badge — because raising one of them alone just makes the
   rail look uneven. The phone layout below is untouched: there the rail is one
   button, and none of this applies. */
.adm{display:grid;grid-template-columns:316px 1fr;gap:0;min-height:calc(100dvh - 64px)}
/* A gentle top-to-bottom gradient rather than one flat navy. It is the
   cheapest thing that stops a tall dark column reading as a wall, and it costs
   nothing on a slow phone. */
.adm-rail{
  background:linear-gradient(180deg,#173043 0%,#132836 45%,#0F2430 100%);
  border-right:1px solid rgba(255,255,255,.08);padding:16px 0;
}
:root[data-theme="dark"] .adm-rail,
:root:not([data-theme="light"]) .adm-rail{background:#0F2531}
@media (prefers-color-scheme:light){:root:not([data-theme="dark"]) .adm-rail{background:#132836}}
.adm-who{padding:0 20px 15px;color:#7f9db0;font-size:13px;letter-spacing:.12em;font-weight:800}
.adm-nav{display:flex;flex-direction:column}
.adm-nav button{
  display:flex;align-items:center;gap:10px;background:none;border:0;color:#cfe0ea;
  padding:11px 18px;font-size:14px;text-align:left;width:100%;border-left:3px solid transparent;
}
.adm-nav button:hover{background:rgba(255,255,255,.06);color:#fff}
.adm-nav button[aria-current="true"]{
  background:rgba(255,255,255,.10);color:#fff;font-weight:700;border-left-color:var(--yellow);
}
.adm-nav .count{
  margin-left:auto;background:var(--yellow);color:#132836;font-size:13px;font-weight:800;
  min-width:26px;height:23px;border-radius:99px;display:grid;place-items:center;padding:0 8px;
}
.adm-nav .count.zero{background:rgba(255,255,255,.14);color:#9fb6c4}

/* ══ THE PANEL MENU, IN BANDS — 15 Sep 2026 ═══════════════════════════════
 * Fifteen flat items became five colour-banded groups, each item carrying its
 * Japanese underneath the English. The rail keeps its navy, its yellow badge
 * and its yellow current-marker; what changed is grouping, colour and the
 * second language. See SECTION_GROUPS in app.js. */
.adm-nav .grp{margin-top:22px}
.adm-nav .grp:first-child{margin-top:4px}
/* ══ THE BAND HEADING, THIRD TIME ═════════════════════════════════════════
 * Razib asked for a bigger category font three times. 10px → 12 → 13.5 was too
 * small a step each time, so this one is decisive: 15px, which is LARGER than
 * the body text on most of this site, and the wide letter-spacing comes back
 * down because tracking that looks deliberate at 10px looks thin and stretched
 * at 15. A hairline runs from the words to the edge of the rail, so a band
 * reads as a section of a panel rather than a word floating above a list. */
.adm-nav .gh{padding:0 18px 10px;font-size:15px;letter-spacing:.07em;font-weight:800;
  display:flex;align-items:center;gap:10px}
.adm-nav .gh .dot{width:12px;height:12px;border-radius:50%;flex:0 0 auto}
.adm-nav .gh::after{content:"";flex:1 1 auto;height:1px;
  background:currentColor;opacity:.22;border-radius:1px}
.adm-nav .nav-list{display:flex;flex-direction:column}
.adm-nav button{align-items:center;padding:9px 14px;gap:14px;border-radius:0 14px 14px 0}
/* ══ THE SYMBOL SITS IN A COLOURED TILE ═══════════════════════════════════
 *
 * Razib asked twice, and the second time with a photograph: *"symbol more biger
 * like locks like app, very colorful panel and user friendly panel of the
 * world"*. Making the emoji a few pixels larger was not the answer — a row of
 * bare emoji on navy is a LIST, and he is comparing this to an app.
 *
 * So every item gets a 40px rounded tile in its band's colour, and the emoji
 * sits inside it. That is the thing that makes a menu findable without reading:
 * Papers is always the grey-blue square in the fifth band, in the same place,
 * whatever language you read. The current item's tile fills solid, so where you
 * are is a block of colour rather than a thin yellow line.
 *
 * The tints are written out rather than computed with color-mix(), which is new
 * enough that the day it is not supported the tiles would vanish entirely. Six
 * colours is not a maintenance burden; a blank rail is. */
.adm-nav .ic{
  width:48px;height:48px;flex:0 0 auto;border-radius:14px;
  display:grid;place-items:center;font-size:26px;line-height:1;
  background:var(--tint,rgba(255,255,255,.10));
  box-shadow:inset 0 0 0 1.5px var(--edge,rgba(255,255,255,.14));
  transition:background .12s ease;
}
.adm-nav button[aria-current="true"] .ic{
  background:var(--band,#8FB8C9);box-shadow:none;
}
.adm-nav .tx{min-width:0}
.adm-nav .tx .en{display:block;font-size:17.5px;font-weight:800;line-height:1.25}
/* ENGLISH IS THE LARGER OF THE TWO, everywhere on this site. The Japanese is
   the gloss, not the other way round — Razib, 15 Sep: "large font of english". */
.adm-nav .tx .ja{display:block;font-size:12.5px;color:#93adbd;line-height:1.35}
.adm-nav .count{align-self:center}

/* One band, one colour — the dot, the heading, the tile, the current marker and
   the badge all read from it, so a new band needs three lines and not eleven. */
.g1{--band:#4FA3E3; --tint:rgba(79,163,227,.20);  --edge:rgba(79,163,227,.42)}
.g2{--band:#B98CE8; --tint:rgba(185,140,232,.20); --edge:rgba(185,140,232,.42)}
.g3{--band:#67D39B; --tint:rgba(103,211,155,.20); --edge:rgba(103,211,155,.42)}
.g4{--band:#F2A65A; --tint:rgba(242,166,90,.20);  --edge:rgba(242,166,90,.42)}
.g5{--band:#8FB8C9; --tint:rgba(143,184,201,.22); --edge:rgba(143,184,201,.44)}
.g6{--band:#E27C7C; --tint:rgba(226,124,124,.20); --edge:rgba(226,124,124,.42)}
.adm-nav .dot,.nav-sheet .dot{background:var(--band)}
.adm-nav .gh,.nav-sheet .gh{color:var(--band)}
.adm-nav button[aria-current="true"],
.nav-sheet button[aria-current="true"]{border-left-color:var(--band)}
/* The current item is a block of its band's colour, not a tint of white. */
.adm-nav button[aria-current="true"]{background:var(--tint,rgba(255,255,255,.10))}
.adm-nav button:hover .ic{background:var(--band)}
.nav-pick{display:none}

@media (max-width:860px){
  /* THE SIDEWAYS STRIP SHOWED THREE OF FIFTEEN. Measured at 390px on 15 Sep:
     Overview, Marking, Speaking tests, and then the edge of the screen — with
     nothing to say the other twelve exist. So on a phone the rail is one
     button naming where you are, and pressing it opens the whole menu. */
  .adm-nav{display:none}
  .nav-pick{display:block;padding:8px 12px}
  .nav-cur{width:100%;background:rgba(255,255,255,.10);color:#fff;border:0;border-radius:10px;
    padding:11px 13px;font-family:inherit;font-size:15px;font-weight:800;text-align:left;
    display:flex;align-items:center;gap:9px;min-height:46px}
  .nav-cur .tx .en{display:block;line-height:1.2}
  .nav-cur .tx .ja{display:block;font-size:10.5px;color:#8fa9b8;font-weight:700;line-height:1.25}
  .nav-cur .ar{margin-left:auto}
  .nav-sheet{display:none;margin-top:8px;background:#0F2531;border-radius:12px;padding:6px 0 10px}
  .nav-sheet.on{display:block}
  .nav-sheet .grp{margin-top:10px}
  .nav-sheet .nav-list{display:flex;flex-direction:column}
  .nav-sheet button{display:flex;align-items:flex-start;gap:9px;background:none;border:0;
    color:#cfe0ea;padding:9px 16px;width:100%;text-align:left;font-family:inherit;
    border-left:3px solid transparent;min-height:44px}
  .nav-sheet button[aria-current="true"]{background:rgba(255,255,255,.10);color:#fff}
  /* The phone menu grew with the rail — it is the one Razib actually uses. */
  /* The phone menu gets the same coloured tiles — it is the one Razib uses. */
  .nav-sheet .ic{width:48px;height:48px;flex:0 0 auto;border-radius:14px;
    display:grid;place-items:center;font-size:26px;line-height:1;
    background:var(--tint,rgba(255,255,255,.10));
    box-shadow:inset 0 0 0 1.5px var(--edge,rgba(255,255,255,.14))}
  .nav-sheet button[aria-current="true"] .ic{background:var(--band,#8FB8C9);box-shadow:none}
  .nav-sheet button[aria-current="true"]{background:var(--tint,rgba(255,255,255,.10))}
  .nav-cur .ic{width:48px;height:48px;border-radius:14px;display:grid;place-items:center;
    font-size:26px;line-height:1;background:rgba(255,255,255,.14)}
  .nav-sheet .tx .en{display:block;font-size:17.5px;font-weight:800;line-height:1.25}
  .nav-sheet .tx .ja{display:block;font-size:12px;color:#8fa9b8;line-height:1.35}
  .nav-sheet button{align-items:center;gap:14px;padding:11px 18px;min-height:60px}
  .nav-sheet .count{margin-left:auto;align-self:center;background:var(--yellow);color:#132836;
    font-size:13px;font-weight:800;min-width:26px;height:23px;border-radius:99px;
    display:grid;place-items:center;padding:0 8px}
  .nav-sheet .count.zero{background:rgba(255,255,255,.14);color:#9fb6c4}
  .nav-sheet .gh{padding:0 18px 10px;font-size:15px;letter-spacing:.07em;font-weight:800;
    display:flex;align-items:center;gap:10px}
  .nav-sheet .gh .dot{width:12px;height:12px;border-radius:50%}
  .nav-sheet .gh::after{content:"";flex:1 1 auto;height:1px;background:currentColor;opacity:.22}
  .nav-cur{font-size:17.5px}
  .nav-cur .tx .ja{font-size:12px}
}
.adm-main{padding:22px 26px 60px;min-width:0}
.adm-h{margin:0 0 4px;font-size:21px;letter-spacing:-.01em}
.adm-sub{margin:0 0 20px;color:var(--ink-soft);font-size:14px}

/* the numbers on the overview */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin:0 0 24px}
.stat{background:var(--surface);border:1px solid var(--line);border-radius:10px;padding:15px 16px}
.stat b{display:block;font-size:27px;line-height:1.1;letter-spacing:-.02em}
.stat span{display:block;font-size:12.5px;color:var(--ink-soft);margin-top:3px}
.stat.go b{color:var(--navy2)}
.stat.warn b{color:#B26B00}

/* rows */
.tbl{background:var(--surface);border:1px solid var(--line);border-radius:10px;overflow:hidden}
.tbl-head{
  display:flex;gap:12px;align-items:center;padding:9px 14px;background:var(--surface2);
  border-bottom:1px solid var(--line);font-size:11.5px;font-weight:700;color:var(--ink-soft);
  letter-spacing:.06em;
}
.row{border-bottom:1px solid var(--line)}
.row:last-child{border-bottom:0}
.row-line{display:flex;gap:12px;align-items:center;padding:11px 14px;flex-wrap:wrap}
.row-line:hover{background:var(--surface2)}
.row-main{flex:1 1 220px;min-width:0}
.row-main b{font-size:14.5px}
/* The sub-line is a block so it sits under the name. The status chip is a span
   too, and the first version of this rule caught it as well — every chip
   stretched the whole width of the row like a progress bar. */
.row-main span:not(.chip){display:block;font-size:12.5px;color:var(--ink-soft);margin-top:1px}
.row-side{font-size:12.5px;color:var(--ink-soft);text-align:right;flex:0 0 auto}
.row-acts{display:flex;gap:7px;flex-wrap:wrap;flex:0 0 auto}
.row-open{padding:0 14px 16px;border-top:1px dashed var(--line);margin-top:-1px}
.btn-s{
  min-height:32px;padding:0 12px;font-size:13px;border-radius:7px;border:1.5px solid var(--line);
  background:var(--surface);display:inline-flex;align-items:center;gap:6px;text-decoration:none;
  color:inherit;
}
.btn-s:hover{border-color:var(--navy2)}
.btn-s.go{background:var(--navy);color:#fff;border-color:var(--navy)}
.btn-s.go:hover{background:var(--navy2)}
/* 🔴 A DELETE BUTTON MUST NOT LOOK LIKE EVERY OTHER BUTTON — 16 Sep 2026.
   The Principal-only Delete on Registrations and Repeat tests was given the
   class `danger`, which had no style at all: it would have rendered as a plain
   grey button sitting beside Cancel, and the two do very different things —
   Cancel keeps the row and its history, Delete destroys the only record that
   money arrived. Both light and dark themes already define --bad and --badbg,
   so it costs two lines to make the dangerous one look dangerous. */
.btn-s.danger{background:var(--badbg);color:var(--bad);border-color:var(--bad)}
.btn-s.danger:hover{background:var(--bad);color:#fff}
.chip{font-size:11.5px;font-weight:700;padding:2px 8px;border-radius:99px;white-space:nowrap}
.chip.wait{background:#FFF3DC;color:#8A5200}
.chip.done{background:var(--okbg);color:var(--ok)}
.chip.off{background:var(--surface2);color:var(--ink-faint)}
.chip.live{background:var(--okbg);color:var(--ok)}
/* An hour that exists and can never be booked. Not "free" — free is a promise
   somebody can take. Added 6 Sep 2026 with the 7-day count on "My times". */
.chip.bad{background:var(--badbg);color:var(--bad)}
.empty{padding:26px 16px;text-align:center;color:var(--ink-faint);font-size:14px}

@media (max-width:860px){
  /* Two things a grid does by default that are wrong here:
     · a grid ITEM will not shrink below its content, so seven tabs made the
       rail 672px wide inside a 390px phone and pushed every row off the right
       edge — `min-width:0` is what lets the strip scroll instead;
     · with one column, `min-height` stretched the FIRST row, so the dark rail
       became a tall black slab above the content. `auto 1fr` gives the height
       to the content, where it belongs. */
  .adm{grid-template-columns:1fr;grid-template-rows:auto 1fr}
  .adm-rail,.adm-main{min-width:0}
  .adm-rail{border-right:0;border-bottom:1px solid rgba(255,255,255,.08);padding:8px 0}
  .adm-who{display:none}
  .adm-nav{flex-direction:row;overflow-x:auto;gap:2px;padding:0 8px;scrollbar-width:none}
  .adm-nav::-webkit-scrollbar{display:none}
  .adm-nav button{white-space:nowrap;width:auto;border-left:0;border-bottom:3px solid transparent;
    padding:9px 12px;border-radius:6px 6px 0 0}
  .adm-nav button[aria-current="true"]{border-left:0;border-bottom-color:var(--yellow)}
  /* `flex:0 0 auto` kept the buttons at their natural width, so the last one
     was clipped off the edge of the row instead of wrapping. On a phone the
     buttons get a line of their own and wrap within it. */
  .row-acts{flex:1 1 100%;min-width:0}
  .adm-main{padding:18px 14px 60px}
  .row-side{flex:1 1 100%;text-align:left}
}


/* ══ THE CANDIDATE'S DASHBOARD ════════════════════════════════════════════
   Razib: "more graphically attractive … writing inside big size and add
   various buttons, colourful". The colour carries meaning here — green is a test
   you can sit, amber is one you have paid for and are waiting on, blue is the
   record. A palette that means nothing is just noise on a small screen. */
.dash{max-width:560px;margin:22px auto 60px;padding:0 14px}
.dash-h{font-size:14px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;
  color:var(--ink-faint);margin:26px 0 10px}

/* ── the hero: their name, on their page ─────────────────────────────────*/
.hero{background:linear-gradient(135deg,var(--navy2,#123047),#1d4a6b);
  border-radius:16px;padding:22px 20px;margin-bottom:16px;color:#fff}
.hero-who{display:flex;align-items:center;gap:16px}
.avatar{width:62px;height:62px;flex:0 0 62px;border-radius:50%;
  background:var(--yellow,#F5C542);color:#12232e;
  display:flex;align-items:center;justify-content:center;
  font-size:24px;font-weight:800;letter-spacing:.02em}
.hero-name{font-size:26px;line-height:1.15;margin:0;color:#fff;font-weight:800;
  word-break:break-word}
.hero-meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:7px}
.hero-grade{background:rgba(255,255,255,.18);border-radius:99px;padding:3px 11px;
  font-size:12.5px;font-weight:700}
.hero-id{background:rgba(0,0,0,.22);border-radius:99px;padding:3px 11px;
  font-size:12.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.hero-since{margin:9px 0 0;font-size:12px;color:rgba(255,255,255,.72)}

/* ── three numbers ───────────────────────────────────────────────────────*/
.tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:6px}
.tile{border-radius:14px;padding:14px 10px;text-align:center;border:1px solid transparent}
.tile-n{font-size:30px;font-weight:800;line-height:1}
.tile-ja{font-size:11.5px;margin-top:5px;opacity:.9}
.tile-en{font-size:10.5px;opacity:.65}
.tile.blue{background:#E8F1FA;color:#14456E;border-color:#CFE1F2}
.tile.green{background:#E6F5EC;color:#146B3A;border-color:#C9E9D6}
.tile.amber{background:#FFF4DC;color:#8A5200;border-color:#F7E1B5}

/* ══ THE CANDIDATE'S BOARD, IN TABS — 15 Sep 2026 ═════════════════════════
 * Five tabs, each with its own colour, and ENGLISH LARGER THAN THE JAPANESE —
 * which is the opposite of what `.tile-en` did until today (10.5px at 65%
 * opacity, under an 11.5px Japanese line). Razib: "large font of english".
 * Above them sits `.todo`, which is never hidden by a tab. */
.tile-ja{font-size:10.5px;margin-top:1px;opacity:.7;order:2}
.tile-en{font-size:13px;font-weight:800;opacity:1;margin-top:5px;line-height:1.2;order:1}
.tile{display:flex;flex-direction:column}
.tile-n{order:0}

.todo{display:grid;gap:7px;margin:12px 0 0}
.u{display:flex;gap:10px;align-items:center;border-radius:12px;padding:10px 12px;
  border-left:5px solid var(--line);background:var(--surface)}
.u .ic{font-size:17px;line-height:1.2;flex:0 0 auto}
.u .tx{min-width:0}
.u .tx .ja{display:block;font-size:11.5px;color:var(--ink-faint);line-height:1.25}
.u .tx .en{display:block;font-size:15px;font-weight:800;line-height:1.25}
.u.w{border-left-color:#8A5200;background:#FFF4DC;color:#2A2113}
.u.p{border-left-color:var(--bad,#B3261E);background:var(--badbg,#FCEAE8);color:#3A1614}
.u.t{border-left-color:#6A3FA0;background:#F1EBFB;color:#241534}
.u.n{border-left-color:var(--yellow);background:#FFFBEA;color:#2A2113}
/* 🔴 A PRESENT, NOT A CHORE — 27 Sep 2026. The free test Razib asked to have
   shown shares a strip built for things that are WRONG: red for money waiting,
   amber for writing that never arrived. Green, so a child can tell at a glance
   which of these lines is good news. */
.u.f{border-left-color:#1B8A4B;background:#E9F7EE;color:#10331F}

/* 🔴 AND THE STRIP HAD NO DARK COLOURS AT ALL — 27 Sep 2026.
 *
 * All five of these set a pale background and no text colour, so the words fell
 * through to `--ink` — near-black navy in light, and LIGHT BLUE in dark. On a
 * dark phone every line in this strip was pale blue on cream: the money
 * warning, the missing writing, the interview time. Exactly the fault recorded
 * for `.en` on a coloured card, in the one place a child is being told
 * something urgent. Every rule here writes both colours out. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .u.w{background:#332715;border-left-color:#C98F2E;color:#F0DFBC}
  :root:not([data-theme="light"]) .u.p{background:#3A1A18;border-left-color:#E0827A;color:#F6D5D1}
  :root:not([data-theme="light"]) .u.t{background:#271A38;border-left-color:#A98AD8;color:#E4D8F6}
  :root:not([data-theme="light"]) .u.n{background:#332715;border-left-color:#D9B44A;color:#F0E4BC}
  :root:not([data-theme="light"]) .u.f{background:#14301F;border-left-color:#4FBF80;color:#CDECD9}
  :root:not([data-theme="light"]) .u .tx .ja{color:inherit;opacity:.72}
}
:root[data-theme="dark"] .u.w{background:#332715;border-left-color:#C98F2E;color:#F0DFBC}
:root[data-theme="dark"] .u.p{background:#3A1A18;border-left-color:#E0827A;color:#F6D5D1}
:root[data-theme="dark"] .u.t{background:#271A38;border-left-color:#A98AD8;color:#E4D8F6}
:root[data-theme="dark"] .u.n{background:#332715;border-left-color:#D9B44A;color:#F0E4BC}
:root[data-theme="dark"] .u.f{background:#14301F;border-left-color:#4FBF80;color:#CDECD9}
:root[data-theme="dark"] .u .tx .ja{color:inherit;opacity:.72}
.u .go{margin-left:auto;flex:0 0 auto;border:0;border-radius:9px;padding:8px 12px;
  background:var(--navy2,#123047);color:#fff;font-family:inherit;cursor:pointer;min-height:40px}
.u .go .en{display:block;font-size:13.5px;font-weight:800;line-height:1.2}
.u .go .ja{display:block;font-size:10px;opacity:.8;line-height:1.2}

.btabs{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;margin:14px 0 0}
.btab{background:var(--surface);border:2px solid var(--line);border-bottom-width:4px;
  border-radius:11px;padding:8px 3px 7px;text-align:center;font-family:inherit;
  color:var(--ink);cursor:pointer;min-height:56px}
.btab .ic{display:block;font-size:16px;line-height:1.1}
.btab .en{display:block;font-size:13.5px;font-weight:800;line-height:1.2;margin-top:3px}
.btab .ja{display:block;font-size:10px;color:var(--ink-faint);line-height:1.2}
.btab .bdg{display:inline-block;min-width:16px;padding:0 4px;border-radius:9px;
  background:var(--bad,#B3261E);color:#fff;font-size:10.5px;font-weight:800;
  margin-left:3px;vertical-align:1px}
.btab[data-k="test"]{border-bottom-color:#14456E}
.btab[data-k="talk"]{border-bottom-color:#6A3FA0}
.btab[data-k="res"]{border-bottom-color:var(--ok,#1B8A4B)}
.btab[data-k="news"]{border-bottom-color:#8A5200}
.btab[data-k="help"]{border-bottom-color:#0F6F7A}
.btab[aria-selected="true"][data-k="test"]{background:#E8F1FA;border-color:#14456E}
.btab[aria-selected="true"][data-k="talk"]{background:#F1EBFB;border-color:#6A3FA0}
.btab[aria-selected="true"][data-k="res"]{background:var(--okbg,#E6F5EC);border-color:var(--ok,#1B8A4B)}
.btab[aria-selected="true"][data-k="news"]{background:#FFF4DC;border-color:#8A5200}
.btab[aria-selected="true"][data-k="help"]{background:#E6F4F6;border-color:#0F6F7A}
.bpane{display:none;margin-top:12px}
.bpane.on{display:block}
.bquiet{color:var(--ink-faint);font-size:14px;line-height:1.7;
  background:var(--surface);border:1px dashed var(--line);border-radius:12px;padding:16px}

/* ── "this book has your name in it" — 25 Sep 2026 ──────────────────────────
   Beside the download button, not at the foot of the card in grey. It is a
   warning, so it is allowed to be seen; it is not an accusation, so it is amber
   and not red. Red on a card belonging to somebody who has just paid reads as
   "we think you are a thief", and most of them are not. */
.bwarn{margin:12px 0 0;padding:12px 14px;border-radius:10px;
  background:#FFF8E1;border:1px solid #E6D48A;color:#5a4a12}
.bwarn b{display:block;font-size:13.5px;line-height:1.7;margin-bottom:4px}
.bwarn p{margin:0 0 6px;font-size:13px;line-height:1.75}
.bwarn p:last-child{margin-bottom:0}
.bwarn p.en{color:#6b5c2a}

/* ══ THE TWO-FOR-ONE CARD — 26 Sep 2026 ═══════════════════════════════════
 *
 * The same class names, the same picture and the same wording as /register
 * and the free test's result screen. Three screens, one look, so a candidate
 * who saw the offer before they had a login recognises it after.
 *
 * 🔴 EVERY COLOUR IS WRITTEN OUT, EXACTLY AS `.bwarn` ABOVE DOES IT. This card
 * stays light gold in dark mode: the drawing inside it is white paper, and
 * `--ink` on it would be right while `--en` (#8FC4F5 in the dark theme) would
 * be pale blue on cream — 1.8:1, invisible. That trap has shipped on this site
 * three times. Nothing in here inherits a colour.
 */
/* 🔴 A FLAT COLOUR UNDER THE GRADIENT — added 1 Oct 2026 while measuring the
   board. The shorthand left `background-color` TRANSPARENT, so anything asking
   this card what colour it is (a contrast checker, a printer stripping images,
   a browser that cannot parse the gradient) was told "nothing" and read the
   page behind it. The flat value is the gradient's DARKEST stop, so the answer
   it gives is also the worst case. No visual change: it sits underneath. */
.o21{margin:0 0 14px;padding:17px 19px 19px;border-radius:12px;
  background-color:#F8E7B4;
  background-image:linear-gradient(135deg,#FFFCF2,#FDF3D2 60%,#F8E7B4);
  border:1.5px solid #E6D48A}
/* Across the top, not in a side column: the drawing is 480×172 and shrinks to
   an unreadable thumbnail beside a paragraph. */
.o21-pic{width:100%;max-width:340px;height:auto;display:block;margin:0 auto 14px}
/* 🔴 NEAR-BLACK ON THE GOLD, NOT WHITE — 1 Oct 2026, measured on the board.
   White on #B8912A is 2.95:1. This pill is the campaign's own name
   (「キャンペーン実施中 · OFFER ON NOW」) and it has been unreadable on all THREE
   screens that carry this card — the board, /register and the free test's score
   screen — since it was written on 26 Sep. #2A2208 on the same gold is 5.35:1.
   The pill keeps its colour, its size and its place; only the ink changes, so
   the one-look rule across the three screens is kept by changing it HERE, once,
   rather than overriding it on one of them. */
.o21-tag{display:inline-block;background:#B8912A;color:#2A2208;font-size:11px;
  font-weight:800;letter-spacing:.08em;padding:4px 10px;border-radius:999px}
.o21 b{display:block;margin:8px 0 4px;font-size:16px;line-height:1.5;color:#4E3F0C}
.o21 p{margin:0;font-size:13.5px;line-height:1.85;color:#5A4A12}
/* 14px, the floor this site holds English to everywhere else. It was 12.5px:
   this is the sentence that explains the offer to a family who cannot read the
   Japanese above it, which makes it the only sentence on the card for them.
   The colour already measured 5.4:1 on the gradient's darkest stop and is
   unchanged. */
.o21 p.en{margin-top:5px;font-size:14px;color:#6B5C2A}
@media (max-width:560px){
  .o21{padding:15px 15px 17px}
  .o21-pic{max-width:280px}
}

/* One line under a Buy button, for the person who is one purchase away from
   it. No picture: this is an answer to "should I", not an advertisement, and
   it has to sit quietly against the button above it. */
.o21line{margin:10px 0 0;padding:10px 13px;border-radius:10px;
  background:#FFF8E1;border:1px solid #E6D48A}
.o21line b{display:block;font-size:13.5px;line-height:1.7;color:#5a4a12}
.o21line span{display:block;font-size:12.5px;line-height:1.7;color:#6b5c2a;margin-top:2px}

/* ── the restart card, drawn only on the rare day it is owed ─────────────── */
.restart-card{border-left:5px solid var(--yellow)}
.restart-now{font-size:14px;font-weight:800;margin:10px 0 2px;padding:9px 11px;border-radius:9px}
.restart-now.clear{background:var(--okbg,#E6F5EC);color:var(--ok,#1B8A4B)}
.restart-now.busy{background:var(--badbg,#FCEAE8);color:var(--bad,#B3261E)}
/* Open papers, but none of them a child's — a true statement should not wear
   the colour of an alarm. */
.restart-now.staffonly{background:var(--amberbg,#FDF1DC);color:var(--amber,#8A5A00)}
.restart-card .btn .en{display:block;font-size:15px;font-weight:800;line-height:1.2}
.restart-card .btn .ja{display:block;font-size:11px;font-weight:600;opacity:.85;line-height:1.2}
.restart-card .btn.arm{background:var(--bad,#B3261E);color:#fff}

/* WHO IS SITTING, as rows he can read — a count was never a fact he could act
   on. Tokens only, so the dark blocks at the foot of this file cover it. */
.restart-who{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;
  padding:9px 11px;margin:5px 0;border:1px solid var(--line);border-radius:9px;
  background:var(--surface)}
.restart-who b{font-size:14px}
.restart-who .small{font-size:12px;opacity:.8;margin:2px 0 0}
/* A staff or demo login is the answer to "is this one important?", so it is
   allowed to look different at a glance rather than only read differently. */
.restart-who.staff{border-style:dashed;opacity:.85}
.restart-tag{flex:0 0 auto;font-size:11px;font-weight:700;padding:3px 8px;border-radius:999px;
  background:var(--badbg,#FCEAE8);color:var(--bad,#B3261E);white-space:normal;max-width:9.5em;
  text-align:center;line-height:1.25}
.restart-who.staff .restart-tag{background:var(--surface2,#EFEFEF);color:var(--ink,#222)}
.restart-force{margin-top:12px;padding:11px;border:1px dashed var(--bad,#B3261E);border-radius:10px}
.restart-force .small{margin:0 0 7px}
.restart-card .btn.force{width:100%}
@media (max-width:420px){
  /* The badge stops competing with the name for the same line. */
  .restart-who{flex-direction:column}
  .restart-tag{max-width:none;align-self:flex-start}
}
@media (max-width:400px){
  .btab .ja{display:none}
  .btab .en{font-size:12.5px}
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .tile.blue{background:#16293A;color:#9CC8EC;border-color:#22405A}
  :root:not([data-theme="light"]) .tile.green{background:#16301F;color:#8FD9AC;border-color:#22482F}
  :root:not([data-theme="light"]) .tile.amber{background:#332715;color:#F0C170;border-color:#4A3A1E}
}
:root[data-theme="dark"] .tile.blue{background:#16293A;color:#9CC8EC;border-color:#22405A}
:root[data-theme="dark"] .tile.green{background:#16301F;color:#8FD9AC;border-color:#22482F}
:root[data-theme="dark"] .tile.amber{background:#332715;color:#F0C170;border-color:#4A3A1E}

/* ── the last result, as a headline ──────────────────────────────────────*/
.result-card{border-left:5px solid var(--line);padding-top:16px}
.result-card.pass{border-left-color:var(--ok,#1B8A4B)}
.result-card.fail{border-left-color:var(--bad,#C0392B)}
.result-card.mid{border-left-color:var(--yellow,#F5C542)}
.rc-label{font-size:11.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:var(--ink-faint)}
.rc-line{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  margin:6px 0 14px}
.rc-band{font-size:38px;font-weight:800;line-height:1;letter-spacing:-.01em}
.result-card.pass .rc-band{color:var(--ok,#1B8A4B)}
.result-card.fail .rc-band{color:var(--bad,#C0392B)}
.rc-right{text-align:right}
.rc-cse{font-size:22px;font-weight:700;font-variant-numeric:tabular-nums}
.rc-sub{font-size:12px;color:var(--ink-faint)}

/* ── buttons: big enough for a thumb, and coloured by what they do ───────*/
.acts{display:flex;flex-wrap:wrap;gap:9px;margin-top:4px}
.acts .btn{flex:1 1 180px;min-height:46px}
.btn.big{min-height:54px;font-size:16px;font-weight:800;margin-top:4px}
.btn.ghost{background:transparent;border:1.5px solid var(--line);color:var(--ink)}
.btn.buy{background:var(--yellow,#F5C542);color:#12232e;border:0;font-weight:800}

/* ── A FULL-WIDTH BUTTON MUST BE ALLOWED TO WRAP ─────────────────────────
 *
 * `.btn` is `white-space:nowrap`, which is right for a small button in a row —
 * it stops "Save" becoming two lines. It is wrong for these, which are as wide
 * as the card and carry a whole bilingual sentence. On a 390px phone the label
 * simply ran off the edge and took the ¥ price with it (Razib's photograph,
 * 9 Sep 2026).
 *
 * Only the full-width ones change. They have the width to wrap into, and
 * vertical padding so two lines do not push out of the box; `min-height` still
 * keeps a one-line button thumb-sized. */
.btn.wide{white-space:normal;line-height:1.35;height:auto;padding:11px 16px;
  display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:2px 10px;text-align:center}
/* The fee is its own word so it can fall onto its own line intact, and lines up
   under itself when several cards are stacked. */
.btn.buy .buy-fee{font-variant-numeric:tabular-nums;font-size:17px}

/* ── one card per grade ──────────────────────────────────────────────────*/
.grade-card{border-left:5px solid var(--line)}
.grade-card.go{border-left-color:var(--ok,#1B8A4B)}
.grade-card.hold{border-left-color:var(--yellow,#F5C542)}
.gc-head{display:flex;align-items:center;justify-content:space-between;gap:10px;
  flex-wrap:wrap;margin-bottom:8px}
.gc-name{font-size:18px;font-weight:800}
.gc-say{font-size:13.5px;line-height:1.65;margin:0 0 12px}
.gc-say.en{color:var(--ink-faint);margin-top:-6px}

/* ── the record ──────────────────────────────────────────────────────────*/
.test-row{display:flex;align-items:center;gap:10px;padding:11px 0;
  border-bottom:1px solid var(--line)}
.test-row:last-child{border-bottom:0}
.test-row > div:first-child{flex:1 1 auto;min-width:0}
.test-row b{display:block;font-size:14.5px}
.test-row .small{display:block;margin-top:1px}

.card-h{font-size:15px;margin:0 0 10px;font-weight:700}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-variant-numeric:tabular-nums;letter-spacing:.02em}
.card.news{border-left:5px solid var(--yellow,#F5C542)}
.card.news .btn-s{margin-top:10px}
.chip.ok{background:var(--okbg);color:var(--ok)}

/* ── paying ──────────────────────────────────────────────────────────────*/
.paybox{margin-top:14px;padding-top:14px;border-top:1px solid var(--line)}
.refline{display:flex;align-items:center;justify-content:space-between;gap:12px;
  background:var(--surface2);border:1px dashed var(--line);border-radius:10px;
  padding:12px 14px;margin:8px 0 12px}
.refline b{font-size:20px;letter-spacing:.06em}
.paymethod{border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin-bottom:10px}
.paymethod b{display:block;margin-bottom:6px;font-size:13.5px}
.paymethod .kv{padding:5px 0;font-size:13.5px}

/* The Pay now button, for a method that carries a link — the card checkout
   and the PayPay link. The SAME shape and weight as the one on /register:
   one action must not have two looks. A full 48px, so a thumb cannot miss
   the last step between a decision and the money.

   🔴 BOTH COLOURS WRITTEN OUT, IN BOTH THEMES. `--navy` is a LIGHT blue
   after dark (#8FB4CC), so `background:var(--navy);color:#fff` would paint
   white on pale blue — the same trap as the site-wide `.en` colour, on the
   one button that takes money. */
.paygo{display:block;width:100%;margin:10px 0 2px;min-height:48px;line-height:48px;
  text-align:center;background:#143143;color:#FFFFFF;text-decoration:none;
  font-weight:700;font-size:15px;border-radius:9px;border:0}
.paygo:hover{background:#1F4A63}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .paygo{background:#2E6183;color:#FFFFFF}
  :root:not([data-theme="light"]) .paygo:hover{background:#3C7FA6}
}
:root[data-theme="dark"] .paygo{background:#2E6183;color:#FFFFFF}
:root[data-theme="dark"] .paygo:hover{background:#3C7FA6}

/* Anything a CANDIDATE presses is at least 44px tall. They are children, often
   on a phone, sometimes in a hurry before a clock starts. The admin panel keeps
   its denser rows on purpose — a teacher at a laptop with a mouse is a different
   person with a different problem. */
.dash .btn-s{min-height:44px;padding:0 16px;font-size:13.5px}
#btnLogout{min-height:40px}

@media (max-width:400px){
  .tiles{gap:7px}
  .tile-n{font-size:25px}
  .hero-name{font-size:22px}
  .rc-band{font-size:32px}
}

/* ── printing IS the PDF button ──────────────────────────────────────────
   The result page is the only thing anyone prints here, so the rule is simply:
   keep the result, drop the furniture. */
@media print{
  .top,.adm-rail,.noprint,button{display:none!important}
  body{background:#fff;color:#000}
  .wrap,.dash{max-width:100%;margin:0;padding:0}
  .card{border:1px solid #ccc;break-inside:avoid;page-break-inside:avoid}
  a[href]:after{content:""}
}

/* ── THE HEADING THAT SAYS WHOSE DOOR THIS IS — 25 Sep 2026 ─────────────────
   Razib: organise the log-in logically. Every way in is now its own card, and
   every card starts with a line naming the person it is for — Google account,
   teacher's ID, never been here. Nobody has to try a door to find out whether
   it is theirs. Used on the log-in screen and the forgotten-password page. */
.cardh{margin:0 0 4px;font-size:15.5px;font-weight:700;color:var(--navy);line-height:1.5}
.cardh .en{display:block;font-size:13px;font-weight:600;color:var(--ink-soft)}

/* The way back in, for a login that now lasts for years. */
.forgot{display:block;text-align:center;margin-top:12px;font-size:13px;
  color:var(--ink-soft);text-decoration:none}
.forgot:hover{text-decoration:underline}

/* The deadline, said plainly on the card rather than buried in an old email. */
.deadline{margin-top:12px;padding:10px 12px;border-radius:10px;
  background:var(--surface2);border:1px solid var(--line);font-size:13px}
.deadline b{display:block;font-size:13.5px}
.deadline span{display:block;color:var(--ink-faint);margin-top:2px}
.deadline.soon{background:#FFF4DC;border-color:#F7E1B5;color:#8A5200}
.deadline.soon span{color:#8A5200;opacity:.8}

/* ── Marking ──────────────────────────────────────────────────────────────
   The screen that was lost when the panel was rebuilt into sections. A teacher
   works down it: what the machine already knows, what the student wrote, the
   photographs of their handwriting, then the boxes to type marks into. */
.mk-h{font-size:11.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-faint);margin:0 0 10px}
.shots{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:8px}
.shots a{position:relative;display:block;width:150px;border:1px solid var(--line);
  border-radius:8px;overflow:hidden;background:var(--surface2)}
.shots img{display:block;width:100%;height:auto}
.shot-late{position:absolute;top:6px;right:6px;background:var(--yellow);color:#12232e;
  font-size:10.5px;font-weight:700;padding:1px 6px;border-radius:99px}
/* A photograph this browser cannot DRAW — a HEIC from an iPhone, say. The frame
   keeps its size and its link and names the format, because an empty frame in a
   marking strip reads as "the child sent nothing". See the note in app.js. */
.shot-raw{display:flex;flex-direction:column;justify-content:center;gap:6px;
  min-height:130px;padding:14px 12px;text-align:center;background:var(--surface2)}
.shot-raw b{font-size:17px;font-weight:800;letter-spacing:.06em;color:var(--ink)}
/* 14px and the page's full-strength ink, not the soft one: this sentence is the
   only thing standing between a teacher and "the child sent nothing". */
.shot-raw small{font-size:14px;line-height:1.5;color:var(--ink)}
.shell:has(#view .adm) .shot-raw b,
.shell:has(#view .adm) .shot-raw small{color:var(--sky-ink,var(--ink))}



/* ══ THE RESULT SHEET ═════════════════════════════════════════════════════
   Printed and handed to a student in class, and read by a parent afterwards.
   So: the school's mark at the top, the verdict where the eye lands first,
   and the small print genuinely small but never hidden. It has to survive a
   black-and-white printer, which is why nothing depends on colour alone. */
.sheet{max-width:660px;margin:26px auto 70px;padding:0 18px}

.sh-head{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;
  padding-bottom:14px;border-bottom:2.5px solid var(--navy2,#123047);flex-wrap:wrap}
.sh-brand{display:flex;align-items:center;gap:12px}
.sh-logo{width:46px;height:46px;border-radius:50%;display:block}
.sh-school{font-size:17px;font-weight:800;letter-spacing:-.01em;line-height:1.2}
.sh-kind{font-size:12px;color:var(--ink-faint);margin-top:2px}
.sh-meta{text-align:right}
.sh-grade{font-size:15px;font-weight:700}
.sh-date{font-size:12px;color:var(--ink-faint);font-variant-numeric:tabular-nums}

.sh-who{padding:16px 0 18px}
.sh-name{font-size:26px;font-weight:800;letter-spacing:-.02em;line-height:1.15}
.sh-id{display:flex;flex-wrap:wrap;gap:14px;margin-top:5px;font-size:12.5px;
  color:var(--ink-faint);font-variant-numeric:tabular-nums}

/* The verdict. Colour AND weight AND a rule, so a mono printer still shows it. */
.sh-verdict{display:flex;justify-content:space-between;align-items:center;gap:16px;
  border:2px solid var(--line);border-left-width:7px;border-radius:12px;
  padding:16px 20px;margin-bottom:22px;flex-wrap:wrap}
.sh-verdict.pass{border-color:var(--ok);background:var(--okbg)}
.sh-verdict.fail{border-color:var(--bad);background:var(--badbg)}
.sh-verdict.mid{border-color:var(--yellow)}
.sh-word{font-size:36px;font-weight:800;line-height:1;letter-spacing:-.02em}
.sh-verdict.pass .sh-word{color:var(--ok)}
.sh-verdict.fail .sh-word{color:var(--bad)}
.sh-note{font-size:12px;color:var(--ink-soft);margin-top:5px}
.sh-nums{text-align:right}
.sh-cse{font-size:27px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}
.sh-outof{font-size:11.5px;color:var(--ink-soft);margin-top:3px}

.sh-block{margin-bottom:20px}
.sh-h{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-faint);padding-bottom:7px;border-bottom:1px solid var(--line);
  margin-bottom:12px}

.sh-skill{margin-bottom:14px}
.sh-skill-top{display:flex;justify-content:space-between;gap:12px;align-items:baseline;
  margin-bottom:5px;flex-wrap:wrap}
.sh-skill-name{font-size:14.5px;font-weight:600}
.sh-skill-val{display:flex;gap:14px;align-items:baseline;font-size:12.5px;
  color:var(--ink-soft);font-variant-numeric:tabular-nums;white-space:nowrap}
.sh-pct{font-weight:700;color:var(--ink);min-width:38px;text-align:right}
.sh-cse-small{color:var(--ink-faint)}
.sh-track{position:relative;height:9px;border-radius:99px;background:var(--surface2);
  border:1px solid var(--line);overflow:hidden}
.sh-fill{height:100%;background:var(--navy2,#123047);border-radius:99px}
.sh-track.weak .sh-fill{background:var(--bad)}
.sh-tick{position:absolute;top:-3px;bottom:-3px;left:60%;width:2px;background:var(--ink-faint)}
.sh-legend{font-size:11.5px;color:var(--ink-faint);margin:10px 0 0}

.sh-line{display:flex;justify-content:space-between;gap:12px;padding:7px 0;
  border-bottom:1px solid var(--line);font-size:14px}
.sh-line:last-child{border-bottom:0}
.sh-line b{font-variant-numeric:tabular-nums;white-space:nowrap}

.sh-focus-row{display:flex;gap:12px;padding:9px 0;border-bottom:1px solid var(--line)}
.sh-focus-row:last-child{border-bottom:0}
.sh-focus-n{flex:0 0 22px;height:22px;border-radius:50%;background:var(--navy2,#123047);
  color:#fff;font-size:12px;font-weight:700;display:flex;align-items:center;
  justify-content:center}
.sh-focus-row b{display:block;font-size:14px}
.sh-focus-row p{margin:2px 0 0;font-size:13px;color:var(--ink-soft);line-height:1.55}

.sh-foot{border-top:2px solid var(--line);margin-top:26px;padding-top:14px}
.sh-foot p{font-size:11px;line-height:1.6;color:var(--ink-faint);margin:0 0 8px}
.sh-foot p:last-child{margin-bottom:0}

@media print{
  /* THE WHOLE PAGE, NOT ONE SCREENFUL.
     `body{height:100vh; overflow:hidden}` is right for an exam that must not
     scroll away under a candidate — and it meant the printer received exactly
     one viewport. Razib's first PDF started halfway down the sheet: no logo, no
     name, no PASS. A fixed-height app has to be released from that height
     before anyone prints it. */
  html, body{height:auto!important;min-height:0!important;overflow:visible!important;
    display:block!important;background:#fff}
  #view, .main, .wrap, .adm, .adm-main{height:auto!important;max-height:none!important;
    overflow:visible!important;display:block!important;padding:0!important}
  .top, .adm-rail, .noprint, button, .acts{display:none!important}

  /* ONE SHEET OF PAPER. The first print ran to two pages with a third of the
     first left blank, because `break-inside:avoid` on whole blocks pushed the
     writing table over rather than letting it flow. A result a school hands to
     a child should be one page: it goes in a folder, not a stapler.
     So: keep breaks off the small units only, and tighten everything. */
  .sheet{max-width:100%;margin:0;padding:0;font-size:12px}
  .sh-verdict, .sh-focus-row, .sh-skill{break-inside:avoid}
  .sh-head, .sh-who, .sh-h{break-after:avoid}

  .sh-who{padding:7px 0 9px}
  .sh-name{font-size:20px}
  .sh-verdict{padding:10px 15px;margin-bottom:11px;border-radius:9px}
  .sh-word{font-size:27px}
  .sh-cse{font-size:21px}
  .sh-block{margin-bottom:10px}
  .sh-h{padding-bottom:5px;margin-bottom:8px;font-size:9.5px}
  .sh-skill{margin-bottom:7px}
  .sh-skill-name{font-size:12px}
  .sh-skill-val{font-size:10.5px;gap:11px}
  .sh-track{height:7px}
  .sh-legend{font-size:9.5px;margin-top:7px}
  .sh-line{padding:4px 0;font-size:11.5px}
  .sh-focus-row{padding:6px 0}
  .sh-focus-row b{font-size:11.5px}
  .sh-focus-row p{font-size:10.5px;line-height:1.45}
  .sh-focus-n{flex-basis:18px;height:18px;font-size:10px}
  .sh-foot{margin-top:10px;padding-top:7px}
  .sh-foot p{font-size:8.5px;line-height:1.5;margin-bottom:5px}

  /* Seven writing criteria in one column is five wasted centimetres. */
  .sh-writing-grid{display:grid;grid-template-columns:1fr 1fr;column-gap:26px}
  /* Colour carries meaning here — the verdict, the bars, the numbered steps —
     so it must survive the printer's default of stripping backgrounds. */
  .sh-verdict, .sh-fill, .sh-track, .sh-focus-n, .sh-logo{
    -webkit-print-color-adjust:exact;print-color-adjust:exact}
  .sh-foot{break-inside:avoid}
  @page{margin:12mm}
}


/* ── the classroom demonstration band ────────────────────────────────────
   Fixed to the top of the window and above everything, including the exam
   shell, because the exam shell is exactly where it matters most: a teacher
   walks the class through a screen built to look like the real S-CBT, and
   nobody in the room should be able to mistake one for the other.
   Red, not yellow: the exam screen is already full of yellow. */
.demoband{
  position:fixed; top:0; left:0; right:0; z-index:9999;
  background:#A8231B; color:#fff; padding:7px 14px;
  display:flex; gap:12px; align-items:baseline; flex-wrap:wrap;
  font-size:13px; line-height:1.5; box-shadow:0 2px 8px rgba(0,0,0,.25);
}
.demoband b{font-size:13px; letter-spacing:.1em; flex:0 0 auto}
.demoband span{opacity:.94}
/* Everything else moves down by the band's height rather than sliding under
   it. Set from JavaScript once the band is measured, because the text wraps to
   two lines on a phone and a hard-coded offset would be wrong there. */
body.is-demo .shell, body.is-demo .exam{padding-top:var(--demoband, 34px)}
/* On a phone the full sentence wrapped to three lines and took an eighth of the
   screen away from the exam it is describing. The short form says the one thing
   that matters; the long form is there for the projector, which is where a class
   is actually reading it. */
.demoband .long{display:none}
@media (min-width:620px){
  .demoband .short{display:none}
  .demoband .long{display:inline}
}


/* ── grade cards that cannot be started ──────────────────────────────────
   A locked grade is quieter than the rest: it is there to show a student the
   ladder, not to invite them. A grade already sat is NOT dimmed — finishing
   your exam is an achievement, and greying it out reads like a refusal. */
.grade-card.locked{opacity:.62}
.grade-card.locked .gc-name{color:var(--ink-soft)}
.grade-card.done{border-left:4px solid var(--ok)}


/* ── who marked what, under a row in the Marking list ────────────────────*/
.whodid{display:flex;flex-wrap:wrap;gap:8px;padding:0 0 10px}
.stamp{
  display:inline-flex;align-items:baseline;gap:7px;font-size:12.5px;
  background:var(--surface2);border:1px solid var(--line);
  border-radius:99px;padding:3px 11px;color:var(--ink-soft);
}
.stamp b{font-weight:700;color:var(--ink-faint);font-size:11px;
  letter-spacing:.06em;text-transform:uppercase}
.stamp .dim{color:var(--ink-faint)}
/* Green only when a person's name is actually on it. A grey stamp is a job
   still to be done, and it should look like one across a long list. */
.stamp.done{background:var(--okbg);border-color:transparent;color:var(--ok)}
.stamp.done b{color:var(--ok);opacity:.75}


/* ── the Principal's "see it as they do" switch ──────────────────────────*/
.viewas{display:flex;gap:4px;margin:0 0 14px;padding:3px;border-radius:8px;
  background:rgba(255,255,255,.07)}
.viewas button{
  flex:1;font:inherit;font-size:10.5px;font-weight:700;letter-spacing:.05em;
  padding:6px 4px;border:0;border-radius:6px;background:transparent;
  color:rgba(255,255,255,.6);cursor:pointer;
}
.viewas button[aria-pressed="true"]{background:var(--yellow);color:#12232e}
.viewas-note{font-size:11.5px;line-height:1.6;color:rgba(255,255,255,.72);
  margin:0 0 14px;padding:8px 10px;border-radius:6px;background:rgba(255,255,255,.07)}


/* ── choosing a time for the interview ───────────────────────────────────*/
.speak-card{border-left:4px solid var(--navy2)}
.gc-say.big{font-size:20px;font-weight:700;color:var(--ink);margin:2px 0 6px}
.slotday{margin:12px 0 0}
.slotday-h{font-size:12px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-faint);margin:0 0 6px}
.slots{display:flex;flex-wrap:wrap;gap:8px}
/* 48px, not the usual 44: this is one tap that fixes an appointment, and the
   buttons sit shoulder to shoulder where a near-miss picks the wrong hour. */
.slotbtn{
  min-height:48px;min-width:84px;padding:0 16px;font:inherit;font-size:16px;font-weight:700;
  border:1.5px solid var(--line);background:var(--surface);color:var(--ink);
  border-radius:10px;cursor:pointer;
}
.slotbtn:hover{border-color:var(--navy2);background:var(--surface2)}
.slotbtn:disabled{opacity:.4}

/* the Mine / Everybody switch on the timetable */
.seg{display:inline-flex;gap:4px;margin:0 0 14px;padding:3px;border-radius:9px;
  background:var(--surface2);border:1px solid var(--line)}
.seg button{font:inherit;font-size:13px;font-weight:600;padding:7px 16px;min-height:38px;
  border:0;border-radius:6px;background:transparent;color:var(--ink-soft);cursor:pointer}
.seg button[aria-pressed="true"]{background:var(--navy);color:#fff}


/* ── "what to do next", on the screen a candidate sees the moment they submit
   A block, not a line. They have just finished an exam and are reaching for the
   lid of the laptop; the one remaining task has to be the loudest thing there. */
.nextup{
  background:var(--navy);color:#fff;border-radius:12px;
  padding:22px 20px;margin:18px 0 14px;text-align:left;
}
.nextup-k{font-size:11.5px;letter-spacing:.14em;font-weight:700;color:var(--yellow);
  margin:0 0 12px}
.nextup h2{font-size:20px;line-height:1.5;margin:0 0 6px;color:#fff}
.nextup h2.en{font-size:17px;margin-top:14px;color:#DCE7EE}
.nextup p{font-size:14.5px;line-height:1.75;margin:0;color:#C6D6E1}
.nextup p.en{color:#A9BECC}
.nextup .btn{margin-top:18px}
.nextup .tiny{font-size:12.5px;margin-top:10px;color:#8FA6B6}

/* ── the host login panel ──────────────────────────────────────────────────
 *
 * Drawn only after somebody presses 🔑, and only for the room they pressed it
 * on. It carries a shared account, so it looks like something to be careful
 * with rather than like the rest of the row.
 */
.hostbox{
  margin:2px 0 12px;padding:12px 14px;border-radius:10px;
  background:var(--surface);border:1px solid var(--line);
  border-left:4px solid var(--warn,#B8860B);
}
.hostbox .small{margin:0 0 8px}
.hostbox .kv{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:6px 0}
.hostbox .kv-k{font-size:12px;color:var(--ink-faint);min-width:130px}
.hostbox .kv-v{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px;
  /* 🔴 `var(--bg)` — A TOKEN THIS PROJECT HAS NEVER DEFINED, found 1 Oct 2026.
     It was here, on `.fc-c` further down, and three times in free.html. An
     unknown custom property is invalid at computed-value time, so the
     declaration is thrown away and the box is simply never painted: these
     panels have been transparent since the day they were written, and they
     looked right only because whatever sat behind them happened to be the
     right colour. `--surface2` is the quiet fill this file actually owns, and
     it has a dark-mode value, which `--bg` never would have had. */
  background:var(--surface2);border:1px solid var(--line);border-radius:6px;
  padding:4px 9px;user-select:all;word-break:break-all;
}
.hostbox .kv-v.secret{letter-spacing:.5px}
.hostbox .warn{color:var(--bad);font-weight:600;margin:10px 0 0}

/* ══ ENGLISH IS NOT THE SMALL PRINT ═══════════════════════════════════════
 *
 * Razib, 1 Sep 2026, looking at the listening screen on a phone:
 *
 *   "still some place only japanese, should be english also. english should be
 *    more visible, now it is difficult to see, font could be big and different
 *    color. in mobile same messege is not in the box."
 *
 * He was right on all three counts, and the third one is the worst. The audio
 * box was `white-space:nowrap; text-overflow:ellipsis`, so on a narrow screen
 * it read "音声を再生してください · Press play to start the…" — and the half that
 * got cut is the half a candidate who does not read Japanese depends on. A
 * design that truncates one language always truncates the same one.
 *
 * Underneath that was a habit: every English line was styled as `small` — 11px,
 * grey, half-opacity — because it was thought of as a gloss on the real
 * instruction. It is not a gloss. For some of these children it IS the
 * instruction, and this is an English exam.
 *
 * So, everywhere in the exam:
 *   · English gets its OWN LINE and never a nowrap ellipsis;
 *   · it is 13.5–15px, never 11px;
 *   · it is a colour of its own, so the eye can find it without reading —
 *     ink-blue on light, pale blue on dark, navy on the yellow play button.
 *
 * A candidate must never have to guess what a button does. Nothing here
 * removes a Japanese word; it stops the English from being decoration.
 */
:root{ --en:#12467a; --en-on-dark:#BDDCFA; --en-on-yellow:#173A5E; }
@media (prefers-color-scheme:dark){ :root{ --en:#8FC4F5; } }

/* the audio box — the one he photographed */
.exstatus{
  white-space:normal;overflow:visible;text-overflow:clip;
  display:flex;flex-direction:column;gap:2px;line-height:1.45;
}
.exstatus b{font-size:14px;font-weight:700}
.exstatus .en{font-size:13.5px;font-weight:600;color:var(--en-on-dark);white-space:normal}

/* 音声は10秒後に自動で始まります / The audio starts by itself in N seconds
 *
 * This is INFORMATION, not an alarm. A child who is about to be spoken to by
 * the computer should be told calmly — a red warning box here would make them
 * think something had gone wrong with their paper. Same shape as the status
 * line above it, one step quieter, and it removes itself when the audio
 * starts. */
.exauto{margin-top:6px;opacity:.92}
.exauto b{font-weight:600}

/* ▶ この部の音声を再生する / Play the audio for this part */
.explay{font-size:15px}
.explay small{
  display:block;font-weight:700;font-size:13.5px;
  color:var(--en-on-yellow);margin-top:3px;letter-spacing:.01em;white-space:normal;
}

/* the English under an official Japanese instruction, in the paper itself */
.exbanden{font-size:14.5px;font-weight:500;color:var(--en);line-height:1.65;margin-top:5px}

/* the part header in the panel — 大問1 / Part 1 */
.exph small{font-weight:600;font-size:13px;color:#20405e}

/* the camera button */
.excam small{font-weight:600;font-size:13.5px;opacity:1;color:var(--en-on-dark)}

/* the grey buttons: Proceed / Finish / Go to the listening test */
.exbtn small{display:block;font-weight:600;font-size:13px;color:var(--en-on-dark);margin-top:2px}
.exfinish small{font-weight:600;font-size:13px}

@media (max-width:900px){
  /* THE TOP BAR STOPPED SHOWING ENGLISH AT ALL on a phone — `display:none` on
     the very line that says which paper this is. Shown, wrapped, smaller. */
  .exbar{flex-wrap:wrap}
  .exbar-l{flex-wrap:wrap;align-items:center}
  .exbar-l small{display:inline;font-size:12.5px;font-weight:600;color:var(--en);flex-basis:100%}
  .exbar-l span{white-space:normal;overflow:visible;text-overflow:clip}
  .exstatus{padding:8px 10px}
}

/* the late-writing card on the candidate's own board */
.late-writing{border-left:4px solid #C0392B}
.late-writing h2{margin-top:0}
.late-writing .sub{font-size:15px;font-weight:600;color:var(--en);margin:2px 0 10px}
.exshot.good{border-color:#1F7A45;border-width:2px}
.exshot.good .st{background:#1F7A45;color:#fff}
.exshot .st{white-space:normal;line-height:1.25;font-size:10.5px}

/* the running "did the photo go?" line in the side panel */
.exdone.good{background:#E2EFDA;color:#1F7A45;border-color:#1F7A45;font-weight:700}
.exdone.bad{background:#FCE4E4;color:#C00000;border-color:#C00000;font-weight:700}
/* the English half of a two-line notice inside an .exinfo box */
.exinfo .en{color:var(--en);font-size:14px;font-weight:600;line-height:1.6}
.exinfo b.en{display:inline-block;margin-top:6px}

/* ⏱ a paper that ran past its own clock. Not an accusation — a fact for the
   teacher, and only shown when it is true, so it still means something. */
.stamp.bad-stamp{background:var(--badbg);color:var(--bad);border-color:var(--bad);font-weight:700}

/* ── the trademark line ──────────────────────────────────────────────────────
   Small, quiet, and on every screen outside the exam itself. It is a legal
   statement rather than a design element, so it is styled to be readable and
   ignorable at the same time: a disclaimer nobody can find is not a disclaimer,
   and one that shouts at a child before a test is not kind. It lives inside
   #shellNaks, so it disappears with that shell when the exam starts — the exam
   screen mirrors the real S-CBT, which has no footer, and the same words are
   said on the briefing card immediately before the clock starts instead. */
.legal-foot{padding:8px 16px 16px;margin-top:16px;border-top:1px solid var(--line)}
.legal-foot .legal{max-width:740px;margin:0 auto;font-size:11.5px;line-height:1.75;
  color:var(--legal-ink)}

/* FOLDED BY DEFAULT — see the comment on the <footer> in index.html.
   The summary is one quiet line; the notice opens under it on a tap. */
.legal-foot summary{cursor:pointer;list-style:none;text-align:center;
  padding:3px 0;color:var(--legal-ink);font-size:11.5px;line-height:1.5}
.legal-foot summary::-webkit-details-marker{display:none}   /* Safari's own ▶ */
.legal-foot summary::before{content:'▸ ';font-size:9.5px;vertical-align:1px}
.legal-foot details[open] summary::before{content:'▾ '}
.legal-foot details[open] summary{margin-bottom:10px}
.legal-foot .legal p{margin:0 0 9px;text-align:left}
.legal-foot .legal p:last-child{margin-bottom:0}
@media (max-width:520px){
  .legal-foot{padding:6px 14px 12px;margin-top:12px}
  /* 🔴 THE 10.5px SHRINK IS GONE — 2 Oct 2026. It was there to keep the fold
   * line to ONE line on a 390px phone, which is Razib's rule and still holds.
   * But the line it was shrinking is 23 characters of Japanese: at 11.5px that
   * is about 265px inside the 362px a 390px phone leaves after the padding, so
   * the shrink was buying nothing and costing the smallest, palest text on the
   * site — 10.5px at 2.76:1 against the ground. The English tail stays hidden
   * here, which is what actually keeps it to one line. */
  .legal-foot .legal p{font-size:11px}
  /* ONE LINE, AND IT IS THE JAPANESE ONE. Razib: *"better if only the Japanese
     text shows"*. With the English tail the summary wrapped to two lines on a
     390px phone, which is half of what we just saved. This is the ONLY place on
     the whole site where English is dropped rather than made bigger, and it is
     allowed exactly because nothing is lost: the full English sits one tap
     below, in the same fold, on every screen size. */
  .legal-foot .en-tail{display:none}
}

/* ── ENGLISH, EVERYWHERE ─────────────────────────────────────────────────────
 * Razib, 2 Sep 2026: *"all English text should be bigger font and more visible
 * and bold .. all place of my website everywhere everywhere"*. He has now asked
 * three times, and each time the fix was applied only to the screen he had
 * photographed. The block above this one did the exam screens; this one does
 * the rest of the site — login, the home board, notices, the result card.
 *
 * WHY IT KEPT COMING BACK. The design put English in `.small`: 12.5px in
 * --ink-faint, a grey chosen to recede. That is the correct treatment for a
 * caption and the wrong one for half the content, and this school has families
 * who read the English line BECAUSE they cannot read the Japanese one. For them
 * the "secondary" line is the only line.
 *
 * Nothing here shrinks, greys or removes a single Japanese word. The English
 * simply stops being decoration. */
.small{font-size:14px;font-weight:600;color:var(--ink-soft);line-height:1.65}
.top .sub{font-size:13px;font-weight:600;opacity:1}
.msg{font-size:15px;line-height:1.6}
.kv span{font-weight:600}

/* the paper itself: the question and its four options are English, and they are
   what the exam is actually made of */
.exqt{font-size:16.5px}
.exopt .l{font-size:16.5px;font-weight:500;line-height:1.55}
.exbanden{font-size:15.5px;font-weight:600}
.exupload .sub{font-size:15px;font-weight:600;color:var(--en)}
.exph small{font-size:13.5px;font-weight:700}
.exbtn small,.exfinish small,.excam small,.explay small{font-size:14px;font-weight:700}
.exstatus .en{font-size:14.5px;font-weight:700}
.exshot .st{font-size:11.5px;font-weight:600}

/* THE TRADEMARK LINE IS THE ONE THING THAT DOES NOT GROW.
   Everything above this makes the English bigger, because half this school's
   families read the English line and nothing else. This is the exception: it is
   a legal notice, not content, and a footer that shouts at a child before a
   test is not kindness. Since 6 Sep it is also folded — so the words are all
   still there, and the screen is the child's again. */
.legal-foot .legal p{font-size:11.5px;font-weight:400}

@media (max-width:520px){
  /* On a phone the English is the line that gets dropped first when space runs
     short. It must not be: wrap it instead of hiding or shrinking it. */
  .exopt .l{font-size:15.5px}
  .exqt{font-size:15.5px}
  .small{font-size:13.5px}
}

/* ── HEADINGS GET A COLOUR OF THEIR OWN ──────────────────────────────────────
 * Razib, 2 Sep 2026, pointing at the paper card: *"head line in different
 * color"*. He asked the same thing of the dashboard a week earlier — *"different
 * section headline not readable very fade color .. make it bigger font and
 * attractive"* — and the fix went in there and nowhere else.
 *
 * The heading was --ink, the same colour as the body text under it, so a card
 * read as one undifferentiated block and the eye had nothing to land on. Navy
 * gives it a job. It is the school's own colour, already on the top bar and the
 * card headers, so nothing new is invented — it is just used where it was
 * missing. The yellow rule beneath repeats the bar's accent and gives the
 * heading a base to sit on. */
h1.t{font-size:24px;font-weight:800;color:var(--navy);letter-spacing:.01em}
h1.t::after{content:"";display:block;width:56px;height:3px;border-radius:2px;
  background:var(--yellow);margin:10px auto 0}
.card h2,.card h3{color:var(--navy);font-weight:700}
.exupload h2{color:var(--navy)}
@media (prefers-color-scheme:dark){
  /* --navy is already the light-on-dark variant in dark mode, so the heading
     stays legible without a second rule; only the underline needs toning. */
  h1.t::after{opacity:.8}
}
@media (max-width:520px){h1.t{font-size:21px}}

/* The mock-test notice on the briefing card. It has to be complete — it is the
   sentence that stops a family believing they sat the official exam — but it is
   a legal notice, not the content of the screen, so it is set smaller than the
   exam facts below it. Complete and quiet, in that order. */
.msg.legal-brief{font-size:12.5px;line-height:1.65}

/* ── THE WATERMARK, AND THE LAZY-COPY BLOCK ──────────────────────────────────
 * Razib, 2 Sep 2026: can screenshots be stopped, to protect his questions?
 * They cannot — no browser can — so this makes a leak TRACEABLE instead, and
 * shuts the easy route of selecting the whole paper and copying it.
 *
 * The watermark must be readable enough to identify an account in a photograph
 * and faint enough that a child sitting an exam never notices it. It takes no
 * clicks (pointer-events:none) and it cannot scroll the page (overflow:hidden),
 * so nothing underneath becomes harder to use. */
/* TWO BOXES, and the reason matters: the outer one is FIXED and clips, the
   inner one is rotated. The first attempt rotated and scaled the outer box
   itself, and a transformed element enlarges the scrollable area of its
   ancestors — every screen gained 365-625px of sideways scroll, which uitest
   caught immediately. Clipping has to happen OUTSIDE the transform. Fixed also
   means the mark always covers what is on the screen, which is exactly what a
   screenshot or a phone photograph captures. */
.exwm{position:fixed;inset:0;overflow:hidden;pointer-events:none;z-index:3;opacity:.055}
.exwm-in{position:absolute;left:-40%;top:-40%;width:180%;height:180%;
  display:flex;flex-wrap:wrap;align-content:flex-start;gap:34px 46px;
  transform:rotate(-24deg);transform-origin:center}
.exwm span{font-size:13px;font-weight:700;color:#0b2b45;white-space:nowrap;letter-spacing:.04em}
@media (prefers-color-scheme:dark){.exwm span{color:#cfe4f5}.exwm{opacity:.075}}

/* Selection is refused on the paper, never in the box the candidate writes in. */
#shellExam{-webkit-user-select:none;-moz-user-select:none;user-select:none}
#shellExam textarea,#shellExam input{-webkit-user-select:text;user-select:text}

/* Printing the live paper gives a clean copy of every question, so the exam
   shell prints as a notice instead. The result sheet still prints normally —
   that is a different screen and the school hands it out on purpose. */
@media print{
  #shellExam .exin,#shellExam .exbar,#shellExam .expanel,#shellExam .exnav{display:none!important}
  #shellExam::after{content:"New Age Kids School — examination paper. Printing is not permitted. / 試験問題の印刷は許可されていません。";
    display:block;padding:40px;font:700 14px/1.7 system-ui;text-align:center}
}

/* The English in the exam top bar had no separation from the Japanese beside
   it — "筆記試験Written test" ran together as one word once the English was
   enlarged. It is a separate clause, so it gets a gap and its own weight, and
   on a narrow screen it drops to its own line rather than being hidden (a bar
   that stops saying which paper this is on a phone was fixed once already). */
.exbar-l small{margin-left:10px;font-weight:600;color:var(--en)}
.exbar-l small::before{content:"· ";opacity:.55}

/* The ownership notice on the briefing card: present and readable, but set below
   the exam facts, because a child about to sit a test needs the times first. */
.legal-own{margin-top:14px;padding-top:12px;border-top:1px solid var(--line);
  font-size:11.5px;font-weight:500;color:var(--ink-faint);line-height:1.7}

/* ── the Settings save bar ──────────────────────────────────────────────────
   Razib, 9 Sep 2026: "my dashboard setting do not have save option". There was
   one; it sat below nineteen groups of fields, which on a phone is four screens
   past the box he had just typed in. A CONTROL THAT MUST BE SCROLLED TO IS A
   CONTROL THAT DOES NOT EXIST.
   Sticky, not fixed: it is the last child of the Settings page, so it rides the
   bottom of the screen while that page is open and leaves with it. Nothing has
   to remember to take it away. */
.savebar{position:sticky;bottom:0;z-index:30;display:flex;gap:12px;align-items:center;
  flex-wrap:wrap;margin:18px -4px 0;padding:12px 14px;border-radius:12px;
  border:1.5px solid var(--line);background:var(--surface);
  box-shadow:0 -6px 18px rgba(0,0,0,.10)}
.savebar .small{color:var(--ink-faint)}
/* Typed and not saved. The colour is not decoration — it is the whole message. */
.savebar.dirty{border-color:var(--navy2);background:var(--paleYellow)}
.savebar.dirty .small{color:var(--navy);font-weight:700}
@media (prefers-color-scheme:dark){.savebar.dirty{background:var(--paleNavy)}}
:root[data-theme=dark] .savebar.dirty{background:var(--paleNavy)}

/* ══ PAPER MANAGEMENT — one paper, opened out ═════════════════════════════
 *
 * Razib, 17 Sep 2026: *"very organished panel for paper management full
 * control to edit"* and *"very colorful panel and user friendly panel of the
 * world"*.
 *
 * The papers list had eight grey buttons on every row and no way to see what a
 * paper was actually made of. Now the row folds open into four coloured cards —
 * questions, recordings, pictures, ready-to-publish — each one a different band
 * colour so the eye finds the right one without reading. The colours are the
 * six already used by the menu bands, so the panel stays one panel. */
/* `align-items:start` so each card is as tall as its own content. Without it a
   grid row stretches every card to the height of the tallest, and the Questions
   card sat with half a screen of white under four lines of text. */
.pm{display:grid;grid-template-columns:repeat(auto-fit,minmax(310px,1fr));gap:14px;
  align-items:start;padding:6px 2px 14px}
.pmc{border:1px solid var(--line);border-top:5px solid var(--pmc,#4FA3E3);
  border-radius:12px;background:var(--surface);padding:14px 15px 15px;min-width:0}
.pmc.q{--pmc:#4FA3E3} .pmc.a{--pmc:#B98CE8} .pmc.p{--pmc:#67D39B} .pmc.k{--pmc:#F2A65A}
.pmc.d{--pmc:#E27C7C}
.pmc h4{margin:0 0 10px;font-size:16px;display:flex;align-items:center;gap:9px;
  letter-spacing:-.01em}
.pmc h4 .e{font-size:22px;line-height:1}
.pmc h4 .n{margin-left:auto;font-size:12px;font-weight:700;color:var(--ink-faint)}
.pml{padding:10px 0;border-top:1px dashed var(--line)}
.pml:first-of-type{border-top:0}
.pml .hd{display:flex;align-items:center;flex-wrap:wrap;gap:8px}
.pml .t{font-weight:800;font-size:15px}
/* The file name is the thing he reads before pressing Replace, so it gets its
   own line and is allowed to break anywhere rather than pushing the card wide. */
.pml .s{display:block;margin-top:5px;font-size:13px;color:var(--ink-soft);
  line-height:1.5;overflow-wrap:anywhere}
.pml .btns{display:flex;gap:6px;flex-wrap:wrap;margin-left:auto}
.pill{display:inline-block;font-size:11.5px;font-weight:800;border-radius:99px;
  padding:2px 9px;line-height:1.7}
.pill.yes{background:var(--okbg);color:var(--ok)}
.pill.no{background:var(--badbg);color:var(--bad)}
.pill.wait{background:var(--paleYellow);color:#7a6a00}
:root[data-theme="dark"] .pill.wait,
:root:not([data-theme="light"]) .pill.wait{color:#E8D66A}
.pmpix{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:10px}
.pmpix figure{margin:0;border:1px solid var(--line);border-radius:9px;overflow:hidden;
  background:var(--surface2)}
.pmpix img{display:block;width:100%;height:74px;object-fit:contain;background:#fff}
.pmpix .none{display:grid;place-items:center;height:74px;font-size:11.5px;
  color:var(--ink-faint);background:var(--surface2)}
.pmpix figcaption{display:flex;align-items:center;gap:4px;padding:5px 6px;font-size:11.5px;
  font-weight:800}
.pmpix figcaption .btns{margin-left:auto;display:flex;gap:4px}
.pmpix .btn-s{min-height:28px;padding:0 8px;font-size:11.5px}
.pmneed{margin:0;padding:0;list-style:none}
.pmneed li{display:flex;gap:8px;align-items:flex-start;font-size:13px;padding:5px 0;
  line-height:1.5}
.pmneed li .m{flex:0 0 auto}
.pm-busy{opacity:.55;pointer-events:none}
@media (max-width:860px){.pm{grid-template-columns:1fr}}

/* ══ THE NOTICE BOARD, AS SEEN ════════════════════════════════════════════
 *
 * Razib, 17 Sep 2026: a board he controls from the panel — "we will be down
 * from 2:23 to 4:44", "this month is free", "buy 2 get 1 free" — shown on the
 * login page, on a candidate's board, before a test starts and on the panel.
 *
 * 🔴 THE SYMBOL CARRIES THE MEANING, NOT THE COLOUR. A child has to tell "the
 * site will be down" from "this month is free" without reading either, and
 * about one boy in twelve cannot separate the red from the green. So each level
 * has its own SHAPE — ℹ️ ⚠️ ⛔ 🎁 — and the colour agrees with it rather than
 * doing the work alone. The left bar is the third signal, for a printed page
 * and for a screen with the colour washed out by sunlight. */
.nb-wrap{display:flex;flex-direction:column;gap:10px;margin:0 0 16px}
.nb{display:flex;gap:12px;align-items:flex-start;padding:13px 15px;
  border:1px solid var(--nbline,var(--line));border-left:6px solid var(--nbbar,var(--navy2));
  border-radius:11px;background:var(--nbbg,var(--surface2))}
.nb-i{font-size:22px;line-height:1.2;flex:0 0 auto}
.nb-t{min-width:0;flex:1 1 auto}
.nb-t b{display:block;font-size:15.5px;line-height:1.35;color:var(--nbink,var(--ink))}
.nb-t p{margin:5px 0 0;font-size:14px;font-weight:600;line-height:1.55;
  color:var(--ink-soft);white-space:pre-wrap}
/* English first and larger, Japanese under it — the rule everywhere on this
   site. The Japanese is drawn only when Razib wrote one. */
.nb-ja{display:block;font-size:13px;font-weight:600;color:var(--ink-soft);
  line-height:1.5;margin-top:2px}
.nb-info { --nbbar:#2E6FA8; --nbbg:#EAF2FA; --nbline:#BBD5EC; --nbink:#0F3A5F }
.nb-warn { --nbbar:#B5761B; --nbbg:#FDF4E3; --nbline:#EFD9AE; --nbink:#6A4308 }
.nb-stop { --nbbar:#C00000; --nbbg:#FCE9E9; --nbline:#F0BFBF; --nbink:#7A0A0A }
.nb-offer{ --nbbar:#1F7A45; --nbbg:#E6F4EA; --nbline:#B9DFC6; --nbink:#0E4B28 }
/* Dark mode is not the light palette dimmed. The bar keeps its hue so the
   meaning survives; the fill goes deep so the page is still a dark page. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .nb-info { --nbbg:#10222F; --nbline:#1E3B52; --nbink:#BDDCFA; --nbbar:#5AA3DE }
  :root:not([data-theme="light"]) .nb-warn { --nbbg:#2A2211; --nbline:#4A3A17; --nbink:#F0D79A; --nbbar:#D79B36 }
  :root:not([data-theme="light"]) .nb-stop { --nbbg:#2E1614; --nbline:#5A2622; --nbink:#F4B3AC; --nbbar:#E2564A }
  :root:not([data-theme="light"]) .nb-offer{ --nbbg:#12291D; --nbline:#20452F; --nbink:#A8E3BF; --nbbar:#3FA96A }
}
:root[data-theme="dark"] .nb-info { --nbbg:#10222F; --nbline:#1E3B52; --nbink:#BDDCFA; --nbbar:#5AA3DE }
:root[data-theme="dark"] .nb-warn { --nbbg:#2A2211; --nbline:#4A3A17; --nbink:#F0D79A; --nbbar:#D79B36 }
:root[data-theme="dark"] .nb-stop { --nbbg:#2E1614; --nbline:#5A2622; --nbink:#F4B3AC; --nbbar:#E2564A }
:root[data-theme="dark"] .nb-offer{ --nbbg:#12291D; --nbline:#20452F; --nbink:#A8E3BF; --nbbar:#3FA96A }

/* ══ THE FREE-TEST COUNT, ON THE PRINCIPAL'S PANEL ═════════════════════════
   Razib, 19 Sep 2026: *"I want to know from my dashboard how many people in
   total sat a free test here."* Three numbers, big enough to read across a
   desk, above the section heading rather than inside a section — it is the
   answer to a question he asks before he has decided what he came in to do. */
.freecount{border:1px solid var(--line);border-radius:12px;background:var(--surface);
           padding:12px 14px;margin:0 0 14px}
.fc-h{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:8px}
.fc-h b{font-size:15px}
.fc-h span{font-size:12px;color:var(--ink-soft)}
.fc-n{display:flex;gap:10px;flex-wrap:wrap}
/* `var(--bg)` again — see the note on `.hostbox .kv-v` above. */
.fc-c{flex:1 1 110px;background:var(--surface2);border:1px solid var(--line);
      border-radius:9px;padding:9px 12px;display:flex;flex-direction:column}
.fc-c b{font-size:26px;line-height:1.1;font-variant-numeric:tabular-nums}
.fc-c span{font-size:11.5px;color:var(--ink-soft);margin-top:2px}
.fc-t{display:flex;gap:7px;flex-wrap:wrap;margin-top:9px}
.fc-t span{font-size:11.5px;color:var(--ink-soft);border:1px solid var(--line);
           border-radius:999px;padding:3px 9px}
.fc-p{font-size:12.5px;color:var(--ink-soft);margin:9px 0 0;line-height:1.7}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE LOG-IN DOOR, IN THE SKY-BLUE SYSTEM — 1 Oct 2026
 *
 * The landing page, /books, /about, /how-to, /register and /free are all one
 * look now: a white bar carrying the crest, a sky-wash hero ending in a rule, a
 * photograph, white rounded cards, an ORANGE primary button and a GREEN one for
 * anything free. The log-in screen is the last public page of the site and it
 * was still the old navy-and-yellow — which means a parent who pressed
 * 「ログイン」 on the front page arrived somewhere that did not look like the
 * school they had just been reading about.
 *
 * ══ 🔴 WHY EVERY RULE HERE IS SCOPED, AND NOT ONE OF THEM IS GLOBAL ════════
 *
 * `index.html` is not one page. It is the whole application: the log-in door,
 * the candidate's dashboard, the result sheet, the staff panel, the marking
 * screens and the Principal's settings all live in the same `.shell`, drawn
 * into the same `#view` by app.js, wearing the same `.top`, `.card`, `.btn`
 * and `.small`. Recolouring any of those would recolour the inside of the
 * school as well as its front door, on a stylesheet that nine separate deploy
 * gates render screens from.
 *
 * So everything below hangs off `.shell:has(#view .forgot)`.
 *
 *   · `a.forgot` is the forgotten-password link. app.js writes
 *     `el('a', 'forgot', 'パスワードをお忘れですか · Forgot your password?')`
 *     exactly ONCE in nine thousand lines, on the log-in screen. It is the only
 *     element unique to that screen that is not also a word a translation could
 *     move.
 *   · `:has()` is already used in this file (`.main:has(.adm)`), so it is not a
 *     new dependency. And its failure mode is the right way round: a browser
 *     that does not support it matches none of these rules and draws the screen
 *     exactly as it was drawn yesterday. A missing photograph, never a broken
 *     log-in.
 *   · The EXAM shell is a sibling of `.shell` and is never inside it, so none
 *     of this can reach the paper a child is sitting.
 *
 * ══ 🔴 AND THE BAND BLUE IS MEASURED, NOT NAMED ═══════════════════════════
 * #1C7FC4 is the blue the owner pointed at. White sits on it at 4.30:1 and
 * every gate on this site fails anything under 4.5, so `--sky-band` is one
 * shade down (#1773B4, white at 5.06:1) and #1C7FC4 survives as `--sky-lite`,
 * for rules, borders and icons — things nobody has to READ.
 *
 * 🔴 THE PRIMARY BUTTON IS ORANGE AND ITS INK IS NEAR-BLACK. White on #EE8A1E
 * is 2.53:1 — unreadable, on the button that says 「ログイン」. #16323F on it is
 * 5.31:1. The pairing is named here so nobody has to rediscover it.
 *
 * These tokens are declared on `:root` rather than inside the scope because a
 * custom property has to be reachable from wherever it is used, and they are
 * only ever USED under the scope. They are copied by value from
 * public/landing.css; if a colour changes there, change it here in the same
 * edit. There is no second palette.
 * ═══════════════════════════════════════════════════════════════════════════ */
:root{
  --sky-band:#1773B4; --sky-deep:#10598F; --sky-lite:#1C7FC4;
  --sky-pale:#D9EDFA; --sky-wash:#E8F4FC; --sky-tint:#E3F2FB;
  --sky-ground:#F2F8FC; --sky-line:#CFE2EF;
  --sky-ink:#16323F; --sky-soft:#4A6373;
  --orange:#EE8A1E; --orange-lift:#F59B38; --on-orange:#16323F;
  --free-green:#0C7A55; --free-green-deep:#0A6349;
  --crest-red:#ED1B24;
}

/* The door commits to ONE look rather than following the system theme — the
   same decision the landing page makes, and for the same reason: a front door
   that changes colour with a visitor's phone settings cannot be art-directed.
   `color-scheme:light` also stops Chrome on Android force-darkening it. Every
   colour below is written out; nothing inherits from the app's theme. */
.shell:has(#view .forgot){color-scheme:light;background:var(--sky-ground);color:var(--sky-ink)}

/* ── the bar ──────────────────────────────────────────────────────────────
   White, with the crest, exactly as every other public page's is. The crest is
   a YELLOW disc and needs a ground to sit against; that is why this bar is
   white and the one strong colour on this screen is the button that matters. */
.shell:has(#view .forgot) .top{
  background:#FFFFFF;color:var(--sky-ink);
  border-bottom:1px solid var(--sky-line);box-shadow:0 4px 14px rgba(16,89,143,.08);
}
.shell:has(#view .forgot) .top .brand{color:var(--sky-deep);font-weight:800}
.shell:has(#view .forgot) .top .sub{color:var(--sky-soft);opacity:1}
.shell:has(#view .forgot) .ghost{background:var(--sky-tint);color:var(--sky-deep);
  border:1px solid var(--sky-line)}
.shell:has(#view .forgot) .ghost:hover{background:#D5EAF8}

/* ── the hero band ────────────────────────────────────────────────────────
   Drawn in index.html as a sibling of #view, so app.js — which clears #view on
   every screen — cannot wipe it and does not have to know it exists. Hidden by
   default: on any screen that is not the log-in screen there is no `.forgot`
   to match, and on a browser with no `:has()` there is no match at all. */
/* ── the way back out of the log-in screen ─────────────────────────────────
   Same mechanism as the hero band below, and for the same reason: hidden by
   the plain rule, revealed only while `#view` holds the forgotten-password
   link, which exists on the log-in screen and nowhere else in this app. So it
   cannot appear over a child's board and cannot appear during a test — and a
   browser with no `:has()` simply never shows it, which is the safe direction.
   Razib, 6 Oct 2026: *"manu other things nai"*. */
/* 🔴 ONLY THE SHOW/HIDE DECISION IS SCOPED TO `:has()`. EVERYTHING ELSE IS NOT,
   AND THAT IS THE WHOLE POINT — 6 Oct 2026, found by measuring, twice.

   First attempt: the width rule that hides the links on a phone was written
   inside the `:has(#view .forgot)` scope. menu.js decides whether to draw the
   ☰ button by asking the browser whether these links are really hidden, and it
   asks as the page loads — which on THIS page is BEFORE app.js has drawn the
   log-in form, so there was no `.forgot` to match, the links were not hidden,
   and the button was never drawn. A 390px phone got the log-in screen with no
   links and no menu — the very fault Razib reported.

   Second attempt: the media rule was moved out of the scope but the rule
   styling the links was left inside it, and `.shell:has(#view .forgot) #loginNav a`
   carries TWO ids (`:has()` takes the specificity of its argument, and
   `#view` is an id) against the media rule's one. The more specific
   `display:block` simply won, at every width, and the links stayed visible with
   no button beside them. The page looked identical to the first fault and had a
   different cause.

   So the styling lives at plain specificity and only `display:none`/`flex` is
   scoped. Nothing inside `#loginNav` can be seen on any other screen whatever
   these rules say, because the container itself is `display:none` there. */
#loginNav{display:none}
.shell:has(#view .forgot) #loginNav{display:flex}
#loginNav{align-items:center;gap:4px;flex-wrap:wrap;justify-content:flex-end;min-width:0}
#loginNav a{
  display:block;text-decoration:none;color:var(--sky-ink);
  padding:7px 10px;border-radius:9px;line-height:1.25;font-size:14px;font-weight:700;
  word-break:auto-phrase;
}
/* 14px and the ink colour, never a grey — the English line is the only line for
   the families this bar is for. */
#loginNav a .en{
  display:block;font-size:14px;font-weight:600;color:var(--sky-ink);opacity:.92;
}
#loginNav a:hover{background:#D5EAF8;color:var(--sky-deep)}
@media (max-width:1000px){
  #loginNav a.only-wide{display:none}
}
/* The bar may wrap; the page may not scroll sideways. Same rule the public
   pages learned on 6 Oct, measured at 768px and 1280px. */
.shell:has(#view .forgot) header.top{flex-wrap:wrap}

#loginArt{display:none}
.shell:has(#view .forgot) #loginArt{
  display:block;max-width:820px;margin:0 auto 18px;
  background:
    radial-gradient(700px 320px at 88% -20%, #DCEFFB 0%, rgba(220,239,251,0) 64%),
    linear-gradient(165deg, var(--sky-wash) 0%, #F7FBFE 76%, #FFFFFF 100%);
  border:1px solid var(--sky-line);border-bottom:5px solid var(--sky-lite);
  border-radius:20px;overflow:hidden;
  box-shadow:0 12px 30px rgba(16,89,143,.14);
}
.shell:has(#view .forgot) .la-in{
  display:grid;grid-template-columns:minmax(0,1fr) 210px;gap:20px;align-items:center;
  padding:20px 22px;
}
.shell:has(#view .forgot) .la-eyebrow{
  display:inline-block;margin:0 0 10px;font-size:12px;font-weight:800;letter-spacing:.12em;
  color:#FFFFFF;background:var(--sky-deep);padding:5px 12px;border-radius:999px;
}
.shell:has(#view .forgot) .la-h{
  margin:0 0 8px;font-size:21px;line-height:1.38;font-weight:800;color:var(--sky-deep);
}
/* The English is its own line, the same near-black as the Japanese and never a
   grey: for the families who read it because they cannot read the Japanese, the
   "secondary" line is the only line. 14.5px, which is the floor this site
   enforces. */
.shell:has(#view .forgot) .la-h span{
  display:block;margin-top:5px;font-size:14.5px;font-weight:700;color:var(--sky-ink);
}
.shell:has(#view .forgot) .la-p{
  margin:0;font-size:14.5px;line-height:1.7;color:var(--sky-soft);font-weight:600;
}
.shell:has(#view .forgot) .la-p .en{
  display:block;margin-top:4px;color:var(--sky-ink);font-size:14.5px;font-weight:600;
}
/* Cropped to fill a fixed box rather than shown whole: the file is 520×391 and
   a photograph stretched past its own width looks like a photograph of a
   photograph. */
.shell:has(#view .forgot) .la-pic{
  width:100%;height:150px;object-fit:cover;border-radius:14px;display:block;
  background:var(--sky-pale);box-shadow:0 8px 20px rgba(16,89,143,.20);
}
@media (max-width:560px){
  .shell:has(#view .forgot) .la-in{grid-template-columns:1fr;gap:14px;padding:16px}
  /* Above the words on a phone, not orphaned under them — a parent deciding
     whether a school is real looks for the picture first. */
  .shell:has(#view .forgot) .la-pic{order:-1;height:132px}
  .shell:has(#view .forgot) .la-h{font-size:19px}
}

/* ── the cards app.js draws ───────────────────────────────────────────────
   Four of them: Google, the ID-and-password box, Apply, and the study books.
   White, rounded, lifted — the same card the rest of the site uses. The hero
   band sits above them now, so the old 6vh of air at the top goes. */
.shell:has(#view .forgot) .main{background:var(--sky-ground)}
.shell:has(#view .forgot) #view .centre{max-width:820px;margin:0 auto;padding:0}
.shell:has(#view .forgot) #view .card{
  background:#FFFFFF;border:1px solid var(--sky-line);border-radius:18px;
  padding:20px 22px;box-shadow:0 8px 22px rgba(16,89,143,.09);
}
/* 🔴 THE RULE UNDER THE HEADING WAS THE CREST'S YELLOW, which is the colour the
   owner turned down — and on this near-white card it measured about 1.1:1, so it
   was a bar of almost nothing. Orange capped in the owner's blue, exactly as
   every section rule on the public pages is. */
.shell:has(#view .forgot) #view h1.t{color:var(--sky-deep);font-size:23px;font-weight:800}
.shell:has(#view .forgot) #view h1.t::after{
  width:58px;height:5px;border-radius:99px;
  background:linear-gradient(90deg,var(--orange) 0 68%, var(--sky-lite) 68% 100%);
}
/* The launcher icon, a little larger and on a ring. It is the FULL lock-up —
   wordmark, slogan and all — so at 74px the words inside it were a smudge; the
   crest in the bar above is the version that reads small, and this one is given
   the room to be itself. It is drawn by app.js and its src cannot be changed
   from here, which is exactly why it is sized rather than swapped. */
.shell:has(#view .forgot) #view .logo{
  width:92px;height:92px;margin-bottom:16px;
  box-shadow:0 0 0 5px #FFFFFF, 0 10px 24px rgba(16,89,143,.20);
}
.shell:has(#view .forgot) #view p.lede{color:var(--sky-soft);font-weight:600}
.shell:has(#view .forgot) #view .cardh{color:var(--sky-deep);font-weight:800}
.shell:has(#view .forgot) #view .cardh .en{color:var(--sky-ink)}
/* 🔴 14.5px, NOT 13. These three lines — the school's name under the brand, and
   「If you have a login from us」 and 「Only after a study book」 under their two
   card headings — were the only English on this screen still under the 14px
   floor the rest of the site has kept since September. They are not captions:
   each one is the sentence that tells a reader whether the card beneath it is
   theirs, and for a family who cannot read the Japanese above it, it is the only
   sentence on that card. Raised inside this scope alone, so the dashboard and
   the staff panel behind the door are untouched. */
.shell:has(#view .forgot) .top .sub{font-size:14.5px}
.shell:has(#view .forgot) #view .cardh .en{font-size:14.5px}
.shell:has(#view .forgot) #view .small{color:var(--sky-soft)}
.shell:has(#view .forgot) #view label{color:var(--sky-ink);font-weight:800}
.shell:has(#view .forgot) #view input{
  background:#FFFFFF;color:var(--sky-ink);border:1.5px solid var(--sky-line);
  border-radius:10px;min-height:48px;
}
.shell:has(#view .forgot) #view input:focus{
  border-color:var(--sky-band);outline:3px solid rgba(23,115,180,.25);outline-offset:0;
}
/* 🔴 ORANGE, WITH NEAR-BLACK ON IT. `.btn.primary` is `--navy2` and white
   site-wide, which is right everywhere inside the school; out here the primary
   action is the orange one, and white on orange is 2.53:1. The two overrides
   below undo the dark-mode `color:#0C141A` rule as well, which would otherwise
   win on a phone set to dark and leave this button near-black on near-black. */
.shell:has(#view .forgot) #view .btn.primary{
  background:var(--orange);border-color:#D2760D;color:var(--on-orange);font-weight:800;
  box-shadow:0 8px 22px rgba(210,118,13,.30);
}
.shell:has(#view .forgot) #view .btn.primary:hover{background:var(--orange-lift)}
/* The secondary — 「参考書を見る」 and 「試験の流れを見る」 — is the blue outline. */
.shell:has(#view .forgot) #view .btn{
  background:#FFFFFF;border:2px solid var(--sky-deep);color:var(--sky-deep);font-weight:800;
}
.shell:has(#view .forgot) #view .btn:hover{background:var(--sky-tint)}
.shell:has(#view .forgot) #view .btn.primary:hover{background:var(--orange-lift)}
.shell:has(#view .forgot) #view .forgot{color:var(--sky-deep);font-weight:700;font-size:14px}
.shell:has(#view .forgot) #view .rule{background:var(--sky-line)}

/* ── the trademark fold at the foot ───────────────────────────────────────
   The deep band blue under an orange rule, as every other public page's footer
   is. NOT ONE WORD CHANGES AND IT IS STILL A FOLD: the summary is the sentence
   that matters — this is not the official examination — and the full notice
   opens under it on a tap, exactly as it has since 6 Sep 2026.

   Every colour is written out. #EAF4FB on this blue is 6.6:1 and #D6E8F5 is
   5.9:1, which is what makes a legal notice readable small rather than
   invisible small. */
.shell:has(#view .forgot) .legal-foot{
  background:var(--sky-deep);border-top:5px solid var(--orange);
  margin-top:26px;padding:18px 16px 24px;
}
.shell:has(#view .forgot) .legal-foot summary{color:#EAF4FB;font-size:12.5px;font-weight:600}
.shell:has(#view .forgot) .legal-foot .legal{color:#D6E8F5}
.shell:has(#view .forgot) .legal-foot .legal p{color:#D6E8F5}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE SCREENS BEHIND THE LOG-IN, IN THE SKY-BLUE SYSTEM — 1 Oct 2026
 *
 * The landing page, /books, /about, /how-to, /register, /free and the log-in
 * door are one look: a white bar carrying the crest, white rounded cards on a
 * blue-white ground, a short orange-capped rule opening each group, ORANGE for
 * the primary action, blue for the secondary and GREEN for anything free.
 *
 * Everything INSIDE the door was still the old navy-and-yellow — so a child
 * logged in and arrived somewhere that did not look like the school they had
 * just been reading about, and the Principal's panel looked like a different
 * product again. This block brings the two screens behind the door into the
 * same system: the candidate's board, and the teacher / Principal panel.
 *
 * ══ 🔴 WHY EVERY RULE IS SCOPED, AND NOT ONE IS GLOBAL ═════════════════════
 *
 * `index.html` is not a page, it is the whole application. The log-in door, the
 * board, the result sheet, the panel, the marking screens and the Principal's
 * Settings are all drawn into the same `#view`, inside the same `.shell`,
 * wearing the same `.card`, `.btn`, `.chip` and `.small`. Recolouring any of
 * those would recolour the result sheet a school PRINTS and hands to a parent,
 * and six public pages share this stylesheet besides.
 *
 * So, exactly as the log-in door above does it, everything here hangs off a
 * `:has()` scope:
 *
 *   `.shell:has(#view .dash)`  the candidate's board. `.dash` is written once
 *                              in app.js (screenStudent) and nowhere else.
 *   `.shell:has(#view .adm)`   the staff panel. `.adm` is written once
 *                              (screenAdmin) and nowhere else.
 *
 * Both are mutually exclusive with the door's `:has(#view .forgot)`, and with
 * each other, because app.js clears `#view` on every screen.
 *
 * 🔴 THE EXAM IS UNTOUCHABLE AND THIS CANNOT REACH IT. `#shellExam` is a
 * SIBLING of `.shell`, never inside it, so no selector below can match a single
 * thing on the paper a child is sitting. That is not a convention, it is the
 * document structure. The exam screen rehearses the real 英検 S-CBT and its
 * plainness is the product; nothing here is allowed near it.
 *
 * 🔴 AND THE SCOPE CARRIES ITS OWN SPECIFICITY ON PURPOSE. `:has()` takes the
 * specificity of its argument, so `.shell:has(#view .dash)` counts an id — which
 * is what lets these rules win against the `:root[data-theme="dark"] .u.w` block
 * further up without `!important` anywhere. A browser with no `:has()` matches
 * none of this and draws yesterday's screen: a plain board, never a broken one.
 *
 * ══ THE COLOURS, AND WHY THESE EXACT ONES ═════════════════════════════════
 * The tokens are the ones declared on `:root` for the door — one palette, copied
 * from public/landing.css by value. Three rules were measured, not assumed:
 *
 *   · #1C7FC4 carries white at 4.30:1, so it is NEVER behind a word. It is the
 *     rule, border and icon blue. The blue that carries words is #1773B4 (5.06).
 *   · White on #EE8A1E is 2.53:1. Orange carries #16323F (5.31:1), never white.
 *   · A pale-blue gloss on #1773B4 is 4.22:1 — under the floor. That is why the
 *     RAIL IS --sky-deep (#10598F) and not the band blue: the Japanese gloss
 *     under each menu item reaches 6.5:1 there, and 4.7:1 at its worst over a
 *     band tint. Every pair in this block was computed before it was typed.
 *
 * 🔴 THE CREST'S YELLOW IS GONE FROM BOTH SCREENS. It was the board's avatar,
 * the rail's current-item marker, its badge, the news card's edge, the Buy
 * button, the "see it as they do" switch and the dirty-Settings bar. The system
 * allows #FEF200 inside the logo image and nowhere else, so each of those now
 * wears the colour that says what it MEANS instead: orange where it is the
 * action, amber where it is a warning, blue where it is information.
 *
 * ══ SEMANTIC COLOUR IS SEPARATE FROM THE ACCENT ═══════════════════════════
 * Orange is the ACTION and nothing else. State is carried by its own family, so
 * that a locked card, a payment waiting, a released result and a failure are
 * four different things at a glance and not four shades of one:
 *
 *   green  #0C7A55   open / ready / passed / free
 *   amber  #B5761B   a warning — writing missing, a deadline close, unsaved
 *   red    #B3221A   their money, a failure, a destructive button
 *   blue   #1773B4   information, a record, a thing already done
 *   slate  #8CA3B4   locked, not yours, nothing to do
 *
 * ══ ONE LOOK, NOT THE SYSTEM THEME ════════════════════════════════════════
 * Both screens commit to the sky palette rather than following the phone's dark
 * setting — the same decision the door and all six public pages make. The sky
 * system is defined in light only; a half-translated dark variant is how this
 * file has shipped unreadable text three times (pale blue `.en` on cream, in the
 * one strip that tells a child their money is waiting). `color-scheme:light`
 * also stops Chrome on Android force-darkening it, and it means a candidate goes
 * board → paper → board without the room changing colour twice. Every colour
 * below is written out; nothing inherits from the app's dark theme.
 *
 * NOTHING IS REMOVED. Every button, link, status line, warning, count, field
 * and state that was on these two screens is still on them. This block changes
 * colour, weight, spacing and grouping — never what is there.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. what the two screens share ─────────────────────────────────────────*/

/* 🔴 THE WHOLE TOKEN SET IS REDECLARED ON THE SCOPE, AND THAT IS THE MOST
 * IMPORTANT TEN LINES IN THIS BLOCK — found by measuring, not by reading.
 *
 * `scripts/` renders these screens on a phone set to DARK. The panel came back
 * with a line of pale mint green on white at 2.02:1, on the Registrations list:
 *
 *     s.style.color = 'var(--ok)';     // app.js, sectionRegistrations
 *
 * — the line that says "sheet sent · login ek-…", which is how the office
 * confirms a payment really went through. `--ok` is #1F7A45 in light and
 * #5FCB8C in dark, and `prefers-color-scheme` is matched against the OS, not
 * against this scope. So `color-scheme:light` made the surface white and left
 * the token dark. A screen that commits to one palette and then reads tokens
 * that do not is committed to nothing.
 *
 * Overriding that one line would have fixed that one line. There are inline
 * `var(--…)` styles, `var(--ok, fallback)` defaults and older rules all over
 * these two screens, and enumerating them is a list that goes stale. Redeclaring
 * the tokens fixes the class: anything inside the board or the panel that
 * resolves one now gets the sky value, whatever the phone is set to, including
 * the rules below that this block never mentions.
 *
 * Copied by value from the door's palette above. Two deliberate re-pointings:
 *   · `--navy`/`--navy2` become the sky blues, because the old navy is not a
 *     colour of this site any more and a dozen old rules still ask for them;
 *   · `--yellow` becomes AMBER, not orange. Anything still reaching for the
 *     crest's yellow is almost always a warning or a marker, and amber is the
 *     safe landing: orange on this site means "press me", and a leftover that
 *     silently became a call to action would be worse than one that is dull. */
.shell:has(#view .dash),
.shell:has(#view .adm){
  color-scheme:light;background:var(--sky-ground);color:var(--sky-ink);

  --ground:#F2F8FC; --surface:#FFFFFF; --surface2:#F4F9FD;
  --ink:#16323F; --ink-soft:#4A6373; --ink-faint:#50657A;
  --line:#CFE2EF;
  --ok:#0C7A55; --okbg:#DFF2E8; --bad:#B3221A; --badbg:#FCE9E7;
  --navy:#10598F; --navy2:#1773B4; --paleNavy:#D9EDFA;
  --yellow:#B5761B; --paleYellow:#FDF3E2;
  --warn:#B5761B; --amber:#8A5A00; --amberbg:#FDF3E2;
  --en:#10598F;
}

/* The bar: white, with the crest, exactly as the door's and every public
   page's. The crest is a yellow disc and needs a ground to sit against — which
   is the whole reason this bar is white and the one strong colour on these
   screens is the button that matters. */
.shell:has(#view .dash) .top,
.shell:has(#view .adm) .top{
  background:#FFFFFF;color:var(--sky-ink);
  border-bottom:1px solid var(--sky-line);box-shadow:0 4px 14px rgba(16,89,143,.08);
}
.shell:has(#view .dash) .top .brand,
.shell:has(#view .adm) .top .brand{color:var(--sky-deep);font-weight:800}
/* 14.5px, the floor this site enforces, and #4A6373 on white is 6.3:1. It is
   the school's name under its own mark, not a caption. */
.shell:has(#view .dash) .top .sub,
.shell:has(#view .adm) .top .sub{color:var(--sky-soft);opacity:1;font-size:14.5px}
/* Log out is a real button again: the bar is white now, so a translucent-white
   fill with white letters on it would have been nothing at all. */
.shell:has(#view .dash) .top .ghost,
.shell:has(#view .adm) .top .ghost{
  background:var(--sky-tint);color:var(--sky-deep);border:1px solid var(--sky-line);
  font-weight:700;
}
.shell:has(#view .dash) .top .ghost:hover,
.shell:has(#view .adm) .top .ghost:hover{background:#D5EAF8}

.shell:has(#view .dash) .main{background:var(--sky-ground)}
.shell:has(#view .adm) .main{background:var(--sky-ground)}

/* Fields. Written out because the scope is committed to light: a phone set to
   dark would otherwise hand these controls the dark theme's pale ink over a
   background the browser picked for itself — the invisible box of 9 Sep 2026. */
.shell:has(#view .dash) input,
.shell:has(#view .dash) select,
.shell:has(#view .dash) textarea,
.shell:has(#view .adm) input,
.shell:has(#view .adm) select,
.shell:has(#view .adm) textarea{
  background:#FFFFFF;color:var(--sky-ink);border:1.5px solid var(--sky-line);
  border-radius:10px;
}
.shell:has(#view .dash) option,
.shell:has(#view .adm) option{background:#FFFFFF;color:var(--sky-ink)}
.shell:has(#view .dash) input:focus,
.shell:has(#view .dash) select:focus,
.shell:has(#view .dash) textarea:focus,
.shell:has(#view .adm) input:focus,
.shell:has(#view .adm) select:focus,
.shell:has(#view .adm) textarea:focus{
  border-color:var(--sky-band);outline:3px solid rgba(23,115,180,.25);outline-offset:0;
}
.shell:has(#view .dash) input::placeholder,
.shell:has(#view .adm) input::placeholder,
.shell:has(#view .dash) textarea::placeholder,
.shell:has(#view .adm) textarea::placeholder{color:#6E8696;opacity:1}
.shell:has(#view .dash) :focus-visible,
.shell:has(#view .adm) :focus-visible{outline:3px solid var(--sky-band);outline-offset:2px}

/* The trademark fold, as every other page of this site wears it: the deep band
   blue under an orange rule. NOT ONE WORD CHANGES AND IT IS STILL A FOLD.
   #EAF4FB on this blue is 6.6:1 — what makes a legal notice readable small
   rather than invisible small. */
.shell:has(#view .dash) .legal-foot,
.shell:has(#view .adm) .legal-foot{
  background:var(--sky-deep);border-top:5px solid var(--orange);
  margin-top:26px;padding:18px 16px 24px;
}
.shell:has(#view .dash) .legal-foot summary,
.shell:has(#view .adm) .legal-foot summary{color:#EAF4FB;font-size:12.5px;font-weight:600}
.shell:has(#view .dash) .legal-foot .legal,
.shell:has(#view .dash) .legal-foot .legal p,
.shell:has(#view .adm) .legal-foot .legal,
.shell:has(#view .adm) .legal-foot .legal p{color:#D6E8F5}

/* ═══════════════════════════════════════════════════════════════════════════
 * 2. THE CANDIDATE'S BOARD
 *
 * It is a TOOL, not a page: scanned and operated by a child who has about four
 * seconds of patience, often on a 360px phone, sometimes with a clock about to
 * start. So the order on the screen is summary → what needs you → detail, and
 * state is carried by SHAPE as well as by words — a stripe down the side of a
 * card, a pill in its heading, a coloured rail on the strip — so that what needs
 * attention can be found without reading a sentence.
 *
 * The structure app.js builds is NOT changed and did not need to be: the tabs,
 * the strip that never hides, the order of the grade cards and the one-card-per-
 * state rule are all decisions already argued out in the comments there, several
 * of them after a child got stuck. Regrouping was done with weight, colour and
 * space, which is what was actually missing.
 * ═══════════════════════════════════════════════════════════════════════════ */

.shell:has(#view .dash) .dash{max-width:600px;margin:20px auto 56px;padding:0 14px}

/* ── a short orange-capped rule opens each group ──────────────────────────
   The section idiom from every public page. It is on the SECTION headings and
   not on every card, because a rule above sixteen cards is wallpaper and a rule
   above three sections is a structure. */
.shell:has(#view .dash) .dash-h{
  font-size:15px;font-weight:800;letter-spacing:.03em;text-transform:uppercase;
  color:var(--sky-deep);margin:28px 0 12px;
}
.shell:has(#view .dash) .dash-h::before{
  content:"";display:block;width:52px;height:5px;border-radius:99px;margin:0 0 9px;
  background:linear-gradient(90deg,var(--orange) 0 68%, var(--sky-lite) 68% 100%);
}
.shell:has(#view .dash) .card-h{
  font-size:16px;font-weight:800;color:var(--sky-deep);margin:0 0 10px;line-height:1.45;
}
.shell:has(#view .dash) .card h2,
.shell:has(#view .dash) .card h3{color:var(--sky-deep)}

/* ── the hero: their name, on their own page ──────────────────────────────
   🔴 --sky-deep AND NOT THE BAND BLUE, and that is a measured decision. The
   pale line under the name ("Member since …") is 4.2:1 on #1773B4 — under the
   floor — and 6.5:1 on #10598F. The hero carries small pale text, so it gets
   the deeper blue; the band blue is used where it only ever carries white. */
.shell:has(#view .dash) .hero{
  /* The flat colour is declared separately and is the LIGHTEST stop, so whatever
     asks this box what colour it is gets the worst case rather than
     `transparent` — see the same note on `.adm-rail` below. */
  background-color:#10598F;
  background-image:
    radial-gradient(520px 260px at 88% -30%, rgba(255,255,255,.06) 0%, rgba(255,255,255,0) 70%),
    linear-gradient(160deg,#10598F 0%,#0B4572 100%);
  border-radius:20px;padding:22px 20px;margin-bottom:16px;color:#FFFFFF;
  border-bottom:5px solid var(--sky-lite);
  box-shadow:0 12px 30px rgba(16,89,143,.22);
}
.shell:has(#view .dash) .hero-who{display:flex;align-items:center;gap:16px}
/* White, not the crest's yellow — the yellow lives in the logo image and
   nowhere else now. Their initials in the school's deep blue on a white disc
   reads as a profile mark rather than a warning dot. */
.shell:has(#view .dash) .avatar{
  width:64px;height:64px;flex:0 0 64px;border-radius:50%;
  background:#FFFFFF;color:var(--sky-deep);
  font-size:25px;font-weight:800;letter-spacing:.01em;
  box-shadow:0 0 0 4px rgba(255,255,255,.18), 0 6px 16px rgba(5,37,62,.30);
}
.shell:has(#view .dash) .hero-name{
  font-size:27px;line-height:1.14;color:#FFFFFF;font-weight:800;word-break:break-word;
}
.shell:has(#view .dash) .hero-meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}
/* Both pills are written out as SOLID colours rather than a translucent white.
   A white film at 16% over this blue puts white text at 4.49:1 — a hundredth
   under the floor, which is exactly the sort of margin that gets lost the next
   time somebody nudges a gradient. A darker inset is 8:1 and cannot drift. */
.shell:has(#view .dash) .hero-grade{
  background:#0C4A7A;border:1px solid rgba(255,255,255,.26);color:#FFFFFF;
  border-radius:99px;padding:4px 12px;font-size:13px;font-weight:800;
}
.shell:has(#view .dash) .hero-id{
  background:#0A3C65;border:1px solid rgba(255,255,255,.14);color:#FFFFFF;
  border-radius:99px;padding:4px 12px;font-size:13px;
}
.shell:has(#view .dash) .hero-since{margin:10px 0 0;font-size:13px;color:#E6F2FB;font-weight:600}

/* ── the strip that never hides ───────────────────────────────────────────
 * This is the one part of the board that COSTS the candidate something if it is
 * missed: writing we never received, money we are waiting for, an interview time
 * that can be chosen once, a free test running down, unread news. app.js lifts
 * it out of the tabs on purpose. Visually it was five pale bars that looked like
 * the rest of the page; it is now one framed block with a severity rail down each
 * row, so "something needs you" is a SHAPE on the screen and not a sentence to
 * read.
 *
 * 🔴 SEVERITY, NOT TAB IDENTITY, PICKS THE COLOUR HERE, and the five are kept
 * apart on purpose: red is their money, amber is marks they are losing, violet
 * is the interview (the Speak tab's own colour, so the row and the tab it opens
 * agree), GREEN IS A PRESENT and blue is information. Four of these must never
 * all be orange — orange on this site means "press me". */
.shell:has(#view .dash) .todo{
  display:grid;gap:8px;margin:14px 0 0;padding:12px;
  background:#FFFFFF;border:1px solid var(--sky-line);border-radius:16px;
  box-shadow:0 8px 22px rgba(16,89,143,.09);
}
.shell:has(#view .dash) .todo::before{
  content:"";display:block;width:52px;height:5px;border-radius:99px;margin:1px 0 3px;
  background:linear-gradient(90deg,var(--orange) 0 68%, var(--sky-lite) 68% 100%);
}
.shell:has(#view .dash) .u{
  display:flex;gap:11px;align-items:center;border-radius:12px;padding:11px 13px;
  border:1px solid var(--uline);border-left:6px solid var(--ubar);
  background:var(--ubg);color:var(--uink);
}
.shell:has(#view .dash) .u .ic{font-size:20px;line-height:1.2;flex:0 0 auto}
.shell:has(#view .dash) .u .tx{min-width:0}
/* The English is the larger of the two and never the fainter — it inherits the
   row's own ink, so it can never fall through to a colour chosen for a
   different background. That exact fault shipped on this strip once. */
.shell:has(#view .dash) .u .tx .en{
  display:block;font-size:15px;font-weight:800;line-height:1.3;color:inherit;
}
.shell:has(#view .dash) .u .tx .ja{
  display:block;font-size:12.5px;line-height:1.3;color:inherit;opacity:.82;
}
.shell:has(#view .dash) .u.w{--ubar:#B5761B;--ubg:#FDF3E2;--uline:#F0DDBB;--uink:#6E460A}
.shell:has(#view .dash) .u.p{--ubar:#B3221A;--ubg:#FDECEA;--uline:#F3CCC8;--uink:#8C1A11}
.shell:has(#view .dash) .u.t{--ubar:#6B40A8;--ubg:#F0E9FB;--uline:#DCCCF3;--uink:#54308A}
.shell:has(#view .dash) .u.f{--ubar:#0C7A55;--ubg:#E2F4EA;--uline:#BFE3D2;--uink:#07543A}
.shell:has(#view .dash) .u.n{--ubar:#1773B4;--ubg:#E4F1FB;--uline:#C4E0F4;--uink:#10598F}
/* The button that opens the tab holding the full card. Blue and not orange:
   it is a way THROUGH to the action, not the action, and five orange buttons in
   a column would tell a child nothing about which one matters.
   44px, because every control a child presses on this board is 44px — the
   40px it had relied on its two lines of text to reach the floor by accident. */
/* 🔴 --sky-deep AND NOT THE BAND BLUE, MEASURED ON THE SECOND LINE. This button
   carries TWO lines — 「ひらく」 over "Open" — and the Japanese one is small and
   pale. #E6F2FB on #1773B4 is 4.45:1, and the inherited `opacity:.8` on that
   line took it to 4.45 as well; on #10598F it is 6.5:1. Same trap as the rail,
   one screen along: the band blue is only safe where white is the only ink.
   `opacity:1` is restated because the rule this one overrides sets .8, and a
   colour faded by an opacity nobody re-measured is how this gets lost again. */
.shell:has(#view .dash) .u .go{
  margin-left:auto;flex:0 0 auto;border:0;border-radius:10px;padding:8px 14px;
  background:var(--sky-deep);color:#FFFFFF;font-family:inherit;cursor:pointer;
  min-height:44px;box-shadow:0 3px 10px rgba(11,69,114,.26);
}
.shell:has(#view .dash) .u .go:hover{background:#0B4572}
.shell:has(#view .dash) .u .go .en{display:block;font-size:14px;font-weight:800;line-height:1.2;color:#FFFFFF;opacity:1}
.shell:has(#view .dash) .u .go .ja{display:block;font-size:11.5px;line-height:1.2;color:#E6F2FB;opacity:1}

/* ── three numbers: the summary, before any detail ────────────────────────
   Blue is the record, green is good news, ORANGE is the one with something
   waiting in it — the same three meanings the rest of the board uses. */
.shell:has(#view .dash) .tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:0 0 14px}
.shell:has(#view .dash) .tile{
  border-radius:14px;padding:14px 10px 13px;text-align:center;
  display:flex;flex-direction:column;border:1px solid var(--tline);
  background:var(--tbg);color:var(--tink);border-top:4px solid var(--tbar);
}
.shell:has(#view .dash) .tile-n{order:0;font-size:31px;font-weight:800;line-height:1;color:inherit}
/* 14px, the floor — and the English is FIRST, above the Japanese gloss, which
   is the order Razib asked for and the order scripts/dashtest.js measures.
   "Ready to sit" is the longest of the three and sets at about 85px inside the
   108px a 360px phone gives each tile, so the floor is affordable here. */
.shell:has(#view .dash) .tile-en{order:1;font-size:14px;font-weight:800;opacity:1;margin-top:6px;line-height:1.2;color:inherit}
.shell:has(#view .dash) .tile-ja{order:2;font-size:11.5px;margin-top:2px;opacity:.82;line-height:1.25;color:inherit}
.shell:has(#view .dash) .tile.blue {--tbg:#E3F2FB;--tink:#10598F;--tline:#C4E0F4;--tbar:#1773B4}
.shell:has(#view .dash) .tile.green{--tbg:#E3F4EC;--tink:#064E35;--tline:#BFE3D2;--tbar:#0C7A55}
.shell:has(#view .dash) .tile.amber{--tbg:#FDEEDB;--tink:#8A4E08;--tline:#F2DBB6;--tbar:#EE8A1E}

/* ── the tabs, as a segmented control ─────────────────────────────────────
   The selected tab is a solid blue block with white on it (5.06:1); the others
   are white cards with their own identity colour on the bottom edge, so the
   five are told apart without reading and the open one is unmistakable. The
   badge is ORANGE with near-black on it — it means "there is something here",
   which is an invitation, not an error, and red was saying the wrong thing. */
/* 🔴 FLEX, NOT `repeat(5,1fr)` — A SIXTH TAB WAS FALLING ONTO ITS OWN ROW.
 *
 * Found while photographing this bar with the study-book shop OPEN. `.btabs` has
 * been a five-column grid since the board was tabbed on 15 Sep, and app.js
 * builds SIX tabs whenever `b.books.on` is true or the candidate already owns a
 * book — Test, Speak, Results, Books, News, Help. The sixth was being pushed
 * onto a second row at one fifth of the width: a single orphan tab sitting under
 * five, which reads as a rendering fault rather than a tab.
 *
 * It was invisible to the deploy gate because scripts/dashtest.js asserts
 * `tabs === 5 || tabs === 0` and its three fixture boards all have the shop
 * shut — so the one state that breaks the layout is the one state never drawn.
 * The shop ships shut, which is why nobody has seen it yet.
 *
 * `flex:1 1 0` with `min-width:0` divides the row by however many tabs there
 * are, so five, six or any future number share it equally and none of them
 * wraps. Nothing is added or removed; the sixth tab simply joins the row. */
.shell:has(#view .dash) .btabs{
  display:flex;flex-wrap:nowrap;gap:7px;margin:18px 0 0;
}
.shell:has(#view .dash) .btab{flex:1 1 0;min-width:0}
.shell:has(#view .dash) .btab{
  background:#FFFFFF;border:1.5px solid var(--sky-line);border-bottom:4px solid var(--bk,#1773B4);
  border-radius:13px;padding:9px 3px 8px;text-align:center;font-family:inherit;
  color:var(--sky-ink);cursor:pointer;min-height:58px;
}
.shell:has(#view .dash) .btab:hover{background:var(--sky-tint)}
.shell:has(#view .dash) .btab .ic{display:block;font-size:17px;line-height:1.1}
/* 14px: the floor this site holds English to, and it fits — six tabs on a 390px
   phone leave 57px each and "Results" sets at about 55px. Below 400px it cannot,
   and that is handled once, in the phone block at the foot of this file, where
   the Japanese gloss is dropped for the same reason. */
.shell:has(#view .dash) .btab .en{display:block;font-size:14px;font-weight:800;line-height:1.2;margin-top:4px;color:inherit}
.shell:has(#view .dash) .btab .ja{display:block;font-size:10.5px;line-height:1.2;color:var(--sky-soft)}
.shell:has(#view .dash) .btab .bdg{
  display:inline-block;min-width:18px;padding:0 5px;border-radius:9px;
  background:var(--orange);color:var(--on-orange);font-size:11px;font-weight:800;
  margin-left:4px;vertical-align:1px;
}
.shell:has(#view .dash) .btab[data-k="test"] {--bk:#1773B4}
.shell:has(#view .dash) .btab[data-k="talk"] {--bk:#6B40A8}
.shell:has(#view .dash) .btab[data-k="res"]  {--bk:#0C7A55}
.shell:has(#view .dash) .btab[data-k="books"]{--bk:#0F6F7A}
/* 🔴 ONE SHADE DOWN FROM THE AMBER USED EVERYWHERE ELSE, because this one gets
   FILLED. The identity colour on the bottom edge of an unselected tab carries
   no words, but the selected tab is a solid block of it with white on top, and
   white on #B5761B is 3.77:1 — measured on the rendered bar. #8A5A00 is 5.93.
   The two read as the same colour at a glance, which is the point: the tab's
   identity does not change when it is pressed, only its legibility. */
.shell:has(#view .dash) .btab[data-k="news"] {--bk:#8A5A00}
.shell:has(#view .dash) .btab[data-k="help"] {--bk:#4A6373}
.shell:has(#view .dash) .btab[aria-selected="true"]{
  background:var(--bk);border-color:var(--bk);color:#FFFFFF;
  box-shadow:0 6px 16px rgba(16,89,143,.22);
}
.shell:has(#view .dash) .btab[aria-selected="true"] .ja{color:#FFFFFF;opacity:.86}
/* On the selected tab the badge sits on a coloured block, so it keeps its own
   fill and its own near-black ink rather than inheriting either. */
.shell:has(#view .dash) .btab[aria-selected="true"] .bdg{background:#FFFFFF;color:var(--sky-deep)}
.shell:has(#view .dash) .bpane{display:none;margin-top:16px}
.shell:has(#view .dash) .bpane.on{display:block}

/* ── white rounded cards on the pale ground ───────────────────────────────*/
.shell:has(#view .dash) .card{
  background:#FFFFFF;border:1px solid var(--sky-line);border-radius:16px;
  padding:18px 20px;margin-bottom:14px;box-shadow:0 8px 22px rgba(16,89,143,.09);
}
/* An empty state is a sentence, not a hole. Pale band, a dashed sky edge so it
   reads as "nothing here yet" rather than "something failed to load", and the
   ink is the page's own — never a grey that recedes, because for some of these
   families the English line IS the line. */
.shell:has(#view .dash) .bquiet{
  color:var(--sky-ink);font-size:14.5px;font-weight:600;line-height:1.75;
  background:#EEF7FD;border:1.5px dashed var(--sky-lite);border-radius:16px;padding:18px 20px;
}
.shell:has(#view .dash) .small{font-size:14px;font-weight:600;color:var(--sky-soft);line-height:1.65}
.shell:has(#view .dash) .kv{border-bottom:1px solid var(--sky-line);font-size:14.5px}
.shell:has(#view .dash) .kv:last-child{border-bottom:0}

/* ── buttons: orange is the action, blue is the way through, green is free ──
   🔴 ORANGE CARRIES #16323F AND NEVER WHITE. White on #EE8A1E is 2.53:1, and
   these are the buttons that say 「試験を始める」 and 「この級を受験する」. */
.shell:has(#view .dash) .btn{
  min-height:var(--tap);padding:0 20px;border-radius:12px;
  background:#FFFFFF;border:2px solid var(--sky-deep);color:var(--sky-deep);
  font-weight:800;font-size:15px;
}
.shell:has(#view .dash) .btn:hover{background:var(--sky-tint)}
.shell:has(#view .dash) .btn.primary,
.shell:has(#view .dash) .btn.buy,
.shell:has(#view .dash) .btn.go{
  background:var(--orange);border:2px solid #D2760D;color:var(--on-orange);
  font-weight:800;box-shadow:0 8px 22px rgba(210,118,13,.28);
}
.shell:has(#view .dash) .btn.primary:hover,
.shell:has(#view .dash) .btn.buy:hover,
.shell:has(#view .dash) .btn.go:hover{background:var(--orange-lift)}
.shell:has(#view .dash) .btn.ghost{
  background:#FFFFFF;border:2px solid var(--sky-line);color:var(--sky-deep);
}
.shell:has(#view .dash) .btn.ghost:hover{background:var(--sky-tint);border-color:var(--sky-deep)}
.shell:has(#view .dash) .btn:disabled{opacity:.45;cursor:not-allowed}
.shell:has(#view .dash) .btn.wide{width:100%;white-space:normal;line-height:1.35;height:auto;
  padding:12px 16px;display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:2px 10px;text-align:center}
.shell:has(#view .dash) .btn.big{min-height:56px;font-size:16.5px}
.shell:has(#view .dash) .btn.buy .buy-fee{font-variant-numeric:tabular-nums;font-size:17.5px;font-weight:800}
.shell:has(#view .dash) .acts{display:flex;flex-wrap:wrap;gap:10px;margin-top:6px}
.shell:has(#view .dash) .acts .btn{flex:1 1 180px;min-height:48px}
/* The small row button. 44px here and dense only on the staff panel — a child
   on a phone is a different person with a different problem. */
.shell:has(#view .dash) .btn-s{
  min-height:44px;padding:0 16px;font-size:14px;font-weight:700;border-radius:10px;
  border:1.5px solid var(--sky-line);background:#FFFFFF;color:var(--sky-deep);
}
.shell:has(#view .dash) .btn-s:hover{border-color:var(--sky-deep);background:var(--sky-tint)}
.shell:has(#view .dash) .btn-s.go{background:var(--sky-band);color:#FFFFFF;border-color:var(--sky-band)}
.shell:has(#view .dash) .btn-s.go:hover{background:var(--sky-deep);border-color:var(--sky-deep)}

/* ── one card per grade, and FOUR STATES THAT MUST NOT LOOK ALIKE ──────────
 * app.js draws a different card for each, and the reasons are written down
 * there: `locked` is the school's decision, `done` is an achievement, `hold` is
 * money already paid, `go` is the one thing they came here to do. Three of them
 * carry no button, and when they were drawn identically a child who had just
 * finished their exam was told nothing existed.
 *
 * So each gets its own stripe AND its own chip, in four different hues:
 *
 *   go     green   open — and the only one with an ORANGE button on it
 *   hold   amber   paid for, paper not ready. A warning colour, because it is
 *                  the school owing them something
 *   done   blue    a record. Finished, not forbidden — and NOT dimmed, because
 *                  greying out a finished exam reads as a refusal
 *   locked slate   not yours. Quiet on purpose: it is there to show the ladder
 *
 * Not one of the four is orange. Orange on this board means "press me", and
 * three of these cards have nothing to press. */
.shell:has(#view .dash) .grade-card{
  border-left:6px solid var(--gline,#8CA3B4);
}
.shell:has(#view .dash) .grade-card.go    {--gline:#0C7A55}
.shell:has(#view .dash) .grade-card.hold  {--gline:#B5761B}
.shell:has(#view .dash) .grade-card.done  {border-left:6px solid #1773B4}
.shell:has(#view .dash) .grade-card.locked{--gline:#8CA3B4;opacity:.72}
.shell:has(#view .dash) .grade-card.locked .gc-name{color:var(--sky-soft)}
/* The one they can sit is lifted off the page — it is the answer to "what am I
   here for?" and it should be the first thing the eye lands on in the pane. */
.shell:has(#view .dash) .grade-card.go{
  box-shadow:0 10px 26px rgba(12,122,85,.16);border-color:#BFE3D2;
}
.shell:has(#view .dash) .gc-head{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  flex-wrap:wrap;margin-bottom:10px;
}
.shell:has(#view .dash) .gc-name{font-size:19px;font-weight:800;color:var(--sky-deep)}
.shell:has(#view .dash) .gc-say{font-size:14px;line-height:1.7;margin:0 0 12px;color:var(--sky-ink)}
/* The English half is a line of its own, the same size, never a grey. */
.shell:has(#view .dash) .gc-say.en{color:var(--sky-soft);margin-top:-6px;font-size:14px}
.shell:has(#view .dash) .gc-say.big{font-size:21px;font-weight:800;color:var(--sky-deep);margin:2px 0 8px}
.shell:has(#view .dash) .gc-say.warn{color:#6E460A;font-weight:700}
.shell:has(#view .dash) .gc-say.ok{color:#07543A;font-weight:700}
.shell:has(#view .dash) .speak-card{border-left:6px solid #6B40A8}
.shell:has(#view .dash) .speak-card .ttl{
  font-weight:800;font-size:13px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--sky-soft);margin:0 0 8px;
}

/* ── the chips: the state, as a pill, in the card's own heading ────────────
   Four meanings, four colours, each one dark ink on its own pale fill so it
   survives sunlight on a phone and a black-and-white printer. */
.shell:has(#view .dash) .chip{
  font-size:12px;font-weight:800;padding:4px 10px;border-radius:99px;white-space:nowrap;
  border:1px solid var(--cline);background:var(--cbg);color:var(--cink);
}
.shell:has(#view .dash) .chip.ok   {--cbg:#DFF2E8;--cink:#07543A;--cline:#BFE3D2}
.shell:has(#view .dash) .chip.done {--cbg:#DEEFFA;--cink:#10598F;--cline:#C4E0F4}
.shell:has(#view .dash) .chip.wait {--cbg:#FCEFD8;--cink:#7A4E07;--cline:#F0DDBB}
.shell:has(#view .dash) .chip.off  {--cbg:#E7EEF3;--cink:#3C5565;--cline:#D2DEE7}
.shell:has(#view .dash) .chip.bad  {--cbg:#FCE9E7;--cink:#99190F;--cline:#F3CCC8}
.shell:has(#view .dash) .chip.live {--cbg:#DFF2E8;--cink:#07543A;--cline:#BFE3D2}

/* ── the last result, as the headline it is ───────────────────────────────
   PASS / NOT YET / BORDERLINE in the largest type on the page, with colour AND
   weight AND a stripe, so none of it depends on colour alone. */
.shell:has(#view .dash) .result-card{border-left:6px solid var(--sky-line);padding-top:16px}
.shell:has(#view .dash) .result-card.pass{border-left-color:#0C7A55;box-shadow:0 10px 26px rgba(12,122,85,.16)}
.shell:has(#view .dash) .result-card.fail{border-left-color:#B3221A}
.shell:has(#view .dash) .result-card.mid {border-left-color:#B5761B}
.shell:has(#view .dash) .rc-label{
  font-size:12px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--sky-soft);
}
.shell:has(#view .dash) .rc-band{font-size:39px;font-weight:800;line-height:1;color:var(--sky-deep)}
.shell:has(#view .dash) .result-card.pass .rc-band{color:#07543A}
.shell:has(#view .dash) .result-card.fail .rc-band{color:#B3221A}
.shell:has(#view .dash) .result-card.mid  .rc-band{color:#8A5A00}
.shell:has(#view .dash) .rc-cse{font-size:23px;font-weight:800;color:var(--sky-deep)}
.shell:has(#view .dash) .rc-sub{font-size:13px;color:var(--sky-soft);font-weight:600}

/* ── the record of past tests ─────────────────────────────────────────────*/
.shell:has(#view .dash) .test-row{border-bottom:1px solid var(--sky-line)}
.shell:has(#view .dash) .test-row:last-child{border-bottom:0}
.shell:has(#view .dash) .test-row b{font-size:15px;color:var(--sky-ink)}
.shell:has(#view .dash) .test-row .small{color:var(--sky-soft)}

/* ── the deadline, said on the card rather than buried in an old email ────
   Quiet while there is time; AMBER — a warning, not an action — once it is two
   days away. A lapsed one is the red `.msg.bad` below, which is right: that one
   has already cost them something. */
.shell:has(#view .dash) .deadline{
  margin-top:12px;padding:11px 13px;border-radius:12px;
  background:#F4F9FD;border:1px solid var(--sky-line);font-size:13.5px;color:var(--sky-ink);
}
.shell:has(#view .dash) .deadline b{display:block;font-size:14px;font-weight:800;color:var(--sky-deep)}
.shell:has(#view .dash) .deadline span{display:block;color:var(--sky-soft);margin-top:3px;font-weight:600}
.shell:has(#view .dash) .deadline.soon{
  background:#FCEFD8;border-color:#F0DDBB;border-left:5px solid #B5761B;color:#6E460A;
}
.shell:has(#view .dash) .deadline.soon b{color:#6E460A}
.shell:has(#view .dash) .deadline.soon span{color:#6E460A;opacity:.92}

/* ── the four messages, each with a left stripe so the level is a shape ───*/
.shell:has(#view .dash) .msg{
  border-radius:12px;padding:13px 15px;margin-bottom:14px;font-size:15px;line-height:1.65;
  border:1px solid var(--mline);border-left:6px solid var(--mbar);
  background:var(--mbg);color:var(--mink);
}
.shell:has(#view .dash) .msg{--mbg:#E8F3FB;--mink:#2B4A5C;--mline:#C4E0F4;--mbar:#1773B4}
.shell:has(#view .dash) .msg.info{--mbg:#E8F3FB;--mink:#2B4A5C;--mline:#C4E0F4;--mbar:#1773B4}
.shell:has(#view .dash) .msg.ok  {--mbg:#DFF2E8;--mink:#07543A;--mline:#BFE3D2;--mbar:#0C7A55}
.shell:has(#view .dash) .msg.warn{--mbg:#FDF3E2;--mink:#6E460A;--mline:#F0DDBB;--mbar:#B5761B}
.shell:has(#view .dash) .msg.err,
.shell:has(#view .dash) .msg.bad {--mbg:#FCE9E7;--mink:#99190F;--mline:#F3CCC8;--mbar:#B3221A}
/* The English half inside a coloured box takes the box's OWN ink. This is the
   trap that has shipped three times on this site: `.en` carrying a colour
   chosen for white, over a cream or a pale red fill, at 1.2:1. */
.shell:has(#view .dash) .msg .en{color:inherit;font-size:14.5px;font-weight:600}
.shell:has(#view .dash) .msg b{color:inherit}
.shell:has(#view .dash) .msg .small{color:inherit;opacity:.92}

/* ── choosing a time for the interview ────────────────────────────────────
   48px and not the usual 44: this is the one tap that fixes an appointment, and
   the buttons sit shoulder to shoulder where a near-miss picks the wrong hour. */
.shell:has(#view .dash) .slotday-h{
  font-size:13px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;
  color:var(--sky-deep);margin:0 0 7px;
}
.shell:has(#view .dash) .slotbtn{
  min-height:48px;min-width:88px;padding:0 16px;font-size:16px;font-weight:800;
  border:2px solid var(--sky-line);background:#FFFFFF;color:var(--sky-ink);border-radius:12px;
}
.shell:has(#view .dash) .slotbtn:hover{border-color:var(--sky-band);background:var(--sky-tint)}
.shell:has(#view .dash) .slotbtn:disabled{opacity:.4}

/* ── paying ───────────────────────────────────────────────────────────────*/
.shell:has(#view .dash) .paybox{margin-top:14px;padding-top:14px;border-top:1px solid var(--sky-line)}
.shell:has(#view .dash) .refline{
  background:var(--sky-tint);border:1.5px dashed var(--sky-lite);border-radius:12px;
  padding:13px 15px;margin:8px 0 12px;
}
.shell:has(#view .dash) .refline b{font-size:21px;letter-spacing:.06em;color:var(--sky-deep)}
.shell:has(#view .dash) .paymethod{
  border:1px solid var(--sky-line);border-radius:12px;padding:13px 15px;margin-bottom:10px;
  background:#FFFFFF;
}
.shell:has(#view .dash) .paymethod b{color:var(--sky-deep);font-size:14.5px}
/* The last step between a decision and the money, so it is ORANGE like every
   other primary action and a full 48px. Both colours written out, in one
   scheme, because this is the button that must not be a near-black smudge on
   a phone set to dark. */
.shell:has(#view .dash) .paygo{
  display:block;width:100%;margin:10px 0 2px;min-height:48px;line-height:48px;
  text-align:center;background:var(--orange);color:var(--on-orange);text-decoration:none;
  font-weight:800;font-size:15.5px;border-radius:12px;border:2px solid #D2760D;
  box-shadow:0 8px 22px rgba(210,118,13,.28);
}
.shell:has(#view .dash) .paygo:hover{background:var(--orange-lift);color:var(--on-orange)}
.shell:has(#view .dash) .mono{color:var(--sky-ink)}

/* ── the study books ──────────────────────────────────────────────────────
   The "your name is in this book" notice keeps its AMBER. It is a warning and
   it is allowed to be seen; it is not an accusation, and red on the card of
   somebody who has just paid reads as "we think you are a thief". Amber is the
   correct semantic colour here, not a leftover from the old palette. */
.shell:has(#view .dash) .bwarn{
  margin:12px 0 0;padding:13px 15px;border-radius:12px;
  background:#FDF3E2;border:1px solid #F0DDBB;border-left:6px solid #B5761B;color:#6E460A;
}
.shell:has(#view .dash) .bwarn b{display:block;font-size:14px;line-height:1.7;margin-bottom:5px;color:inherit}
/* 14px: this is the notice that says a copy of the book can be traced back to
   the person who shared it. It is prose a parent reads, not a caption. */
.shell:has(#view .dash) .bwarn p{margin:0 0 6px;font-size:14px;line-height:1.75;color:inherit}
.shell:has(#view .dash) .bwarn p:last-child{margin-bottom:0}
.shell:has(#view .dash) .bwarn p.en{color:inherit;opacity:.92}

/* ── "we still do not have your writing" ──────────────────────────────────
   RED, and it should be: this is the one card on the board that is actively
   costing them marks. */
.shell:has(#view .dash) .late-writing{border-left:6px solid #B3221A}
.shell:has(#view .dash) .late-writing h2{margin-top:0;color:#8C1A11;font-size:19px;font-weight:800}
.shell:has(#view .dash) .late-writing .sub{font-size:15px;font-weight:700;color:var(--sky-soft);margin:2px 0 10px}

/* The news card is INFORMATION, so it is blue. It was the crest's yellow. */
.shell:has(#view .dash) .card.news{border-left:6px solid #1773B4}

/* ═══════════════════════════════════════════════════════════════════════════
 * 3. THE TEACHER / PRINCIPAL PANEL
 *
 * A panel is a different animal from a board: a teacher at a laptop with a
 * mouse, working down a list of sixteen papers, all day. So the rows stay dense
 * on purpose — that is written down further up and it is right. What changes is
 * the WORLD it sits in: a deep sky rail instead of a navy one, a blue-white
 * ground instead of a grey one, orange where the old yellow was, and one
 * semantic family for state so that a thing waiting, a thing done and a thing
 * that went wrong are three shapes and not three greys.
 *
 * 🔴 THE PANEL IS ENGLISH-FIRST AND THAT IS DELIBERATE — app.js, line 27:
 * Razib was explicit that he and the teachers do not read Japanese, so
 * everything only staff see is English, with the Japanese kept as the smaller
 * gloss under each rail item. Nothing here changes that, and nothing here
 * changes the floor it is held to: the rail's English is 17.5px and its
 * Japanese gloss 12.5px, both measured by scripts/panetest.js.
 *
 * 🔴 AND THE RAIL IS --sky-deep, NOT THE BAND BLUE. #1773B4 would put the
 * Japanese gloss at 4.22:1 and the band headings lower still. On #10598F the
 * gloss is 6.5:1 on the plain rail and 4.7:1 at its very worst, sitting over a
 * band tint on the current item. The band blue is reserved for places that only
 * ever carry white.
 * ═══════════════════════════════════════════════════════════════════════════ */

.shell:has(#view .adm) .adm{min-height:calc(100dvh - 64px)}

/* ── the rail ─────────────────────────────────────────────────────────────
   A gentle top-to-bottom gradient rather than one flat blue — the cheapest
   thing that stops a tall column reading as a wall. Both ends are at least as
   dark as --sky-deep, so no text on it can drift under the floor wherever it
   happens to sit. */
/* 🔴 A FLAT COLOUR UNDER THE GRADIENT, AND NOT ONLY FOR SAFETY. A `background`
   shorthand carrying only a gradient leaves `background-color` TRANSPARENT, so
   anything that asks this element what colour it is — a contrast checker, a
   printer stripping images, a browser that cannot parse the gradient — is told
   "nothing" and reads the page ground behind it instead. White letters then look
   like white-on-white to every one of them. The flat colour is the lightest stop
   of the gradient, so the measured answer is also the worst case. */
.shell:has(#view .adm) .adm-rail{
  background-color:#10598F;
  background-image:linear-gradient(180deg,#10598F 0%,#0E5184 45%,#0B4572 100%);
  border-right:1px solid rgba(255,255,255,.10);padding:16px 0;
}
.shell:has(#view .adm) .adm-who{
  padding:0 20px 15px;color:#C9E1F4;font-size:13.5px;letter-spacing:.12em;font-weight:800;
}
.shell:has(#view .adm) .adm-nav button{
  color:#FFFFFF;border-left:3px solid transparent;
}
.shell:has(#view .adm) .adm-nav button:hover{background:rgba(255,255,255,.10);color:#FFFFFF}
.shell:has(#view .adm) .adm-nav .tx .en{color:#FFFFFF}
/* 🔴 #E6F2FB AND NOT A DIMMER BLUE. The gloss has to clear 4.5:1 at its worst
   point, which is over the lightest band tint on the current item — #DCEDF8
   measured 4.43 there, a hundredth and a half under, and that is precisely the
   kind of miss this file keeps recording. */
.shell:has(#view .adm) .adm-nav .tx .ja{color:#E6F2FB;opacity:.9}
/* The current item is a block of its band's colour with an ORANGE marker down
   its edge. The marker was the crest's yellow, which the system no longer
   allows outside the logo; orange is the colour this site uses for "here" and
   "press me", and it is what the public pages' section rules are capped with. */
.shell:has(#view .adm) .adm-nav button[aria-current="true"]{
  background:var(--tint,rgba(255,255,255,.12));color:#FFFFFF;font-weight:800;
  border-left-color:var(--orange);
}
.shell:has(#view .adm) .adm-nav button[aria-current="true"] .tx .ja{color:#FFFFFF;opacity:.92}
/* The badge: orange carrying near-black, which is 5.31:1. White on orange is
   2.53:1 and this is a number a teacher reads across a desk. */
.shell:has(#view .adm) .adm-nav .count,
.shell:has(#view .adm) .nav-sheet .count{
  background:var(--orange);color:var(--on-orange);font-size:13px;font-weight:800;
  min-width:26px;height:24px;border-radius:99px;padding:0 8px;
}
/* A zero is not news. Solid, never a translucent film, so it cannot land on an
   unexpected composite and disappear. */
.shell:has(#view .adm) .adm-nav .count.zero,
.shell:has(#view .adm) .nav-sheet .count.zero{background:#0A3C65;color:#C9E1F4}

/* The band heading: the WORDS are pale and readable, the band's colour runs
   across the hairline beside them and fills the dot and the tiles below. That
   split is the point — colour for identity, contrast for reading. The six
   heading colours were 3.4–4.3:1 on this lighter rail, which is why the text no
   longer carries the hue and the rule does. */
.shell:has(#view .adm) .adm-nav .gh,
.shell:has(#view .adm) .nav-sheet .gh{color:#E6F2FB}
.shell:has(#view .adm) .adm-nav .gh::after,
.shell:has(#view .adm) .nav-sheet .gh::after{background:var(--band);opacity:.85}
.shell:has(#view .adm) .adm-nav .gh .dot,
.shell:has(#view .adm) .nav-sheet .gh .dot{background:var(--band)}

/* Six bands, retuned for a blue rail rather than a navy one — the old set put
   its blue and its steel almost on top of the new ground. Still six distinct
   hues, which is what scripts/panetest.js measures (it fails if four tiles ever
   collapse to one colour, i.e. if these custom properties stop resolving). */
.shell:has(#view .adm) .g1{--band:#8ED0F5; --tint:rgba(142,208,245,.22); --edge:rgba(142,208,245,.46)}
.shell:has(#view .adm) .g2{--band:#CBA8F2; --tint:rgba(203,168,242,.22); --edge:rgba(203,168,242,.46)}
.shell:has(#view .adm) .g3{--band:#6FD9A6; --tint:rgba(111,217,166,.22); --edge:rgba(111,217,166,.46)}
.shell:has(#view .adm) .g4{--band:#F7B263; --tint:rgba(247,178,99,.22);  --edge:rgba(247,178,99,.46)}
.shell:has(#view .adm) .g5{--band:#BFD6E4; --tint:rgba(191,214,228,.24); --edge:rgba(191,214,228,.48)}
.shell:has(#view .adm) .g6{--band:#F09A94; --tint:rgba(240,154,148,.22); --edge:rgba(240,154,148,.46)}

/* ── the Principal's "see it as they do" switch ───────────────────────────
   Pressed is ORANGE with near-black on it — it was the crest's yellow. The note
   under it is a SOLID pale blue, not white at 72%: that film measured 4.09:1 on
   this rail, and an explanation nobody can read is not an explanation. */
.shell:has(#view .adm) .viewas{background:rgba(255,255,255,.10)}
/* 13px, up from 10.5. Three words share a 316px rail with room to spare, and
   this switch is how the Principal answers "what does my teacher see?" while
   somebody is on the telephone — it should not be the smallest type on the
   screen. The panel is English-only by Razib's own instruction (app.js line 27),
   so this is the only label here, not the second half of a pair. */
.shell:has(#view .adm) .viewas button{color:#E6F2FB;font-size:13px;letter-spacing:.03em}
.shell:has(#view .adm) .viewas button[aria-pressed="true"]{background:var(--orange);color:var(--on-orange)}
.shell:has(#view .adm) .viewas-note{
  font-size:12.5px;line-height:1.6;color:#E6F2FB;background:#0A3C65;
  border:1px solid rgba(255,255,255,.12);
}
.shell:has(#view .adm) .nav-cur{background:rgba(255,255,255,.12);color:#FFFFFF}
.shell:has(#view .adm) .nav-cur .tx .ja{color:#E6F2FB}
.shell:has(#view .adm) .nav-sheet{background:#0B4572}
.shell:has(#view .adm) .nav-sheet button{color:#FFFFFF;border-left:3px solid transparent}
.shell:has(#view .adm) .nav-sheet .tx .en{color:#FFFFFF}
.shell:has(#view .adm) .nav-sheet .tx .ja{color:#E6F2FB}
.shell:has(#view .adm) .nav-sheet button[aria-current="true"]{
  background:var(--tint,rgba(255,255,255,.12));color:#FFFFFF;border-left-color:var(--orange);
}

/* ── the work itself ──────────────────────────────────────────────────────*/
.shell:has(#view .adm) .adm-main{background:var(--sky-ground);padding:22px 26px 60px}
/* The section heading gets the same short orange-capped rule that opens every
   group on every public page. It is the one thing that says "this panel and
   that website are the same school". */
.shell:has(#view .adm) .adm-h{
  margin:0 0 10px;font-size:23px;font-weight:800;color:var(--sky-deep);letter-spacing:-.01em;
}
.shell:has(#view .adm) .adm-h::before{
  content:"";display:block;width:52px;height:5px;border-radius:99px;margin:0 0 10px;
  background:linear-gradient(90deg,var(--orange) 0 68%, var(--sky-lite) 68% 100%);
}
.shell:has(#view .adm) .adm-sub{margin:0 0 20px;color:var(--sky-soft);font-size:14.5px;font-weight:600}
.shell:has(#view .adm) .card{
  background:#FFFFFF;border:1px solid var(--sky-line);border-radius:14px;
  box-shadow:0 6px 18px rgba(16,89,143,.08);
}
.shell:has(#view .adm) .card-h,
.shell:has(#view .adm) .card h2,
.shell:has(#view .adm) .card h3{color:var(--sky-deep);font-weight:800}
.shell:has(#view .adm) .small{font-size:14px;font-weight:600;color:var(--sky-soft);line-height:1.65}
.shell:has(#view .adm) .mono{color:var(--sky-ink)}
.shell:has(#view .adm) label{color:var(--sky-ink);font-weight:800;font-size:13.5px}

/* the numbers on the overview */
.shell:has(#view .adm) .stats{gap:12px;margin:0 0 24px}
.shell:has(#view .adm) .stat{
  background:#FFFFFF;border:1px solid var(--sky-line);border-top:4px solid var(--sky-lite);
  border-radius:13px;padding:15px 16px;box-shadow:0 6px 18px rgba(16,89,143,.08);
}
.shell:has(#view .adm) .stat b{font-size:28px;color:var(--sky-deep);letter-spacing:-.02em}
.shell:has(#view .adm) .stat span{font-size:13px;color:var(--sky-soft);font-weight:600;margin-top:4px}
.shell:has(#view .adm) .stat.go{border-top-color:#0C7A55}
.shell:has(#view .adm) .stat.go b{color:#07543A}
/* A count that needs somebody is AMBER, which is a warning — it is not the
   action colour, and a panel where every number shouted would say nothing. */
.shell:has(#view .adm) .stat.warn{border-top-color:#B5761B}
.shell:has(#view .adm) .stat.warn b{color:#8A5A00}

/* rows: a list you can scan is the whole point of a panel */
.shell:has(#view .adm) .tbl{
  background:#FFFFFF;border:1px solid var(--sky-line);border-radius:14px;overflow:hidden;
  box-shadow:0 6px 18px rgba(16,89,143,.08);margin-bottom:16px;
}
.shell:has(#view .adm) .tbl-head{
  background:#E8F3FB;border-bottom:1px solid var(--sky-line);
  font-size:12px;font-weight:800;color:var(--sky-deep);letter-spacing:.06em;
}
.shell:has(#view .adm) .row{border-bottom:1px solid var(--sky-line)}
.shell:has(#view .adm) .row:last-child{border-bottom:0}
.shell:has(#view .adm) .row-line:hover{background:var(--sky-tint)}
.shell:has(#view .adm) .row-main b{font-size:15px;color:var(--sky-ink)}
.shell:has(#view .adm) .row-main span:not(.chip){font-size:13px;color:var(--sky-soft);font-weight:600}
.shell:has(#view .adm) .row-side{font-size:13px;color:var(--sky-soft);font-weight:600}
.shell:has(#view .adm) .row-open{border-top:1px dashed var(--sky-line);background:#F8FCFE}
.shell:has(#view .adm) .empty{padding:28px 16px;color:var(--sky-soft);font-size:14.5px;font-weight:600}

/* ── the panel's buttons ──────────────────────────────────────────────────
   Blue outline for the dozen ordinary actions, a solid band blue for the one
   that goes somewhere, ORANGE only for a genuine primary, and RED kept for the
   one that destroys a record. Four weights, and they do not blur into each
   other the way a wall of grey buttons does. */
.shell:has(#view .adm) .btn-s{
  min-height:34px;padding:0 13px;font-size:13.5px;font-weight:700;border-radius:9px;
  border:1.5px solid var(--sky-line);background:#FFFFFF;color:var(--sky-deep);
  text-decoration:none;
}
.shell:has(#view .adm) .btn-s:hover{border-color:var(--sky-deep);background:var(--sky-tint)}
.shell:has(#view .adm) .btn-s.go{background:var(--sky-band);color:#FFFFFF;border-color:var(--sky-band)}
.shell:has(#view .adm) .btn-s.go:hover{background:var(--sky-deep);border-color:var(--sky-deep)}
/* 🔴 A DELETE BUTTON MUST NOT LOOK LIKE EVERY OTHER BUTTON — the note from
   16 Sep 2026 stands. Cancel keeps the row and its history; Delete destroys the
   only record that money arrived. */
.shell:has(#view .adm) .btn-s.danger{background:#FCE9E7;color:#99190F;border-color:#B3221A}
.shell:has(#view .adm) .btn-s.danger:hover{background:#B3221A;color:#FFFFFF;border-color:#B3221A}
.shell:has(#view .adm) .btn{
  min-height:var(--tap);border-radius:11px;background:#FFFFFF;
  border:2px solid var(--sky-deep);color:var(--sky-deep);font-weight:800;
}
.shell:has(#view .adm) .btn:hover{background:var(--sky-tint)}
.shell:has(#view .adm) .btn.primary{
  background:var(--orange);border:2px solid #D2760D;color:var(--on-orange);
  box-shadow:0 6px 18px rgba(210,118,13,.26);
}
.shell:has(#view .adm) .btn.primary:hover{background:var(--orange-lift)}
.shell:has(#view .adm) .btn.ghost{background:#FFFFFF;border:2px solid var(--sky-line);color:var(--sky-deep)}
.shell:has(#view .adm) .btn:disabled{opacity:.45}
.shell:has(#view .adm) .btn.wide{width:100%}

/* state, as one family, across every section of the panel */
.shell:has(#view .adm) .chip{
  font-size:12px;font-weight:800;padding:3px 9px;border-radius:99px;white-space:nowrap;
  border:1px solid var(--cline);background:var(--cbg);color:var(--cink);
}
.shell:has(#view .adm) .chip.ok   {--cbg:#DFF2E8;--cink:#07543A;--cline:#BFE3D2}
.shell:has(#view .adm) .chip.live {--cbg:#DFF2E8;--cink:#07543A;--cline:#BFE3D2}
.shell:has(#view .adm) .chip.done {--cbg:#DFF2E8;--cink:#07543A;--cline:#BFE3D2}
.shell:has(#view .adm) .chip.wait {--cbg:#FCEFD8;--cink:#7A4E07;--cline:#F0DDBB}
.shell:has(#view .adm) .chip.off  {--cbg:#E7EEF3;--cink:#3C5565;--cline:#D2DEE7}
.shell:has(#view .adm) .chip.bad  {--cbg:#FCE9E7;--cink:#99190F;--cline:#F3CCC8}
.shell:has(#view .adm) .pill{font-size:12px;font-weight:800;border-radius:99px;padding:3px 10px}
.shell:has(#view .adm) .pill.yes {background:#DFF2E8;color:#07543A}
.shell:has(#view .adm) .pill.no  {background:#FCE9E7;color:#99190F}
.shell:has(#view .adm) .pill.wait{background:#FCEFD8;color:#7A4E07}

/* who marked what. Green only when a person's name is really on it — a grey
   stamp is a job still to be done, and it should look like one down a long list. */
.shell:has(#view .adm) .stamp{
  background:#EFF5FA;border:1px solid var(--sky-line);color:var(--sky-soft);font-size:13px;
}
.shell:has(#view .adm) .stamp b{color:var(--sky-deep);font-size:11.5px}
.shell:has(#view .adm) .stamp.done{background:#DFF2E8;border-color:#BFE3D2;color:#07543A}
.shell:has(#view .adm) .stamp.done b{color:#07543A;opacity:.8}
/* ⏱ a paper that ran past its own clock. A fact for the teacher, not an
   accusation, and only drawn when it is true — so it still means something. */
.shell:has(#view .adm) .stamp.bad-stamp{background:#FCE9E7;color:#99190F;border-color:#B3221A}

.shell:has(#view .adm) .seg{background:#FFFFFF;border:1.5px solid var(--sky-line);border-radius:11px}
.shell:has(#view .adm) .seg button{color:var(--sky-deep);font-weight:700;border-radius:8px}
.shell:has(#view .adm) .seg button[aria-pressed="true"]{background:var(--sky-band);color:#FFFFFF}

/* ── the Settings save bar ────────────────────────────────────────────────
   Typed and not saved is a WARNING, so it is amber. It was --paleYellow, which
   the system no longer allows, and amber is what the state actually means. */
.shell:has(#view .adm) .savebar{
  border:1.5px solid var(--sky-line);background:#FFFFFF;border-radius:14px;
  box-shadow:0 -6px 18px rgba(16,89,143,.14);
}
.shell:has(#view .adm) .savebar .small{color:var(--sky-soft)}
.shell:has(#view .adm) .savebar.dirty{background:#FDF3E2;border-color:#B5761B}
.shell:has(#view .adm) .savebar.dirty .small{color:#6E460A;font-weight:800}
/* 🔴 SOMETHING WAS TYPED AND REFUSED — 1 Oct 2026. Until today a value this
   screen could not parse was dropped in silence and the bar said 「Nothing was
   different」. It now says which box and why, and it has to be readable while
   it says it: 15px, its own ink, and a red edge, because this is the one
   message on this page that must not look like a routine confirmation.
   #8A1C1C on #FBEAEA is 7.98:1; after dark it uses the theme's own red. */
.shell:has(#view .adm) .savebar.refused{background:#FBEAEA;border-color:#C00000}
.shell:has(#view .adm) .savebar.refused .small{color:#8A1C1C;font-weight:700;font-size:15px}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .shell:has(#view .adm) .savebar.refused{
    background:var(--badbg);border-color:var(--bad);
  }
  :root:not([data-theme="light"]) .shell:has(#view .adm) .savebar.refused .small{color:var(--bad)}
}
:root[data-theme="dark"] .shell:has(#view .adm) .savebar.refused{
  background:var(--badbg);border-color:var(--bad);
}
:root[data-theme="dark"] .shell:has(#view .adm) .savebar.refused .small{color:var(--bad)}

/* ── paper management: four cards, one per thing a paper is made of ───────
   The six band colours again, as a top rule, so the panel stays one panel. */
.shell:has(#view .adm) .pmc{
  border:1px solid var(--sky-line);border-top:5px solid var(--pmc,#1773B4);
  border-radius:13px;background:#FFFFFF;box-shadow:0 6px 18px rgba(16,89,143,.08);
}
.shell:has(#view .adm) .pmc.q{--pmc:#1773B4} .shell:has(#view .adm) .pmc.a{--pmc:#8457C4}
.shell:has(#view .adm) .pmc.p{--pmc:#0C7A55} .shell:has(#view .adm) .pmc.k{--pmc:#EE8A1E}
.shell:has(#view .adm) .pmc.d{--pmc:#B3221A}
.shell:has(#view .adm) .pmc h4{color:var(--sky-deep);font-size:16.5px;font-weight:800}
.shell:has(#view .adm) .pmc h4 .n{color:var(--sky-soft);font-size:12.5px}
.shell:has(#view .adm) .pml{border-top:1px dashed var(--sky-line)}
.shell:has(#view .adm) .pml .t{color:var(--sky-ink);font-size:15px}
.shell:has(#view .adm) .pml .s{color:var(--sky-soft);font-size:13px}
.shell:has(#view .adm) .pmpix figure{border:1px solid var(--sky-line);border-radius:10px;background:var(--sky-tint)}
.shell:has(#view .adm) .pmpix .none{color:var(--sky-soft);background:var(--sky-tint);font-size:12px}
.shell:has(#view .adm) .pmpix figcaption{color:var(--sky-deep)}
.shell:has(#view .adm) .pmpix .btn-s{min-height:30px;padding:0 9px;font-size:12px}
.shell:has(#view .adm) .pmneed li{font-size:13.5px;color:var(--sky-ink)}

/* ── the free-test count, and the host login box ─────────────────────────*/
.shell:has(#view .adm) .freecount{
  border:1px solid var(--sky-line);border-radius:14px;background:#FFFFFF;
  box-shadow:0 6px 18px rgba(16,89,143,.08);padding:14px 16px;
}
.shell:has(#view .adm) .fc-h b{font-size:16px;color:var(--sky-deep);font-weight:800}
.shell:has(#view .adm) .fc-h span{font-size:13px;color:var(--sky-soft);font-weight:600}
.shell:has(#view .adm) .fc-c{background:var(--sky-tint);border:1px solid var(--sky-line);border-radius:11px}
.shell:has(#view .adm) .fc-c b{color:var(--sky-deep);font-size:27px}
.shell:has(#view .adm) .fc-c span{color:var(--sky-soft);font-size:12.5px;font-weight:600}
.shell:has(#view .adm) .fc-t span{color:var(--sky-soft);border:1px solid var(--sky-line);font-size:12.5px;font-weight:600}
.shell:has(#view .adm) .fc-p{color:var(--sky-soft);font-size:13.5px}
/* A shared account, so it looks like something to be careful with. Amber, the
   warning colour, rather than the panel's ordinary edge. */
.shell:has(#view .adm) .hostbox{
  background:#FFFFFF;border:1px solid var(--sky-line);border-left:5px solid #B5761B;border-radius:12px;
}
.shell:has(#view .adm) .hostbox .kv-k{color:var(--sky-soft);font-size:13px;font-weight:600}
.shell:has(#view .adm) .hostbox .kv-v{background:var(--sky-tint);border:1px solid var(--sky-line);color:var(--sky-ink)}
.shell:has(#view .adm) .hostbox .warn{color:#99190F;font-weight:800}

/* ── marking ──────────────────────────────────────────────────────────────*/
.shell:has(#view .adm) .mk-h{
  font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  color:var(--sky-deep);margin:0 0 10px;
}
.shell:has(#view .adm) .shots a{border:1px solid var(--sky-line);border-radius:10px;background:var(--sky-tint)}
/* A photograph that arrived after the clock. Amber — the teacher has to know,
   and it is not the child's fault. It was the crest's yellow. */
.shell:has(#view .adm) .shot-late{background:#B5761B;color:#FFFFFF;font-size:11px;font-weight:800;padding:2px 7px}

/* ── the restart card, drawn only on the rare day it is owed ─────────────*/
.shell:has(#view .adm) .restart-card{border-left:6px solid #B5761B}
.shell:has(#view .adm) .restart-now.clear{background:#DFF2E8;color:#07543A}
.shell:has(#view .adm) .restart-now.busy{background:#FCE9E7;color:#99190F}
/* Open papers, but none of them a child's — a true statement should not wear
   the colour of an alarm. */
.shell:has(#view .adm) .restart-now.staffonly{background:#FDF3E2;color:#6E460A}
.shell:has(#view .adm) .restart-card .btn.arm{background:#B3221A;border-color:#B3221A;color:#FFFFFF}
.shell:has(#view .adm) .restart-card .btn.arm:hover{background:#99190F}
.shell:has(#view .adm) .restart-who{border:1px solid var(--sky-line);border-radius:11px;background:#FFFFFF}
.shell:has(#view .adm) .restart-who b{color:var(--sky-ink)}
.shell:has(#view .adm) .restart-tag{background:#FCE9E7;color:#99190F;font-size:11.5px;font-weight:800}
.shell:has(#view .adm) .restart-who.staff .restart-tag{background:#E7EEF3;color:#3C5565}
.shell:has(#view .adm) .restart-force{border:1.5px dashed #B3221A;border-radius:12px}

/* ── the messages the server sends back ──────────────────────────────────*/
.shell:has(#view .adm) .msg{
  border-radius:12px;padding:12px 15px;font-size:14.5px;line-height:1.6;
  border:1px solid var(--mline);border-left:6px solid var(--mbar);
  background:var(--mbg);color:var(--mink);
}
.shell:has(#view .adm) .msg{--mbg:#E8F3FB;--mink:#2B4A5C;--mline:#C4E0F4;--mbar:#1773B4}
.shell:has(#view .adm) .msg.info{--mbg:#E8F3FB;--mink:#2B4A5C;--mline:#C4E0F4;--mbar:#1773B4}
.shell:has(#view .adm) .msg.ok  {--mbg:#DFF2E8;--mink:#07543A;--mline:#BFE3D2;--mbar:#0C7A55}
.shell:has(#view .adm) .msg.warn{--mbg:#FDF3E2;--mink:#6E460A;--mline:#F0DDBB;--mbar:#B5761B}
.shell:has(#view .adm) .msg.err,
.shell:has(#view .adm) .msg.bad {--mbg:#FCE9E7;--mink:#99190F;--mline:#F3CCC8;--mbar:#B3221A}
.shell:has(#view .adm) .msg b,
.shell:has(#view .adm) .msg .en,
.shell:has(#view .adm) .msg .small{color:inherit}
.shell:has(#view .adm) .kv{border-bottom:1px solid var(--sky-line)}
.shell:has(#view .adm) .kv:last-child{border-bottom:0}
.shell:has(#view .adm) .rule{background:var(--sky-line)}
.shell:has(#view .adm) .slotbtn{
  border:2px solid var(--sky-line);background:#FFFFFF;color:var(--sky-ink);border-radius:11px;
}
.shell:has(#view .adm) .slotbtn:hover{border-color:var(--sky-band);background:var(--sky-tint)}

/* ── the notice board, as staff and candidates both see it ───────────────
 * 🔴 THE SYMBOL CARRIES THE MEANING, NOT THE COLOUR — the note from 17 Sep
 * stands and nothing here weakens it. Four levels, four symbols (ℹ️ ⚠️ ⛔ 🎁),
 * four colours that AGREE with the symbol, and a left bar as the third signal
 * for a printed page and for a screen washed out by sunlight. Only the hues
 * move, into the same semantic family the rest of these two screens now use. */
.shell:has(#view .dash) .nb,
.shell:has(#view .adm) .nb{
  border:1px solid var(--nbline);border-left:6px solid var(--nbbar);
  border-radius:12px;background:var(--nbbg);
}
.shell:has(#view .dash) .nb-t b,
.shell:has(#view .adm) .nb-t b{font-size:16px;line-height:1.35;color:var(--nbink);font-weight:800}
.shell:has(#view .dash) .nb-t p,
.shell:has(#view .adm) .nb-t p{font-size:14.5px;font-weight:600;color:var(--nbink);opacity:.92}
.shell:has(#view .dash) .nb-ja,
.shell:has(#view .adm) .nb-ja{font-size:13.5px;font-weight:600;color:var(--nbink);opacity:.86}
.shell:has(#view .dash) .nb-info,
.shell:has(#view .adm) .nb-info { --nbbar:#1773B4; --nbbg:#E4F1FB; --nbline:#C4E0F4; --nbink:#0F3A5F }
.shell:has(#view .dash) .nb-warn,
.shell:has(#view .adm) .nb-warn { --nbbar:#B5761B; --nbbg:#FDF3E2; --nbline:#F0DDBB; --nbink:#6A4308 }
.shell:has(#view .dash) .nb-stop,
.shell:has(#view .adm) .nb-stop { --nbbar:#B3221A; --nbbg:#FCE9E7; --nbline:#F3CCC8; --nbink:#7A0A0A }
.shell:has(#view .dash) .nb-offer,
.shell:has(#view .adm) .nb-offer{ --nbbar:#0C7A55; --nbbg:#DFF2E8; --nbline:#BFE3D2; --nbink:#0E4B28 }

/* ═══════════════════════════════════════════════════════════════════════════
 * 4. NARROW SCREENS — AND WHY THESE RULES HAVE TO BE REPEATED HERE
 *
 * 🔴 THE SCOPE CARRIES AN ID'S WORTH OF SPECIFICITY, which is what lets it beat
 * the dark-theme blocks further up without `!important`. The cost is that it
 * ALSO beats the plain `@media (max-width:400px)` and `(max-width:860px)` rules
 * those blocks sit beside — so every phone override that touches something
 * recoloured above has to be restated inside the scope, or the phone silently
 * gets the desktop size.
 *
 * That is not theoretical. `.tile-n` at 31px, `.hero-name` at 27px and a visible
 * Japanese line on five tabs are what pushes a 360px screen into sideways
 * scroll — and sideways scroll on this board is a check in
 * scripts/dashtest.js that has caught it before.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── the board on a small phone ───────────────────────────────────────────*/
@media (max-width:400px){
  .shell:has(#view .dash) .dash{padding:0 11px}
  .shell:has(#view .dash) .tiles{gap:7px}
  .shell:has(#view .dash) .tile{padding:12px 6px 11px}
  .shell:has(#view .dash) .tile-n{font-size:25px}
  .shell:has(#view .dash) .tile-en{font-size:14px}
  .shell:has(#view .dash) .tile-ja{font-size:11px}
  .shell:has(#view .dash) .hero-name{font-size:22px}
  .shell:has(#view .dash) .avatar{width:54px;height:54px;flex-basis:54px;font-size:21px}
  .shell:has(#view .dash) .rc-band{font-size:32px}
  .shell:has(#view .dash) .btabs{gap:5px}
  .shell:has(#view .dash) .btab{padding:8px 1px 7px}
  /* 🔴 THE ONE EXCEPTION TO BOTH LANGUAGE RULES ON THESE TWO SCREENS, AND IT IS
     THE SAME EXCEPTION THAT WAS ALREADY HERE.
     Below 400px the tab bar has about 52px per tab with the book shop open. A
     14px English label will not fit in it and two stacked languages certainly
     will not, so the Japanese gloss is hidden and the English drops to 12.5px.
     NOTHING IS LOST: a tab label is a NAME, not prose — the Japanese heading and
     the full English sentence are both on the pane the tab opens, one press
     away, at full size, on every screen width. That is the same argument the
     `.legal-foot .en-tail` rule makes further up, and it is the only other place
     on this site where a language is dropped rather than enlarged.
     Everywhere else on the board the English stays at or above 14px. */
  .shell:has(#view .dash) .btab .ja{display:none}
  .shell:has(#view .dash) .btab .en{font-size:12.5px}
  .shell:has(#view .dash) .btab .ic{font-size:15px}
  .shell:has(#view .dash) .u{padding:10px 11px;gap:9px}
  .shell:has(#view .dash) .u .go{padding:8px 11px}
  .shell:has(#view .dash) .card{padding:16px 15px}
  .shell:has(#view .dash) .bquiet{padding:15px 15px}
  .shell:has(#view .dash) .gc-name{font-size:17.5px}
}
@media (max-width:560px){
  .shell:has(#view .dash) .hero{padding:19px 17px}
  .shell:has(#view .dash) .hero-name{font-size:24px}
}

/* ── the panel on a phone: the rail is one button ────────────────────────
   Restated so the recolour above cannot take back the padding and the edges
   that the 860px layout depends on — the rail there is a strip across the top,
   not a column, and a 16px column padding leaves a blue slab over the work. */
@media (max-width:860px){
  .shell:has(#view .adm) .adm-rail{
    border-right:0;border-bottom:1px solid rgba(255,255,255,.10);padding:8px 0;
    background-color:#10598F;
    background-image:linear-gradient(180deg,#10598F 0%,#0B4572 100%);
  }
  .shell:has(#view .adm) .adm-main{padding:18px 14px 60px}
  .shell:has(#view .adm) .adm-h{font-size:21px}
}
@media (max-width:420px){
  .shell:has(#view .adm) .adm-main{padding:16px 11px 56px}
}

/* ── printing ─────────────────────────────────────────────────────────────
   🔴 A DEEP BLUE FOOTER IS A TRAP ON PAPER. Browsers strip backgrounds when
   they print unless told otherwise, so #D6E8F5 text on a blue that is no longer
   there is white ink on white paper — the trademark line would vanish from the
   one document a family keeps. On paper it goes back to dark text on white.
   Nothing is hidden; it is the same words, legible. */
@media print{
  .shell:has(#view .dash) .legal-foot,
  .shell:has(#view .adm) .legal-foot{
    background:#FFFFFF;border-top:2px solid #10598F;padding:10px 0 0;
  }
  .shell:has(#view .dash) .legal-foot summary,
  .shell:has(#view .adm) .legal-foot summary{color:#16323F}
  .shell:has(#view .dash) .legal-foot .legal,
  .shell:has(#view .dash) .legal-foot .legal p,
  .shell:has(#view .adm) .legal-foot .legal,
  .shell:has(#view .adm) .legal-foot .legal p{color:#16323F}
  /* The stripes and chips on a card are the state. Keep them, so a printed
     board still says which grade is open and which is locked. */
  .shell:has(#view .dash) .grade-card,
  .shell:has(#view .dash) .result-card,
  .shell:has(#view .dash) .u,
  .shell:has(#view .dash) .chip,
  .shell:has(#view .adm) .chip,
  .shell:has(#view .adm) .stamp{-webkit-print-color-adjust:exact;print-color-adjust:exact}
}

/* ── one thing deliberately left as it was ────────────────────────────────
 * 🔴 THE TWO-FOR-ONE CARD (`.o21`, `.o21line`) KEEPS ITS GOLD, and that is a
 * decision rather than an oversight.
 *
 * It is the same card, with the same class names, the same wording and the same
 * drawing, on /register, on the free test's result screen and here — so that a
 * candidate who saw the offer before they had a login recognises it after. The
 * picture inside it (`/offer21.svg`) is drawn on white paper in gold, and the
 * note above its rules in this file explains why every colour in it is written
 * out rather than themed. Recolouring it on the board ALONE would give one
 * campaign two faces and put a second visual language on this page, which is
 * the one thing the system forbids outright. It is a poster, not furniture.
 *
 * If the offer card is ever restyled it has to be restyled in all three places
 * in one edit, with a new drawing, and that is a separate job.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
 * 5. THE PANEL ON A WIDE SCREEN — 1 Oct 2026
 *
 * Razib, on the staff panel: *"design more logical organized and user friendly
 * … add picture in the panel too to make it attractive … why it is in the
 * center of the pc screen you can use all area"*.
 *
 * WHAT WAS ACTUALLY WRONG, measured on his own data at 1920×1200 before any of
 * this was written. The panel was NOT in a narrow column — `.wrap:has(.adm)`
 * already took the 820px cap off, and the work area really was 1604px. What it
 * was doing with those 1604px was the problem, and it was three separate
 * things:
 *
 *   1. 470px of the window — 40% of its height — went on the closed-site
 *      banner, the notices and the free-test count, at the top of all SIXTEEN
 *      sections, above the heading. Seven rows of work were visible. The
 *      Overview is 4955px tall and he opens it every morning.
 *   2. Every row put its name hard left and its buttons hard right with up to
 *      a thousand pixels of nothing in between — the width was spent on a gap.
 *   3. Settings stacked sixteen cards in one column (7853px), Students stacked
 *      six grade groups, Zoom stacked seventeen 355px rows. Every one of those
 *      is a list that would fit two or three abreast and was drawn one abreast.
 *
 * SO THE WIDTH IS SPENT THREE WAYS, and each one is a measured decision:
 *
 *   · an aside, for the things that are true today regardless of which section
 *     is open — it is the only new column, and it is on the right, where
 *     nothing was;
 *   · columns inside the lists that fit, at the width where they really fit;
 *   · and a CAP ON RUNNING PROSE, because a paragraph of instructions at
 *     1550px is not using the width, it is unreadable. 74ch is the measure
 *     every public page of this site already holds its body text to.
 *
 * 🔴 NOTHING HERE APPLIES BELOW 1360px. A teacher marks on a laptop and Razib
 * reads the Overview on a phone, and both of those get exactly the panel they
 * had, one column, in the order they had it.
 *
 * Every rule hangs off `.shell:has(#view .adm)` like the rest of section 3, so
 * nothing in here can reach the exam screen, the candidate's board or the door.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── running prose, at a measure somebody can read ────────────────────────
   The width is for TABLES. A sentence of instructions gets 74ch wherever it
   appears, and the table beside it keeps the rest. */
.shell:has(#view .adm) .adm-sub{max-width:74ch}
.shell:has(#view .adm) .card>.small,
.shell:has(#view .adm) .card>p,
.shell:has(#view .adm) .msg{max-width:88ch}

/* ── the picture beside each heading ──────────────────────────────────────
   A band, not a banner: the photograph is a 2:1 crop 220px wide sitting beside
   the words, so it costs ~40px of height instead of ~200. Below 900px it is
   not drawn at all — see the note in app.js. */
.shell:has(#view .adm) .adm-hero{
  display:flex;align-items:center;gap:20px;margin:0 0 20px;
}
.shell:has(#view .adm) .adm-hero-t{min-width:0;flex:1 1 auto}
.shell:has(#view .adm) .adm-hero-t .adm-h{margin:0 0 8px}
.shell:has(#view .adm) .adm-hero-t .adm-sub{margin:0}
/* 「お支払いの確認は校長のみが行えます。」 — who may actually do the thing the
   instruction above describes. Japanese on its own line, English under it, both
   at 14.5px or more, and neither on colour alone.

   🔴 ITS OWN CLASS, BECAUSE IT BORROWED THE RAIL'S AND WAS HIDDEN — 1 Oct 2026,
   third pass. This sentence was built as `adm-sub adm-who`, and `.adm-who
   {display:none}` inside `@media (max-width:860px)` above — written for the dark
   rail's PRINCIPAL / ADMIN / TEACHER caption, which has carried that class for
   weeks and is rightly dropped when the rail becomes a tab strip — hid it on
   every phone. The office, who is the only reader it is drawn for, never saw
   it below 861px.

   `.adm-money` is matched by nothing in that media query, so it survives every
   width; the rail's rule is left exactly as it was. Keep this block AFTER
   `.adm-hero-t .adm-sub` — same specificity, and the later margin is the one
   that must win. */
.shell:has(#view .adm) .adm-hero-t .adm-money{margin:8px 0 0}
.adm-money b{display:block;font-size:15px;color:var(--sky-ink,var(--ink));line-height:1.5}
.adm-money span{display:block;font-size:14.5px;line-height:1.6;color:var(--sky-ink,var(--ink))}
.shell:has(#view .adm) .adm-hero-pic{
  flex:0 0 auto;width:220px;height:110px;object-fit:cover;display:block;
  border-radius:14px;border:1px solid var(--sky-line);
  box-shadow:0 6px 18px rgba(16,89,143,.14);
}
@media (max-width:900px){
  .shell:has(#view .adm) .adm-hero-pic{display:none}
  .shell:has(#view .adm) .adm-hero{display:block}
  .shell:has(#view .adm) .adm-hero-t .adm-h{margin:0 0 10px}
}

/* ── the empty state gets the picture instead ─────────────────────────────
   The one place a photograph is allowed to be big, because there is no data
   under it to get in the way of — and "nothing is waiting" is the one message
   on this panel that is good news. */
.shell:has(#view .adm) .empty-pic{
  width:148px;height:74px;object-fit:cover;border-radius:12px;display:block;
  margin:0 auto 12px;opacity:.9;border:1px solid var(--sky-line);
}

/* ── two columns: the work, and what is true today ────────────────────────
 * 🔴 THE FLOOR AND THE ASIDE'S WIDTH WERE SOLVED TOGETHER, not chosen. The
 * aside may only cost the work column more than the work column can spare:
 * the widest row on the panel that is not Papers is Marking at 735px, and the
 * work gets (viewport − 316 rail − 56 padding − 26 gap − aside). At 1440 — the
 * commonest laptop there is — a 290px aside leaves 752px, which still clears
 * 735. A narrower screen than that keeps exactly the panel it has today, one
 * column, in the order it has today. The aside then grows with the screen,
 * because at 1920 a 290px column of notices looks like an offcut. */
@media (min-width:1440px){
  .shell:has(#view .adm) .adm-main{
    display:flex;align-items:flex-start;gap:26px;padding:22px 28px 60px;
  }
  .shell:has(#view .adm) .adm-work{flex:1 1 auto;min-width:0}
  /* ORDER, NOT DOM ORDER — see the note in app.js. The aside is written first
     so that a phone and a screen reader meet THE SITE IS CLOSED before the
     work; here it is moved to the right without moving the words. */
  .shell:has(#view .adm) .adm-side{order:2;flex:0 0 290px;width:290px;min-width:0}
  /* It follows him down a long list. `.main` is the scrolling box, so this
     sticks inside it; `max-height` keeps a tall aside from running past the
     bottom of the window with no way to reach the end of it. */
  .shell:has(#view .adm) .adm-side{
    position:sticky;top:0;max-height:calc(100dvh - 140px);overflow-y:auto;
    overscroll-behavior:contain;
  }
  /* 🔴 AN EMPTY ASIDE GIVES THE WIDTH BACK. A teacher has no free-test count,
     so on a day with no notice on the board her aside holds nothing at all —
     and a 290px empty column that narrows the list she is marking in is worse
     than the layout it replaced. `:has()` is live, so the column appears the
     moment a notice arrives and disappears when the last one is taken down.
     The panel already depends on `:has()` for its entire scope. */
  .shell:has(#view .adm) .adm-side:not(:has(.nb)):not(:has(.freecount)){display:none}
  .shell:has(#view .adm) .adm-side .nb{margin:0 0 12px}
  .shell:has(#view .adm) .adm-side .nb-t b{font-size:15px}
  .shell:has(#view .adm) .adm-side .freecount{padding:0 0 14px;overflow:hidden}
  .shell:has(#view .adm) .adm-side .fc-pic{
    width:100%;height:96px;object-fit:cover;display:block;margin:0 0 12px;
  }
  .shell:has(#view .adm) .adm-side .fc-h,
  .shell:has(#view .adm) .adm-side .fc-n,
  .shell:has(#view .adm) .adm-side .fc-t,
  .shell:has(#view .adm) .adm-side .fc-p{padding-left:16px;padding-right:16px}
  /* Three numbers, three columns, in a narrower box: `.fc-c` is `flex:1 1 110px`
     and in a 324px aside that wraps to two and one. A number nobody can line up
     with the one beside it is a number he reads twice. */
  .shell:has(#view .adm) .adm-side .fc-n{gap:8px;flex-wrap:nowrap}
  .shell:has(#view .adm) .adm-side .fc-c{flex:1 1 0;min-width:0;padding:10px 6px}
  .shell:has(#view .adm) .adm-side .fc-c b{font-size:21px}
  .shell:has(#view .adm) .adm-side .fc-c span{font-size:11px}
}
@media (min-width:1620px){
  .shell:has(#view .adm) .adm-side{flex-basis:324px;width:324px}
}
@media (min-width:1800px){
  .shell:has(#view .adm) .adm-side{flex-basis:360px;width:360px}
}
/* The picture at the head of the free-test card is only ever drawn in the
   aside, where it has a column of its own to fill. In the single-column panel
   it would be a photograph between the notices and the work. */
.shell:has(#view .adm) .fc-pic{display:none}

/* ── lists that fit more than one abreast ─────────────────────────────────
 *
 * 🔴 THE BREAKPOINTS ARE MEASURED, NOT CHOSEN. Each section's widest row was
 * measured with its real content (thirty candidates, seventeen rooms, eight
 * grades) by cloning every `.row-line` into an unconstrained box:
 *
 *     Zoom rooms     355px      Teachers    494px      Students   530px
 *     Not sat yet    592px      Interviews  606px      Speaking   709px
 *     Marking        735px      Papers      957px
 *
 * A list goes two abreast at the width where two of its own widest rows plus
 * the rail and the aside actually fit, and not before — a column that forces
 * every row to wrap onto three lines has made the list TALLER, which is the
 * opposite of the point. Papers and Marking are therefore never split: at 957
 * and 735 they would need a 2400px window, and both are lists whose rows carry
 * four buttons each.
 *
 * `.tbl.two` is opted into by the section that wants it, in app.js. It is not
 * a blanket rule on `.tbl`, because the whole point is that the answer differs
 * per list.
 *
 * The hairlines are drawn by a 1px gap over a line-coloured background rather
 * than by borders, so the grid gets its vertical rule between the columns for
 * free and no row has to know which column it landed in. */

/* 🔴 THE COLUMN HAS TO BE WIDER THAN THE ROW IN IT. A two-column list whose
   rows wrap to three lines each is TALLER than the one-column list it replaced,
   and that mistake was made once already at 1440 before these numbers were
   checked against a screenshot. The floors below are (rail 316 + padding 56 +
   gap 26 + aside 324) plus two columns wide enough for the measured row.
   The columns are separated by white space and not by a filled gap: a grid
   whose gaps are painted shows the paint through every cell a short list does
   not fill, which is a blue rectangle where nothing is. */
@media (min-width:1800px){
  .shell:has(#view .adm) .tbl.two{
    display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:26px;
  }
  .shell:has(#view .adm) .tbl.two>.row{min-width:0}
  .shell:has(#view .adm) .tbl.two>.tbl-head,
  .shell:has(#view .adm) .tbl.two>.empty{grid-column:1/-1}
  /* A row that has OPENED a form in it takes the whole width back. A form is
     not a list item and it has never been one.
     🔴 `:not([hidden])` is the whole rule. Every folded row carries its panel
     in the page the whole time — `fold()` hides it rather than building it on
     demand — so `:has(.row-open)` alone matched every row in the list and put
     the entire list back into one column while looking as though it had done
     nothing. Found by looking at a screenshot of Students. */
  .shell:has(#view .adm) .tbl.two>.row:has(.row-open:not([hidden])){grid-column:1/-1}
}
/* The shortest rows on the panel — a Zoom room is a code, a label and two
   buttons, 355px of content — so they start doubling up far earlier and go
   three abreast on a big monitor. */
@media (min-width:1500px){
  .shell:has(#view .adm) .tbl.tight{
    display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:26px;
  }
  .shell:has(#view .adm) .tbl.tight>.row{min-width:0}
  .shell:has(#view .adm) .tbl.tight>.tbl-head,
  .shell:has(#view .adm) .tbl.tight>.empty{grid-column:1/-1}
  .shell:has(#view .adm) .tbl.tight>.row:has(.row-open:not([hidden])){grid-column:1/-1}
}
@media (min-width:1900px){
  .shell:has(#view .adm) .tbl.tight{grid-template-columns:repeat(3,minmax(0,1fr))}
}

/* ── groups of cards, side by side ────────────────────────────────────────
   Settings stacked sixteen cards 7853px tall; Students stacked a table per
   grade. Both are a SET of independent things, which is what a grid is for. */
@media (min-width:1800px){
  .shell:has(#view .adm) .adm-grid{
    display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
    gap:16px;align-items:start;
  }
  /* 🔴 A GRID ROW IS AS TALL AS THE TALLEST THING IN IT, and Settings' sixteen
     cards range from three fields to twelve. Paired off in rows, the short card
     beside a tall one leaves the difference as white space, and the page came
     out LONGER than the single column — measured at 8,401px against 7,853. CSS
     columns pack by height instead of by row, so the two columns come out level
     and nothing has a hole under it. A card is never split in half.
     `.adm-grid.flow` is opted into by the section; Students keeps the plain
     grid, where a grade group beside another grade group is the point. */
  .shell:has(#view .adm) .adm-grid.flow{
    display:block;column-count:2;column-gap:20px;
  }
  .shell:has(#view .adm) .adm-grid.flow>*{
    break-inside:avoid;-webkit-column-break-inside:avoid;
    display:block;width:100%;margin-bottom:16px;
  }
  .shell:has(#view .adm) .adm-grid>*{min-width:0;margin-bottom:0}
  /* Anything that must not be cut in half by a column — a save bar, a heading
     that introduces the set, a form that has been opened out. */
  .shell:has(#view .adm) .adm-grid>.span{grid-column:1/-1}
}
@media (min-width:2300px){
  .shell:has(#view .adm) .adm-grid.wide3{grid-template-columns:repeat(3,minmax(0,1fr))}
}

/* ── and the row itself stops being a name and a button a metre apart ─────
 *
 * `.row-main` is `flex:1 1 220px`, so it absorbed every spare pixel: at 1604px
 * a row read as a name on the far left, a button on the far right and a white
 * field between them. Capping the name block at 52ch puts the slack back where
 * a table wants it — between the NAME and the STATE — so the dates, the marks
 * and the buttons line up in columns down the list and can be scanned with one
 * eye. It is the same row, with the same things on it, in the same order. */
@media (min-width:1440px){
  .shell:has(#view .adm) .tbl:not(.two):not(.tight) .row-main{max-width:52ch}
  .shell:has(#view .adm) .tbl:not(.two):not(.tight) .row-side{margin-left:auto;min-width:110px}
}

/* ── Settings: a field is not a runway ────────────────────────────────────
   Every box in Settings is `flex:1 1 260px`, so a school name got a 1,270px
   text field on a wide screen. A box longer than what can go in it is a box
   that tells you nothing about what it wants; 560px holds a bank account, a
   URL and a phone number with room over. The textareas keep their own height.
   The CAP is here and the `flex` stays in app.js: this is a width question and
   it belongs in the stylesheet. */
@media (min-width:1100px){
  .shell:has(#view .adm) .set-groups input,
  .shell:has(#view .adm) .set-groups select,
  .shell:has(#view .adm) .set-groups textarea{max-width:560px}
}

/* ── one setting: a label, a box, and the note that explains it ───────────
   Lifted out of three inline styles in app.js so that it can answer to the
   width it is actually in — see the note there. Side by side while the card is
   wide enough for both; stacked when it is not, which is a phone AND either of
   the two columns Settings goes into on a big monitor. */
.shell:has(#view .adm) .set-line{
  display:flex;gap:14px;align-items:center;margin:0 0 10px;flex-wrap:wrap;
}
.shell:has(#view .adm) .set-lab{flex:0 0 206px;margin:0;font-size:13.5px}
.shell:has(#view .adm) .set-hint{margin:-6px 0 12px 220px;max-width:72ch}
/* The label stacks above its box only on a phone, where there is no room for
   both. The NOTE gives up its indent much sooner: lining a paragraph up with
   the box above it looks tidy and costs it a third of its width, and in the
   two-column Settings that turned a two-line note into a seven-line ribbon —
   which is how this page came out TALLER than the single column it replaced
   the first time it was measured. */
@media (max-width:1100px){
  .shell:has(#view .adm) .set-lab{flex:1 1 100%}
}
@media (max-width:1100px),(min-width:1800px){
  .shell:has(#view .adm) .set-hint{margin:-4px 0 14px 0}
}

/* 🔴 AND THE CAP COMES OFF AGAIN INSIDE A COLUMN. The 52ch name block and the
   110px state column exist to stop a row spreading across 1,200px; inside one
   half of `.adm-grid` there is no spare width to spread into, and holding the
   name to 52ch there pushed the buttons onto a second line and made the list
   taller than the single column it replaced. Measured on Students. */
.shell:has(#view .adm) .adm-grid .tbl .row-main{max-width:none}
.shell:has(#view .adm) .adm-grid .tbl .row-side{margin-left:auto;min-width:0}

/* ── a row's buttons wrap; they are never cut in half ────────────────────
 * 🔴 `.row-acts` is `flex:0 0 auto`, so it refuses to shrink, and `.tbl` has
 * `overflow:hidden` — which means that the moment a row's buttons are wider
 * than the row, the last one is CLIPPED rather than wrapped, silently, with no
 * sideways scrollbar to give it away. That is why the screen-width check in
 * panetest.js passes on it. It was found by looking at the Papers screen at
 * 1440 with the aside in place: "Upload answers copy" ended mid-word.
 *
 * Letting the box shrink is enough — it already wraps inside itself. The phone
 * rule below 861px, which gives the buttons a line of their own, stands.
 *
 * This is not new to the aside: any row with seven buttons on a 1366 laptop
 * could already lose one, and nothing would have said so. */
@media (min-width:861px){
  .shell:has(#view .adm) .row-acts{flex:0 1 auto;min-width:0}
}

/* ══ THE OVERVIEW, REBUILT TO THE SHAPE RAZIB DREW — 1 Oct 2026 ═══════════
 *
 * *"this is very beautiful design follow it and add the functional analysis
 * that was in the last picture"*, with two mockups: a greeting band, a row of
 * four stat cards, three panels under solid coloured headings, and a set of
 * charts.
 *
 * 🔴 EVERY SELECTOR HANGS OFF `.shell:has(#view .adm)`, like the rest of this
 * file's last third, so none of it can reach the exam screen — `#shellExam` is
 * a SIBLING of `.shell` and nothing here can match inside it. The paper a
 * child sits under a clock is untouched by all of this.
 *
 * 🔴 EVERY COLOUR BELOW WAS MEASURED BEFORE IT WAS TYPED, against the surface
 * it actually sits on:
 *   · white on the three heading bars — #1773B4 5.06 · #10598F 7.37 ·
 *     #0C7A55 5.34 · #8A5A00 5.93. #1C7FC4 is 4.30 and carries no word here.
 *   · --sky-soft #4A6373 — 6.32 on white, 5.90 on the ground, 5.65 on the pale
 *     wash, 5.25 on the pale band. It is the English gloss everywhere.
 *   · every English line is at least 14px, as the panel owes.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── the greeting ────────────────────────────────────────────────────────── */
.shell:has(#view .adm) .ov-hello{
  display:flex;align-items:stretch;gap:16px;margin:0 0 20px;
  background:var(--sky-pale);border:1px solid var(--sky-line);
  border-left:6px solid var(--sky-band);border-radius:14px;overflow:hidden;
}
.shell:has(#view .adm) .ov-hello-t{
  display:flex;gap:13px;align-items:flex-start;padding:16px 4px 16px 16px;flex:1 1 auto;min-width:0;
}
.shell:has(#view .adm) .ov-sun{font-size:30px;line-height:1.1;flex:0 0 auto}
.shell:has(#view .adm) .ov-hello-w{min-width:0}
.shell:has(#view .adm) .ov-hello-w b{
  display:block;font-size:21px;line-height:1.3;color:var(--sky-deep);font-weight:800;
}
.shell:has(#view .adm) .ov-hello-w .en{
  display:block;font-size:15px;font-weight:700;color:var(--sky-soft);margin-top:1px;
}
.shell:has(#view .adm) .ov-hello-w p{margin:9px 0 0;font-size:15px;line-height:1.5;color:var(--sky-ink)}
.shell:has(#view .adm) .ov-hello-w p.en{font-size:14.5px;font-weight:600;color:var(--sky-soft);margin-top:2px}
.shell:has(#view .adm) .ov-hello-pic{
  flex:0 0 232px;width:232px;height:auto;object-fit:cover;display:block;align-self:stretch;
}
@media (max-width:900px){
  .shell:has(#view .adm) .ov-hello-pic{display:none}
  .shell:has(#view .adm) .ov-hello-t{padding:14px}
  .shell:has(#view .adm) .ov-hello-w b{font-size:19px}
}

/* ── the four stat cards ─────────────────────────────────────────────────
   A card is a BUTTON wherever it has somewhere to go, so the arrow is not
   decoration. It keeps the panel's own focus ring and a 44px target. */
.shell:has(#view .adm) .ov-cards{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(214px,1fr));gap:12px;margin:0 0 22px;
}
.shell:has(#view .adm) .ov-card{
  display:flex;gap:12px;align-items:flex-start;text-align:left;width:100%;
  background:var(--surface);border:1px solid var(--sky-line);border-radius:13px;
  padding:14px 13px;color:var(--sky-ink);font:inherit;cursor:default;
  box-shadow:0 1px 2px rgba(16,89,143,.06);
}
.shell:has(#view .adm) button.ov-card{cursor:pointer}
.shell:has(#view .adm) button.ov-card:hover{border-color:var(--sky-lite);background:var(--sky-wash)}
.shell:has(#view .adm) button.ov-card:focus-visible{outline:3px solid var(--sky-band);outline-offset:2px}
.shell:has(#view .adm) .ov-card .ic{
  flex:0 0 auto;width:44px;height:44px;border-radius:11px;display:flex;
  align-items:center;justify-content:center;font-size:21px;line-height:1;
  background:var(--sky-tint);border:1px solid var(--sky-line);
}
.shell:has(#view .adm) .ov-card .tx{min-width:0;flex:1 1 auto;display:block}
.shell:has(#view .adm) .ov-card .tx b{
  display:block;font-size:28px;line-height:1.05;letter-spacing:-.02em;color:var(--sky-deep);
}
.shell:has(#view .adm) .ov-card .tx .ja{
  display:block;font-size:14.5px;font-weight:700;color:var(--sky-ink);margin-top:3px;
}
.shell:has(#view .adm) .ov-card .tx .en{
  display:block;font-size:14px;font-weight:600;color:var(--sky-soft);line-height:1.35;
}
.shell:has(#view .adm) .ov-card .tx .wy{
  display:block;font-size:14px;color:var(--sky-soft);line-height:1.45;margin-top:5px;
}
.shell:has(#view .adm) .ov-card .ar{flex:0 0 auto;font-size:18px;color:var(--sky-band);font-weight:800}
/* Amber marks a count that is WORK WAITING. The tile, not the number, carries
   it — #B5761B on white is 3.4:1 and would fail as text. */
.shell:has(#view .adm) .ov-card.warn .ic{background:var(--amberbg);border-color:#EBD6AE}
.shell:has(#view .adm) .ov-card.go .ic{background:var(--okbg);border-color:#BFE3D2}

/* ── the three panels ────────────────────────────────────────────────────
   A solid heading bar, which is the thing the mockup is built on. White on
   each of the four bar colours was measured; see the block header. */
.shell:has(#view .adm) .ov-panels{
  display:grid;grid-template-columns:1fr;gap:16px;margin:0 0 20px;
}
@media (min-width:1180px){
  .shell:has(#view .adm) .ov-panels{grid-template-columns:repeat(3,minmax(0,1fr));align-items:start}
}
.shell:has(#view .adm) .ov-pan{
  background:var(--surface);border:1px solid var(--sky-line);border-radius:14px;
  overflow:hidden;min-width:0;margin:0 0 20px;
}
.shell:has(#view .adm) .ov-panels>.ov-pan{margin:0}
.shell:has(#view .adm) .ov-pan-h{padding:11px 15px;background:var(--sky-band)}
.shell:has(#view .adm) .ov-pan.deep .ov-pan-h{background:var(--sky-deep)}
.shell:has(#view .adm) .ov-pan.green .ov-pan-h{background:var(--free-green)}
.shell:has(#view .adm) .ov-pan.amber .ov-pan-h{background:#8A5A00}
.shell:has(#view .adm) .ov-pan-h b{display:block;font-size:17px;line-height:1.3;color:#fff;font-weight:800}
.shell:has(#view .adm) .ov-pan-h span{
  display:block;font-size:14px;font-weight:600;line-height:1.35;color:#fff;opacity:.94;
}
.shell:has(#view .adm) .ov-pan-b{padding:15px}
/* The blocks moved in here keep their own spacing; the last one does not need
   a 22px gap above the panel's own bottom edge. */
.shell:has(#view .adm) .ov-pan-b>.today-block:last-child{margin-bottom:0 !important}
.shell:has(#view .adm) .ov-note{
  padding:14px;background:var(--sky-wash);border:1px solid var(--sky-line);border-radius:10px;
}
.shell:has(#view .adm) .ov-note b{display:block;font-size:15px;line-height:1.4;color:var(--sky-ink)}
.shell:has(#view .adm) .ov-note span{
  display:block;font-size:14px;line-height:1.5;color:var(--sky-soft);margin-top:3px;
}

/* ── the four tiles inside かんたん統計, and the school-health block ────── */
.shell:has(#view .adm) .ov-tiles{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;
}
.shell:has(#view .adm) .ov-tile{
  background:var(--sky-wash);border:1px solid var(--sky-line);border-radius:11px;padding:12px 11px;
}
.shell:has(#view .adm) .ov-tile b{
  display:block;font-size:26px;line-height:1.05;letter-spacing:-.02em;color:var(--sky-deep);
}
.shell:has(#view .adm) .ov-tile .ja{
  display:block;font-size:14px;font-weight:700;color:var(--sky-ink);margin-top:4px;line-height:1.35;
}
.shell:has(#view .adm) .ov-tile .en{
  display:block;font-size:14px;color:var(--sky-soft);line-height:1.4;
}

/* ── すぐできること: a list of links with a symbol and an arrow ─────────── */
.shell:has(#view .adm) .ov-qa{display:flex;flex-direction:column;gap:8px;margin-top:12px}
.shell:has(#view .adm) .ov-qa-i{
  display:flex;gap:11px;align-items:center;width:100%;text-align:left;min-height:46px;
  padding:8px 11px;border:1px solid var(--sky-line);border-radius:11px;
  background:var(--surface);color:var(--sky-ink);font:inherit;cursor:pointer;
}
.shell:has(#view .adm) .ov-qa-i:hover{background:var(--sky-wash);border-color:var(--sky-lite)}
.shell:has(#view .adm) .ov-qa-i:focus-visible{outline:3px solid var(--sky-band);outline-offset:2px}
.shell:has(#view .adm) .ov-qa-i .ic{
  flex:0 0 auto;width:34px;height:34px;border-radius:9px;display:flex;align-items:center;
  justify-content:center;font-size:17px;background:var(--sky-tint);border:1px solid var(--sky-line);
}
.shell:has(#view .adm) .ov-qa-i .tx{flex:1 1 auto;min-width:0}
.shell:has(#view .adm) .ov-qa-i .tx b{display:block;font-size:15px;line-height:1.3}
.shell:has(#view .adm) .ov-qa-i .tx span{
  display:block;font-size:14px;color:var(--sky-soft);line-height:1.35;
}
.shell:has(#view .adm) .ov-qa-i .ar{flex:0 0 auto;font-size:17px;color:var(--sky-band);font-weight:800}

/* ── 分析: the section heading over the charts ───────────────────────────── */
.shell:has(#view .adm) .ov-sec{margin:4px 0 14px}
.shell:has(#view .adm) .ov-sec b{
  display:block;font-size:20px;font-weight:800;color:var(--sky-deep);line-height:1.3;
}
.shell:has(#view .adm) .ov-sec span{
  display:block;font-size:14.5px;color:var(--sky-soft);line-height:1.5;margin-top:2px;
}
/* 🔴 THE COLUMN NEVER GOES BELOW 320px. The charts are inline SVG and an SVG
   scales its own text with its viewBox, so a card narrower than the viewBox
   would shrink 15px axis labels under the 14px this panel owes everybody.
   `minmax(320px,1fr)` is the floor and `vizSvg`'s 440px cap is the ceiling:
   between them every label lands between about 14px and 21px. */
.shell:has(#view .adm) .ov-charts{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px;margin:0 0 22px;
}
.shell:has(#view .adm) .ov-chart{
  background:var(--surface);border:1px solid var(--sky-line);border-radius:14px;
  border-top:5px solid var(--sky-lite);padding:15px;min-width:0;
}
.shell:has(#view .adm) .ov-chart-h b{
  display:block;font-size:17px;font-weight:800;color:var(--sky-deep);line-height:1.3;
}
.shell:has(#view .adm) .ov-chart-h .en{
  display:block;font-size:14.5px;font-weight:700;color:var(--sky-ink);line-height:1.35;
}
.shell:has(#view .adm) .ov-chart-n{margin:7px 0 13px}
.shell:has(#view .adm) .ov-chart-n .ja{display:block;font-size:14px;color:var(--sky-ink);line-height:1.5}
.shell:has(#view .adm) .ov-chart-n .en{display:block;font-size:14px;color:var(--sky-soft);line-height:1.5}
.shell:has(#view .adm) .ov-chart-k{
  margin:10px 0 0;font-size:14px;line-height:1.55;color:var(--sky-soft);
}
.shell:has(#view .adm) .ov-chart svg{max-width:440px}

/* the legend — a swatch, a name, a number. Identity is never colour alone. */
.shell:has(#view .adm) .ov-leg{
  list-style:none;margin:13px 0 0;padding:0;display:flex;flex-direction:column;gap:6px;
}
.shell:has(#view .adm) .ov-leg li{display:flex;gap:9px;align-items:center}
.shell:has(#view .adm) .ov-leg .sw{
  flex:0 0 auto;width:15px;height:15px;border-radius:4px;border:1px solid rgba(22,50,63,.14);
}
.shell:has(#view .adm) .ov-leg .tx{flex:1 1 auto;min-width:0}
.shell:has(#view .adm) .ov-leg .tx b{font-size:14.5px;font-weight:700;color:var(--sky-ink)}
.shell:has(#view .adm) .ov-leg .tx span{font-size:14px;color:var(--sky-soft);margin-left:6px}
.shell:has(#view .adm) .ov-leg .nu{
  flex:0 0 auto;font-size:14.5px;font-weight:800;color:var(--sky-deep);font-variant-numeric:tabular-nums;
}

/* the table twin — the same figures in words, for anyone the colours fail */
.shell:has(#view .adm) .ov-nums{margin:13px 0 0;border-top:1px solid var(--sky-line);padding-top:10px}
.shell:has(#view .adm) .ov-nums summary{cursor:pointer;font-size:14.5px;color:var(--sky-deep)}
.shell:has(#view .adm) .ov-nums summary b{font-weight:800}
.shell:has(#view .adm) .ov-nums summary span{color:var(--sky-soft);margin-left:7px;font-weight:600}
/* 🔴 THE SCROLLER THAT WAS NOWHERE — see the note on `vizTable` in app.js.
   Six columns could not fit a 366px card, nothing between the table and the
   page would scroll, and the overflow pushed the WHOLE panel 71px wider than
   the phone. The box scrolls now, and the panel does not. `min-width:0` is
   what lets a grid child be narrower than its content at all. */
.shell:has(#view .adm) .ov-tabwrap{
  overflow-x:auto;overflow-y:hidden;min-width:0;max-width:100%;
  -webkit-overflow-scrolling:touch;
}
/* A focused scroller says so — this is the only way to it from a keyboard. */
.shell:has(#view .adm) .ov-tabwrap:focus-visible{
  outline:3px solid var(--sky-band,#166CA9);outline-offset:2px;border-radius:6px;
}
/* 🔴 NO `min-width:max-content` HERE, AND THAT IS DELIBERATE. It was tried
   first and it reads better on a phone — but it also makes this table 670px on
   a 1440px desktop, where the four-column grid gives each card 343px and the
   table previously fitted in it exactly. One fix must not hand the wide screen
   the fault it is removing from the narrow one. Left to itself the table
   shrinks to its own minimum (434px with the headings wrapped at 390px, 313px
   where there is room), so the scroller above engages only where it is needed
   and the desktop is untouched. Both measured. */
.shell:has(#view .adm) .ov-tab{
  width:100%;border-collapse:collapse;margin-top:10px;font-size:14px;
  font-variant-numeric:tabular-nums;
}
.shell:has(#view .adm) .ov-tab th,
.shell:has(#view .adm) .ov-tab td{
  text-align:left;padding:6px 8px;border-bottom:1px solid var(--sky-line);color:var(--sky-ink);
}
.shell:has(#view .adm) .ov-tab th{font-size:14px;font-weight:800;color:var(--sky-soft)}

/* 「まだデータがありません」 — an ANSWER, not an empty frame */
.shell:has(#view .adm) .ov-nodata{
  padding:26px 16px;text-align:center;background:var(--sky-wash);
  border:1px dashed var(--sky-line);border-radius:11px;
}
.shell:has(#view .adm) .ov-nodata b{display:block;font-size:16px;color:var(--sky-ink);line-height:1.4}
.shell:has(#view .adm) .ov-nodata span{
  display:block;font-size:14px;color:var(--sky-soft);line-height:1.5;margin-top:4px;
}

/* ── the alerts ──────────────────────────────────────────────────────────
   Symbol, then words, then the tint. On this site amber and the pale reds sit
   under 3:1 by design, so the colour supports the reading and never carries
   it; the ink on each tint was measured. */
.shell:has(#view .adm) .ov-alert{
  display:flex;gap:11px;align-items:center;padding:11px 12px;border-radius:11px;
  border:1px solid var(--sky-line);background:var(--sky-wash);margin:0 0 9px;
}
.shell:has(#view .adm) .ov-alert:last-of-type{margin-bottom:0}
.shell:has(#view .adm) .ov-alert .ic{flex:0 0 auto;font-size:17px;font-weight:800;width:22px;text-align:center}
.shell:has(#view .adm) .ov-alert .tx{flex:1 1 auto;min-width:0}
.shell:has(#view .adm) .ov-alert .tx b{display:block;font-size:15px;line-height:1.35;color:var(--sky-ink)}
.shell:has(#view .adm) .ov-alert .tx span{
  display:block;font-size:14px;line-height:1.4;color:var(--sky-soft);
}
.shell:has(#view .adm) .ov-alert.serious{background:var(--badbg);border-color:#F3CCC8}
.shell:has(#view .adm) .ov-alert.serious .ic{color:#99190F}
.shell:has(#view .adm) .ov-alert.warning{background:var(--amberbg);border-color:#EBD6AE}
.shell:has(#view .adm) .ov-alert.warning .ic{color:#7A4E07}
.shell:has(#view .adm) .ov-alert.info .ic{color:var(--sky-deep)}
.shell:has(#view .adm) .ov-alert.good{background:var(--okbg);border-color:#BFE3D2}
.shell:has(#view .adm) .ov-alert.good .ic{color:#07543A}

/* ── the line at the foot ────────────────────────────────────────────────── */
.shell:has(#view .adm) .ov-foot{
  margin:6px 0 0;padding:15px 16px;border-top:2px solid var(--sky-line);text-align:center;
}
.shell:has(#view .adm) .ov-foot b{display:block;font-size:15.5px;font-weight:800;color:var(--sky-deep)}
.shell:has(#view .adm) .ov-foot span{
  display:block;font-size:14px;color:var(--sky-soft);line-height:1.55;margin-top:3px;
}

/* 🔴 ONE COLUMN INSIDE A PANEL THAT IS ALREADY A COLUMN.
 * `.tbl.two` goes two abreast above 1360px, which is right when a list owns
 * the whole width and wrong inside 「今日の予定」 or 「すぐできること」 — those
 * are a third of it. At 1920 it put "1 paper to check and publish" and its
 * Open button into a 170px box. The full-width 「対応が必要」 keeps the pairing;
 * only the three-across row gives it up. Measured at 1920 and 1440. */
.shell:has(#view .adm) .ov-panels .tbl.two,
.shell:has(#view .adm) .ov-panels .tbl.tight{display:block}

/* ═══════════════════════════════════════════════════════════════════════════
 * 6. THE CANDIDATE'S BOARD ON A WIDE SCREEN — 1 Oct 2026
 *
 * Razib, of the staff panel and then of the Overview that came out of it:
 * *"design more logical organized and user friendly … add picture in the panel
 * too to make it attractive … why it is in the center of the pc screen you can
 * use all area"*, and then *"this is very beautiful design follow it"*.
 *
 * 🔴 THE SAME IDEAS, NOT THE SAME SCREEN. The person here is nine to seventeen
 * years old with a parent beside them, not the Principal. So the panel's aside,
 * its measured columns, its capped prose and its photographs are all used —
 * and its density is not. Nothing here gets smaller, nothing here gets denser,
 * and every control a thumb lands on is still at least 44px.
 *
 * WHAT WAS WRONG. `.dash` was 600px wide and `.wrap` capped it at 820 anyway,
 * so on the commonest laptop there is the board was a ribbon down the middle of
 * the window with four hundred pixels of empty blue-white either side. A child
 * has few rows, so widening the ribbon would only have given one sentence a
 * longer line to read.
 *
 * SO THE WIDTH IS SPENT THREE WAYS, each one measured:
 *
 *   · A COLUMN FOR WHAT IS WAITING. The strip that never hides already sits
 *     outside the tabs because missing one of its lines costs the candidate
 *     something — writing, money, an interview hour, a free test running out.
 *     Above 1180px it becomes a sticky column on the right instead of a banner
 *     that scrolls away, so it is on screen for as long as they are.
 *   · TWO GRADE CARDS ABREAST. Six taught grades in one column is a 3,000px
 *     scroll; at ~400px each they are the width a phone already proves is
 *     right, and the card they can actually sit is still the first one.
 *   · A CAP ON RUNNING PROSE at 74ch, the measure every public page of this
 *     site holds its body text to. A sentence across 1,200px is not using the
 *     width, it is unreadable.
 *
 * 🔴 NOTHING IN THIS BLOCK APPLIES BELOW 1180px. A child on a phone gets
 * exactly the board they had, in the order they had it — `.dash-cols` is a
 * plain block and the strip is a banner above the tabs, which is the order
 * argued out in app.js after a child got stuck. The pictures are the one thing
 * drawn at every width, because an empty tab with a photograph in it is better
 * on a 360px phone than an empty tab without one.
 *
 * 🔴 EVERY SELECTOR HANGS OFF `.shell:has(#view .dash)` LIKE THE REST OF THIS
 * FILE. `#shellExam` is a SIBLING of `.shell` and is never inside it, so not
 * one rule below can reach the paper a child sits under a clock. That is the
 * document structure, not a convention.
 *
 * 🔴 EVERY COLOUR WAS MEASURED BEFORE IT WAS TYPED, against the surface it
 * actually sits on:
 *   · on the hero's #10598F — white 7.37, #CFE6F7 5.73, #D6E8F5 5.87. The
 *     darker gradient stop #0B4572 is better again, so the lighter one is the
 *     one quoted. #1C7FC4 carries 4.30 and carries no word anywhere here.
 *   · --sky-deep #10598F on white 7.37 · --sky-soft #4A6373 on white 6.32 and
 *     5.82 on the empty state's #EEF7FD.
 *   · every English line is at least 14px, as this board owes everybody.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── a greeting, above their name ─────────────────────────────────────────
   One line, two languages, and it knows only the clock. See heroCard. */
.shell:has(#view .dash) .hero-hi{
  margin:0 0 5px;display:flex;flex-wrap:wrap;align-items:baseline;gap:0 7px;
}
.shell:has(#view .dash) .hero-hi .ja{font-size:14px;font-weight:700;color:#CFE6F7;line-height:1.35}
.shell:has(#view .dash) .hero-hi .en{
  font-size:15.5px;font-weight:800;color:#FFFFFF;line-height:1.35;letter-spacing:.01em;
}
.shell:has(#view .dash) .hero-hi .en::before{content:"· ";color:#CFE6F7;font-weight:700}
/* 🔴 A SEPARATOR THAT STARTS A LINE IS NOT A SEPARATOR — seen at 390px, where
   the two languages wrapped and the greeting read 「おはようございます」 over
   "· Good morning". Below 560 they stack as two plain lines, which is the
   idiom every other pair on this board uses when it runs out of room. */
@media (max-width:560px){
  .shell:has(#view .dash) .hero-hi{display:block}
  .shell:has(#view .dash) .hero-hi .ja,
  .shell:has(#view .dash) .hero-hi .en{display:block}
  .shell:has(#view .dash) .hero-hi .en::before{content:none}
}

/* ── the photograph in the greeting band ──────────────────────────────────
 * A column of the band, not a banner over it: the words keep their place and
 * the picture takes the width that was empty.
 *
 * 🔴 IT APPEARS WITH THE SECOND COLUMN AND NOT A PIXEL BEFORE — 1180px, the
 * same floor as the layout below, and that is a measured correction. The first
 * version drew it from 900px, where the board is still a 600px ribbon: the
 * picture took 268 of the band's 572, the name broke across two lines and the
 * greeting wrapped so that "· Good morning" began a line with the separator.
 * Found by rendering at 900, 920, 1000, 1024, 1100 and 1179 and looking.
 * The width the picture needs is the BAND'S, not the window's. */
.shell:has(#view .dash) .hero-pic{display:none}
@media (min-width:1180px){
  .shell:has(#view .dash) .hero{
    display:flex;align-items:stretch;padding:0;overflow:hidden;
  }
  .shell:has(#view .dash) .hero-who{flex:1 1 auto;min-width:0;padding:22px 20px}
  .shell:has(#view .dash) .hero-pic{
    display:block;flex:0 0 268px;width:268px;height:auto;object-fit:cover;
    object-position:50% 34%;align-self:stretch;
    border-left:1px solid rgba(255,255,255,.22);
  }
}
@media (min-width:1400px){
  .shell:has(#view .dash) .hero-pic{flex-basis:330px;width:330px}
}

/* ── a photograph at the head of a card, and nowhere else ─────────────────
 * 🔴 NEVER INSIDE A ROW OF DATA — see the note on quietPic in app.js. These
 * are empty states and the heads of two cards that are invitations rather than
 * lists. The record of past tests, the grade cards, the strip and the pay rows
 * are all read by scanning down one edge and none of them gets a picture.
 *
 * The negative margins pull it out to the card's own padding box so it reads
 * as part of the card rather than a stamp inside it; `overflow:hidden` on the
 * parent is what keeps the rounded corner, and it is asked for by `:has()` so
 * that only the cards that actually carry a picture are clipped. */
.shell:has(#view .dash) .card:has(.dpic),
.shell:has(#view .dash) .bquiet:has(.dpic){overflow:hidden}
.shell:has(#view .dash) .dpic{
  display:block;width:calc(100% + 40px);height:104px;object-fit:cover;object-position:50% 38%;
  margin:-18px -20px 14px;border-bottom:1px solid var(--sky-line);
}
/* `.result-card` carries 16px of its own padding-top, so its picture needs the
   matching pull — two pixels, and without them the band sits on a white seam. */
.shell:has(#view .dash) .result-card .dpic{margin-top:-16px}
.shell:has(#view .dash) .result-card.pass .dpic{border-bottom-color:#BFE3D2}
@media (min-width:700px){
  .shell:has(#view .dash) .dpic{height:132px}
}
/* The phone block above tightens `.card` and `.bquiet` to 15px of side padding,
   so the pull has to tighten with it or the picture hangs 5px off each edge. */
@media (max-width:400px){
  .shell:has(#view .dash) .dpic{width:calc(100% + 30px);margin:-15px -15px 12px;height:96px}
  .shell:has(#view .dash) .result-card .dpic{margin-top:-15px}
}
.shell:has(#view .dash) .bquiet .dpic{margin-bottom:15px}
.shell:has(#view .dash) .bquiet p{margin:0}

/* 🔴 A BAND WIDER THAN THE PHOTOGRAPH IS A BLURRED PHOTOGRAPH, and that is a
 * fact about the files, not a taste. `pan-*.jpg` are 440px wide and `object-fit:
 * cover` scales to fill, so an 834px work column blew a 440px crop up to 1.9×
 * and the Help card's picture came back soft — measured by looking at it at
 * 1440. A card that carries one is therefore capped at a width the picture can
 * actually fill: 660px is 1.5× at worst, which a decorative photograph carries.
 * The empty states need no rule — 74ch already holds them to about 530px.
 * `.result-card` is excluded because its picture stops being a band below. */
@media (min-width:1180px){
  .shell:has(#view .dash) .card:has(.dpic):not(.result-card){max-width:660px}
}

/* ── the one picture that is not a band ───────────────────────────────────
 * The celebrating child on a PASS. As a full-width band on a laptop she was
 * blown up to a 400px face; at 208px she is drawn SMALLER than the file, which
 * is the only way a photograph stays crisp. A float, deliberately: `.rc-line`
 * and `.acts` are flex containers, which do not overlap a float, so the band,
 * the score and the two buttons all step aside without a wrapper being added
 * to a card whose shape three other checks read. */
@media (min-width:1180px){
  .shell:has(#view .dash) .result-card .dpic{
    float:right;width:208px;height:170px;object-position:50% 26%;
    margin:-16px -20px 14px 20px;border:0;border-left:1px solid var(--sky-line);
    border-bottom:1px solid var(--sky-line);
  }
  .shell:has(#view .dash) .result-card.pass .dpic{
    border-left-color:#BFE3D2;border-bottom-color:#BFE3D2;
  }
}

/* ── the strip that never hides gets a name ───────────────────────────────
   A framed block with no heading is furniture. Japanese first and English
   under it and larger, exactly as every row inside it already is. */
.shell:has(#view .dash) .todo-h{margin:1px 0 2px}
.shell:has(#view .dash) .todo-h .ja{
  display:block;font-size:13px;font-weight:700;color:var(--sky-soft);line-height:1.35;
}
.shell:has(#view .dash) .todo-h .en{
  display:block;font-size:15.5px;font-weight:800;color:var(--sky-deep);line-height:1.3;
}

/* ── running prose, at a measure a child can read ─────────────────────────
   The width is for CARDS AND LISTS. A sentence gets 74ch wherever it appears,
   which is the measure /about, /books and the landing page all hold to. */
.shell:has(#view .dash) .gc-say,
.shell:has(#view .dash) .card>p,
.shell:has(#view .dash) .bquiet,
.shell:has(#view .dash) .msg{max-width:74ch}

/* ══ TWO COLUMNS, AND THE WIDTH TO PUT THEM IN ════════════════════════════
 * 🔴 `.wrap` IS THE REAL CAP AND IT IS EASY TO MISS. `.dash` said 600px, but
 * `.wrap` says 820px for the whole application, so lifting one without the
 * other moves nothing. The panel escapes the same cap two hundred lines up
 * (`.wrap:has(.adm)`), and this is the same escape for the same reason.
 *
 * 🔴 THE FLOOR AND THE ASIDE WERE SOLVED TOGETHER, not chosen. At 1180px the
 * board gets (viewport − 36 of `.main` padding − 40 of its own) and the aside
 * takes 340 plus a 26px gap, which leaves the work 738px — wide enough for two
 * grade cards at ~356 each, which is a 360px phone's card. Narrower than that
 * and two abreast would make the list TALLER than the single column it
 * replaced, so below 1180 there is one column and nothing changes at all. */
@media (min-width:1180px){
  .wrap:has(.dash){max-width:none}
  .shell:has(#view .dash) .dash{max-width:1240px;padding:0 20px}
  .shell:has(#view .dash) .dash-cols{display:flex;align-items:flex-start;gap:26px}
  .shell:has(#view .dash) .dash-main{flex:1 1 auto;min-width:0}
  /* ORDER, NOT DOM ORDER. The strip is written first so that a phone and a
     screen reader meet WHAT IS WAITING before the work; here it is moved to
     the right without moving the words. */
  .shell:has(#view .dash) .dash-side{
    order:2;flex:0 0 340px;width:340px;min-width:0;
    /* `.main` is the scrolling box, so this sticks inside it. The ceiling is
       the window less the header bar and the trademark fold, both of which sit
       OUTSIDE that box — without them in the sum a tall strip runs past the
       bottom of the window with no way to reach the end of it. */
    position:sticky;top:0;max-height:calc(100dvh - 170px);
    overflow-y:auto;overscroll-behavior:contain;
  }
  /* 🔴 AN EMPTY ASIDE GIVES THE WIDTH BACK. A child with nothing waiting has
     no strip at all — app.js only appends it when it has rows — and a 340px
     empty column narrowing their work would be worse than the ribbon it
     replaced. `:has()` is live, so the column appears the moment a notice or a
     transfer arrives and goes when the last one clears. */
  .shell:has(#view .dash) .dash-side:not(:has(.todo)){display:none}
  .shell:has(#view .dash) .dash:not(:has(.todo)){max-width:900px}

  /* ── two grade cards abreast ──────────────────────────────────────────
     🔴 COLUMNS, NOT A GRID, AND THAT IS THE SAME LESSON SETTINGS TAUGHT. A
     grid row is as tall as the tallest thing in it, and these four states are
     very different heights — the one they can sit carries a sentence and a
     56px button, a locked one carries two lines. Paired off in rows the short
     card beside the tall one leaves the difference as a hole. CSS columns pack
     by height, so the two come out level and nothing has white space under it,
     and `break-inside:avoid` means no card is ever cut in half. */
  .shell:has(#view .dash) .gcards{column-count:2;column-gap:18px}
  .shell:has(#view .dash) .gcards>*{
    break-inside:avoid;-webkit-column-break-inside:avoid;display:block;width:100%;
  }
  /* 🔴 ONE CARD DOES NOT MAKE TWO COLUMNS. A child entered for one grade and
     nothing else — which is every NAKS child until the school opens the grade
     above — had their only card put in the left column with an empty half
     beside it, which reads as something that failed to load. Asked of the
     cards themselves, so it follows the board rather than a number typed
     here. */
  .shell:has(#view .dash) .gcards:not(:has(>:nth-child(2))){column-count:1}
  /* The record of past tests and the tiles keep the full width of the work
     column: they are a table and a summary, and neither halves usefully. */
  .shell:has(#view .dash) .tiles{gap:14px}
}
@media (min-width:1560px){
  .shell:has(#view .dash) .dash-side{flex-basis:376px;width:376px}
}
/* On a big monitor the board may have more of it, but never all of it: the
   prose is already held to 74ch, so the extra goes to the two grade columns
   and to the strip, and a sentence is no longer than it was at 1440. */
@media (min-width:1700px){
  .shell:has(#view .dash) .dash{max-width:1400px}
}

/* ── and the row inside a 340px column still fits a thumb ─────────────────
   The strip's button keeps its 44px and its two lines; what gives is the gap
   either side of it, because a column is narrower than a banner was. */
@media (min-width:1180px){
  .shell:has(#view .dash) .dash-side .u{padding:11px 11px;gap:9px}
  .shell:has(#view .dash) .dash-side .u .go{padding:8px 11px}
}

/* ── and none of the photographs goes on paper ────────────────────────────
   A board is printed to keep the record on it — the grades, the result, the
   dates. A decorative stock photograph on that sheet is a page of somebody
   else's ink, and on the PASS card it is a child who is not the one holding
   the paper. Every word, chip and stripe still prints; see the print block
   further up, which this joins. */
@media print{
  .shell:has(#view .dash) .dpic,
  .shell:has(#view .dash) .hero-pic{display:none!important}
}

/* ══ THE RELEASED RESULT — JAPANESE FIRST, ENGLISH UNDER IT ═════════════════
 *
 * 🔴 1 Oct 2026. This screen was English only: 「Reading — needs work」,
 * 「NOT YET」, 「Estimated CSE」, the 大問 names and every line of advice. It is
 * the one page in this system whose number and whose verdict HAVE to be
 * understood, and it is read by a nine-year-old and by a parent who may read no
 * English at all. See the note at the top of `screenResult` in app.js.
 *
 * THE RULES THESE CLASSES EXIST TO KEEP, because `.small` broke two of them:
 *   · the English is 14px or more. `.small` is 12.5px on the NAKS shell, which
 *     is a caption size — fine for a hint, not for the half of a sentence a
 *     parent is relying on.
 *   · the English has its own colour, `--en`, which is a real ink on both
 *     schemes (#12467a on white, #8FC4F5 after dark) rather than the faint grey
 *     a hint uses.
 *   · the Japanese comes first, on its own line, and is never smaller.
 * Both halves measured in Chromium, light and dark; nothing here is under
 * 4.5:1 and no English is under 14px.
 */
/* 🔴 THE NUMBER DROPS TO ITS OWN LINE RATHER THAN INTERLEAVING. At 390px
   「一次試験（合格ライン 1103）」 wraps onto two lines, and with the number held
   at the top right the eye read 「一次試験（合格ライン  1237 · 合格 · PASS
   1103）」 — the label and the score woven together into one nonsense line.
   Wrapping the row and pushing the number with `margin-left:auto` keeps it
   right-aligned when it fits and gives it a line of its own when it does not. */
.res-kv{align-items:flex-start;flex-wrap:wrap}
.res-tx{flex:1 1 150px;min-width:0;display:flex;flex-direction:column;gap:1px}
.res-tx b{font-size:15.5px;font-weight:700;color:var(--ink);line-height:1.5}
.res-tx span{font-size:14px;font-weight:600;color:var(--en);line-height:1.5}
.res-v{flex:0 1 auto;margin-left:auto;font-variant-numeric:tabular-nums;font-size:15px;
  font-weight:800;color:var(--ink);text-align:right;white-space:nowrap}
/* On a phone the number takes a line of its own, right under its own label and
   right-aligned — ALWAYS, not only when it happens not to fit. Letting it fall
   back and forth between beside and below is what made these rows unreadable:
   「一次試験　合格 1237 · 合格 · PASS 1103」 is two facts woven together. One
   predictable shape, and 「21/30 · 70% · CSE 413」 never has to be squeezed. */
@media (max-width:520px){
  .res-tx{flex:1 1 100%}
  .res-v{margin-top:2px}
}

/* a section heading: 「読解 — どこで点を落としたか」 over its English */
.res-h{margin:0 0 10px}
.res-h b{display:block;font-size:16.5px;font-weight:800;color:var(--navy);line-height:1.35}
.res-h span{display:block;font-size:14px;font-weight:600;color:var(--en);line-height:1.45}

/* the advice and the estimate notice — two halves, both readable */
.res-small{margin:0;line-height:1.7}
.res-small b{display:block;font-size:14.5px;font-weight:600;color:var(--ink);line-height:1.7}
.res-small span{display:block;font-size:14px;font-weight:500;color:var(--en);line-height:1.7}

/* 「次に取り組むこと」 — one numbered thing to practise */
.res-focus{padding:10px 0;border-bottom:1px solid var(--line)}
.res-focus:last-child{border-bottom:0}
.res-focus b{font-size:15.5px;font-weight:800;color:var(--ink);line-height:1.45}
.res-focus-en{display:block;font-size:14px;font-weight:600;color:var(--en);line-height:1.45}

/* 合格 / あと一歩 — the verdict. The band word is large, in both languages, and
   the colour is never the only thing that says which it is: the word does.
   Both halves inherit the message box's own ink, because `--en` is a blue and
   this box may be red. */
/* 🔴 AND IT DOES NOT INHERIT `--ok` — 1 Oct 2026, measured.
   `--ok` (#1F7A45) on `--okbg` (#E2EFDA) is 4.48:1 — a hair under the 4.5 this
   site owes every reader, which nobody would ever notice on a chip or a border
   and which is not good enough for the single largest, most consequential word
   on a child's result. A darker green on the same field reads 6.20:1. The
   TOKEN is left alone deliberately: it is used by every `.msg.ok` on the site
   and moving it is a change to twenty screens, not to this one. The red half
   already clears it (#C00000 on #FCE4E4 is 5.35:1) and after dark both are
   above 6.8:1, so only the light-mode green is named. */
.res-verdict{display:block;text-align:center}
.res-verdict.ok{color:#176237}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .res-verdict.ok{color:var(--ok)}
}
:root[data-theme="dark"] .res-verdict.ok{color:var(--ok)}
.res-band-ja{display:block;font-size:26px;font-weight:800;line-height:1.25}
.res-band-en{display:block;font-size:17px;font-weight:700;line-height:1.3;letter-spacing:.02em}
.res-band-n{font-size:15px;font-weight:600;margin-top:4px;font-variant-numeric:tabular-nums}

/* the two notes above the card — both halves, both readable */
.res-note b{display:block;font-size:15px;font-weight:700;color:var(--ink);line-height:1.6}
.res-note span{display:block;font-size:14px;font-weight:600;color:var(--en);line-height:1.6;
  margin-top:2px}

/* ══ 3D BUTTONS, THE SAME AS THE SCHOOL PANEL — Razib, 20 Oct 2026 ══════════
 * Every button on the site sits on a small shelf and presses down when touched,
 * like mapp's. Colours are untouched (the contrast test measures them); only
 * the shape, the shadow and the press change. */
.btn,.btn-s{border-radius:14px;box-shadow:0 4px 0 rgba(0,0,0,.18),0 6px 14px rgba(0,0,0,.07);
  transition:transform .08s ease,box-shadow .08s ease}
.btn:active:not(:disabled),.btn-s:active:not(:disabled){transform:translateY(3px);box-shadow:0 1px 0 rgba(0,0,0,.18)}
.btn.ghost{box-shadow:0 3px 0 rgba(0,0,0,.10)}
.btab{border-radius:16px;box-shadow:0 5px 0 rgba(0,0,0,.10),0 8px 16px rgba(0,0,0,.06);transition:transform .08s ease,box-shadow .08s ease}
.btab:active{transform:translateY(3px);box-shadow:0 1px 0 rgba(0,0,0,.10)}
.btn:disabled,.btn-s:disabled{box-shadow:none}
.gc-sheet{margin-top:10px;display:flex;align-items:center;justify-content:center;text-decoration:none;
  background:var(--surface);border:2px solid var(--navy2);color:var(--navy2);font-weight:800}
.gc-sheet-note{margin:6px 0 0;font-weight:700}
@media (prefers-color-scheme:dark){.btn,.btn-s,.btab{box-shadow:0 4px 0 rgba(0,0,0,.45)}}
@media (prefers-reduced-motion:reduce){.btn,.btn-s,.btab{transition:none}}

/* ══ mobile-pwa-v1 — 24 Oct 2026: the bar, on a phone, for people who came from mapp ══════════
 *
 * Razib, 24 Oct: going from mapp to EIKEN, the site was not mobile responsive — it broke in various
 * ways and the page went far down. Measured at 360px before this block: the board, the brief,
 * the result and every staff screen were 154–194px WIDER than the phone. The cause was the 19 Oct
 * "Back to mapp" button — written with an inline `white-space:nowrap; margin-left:auto` into a bar
 * that never wraps — so the browser zoomed the whole page out, Log out stood one letter per line, and
 * the screen looked broken and pushed down. Outsiders and class logins get no such button, which is
 * why the fault appeared ONLY for people arriving from mapp.
 *
 * Now: name on the left, the buttons in one box (#topActs); on a phone that box takes its own full row,
 * two equal buttons, each at least 44px tall. Nothing about the exam shell changes. */
.top{flex-wrap:wrap;row-gap:8px}
.top > div:not(.spacer):not(.links):not(.topacts){min-width:0;flex:0 1 auto}
.top .sub{max-width:100%}
.topacts{display:flex;align-items:center;gap:8px;flex:0 0 auto}
.topacts:not(:has(> :not(.hide))){display:none}
.btn-mapp{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:44px;padding:4px 14px;
  border-radius:14px;background:#1773B4;color:#FFFFFF;font-weight:800;font-size:14px;line-height:1.2;
  text-decoration:none;box-shadow:0 4px 0 #0B4470;text-align:center}
.btn-mapp:active{transform:translateY(3px);box-shadow:0 1px 0 #0B4470}
.btn-mapp .bm-en{color:inherit}
.btn-mapp .bm-t::after{content:' · '}
#btnLogout{min-height:44px}
@media (max-width:640px){
  .top{padding:8px 12px}
  .top .spacer{flex:1 1 0}
  .topacts{flex:1 1 100%;order:9}
  .topacts > *{flex:1 1 0;min-width:0}
  .btn-mapp{flex-direction:column;gap:0;padding:4px 8px;font-size:13.5px}
  .btn-mapp .bm-ico{display:none}
  .btn-mapp .bm-t::after{content:''}
  .topacts #btnLogout{white-space:normal;line-height:1.2;padding:4px 8px}
}
@media (prefers-reduced-motion:reduce){.btn-mapp:active{transform:none}}

/* the staff overview charts asked for 320px columns, which a 320px phone (minus its padding) does not have:
   the column now never asks for more than the room there is */
.shell:has(#view .adm) .ov-charts{grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr))}

/* The answer rows and Next buttons a child presses during the paper: legibility and thumb size only,
 * never the look (the exam screen rehearses the real S-CBT on purpose). */
@media (pointer:coarse){
  .exopt, .exbtn, .exflag, .exfinish{min-height:44px}
}

/* ══ the "use it as an app" card (public/install.js) ═══════════════════════════════════════ */
.pwa-install{margin:14px 0;padding:14px 16px;border-radius:16px;background:#FFFFFF;color:#16323F;
  border:2px solid #CFE2EF;box-shadow:0 4px 14px rgba(16,89,143,.08);text-align:left}
.pwa-install .pi-h{display:flex;align-items:center;gap:10px;margin:0 0 6px}
.pwa-install .pi-h img{width:40px;height:40px;border-radius:10px;flex:0 0 auto}
.pwa-install .pi-t{font-weight:800;font-size:16px;line-height:1.3;color:#10598F}
.pwa-install .pi-t .en{display:block;font-size:14.5px;color:#16323F;font-weight:700}
.pwa-install p{margin:6px 0;font-size:14.5px;line-height:1.5;color:#16323F}
.pwa-install p .en{display:block;color:#4A6373}
.pwa-install .pi-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
.pwa-install .pi-go{flex:1 1 180px;min-height:48px;border:0;border-radius:14px;background:#EE8A1E;color:#16305E;
  font-weight:800;font-size:15px;line-height:1.25;cursor:pointer;box-shadow:0 4px 0 #B5650F;padding:6px 12px}
.pwa-install .pi-go .en{display:block;color:inherit;font-size:13.5px}
.pwa-install .pi-later{flex:0 1 auto;min-height:48px;border:1px solid #CFE2EF;border-radius:14px;background:#F2F8FC;
  color:#10598F;font-weight:700;font-size:14px;padding:6px 14px;cursor:pointer}
.pwa-install ol{margin:6px 0 0;padding-left:22px;font-size:14.5px;line-height:1.6}
.pwa-install ol .en{display:block;color:#4A6373;font-size:14px}
.pwa-install[hidden]{display:none !important}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .pwa-install{background:#13283A;color:#E7F0F7;border-color:#2B4A63}
  :root:not([data-theme="light"]) .pwa-install .pi-t{color:#BDDCFA}
  :root:not([data-theme="light"]) .pwa-install .pi-t .en,
  :root:not([data-theme="light"]) .pwa-install p,
  :root:not([data-theme="light"]) .pwa-install ol{color:#E7F0F7}
  :root:not([data-theme="light"]) .pwa-install p .en,
  :root:not([data-theme="light"]) .pwa-install ol .en{color:#BDDCFA}
  :root:not([data-theme="light"]) .pwa-install .pi-later{background:#0F2531;color:#BDDCFA;border-color:#2B4A63}
}
:root[data-theme="dark"] .pwa-install{background:#13283A;color:#E7F0F7;border-color:#2B4A63}

/* an installed app has no browser bar: keep clear of the notch and the home indicator */
@media (display-mode:standalone){
  .top{padding-top:calc(8px + env(safe-area-inset-top,0px))}
  body{padding-bottom:env(safe-area-inset-bottom,0px)}
}

/* iPhone Safari zooms the whole page into any box whose text is under 16px and does not zoom back;
   a child then sees half a screen. Every typing box is 16px on a phone (mobile-pwa-v1). */
@media (max-width:640px){
  input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=file]), select, textarea{font-size:16px}
}
