/* ==========================================================================
   Edilis — interface skeleton (BL-12)
   --------------------------------------------------------------------------
   Builds exclusively on the tokens from tokens.css. No colour, spacing, radius or
   font size is hard-coded here — the rule from docs/design-system.md §1, guarded by
   tests/web/test_kostra.py::test_vlastne_styly_nepisu_hodnoty_natvrdo. The few px
   lengths that remain are graphic dimensions the design system writes down itself
   (a hairline, the 8px dot, the 32px click target); the guard lists every one of
   them with its reason, so a new number cannot slip in unnoticed.
   The components and their behaviour are described in docs/design-system.md §4;
   the screen layouts in docs/design.md.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

/* `hidden` is the attribute that says "not here yet" (§4.1 — a state is an attribute,
   not a class), and until BL-351 nothing in this stylesheet honoured it: hiding rested
   on the browser's own `[hidden] { display: none }`, which any author rule setting
   `display` outranks regardless of specificity. That is how the thank-you of the bug
   widget — `<p class="message message--success" hidden>` — burned in the dialog from
   the moment it opened: `.message` sets `display: flex`. The places that hid correctly
   were lucky rather than right (`.hint` simply sets no `display`), so the rule belongs
   here in the base layer and not on `.message`. `!important` is the point, not sloppiness:
   the declaration has to beat every component that sets `display`, and an element that
   is not there has nothing to lay out. An element that must stay in the layout while
   being invisible is a different job — `.visually-hidden` (§4.3), which never hides. */
[hidden] { display: none !important; }

html {
  color-scheme: light dark;
  /* A jump to an anchor lands *below* the sticky header, never behind it (BL-136).
     `.header` is `position: sticky; top: 0` and taller than a `.panel-header`, so a
     target scrolled to the top of the viewport ends up covered together with the
     heading the anchor names — the reader arrives with no confirmation of where they
     are. The offset is the header plus the gap every other sticky element leaves
     (`.calculator-summary`). It sits on
     the document and not on the two zones of the calculator: `#content` of the skip
     link is scrolled to the same way, and a rule per target would be a list nobody
     extends.

     **Measured first, token as the fallback** (§5, BL-691). The token is a
     `min-height` and the header is taller whenever it wraps, so a jump offset by the
     token alone lands the target *behind* the header on exactly the widths where the
     header wraps: measured at 390×844, `#content` came to rest 53px under the header
     before BL-691 and 84px after it, because the group of controls now takes the rows
     it needs. `--h-header-real` is what `web/js/rail.js` publishes from
     `getBoundingClientRect()`; without the script this is the token, that is, the
     behaviour of the day before. */
  scroll-padding-top: calc(var(--h-header-real, var(--h-header)) + var(--s-4));
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-base);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* A word with nowhere to break — an IBAN, a long e-mail, an attachment file name —
     breaks rather than pushing the page sideways (§5). The domain is full of them:
     §3 names IBAN and annex codes as mono values. `break-word` only breaks when the
     word genuinely does not fit, so ordinary prose is untouched. */
  overflow-wrap: break-word;
}

/* State is never colour alone (§6) — hence focus has its own outline, not just a hue. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

a { color: var(--accent); }

/* The hit a filter found, inside a row it kept (RQ-12). The native <mark> and no class
   of our own: `.mark` is taken by the wordmark (§4.3 Značka), where it means
   `display: block` and a width in pixels — an inline highlight wearing that name would
   break every marked word onto a line of its own. The element is also the semantic one
   for "relevant in the context the reader is in", which a search hit is.
   The accent pair, never a state colour (§1.1): a hit is a property of the reader's
   query, not a verdict about the building. --accent-ink on --accent-soft is the pair
   `.pill--on` and the active navigation item already carry, measured at 10.45:1.
   The browser default is a yellow that belongs to neither theme, so both halves are
   set — background alone would leave the dark theme's light text on it. */
mark {
  background: var(--accent-soft);
  color: var(--accent-ink);
  border-radius: var(--r-sm);
  padding: 0 0.15em;
}

/* The control height is a token, not a floor that never binds: --h-control is what
   these actually measure, and on a coarse pointer it grows (§6). The header height
   is derived from it, so both move together. */
button, .btn, select, input[type="submit"] { min-height: var(--h-control); }

@media (prefers-reduced-motion: no-preference) {
  .nav-item, .btn { transition: background-color 120ms ease, color 120ms ease; }
}

/* Skip link (§6) — the first focusable element of the page, parked one header height
   above the viewport until it takes focus. .visually-hidden cannot do this job: it has
   no :focus branch, so the link would stay clipped to 1×1 px exactly when it is needed.
   It sits above the sticky header, which is why its z-index outranks it. */
.skip-link {
  position: absolute;
  top: calc(-1 * var(--h-header));
  left: var(--s-3);
  z-index: 30;
  padding: var(--s-2) var(--s-3);
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow);
}

.skip-link:focus-visible { top: var(--s-2); }

/* The slide is decoration; with reduced motion the link simply appears (§6). */
@media (prefers-reduced-motion: no-preference) {
  .skip-link { transition: top 120ms ease; }
}

/* --- Application layout ---------------------------------------------------
   The header is sticky; below it the sidebar and the content. Under 860 px the
   area collapses into a single column and the navigation turns horizontal (§5). */

/* `minmax(0, 1fr)`, not `1fr`: an auto-sized grid track takes its minimum from the
   content, so one long organization name in the sticky header inflated the page's
   only column past the viewport and every screen scrolled sideways — the ellipsis
   never got a chance, because the column had already grown to fit the untruncated
   string. Flooring the track at 0 lets the header shrink and the text truncate. */
.app {
  min-height: 100vh;
  display: grid;
  grid-template-rows: auto 1fr;
  grid-template-columns: minmax(0, 1fr);
}
/* The subscription band takes a row of its own — BL-565. Stated with :has() rather
   than by always declaring three rows, because the band is usually absent and a bare
   `auto auto 1fr` would then hand the 1fr to the body's *neighbour* and leave the body
   itself unstretched. Same shape as `.workspace:has(> .whats-missing)` (§4.3). */
.app:has(> .message--band) { grid-template-rows: auto auto 1fr; }

/* The header asserts the height every sticky element offsets from, so --h-header
   is true rather than descriptive. It wraps rather than overflows: its three groups
   do not fit a phone on one line, and until BL-66 they simply pushed the page to
   636px and every screen scrolled sideways. **Half of that was still true until
   BL-691** — the groups wrapped, the controls inside the right-hand one did not, so
   the page went on scrolling sideways by 190px at 320px; see `.header-right` below.
   It wraps in DOM order — no `order`, no grid re-placement. Moving the groups
   visually would leave Tab travelling up and down the header (WCAG 2.4.3). */
.header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-4);
  min-height: var(--h-header);
  padding: var(--s-3) var(--s-5);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

/* The mark: four drawings picked by size, never one file scaled (§4.3, and the
   rules of the mark in decisions/vizualna-identita-edilis.md). The two custom
   properties are what makes the dark theme and the inverse variant free — they
   take their values from the palette, so a theme that moves the accent moves the
   stonework with it and no second set of files exists.
   Dimensions come in even numbers only: at odd sizes the vault lands on a half
   pixel and the joints smear even where they would otherwise hold. */
.mark {
  --m-stone: var(--accent);
  --m-key: var(--keystone);
  display: block;
  flex: none;
}
.mark-stone { fill: var(--m-stone); }
.mark-key   { fill: var(--m-key); }

/* A seal, a fax, a single-colour print: both properties inherit the text colour,
   so one declaration colours the whole mark. Never below grade S — in XS the
   keystone is separated by colour alone and a single-colour XS loses it. */
.mark--mono { --m-stone: currentColor; --m-key: currentColor; }

.mark--l  { width: 64px; height: 60px; }
.mark--m  { width: 32px; height: 30px; }
.mark--s  { width: 22px; height: 21px; }
.mark--xs { width: 16px; height: 15px; }

/* The lockup of mark and logotype. The clear space is a RULE of the mark, not a
   margin somebody typed into one template: one pillar wide, and the pillar is 18
   of the drawing's 100 units, so it is 0.18 of the mark's own width. */
.lockup {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--s-2) + 2px);
  padding-inline: calc(var(--s-2) + 2px);
}
.lockup--vertical {
  flex-direction: column;
  gap: var(--s-3);
  padding-block: var(--s-3);
}

/* Inter SemiBold with the identity's tracking. The word is small here, and that is
   the whole reason it is not Spectral: at this size the serifs disappear and the
   logotype reads as a defect rather than as a serif. */
.lockup-word {
  font-family: var(--sans);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: var(--ls-wordmark);
  /* Tracking is added AFTER the last letter too, so a centred word sits off-centre
     by half of it unless the indent gives it back. */
  text-indent: var(--ls-wordmark);
  text-transform: uppercase;
  color: var(--ink);
}

/* The large setting and print: Spectral, wider tracking, and here the serifs are
   the point. */
.lockup-word--print {
  font-family: var(--serif);
  font-size: var(--t-md);
  font-weight: 500;
  letter-spacing: var(--ls-wordmark-lg);
  text-indent: var(--ls-wordmark-lg);
}

/* A standalone control of the shell, not a link in a sentence — so it carries the
   click target of §6 rather than the height of its own text (it measured 23px). */
.wordmark {
  display: inline-flex;
  align-items: center;
  min-height: var(--h-control);
  color: var(--ink);
  text-decoration: none;
}

.header-right { margin-left: auto; display: flex; align-items: center; gap: var(--s-3); }

/* …and in the application header the group wraps **inside itself** too (BL-691).
   `.header` has wrapped since BL-66, but the automatic minimum of a `nowrap` flex
   item is the **sum** of its children's min-contents, not the width of the widest
   one: the group therefore never shrank below ~490px however much room it was
   given, and every screen of the application scrolled sideways on a phone — 190px
   at 320px, 120px at 390px. A row of its own (`flex-basis: 100%` below) was never
   the missing half; permission to break that row is.
   Scoped to the child of `.header` on purpose. The same class names the right-hand
   side of the **screen** header (§4.3), and there not wrapping is the intent: that
   group breaks whole, under its heading, rather than into its pieces. */
.header > .header-right { flex-wrap: wrap; }

/* Platform administration: three links above the breakpoint, one control below it
   (BL-723). What BL-691 bought with the wrap was height, and the header is **sticky**,
   so the rows do not scroll away — at 320×256, the viewport WCAG 1.4.10 measures reflow
   at, a platform administrator had 269px of header and no application under it.
   Measured after: 217px there, 165px at 390×844 and 113px on a phone held sideways.
   The two hosts render the same partial and exactly one of them is ever displayed;
   `display: none` also takes the other out of the tab order, which a `<details>` merely
   un-collapsed by CSS would not — it stays closed in the accessibility tree. */
.platform-links { display: contents; }
.platform-switcher { display: none; }
@media (max-width: 860px) {
  .platform-links { display: none; }
  .platform-switcher { display: block; }
  /* The key cap says nothing on a device without that key, and it costs ~51px — a third
     of the width the group has at 320px (BL-723). The shortcut itself stays: `jump.js`
     binds it to the document, not to this element. */
  .jump-opener kbd { display: none; }
}

/* Its own summary wears `.btn .btn--quiet`, so the row of controls reads as one row and
   not as a control with a stray label beside it. Where its panel hangs is stated below
   `.switcher > .panel`, which it has to outrank. */
.platform-switcher > summary::-webkit-details-marker { display: none; }
/* The arrow is drawn by the shared rule further down (three consumers, one drawing); here
   it only stops claiming the rest of the row — inside a `.btn` the summary is as wide as
   its content, so `margin-left: auto` has nothing to push against and the `gap` of the
   button is the spacing the arrow needs. */
.platform-switcher > summary::after { margin-left: 0; }

/* Language switcher (UC-69). The two codes sit tight together so they read as one
   control rather than two unrelated buttons; the active one is disabled, which is
   what says "this is the language you are in" without a second label.            */
.language-switcher { display: flex; gap: 0; }
.language-switcher .btn { padding-inline: var(--s-2); }
.language-switcher .btn[aria-current="true"] {
  /* Not --ink-muted: this is small text, which §6 keeps off the muted token. */
  color: var(--ink-soft);
  opacity: 1;
  text-decoration: underline;
}

/* Below the breakpoint the group of controls takes a row of its own: identity and the
   active organization on top, the controls beneath them — and as many rows as the
   controls need, since BL-691 let them wrap inside the group (see above; a clerk takes
   three at 320px, a platform administrator four). Nothing is hidden and nothing is
   re-ordered — the switcher stops forcing a wrap (`flex-basis: 0`, so it claims the
   leftover width instead of its full text width). Reading order and DOM order stay
   identical, so Tab still walks the header top to bottom (§5.1). */
@media (max-width: 860px) {
  .org-switcher { flex: 1 1 0; }
  .header-right { flex-basis: 100%; }
}

/* Collapsible switcher — **one component with two hosts** (design-system.md §4.3).
   Every mechanic lives here; `.org-switcher` in the application header switches the
   context of the whole application, `.calculation-switcher` in the screen header
   switches one record, and each host declares only what differs. Written out twice
   the absolute panel below would drift apart at the first touch — that is how
   `.breakdown` came to be `.meter` under a second name (BL-91).

   `min-width: 0` all the way down is what makes an ellipsis inside actually work: a
   flex item defaults to `min-width: auto`, which for `white-space: nowrap` text is
   the full string, so the name refused to shrink and pushed the header off-screen. */
.switcher { position: relative; min-width: 0; }
/* The layout belongs to the <summary>, never to the <details>: with `display: flex`
   on the parent the panel becomes a flex sibling of the summary and spills sideways. */
.switcher summary { display: flex; align-items: center; gap: var(--s-2); cursor: pointer; list-style: none; min-width: 0; }

/* The switcher's panel overlays the page; it must not push it. In flow it doubled
   the sticky header (86 → 184px on a phone) and shoved every screen down, which
   also made --h-header wrong exactly while the sticky offsets were being read.
   The `position: relative` above was already here and unused — this is its purpose.
   The application header owns a stacking context (z-index 20), so z-index 1 clears
   the content below without competing with the dialog. The screen header owns none,
   and must not be given one: above the dialog the panel would outrank a confirmation. */
.switcher > .panel {
  position: absolute;
  top: calc(100% + var(--s-2));
  left: 0;
  z-index: 1;
  width: max-content;
  max-width: min(22rem, calc(100vw - 2 * var(--s-5)));
}
/* Hosts whose panel hangs from the **right** edge. Two classes, like the base rule above,
   so they have to stand below it: written next to their hosts, higher in the file, both
   lost to its `left: 0` and hung from the left edge whatever their comments said (BL-720).

   The platform switcher sits near the right edge of a group that is itself right-aligned,
   so `left: 0` would push its panel past the viewport.

   The organization switcher does so only below 860px. Taking the leftover width there
   (`flex: 1 1 0`, the header's rule), it ends where the header's padding starts and its
   arrow sits at that edge; its left edge is wherever the wordmark ends. From the left, at
   320px the host started at x = 138, the panel was 207px wide and the open panel scrolled
   the document sideways by 26px. From the right it hangs under the arrow, and the width
   cap above is the header's own padding, so it fits at any width — narrowing the panel
   would only have moved the edge. Above 860px the host is as wide as its name and the
   panel hangs from the left, under the name, as before. */
.platform-switcher > .panel { left: auto; right: 0; }
@media (max-width: 860px) {
  .org-switcher > .panel { left: auto; right: 0; }
}

/* Switching is the panel's whole purpose, so its links are controls and take the
   target of §6 — they measured 22px. */
.switcher > .panel a {
  display: flex;
  align-items: center;
  min-height: var(--h-control);
}

/* Host: the organization switcher — the active organization must be apparent on
   every screen. All it adds is the width of the name (the two-row break under 860px
   is the header's own rule above); the mechanics are `.switcher`. */
.org-active {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  max-width: 18rem;
}

/* Host: the switcher of saved calculations in the *screen* header (BL-127). Its rows
   carry a calculation's name and the year of its call, so the panel is wider than the
   organization's; the row itself then stacks, which is the second and last difference
   and is reasoned about at the rule below. Everything else is the shared base, which
   is the whole point of having one. It overrides by **order**, not by weight: both
   selectors are two classes, so this rule has to stay below `.switcher > .panel`. */
.calculation-switcher > .panel {
  max-width: min(26rem, calc(100vw - 2 * var(--s-5)));
}
/* And it looks like a control, because on this screen nothing else says it is one
   (#217). In the application header the same component stands alone and its weight is
   enough; in the screen header it stood in a row of pills, and an unframed `<summary>`
   between two labels reads as a third label. The frame is therefore the **host's**, not
   the base's: on `.switcher summary` it would follow into the application header, where
   the organization switcher would suddenly grow a box it never asked for — and there is
   no `.org-switcher` rule to take it back.
   `width: max-content` sits on the `<details>`, not on the `<summary>`: the panel is
   absolutely positioned at `left: 0` of the `<details>`, so a full-width host would
   anchor the panel to the whole column. `max-width: 100%` keeps a long name from
   pushing the header out on a phone; the base's `min-width: 0` lets it shrink. */
.calculation-switcher {
  width: max-content;
  max-width: 100%;
}
.calculation-switcher > summary {
  min-height: var(--h-control);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
}
.calculation-switcher > summary:hover { background: var(--surface-2); }
/* A row here carries three data (name, building, year of the call), so it stacks:
   the name on the first line, the `.hint` with the other two under it (BL-137). On one
   line they do not fit under 400px and what an ellipsis would eat is the **name** —
   the only thing telling two calculations of one building apart. `align-items` has to
   be restated: the base centres a one-line row on the cross axis, which in a column is
   the horizontal one, and every row would sit in the middle of the panel. The two
   permanent escapes below the list are single-line and stay where they were. */
.calculation-switcher > .panel a {
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
}

/* Same reason as .app: the content column is floored at 0 so a wide table or a long
   unbroken string cannot push the page past the viewport. Wide content scrolls
   inside its own frame (§5).

   One column by default, two only where the sidebar actually is. The navigation
   exists only inside an organization, but the two-track list used to be stated
   unconditionally — so on every screen outside one (sign-in, the organization
   picker, founding an organization, accepting an invitation) the lone .content
   auto-placed into the *sidebar's* track and the whole screen rendered 208px wide,
   with 160px left after its padding. The login card, the picker's list and the role
   pills broke letter by letter, on a 1440px display (BL-74). Stating the columns on
   the layout that has two of them keeps the fact in one place: the grid follows the
   DOM instead of predicting it. */
.body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
/* The upper bound of the content column (§5.2) sits on **this** layout and not on
   .content inside it; why it is not written there stands at .content below, where the
   question is asked.

   The bounded pair is **not** centered, it is anchored to the left edge, and that
   reverses the conclusion of the BL-81 review (BL-120). With `margin-inline: auto` the
   shell let go of the left edge above the bound and then travelled right with
   every further hundred pixels of window, while the header stayed where it was — it is
   full-bleed on purpose, its background and its bottom rule are the edge of the window,
   not of the content. The interface therefore had two vertical edges, the wordmark's
   and the navigation's, and the one belonging to the navigation moved: the sidebar
   started at 0 on a 1440px display and ~950px in on a 3440px one. Empty space on the
   right is quiet; navigation that stands somewhere else on every monitor is not. The
   bound itself stays — what BL-81 was fixing, a four-field panel read across 3000px, is
   the width and not the position.

   calc() and not a track of `minmax(0, var(--w-content))`: a track with no `fr` sizes to
   its content, so a sparse screen would shrink below the measure instead of holding it.
   Capping the container keeps `1fr` doing the work — at 1440px the container is under
   the cap and the column measures the same 1232px as before, above the cap the shell is
   2000px wide and starts where the narrower window already had it.

   2000px, because 112rem of content is what the widest table of the application needs
   beside its step navigation and its rail; the measurement, column by column, is in
   §5.2 and a guard ties it to the token (#215). */
.body:has(> .sidebar) {
  grid-template-columns: 208px minmax(0, 1fr);
  max-width: calc(208px + var(--w-content));
}
@media (max-width: 860px) { .body:has(> .sidebar) { grid-template-columns: minmax(0, 1fr); } }

/* The gateway — every screen reached without navigation: sign-in, sign-up, password
   reset, signing out, and the four screens outside an organization (picking one,
   founding one, accepting an invitation). Here the content is not a column beside
   navigation, it *is* the screen, so it takes one measure and centers.

   The width sits on the layout, not on a wrapper the template has to remember: the
   card used to be a `.gate` div inside the block, so the picker and the invitation
   never got it, and the flash messages — which base.html includes above the block —
   stayed full-width beside a centered card. Stated here, the messages, the heading
   and the panel share one edge.

   `minmax(0, …)` rather than a flat width so a phone narrower than the measure gets
   the viewport instead of an overflowing track. */
.body:not(:has(> .sidebar)) {
  grid-template-columns: minmax(0, 26rem);
  justify-content: center;
}
/* An entrance screen is short; without the extra air the card sticks to the header. */
.body:not(:has(> .sidebar)) .content { padding-block-start: var(--s-10); }

/* A working screen outside an organization — the platform administrator's overview of
   the use cases (BL-52). Without a sidebar the layout would center it into the 26rem
   gateway measure above and its table would scroll inside a column narrower than its
   own heading. The width belongs to the layout, not to a wrapper the template has to
   remember; the modifier matches the specificity of the gateway rule and wins by
   coming after it. */
.body.body--wide { grid-template-columns: minmax(0, 1fr); justify-content: normal; }
/* The same screen is also the exception to the upper bound of the content column
   (§5.2): the use-case overview, the form registry and the mapping diff are
   administrator tables that want every pixel. It is already the exception to the
   gateway measure above, so being the exception to --w-content too is the consistent
   answer — and since the bound sits on the layout with the sidebar, which this screen
   has none of, the exception is now the absence of a rule rather than an override. */
.body.body--wide .content { padding-block-start: var(--s-6); }

/* A screen outside an organization that is **read** rather than filled in — the help
   centre and one article of it (BL-655). It has no sidebar either, so until now it took
   the gateway measure above: on a 1440px window the signpost of forty articles was a
   416px ribbon 6437px tall, its lines broke at forty characters and the 65ch `.subtext`
   is bounded by never came into play. The gateway is the measure of a sign-in card —
   a form of two fields — and the privacy policy is right to wear it, because that is
   one screen of text behind a form; a catalogue read like a book is not.

   68rem is not a third number invented here: it is the reading measure `.band-inner`
   already states for the entrance page (§4.3) — the measure of a page where nothing
   sits beside navigation, as against `--w-content`, which is the measure of the working
   plane. Centering is not restated because the gateway rule above already carries it
   and still matches; only the track changes. Same specificity as that rule and stated
   after it, exactly as `.body--wide` is — and it outranks the two-column shell for the
   same reason, so it must never meet a sidebar either (BL-121, the named list in
   tests/web/test_shell.py). */
.body.body--reading { grid-template-columns: minmax(0, 68rem); }
/* The extra air of the gateway is for a card short enough to stick to the header. A
   text that scrolls for pages is not, so it takes the step the wide screens take. */
.body.body--reading .content { padding-block-start: var(--s-6); }

/* The offset is the height of the sticky header — never a copied number, so both move
   together (design-system.md §5). It is `--h-header-real`, what the header **measures**,
   and the token only as the fallback: `--h-header` is a `min-height` and the header wraps
   as soon as it runs out of room, at no breakpoint at all. Between 861 px — where this
   column stops being sticky — and the width where the head still fits on one line, the
   token said 65 px against a header of 113 px, and the first item of the column spent
   those 28 px underneath it (BL-647, measured on the detail of a building at 900 px).
   Above that width the two are the same number and nothing moves; without the script the
   fallback is the token, which is where this started. */
.sidebar {
  position: sticky;
  top: var(--h-header-real, var(--h-header));
  padding: var(--s-4) var(--s-3);
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  border-right: 1px solid var(--border);
  min-height: calc(100vh - var(--h-header-real, var(--h-header)));
}
@media (max-width: 860px) {
  .sidebar {
    position: static;
    min-height: 0;
    flex-direction: row;
    overflow-x: auto;
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  /* The one place `nowrap` is right: a horizontal strip that scrolls. */
  .sidebar .nav-item { white-space: nowrap; }
}

/* The label wraps; it is never truncated (§8 — PL and HU are longer than SK). Only
   the horizontal scroller below the breakpoint wants `nowrap`, and it has somewhere
   to scroll. Everywhere else — the vertical sidebar, the workspace tabs — a long
   label used to run straight off the screen. */
.nav-item {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  /* The click target of §6, which the item had never been given: --t-base in a 8px/8px
     padding is 37,7px, so every item of every navigation in the product was 6,3px under
     the 44px a coarse pointer is owed — measured, not estimated (BL-580). The token and
     not a number, so the item grows with the pointer the way §6 asks: 40px on a mouse,
     44px on a finger. It is stated on the item and not below a breakpoint because §6 says
     so in as many words — "rozhoduje vstup, ne šířka … `@media (pointer: coarse)`, nikdy
     `max-width`": a touch laptop is wide and is still driven by a thumb. */
  min-height: var(--h-control);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  color: var(--ink-soft);
  text-decoration: none;
}
.nav-item:hover { background: var(--surface-2); color: var(--ink); }
.nav-item[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 600;
}
/* A module that does not exist yet. It stays in the navigation so that the whole
   information architecture is visible, but it does not pretend to lead anywhere.
   The only remaining --ink-muted paint: --t-base text on --bg, 4.82:1 light and
   5.20:1 dark, so it holds AA and keeps the item visibly quieter (BL-59). */
.nav-item[aria-disabled="true"] {
  color: var(--ink-muted);
  cursor: default;
  pointer-events: none;
}
.nav-item .coming-soon {
  margin-left: auto;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow);
  /* Micro text never on --ink-muted (§6); not the accent either — a label on a
     disabled item must not look interactive. */
  color: var(--ink-soft);
}

/* The width of the content column comes from the layout above (.body), never from
   here: the measure is a property of the shell, and .content is the same element on the
   gateway, in the two-column shell and on the wide workspace, each of which wants a
   different one. Until the review of BL-81 the upper bound of §5.2 stood here with a
   margin-inline: auto, and centering a column inside the track left over by the
   sidebar put 960px of nothing between the navigation and the content it belongs to. */
.content {
  padding: var(--s-6) var(--s-6) var(--s-10);
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  min-width: 0;
}

/* --- Workspace -------------------------------------------------------------
   The two-column layout of the building detail and of the application wizard
   (design-system.md §4.3): the sub-navigation of the screen on the left, its
   sections on the right. Both screens used to borrow .body — the application
   shell — and force its columns with an inline style; an inline style outranks
   @media, so under 860 px they stayed two-column and a phone got a 230px column
   next to a sliver of content (BL-56). The width lives here, in the class (§5). */

.workspace {
  display: grid;
  /* `minmax(0, 1fr)` and never a bare `1fr` (§5.1): an automatically sized track takes its
     minimum from its content, so one long unbreakable word pushes the whole screen into a
     horizontal scroll before the ellipsis is ever asked for. The floor used to be stated
     only on `.workspace:has(> .whats-missing)` below, which was enough while the rail was
     always a child of the plane; since BL-580 it leaves for the head of the screen, the
     `:has()` stops matching and this is the rule that is left. */
  grid-template-columns: 230px minmax(0, 1fr);
  align-items: start;
  gap: var(--s-5);
}

/* Sticky under the application header — the offset is the token, never a copied
   number, so both move together (§5).

   `--h-sticky-top` is the bottom edge of everything already pinned under the header, and
   below 1180 px on the wizard that is the header plus the closed bar of `.whats-missing`
   (BL-580). Written by `web/js/rail.js`, because only the running page knows the height of
   a head that wraps. Without it the bar and the steps offset from the same line, land on
   top of each other and the bar paints over the steps: measured on 1100 × 900, a click on
   *Účely obnovy* lands on the bar's header and does not navigate.

   Unset — on a screen with no rail, which is every screen but the wizard's steps — the
   offset falls to `--h-header-real` and only then to the token, for the reason `.sidebar`
   states above: the token is a `min-height` and a wrapped header is taller than it says.
   Both columns of this grid have to lock onto the same line, so they take the same chain
   (BL-647). */
.workspace-nav {
  position: sticky;
  top: var(--h-sticky-top, var(--h-header-real, var(--h-header)));
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}

/* A named group of the sub-navigation — Evidencia / Agenda (design-system.md §4.3).
   Which section belongs where and in what order is domain data
   (`portfolio.rules.SECTION_GROUPS`); this class only puts the label above its items
   and never decides membership. */
.workspace-nav-group {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}

/* The label lines up with the *text* of the items below it, not with their box — the
   inline padding is .nav-item's. Micro caps in the accent (§6: --t-micro never on
   --ink-muted), which also says it is not a click target. */
.workspace-nav-group > .eyebrow { padding: 0 var(--s-3); }

/* Groups stand further apart than the items inside them; the spacing is the parent's
   gap, never a margin on a child (§5). Stated through :has() because .workspace-nav is
   shared with the wizard and the voting screen, which have no groups. */
.workspace-nav:has(> .workspace-nav-group) { gap: var(--s-5); }

/* One section = one panel; the column only spaces the panels out and never lets a
   wide table stretch the grid past the screen. */
.workspace-sections {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  min-width: 0;
}

@media (max-width: 860px) {
  .workspace { grid-template-columns: 1fr; }
  /* The sub-navigation becomes a wrapping row of tabs (§5.1). It must never simply
     disappear the way the prototype hides it — the sections of the building and the
     steps of the wizard would become unreachable on a phone. */
  .workspace-nav {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--s-2);
    padding-bottom: var(--s-3);
    border-bottom: 1px solid var(--border);
  }
  /* A grouped sub-navigation keeps its groups here too: what becomes a wrapping row of
     tabs is the GROUP, not the whole navigation. Left as a row, "Evidencia" and "Agenda"
     would each collapse into a narrow strip of stacked tabs and the label would stop
     reading as the heading of the row under it — §5.1 lets the sub-navigation change
     shape, never lose a section or its grouping. */
  .workspace-nav:has(> .workspace-nav-group) { flex-direction: column; gap: var(--s-4); }
  .workspace-nav-group { flex-direction: row; flex-wrap: wrap; align-items: center; }
  /* On the tab row the label is the first item of the line, so it takes the rhythm of
     the tabs beside it instead of the padding of a heading above them. */
  .workspace-nav-group > .eyebrow { padding: 0; }
  /* The steps of a vote as a wrapping row of tabs (RQ-22): each tab keeps its number,
     name, state and second line — the row only changes shape, it drops nothing (§5.1).
     The foot takes a row of its own so that cancelling is never read as a ninth tab. */
  .workspace-nav .nav-item--step { flex: 1 1 10rem; }
  .workspace-nav-foot { flex-basis: 100%; margin-top: 0; }
}

/* --- What is missing before submission --------------------------------------
   The permanent rail of the wizard (design.md §4, rule 2): a third column beside the
   steps and the sections, sticky under the header. It carries the CROSS-CUT — state
   and a way in — never the wording of the messages: a sticky block taller than the
   viewport has a bottom nobody can scroll to (BL-77 hit the same trap in the
   calculator's summary), so the sentences stay in the step where the fault is
   corrected (BL-141 moved them there out of a standing .readiness checklist that
   repeated this very list two columns to the left, on all nine steps). Two readers of
   one gate, with a difference that can be said in one sentence: the rail says WHERE to
   go, the step WHAT exactly is wrong.

   The column is stated only where the panel actually is (§5): .workspace is
   shared with the building's workspace, which has no rail, and an unconditional third
   track would leave an empty column on every section of every building. `:has()` reads
   the DOM that decides, so the condition is not copied into the template. 20rem and
   not a token: it is the dimension of one component, like the 230px of the
   sub-navigation, and a rail of short lines grows with the text.                */
.workspace:has(> .whats-missing) { grid-template-columns: 230px minmax(0, 1fr) 20rem; }

/* Sticky under the application header. The two sticky columns of one grid have to lock
   onto the same line, which is why this is not the calc() of .calculator-summary (§5) —
   and the line is the one .workspace-nav takes: `--h-header-real`, what the header
   measures, with the token only as the fallback (BL-721). It took the bare token while
   the sub-navigation did, and kept it when BL-647 moved the sub-navigation to the
   measurement, so above 1180px, where the header can still wrap, the head of the rail
   hid under it: 113px of header against an offset of 65px at 1200px of window.
   `--h-sticky-top`, the first link of the sub-navigation's chain, is not taken: it is the
   header *plus this panel as a bar*, and it is unset whenever the panel is a column. It
   rides on .panel: the surface, border, radius and column layout are the panel's, only
   the position is the rail's. */
.whats-missing {
  position: sticky;
  top: var(--h-header-real, var(--h-header));
}

/* The rows are **one list, separated once** — BL-175. They used to be direct children of
   .panel-body, which is a flex column with `gap: var(--s-4)`, so every hairline between
   two rows came with 16px of gap on top of it: thirteen gaps, 208px, separating a list
   that its own hairlines already separate. The wrapper is the idiom of .list-steps, for
   the same reason it exists there. */
.whats-missing-list { display: flex; flex-direction: column; }

/* One item = one thing that is missing: the dot carries the state, the name says what it
   is, the state stands in words at the right edge, and the whole row is the way there —
   a two-word link is a target on a hairline (§6).

   **One row, one line** (BL-175). It was a flex row with the state on a second line
   under the name, which is 57px × 14 rows, and the rail's bottom then had nowhere to be
   scrolled to (§5, the trap BL-77 hit in the calculator's summary). The grid is what
   makes the single line hold: the name track is `minmax(0, 1fr)` so it yields first, and
   the state track is `auto` so a word never breaks to save a name that could have
   wrapped instead. Written as a grid and not `margin-left: auto` on the hint for exactly
   that reason — the flex version squeezes nothing and grows the row instead.

   The state track only holds while the state is a **word**. A sentence in it collapses
   to min-content — one word per line — and takes the row to 341px, which is why the
   foot's message is a state and not a sentence (application.html). */
.whats-missing-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--h-control);
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--border);
  color: var(--ink);
  text-decoration: none;
}
.whats-missing-item:last-child { border-bottom: 0; }
/* Only the clickable variant reacts to the mouse: the item of a screen the member may
   not open is not a link (§6, the same rule as .list-row--link). */
a.whats-missing-item:hover { background: var(--surface-2); }
/* The state reads back from the right edge, against the name it belongs to. */
.whats-missing-item .hint { text-align: right; }

/* The ratio is the panel's headline and rides in the header since BL-175, where a panel
   writes what it is worth — .readiness-head with its track was 58px of the one column
   that has none to spare, and the catalogue names the head as the part that gives way
   (§4.3). The step is the heading's, the same one the head gave it (§1.2). */
.whats-missing > .panel-header .mono { font-size: var(--t-md); }

/* §5.1: below 1180 px the rail stops being a column. What it does instead has two
   answers, and this block writes both of them.

   Without a script it drops under the content and stops being sticky — a block the page
   has already scrolled past has nothing left to stick to. Its place in the grid has to be
   said: auto-placement would put it in the second row of the **navigation's** 230px track
   and render the panel 230px wide.

   With `web/js/rail.js` it becomes the bar below (BL-580), and `--rail-bar` is how the
   script is told: the breakpoint is a decision of §5.1 and stays here in the stylesheet,
   so the script asks which side of it the window is on rather than repeating 1180 as a
   fourth home for the number. */
@media (max-width: 1180px) {
  .workspace:has(> .whats-missing) { grid-template-columns: 230px minmax(0, 1fr); }
  .whats-missing { position: static; grid-column: 1 / -1; --rail-bar: 1; }
}
/* Under 860 px the whole workspace is one column (§5.1). Restated for the `:has()`
   selector because its specificity (0,2,0) outranks the unconditional one-column rule
   above. */
@media (max-width: 860px) {
  .workspace:has(> .whats-missing) { grid-template-columns: minmax(0, 1fr); }
}

/* --- The rail as a bar (BL-580) --------------------------------------------
   Below the breakpoint the panel is moved by `web/js/rail.js` to the head of `.content`
   and marked; here it becomes a bar under the header, folded to its own headline —
   the heading and the ratio — until the reader asks for the list.

   Until BL-580 the rule here was the opposite one, and it was written down twice: the
   rail "never becomes a disclosure — what is missing before submission is the one thing
   that must not need opening" (§4.3). That argument was made for a column standing beside
   the work, and it holds there: **above the breakpoint nothing folds and nothing may.**
   Below it the same sentence was buying nothing, because the panel it defended stood
   5 496 px under the header of the step Prílohy on a 390 px phone (measured, UX-13) —
   open, and unreachable. A closed bar with the number in its head is what an open panel
   nobody scrolls to was pretending to be.

   The panel is a **flow** element here, not a fixed one: it reserves its own height,
   never has to be told the width of the sidebar or of the open help panel, and its place
   in the DOM is its place on the screen (§5.1). The offset is `--h-header-real`, written
   by the script, because `--h-header` is a `min-height` and the header wraps as soon as it
   runs out of room, at no breakpoint at all: 65px of header at 950px of window, 113px at
   900px, 134px at 390px, against a token that says 65px throughout. The fallback keeps the
   rule truthful above ~950px, where the two are the same number, and without the script,
   where nothing has moved anyway. */
.whats-missing[data-rail="bar"] {
  position: sticky;
  top: var(--h-header-real, var(--h-header));
  z-index: 10;
  /* Opened, fourteen rows of --h-control are 714px against the 710px a 390px phone has
     under its header: the bar has to stop at the viewport and let the list scroll inside
     it, or its bottom is where nobody can reach — the trap BL-77 hit in the calculator's
     summary and the whole reason the rail carries states and not sentences.

     Measured from `--rail-top`, which the script writes at the moment the bar opens, not
     from the sticky offset: at the top of the page the bar starts below the header, the
     tab strip of the sidebar and the padding of the screen, so the offset would promise
     97px the bar does not have there. The fallback is the offset, for the closed bar and
     for a browser between the script and the first fold, where the promise is not being
     cashed. `dvh` first for the phone whose toolbars come and go; `vh` under it for
     anything that has not heard of `dvh` yet. */
  max-height: calc(100vh - var(--rail-top, var(--h-header-real, var(--h-header))) - var(--s-5));
  max-height: calc(100dvh - var(--rail-top, var(--h-header-real, var(--h-header))) - var(--s-5));
}
/* The list is what scrolls, never the whole bar: the head carries the number and has to
   stay legible while the reader walks the rows. `min-height: 0` because a flex item's
   floor is its content, so without it the body would grow past the bar instead of
   scrolling inside it (§5.2, the same rule that makes a wide table scroll in its frame). */
.whats-missing[data-rail="bar"] > .panel-body {
  overflow-y: auto;
  min-height: 0;
}
/* Folded is a state of the panel, and a state is an attribute (§4.1) — the very one the
   toggle carries for the reader, so there is no second bookkeeping to drift out of step.

   **Not `hidden` on the body.** That attribute means "not here yet" and the base layer
   honours it with `!important` (§4.1, BL-351), which is right for a thing that is not part
   of the document and wrong for a list that is merely folded away on a narrow screen: on
   paper the fold has no meaning and the list has to come back, and an author `!important`
   in the base layer cannot be undone by `@media print` without a second one. */
.whats-missing[data-rail="bar"]:has(.whats-missing-toggle[aria-expanded="false"]) > .panel-body {
  display: none;
}
/* Folded, the head is the whole panel and the hairline under it would be a border on
   nothing — the same retreat `.panel--collapsible` makes when it closes. */
.whats-missing[data-rail="bar"]:has(.whats-missing-toggle[aria-expanded="false"]) > .panel-header {
  border-bottom-width: 0;
}

/* The fold's control. It stands `hidden` in the template and the script unhides it where
   the panel is a bar: without the script the rail is the panel it has always been, and a
   control that folds nothing must not be drawn (§6 — the same reason `.explainer-close`
   waits for `help.js` and `aria-pressed` for `theme.js`). Its name is the heading beside
   it (`aria-labelledby`), so the mark it carries is drawn and not written — a chevron
   typed as text is read out as a character, exactly as `.panel--collapsible` explains. */
.whats-missing-toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--h-control);
  min-height: var(--h-control);
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}
.whats-missing-toggle::after {
  content: "";
  width: 0.5em;
  height: 0.5em;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-25%) rotate(45deg);
}
.whats-missing-toggle[aria-expanded="true"]::after { transform: translateY(25%) rotate(225deg); }
@media (prefers-reduced-motion: no-preference) {
  .whats-missing-toggle::after { transition: transform 120ms ease; }
}

/* --- Calculator plane ------------------------------------------------------
   The informative calculator: the input panels on the left, the summary of the
   result on the right and sticky under the header. It exists because these numbers
   are read *while* they are typed — two panels lower the user corrects a cost
   without seeing what the correction did, which is the blindness the guardrails
   are there to end (design-system.md §4.3). It is not .workspace: that one
   carries the sub-navigation of a screen on the left, here the left column is the
   content itself.                                                            */

.calculator {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 26rem;
  align-items: start;
  gap: var(--s-6);
}
/* `minmax(0, 1fr)` above and `min-width: 0` here are one rule stated twice: a grid
   item's automatic minimum is its content, so the table of purposes would widen the
   column instead of scrolling inside its own frame (§5.1). */
.calculator-inputs { display: flex; flex-direction: column; gap: var(--s-6); min-width: 0; }
/* The save bar of the calculator stands **outside** a panel: what it saves is the whole
   form, not one section of it, and a panel around it would put a fourth, unnumbered
   card into a column of three numbered ones — the contradiction BL-128 removed. Out
   there it has no frame to borrow, so it draws its own: the shorthand replaces the
   top hairline the bar wears as a panel's footer, and the radius closes all four
   corners instead of the bottom two. */
.calculator-inputs > .save-bar {
  border: 1px solid var(--border);
  border-radius: var(--r);
}
/* The offset is what the header measures plus the gap, for the reason `.whats-missing`
   states above: the token is a `min-height`, and at 1200px of window the header wraps to
   113px while the token says 65, so the head of the summary sat 32px under it (BL-721). */
.calculator-summary {
  position: sticky;
  top: calc(var(--h-header-real, var(--h-header)) + var(--s-4));
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  min-width: 0;
}
/* A guardrail card standing directly in the summary needs a plane of its own: the
   column sits on --bg and .guard is nothing but a left bar. A summary that wraps its
   cards in a .panel is untouched — the cards are then no longer its children. */
.calculator-summary > .guard {
  background: var(--surface);
  border-radius: 0 var(--r) var(--r) 0;
  padding: var(--s-3) var(--s-4);
  box-shadow: var(--shadow);
}
/* The summary drops under the inputs before either column gets too narrow to read —
   26rem of summary beside a five-column table of purposes is the floor. Below the
   break it stops being sticky: a block the page has already scrolled past has
   nothing left to stick to. */
@media (max-width: 1180px) {
  .calculator { grid-template-columns: minmax(0, 1fr); }
  .calculator-summary { position: static; }
}

/* --- Screen typography ----------------------------------------------------- */

.eyebrow {
  font-size: var(--t-micro);
  text-transform: uppercase;
  /* The identity's tracking for a section eyebrow, from tokens — a component that
     writes the number by hand is that value's second home (§1). */
  letter-spacing: var(--ls-eyebrow);
  /* The accent is prescribed by §3: an identity element, and the only shade that
     holds contrast at --t-micro in both themes. */
  color: var(--accent);
}
.page-h {
  font-family: var(--serif);
  font-size: var(--t-lg);
  font-weight: 600;
  margin: 0;
  text-wrap: balance;
}
.section-h {
  font-size: var(--t-md);
  font-weight: 600;
  margin: 0;
  text-wrap: balance;
}
.subtext { color: var(--ink-soft); max-width: 65ch; }

.screen-header { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-5); align-items: baseline; }
/* The screen heading is where the longest names land — a building's, an
   organization's. `overflow-wrap` on its own cannot save them: a flex item's
   automatic minimum is its content, so an unbroken IBAN or e-mail widened the group
   before the text was ever offered a chance to break. */
.screen-header > * { min-width: 0; }

/* --- Panel and form section ------------------------------------------------ */

/* `min-width: 0`: a panel is a flex item, and a flex item's automatic minimum size
   is its content — so a wide table inside stretched the panel past the screen
   instead of scrolling in its own frame. */
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.panel-header {
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  align-items: baseline;
}
/* The sentence that says what follows from the panel takes a line of its own: in the
   header's wrap it would otherwise be squeezed between the heading and the pill. It
   keeps the measure of prose (§3) and loses the paragraph's default margin, which the
   header's baseline row does not expect. */
.panel-header .hint { flex-basis: 100%; margin: 0; max-width: 65ch; }
.panel-body { padding: var(--s-5); display: flex; flex-direction: column; gap: var(--s-4); }

/* Filter over a listing that asks a single question — `components/filters.html` (#581).
   Not a `.panel`: a heading saying *Filtre* over one input that already carries its own
   label, plus a save bar under it, cost 260 px before the first row of the list they
   narrow. The row is the same GET form with the same controls in the same order (§4.3),
   laid out on one line.

   `align-items: center`, the same answer `.field-action` gives and not the `end` of
   `.picker-search`: `.field` is a column of label over widget over `.hint`, and both
   filters that take this shape carry a hint — so the bottom edge is the hint's, two
   lines below the input, and buttons aligned to it would sit under the field rather than
   beside it. Centred against the whole block, they meet the input, which is the control
   they act on. `flex-wrap` lets the pair drop under the field at the narrow widths of
   §5.1 instead of squeezing the input to nothing — hence the growing field with a floor
   of its own, and buttons that never shrink.

   No plane, no border, no radius. The row is a control above a table, and a second frame
   around it would read as a panel that lost its heading. */
.filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--s-4);
}
.filter-row > .field { flex: 1 1 min(22rem, 100%); }
.filter-row > .actions { flex: 0 0 auto; }

/* Pager under a long table — `components/pagination.html` (RQ-13, BL-598).
   Its own strip and not a `.save-bar`: that bar is the foot of a panel and says the
   section is saved on its own, while this is navigation over what the panel shows. It
   therefore sits **inside** `.table-frame`'s panel, under the table, with a hairline
   above it and the recessed plane the foot of a panel has.

   The sentence and the numbers are two rows on a narrow screen and one row from 640 px
   up: "Zobrazené 51–100 z 300" is the answer to "am I seeing everything", and squeezing
   it beside twelve digits is how it stops being read.

   The pages are `.btn--quiet` rather than a shape of their own — a page is a link and
   the application already has one link that looks like a control. What the current page
   adds is `aria-current="page"` and the accent pair, never the pair alone (§6). */
.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2) var(--s-4);
  padding: var(--s-3) var(--s-5);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: 0 0 var(--r) var(--r);
}
.pager > .hint { margin: 0; }
.pager-pages { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1); }
.pager-page { min-width: var(--h-control); justify-content: center; font-variant-numeric: tabular-nums; }
.pager-page--on {
  background: var(--accent-soft);
  color: var(--accent-ink);
  border-color: var(--accent-ink);
}
.pager-gap { color: var(--ink-soft); padding-inline: var(--s-1); }

/* A **modifier of .panel, never a second component** (§4.1): a destructive panel is a
   panel read differently, not a different anatomy. The border and the header carry
   --bad; the body deliberately does not, because a surface tinted throughout would read
   as an error state of the whole screen — and this is not an error. It is the one place
   on the screen where something irreversible can be done, and it says so before the
   click. The heading pair is the one .pill--bad already uses (--bad on --bad-soft). */
.panel--dangerous { border-color: var(--bad); }
.panel--dangerous > .panel-header {
  border-bottom-color: var(--bad);
  background: var(--bad-soft);
  border-radius: var(--r) var(--r) 0 0;
}
.panel--dangerous > .panel-header .section-h { color: var(--bad); }

/* A panel that folds away — again a **modifier of .panel, never a second component**
   (§4.1): the anatomy is unchanged, `<details>` merely carries it and the header is
   its `<summary>`. Native, so it opens with no JavaScript at all, which is the whole
   reason the reference table of the call may be closed by default.

   It is for a panel the reader typically does **not** read — reference beside work,
   uploaded attachments beside the ones still missing. Never over anything that blocks
   submission: closing a blocker hides it, and design.md §5 forbids a blockage without
   a way out, not merely a blockage.

   The affordance is drawn and not written: a glyph in `content` would be spoken by the
   very screen reader that already announces the open state of <details>. A square with
   two borders turned 45° is the chevron; the open state turns it up rather than
   swapping a second shape in.

   Dropping the native triangle takes **two** declarations, not one, because the engines
   draw it in two different places. Blink and Gecko give the <summary> `display:
   list-item` and the triangle is its `::marker`, so `.panel-header`'s own `display:
   flex` already removes it and `list-style: none` is what says so out loud; WebKit draws
   it as `::-webkit-details-marker`, a pseudo-element child no `display` on the summary
   reaches. */
.panel--collapsible > summary { cursor: pointer; list-style: none; }
.panel--collapsible > summary::-webkit-details-marker { display: none; }
/* The disclosure arrow, drawn rather than written: a glyph in `content` would be read
   out by a screen reader on top of the open/closed state `<details>` announces itself.
   **Three consumers, one drawing** — the collapsible panel, the framed switcher of saved
   calculations (#217) and the platform control the header collapses into below the
   breakpoint (BL-723). A second copy would drift the first time one of them changed its
   ink; the third consumer is the reason this list is a list and not two selectors. */
.panel--collapsible > summary::after,
.calculation-switcher > summary::after,
.platform-switcher > summary::after {
  content: "";
  align-self: center;
  margin-left: auto;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--ink-soft);
  border-bottom: 2px solid var(--ink-soft);
  transform: rotate(45deg);
}
.panel--collapsible[open] > summary::after,
.calculation-switcher[open] > summary::after,
.platform-switcher[open] > summary::after { transform: rotate(-135deg); }
/* Closed, the header **is** the bottom edge of the panel, and its own hairline right
   above the panel's border read as a double line. */
.panel--collapsible:not([open]) > summary { border-bottom-width: 0; }

/* --- The fold a panel gets from its width (BL-646) --------------------------
   `.panel--collapsible` above is the domain's answer to "may this be closed" and it holds
   at every width. This is a different question — "is there room to hold it open" — asked
   of a panel the domain did **not** make collapsible, and answered only below 860px, where
   §5.1 puts the whole workspace in one column.

   Measured on the step Prílohy over `seed_dev` at 390px: 5 526px, 6,6 viewports, of which
   three open groups are 3 888px. (4 998px of it is what the step measured before this
   branch; BL-654 added 528 by giving eleven rows a third line.) The two groups the domain already folds carry the same
   kind of content — eight rows — in 138px. The length is not in what a group holds but in
   whether it stands open.

   **Not a `<details>`.** The state is only true below the breakpoint, and a `<details>` in
   the markup folds at every width, script or no script — the reason BL-580 gave when the
   rail needed the same thing, and the reason the desktop group would otherwise become
   foldable by the same markup. So the control is a button, the state is `aria-expanded` on
   it — the very attribute the reader is given, so there is no second bookkeeping to drift
   out of step (§4.1) — and the stylesheet reads it.

   `--panel-fold` is how the script is told which side of the breakpoint the window is on:
   §5.1 owns 860 and a fourth copy of it in a script is a second home for a decision that
   has one. The same instrument `--rail-bar` is for the rail. */
@media (max-width: 860px) {
  .panel:has(> .panel-header > .panel-fold) { --panel-fold: 1; }
}
.panel:has(> .panel-header > .panel-fold[aria-expanded="false"]) > .panel-body {
  display: none;
}
/* Folded, the head is the whole panel and the hairline under it would be a border on
   nothing — the same retreat `.panel--collapsible` makes when it closes. */
.panel:has(> .panel-header > .panel-fold[aria-expanded="false"]) > .panel-header {
  border-bottom-width: 0;
}
/* The fold's control. It stands `hidden` in the template and the script unhides it below
   the breakpoint: without the script the panel is the open one it has always been, and a
   control that folds nothing must not be drawn (§6). Its name is the heading beside it
   (`aria-labelledby`), so the mark it carries is drawn and not written — a chevron typed
   as text is read out as a character, exactly as `.panel--collapsible` explains. */
.panel-fold {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--h-control);
  min-height: var(--h-control);
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}
.panel-fold::after {
  content: "";
  width: 0.5em;
  height: 0.5em;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-25%) rotate(45deg);
}
.panel-fold[aria-expanded="true"]::after { transform: translateY(25%) rotate(225deg); }
@media (prefers-reduced-motion: no-preference) {
  .panel-fold::after { transition: transform 120ms ease; }
}

/* The ordinal of a panel that answers one question of a series (design-system.md §4.3).
   The number is the **reading order**, not a mandatory sequence — the calculator computes
   from the purposes and the maturity alone. Until BL-128 a sentence above the panels said
   so; now the column says it by its shape: exactly three numbered panels and nothing else,
   the name field riding in the save bar beneath them. The circle claims no state either:
   --accent is brand and order, never a state (§1.1), which is why it needs no second
   modality.

   The digit is --accent-ink and not --accent on this plane: both hold AA (5.00 light /
   4.64 dark for --accent), but 11.5px bold is not where a ratio without reserve is
   trusted (§6), and --accent-ink on --accent-soft is the pair the active navigation item
   already uses (10.45 / 10.26:1).

   min-width and not width: a two-digit ordinal would overflow a fixed circle, and 999px
   turns it into a stadium instead of clipping it. The size is in rem because a badge
   grows with its text — the same reason --w-field-* are (§1.4). */
.step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  min-width: 1.5rem;
  height: 1.5rem;
  padding-inline: var(--s-1);
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-family: var(--mono);
  font-size: var(--t-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Section save bar — confirms that the section is saved on its own (§4). */
.save-bar {
  padding: var(--s-3) var(--s-5);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: 0 0 var(--r) var(--r);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
}
/* Small text on a recessed plane: --ink-soft, never --ink-muted (§6). */
.save-bar .time { font-size: var(--t-xs); color: var(--ink-soft); }
/* Only the offset is contextual here — the group itself is a block of its own. */
.save-bar .actions { margin-left: auto; }
/* A panel merged from two records of one section carries a bar per record (§4.3), and
   a bar that is not the panel's foot must not draw one: the bottom radius is the
   panel's own corner, so mid-panel the --surface of the panel showed through both of
   them and the strip read as the panel ending. The hairline closes it against the
   record that follows. */
.panel > form:not(:last-child) > .save-bar {
  border-radius: 0;
  border-bottom: 1px solid var(--border);
}

/* Selection bar of a list — the act over what is ticked in the table above (§4.3).
   Not a modifier of .save-bar: the anatomy is the same, but a save bar is the foot of
   a panel and does not move, while a selection over a hundred rows is made at the
   bottom of the screen with that foot long out of view. A modifier would lie about
   what the component does. */
.selection-bar {
  position: sticky;
  bottom: 0;
  z-index: 2;
  padding: var(--s-3) var(--s-5);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: 0 0 var(--r) var(--r);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  box-shadow: var(--shadow);
}
.selection-bar .time { font-size: var(--t-xs); color: var(--ink-soft); }
.selection-bar .actions { margin-left: auto; }

.field { display: flex; flex-direction: column; gap: var(--s-1); }
.field > label { font-size: var(--t-sm); color: var(--ink-soft); }
/* The label of a field lives in a wrapper since BL-533, so that the explainer can stand
   beside it. A descendant combinator does not reach through a real element, so the rule
   above has to be repeated for the wrapped shape — the alternative was wrapping only the
   two fields that carry a mark, i.e. two shapes of the same pattern. The checkbox branch
   of `components/field.html` stays unwrapped and keeps the rule above. */
.field > .field-label > label { font-size: var(--t-sm); color: var(--ink-soft); }
/* The floor is on **every** label row and not only where a mark stands (BL-640): the
   explainer's circle is `--s-5`, a hair more than the 19.4px line box of a 12.5px label,
   and a hair is all it takes to put the inputs of one `.grid` row at two heights. The
   floor makes the two shapes measure the same rather than nearly the same, so the row
   stays level whichever of its fields the fund defines in its own words. */
.field-label { display: flex; align-items: center; gap: var(--s-1); min-height: var(--s-5); }
/* The same wrapper for the checkbox branch (BL-620). It exists so the explainer can stand
   **beside** the `<label>` instead of inside it — the label wraps the box and is the click
   target, and §6 does not allow another control in it. `align-items: center` and not
   `baseline`: the mark is a circle, the label one line of text, and a baseline would hang
   the circle below the row. */
.field-check { display: flex; align-items: center; gap: var(--s-1); min-height: var(--s-5); }
.field-check > label { flex: 0 1 auto; }
/* A field is a user interface component, so its boundary falls under WCAG 1.4.11
   (3:1) — hence --border-strong, not the decorative --border, plus a plane of its
   own so it never dissolves into the --surface of the panel (BL-59). */
.field input:not([type="checkbox"]):not([type="radio"]), .field select, .field textarea {
  font: inherit;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  padding: var(--s-2) var(--s-3);
  width: 100%;
  /* The click target is asserted here, not left to the padding. The reset above
     floors the button, the select and the submit, so until BL-81 the text field was
     the one control whose height was a coincidence — and a narrowed field (below)
     would have dropped under the target in both dimensions at once (§6). */
  min-height: var(--h-control);
}

/* --- The size of a field -----------------------------------------------------
   The width of a field is part of what it says (§4.3, Velikost pole): an input for two
   digits as wide as one for a 120-character name looks like it expects a sentence.
   Every field used to take the whole 15rem track of .grid, because `width: 100%`
   above was the only statement about width in the system.

   max-width over width:100%, never a fixed width: a ceiling can never make a field
   overflow its track, so a track narrower than the size — a one-column .grid on a
   phone — gets a field the width of the track and not a field sticking out of the
   panel. The narrowing is a visual signal and never a limit on the input either: a
   value longer than expected is still typed and read, the field scrolls within itself.
   The click target does not shrink with the width; --h-control above floors it and §6
   grows it on a coarse pointer. The class rides on the input itself (that is what a looped
   {% for field in form %} can be given), not on the .field wrapper. Inputs in
   .purposes get no size: their width is the table column's and a second bound would
   argue with it. */
.field .field-number   { max-width: var(--w-field-number); }
.field .field-amount { max-width: var(--w-field-amount); }
.field .field-code     { max-width: var(--w-field-code); }

/* --- A value that came from somewhere else -----------------------------------
   The carrier of "derived is not edited" (design.md §4.3). Every field already stands
   on --surface-2 (BL-59), so what says "not yours to correct" is the dashed boundary.
   Only the STYLE of the border changes: a field is still a component boundary under
   WCAG 1.4.11, so it keeps --border-strong and its contrast (§6), and the text keeps
   --ink because the whole point is that it is READ.

   `readonly`, NEVER `disabled` — a disabled field leaves the tab order and a screen
   reader walks past it, so the value the user is meant to read would disappear for
   exactly the user who cannot see it (§6). The attribute is set together with this
   class in web/forms/applications.py (`DerivedField`); `readonly` is advisory, so the server
   ignores what the browser sends back instead of trusting it.

   The selector repeats the shape of the base field rule above on purpose: `:not()`
   carries the specificity of its argument, so that rule counts (0,3,1) and a plain
   `.field .field--derived` (0,2,0) would lose to the `border` shorthand it declares.
   The class rides on the widget, like the sizes above — a form rendered by a loop
   cannot be handed a class per field. A derived `select` or `textarea` does not exist
   yet; the selector grows with the first one instead of guessing now.           */
.field input.field--derived:not([type="checkbox"]):not([type="radio"]) { border-style: dashed; }

/* A checkbox is not a text field. `width: 100%` used to catch these too, so every
   sworn declaration rendered as a 732 × 13px bar — the shape of an input, the target
   of a hairline. The box keeps its intrinsic size and the whole label row becomes the
   target, which is what §6 sizes with --h-control. Clicking the text already toggles
   the box; this only makes the area worth aiming at. */
.field > label:has(> input[type="checkbox"]), .field > label:has(> input[type="radio"]),
.group label:has(> input[type="checkbox"]), .group label:has(> input[type="radio"]),
.purposes th label:has(> input[type="checkbox"]) {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--h-control);
  cursor: pointer;
}
.field input[type="checkbox"], .field input[type="radio"],
.group input[type="checkbox"], .group input[type="radio"],
.purposes input[type="checkbox"] {
  flex: none;
  accent-color: var(--accent);
}
.hint { font-size: var(--t-xs); color: var(--ink-soft); }
/* An error says what is wrong and what to do (§7) — never just a coloured border. */
.error { font-size: var(--t-xs); color: var(--bad); }
.field input[aria-invalid="true"], .field select[aria-invalid="true"], .field textarea[aria-invalid="true"] { border-color: var(--bad); }

/* A group of controls that answer one question together (a checkbox list). Only a
   <fieldset> carries the group's name to a screen reader (§6) — the individual
   checkboxes have labels of their own, the group has none. The browser's frame,
   margin and padding are not part of the design system, hence the reset; the
   min-inline-size keeps a fieldset from refusing to shrink inside a grid. It is not
   `.field`: that class dresses every input it contains as a text field, which turns a
   checkbox into a full-width box. */
.group {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  border: 0;
  margin: 0;
  padding: 0;
  min-inline-size: 0;
}
/* A group whose options each say something of their own — the three forms of security
   and the attachments each of them adds to the file (BL-146). A modifier and not a
   second component (§4.1): the fieldset, the nested labels and the name from
   aria-labelledby are unchanged, only the distance between the options is. The base
   gap is a hairline because the base group is a column of single-line checkboxes; an
   option two lines tall needs the gap to separate options rather than the lines
   inside one, otherwise the sentence of the first choice reads as the second's. */
.group--explained { gap: var(--s-3); }
/* The option and the sentence about it are a column with a `gap` (§5), never a
   paragraph carrying the browser's margin: `.hint` sets the size of the type and
   nothing else, so without this the distance between a choice and its price came from
   the UA stylesheet and stood outside the --s-* scale. */
.group--explained > div { display: flex; flex-direction: column; gap: var(--s-1); }
.group--explained > div > p { margin: 0; }

/* `min(15rem, 100%)` and not a bare `15rem`: a track floor wider than the box it sits
   in overflows it, and at 320px the box really is narrower — 320 less the padding of
   .content, the panel border and the padding of .panel-body leaves about 230px for a
   240px floor, so the single column stuck out and the **page** scrolled sideways,
   which §5 forbids. `min()` lets the last column give up its floor instead of the
   viewport giving up its width; above the floor nothing changes, so the number of
   columns is still the browser's answer and never a fixed count (§4.3). Not a fourth
   breakpoint (§5.2) — the same rule holds at every width. */
.grid { display: grid; gap: var(--s-4); grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); }

/* A fixed set of tiles read as one block — the six indicators of the dashboard (#560).
   `auto-fit` hands them four tracks on a 1440px display, so the second row carries two
   tiles and two holes. A wider floor is no answer: auto-fit gives whatever fits, so the
   same six orphan two tiles at four tracks and one at five, and the width where that
   stops is a number §5.1 does not have. The count is stated instead, at the breakpoints
   the document already gives — three columns, two under 1080px, one under 860px.
   `minmax(0, 1fr)` and never a bare `1fr`: an auto track takes its minimum from its
   content, so one long label would push the row past the panel (§5.2). Only a grid whose
   number of children is fixed and divisible may wear it. */
.grid--thirds { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1080px) { .grid--thirds { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 860px) { .grid--thirds { grid-template-columns: minmax(0, 1fr); } }

/* --- List of rows inside a panel -------------------------------------------
   A pending invitation, a choice of organization. These rows used to wear
   .nav-item, which belongs to the sidebar: it lit up under the mouse where
   nothing was clickable (§6), muted the very text that mattered and never wrapped,
   so a long e-mail overflowed the panel instead of breaking (§4.1).            */

.list { display: flex; flex-direction: column; gap: var(--s-1); }
/* The row keeps the plane of the panel: the neutral pill is drawn on --surface-2 and
   would dissolve into a row painted with the same shade. */
/* Wrapping, because a control in the row is not text and does not shrink: a file input
   beside a name and a sentence overflowed the panel instead of moving to a second line
   (BL-148). Text alone never reaches the break — `overflow-wrap` above lets it shrink
   first — so the older rows keep the single line they had. The step that asked for it
   has since left the base row altogether and is drawn as `.rowcard` below (BL-454,
   BL-476); what is left here holds for the rows that carry one control, which is what
   the base row is for. */
.list-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  color: var(--ink);
  overflow-wrap: anywhere;
}
/* Whatever the row says about itself is pushed away from the text: in a row of text
   only, that puts the pill on the right edge. In a row that carries a control the
   auto margin moves the pill **and everything after it** — the pill then sits just
   before the control and the right edge is held by the control itself, which is what
   the Prílohy step looks like (BL-148). Both are the same rule: the pill leaves the
   sentence, it does not claim the edge. */
.list-row .pill { margin-left: auto; }
/* The clickable variant — and only it reacts to the mouse. */
.list-row--link { text-decoration: none; }
.list-row--link:hover { background: var(--surface-2); }
/* The register variant: caption, sentence, control — one shape for every property of
   one record (BL-139). A grid and not the flex of the base row, because "one shape"
   is a claim about alignment **across siblings**: every .list-row is its own
   container, so a `max-content` caption comes out a different width in each row and
   the three rows drift apart again — which is the sequence of shapes this replaced.
   Only a fixed track shares geometry between siblings. Declared after --link on
   purpose: same specificity, so order decides (the reason written down at
   .guard--ok/--bad). The two never meet — --link makes the whole row an anchor, and a
   <select> or a <button> inside an anchor is a nested interactive element (§6), not a
   variant. */
.list-row--register {
  display: grid;
  grid-template-columns: minmax(0, 9rem) minmax(0, 1fr) minmax(0, auto);
  gap: var(--s-2) var(--s-4);
  align-items: center;
}
/* One column below the narrow break (§5.2): three tracks on a phone leave the
   sentence a word wide. */
@media (max-width: 860px) { .list-row--register { grid-template-columns: minmax(0, 1fr); } }

/* --- The collapsible row of an attachment (BL-476) --------------------------
   A `<details>` on a bordered card. The header is **one line of fixed height** — a name
   cut with an ellipsis, the number of the fund's register, the chip that says the state
   — and the body is where everything the row has to say gets room to say it in words.

   It replaces `.list-row--worksheet`, the three-track grid BL-454 built when RQ-14 put
   six new things on a row drawn for a name, a sentence, a pill and one control. The grid
   held; what it could not hold was the third track filling up. Seven equal outlined
   buttons is not a layout problem — nothing among seven equal offers says which one to
   take — so the header now carries the **one** move the domain calls next, and folding is
   what buys the rest of them room for words. Icons and a "⋯" menu were both weighed and
   both dropped: a menu hides actions behind a click in a component this repo does not
   have, and an icon without a word is a riddle for the people who read this step.

   The chevron is drawn and not written, for the reason `.panel--collapsible` gives just
   above: a glyph in `content` is read out on top of the state `<details>` announces for
   itself. Both declarations are needed to be rid of the native marker — `list-style` for
   Blink and Gecko, `::-webkit-details-marker` for WebKit — and `:focus-visible` is
   handled globally, because `<summary>` is focusable by itself.                */
.rowcard {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  /* The row asks **its own width**, not the window's, and it is the one component in
     this stylesheet that has to. BL-457 wrote the reason down and could not act on it:
     the column this row stands in is not predicted by the viewport — at a 1181px window
     it is narrower than at 900px, because the rail comes back. Measured on the real
     step: the card is 552px at a 1440px window, 352px at 900px and 710px at 800px. A
     `@media` flip is therefore keyed to the wrong number, and BL-457's other instrument
     — a cap in `%` — cannot help either, because a share of 352px is still less than
     the lead needs. This is not a fourth breakpoint (§5.1): a breakpoint is a decision
     about the page, and this is a component asking whether it has room. */
  container-type: inline-size;
  /* The height of one line of the header, named once because two rules have to agree on
     it: the header reserves the second line with it and the lead is placed at it. Read
     off `--h-control`, so a coarse pointer moves both together. */
  --rowcard-line: calc(var(--h-control) + 2 * var(--s-1));
}
.rowcard-summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  /* Never wraps, at any width, and that is the component twice over. A header that grows
     with the name stops being a state to scan down — and it also stops having a height,
     which is what the lead's `top` is measured against: the second line can only be
     given an address if the first one has one (#479). What gives way is the name, below.
     A wrapping header was tried and lost both ways: with the default flex basis it stood
     the chevron alone above the name, and with a zero basis it squeezed a long name into
     a column six lines tall. */
  flex-wrap: nowrap;
  gap: var(--s-2) var(--s-3);
  padding: var(--s-1) var(--s-3);
  min-height: var(--rowcard-line);
}
.rowcard-summary::-webkit-details-marker { display: none; }
.rowcard-summary:hover { background: var(--surface-2); border-radius: var(--r-sm); }
/* `min-width: 0`, because the automatic minimum size of a flex item is its content:
   without it the name pushes the header wider instead of being cut, and the ellipsis
   never runs. The full wording stands in the body, so cutting loses nothing. */
.rowcard-name {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rowcard-summary .pill,
.rowcard-state { flex: none; }
/* The state of the row is one answer that sometimes needs two words, so the chips travel
   as one thing — and on a narrow card they have to, because there the header is a grid
   and a cell holds one item. */
.rowcard-state { display: flex; align-items: center; gap: var(--s-2); min-width: 0; }
.rowcard-chevron {
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--ink-soft);
  border-bottom: 2px solid var(--ink-soft);
  transform: rotate(-45deg);
  flex: none;
}
details[open] > .rowcard-summary .rowcard-chevron { transform: rotate(45deg); }
/* The one control of the header is a **sibling** of the `<details>`: a control nested in
   a summary also runs the summary's activation behaviour on a click, so the segmented
   pair could not be operated at all — the row toggled instead of answering. That is what
   it buys and this is what it costs: nothing encloses the lead, so its place has to be
   given to it rather than fallen into.

   **It is always placed, and the only question is which line of the header it is placed
   on** (#479). Left in the flow it lands after the whole `<details>` — under the body of
   an open row, past the quiet actions and past the destructive ones, so the row's one
   outlined control ends up last and §4.0's order reads backwards. So the header is one
   line or **exactly two**, never "however it happens to wrap", and the lead is laid over
   the free edge of the first line or over the whole of the second.

   The second line is the base and the first is the enhancement. A reserve taken off a
   header that never wraps leaves the name 0px wide on a narrow card — BL-457's failure
   in a second shape, the thing that carries the content being the thing that gives way —
   so a card nobody measured degrades into two legible lines instead of one illegible
   one. */
.rowcard-lead {
  position: absolute;
  top: var(--rowcard-line);
  left: var(--s-3);
  right: var(--s-2);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--h-control);
}
/* Two lines with addresses, not two lines that happened. The header is a **grid** here
   because the second row has to be a row of a known height — the lead is laid over it and
   `top` cannot be an estimate — and because the pill has to be moved into it, which no
   amount of flex wrapping does deterministically.

   **The pill goes to the second line and the chips stay on the first**, and that is where
   the width for the name comes from: it is the largest of the three and the least
   scanned. The number of the fund's register is what you look up when you fill U 505; the
   state is what you read when you run your eye down the step. On a 352px card the name
   went from 96px to 191px by moving it (#479). */
.rowcard--haslead .rowcard-summary {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: var(--rowcard-line) var(--rowcard-line);
  align-items: center;
  column-gap: var(--s-3);
  row-gap: 0;
  padding-block: 0;
  min-height: 0;
}
.rowcard--haslead .rowcard-summary .rowcard-chevron { grid-area: 1 / 1; }
.rowcard--haslead .rowcard-summary .rowcard-name { grid-area: 1 / 2; }
.rowcard--haslead .rowcard-summary .rowcard-state { grid-area: 1 / 3; }
.rowcard--haslead .rowcard-summary .pill { grid-area: 2 / 2; justify-self: start; }
/* Right-aligned, so the free left of the second row belongs to the pill. */
.rowcard-lead { justify-content: flex-end; }
/* A card with no lead at all keeps the single flex line it always had — there is no
   second row to place anything on. */
@container (min-width: 34rem) {
  /* Room for the lead beside the name: one line again, and the header goes back to being
     a flex row, where the pill stands between the name and the chips. */
  .rowcard-lead { top: var(--s-1); left: auto; flex-wrap: nowrap; }
  .rowcard--haslead .rowcard-summary {
    display: flex;
    padding-block: var(--s-1);
    min-height: var(--rowcard-line);
  }
  /* The reserve, and it is the width of the lead this row really has rather than of the
     widest one any row could have. Measured in the browser on the real step: the
     segmented pair is 211px, a named move ("Potvrdiť správnosť") 165px and the file chip
     is capped at 12rem — so one reserve for all three spent 48px of the name's width on
     rows that never needed it, and on a 552px card that is a quarter of what the name
     had left. `:has()` is the same instrument `.filepicker` already focuses with. */
  .rowcard--haslead .rowcard-summary { padding-right: 11.5rem; }
  /* The file chip is capped at 12rem and the lead sits `--s-2` in from the edge, so a
     row that leads out of itself needs half a rem more than one that leads to a named
     move — without it the chip is drawn over the state it stands beside. */
  .rowcard--haslead:has(.rowcard-lead .filechip) .rowcard-summary { padding-right: 12.5rem; }
  .rowcard--haslead:has(.rowcard-lead .segmented) .rowcard-summary { padding-right: 14rem; }
}
/* The pair of answers is the widest lead there is, and a card wide enough for **it** is
   not yet a card wide enough for the name beside it: on a 552px column the reserve, the
   pill and the chip left the name 84px — twelve characters of a sentence-long title, on
   every row of the group that asks the fund's question. So this one lead keeps the second
   line until the card can pay for both, and the reader gets the name at 391px instead.

   It is the one place where BL-476's "a collapsed row is always one line" gives way, and
   it gives way to the thing that line was for: a row you can tell from the row above it.
   40rem is measured, not chosen — 14rem of lead plus 11rem of name plus what the chevron,
   the chip and the gutters take. */
@container (width < 40rem) {
  .rowcard--haslead:has(.rowcard-lead .segmented) .rowcard-summary {
    display: grid;
    padding-block: 0;
    padding-right: var(--s-3);
    min-height: 0;
  }
  .rowcard--haslead:has(.rowcard-lead .segmented) .rowcard-lead {
    top: var(--rowcard-line);
    left: var(--s-3);
    flex-wrap: wrap;
  }
}
/* The second line has **two tenants and only one of them is in the flow**, so past a
   width they stop fitting side by side and the pair simply paints the number out. The
   grid places the pill (`2 / 2`, `justify-self: start`); the lead is laid over the same
   row and right-aligned, and an absolutely positioned element can neither push the pill
   aside nor wrap under it — `flex-wrap` on the lead wraps what is *inside* it and has
   never been able to see what is underneath. Measured on the step at 320 px: eleven rows
   of the eleven that ask the question, 66 px of the number covered at worst, and the rows
   that lose most are the ones with the longest number — `CL-PRIL-06`, `50 – 2. časť` —
   which is to say the ones there is something to read on.

   Where they stop fitting is a **sum, not a breakpoint**: `--s-3` of padding, the 8px
   chevron, `--s-3` of column gap, the pill itself, `--s-2` and the 211px pair — 251px plus
   the pill. The widest number measured on the step is 80px, so 21rem is the first card
   that holds the pair beside every pill this step has, and 20rem is the last one that
   does not (measured: 33px of overlap on a 390px phone, 11px at 412px, none at 430px).

   Under it the two stop sharing the line and the pair takes a third one. **The pill keeps
   the second**, because the number in the fund's register is what a person looks up while
   filling U 505 and is the whole reason the second line was made (#479) — covering it is
   the one outcome this layout must not have. Three legible lines beat two that overlap,
   which is the trade §4.3 already made when it let this one lead keep a second line at
   all. */
@container (width < 21rem) {
  .rowcard--haslead:has(.rowcard-lead .segmented) .rowcard-summary {
    grid-template-rows: var(--rowcard-line) var(--rowcard-line) var(--rowcard-line);
  }
  .rowcard--haslead:has(.rowcard-lead .segmented) .rowcard-lead {
    top: calc(2 * var(--rowcard-line));
  }
}
/* A picker the script has not taken over is **two** controls wide — the browser's own
   input plus the *Nahrať* beside it, which the server deliberately never sends hidden
   (#471, `web/forms/attachments.py`) — and no reserve a name could survive would hold it. It keeps
   the second line at every width, and gets a second one of its own if it needs it.
   `data-autosubmit` is the token `web/js/forms.js` writes when the pair really was
   armed, the same anchor idiom as `data-needs-value` on the calculator: the stylesheet
   asks what the script decided instead of guessing. Declared after the container query
   on purpose — same layer, higher specificity, later. */
.rowcard:has(.rowcard-lead input[type="file"]:not([data-autosubmit])) .rowcard-lead {
  top: var(--rowcard-line);
  left: var(--s-3);
  flex-wrap: wrap;
}
.rowcard--haslead:has(.rowcard-lead input[type="file"]:not([data-autosubmit])) .rowcard-summary {
  display: grid;
  /* Two controls' worth of second row, because that is what the pair takes when it wraps
     on a narrow card. */
  grid-template-rows: var(--rowcard-line) calc(2 * var(--h-control) + var(--s-2));
  padding-block: 0;
  padding-right: var(--s-3);
  min-height: 0;
}
.rowcard-lead .filechip { max-width: 12rem; }
.rowcard-lead .filechip-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Indented under the chevron, so the body reads as belonging to the name above it. */
.rowcard-body {
  padding: var(--s-3) var(--s-4) var(--s-4) calc(var(--s-3) + 8px + var(--s-3));
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  border-top: 1px dashed var(--border);
}
.rowcard-body .filechip { align-self: flex-start; }
.rowcard-body .actions { padding-top: var(--s-1); }
/* A `<form>` of the row draws no box. It is a fact of the protocol — what travels in one
   POST — and not a step of the layout, which is what a box made of it (BL-458). Since
   BL-476 they hold nothing but a CSRF token: the buttons name their form with `form=` and
   stand in the order the reader needs them in, which is by kind and not by POST. Without
   this each empty form would still take a slot and the `gap` beside it. */
.rowcard-body .actions > form { display: contents; }
@media (prefers-reduced-motion: no-preference) {
  .rowcard-chevron { transition: transform 120ms ease; }
}

/* --- The state of a row, in one word (BL-476) -------------------------------
   The family of `.trail-step` and a block of its own: this chip says the **state of the
   row**, a step says a **place on the trail**, and a shared class would let a refactor of
   the trail rewrite the headers of the step in silence.

   `--warn` carries the dot as well as the colour, because a state is never only a colour
   (§6). The words and the tone both arrive from `applications.rules` — a header that
   composed its own would be a second place deciding what the row says.           */
.statechip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  font-size: var(--t-xs);
  padding: 1px var(--s-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--ink-soft);
  background: var(--surface);
  white-space: nowrap;
}
.statechip--ok { border-color: transparent; background: var(--ok-soft); color: var(--ok); font-weight: 600; }
.statechip--warn { border-color: transparent; background: var(--warn-soft); color: var(--warn); font-weight: 600; }

/* --- A question, drawn as a question (BL-476) -------------------------------
   Two submit buttons of one form inside one border. The fund's "Vyžaduje ju / Netýka sa"
   is a **decision** and not work, and two `.btn` standing among working verbs let it read
   as one more of them. There is no "selected" state and there is not going to be one: the
   answer moves the row into another group, and the reversal is a quiet action in the body.
                                                                                */
.segmented {
  display: inline-flex;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
}
/* The corners are rounded on the buttons themselves and **not** by `overflow: hidden` on
   the frame. The frame has no padding, so its clipping box is exactly the border box of
   the pair: a hidden overflow would cut the global focus ring (`outline-offset: 2px`)
   off both answers, and a control the keyboard cannot be seen on is the one thing §6
   does not forgive (WCAG 2.4.7). Radius less the 1px border, so the inner curve sits
   inside the outer one. */
.segmented > button:first-child { border-radius: calc(var(--r-sm) - 1px) 0 0 calc(var(--r-sm) - 1px); }
.segmented > button:last-child { border-radius: 0 calc(var(--r-sm) - 1px) calc(var(--r-sm) - 1px) 0; }
.segmented > button {
  border: 0;
  background: none;
  padding: 0 var(--s-3);
  /* Less the border of the frame, so the pair measures exactly one control high. */
  min-height: calc(var(--h-control) - 2px);
  font: inherit;
  font-size: var(--t-sm);
  color: var(--ink);
  cursor: pointer;
}
.segmented > button + button { border-left: 1px solid var(--border); }
.segmented > button:hover { background: var(--surface-2); }

/* --- The steps of a vote as screens (RQ-22) --------------------------------
   The lifecycle strip `.lifecycle` (BL-573) said where a vote stood above four parts that
   said where the work was; two lists of one vote. RQ-22 made the steps the screens, so
   the list is one: the sub-navigation.

   `.nav-item--step` is a `.nav-item` that knows the state of its step — the number, the
   name, the state **as a word** (never only a colour, §6) and a second line with the day,
   the deadline or the count the step carries. A grid rather than a flex row because the
   two lines share their columns: the number spans both rows and the second line runs
   under the name and the state together. `--plain` is the unnumbered *Výsledky*.

   The state word is `--t-xs` on the item's own ground: `--ink-soft`, `--ok` or `--warn`,
   never `--ink-muted` (§6). On the current item the accent pair of `.nav-item` applies to
   the name and the number; the state keeps its own colour, because it is the fact. */
.workspace-nav .nav-item--step {
  display: grid;
  grid-template-columns: 1.1rem minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  column-gap: var(--s-2);
  row-gap: 0;
  align-items: baseline;
  padding: var(--s-2) var(--s-3);
}
.nav-item--step .nav-item-ord {
  grid-row: 1 / 3;
  align-self: start;
  padding-top: 2px;
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--ink-soft);
}
.nav-item--step[aria-current="page"] .nav-item-ord { color: var(--accent-ink); }
.nav-item--step .nav-item-name { grid-column: 2; }
.nav-item--step .nav-item-state {
  grid-column: 3;
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
}
.nav-item--step .nav-item-state--ok { color: var(--ok); }
.nav-item--step .nav-item-state--now { color: var(--warn); }
.nav-item--step .nav-item-when {
  grid-column: 2 / 4;
  font-size: var(--t-xs);
  font-weight: 400;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.nav-item--step.nav-item--plain { grid-template-rows: auto; }
/* The foot of the navigation — the quiet actions over the vote as a whole (cancelling
   it), apart from the steps by a rule so that nobody reads them as an eighth step. */
.workspace-nav-foot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-1);
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--border);
}

/* `.stepnote` — the note a step opens with: the state, the heading as a question to the
   clerk, one sentence with the day and the counts, and at most one primary button. The
   sibling of `.stephelp` — the same plane and the same rule on the start edge — so that
   the two voices of the guide read as one zone above the work; the rule takes the
   colour of the state, the pill says it in a word. */
.stepnote {
  display: grid;
  gap: var(--s-2);
  padding: var(--s-4) var(--s-5);
  background: var(--surface-2);
  border-inline-start: 2px solid var(--accent);
  border-radius: var(--r-sm);
}
.stepnote > p { margin: 0; max-width: 72ch; }
.stepnote-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-3);
}
.stepnote-h {
  margin: 0;
  font-family: var(--serif);
  font-size: var(--t-lg);
  font-weight: 600;
  line-height: 1.2;
  text-wrap: balance;
}
.stepnote--now { border-inline-start-color: var(--warn); }
.stepnote--done { border-inline-start-color: var(--ok); }
.stepnote--ahead { border-inline-start-color: var(--border-strong); }
/* The next step of a building's register that blocks the loan (RQ-23, R2 and R3). */
.stepnote--bad { border-inline-start-color: var(--bad); }
.stepnote .actions { margin-top: var(--s-1); }

/* `.source-cards` — the three sources of a building's register side by side (RQ-23):
   a deed, a DOMUS export, typing in. Three columns while there is room and one below
   it; `auto-fit` rather than a fixed count, the rule of `.grid`. */
.source-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
  gap: var(--s-4);
}
/* `.source-card` — one source: what it brings and what not, where to get it, when to
   renew it, and the one button. Not a `.panel`: nothing is filled in here. The card the
   next step recommends carries the accent ring — never a primary button: the section has
   one, in the note of its next step. `-carries` is read by the preview's band too. */
.source-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-4);
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  min-width: 0;
}
.source-card > p { margin: 0; }
.source-card--recommended { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.source-card-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
.source-card-carries { display: flex; flex-wrap: wrap; gap: var(--s-1); margin: 0; padding: 0; list-style: none; }
.source-card-where { margin: 0; padding-left: 1.2em; font-size: var(--t-sm); color: var(--ink-soft); }
/* The line about this very building — its deed number, its DOMUS code. */
.source-card-here {
  background: var(--accent-soft);
  color: var(--accent-ink);
  border-radius: var(--r-sm);
  padding: var(--s-2) var(--s-3);
  font-size: var(--t-sm);
}
.source-card .actions { margin-top: auto; }

/* `.step-foot` — the foot of a step: the way back on the left, the way on on the right.
   Not a `.save-bar`: nothing is saved here, the reader moves on — and never primary, the
   one recommendation of the screen being the note's or the body's. */
.step-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
}

/* The rows of a step's list with a state of their own — RQ-22. `.step-row` carried a
   value on the right; a step of the guide says whether the row is done, and a done row
   says it in the word and the dot together (never only the colour, §6). The actions and
   the field of a row take its text columns, under the name, so a row of the list stays
   one row of the order and its controls do not float to the right edge. */
.step-row-state {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  font-size: var(--t-xs);
  color: var(--ink-soft);
  white-space: nowrap;
}
.step-row--done .step-row-state { color: var(--ok); font-weight: 600; }
.step-row > .step-row-actions, .step-row > .step-row-field { grid-column: 2 / -1; }
.step-row > .step-row-actions { display: flex; flex-wrap: wrap; gap: var(--s-2); margin: var(--s-1) 0 var(--s-2); }
.step-row > .step-row-field { max-width: 22rem; margin: var(--s-1) 0 var(--s-2); }

/* `.panel--confirm` — a panel that asks for a decision over stated numbers: the breached
   notice period, the return of a vote to preparation, its cancellation (RQ-22). Narrow,
   because a confirmation is read, not scanned, and its sentences are the content. */
.panel--confirm { max-width: 46rem; }
.panel--confirm .summary { margin-bottom: var(--s-2); }

/* `.scope` — the scope of a list as two links, the open one in `aria-current` (RQ-22
   acceptance 14: live votes by default, all of them on request). Links and not a control:
   the scope is a state of the address, so it works with no script and can be sent. */
.scope { display: inline-flex; border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden; }
.scope a { padding: var(--s-1) var(--s-3); font-size: var(--t-sm); color: var(--ink-soft); text-decoration: none; }
.scope a + a { border-inline-start: 1px solid var(--border); }
.scope a[aria-current="true"] { background: var(--accent-soft); color: var(--accent-ink); font-weight: 600; }

/* The open question of a meeting's tally — a state of the control, in `aria-current`
   rather than in `--primary`: the one primary of the step is the button that writes. */
.actions[role="group"] > .btn[aria-current="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}

/* --- Where the document is on its way into the file (BL-454) ----------------
   Three steps, of which **at most one** is `--now`: that is the whole component. A list
   of words without it is a legend the reader has to place themselves — and a finished
   trail carries none, because nothing is waiting.

   The state is **never only the colour** (§6): `--now` carries a word for the screen
   reader through `aria-current="step"` in the markup, and the labels differ from each
   other by what they say, not by their shade. */
.trail { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1); }
.trail-step {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  font-size: var(--t-xs);
  padding: 1px var(--s-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  /* `--ink-soft` and not `--ink-muted`: §6 refuses the muted ink under `--t-xs`, and the
     step that is still ahead is the one the reader looks for. */
  color: var(--ink-soft);
  background: var(--surface);
  white-space: nowrap;
}
.trail-step--done { border-color: transparent; background: var(--ok-soft); color: var(--ok); font-weight: 600; }
/* The step that waits carries a dot as well as a colour. Green against amber is the
   classic pair deuteranopia cannot tell apart, and both steps are otherwise the same
   shape and weight — so without the dot the one thing the component exists to say
   would be said by hue alone (§6, and `.pill`: a dot announces a state). */
.trail-step--now { border-color: transparent; background: var(--warn-soft); color: var(--warn); font-weight: 600; }
/* The arrow is decoration for the eye and `aria-hidden` for the reader, but it is still
   drawn text at `--t-xs` — the same rule as the step above it (§6). */
.trail-sep { color: var(--ink-soft); font-size: var(--t-xs); }

/* --- Buttons --------------------------------------------------------------- */

/* Group of buttons in a row. A block of its own: styled only inside .save-bar
   and the confirmation dialog, the same class did nothing anywhere else — and a
   screen that borrowed it got no layout at all (§4.1, §9). */
.actions { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.actions--right { justify-content: flex-end; }

/* **Where the answer is given by the place, the place gives it** (BL-290). The foot
   of a `<dialog>` is such a place: the pair confirm/cancel belongs at the right edge
   on every dialog there is, so it is not a choice the author can forget. It was one:
   `confirmation.html` wore `--right` and `feedback_widget.html` did not, both are the
   foot of a dialog, and both passed — the modifier is only remembered by whoever
   remembers it. The same shape as `.save-bar .actions` above and `.body:has(> .sidebar)`
   in the shell: the DOM already knows the answer, so the stylesheet reads it there
   instead of asking the template to repeat it.

   `--right` stays for the group standing free in a panel body, where the alignment
   really is a decision (the calculator's three). */
.dialog-body .actions { justify-content: flex-end; }

/* One input and the single action that operates on it, on one line (#218). It is not
   `.actions` — that is a group of buttons and says nothing about a field — and not
   `.save-bar`, which would claim the section saves itself. The bound on the field
   block is what keeps the pair readable: the field carries its own `.hint` in the
   column below the control, and at full panel width that sentence would run to the
   far edge while the control sat at 14rem. The action centres against the block for
   the same reason — the block is taller than the control it belongs to, and a top or
   bottom alignment would put the button beside the label or beside the hint instead
   of beside the input. Under 400px the pair stacks, which is the only sane end. */
.field-action {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--s-4);
}
.field-action > .field { flex: 0 1 28rem; }

/* --- Picking from a long catalogue, with a search (BL-575, RQ-19) -----------
   Two renderings of one field, and only the second needs drawing: the base is a
   `<select>` with `<optgroup>`, which `.field` already dresses.

   The search row is a `<form>` of its own — forms do not nest and the choice under it is
   a POST — so it lays a `.field`, a button and the loading state out rather than being
   one. `align-items: end` so that the button's edge meets the input's and not the
   label's, two lines higher. */
.picker-search {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: end;
  gap: var(--s-2);
}

/* One row of the shortlist: the radio, two lines of text, the quorum.

   A grid and not `.list-row`, for the reason `--register` gives — a shape shared down a
   column of siblings can only come out of fixed tracks — and with a second line on
   purpose: the quorum has to be readable **before** the choice (RQ-19) and the wording
   carries the placeholders the clerk is about to fill in. Neither fits on the single
   line the base row gives.

   The whole row is the `<label>` of its own radio, which is the one way a row may be the
   click target without `--link`: a label is not an interactive element nested inside
   another (§6) — the same reason `.field-check` wraps a checkbox together with its
   sentence. `--link` is precisely the variant that could not be used here, because it
   makes the row an `<a>`, and a radio inside an anchor **is** nesting. */
.picker-option {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--s-2);
  align-items: baseline;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  cursor: pointer;
}
.picker-option:hover,
.picker-option:focus-within { background: var(--surface-2); }
/* The chosen row, and the colour is never alone: the radio beside it carries the state
   in its shape as well (§6). */
.picker-option:has(input:checked) { background: var(--accent-soft); }
/* The middle track stacks, so the code sits over the wording rather than beside it.
   `min-width: 0` because a grid item's minimum is otherwise its content, and the wording
   is a paragraph. */
.picker-option-text {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  min-width: 0;
  overflow-wrap: anywhere;
}
/* One column under the narrow break (§5.1), the same call `.list-row--register` makes:
   three tracks on a phone leave the wording a word wide. */
@media (max-width: 860px) {
  .picker-search { grid-template-columns: minmax(0, 1fr); }
  .picker-option { grid-template-columns: auto minmax(0, 1fr); }
  .picker-option .subtext { grid-column: 2; }
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-4);
  border-radius: var(--r-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  text-decoration: none;
}
.btn:hover { background: var(--surface-2); }
.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--surface);
  font-weight: 600;
}
.btn--primary:hover { background: var(--accent-strong); }
.btn--quiet { border-color: transparent; background: transparent; color: var(--ink-soft); }
.btn--quiet:hover { background: var(--surface-2); color: var(--ink); }
/* The button of an irreversible action: outlined at rest and filled on hover — the shape
   .btn--primary has, in the state colour instead of the accent. A destructive action must
   never sit in --accent, which everywhere else in the product means "the ordinary way
   on" (§1.1); and it is not filled at rest either, because the loudest thing on a panel
   should not be the thing nobody normally wants. */
.btn--destructive { border-color: var(--bad); color: var(--bad); }
.btn--destructive:hover { background: var(--bad); border-color: var(--bad); color: var(--surface); }
/* The quiet form and the colour of the consequence, together — the shape a secondary
   destructive action wears in the body of an attachment row (BL-476). It has to be
   written down rather than left to the cascade: both variants have the same specificity
   and `--destructive` is declared later, so `class="btn btn--quiet btn--destructive"`
   would otherwise take the outline back and fill the plane on hover — the loud form,
   which is the exact opposite of what the two classes together were asked for.

   `--quiet` alone is not enough for these (a removal has to look like one) and
   `--destructive` alone is not either: an outlined red button among six quiet ones is
   the loudest thing on the row, and what it destroys is one attachment, not the file. */
.btn--quiet.btn--destructive { border-color: transparent; background: transparent; color: var(--bad); }
.btn--quiet.btn--destructive:hover { background: var(--bad-soft); border-color: transparent; color: var(--bad); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .55; cursor: not-allowed; }

/* The pressed state of a toggle (BL-692). The application has exactly one — the theme
   switch — and until BL-692 the family had no rule for it at all, so the button was
   split in two: `aria-pressed` told a screen reader the truth ("Tmavý motív, pressed")
   while a sighted user read an unchanging label as an **offer**, false in one of the
   two states. The label cannot alternate to say it: with `aria-pressed` the accessible
   name must name what is switched, not the state, or the reader says "Svetlý motív,
   pressed" over a dark page — a double negative meaning the opposite (BL-68).

   So the state is said here instead, and in **two channels**, because colour alone is
   never a state (§6): a plane and a border where `--quiet` had neither (shape), and a
   marker in the accent before the label. The marker is a pseudo-element on purpose —
   the template must not write the state in any form (§6, BL-63): only `theme.js` knows
   which theme is really in effect, so anything the template rendered would be a claim
   made before the answer exists. `.btn` is an `inline-flex` with a `gap`, so the marker
   falls into the row as a flex item without a rule of its own for spacing.
   `aria-pressed="false"` deliberately draws nothing: "off" is the resting look.

   Measured, because §6 asks for the numbers: the marker has 4.51:1 against the plane
   it sits on in **both** themes and the border 3.02:1 light / 4.16:1 dark (3.60 / 4.81
   against the header's own plane on the other side of it) — the floor for a graphic
   object under WCAG 1.4.11 is 3:1. The plane alone is 1.19:1, which is exactly why it
   is not what carries the state. */
.btn[aria-pressed="true"] {
  background: var(--surface-2);
  border-color: var(--border-strong);
  color: var(--ink);
}
/* The pressed button still answers the pointer: without this it would not, because
   `.btn--quiet:hover` and the rule above have the same specificity and the later one
   wins. Hover goes one plane deeper, never back up — "on, and under the cursor". */
.btn[aria-pressed="true"]:hover { background: var(--surface-3); }
.btn[aria-pressed="true"]::before {
  content: "";
  flex: none;
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: var(--accent);
}

/* --- Picking a file (BL-458) -------------------------------------------------
   The one control of the application that nobody here draws: a bare `<input
   type="file">` is rendered by the browser, in the **browser's** language, on a plane
   of its own and at a width nobody chose. On the Prílohy step that is "Choose file / No
   file chosen" in the middle of a Slovak screen, nineteen times over. The button cannot
   be renamed from a stylesheet at any price — `::file-selector-button` dresses it and
   the words inside it stay the browser's — so the picker puts a `<label>` in front of
   the input and the input goes out of sight.

   **The swap belongs to the script** (`web/js/forms.js`), which is why nothing here
   hides the input: a clipped input with no button in front of it is a control that
   cannot be operated at all, and a stylesheet cannot know whether the script ran.
   Without JavaScript the browser's own control stays exactly where it was — English,
   but working. The same bargain as the confirmation dialogs.

   **One `<label for>` per input, and it is this button** (§6). The row of an attachment
   carried a `.visually-hidden` label of its own, because nineteen controls named
   "Súbor" name nothing; that qualifier moved into the button, where it names its
   attachment the same way the ten controls beside it do. */
.filepicker {
  /* Inline, because the picker shares its line with the action that consumes it —
     "Nahrať" stands in the same `<form>`. It wraps because the lane it stands in is
     110-210px wide on an ordinary laptop (BL-457), so the name of the file goes
     **under** the button rather than beside it. */
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  max-width: 100%;
  min-width: 0;
}
/* The button is a `.btn` and takes everything from it: the boundary that identifies a
   control (`--border-strong`, 3:1 under WCAG 1.4.11, §6) and the target floored by
   `--h-control`. What is left here is the ceiling, so two words cannot widen the track
   they stand in. */
.filepicker-button { max-width: 100%; }
/* The native control for as long as it is the visible one — the rule belongs to the
   control and not to the place, so it holds on every screen that asks for a file.
   BL-457 measured what it costs to let it size the row: it does not shrink, so as a
   flex item with an automatic minimum it held the whole form at ~240px and pushed the
   surplus out of the track — and `justify-content: flex-end` sends an overflow towards
   the **start**, onto the sentence the track sizing there exists to protect. A
   percentage max-width contributes zero to intrinsic sizing, which is what lets the
   control give way. */
.filepicker input[type="file"] { max-width: 100%; min-width: 0; }
/* A clipped input still takes the focus, so the ring is drawn where the eye is looking.
   `:focus-visible` and not `:focus`: clicking the label focuses the input as well, and a
   ring after every click is noise. The script clips the input **only** where this
   selector is understood, so the ring and the clipping can never part ways and leave the
   focus on a control 1x1px in size (§6, WCAG 2.4.7). Same 2px accent at 2px offset as the
   global rule at the top of the file. */
.filepicker:has(input[type="file"]:focus-visible) > .filepicker-button {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Which file was picked — the seeing half of the answer; the other half is the value of
   the input itself, which a screen reader announces when the browser hands the focus
   back. Mono, because a file name is read character by character when it is being
   checked, and `anywhere`, because it is one long token in a narrow lane. `--ink-soft`
   and not `--ink-muted`: §6 refuses the muted ink under `--t-xs`.

   Out of the layout while it is empty, or the `gap` above would hold a space open for a
   word that is not there — nineteen times over. That is also why the partial writes the
   element with no whitespace inside: a newline is content and `:empty` would stop
   matching. */
.filepicker-name {
  font-family: var(--mono);
  font-size: var(--t-xs);
  color: var(--ink-soft);
  overflow-wrap: anywhere;
  min-width: 0;
}
.filepicker-name:empty { display: none; }

/* --- The way out of a row that has a file (BL-460) --------------------------
   The whole chip is the link, the same bargain `a.kpi` makes: a file name set at
   `--t-xs` is a hairline to hit, and §6 asks that the target be the thing the eye is
   aiming at. It replaced a `.btn--quiet` reading *Otvoriť* — a verb that named the
   action and hid its object, nineteen times down one step, when what the reader is
   checking is **which** document got into the file.

   Not a `.pill`: that row of the catalogue says a pill is a label or a counter and that
   identity is not a state, whereas this is a way out. Not a `.btn` either — a button
   with a file name in it is a control wearing a value. */
.filechip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  max-width: 100%;
  min-width: 0;
  padding: var(--s-1) var(--s-2);
  /* The floor every target in the application stands on (§6) — `--t-xs` and one step of
     padding come out under it, and the token grows on a coarse pointer. The same rule
     `.guard > a` follows, and it is why the chip stands beside `.btn` siblings without
     being one: they measure `--h-control` too. */
  min-height: var(--h-control);
  /* Inherited, not the accent: the name of a file is a value, and the global `a` rule
     would repaint it — the same reason `a.kpi` says so. What identifies the chip as a
     way out is its boundary and the mark at its end. */
  color: inherit;
  text-decoration: none;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}
.filechip:hover { border-color: var(--border-strong); }
/* Mono and `anywhere`, for the reason `.filepicker-name` beside it is both: a file name
   is read character by character when it is being checked, and it is one long token in
   a lane that wraps. `--ink-soft` and not `--ink-muted` — §6 refuses the muted ink
   under `--t-xs`. */
.filechip-name { font-size: var(--t-xs); color: var(--ink-soft); overflow-wrap: anywhere; }
/* The mark that says the link leaves the row, **drawn** rather than written: a glyph in
   `content` is announced by a screen reader on top of the accessible name the chip
   already carries. The same idiom as the disclosure arrow further up — a corner of two
   borders, here pointing up and to the right. */
.filechip::after {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  border-top: 2px solid var(--ink-soft);
  border-right: 2px solid var(--ink-soft);
}

/* --- State elements -------------------------------------------------------
   A semantic colour is not the accent (§1) and state is never carried by colour
   alone (§6): the dot has text next to it, the pill carries a label.        */

.dot {
  width: 8px; height: 8px; border-radius: 50%;
  display: inline-block; flex: none;
  background: var(--border-strong);
}
.dot--ok   { background: var(--ok); }
.dot--warn { background: var(--warn); }
.dot--bad  { background: var(--bad); }
.dot--info { background: var(--info); }

.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  /* --t-xs is exactly this size; writing the number again only hid it from the
     scale (§1.2). The vertical padding goes on the 4px grid for the same reason (§1.3). */
  font-size: var(--t-xs);
  font-weight: 700;
  padding: var(--s-1) var(--s-2);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-soft);
  /* A pill wraps rather than overflows. It carries a state name or a role, and those
     grow in translation (§8) — `nowrap` pushed a long one 495px wide on a phone.
     `max-width` keeps it inside its parent even as one unbroken word. */
  max-width: 100%;
  /* Between words, never inside one. `.list-row` sets `overflow-wrap: anywhere`
     so a long unbroken organization name cannot blow the row, and the pill inherited
     it — which made its own min-content one character, so flex shrank it first and
     the role read "Administráto / r" while the name beside it kept a whole line.
     Only the row's free text needs breaking anywhere; a label of a closed vocabulary
     is what should push back (BL-74). */
  overflow-wrap: break-word;
}
.pill--ok   { background: var(--ok-soft);   color: var(--ok); }
.pill--warn { background: var(--warn-soft); color: var(--warn); }
.pill--bad  { background: var(--bad-soft);  color: var(--bad); }
.pill--info { background: var(--info-soft); color: var(--info); }
/* "filter aktívny" over a listing (RQ-12). Deliberately **not** a semantic colour: the
   four above mean "splnené", "vyžaduje pozornosť", "blokuje" and "informácia" about a
   record, and a narrowed view is none of those — recycling one of them would report a
   verdict on rows that have none. The accent is what is left, and it is allowed here
   because this is a property of the view rather than a state (§1.1); the pair
   --accent-ink on --accent-soft is the one the active navigation item and `.step`
   already carry, measured at 10.45 / 10.26:1, which the pill's 11.5px bold needs (§6).
   No dot: a dot announces a state, and the word alone carries this one. */
.pill--on   { background: var(--accent-soft); color: var(--accent-ink); }

/* Operation messages. A confirmation says what happened (§7). */
.messages { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-2); }
.message {
  display: flex; align-items: flex-start; gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-sm);
  border-left: 4px solid var(--border-strong);
  background: var(--surface-2);
}
.message--success { border-left-color: var(--ok);   background: var(--ok-soft); }
.message--warning { border-left-color: var(--warn); background: var(--warn-soft); }
.message--error   { border-left-color: var(--bad);  background: var(--bad-soft); }
.message--info    { border-left-color: var(--info); background: var(--info-soft); }

/* The band under the header — BL-565. The plain .message is a card **inside** the
   content column; this one is a row of the skeleton, so it loses the radius and the
   4px stripe (a full-bleed rule under a full-bleed header reads as a border, not as a
   card) and closes with a hairline instead. The state is never carried by the colour
   alone (§6): the heading says it in words — "Zápis je uzavretý". */
.message--band {
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-5);
  border-radius: 0;
  border-left: none;
  border-bottom: 1px solid var(--border-strong);
}
/* The sentence, so the action can be pushed to the far end without the heading and
   the sentence drifting apart when the band wraps on a phone. */
.message-text {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  margin-inline-end: auto;
}

/* Readiness checklist — a standing list of what is still missing. Deliberately not
   a .message: a message is feedback after an action, this state was there before the
   user arrived, and painting it as a warning cries wolf on every visit (§4.3).
   The state of an item is carried by the dot next to the text, never by colour
   alone (§6). */
.readiness { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.readiness-item {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--border);
}
.readiness-item:last-child { border-bottom: 0; }
/* What exactly is wrong is a line of its own, not a tail of the name: the item carries a
   whole sentence, and a sentence beside the name would squeeze it (BL-95). Consumers are
   the building and the organization profile — the application wizard dropped this list in
   BL-141, where each sentence moved into the step it is corrected in. */
.readiness-item .subtext { display: block; }

/* Head of the checklist — one number over the track (BL-123). The list alone does not
   say how far the whole is; the head does, and its denominator is never the count of
   sections on the screen: on the building it is the "Evidencia" group of the
   sub-navigation (§4.3). It is **not** `.meter`: a meter is a track with a limit marker
   and there is no limit here, the whole track is the group — which is also why the track
   is composed from `.bar` and these rules declare no geometry of their own.

   The track is the third child and takes the whole next line, the same flex-basis idiom
   `.panel-header .hint` uses.

   **The building is the only host again since BL-175.** BL-141 gave it a second one, the
   wizard's rail, and named it the part that gives way if that rail ever outgrew the
   viewport; it did, so it gave way. The ratio of the gate stayed — it is the `.mono` in
   the rail's own `.panel-header` — but the head with its track did not, because the track
   is a second shape of a number the rail already prints. The component is not withdrawn,
   one of its two uses is (the same move BL-141 made on `.readiness`). */
.readiness-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-1) var(--s-4);
}
.readiness-head .bar { flex-basis: 100%; }
/* The number is the headline of the panel, so it takes the section-heading step (§1.2) —
   readable at a glance, not a display number that would cost a row of its own. */
.readiness-head .mono { font-size: var(--t-md); }

/* On the building every item is the way to its own fix, so the whole row is the target
   and not the two words of the name (§6). Written as a descendant of the item rather
   than on it: the organization profile renders this same list with **no** link — the way
   in there is the form below it — and it may not grow a 40px row for a target it does
   not have. */
.readiness-item > a {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex: 1;
  min-width: 0;
  min-height: var(--h-control);
  color: inherit;
  text-decoration: none;
}
/* With the whole row a target, the padding belongs to the target: a hover surface that
   stops short of the hairline above and below reads as a row that is broken in half. */
.readiness-item:has(> a) { padding: 0; }
.readiness-item > a:hover { background: var(--surface-2); }

/* --- Data table -----------------------------------------------------------
   Every number is mono + tabular-nums and right-aligned (§3). Wide content
   scrolls inside its own frame, the page never horizontally (§5).          */

/* The frame only ever scrolls horizontally: it has no height limit, so nothing
   scrolls inside it vertically and a sticky thead would have nothing to stick to —
   the head would stay a promise the layout cannot keep (BL-64). A table long enough
   to need one is a pagination problem first; only then a frame of its own. */
/* `min-width: 0` is what makes the horizontal scroll actually happen: without it the
   frame is a flex item sized to the table's own min-content, so it grew to the width
   of the widest row and pushed the page sideways instead of scrolling inside itself.
   The members table did exactly that on every phone width. */
/* The frame is also the container its columns ask for their room. The viewport does not
   predict this width: the step of a wizard gives the table a content column narrowed by
   a 20rem rail, so the same window is a narrow table here and a wide one on a listing
   (#559). The same reason `.rowcard` asks (§5.1). The container is **named**, so a query
   written for this table cannot bind to `.rowcard`'s unnamed one. */
.table-frame {
  overflow-x: auto;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--r);
  container: table-frame / inline-size;
}
table.data { border-collapse: collapse; width: 100%; background: var(--surface); }
table.data thead th {
  background: var(--surface-2);
  text-align: left;
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: .09em;
  font-weight: 600;
  color: var(--ink-soft);
  padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
table.data tbody td,
table.data tbody th { padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--border); }
/* A row heading sits where the first cell would and reads like one: left, top, with the
   same hairline — the browser default centred it with no padding (RQ-23, *Čo dom má*). */
table.data tbody th { text-align: left; vertical-align: top; font-weight: 600; }
table.data tbody tr:last-child td,
table.data tbody tr:last-child th { border-bottom: 0; }
/* A second line inside a cell: the name on the first, what qualifies it on the second.
   `.subtext` is a span, so glued straight after the text of the cell it read as one word
   — "Ľuboš FábryVrbenského 14" on the unit, "1vchod A" in the register of flats (#562).
   The separator belongs here rather than to a <br> in three templates: the cells that
   carry a name and its qualifier are all written the same way, so a fourth one inherits
   the rule instead of remembering it. Scoped like `.readiness-item .subtext` — outside a
   cell the class dresses a paragraph and already blocks. */
table.data tbody td .subtext { display: block; }
/* …and in the heading cell of a row, where *Čo dom má* says what the datum is for (RQ-23). */
table.data tbody th .subtext { display: block; font-weight: 400; }
table.data tfoot td {
  padding: var(--s-2) var(--s-3);
  border-top: 1px solid var(--border-strong);
  background: var(--surface-2);
  font-weight: 600;
}
table.data .number, table.data th.number {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

/* Where the frame itself is narrow, a money column may wrap its **heading** — the
   heading is prose and the amount is not. `white-space: nowrap` on the value stays: a
   sum broken across two lines stops being one number. Asked of the frame and not of the
   window, because the frame is what the table has to fit into (#559). */
@container table-frame (width < 46rem) {
  table.data thead th.number { white-space: normal; }
}
table.data tr.notice td { background: var(--warn-soft); }
/* The row the others are read against — the maturity actually chosen in the
   sensitivity table (§4.3). The state lives in `aria-current`, so the style is read
   from it: a second class beside the attribute is two homes for one fact and they
   part ways. The row carries a text anchor as well; the tint alone would say
   "current" only to those who see it (§6). */
table.data tbody tr[aria-current="true"] > * { background: var(--accent-soft); }
table.data tbody tr[aria-current="true"] > *:first-child {
  box-shadow: inset 4px 0 0 var(--accent);
}

/* A cell that carries a value and, under it, the mark qualifying it — the pill saying
   which constraint binds a row of renovation purposes (design.md §4 rule 3). Under the
   name and not in a column of its own, because a column costs the width the amounts need
   and the amounts are what fell out of the frame (#559). Not `.list`: that is a column of
   rows inside a panel, and a table cell is neither. */
.cell-stack { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-1); }

/* The purposes of the call, grouped by the cap they share (design-system.md §4.3).
   The grouping is data of the call, so nothing here may assume how many groups
   arrive or which one comes first — one <tbody> per group, whatever they are. */
.purposes th, .purposes td { vertical-align: top; }
/* The name of the purpose is the row's header, and a header inside the body is
   styled nowhere else in the file — it would otherwise be bold and unpadded. */
.purposes tbody th {
  padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--border);
  text-align: left;
  font-weight: 400;
}
.purposes tbody tr:last-child th { border-bottom: 0; }
/* The row that opens a group: a colgroup header, not data. Recessed plane plus
   accent is the pair .eyebrow uses, so the table reads as named sections. */
.purposes-header th {
  background: var(--surface-2);
  padding: var(--s-2) var(--s-3);
  font-size: var(--t-xs);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--accent);
}
/* The sentence that says what holds the group together is prose again, not a label. */
.purposes-header .hint { display: block; text-transform: none; letter-spacing: normal; }
/* The narrow columns wrap, they never truncate (§7): a longer language mutation has
   to stay readable rather than end in an ellipsis. The head is restated with the
   table's own class because `table.data thead th` is the more specific selector and
   would keep its `nowrap`. */
table.data.purposes thead th { white-space: normal; }
/* The cost and the count are typed inside the cell, so they wear the field skin of
   §4 without a .field wrapper — a bare browser input in a designed table reads as a
   defect, and the numeric columns want the mono of §3. The vertical padding is one
   step smaller so the row stays a table row, but the target is not: --h-control is
   the floor §6 sets, and these are the most used inputs on the screen — beside a
   checkbox that already measures 40/44px they must not be the smallest target in
   the application.

   The selector no longer names a type: since #562 the cost is `<input type="text">`
   with `inputmode="decimal"` (design-system.md §3.1), while the count is still a number
   and the tick of the row has to keep the browser's own box. */
.purposes input:not([type="checkbox"]) {
  min-height: var(--h-control);
  font: inherit;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  padding: var(--s-1) var(--s-2);
  text-align: right;
  width: 100%;
  max-width: 100%;
}
.purposes input[aria-invalid="true"] { border-color: var(--bad); }
/* The message belongs under its input, not beside it — the cell is already narrow. */
.purposes .error { display: block; }

/* Findings inside a table cell — a list, not prose, because each one is its own
   link to the step that fixes it.                                              */
.list-findings { margin: 0; padding-left: var(--s-4); display: grid; gap: var(--s-1); }

.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* --- KPI tile ---------------------------------------------------------------
   One number that **measures** something and has no ceiling. A number *against*
   a ceiling is `.meter` (a track with a marker) and a number that **guards**
   something is `.guard`; if the difference cannot be said in one sentence, the
   three belong merged rather than multiplied (design-system.md §4.3).

   The tile used to live under the meter's name, which is why it got written
   twice — the dashboard as `a.meter`, /platforma/rozpracovanost/ as a plain
   `<div class="meter">` — and the second one rendered its number at body size.
   One name, one component (BL-90); the single home of the markup is
   `web/components/kpi.html`.                                                  */

.kpi { display: flex; flex-direction: column; gap: var(--s-1); }

/* A tile that is also the way into what it counts (BL-31). The whole tile is
   the target, not just the number — a 2-digit link is a hairline to hit (§6).
   `color: inherit` is not cosmetic: the global `a` rule would otherwise repaint
   the caption in the accent.                                                  */
a.kpi {
  color: inherit;
  text-decoration: none;
  padding: var(--s-3);
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
}
a.kpi:hover { border-color: var(--border-strong); }
/* The value the tile exists for, at the same size as .guard's. It hangs on the
   element and not on a descendant `.mono` for the reason `.guard > .mono` is
   scoped to a direct child: a `.mono` nested in the caption must not inflate. */
.kpi-value { font-size: var(--t-xl); }
.kpi-label { font-size: var(--t-xs); color: var(--ink-soft); }

/* A module that does not exist yet: named, never shown as a zero. Quieter than a
   live tile and deliberately not a link — there is nowhere to go. `--ink-soft` and
   not `--ink-muted`: the sentence inside is small text, which §6 keeps off the
   muted token (BL-59).                                                          */
.kpi--unavailable {
  padding: var(--s-3);
  border: 1px dashed var(--border);
  border-radius: var(--r);
  color: var(--ink-soft);
}

/* --- Track of one whole, optionally in two named parts ---------------------
   Extracted from `.meter` and `.breakdown`, which held the same four rules under
   two names (BL-89 recorded the duplication, BL-91 removes it). The track knows
   no domain object: the caller passes the widths, the state and the captions.

   Two segments are drawn as **two widths, not a width and an offset**: `--second`
   carries the whole filled length and the first segment lies on top of it, so
   only the part beyond the first one shows. Independently rounded percentages
   would otherwise leave a hairline of track between the parts. **Two parts is
   the limit** — and the third part does not go to `.chart`, which is an inline SVG
   with an axis and a grid: a whole divided into three or more named parts is
   `.composition` below (BL-456). The two are not variants of one another. Here the
   empty stretch of track means "how much is left before the limit" and the fill
   grows from zero; there the parts always add up to the whole and there is no
   remainder to draw.                                                          */

.bar {
  position: relative;
  height: 10px;
  background: var(--surface-3);
  border-radius: 999px;
  /* The limit marker overhangs the track; clipping would hide the one thing the
     bar is about (§4.3). */
  overflow: visible;
}
.bar-fill {
  position: absolute; inset: 0 auto 0 0;
  background: var(--ok);
  border-radius: 999px;
}
.bar-fill--warn { background: var(--warn); }
.bar-fill--bad  { background: var(--bad); }
/* The second part is never a second state token: both parts are equally
   legitimate, so it takes `--info` — derived by the system, a neutral fact
   (§1.1). Its width is the whole of the fill, not the width of the second part;
   the first segment lies **on** it. */
.bar-fill--second { background: var(--info); }
/* The boundary between the parts needs a line of its own. `--info` against `--ok`
   is 1.23:1 in the light theme and **1.01:1 in the dark one** — two adjacent fills
   of one track would read as a single bar. The hairline is the colour of the track,
   which every fill clears 4.13:1 against, and it is a shadow rather than a border
   so it costs no width (tests/web/test_kontrast_tokenov.py). The selector is why
   the DOM order is load-bearing: `--second` first, the state segment on top. */
.bar-fill--second + .bar-fill { box-shadow: 1px 0 0 0 var(--surface-3); }
/* `left` is inline, computed from the data — the marker is at the end of the track
   for a plan's limit and at 85 % for the repayment rule. It is painted after the
   fills in the DOM, so a full track never buries it. */
.bar-marker {
  position: absolute; top: -3px; bottom: -3px;
  width: 2px;
  background: var(--ink);
}
/* The parts named. Two shades with no legend are colour without meaning (§6), so a
   divided track always carries this row. It wraps: a purpose's row may name the
   part, the remainder and the whole, and the cell it sits in is narrow. */
.bar-parts {
  display: flex; justify-content: space-between; gap: var(--s-2);
  flex-wrap: wrap;
  font-size: var(--t-xs); color: var(--ink-soft);
}

/* --- Composition of one whole in named parts -------------------------------
   A whole split into three or more parts — the attachments of an application by
   whose move each of them is (BL-456). **Not `.bar`**: the shapes are alike and the
   claims are not. On a bar the empty stretch of track is "how much is left before
   the limit" and the fill grows from zero, whereas a composition has no remainder —
   the parts always add up to the whole. And `.bar` carries two segments as two
   widths, the second holding the whole of the fill with the first lying on top of
   it; that idiom is exact for two parts and has no third step.

   **The segments do not touch.** All four state tokens of §1.1 were tuned against
   the surface behind them and never against one another, so two adjacent fills
   cannot be told apart by lightness: in the light theme `--warn`|`--bad` is 1.00:1,
   `--warn`|`--ok` and `--ok`|`--bad` 1.01, the rest 1.16–1.17; in the dark theme
   `--info`|`--ok` is 1.01, `--ok`|`--bad` 1.14, up to 1.37 for `--warn`|`--bad`. A
   hairline between two such fills says "a boundary is here", never "this part is
   that group". With a **gap** every segment gets an edge against the panel instead,
   where the same four tokens measure 4.6–6.8:1 against `--surface`; and the gap is
   the only thing that separates the two segments which legitimately share `--bad`
   (`views._ATTACHMENT_GROUP_TONES` — both block the filing). The second modality is
   therefore the gap plus the key under the track, and no segment needs a hatch.

   The track has **no background of its own**: what shows between the segments is
   the panel, which is the very surface those contrasts are measured against.

   Geometry is `flex-grow` carrying the raw count (`style="flex-grow: 12"`), not a
   percentage — a percentage is a decimal, `3.7` renders as `3,7` under the Slovak
   locale and CSS does not read that number at all. Geometry from data is the one
   thing that may be written inline (§4.4). The gaps take their width off the free
   space, so the parts still come out in the ratio of the counts.

   `min-width` keeps one row out of sixty from vanishing. It makes the proportions
   lie by a pixel or two, and that is the cheaper lie: a segment nobody can see is a
   group the picture denies, while the exact number stands in the key underneath. No
   `overflow: hidden` on the track — the radius belongs to the segments, and each of
   them carries its own.                                                        */

.composition { display: flex; flex-direction: column; gap: var(--s-2); }
/* The sentences of the block stand in a flex column that already has a gap, so the
   browser's paragraph margins would be a second spacing laid over the first — the
   same reset `.state-block p` makes and for the same reason. */
.composition > p { margin: 0; }
.composition-track {
  display: flex;
  gap: var(--s-1);
  /* The height of `.bar`, deliberately: two tracks of one system that differed by
     two pixels would read as two components. */
  height: 10px;
}
.composition-part {
  /* 4px and not a number chosen by eye: it is the step of the §1.3 grid and the width
     the state bar of `.message` already uses, so the narrowest segment is exactly as
     wide as the thinnest thing the system draws on purpose. */
  min-width: 4px;
  border-radius: 999px;
  /* A part with no state named. Neutral has no token of its own (§1.1), so it takes
     the same `--border-strong` the neutral dot does. */
  background: var(--border-strong);
}
.composition-part--ok   { background: var(--ok); }
.composition-part--warn { background: var(--warn); }
.composition-part--bad  { background: var(--bad); }
.composition-part--info { background: var(--info); }
/* The key under the track is `.legend` (§4.4) — one legend component, not two — and
   its swatch **is** a segment: the same class, so a segment that ever changes colour
   cannot leave its sample behind. A `<span>` rather than the legend's own `<i>`,
   whose rule paints the accent dot of a chart bubble and outranks the segment. */
.legend-item > .composition-part {
  width: 8px; height: 8px; min-width: 0; flex: none;
}

/* --- Meter with a limit marker --------------------------------------------
   A guardrail per §5 of design.md: a bare "compliant" is not enough, it must
   be visible where the limit is and how much remains. The component knows no
   domain object: the seats of a plan put the marker at the end of the track,
   the 85 % repayment rule puts it partway along it, and both are the same
   meter — the caller passes the position, the state and the sentence.

   A value with **no** limit is not a meter: `components/meter.html` hands that
   branch to `kpi.html`, so `.meter` always draws a `.bar` with `-marker`. That
   is why it needs no rule for the size of its value — the number in `--t-xl` is
   the tile's, and this component's number lives in the scale.

   The track itself is `.bar` (BL-91). What is left here is the wrapper and its
   caption rows: `-scale` in mono/`--t-micro` for the two ends of the track, and
   `-label` for the sentence; the names of a divided track are `.bar-parts`. */

.meter { display: flex; flex-direction: column; gap: var(--s-1); }

.meter-scale {
  display: flex; justify-content: space-between;
  /* The two ends of the scale never run into each other: the right one may be a
     whole phrase ("1 200 € tvorby"), not just the bare limit of a plan. */
  gap: var(--s-2);
  /* Numbers, not an identity element — --ink-soft, so the scale does not read as
     clickable; --ink-muted is out at --t-micro (§6). */
  font-size: var(--t-micro); color: var(--ink-soft);
  font-family: var(--mono);
}
.meter-label { font-size: var(--t-xs); color: var(--ink-soft); }

/* --- Cost against the legal cap -------------------------------------------
   One bar per row of the calculation: the acquisition cost drawn against the
   cap of the act, the cap itself a marker on the track. The denominator is
   shared by the whole table, because two bars of equal length have to mean
   equal money (design-system.md §4.3) — and that shared denominator is this
   component's own rule, computed in the view.

   After BL-91 this is a wrapper and nothing else: the track is `.bar` and the
   captions under it are `.bar-parts`. Its four geometry rules used to be
   `.meter`'s copied under a second name.                                    */

.breakdown { display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; }

/* --- Empty and error state -------------------------------------------------- */

.state-block {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-3);
  padding: var(--s-8) var(--s-6);
  background: var(--surface);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r);
}
.state-block h2 { margin: 0; font-size: var(--t-md); font-weight: 600; }
.state-block p { margin: 0; color: var(--ink-soft); max-width: 60ch; }
.state-block--error { border-style: solid; border-color: var(--bad); background: var(--bad-soft); }

/* --- Confirmation dialog --------------------------------------------------
   A native <dialog>: focus and Esc are handled by the browser, which is more
   reliable than a hand-rolled focus trap.                                   */

dialog.confirmation {
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  color: var(--ink);
  padding: 0;
  max-width: 32rem;
  box-shadow: var(--shadow-lg);
}
/* The scrim has a token of its own — the dark theme needs a denser one, which a
   literal colour here could never express (tokens.css, §1.4). */
dialog.confirmation::backdrop { background: var(--overlay); }
dialog.confirmation .dialog-body { padding: var(--s-5); display: flex; flex-direction: column; gap: var(--s-3); }

/* --- HTMX ----------------------------------------------------------------
   The conventions are described in docs/decisions/fe-pipeline-a-htmx.md. The
   indicator is the only thing HTMX needs from the styles, and it is the loading
   state of design-system.md §10 — it stays defined even while the library itself
   is not loaded, so the first fragment finds the state ready.               */

.htmx-indicator { opacity: 0; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }

/* `left: 0` is not cosmetic. Without it the box keeps its static position, and with
   no positioned ancestor that position is measured against the page — so a label
   hidden inside a horizontally scrolled table sat at x=436 and stretched the
   document to 437px on every phone width, while the table itself scrolled correctly.
   Pinning it costs nothing: a screen reader reads it from the DOM, not from where
   the box lands. */
.visually-hidden {
  position: absolute; left: 0; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* The season of a call — one line under the screen header, built on `.bar` (BL-224).
   Deliberately **not** a `.meter`: a meter is a number against a limit and draws the
   limit as a marker inside the track, whereas the end of the submission window is the
   end of the track itself. Not a `.guard` either — it guards nothing and offers no
   way out, it only says where in the year we are.

   It takes the surface of a panel, and it is deliberately **not** one: a panel has a
   header, a body and a section heading, and would put the season into the running for
   the first look, which belongs to the list of interventions. What it borrows is only
   the plane a row needs to sit on above the panels — hence one line of content and no
   `.panel-header` anywhere in it. Name and remaining days sit on the first line, the
   track spans both columns beneath them, the two dates close it. */
.season {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, auto);
  align-items: baseline;
  gap: var(--s-2) var(--s-4);
  padding: var(--s-3) var(--s-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
}
.season > * { min-width: 0; }
.season-name { font-weight: 600; }
/* The remaining days are the fact the row exists for, so they close the first line
   where the eye leaves it. */
.season-left { justify-self: end; text-align: right; }
.season > .bar { grid-column: 1 / -1; }
/* The dates of the window, one at each end of the track they belong to. `--end` is
   the right-hand one; it is a modifier rather than a second element, because the two
   differ in nothing but which end they sit at. */
.season-range { font-size: var(--t-xs); color: var(--ink-soft); }
.season-range--end { justify-self: end; }

/* The list of interventions — the first panel of the dashboard (BL-223).
   One panel with named groups, because "a statutory deadline is running" and "this
   blocks a submission" are different questions and the screen used to answer them
   with two unrelated components in no common order.

   Not `.whats-missing-item`, which is the closest neighbour: that rail is the wizard's
   list of what one application still needs and it is sticky, single-level and scoped to
   one record. This is a portfolio-wide list whose rows say **which house**, are grouped
   by the kind of deadline, and carry a right-hand column whose meaning the group
   decides. Not `.guard` either — a guardrail card carries one number, and a row here
   carries a sentence and a way in. */
.attention { display: flex; flex-direction: column; gap: var(--s-5); }
.attention-group { display: flex; flex-direction: column; }
/* The name of the group carries what the whole group shares — the kind of deadline,
   and the deadline itself where the group has one. */
.attention-group > .eyebrow { padding-bottom: var(--s-1); }
/* dot · house and sentence · right column, and the **whole row** is the link: a
   two-word target is a hairline to hit (§6), which is why the row also keeps the
   control height. */
.attention-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(0, auto);
  align-items: baseline;
  gap: var(--s-1) var(--s-3);
  padding: var(--s-2) 0;
  min-height: var(--h-control);
  border-top: 1px solid var(--border);
  color: var(--ink);
  text-decoration: none;
}
.attention-row > * { min-width: 0; }
.attention-group > .attention-row:first-of-type { border-top: 0; }
a.attention-row:hover { background: var(--surface-2); }
/* The right-hand column. One meaning within a group — time under the statutory
   deadlines, the place the click lands under the blockers — and the state modifiers
   only ever colour a text that already says the same thing in words (§6). */
.attention-where { justify-self: end; text-align: right; font-size: var(--t-xs); color: var(--ink-soft); }
.attention-where--warn { color: var(--warn); }
.attention-where--bad { color: var(--bad); }
@media (max-width: 860px) {
  /* Under pressure the right column goes under the sentence rather than squeezing
     it — the sentence is what makes the row scannable. */
  .attention-row { grid-template-columns: auto minmax(0, 1fr); }
  .attention-where { grid-column: 2; justify-self: start; text-align: left; }
}

/* Guardrail card — a left bar in the state colour, label → value → derivation
   (design-system.md §4). */
.guard {
  border-left: 4px solid var(--border-strong);
  padding: var(--s-2) var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}
/* The value the card exists for (design-system.md §4.3: label → value in mono at
   --t-xl → derivation). Scoped to a direct child so the mono spans inside a nested
   .derivation keep the smaller size of the derivation — the origin of a number must
   not compete with the number. */
.guard > .mono { font-size: var(--t-xl); }
/* The state of the card is its bar, not a pill dropped inside it: the calculator's
   summary carried it in `.pill--bad`, so the state was told by a nested element
   instead of by the component that is supposed to carry it. The word stays — the
   bar is the second modality, never the replacement of the sentence (§6).
   Specificity is 0,1,0, the same as `.guard`, so these have to stand *after* the
   base declaration.

   `--warn` is "needs attention" — not a failure, an unfinished or unusual thing — and
   it arrived with its consumers (BL-702): eight cards carried that state in a
   `.pill--warn`, most of them with a `.dot--warn` inside the pill, against the sentence
   above — and the rows of *Lehoty* carried met/breached the same way in `--ok`/`--bad`.
   Their pills keep the word and lose the colour; the bar says the rest. */
.guard--ok   { border-left-color: var(--ok); }
.guard--warn { border-left-color: var(--warn); }
.guard--bad  { border-left-color: var(--bad); }
/* A pill in the card is a label and hugs its text. `.guard` is a flex column with the
   default `align-items: stretch`, so the inline-flex pill was stretched across the card
   — 856 of 892px at 1440px — and read as a band of state instead (BL-702). Aligned on
   the pill alone: the anchor and the `.derivation` of the card want the full width. */
.guard > .pill { align-self: flex-start; }
/* The way out of the state the card reports (BL-136). Where the number is a problem, the
   card carries one anchor into the zone that resolves it — the way out itself is three
   sentences long and stays there, never in a sticky column. The anchor is a control and
   takes the target of §6, exactly as `.switcher > .panel a` does; its colour it already
   has from `a`. No class of its own: `.guard` is a column with a gap and lays the link
   out unchanged, so a class would be bought for nothing but a name. */
.guard > a {
  display: flex;
  align-items: center;
  min-height: var(--h-control);
}

/* Where a number came from (design-system.md §4.3). The intermediate steps are read
   downwards as one sentence, not as a table: columns would promise a comparison
   between the steps that makes no sense. It stands inside a .guard or right under
   one — the card carries the number, this carries its origin. */
.derivation {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  padding: var(--s-3);
  background: var(--surface-2);
  border-radius: var(--r);
  font-size: var(--t-sm);
  min-width: 0;
}
.derivation-row { display: flex; justify-content: space-between; gap: var(--s-3); color: var(--ink-soft); }
/* The step that actually bound the result. The accent is emphasis; what says it is
   the word „← obmedzujúce" in the label, because colour never carries a state on
   its own (§6). */
.derivation-row--binding { color: var(--accent); font-weight: 600; }
/* What follows from the steps above — hence a rule over it rather than a gap. */
.derivation-result { color: var(--ink); border-top: 1px solid var(--border); padding-top: var(--s-1); }

/* --- Import preview ---------------------------------------------------------
   What an import would change, shown before anything is written (UC-28,
   design-system.md §4.3). Three parts read left to right as one sentence: the value
   the register holds, the value the file carries, and what the difference means.

   Colour never carries the state on its own (§6): the old value is struck through,
   the new one is named by a word in the row's own label, and `--ok` / `--info` are
   emphasis on top of that. A struck-through value keeps `--ink-muted` rather than a
   state colour — it is the value that is *going away*, and painting it red would read
   as an error where nothing is wrong.                                            */

.change-preview { display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; }
.change-preview-old {
  color: var(--ink-muted);
  text-decoration: line-through;
}
.change-preview-new { color: var(--ink); font-weight: 600; }
.change-preview-diff { color: var(--ink-soft); font-size: var(--t-sm); }
/* The difference is a value the import will write. */
.change-preview-diff--ok { color: var(--ok); }
/* …and this one it will not: an area kept because a better source already filled it,
   or an owner the cadastre stated and this file must not touch. The sentence beside it
   says which — the colour only tells the eye where to look. */
.change-preview-diff--info { color: var(--info); }

/* --- Key–value summary of one run ------------------------------------------
   Metadata of a single run or record — commit, branch, count, duration
   (design-system.md §4.3). Not `.guard`: that carries **one** number that guards
   something, this carries several that guard nothing. And not `table.data`: the
   row is the only one there is, so columns would promise a comparison between runs
   that this is not.                                                            */

.summary { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-6); }
.summary > div { display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; }
/* The key is the small-caps micro-caption `.eyebrow` carries elsewhere; the value is
   mono, because every one of them is an identifier or a number. */
.summary .mono { font-size: var(--t-base); overflow-wrap: anywhere; }

/* --- Photos attached to a fault report -------------------------------------
   At most three (`maintenance.photos.MAX_PHOTOS`), so a wrapping row and not
   `.grid`: three auto-fit tracks would stretch a single photo across the panel and
   make one report look like a gallery. Each thumbnail links to the full photo,
   because the small one answers "is there water" and the full one answers "where
   is it coming from".

   `aspect-ratio` with `object-fit: cover` on purpose: photos arrive portrait and
   landscape from the same entrance, and a row of mixed heights reads as damage to
   the layout rather than as evidence. The intrinsic size travels in the HTML
   `width`/`height` attributes (the row stores them), so the box is reserved before
   the image arrives — geometry from data, the §4.4 exception, and the only thing
   that keeps the panel from jumping when three photos load.                     */

.photo-strip { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.photo-strip img {
  display: block;
  width: 12rem;
  max-width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  /* The plate under a photo that has not loaded — and under a transparent PNG that
     was flattened onto white by the re-encode. */
  background: var(--surface-2);
}

/* --- Grid of panels ---------------------------------------------------------
   Two panels that are **read as a pair** (coverage against the floor × the same
   coverage against the size of the module). Not `.grid` — that hands tracks to
   fields inside a panel, this lays panels out on the screen. The breakpoint is
   1080 px from §5.1, not a fourth number of its own.                           */

.panel-grid { display: grid; gap: var(--s-4); align-items: start; }
@media (min-width: 1080px) {
  .panel-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The ratio belongs to a modifier, never to an inline style (§5). */
  .panel-grid--two { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
}

/* --- Chart ------------------------------------------------------------------
   Inline SVG over tokens, never `<canvas>` and without a library
   (design-system.md §4.4): the theme switches on a `data-theme` attribute and
   `var()` inside SVG repaints itself, whereas a canvas would have to listen on a
   `MutationObserver` and redraw. A library would bring its own palette and with it
   a second home for colour.

   The ink of the chart does **not** come from the state scale: the grid and the
   axis have a pair of tokens of their own, derived from the neutrals, because a
   guide line carrying a hue reads as data. The mark is `--accent`; `--bad` on it
   means what it means everywhere else (§1.1).

   **The user units of the viewBox are CSS pixels**, which is what lets the captions
   inside the SVG take a `--t-*` token instead of a number picked to look right at
   one size. A viewBox of 0..100 would have forced a hand-tuned `font-size`, i.e. a
   value with a second home (§1).

   Geometry is computed in the **view**, not here and not in the template: the
   shared denominator is a property of the whole set, so two bubbles of the same
   size have to mean the same thing.                                            */

.chart { display: block; width: 100%; height: auto; }
.chart-grid { stroke: var(--chart-grid); stroke-width: 1; }
.chart-axis { stroke: var(--chart-axis); stroke-width: 1.5; }
.chart-label { fill: var(--ink-soft); font-size: var(--t-micro); font-family: var(--mono); }
.chart-value { fill: var(--ink); font-size: var(--t-xs); font-family: var(--mono); }
.chart-bubble { fill: var(--accent); fill-opacity: .55; stroke: var(--accent); stroke-width: 1; }
/* A module below the risk line. The hatch is the second modality — `--warn` and
   `--bad` are 1,5 ΔE apart for a deuteranope, so a colour on its own would say
   nothing to them (§6). The legend carries the same fill *and* the same hatch. */
.chart-bubble--risk { fill: var(--bad); stroke: var(--bad); fill-opacity: .5; }

/* --- Legend -----------------------------------------------------------------
   Explains the colours and the textures of a chart, of a tile grid and of the
   state dots alike — one component, not three. A legend that leaves out the hatch
   contradicts the second modality; a legend hidden on a phone leaves the colours
   without meaning, so it never hides under a breakpoint (§6).                  */

.legend {
  display: flex; flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  font-size: var(--t-xs); color: var(--ink-soft);
}
.legend-item { display: flex; align-items: center; gap: var(--s-2); }
/* The swatch is the 8px of the state dot (§4.3), not a size of its own: both are
   "a sample of a colour beside a word", and a second number would be a second home. */
.legend-item i {
  width: 8px; height: 8px; flex: none;
  border-radius: var(--r-sm);
  background: var(--accent); opacity: .55;
  border: 1px solid var(--accent);
}
.legend-item--risk i {
  background: var(--bad); opacity: .5; border-color: var(--bad);
  background-image: repeating-linear-gradient(45deg, transparent 0 2px, var(--surface) 2px 4px);
}
.legend-item--exception i {
  background: var(--surface-2); border-style: dashed; border-color: var(--border-strong); opacity: 1;
}

/* --- Sčítacia mriežka -------------------------------------------------------
   Zápis hlasov, a **stĺpce sú vždy voľby** (BL-687, RQ-21). Riadok je to, čo sa práve
   sčítava — otázka pri písomnom hlasovaní, byt na schôdzi —, takže obidva režimy sú
   jedna komponenta s dvoma obsahmi, nie dve obrazovky.

   Popisok voľby stojí **raz v hlavičke**, nie pri každom ovládači. To nie je úspora
   znakov: pri siedmich otázkach to boli štyri popisky sedemkrát, ovládače rôznej šírky
   a na otázku samu zostalo 450 px z 810. S hlavičkou má otázka 58 % šírky a bunky sú
   rovnako široké **z konštrukcie**, nie náhodou — to robí `table-layout: fixed`, bez
   ktorého si šírku berie obsah a stĺpce sa rozídu riadok od riadku.

   **Nie je to `table.data`**: tá zobrazuje, toto zapisuje. Bunka je cieľ kliknutia
   s `--h-control` (§6) a stav v nej nesie rádio, nie len výplň — farba nikdy nie je sama.
   A nie je to `.picker-option`: tá je riadok dlhého zoznamu s hľadaním (RQ-19).        */
table.tally {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: var(--t-sm);
}
table.tally th {
  text-align: left;
  font-weight: 400;
  color: var(--ink);
  padding: var(--s-2) var(--s-3) var(--s-2) var(--s-5);
  vertical-align: middle;
}
table.tally thead th {
  font-size: var(--t-micro);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-soft);
  background: var(--surface-2);
  padding: var(--s-2) var(--s-1);
}
/* Šírka stĺpca je **najdlhšie slovo jeho popisku**, nie okrúhle číslo. „NEPLATNÝ“ meria
   v typografii hlavičky (`--t-micro` 10,5 px, verzálky, `--ls-eyebrow` .14em) ≈ 68 px,
   kdežto 4,5 rem dávalo 64 px obsahu — hlavička sa preto lámala uprostred slova
   („NEPLATN / Ý HLAS“) v **každej** šírke vrátane 1920 px (#704). 5 rem bez vnútornej
   výplne dáva 80 px; popisok je centrovaný, takže odstup robí voľné miesto v stĺpci
   a nie výplň, ktorá by z tých 80 px ubrala späť. Selektor preto musí byť **rovnako
   silný** ako `table.tally thead th` o pár riadkov vyššie: holé `.tally-head` (0,1,0)
   by výplň neprebilo, deklarácia by bola tichá ozdoba a výpočet vyššie by bol o 8 px
   nadsadený (nález oponentúry BL-704).

   `overflow-wrap: normal` ruší `break-word`, ktoré sem dedí `body` — pravidlo pre IBAN
   a mená súborov. Pri IBANe je lámanie slova záchrana, v hlavičke stĺpca vada: ďalší
   popisok, ktorý sa nezmestí, má pretiecť nahlas, nie sa ticho rozsypať po písmenách. */
table.tally thead th.tally-head {
  width: 5rem;
  padding-inline: 0;
  text-align: center;
  white-space: normal;
  overflow-wrap: normal;
  line-height: 1.2;
}
/* Stĺpec, ktorý voľbu **ruší**, nie je piata voľba — vlastná linka zľava ho oddelí.
   Bez nej sa hlavička „Bez voľby" čítala ako pokračovanie stĺpca „Neplatný".
   Šírku **nedeklaruje** — nesie ju `.tally-head`, ktorý ten istý `<th>` nesie tiež,
   a druhý zápis tej istej hodnoty je druhý domov jedného faktu (pravidlo 2). */
.tally-head--clear { border-left: 1px solid var(--border-strong); }
table.tally td { padding: 0; border-top: 1px solid var(--border); }
table.tally tbody th[scope="row"] { border-top: 1px solid var(--border); }
.tally-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--h-control);
  border-left: 1px solid var(--border);
  cursor: pointer;
}
.tally-cell input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
.tally-cell:hover { background: var(--surface-2); }
.tally-cell:focus-within { outline: 2px solid var(--accent); outline-offset: -2px; }
.tally-cell.c-none { border-left-color: var(--border-strong); }
/* Popisok voľby pri ovládači. Nad zlomom ho nesie hlavička stĺpca, takže je **vizuálne**
   skrytý — nie `display: none` a nie `aria-hidden`.

   Je to jediné prístupné meno, ktoré to rádio má: `<th>` stĺpca nepomenuje formulárový
   ovládač v žiadnej šírke, takže s `aria-hidden` na tomto texte ohlasoval odčítač päť
   nepomenovaných prepínačov v rade (§6, nález oponentúry BL-688). Zopakovanie oproti
   hlavičke je cena, ktorú za pomenovaný ovládač stojí za to zaplatiť. */
.tally-label {
  position: absolute;
  /* `left: 0` nie je kozmetika — je to tá istá vada, akú `.visually-hidden` už raz
     zaplatila: bez nej si box drží statickú pozíciu, meranú voči stránke, a skrytý
     popisok vnútri vodorovne posúvanej tabuľky roztiahne dokument. */
  left: 0;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  font-size: var(--t-xs);
  color: var(--ink-soft);
}
/* Výplň zaškrtnutej bunky je **tá istá výplň, akú nesie dlaždica výsledku** — čo referent
   klikne, to potom vidí v pruhu aj v zápisnici. Preto sa deklaruje raz, tu, a `.tiles--votes`
   nižšie ju číta: legenda, ktorej vzorka sa líši od značky, vysvetľuje obrázok, čo na
   obrazovke nie je (§4.4). */
.tally-cell.c-for:has(input:checked) {
  border-color: var(--ok);
  background: var(--ok-soft);
}
.tally-cell.c-against:has(input:checked) {
  border-color: var(--bad);
  background-color: var(--bad-soft);
  background-image: repeating-linear-gradient(45deg, transparent 0 2px, var(--surface) 2px 4px);
}
.tally-cell.c-abstained:has(input:checked) {
  border-color: var(--border-strong);
  background: var(--surface-2);
}
.tally-cell.c-invalid:has(input:checked) {
  border-color: var(--warn);
  background-color: var(--warn-soft);
  background-image: repeating-linear-gradient(-45deg, transparent 0 2px, var(--surface) 2px 4px);
}
.tally .ord { font-family: var(--mono); color: var(--ink-muted); }
/* Kto lístok podpísal — veta, nie tri polia. Pri byte s jediným vlastníkom (väčšina)
   nie je z čoho vyberať a tri polia stáli 100 px výšky karty. */
.ballot-who {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2) var(--s-4);
  margin: 0;
}
/* **Mriežka sa pýta svojho rámu, nie okna** (§5.1, tá istá úvaha ako `.rowcard`). Stojí
   vo `.workspace-sections`, teda v stĺpci, ktorý výrez nepredpovedá: rám je tam o ~300 px
   užší než stĺpec obsahu a o ~508 px užší než okno. So zlomom na okne mala otázka medzi
   861 a 1180 px okna 0 až 310 px a jeden riadok meral 6 698 px — o pixel nižšie sa tá
   istá stránka rozpadla na karty a bola v poriadku (#704). `.table-frame` je pomenovaný
   kontajner už od #559; mriežka bola jediná komponenta v ňom, ktorá sa ho nepýtala.

   **Prah je súčet, nie štvrtý zlom.** Päť stĺpcov volieb je 5 × 5 rem = 25 rem a predmet
   riadku potrebuje 20 rem, z ktorých 2 rem zoberie vlastná výplň `table.tally th` — na
   prózu teda zostáva ~288 px ≈ 46 znakov `--t-sm`, spodná hrana čitateľnej sadzby.
   25 + 20 = 45 rem. Pod ním sa hlavička stráca, popisok sa vracia k ovládaču a riadok sa
   rozpadne na predmet a päť rovnakých buniek pod ním.

   **Jeden prah pre oba obsahy**, hoci Schôdza by uniesla užší rám: predmet jej riadku je
   štítok („1 byt“), nie próza. Druhý prah sa však nedá napísať bez druhého opisu celého
   bloku kariet — prelude `@container` neprijme `var()` — a dva opisy jedného rozhodnutia
   sa rozídu pri prvej zmene. Rozhoduje náročnejší obsah. */
@container table-frame (width < 45rem) {
  table.tally,
  table.tally tbody,
  table.tally td { display: block; width: auto; }
  table.tally thead { display: none; }
  table.tally tbody tr {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: var(--s-1);
    padding: var(--s-2) 0;
    border-top: 1px solid var(--border);
  }
  table.tally tbody th[scope="row"] {
    display: block;
    grid-column: 1 / -1;
    border-top: 0;
    padding: 0 0 var(--s-2);
  }
  table.tally td { border-top: 0; }
  .tally-cell {
    flex-direction: column;
    gap: 2px;
    border: 1px solid var(--border-strong);
    border-radius: var(--r-sm);
    padding: var(--s-1) 0;
  }
  /* Pod prahom hlavička mizne, takže sa popisok vracia k ovládaču aj očiam — **a zalomí
     sa v bunke**. Tri deklarácie, tri poschodia jednej odpovede (#703): zalom normálne,
     kde slovo nestačí rozdeľ na slabike, a kde to engine nevie, zlom kdekoľvek — ale von
     nikdy. Bez nich si viditeľný popisok niesol `white-space: nowrap` z receptu skrytého
     textu vyššie a „Neplatný hlas“ (74 px) vystupoval z bunky (62 px pri 390 px výrezu)
     o 5,8 px na každú stranu (74 px textu proti 62,4 px obsahovej šírky bunky),
     sedemkrát na stránku, a prekrýval susedné „Bez voľby“.

     `hyphens: auto` je **vylepšenie, nie základ** (§5.1): `<html lang>` je nastavený,
     takže Chromium delí slovensky; engine bez slovníka neurobí nič a podlahu drží
     `overflow-wrap: anywhere` pod ním. Preto tu nestojí žiadny `-webkit-` prefix. */
  .tally-label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
    white-space: normal;
    hyphens: auto;
    overflow-wrap: anywhere;
    text-align: center;
  }
}

/* --- Tile grid --------------------------------------------------------------
   **One tile = one thing**, never an aggregate: one invariant, one claim, one flat
   in a vote. A tile that cannot be described in one word does not belong in the
   grid — that is what `.kpi` and `.guard` are for.

   An exemption is not silence: it looks different (`--vynimka`), it is in the
   legend and it **carries its reason** in the table beside the grid. Without that
   it turns into a permanent alibi.                                             */

.tiles { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.tiles-item {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--h-control); min-height: var(--h-control);
  padding: 0 var(--s-2);
  border: 1px solid var(--ok);
  border-radius: var(--r-sm);
  background: var(--ok-soft);
  color: var(--ink);
  font-family: var(--mono); font-size: var(--t-xs);
}
/* The hatch, not only the fill (§6). `--surface` shows through the stripes, so the
   texture reads on both themes without a token of its own. */
.tiles-item--bad {
  border-color: var(--bad);
  background-color: var(--bad-soft);
  background-image: repeating-linear-gradient(45deg, transparent 0 2px, var(--surface) 2px 4px);
}
.tiles-item--exception {
  border-style: dashed; border-color: var(--border-strong);
  background: var(--surface-2); color: var(--ink-soft);
}
/* Hundreds of items (128 claims) overflow the panel at full size. A modifier of the
   grid, not a second component — the difference is the dimension (§4.1). The click
   target of `--h-control` goes with it, which is why the tiles are **not** links:
   they are labels, and §6 governs targets, not text. */
.tiles--small { gap: var(--s-1); }
.tiles--small .tiles-item {
  min-width: 0; min-height: 0;
  padding: 0 var(--s-1);
  font-size: var(--t-micro);
}

/* --- The strip of votes -----------------------------------------------------
   The tile grid with a domain meaning of the tile (design.md §5): **one tile = one
   unit of the building**, in the order of the units, never a cluster by choice — two
   equally long groups would have to mean the same thing, and here they mean nothing.

   Which is why the boundary of the quorum is **not** drawn in here. A line at the 23rd
   tile of a strip ordered by flat number points at a flat, not at a threshold. The
   strip answers "who voted how"; whether it is enough is the `.meter` above it, where
   the fill is the votes for and the marker is the threshold. Two statements, two
   components (BL-98).

   The fill says what the owner **said**, not what it does to the arithmetic: for and
   against are the two opinions, an abstention and an invalid paper are taking part
   without one, and no tile filled at all is not taking part. For the threshold the last
   three are the same thing — the strip is not the place that says so, the meter is.

   Always `--small`, whatever the size of the building: the set is the building's units
   and that has no upper bound, so a strip picking its own size would need a threshold
   nobody owns, and two buildings drawn at two sizes would stop being comparable.

   "Za" is the plain tile and "proti" is `--bad`, i.e. the fill and the hatch of the grid
   itself; the three below are this strip's own. Each is declared **once** for the tile
   and for its swatch in the legend, because a legend whose sample differs from the mark
   explains a picture that is not on the screen (§4.4).                              */

/* Took part without an opinion. Not `--bad`: an abstention is not a vote against, and
   one fill for both would make the strip lie about the result. There is no token for
   neutral (§1.1), so it wears the pair a pill without a state wears. */
.tiles--votes .tiles-item--abstained,
.legend-item--abstained i {
  border-color: var(--border-strong);
  background: var(--surface-2);
  color: var(--ink-soft);
  opacity: 1;
}
/* A paper returned with nothing marked on it [CL-BYT-32]: it counts into the base and
   into nothing else, which is worth noticing — `--warn` plus a hatch of its own. The
   angle is the mirror of the one `--bad` wears, so the two textures are told apart
   without the colour (§6). */
.tiles--votes .tiles-item--invalid,
.legend-item--invalid i {
  border-color: var(--warn);
  background-color: var(--warn-soft);
  background-image: repeating-linear-gradient(-45deg, transparent 0 2px, var(--surface) 2px 4px);
  opacity: 1;
}
/* No vote at all — the absence of a row, not a choice (`voting.models.VoteChoice`). It
   is drawn from the difference against the units of the building, so an empty tile is
   the only honest picture. Dotted, not dashed: dashed is an exemption with a reason,
   and a flat that did not vote owes nobody one. */
.tiles--votes .tiles-item--not-voted,
.legend-item--not-voted i {
  border-style: dotted; border-color: var(--border-strong);
  background: var(--surface);
  color: var(--ink-soft);
  opacity: 1;
}
/* --- Pruh postupu zápisu ----------------------------------------------------
   Ten istý obrázok ako pruh výsledku, s inou otázkou: nie „kto ako hlasoval", ale
   „koľko zostáva". Vždy `--small`, lebo je to **obrázok, nie navigátor** — namerané
   40 px pri 69 bytoch a 148 px pri 300, proti 136 a 568 px klikateľnej varianty, ktorá
   by u veľkého domu zabrala vyše polovice výrezu, aby povedala „zostáva 143". Skok na
   byt obstará odkaz na riadku tabuľky pod ním (BL-687).

   **Výplne volieb tu byť nesmú.** V pruhu výsledku znamená zelená „za", takže dom, ktorý
   úver zamietol, by mal zelený pruh postupu — dva stavy preto majú vlastný nízkochromatický
   pár a §6 drží **tvar okraja** (plný proti bodkovanému), nie farba.                     */
.tiles--progress .tiles-item--recorded {
  border-color: var(--border-strong);
  background: var(--surface-2);
  color: var(--ink);
}
.tiles--progress .tiles-item--waiting {
  border-style: dotted;
  border-color: var(--border-strong);
  background: var(--surface);
  color: var(--ink-muted);
}
/* Otvorený lístok — jediné miesto pruhu, kde je accent, a preto je na obrazovke práve
   jedno. Prstenec, nie len výplň: pri `--small` dlaždici je plocha 8 × 8 px sotva vidieť. */
.tiles-item--now {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
  box-shadow: 0 0 0 2px var(--accent);
  font-weight: 600;
}
.legend-item--recorded i { background: var(--surface-2); border-color: var(--border-strong); }
.legend-item--now i { background: var(--accent-soft); border-color: var(--accent); }

/* The swatch of the plain tile. The legend's own sample is the accent of a chart bubble,
   which is not what a tile is filled with. */
.legend-item--ok i {
  border-color: var(--ok);
  background: var(--ok-soft);
  opacity: 1;
}

/* --- "Where to start" list --------------------------------------------------
   An ordered list of what is worth opening first. **The order is carried by the
   `<ol>`**, not by the rendered number — otherwise a screen reader never hears it
   and a filter throws it off. Not `.readiness`: that says what is missing, this
   says what to begin with, and those are different statements.                 */

.list-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.step-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--s-2) var(--s-3);
  padding: var(--s-2) 0;
  border-top: 1px solid var(--border);
}
.list-steps > .step-row:first-child { border-top: 0; }
/* The ordinal is decoration for a screen reader — the `<ol>` already carries the
   order, and reading it twice is noise. It is not `.step`: that is the numbered
   circle in a panel header of the calculator (§4.3). */
.step-row > .mono:first-child { color: var(--ink-muted); font-size: var(--t-xs); }
.step-row .hint { grid-column: 2 / -1; }

/* --- The ways into an empty portfolio ---------------------------------------
   An ordered offer of the ways data gets in (RQ-06). Not `.state-block`: an empty
   state carries one sentence and buttons, and each of these ways needs a sentence
   of its own plus the formats it reads — three buttons under one sentence cannot
   say which of them saves a manager an afternoon. Not `.list-row--register`
   either: that row is caption · sentence · **control**, aligned across siblings on
   a fixed caption track, and here the left column stacks three things while the
   right one is a noun, not something to operate.

   The second column carries one meaning in the whole panel — the scope the way
   founds — which is what lets the eye compare the three by running down it.       */

.paths { display: flex; flex-direction: column; gap: var(--s-2); }
.paths-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, auto);
  gap: var(--s-1) var(--s-4);
  padding: var(--s-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
}
/* The quietest of the three, and the only difference between them: order and
   weight carry the same recommendation, so the last way loses the surface it
   would otherwise stand on rather than gaining a warning of its own. */
.paths-item--quiet { background: none; border-style: dashed; }
.paths-name { font-weight: 600; color: var(--ink); }
.paths-note { margin: 0; color: var(--ink-soft); max-width: 60ch; }
.paths-formats { margin: 0; font-family: var(--mono); font-size: var(--t-xs); color: var(--ink-muted); }
/* Spanning every row of its item, so the scope sits beside the whole way and not
   beside its first line. `start`, because a two-line sentence must not drag it
   down to the middle of nothing. */
.paths-scope {
  grid-column: 2; grid-row: 1 / -1; align-self: start; justify-self: end;
  margin: 0; color: var(--ink-muted); font-size: var(--t-sm); text-align: right;
}
/* One column below the narrow break (§5.2) — the same reason `.list-row--register`
   folds: on a phone the scope would leave the sentence a word wide. */
@media (max-width: 860px) {
  .paths-item { grid-template-columns: minmax(0, 1fr); }
  .paths-scope { grid-column: 1; grid-row: auto; justify-self: start; text-align: left; }
}

/* --- Bug reporting widget -------------------------------------------------
   A beetle fixed to the **viewport**, in the bottom-left corner of the window
   and not of the page (BL-274). It sits outside `.app` in base.html, or the sticky
   header's stacking context would put it underneath.

   **The `z-index` is what keeps it reachable, and that is not a war but the point of the
   control.** It was written here as the only fixed element in the stylesheet and needing no
   stacking order at all; that stopped being true with the help panel (BL-638) and again with
   the bar of `.whats-missing` (BL-580), whose open list runs to the foot of the viewport and
   covered the circle — measured on 390 × 844, a click on it landed on `.panel-body`. A
   reporter of faults has to be reachable **while** the interface is doing something odd,
   which is exactly when something is over it. It stays under the header (20), the help panel
   (25) and the skip link (30): those three are the shell, and the shell outranks a widget.

   **The left corner, and that is the whole of #558.** Every primary action of this
   interface stands at the right edge, and this stylesheet is what puts it there:
   `.save-bar .actions` and `.dialog-body .actions`. A circle fixed to the right
   corner of the window therefore sat on top of "Uložiť …" whenever a save bar was
   near the foot of the viewport — and at the end of a document it stayed there,
   because there was nothing left to scroll. Reserving a strip at the foot of
   `.content` would clear that last bar only and leave every bar above it: the corner
   is the cause. The same corner at every width, too — a control that moves at a
   breakpoint is a control looked for twice. Above 860px the bottom-left belongs to
   the navigation, which fills itself from the top, so its foot is empty.

   **Below 860px it is not** (BL-700): the navigation becomes a band at the top, the
   content starts at x = 0 and the circle lies on it — on the hint of the last attachment
   row, on the subtext of the calculator's last panel. The corner stays the same, for the
   reason above, and what is paid for it is a strip at the foot of `.content`, so that
   the end of every screen can be scrolled out from under the circle: its own height and
   its offset twice, once below it and once as air above it. While the page scrolls the
   circle passes over the content like any floating control, and nothing under it is out
   of reach. The strip here is not the argument #558 rejected: that one was a strip
   *instead of* the corner, for a corner that stood on every action; this one is a strip
   *with* the corner, for the one row nothing could scroll away. Only where the circle
   is — `EDILIS_FEEDBACK` off leaves no strip behind — and at every width on a screen
   that has no navigation to leave that corner empty.

   Deliberately quiet: `--surface` and a border, not the accent. It is on every
   screen of the application and a permanently coloured circle in the corner would
   compete with the primary action of whatever the user actually came to do (§9 —
   nothing shouts unless it has to).                                         */

.bug-widget {
  position: fixed;
  left: var(--s-5);
  bottom: var(--s-5);
  z-index: 15;
  display: grid;
  place-items: center;
  width: var(--h-control);
  height: var(--h-control);
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-muted);
  box-shadow: var(--shadow);
}
.bug-widget:hover { color: var(--ink); border-color: var(--border-strong); }
@media (max-width: 860px) {
  body:has(> .bug-widget) .content {
    padding-block-end: calc(var(--h-control) + 2 * var(--s-5));
  }
}
/* A screen with no navigation at all (the gateway, help, `.body--reading`) has no foot of
   a navigation in that corner at any width: at 900 × 600 the circle lay on "Späť na
   všetky témy" at the end of a help article (review of BL-700). */
body:has(> .bug-widget) .body:not(:has(> .sidebar)) .content {
  padding-block-end: calc(var(--h-control) + 2 * var(--s-5));
}

/* The dialog is the confirmation dialog's sibling, wider because it holds a form
   rather than a question. Everything else — the scrim token, the padding of the
   body — is shared, so the application has one dialog and not two.            */
dialog.bug-dialog {
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  color: var(--ink);
  padding: 0;
  width: min(34rem, calc(100vw - 2 * var(--s-5)));
  box-shadow: var(--shadow-lg);
}
dialog.bug-dialog::backdrop { background: var(--overlay); }
dialog.bug-dialog .dialog-body { padding: var(--s-5); display: flex; flex-direction: column; gap: var(--s-4); }

/* A floating button is furniture of the screen, not of the document. */
@media print { .bug-widget, dialog.bug-dialog { display: none; } }

/* --- What a printed page keeps (BL-435) -------------------------------------
   The súpis stapled to the paper attachments is a screen, not a generated form: the
   fund publishes none and a second home for a list the screen already holds would be
   one more thing to keep in step. What print takes away is the **furniture** — the
   header, the navigation and every control — and what it keeps is the document.

   No new class anywhere: the rules address the shell by the names it already has, so
   the catalogue of §4.3 gains nothing to keep in step either. A page that wanted to
   print differently would be the moment for one. */
@media print {
  .header,
  .sidebar,
  .actions,
  .save-bar,
  /* The palette is furniture like the header it hangs from — and an open dialog would
     otherwise print over the document it was opened on top of. */
  dialog.jump,
  .messages { display: none; }
  /* The panel is a card on screen and a heading with a body on paper: its shadow and
     its border cost toner and say nothing without the colour they carry on screen. */
  .panel { box-shadow: none; border: 0; break-inside: avoid; }
  /* Folded is a state of the **screen**, not of the document (BL-580). Left alone, the same
     step printed from a narrow window would carry the head of the rail and nothing under it,
     and from a wide one the whole gate — the paper would depend on the width of the window
     it was printed from. The bar also stops sticking and stops being capped: on paper there
     is no viewport to stay inside, and a `max-height` with a scrollbar is a list cut off at
     the fold. The toggle goes with the rest of the controls above. */
  .whats-missing-toggle { display: none; }
  .whats-missing[data-rail="bar"]:has(.whats-missing-toggle[aria-expanded="false"]) > .panel-body {
    display: flex;
  }
  .whats-missing[data-rail="bar"] { position: static; max-height: none; }
  .whats-missing[data-rail="bar"] > .panel-body { overflow-y: visible; }
  /* The same sentence for the fold a group gets from its width (BL-646): printed from a
     narrow window the step would be five headings and no attachments at all. */
  .panel-fold { display: none; }
  .panel:has(> .panel-header > .panel-fold[aria-expanded="false"]) > .panel-body {
    display: flex;
  }
  .data { break-inside: auto; }
  .data tr { break-inside: avoid; }
}

/* --- The wording of a voting question, filled in place (BL-330) -------------
   The prose is legally reviewed text out of the catalogue and the clerk does not
   want to touch it; what they came for are the holes. So the paragraph reads as a
   paragraph and the holes are inputs inside the line — not a five-line textarea
   with `[[…]]` to hunt for by eye. Rewriting the prose is behind a switch (§4.3).  */
.wording {
  /* Line height above the interface default: an input in the middle of a line makes
     the line taller than the text around it, and at the base 1.55 of `body` the sentence would
     read as a stack of unrelated rows. */
  line-height: 2.2;
  white-space: pre-wrap;
}
/* The box and its unit travel together — a sign that wrapped onto the next line
   would say the amount belongs to the sentence after it. */
.wording-hole { display: inline-flex; align-items: baseline; gap: var(--s-1); white-space: nowrap; }
/* A field inside a sentence, so it carries the field's boundary (WCAG 1.4.11, 3:1)
   without the field's block layout: `width: 100%` would push it onto a line of its
   own, and the height comes from the line rather than from --h-control, which on a
   coarse pointer the input still reaches through its padding. */
.wording-input {
  font: inherit;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  padding: var(--s-1) var(--s-2);
  max-width: 100%;
}
.wording-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The unit is printed, never typed: it comes from the type of the placeholder and
   `Program.currency`, so it is not part of the value and must not look editable. */
.wording-unit { color: var(--ink-soft); font-size: var(--t-sm); }

/* --- What the application changed under a prepared vote (BL-330) ------------
   One panel above the questions, not a message beside each: three changed values in
   three questions are one piece of news with one take-over. It is an offer and it
   looks like one — the text the clerk has read must not move on its own.          */
.drift {
  border: 1px solid var(--warn);
  border-radius: var(--r);
  background: var(--warn-soft);
  padding: var(--s-3) var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.drift-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-1); }
.drift-list s { color: var(--ink-soft); }

/* --- Public entrance page (BL-390) -------------------------------------------
   The one screen read by people who have no account yet. It is built from the
   components above wherever the design drew something the application already has
   — panel, pill, kpi, bar, list-row, summary, guard — and adds only what a marketing
   page needs and a workspace never does: full-bleed bands, a two-column hero, a
   diagram, a year strip and a grid of framed excerpts. Everything stands on tokens;
   the type stays on the seven-step scale of §1.2, which is why the hero heading is
   `--t-xl` in Spectral Light rather than a display size of its own.                */

/* The page owns its own rhythm: bands paint edge to edge, so the content column gives
   up its padding the same way it gives up its gateway measure for `.body--wide`. */
.content:has(> .landing) { padding: 0; gap: 0; }
.landing { display: flex; flex-direction: column; min-width: 0; }

/* A band is a full-width section; its inner measure is the reading column of the
   page, centred, wider than a workspace panel and narrower than the screen. */
.band {
  border-top: 1px solid var(--border);
  padding-block: clamp(var(--s-10), 7vw, calc(var(--s-10) * 2));
}
.band--tint { background: var(--surface-2); }
.band-inner {
  max-width: 68rem;
  margin-inline: auto;
  padding-inline: clamp(var(--s-5), 4vw, var(--s-10));
}
.band-head {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-bottom: clamp(var(--s-6), 4vw, var(--s-10));
}
.band-title {
  font-family: var(--serif);
  font-size: var(--t-xl);
  font-weight: 500;
  line-height: 1.2;
  margin: 0;
  text-wrap: balance;
}

/* The hero: the sentence on the left, the product on the right. The panel is the
   picture — an excerpt of the application over an invented house, not a screenshot,
   so it follows the theme and the tokens like every other panel. Below 1080px the
   panel drops under the copy, the same breakpoint at which a pair of panels stops
   reading side by side (§5.1). */
.hero { padding-block: clamp(var(--s-10), 8vw, calc(var(--s-10) * 2)); }
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(var(--s-8), 5vw, calc(var(--s-10) + var(--s-6)));
  align-items: center;
}
.hero-copy { display: flex; flex-direction: column; gap: var(--s-5); }
.hero-copy > .subtext { font-size: var(--t-md); }
.hero-title {
  font-family: var(--serif);
  font-size: var(--t-xl);
  font-weight: 300;
  line-height: 1.2;
  margin: 0;
  text-wrap: balance;
}
.hero-panel { justify-self: end; width: min(100%, 30rem); }
@media (max-width: 1080px) {
  .hero-grid { grid-template-columns: minmax(0, 1fr); }
  .hero-panel { justify-self: stretch; width: 100%; }
}

/* A KPI tile that is not a link still wants the frame of one on a tinted band —
   `a.kpi` carries the frame because the tile is the target; here the tile is read,
   not followed, so the frame comes from a modifier rather than from a fake anchor. */
.kpi--framed {
  padding: var(--s-4);
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
}

/* The diagram: what the customer has → Edilis → what comes out. Five tracks — a
   column of sources, an arrow, the application icon, an arrow, a column of
   documents — and below 860px the same five stacked, with the arrows turned. The
   sources are plain cards; the documents wear a folded corner, because the whole
   point of the drawing is that data goes in and paper comes out. */
.flow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto minmax(0, 1fr);
  gap: var(--s-5) var(--s-4);
  align-items: center;
}
.flow-col {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  align-self: stretch;
  justify-content: center;
  min-width: 0;
}
.flow-source,
.flow-doc {
  display: flex;
  flex-direction: column;
  gap: calc(var(--s-1) / 2);
  padding: var(--s-3) var(--s-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  min-width: 0;
}
.flow-doc {
  position: relative;
  border-radius: var(--r-sm);
  box-shadow: var(--shadow);
}
.flow-doc::after {
  content: "";
  position: absolute;
  top: -1px;
  right: -1px;
  width: var(--s-4);
  height: var(--s-4);
  background: var(--surface-2);
  border-left: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  border-bottom-left-radius: var(--r-sm);
  border-top-right-radius: var(--r-sm);
}
.flow-arrow { display: flex; align-items: center; justify-content: center; color: var(--accent); }
.flow-arrow svg { width: var(--s-6); height: var(--s-6); }
.flow-core {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-3);
  padding-inline: var(--s-2);
}
/* The application icon as an image: the slate plane never inverts, which is exactly
   what an inlined mark over tokens could not promise (img/README.md). The corners
   are masked here the way a platform would mask them. */
.flow-icon {
  width: 6rem;
  height: 6rem;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}
@media (max-width: 860px) {
  .flow { grid-template-columns: minmax(0, 1fr); }
  .flow-arrow svg { transform: rotate(90deg); }
}

/* The year strip: twelve months as hairlines, the submission window as a filled
   span with its two dates at the ends. **Not `.season`** — the season says where in
   the current window we are today; this says where in the year the window is at
   all. Start and width are inline, computed by the view from the call (§4.4). */
.year {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-top: clamp(var(--s-6), 4vw, var(--s-10));
}
.year-track {
  position: relative;
  height: var(--h-control);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background-image: repeating-linear-gradient(
    to right,
    transparent,
    transparent calc(100% / 12 - 1px),
    var(--border) calc(100% / 12 - 1px),
    var(--border) calc(100% / 12)
  );
}
.year-fill {
  position: absolute;
  top: var(--s-1);
  bottom: var(--s-1);
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: var(--r-sm);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  padding-inline: var(--s-2);
  min-width: 0;
}
.year-tag {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-xs);
  font-weight: 500;
  color: var(--accent-ink);
  white-space: nowrap;
}
.year-months { display: flex; }
.year-months > span {
  flex: 1;
  text-align: center;
  font-family: var(--mono);
  font-size: var(--t-micro);
  color: var(--ink-muted);
}
@media (max-width: 860px) {
  .year-months > span:nth-child(2n) { visibility: hidden; }
}

/* Four excerpts of four screens, each introduced by a serif title and one line. The
   panels are the application's own; only the grid and the title are new. Items
   align to the top of their row: a taller panel beside a shorter one is the honest
   shape of two different screens, not something to even out. */
.showcase {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(21rem, 100%), 1fr));
  gap: clamp(var(--s-6), 3vw, var(--s-10));
  align-items: start;
}
.showcase-item { display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; }
.showcase-item > .hint { margin-bottom: var(--s-3); }
.showcase-title {
  font-family: var(--serif);
  font-size: var(--t-lg);
  font-weight: 500;
  margin: 0;
  text-wrap: balance;
}

/* The printed sticker of a QR point, as it hangs by a door: black on white in both
   themes, because paper does not follow the theme. The quiet zone is inside the
   image, so the frame sits flush. */
.sticker-row { display: flex; gap: var(--s-4); align-items: center; }
.sticker {
  width: 6rem;
  height: 6rem;
  flex: none;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}

/* The form stands alone in the middle of its band, in the gateway's own 30rem —
   one question deserves the width of a login, not of a dashboard. */
.cta { display: flex; justify-content: center; }
.cta > .panel { width: min(100%, 30rem); box-shadow: var(--shadow-lg); }

.landing-footer {
  border-top: 1px solid var(--border);
  padding-block: clamp(var(--s-10), 6vw, calc(var(--s-10) + var(--s-6)));
  padding-inline: clamp(var(--s-5), 4vw, var(--s-10));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-5);
  text-align: center;
}
.landing-footer > .subtext { font-size: var(--t-sm); max-width: 44ch; }
.landing-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-2) var(--s-4);
  font-size: var(--t-xs);
  color: var(--ink-muted);
}
.landing-meta a { color: var(--ink-muted); }

/* Motion is an addition, never the carrier (§6) — and it never costs the first frame
   (#561). The stylesheet hides nothing on its own: `opacity: 0` hangs on the value
   `pending`, which only `landing.js` writes, and only on a block that starts entirely
   below the fold. Without the script, before it has run, in a link preview, in a
   screenshot and on paper the page is simply there — until #561 two whole bands were
   blank rectangles in all four. The state is an attribute, not a class (§4.1). */
@media screen and (prefers-reduced-motion: no-preference) {
  [data-reveal] { transition: opacity 550ms ease, transform 550ms ease; }
  [data-reveal="pending"] { opacity: 0; transform: translateY(var(--s-3)); }
  .showcase-item > .panel { transition: box-shadow 180ms ease, transform 180ms ease; }
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .showcase-item > .panel:hover { box-shadow: var(--shadow-lg); transform: translateY(calc(var(--s-1) / -2)); }
}

/* --- Public help centre (BL-531) --------------------------------------------
   One entry of the signpost and of the search results: the question as a heading, the
   answer under it. Two lines and not the flex of `.list-row` — the row wraps only when
   it runs out of width, so the lead would sit beside the title on a desktop and under
   it on a phone, and the lead is the answer rather than a remark after the name. The
   whole entry is the link (§6), so the target is the pair and not the four words of a
   title. */
.help-item {
  display: grid;
  gap: var(--s-1);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  color: var(--ink);
  text-decoration: none;
}
.help-item:hover { background: var(--surface-2); }
.help-item-title { font-weight: 600; }

/* --- Explainer beside a field's label (BL-533) --------------------------------
   Native `<details>`, so the open state is an attribute rather than a class (§4.1) and
   the mark itself works with no script. The body is **out of flow**: an explainer that
   pushed the form down would move the field the reader is about to type into, which is
   the one thing a help control must never do. `position: relative` sits on the
   `<details>` and not on `.field`, so the bubble is anchored to the mark and not to the
   whole row. */
.explainer { position: relative; display: inline-flex; }
/* The circle measures the label's own line — `--s-5` against the 19.4px line box of a
   12.5px label — and deliberately **not** `--h-control` (BL-640). A mark the height of a
   control made the label row of a field that carries one 21px taller than the label row
   of a field that does not, so in a `.grid` row the inputs stood at two different
   heights and a form that was correct read as broken. The click target keeps the 40/44px
   of §6 all the same — it is drawn by the pseudo-element below, and that separation of
   the drawn circle from the aimed-at area is the whole fix.

   Filled and blue, because a bare glyph inside a label reads as a typo rather than as a
   control (BL-639). `--info` is the state of "this is what the system says", which is
   exactly what a definition of the fund is; the hover and the open state deepen the same
   hue instead of introducing a second one, so the highlight says "more of this" and not
   "something else". `--surface` for the glyph on the deep fill is not a light literal —
   the token flips with the theme, so the pair holds in both: 6.77:1 light and 5.35:1 dark,
   against 5.25 and 4.74 at rest. Both are asserted over `tokens.css` by
   `tests/web/test_token_contrast.py`, not measured here in prose. */
.explainer > summary {
  list-style: none;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--s-5);
  height: var(--s-5);
  border-radius: 50%;
  border: 1px solid var(--info);
  background: var(--info-soft);
  color: var(--info);
  font-size: var(--t-xs);
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
}
.explainer > summary::-webkit-details-marker { display: none; }
/* The target of §6, `--h-control` in both axes, grown into the space where nothing else
   can be clicked: to the right, where `.field-label` has its free end; upwards, into the
   gap above the label; and downwards by **exactly** `--s-1`, which is the gap of `.field`
   and therefore stops at the top edge of the input. A halo grown symmetrically would be
   one line shorter and would take the top 6px of the input with it — the field the
   reader is aiming at would then sometimes open the help instead of taking the caret. */
.explainer > summary::after {
  content: "";
  position: absolute;
  inset-block: calc(var(--s-5) + var(--s-1) - var(--h-control)) calc(-1 * var(--s-1));
  inset-inline: 0 calc(var(--s-5) - var(--h-control));
}
.explainer > summary:hover,
.explainer[open] > summary { background: var(--info); color: var(--surface); }
/* The bubble stands on a plane of its own (BL-639). It used to be `--surface`, the very
   colour of the panel it opens over, so the only thing that separated the answer from
   the form under it was a hairline and a shadow. `--info-soft` is the plane of the mark
   that opened it, so the two read as one component; `--ink` on it measures 12.77:1 light
   and 9.92:1 dark, the `.hint` 6.45:1 and 6.10:1. */
.explainer-body {
  position: absolute;
  z-index: 2;
  top: 100%;
  inset-inline-start: 0;
  width: max-content;
  max-width: min(28rem, 78vw);
  display: grid;
  gap: var(--s-2);
  padding: var(--s-3);
  background: var(--info-soft);
  border: 1px solid var(--info);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-lg);
  /* How far back into the shell the script had to slide it — BL-701. The default is the
     zero of a bubble that fits, so this declaration changes nothing until something
     measures; what it does is give the measurement one place to land. A **length and
     therefore not an attribute**: §4.1 asks for state as an attribute, and the side the
     bubble opened to is a state — how far it then had to move is not. `translate` and not
     `inset-inline-start`, because the offset is the physical distance between two
     rectangles and inset would flip its meaning with the direction of the document.
     The fallback is `none` and not `0px`: the two paint identically, but a translation of
     zero still makes the element a containing block for anything `position: fixed` inside
     it, and a default that quietly changes what a descendant is anchored to is a default
     that will be debugged one day. */
  translate: var(--explainer-nudge, none);
}
/* Which side the bubble opens to. It opens to the **end** by default, and `web/js/help.js`
   flips it when 28rem of answer would not fit there — a field in the last column of a
   `.grid` is exactly where that happens, and until BL-639 the overflow was merely ugly:
   now the cross lives in the corner that was hanging off the screen. A state as an
   attribute (§4.1), written by the script, because no selector can measure a viewport.

   **The flip alone is not enough and never was** (BL-701): under roughly 740px neither
   side holds 28rem — at 390px the bubble is 304px against a line the mark sits in the
   middle of — so flipping an overflow on the right produced one on the left, where
   nothing measured it. What the script does now is flip first and then slide, and the
   slide is the `--explainer-nudge` above. */
.explainer[data-flip="start"] > .explainer-body { inset-inline-start: auto; inset-inline-end: 0; }
/* The cross that closes it (BL-639). Until then the bubble had exactly one way out — a
   second click on the mark — and a reader who had read it looked for the cross every
   interface has and did not find one. It is **hidden in the template and unhidden by
   `web/js/help.js`**, for the reason `aria-pressed` is not hard-coded either (§6): with
   no script the button would be a control that answers nothing, and the mark still
   closes the bubble the way it always did.
   `display` is restated for the hidden state, because the rule below sets one and an
   author-declared `display` beats the browser's `[hidden]`. */
.explainer-close {
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--h-control);
  height: var(--h-control);
  border: 0;
  background: none;
  color: var(--ink-soft);
  border-radius: var(--r-sm);
  cursor: pointer;
}
.explainer-close[hidden] { display: none; }
.explainer-close:hover { color: var(--ink); }
/* The cross itself, drawn here and not written into the markup: a glyph in a template is
   text outside i18n and `tests/web/test_language_policy.py` counts it as one, which is
   right — a mark is not a word. Two bars in `currentColor`, so the button's own hover
   carries them, the way `.rowcard-chevron` is drawn from borders one component over.
   `.helppanel-close` wears the same cross and it is the same cross: two drawings of one
   mark are how the second one comes to be 1px off. */
.explainer-close::before, .explainer-close::after,
.helppanel-close::before, .helppanel-close::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  inline-size: var(--s-3);
  block-size: 2px;
  border-radius: 1px;
  background: currentColor;
}
.explainer-close::before, .helppanel-close::before { transform: translate(-50%, -50%) rotate(45deg); }
.explainer-close::after, .helppanel-close::after { transform: translate(-50%, -50%) rotate(-45deg); }
/* Room for the cross is made only where the cross is: the script unhides the button, and
   a browser without it gets neither the control nor a hole where the control would be. */
.explainer-body:has(> .explainer-close:not([hidden])) > p { padding-inline-end: var(--h-control); }

/* --- The help centre beside the work (BL-638) ---------------------------------
   The "Pomoc" button of the header stays an ordinary link to `/pomoc/`; `web/js/help.js`
   upgrades it into this panel and fetches the same address with `panel=1`. Without the
   script — and for a visitor who arrives at the centre from a search engine — the link
   still leads to the screen it always led to, so nothing here is load-bearing for
   reading the help.

   **The shell makes room instead of being covered.** A drawer laid over the work hides
   the very field whose definition is being read, and a reader who has to close the help
   to see the question stops opening it; `.app` is padded by the panel's width, so the
   two stand side by side and the drag is what decides how the desk is split. Below
   860px there is no desk to split — the panel takes the window, as every other
   two-column shape of §5.2 does at that width.

   The width is a property of the **document**, not of the panel: the shell reads it as
   well, and one custom property written by one script is what keeps the two from
   disagreeing mid-drag. It is not in `tokens.css` because it is not a token — it is the
   reader's own answer, restored from their browser, and the value below is only where
   the first drag starts from. */
:root { --helppanel-w: 24rem; }
.helppanel {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  z-index: 25;
  inline-size: var(--helppanel-w);
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  background: var(--surface);
  border-inline-start: 1px solid var(--border-strong);
  box-shadow: var(--shadow-lg);
}
/* `display` is restated for the hidden state, because the rule above sets one and an
   author-declared `display` beats the browser's `[hidden]`. */
.helppanel[hidden] { display: none; }
/* Above the sticky header (z-index 20) and under the skip link (30): the panel covers the
   header of the screen it stands beside, and nothing may cover the first focusable
   element of the document. */
:root[data-help="open"] .app { padding-inline-end: var(--helppanel-w); }
.helppanel-head {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border-block-end: 1px solid var(--border);
}
/* The address of what the panel is showing, as a link — that is the whole reason the
   panel fetches a real address instead of a private fragment format: what the reader is
   reading can be sent to somebody. */
.helppanel-page { margin-inline-start: auto; }
.helppanel-close {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--h-control);
  height: var(--h-control);
  border: 0;
  background: none;
  color: var(--ink-soft);
  border-radius: var(--r-sm);
  cursor: pointer;
}
.helppanel-close:hover { background: var(--surface-2); color: var(--ink); }
.helppanel-body {
  overflow-y: auto;
  display: grid;
  gap: var(--s-4);
  align-content: start;
  padding: var(--s-4);
}
/* The title and the lead of an article inside the panel: the pair the screen draws in
   `.screen-header`, without the measures of a screen. */
.helppanel-doc { display: grid; gap: var(--s-1); }
/* The grip. A `<button>` and not a bare `<div>` with a cursor: what can be dragged has to
   be reachable from the keyboard as well, and §6 knows no third kind of control — the
   script moves the edge by a step on the arrow keys. It straddles the panel's own border,
   so the thing under the pointer is the seam the reader is pulling. */
.helppanel-resizer {
  position: absolute;
  inset-block: 0;
  inset-inline-start: calc(-1 * var(--s-1));
  inline-size: var(--s-2);
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: col-resize;
  touch-action: none;
}
.helppanel-resizer:hover, .helppanel-resizer:focus-visible { background: var(--accent-soft); }
@media (max-width: 860px) {
  .helppanel { inline-size: 100%; }
  :root[data-help="open"] .app { padding-inline-end: 0; }
  .helppanel-resizer { display: none; }
}

/* --- Help panel of a part of a screen (BL-534) --------------------------------
   Full width above the content and short. Not a `.panel`: a panel is a frame around one
   thing the reader works with, and this is an aside that must not read as another thing
   to fill in — hence the tinted plane and the accent rule instead of a border on four
   sides. The design's third column is deliberately not here: the parts live in
   `.workspace`, whose second column is already navigation. */
.stephelp {
  display: grid;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  background: var(--surface-2);
  border-inline-start: 2px solid var(--accent);
  border-radius: var(--r-sm);
}
/* Tabs are links, so the open one is `aria-current` — a state of the document, in an
   attribute rather than in a class (§4.1). They wrap; two topics fit a phone, four do
   not, and a row that scrolls sideways hides the last one. */
.stephelp-tabs { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-3); }
.stephelp-tabs a { font-size: var(--t-sm); color: var(--ink-soft); text-decoration: none; }
.stephelp-tabs a:hover { color: var(--ink); text-decoration: underline; }
.stephelp-tabs a[aria-current="true"] { color: var(--ink); font-weight: 600; }

/* --- Public questions of the entrance page (BL-535) ---------------------------
   Two columns of question and answer, one below 860 px. Not `.showcase`, which lays out
   panels and reads as a list of features, and not `.help-item`, which is a link whose
   whole area is the target — here the answer is the content and the link under it is a
   way on, not the point. */
.faq { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-6) var(--s-8); }
.faq-item { display: grid; gap: var(--s-2); align-content: start; }
.faq-q { font-family: var(--serif); font-size: var(--t-lg); font-weight: 500; }
@media (max-width: 860px) { .faq { grid-template-columns: minmax(0, 1fr); } }

/* --- The palette „Prejsť na" (BL-578, RQ-20) ----------------------------------
   A native <dialog>, like `.confirmation`: the focus trap and Esc are the browser's,
   and the top layer sidesteps the stacking ladder of the shell — switcher panel 1,
   sticky 2/10, `.bug-widget` 15, `.header` 20, `.helppanel` 25, `.skip-link` 30 — rather
   than adding a rung to it. The mock-up of this screen drew the palette at `z-index: 30`,
   which is the skip link's; there is no number here at all, which is the whole reason a
   dialog was chosen over a positioned panel.

   It hangs near the top instead of centring, because it is opened over work the reader
   is looking at and the answer should not cover the middle of it. */
dialog.jump {
  border: 1px solid var(--border-strong);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--ink);
  padding: 0;
  width: min(40rem, calc(100vw - 2 * var(--s-5)));
  max-width: none;
  margin-block-start: 10vh;
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
dialog.jump::backdrop { background: var(--overlay); }

/* The query row: the label of the field is `.visually-hidden`'s job, so the three
   tracks are the eyebrow, the input and the key cap. `align-items: center` and not
   `end`, because there is no visible label to line the button up with here. */
.jump-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--border);
}
/* The field inside the head is the row, not a block of its own: its label is read by a
   screen reader and never drawn, and its margin would push the cap out of line. */
.jump-head .field { margin: 0; min-width: 0; }
/* The **wrapper**, not the `<label>` inside it: `components/field.html` puts the label
   into a `.field-label` row, so hiding only the label left a 20 px empty band above the
   input — invisible on a wide window, a second line of the head on a phone. Clipped
   rather than `display: none`, so the input keeps its accessible name (§6). */
.jump-head .field-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* The `:not()` pair is not decoration: `.field input:not([type="checkbox"]):not([type="radio"])`
   is (0,3,1) and a plain `.jump-head .field input` is (0,2,1), so without it the field
   keeps its frame and the head reads as a form in a dialog rather than as one field. */
.jump-head .field input:not([type="checkbox"]):not([type="radio"]) {
  border: 0;
  background: transparent;
  padding-inline: 0;
  font-size: var(--t-md);
}
/* The one place a focus ring is dropped, and only because the whole dialog is the
   control: the field takes focus the moment the palette opens and never gives it up, so
   a ring around it marks nothing the reader could confuse it with (§6 asks for a visible
   focus on what the keyboard is *moving* through — here that is `.jump-row.is-current`). */
.jump-head .field input:not([type="checkbox"]):focus-visible { outline: 0; }

/* The answers. Capped and scrolling, because the dialog must not grow past the window
   on a laptop with five groups open. */
.jump-body {
  overflow-y: auto;
  max-block-size: min(60vh, 32rem);
  padding-block: var(--s-2);
}
/* On the screen behind the palette there is no window to stay inside: the panel is the
   page and the list is as long as it is. */
.jump-body--screen { max-block-size: none; overflow-y: visible; padding: var(--s-2) 0; }

.jump-group { padding: var(--s-2) var(--s-4) var(--s-1); }

/* One row of an answer. An `<a>` and never a div with a handler (§6), and a grid rather
   than `.list-row` for the reason `.picker-option` gives: a shape shared down a column of
   siblings only comes out of fixed tracks, and this row has two lines of text where the
   base row has one. */
.jump-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0 var(--s-3);
  min-height: var(--h-control);
  padding: var(--s-2) var(--s-4);
  color: var(--ink);
  text-decoration: none;
}
.jump-row .subtext { grid-column: 1; font-size: var(--t-sm); }
/* **Both coordinates, not just the row.** A grid places explicitly positioned items
   before it auto-places the rest, so `grid-row: 1` alone took the first cell of the row
   and pushed the name of the house into the second — the time on the left, the name on
   the right, which is the reverse of what the row says. */
.jump-row .time { grid-column: 2; grid-row: 1; }
.jump-row:hover { background: var(--surface-2); }
/* The row the keyboard is on. The state is an **attribute**, never a class (§4.1): the
   script has to write `aria-selected` for the screen reader either way, and a class
   beside it would be the same fact stored twice — the shape `aria-current` has on tabs.
   Not colour alone (§6): the row carries a left edge as well, so it is told apart in a
   high-contrast mode and on a printed screenshot. */
.jump-row:focus-visible,
.jump-row[aria-selected="true"] {
  background: var(--accent-soft);
  /* 4px, the width §4.3 prescribes for the state bar of `.message` and `.guard` — the
     same idea in a narrower place, and an inset shadow rather than a border so the row
     does not shift by its own width when the keyboard reaches it. */
  box-shadow: inset 4px 0 0 var(--accent);
  outline: 0;
}

/* The sections of a house, under the row of that house. A row of small links and not a
   second column, because there are eight of them and a column would be a phone's whole
   width per name. */
.jump-sections {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-2);
  padding: 0 var(--s-4) var(--s-2) var(--s-4);
}
.jump-section {
  display: inline-flex;
  min-height: auto;
  padding: 2px var(--s-2);
  border-radius: var(--r-sm);
  font-size: var(--t-xs);
  color: var(--ink-soft);
  background: var(--surface-2);
}
.jump-section:hover { color: var(--ink); }

.jump-empty { padding: var(--s-4); }

/* The key legend. `--ink-soft` and not `--ink-muted`: §6 forbids the muted ink at this
   size, and the legend is the smallest text of the dialog. */
.jump-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-4);
  margin: 0;
  padding: var(--s-2) var(--s-4);
  border-top: 1px solid var(--border);
  font-size: var(--t-xs);
  color: var(--ink-soft);
}
.jump-foot-note { margin-inline-start: auto; }

/* The opener in the header. It wears `.btn btn--quiet` and only widens into a field-like
   target: a palette that looks like a button says "one more control", one that looks like
   an input says "type here". Below the narrow break it gives the width back, so the
   header still wraps into two lines instead of three. */
.jump-opener {
  min-width: 18rem;
  justify-content: flex-start;
  gap: var(--s-3);
  color: var(--ink-soft);
}
/* The key cap sits at the far edge of the control, the way every palette advertises
   its shortcut. `margin-inline-start: auto` and not a third grid track: the cap is
   written by the script, so on a browser without it the element is empty and must take
   no room at all. */
.jump-opener kbd { margin-inline-start: auto; }
.jump-opener kbd:empty { display: none; }
@media (max-width: 860px) { .jump-opener { min-width: 0; } }

/* The key caps, an element rule with no class of its own — the same shape `mark` has. */
kbd {
  font: 600 var(--t-xs)/1 var(--mono);
  /* 2px is a hairline's business, not the 4px grid's (§1.3): a key cap taller than its
     own glyph stops reading as a key. The horizontal side comes from the scale. */
  padding: 2px var(--s-1);
  border: 1px solid var(--border-strong);
  /* The thicker bottom edge is the whole drawing — a cap is a key seen from above. */
  border-bottom-width: 2px;
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink-soft);
}

/* --- Forced colours (BL-722) -------------------------------------------------
   Windows High Contrast and its successors replace the colour of text, surfaces and
   borders with the system palette and drop `box-shadow` altogether. Measured in
   Chromium with `forced_colors="active"` over the styleguide, two kinds of thing
   stopped meaning anything, and they are answered in two different ways
   (design-system.md §6):

   1. **Graphics that carry a value by shape and surface vanished whole** — the fill and
      the limit of every `.bar` (so `.breakdown`, `.meter`, `.readiness`, `.season`),
      the parts of `.composition`, the fill and hatching of `.tiles-item`, the month
      grid of `.year-track`, the swatches of `.legend`, every `.dot`. They keep their own colours: `forced-color-adjust:
      none` is the way out the mode itself provides for graphics, and it is inherited,
      so it is set on the drawing and not on each of its marks. The state beside them
      is words anyway (§6 — never colour alone); what was lost was the drawing.

   2. **The state of a control merged with its neighbours** — a pressed and an
      unpressed toggle, the current item of the navigation, the open tab of a step's
      help, the row of the palette the keyboard is on, the current row of a table, the
      step that is "now". These take the **system** keywords: the contrast of controls
      belongs to the user in this mode, so a control is not handed back its own colours.
      `Highlight` / `HighlightText` is the pair the system uses for "selected" itself.

   What is **not** answered here: the coloured left bar of `.guard` and `.message`
   turns `CanvasText` like every border. The bar stays, as a shape, and the state is the
   word the card carries — which is why a card whose state was *only* its colour had to
   get a word (the rows of *Lehoty*, BL-702).

   One block and not a line per component, because it is one decision about the whole
   design system; a component that starts to carry state by surface or shadow belongs
   here too. */
@media (forced-colors: active) {
  .bar,
  .composition-track,
  .tiles,
  .year-track,
  .legend-item i,
  .legend-item > .composition-part,
  .dot {
    forced-color-adjust: none;
  }

  /* Selected controls, rows and items take the system's own pair for "selected".
     `forced-color-adjust: none` is what lets the pair through: left to the mode, every
     run of text gets a backplate in `Canvas`, and `HighlightText` on it was white on
     white — measured, the pressed toggle read as a blank bar. The keywords are still
     the system's, so the user's palette decides the colours; only the rewriting stops.
     It is inherited, so the children (hints, times, the name of a building) come along
     and are coloured below rather than keeping the brand's. Two children keep a meaning
     of their own and are left out of that: the `.dot` of a step, which in the
     sub-navigation of a record is the only visible carrier of the step's state (the word
     is `.visually-hidden`), and the `<mark>` of the palette's match. */
  .btn[aria-pressed="true"],
  .nav-item[aria-current="page"],
  .stephelp-tabs a[aria-current="true"],
  .jump-row[aria-selected="true"] {
    forced-color-adjust: none;
    background: Highlight;
    border-color: Highlight;
    color: HighlightText;
  }
  .btn[aria-pressed="true"]:hover { background: Highlight; }
  .btn[aria-pressed="true"]::before { background: HighlightText; }
  .nav-item[aria-current="page"] :not(.dot),
  .jump-row[aria-selected="true"] :not(mark) {
    background: transparent;
    border-color: HighlightText;
    color: HighlightText;
  }
  .nav-item[aria-current="page"] .dot { outline: 1px solid HighlightText; }
  .jump-row[aria-selected="true"] mark { background: Mark; color: MarkText; }
  /* The palette drew the keyboard's row and its focus as an inset shadow with the
     outline removed, and the mode drops shadows: the row the keyboard was on had no
     mark at all. The outline comes back here. */
  .jump-row:focus-visible { outline: 2px solid CanvasText; outline-offset: -2px; }

  table.data tbody tr[aria-current="true"] > *:first-child {
    border-inline-start: 4px solid Highlight;
  }

  /* An open bubble's mark was a surface of its own, and the two states of the "?"
     looked the same; the bubble itself is visible, but the mark said nothing. */
  .explainer[open] > summary {
    forced-color-adjust: none;
    background: Highlight;
    color: HighlightText;
  }

  .trail-step--now {
    border-width: 2px;
    border-color: Highlight;
  }

  /* The crosses are two bars painted with `currentColor` as a background, and a
     background is forced to `Canvas`: the close controls of the help panel and of a
     bubble were blank buttons. */
  .explainer-close::before, .explainer-close::after,
  .helppanel-close::before, .helppanel-close::after { background: ButtonText; }
}
