/*
 * Base styles.
 *
 * Deliberately small and dependency-free. This product is used on a phone, in a stairwell,
 * once or twice a year: the thing that matters is that the text is legible and the buttons are
 * reachable with a thumb, not that a design system is installed.
 *
 * Two designs, each in a light and a dark scheme, chosen by the reader in the header and applied
 * as two attributes on `<html>` before the first paint (`public/appearance.js`):
 *
 *   data-design   absent = "podstawowy" (stamp ink)   "mono" = "czarno-biały"
 *   data-theme    absent = follow the system          "light" / "dark" = fixed
 *
 * `prefers-color-scheme` stays the default because the flow happens outdoors as often as indoors;
 * the fixed schemes are for the person whose system setting is wrong for this particular screen.
 */

/*
 * ── The palettes ────────────────────────────────────────────────────────────
 *
 * Both come from the design-system package of 24 September 2026 (`docs/design-system/`) and share
 * everything but colour: type, spacing, radii and the mark's geometry are the same, and so are
 * the custom-property NAMES, which is what lets the second design be a block of overrides.
 *
 * PODSTAWOWY - "Tusz pieczątki" by day, "Grafit i fiolet" by night. The violet is the ink of an
 * official rubber stamp, the colour that says a document has been confirmed, and it is used only
 * for STRUCTURE: headings, the wordmark, the mark, links. The button is deliberately NOT violet -
 * near-black by day, off-white by night - which inverts the usual pattern: colour organises the
 * page and the one thing to press stays neutral. A violet button would read as one more heading.
 *
 * CZARNO-BIAŁY - "Czerń i biel" by day, "Grafit" by night. Monochrome: heading, ink and button are
 * one black, and hierarchy comes from type rather than colour. The only hues on screen are the
 * three states. Links are told from text by their underline alone, so nothing may remove it.
 *
 * What holds in both, and is the reason each token exists:
 *
 *   `--accent` is the primary button fill, once per region (rule 16b), with `--accent-ink` on it.
 *   `--mark` is what has to be visible ON that fill: the rule down a `commit` button's leading
 *   edge and the ground of the current stage numeral.
 *   `--line` divides content and is meant to disappear; `--edge` bounds a control and is held at
 *   3:1 on every ground. Merging them is what once made every outlined control fail 1.4.11.
 *   `--tint` is "it is your turn" - once a screen, always with the words. `--tint-soft` is barely
 *   a colour: which column is about the state of the record rather than its content.
 *   `--danger`, `--ok`, `--warn` are WORD colours or a 2px border, never a fill, never alone.
 *
 * Ratios, lowest across bg, surface, raised and the tints (WCAG 2.2):
 *
 *                            podstawowy        czarno-biały
 *                            light   dark      light   dark
 *   ink                      14.3    12.9      16.6     9.1
 *   muted                     6.1     6.3       6.9     5.7
 *   heading / link            9.7     7.4      18.9    13.5
 *   edge                      3.9     3.7       4.0     3.3
 *   accent-ink on accent     17.2    15.6      18.9    13.6
 *   mark on accent            9.6     7.3      12.7     5.1
 *   danger / ok / warn      >= 5.3  >= 7.6    >= 5.6  >= 5.7
 *
 * Before changing any value, change it in `docs/design-system/<variant>/palette.json` as well and
 * run `node docs/design-system/check-contrast.mjs b-tusz-pieczatki` (and `a-czern-i-biel`).
 *
 * The selectors are arranged so specificity does the choosing: the base design's dark scheme is
 * `:root:not([data-theme="light"])` inside the media query and `:root[data-theme="dark"]` outside
 * it, and the mono design repeats both with one attribute more - so a fixed scheme beats the
 * system's, and a chosen design beats the default, with no `!important` anywhere. Each dark
 * scheme is written twice because a custom property cannot be declared once under two conditions.
 */

/* Podstawowy, light: "Tusz pieczątki". */
:root {
  --bg: #fbfbfc;
  --surface: #ffffff;
  /* An inset INSIDE a card - the hash block, the suggestion under a question. */
  --raised: #f3f1f6;
  --tint: #efe7fa;
  --tint-soft: #f6f4f9;
  --ink: #1d1a22;
  --muted: #595466;
  /* Stamp violet: STRUCTURE, never an action. A heading in the button's colour gets clicked. */
  --heading: #4a2574;
  --link: var(--heading);
  --line: #e5e2ea;
  --edge: #77727f;
  /* Near-black, and the strongest thing on the screen for exactly that reason. */
  --accent: #1d1a22;
  --accent-ink: #ffffff;
  --mark: #cdb9ea;
  --mark-ink: #1d1a22;
  --danger: #b42318;
  --ok: #1c6b3a;
  --warn: #8a5300;

  /*
    Two widths, because there are two kinds of screen here.

    `--measure` is a column of prose and form fields, which is what a step, a sign-in and a legal
    text are. `--page` is the record: three header cards, a snapshot beside its state, a trail.
    One number for both meant the record was read through a 34rem straw on a monitor.
  */
  --measure: 34rem;
  --page: 72rem;
  --gutter: 1.25rem;

  color-scheme: light dark;
}

/*
 * Podstawowy, dark: "Grafit i fiolet". A neutral graphite ground rather than a violet-tinted one;
 * the violet lightens to lilac and stays only in headings and links, and the button turns
 * off-white.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #141416;
    --surface: #1c1c1f;
    --raised: #252528;
    --tint: #2a2236;
    --tint-soft: #19181d;
    --ink: #ececee;
    --muted: #a6a6ad;
    --heading: #c4a9ee;
    --line: #303035;
    --edge: #7d7d85;
    --accent: #ececee;
    --accent-ink: #141416;
    --mark: #5b3a8c;
    --mark-ink: #ececee;
    --danger: #f4a59b;
    --ok: #7cc79a;
    --warn: #e0b35f;
  }
}
:root[data-theme="dark"] {
  --bg: #141416;
  --surface: #1c1c1f;
  --raised: #252528;
  --tint: #2a2236;
  --tint-soft: #19181d;
  --ink: #ececee;
  --muted: #a6a6ad;
  --heading: #c4a9ee;
  --line: #303035;
  --edge: #7d7d85;
  --accent: #ececee;
  --accent-ink: #141416;
  --mark: #5b3a8c;
  --mark-ink: #ececee;
  --danger: #f4a59b;
  --ok: #7cc79a;
  --warn: #e0b35f;
}

/*
 * Czarno-biały, light: "Czerń i biel". The page and a card are the same white, so the card's
 * `--line` border is the whole of what separates them - a card that announced itself with a
 * shadow would read as software, and this product makes a document. Links are `--ink`.
 */
:root[data-design="mono"] {
  --bg: #ffffff;
  --surface: #ffffff;
  --raised: #f4f4f4;
  --tint: #f0f0f0;
  --tint-soft: #f7f7f7;
  --ink: #111111;
  --muted: #525252;
  --heading: #111111;
  --link: var(--ink);
  --line: #e4e4e4;
  --edge: #767676;
  --accent: #111111;
  --accent-ink: #ffffff;
  --mark: #d4d4d4;
  --mark-ink: #111111;
  --danger: #b42318;
  --ok: #1c6b3a;
  --warn: #8a5300;
}

/* Czarno-biały, dark: "Grafit". A soft graphite rather than black, to cut glare in the dark. */
@media (prefers-color-scheme: dark) {
  :root[data-design="mono"]:not([data-theme="light"]) {
    --bg: #1c1c1e;
    --surface: #262628;
    --raised: #2f2f32;
    --tint: #3a3a3d;
    --tint-soft: #222224;
    --ink: #e6e6e6;
    --muted: #b8b8bd;
    --heading: #f2f2f2;
    --line: #3a3a3d;
    --edge: #8a8a90;
    --accent: #e6e6e6;
    --accent-ink: #1c1c1e;
    --mark: #5f5f64;
    --mark-ink: #f2f2f2;
    --danger: #f4a59b;
    --ok: #7cc79a;
    --warn: #e0b35f;
  }
}
:root[data-design="mono"][data-theme="dark"] {
  --bg: #1c1c1e;
  --surface: #262628;
  --raised: #2f2f32;
  --tint: #3a3a3d;
  --tint-soft: #222224;
  --ink: #e6e6e6;
  --muted: #b8b8bd;
  --heading: #f2f2f2;
  --line: #3a3a3d;
  --edge: #8a8a90;
  --accent: #e6e6e6;
  --accent-ink: #1c1c1e;
  --mark: #5f5f64;
  --mark-ink: #f2f2f2;
  --danger: #f4a59b;
  --ok: #7cc79a;
  --warn: #e0b35f;
}

/*
  A fixed scheme fixes the browser's own parts too - form controls, scrollbars, the date picker -
  which `color-scheme: light dark` would otherwise still draw from the system setting.
*/
:root[data-theme="light"] {
  color-scheme: light;
}
:root[data-theme="dark"] {
  color-scheme: dark;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-text-size-adjust: 100%;
}

/*
  The page is a column: header, content, footer, with the footer pushed to the bottom on short
  pages rather than floating halfway up.
*/
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/*
  Two column widths, and no screen has to ask for the one it already had.

  `main` is as wide as the page now, and every direct child is pulled back to the reading column
  unless it says `.wide`. So the dozen screens that are a column of prose and form fields are
  untouched, and the two that show a whole record opt in. Block layout, not grid, deliberately:
  grid items do not collapse their margins, and switching would have loosened the vertical rhythm
  of every paragraph and heading in the app as a side effect of changing a width.
*/
main {
  max-width: var(--page);
  margin: 0 auto;
  padding: 2rem var(--gutter) 4rem;
  width: 100%;
  box-sizing: border-box;
  flex: 1;
}

main > *:not(.wide) {
  max-width: var(--measure);
  margin-inline: auto;
}

/*
  The column rule works on BLOCKS, and silently does nothing for anything else.

  `margin-inline: auto` has nothing to centre when the child is inline or inline-block, so a bare
  <button>, a <Link> that is a control, or a loose <input> stays at the left edge of a `main` that
  is now as wide as the page - while the fields it belongs to sit in the column. That is what put
  "Utwórz" 280px to the left of the form it submits, and the 404's "Wróć" and both of the invite
  screen's controls in the same place. Nothing about it looks like a mistake in the markup, which
  is why it survived three screens.

  So a control that is a direct child of `main` spans the column, which is where a form's closing
  action wants to be anyway. Anything that should sit beside something else goes in a `.row`, and
  a `.row` is a div - a block, and already correct.
*/
main > input:not(.wide),
main > select:not(.wide),
main > textarea:not(.wide),
main > label:not(.wide) {
  display: block;
  width: 100%;
}

/* `flex` rather than `block`, so `.button` keeps the centring it does with `inline-flex`. */
main > button:not(.wide),
main > .button:not(.wide) {
  display: flex;
  width: 100%;
}

/*
  The skip link: off-screen until focused.

  Not `display: none`, which would take it out of the tab order and defeat the point - it is
  moved out of view and brought back the moment a keyboard reaches it.
*/
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--bg);
  padding: 0.6rem 1rem;
  z-index: 10;
}
.skip:focus {
  left: 0;
}

.shell-header,
.shell-footer {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0.9rem var(--gutter);
  width: 100%;
  box-sizing: border-box;
}

/*
  Centred, not baseline-aligned.

  An inline-flex whose first child is an `<svg>` has no text baseline to offer - the browser falls
  back to the bottom of the mark - so aligning this row on baselines put the wordmark and the
  navigation on two different lines, by an amount that changed with the size of the logo. What the
  row actually wants is one optical line: a mark, a word and two links, centred against each other.
*/
.shell-header {
  display: flex;
  align-items: center;
  gap: 1rem;
  border-bottom: 1px solid var(--line);
}

/*
  The account group sits at the far right, apart from the primary navigation.

  "Sesje" had no entry anywhere in the frame: the route existed and the only way to it was typing
  the address. Putting it beside "Moje transakcje" would have said it is a place you go to do the
  job, which it is not - it is where you look at your own account.

  Selected by CLASS, not by `[aria-label="Konto"]`. The label is a translated string now, so a
  selector matching on it would have styled the header in Polish and left it unstyled in every
  other language - which is the quiet kind of breakage a screenshot in one language never shows.
*/
.shell-header nav.nav-account {
  margin-inline-start: auto;
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
}

/*
  The appearance switcher: two plain selects, deliberately quiet.

  They change nothing about any record, so they must not look like controls that do. They sit
  with the account navigation rather than in the main one for the same reason. The border is
  `--edge` rather than `--line`: it is the whole of what tells somebody this is a control, and
  that has to clear 3:1 (1.4.11).
*/
.appearance-switcher {
  display: inline-flex;
  gap: 0.4rem;
}

.appearance-switcher select {
  font: inherit;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--edge);
  border-radius: 6px;
  padding: 0.15rem 0.3rem;
}

/*
  Said once, where somebody is about to be held to words that are not in their language.

  Marked rather than muted: it is not an aside. A person reading the interface in English while
  signing a Polish agreement has to see this, and a grey line under the fold is where a notice
  goes to be ignored.
*/
.record-language-notice {
  max-width: var(--page);
  margin: 0.75rem auto;
  padding: 0.6rem 0.8rem;
  border-inline-start: 3px solid var(--accent);
  /*
    `var(--ivory, #fff9ef)` was written here, and `--ivory` does not exist - so this fell through
    to the literal and stayed cream in the dark palette, where it would have been a near-white
    slab. The note at the top of this file says it in as many words: a token that only exists at
    its call site is not a token.
  */
  background: var(--surface);
  font-size: 0.95rem;
}

/*
  The lockup: the mark, then the word.

  `align-items: center` and nothing else clever - the header is baseline-aligned, and a 26px
  square dropped into a baseline row sits on the text baseline with its whole body above it. The
  gap is deliberately tight: at anything wider the two stop reading as one mark and start reading
  as an icon that happens to be next to a link.
*/
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-weight: 600;
  font-size: 1.1rem;
  text-decoration: none;
  /*
    The word and the mark are both `--heading`, like every other heading: they are what the
    product IS. `--accent` is what a screen is FOR, and the mark drawn in the button's colour
    would be a logo people try to press.
  */
  color: var(--heading);
}

.brand svg {
  /* The mark is drawn in the colour of structure, like the word beside it - never the button's. */
  color: var(--heading);
  flex: 0 0 auto;
}

/*
  The wordmark is the one piece of type on the page that is not the page's type.
  
  Everything else here is set in the two families the palette names; a logotype is a picture of a
  word, so it keeps its own weight, its own case and its own spacing, and it does not inherit the
  display serif the rest of `.brand` is set in. The stack is deliberately the geometric end of
  what is installed - Avenir, Segoe, Roboto - rather than a web font: a face fetched from
  somewhere else is a third party on every page load, which is a decision about processors and
  not about typography.
*/
.wordmark {
  font-family: "Avenir Next", Avenir, "Segoe UI Variable Display", "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  font-weight: 800;
  font-size: 1.45rem;
  letter-spacing: -0.025em;
  line-height: 1;
  /* Lower case is the mark. Nothing may raise it - not a heading rule, not a hover. */
  text-transform: lowercase;
  font-variant-numeric: normal;
  /*
    The letters in the body's ink - black on the light schemes, light on the dark ones - so the
    violet is spent on the dot alone and the dot is what the eye lands on.
  */
  color: var(--ink);
}

/* The dot: the one violet thing in the word, round, and larger than the tittle it replaces. */
.wordmark-i {
  position: relative;
}
.wordmark-i::after {
  content: "";
  position: absolute;
  left: 50%;
  /*
    Measured from the top of the INLINE BOX, which is the font's ascent - not from the top of the
    letter. A negative value here put the dot above the ascender of the "k" beside it, floating
    clear of the word; it belongs just over the x-height, close enough to read as a tittle.
  */
  top: 0.17em;
  width: 0.26em;
  height: 0.26em;
  transform: translateX(-50%);
  border-radius: 50%;
  background: var(--heading);
}

.shell-header nav {
  display: flex;
  gap: 1rem;
}

/* The current page is marked by weight as well as colour, so it is not carried by hue alone. */
.shell-header nav a.on {
  font-weight: 600;
}

.shell-footer {
  border-top: 1px solid var(--line);
  padding-top: 1.25rem;
  padding-bottom: 2rem;
  font-size: 0.9rem;
}

.shell-footer ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  margin: 0 0 0.75rem;
  padding: 0;
}

.shell-footer p {
  margin: 0.25rem 0 0;
}

@media (max-width: 30rem) {
  /* Two lines beats a squeezed row: the brand keeps its size and the links stay tappable. */
  .shell-header {
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 1rem;
  }
  .brand {
    flex: 1 0 100%;
  }
  /* Stacked, the auto margin would shove the account links off to the right on their own row. */
  .shell-header nav.nav-account {
    margin-inline-start: 0;
  }
}

/*
 * ── Szeryfowe nagłówki — NA PRÓBĘ ───────────────────────────────────────────
 *
 * Wszystko od tego komentarza do jego zamknięcia jest jedną decyzją i da się je usunąć w całości,
 * nie zostawiając niczego za sobą. Reszta pliku nie wie, że to tu jest.
 *
 * Co robi szeryf: dzieli ekran na dwa rodzaje materiału. Nagłówek jest tym, co ten rekord MÓWI
 * - nazwa przedmiotu, "Teraz Twoja kolej", "Stan i znane wady". Wszystko inne jest tym, czym się
 * ten rekord OBSŁUGUJE: etykiety pól, przyciski, dane w tabelach, słowa stanu. Jeden krój przez
 * cały ekran nie potrafi tej różnicy nieść, a ona jest tym, co odróżnia dokument od formularza.
 *
 * Krój systemowy, nie pobierany. Wczytany krój to zewnętrzny zasób na ekranie, który ma działać
 * na klatce schodowej przy jednej kresce zasięgu, i osobny wpis w CSP - a `font-display: swap`
 * przestawia nagłówek pod palcem czytającego. Stos jest ułożony pod CYFRY: "X-T30 II" i
 * "3 200,00 zł" trafiają do nagłówków, a Georgia rysuje cyfry mediewalne - ładne w zdaniu,
 * niemożliwe obok wersalików. Cambria i Palatino Linotype mają cyfry wersalikowe i są na
 * Windowsie; Iowan Old Style jest na macOS i iOS. Georgia jest ostatnią deską ratunku, bo jest
 * wszędzie, a `ui-serif` stoi za nią, bo w Chrome na Windowsie oznacza Times New Roman.
 *
 * Czego szeryf NIE dostaje i to nie jest przeoczenie:
 *   - `.record-cards h2` - "STRONA SPRZEDAJĄCA" to wersaliki z rozstrzeloną spacją, czyli
 *     etykieta rubryki, nie nagłówek. Szeryf w wersalikach z trackingiem wygląda jak pomnik.
 *   - `.handover-code` - sześć znaków odczytywanych na głos przez stół, na dworze. Bezszeryfowy
 *     nie dlatego, że ładniejszy, tylko dlatego, że 0 i O muszą się różnić.
 *   - `legend`, `label`, przyciski, `.fact`, `.state` - to jest obsługa, nie treść.
 */
:root {
  --font-display:
    "Iowan Old Style", "Palatino Linotype", Palatino, Cambria, Georgia, ui-serif, serif;
}

h1,
h2,
h3,
.brand,
.big {
  font-family: var(--font-display);
  /*
    Cyfry wersalikowe wszędzie tam, gdzie krój potrafi je przełączyć. Nazwa przedmiotu prawie
    zawsze ma w sobie liczbę - "X-T30 II", "iPhone 13" - i mediewalne "3" schodzące pod linię
    pisma obok "X-T" czyta się jak literówka, a nie jak model aparatu.
  */
  font-variant-numeric: lining-nums;
  /*
    Lżej niż domyślne 700. Szeryf niesie nagłówek samym rysunkiem liter; pogrubiony do tego
    robi się ciężki, a na Windowsie bez cięcia półgrubego - sztucznie pogrubiony, czyli rozmazany.
  */
  font-weight: 600;
  letter-spacing: -0.005em;
}

/*
  Wersalikowa etykieta rubryki to nie nagłówek - patrz komentarz wyżej. Tylko krój: rozmiarem,
  trackingiem i kolorem tej etykiety rządzi `.record-cards h2` niżej w pliku i ono wygrywa
  specyficznością. Powtórzenie ich tutaj byłoby dokładnie tym rozjazdem, przed którym ostrzega
  reguła 16a.
*/
.record-cards h2 {
  font-family: inherit;
}
/*
  The heading colour, and only the headings.

  `.big` is in the display face above but not here: it is a VALUE - an item's name, a price - and
  drawing it in the heading colour would make every record card read as a table of contents.
*/
h1,
h2,
h3,
.brand {
  color: var(--heading);
}
/* ── koniec próby ──────────────────────────────────────────────────────────── */

h1 {
  font-size: 1.75rem;
  line-height: 1.25;
  margin: 0 0 0.75rem;
}

h2 {
  font-size: 1.1rem;
  margin: 2rem 0 0.5rem;
}

p {
  margin: 0 0 1rem;
}

.muted {
  color: var(--muted);
  font-size: 0.9rem;
}

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 1rem;
  margin-bottom: 0.75rem;
}

/*
 * The fee, on its own line and at the size of the thing somebody came to find out.
 *
 * It is one number and it answers the whole screen's question, so it is set apart from the
 * sentences around it rather than buried in one of them. Tabular figures because a price is read
 * as digits rather than as a word.
 */
.card .fee {
  margin: 0 0 0.25rem;
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

/*
 * A paragraph that holds a control rather than a sentence.
 *
 * Without it the button inherits the paragraph rhythm meant for text and sits too close to the
 * line above, and on a narrow screen a bare `.button` in a card stretched to the card's width -
 * which reads as a banner rather than as something to press. `align-items: start` keeps it the
 * width of its own words.
 */
.card .actions {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  gap: 0.5rem;
  margin: 1rem 0 0;
}

/*
 * The width of its own words, not of the card.
 *
 * Controls in this app stretch by default - `main > .button` is `width: 100%`, which is right for
 * a form that fills the column and wrong for a control sitting in a card of prose, where a
 * full-width filled rectangle reads as a banner rather than as something to press. Stated rather
 * than inherited, because the default is deliberate everywhere else.
 */
.card .actions > .button:not(.commit) {
  width: auto;
  flex: 0 0 auto;
}

/*
 * A card you can go into, versus a card you read.
 *
 * `.card` was doing both, and nothing distinguished them: the list of transactions and a panel
 * explaining why acceptance is blocked were the same box, one of them clickable. `.card.block`
 * is the link; the hover, the focus ring and the chevron are what make it look like one.
 */
.card.block:hover {
  border-color: var(--accent);
}
.card.block:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.card.block {
  position: relative;
  padding-right: 2.25rem;
}
.card.block::after {
  content: "›";
  position: absolute;
  top: 50%;
  right: 1rem;
  transform: translateY(-50%);
  color: var(--muted);
  font-size: 1.4rem;
  line-height: 1;
}

/*
 * Four levels, because there were none.
 *
 * Every control in the app was `.button`: one filled rectangle for "Wstecz", for "Dodaj wadę" and
 * for "Zamknij rekord". Forty-two of them, and nothing on screen said which of the three changes
 * anything, which merely moves, and which cannot be undone.
 *
 *   primary    the one thing this screen is for. At most one per view.
 *   secondary  a real action that is not the point of the screen, and every step backwards.
 *   quiet      remove, cancel, change - reversible, and it must not compete.
 *   commit     signs, confirms, closes. Irreversible (rule 14), and never in a row with "Dalej".
 *
 * The difference is never carried by hue alone: primary is filled, secondary is an outline, quiet
 * drops the box entirely, and commit is the only one with a rule down its leading edge.
 */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 44px; /* thumb-sized: this is filled in while standing up */
  padding: 0.7rem 1.25rem;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: var(--accent);
  color: var(--accent-ink);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  text-align: center;
}

/*
 * `--edge`, not `--line`: the border IS the control.
 *
 * A filled button is a shape whether or not you can see its border; an outlined one is nothing
 * but its border, and drawing that in the colour of a table rule (1.5:1) leaves a label floating
 * on the page with no sign it can be pressed. 3:1 against every ground it lands on, which is
 * what 1.4.11 asks of a control boundary and what the hairline was never meant to give.
 */
.button.secondary {
  background: transparent;
  color: var(--ink);
  border-color: var(--edge);
}

.button.quiet {
  background: transparent;
  color: var(--muted);
  border-color: transparent;
  font-weight: 400;
  padding-inline: 0.5rem;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/*
 * The irreversible ones.
 *
 * Heavier than primary rather than merely a different colour, and carrying a rule down the
 * leading edge so the distinction survives a greyscale screen and a colour-blind reader. It is
 * also `display: flex` at full width by default: an action that ends the record does not sit in a
 * row beside navigation, and giving it the whole line is the cheapest way to enforce that.
 *
 * The rule is `--mark`, not `--ok`. It sits ON the filled accent, and `--ok` against that fill is
 * 2:1 - so the one mark that separates an irreversible control from an ordinary one was being
 * drawn in a colour you cannot see there. `--mark` is the token for precisely this (8.9:1).
 */
.button.commit {
  /*
    An INSET SHADOW rather than a left border, and the difference is visible rather than academic.

    A 4px border on one side of a box whose other three are 1px, with an 8px radius, leaves the
    renderer blending two widths and two colours around each corner - and what it draws there is a
    pale wedge that reads as a second, misaligned rectangle behind the button. It was reported as
    "what is this frame?", which is the correct reaction to it.

    A shadow follows the radius exactly and takes part in no corner join, so the mark is a clean
    rule down the leading edge and the border stays one width all the way round. Everything the
    mark is FOR is unchanged: it survives a greyscale screen and a colour-blind reader, and it is
    `--mark` (8.9:1 on the fill) rather than `--ok`, which at 2:1 could not be seen there at all.
  */
  box-shadow: inset 4px 0 0 var(--mark);
  padding-left: 1rem;
  width: 100%;
}

.button:disabled,
.button[aria-disabled="true"] {
  opacity: 0.5;
  cursor: default;
}

.button:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/*
  A row is a SEQUENCE. Things in it sit next to each other, in the order they were written.

  It defaulted to `space-between`, which is not a sequence - it is two ends with a hole in the
  middle, sized by whatever the container happens to be. Four category chips landed one at each
  corner of the reading column with 90px of nothing between them, so "Telefon" and "Rower /
  e-bike" read as unrelated controls rather than as four answers to one question, and the gaps
  changed width every time the column did.

  The two-ends case is real - a title beside its price, a session beside the button that ends it -
  but it is the special one, and `.between` now says so. Three cards ask for it; a dozen groups of
  buttons and chips did not and were getting it anyway.
*/
.row {
  display: flex;
  justify-content: flex-start;
  gap: 1rem;
  align-items: baseline;
}

/* A line with something at each end, not a sequence: a name and its price, a state, an action. */
.row.between {
  justify-content: space-between;
}

/*
  A field beside its own control - a search box and the button that submits it.

  The field is a stack of label, hint and input, so `baseline` aligns the button with the LABEL
  and leaves it hanging above an input two lines below. `end` puts it level with the input, which
  is the thing it acts on, and the field takes whatever width is left rather than collapsing to
  the width of its placeholder.
*/
.row:has(> .field) {
  align-items: end;
}
.row > .field {
  flex: 1 1 auto;
}

/*
  And the control's own bottom, not the gap under it.

  Every text field carries a 1rem bottom margin, which is what separates one question from the
  next down a form. In a ROW it is not a separator at all - it is space between the input and the
  edge `end` aligns to, so the button sat a clear 16px below the box it submits. The margin goes
  where the field is beside a control rather than above another question, and the button takes the
  input's vertical padding so the two are one height rather than three pixels apart.
*/
.row > .field :is(input, textarea) {
  margin-bottom: 0;
}

/* The separation the input's own margin used to give, moved to the thing it belongs to. */
.row:has(> .field) {
  margin-bottom: 1rem;
}

.row:has(> .field) > .button {
  padding-top: 0.6rem;
  padding-bottom: 0.6rem;
}

.price {
  font-variant-numeric: tabular-nums;
}

.claims {
  padding-left: 1.1rem;
  margin: 0 0 1rem;
}

.claims li {
  margin-bottom: 0.4rem;
}

/*
  Violet in the base design, ink in the mono one (`--link`), and underlined in both - in the mono
  design the underline is the ONLY thing telling a link from text, so no rule may take it away
  from a link in running text. Links that are controls (`.button`, `.card.block`) set their own.
*/
a {
  color: var(--link);
}

/* Form controls. Sized for a thumb on a phone, since that is where this is filled in. */
label {
  display: block;
  margin-bottom: 0.35rem;
}

/*
   The Turnstile iframe sets its own size; this only reserves the space so the button below does
   not jump under the user's thumb as the widget loads.

   The bare `form > div` that used to sit beside it matched EVERY direct div child of a form -
   `Field` renders one - so the reservation for one widget was padding out every input on every
   form in the app by 66px and a margin.
*/
form > div:has(iframe) {
  min-height: 66px;
  margin-bottom: 1rem;
}

.error {
  color: var(--danger);
  margin-top: 0.75rem;
}

/*
 * The step rail.
 *
 * It was a row of pill buttons that scrolled sideways with no sign it could be scrolled, and all
 * seven looked identical: nothing said which steps had anything in them. It is a numbered LIST
 * now - the thing it always was - and it wraps instead of scrolling, which text can afford and
 * chips could not.
 *
 * The elements underneath are unchanged: still `<button>`, still `aria-current="step"`. A step
 * you can jump to is a control, and dressing a control as text is a visual decision, not a
 * semantic one.
 */
.steps {
  margin: 0 0 1.25rem;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.4rem;
  align-items: baseline;
}

.step {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  min-height: 32px;
  padding: 0.25rem 0.5rem;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.95rem;
  cursor: pointer;
  text-align: left;
}

.step:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 1px;
}

/* Current: weight and a rule under it, so it is not the colour doing the work. */
.step-current {
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--heading);
  border-radius: 6px 6px 0 0;
}

/* The state mark. A glyph as well as a colour - `done` has to survive greyscale. */
.step-mark {
  font-size: 0.8rem;
}
.step-done .step-mark {
  color: var(--ok);
}
.step-done {
  color: var(--ink);
}
.step-partial .step-mark {
  color: var(--muted);
}

.step-detail {
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

/*
  The separator between steps, drawn by the list rather than written into each item.

  It was a numeral in front of every label - "1 Przedmiot  2 Sprawdzenie" - which is the list
  saying its own index out loud. An ordered list already carries position, a screen reader already
  announces it, and eight numerals in a row read as eight things to count rather than as one path.
  A `»` says the only thing the numeral was really for: these come in an order.

  In CSS and not in the markup because it is punctuation between items, not an item: an extra
  `<li>` would make a list of eight steps announce fifteen entries.
*/
.steps > li {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
}

/*
  After the item, not before the next one.

  The rail wraps on a phone, and `li + li::before` puts the separator at the START of a wrapped
  line - an orphan pointing at nothing. Trailing it keeps it attached to the step it follows, so a
  line that runs out ends with "Sprawdzenie 0/9 »" and reads as continuing.
*/
.steps > li:not(:last-child)::after {
  content: "»";
  /*
    `--muted`, not `--line`. A hairline is meant to disappear against the surface it divides -
    `--line` on the page is about 1.3:1, which is right for a 1px rule and invisible for a glyph.
    A separator that cannot be seen is not a separator; this is the secondary-text weight, which
    is what a piece of punctuation between two labels is.
  */
  color: var(--muted);
  user-select: none;
}

.chip {
  flex: 0 0 auto;
  min-height: 40px;
  padding: 0.45rem 0.8rem;
  /* A control, so `--edge` - same reasoning as `.button.secondary`. */
  border: 1px solid var(--edge);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}

.chip-on {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
}

/*
 * Choice groups.
 *
 * The chips used to be bare <button>s whose only signal of being selected was `.chip-on`, which
 * a screen reader cannot see and a keyboard cannot navigate as a group. They are now native
 * radios inside a fieldset, wrapped in the label so the whole chip stays the hit target - the
 * appearance is unchanged and the semantics are the browser's.
 */
.choices {
  border: 0;
  padding: 0;
  margin: 1rem 0 0;
}
.choices legend {
  padding: 0;
  margin-bottom: 0.35rem;
  font-weight: 600;
}
.chip:has(input) {
  display: inline-flex;
  align-items: center;
  gap: 0;
}
/*
 * Hidden from sight, NOT from the accessibility tree or the focus order: `display: none` and
 * `visibility: hidden` would take the control out of both, which is the usual way this pattern
 * is got wrong. The chip carries the focus ring on the input's behalf.
 */
.chip input[type="radio"],
.chip input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/*
 * The tick on an included chip.
 *
 * A filled chip means "this one" in the radio groups a step away; in a set it has to mean "this
 * one, and others may be too". Decorative - the state is the checkbox's - and it is what stops
 * the two kinds of group reading as one on a greyscale screen.
 */
.chip-mark {
  margin-inline-end: 0.4rem;
  font-size: 0.9em;
  line-height: 1;
}

.chip:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.row.wrap { flex-wrap: wrap; }

/*
 * Out of sight, in the accessibility tree.
 *
 * For the word beside a state glyph. `display: none` would take it out of both, which is the
 * usual way this is got wrong - and then the mark means nothing to anybody who cannot see it.
 */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.card.block {
  display: block;
  /* A whole card that is a link is a control, not a word in a sentence: body colour, no line. */
  color: var(--ink);
  text-decoration: none;
}

.offer {
  border-color: var(--accent);
}

/*
 * The same offer, made where the answer that raised it is.
 *
 * As a `.card` of its own it sat at the foot of a nine-question step, a screen away from the
 * answer it was about. Inside the question's card it cannot borrow that card's border, so the
 * marker moves to the leading edge - the rule this project already uses to say "this is a
 * different kind of thing" without relying on hue alone.
 */
.card > .offer {
  margin-top: 0.75rem;
  padding: 0.75rem;
  border: 0;
  border-left: 3px solid var(--accent);
  border-radius: 0 6px 6px 0;
  background: var(--raised);
}
.card > .offer p {
  margin-top: 0;
}

/*
 * The checks step: topics that fold, and questions as the lines of a list.
 *
 * Each question used to be a card of its own - border, padding, legend, hint, chips - and ten of
 * them down a phone read as a form somebody files rather than a few things somebody is asked.
 * A topic is the one box now; inside it a question is a line between hairlines, with its answer
 * beside it where the answer is short. Nothing is taken away: the same fieldsets, the same radios,
 * the same hint wired to the same `aria-describedby`.
 */
.check-group {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  margin-bottom: 0.75rem;
}
.check-group > summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.25rem 1rem;
  padding: 0.85rem 1rem;
  cursor: pointer;
  list-style: none;
}
.check-group > summary::-webkit-details-marker {
  display: none;
}
/* The disclosure's own sign, since a flex summary loses the marker - a shape, turned when open. */
.check-group-title::before {
  /* Empty alternative text: the shape is for the eye, and the summary already says it expands. */
  content: "›" / "";
  display: inline-block;
  width: 1em;
  color: var(--muted);
  transition: transform 0.15s ease;
}
.check-group[open] .check-group-title::before {
  transform: rotate(90deg);
}
.check-group-title {
  font-weight: 600;
}
.check-group-count {
  color: var(--muted);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}
.check-group > summary:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
  border-radius: 10px;
}
.check-group-body {
  padding: 0 1rem 0.25rem;
}
.check-row {
  padding: 0.75rem 0;
  border-top: 1px solid var(--line);
}
.check-row > .offer {
  margin-top: 0.75rem;
  padding: 0.75rem;
  border: 0;
  border-left: 3px solid var(--accent);
  border-radius: 0 6px 6px 0;
  background: var(--raised);
}
.check-row > .offer p {
  margin-top: 0;
}

/*
 * A question as one line: the words, and beside them the answer.
 *
 * `row-question-inline` puts a short control on the right; `row-question-stacked` keeps the
 * rhythm with the control underneath, for four options or a sentence to type. Below 30rem both
 * stack, because beside leaves the question a column two words wide.
 */
.row-question {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.4rem 1rem;
  margin: 0;
  padding: 0;
  border: 0;
}
.row-question-text {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  min-width: 0;
}
.row-question-text > label,
.row-question-text > span {
  font-weight: 500;
}
.row-question-control {
  justify-content: flex-end;
  align-items: center;
  gap: 0.5rem;
}
.row-question-control input {
  width: 8rem;
}
.row-question-stacked {
  grid-template-columns: minmax(0, 1fr);
}
.row-question-stacked .row-question-control {
  justify-content: flex-start;
}
.row-question-stacked .row-question-control input {
  width: 100%;
}
.row-question > .row-hint,
.row-question > .error {
  grid-column: 1 / -1;
  margin: 0;
}
@media (max-width: 30rem) {
  .row-question {
    grid-template-columns: minmax(0, 1fr);
  }
  .row-question-control {
    justify-content: flex-start;
  }
}

/*
 * The "?" that unfolds a question's explanation.
 *
 * 28px is above the 24px WCAG 2.2 minimum target; a circle so it reads as a control beside the
 * words rather than as punctuation at the end of them.
 */
.hint-toggle {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--edge);
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font: inherit;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  align-self: center;
}
.hint-toggle[aria-expanded="true"] {
  background: var(--raised);
  color: var(--ink);
}
.hint-toggle:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/*
 * The wizard's rail on a phone: one line, "Krok 2 z 7: Przedmiot", and the list on a tap.
 *
 * Above the breakpoint the summary is not drawn and the details is held open, so a monitor shows
 * the list exactly as before. `NARROW_RAIL` in viewport.ts is the same width.
 */
.steps-fold > summary {
  display: none;
}
@media (max-width: 47.99rem) {
  .steps-fold > summary {
    display: list-item;
    padding: 0.4rem 0;
    margin-bottom: 0.5rem;
    color: var(--ink);
    font-weight: 600;
    cursor: pointer;
  }
  .steps-fold > summary::marker {
    color: var(--muted);
  }
}

.big { font-size: 1.5rem; font-weight: 600; }

.thumb {
  margin: 0;
  width: 96px;
}
.thumb img {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--line);
  display: block;
}
.thumb figcaption {
  font-size: 0.7rem;
  line-height: 1.25;
  margin-top: 0.25rem;
}

.qr,
.qr-placeholder {
  display: block;
  margin: 0 auto 1rem;
  border-radius: 8px;
}
.qr-placeholder {
  background: var(--line);
}

/*
  Every text field in the product, in one rule.

  It was two: `type="email"` had its own block near the top of this file and everything else had
  a second, identical one down here - same display, width, height, padding, margin, border,
  radius, background, colour and font, written twice. Adding a field type meant finding the right
  list, and the comments on the second one record two that were missed for exactly that reason:
  `type="search"`, so the app's one search box was a browser default next to 44px controls, and
  `textarea`, so the only multi-line field - the address on the parties step and the join screen -
  rendered at the browser's 20 columns beside inputs filling the reading column. Both were found
  the same way: by looking at the one screen that has one.

  The list stays EXPLICIT rather than becoming a bare `input`. A hidden file input, a radio and a
  checkbox are built differently and are styled by the chip they sit in; giving them a 44px box
  and a bottom margin would break the three screens that use them.
*/
input[type="email"],
input[type="text"],
input[type="search"],
input[type="date"],
input:not([type]),
input[readonly],
textarea {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 0.6rem 0.75rem;
  margin-bottom: 1rem;
  /*
    `--edge`, for the same reason as the outlined button: on a warm ground an empty field drawn
    in the hairline colour is a faintly paler rectangle, and the person is meant to find it and
    aim a thumb at it while standing up.
  */
  border: 1px solid var(--edge);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  /* 16px minimum: iOS Safari zooms the page on focus for anything smaller. */
  font: inherit;
}

/*
  A date box is as wide as a date, not as wide as the column.

  It belongs in the list above for everything that makes a field look like a field here - the
  44px box, the padding, the border, the radius, the colours, the 16px font that stops iOS
  zooming - and it was missing from it, so the two dates on the fulfilment screen rendered as
  browser defaults beside 44px controls. That is the third time this list has been found short,
  and the earlier two are recorded above it.

  The width is the one thing it does not want from that list. A date has a fixed shape and the
  browser draws its own picker button at the end of the box; stretched across the reading column
  it reads as an empty text field somebody forgot to fill in.
*/
input[type="date"] {
  width: auto;
  min-width: 12rem;
  max-width: 100%;
}

/*
  Dimmed while it cannot be used - now for every text field, not only the e-mail one.

  A deliberate widening rather than a side effect of merging the rules. Only the sign-in address
  is disabled today, while a request is in flight; the next field that needs it would otherwise
  have looked perfectly editable, which is the one thing a disabled control must not do.
*/
input[type="email"]:disabled,
input[type="text"]:disabled,
input[type="search"]:disabled,
input[type="date"]:disabled,
input:not([type]):disabled,
textarea:disabled {
  opacity: 0.6;
}

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 1.5rem 0;
}

.thumb-remove {
  min-height: 28px;
  padding: 0.15rem 0.5rem;
  font-size: 0.75rem;
  margin-top: 0.25rem;
  width: 100%;
}

/* The handover code is read off a screen, across a table, outdoors. Big, spaced, tabular. */
.handover-code {
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  font-variant-numeric: tabular-nums;
  text-align: center;
  margin: 0.75rem 0 0.25rem;
}

/* A 64-character hash has to be selectable and readable, and must not blow out the layout. */
.hash {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.75rem;
  word-break: break-all;
  /*
    `--raised`, not `--bg`. It is an inset INSIDE a card, and it read as recessed only while the
    page was darker than the card - on a white page the same token would have made it the
    brightest thing on the screen. `--raised` is the token for exactly this, and `.offer` uses it
    for the same reason.
  */
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.5rem;
  user-select: all;
}

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85em;
}

/*
  The session list.

  A two-column definition list, because that is what the content is: a label and a recorded
  value. It collapses to one column on a narrow screen - the values here are long strings
  (a user agent is not a word) and squeezing them into half a phone makes them unreadable.
*/
dl.session {
  display: grid;
  grid-template-columns: 14rem 1fr;
  gap: 0.2rem 1rem;
  margin: 0.5rem 0 0;
}
dl.session dt {
  color: var(--muted);
}
dl.session dd {
  margin: 0;
  overflow-wrap: anywhere;
}
@media (max-width: 40rem) {
  dl.session {
    grid-template-columns: 1fr;
  }
  dl.session dd {
    margin-bottom: 0.4rem;
  }
}

/*
  The photo viewer.

  `::backdrop` is the browser's own overlay for a modal `<dialog>` - no z-index stack of our own,
  and it disappears with the element rather than being cleaned up by hand.
*/
.viewer {
  border: none;
  border-radius: 0.5rem;
  padding: 1rem;
  max-width: min(92vw, 44rem);
  max-height: 90vh;
  background: var(--bg);
  color: var(--ink);
  overflow: auto;
}
.viewer::backdrop {
  background: rgb(0 0 0 / 0.7);
}
.viewer img {
  max-width: 100%;
  max-height: 70vh;
  display: block;
  margin: 0 auto 0.75rem;
}
.viewer p {
  margin: 0 0 0.5rem;
}

/* The thumbnail is a control now, so it must not look like a button around a picture. */
.thumb-open {
  padding: 0;
  border: none;
  background: none;
  cursor: zoom-in;
  display: block;
}
.thumb-open:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* The validation summary. Bordered rather than tinted, so it does not rely on colour alone. */
.error-summary {
  border: 2px solid var(--danger);
}
.error-summary ul {
  margin: 0.4rem 0 0;
  padding-left: 1.2rem;
}
.error-summary:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* ── The record: header, work panel, actions ──────────────────────────────── */

/*
 * The three cards at the top of a record.
 *
 * The wizard used to be a form with a step strip: what the record already said was somewhere
 * behind you, and who was on the other side was step seven. The case, the selling side and the
 * buying side sit above the work now and stay there on every step, which is what the product
 * claims to be - a shared record two people add to, not a questionnaire.
 *
 * `auto-fit` with a 16rem floor does 3 -> 2 -> 1 column on its own; no breakpoint decides it.
 */
.record-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}

/* One process and its document stay together when the two columns stack. */
.case-title, .case-actions {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.75rem;
}
.case-title { justify-content: space-between; margin-bottom: 1rem; }
.case-title h1 { margin: 0; }
/*
  The reference follows the heading; the link keeps the far edge.

  `margin-inline-end: auto` rather than a second flex group: the row is three things and only the
  last one belongs on the right, so the space goes between the second and the third.
*/
.case-ref { margin-inline-end: auto; color: var(--muted); }
/*
  One surface for the whole process, not one per half.

  The document side was `--surface` and the work side was left to show the page through, so a
  process read as a white card with a grey panel bolted to its left - and the grey half is the one
  carrying the state and the action. That put the recessed treatment on the part somebody is there
  to act on, and made two halves of one thing look like two things.

  What separates them is the hairline, which is the honest separator: these are two aspects of one
  process - what is happening, and what it has produced - so they sit on one sheet and a rule runs
  between them.
*/
.case-process {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  border: 1px solid var(--line);
  border-radius: 0.75rem;
  margin-bottom: 1rem;
  overflow: hidden;
  background: var(--surface);
}
/*
  The gap does the spacing, so nothing inside a half carries a margin of its own.

  The same arrangement as `.record-cards > .card` below, and for the same reason: four lines each
  pushing a 1rem paragraph margin ahead of them, inside 1.5rem of padding, gave a card that was
  mostly air - a heading, three short lines and a control, spread over the height of a paragraph
  of prose. Written as one gap rather than as tuned margins because a margin per element is what
  drifts: the next line added to one of these halves would arrive with the old rhythm.

  `flex-start` is load-bearing. These halves hold a control, and a stretched flex item would make
  every "Kontynuuj realizację" as wide as the card - which is the weight of a control, not a
  spacing decision (rule 16b).
*/
.case-work, .case-document {
  padding: 1.15rem 1.25rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
  min-width: 0;
  overflow-wrap: anywhere;
}
.case-work > *, .case-document > * { margin: 0; }
.case-document { border-inline-start: 1px solid var(--line); }
.case-state { font-weight: 600; }
.case-eyebrow { color: var(--muted); font-size: 0.8rem; }
/*
  Air before the thing that is pressed, and only there.

  Compact is not the same as cramped: the lines above describe a state and belong together, the
  control acts on it and does not. Half a line of separation is the difference between a tight
  card and a list of things that have run into each other.
*/
.case-work > .button, .case-document > .case-actions { margin-top: 0.5rem; }
@media (max-width: 47.99rem) {
  .case-process { grid-template-columns: minmax(0, 1fr); }
  .case-document { border-inline-start: 0; border-top: 1px solid var(--line); }
  .case-work, .case-document { padding: 0.9rem 1rem; }
  .case-overview .record-cards { grid-template-columns: minmax(0, 1fr); }
}

.record-cards > .card {
  margin-bottom: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

/* The gap does the spacing, so nothing inside a header card carries a margin of its own. */
.record-cards > .card > *,
.record-cards > .card > dl > * {
  margin: 0;
}

.record-cards h2 {
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
  font-weight: 600;
}

/*
 * Collapsed on a phone, open on a monitor.
 *
 * Three cards stacked above the form push the actual question off the bottom of a phone on every
 * single step, which is the one way this layout could be worse than the strip it replaces. The
 * `open` attribute is driven by a media query in React rather than by CSS, because the content
 * has to stay in ONE place in the DOM - rendering a narrow copy and a wide copy is two of every
 * heading and two of every id.
 */
.record-header {
  margin-bottom: 0;
}

.record-header > summary {
  cursor: pointer;
  padding: 0.6rem 0;
  color: var(--muted);
  font-size: 0.95rem;
}

.record-header > summary::marker {
  /* Same mistake as the step separator: this triangle is the only sign the brief expands. */
  color: var(--muted);
}

@media (min-width: 64rem) {
  /* Nothing to expand: the cards are all visible, so the control would be a lie. */
  .record-header > summary {
    display: none;
  }
}

/*
 * The way out, above the brief rather than inside it.
 *
 * It used to sit in the "Sprawa" card - inside the part that collapses on a phone - so on the
 * screen it exists for, the exit from a record still being prepared was folded behind a summary
 * line that says nothing about it.
 */
.record-back {
  margin: 0 0 0.5rem;
}

/*
  The three stages, above the record on every screen that shows one.

  A LIST, not controls. The stages say where the record is, and a button offering to move to one
  would promise something only the record decides. So there is nothing here to press, nothing that
  takes focus, and the current stage is carried by `aria-current="step"` as well as by the styling
  - a rail whose only signal is a colour says nothing to somebody who cannot see it.

  One line, in the same shape as the wizard's step rail. It was three bordered cards each carrying
  a name and a line of explanation, which stacked into three on a phone: about 250px of furniture
  above a screen whose job is to show a document. Weight is not what tells somebody how far along
  they are - position and emphasis are, and both are free.

  Numbers rather than ticks. A tick reads as verification (rule 17), and what a completed stage
  means here is "this part is behind you", not "this was checked".
*/
.stage-rail {
  margin: 0 0 0.4rem;
}

.stage-rail ol {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.1rem 0.5rem;
  align-items: baseline;
  font-size: 0.9rem;
}

.stage {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  color: var(--muted);
}

.stage-index {
  font-variant-numeric: tabular-nums;
}

/*
  The current stage's number, on chartreuse.

  The only use of `--mark` outside the commit rule, and the only shape in the product that is
  filled for the sake of being seen. It is not carrying the state on its own - `.stage-current`
  is already the ink colour against `--muted` and already 600 weight, and `aria-current="step"`
  is what a screen reader is told. It is the thing the eye lands on first when the record is
  opened, which is the one job a colour with 1.4:1 against paper can do: be a ground, under a
  numeral dark enough to read off it (8.3:1).

  Not a tick and not a badge. A tick reads as verification (rule 17), and a filled disc that
  moves along a row of numbers says "this part is behind you", which is the only claim the rail
  is entitled to make.
*/
.stage-current .stage-index {
  background: var(--mark);
  color: var(--mark-ink);
  border-radius: 999px;
  min-width: 1.5em;
  padding: 0.05em 0.35em;
  text-align: center;
}

/*
  After the item, not before the next one - the same reasoning as `.steps`: the rail wraps on a
  phone, and a separator at the start of a wrapped line is an orphan pointing at nothing.
*/
.stage:not(:last-child)::after {
  content: "»";
  /* `--muted`, not `--line`: a hairline is meant to disappear, a glyph is not. */
  color: var(--muted);
  user-select: none;
}

.stage-current {
  color: var(--ink);
  font-weight: 600;
}

/*
  Whose move it is.

  The sentence somebody opens the record to read after two days away, so it keeps a weight of its
  own - but a rule down the leading edge, not a card. A bordered box here made two blocks of
  furniture where the rail was already one, and boxed text is the first thing an eye learns to
  skip. The owner changes the edge, never the hue alone.

  Heading and sentence share a line where there is room and wrap where there is not; anything
  after them - who we are waiting for, the way to where the act lives - takes a line of its own.
*/
.next-step {
  margin: 0 0 1rem;
  padding: 0.5rem 0.75rem 0.5rem 0.7rem;
  border-left: 3px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 0.5rem;
}

.next-step h2 {
  margin: 0;
  font-size: 0.95rem;
}

.next-step p {
  margin: 0;
}

.next-step .muted,
.next-step .button {
  flex-basis: 100%;
}

.next-step .button {
  margin-top: 0.5rem;
  align-self: start;
  /* `flex-basis: 100%` would otherwise stretch it across the column. */
  width: fit-content;
}

/*
  The reader's own move is the one case worth marking out - and the only place celery is used.

  The fill is IN ADDITION to the edge, never instead of it: `.next-step-counterparty` and
  `.next-step-you` already differ by the colour of a 3px rule, which is a difference somebody
  who cannot see hue does not get. A ground does carry - it is area rather than a line, and it
  is the thing that makes the sentence findable after two days away, from across the room, on a
  phone held at arm's length. Still not a card: no border, no shadow, and it keeps the leading
  rule it always had, because a box here would put a second block of furniture under the rail
  and boxed text is the first thing an eye learns to skip.

  `--tint` appears exactly here in the whole product. That is what makes it mean something.
*/
.next-step-you {
  border-left-color: var(--accent);
  background: var(--tint);
  border-radius: 0 8px 8px 0;
}

.next-step-counterparty,
.next-step-kvitant {
  border-left-color: var(--muted);
}

/*
  Nothing to do, and nothing pretending otherwise. An abandoned record is a fact about what
  happened to it, not a failure state - so it is quiet rather than red.
*/
.next-step-nobody {
  border-left-color: var(--line);
  color: var(--muted);
}

/*
  Thumbnails of the handover, beside the declarations that describe it.

  A row that wraps rather than a grid: this is a short list somebody adds to at a meeting, not a
  gallery, and a grid with one item in it draws a frame around emptiness.
*/
/*
  Handover photographs, in a row beside the declarations.

  Only the ROW is described here. What one photograph looks like is `.thumb`, and it used to be
  described twice - 9rem here against 96px there, two borders, two radii - so the same picture
  was a different size depending on which screen it was on. A thumbnail is a rendering and never
  the evidence (rule 15i); which screen it is on does not change what it is.
*/
.evidence-strip {
  margin: 0 0 0.75rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

/* The one-line brief in the summary: item, price, both sides, all tabular and quiet. */
.record-brief {
  font-variant-numeric: tabular-nums;
}

.record-ref {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85em;
}

/*
 * A side is a slot that may hold more than one person (par. 4, rule 15h), so this is a list even
 * when it has one row in it. A free seat is a normal state and reads as one - it is not an error,
 * and colouring it like one would be wrong on the majority of records that are mid-flight.
 */
.side-people {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.side-people li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
}

/*
  The address on its own line, under the name it belongs to.

  `flex-basis: 100%` rather than a second list: the row is already a wrapping flex line, so this
  is what says "start a new one" without the name and the state word losing their baseline.
*/
.side-email {
  flex-basis: 100%;
  font-size: 0.85rem;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.side-empty {
  color: var(--muted);
}

/* A state word, not a badge: no fill, no pill, nothing that reads as a verified mark (par. 16). */
.state {
  font-size: 0.8rem;
  color: var(--muted);
}
.state-ok {
  color: var(--ok);
}

.fact {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: baseline;
  font-size: 0.95rem;
}
.fact dt,
.fact-key {
  color: var(--muted);
}
.fact dd,
.fact-value {
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/*
 * The work panel: what this step is asking for.
 *
 * Same surface as a card, but it is the only thing on the screen with an action bar attached, and
 * that is what tells you where the step ends and the record begins.
 */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 1.25rem;
}

.panel > section > h2:first-child,
.panel > h2:first-child {
  margin-top: 0;
}

/*
 * The actions for THIS step, in one place at the end of it.
 *
 * Sticky at the bottom on a narrow screen: a step with a long list in it put "Dalej" below the
 * fold, and the safe-area padding keeps it clear of the home indicator.
 *
 * Two rows, and on a phone that is the point. "Wstecz", a note about unsaved work, "Przejdź do
 * ustaleń" and "Dalej" were one row that wrapped wherever the words happened to run out, so the
 * bar changed shape as the note came and went and the two controls a person navigates with landed
 * in different places on different steps. The navigation is now one pair, together, at the end
 * where a thumb is; anything that LEAVES the wizard gets its own line under it, because it is a
 * different kind of move and it is not what "Dalej" is next to.
 */
.panel-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  position: sticky;
  bottom: 0;
  background: var(--surface);
  padding-bottom: max(0.75rem, env(safe-area-inset-bottom));
}

.panel-actions .actions-end {
  margin-inline-start: auto;
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

/*
 * A refused way out, on its own line under the pair.
 *
 * The last step's primary can fail at something the record itself is fine with - freezing
 * version 1 - and the message has to land where the button is rather than replacing the screen
 * somebody is standing on.
 */
.panel-actions .onward-error {
  flex-basis: 100%;
  margin: 0;
  text-align: end;
}

/*
 * The same placement for the note that PRECEDES the press rather than reporting it.
 *
 * Its own line beneath the pair, and left-aligned: it is a sentence to read, not a status to
 * glance at, and a paragraph set flush right is read last if at all.
 */
.panel-actions .onward-note {
  flex-basis: 100%;
  margin: 0;
}

/* Its own line, under the navigation rather than wedged into it. */
.panel-actions .actions-onward {
  flex-basis: 100%;
  display: flex;
  justify-content: flex-end;
}

@media (min-width: 64rem) {
  /* Nothing is out of reach on a monitor, and a sticky bar there only eats the page. */
  .panel-actions {
    position: static;
    padding-bottom: 0;
  }
}

/* Unsaved work, said before the person leaves rather than in a dialogue after they tried to. */
.dirty-note {
  font-size: 0.85rem;
  color: var(--muted);
}

/*
  A line that comes and goes keeps its room.

  "Zapisuję…" then "Zapisano" then nothing is three states of one line, and with no height
  reserved each transition moved everything below it - on the parties step, where choosing an
  option saves immediately, the controls slid 16px down and back under the finger of somebody
  about to press the next one. `min-height` rather than a non-breaking space, so a screen reader
  is handed an empty region rather than a phantom character.
*/
.status-line {
  /* `em` first for anything that does not know `lh`; `lh` is exactly one line box, so the
     reserved row and the rendered message are the same height rather than 0.7px apart. */
  min-height: 1.5em;
  min-height: 1lh;
}

/*
 * The agreement screen: the document on the left, its state on the right.
 *
 * `agreementState()` is the single answer to "can this be signed" (rule 15d), so it gets a single
 * fixed place to be shown, rather than a button that follows the end of a long document. Below
 * 60rem the aside comes FIRST in the source order and stays first, because on a phone the state
 * of the agreement is what you opened the screen to find out.
 */
.record-layout {
  display: grid;
  gap: 1.5rem;
  align-items: start;
}

@media (min-width: 60rem) {
  .record-layout {
    grid-template-columns: minmax(0, 1fr) 20rem;
  }
  .record-aside {
    position: sticky;
    top: 1rem;
  }
}

.record-aside > .card:last-child {
  margin-bottom: 0;
}

/*
 * The state column reads as a different kind of surface from the document beside it.
 *
 * Ivory is what the RECORD is made of - the snapshot, the header cards, the panel somebody types
 * into. The aside is not part of the record: it is what the server says about it, and the only
 * place the acceptance is signed from. At full celery it would compete with "Teraz Twoja kolej"
 * and both would stop being a signal, so it is `--tint-soft` - 1.08:1 against ivory, which is
 * near the floor of what a screen can show and exactly as much as this needs to say.
 */
.record-aside > .card {
  background: var(--tint-soft);
}

/*
 * A form field keeps its reading width even when the panel is wide.
 *
 * The panel is as wide as the record because the record is what it sits under - but an input
 * stretched to 1100px is not easier to fill in, it is harder to aim at and harder to read back.
 * The control column stays at the measure; the panel around it does not.
 */
.panel input,
.panel textarea,
.panel select {
  max-width: var(--measure);
}


/* The heading of a step is the first thing in the panel, so it brings no space of its own. */
.panel section > h2:first-child,
.panel > div > h2:first-child {
  margin-top: 0;
}

/*
 * A button in a column card is sized by its label, not by the card.
 *
 * `.record-cards > .card` is a flex column, and a flex column stretches its children across -
 * which turned "Zaproś" into a full-width bar with three words floating in the middle of it.
 */
.record-cards .button {
  align-self: start;
}

/*
 * A suggestion read off the photographs, and the two things its shape has to say.
 *
 * It is NOT part of the form. The dashed edge and the inset are what make that legible before a
 * word is read: a field is a solid box somebody types in, and this sits beside one, obviously
 * offered rather than filled. A suggestion styled like an input is a suggestion people believe
 * they have already accepted.
 *
 * The proposed text is set in the same face and weight it will have once it is in the record,
 * because that is the only honest preview of what taking it does. What carries the caveat is the
 * muted line above it and the muted line below - never the text itself (see `shared/ai.ts`).
 *
 * The leading edge is `--line`, not `--accent`. Accent is the product's one emphasis colour and
 * spending it here would rank a machine's reading of a photograph alongside the controls that
 * commit somebody to something.
 */
.ai-suggestion {
  border: 1px dashed var(--line);
  border-inline-start: 3px solid var(--line);
  border-radius: 8px;
  padding: 0.6rem 0.75rem;
  margin: -0.25rem 0 0.9rem;
  background: transparent;
}

.ai-suggestion-from,
.ai-suggestion-check {
  margin: 0;
  font-size: 0.85rem;
}

.ai-suggestion-value {
  margin: 0.35rem 0;
  /* Pre-wrap: a proposed description is a paragraph, and a serial number must not be re-flowed. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.ai-suggestion-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* The panel that runs it says what it costs before it is pressed, so it reads as a paragraph. */
.ai-panel p {
  margin: 0 0 0.5rem;
}

.ai-panel .button {
  align-self: start;
  margin-bottom: 0.5rem;
}

/* Where the findings went. A list, because it is one - and never a list of the values. */
.ai-found {
  margin: 0 0 0.5rem;
  padding-inline-start: 1.1rem;
  color: var(--muted);
  font-size: 0.9rem;
}

/*
 * The public verifier (/weryfikacja).
 *
 * A target somebody drops a file onto, and a report they read afterwards. The dashed border is
 * doing real work here: nothing else in this application accepts a dropped file, so the shape has
 * to say what it is before the sentence inside it does.
 */
.dropzone {
  max-width: var(--page);
  margin: 1rem auto;
  padding: 1.5rem 1.25rem;
  text-align: center;
  background: var(--raised);
  border: 2px dashed var(--edge);
  border-radius: 10px;
}

.dropzone p:first-child {
  margin-top: 0;
}

/*
 * A statement the reader has to see before acting, rather than an aside.
 *
 * Same treatment as `.record-language-notice`, for the same reason: "nothing is uploaded" is a
 * promise somebody decides whether to trust BEFORE they let go of a stranger's documents, and a
 * grey line under the fold is where a notice goes to be ignored.
 */
.notice {
  max-width: var(--page);
  margin: 0.75rem auto;
  padding: 0.6rem 0.8rem;
  border-inline-start: 3px solid var(--accent);
  background: var(--tint);
  border-radius: 0 6px 6px 0;
}

.verify-report {
  max-width: var(--page);
  margin: 1.5rem auto;
}

/*
 * The checksum, wherever one is printed.
 *
 * Monospace and breakable: it is 64 characters somebody compares CHARACTER BY CHARACTER against a
 * copy in another window, and a proportional font makes that harder than it already is. A class
 * of its own rather than `.verify-report p.muted`, which would have caught every explanatory
 * line on the page and set somebody's sentences in a terminal font.
 */
.hash {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  overflow-wrap: anywhere;
}

/*
 * The verifier's link in the footer: its own line, above the documents.
 *
 * Inside their list it was a tool filed among four legal texts, under a heading that says
 * "Dokumenty" - present and unfindable. Somebody arrives here having been handed an archive,
 * which is an errand rather than something to read.
 */
.footer-tool {
  margin: 0 0 0.5rem;
}

/*
 * The verifier's two views.
 *
 * Tabs rather than links, because there is no second page to link to - nothing leaves the device,
 * so the archive exists only in this tab's memory. The current one is marked by weight and by a
 * rule under it, never by hue alone.
 */
.view-switch {
  display: flex;
  gap: 0.25rem;
  max-width: var(--page);
  margin: 1.5rem auto 0;
  border-bottom: 1px solid var(--line);
}

.view-tab {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 3px solid transparent;
  padding: 0.5rem 0.9rem;
  font: inherit;
  color: var(--muted);
  cursor: pointer;
}

.view-tab.current {
  color: var(--heading);
  font-weight: 600;
  border-bottom-color: var(--heading);
}

.view-tab:focus-visible {
  outline: 2px solid var(--edge);
  outline-offset: -2px;
}

.verify-contents {
  max-width: var(--page);
  margin: 1.5rem auto;
}

/*
 * The verdict over the contents, and it is not decoration.
 *
 * Somebody can be sent straight here to "just look at the document". The first thing they meet
 * has to be whether what they are about to read is what was signed - so it sits above the
 * document, on every visit, and cannot be dismissed.
 */
.verdict {
  padding: 0.7rem 0.9rem;
  border-inline-start: 3px solid var(--accent);
  background: var(--tint);
  border-radius: 0 6px 6px 0;
}

.verdict.error {
  border-inline-start-color: var(--danger);
}

/*
 * Somebody else's HTML, in a frame that can do nothing.
 *
 * Tall enough to read without becoming a scroll-within-a-scroll trap on a phone.
 */
.document-frame {
  width: 100%;
  height: min(70vh, 600px);
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
}

.photo-list {
  list-style: none;
  padding: 0;
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr));
}

.photo {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.75rem;
  background: var(--surface);
}

.photo img {
  width: 100%;
  height: auto;
  border-radius: 6px;
}

/* A file that does not match the manifest is marked on itself, not in a list further up. */
.photo.broken {
  border-color: var(--danger);
  border-width: 2px;
}


/*
 * A list that is a list of THINGS, not of sentences.
 *
 * `.plain` was used in three places before it existed, which is the worst of both: a browser's
 * default bullets and default indent around a download button, a hash and a photograph prompt.
 * The marker is dropped because the items are controls - a bullet beside a button claims the
 * button is prose - while the list stays a list, so a screen reader still announces how many
 * documents a record has issued.
 */
.plain {
  list-style: none;
  margin: 0.75rem 0;
  padding: 0;
  display: grid;
  gap: 0.75rem;
}

/*
 * Each item stacks: the control, then what identifies it.
 *
 * A manifest hash is sixty-four characters and belongs UNDER the button rather than beside it.
 * Inline, it wrapped into a second block that looked like a separate row and pushed the layout
 * about - and the hash is the thing a person compares against another copy, so it has to be
 * readable rather than squeezed into whatever space a button left over.
 */
.plain > li {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
}

.plain > li > .hash {
  /* The whole value, wrapping where it must: a truncated hash is not a hash. */
  max-width: 100%;
  overflow-wrap: anywhere;
}

/*
 * Superseded editions, folded away.
 *
 * A record that was corrected three times listed four download buttons of equal weight, and the
 * newest one is not the one at the top of the screen - so the screen asked a person to read
 * version numbers to find the document they came for. What is kept rather than hidden is the LINK:
 * an older version is still the thing somebody may have quoted, so it stays reachable, one fold
 * away and never as a control competing with the current one.
 */
.superseded > summary {
  cursor: pointer;
  padding: 0.4rem 0;
  color: var(--muted);
  font-size: 0.95rem;
}

.superseded > summary::marker {
  /* The triangle is the only sign there is anything behind it. */
  color: var(--muted);
}

.superseded .plain {
  margin: 0.25rem 0 0.5rem;
}

/*
 * ── Stan usługi ─────────────────────────────────────────────────────────────
 *
 * Four states, and the colour is never what carries them: every row prints the state as a word,
 * and the grid of thirty days has a sentence under it saying what the squares say. The dot and
 * the squares are `aria-hidden` decoration, so the colour only has to meet 3:1 as a non-text
 * indicator rather than 4.5:1 as text.
 *
 * `--warn` (declared with the rest of the palette at the top) is the status page's own value. Amber rather than a second red, because "z zakłóceniami" and
 * "nie działa" are different answers and a reader glancing at the page has to be able to tell
 * them apart at a distance - which two reds, at the sizes here, do not allow. It is held at 3:1
 * on the palest ground it can land on, like `--edge`.
 *
 * `unknown` is deliberately the QUIETEST of the four, drawn in the rule colour. It is not a
 * warning: absence of signal ranks below health in the reducer, and a grey square is the honest
 * drawing of "nobody used this on a Sunday". Thirty of them on a new deployment is correct.
 */
.status-overall {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 1.15rem;
  margin: 1.25rem 0;
}

.status-state {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--muted);
  white-space: nowrap;
}

.status-dot {
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  background: var(--state, var(--line));
  flex: none;
}

.status-operational {
  --state: var(--ok);
}
.status-degraded {
  --state: var(--warn);
}
.status-outage {
  --state: var(--danger);
}
.status-unknown {
  --state: var(--line);
}

.status-systems {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 0.75rem;
}

.status-systems h2 {
  margin: 0;
  font-size: 1.05rem;
}

/*
 * Thirty squares that must fit a phone: they shrink rather than wrap, because a grid that wraps
 * into two rows stops reading as a timeline. `1fr` columns with a minimum of 3px keeps them
 * square-ish at 320px and comfortable on a monitor.
 */
.status-grid {
  display: grid;
  grid-template-columns: repeat(30, minmax(3px, 1fr));
  gap: 2px;
  margin: 0.75rem 0 0.4rem;
}

.status-day {
  height: 1.6rem;
  border-radius: 2px;
  background: var(--state, var(--line));
}

.status-legend,
.status-detail {
  margin: 0;
  font-size: 0.9rem;
}

/* ── The operator console ──────────────────────────────────────────────────
   Deliberately plain. This is an internal tool with one reader, and every rule
   here is one more thing that has to keep agreeing with the rest of the sheet;
   it borrows the card, the button and the container and adds only a table and a
   definition list, which the product itself has no use for. */

.panel-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
}

.panel-table th,
.panel-table td {
  text-align: left;
  padding: 0.5rem 0.75rem 0.5rem 0;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}

.panel-table th {
  font-weight: 600;
  white-space: nowrap;
}

.panel-facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.25rem 1rem;
  margin: 0.5rem 0;
}

.panel-facts dt {
  color: var(--muted);
}

.panel-facts dd {
  margin: 0;
}

/*
  A payment folds, and the one that took money is open.

  `<details>` rather than a button and state: the browser already knows how to do this, it works
  before the script runs, and the open one is expressible as an attribute rather than as something
  an effect has to set after mounting.
*/
details.card > summary {
  cursor: pointer;
  list-style-position: outside;
}

details.card[open] > summary {
  margin-bottom: 0.75rem;
}

/*
  A gateway reference is sixty characters with no spaces in it, and a postal address is not far
  behind. Without this the definition list decides the card's width and the card decides the
  page's, which is how one field pushes a layout sideways on a phone.
*/
.panel-facts dd {
  overflow-wrap: anywhere;
}

/*
 * An illustration on an editorial page, wider than the column of prose around it.
 *
 * The pictures are four-panel stories with small lettering inside, and the reading column (34rem)
 * shrinks that lettering below what anybody can read. So the figure breaks out of the column to
 * at most 56rem, centred on it, and on a phone it simply takes the width between the gutters -
 * where it is no wider than the column anyway, so the margin works out to zero.
 * The frame and the white ground are for the dark schemes: the illustrations are drawn on white,
 * and without an edge they read as a hole cut in the page.
 */
.guide-figure {
  --figure: min(56rem, calc(100vw - 2 * var(--gutter)));
  width: var(--figure);
  margin: 1.5rem 0 2rem calc((100% - var(--figure)) / 2);
}
.guide-figure img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #ffffff;
}
.guide-figure figcaption {
  max-width: var(--measure);
  margin: 0.6rem auto 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/*
 * The full-size illustration on an editorial page (public/editorial/figure.js).
 *
 * The photo viewer's dialog, widened: these pictures are read rather than looked at, so on a
 * phone the image keeps a width at which its lettering is legible and scrolls sideways inside
 * the dialog, instead of being shrunk to the screen - which is the size it already had on the page.
 */
.viewer-wide {
  width: min(96vw, 110rem);
  max-width: none;
  max-height: 94vh;
}
.viewer-scroll {
  overflow: auto;
  max-height: calc(94vh - 6rem);
  margin-bottom: 0.75rem;
  border-radius: 6px;
  background: #ffffff;
}
.viewer-scroll:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.viewer-wide .viewer-scroll img {
  display: block;
  width: 100%;
  min-width: 56rem;
  max-width: none;
  max-height: none;
  height: auto;
  margin: 0;
}
.guide-figure img.zoomable {
  cursor: zoom-in;
}
