/* One stylesheet for every App and the standalone shells. Plain and technical: the sentence is
   the largest thing on the screen, everything else is small and quiet, nothing is boxed, one
   primary action per screen. Colour says one thing only - correct, incorrect, the live slot -
   and a word says it beside the colour. */
/* The light palette on the root; the dark one where the system is dark and the reader chose
   nothing, and where the reader chose dark (games/shared/theme.js stamps data-theme on the
   root). Plain blocks, not light-dark(), which a browser before 2024 does not read and
   would drop every colour for. The primary action in the dark is light enough to be the
   strongest thing on the screen, not a white slab. */
:root {
  color-scheme: light;
  --bg: #ffffff; --card: #ffffff; --soft: #f4f4f4; --ink: #000000; --muted: #666666; --line: #e5e5e5;
  --accent: #0057d9; --good: #0b7a3b; --bad: #c62828; --warn: #8a6d00; --part: #c47a00;
  --btn: #000000; --btn-ink: #ffffff;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans KR", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
  font-size: 15px;
  line-height: 1.45;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #000000; --card: #000000; --soft: #141414; --ink: #ffffff; --muted: #a3a3a3; --line: #2a2a2a;
    --accent: #4c8dff; --good: #4ade80; --bad: #ff6b6b; --warn: #e0b400; --part: #f5b83d;
    --btn: #cfcfcf; --btn-ink: #000000;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #000000; --card: #000000; --soft: #141414; --ink: #ffffff; --muted: #a3a3a3; --line: #2a2a2a;
  --accent: #4c8dff; --good: #4ade80; --bad: #ff6b6b; --warn: #e0b400; --part: #f5b83d;
  --btn: #cfcfcf; --btn-ink: #000000;
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
/* `main` and the status block both set `display`, which beats the UA's [hidden] rule — so
   `el.hidden = true` left the round drawn underneath the page that had replaced it. */
[hidden] { display: none !important; }
html, body { margin: 0; background: var(--bg); color: var(--ink); }
body { min-height: 100dvh; display: flex; flex-direction: column; padding: max(12px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom)); }
a { color: inherit; }
button { font: inherit; color: inherit; }
button:focus-visible, input:focus-visible, a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }

/* The strip: the page's name at the start, the language and the gear in the middle, the way
   home at the end - three columns, the outer two equal, so the middle one is centred on the
   page whatever the name's length. */
header.strip { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 12px; font-size: 14px; color: var(--muted); padding-bottom: 12px; }
header.strip > .title { grid-column: 1; justify-self: start; font-weight: 600; color: var(--ink); font-size: 15px; }
header.strip > .lang { grid-column: 2; justify-self: center; }
header.strip > a.hub:not(.lang) { grid-column: 3; justify-self: end; }
header.strip .title .lang { font-weight: 400; color: var(--muted); margin-left: 8px; }
/* The way out of a round to somewhere else: the hub link in the standalone shells, and the
   interests page in every App. A text link, sized to be a target. */
header.strip .hub { text-decoration: none; border: 0; background: transparent; color: var(--muted); padding: 8px 0 8px 12px; min-height: 32px; font-size: 14px; cursor: pointer; white-space: nowrap; }
header.strip .hub:hover { color: var(--ink); text-decoration: underline; }
header.strip .lang svg { vertical-align: -2px; margin-left: 4px; }
header.strip .lang svg:only-child { margin-left: 0; }
header.strip a.hub[data-icon] { display: inline-flex; align-items: center; min-width: 32px; justify-content: flex-end; }

main { flex: 1; display: flex; flex-direction: column; gap: 20px; max-width: 560px; width: 100%; margin: 0 auto; }
/* A card is a group, not a box: the content sits on the ground. */
.card { padding: 0; }
/* A paragraph's space is the unit's, not the browser's 1em. */
p { margin: 12px 0; }
.gloss { color: var(--muted); font-size: 15px; margin: 0 0 8px; }
.gloss.task { color: var(--ink); font-size: 22px; line-height: 1.4; margin-bottom: 12px; }
/* A sentence's line (View.line): the words, and at the line's end, on its first line of
   text, the register and the small i. */
.sline { display: flex; align-items: flex-start; gap: 8px; }
.sline > .ko, .sline > .built { flex: 1; min-width: 0; }
/* Order's line: the rule it builds on runs under the whole line, register and i included. */
.sline.built-line { border-bottom: 2px solid var(--line); padding-bottom: 8px; margin-bottom: 12px; }
.sline.built-line > .built { border-bottom: 0; padding-bottom: 0; margin-bottom: 0; }
.sline > .aside { flex: none; display: inline-flex; align-items: center; gap: 4px; min-height: 39px; }
.built + .aside, .sline > .built ~ .aside { min-height: 46px; }
.played .sline > .aside { min-height: 28px; }
.register { font-size: 13px; font-weight: 400; color: var(--muted); }
.ghost.quiet.about-i { border: 0; min-width: 32px; min-height: 32px; padding: 4px; }
.ghost.about-i[aria-expanded="true"] { color: var(--ink); }
/* Bullet's one line of instruction, under the sentence. */
.hint { font-size: 13px; color: var(--muted); margin: 8px 0 0; }
.ko { font-size: 28px; line-height: 1.4; font-weight: 600; margin: 0; word-break: keep-all; }
/* A blank's rule is drawn as a shadow, not a border: it takes no height, so the line is as
   tall with its blanks as filled, and filling the last one moves nothing below it. */
.ko .slot { display: inline-block; position: relative; min-width: 1.2em; box-shadow: inset 0 -2px 0 var(--line); }
.ko .slot .ghost-word { visibility: hidden; }
/* The blank being asked for: the accent, and a "?" - which one is live is readable without
   counting along the line. The "?" is laid over the transparent word, so the blank is as
   wide as the word it waits for and a word filling it moves nothing. */
.ko .slot.here { box-shadow: inset 0 -3px 0 var(--accent); }
.ko .slot.here .ask { position: absolute; inset: 0; text-align: center; color: var(--accent); }
.ko .mark { color: var(--bad); text-decoration: underline wavy; text-underline-offset: 4px; }
/* Bullet: every word of the line is a tap target that reads as the word itself, inline in
   the sentence. The changed word, found, is red and wavy; restored, green and underlined; a
   word that was right flashes under the tap and keeps its colour - the tally says what the
   tap cost, so the flash is never the only statement. */
.ko button.w { display: inline; font: inherit; color: inherit; background: transparent; border: 0; padding: 0; margin: 0; border-radius: 4px; cursor: pointer; }
.ko button.w.bad { color: var(--bad); text-decoration: underline wavy; text-underline-offset: 4px; }
.ko button.w.good { color: var(--good); text-decoration: underline; text-underline-offset: 4px; }
.ko button.w.flash, .ghost.flash { animation: flash 0.4s ease; }
/* The alternatives for the changed word, under the line and clear of its underline. */
.choices.fix { margin-top: 16px; }
@keyframes flash { 0% { background: var(--line); } 100% { background: transparent; } }
.ko .filled { text-decoration: underline; text-underline-offset: 5px; text-decoration-color: var(--accent); }
.ko .new { border-bottom: 2px dashed var(--warn); }
.newrow { color: var(--muted); font-size: 13px; margin: 8px 0 0; }

/* Answers. A grid of two for a pick between short words; a hairline list for longer ones. */
.choices { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.choice, .big { border: 1px solid var(--line); background: var(--card); color: var(--ink); border-radius: 8px; padding: 16px 8px; font-size: 20px; font-weight: 500; min-height: 56px; cursor: pointer; word-break: keep-all; }
.choice:active, .big:active, .tile:active, .opt:active { background: var(--soft); }
.choice.right, .big.right, .tile.good, .opt.right { border-color: var(--good); color: var(--good); box-shadow: inset 0 0 0 1px var(--good); }
.choice.wrong, .big.wrong, .tile.wrong, .opt.wrong { border-color: var(--bad); color: var(--bad); box-shadow: inset 0 0 0 1px var(--bad); }
.big:disabled:not(.right):not(.wrong), .choice:disabled:not(.right):not(.wrong) { color: var(--muted); }
.opts { display: flex; flex-direction: column; border-top: 1px solid var(--line); }
.opt { border: 0; border-bottom: 1px solid var(--line); background: transparent; color: var(--ink); border-radius: 0; padding: 16px 4px; font-size: 19px; font-weight: 500; text-align: left; cursor: pointer; width: 100%; word-break: keep-all; min-height: 52px; }
.opt.right, .opt.wrong { box-shadow: none; padding-left: 12px; border-left: 3px solid currentColor; }
.opt.dim { color: var(--muted); }
.opt:disabled { cursor: default; }

.row { display: flex; gap: 8px; align-items: center; }
/* A list of played sentences on an end card (Bullet): the sentences a block gap apart, as
   the page's blocks are, and inside one the lines a unit apart with no margins of their own. */
.played-list { display: flex; flex-direction: column; gap: 20px; }
/* A box laid over the column (the language chooser over a round): its children are the
   column's own blocks, a block gap apart, and the box itself takes no space. */
.takeover { display: contents; }
/* A stack: lines and rows a unit apart, none carrying a margin of its own (a played
   sentence on Bullet's end card, the languages card). */
.stack, .played { display: flex; flex-direction: column; gap: 8px; }
.stack > p, .stack > .row, .played > p, .played > .row { margin: 0; }
.played .ko { font-size: 20px; }
/* The list growing under a round in play: dimmed, every part of it - the changed word keeps
   its wavy mark but not its red, a word's meter and the buttons take the muted ink - so the
   line in play stays the thing; the list stands a unit further from the round than the
   round's own blocks stand from each other, so it reads as another region. */
.played-list.dim { color: var(--muted); margin-top: 12px; }
.played-list.dim .mark { color: inherit; text-decoration-color: var(--bad); }
.played-list.dim .winfo b, .played-list.dim .ghost { color: var(--muted); }
.played-list.dim .meter i.on { background: var(--muted); }
.played-list.dim .meter i.half { background: linear-gradient(90deg, var(--muted) 50%, var(--line) 50%); }
.row > * { flex: 1; }
.row.wrap { flex-wrap: wrap; }
.row.wrap > * { flex: 0 1 auto; }
.primary { background: var(--btn); color: var(--btn-ink); border: 0; border-radius: 8px; padding: 16px; font-size: 16px; font-weight: 600; cursor: pointer; min-height: 48px; }
.ghost { background: transparent; border: 1px solid var(--line); color: var(--ink); border-radius: 8px; padding: 12px; font-size: 15px; cursor: pointer; min-height: 44px; }
/* A link drawn as a ghost button (the "+" that asks for a language). */
a.ghost { display: inline-flex; align-items: center; text-decoration: none; }
/* A symbol from the one icon family (games/shared/icons.js) on a control: it sits in the
   text's line, in the text's colour. A control that is only a symbol is square, a target
   the size of the others, the symbol centred. */
svg.icon { display: inline-block; vertical-align: middle; flex: none; }
.icon-only { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; }
/* The asks put to the coach under a sentence (View.asks): the row's whole width, in equal
   columns, one under the other where two would not fit their words. */
.asks { display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); gap: 8px; }
/* A control with its symbol and its word (View.labelButton). */
.labelled { display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
.primary.icon-only { min-width: 96px; }
/* A door out of the page in a line of text: to the install page. */
a.door { color: var(--muted); font-size: 14px; text-decoration: underline; text-underline-offset: 4px; }
/* What is known about a sentence (View.about), opened in place under its small i. */
.about { border-left: 2px solid var(--line); padding: 4px 0 4px 12px; margin-top: 8px; }
.about > p, .about > .winfo { margin: 0; }
/* A search field with its symbol inside, at the start. */
label.search { position: relative; display: block; color: var(--muted); }
label.search svg.icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); pointer-events: none; }
label.search input { padding-left: 40px; }
/* Secondary: the way out of a round, and the asks put to the coach. Reachable, and never
   heavier than the exercise. */
.ghost.quiet { padding: 8px 12px; font-size: 14px; min-height: 32px; border-radius: 6px; color: var(--muted); }
.ghost.quiet:hover { color: var(--ink); }
.ghost:disabled, .ghost.quiet:disabled:hover { color: var(--muted); cursor: default; }
/* A control that talks to the coach while no assistant is connected: drawn as not set up
   (dashed, muted); a tap opens the setup (app.js). */
:root[data-coach="missing"] [data-ask], :root[data-coach="missing"] [data-coach-needed] { border-style: dashed; color: var(--muted); }
:root[data-coach="missing"] .primary[data-coach-needed] { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1px var(--line); }
/* The setup under the assistants on the settings card: the assistant's own steps, the
   address with its copy control, the test; a mark once the assistant has called. */
ol.steps { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 4px; font-size: 14px; }
.copyrow code.url { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; overflow-wrap: anywhere; padding: 8px 0; flex: 1 1 200px; }
.connected { display: inline-flex; align-items: center; gap: 8px; color: var(--good); margin: 0; }
.unsent { margin: 4px 0 0; display: inline-flex; align-items: center; gap: 4px; }
/* What a tap did, on its own line under the control's row (host.js noteUnder). */
.tapnote { margin: 4px 0 0; }
/* A choice marked on a card of choices (the languages card): the ink, not the line, around it. */
.ghost[aria-pressed="true"] { border-color: var(--ink); font-weight: 600; }
.primary:disabled { opacity: .4; cursor: default; }
.wayout { margin-top: 0; }
.result { font-size: 15px; color: var(--muted); margin: 8px 0 0; }
.result:empty { display: none; }
/* A verdict line a round reserves before the answer: it holds its height empty, so the
   verdict appearing moves nothing under it. */
.result.hold, .result.hold:empty { display: block; min-height: 22px; }
.result b { color: var(--ink); font-weight: 600; }
/* A verdict as a mark (View.mark): green, amber after one mistake, red; the right word after
   an arrow. */
.verdict { display: inline-flex; vertical-align: middle; }
.verdict.right { color: var(--good); }
.verdict.partial { color: var(--part); }
.verdict.wrong { color: var(--bad); }
.result .fix { display: inline-flex; align-items: center; gap: 8px; margin-left: 8px; vertical-align: middle; }
.result .fix b { font-size: 17px; }
.muted { color: var(--muted); font-size: 14px; }
.attribution { font-size: 12px; margin: 8px 0 0; }

/* The words in front of you: word, place on the list, a five-cell meter of how well it is
   known, and the schedule's word when it asked for it. One small line, nothing to tap. */
.winfo { font-size: 13px; color: var(--muted); margin: 12px 0 0; display: flex; flex-wrap: wrap; gap: 4px 16px; }
.winfo .wi { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
.winfo b { font-weight: 500; color: var(--ink); }
.winfo .n { font-variant-numeric: tabular-nums; }
.meter { display: inline-flex; gap: 2px; }
.meter i { display: block; width: 6px; height: 9px; background: var(--line); }
.meter i.on { background: var(--ink); }
.meter i.half { background: linear-gradient(90deg, var(--ink) 50%, var(--line) 50%); }
.meter.none { width: 38px; height: 9px; border: 1px dashed var(--line); }

/* Progress: one bar at the top of the round, and, in Bullet, the clock as a bar and a line. */
/* Progress as segments (View.segments): one per item of the round. */
.segments { display: flex; gap: 4px; }
.segments i { flex: 1; height: 4px; border-radius: 2px; background: var(--line); }
.segments i.right { background: var(--ink); }
.segments i.partial { background: var(--part); }
.segments i.missed { background: var(--bad); }
.segments i.skipped { background: transparent; box-shadow: inset 0 0 0 1px var(--line); }
.segments i.current { background: var(--accent); }
.segments i.current-partial { background: var(--part); opacity: 0.6; }
.segments i.current-missed { background: var(--bad); opacity: 0.6; }
/* On an end card the segments stand between the heading and the score. */
.summary .segments { margin: 8px 0 12px; }
/* The score on an end card (View.score): a mark and the numbers. */
.score { display: inline-flex; align-items: center; gap: 8px; }
.score.quiet { color: var(--muted); }
/* Bullet's tally: the tick with the count and the chip with the seconds lost, one row. */
.tally { display: inline-flex; align-items: center; gap: 12px; }
/* A cost, said as a number on a chip: Bullet's seconds lost. */
.chip-cost { display: inline-block; padding: 0 8px; border-radius: 8px; color: var(--bad); box-shadow: inset 0 0 0 1px var(--bad); font-variant-numeric: tabular-nums; }
.track, .bar { height: 2px; background: var(--line); overflow: hidden; }
.track > div, .bar > div { height: 100%; background: var(--ink); width: 0; transition: width 0.25s ease; }
.bar > div.low { background: var(--bad); }
.countdown { font-variant-numeric: tabular-nums; font-size: 13px; color: var(--muted); margin: 8px 0 0; }
.countdown.low { color: var(--bad); }

/* Order: the words to place, and the words placed. */
.tiles { display: flex; flex-wrap: wrap; gap: 8px; }
.tile { border: 1px solid var(--line); background: var(--card); color: var(--ink); border-radius: 8px; padding: 12px 16px; font-size: 20px; font-weight: 500; cursor: pointer; min-height: 44px; }
/* A tile already placed: greyed, inert, and still legible (the contrast law holds for it). */
.tile.used { color: var(--muted); border-color: var(--line); background: var(--soft); pointer-events: none; }
.tile.close { border-color: var(--warn); color: var(--warn); }
/* The line being built: a rule to build on. Empty, it holds one invisible tile, so it is
   exactly as tall as a placed tile from the first screen and the first tile placed moves
   nothing under it. */
.built { margin-bottom: 12px; padding-bottom: 8px; border-bottom: 2px solid var(--line); display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.built .tile { display: inline-block; }
.built .tile.hold { visibility: hidden; }
.built .tile { cursor: pointer; }

/* The end of a round: the score, large; the ways on, under it. */
.summary .gloss { font-size: 13px; margin: 0; }
.summary #score { font-size: 24px; font-weight: 600; margin: 4px 0 0; }
.summary #score b { font-weight: 600; }
.summary #score .muted { font-size: 15px; font-weight: 400; }

.say { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--line); background: transparent; color: var(--ink); border-radius: 6px; padding: 8px 12px; min-height: 32px; font-size: 13px; cursor: pointer; }
.say svg { flex: none; }
.reveal { border-top: 1px solid var(--line); margin-top: 12px; padding-top: 12px; }
footer { color: var(--muted); font-size: 12px; text-align: center; padding-top: 16px; }
footer button { background: none; border: 0; color: var(--muted); text-decoration: underline; cursor: pointer; font-size: 12px; }

/* The standalone hub: one row per App. */
.hub-list { display: flex; flex-direction: column; border-top: 1px solid var(--line); }
.hub-list a { text-decoration: none; }
.hub-list a.card { display: flex; flex-direction: column; gap: 4px; color: var(--ink); padding: 16px 0; border-bottom: 1px solid var(--line); }
.hub-list a.card b { font-size: 17px; font-weight: 600; }
.hub-list a.card .muted { font-size: 13px; }
iframe.app { display: block; width: 100%; border: 0; min-height: 320px; background: transparent; }
input.ghost { width: 100%; border-radius: 8px; }
.status { font-size: 13px; color: var(--muted); }
button.link { background: none; border: 0; color: var(--accent); text-decoration: underline; cursor: pointer; font-size: inherit; padding: 0; }

/* The interests page (games/shared/library.js): a search box and a list of rows, and the
   row is the control. It takes the place of `main` without rewriting it. */
.library { display: flex; flex-direction: column; gap: 8px; max-width: 560px; width: 100%; margin: 0 auto; }
.lib-head { justify-content: space-between; margin-bottom: 4px; }
.lib-title { font-size: 15px; font-weight: 600; margin: 0; }
.library input.ghost { width: 100%; font-size: 17px; padding: 12px 16px; }
.lib-group { font-size: 13px; color: var(--muted); margin: 16px 0 0; padding-bottom: 8px; border-bottom: 1px solid var(--line); }
.lib-note { font-size: 13px; margin: 12px 0 0; }
.lib-picked { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.lib-picked-lead { font-size: 13px; color: var(--muted); }
.lib-picked .chips { margin: 0; }
.picks { display: flex; flex-direction: column; }
/* A source: the pick control, and beside it the line to the coach. */
.pick-row { display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--line); }
.pick { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex: 1; min-width: 0;
        text-align: left; background: transparent; border: 0;
        color: var(--ink); padding: 12px 0; min-height: 48px; cursor: pointer; }
.pick-what { display: flex; flex-direction: column; gap: 0px; min-width: 0; }
.pick-name { font-size: 16px; font-weight: 500; overflow-wrap: anywhere; }
.pick-detail { font-size: 13px; color: var(--muted); overflow-wrap: anywhere; }
/* A state, not a second control: the row is the target; the word on the right says what a
   tap does, and turns into what happened. */
.pick-state { flex: none; font-size: 14px; color: var(--muted); padding: 4px 0; }
.pick.on .pick-state { color: var(--good); font-weight: 600; }
.pick.on .pick-name { color: var(--good); }
.pick:disabled { opacity: .6; cursor: default; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip { border: 1px solid var(--line); background: transparent; color: var(--ink); border-radius: 6px; padding: 4px 12px; min-height: 28px; font-size: 14px; cursor: pointer; line-height: 1.3; }
.chip.picked { background: var(--soft); }
