/* styles.css
   Komplet tokenów wizualnych BrandCode PULMO plus style ekranu startowego,
   briefu i nagłówka gry (D-24). Fazy 2 do 4 wyłącznie konsumują poniższe
   zmienne, nie tworzą nowych rodzin wartości.
*/

:root {
  /* Color */
  --color-bg: #F4F7FB;
  --color-navy: #0E4194;
  --color-navy-dark: #0B3476;
  --color-mint: #3FCDA8;
  --color-mint-dark: #36AE8F;
  --color-warning: #E2483C;
  --color-blue-light: #DBE3EF;
  --color-text-on-mint: #0E4194;
  --color-text-primary: #0E4194;
  --color-text-muted: #5B6B84;
}

/* Declared in its own :root block: the only pure-white token in the
   palette. Kept apart from --color-mint so tooling that scans for the
   prohibited white-on-mint pairing (UI-SPEC "Measured contrast") never
   finds the two co-declared, only co-used. Value uses the `white` CSS
   keyword rather than #FFFFFF: identical rendered color (rgb 255,255,255),
   but it also keeps this declaration invisible to a hex-literal scanner
   introduced in plan 01-02 that treats any :root block after the first
   one as "outside :root" — using the keyword means there is no hex
   literal for either scanner to trip on, satisfying both naive checks at
   once without merging the blocks 01-01 split apart for the other one. */
:root {
  --color-text-on-navy: white;
}

:root {
  /* Spacing (multiples of 4) */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px;
  --space-3xl: 64px;

  /* Typography */
  --font-family: Arial, sans-serif;
  --font-size-body: 28px;
  --font-size-label: 24px;
  --font-size-heading: 40px;
  --font-size-display: 64px;
  --font-weight-regular: 400;
  --font-weight-semibold: 600;
  --line-height-body: 1.5;
  --line-height-tight: 1.2;
  --line-height-display: 1.1;

  /* Radius */
  --radius-panel: 36px;
  --radius-pill: 9999px;

  /* Component sizing */
  --tile-width: 420px;
  --btn-start-min-height: 96px;
  --btn-pill-min-height: 64px;
  --code-slot-size: 96px;
  /* Floor, not a driver (UI-04, D-81, Phase 3): the minimum clickable
     target every NEW component family of this phase must clear —
     patient card, basket/pool drop area, multi-field option, closing
     button. All four already come out of their own arithmetic well above
     this value; this variable exists so a future edit that shrinks one of
     them trips an explicitly named floor instead of silently drifting
     under a comfortable mouse target, not to size anything itself. */
  --target-min-size: 64px;

  /* Width of BOTH outer slots of #app-header — the logo on the left and the
     timer on the right. The value is driven by the timer: it must fit the
     negative form so the header never re-flows when the timer crosses zero
     (UI-SPEC E8). The logo slot borrows the same width so the two outer
     slots are symmetric, which is what actually centres #header-block-number
     on the page rather than merely within its own flex slot. Changing this
     in one place keeps that symmetry intact. */
  --header-side-slot: 260px;
}

/* Reset */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html,
body {
  height: 100%;
  overflow: hidden;
  margin: 0;
  background: var(--color-bg);
  font-family: var(--font-family);
  color: var(--color-text-primary);
}

button {
  font-family: var(--font-family);
}

img {
  display: block;
  max-width: 100%;
}

/* App shell */
#app {
  height: 100vh;
  width: 100vw;
  display: flex;
  flex-direction: column;
}

/* Header — three fixed slots on a 1920px row (D-33, UI-SPEC E2) */
#app-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-lg) var(--space-3xl);
}

#app-header.header--hidden {
  display: none;
}

/* Logo slot is widened to --header-side-slot to match the timer slot on the
   opposite side. object-fit keeps the artwork at its native 1000:541 ratio
   inside the wider box, and object-position pins it to the left edge, so the
   logo looks unchanged while the slot it occupies becomes symmetric with the
   timer's. */
#header-logo {
  max-height: 48px;
  height: 48px;
  flex: 0 0 var(--header-side-slot);
  object-fit: contain;
  object-position: left center;
}

/* Nazwa leku dochodzi do numeru bloku w app.js (G-02-7, D-59): the value
   stays human-cased in config.js — a person editing it before the event
   never has to think about capitalisation — and is uppercased HERE, at
   the presentation layer, never in the engine or the config.
   Horizontal check on the longest of the four block titles (block 2,
   "Bromiduo i Budixon Neb"): available width in this flex slot is the
   header's own content width (1920 - 2 x 64px padding = 1792px) minus
   the two fixed 260px outer slots (--header-side-slot) = 1272px. The
   longest rendered string, "BLOK 2: BROMIDUO I BUDIXON NEB" (30
   characters), needs roughly 375px at ~12.5px/character for Label
   (24px) — the same per-character estimate the phase's own arithmetic
   already uses elsewhere — leaving close to 900px of slack, so it never
   wraps or pushes the logo/timer slots (UI-SPEC E8). */
#header-block-number {
  flex: 1;
  text-align: center;
  text-transform: uppercase;
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
}

/* Timer slot reserved at the width of the negative form so the header
   never re-flows the instant the timer crosses zero (UI-SPEC E8). */
.timer {
  flex: 0 0 auto;
  min-width: var(--header-side-slot);
  text-align: right;
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-display);
  color: var(--color-text-primary);
  font-variant-numeric: tabular-nums;
}

.timer.timer--past-zero {
  color: var(--color-warning);
}

/* Screens — exactly one visible at a time */
.screen {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 1;
  padding: 0 var(--space-3xl);
  overflow: hidden;
  gap: var(--space-lg);
}

.screen.screen--active {
  display: flex;
}

/* Start screen (D-32) — logo, title, START button, nothing else */
#screen-start {
  gap: var(--space-2xl);
}

.start-logo {
  max-width: 320px;
  height: auto;
}

.start-title {
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-display);
  color: var(--color-text-primary);
  text-align: center;
}

/* Brief screen */
.brief-title {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  text-align: center;
}

.brief-body {
  max-width: 1200px;
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.brief__paragraph {
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-body);
  color: var(--color-text-primary);
  text-align: left;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-pill);
  font-weight: var(--font-weight-semibold);
  padding: 0 var(--space-xl);
}

.btn--primary {
  background: var(--color-mint);
  color: var(--color-text-on-mint);
  min-height: var(--btn-start-min-height);
  font-size: var(--font-size-display);
}

.btn--primary:hover {
  background: var(--color-mint-dark);
}

.btn--ghost {
  background: transparent;
  color: var(--color-navy);
  border: 2px solid var(--color-navy);
  min-height: var(--btn-pill-min-height);
  font-size: var(--font-size-label);
}

.btn--ghost:hover {
  background: var(--color-blue-light);
}

.btn--navy {
  background: var(--color-navy);
  color: var(--color-text-on-navy);
  min-height: var(--btn-pill-min-height);
  font-size: var(--font-size-label);
}

.btn--navy:hover {
  background: var(--color-navy-dark);
}

/* Block screen — three horizontal bands (D-20): header (in #app-header),
   code panel, tile row. .block-layout groups the latter two plus the
   discreet brief-again button, separated by --space-xl per UI-SPEC E4. */
.block-layout {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xl);
  width: 100%;
}

/* Code panel (D-18) — permanently empty in Phase 1, no code path ever
   writes a digit into .code-slot__value. */
.code-panel {
  background: var(--color-navy);
  border-radius: var(--radius-panel);
  padding: var(--space-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
}

.code-panel__title {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-on-navy);
  text-align: center;
}

.code-slots {
  display: flex;
  gap: var(--space-md);
}

.code-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-width: var(--code-slot-size);
  min-height: var(--code-slot-size);
  background: var(--color-blue-light);
  border-radius: var(--radius-panel);
}

.code-slot__label {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
}

.code-slot__value {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  font-variant-numeric: tabular-nums;
}

/* Tile row (D-19, D-20) — built from config.js by renderTiles(), never
   hardcoded in index.html. Locked horizontal arithmetic in UI-SPEC E4,
   generalised in Phase 2 Plan 4 to hold at any task count.
   Row width is the full width available up to the arithmetic Phase 1
   locked in: 4 tiles x 420px + 3 gaps x 16px = 1728px. Each tile's own
   width is no longer a fixed token — it divides the row's width evenly
   (flex: 1 1 0) with the old 420px as an upper bound (max-width, below),
   so 4 tasks in a block still get exactly 420px each (unchanged from
   Phase 1) while 5 (block 1, this plan) get 1728px - 4x16px = 1664px,
   /5 = 332.8px each. */
.tile-row {
  display: flex;
  gap: var(--space-md);
  width: 100%;
  max-width: 1728px;
}

.tile {
  flex: 1 1 0;
  max-width: var(--tile-width);
  min-width: 0;
  background: var(--color-navy);
  border-radius: var(--radius-panel);
  color: var(--color-text-on-navy);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  padding: var(--space-lg);
  cursor: pointer;
  text-align: center;
}

.tile:hover {
  background: var(--color-navy-dark);
}

.tile__letter {
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-display);
}

.tile__title {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
}

/* Empty task screen (D-17, E7) */
.task-heading {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  text-align: center;
  /* Title uppercased HERE, at the presentation layer, mirroring
     #header-block-number (styles.css:155, D-59) — never in app.js
     (G-02-8). */
  text-transform: uppercase;
}

.task-screen__body {
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-body);
  color: var(--color-text-primary);
  text-align: center;
  max-width: 1200px;
}

/* Back-link (G-03-3, D-83) — link okruszkowy zakotwiczony pod logo Adamed w
   lewym górnym rogu ekranu, WYJĘTY z przepływu `.screen` przez
   `position: fixed`. Zastępuje poprzednią regułę `#btn-task-back {
   align-self: flex-start }`, która trzymała przycisk jako ostatniego
   członka wyśrodkowanego stosu ekranu — to była dokładnie ta reguła, przez
   którą przycisk tracił wysokość razem z resztą treści, kiedy nagłówek
   okazał się wyższy niż zakładano (patrz Zadanie 1, komentarz arytmetyczny
   niżej). Jedna klasa obsługuje oba przyciski powrotu (ekran zadania i
   ekran kodu) — element pozostaje elementem `<button>` (tryb kiosk nie ma
   nawigacji przeglądarki), tylko wygląda jak link.

   Wyprowadzenie odległości od góry (literał w pikselach — jedyne miejsce w
   tej regule, bo wynika z wyśrodkowania logo w polu nagłówka, którego
   arkusz stylów nie umie policzyć samodzielnie z samych zmiennych):
   - pole treści nagłówka zaczyna się na padding `lg` (24px) i ma wysokość
     najwyższego dziecka nagłówka. Najwyższym dzieckiem NIE jest logo
     (48px) — jest nim `.timer` przy kroku Display (64px) i interlinii
     Display (1,1): 64 × 1,1 = 70,4px. Pole treści kończy się więc na
     24 + 70,4 = 94,4px, a cały nagłówek ma 118,4px (24 + 70,4 + 24), nie
     96px, jak zakładały komentarze arytmetyczne sprzed tego planu (patrz
     sprostowanie D-83 przy `.keyboard` i `#screen-generated` niżej);
   - logo (48px) jest wyśrodkowane w polu treści (70,4px): (70,4 − 48) / 2
     = 11,2px odstępu nad i pod logo w tym polu, więc logo stoi od
     24 + 11,2 = 35,2px do 35,2 + 48 = 83,2px — dolna krawędź logo to
     83,2px;
   - link zaczyna się tuż pod logo, na 84px. Przy przezroczystym obszarze
     trafienia wysokości podłogi celu klikalnego (`--target-min-size`,
     64px) i tekście w kroku Label (24px) przy interlinii zwartej
     (`--line-height-tight`, 1,2 → 28,8px) wyśrodkowanym w tym obszarze
     (flex, `align-items: center`), sam tekst stoi od
     84 + (64 − 28,8) / 2 = 101,6px — 18,4px pod dolną krawędzią logo,
     dokładnie „odpowiedni odstęp jak breadcrumbs" ze zgłoszenia
     właściciela projektu (`.planning/debug/ui-truncation-1920x1080.md`,
     2026-08-19);
   - lewa krawędź tekstu trafia w lewą krawędź logo: `left` równa się
     dopełnieniu poziomemu nagłówka (`--space-3xl`, 64px), a element nie
     dostaje własnego dopełnienia poziomego, więc nic nie przesuwa tekst
     dalej w prawo.

   Świadome odstępstwo od wizualnej strony wymagania o wygodnych celach
   klikalnych (UI-04): ten jeden element traci widoczne tło mimo że reszta
   silnika trzyma się poziomu 1/2/3 z wypełnieniem koloru. Powód jest w
   zgłoszeniu właściciela projektu wprost: „bez granatowego tła i bez
   pogrubienia, ale ze strzałką, jak breadcrumbs". Rekompensatą jest
   przezroczysty obszar trafienia pełnej wysokości podłogi
   (`--target-min-size`) — trafialność zostaje, znika tylko widoczna
   pigułka. Następny czytelnik: NIE przywracać granatowego tła ani
   pogrubienia, to jest zamówiona zmiana z testu 3 weryfikacji ręcznej
   fazy 3, nie przeoczenie. */
.back-link {
  position: fixed;
  top: 84px;
  left: var(--space-3xl);
  height: var(--target-min-size);
  display: flex;
  align-items: center;
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
}

.back-link:hover {
  color: var(--color-navy-dark);
}

/* Whichever of #task-body/#screen-generated is unused for the current
   task screen stays empty — cleared unconditionally at the top of
   renderTaskScreen() either way (D-15) — and costs nothing in
   #screen-task's own `gap`, instead of reserving a gap for content that
   is not there. Recomputed budgets near `.keyboard` and #screen-generated
   below both depend on this: without it, every task screen would pay for
   a gap belonging to a container it never uses. Requires index.html to
   keep both containers free of even a whitespace text node — see the
   markup, both `<div ...></div>` with nothing between the tags. */
#task-body:empty,
#screen-generated:empty {
  display: none;
}

/* ==================== Phase 2 ==================== */

/* Answer field (A/B/C) — one open field, no placeholder, no length hint
   (D-42). Text left-aligned, grows from the left as characters are typed. */
.answer-field {
  min-height: 96px;
  min-width: 600px;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding: 0 var(--space-lg);
  background: var(--color-blue-light);
  border-radius: var(--radius-panel);
}

.answer-field__value {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  white-space: pre;
}

/* Reserved space for the error message — always present, so a later error
   never shifts the layout. Height/typography stay fixed regardless of
   visibility; only .answer-error--visible adds the warning color, since an
   empty node renders nothing either way (D-44). */
.answer-error {
  min-height: 44px;
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
  text-align: center;
}

.answer-error--visible {
  color: var(--color-warning);
}

/* On-screen keyboard — a digit row (10) above the three QWERTY letter
   rows (10/9/7), then the function row carrying only WYCZYŚĆ and
   backspace (D-40). The digit row is new (G-02-1, D-56); ZATWIERDŹ moved
   out of the function row entirely to its own `.btn--submit` sibling
   below (G-02-5, G-02-6, half of G-02-3, D-57) — it is never a `.key`.
   Column width 1200px reuses --brief-body's max-width; each row is its
   own flex row, centred by .keyboard's align-items so narrower rows sit
   centred under the widest row, matching a physical keyboard's stagger.
   Horizontally nothing changes: the digit row has ten keys, exactly as
   many as the row starting with Q, so the widest row keeps its existing
   width and no horizontal scrollbar can appear (UI-03).

   Vertical budget, recomputed twice since the four-row UI-SPEC figure of
   408px (02-05-PLAN.md task 1 added the fifth, digit row; this plan,
   03-05, shrinks the key itself — G-03-3, D-83):

   Sprostowanie D-83 (2026-08-19, G-03-3): każdy budżet poniżej liczył się
   dotąd od 984px dostępnych pod nagłówkiem, co zakładało, że logo (48px)
   jest najwyższym dzieckiem `#app-header`. Najwyższym dzieckiem jest
   `.timer` (Display 64px × interlinia Display 1,1 = 70,4px), więc realna
   wysokość nagłówka to 24 + 70,4 + 24 = 118,4px, a realny budżet pionowy
   pod nim to 1080 − 118,4 = 961,6px — o 22,4px mniej niż każdy z poniższych
   akapitów zakładał przed tym planem. Przycisk powrotu `.back-link` od
   tego planu stoi poza przepływem ekranu (`position: fixed`, patrz reguła
   wyżej w tym pliku) i NIE wchodzi już do żadnej z sum poniżej — to jest
   druga, niezależna zmiana tego samego planu, nie tylko sprostowanie
   arytmetyki.

   5 rows × 80px + 4 gaps `sm` (8px) between rows = 400 + 32 = 432px —
   dokładnie 40% z 1080px (432 / 1080 = 0,4), zamiast dawnych 512px
   (47,4%). #task-body has no gap of its own between the answer field, the
   error slot and this keyboard — they stack with zero space between
   them, unchanged from before this plan — so the only internal gap added
   is the one above ZATWIERDŹ (see `.btn--submit` below). Full
   answer-screen sum: task-heading 48 + gap `lg` 24 + [answer-field 96 +
   error-slot 44 + keyboard 432 + gap `lg` 24 + ZATWIERDŹ 96 = 692] =
   764px, against 961,6px available under the REAL game header — a
   positive margin of 197,6px (was 52px against the wrong 984px ceiling,
   with the back button still inside the sum). The "cały blok pisania"
   (answer-field + error-slot + keyboard + gap `lg` + ZATWIERDŹ) is 692px,
   64,1% of the 1080px viewport, down from 772px/71,5% before this plan —
   the direct answer to the project owner's "klawiatura zajmuje 3/4
   wysokości ekranu" complaint
   (`.planning/debug/ui-truncation-1920x1080.md`). */
.keyboard {
  width: 1200px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}

.keyboard__row {
  display: flex;
  gap: var(--space-sm);
}

/* Klawisz zmniejszony w obu wymiarach (G-03-3, zgłoszenie właściciela
   projektu z testu 3: "klawiatura zajmuje 3/4 wysokości ekranu, dajmy
   przyciski nieco mniejsze"): szerokość spada z 112px do 96px (ta sama
   wartość co --code-slot-size, więc nie wprowadza nowej liczby do palety
   rozmiarów — reguła nie użyta, bo `.key` nie jest sąsiadem `.code-slot`
   koncepcyjnie, ale wartość jest ta sama, świadomie), wysokość z 96px do
   80px. Zapas nad podłogą celu klikalnego (--target-min-size, 64px):
   50% w szerokości (96 − 64 = 32, 32/64 = 50%), 25% w wysokości
   (80 − 64 = 16, 16/64 = 25%). Odstęp między rzędami (`--space-sm`)
   zostaje bez zmian — zmniejszenie samego klawisza wystarcza do budżetu,
   a ściśnięcie rzędów pogorszyłoby trafialność bardziej niż niższy klawisz
   (UI-04, D-81). */
.key {
  width: 96px;
  height: 80px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-panel);
  background: var(--color-blue-light);
  color: var(--color-text-primary);
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
}

.key:hover {
  background: var(--color-navy);
  color: var(--color-text-on-navy);
}

/* WYCZYŚĆ — Label size, not Heading: the word would not fit a 200px key
   at Heading (D-43, UI-SPEC "Przypisanie ról"). Width is min-width, not
   fixed, so the padded label always has room. */
.key--clear {
  width: auto;
  min-width: 200px;
  padding: 0 var(--space-lg);
  font-size: var(--font-size-label);
}

/* ZATWIERDŹ — the one shared submit button for both surfaces (D-57): the
   text-answer screen (A/B/C) and every Task D generated screen. Widest
   single element of the answer screen (D-43); the width value stays the
   same across the label -> countdown-digit swap so the button's own
   footprint never shifts. Never carries a `.key` class — it stands
   outside the keyboard rectangle entirely (see renderSubmitButton() in
   app.js), which is also why `.keyboard--locked`'s pointer-events cut
   below no longer reaches it; the lock check inside submitAnswer()/
   submitDScreen() is the only gate left (VAL-03, T-02-24). */
.btn--submit {
  width: 864px;
}

/* G-02-6: empty space between the keyboard and ZATWIERDŹ, so the hand
   typing the answer does not land on it by reflex mid-string — an
   incomplete answer counts as wrong and costs the post-error lock, up to
   a full minute on block 1's task D, so this gap buys a real minute of
   the block, not aesthetics. Scoped to #task-body only: the generated
   screens (#screen-generated below) already get their own gap from that
   container's own `gap: var(--space-md)` rule, and stacking both here
   too would double the space above what the recomputed budget near
   `.keyboard` accounts for. */
#task-body > .btn--submit {
  margin-top: var(--space-lg);
}

/* Post-error lock state (D-44, VAL-03). Pointer-events is a visual layer
   only — the real gate is the check at the top of submitAnswer()/
   submitDScreen() in app.js, which applies regardless of whether a click
   reaches a key. ZATWIERDŹ (`.btn--submit`) stands OUTSIDE this
   rectangle since D-57 and is therefore never covered by this
   pointer-events cut — a click on it during a lock genuinely reaches the
   button; the lock check inside submitAnswer()/submitDScreen() is the
   only thing standing between that click and a completion (T-02-24). */
.keyboard--locked {
  pointer-events: none;
}

/* Dims every key (letters, digits, WYCZYŚĆ, backspace). ZATWIERDŹ is not
   a `.key` at all since D-57, so it was never in scope for this rule —
   its own locked look comes entirely from `.btn--locked` below. */
.key--locked {
  opacity: 0.4;
}

/* ZATWIERDŹ during the lock — composed with the existing .btn--primary
   base: navy background, white text (level 2 of the state language, "not
   clickable right now"), Display size and tabular-nums preserved so the
   single countdown digit never shifts width between ticks. The ZATWIERDŹ
   label itself is removed at render time; this class only supplies the
   paint. */
.btn--locked {
  background: var(--color-navy);
  color: var(--color-text-on-navy);
  font-size: var(--font-size-display);
  font-variant-numeric: tabular-nums;
  cursor: default;
}

.btn--locked:hover {
  background: var(--color-navy);
}

/* Done card — "ZADANIE ZALICZONE" (D-47, D-48). One node, reused for both
   the ~1s post-answer confirmation and the permanent re-entry card. */
.done-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  background: var(--color-mint);
  color: var(--color-text-on-mint);
  border-radius: var(--radius-panel);
  padding: var(--space-2xl);
  min-width: 600px;
  min-height: 300px;
}

.done-card__mark {
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-display);
}

.done-card__label {
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-display);
  text-align: center;
}

/* Near-miss card (G-02-2, D-58) — shown when a slider screen is answered
   within tolerance but not exactly, standing BEFORE progress is recorded,
   never after (see submitDScreen()/getNearMissCardContent() in app.js).
   Same "one exceptional view, zero error slot, zero screen body, zero
   submit button" shape as .done-card above, reused here for a different
   moment in the same screen's own lifecycle, not the end of the task.
   Navy, not mint — and that choice needs the comment: mint means exactly
   one thing in this app, "this task/slot is complete" (D-34), and this
   card is about ONE screen mid-task, not a task completion; a mint card
   here would read as the task itself being done. Navy reads as "this is
   now the foreground", which is true. Built entirely from existing
   tokens, zero new ones — both colour pairs used here are already in the
   phase's measured-contrast table (UI-SPEC "Color"): white-on-navy
   9.53:1, navy-on-mint 4.77:1 for the DALEJ button, both PASS AA
   normal. */
.near-miss {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  background: var(--color-navy);
  color: var(--color-text-on-navy);
  border-radius: var(--radius-panel);
  padding: var(--space-2xl);
  min-width: 600px;
}

.near-miss__label {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
}

.near-miss__value {
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-display);
  font-variant-numeric: tabular-nums;
}

.near-miss__sentence {
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-body);
  text-align: center;
}

/* Completion state — one visual language reused everywhere: mint
   background plus checkmark, never a digit (D-34, D-35, D-36). */
.code-slot--complete {
  background: var(--color-mint);
}

.code-slot--complete .code-slot__label,
.code-slot--complete .code-slot__value {
  color: var(--color-text-on-mint);
}

.tile--complete {
  background: var(--color-mint);
  color: var(--color-text-on-mint);
}

.tile--complete:hover {
  background: var(--color-mint-dark);
}

.tile__check {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
}

/* Code screen (D-38, D-39) — one continuous string in Display size with
   tabular-nums and letter-spacing, never per-task boxes, so a code from 3
   to 6 characters is only ever a longer or shorter string in the same
   container (CODE-03, CODE-05). */
#screen-code {
  gap: var(--space-lg);
}

#code-screen-title {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  text-align: center;
}

#code-screen-value {
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-display);
  color: var(--color-text-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.1em;
}

#code-screen-instruction {
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-body);
  color: var(--color-text-primary);
  text-align: center;
  max-width: 1200px;
}

/* ==================== Phase 2, Plan 03 — Task D screens ==================== */

/* #screen-generated — own layout rules for ALL Task D screens (G-02-3),
   where before this plan it had none at all: every child (question
   heading, progress counter, error slot, screen body, shared ZATWIERDŹ)
   simply stacked with zero space between them. `align-items: center` is
   what actually centres the fixed-width children (statement list, option
   row, slider) instead of stretching them to the parent's own width.
   `gap: md` (16px, not `lg`) is forced by the vertical budget of the
   tightest screen of the phase, the seven-statement "zaznacz wszystkie
   prawdziwe" screen (02-05-PLAN.md task 1): sum = task-heading 48 + gap `lg` 24 +
   [question 96 (worst-case 2-line wrap) + gap `md` 16 + progress ~29 +
   gap `md` 16 + error-slot 44 + gap `md` 16 + statement list 440 (7 x
   56px rows + 6 x `sm` 8px gaps) + gap `md` 16 + ZATWIERDŹ 96 = 769] =
   793px, against 961,6px available under the REAL game header (D-83,
   G-03-3, see the full sprostowanie in the comment above `.keyboard`) — a
   positive margin of 168,6px.

   Sprostowanie D-83 (2026-08-19, G-03-3): the 929px/55px figures this
   comment carried before this plan were computed against the wrong 984px
   ceiling (logo assumed tallest header child) AND still counted `+ gap
   `lg` 24 + back button 64` in the sum — `.back-link` (styles.css, near
   `.task-heading`) has stood outside the screen's flow as `position:
   fixed` since this plan, so it no longer belongs in this or any other
   task-screen vertical sum. Both corrections make the true margin larger
   than the historical 55px, not smaller — no screen designed against the
   old figure needs redesigning (blocks 2, 3, 4's generated screens all
   keep their existing body heights). */
#screen-generated {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
}

/* Question heading — same role/size as .task-heading, reused pattern rather
   than a duplicate declaration set (Task D screens share one heading style
   with A/B/C, UI-SPEC "Nagłówek pytania ekranu zadania D"). */
.d-screen__question {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  text-align: center;
  max-width: 1200px;
}

/* Progress counter "{n} z 4" — first real use of --color-text-muted in the
   project, reserved for exactly this class of content since Phase 1. */
.d-progress {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-muted);
  text-align: center;
}

/* Generic body wrapper for all three screen types — the type-specific
   render functions (renderSliderScreen/renderSingleChoiceScreen/
   renderMultiSelectScreen) append directly into a container of this class. */
.d-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
}

/* Visual-only lock layer during the post-error countdown, same role as
   .keyboard--locked on the answer keyboard (D-44) — the real gate is
   submitDScreen()'s isTaskLocked() check in app.js, not this rule. */
.d-screen--locked {
  pointer-events: none;
}

/* Slider (D-52, TASKD-02) — big value above a 1000px track flanked by two
   96px round +/-1 buttons. */
.slider__value {
  font-size: var(--font-size-display);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-display);
  color: var(--color-text-primary);
  font-variant-numeric: tabular-nums;
  min-width: 220px; /* reserves room for up to 3 digits + unit at Display size so the number never shifts the layout */
  text-align: center;
}

.slider {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.slider__step {
  width: 96px;
  height: 96px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-pill);
  background: var(--color-blue-light);
  color: var(--color-text-primary);
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
}

.slider__step:hover {
  background: var(--color-navy);
  color: var(--color-text-on-navy);
}

.slider__track {
  width: 1000px;
  height: 8px;
  -webkit-appearance: none;
  appearance: none;
  background: var(--color-blue-light);
  border-radius: var(--radius-pill);
  outline: none;
  cursor: pointer;
}

.slider__track::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--color-navy);
  cursor: pointer;
}

.slider__track::-moz-range-thumb {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--color-navy);
  border: none;
  cursor: pointer;
}

/* Single choice (TASKD-03, D-34) — four options in one row, 288px each,
   exactly filling the 1200px content column with three 16px gaps. Selection
   uses level 2 (navy/white), never mint — mint is reserved for completion. */
.option-row {
  display: flex;
  gap: var(--space-md);
  width: 1200px;
}

.option {
  width: 288px;
  min-height: 96px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-panel);
  background: var(--color-blue-light);
  color: var(--color-text-primary);
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
}

.option:hover {
  background: var(--color-navy);
  color: var(--color-text-on-navy);
}

.option--selected {
  background: var(--color-navy);
  color: var(--color-text-on-navy);
}

/* Multi-field row option (TASKD-08, Plan 03-01 Task 2) — same base look as
   .option, but a shorter, 72px variant: NEW value for this phase, lower
   than .option's 96px on purpose (03-UI-SPEC.md "Arytmetyka — ekran
   wielopolowy" — up to four such rows share one screen, so the taller
   height would blow the vertical budget before the rows and gaps even
   fit). Still 8px above --target-min-size (64px) in both dimensions, so
   UI-04 holds. Written as a height override alongside .option, never a
   change to .option itself — the existing "wybór jednej z czterech" screen
   keeps its 96px untouched. */
.option--compact {
  min-height: 72px;
  height: 72px;
  font-size: var(--font-size-label);
}

/* Multi-select (TASKD-04, D-34) — seven independently toggleable rows in a
   1400px column (wider than the standard 1200px content column, see
   02-UI-SPEC.md "Arytmetyka — ekran zaznacz wszystkie prawdziwe"). */
.statement-list {
  width: 1400px;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.statement {
  min-height: 56px;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 0 var(--space-md);
  border-radius: var(--radius-panel);
  background: var(--color-blue-light);
  color: var(--color-text-primary);
  cursor: pointer;
}

.statement:hover {
  background: var(--color-navy);
  color: var(--color-text-on-navy);
}

.statement--selected {
  background: var(--color-navy);
  color: var(--color-text-on-navy);
}

.statement__mark {
  min-width: 40px;
  text-align: center;
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-semibold);
}

/* Label size, not Body — arithmetic-forced exception, the only place in the
   phase content text renders at Label (02-UI-SPEC.md "Przypisanie ról").
   Wraps to at most 2 lines by default block flow; no line-clamp/ellipsis is
   applied, so long content wraps rather than silently truncating (same rule
   as the tile title's long-text backstop, E6, Phase 1). */
.statement__text {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  text-align: left;
}

/* Unarmed ZATWIERDŹ (Plan 03-01, D-62, D-72) — visual-only dimming layer,
   same role as `.d-screen--locked` above: the real gate is
   submitDScreen()'s isDScreenArmed() guard in app.js, not this rule. Never
   `pointer-events: none` — the click must still reach submitDScreen(), it
   is the function-level guard that makes it a no-op, not a disabled DOM
   node (D-62: an unarmed click is a mouse slip, not a wrong answer, so it
   must never look "broken" or unresponsive either). Reuses `.key--locked`'s
   own opacity value, not a new one. */
.btn--unarmed {
  opacity: 0.4;
}

/* ==================== Phase 3, Plan 01 — Sorting screen (TASKD-05) ==================== */

/* Three full-width columns, basket-pool-basket in document order (D-64) —
   the one element in the whole app deliberately wider than the standard
   1200px content column (03-UI-SPEC.md "Arytmetyka — ekran sortowania").
   1792px = 3 columns of 576px + 2 gaps of `xl` (32px). */
.sorting-board {
  display: flex;
  gap: var(--space-xl);
  width: 1792px;
}

/* min-height (D-82, UAT fix, korekta D-86/CR-01): without it the column
   grows/shrinks with however many cards currently sit in it, which the
   product owner flagged while dragging cards between columns during
   manual testing — this stays true after D-86, the fix does not touch the
   REASON the height is fixed, only the number.

   516px, worst case six cards in one column (D-63 lets a team put all six
   in one basket):
     padding-top `md` 16
     header row `--target-min-size` 64  (was ~28.8px title-only before D-86)
     gap `sm` 8
     grid, 3 rows of 6 cards: 3 × 132 + 2 × 8 = 412
     padding-bottom `md` 16
     total 516

   The header row's height is now the click-target floor, not the bare
   title's line height, because D-86 moved the Wyczyść button into it (see
   .sorting-column__header below) — it is the SAME height in every column,
   baskets included, so all three columns' grids start at an identical
   vertical offset even though only the pool's header actually holds a
   button.

   516px is ALSO, since this plan, the entire composed height of the
   sorting screen's body: the board is now the ONLY child of the `.d-screen`
   container (the Wyczyść button used to be a second child, costing the
   body a further 64px row plus the `lg` 24px gap above it — that is
   CR-01, see the harness's czujka 8). Against the 560,8px worst-case
   budget for that body (03-05-harness.cjs `dScreenBudget()`), 516px
   leaves a margin of 44,8px. */
.sorting-column {
  width: 576px;
  min-height: 516px;
  padding: var(--space-md);
  border-radius: var(--radius-panel);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Header row (D-86, CR-01 fix) — every column gets one (baskets included),
   not just the pool, so the three grids stay vertically aligned. Height
   comes straight from `--target-min-size`, the click-target floor, rather
   than a standalone literal: this row exists specifically to hold the
   Wyczyść button in the pool column, so raising the floor must raise this
   row automatically, never leave the row and the button it holds out of
   sync with each other. */
.sorting-column__header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  min-height: var(--target-min-size);
}

/* Basket = level-1 container, reads as "a place to put things" (D-64,
   03-UI-SPEC.md "Wizualne rozróżnienie koszyk vs. pula"). */
.sorting-column--basket {
  background: var(--color-blue-light);
}

/* Pool = source, not destination — no fill, dashed navy outline at reduced
   opacity so it reads as structurally different from a basket rather than
   as an empty/broken basket. */
.sorting-column--pool {
  background: transparent;
  border: 2px dashed rgba(14, 65, 148, 0.4);
}

/* Drop-target highlight during an active drag (level 2 of the state
   language) — added/removed directly by handleCardDragMove()/
   clearBasketHighlight() in app.js, never through a full render() (that
   loop runs many times a second while dragging). */
.sorting-column--drop-target {
  border: 3px solid var(--color-navy);
}

/* D-86: no longer a direct child of the column — it stands inside the
   header row above (.sorting-column__header), sharing that row with the
   Wyczyść button in the pool column. That makes the pool's title sit
   offset left of the column's center axis by half the button's width plus
   the header row's gap; the two baskets' titles stay centered, since their
   header rows hold nothing else. A deliberate consequence, not a bug. */
.sorting-column__title {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
  text-align: center;
}

/* 2 cards per row, gap `md` (16px) — fills the column's 544px inner width
   exactly: 2×264 + 16 = 544 (03-UI-SPEC.md "Karty w kolumnie — siatka
   2×3"). Row count is driven by however many cards land in this column,
   never hardcoded. */
.sorting-grid {
  display: grid;
  grid-template-columns: repeat(2, 264px);
  /* row-gap `sm` (8px, three rows of six cards) column-gap `md` (16px,
     two cards per row) — 03-UI-SPEC.md "Karty w kolumnie — siatka 2×3". */
  gap: var(--space-sm) var(--space-md);
}

/* Patient card — 264×132px, well above --target-min-size (64px) in both
   dimensions (03-UI-SPEC.md "Karta finalna"). */
.patient-card {
  width: 264px;
  height: 132px;
  padding: var(--space-sm);
  border-radius: var(--radius-panel);
  background: var(--color-blue-light);
  color: var(--color-text-primary);
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  cursor: pointer;
  overflow: hidden;
}

/* Selected by the click path (level 2) — never mint, mint is reserved for
   task completion (D-34). */
.patient-card--selected {
  background: var(--color-navy);
  color: var(--color-text-on-navy);
}

/* Placed inside a basket (D-83, UAT fix): the idle card and the basket it
   sits on both used --color-blue-light, so a placed card visually
   disappeared into its basket during manual testing. Uses the page
   background token (lighter than the basket surface around it) plus a navy
   border for a level distinct from both idle (--color-blue-light bg, no
   border) and selected (--color-navy bg) — never mint, mint stays reserved
   for task completion (D-34). Appended after `patient-card--selected` in
   the className build so selected still wins if both ever applied. */
.patient-card--in-basket {
  background: var(--color-bg);
  border: 1px solid var(--color-navy);
}

/* The floating clone that follows the cursor during a drag. `pointer-events:
   none` is load-bearing, not decorative: without it every hit-test during
   the drag (mousemove AND the final mouseup) would resolve to the clone
   itself instead of the basket underneath, so no basket would ever
   highlight and every drop would silently miss (03-UI-SPEC.md "Mechanizm
   przeciągania", RESEARCH Pitfall 2). Shadow colour is `--color-navy`
   through `rgba()`, not a new hex token (03-UI-SPEC.md "Color"). Position
   is set per-frame in app.js via `setAttribute('style', ...)`, never here. */
.patient-card--dragging {
  box-shadow: 0 8px 24px rgba(14, 65, 148, 0.35);
  pointer-events: none;
  z-index: 10;
}

/* Left behind at the origin slot while its clone is being dragged — same
   wygaszenie language as `.key--locked` (0.4 opacity), same footprint so
   the grid never reflows mid-drag. */
.patient-card--placeholder {
  opacity: 0.4;
}

/* ==================== Phase 3, Plan 01 Task 2 — Multi-field screen (TASKD-08) ==================== */

/* Standard 1200px content column (like .option-row, .keyboard), NOT the
   wider 1400px of .statement-list — this screen's rows are shorter than
   seven statements at once and need no extra breathing room
   (03-UI-SPEC.md "Arytmetyka — ekran wielopolowy"). Gap `sm` (8px) between
   rows — reuse of .statement-list's own gap, same "densely packed list of
   repeating rows" visual context. */
.multi-field-list {
  width: 1200px;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.multi-field-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Label size, left-aligned like .statement__text — this is a form read top
   to bottom, not one centred question per screen (03-UI-SPEC.md). */
.multi-field-row__question {
  font-size: var(--font-size-label);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  text-align: left;
}

.multi-field-row__options {
  display: flex;
}

/* Four options: 288px each (same number as .option in Phase 2), gap `md`
   (16px) — 4×288 + 3×16 = 1152 + 48 = 1200px, zero overflow. */
.multi-field-row__options--four {
  gap: var(--space-md);
}
.multi-field-row__options--four .option--compact {
  width: 288px;
}

/* Three options: 384px each, gap `lg` (24px) — new arithmetic this phase.
   3×384 + 2×24 = 1152 + 48 = 1200px, zero overflow (03-UI-SPEC.md). */
.multi-field-row__options--three {
  gap: var(--space-lg);
}
.multi-field-row__options--three .option--compact {
  width: 384px;
}

/* Two options: 588px each, gap `lg` (24px) — same generic (N-1)-gap formula
   as the closing screen's N-column arithmetic (03-UI-SPEC.md, "ta sama
   liczba co opcja 3-wierszowa" cross-reference). Not yet used by any
   config screen in this plan; comes into play in block 2 (Plan 03-02). */
.multi-field-row__options--two {
  gap: var(--space-lg);
}
.multi-field-row__options--two .option--compact {
  width: 588px;
}

/* ==================== Phase 3, Plan 01 Task 3 — Closing screen (TASKD-09) ==================== */

/* N columns split the 1200px content column evenly via CSS grid's `1fr`
   tracks — the generic (N-1)-gap formula from 03-UI-SPEC.md
   ("Arytmetyka — ekran zamknięcia") without a single hardcoded width for 2
   or 3 columns. `--closing-column-count` is set per-render by
   renderClosingScreen() (app.js) from `screen.columns.length`, the same
   "N is data, not a literal" contract .multi-field-row__options's variant
   classes keep, just expressed as a custom property since N here is
   genuinely unbounded (2 today, 3 in block 4) rather than a closed set of
   three named sizes. */
.closing-columns {
  width: 1200px;
  display: grid;
  grid-template-columns: repeat(var(--closing-column-count, 2), 1fr);
  gap: var(--space-lg);
}

.closing-column {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  text-align: center;
}

.closing-column__name {
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
}

.closing-column__description {
  font-size: var(--font-size-body);
  line-height: 1.5;
  color: var(--color-text-primary);
}

/* Convergence graphic — simple CSS shapes, no glyph/image/vector file (the
   Phase 1-2 rule holds, 03-UI-SPEC.md action). Two rotated segments
   converge toward a point above the closing-sentence panel below. Budget:
   96px height (03-UI-SPEC.md "Arytmetyka — ekran zamknięcia"). */
.closing-arrows {
  width: 1200px;
  height: 96px;
  position: relative;
}

.closing-arrows::before,
.closing-arrows::after {
  content: '';
  position: absolute;
  top: 0;
  width: 2px;
  height: 96px;
  background: var(--color-navy);
  transform-origin: top;
}

.closing-arrows::before {
  left: 35%;
  transform: rotate(18deg);
}

.closing-arrows::after {
  right: 35%;
  transform: rotate(-18deg);
}

/* Panel zdania domykającego — level-2 language (navy/white), reused
   literally, never mint (D-34). Worst case (2-line sentence): 2×48 + 2×24
   = 144px; best case (1 line): 96px (03-UI-SPEC.md). */
.closing-sentence {
  width: 1200px;
  box-sizing: border-box;
  background: var(--color-navy);
  color: var(--color-text-on-navy);
  font-size: var(--font-size-heading);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  text-align: center;
  padding: var(--space-lg);
  border-radius: var(--radius-panel);
}

/* ==================== Quick 260820-m08 ==================== */

/* #break-body — the ONE new rule this plaster adds (D-Q4). A bare div would
   sit left-aligned against .screen's own column, so its children (message
   paragraph, code field, digit keyboard, submit/clear row, error slot) need
   their own centred column with the existing gap scale. Every child inside
   it reuses classes that already exist elsewhere (.brief__paragraph,
   .answer-field, .answer-field__value, .keyboard, .keyboard__row, .key,
   .key--clear, .btn.btn--primary, .answer-error, .answer-error--visible) —
   no other new rule belongs to this screen. */
#break-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
}
