/* 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 dark is a warm near-black under an off-white ink, the
   same pair the assistants' own apps read in, not black under pure white, which glares; the
   primary action in it 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: #0071e3; --good: #0b7a3b; --bad: #c62828; --warn: #8a6d00; --part: #c47a00;
  /* The main button: Apple's own filled blue (apple.com's buy button), a white label at 4.7:1. */
  --btn: #0071e3; --btn-ink: #ffffff;
  /* A progress mark still to come or skipped: 3:1 against the page (WCAG 1.4.11), not the divider colour. */
  --todo: #8c8c8c;
  --shot-blend: multiply;
  /* Next Word's keys: pastel keycaps in light, browns and greys in dark, five tones in turn. */
  --cap0-top: #d3f1e3; --cap0-side: #a9d9c4; --cap0-edge: #86bfa6;
  --cap1-top: #e0daf8; --cap1-side: #bdb4e6; --cap1-edge: #9d93cf;
  --cap2-top: #ffdccb; --cap2-side: #f0b89e; --cap2-edge: #d99d80;
  --cap3-top: #d3e8fb; --cap3-side: #a9cdee; --cap3-edge: #88b2da;
  --cap4-top: #f9f0c4; --cap4-side: #e4d58f; --cap4-edge: #c9b971;
  --cap-ink: #3b3540; --cap-shadow: rgba(60, 50, 70, .22); --cap-shine: rgba(255, 255, 255, .65);
  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: #151515; --card: #151515; --soft: #20201f; --ink: #f9f9f7; --muted: #96958d; --line: #313131;
    --accent: #2997ff; --good: #4ade80; --bad: #ff6b6b; --warn: #e0b400; --part: #f5b83d;
    --btn: #0071e3; --btn-ink: #ffffff; --todo: #6b6b6b; --shot-blend: normal;
    --cap0-top: #4c3b30; --cap0-side: #33271f; --cap0-edge: #221a14;
    --cap1-top: #4b4b50; --cap1-side: #333337; --cap1-edge: #222225;
    --cap2-top: #5e4838; --cap2-side: #3f3026; --cap2-edge: #2a2019;
    --cap3-top: #58534d; --cap3-side: #3b3834; --cap3-edge: #282623;
    --cap4-top: #6b5b4d; --cap4-side: #4a3e35; --cap4-edge: #322a24;
    --cap-ink: #efe7dc; --cap-shadow: rgba(0, 0, 0, .55); --cap-shine: rgba(255, 255, 255, .10);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #151515; --card: #151515; --soft: #20201f; --ink: #f9f9f7; --muted: #96958d; --line: #313131;
  --accent: #2997ff; --good: #4ade80; --bad: #ff6b6b; --warn: #e0b400; --part: #f5b83d;
  --btn: #0071e3; --btn-ink: #ffffff; --todo: #6b6b6b; --shot-blend: normal;
  --cap0-top: #4c3b30; --cap0-side: #33271f; --cap0-edge: #221a14;
  --cap1-top: #4b4b50; --cap1-side: #333337; --cap1-edge: #222225;
  --cap2-top: #5e4838; --cap2-side: #3f3026; --cap2-edge: #2a2019;
  --cap3-top: #58534d; --cap3-side: #3b3834; --cap3-edge: #282623;
  --cap4-top: #6b5b4d; --cap4-side: #4a3e35; --cap4-edge: #322a24;
  --cap-ink: #efe7dc; --cap-shadow: rgba(0, 0, 0, .55); --cap-shine: rgba(255, 255, 255, .10);
}
* { 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); }
/* As tall as the smallest screen the browser shows (its address bar out), never taller: a page
   whose content fits keeps its end on the screen, whatever the bar did while a long card
   was scrolled. 100vh first for a browser before 2022. */
/* The page's side margin, one value: a bar that runs edge to edge (.sheethead) cancels exactly this. */
:root { --gutter: 16px; }
body { min-height: 100vh; min-height: 100svh; display: flex; flex-direction: column; padding: max(12px, env(safe-area-inset-top)) var(--gutter) 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. */
/* The header's symbols are Material's icon-button size, 24px, in 44px targets (Apple's minimum),
   and its words the size of a navigation bar's title, 17px, so the two read as one bar. */
header.strip { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 12px; font-size: 17px; color: var(--muted); padding-bottom: 8px; }
/* The header, the same on every page: the language and the settings at the left, the page's
   name in the middle (none on the home page), what leads out of the page at the right. */
header.strip > .lang { grid-column: 1; justify-self: start; }
/* A round starts well clear of the bar above it, framed by our own page or under the App's own
   strip: the sentence reads as the start of the page, not as part of its header. */
header.strip[hidden] + main { margin-top: 40px; }
header.strip:not([hidden]) + main { margin-top: 32px; }
header.strip > .hub.lang { padding-left: 0; padding-right: 12px; }
header.strip > .title { grid-column: 2; justify-self: center; font-weight: 600; color: var(--ink); font-size: 17px; }
header.strip > a.hub:not(.lang), header.strip > .share { grid-column: 3; justify-self: end; }
header.strip > .share { display: inline-flex; align-items: center; justify-content: flex-end; min-width: 44px; }
/* The home page's bottom bar: Install and Share side by side, pinned to the bottom edge over
   the page, opaque, a rule above it, clear of the phone's home indicator (host.js doorsHTML).
   The page keeps room under its last line so the bar never covers it. */
.bottombar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 3; display: flex; background: var(--bg);
  border-top: 1px solid var(--line); padding: 4px 16px max(4px, env(safe-area-inset-bottom)); }
.bottombar > button { flex: 1; justify-content: center; min-height: 48px; border: 0; background: transparent;
  color: var(--ink); font-size: 15px; cursor: pointer; }
/* Only a real pointer hovers: on a phone a tap leaves :hover set, and the shade stayed behind
   the button after it. */
@media (hover: hover) { .bottombar > button:hover { background: var(--soft); } }
/* The bar goes when its one control (Install) is not offered, and the page's room for it. */
.bottombar:not(:has(> button:not([hidden]))) { display: none; }
body.has-bottombar:not(:has(.bottombar > button:not([hidden]))) { padding-bottom: max(16px, env(safe-area-inset-bottom)); }
.barnote { position: fixed; left: 16px; right: 16px; bottom: max(16px, env(safe-area-inset-bottom)); z-index: 3; margin: 0;
  padding: 12px 16px; border-radius: 8px; background: var(--ink); color: var(--bg); font-size: 14px; overflow-wrap: anywhere; }
body.has-bottombar { padding-bottom: calc(64px + env(safe-area-inset-bottom)); }
body:has(.bottombar > button:not([hidden])) .barnote { bottom: calc(64px + env(safe-area-inset-bottom)); }
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: 44px; font-size: 17px; cursor: pointer; white-space: nowrap; }
@media (hover: hover) { header.strip .hub:hover { color: var(--ink); text-decoration: underline; } }
header.strip .hub.lang { display: inline-flex; align-items: center; }
header.strip .lang svg { margin-left: 8px; }
header.strip .lang svg:only-child { margin-left: 0; }
header.strip a.hub[data-icon] { display: inline-flex; align-items: center; min-width: 44px; justify-content: flex-end; }
/* The way home under the finger, and while the home page opens (host.js openingHome): the
   house in the pressed shade a game's row takes, then in its place the ring a game's row turns, the same size,
   in the accent; with reduced motion the ring stands still (client/the-way-home-answers-at-once). */
header.strip a.hub[data-icon="home"] { position: relative; isolation: isolate; }
header.strip a.hub[data-icon="home"].pressed { color: var(--ink); }
header.strip a.hub[data-icon="home"].pressed::before { content: ""; position: absolute; z-index: -1; right: -8px; top: 50%;
  width: 40px; height: 40px; margin-top: -20px; border-radius: 8px; background: var(--soft); }
header.strip a.hub[data-icon="home"].opening svg { visibility: hidden; }
header.strip a.hub[data-icon="home"].opening::after { content: ""; position: absolute; right: 4px; top: 50%; width: 16px; height: 16px; margin-top: -8px;
  box-sizing: border-box; border: 2px solid var(--line); border-top-color: var(--accent); border-radius: 50%; animation: opening 0.8s linear infinite; }
@media (prefers-reduced-motion: reduce) { header.strip a.hub[data-icon="home"].opening::after { animation: none; } }

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 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, the 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; }
.played .sline > .aside { min-height: 28px; }
.ghost.quiet.about-i { border: 0; min-width: 24px; min-height: 24px; padding: 4px; color: var(--muted); }
.ghost.about-i[aria-expanded="true"] { color: var(--ink); }
/* Bullet's one line of instruction, above the sentence it is about. */
.hint { font-size: 13px; color: var(--muted); margin: 0 0 8px; }
/* The line under a sentence where its meaning appears, held from the start by the control
   that shows it, so the meaning arriving moves nothing. */
.meaningline { display: flex; align-items: center; margin-top: 8px; }
/* A meaning held until Translate is tapped: its room kept, its words unseen (View.held). */
.gloss.held { visibility: hidden; }
.meaningline .gloss { margin: 0; }
/* A sentence with no meaning to show keeps no room for one. */
.meaningline:empty { display: none; }
.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(--muted); }
/* Bullet: every word of the line is a tap target that reads as the word itself, inline in
   the sentence. The changed word, found, takes its hole and, restored, stands in the line's
   ink; a word that was right flashes under the tap and keeps its colour - nothing marks the
   learner (client/the-exercises-judge-no-one-2). */
.ko button.w { display: inline; position: relative; font: inherit; color: inherit; background: transparent; border: 0; padding: 0; margin: 0; border-radius: 4px; cursor: pointer; }
.ko button.w.good { color: inherit; }
.ghost.flash { animation: flash 0.4s ease; }
/* The alternatives for the changed word, under the line and clear of its underline. */
@keyframes flash { 0% { background: var(--line); } 100% { background: transparent; } }
/* Bullet's shots, drawn in the word they hit and gone but for a hit's hole (client/a-shot-at-bullets-sentence-or-a-plate-is-heard-and-felt):
   nothing in them takes a tap. The label over each word forwards its tap to the hidden switch,
   which is what gives an iPhone its haptic. */
.ko button.w .shot { position: absolute; pointer-events: none; z-index: 2; background-repeat: no-repeat; }
.ko button.w .shot.hole { z-index: 1; background-size: 100% 100%; mix-blend-mode: var(--shot-blend); }
.ko button.w .hap, .knock .hap, .inline-plate .hap { position: absolute; inset: 0; z-index: 3; cursor: pointer; touch-action: manipulation; }
/* Hidden as ios-haptics hides it: out of sight and out of the page's controls, still a switch a label can press. */
.haptic { position: absolute; left: 0; top: 0; width: 1px; height: 1px; margin: 0; visibility: hidden; }
.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; }
/* A word typed into its place (Next Word, Order, Drill): no rule under the line and no
   mark under the word - the sentence is written, not graded - and a blinking cursor where
   the next word goes (client/a-word-that-fits-is-typed-into-its-place). */
.ko .filled.typed { text-decoration: none; }
.built.typed, .sline.typed { border-bottom: 0; }
.caret { display: inline-block; width: 2px; height: 1.05em; background: var(--accent); vertical-align: -0.14em; animation: caret-blink 1.06s steps(1) infinite; }
@keyframes caret-blink { 50% { opacity: 0; } }
/* A try answered by the task (games/shared/feel.js): a box popped or pushed away keeps its
   room; the word that fits leaves its box empty and travels as a copy. */
.gone, .pushed { visibility: hidden; }
.leaving { color: transparent; }
.feel-fly { position: fixed; z-index: 50; pointer-events: none; white-space: nowrap; transform-origin: 0 0; }
.feel-ring { position: fixed; width: 24px; height: 24px; box-sizing: border-box; border-radius: 50%; border: 2px solid var(--ink); pointer-events: none; z-index: 60; opacity: .6; transition: transform 220ms ease-out, opacity 220ms ease-out; }
.feel-drop { position: fixed; width: 4px; height: 4px; border-radius: 50%; background: var(--ink); pointer-events: none; z-index: 60; opacity: .5; transition: transform 240ms ease-out, opacity 240ms ease-in; }

/* The words a learner taps - Next Word's choices, Drill's options, Order's words - are one
   kind of control, drawn one way: an outlined box, the same corners, height and type, each
   as wide as its word (.words, below). */
.choices, .opts { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.choice, .big, .opt { border: 1px solid var(--line); background: var(--card); color: var(--ink); border-radius: 8px; padding: 12px 8px; font-size: 19px; font-weight: 500; min-height: 52px; 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); }
.opt.dim { color: var(--muted); }
/* Next Word's choices and Drill's options, each box sized to its word, wrapped as words are
   (client/the-words-to-tap-are-sized-to-their-words). */
.choices.words, .opts.words { display: flex; flex-wrap: wrap; gap: 8px; }
.words > .choice, .words > .opt { flex: none; padding: 12px 16px; }
/* Read by a screen reader, not drawn: the visually-hidden pattern in its current form,
   clipped to nothing, with no margin to break the spacing scale. */
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* The background of our own pages (host.js paintBackdrop): the cloud of the set's words, one
   image the size of the screen, fixed behind everything, faint. Over it the page stands on
   solid ground - the bar, and the column of the page down to 24 px under its last row, or to
   its grip on a game - and the column's edge, a full-bleed hairline in the divider colour
   (Material 3's divider: 1 dp, about 12% of the ink), cuts the drawing. */
@font-face { font-family: "Language Coach Cloud"; src: url("fonts/Dongle-Bold.woff2") format("woff2"); font-display: block; }
.backdrop { position: fixed; inset: 0; z-index: -1; overflow: hidden; color: var(--ink); pointer-events: none; }
.backdrop svg { display: block; opacity: .06; }
/* The page's colour on the root, the body clear: the background lies between the two. */
:root.shell body { background: transparent; }
:root.shell header.strip { background: var(--bg); margin: -12px calc(-1 * var(--gutter)) 0; padding: 12px var(--gutter) 8px; }
:root.shell main { flex: none; box-sizing: content-box; background: var(--bg); margin: 0 calc(-1 * var(--gutter)); padding: 0 var(--gutter) 24px;
  border-bottom: 1px solid var(--line); max-width: 560px; width: auto; }
/* Wider than the column and its gutters, the column is centred at its full width: centred by
   auto margins alone, it shrank to its content (the home page 196 px wide in a 900 px window). */
@media (min-width: 592px) { :root.shell main { margin: 0 auto; width: 100%; } }
:root.shell header.strip:not([hidden]) + main { margin-top: 0; padding-top: 32px; }
/* The settings take the whole screen from its top: nothing of the background above them. */
:root.shell body.in-settings main, :root.shell body.in-settings header.strip + main {
  margin-top: calc(-1 * max(12px, env(safe-area-inset-top))); padding-top: max(12px, env(safe-area-inset-top)); }
:root.shell body:not(.in-settings) main:has(> iframe.app:not(.pending)) { padding-bottom: 0; }
/* A game's page (client/the-exercise-panel-is-fixed-the-controls-move-and-bullets-list-shows-at-three-lines):
   the bar, its edge, a 24 px band of the background, then the game's frame, the column's
   full width, on solid ground from its top edge to the controls' bottom edge; the
   background under it down to the screen's foot. The settings opened over a game are laid
   out as on any other page, on the column's ground with its margins. */
:root.shell body:not(.in-settings):has(main > iframe.app:not(.pending)) { padding-bottom: 0; }
:root.shell body:not(.in-settings):has(main > iframe.app:not(.pending)) header.strip { border-bottom: 1px solid var(--line); }
:root.shell body:not(.in-settings) main:has(> iframe.app:not(.pending)) { background: transparent; border-bottom: 0; padding-left: 0; padding-right: 0; }
/* Until the frame is shown, the page keeps its own margins and ground - what it says while the
   round loads ("Loading…") stands where it stood, off the screen's edge - and the hidden frame
   is laid out at the width it will have once shown, so the round does not reflow as it appears. */
:root.shell body:not(.in-settings) main > iframe.app.pending { margin: 0 calc(-1 * var(--gutter)); width: calc(100% + 2 * var(--gutter)); }
:root.shell body:not(.in-settings) header.strip:not([hidden]) + main:has(> iframe.app:not(.pending)) { padding-top: 24px; }
/* A game in our shell fills its frame on solid ground, edged at its top and at the
   controls' foot: its two panels, the exercise's and the controls', and the space between
   them are one ground. The frame starts 24 px under the bar and its content 8 px into it,
   where the exercise panel centres the round's tallest exercise
   (client/space-says-what-belongs-together-and-a-games-exercise-is-centred); the
   game's own bar is hidden there, and the space kept for a page with no bar is not added.
   The frame is the column's width, so the game keeps the column's side margin itself. */
:root[data-shell="play"] { background: var(--bg); }
:root[data-shell="play"] body { background: transparent; }
:root[data-shell="play"] body::before, :root[data-shell="play"] body::after { content: ""; position: fixed; left: 0; right: 0; height: 1px; z-index: 3; background: var(--line); pointer-events: none; }
:root[data-shell="play"] body::before { top: 0; }
/* The controls' foot: drawn over the docked row, whose ground runs down past the grip and
   covered the plate's own edge. */
:root[data-shell="play"] body::after { bottom: 0; }
:root[data-shell="play"] { --gutter: 24px; }
:root[data-shell="play"] body { min-height: 100vh; padding-top: 8px; padding-bottom: 8px; }
:root[data-shell="play"] header.strip[hidden] + main { margin-top: 0; }
/* The panels' plates, under what they hold: the exercise's from the frame's top, the
   controls' from above the first of them to the frame's foot. They measure the two panels
   (app.js fit, turnAbout); the ground and its two edges are the frame's. */
.plate { position: absolute; left: 0; right: 0; z-index: -1; background: var(--bg); pointer-events: none; }
.plate.ex { top: 0; }
.plate.ctl { position: fixed; bottom: 0; }
/* What a game lets shrink when its exercise and its own controls would overlap with the
   controls at their limit (app.js fit sets --fit; client/a-game-shrinks-its-text-and-bullets-
   list-never-its-generic-controls): the sentence, the task, the flashcard's face, the words to
   tap and Bullet's list of sentences played - their text and the room above and below it - and
   the space between the page's blocks, on the 4 px unit. Never the row, the grip, the small
   controls beside a sentence, the icons, the meaning's small text; and a word to tap shrinks
   to 32 px tall at the least - above the 24 px of WCAG 2.5.8. */
:root[data-shell="play"] main { gap: round(down, calc(16px * var(--fit, 1)), 4px); }
:root[data-shell="play"] .stack { gap: round(down, calc(8px * var(--fit, 1)), 4px); }
:root[data-shell="play"] .ko { font-size: calc(26px * var(--fit, 1)); }
:root[data-shell="play"] .played .ko { font-size: calc(20px * var(--fit, 1)); }
:root[data-shell="play"] .gloss.task { font-size: calc(22px * var(--fit, 1)); }
:root[data-shell="play"] .choice, :root[data-shell="play"] .big, :root[data-shell="play"] .opt { font-size: calc(19px * var(--fit, 1));
  padding-top: round(down, calc(12px * var(--fit, 1)), 4px); padding-bottom: round(down, calc(12px * var(--fit, 1)), 4px);
  min-height: max(32px, calc(52px * var(--fit, 1))); }
:root[data-shell="play"] .tile { font-size: calc(19px * var(--fit, 1)); min-height: max(32px, calc(52px * var(--fit, 1)));
  padding-left: round(down, calc(16px * var(--fit, 1)), 4px); padding-right: round(down, calc(16px * var(--fit, 1)), 4px);
  padding-top: round(down, calc(12px * var(--fit, 1)), 4px); padding-bottom: round(down, calc(12px * var(--fit, 1)), 4px); }
/* In our shell a card has no height of its own: it fills the space above the controls
   (client/a-flashcard-fills-the-space-above-the-controls). */
:root[data-shell="play"] .face { min-height: 0;
  padding-top: round(down, calc(48px * var(--fit, 1)), 4px); padding-bottom: round(down, calc(40px * var(--fit, 1)), 4px); }
/* The tick in Bullet's instruction: a control in a line of text, the text's own size. */
/* Bullet's plate (client/nothing-to-fix-is-a-steel-plate-after-the-sentence-that-falls-when-hit-2): after the last word, a space away,
   in the line like the words; its size and its drop below the line are set in em by Shots.fit from
   the letters as drawn, its frames stepped by Shots.fall. The task line's small plate is its first frame. */
.ko .knock { display: inline; position: relative; padding: 0 8px 0 0; border: 0; background: none; font: inherit; color: inherit; cursor: pointer; }
.ko .knock .shot { position: absolute; pointer-events: none; z-index: 2; background-repeat: no-repeat; }
.steel { display: inline-block; position: relative; vertical-align: baseline; background-repeat: no-repeat; background-position: 0 0; }
/* Bullet's fixes as steel plates (client/bullets-fixes-are-steel-plates-on-a-hinge-a-row): a plate button with
   its word painted over the blank plate, and one hinge rod a row along the plates' feet. */
#fix { position: relative; }
.fixroom .choices.fix { display: flex; flex-wrap: wrap; gap: 4px; align-items: flex-start; }
.choices .choice.fixplate.right, .choices .choice.fixplate.wrong { color: inherit; }
.choices .choice.fixplate, :root[data-shell="play"] .fixroom .choice.fixplate { position: relative; justify-self: start; min-height: 0; padding: 0; border: 0; border-radius: 0; background: none; box-shadow: none; font-size: 16px; }
:root[data-shell="play"] .fixroom .choice.fixplate { font-size: calc(20px * var(--fit, 1)); }
.fixplate .steel { display: block; background-repeat: no-repeat; background-position: 0 0; }
.fixplate .fixword { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; padding-top: 0.14em; font-weight: 700; color: #141414; mix-blend-mode: multiply; transform-origin: 50% 100%; -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }
.fixplate .hap { position: absolute; inset: 0; z-index: 3; cursor: pointer; touch-action: manipulation; }
.fixplate .shot { position: absolute; pointer-events: none; z-index: 4; background-repeat: no-repeat; }
#fix .rod { position: absolute; height: 5px; pointer-events: none; background-repeat: no-repeat; background-size: 100% 100%; z-index: 1; }
.hint .inline-plate { display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; position: relative; min-width: 24px; min-height: 24px; padding: 0; border: 0; background: none; font: inherit; cursor: pointer; }
.steel.mini { width: 0.94em; height: 1.16em; background-size: 11.28em 1.16em; }
/* The grip under a round's controls in our own shell (app.js placeGrip): a short bar in the
   colour of a step still to come, in a 48 by 24 target; drag it, or use the arrow keys. */
.rowgrip { display: flex; align-items: center; justify-content: center; width: 48px; height: 24px; margin: 4px auto 0; padding: 0; border: 0; background: transparent; cursor: ns-resize; touch-action: none; }
.rowgrip span { display: block; width: 28px; height: 4px; border-radius: 2px; background: var(--todo); }
.rowgrip.held span { background: var(--muted); }
@media (hover: hover) { .rowgrip:hover span { background: var(--muted); } }
/* What a round's content leaves above the line its controls stand on (app.js holdRow). */
.rowfill { flex: none; }
/* In our shell the row and its grip stand at the frame's bottom, on the learner's line:
   pushed there on a short screen, stuck there on a long one, whose content scrolls under
   them - the row's ground runs down past the grip. */
.wayout.docked { position: sticky; z-index: 1; margin-top: auto; background: var(--bg); box-shadow: 0 96px 0 96px var(--bg); }
:root[data-shell="play"] .rowgrip { position: sticky; z-index: 2; }
/* What a screen marks [data-dock] just above its row - the words to tap - is
   part of the controls: it stands with them, on their ground. */
.docked-with { position: sticky; z-index: 1; background: var(--bg); }
/* Docked, a control keeps no margin of its own: the page's gap alone spaces the controls
   panel, so its plate starts the panel's padding above the first of them. */
:root[data-shell="play"] .docked-with { margin: 0; }
.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; }
.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; }
/* A Free learner's day used up, where the content would stand (games/shared/library.js
   freeLimitHTML; client/a-free-learner-past-the-day-is-told-in-place) */
.limit { display: flex; flex-direction: column; gap: 8px; padding: 16px 0; }
.limit h3 { margin: 0; font-size: 17px; line-height: 24px; font-weight: 600; color: var(--ink); }
.limit p { margin: 0; color: var(--muted); font-size: 15px; line-height: 20px; }
.limit .primary { margin-top: 8px; width: 100%; }
.nowrap { white-space: nowrap; }
.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 - in our shell,
   the back of the exercise's panel (app.js turnAbout), in its room, turned over as a card. */
.plate-back { position: absolute; top: 0; left: 0; right: 0; z-index: 2; overflow-y: auto; background: var(--bg);
  padding: 16px var(--gutter); }
.plate-back:focus { outline: none; }
/* While it shows, the game's own controls step aside in their room (app.js turnAbout); the
   asks with the i and the row stay. */
body.back-open .docked-with:not(.lineasks) { visibility: hidden; }
.plate-back > .about { border-left: 0; padding: 0; margin: 0; }
/* Report, moved to the controls' row, leaves the back nothing to ask. */
.about .asks:empty { display: none; }
.about { border-left: 2px solid var(--line); padding: 4px 0 4px 12px; margin-top: 8px; }
/* In the i, the asks are small controls in a row, each as wide as its words: the panel is
   facts first, and they are what may be done about them. */
.about .asks { display: flex; flex-wrap: wrap; gap: 8px; }
/* The row under a sentence's line: small actions on that sentence (Listen, Explain, Check
   meaning), close under it and borderless in the muted ink, so they read as belonging to the
   sentence and never as the task's own controls under them; the space after the row is
   larger than the space before it. Not yet connected to an assistant, an ask is underlined
   dashed, the mark the bordered controls carry as a dashed border. */
.lineasks { gap: 4px 16px; margin: 4px 0 8px; }
.row.lineasks > button, .about .asks > button { border: 0; background: transparent; padding: 4px 0; min-width: 24px; min-height: 32px; justify-content: flex-start; font-size: 14px; color: var(--muted); }
@media (hover: hover) { .row.lineasks > button:not(:disabled):hover, .about .asks > button:hover { color: var(--ink); } }
/* One of the three that cannot act now - Listen with nothing to read yet, Translate with the
   meaning shown or the task itself - stands in its place, inactive. */
.row.lineasks > button:disabled { opacity: .4; cursor: default; }
/* In our shell Report and the i stand at the row's right end (app.js dockRow). */
.row.lineasks > .rowend { margin-left: auto; }
.row.lineasks > .about-i { justify-content: center; }
/* An i left on a sentence's line in play - a flashcard's other face - is not drawn there. */
:root[data-shell="play"] .sline .about-i:not(.played *, .played-list *) { display: none; }
:root[data-coach="missing"] .row.lineasks > [data-ask], :root[data-coach="missing"] .about .asks > [data-ask] { text-decoration: underline dashed; text-underline-offset: 4px; }
.about > p, .about > .winfo { margin: 0; }
/* A choice of one among many, as a select: the current one with a chevron (host.js, the
   meaning language). */
.ghost.select { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; text-align: left; font-weight: 600; }
.ghost.select[aria-expanded="true"] svg.icon { transform: rotate(180deg); }
/* The languages under it: a list of rows, the row the target. */
.langlist { display: flex; flex-direction: column; border-top: 1px solid var(--line); }
.langlist button { background: transparent; border: 0; border-bottom: 1px solid var(--line); color: var(--ink); text-align: left; padding: 12px 4px; min-height: 44px; font-size: 16px; cursor: pointer; }
.langlist button:active { background: var(--soft); }
.langlist p { margin: 12px 4px; }
/* 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); }
@media (hover: hover) { .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; }
.refusal { display: inline-flex; align-items: center; gap: 8px; color: var(--bad); margin: 0; }
/* A setting that is on or off: its words say what it does when on, the knob says which. */
.switchrow { display: flex; align-items: center; justify-content: space-between; gap: 16px; width: 100%; min-height: 44px; padding: 8px 0; background: transparent; border: 0; color: var(--ink); font-size: 15px; text-align: left; cursor: pointer; }
/* Off: an outlined track and a grey knob; on: a filled track and a white knob, as Material 3
   draws them, so the knob stands out from its track in both schemes. */
.switchrow .knob { flex: none; position: relative; width: 44px; height: 26px; border-radius: 13px; background: transparent; box-shadow: inset 0 0 0 2px var(--muted); transition: background .15s; }
.switchrow .knob::after { content: ""; position: absolute; top: 5px; left: 5px; width: 16px; height: 16px; border-radius: 50%; background: var(--muted); transition: transform .15s; }
.switchrow[aria-checked="true"] .knob { background: var(--accent); box-shadow: none; }
.switchrow[aria-checked="true"] .knob::after { background: #fff; }
.switchrow[aria-checked="true"] .knob::after { transform: translateX(18px) scale(1.25); }
@media (prefers-reduced-motion: reduce) { .switchrow .knob, .switchrow .knob::after { transition: none; } }
.unsent { margin: 4px 0 0; display: inline-flex; align-items: center; gap: 4px; }
/* The settings' Close: pinned to the top of the page while the card scrolls under it. */
/* The settings card's bar: its name and Close, pinned at the top edge to edge, opaque, with
   a rule under it so what scrolls beneath passes under an edge, not through the text. */
/* The settings cover the screen: the app's bars go while they are open (host.js settingsCard). */
body.in-settings header.strip, body.in-settings .bottombar { display: none; }
body.in-settings.has-bottombar { padding-bottom: max(16px, env(safe-area-inset-bottom)); }
.sheethead { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; justify-content: flex-start; gap: 4px;
  margin: 0 calc(-1 * var(--gutter)); padding: 4px var(--gutter); min-height: 52px; background: var(--bg); border-bottom: 1px solid var(--line); }
.sheethead h2 { margin: 0; font-size: 17px; line-height: 24px; font-weight: 600; color: var(--ink); }
.sheethead .close { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin-left: -12px; padding: 0; background: transparent; border: 0; border-radius: 22px; color: var(--ink); cursor: pointer; }
@media (hover: hover) { .sheethead .close:hover { background: var(--soft); } }
/* The settings' bar: Cancel at its leading edge, the name in its middle, Done at its trailing
   edge (Apple HIG, sheets), both words in the accent, Done the heavier. */
.sheethead.edit { display: grid; grid-template-columns: 1fr auto 1fr; gap: 8px; }
.sheethead .barbtn { min-height: 44px; padding: 8px 0; border: 0; background: transparent; font: inherit; font-size: 17px; color: var(--accent); cursor: pointer; }
.sheethead .barbtn.lead { justify-self: start; }
.sheethead .barbtn.done { justify-self: end; font-weight: 600; }
.sheethead .barbtn:disabled { color: var(--muted); cursor: default; }
/* The question before an act that loses something (host.js confirmSheet): at the foot, over a
   veil, the act and the way back each a row of its own group. */
.askveil { position: fixed; inset: 0; z-index: 20; background: rgba(0, 0, 0, .32); }
.asksheet { position: fixed; left: 8px; right: 8px; bottom: max(8px, env(safe-area-inset-bottom)); z-index: 21; display: flex; flex-direction: column; gap: 8px; max-width: 560px; margin: 0 auto; }
.asksheet .group > p { margin: 0; padding: 12px 16px; text-align: center; }
.asksheet .srow { justify-content: center; border-top: 1px solid var(--line); }
.asksheet .group > .srow:first-child { border-top: 0; }
.asksheet .srow.back { font-weight: 600; }
/* 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; }
/* A horizontal swipe on a round is the page's own (app.js: the item follows the finger);
   the browser keeps the vertical scroll and the pinch. */
main { touch-action: pan-y pinch-zoom; }
/* The row a round's screen ends on (View.wayOut): back and End at its left, the one way
   forward at its right, every control the same height, the forward one the same width
   whether it skips or is the main action. */
.row.nav > * { flex: none; }
.row.nav > button { min-height: 44px; padding-top: 8px; padding-bottom: 8px; }
.row.nav > .icon-only { min-width: 44px; }
/* Back and End are symbols alone, unbordered: the forward control is the one the row offers. */
.row.nav > .ghost.quiet:not(.fwd) { border-color: transparent; }
.row.nav > .fwd { margin-left: auto; min-width: 48px; }
.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 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. */
/* The words and grammar forms on the i's back (View.words): a small table, its header naming the
   columns (rank, known, practice) and the words' group, a row naming the grammar's. */
.winfo { border-collapse: collapse; font-size: 13px; color: var(--muted); margin: 0; }
.winfo th, .winfo td { padding: 4px 12px 0 0; text-align: left; font-weight: 400; vertical-align: middle; white-space: nowrap; }
.winfo thead th, .winfo .grp th { font-size: 12px; }
.winfo .grp th { padding-top: 12px; }
.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 as dots (View.segments): one per item of the round, between the controls of the row
   the screen ends on - a mobile stepper's dots between its Back and Next (Material Design,
   Steppers) - and on the end card. */
.segments { display: flex; align-items: center; gap: 6px; }
.segments i { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--todo); }
/* The dots stand at the row's centre, whatever stands at its ends - a flashcard's Again
   before its arrow does not push them aside. */
.row.nav:not(.docked) { position: relative; }
.row.nav > .segments { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); pointer-events: none; }
.segments i.right, .segments i.done { 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(--todo); }
.segments i.current { background: var(--accent); }
.segments i.current-partial { background: var(--part); }
.segments i.current-missed { background: var(--bad); }
/* A finished sentence looked back at: its dot ringed. */
.segments i.viewing { outline: 2px solid var(--accent); outline-offset: 2px; }
/* On an end card the dots stand between the heading and the score. */
/* 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 round clock, picked in the row just left of Start: the phone's own list of the
   chess bullet controls (client/bullets-clock-is-a-chess-bullet-control-picked-left-of-start). */
.row.nav > .tc { margin-left: auto; position: relative; display: inline-flex; align-items: center; }
.row.nav > .tc + .fwd { margin-left: 12px; }
.tc select { appearance: none; -webkit-appearance: none; min-height: 44px; padding: 0 36px 0 12px; border: 1px solid var(--line);
  border-radius: 8px; background: var(--card); color: var(--ink); font: inherit; font-size: 17px; font-weight: 600;
  font-variant-numeric: tabular-nums; cursor: pointer; }
.tc > svg.icon { position: absolute; right: 12px; pointer-events: none; color: var(--ink); }
.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); }
/* Bullet's clock, at the top of the exercise's panel: the bar, and under it the digits of the
   last ten seconds, on a line held from the start. The sentence stands with the controls, so
   nothing under the clock moves in any case. */
.clock { display: flex; flex-direction: column; gap: 8px; }
.clock .countdown { height: 16px; margin: 0; line-height: 16px; }
/* The room under Bullet's clock, held empty so its fixes stand right above the sentence: it
   takes the rest of the exercise panel (app.js, [data-fill="scroll"]). The sentences played
   are behind the row's symbol, as in every game. In a chat host, which fits its frame to the
   App, the room is held at 72 px in play. */
.trail { overflow-y: auto; }
:root:not([data-shell="play"]) main:has(.stage) .trail { height: 72px; }
/* Bullet's controls in our shell, stacked at the bottom with nothing kept in reserve: the
   sentence at 21 px, the task and the meaning close to it, the fixes as wide as their words,
   piled from the bottom onto as many rows as the words need, 12 px from the list and the
   sentence (client/bullets-controls-stack-at-the-bottom-a-quarter-larger). */
:root[data-shell="play"] main:has(.trail) { gap: round(down, calc(8px * var(--fit, 1)), 4px); }
:root[data-shell="play"] .stage .hint { margin-bottom: 4px; font-size: 16.25px; }
:root[data-shell="play"] .stage .sline > .aside { min-height: 0; }
/* Bullet's content and its own controls, 1.25 times the size they were: the sentence, the task
   line, the meaning, the fixes (client/the-exercise-panel-is-fixed-the-controls-move-and-bullets-list-shows-at-three-lines). */
:root[data-shell="play"] .stage .ko { font-size: calc(26.25px * var(--fit, 1)); }
:root[data-shell="play"] .stage .gloss { font-size: 18.75px; }
:root[data-shell="play"] .stage .meaningline { margin-top: 4px; }
/* the sentence and its fixes stand a little above the row of asks: the list of sentences played
   no longer stands over them (design/played) */
:root[data-shell="play"] .card.stage { padding-bottom: 16px; }
:root[data-shell="play"] .fixroom { padding: 4px 0; }
:root[data-shell="play"] .fixroom .choices { display: flex; flex-wrap: wrap-reverse; gap: 4px; align-content: flex-start; align-items: flex-start; }
:root[data-shell="play"] .fixroom .choice { font-size: calc(16px * var(--fit, 1)); min-height: max(32px, calc(38px * var(--fit, 1))); padding: 4px 12px; }
/* No room is kept for the meaning: Translate adds it under the sentence. */
:root[data-shell="play"] .stage .meaningline:has(> .gloss.held) { display: none; }

/* 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: 19px; font-weight: 500; cursor: pointer; min-height: 52px; }
/* 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; }
/* A word on its way to its place leaves its box empty (Feel.travel): the box's own colour,
   set just above, must not draw it back. */
.tile.leaving, .choice.leaving { color: transparent; }
.tile.close { border-color: var(--warn); color: var(--warn); }
/* The line being built: the sentence in its own type, on a rule to build on, every word in
   its room from the first screen - a word not placed yet is held there unseen - so a word
   placed shows where it stands and moves nothing
   (client/space-says-what-belongs-together-and-a-games-exercise-is-centred). */
.built { margin-bottom: 12px; padding-bottom: 8px; border-bottom: 2px solid var(--line); }
.built .hold { visibility: hidden; }
.built .good { color: var(--good); }

/* The end of a round: the score, large; the ways on, under it. */
/* A round's end is its row: the dots and the score between nothing and another round. */
.row.nav > .endline { flex: 1; display: flex; align-items: center; justify-content: center; gap: 12px; font-size: 17px; font-weight: 600; }
.endline .score svg { width: 20px; height: 20px; }

.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; }
/* A Listen control while its line is spoken: the accent, and its waves pulsing
   (client/listen-shows-it-is-speaking); with reduced motion the colour alone. */
:root[data-speaking] .say, :root[data-speaking] [data-say], :root[data-speaking] .row.lineasks > .say:hover { color: var(--accent); }
:root[data-speaking] .say svg, :root[data-speaking] [data-say] svg { animation: speaking 0.9s ease-in-out infinite; }
@keyframes speaking { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
.reveal { border-top: 1px solid var(--line); margin-top: 12px; padding-top: 12px; }
/* Flashcards: a card that turns over. Both faces share one grid cell, so the card is as tall
   as its taller face and turning it moves nothing; the back is turned away until then. With
   reduced motion the card simply shows its other face. */
.flip { perspective: 1200px; position: relative; isolation: isolate; touch-action: pan-y; -webkit-user-select: none; user-select: none; }
:root[data-shell="play"] .flip { touch-action: none; }
/* In a host's narrow gutter the card stands in a little, so the heap's cards, off square, stay
   on the screen. */
:root:not([data-shell="play"]) .flip { margin-inline: 8px; }
.flip-inner { display: grid; transition: transform 0.45s ease; transform-style: preserve-3d; }
.flip.on .flip-inner { transform: rotateY(180deg); }
.face { grid-area: 1 / 1; position: relative; display: flex; flex-direction: column; justify-content: flex-start; gap: 8px;
  min-height: 176px; padding: 48px 16px 40px; border: 1px solid var(--line); border-radius: 16px; background: var(--card);
  backface-visibility: hidden; -webkit-backface-visibility: hidden; }
.face.front, .flip.on .face.back { cursor: pointer; }
/* In our shell the card reaches down to the controls (app.js dockRow): what it says stands
   in its middle. */
:root[data-shell="play"] .face { justify-content: center; }
.face.back { transform: rotateY(180deg); }
/* Turned away, the back is not on the screen at all - not to be read, found or counted - until the turn begins. */
.flip:not(.on) .face.back { visibility: hidden; }
.face .ko, .face .gloss.task { text-align: center; }
.face .gloss.task { margin: 0; }
/* On a card the sentence is centred on the card: its i goes to the corner, its row under it. */
.face .sline { justify-content: center; }
.face .sline > .aside { position: absolute; top: 12px; right: 12px; min-height: 0; }
.face .row.lineasks { justify-content: center; }
.face > .about { text-align: left; }
.face > .gloss, .face > .muted { text-align: center; margin: 0; }
.flip-mark { position: absolute; right: 12px; bottom: 12px; color: var(--muted); line-height: 0; }
/* A real card on an uneven heap: paper, a grain, a soft shadow; under it up to three cards of
   the deck, each a little off square (client/a-flashcard-is-thrown-with-a-swing-or-put-under-the-heap). */
.flip .face, .flip .heap { background-color: color-mix(in oklab, var(--bg) 93%, #c9a36a);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .5 0 0 0 0 .45 0 0 0 0 .4 0 0 0 .07 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  border-color: color-mix(in oklab, var(--line) 75%, #a4845a);
  box-shadow: 0 1px 1px rgba(0,0,0,.10), 0 4px 14px rgba(0,0,0,.10); }
.flip .heap { position: absolute; inset: 0; border-width: 1px; border-style: solid; pointer-events: none; }
/* Again, left of the arrow: live while the card shows its back, plainly inactive before. */
.row.nav > .again { margin-left: auto; color: var(--ink); }
.row.nav > .again + .fwd { margin-left: 8px; }
.row.nav > .again:disabled { opacity: .3; }
@media (prefers-reduced-motion: reduce) { .flip-inner { transition: none; } }
.choices[hidden-held] { visibility: hidden; }

/* 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: grid; grid-template-columns: 24px 1fr 24px; align-items: center; gap: 16px; min-height: 56px; color: var(--ink); padding: 8px 0; border-bottom: 1px solid var(--line); }
.hub-list a.card b { font-size: 17px; font-weight: 600; }
/* A game's row under the finger, and while its game opens (host.js openingRows): the pressed
   shade, and in the chevron's place a ring turning in the accent; with reduced motion the
   ring stands still. */
/* The shade runs to the column's edges, past the row's own, as a phone's lists do, and the
   row's lines above and below run with it; the icon and the ring stand a gutter in from the
   shade's ends (client/a-pressed-game-rows-lines-run-to-the-screens-edges). */
.hub-list a.card { position: relative; }
.hub-list a.card.pressed, .hub-list a.card.opening { background: var(--soft);
  box-shadow: calc(-1 * var(--gutter)) 0 0 var(--soft), var(--gutter) 0 0 var(--soft); }
.hub-list a.card.pressed::before, .hub-list a.card.opening::before { content: ""; position: absolute;
  left: calc(-1 * var(--gutter)); right: calc(-1 * var(--gutter)); top: -1px; bottom: -1px;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); pointer-events: none; }
.hub-list .game-in { position: relative; }
.hub-list a.card.opening .game-in svg { visibility: hidden; }
.hub-list a.card.opening .game-in::after { content: ""; position: absolute; right: 1px; top: 50%; width: 16px; height: 16px; margin-top: -8px;
  box-sizing: border-box; border: 2px solid var(--line); border-top-color: var(--accent); border-radius: 50%; animation: opening 0.8s linear infinite; }
@keyframes opening { to { transform: rotate(360deg); } }
/* The page's reduced-motion rule reaches elements, not a pseudo-element: the ring's own. */
@media (prefers-reduced-motion: reduce) { .hub-list a.card.opening .game-in::after { animation: none; } }
/* A game's icon and the way in: the page's columns, in the muted ink; the name between. */
.hub-list .game-icon, .hub-list .game-in { display: inline-flex; color: var(--muted); }
.hub-list .game-in { justify-content: flex-end; }
/* Sign out ends the settings card, set apart from what is above it. */
.signout-row { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line); }

/* The settings (host.js settingsCard): groups of rows, as a phone's own settings are
   (client/the-settings-are-grouped-rows-under-their-own-bar). A group is outlined and its rows are parted by a
   hairline; a row is its label, its value in the muted ink and a chevron, or an action in the
   accent; a choice opens its options inside the group, the chosen one checked. */
.settings { display: flex; flex-direction: column; gap: 24px; }
.group { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--card); }
.settings .group > * + * { border-top: 1px solid var(--line); }
.settings .group > .rownote { border-top: 0; }
.srow { display: flex; align-items: center; gap: 12px; width: 100%; min-height: 52px; padding: 12px 16px; background: transparent; border: 0; border-radius: 0; color: var(--ink); font-size: 16px; line-height: 1.3; text-align: left; text-decoration: none; cursor: pointer; }
.srow > span:first-child { flex: none; }
.srow .val { flex: 1 1 auto; min-width: 0; text-align: right; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.srow > svg.icon { flex: none; color: var(--muted); margin-left: auto; }
.srow .val + svg.icon { margin-left: 0; }
.srow[aria-expanded="true"] > svg.icon { transform: rotate(180deg); }
.srow:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
@media (hover: hover) { .srow:hover { background: var(--soft); } }
.srow.act { color: var(--accent); }
/* An action row with nothing to act on yet (Save before a change): in its place, inactive. */
.srow.act:disabled { color: var(--muted); cursor: default; }
@media (hover: hover) { .srow.act:disabled:hover { background: transparent; } }
/* What the username typed came to - Taken with two free names to tap, or Can't be used - on a
   line under the field that is part of its row (no line between them) and holds its room, so
   nothing below moves when it speaks. */
.rownote.namenote, .rownote.namenote:empty { display: flex; align-items: center; justify-content: flex-end; gap: 8px; min-height: 36px; padding: 0 16px 8px; white-space: nowrap; }
.namenote .refusal { font-size: 14px; }
.srow.danger { color: var(--bad); }
/* Sign out: one action, a word in the colour of what ends something, not a group of its own;
   its words in line with the words inside the groups. */
.settings > .textbtn.signout { align-self: flex-start; padding: 12px 16px; color: var(--bad); font-size: 16px; font-weight: 400; }
/* A choice's options, set in from its row; the chosen one carries the check. */
.settings .opts, .library .opts { display: flex; flex-direction: column; background: var(--soft); }
.settings .opts > .srow, .library .opts > .srow { padding-left: 28px; min-height: 48px; }
.settings .opts > .srow + .srow, .library .opts > .srow + .srow { border-top: 1px solid var(--line); }
.settings .opts > .srow.sopt > svg.icon, .library .opts > .srow.sopt > svg.icon { color: var(--accent); visibility: hidden; }
.settings .opts > .srow.sopt[aria-checked="true"], .library .opts > .srow.sopt[aria-checked="true"] { font-weight: 600; }
.settings .opts > .srow.sopt[aria-checked="true"] > svg.icon, .library .opts > .srow.sopt[aria-checked="true"] > svg.icon { visibility: visible; }
.settings .opts label.search { margin: 8px 12px; }
.settings .opts .langlist { border-top: 0; }
.settings .opts .langlist button { padding: 12px 28px; }
/* What a row says back - Taken, Saved, Connected, the steps to connect - under it, in the group. */
.rownote { padding: 0 16px 12px; }
.rownote:empty { display: none; }
/* A group whose one row is hidden (Install, while the browser offers nothing) is not drawn. */
.group:not(:has(> :not([hidden]))) { display: none; }
/* The username, typed on its own row: the label, and the field taking the rest. */
.srow.field { cursor: text; }
.srow.field label { flex: none; }
.srow.field input { flex: 1 1 auto; min-width: 0; border: 0; background: transparent; color: var(--ink); font: inherit; font-size: 16px; text-align: right; padding: 0; outline: none; }
.srow.field:focus-within { background: var(--soft); }
.srow.switchrow { justify-content: space-between; }
.srow.switchrow > span:first-child { flex: 1 1 auto; min-width: 0; }
/* The colour scheme: three words, one chosen, in one control. */
.seg { display: flex; gap: 2px; padding: 2px; border: 1px solid var(--line); border-radius: 12px; }
.seg button { flex: 1; min-height: 44px; border: 0; border-radius: 10px; background: transparent; color: var(--ink); font-size: 15px; cursor: pointer; }
.seg button[aria-checked="true"] { background: var(--soft); font-weight: 600; box-shadow: inset 0 0 0 1px var(--line); }
.seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Signed out, the home page is the sign-in step alone: the app's mark and Continue with
   Google in the middle of the screen; the games, their actions and the status wait. */
body.signed-out main > :not(.step), body.signed-out #nudge, body.signed-out header.strip,
body.signed-out #bottombar { display: none !important; }
body.signed-out { padding-bottom: max(16px, env(safe-area-inset-bottom)); }
.step { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 32px 0; }
.step-mark { display: block; width: 80px; height: 72px; color: var(--ink); }
.step-mark-lines { fill: var(--bg); stroke: var(--bg); }
.step-name { margin: 16px 0 32px; font-size: 20px; font-weight: 600; color: var(--ink); }
/* Google's sign-in button as Google draws it: a pill, its G, its words; light and dark. */
.gsi { display: inline-flex; align-items: center; gap: 12px; height: 44px; padding: 0 16px; border-radius: 22px;
  background: #ffffff; border: 1px solid #747775; color: #1f1f1f; font: 500 14px/20px Roboto, Arial, sans-serif; cursor: pointer; }
.gsi-g { width: 20px; height: 20px; flex: none; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .gsi { background: #131314; border-color: #8e918f; color: #e3e3e3; } }
:root[data-theme="dark"] .gsi { background: #131314; border-color: #8e918f; color: #e3e3e3; }
/* The home page's ways to more sentences, under the list of games: text buttons alone. */
.moreacts { display: flex; flex-direction: column; align-items: flex-start; }
.textbtn { background: transparent; border: 0; padding: 12px 0; min-height: 44px; font: inherit; font-size: 15px;
  font-weight: 600; color: var(--accent); cursor: pointer; }
@media (hover: hover) { .textbtn:hover { text-decoration: underline; } }
/* Their icons in the games' icon column, their words where the games' names start. */
.moreacts .textbtn { gap: 16px; }
iframe.app { display: block; width: 100%; border: 0; background: transparent; }
/* Transparent, not hidden: Chromium stops drawing a cross-origin frame it counts as hidden, so
   the App never reported its drawn round and the frame waited out host.js's 1.5 s instead. */
iframe.app.pending { opacity: 0; pointer-events: none; }
input.ghost { width: 100%; border-radius: 8px; }
button.link { background: none; border: 0; color: var(--accent); text-decoration: underline; cursor: pointer; font-size: inherit; padding: 0; }

/* The Collections page (games/shared/library.js): a search field, two groups of rows that
   show and never edit, and a collection's modal. The field as iOS draws one (UIKit's
   UISearchTextField defaults): 36 px tall with 10 px corners in the tertiary fill, the loupe
   8 px in, the placeholder in the secondary label colour, 17 px text; a clear button once
   there is text; Cancel in the tint beside it while it is edited, the field giving it room. */
.library { display: flex; flex-direction: column; gap: 8px; max-width: 560px; width: 100%; margin: 0 auto; }
.lib-head { margin-bottom: 4px; }
.lib-page { display: flex; flex-direction: column; gap: 8px; }
.searchbar { display: flex; align-items: center; margin: 4px 0 0; }
.sfield { position: relative; flex: 1; min-width: 0; display: flex; align-items: center; height: 36px; border-radius: 10px;
  background: rgba(118, 118, 128, .12); color: rgba(60, 60, 67, .6); }
.sfield .loupe { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); display: inline-flex; pointer-events: none; }
.sfield .lsearch { flex: 1; min-width: 0; height: 36px; margin: 0; padding: 0 32px; border: 0; border-radius: 10px; background: transparent;
  font: inherit; font-size: 17px; line-height: 22px; color: var(--ink); -webkit-appearance: none; appearance: none; }
.sfield .lsearch::placeholder { color: rgba(60, 60, 67, .6); opacity: 1; }
.sfield .lsearch::-webkit-search-cancel-button, .sfield .lsearch::-webkit-search-decoration { -webkit-appearance: none; display: none; }
.sfield .lsearch:focus { outline: none; }
.sfield .sclear { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); width: 22px; height: 22px; padding: 0; border: 0; border-radius: 50%;
  background: rgba(60, 60, 67, .6); color: var(--bg); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
.scancel { flex: none; width: 0; padding: 0; overflow: hidden; border: 0; background: transparent; color: var(--accent); font: inherit; font-size: 17px; line-height: 22px;
  white-space: nowrap; cursor: pointer; transition: width .2s ease, margin .2s ease; }
.searchbar.editing .scancel { width: auto; margin-left: 8px; padding: 0 2px; }
@media (prefers-reduced-motion: reduce) { .scancel { transition: none; } }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .sfield { background: rgba(118, 118, 128, .24); color: rgba(235, 235, 245, .6); }
  :root:not([data-theme="light"]) .sfield .lsearch::placeholder { color: rgba(235, 235, 245, .6); }
  :root:not([data-theme="light"]) .sfield .sclear { background: rgba(235, 235, 245, .6); } }
:root[data-theme="dark"] .sfield { background: rgba(118, 118, 128, .24); color: rgba(235, 235, 245, .6); }
:root[data-theme="dark"] .sfield .lsearch::placeholder { color: rgba(235, 235, 245, .6); }
:root[data-theme="dark"] .sfield .sclear { background: rgba(235, 235, 245, .6); }
.noresults { padding: 48px 16px; text-align: center; }
.nr-title { margin: 0; font-size: 22px; font-weight: 700; }
.nr-sub { margin: 4px 0 0; color: var(--muted); font-size: 17px; }
/* the groups' headings, and a row: the title, by whom and how many sentences, at its end
   what is done to it - practising, off - then the chevron; off dims it */
.ghead { margin: 12px 4px 0; font-size: 13px; color: var(--muted); display: flex; justify-content: space-between; }
.ghead .count { font-variant-numeric: tabular-nums; }
.lrow { display: flex; align-items: center; gap: 8px; width: 100%; min-height: 56px; padding: 0 12px; border: 0; border-top: 1px solid var(--line);
  background: transparent; color: var(--ink); font: inherit; text-align: left; cursor: pointer; }
.group > .lrow:first-child { border-top: 0; }
.lcol { flex: 1; min-width: 0; display: flex; flex-direction: column; padding: 8px 0; }
.lt { font-weight: 600; font-size: 16px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ls { font-size: 13px; color: var(--muted); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* a row's marks: a glyph from the icon family with its figure beside it, in the second
   line's muted ink; practising in the ink with the i's five-cell meter; off alone at the
   row's end, the power symbol struck, in the grey of a step still to come */
.ls .mark { display: inline-flex; align-items: center; gap: 3px; vertical-align: -2px; font-variant-numeric: tabular-nums; }
.ls .mark.practise { color: var(--ink); }
.ls .mark .meter { margin-left: 2px; }
.ls .mark .meter i { width: 4px; height: 8px; }
.lrow > .mark.off { flex: none; display: inline-flex; color: var(--todo); }
.lrow.off .lt, .lrow.off .ls { opacity: .55; }
.lrow > svg.icon { color: var(--muted); flex: none; }
.srow.more { color: var(--accent); }
/* a corpus holding works: the row opens its settings; its chevron, a target of its own,
   opens it out and closes it, turning up when open as the settings' choices do */
.lrow.chead { padding: 0; gap: 0; cursor: default; }
.lrow.chead .lwhat { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; min-height: 56px; padding: 0 0 0 12px;
  border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }
.lrow.chead .chevb { flex: none; width: 56px; min-height: 56px; display: inline-flex; align-items: center; justify-content: flex-end;
  padding: 0 12px 0 0; border: 0; background: transparent; color: var(--muted); cursor: pointer; }
.lrow.chead .chevb svg { transition: transform .2s; }
.lrow.chead .chevb[aria-expanded="true"] svg { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .lrow.chead .chevb svg { transition: none; } }
.lrow.chead.off .lwhat .lt, .lrow.chead.off .lwhat .ls { opacity: .55; }
.lrow.chead .lwhat:focus-visible, .lrow.chead .chevb:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* its works, set in under it */
.lrow.sub { padding-left: 28px; min-height: 52px; }
.lrow.sub .lt { font-weight: 500; font-size: 15px; }
.srow.more.sub { padding-left: 28px; border-top: 1px solid var(--line); }
.loadmore.sub { margin: 0; padding: 12px 28px; border-top: 1px solid var(--line); }
/* a work's On: the value and the chevron open its choices in place, as the settings' do */
.srow.pick { padding-right: 8px; }
.srow.pick .pickb { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; border: 0; background: transparent; color: var(--ink);
  font: inherit; font-size: 16px; padding: 0; min-height: 44px; text-align: left; cursor: pointer; }
.srow.pick .pickc { flex: none; display: inline-flex; align-items: center; border: 0; background: transparent; color: var(--muted);
  padding: 0 4px; min-width: 32px; min-height: 44px; justify-content: center; cursor: pointer; }
.srow.pick .pickc svg { transition: transform .2s; }
.srow.pick .pickc[aria-expanded="true"] svg { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .srow.pick .pickc svg { transition: none; } }
.empty { color: var(--muted); margin: 12px 4px; }
/* a collection's modal: a sheet at the foot over a scrim, scrolling inside */
.library .scrim { position: fixed; inset: 0; z-index: 20; background: rgba(0, 0, 0, .35); }
.library .sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 21; max-height: 88%; display: flex; flex-direction: column; max-width: 560px; margin: 0 auto;
  background: var(--bg); border-radius: 20px 20px 0 0; padding: 8px 20px 0; box-shadow: 0 -4px 24px rgba(0, 0, 0, .2); }
.library .sbar { display: flex; justify-content: flex-end; }
.library .sclose { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin-right: -12px; border: 0; background: transparent; color: var(--muted); cursor: pointer; }
.library .sbody { position: relative; overflow-y: auto; padding-bottom: max(28px, env(safe-area-inset-bottom)); display: flex; flex-direction: column; }
/* what it holds keeps its height and scrolls, never squeezed shut */
.library .sbody > * { flex: none; }
.library .sheet h3 { margin: 0 0 4px; font-size: 22px; text-wrap: balance; }
.smeta { margin: 0 0 12px; color: var(--muted); font-size: 14px; }
.sdesc { margin: 0 0 12px; line-height: 1.45; }
.library .sheet .attribution { margin: 0 0 8px; }
.sample { border-left: 2px solid var(--line); padding: 2px 0 2px 12px; margin: 0 0 12px; }
.sline-ko { margin: 8px 0 0; font-size: 17px; font-weight: 500; word-break: keep-all; }
/* its meaning on the line after a dash, in the muted small text a round shows a meaning in */
.sline-beside { font-size: 15px; font-weight: 400; color: var(--muted); }
.sample .lineasks { margin: 4px 0 0; }
.sample .lineasks > button { flex: 0 1 auto; text-align: left; padding-left: 0; }
.library .sheet .ghead { margin-top: 8px; }
.library .sheet .group + .group { margin-top: 12px; }
/* What a collection's lines mark to practise: a word underlined, the words of a grammar form
   dotted under them, in the accent; under Practise, the list of them, an item a word inside a
   line of text, and the row a tap opens (corpus/a-collection-practises-what-its-sentences-mark). */
.mkword { text-decoration: underline; text-decoration-color: var(--accent); text-decoration-thickness: 2px; text-underline-offset: 4px; }
.mkform { text-decoration: underline dotted; text-decoration-color: var(--accent); text-decoration-thickness: 2px; text-underline-offset: 4px; }
.mkword.mkform { text-decoration-style: double; }
.mkgroup { padding: 12px 16px; display: flex; flex-direction: column; gap: 12px; }
.mkpart { display: flex; flex-direction: column; gap: 4px; }
.mklabel { margin: 0; display: flex; justify-content: space-between; gap: 8px; font-size: 14px; }
.mklabel .count { color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; }
.mkrun { margin: 0; font-size: 15px; line-height: 1.6; }
.mki { white-space: nowrap; }
.mkw { background: none; border: 0; padding: 0; font: inherit; color: var(--ink); cursor: pointer; }
.mkw.sel { color: var(--accent); font-weight: 600; }
.mkgroup .srow.more { margin: -8px -16px 0; width: calc(100% + 32px); }
.mkdet { display: flex; flex-direction: column; gap: 8px; }
.mkdet .sline-ko { margin: 0; }
/* a grammar form's name is long: it wraps in its cell, so the row keeps its columns on a narrow screen */
.mkdet .winfo tbody th { white-space: normal; }
/* How well she knows the marked words: the heatmap, its key, and the histogram along the whole
   list that scrolls under its count axis, with the strip of the whole list under it
   (client/a-collections-sheet-shows-how-its-marked-words-are-known). */
.library .stmap { display: flex; flex-direction: column; gap: 4px; }
.library .stheat { display: grid; grid-template-columns: auto 1fr auto; column-gap: 8px; row-gap: 4px; align-items: center; }
.library .strank { font-size: 11px; color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }
.library .strank.stto { text-align: left; }
.library .strow { display: grid; gap: 4px; }
.library .strow.cols16 { grid-template-columns: repeat(16, 1fr); }
.library .strow.cols12 { grid-template-columns: repeat(12, 1fr); }
.library .hytitle { transform-box: fill-box; transform-origin: center; transform: rotate(-90deg); }
.library .stc { width: 100%; aspect-ratio: 1; padding: 0; border: 0; border-radius: 3px; cursor: pointer; display: block; }
.library .stc.l0 { background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 28%, transparent); }
.library .stc.l1 { background: color-mix(in srgb, var(--accent) 38%, var(--card)); }
.library .stc.l2 { background: color-mix(in srgb, var(--accent) 54%, var(--card)); }
.library .stc.l3 { background: color-mix(in srgb, var(--accent) 70%, var(--card)); }
.library .stc.l4 { background: color-mix(in srgb, var(--accent) 85%, var(--card)); }
.library .stc.l5 { background: var(--accent); }
.library .stc.sel { outline: 2px solid var(--ink); outline-offset: 1px; }
.library .stkey { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; font-size: 12px; color: var(--muted); }
.library .stkey .krow { display: inline-flex; align-items: center; gap: 4px; }
.library .stkey .stc { width: 12px; height: 12px; cursor: default; }
.library .sthist { display: flex; flex-direction: column; gap: 4px; }
.library .sthint { margin: 0; font-size: 13px; }
.library .hwrap { display: flex; align-items: flex-start; }
.library .hyax { flex: none; display: block; overflow: visible; }
.library .hscroll { flex: 1; min-width: 0; overflow-x: auto; overscroll-behavior-x: contain; }
.library .hplot { display: block; overflow: visible; }
.library .hhit { fill: transparent; cursor: pointer; }
.library .hall { fill: color-mix(in srgb, var(--ink) 30%, transparent); }
.library .hmet { fill: var(--accent); }
.library .hbar.sel .hall { fill: color-mix(in srgb, var(--ink) 55%, transparent); }
.library .hax, .library .htick { stroke: var(--muted); stroke-width: 1; }
.library .hnum { fill: var(--muted); font-size: 10px; font-variant-numeric: tabular-nums; }
.library .hover-wrap { margin: 4px 0 0 40px; display: flex; flex-direction: column; gap: 4px; }
.library .hstrip { position: relative; touch-action: none; cursor: grab; padding: 4px 0; }
.library .hover { width: 100%; height: 24px; display: block; }
.library .hhandle { position: absolute; top: 0; bottom: 0; border-radius: 4px; border: 2px solid var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); box-sizing: border-box; display: flex; align-items: center; justify-content: center; gap: 4px; min-width: 12px; pointer-events: none; }
.library .hhandle i { width: 1px; height: 8px; background: var(--accent); }
.library .hends { display: flex; justify-content: space-between; font-size: 10px; color: var(--muted); font-variant-numeric: tabular-nums; }
.library .hkey { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; font-size: 12px; color: var(--muted); margin: 8px 0 0 40px; }
.library .hkey > span { display: inline-flex; align-items: center; gap: 4px; }
.library .hkey .hk { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
.library .hkey .hk.met { background: var(--accent); }
.library .hkey .hk.all { background: color-mix(in srgb, var(--ink) 30%, transparent); }
.library .hkey .hx { flex-basis: 100%; }
/* a row of the modal: its word, the room, the small i, the switch; on or off is iOS's switch */
.srow.still { cursor: default; }
.srow .sl { flex: none; }
.srow .sv { flex: 1; min-width: 0; text-align: right; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.switch { flex: none; width: 51px; height: 31px; border-radius: 16px; border: 0; background: rgba(120, 120, 128, .16); position: relative; cursor: pointer; padding: 0; transition: background .2s; }
.switch span { position: absolute; top: 2px; left: 2px; width: 27px; height: 27px; border-radius: 50%; background: #fff; box-shadow: 0 3px 8px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .16); transition: left .2s; }
.switch.on { background: #34c759; }
.switch.on span { left: 22px; }
.switch:disabled { opacity: .4; cursor: default; }
@media (prefers-reduced-motion: reduce) { .switch, .switch span { transition: none; } }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .switch { background: rgba(120, 120, 128, .32); } }
:root[data-theme="dark"] .switch { background: rgba(120, 120, 128, .32); }
/* the page stands on solid ground, as the settings do, the column's width */
:root.shell .library { background: var(--bg); width: auto; margin: 0 calc(-1 * var(--gutter)); padding: 0 var(--gutter) 24px; min-height: 100vh; min-height: 100svh; box-sizing: border-box; max-width: none; }
@media (min-width: 592px) { :root.shell .library { margin: 0 auto; max-width: 560px; padding-left: 0; padding-right: 0; } }
.srow.dim { opacity: .45; }
.srow .about-i { flex: none; margin-right: 4px; }
.srow .sv.date { font: inherit; font-size: 15px; border: 0; background: transparent; color: var(--ink); text-align: right; flex: none; }
.srow .sv.date:disabled { color: var(--muted); }
.wrow { display: inline-flex; align-items: center; gap: 8px; flex: 1; justify-content: flex-end; }
.wrow input[type=range] { width: 160px; max-width: 50%; accent-color: var(--accent); }
.wv { font-variant-numeric: tabular-nums; font-size: 14px; color: var(--muted); min-width: 28px; text-align: right; }
/* the help: a popover on the i that opened it, as iOS draws one, an arrow on the control */
.about-i.open { color: var(--ink); }
.pop { position: absolute; z-index: 5; width: 260px; max-width: calc(100% - 24px); box-sizing: border-box; padding: 10px 12px; border-radius: 12px;
  background: var(--bg); color: var(--ink); border: 1px solid var(--line); box-shadow: 0 8px 24px rgba(0, 0, 0, .2); }
.pop p { margin: 0; font-size: 15px; line-height: 1.45; }
.pop::after { content: ""; position: absolute; left: var(--ax, 50%); bottom: -6px; width: 12px; height: 12px; margin-left: -6px; background: var(--bg);
  border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); transform: rotate(45deg); border-radius: 0 0 2px 0; }
.pop.below::after { bottom: auto; top: -6px; border: 0; border-left: 1px solid var(--line); border-top: 1px solid var(--line); border-radius: 2px 0 0 0; }
/* a sentence's i: the collection's name and the contributor's on the source line are
   links, and a name opens a menu from it - the way to it, then Show less and Block on it,
   the one chosen checked */
.srclink { border: 0; background: transparent; padding: 0; font: inherit; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.srclink[aria-expanded="true"] { color: var(--accent); }
.menuroom { position: relative; }
.menu { position: absolute; left: 0; top: 2px; z-index: 6; min-width: 220px; background: var(--bg); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .18); padding: 6px; display: flex; flex-direction: column; }
.mtitle { margin: 6px 12px 4px; font-size: 13px; color: var(--muted); }
.mitem { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px; padding: 0 12px; border: 0; border-radius: 8px;
  background: transparent; color: var(--ink); font: inherit; font-size: 15px; text-align: left; cursor: pointer; }
@media (hover: hover) { .mitem:hover { background: var(--soft); } }
.msep { height: 1px; margin: 4px 0; background: var(--line); }

/* Going into a game slides it in from the right over the home page, which draws back a
   little; coming home slides the other way: the platforms' push and pop between two pages of
   the app (pwa.js names the direction). Nothing moves for a reader who turned motion off. */
@view-transition { navigation: auto; }
@keyframes lc-in-from-right { from { transform: translateX(100%); } }
@keyframes lc-out-to-left { to { transform: translateX(-30%); } }
@keyframes lc-in-from-left { from { transform: translateX(-30%); } }
@keyframes lc-out-to-right { to { transform: translateX(100%); } }
html:active-view-transition-type(forward)::view-transition-new(root) { animation: 280ms cubic-bezier(.2, .8, .2, 1) both lc-in-from-right; z-index: 1; }
html:active-view-transition-type(forward)::view-transition-old(root) { animation: 280ms cubic-bezier(.2, .8, .2, 1) both lc-out-to-left; }
html:active-view-transition-type(back)::view-transition-new(root) { animation: 280ms cubic-bezier(.2, .8, .2, 1) both lc-in-from-left; }
html:active-view-transition-type(back)::view-transition-old(root) { animation: 280ms cubic-bezier(.2, .8, .2, 1) both lc-out-to-right; z-index: 1; }
@media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }

/* Next Word as one flow (games/next/flow.js; client/next-word-is-one-flow-of-keys-4). */
/* the sentence in play: the timer line on top, the meaning whole above the rows its words take,
   the words whole on up to three rows ending at the caret */
.flow-now { position: relative; height: calc((16px + 3 * 22px * 1.3 + 12px + 3 * 28px * 1.4) * var(--fit, 1)); }
.flow-now .meaning-now { position: absolute; left: 0; right: 0; margin: 0; line-height: 1.3; color: var(--ink); transition: bottom .3s ease; }
.flow-rows { position: absolute; left: 0; right: 0; bottom: 0; height: calc(3 * 28px * 1.4 * var(--fit, 1)); font-weight: 600; }
.flow-rows .ww { position: absolute; left: 0; top: 0; white-space: nowrap; line-height: 1.4; color: var(--ink); transition: transform .28s ease, opacity .28s ease; will-change: transform; }
.flow-rows .ww.missed, .flow-rows .ww.partial { color: var(--muted); }
.flow-rows .flow-caret { position: absolute; left: 0; top: 0; width: 2px; background: var(--accent); animation: caret-blink 1.06s steps(1) infinite; transition: transform .28s ease; }
@media (prefers-reduced-motion: reduce) { .flow-now .meaning-now, .flow-rows .ww, .flow-rows .flow-caret { transition: none; } }
/* the timer: one piece a word still to come, the word due next the first, shortening */
.flow-now .timer { position: absolute; left: 0; right: 0; top: 0; height: 12px; pointer-events: none; }
.flow-now .timer .sg { position: absolute; top: 4px; height: 4px; border-radius: 2px; background: var(--muted); }
.flow-now .timer .sg.now { background: var(--ink); }
/* the learner's own keyboard types into a field nobody sees, laid over the sentence */
.flow-now .typed { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; border: 0; padding: 0; font-size: 16px; color: transparent; background: transparent; caret-color: transparent; }
/* the sentences played, on the exercise panel's back: in the order played from its top, each a
   line to tap - the sentence, its meaning under it */
.played-sentences { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.played-sentences .played-line { display: flex; flex-direction: column; gap: 4px; width: 100%; padding: 8px 0; border: 0; border-bottom: 1px solid var(--soft); background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer; }
.played-sentences .played-line .ko { font-size: 17px; font-weight: 600; }
.played-sentences .played-line .gloss { margin: 0; font-size: 14px; color: var(--muted); }
.played-sentences .played-line .missed { color: var(--muted); }
.played-sentences .played-facts { padding-block: 8px 12px; }
.played-sentences .played-facts { touch-action: pan-y; }
/* how the sentence was answered, shown when asked for: one more group of the info, in the
   info's own type - a link like the source's, the words in ink like its forms; one ink, no
   mark of right or wrong */
.played-facts .answer-group { display: flex; flex-direction: column; gap: 4px; }
.played-facts .answer-head { margin: 0; font-size: 12px; color: var(--muted); }
.played-facts .answer { margin: 0; font-size: 13px; line-height: 1.5; color: var(--muted); }
.played-facts .answer-part { display: block; }
.played-facts .answer-w { font-weight: 500; color: var(--ink); white-space: nowrap; }
/* the sentence open and its details on one card, its line held at the panel's top while its
   details scroll under it */
.played-sentences > li { border-radius: 12px; transition: background-color .2s ease; }
.played-sentences > li.open { margin-inline: -12px; padding: 0 12px 4px; background: var(--soft); overflow-x: clip; }
.played-sentences > li.open > .played-line { position: sticky; top: 0; z-index: 1; width: calc(100% + 24px); margin-inline: -12px; padding-inline: 12px; background: var(--soft); border-bottom-color: var(--line); }
/* what scrolls past the panel's top and foot fades out in its padding, never cut on a line: a
   shade of the page's own ground held over each, so nothing under the panel shows through */
.plate-back:has(.played-sentences)::before, .plate-back:has(.played-sentences)::after { content: ""; display: block; position: sticky; z-index: 2; height: 16px; pointer-events: none; }
.plate-back:has(.played-sentences)::before { top: -16px; margin-block: -16px 0; background: linear-gradient(var(--bg), transparent); }
.plate-back:has(.played-sentences)::after { bottom: -16px; margin-block: 0 -16px; background: linear-gradient(transparent, var(--bg)); }
/* the sentence's own controls are docked over the row; its empty line holds no room */
.flowcard > .sline { height: 0; overflow: hidden; }

/* the keys: a keyboard of the words to come, each key as wide as its word */
.keys { --key-h: calc(50px * var(--fit, 1)); display: flex; flex-wrap: wrap; align-content: flex-start; gap: 8px; overflow: hidden; }
.key[data-tone="0"] { --top: var(--cap0-top); --side: var(--cap0-side); --edge: var(--cap0-edge); }
.key[data-tone="1"] { --top: var(--cap1-top); --side: var(--cap1-side); --edge: var(--cap1-edge); }
.key[data-tone="2"] { --top: var(--cap2-top); --side: var(--cap2-side); --edge: var(--cap2-edge); }
.key[data-tone="3"] { --top: var(--cap3-top); --side: var(--cap3-side); --edge: var(--cap3-edge); }
.key[data-tone="4"] { --top: var(--cap4-top); --side: var(--cap4-side); --edge: var(--cap4-edge); }
.key { position: relative; flex: none; display: flex; align-items: flex-start; justify-content: center; height: var(--key-h, 50px); padding: 0 4px 8px;
  border: 1px solid var(--edge); border-radius: 9px; background: var(--side); color: var(--cap-ink);
  box-shadow: 0 2px 3px var(--cap-shadow); font: inherit; font-size: calc(17px * var(--fit, 1)); font-weight: 600; letter-spacing: -0.02em;
  white-space: nowrap; cursor: pointer; touch-action: manipulation; user-select: none; -webkit-user-select: none; }
/* the top face, dished slightly toward its centre, set in from the side */
.key > span { position: relative; top: 2px; display: flex; align-items: center; justify-content: center; height: 100%; padding: 0 12px;
  border-radius: 7px; background: radial-gradient(120% 90% at 50% 35%, var(--top) 55%, color-mix(in srgb, var(--top) 80%, var(--side)) 100%);
  box-shadow: inset 0 1px 0 var(--cap-shine), 0 1px 0 color-mix(in srgb, var(--side) 70%, black 30%); }
/* pressed: the key goes down and its side shortens, for as long as the finger is on it */
.key.pressed { transform: translateY(4px); box-shadow: 0 0 1px var(--cap-shadow); padding-bottom: 4px; }
.key.pressed > span { top: 3px; }
/* a key for a later word: only darker while it is down */
.key.dim > span { filter: brightness(.84); }
/* the light around a right key: ink under yellow, as a cartoon draws it */
.burst { pointer-events: none; z-index: 6; overflow: visible; }
.burst polygon { fill: none; stroke-linejoin: miter; stroke-miterlimit: 8; }
.burst .ink { stroke: #6b3d00; stroke-width: 4.2; }
.burst .flash { stroke: #ffe600; stroke-width: 2.4; }

/* the learner's choices, where the round's dots stood: speed and sound at the left, the rows of
   keys at the right, spread evenly, the keyboard toggle after them - each a symbol between
   minus and plus, its level a tiny number above the symbol */
.flowset { gap: 12px; color: var(--muted); font-size: 14px; }
.flowset .controls { flex: 1; display: flex; align-items: center; justify-content: space-between; min-width: 0; }
.flowset .choice-set { display: inline-flex; align-items: center; gap: 4px; }
.flowset .level { display: inline-flex; flex-direction: column; align-items: center; justify-content: center; width: 24px; line-height: 1; color: var(--muted); }
.flowset .level b { font-size: 10px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.flowset .notch { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; min-height: 0; padding: 0; border: 0; border-radius: 6px;
  background: var(--soft); color: var(--ink); font: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
.flowset .notch:disabled { color: var(--todo); background: transparent; cursor: default; }
.row.flowset > .own { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 28px; min-height: 0; padding: 0; border: 0;
  border-radius: 6px; background: transparent; color: var(--muted); cursor: pointer; }
.row.flowset > .own[aria-pressed="true"] { background: var(--soft); color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink); }
