/* fr.brianychang.com — core.css: the one stylesheet every page loads.

   Layout: the accent pairs (one per game, one for the hub, chosen by
   <html data-game="…">), then shared/style/base.css verbatim — tokens, type,
   cards, buttons, header, menus, dialog, sheet, result, board, stats — then
   the rules two or more of the old app.css files carried (home options, the
   curriculum list, pips, typed input, lobby code and between-rounds board),
   then the hub's own tiles and the privacy prose. Admin-only rules live in
   admin.css; each game's own rules in <id>.css.

   One rule: a game is identified by its accent colour and by nothing else. */

/* ---------- Accents: hub neutral, NEF green, FEF blue, PEF plum ----------
   `:root` and `[data-game=…]` tie on specificity, so the hub's pair is the
   default and each game's follows and wins. */
:root, [data-game="zh"] { --accent: #a03c2e; --accent-fg: #ffffff; }
@media (prefers-color-scheme: dark) {
  :root, [data-game="zh"] { --accent: #e08a7a; --accent-fg: #2a120c; }
}

/* ==== BEGIN shared/style/base.css — synced by tools/sync_style.py; edit the
   original, not this copy ==== */
/* shared/style/base.css — the look every game and the hub share.

   One rule: a game is identified by its accent colour and by nothing else.
   Everything here — tokens, type, cards, buttons, the result and scoreboard
   shapes, the stats summary — is the same in NEF, FEF, PEF and on the landing
   page. `--accent` / `--accent-fg` are NOT set here; each app sets its own pair
   (light and dark) in its own app.css, above the synced block.

   This file is COPIED into each app's app.css by tools/sync_style.py, between
   two marker comments, so every app still uploads as one self-contained
   directory. Edit it here, run the sync, never edit the copy. */

:root {
  color-scheme: light dark;
  --bg: #fbfaf8;
  --fg: #1b1a18;
  --mut: #6b6862;
  --line: #e3dfd8;
  --card: #ffffff;
  --tile: #ffffff;
  --good: #2f6f4e;
  --bad: #b23a34;
  --warm: #c8792a;
  --shadow: 0 1px 2px rgba(20,18,14,.06), 0 4px 14px rgba(20,18,14,.05);
  --r: .8rem;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #15161a;
    --fg: #ecebe7;
    --mut: #97948d;
    --line: #2b2d33;
    --card: #1d1f24;
    --tile: #23262c;
    --good: #5fb98a;
    --bad: #e2726a;
    --warm: #e0a45c;
    --shadow: 0 1px 2px rgba(0,0,0,.35), 0 6px 18px rgba(0,0,0,.28);
  }
}

* { box-sizing: border-box; }
/* Screens and several buttons are toggled with the `hidden` attribute, and a
   class that sets `display` beats the UA rule for it on equal specificity.
   .primary alone would keep a hidden button on screen. */
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  padding: 0 1rem env(safe-area-inset-bottom);
  -webkit-tap-highlight-color: transparent;
}
main { max-width: 34rem; margin: 0 auto; padding-bottom: 3rem; }

/* ---------- Header ---------- */
.top {
  max-width: 34rem; margin: 0 auto;
  display: flex; align-items: center; gap: .75rem;
  padding: 1rem 0 .5rem;
}
.brand {
  appearance: none; background: none; border: 0; padding: 0; margin: 0;
  display: flex; align-items: center; gap: .55rem; cursor: pointer;
  color: inherit; font: inherit; flex: 1; text-align: left;
  text-decoration: none;
}
.brand-mark {
  display: grid; place-items: center;
  width: 2rem; height: 2rem; border-radius: .5rem;
  background: var(--accent); color: var(--accent-fg);
  font-weight: 700; font-size: .9rem; font-variant-numeric: tabular-nums;
}
.brand-name { font-weight: 650; letter-spacing: -.01em; }
.chip {
  appearance: none; border: 1px solid var(--line); background: var(--card);
  color: var(--mut); font: inherit; font-size: .8rem;
  padding: .3rem .7rem; border-radius: 999px; cursor: pointer;
  max-width: 11rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The menu under the name chip: who you are, and the two or three things you
   can do about it. Same on the hub and in every game. */
.top { position: relative; }
.who-menu {
  position: absolute; right: 0; top: calc(100% - .25rem); z-index: 5;
  min-width: 14rem; padding: .6rem; border-radius: .7rem;
  background: var(--card); border: 1px solid var(--line); box-shadow: var(--shadow);
}
.who-menu .who-id { font-size: .82rem; color: var(--mut); padding: .25rem .5rem .55rem; border-bottom: 1px solid var(--line);
  margin-bottom: .35rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who-menu .who-id b { display: block; color: var(--fg); font-weight: 600; }
.who-menu button, .who-menu a {
  appearance: none; display: block; width: 100%; text-align: left; background: none; border: 0;
  padding: .5rem .5rem; border-radius: .45rem; color: var(--fg); font-size: .9rem;
  text-decoration: none; font: inherit; cursor: pointer;
}
.who-menu button:hover, .who-menu button:focus-visible,
.who-menu a:hover, .who-menu a:focus-visible { background: var(--bg); }
/* The admin link is not a thing most people have; when it is there it should
   read as a place to go, not as another setting. */
.who-menu a#adminLink { color: var(--accent); font-weight: 600; }
.who-menu button.danger { color: var(--bad); font-size: .8rem; margin-top: .35rem; border-top: 1px solid var(--line); border-radius: 0; padding-top: .6rem; }
/* Two careful things in a row are ONE block, not two. The rule and the gap
   belong above the first of them; a separator between them would read as two
   unrelated warnings rather than one area you are meant to slow down in. */
.who-menu button.danger + button.danger { margin-top: 0; border-top: 0; padding-top: .35rem; }

/* The app navigator, under the brand mark. Every app carries the same list,
   and each row wears its own game's colour rather than the host app's accent
   — the marks are how you tell the games apart everywhere else.

   The negative offset is deliberate: it cancels the menu's own padding so a
   row's mark sits exactly under the brand mark that opened it, and the column
   of icons reads as one line rather than two that nearly agree. */
.nav-menu {
  position: absolute; left: calc(-.95rem - 1px); top: calc(100% - .1rem); z-index: 6;   /* 1px = the menu's own border */
  min-width: 15rem; max-width: min(22rem, calc(100vw - 1.2rem)); padding: .45rem;
  border-radius: .7rem; background: var(--card); border: 1px solid var(--line); box-shadow: var(--shadow);
}
.nav-item {
  display: flex; align-items: center; gap: .55rem; width: 100%; text-align: left;
  padding: .4rem .5rem; border: 0; background: none; border-radius: .5rem;
  color: inherit; text-decoration: none; font: inherit;
}
a.nav-item:hover, button.nav-item:hover, .nav-item:focus-visible { background: var(--bg); }
/* Same size and shape as the header's own mark, so the two line up. */
.nav-item .brand-mark { width: 2rem; height: 2rem; border-radius: .5rem; flex: none; }
.nav-item b { font-size: .92rem; font-weight: 600; letter-spacing: -.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-item.current { background: var(--bg); }
.nav-hub .brand-mark { background: #3b3835; color: #fff; font-size: .85rem; }
.nav-nef .brand-mark { background: #2f6f4e; color: #fff; font-size: .9rem; }
.nav-fef .brand-mark { background: #2f5aa8; color: #fff; font-size: .78rem; letter-spacing: -.02em; }
.nav-pef .brand-mark { background: #8a3f6a; color: #fff; font-size: .8rem; letter-spacing: -.02em; }
@media (prefers-color-scheme: dark) {
  .nav-hub .brand-mark { background: #d9d4cb; color: #15161a; }
  .nav-nef .brand-mark { background: #5fb98a; color: #10231a; }
  .nav-fef .brand-mark { background: #7fa3e8; color: #0f1a2e; }
  .nav-pef .brand-mark { background: #d78bb6; color: #2a1020; }
}

/* The footer, on every page of every app. */
.foot {
  max-width: 34rem; margin: 2.5rem auto 1.5rem;
  display: flex; justify-content: center; align-items: baseline; flex-wrap: wrap;
  gap: .5rem; font-size: .78rem; color: var(--mut);
}
.foot a { color: var(--mut); text-decoration: underline; text-underline-offset: 3px; }
.foot .sep { opacity: .45; }

/* The privacy sheet. Rather than four copies of the terms, or a page that
   costs you the round you were in the middle of, the link FETCHES
   privacy.html and shows its <main> in place. The link is a real link, so
   with no script (or a failed fetch) it simply opens the page as before. */
.sheet-back {
  position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,.45);
  display: grid; place-items: end center;
}
.sheet {
  width: 100%; max-width: 38rem; max-height: 88vh; overflow-y: auto;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r) var(--r) 0 0; padding: 0 1.2rem 2rem;
}
@media (min-width: 40rem) {
  .sheet-back { place-items: center; padding: 1rem; }
  .sheet { border-radius: var(--r); }
}
.sheet-head {
  position: sticky; top: 0; z-index: 1; margin: 0 -1.2rem .4rem; padding: .85rem 1.2rem;
  background: var(--card); border-bottom: 1px solid var(--line);
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
}
.sheet-head b { font-size: 1rem; }
.sheet-head .link { margin: 0; padding: 0; }
.sheet h1 { display: none; }              /* the sheet header says it already */
.sheet h2 { margin-top: 1.4rem; font-size: 1rem; }
.sheet p, .sheet li { font-size: .92rem; }
.sheet ul { padding-left: 1.2rem; }
.sheet li { margin: .35rem 0; }
.sheet .hub-link, .sheet .foot { display: none; }

/* In-page dialog (see ask() in each app's script). */
.dlg-back { position: fixed; inset: 0; z-index: 50; background: rgba(0,0,0,.45); display: grid; place-items: center; padding: 1rem; }
.dlg { width: 100%; max-width: 22rem; background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: 1.1rem; box-shadow: var(--shadow); }
.dlg-msg { margin: 0 0 .9rem; font-size: .95rem; line-height: 1.45; }
.dlg input { margin-bottom: .9rem; }
.dlg-row { display: flex; gap: .6rem; }
.dlg-row .primary, .dlg-row .ghost { flex: 1; margin-top: 0; padding: .65rem .8rem; }
.dlg-row .primary.danger { background: var(--bad); border-color: var(--bad); color: #fff; }

/* ---------- Type ---------- */
h1 { font-size: 1.6rem; line-height: 1.2; letter-spacing: -.025em; margin: .5rem 0 .6rem; }
h2 { font-size: 1.05rem; margin: 0 0 .75rem; letter-spacing: -.01em; }
.lede { color: var(--mut); margin: 0 0 1.25rem; }
.label { color: var(--mut); font-size: .78rem; text-transform: uppercase;
         letter-spacing: .07em; margin: 0 0 .3rem; }
.fineprint { color: var(--mut); font-size: .82rem; margin: .75rem 0 0; }
.fineprint a.plain { color: var(--mut); text-decoration: underline; text-underline-offset: 3px; }
/* A link-button sitting inside a line of fineprint reads as a word, not a control. */
.fineprint .link { display: inline; padding: 0; margin: 0; font-size: inherit; }
.err { color: var(--bad); font-size: .9rem; margin: .85rem 0 0; }

/* The version notice. Sits above whatever screen is up, and only appears when
   an automatic refresh has already been tried and did not take. */
.stale {
  margin: .75rem 0 0; padding: .7rem .85rem;
  border: 1px solid var(--warm); border-radius: .6rem;
  background: color-mix(in srgb, var(--warm) 12%, var(--card));
  font-size: .85rem;
}
.stale .link { display: inline; margin: 0 0 0 .15rem; padding: 0; color: var(--fg); }

/* ---------- Cards & forms ---------- */
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r); padding: 1.1rem; margin-bottom: 1rem;
  box-shadow: var(--shadow);
}
.form label {
  display: block; font-size: .82rem; color: var(--mut);
  margin: .85rem 0 .3rem; font-weight: 550;
}
.form label:first-of-type { margin-top: 0; }
.opt { font-weight: 400; text-transform: none; letter-spacing: 0;
       opacity: .65; font-size: .95em; }
/* A card whose first child is the button itself needs no leading gap. */
.card > .ghost:first-child, .card > .primary:first-child { margin-top: 0; }
input {
  width: 100%; font: inherit; color: var(--fg);
  background: var(--bg); border: 1px solid var(--line);
  border-radius: .55rem; padding: .7rem .8rem;
}
input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
#joinCode, #lobbyCode { text-transform: uppercase; letter-spacing: .18em; font-weight: 700; }

/* ---------- Buttons ---------- */
button { font: inherit; cursor: pointer; }
.primary, .ghost {
  display: block; width: 100%; margin-top: 1rem;
  padding: .8rem 1rem; border-radius: .6rem;
  font-weight: 600; text-align: center;
  transition: transform .06s ease, filter .15s ease;
}
.primary { background: var(--accent); color: var(--accent-fg); border: 1px solid var(--accent); }
.ghost   { background: transparent; color: var(--fg); border: 1px solid var(--line); }
.primary:active, .ghost:active { transform: scale(.985); }
.primary[disabled], .ghost[disabled] { opacity: .45; cursor: default; transform: none; }
.big { text-align: left; padding: 1rem 1.1rem; }

/* The home menu's rows are choices of equal standing, not one shout and two
   whispers. Filling the first with the accent made it read as a HEADING rather
   than a control — worst on a phone, where it is the widest thing on screen
   and sits directly under the title. So every row wears the same quiet frame,
   and the accent arrives on contact instead: the colour now means "you are
   touching this", which is information, where before it meant nothing.

   Only .big is affected. Submit, Continue and Next round are .primary WITHOUT
   .big, and they stay filled — those genuinely are the one thing to do next. */
.big.primary, .big.ghost {
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--line);
  transition: transform .06s ease, border-color .15s ease, background-color .15s ease;
}
/* Hover only where hovering exists. On a touch screen :hover latches after a
   tap, so the row you just left keeps looking pressed. */
@media (hover: hover) {
  .big.primary:not([disabled]):hover, .big.ghost:not([disabled]):hover {
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 10%, transparent);
  }
}
.big.primary:not([disabled]):active, .big.ghost:not([disabled]):active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 20%, transparent);
}
/* Keyboard users get the same signal a pointer gets. */
.big.primary:focus-visible, .big.ghost:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.big b { display: block; font-size: 1.02rem; }
.big small { display: block; font-weight: 400; font-size: .84rem; opacity: .8; margin-top: .15rem; }
.menu { padding: .5rem 1.1rem 1.1rem; }
/* A link-button standing on its own is centred: it is nearly always the one
   quiet exit under a card ("Back to menu", "← Back", "Leave game"), and left
   aligned it reads as the start of another column of content. The inline uses
   below (inside .fineprint, .stale, a .bulk row) set their own display and are
   unaffected. */
.link {
  appearance: none; background: none; border: 0; color: var(--mut);
  display: block; margin: .5rem auto 0; padding: .5rem 0; text-align: center;
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
/* An escape hatch that has to be findable the moment it is wanted, and
   invisible the rest of the time. Centred and small: present, not competing
   with the primary action. */
.link.quiet { display: block; margin: 1.5rem auto 0; font-size: .78rem; opacity: .6; }

/* The way back to the hub. One anchor, on the sign-in and home screens only —
   the hub does not follow you into a game. Same voice as .link.quiet, with the
   arrow doing the pointing. */
.hub-link {
  display: block; width: fit-content; margin: 1.25rem auto 0;
  color: var(--mut); font-size: .8rem; text-decoration: none;
  padding: .35rem .6rem; border-radius: 999px;
}
.hub-link:hover, .hub-link:focus-visible { color: var(--fg); background: var(--card); }
.hub-link::before { content: '‹ '; }

/* ---------- Result ----------
   The score is the headline. The verdict sits above it as a caption and the
   breakdown below it in fine print, so the number is the thing you see first. */
.result { text-align: center; }
.result .review { text-align: left; }
.verdict { font-size: 1.05rem; font-weight: 650; margin: 0; color: var(--mut);
           letter-spacing: -.01em; }
.score-hero { margin: 0; display: flex; align-items: baseline; justify-content: center;
              gap: .4rem; }
.score-hero b {
  font-size: 3.2rem; font-weight: 800; line-height: 1.05;
  letter-spacing: -.045em; font-variant-numeric: tabular-nums;
  color: var(--accent);
}
.score-hero span { color: var(--mut); font-size: .9rem; font-weight: 600; }
.scoreline { color: var(--mut); font-size: .85rem; margin: .25rem 0 1.1rem; }

/* The one outcome that proves it was skill and not the retries: its own banner. */
.firstgo {
  display: block; margin: 0 0 .75rem; padding: .6rem .8rem;
  border-radius: .6rem; border: 1px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--card));
  animation: firstgo-in .32s ease-out;
}
.firstgo b { display: block; font-size: 1.15rem; letter-spacing: -.02em; color: var(--accent); }
.firstgo span { display: block; font-size: .8rem; color: var(--mut); margin-top: .1rem; }
@keyframes firstgo-in {
  from { opacity: 0; transform: translateY(-.35rem) scale(.97); }
  to   { opacity: 1; transform: none; }
}
.review { list-style: none; margin: 0; padding: 0; }
.review li {
  display: flex; align-items: baseline; gap: .6rem;
  padding: .55rem 0; border-top: 1px solid var(--line);
}
.review li:first-child { border-top: 0; }
.review .mark { width: 1.1rem; font-weight: 700; }
.review .hit .mark { color: var(--good); }
.review .miss .mark { color: var(--bad); }

/* ---------- Scoreboard ---------- */
.board { margin: .25rem 0 0; }
.board .row {
  display: flex; align-items: center; gap: .6rem;
  padding: .55rem 0; border-top: 1px solid var(--line);
}
.board .row:first-child { border-top: 0; }
.board .who { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board .who b { font-weight: 600; }
.board .tag { font-size: .72rem; color: var(--mut); margin-left: .35rem; }
.board .pts { font-variant-numeric: tabular-nums; font-weight: 650; }
.board .dot { width: .5rem; height: .5rem; border-radius: 999px; background: var(--line); flex: none; }
.board .dot.on { background: var(--good); }
.board .dot.wait { background: var(--warm); }

/* ---------- Stats ---------- */
/* Two by two on a phone, one row on anything wider. A flex wrap left the
   fourth tile stranded on its own line whenever the values were short. */
.summary { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem 1.25rem; }
@media (min-width: 30rem) { .summary { grid-template-columns: repeat(4, 1fr); } }
.summary div b { display: block; font-size: 1.5rem; font-variant-numeric: tabular-nums;
                 letter-spacing: -.02em; }
.summary div span { color: var(--mut); font-size: .8rem; }
/* The "what to work on" list. Rows, not chips: the labels are long and ragged
   chip widths make the column impossible to scan. */
.weak { display: flex; flex-direction: column; }
.weak .w {
  display: flex; align-items: baseline; gap: .55rem;
  padding: .5rem 0; border-top: 1px solid var(--line);
}
.weak .w:first-child { border-top: 0; }
/* The label and the figures. One line on a wide screen; on a narrow one the
   figures drop to a second line rather than eat the label. */
.weak .w .body { flex: 1; display: flex; flex-wrap: wrap; align-items: baseline;
                 justify-content: space-between; gap: 0 .5rem; }
.weak .w .meta { margin-left: auto; font-size: .8rem; white-space: nowrap;
                 color: var(--mut); font-variant-numeric: tabular-nums; }
/* The combined score leads and carries the colour, because it is what the list
   is sorted by. The figures after it are the breakdown and stay muted —
   colouring "100% right" made a thing that is merely SLOW look like a mistake. */
.weak .w .score { color: var(--bad); font-weight: 650; }
.weak .w .rate.bad { color: var(--bad); }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
/* ==== END shared/style/base.css ==== */

/* ---------- The brand mark, per page ----------
   `fr` on the hub, `99` / `ent` / `pas` in the games. Letters set a little
   smaller than a number so three of them fit the same square. Scoped to the
   header's own mark: the navigator rows size theirs above, in base. */
/* The brand mark is the seal, not a letterform. Inline SVG rather than a
   mask or a background image: it inherits --accent-fg through currentColor,
   the well is a hole in the path so the accent shows through it, and there is
   no second file to load or CSP rule to satisfy. */
.brand-mark > .mark { width: 74%; height: 74%; display: block; color: var(--accent-fg); }
[data-game="hub"] .brand > .brand-mark { font-size: .85rem; letter-spacing: -.02em; }
[data-game="fef"] .brand > .brand-mark { font-size: .78rem; letter-spacing: -.02em; }
[data-game="pef"] .brand > .brand-mark { font-size: .8rem; letter-spacing: -.02em; }

/* ---------- Shared by the games (FEF and PEF carried these verbatim) ---------- */
/* A heading standing on its own rather than inside a card. */
.screen-title { font-size: 1.25rem; margin: .5rem 0 .35rem; letter-spacing: -.02em; }

/* Two quiet exits on one line under the play screen. .quiet centres itself
   with an auto margin, which is right when it is alone and fights the row when
   it is not, so the row takes the spacing over. */
.playfoot .link.quiet { margin: 0; }
.playfoot .link.quiet[disabled] { text-decoration: none; opacity: .5; }

/* The readiness nudge. The engine has the figures to know when a stage is
   done; unlocking the next one is still the player's tap. */
.nudge {
  display: block; width: 100%; text-align: left;
  margin: 0 0 1rem; padding: .8rem .95rem;
  border: 1px solid var(--accent); border-radius: .6rem;
  background: color-mix(in srgb, var(--accent) 10%, var(--card));
  color: var(--fg); font-weight: 550; font-size: .9rem;
}
.nudge span { display: block; font-weight: 400; font-size: .82rem;
              color: var(--mut); margin-top: .15rem; }

/* The focus chips: which slice of the unlocked material is in play today. */
.focus { display: flex; flex-wrap: wrap; gap: .35rem; margin: 0 0 1rem; }
.focus button {
  appearance: none; font: inherit; font-size: .82rem;
  border: 1px solid var(--line); background: var(--card); color: var(--mut);
  border-radius: 999px; padding: .3rem .8rem;
}
.focus button.on {
  border-color: var(--accent); color: var(--accent); font-weight: 600;
  background: color-mix(in srgb, var(--accent) 12%, var(--card));
}

/* The play header: a label and the round's progress. */
.playhead { display: flex; justify-content: space-between; align-items: center;
            margin: .25rem 0 .9rem; }
/* One pip per item, filled as you go. They carry the round's whole state. */
.pips { display: flex; gap: .3rem; }
.pips i {
  width: .55rem; height: .55rem; border-radius: 999px;
  background: var(--line); display: block;
}
.pips i.hit { background: var(--good); }
.pips i.miss { background: var(--bad); }
.pips i.now { background: var(--accent); transform: scale(1.25); }
.next { margin-top: 1.2rem; }

/* The typed-answer input. Big, because it replaces the tap targets with one. */
/* ---------- Typing, with a keyboard covering half the screen ----------
   Focusing an input makes the browser scroll it into view. On a phone the
   keyboard has just eaten most of the viewport, so "into view" can mean the
   sentence being asked about scrolls off the top — leaving you typing an
   answer to a question you can no longer see.

   Three things stop that. The viewport meta (interactive-widget=resizes-content)
   asks the browser to REFLOW the page into the space the keyboard leaves,
   rather than keep a tall layout and scroll around inside it. scroll-margin-top
   reserves room above the field, so when the browser does scroll it into view
   the prompt comes too. And the play screen drops its bottom padding, because
   every spare pixel below the answer is somewhere the question can be scrolled
   away to. */
.typed input { scroll-margin-top: 12rem; scroll-margin-bottom: 1rem; }
#screen-play { padding-bottom: 0; }
#screen-play ~ .foot, #screen-play:not([hidden]) ~ .foot { margin-top: .75rem; }

.typed { display: flex; gap: .6rem; align-items: stretch; }
.typed .primary { margin-top: 0; width: auto; padding-left: 1.4rem; padding-right: 1.4rem; }
.typed input.ok  { border-color: var(--good); color: var(--good); }

/* The options row on the home menu: round length and typed mode. A row rather
   than a .big button, because it sets a mode rather than going anywhere. */
/* The practice row and the two settings that change it, as one box.

   Three placements were wrong before this one. Between the first tile and the
   rest with a card-wide rule above them, the rule read as a section break and
   `S’entraîner` became a heading for the settings. Above the card, the strip
   read as a subtitle of the page title. At the foot of the card, it was tidy
   but attached to nothing — round length and typed mode change the PRACTICE
   round and nothing else, and that has to be visible.

   So the options are a tray hanging off the bottom of the button: same width,
   no gap, top corners squared and the shared edge suppressed, on the recessed
   page background so they read as settings rather than as another choice. */
.play { margin-top: 1rem; }
.card > .play:first-child { margin-top: 0; }
.play > .big {
  margin-top: 0;
  border-bottom-left-radius: 0; border-bottom-right-radius: 0;
}
.opt-row {
  display: flex; gap: .4rem 1.1rem; align-items: center; justify-content: center;
  flex-wrap: wrap;
  padding: .5rem 1rem .55rem;
  background: var(--bg);
  border: 1px solid var(--line); border-top: 0;
  border-radius: 0 0 .6rem .6rem;
  font-size: .78rem; font-weight: 550; color: var(--mut);
}
.opt-row label { display: flex; gap: .45rem; align-items: center; cursor: pointer; }
.opt-row input[type="checkbox"] {
  width: .95rem; height: .95rem; margin: 0; flex: none; accent-color: var(--accent);
}
.opt-row .tick:has(input:checked) { color: var(--accent); font-weight: 650; }
.rounds input[type="range"] {
  width: 4.5rem; padding: 0; margin: 0; background: none; border: 0;
  accent-color: var(--accent);
}
.rounds #roundLenLabel {
  min-width: 1.1rem; text-align: right;
  font-variant-numeric: tabular-nums; font-weight: 650; color: var(--fg);
}
.rounds .cap { white-space: nowrap; }

/* Result review rows: the item, its context, and what you picked. */
.review li { padding: .6rem 0; }
.review .body { flex: 1; min-width: 0; }
.review .form { font-weight: 650; }
.review .ctx { color: var(--mut); font-size: .82rem; display: block; }

/* ---------- Lobby & board ---------- */
.code { font-size: 2.2rem; margin: 0 0 .5rem; font-variant-numeric: tabular-nums; }
/* How far into the round someone is, for the games that commit each item. */
.board .at { font-size: .72rem; color: var(--warm); margin-left: .35rem;
             font-variant-numeric: tabular-nums; }
/* The between-rounds board sits outside a card, so it brings its own. */
#resultBoard { background: var(--card); border: 1px solid var(--line);
               border-radius: var(--r); padding: .35rem 1.1rem; margin-bottom: 1rem; }
#resultBoard:empty { display: none; }

/* ---------- The curriculum: stage rows ---------- */
.stagebox {
  background: var(--card); border: 1px solid var(--line);
  border-radius: .45rem; margin-bottom: .25rem; overflow: hidden;
}
.stagebox.on { border-color: var(--accent); }
.stage {
  display: flex; align-items: center; gap: .6rem;
  background: none; border: 0; padding: .5rem .7rem;
  width: 100%; text-align: left; color: inherit;
}
.stagebox .box {
  flex: none; width: .95rem; height: .95rem; border-radius: .25rem;
  border: 1.5px solid var(--line); display: grid; place-items: center;
  font-size: .62rem; font-weight: 700; line-height: 1;
}
.stagebox.on .box { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
/* A lesson is a ROW, not a card: 152 of them, so the name and its numbers
   sit on one line and the list stays scannable. */
.stage .body { flex: 1; min-width: 0; display: flex; align-items: baseline;
  gap: .5rem; justify-content: space-between; }
.stage .body b { font-weight: 600; font-size: .85rem; white-space: nowrap; }
.stage .body span { color: var(--mut); font-size: .72rem; white-space: nowrap;
  font-variant-numeric: tabular-nums; }
.stage .body .count { font-weight: 650; color: var(--fg); }
.stage .ready { color: var(--accent); font-weight: 600; }

/* ---------- Stats ---------- */
h2 .sub { font-weight: 400; color: var(--mut); }
.weak .w .ctx { color: var(--mut); font-size: .8rem; font-style: italic; }

/* ---------- Hub only ---------- */
[data-game="hub"] h1 { margin-top: .75rem; }

/* The game tiles. Each is a card and a link at once; the mark carries the
   game's colour, which is the only thing that tells the tiles apart. */
.games { display: flex; flex-direction: column; gap: .75rem; margin: 0 0 1.5rem; }
.game {
  display: flex; align-items: center; gap: .9rem;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r); padding: 1rem 1.1rem;
  box-shadow: var(--shadow);
  color: inherit; text-decoration: none;
  transition: transform .06s ease, border-color .12s ease;
}
a.game:hover, a.game:focus-visible { border-color: var(--accent); }
a.game:active { transform: scale(.985); }
.game .brand-mark { width: 2.6rem; height: 2.6rem; border-radius: .7rem; font-size: 1rem; flex: none; }
.game .body { flex: 1; min-width: 0; }
.game .body b { display: block; font-size: 1.05rem; letter-spacing: -.01em; }
.game .tag { display: block; color: var(--mut); font-size: .84rem; margin-top: .1rem; }
.game .last { display: block; color: var(--accent); font-size: .78rem; margin-top: .2rem; }
.game .last:empty { display: none; }
.game .go { color: var(--mut); font-size: 1.4rem; line-height: 1; }
/* Each tile's colour is its game's, so the tiles are the games' marks in a
   row — the same green and blue the games themselves open with. */
.game.nef { --accent: #2f6f4e; --accent-fg: #ffffff; }
.game.fef { --accent: #2f5aa8; --accent-fg: #ffffff; }
.game.pef { --accent: #8a3f6a; --accent-fg: #ffffff; }
@media (prefers-color-scheme: dark) {
  .game.nef { --accent: #5fb98a; --accent-fg: #10231a; }
  .game.fef { --accent: #7fa3e8; --accent-fg: #0f1a2e; }
  .game.pef { --accent: #d78bb6; --accent-fg: #2a1020; }
}
/* Not yet: same silhouette, no colour, no cursor. */
.game.soon { opacity: .55; cursor: default; box-shadow: none; }
.game.soon .brand-mark { background: var(--line); color: var(--mut); }

#signedNote { text-align: center; margin: 0; }

/* The privacy page, and the footer link to it. */
.prose a, .fineprint a { color: var(--mut); text-decoration: underline; text-underline-offset: 3px; }
.prose h2 { margin-top: 1.5rem; }
.prose ul { padding-left: 1.2rem; }
.prose li { margin: .35rem 0; }
.prose p, .prose li { color: var(--fg); }
#deleteBtn { color: var(--bad); opacity: .8; }

/* ---------- The optional word on a report ----------
   Shown only after the report has gone, so it never stands between a player
   and filing one. Sized to match the two quiet links directly above it — a
   slim field and a plain Send, not a form. The first version of this was a
   full-width textarea and a solid button, which shouted next to neighbours
   set at .78rem and 60% opacity. */
.report-note {
  display: flex; align-items: center; justify-content: center;
  gap: .7rem; margin: .7rem auto 0; max-width: 23rem;
}
.report-note textarea {
  flex: 1; min-width: 0;
  font: inherit; font-size: .8rem; line-height: 1.45;
  padding: .3rem .5rem; border-radius: .4rem; resize: none; overflow: hidden;
  border: 1px solid var(--line); background: transparent; color: var(--fg);
  opacity: .75; transition: opacity .15s ease, border-color .15s ease;
}
.report-note textarea::placeholder { color: var(--mut); }
.report-note textarea:focus-visible {
  opacity: 1; border-color: var(--accent); outline: none;
}
.report-note .link.quiet { margin: 0; flex: none; padding: .25rem 0; }
