/*
 * What Bootstrap does not cover, and the corrections that keep the delivered
 * pages inside `content/design-tokens.md`.
 *
 * Four kinds of rule live here and nothing else:
 *   the base layer, which is what an unclassed element looks like;
 *   the corrections, where a Bootstrap component ships a colour of its own
 *   and has to be pointed at a token instead;
 *   the building blocks this design has and Bootstrap has not — the icon,
 *   the button family, the badge, the switch, the segmented control, the
 *   panel, the empty state, the word mark, the theme control;
 *   and the two queries at the end, motion and print.
 *
 * A rule here that merely rebuilds a Bootstrap class is a rule to delete.
 * Every value is a token; there is no literal colour, radius or duration in
 * this file, and `tests/test_design_system.py` reports one that appears.
 *
 * The vocabulary is `docs/referenzen/steinmann-website.md`, the survey of the
 * company's own site, which is the design this application follows. Four of
 * its decisions reach almost every rule below: nothing is rounded, every
 * heading is a serif, no weight is heavier than 500, and small text is upper
 * case and tracked far apart. Gold is a line before it is a fill.
 */

/* ------------------------------------------------------------------ *
 * Base layer
 * ------------------------------------------------------------------ */

html {
  /* N-10: the whole scale hangs on this one value. */
  font-size: var(--root-font-size);
  -webkit-text-size-adjust: 100%;
  /* What the browser draws itself follows the palette rather than the system
   * blue. To be exact about what this does today: nothing visible. Every
   * checkbox in the project carries `appearance: none` -- Bootstrap's on
   * `.form-check-input`, this file's on `.switch__control` -- and there is no
   * radio, no `progress`, no `meter` and no range in any template, so there
   * is currently no control for it to reach. It stands as the default for the
   * first one that is added, and it costs one line. */
  accent-color: var(--color-primary-fill);
}

body {
  font-family: var(--font-family-base);
  font-size: var(--text-base);
  line-height: var(--text-base-line);
  color: var(--color-text);
  background-color: var(--color-ground);
  -webkit-font-smoothing: antialiased;
  /* A word longer than the column breaks rather than pushing the page
   * sideways. German compounds and the long URLs in the legal pages are what
   * this is for, and it only ever acts where a line cannot otherwise be made
   * — at the largest font size on a phone, which is exactly where N-10 puts
   * the people who need it. */
  overflow-wrap: break-word;
}

/* Hyphenation belongs to prose and not to a table cell or a button. The page
 * declares `lang="de-ch"`, so the browser hyphenates German properly. */
.prose,
.lead,
.article-card__excerpt,
.empty-state p { hyphens: auto; }

/* `--color-on-selection` and not the heading colour: the selection ground is
 * the brand gold in both modes, and in the dark set the heading colour is the
 * pale `--text-cool`, which on gold measures below the bar for running text.
 * The role exists for exactly this pair. */
::selection {
  background-color: var(--color-selection);
  color: var(--color-on-selection);
}

/* Every heading is a serif and none of them is heavier than the regular cut.
 *
 * That is the site's own division of labour: Cormorant sets what is read as
 * language, Jost sets what is read as interface, and a heading in the sans is
 * a finding. Weight marks nothing here any more — `--font-weight-semibold` and
 * `--font-weight-bold` both resolve to 500 in the token file — so a heading is
 * carried by the family, by the size and by the tight display leading.
 * Cormorant needs no tracking correction either: it is drawn for text sizes
 * and sets correctly at display sizes on its own, so the four `--tracking-*`
 * declarations that stood here are gone rather than set to their zero.
 *
 * Each size is one step above what the sans scale used, because Cormorant's
 * x-height is markedly smaller than Jost's: at the same nominal size an h4
 * stopped reading as a heading beside the body text under it. */
h1, h2, h3, h4, h5, h6 {
  color: var(--color-heading);
  font-family: var(--font-family-display);
  /* Cormorant sets old-style figures by default, and in a reference or a
   * date that is a misreading: "SRE-DEMO-015" came out as "oI5" and
   * "18.09." as "I8.o9". Every serif rule in the four stylesheets carries
   * the same line. */
  font-variant-numeric: lining-nums;
  font-weight: var(--font-weight-regular);
  line-height: var(--display-line);
  margin-block: 0 var(--space-4);
  text-wrap: balance;
}

h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-3xl); }
h3 { font-size: var(--text-2xl); }
h4 { font-size: var(--text-xl); }

/* h5 and h6 carried no size of their own and fell to the browser's `0.83em`
 * and `0.67em` — smaller than the body text they head. Bold was the only
 * thing that made them read as headings at all, and there is no bold left. */
h5 { font-size: var(--text-lg); }
h6 { font-size: var(--text-base); }

p { margin-block: 0 var(--space-4); }

/* 500 is the heaviest weight this design owns, and both `strong` and a table
 * head default to 700. Jost is served in 300, 400 and 500 only, so a 700 does
 * not reach a heavier cut: the engine synthesises one by smearing the
 * outlines, which is the single thing that makes this typeface look wrong.
 * The jump from 400 to 500 is what emphasis is, here as everywhere else. */
strong,
b,
th,
dt { font-weight: var(--font-weight-medium); }

/* A heading that follows something needs air above it. Without this a
 * subheading sat directly on the row of buttons before it, which is what made
 * a long page read as one undivided column. `:not(:first-child)` keeps a
 * panel title, a card heading and a page header exactly where they were. */
:where(h2):not(:first-child) { margin-top: var(--space-10); }
:where(h3, h4):not(:first-child) { margin-top: var(--space-5); }

/* An eyebrow belongs to the heading under it. Without this the heading takes
 * its full distance from the pill above it and the two stop reading as one
 * unit.
 *
 * The second selector is the same rule again, one level deeper, and it is not
 * decoration. `:where()` contributes no specificity, so the first line weighs
 * (0,1,0) -- and `.prose h2` further down weighs (0,1,1) and won. Inside a
 * `.prose` block the pair therefore stood 52 px apart instead of 12, measured
 * on `/immobilie-verkaufen/` and `/immobilienbewertung/`: exactly the defect
 * this rule says it prevents, in the one place nobody looked. */
.section-label + :where(h1, h2, h3, h4),
.prose .section-label + :where(h1, h2, h3, h4) { margin-top: 0; }

.table,
figure { margin-bottom: 0; }

a {
  color: var(--color-link);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

a:hover { color: var(--color-link-hover); }

hr {
  border: 0;
  border-top: var(--border-hairline) solid var(--color-hairline);
  margin-block: var(--space-6);
  opacity: 1;
}

small, .u-small {
  font-size: var(--text-sm);
  line-height: var(--text-sm-line);
}

code {
  /* A step of the scale rather than `0.9em`. The relative size produced
   * 13.5 px inside body text and 16.2 px inside a lead — two values that are
   * in no scale, and the only two the whole application had. */
  font-size: var(--text-sm);
  color: var(--color-heading);
  background-color: var(--color-surface-tinted);
  padding: 0.1em 0.35em;
  /* A name is typed as it stands. Inherited from the prose, `hyphens: auto`
   * broke "htmx-current-path-for-history" with a hyphen of its own that
   * nobody could tell from the name's. */
  hyphens: manual;
  border-radius: var(--radius-sm);
  /* `anywhere` rather than `break-word`, and the difference is the whole
   * reason this line exists: `break-word` breaks a long word when a line
   * cannot be made but does not lower the element's min-content width, so the
   * box it stands in never shrinks far enough for the break to get a turn.
   *
   * Measured on the second-factor setup page, where the enrolment URI is one
   * unbreakable string: 543 px inside a 360 px telephone, and the page
   * scrolled sideways by 224. 1.4.10 Reflow. There is nothing to read in the
   * middle of a base32 secret, so breaking it anywhere costs nothing. */
  overflow-wrap: anywhere;
}

pre {
  overflow-x: auto;
  max-width: 100%;
}

/* The original under a reply or a forward. A mail's own line breaks are
 * kept, but it is read as a letter: the body face, wrapped to the card, and
 * never a line running out of it on a telephone. */
pre.quoted {
  margin: 0;
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------------ *
 * The icon family. `core/templatetags/icons.py` prints the markup; this
 * decides the size, the alignment and — the part that matters — that it
 * takes the colour of the text it stands beside.
 * ------------------------------------------------------------------ */

.icon {
  width: var(--icon-size);
  height: var(--icon-size);
  flex: 0 0 auto;
  display: inline-block;
  vertical-align: -0.15em;
  stroke-width: var(--icon-stroke);
}

.icon--sm { width: var(--icon-size-sm); height: var(--icon-size-sm); }
.icon--lg { width: var(--icon-size-lg); height: var(--icon-size-lg); }
.icon--xl { width: var(--space-6); height: var(--space-6); }

/* An icon that carries a tone of its own — the glyph in a notice, the mark
 * beside a figure. Everything else inherits. */
.icon--accent { color: var(--color-link); }
.icon--muted { color: var(--color-muted); }

/* ------------------------------------------------------------------ *
 * Focus. `outline: none` without a replacement is the most common
 * accessibility violation there is, so the ring is declared once, here,
 * on every focusable thing.
 * ------------------------------------------------------------------ */

/* Gold, two pixels, and offset from the control rather than drawn on it.
 * `--color-focus` is `--gold-edge` on a light ground and the brand gold on a
 * dark one, which is the pair that clears the 3:1 WCAG 2.2 1.4.11 asks of an
 * indicator against both. The ring sets no shape of its own — a focus state
 * marks where the keyboard is, it does not restyle the control. */
:focus-visible {
  outline: var(--border-focus) solid var(--color-focus);
  outline-offset: var(--border-focus-offset);
}

/* WCAG 2.4.11: the focused element must not end up behind the sticky header
 * or the consent banner. */
:where(a, button, input, select, textarea, summary, [tabindex]) {
  scroll-margin-top: var(--focus-scroll-margin);
  scroll-margin-bottom: var(--focus-scroll-margin);
}

.skip-link {
  position: absolute;
  left: var(--space-2);
  top: var(--space-2);
  z-index: 100;
  transform: translateY(-200%);
  display: inline-block;
  padding: var(--space-2) var(--space-4);
  background-color: var(--color-surface);
  color: var(--color-link);
  border: var(--border-control) solid var(--color-border-control);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow-overlay);
}

.skip-link:not(:focus) { box-shadow: none; }

.skip-link:focus {
  transform: translateY(0);
}

/* ------------------------------------------------------------------ *
 * Buttons.
 *
 * Four ranks, and they are the site's own four:
 *
 *   `.btn-action`  the one main action of a screen — a gold fill with a black
 *                  label, lightening rather than darkening on hover, because
 *                  a gold that darkens reads as switched off;
 *   `.btn-primary` the ordinary submission — a black fill with an ivory
 *                  label, which empties out on hover to the same label in
 *                  black on nothing behind a black edge;
 *   `.btn-quiet`   the second action beside it — nothing but an edge;
 *   `.btn-ghost`   the third — nothing but the label.
 *
 * Every one of them is square, upper case, tracked at `--tracking-button` and
 * set at 500. That last group is not decoration: with no weight above 500 in
 * the whole design, the case and the tracking are what say "control" where a
 * bolder cut used to.
 *
 * The height is `--control-height`, 38 px. The accessibility statement says
 * every control measures at least 24 x 24 and the important ones 44 x 44;
 * `.btn-action`, `.btn-lg` and the row handles carry the larger figure, and
 * everything here clears the smaller one twice over.
 * ------------------------------------------------------------------ */

.btn {
  --bs-btn-border-radius: var(--radius-control);
  /* 500, which is the heaviest weight there is here. */
  --bs-btn-font-weight: var(--font-weight-medium);
  /* 0.78 rem, which is the site's own button size and a step of its own: the
   * text scale goes 0.75, 0.8125, and neither is the value the survey
   * recorded. */
  --bs-btn-font-size: var(--text-button);
  --bs-btn-padding-y: var(--space-2);
  --bs-btn-padding-x: var(--space-4);
  --bs-btn-focus-box-shadow: none;
  --bs-btn-border-width: var(--border-control);
  min-height: var(--control-height);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  /* A label that cannot fit wraps rather than pushing the page sideways.
   * `nowrap` was the obvious choice and it was wrong at the largest font
   * size of N-10: "Konto anlegen und Link senden" is 379 pixels there and
   * the column is 358. A toolbar does not suffer for it — the row is a flex
   * container and moves a button to the next line before it has to wrap
   * one. */
  white-space: normal;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: var(--tracking-button);
  transition: all var(--motion-base) var(--motion-ease);
}

/* On a telephone the capitals close up a little. At the full tracking the
 * common labels, "Anfrage zu diesem Objekt" and "Besichtigungstermin
 * wählen", broke onto two lines and left the arrow or the glyph alone. */
@media (max-width: 30rem) {
  .btn { letter-spacing: calc(var(--tracking-button) * 0.6); }
}


.btn:focus-visible {
  outline: var(--border-focus) solid var(--color-focus);
  outline-offset: var(--border-focus-offset);
  box-shadow: none;
}

/* The one main action per screen: the gold fill with the black label, 7.93:1,
 * and the single loudest thing on the page. On hover it lightens to
 * `--gold-soft` rather than darkening — a gold that darkens reads as switched
 * off, a gold that lightens reads as lit. It carries the larger target size. */
.btn-action {
  --bs-btn-bg: var(--color-action-fill);
  --bs-btn-border-color: var(--color-action-fill);
  --bs-btn-color: var(--color-on-action);
  --bs-btn-hover-bg: var(--color-action-fill-hover);
  --bs-btn-hover-border-color: var(--color-action-fill-hover);
  --bs-btn-hover-color: var(--color-on-action);
  --bs-btn-active-bg: var(--color-action-fill-hover);
  --bs-btn-active-border-color: var(--color-action-fill-hover);
  --bs-btn-active-color: var(--color-on-action);
  --bs-btn-disabled-bg: var(--color-surface-tinted);
  --bs-btn-disabled-border-color: var(--color-border-disabled);
  --bs-btn-disabled-color: var(--color-disabled);
  --bs-btn-disabled-opacity: 1;
  min-height: var(--target-primary);
}

/* The ordinary submission: a black fill with an ivory label, which on hover
 * empties out to the label alone behind its own edge. `--color-primary-fill`
 * on all three of fill, edge and hover label is what keeps that legible in
 * both modes — it is the near-black on paper and the ivory on the dark page,
 * so the button empties out to black on light and to ivory on dark without
 * either side naming a colour of its own. */
.btn-primary {
  --bs-btn-bg: var(--color-primary-fill);
  --bs-btn-border-color: var(--color-primary-fill);
  --bs-btn-color: var(--color-on-primary);
  --bs-btn-hover-bg: transparent;
  --bs-btn-hover-border-color: var(--color-primary-fill);
  --bs-btn-hover-color: var(--color-primary-fill);
  --bs-btn-active-bg: transparent;
  --bs-btn-active-border-color: var(--color-primary-fill);
  --bs-btn-active-color: var(--color-primary-fill);
  --bs-btn-disabled-bg: var(--color-surface-tinted);
  --bs-btn-disabled-border-color: var(--color-border-disabled);
  --bs-btn-disabled-color: var(--color-disabled);
  --bs-btn-disabled-opacity: 1;
}

/* On a touch device the second rank keeps the first rank's target.
 *
 * The two ranks differ in what they mean, not in how big a thumb is. Without
 * this line, moving a button from `.btn-action` to `.btn-primary` -- which is
 * what the rank is for -- takes it from 44 px to 38, and it does that on the
 * public enquiry paths first, because those are where the demoted buttons sit.
 * 38 px still clears the 24 of WCAG 2.2 AA 2.5.8 with room, so nothing is
 * broken by it; but a run that tidies a class name must not make a target
 * smaller on the device where the target is a finger, and the enhanced figure
 * of 2.5.5 is the one this project builds to.
 *
 * `pointer: coarse` and not a width, for the reason the field rule below gives:
 * a tablet in landscape is 1024 px wide and is still a thumb, and a narrow
 * window on a desk is a mouse. Neither `.btn-sm` nor `.btn-icon` is ever
 * combined with `.btn-primary` in this project, so the selector needs no
 * guard -- and if one ever is, the guards on `.btn-action.btn-icon` above are
 * the pattern to copy. */
@media (pointer: coarse) {
  .btn-primary { min-height: var(--target-primary); }
}

/* The second rank: an edge and nothing else, which is what the site's own
 * outlined button is. The fill was white and the shadow was the card's, and
 * both are gone — a soft wide shadow under a 38 px control is what makes a
 * toolbar look like a set of tiles rather than a row of actions, and it is
 * not a shape this site has anywhere.
 *
 * The edge stays `--color-border-control` rather than `--color-hairline`, and
 * that is WCAG 1.4.11 rather than taste: with no fill at all the edge is now
 * the whole of what says "button" rather than "sentence". The hairline
 * measured 1.23:1 against the surface — an edge nobody with reduced contrast
 * sensitivity can see, on 73 controls including "Abmelden" in the frame of
 * every application page — while `--color-border-control` is the lightest
 * grey that clears 3:1 on both grounds. The resting and hovered states differ
 * by a wash rather than by whether there is an outline at all. */
.btn-quiet {
  --bs-btn-bg: transparent;
  --bs-btn-border-color: var(--color-border-control);
  --bs-btn-color: var(--color-heading);
  --bs-btn-hover-bg: var(--color-brand-wash);
  --bs-btn-hover-border-color: var(--color-brand-edge);
  --bs-btn-hover-color: var(--color-heading);
  --bs-btn-active-bg: var(--color-brand-wash);
  --bs-btn-active-border-color: var(--color-brand-edge);
  --bs-btn-active-color: var(--color-heading);
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: var(--color-border-disabled);
  --bs-btn-disabled-color: var(--color-disabled);
  --bs-btn-disabled-opacity: 1;
}

/* The glyph carries the brand where the button itself does not. On a light
 * ground `--color-link` is the readable gold, not the brand value. */
.btn-quiet .icon { color: var(--color-link); }
.btn-quiet:disabled .icon { color: var(--color-disabled); }

/* The fourth rank: the label and nothing else, for the action that stands
 * beside a stronger one and must not compete with it. It carried a tinted
 * fill before, which on a tinted panel made it a second card rather than a
 * quieter button. What marks it now is the case and the tracking every button
 * here has, and on hover the gold wash the outlined one uses. */
.btn-ghost {
  --bs-btn-bg: transparent;
  --bs-btn-border-color: transparent;
  --bs-btn-color: var(--color-heading);
  --bs-btn-hover-bg: var(--color-brand-wash);
  --bs-btn-hover-border-color: transparent;
  --bs-btn-hover-color: var(--color-brand-ink);
  --bs-btn-active-bg: var(--color-brand-wash);
  --bs-btn-active-border-color: transparent;
  --bs-btn-active-color: var(--color-brand-ink);
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: transparent;
  --bs-btn-disabled-color: var(--color-disabled);
  --bs-btn-disabled-opacity: 1;
}

/* Confirming inside a dialog, and the one place a second filled hue appears
 * — the survey found it there and nowhere else. */
.btn-success {
  --bs-btn-bg: var(--color-success-fill);
  --bs-btn-border-color: var(--color-success-fill);
  --bs-btn-color: var(--color-on-success);
  --bs-btn-hover-bg: var(--color-success-fill-hover);
  --bs-btn-hover-border-color: var(--color-success-fill-hover);
  --bs-btn-hover-color: var(--color-on-success);
  --bs-btn-active-bg: var(--color-success-fill-hover);
  --bs-btn-active-border-color: var(--color-success-fill-hover);
  --bs-btn-active-color: var(--color-on-success);
}

.btn-danger {
  --bs-btn-bg: var(--color-danger-fill);
  --bs-btn-border-color: var(--color-danger-fill);
  --bs-btn-color: var(--color-on-danger);
  --bs-btn-hover-bg: var(--color-danger-fill-hover);
  --bs-btn-hover-border-color: var(--color-danger-fill-hover);
  --bs-btn-hover-color: var(--color-on-danger);
  --bs-btn-active-bg: var(--color-danger-fill-hover);
  --bs-btn-active-border-color: var(--color-danger-fill-hover);
  --bs-btn-active-color: var(--color-on-danger);
}

/* Destructive as an outline rather than as a fill, which is what the survey
 * found: a red block on a working screen shouts, a red label behind an edge
 * says the same thing at the right volume. It empties out like the quiet
 * button it is a variant of, and takes the same visible edge for the same
 * reason — a control with neither fill nor readable edge is a sentence. */
.btn-danger-quiet {
  --bs-btn-bg: transparent;
  --bs-btn-border-color: var(--color-border-control);
  --bs-btn-color: var(--color-danger);
  --bs-btn-hover-bg: var(--color-ground-problem);
  --bs-btn-hover-border-color: var(--color-danger);
  --bs-btn-hover-color: var(--color-danger);
  --bs-btn-active-bg: var(--color-ground-problem);
  --bs-btn-active-border-color: var(--color-danger);
  --bs-btn-active-color: var(--color-danger);
}

.btn-danger-quiet .icon { color: var(--color-danger); }

/* The switched-off state, for the four variants that did not declare one.
 *
 * Without this they fell through to Bootstrap's own disabled values, and that
 * is not a shade too pale but a different palette: measured, a disabled
 * `.btn-danger` rendered Bootstrap's own red and a disabled `.btn-success`
 * its own green, at 65 % opacity -- two colours this project does not own.
 * The measured values are in the run's report rather than here, because a
 * foreign colour written into a comment is how one finds its way back into a
 * declaration, and the design-system test reads comments too. It is reachable
 * rather than theoretical: the account deletion page and the member page both
 * submit through a `.btn-danger`.
 *
 * The four variants that already had it use opacity 1 and a tinted surface,
 * because a control that is off should read as off rather than as faded --
 * washing a colour out with opacity leaves whatever is behind it showing
 * through, which is how a disabled button on a tinted panel ends up a
 * different colour from the same button on a white one. */
.btn-success,
.btn-danger {
  --bs-btn-disabled-bg: var(--color-surface-tinted);
  --bs-btn-disabled-border-color: var(--color-border-disabled);
  --bs-btn-disabled-color: var(--color-disabled);
  --bs-btn-disabled-opacity: 1;
}

/* The quiet destructive button switches off like the quiet button it is a
 * variant of: it keeps its empty ground rather than taking the tinted one, so
 * that two buttons of the same rank do not read differently when they are
 * both unavailable. */
.btn-danger-quiet {
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: var(--color-border-disabled);
  --bs-btn-disabled-color: var(--color-disabled);
  --bs-btn-disabled-opacity: 1;
}

/* The link button keeps no surface and no edge when it is off, exactly as it
 * has none when it is on. Declared here in full rather than as a correction
 * to a shared block, because three `.btn-link` blocks within twenty lines is
 * a result that depends on the order of all three. */
.btn-link {
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: transparent;
  --bs-btn-disabled-color: var(--color-disabled);
  --bs-btn-disabled-opacity: 1;
}

/* A button that reads as a link, for the second action in a row. Still a
 * button, because what it does is a submission and not a navigation. */
.btn-link {
  --bs-btn-color: var(--color-link);
  --bs-btn-hover-color: var(--color-link-hover);
  --bs-btn-active-color: var(--color-link-hover);
  --bs-btn-disabled-color: var(--color-disabled);
  --bs-btn-padding-x: var(--space-2);
  --bs-btn-border-color: transparent;
  min-height: var(--control-height-sm);
}

.btn-sm {
  --bs-btn-padding-y: var(--space-1);
  --bs-btn-padding-x: var(--space-3);
  --bs-btn-font-size: var(--text-xs);
  min-height: var(--control-height-sm);
}

.btn-lg {
  --bs-btn-padding-y: var(--space-3);
  --bs-btn-padding-x: var(--space-6);
  --bs-btn-font-size: var(--text-md);
  min-height: var(--target-primary);
}

/* The square icon-only control: the overflow dots, the collapse handle, the
 * call glyph beside a row. It always carries its name in a
 * `visually-hidden` span, because an icon on its own says nothing to a
 * screen reader. */
.btn-icon {
  --bs-btn-padding-x: 0;
  --bs-btn-padding-y: 0;
  width: var(--control-height);
  min-width: var(--control-height);
  height: var(--control-height);
  min-height: var(--control-height);
}

/* A symbol button that is also the main action keeps the larger figure.
 * `.btn-icon` sets width and height at the same specificity as
 * `.btn-action`'s `min-height` and stands after it, so the 44 px the main
 * action is given was quietly taken back to 38 -- and a symbol button with no
 * label is the case where WCAG 2.2 AA 2.5.8 bites soonest, because there is
 * no text beside it to enlarge the target.
 *
 * Today the combination occurs once, on the style guide, which ships only
 * under DEBUG. So this changes nothing a customer sees; it makes the two
 * classes mean together what each means alone, before somebody uses them on a
 * row handle and gets 38 px without being told. */
.btn-action.btn-icon:not(.btn-sm) {
  width: var(--target-primary);
  min-width: var(--target-primary);
  height: var(--target-primary);
  min-height: var(--target-primary);
}

.btn-icon.btn-sm {
  width: var(--control-height-sm);
  min-width: var(--control-height-sm);
  height: var(--control-height-sm);
  min-height: var(--control-height-sm);
}

/* A group that reads as one control: the call button with its settings, the
 * previous and next of a month. The seam is a single hairline rather than a
 * gap. */
.btn-group-tight {
  display: inline-flex;
  align-items: stretch;
}

.btn-group-tight > .btn:not(:first-child) {
  margin-inline-start: calc(-1 * var(--border-control));
}

/* A row of actions, and the gap between two of them is the 24 px WCAG 2.5.8
 * asks for between adjacent targets. */
.btn-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.btn-row:last-child { margin-bottom: 0; }

/* ------------------------------------------------------------------ *
 * Forms.
 *
 * The control border is `--color-border-control` rather than the card
 * hairline: WCAG 1.4.11 asks for 3:1 on the parts of a component needed to
 * identify it, and a field whose edge is invisible is a field nobody finds.
 * The site's own fields are white boxes with a faint warm edge, so this is
 * the one visible place the application is firmer than its reference.
 * ------------------------------------------------------------------ */

.form-control,
.form-select {
  --bs-border-color: var(--color-border-control);
  background-color: var(--color-surface);
  color: var(--color-heading);
  border-radius: var(--radius-control);
  border-width: var(--border-control);
  padding: var(--space-2) var(--space-3);
  min-height: var(--control-height);
  font-size: var(--text-sm);
  transition: border-color var(--motion-fast) var(--motion-ease),
              box-shadow var(--motion-fast) var(--motion-ease);
}

/* On a touch device the fields are set at `--text-md` and not at
 * `--text-sm`, and the reason is not legibility but a behaviour of the
 * browser: iOS Safari zooms the page in when a field with type smaller than
 * 16 px takes focus, and it does not zoom back out afterwards. The visitor is
 * then left on a page that no longer fits, having done nothing but tap into a
 * field. `va-14-accessibility-run` measured 69 fields at 13 px and handed the
 * question here, because it is the type scale.
 *
 * The query is `pointer: coarse` rather than a width. What triggers the zoom
 * is the device, not the size of the window: a tablet in landscape is 1024 px
 * wide and zooms all the same, while a narrow window on a desk does not and
 * has no reason to give up the denser setting. */
@media (pointer: coarse) {
  .form-control,
  .form-select,
  .select-filter__input.form-control,
  .recipients__entry {
    font-size: var(--text-md);
    line-height: var(--text-md-line);
  }
}

/* A select is as wide as its box, whatever its chosen entry says. Safari
 * draws a select styled with `appearance: none` with its text overflowing,
 * so an entry of eighty characters ("SRE-DEMO-002 — Neubauprojekt am
 * Seerücken: ...") made the whole page wider than the telephone and let it
 * slide sideways (measured on 2026-10-16 on nine forms). Chromium clips. */
.form-select {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.form-control::placeholder { color: var(--color-muted); opacity: 1; }

/* Focus on a field is the gold edge, and the outline on top of it.
 *
 * The soft ring below is written for `:focus` and is in practice never seen on
 * a text field: `:focus-visible` matches whenever a text input has the focus,
 * by mouse as much as by keyboard, and the `:focus-visible` rule under it sets
 * `box-shadow: none`. So the ring reaches only the controls that are not text
 * inputs and are focused by pointer. That is deliberate -- two indicators at
 * once read as a thicker, blurrier edge rather than a clearer one -- but the
 * comment here twice said something else: first that the ring stays under the
 * outline, then that both arrangements exist. Neither was true of a field. */
.form-control:focus,
.form-select:focus {
  background-color: var(--color-surface);
  color: var(--color-heading);
  border-color: var(--color-focus);
  box-shadow: var(--shadow-ring);
}

.form-control:focus-visible,
.form-select:focus-visible {
  outline: var(--border-focus) solid var(--color-focus);
  outline-offset: var(--border-focus-offset);
  box-shadow: none;
}

.form-control:disabled,
.form-select:disabled {
  background-color: var(--color-surface-tinted);
  color: var(--color-disabled);
  border-color: var(--color-border-disabled);
}

textarea.form-control { min-height: calc(var(--space-24) + var(--space-4)); }

/* A group of boxes or buttons: the options in a row, each box the browser's
 * own in the focus colour, so a ticked one reads as ticked. */
div.choice-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
}

/* `flex`, not `inline-flex`: an inline box sits on the baseline of its first
 * item, and the box's baseline is its lower edge when clear and its tick
 * when ticked. A ticked box and its label stood a few pixels lower. */
div.choice-group label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

input.choice-group {
  width: 1.125rem;
  height: 1.125rem;
  margin: 0;
  accent-color: var(--color-focus);
}

/* The boxes of a multiple choice look like every other box in this design:
 * square, the control edge, the full target, and when ticked the dark fill
 * with an ivory tick. The browser's own box was rounded, smaller, gold-ticked
 * and grey in the dark set, beside the project's box in the same form. The
 * tick is drawn with two borders because the one Bootstrap ships is a data
 * URI with its colour baked in. */
input.choice-group[type="checkbox"] {
  appearance: none;
  flex: 0 0 auto;
  display: inline-grid;
  place-content: center;
  width: var(--target-min);
  height: var(--target-min);
  border: var(--border-control) solid var(--color-border-control);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  cursor: pointer;
}

input.choice-group[type="checkbox"]:checked {
  background-color: var(--color-accent-surface);
  border-color: var(--color-check-edge);
}

input.choice-group[type="checkbox"]:checked::before {
  content: "";
  width: 0.6rem;
  height: 0.32rem;
  margin-top: -0.2rem;
  border-left: 2px solid var(--color-on-accent);
  border-bottom: 2px solid var(--color-on-accent);
  transform: rotate(-45deg);
}

input.choice-group[type="checkbox"]:focus-visible {
  outline: var(--border-focus) solid var(--color-focus);
  outline-offset: var(--border-focus-offset);
}

.form-select,
select.form-control:not([multiple]) {
  /* Bootstrap's own chevron is a data URI with a colour baked into it. This
   * one is drawn from two gradients of `currentColor`, so the glyph follows
   * the theme and no colour is declared outside the token file. */
  --bs-form-select-bg-img: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right var(--space-4) center, right var(--space-3) center;
  background-size: 0.3em 0.3em, 0.3em 0.3em;
  background-repeat: no-repeat;
  padding-right: var(--space-10);
}

/* The label above a field: small, upper case and tracked far apart, which is
 * the loudest single signal of this design and the reason a label no longer
 * needs a weight to be told from the value under it. `--tracking-label` is
 * the site's own 0.32em, and the size drops a step to pay for it — 0.6875 rem
 * at 0.32em is almost exactly the site's `.card-loc`, which is the same kind
 * of line in the same position. */
.form-label {
  display: block;
  font-size: var(--text-2xs);
  line-height: var(--text-2xs-line);
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-muted);
  margin-bottom: var(--space-2);
}

.form-text {
  /* A block, because the hint is a `span`: inline, its own line height lost
   * to the field's and a two-line hint stood at nearly double spacing. */
  display: block;
  font-size: var(--text-xs);
  line-height: var(--text-xs-line);
  color: var(--color-muted);
  margin-top: var(--space-1-5);
}

/* `[type=checkbox]` as well as the class: Bootstrap sets `0.25em` on the
 * attribute selector, which is more specific than the class and produced a
 * 3.75 px corner. Every radius in this design is zero, so the box is square —
 * which is also what the site's own keyed option squares are. */
.form-check-input,
.form-check-input[type="checkbox"] {
  --bs-border-color: var(--color-border-control);
  border-width: var(--border-control);
  border-radius: var(--radius-sm);
  width: var(--target-min);
  height: var(--target-min);
  margin-top: 0;
  background-color: var(--color-surface);
}

/* Bootstrap draws the tick as a background image with a white stroke baked
 * into the data URI, so the fill under it has to be dark in BOTH modes.
 * `--color-primary-fill` is the near-black on paper and the IVORY on the dark
 * page, which put a white tick on an ivory box and left a checked box looking
 * empty. `--color-accent-surface` is the role that is dark in both sets — the
 * black band of the site in the light one, the raised ground in the dark. */
.form-check-input:checked {
  background-color: var(--color-accent-surface);
  border-color: var(--color-check-edge);
}

.form-check-input:focus {
  border-color: var(--color-focus);
  box-shadow: var(--shadow-ring);
}

.form-check-input:focus-visible {
  outline: var(--border-focus) solid var(--color-focus);
  outline-offset: var(--border-focus-offset);
  box-shadow: none;
}

.form-check {
  display: flex;
  align-items: start;
  gap: var(--space-3);
  padding-left: 0;
  min-height: var(--target-min);
}

.form-check .form-check-input { flex: 0 0 auto; margin-left: 0; }

.form-check-label {
  font-size: var(--text-sm);
  line-height: var(--text-sm-line);
  color: var(--color-heading);
}

/* Every field sits in one of these, so that the gap between two touch
 * targets is the 24 px WCAG 2.5.8 asks for. */
.field {
  margin-bottom: var(--space-5);
}

/* A field that was rejected shows it, whatever kind of field it is. The rule
 * used to name `.form-control` and `.form-select` only, so a checkbox and a
 * switch kept their ordinary edge while the error text appeared underneath
 * them -- the message said something was wrong and the control said nothing.
 * The switch needs the track rather than the box, because the box is the
 * thing that is invisible by design. */
.field--invalid .form-control,
.field--invalid .form-select,
.field--invalid .form-check-input {
  border-color: var(--color-danger);
}

/* The switch draws no border in its ordinary state, so a colour has nothing
 * to change and it gets an edge only when it is wrong. `outline` rather than
 * `border`, because the track is `box-sizing: border-box` and the knob is
 * positioned from the padding edge: a border would move the knob a pixel and
 * shorten its travel by two, so the switch would sit slightly differently in
 * its error state than in its ordinary one. An outline takes no space. */
.field--invalid .switch__control {
  outline: var(--border-control) solid var(--color-danger);
  outline-offset: 0;
}

.field-error {
  display: flex;
  align-items: center;
  gap: var(--space-1-5);
  margin-top: var(--space-1-5);
  font-size: var(--text-xs);
  line-height: var(--text-xs-line);
  color: var(--color-danger);
  font-weight: var(--font-weight-medium);
}

.field-required {
  color: var(--color-danger);
  margin-left: var(--space-1);
}

/* A field with an action flush inside its right edge: the mail glyph on an
 * address, the external-link glyph on a website, the calendar on a date. */
.field-attached {
  display: flex;
  align-items: stretch;
}

.field-attached .form-control,
.field-attached .form-select { flex: 1 1 auto; }

.field-attached .btn {
  margin-inline-start: calc(-1 * var(--border-control));
}

/* Two columns where the field pairs read as pairs. */
.field-pair {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0 var(--space-4);
}

@media (min-width: 640px) {
  .field-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ------------------------------------------------------------------ *
 * The switch. The setting control of this design: something is turned on
 * rather than chosen from a list.
 *
 * It is a real checkbox under it, so it submits, it is reachable by keyboard
 * and it is announced as a checkbox. What is drawn is the track and the
 * knob; the state is never written out beside it, because the control says
 * it — but the label always names what is being switched.
 * ------------------------------------------------------------------ */

.switch {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: var(--target-primary);
  cursor: pointer;
}

.switch__label {
  font-size: var(--text-sm);
  line-height: var(--text-sm-line);
  color: var(--color-heading);
  font-weight: var(--font-weight-medium);
}

.switch__hint {
  display: block;
  font-size: var(--text-xs);
  line-height: var(--text-xs-line);
  color: var(--color-muted);
  font-weight: var(--font-weight-regular);
}

.switch__control {
  appearance: none;
  flex: 0 0 auto;
  position: relative;
  /* The track, the knob and its travel are the only measurements in this
   * file that stood as literals while the matching tokens lay beside them:
   * 2.75rem is `--control-height-lg`, 1.5rem is `--space-6`, the knob's
   * 1.125rem is `--icon-size-lg` and its travel of 1.25rem is `--space-5`.
   * Same rendered pixels, one fewer place where the scale is a coincidence. */
  width: var(--control-height-lg);
  height: var(--space-6);
  margin: 0;
  border: 0;
  /* The track is square like everything else and only the knob stays round:
   * `--radius-pill` is kept for a round HANDLE, because a round handle reads
   * as a handle, and a switch is the one control in the working area that has
   * one. */
  border-radius: var(--radius-track);
  background-color: var(--color-switch-off);
  cursor: pointer;
  transition: background-color var(--motion-base) var(--motion-ease);
}

.switch__control::after {
  content: "";
  position: absolute;
  /* The inset is what is left when a 1.125rem knob sits in a 1.5rem track,
   * halved -- it follows from the two tokens above rather than standing
   * beside them. */
  top: calc((var(--space-6) - var(--icon-size-lg)) / 2);
  left: calc((var(--space-6) - var(--icon-size-lg)) / 2);
  width: var(--icon-size-lg);
  height: var(--icon-size-lg);
  border-radius: var(--radius-pill);
  background-color: var(--color-switch-knob);
  transition: transform var(--motion-base) var(--motion-ease);
}

.switch__control:checked { background-color: var(--color-primary-fill); }

.switch:hover .switch__control { background-color: var(--color-border-control); }

.switch:hover .switch__control:checked {
  background-color: var(--color-primary-fill-hover);
}

/* The travel is the track less the knob less both insets, which is exactly
 * `--space-5`. */
.switch__control:checked::after { transform: translateX(var(--space-5)); }

.switch__control:focus-visible {
  outline: var(--border-focus) solid var(--color-focus);
  outline-offset: var(--border-focus-offset);
}

.switch__control:disabled {
  background-color: var(--color-border-disabled);
  cursor: not-allowed;
}

/* ------------------------------------------------------------------ *
 * The segmented control. A square tinted track whose chosen segment fills
 * black, which is what the site's own filter chips do. Used where two or
 * three views of the same thing are switched between — never for navigation
 * to another page.
 * ------------------------------------------------------------------ */

.segmented {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1);
  border-radius: var(--radius-track);
  background-color: var(--color-surface-tinted);
  list-style: none;
  margin: 0 0 var(--space-5);
  max-width: 100%;
  overflow-x: auto;
  /* The same reason as `.table-responsive`: a scroll container's content
   * still counts towards the document's scroll width in Chromium unless the
   * paint is contained. Found at 390 px with the font size at 150 per cent,
   * where a three-segment control is wider than a phone. */
  contain: paint;
}

/* A segmented control wraps on a telephone instead of scrolling its last
 * segments out of sight: "Mein Kalender / Alle" was cut at "MEIN S". */
@media (max-width: 767px) {
  .segmented { flex-wrap: wrap; }
}

.segmented a,
.segmented button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-height-sm);
  padding: var(--space-1-5) var(--space-4);
  border: 0;
  border-radius: var(--radius-control);
  background-color: transparent;
  color: var(--color-heading);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-button);
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.segmented .icon { color: var(--color-muted); }

/* Hover on a segment that is not the chosen one. The `:not()` is what keeps
 * the chosen one chosen: `.segmented a:hover` weighs (0,2,1) and
 * `.segmented [aria-current="page"]` only (0,2,0), so pointing at the active
 * segment took its own ground away -- while its glyph kept it, because that
 * selector weighs (0,3,0) and won. The active state came apart into two
 * halves under the pointer. */
.segmented a:not([aria-current="page"]):not([aria-selected="true"]):hover,
.segmented button:not([aria-current="page"]):not([aria-selected="true"]):hover {
  background-color: var(--color-brand-wash);
  color: var(--color-heading);
}

/* The chosen segment fills, and it fills with the near-black rather than with
 * a raised white pill. The pill was an elevation, and elevation is the one
 * signal that does not survive this palette: in the light set it sat 3.65 L*
 * above its tinted track and read as raised, in the dark set the two swapped
 * round and it measured 1.14:1 BELOW its own track while the black shadow on
 * a near-black ground showed nothing at all. A fill states the same thing in
 * both sets and in forced colours, and it is what the site's own filter bar
 * does. `--color-primary-fill` inverts with the mode and its label role comes
 * with it, so the pair is legible either way round. */
.segmented [aria-current="page"],
.segmented [aria-selected="true"] {
  background-color: var(--color-primary-fill);
  color: var(--color-on-primary);
}

.segmented [aria-current="page"] .icon,
.segmented [aria-selected="true"] .icon { color: var(--color-on-primary); }

/* ------------------------------------------------------------------ *
 * The panel — a section with a heading, which is what this application has
 * instead of Bootstrap's card with its header, body and footer.
 * ------------------------------------------------------------------ */

.panel {
  background-color: var(--color-surface);
  border: var(--border-hairline) solid var(--color-hairline);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: var(--space-6);
  margin-bottom: var(--space-6);
}

.panel > :last-child { margin-bottom: 0; }

.panel__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* `anywhere` and not the `break-word` the body already carries, and the
   * difference is the only reason this line exists: `break-word` breaks a
   * long word when a line cannot be made, but it does **not** lower the
   * element's min-content width. This title is a flex container, its text is
   * an anonymous flex item, and such an item's floor is its min-content --
   * so the box never shrank far enough for `break-word` to get a turn.
   * `anywhere` lowers that floor, which is what makes the intent stated at
   * the top of this file actually reach here.
   *
   * Measured: the guide article at 320 px with the base font at the largest
   * step of N-10 pushed the page 17 px sideways, on the FAQ question whose
   * first noun is the 22-letter compound for the property gains tax --
   * 1.4.10 and 1.4.4. Now 0, and unchanged on the questions page, the home
   * page and the valuation page.
   *
   * Hyphenation would read better and is deliberately not used: the rule at
   * the top of this file keeps `hyphens: auto` to prose and off controls, and
   * a `summary` is the control that opens a disclosure. Handed to
   * `va-18-design-run` as the prettier option. */
  overflow-wrap: anywhere;
  /* A serif at the regular cut, like every other heading — and a step larger
   * than the sans it replaces, because at `--text-md` Cormorant read as body
   * text rather than as the title of the panel under it. */
  font-family: var(--font-family-display);
  font-variant-numeric: lining-nums;
  font-size: var(--text-xl);
  line-height: var(--display-line);
  font-weight: var(--font-weight-regular);
  margin-bottom: var(--space-4);
}

.panel__title .icon { color: var(--color-muted); }

/* A unit inside a label in tracked capitals keeps its own case: "m²", not
 * "M²". `core.templatetags.forms.label_units` puts it in this span. */
.form-label .unit {
  text-transform: none;
  letter-spacing: 0;
}

/* Everything that opens and closes. The summary carries one chevron after
 * its text that turns when it is open. Before this there was no rule at all:
 * the browser drew its own black triangle, beside a second small chevron icon
 * where a template had added one. A summary drawn as a button is left alone. */
details > summary:not(.btn) {
  list-style: none;
  cursor: pointer;
}

details > summary:not(.btn)::-webkit-details-marker { display: none; }

details > summary:not(.btn)::after {
  content: "";
  display: inline-block;
  flex: 0 0 auto;
  width: 0.5em;
  height: 0.5em;
  margin-left: 0.6em;
  border-right: var(--border-control) solid currentColor;
  border-bottom: var(--border-control) solid currentColor;
  transform: translateY(-0.2em) rotate(45deg);
  transition: transform var(--motion-base) var(--motion-ease);
}

details[open] > summary:not(.btn)::after {
  transform: translateY(0.1em) rotate(-135deg);
}

/* A panel that opens and closes: the summary is its header line and the
 * chevron stands at its far end. A closed panel drops the title's bottom
 * margin, which otherwise left its one line sitting high in the box. */
details.panel > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

/* The chevron keeps its place at the end of the first line: with a wrapping
 * row a two-line question pushed it onto a line of its own. */
details.panel > summary > * { min-width: 0; }

details.panel > summary::after { margin-left: auto; }

details.panel:not([open]) > summary { margin-bottom: 0; }

details.panel[open] > summary { margin-bottom: var(--space-4); }

/* The heading with an action on its right — "Neu hinzufügen" beside
 * "Ansprechpartner". */
.panel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.panel__head .panel__title { margin-bottom: 0; }

.panel--flush { padding: 0; overflow: hidden; }

/* A panel inside a grid carries no margin of its own: the grid's own gap is
 * the distance, and the margin was adding a second one under every card. */
.cards > .panel,
.split > .panel,
.metric-grid > .panel { margin-bottom: 0; }

/* Three or four cards across, as many as fit, one under the other on a
 * phone. `.split` is the two-column layout and stays what it is; a row of
 * three principles in a two-column grid is two and then a lonely one. */
.cards {
  display: grid;
  gap: var(--space-4);
  /* `min(…, 100%)` around the track minimum, and it is the font-size
   * control that needs it. A minimum written in `rem` grows with the
   * text: at the 150 % step 15rem is 360 px, which is wider than a 320 px
   * phone, so the grid could not place a single column and the page went
   * sideways -- measured 384 against 320 on the home page. `min()` caps
   * the minimum at the width that is actually available and changes
   * nothing wherever the room is there. The same cap is on the four other
   * grids whose minimum is a `rem`. */
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
}

/* Two columns where there is room for two.
 *
 * It lives here rather than in `app.css` because both shells use it — six
 * public templates among them. It was in `app.css` alone, which the public
 * pages do not load, so the contact page, the valuation page and the two
 * property pages laid their two columns out as one and nobody could see why. */
.split {
  display: grid;
  gap: var(--space-5);
}

@media (min-width: 768px) {
  .split { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Two cards side by side that each keep their own height, where one of them
 * runs far longer than the other: stretched, the short one became an empty
 * white box beside the long one. */
.split--top { align-items: start; }

/* Two columns of unequal weight: the thing being done on the left, what
 * supports it on the right. A form and its rail are the case this exists for
 * -- two equal halves give the form 578 px, which is one field a line, and
 * the rail 578 px of mostly nothing. */
@media (min-width: 62rem) {
  .split--wide { grid-template-columns: minmax(0, 1.75fr) minmax(0, 1fr); }
  .split--rail { grid-template-columns: minmax(0, 1fr) var(--aside-width); }
}

/* A grid track is `auto` wide by default, which means its content can push it
 * past the page. Anything that scrolls inside a column — a segmented control,
 * a table, a long word — needs the track to be allowed to be narrower than
 * its content, and `min-width: 0` is what says so. Without it the style guide
 * scrolled sideways on a phone. */
.split > *,
.cards > *,
.metric-grid > *,
.article-grid > *,
.property-grid > *,
.gallery > *,
.unit-list > * { min-width: 0; }

.panel--tinted { background-color: var(--color-surface-subdued); }

/* The one dark card in a rail of light ones: what the reader is meant to do
 * next, and it is the same black the site closes every page with. Its own
 * heading colour is the brand gold rather than the ink -- on this ground the
 * gold reaches 7.06:1 and is the one place `--gold` may carry a word. */
.panel--accent {
  background-color: var(--black);
  border-color: var(--color-brand-line);
  color: var(--text-cool-soft);
}

.panel--accent .panel__title,
.panel--accent h2,
.panel--accent h3 { color: var(--color-brand); }

.panel--accent p,
.panel--accent li { color: var(--text-cool-soft); }

/* A numbered list that is read as a sequence rather than as an enumeration:
 * the figure in the gold, on its own line-height, and the step beside it. */
.steps {
  counter-reset: step;
  margin: 0;
  padding: 0;
  list-style: none;
}

.steps > li {
  counter-increment: step;
  position: relative;
  padding-left: var(--space-8);
  margin-bottom: var(--space-3);
}

.steps > li:last-child { margin-bottom: 0; }

.steps > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  font-family: var(--font-family-display);
  font-variant-numeric: lining-nums;
  font-size: var(--text-lg);
  line-height: 1.2;
  color: var(--color-brand-ink);
}

.panel--accent .steps > li::before { color: var(--color-brand); }

/* The one place gold is a frame rather than a mark on its own. */
.panel--brand {
  border-color: var(--color-brand-line);
  border-width: var(--border-focus);
}

/* ------------------------------------------------------------------ *
 * Tables
 * ------------------------------------------------------------------ */

.table {
  --bs-table-color: var(--color-text);
  --bs-table-bg: transparent;
  --bs-table-border-color: var(--color-hairline);
  --bs-table-striped-bg: var(--color-surface-subdued);
  --bs-table-striped-color: var(--color-text);
  --bs-table-hover-bg: var(--color-primary-softer);
  --bs-table-hover-color: var(--color-heading);
  margin-bottom: 0;
  font-size: var(--text-sm);
}

.table > :not(caption) > * > * {
  padding: var(--space-3) var(--space-4);
  vertical-align: middle;
  background-color: transparent;
}

/* `min-height` has no effect on a `display: table-cell`, so this declaration
 * never did anything -- and what it hid is a real disagreement: `--row-height`
 * says 40 px and the rendered rows measure 49, because the height comes from
 * the cell padding and the line height rather than from here. The declaration
 * is gone so the disagreement is visible; closing it would mean changing the
 * padding on fifteen list templates, and how dense a table should be is the
 * owner's decision rather than a side effect of a tidy-up.
 * See the run's report -> offene Posten. */

/* The header band: tinted, quiet, and the sort target a plain link inside it
 * rather than the whole cell taking a colour.
 *
 * Upper case and tracked, because the weight it used to be told apart by is
 * gone: 500 against the 400 of the cells under it is a difference nobody
 * reads across a table of forty rows. The size drops a step to pay for the
 * tracking, which is the same trade the form label makes. */
.table thead th {
  background-color: var(--color-surface-tinted);
  color: var(--color-muted);
  font-size: var(--text-2xs);
  line-height: var(--text-2xs-line);
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  white-space: nowrap;
  border-bottom-color: var(--color-hairline);
}

.table thead th a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: var(--target-min);
  min-width: var(--target-min);
  color: var(--color-muted);
  text-decoration: none;
}

.table thead th a:hover {
  color: var(--color-heading);
  text-decoration: underline;
}

.table tbody td { color: var(--color-text); }

/* The first cell of a row is the record, and it is what carries the name.
 * Gold and not underlined until it is pointed at: a column of underlined
 * names reads as a column of rules. */
.table tbody td a {
  font-weight: var(--font-weight-medium);
  text-decoration: none;
}

.table tbody td a:hover { text-decoration: underline; }

.table caption {
  caption-side: top;
  padding: 0 0 var(--space-3);
  color: var(--color-muted);
  font-size: var(--text-sm);
}

/* The column that carries running text: a title, a subject, a reason.
 *
 * A table that does not fit gives every column the width of its longest
 * word, and the column with the most words loses the most: at 1024 px the
 * title of a property stood at four lines of one word each beside a status
 * column with room to spare. The floor keeps it a column of sentences; the
 * table scrolls sideways a little earlier and reads properly when it does.
 * `min()` because the font-size control scales a `rem`, and a floor wider
 * than a telephone would be a scroll on every screen. */
.table td.cell-text { min-width: min(16rem, 70vw); overflow-wrap: anywhere; }

/* Figures line up under one another and read as a column of numbers. */
.table .u-num,
.table td.u-num,
.table th.u-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* A figure is one unit: "CHF" never on one line and "500.00" on the next.
 * The heads of numeric columns still wrap, the values do not. */
.table td.u-num,
.amount {
  white-space: nowrap;
}

/* Wide content scrolls inside its own container and is reachable by keyboard
 * while it does — a scroll container without a tab stop cannot be scrolled
 * without a mouse.
 *
 * `contain: paint` is not decoration. Without it a table wider than the
 * container still counts towards the *document's* scroll width in Chromium,
 * even though the container clips it: the page then scrolls sideways into
 * two hundred pixels of nothing on a phone, on exactly the two screens whose
 * tables have the most columns. Measured with `scripts/`-less browser
 * checks at 390 px; `overflow-x` on any ancestor does not fix it and
 * `contain: paint` does, while the table inside still scrolls. */
.table-responsive {
  border: var(--border-hairline) solid var(--color-hairline);
  border-radius: var(--radius-card);
  background-color: var(--color-surface);
  contain: paint;
}

/* On a telephone a table that runs wider than the screen says so: a soft
 * shade on the side where there is more to scroll to, gone once that edge
 * is reached. Two covers in the surface colour travel with the content and
 * two shades stay with the box, so each shade shows only while content lies
 * beyond it. Without it the table simply ended at the card's edge with
 * columns cut in half and nothing to suggest the rest. */
@media (max-width: 767px) {
  .table-responsive {
    background:
      linear-gradient(to right, var(--color-surface) 40%, transparent) left center / 2rem 100% no-repeat local,
      linear-gradient(to left, var(--color-surface) 40%, transparent) right center / 2rem 100% no-repeat local,
      linear-gradient(to right, var(--shadow-tint-overlay-near), transparent) left center / 0.75rem 100% no-repeat scroll,
      linear-gradient(to left, var(--shadow-tint-overlay-near), transparent) right center / 0.75rem 100% no-repeat scroll,
      var(--color-surface);
  }
}

.table-responsive:focus-visible {
  outline: var(--border-focus) solid var(--color-focus);
  /* Inset, and it is the one place that is: the ring of a scroll container
   * drawn outside its own box lands under the panel edge beside it. The
   * magnitude stays the token's. */
  outline-offset: calc(-1 * var(--border-focus-offset));
}

/* ------------------------------------------------------------------ *
 * The building blocks this design has and Bootstrap has not
 * ------------------------------------------------------------------ */

/* The word mark. Gold on a dark ground, near-black on a light one, and the
 * division is measured rather than chosen: the brand gold reaches 2.26:1 on
 * the paper ground and 7.93:1 on the black band, so the light set takes the
 * heading colour and only the dark ground gets the gold itself. */
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: var(--font-weight-medium);
  font-size: var(--text-sm);
  line-height: var(--text-sm-line);
  letter-spacing: var(--tracking-wordmark);
  text-transform: uppercase;
  color: var(--color-heading);
  text-decoration: none;
  white-space: nowrap;
  min-height: var(--target-min);
}

.wordmark:hover { color: var(--color-link); }

.wordmark--on-dark,
.wordmark--on-dark:hover { color: var(--color-brand); }

/* The mark itself: the initials in gold on a black square, so the sidebar
 * and the header start with a shape rather than with a word. Square, like
 * everything else, and it is the one place a seal is what is wanted. */
.wordmark__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-6);
  height: var(--space-6);
  border-radius: var(--radius-sm);
  border: var(--border-hairline) solid var(--color-brand-line);
  background-color: var(--color-accent-surface);
  color: var(--color-brand);
  font-size: var(--text-2xs);
  letter-spacing: 0;
  flex: 0 0 auto;
}

/* The gold rule under the header. On the site it is a hairline at 45 per
 * cent, and it is the first of the four places gold appears at all. */
.brand-rule {
  border: 0;
  border-top: var(--border-focus) solid var(--color-brand-line);
  margin: 0;
  opacity: 1;
}

/* ------------------------------------------------------------------ *
 * The badge, and the tone set behind it.
 *
 * A badge is a square tag with a pale ground and dark text of the same hue,
 * set upper case and tracked. It is how every state in this application is
 * drawn: a phase count, a table state cell, a role, a result. Never a
 * saturated fill, and — since the radius went to zero everywhere — never a
 * pill either, whatever the class is still called. The name stays because it
 * is in a hundred templates and a rename is a bigger change than a shape.
 *
 * There is no coloured dot in front of it any more. A dot that is the same
 * shape in every tone tells a colour-blind reader nothing that the word
 * beside it does not already say, and it was what made these look dated.
 * Where a state deserves a shape, it carries a glyph — `{% icon %}` inside
 * the badge — and that shape genuinely differs per state.
 * ------------------------------------------------------------------ */

/* A row of pills, e.g. the marks of a contact. */
.pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.pill {
  display: inline-flex;
  font-family: var(--font-family-base);
  align-items: center;
  gap: var(--space-1-5);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-2xs);
  line-height: var(--text-2xs-line);
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-pill);
  text-transform: uppercase;
  background-color: var(--color-tone-grey-soft);
  color: var(--color-on-tone-grey);
  white-space: nowrap;
  vertical-align: middle;
  text-decoration: none;
}

a.pill:hover { text-decoration: underline; }

/* A pill that is a LINK may break; one that is a badge may not, and the
 * distinction is exactly the element. A badge is a `span` and says one short
 * word -- „Verkauft", „Reserviert" -- which must stay on one line to read as a
 * badge. A link wearing the same shape carries the name of something, and a
 * name can be long: at the 150 % step of the font-size control the rubric
 * „Recht und Steuern beim Verkauf" measured 303 px inside a 272 px column and
 * took the whole page into sideways scrolling.
 *
 * A badge that carries a name is a name as well and says so with
 * `pill--name`: the delivery a portal goes through, „über SMG
 * (ImmoScout24.ch und Homegate.ch)", measured 432 px on a 390 px telephone
 * on 2026-09-26. */
a.pill,
.pill--name { white-space: normal; }

.pill .icon { width: var(--icon-size-sm); height: var(--icon-size-sm); }

.pill--blue { background-color: var(--color-tone-blue-soft);
              color: var(--color-on-tone-blue); }
.pill--green,
.pill--positive { background-color: var(--color-tone-green-soft);
                  color: var(--color-on-tone-green); }
.pill--red,
.pill--problem { background-color: var(--color-tone-red-soft);
                 color: var(--color-on-tone-red); }
.pill--amber { background-color: var(--color-tone-amber-soft);
               color: var(--color-on-tone-amber); }
.pill--violet { background-color: var(--color-tone-violet-soft);
                color: var(--color-on-tone-violet); }
.pill--teal { background-color: var(--color-tone-teal-soft);
              color: var(--color-on-tone-teal); }
.pill--grey,
.pill--quiet { background-color: var(--color-tone-grey-soft);
               color: var(--color-on-tone-grey); }

/* An outlined badge, for a count that must not read as a state. */
.pill--outline {
  background-color: transparent;
  border: var(--border-hairline) solid var(--color-hairline);
  color: var(--color-muted);
}

/* The eyebrow above a section heading. It is a modifier on the badge rather
 * than a variant of it, and what it modifies is now the ground: the site's
 * eyebrow is a gold line of small capitals on nothing, and a tinted block
 * behind it turns it back into the badge it is written on top of. It used to
 * carry the blue tone, which this brand does not have at all.
 *
 * `display: inline-flex` and not `block`: a block pill stretches to the width
 * of the column, which is what it did until this was written. `public.css`
 * takes it further on the public side — flex, and the 44 px gold rule the
 * survey found before nearly every one — and that rule works only because
 * nothing here paints a ground under it. The size is `--text-xs` for the same
 * reason: the two sheets have to say the same thing about the same line. */
.section-label {
  display: inline-flex;
  margin-bottom: var(--space-3);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  background-color: transparent;
  color: var(--color-brand-ink);
  padding-inline: 0;
}

/* The two states that wear the eyebrow rather than a badge — "Abgelaufen"
 * over the heading of a dead link page, "Gesendet" over a confirmation. They
 * keep the ground, because a state is a tone in this design and losing the
 * tone would leave two red and green words indistinguishable from the gold
 * eyebrow beside them on the next page. Only the plain eyebrow goes bare. */
.pill--problem.section-label {
  background-color: var(--color-tone-red-soft);
  color: var(--color-on-tone-red);
  padding-inline: var(--space-3);
}

.pill--positive.section-label {
  background-color: var(--color-tone-green-soft);
  color: var(--color-on-tone-green);
  padding-inline: var(--space-3);
}

/* A status pill keeps its own width and has no eyebrow rule in front of it.
 * As a flex box it stretched across the card with the gold line inside. */
:is(.pill--positive, .pill--problem, .pill--quiet).section-label {
  display: inline-flex;
  width: fit-content;
}

:is(.pill--positive, .pill--problem, .pill--quiet).section-label::before {
  content: none;
}

/* Muted text that ends up on a tinted block takes the tone measured for that
 * ground rather than the one measured for a surface. */
.muted-on-tint,
.section--tinted .muted { color: var(--color-on-tint); }

/* ------------------------------------------------------------------ *
 * Avatars
 * ------------------------------------------------------------------ */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: var(--avatar-size);
  height: var(--avatar-size);
  border-radius: var(--radius-pill);
  /* The hairline is for the dark set, where the accent surface and the
   * surface it sits on are one step apart and the circle otherwise has no
   * edge at all. On white it is invisible and costs nothing. */
  border: var(--border-hairline) solid var(--color-hairline);
  background-color: var(--color-accent-surface);
  color: var(--color-brand);
  font-size: var(--text-2xs);
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-pill);
  text-transform: uppercase;
  /* The top bar's avatar is a link to the account; initials are not a
   * sentence and take no underline. */
  text-decoration: none;
}

.avatar--sm {
  width: var(--avatar-size-sm);
  height: var(--avatar-size-sm);
}

.avatar--lg {
  width: var(--avatar-size-lg);
  height: var(--avatar-size-lg);
  font-size: var(--text-xs);
}

/* ------------------------------------------------------------------ *
 * Checkmark list, with a gold-tinted square tick instead of a bullet.
 * ------------------------------------------------------------------ */

.checklist {
  list-style: none;
  padding-left: 0;
  margin-block: 0 var(--space-4);
}

.checklist li {
  position: relative;
  padding-left: var(--space-8);
  margin-bottom: var(--space-3);
}

.checklist li::before {
  content: "\2713";
  position: absolute;
  left: 0;
  top: 0.1em;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--space-5);
  height: var(--space-5);
  border-radius: var(--radius-sm);
  background-color: var(--color-tone-amber-soft);
  color: var(--color-on-tone-amber);
  font-size: var(--text-2xs);
  font-weight: var(--font-weight-medium);
}

/* ------------------------------------------------------------------ *
 * The tinted section grounds. Colour carries meaning at section level here,
 * not only at component level.
 * ------------------------------------------------------------------ */

.split > .section--problem { align-self: start; }

/* A panel beside a taller one ends where its content ends. */
.split:has(> .locality),
.split--wide { align-items: start; }

.section--problem {
  background-color: var(--color-ground-problem);
  border-radius: var(--radius-card);
  padding: var(--space-6);
}

.section--solution {
  background-color: var(--color-ground-solution);
  border-radius: var(--radius-card);
  padding: var(--space-6);
}

.section--accent {
  background-color: var(--color-accent-surface);
  color: var(--color-on-accent);
  border-radius: var(--radius-card);
  padding: var(--space-8);
}

.section--accent h1,
.section--accent h2,
.section--accent h3 { color: var(--color-on-accent); }

.section--accent a:not(.btn) { color: var(--color-brand); }

/* ------------------------------------------------------------------ *
 * System messages. Every one of them carries a word and a glyph as well as
 * a colour — the tint alone is not the statement.
 * ------------------------------------------------------------------ */

.notice {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-4);
  border-radius: var(--radius-card);
  border: var(--border-hairline) solid transparent;
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  line-height: var(--text-sm-line);
}

.notice > :last-child { margin-bottom: 0; }

.notice__body { flex: 1 1 14rem; min-width: 0; }

.notice__body > :last-child { margin-bottom: 0; }

.notice .icon {
  margin-top: 0.15em;
  width: var(--icon-size-lg);
  height: var(--icon-size-lg);
}

/* Each notice is framed in its own text colour rather than in a second tone.
 * The three borders named a DIFFERENT tone from the ground they sat on — a
 * blue edge around a gold notice — which was invisible while the palette was
 * one cool family and is a plain mistake in this one. The text colour is the
 * value measured at 4.5:1 against that very ground, so a hairline of it is
 * the one edge that is certain to be seen. */
.notice--info {
  background-color: var(--color-ground-solution);
  color: var(--color-on-ground-solution);
  border-color: var(--color-on-ground-solution);
}

.notice--success {
  background-color: var(--color-ground-positive);
  color: var(--color-on-ground-positive);
  border-color: var(--color-on-ground-positive);
}

.notice--warning,
.notice--error {
  background-color: var(--color-ground-problem);
  color: var(--color-on-ground-problem);
  border-color: var(--color-on-ground-problem);
}

/* The word in front of the sentence. Weight told it from the sentence before
 * and no longer can, so it takes the case and the tracking every other small
 * label in this design has. */
.notice__label {
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-button);
  text-transform: uppercase;
}

/* A notice that carries an action on its right — the documents band, the
 * second-factor reminder.
 *
 * `flex: 0 1 auto` and not `0 0 auto`, and the shrink digit is the whole of
 * it. With `0` the form is sized to the button's max-content width and refuses
 * to give any of it back, so at 320 px with the base font raised by the
 * control of N-10 the documents band pushed the whole page sideways — 44 px
 * at the largest step, which is 1.4.10 Reflow and 1.4.4 together. `.btn`
 * already carries `white-space: normal` for exactly this case; it was simply
 * never given the room to use it. The label now wraps to a second line and
 * the page does not move. `min-width: 0` is the companion the flex spec
 * needs: a flex item's floor is its min-content width unless it is said
 * otherwise, and without this line the shrink digit alone does nothing.
 *
 * Measured on `/app/` at 320, 375, 768 and 1440 across all four font scales:
 * 44 px and 5 px of sideways scrolling to 0, nothing worse anywhere, the
 * button above 24 × 24 in every cell, and untouched at 768 and 1440.
 *
 * Found on 2026-09-02 and not before, because the band only renders for an
 * account whose accepted version differs — and `PRIVACY_POLICY_VERSION` was
 * raised to 2026-09-02 by the compliance pass. On the day that ships it is
 * every account at once. */
.notice__action { margin-inline-start: auto; flex: 0 1 auto; min-width: 0; }

/* ------------------------------------------------------------------ *
 * Empty state. An unexplained empty table is where a fresh account is
 * abandoned, so this one is a block rather than a sentence.
 * ------------------------------------------------------------------ */

/* An empty state inside a list card is a band of that card rather than a card
 * of its own: it has no edge and no corners, because the card already has
 * both. */
.list-card .empty-state {
  border: 0;
  border-radius: 0;
}

.empty-state {
  text-align: center;
  padding: var(--space-12) var(--space-6);
  background-color: var(--color-surface);
  border: var(--border-hairline) solid var(--color-hairline);
  border-radius: var(--radius-card);
}

.empty-state__glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-12);
  height: var(--space-12);
  margin-bottom: var(--space-4);
  /* Square with an edge, rather than a tinted disc. With every radius at zero
   * the disc would have become a plain tinted block, and a block of ivory on
   * a white card has too little of an edge to read as a shape at all. */
  border-radius: var(--radius-card);
  border: var(--border-hairline) solid var(--color-brand-line);
  background-color: var(--color-brand-wash);
  color: var(--color-brand-ink);
}

.empty-state__glyph .icon {
  width: var(--space-5);
  height: var(--space-5);
}

.empty-state h2 {
  font-size: var(--text-2xl);
  margin-bottom: var(--space-2);
}

.empty-state p {
  max-width: var(--prose-max);
  margin-inline: auto;
  color: var(--color-muted);
  font-size: var(--text-sm);
  line-height: var(--text-sm-line);
}

/* ------------------------------------------------------------------ *
 * Breadcrumbs, pagination
 * ------------------------------------------------------------------ */

.breadcrumb {
  --bs-breadcrumb-divider: "\203A";
  --bs-breadcrumb-divider-color: var(--color-muted);
  --bs-breadcrumb-item-padding-x: var(--space-2);
  --bs-breadcrumb-margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  line-height: var(--text-sm-line);
  /* Bootstrap's breadcrumb wraps and brings no row gap with it. On a
   * three-level path at 375 px the second line moves under the first, and
   * the centres of two entries then sit 23.99 px apart -- WCAG 2.2 asks
   * for 24 under the spacing exception of 2.5.8, so the path failed by a
   * hundredth of a pixel. Four pixels of row gap put it beyond argument,
   * and at every width where the path does not wrap nothing moves. */
  row-gap: var(--space-1);
}

.breadcrumb-item.active { color: var(--color-muted); }

.pagination-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-top: var(--border-hairline) solid var(--color-hairline);
  background-color: var(--color-surface);
  font-size: var(--text-xs);
  line-height: var(--text-xs-line);
  color: var(--color-muted);
}

.pagination-bar nav ul {
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding-left: 0;
  margin-bottom: 0;
}

.pagination-bar a,
.pagination-bar span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--control-height-sm);
  min-width: var(--control-height-sm);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-control);
}

.pagination-bar a {
  border: var(--border-control) solid var(--color-hairline);
  color: var(--color-link);
  text-decoration: none;
  background-color: var(--color-surface);
}

.pagination-bar a:hover {
  background-color: var(--color-surface-tinted);
  border-color: var(--color-border-control);
}

.pagination-bar [aria-current="page"] {
  border: var(--border-control) solid var(--color-hairline);
  background-color: var(--color-surface-tinted);
  color: var(--color-heading);
  font-weight: var(--font-weight-medium);
}

/* ------------------------------------------------------------------ *
 * The consent banner overlays rather than pushing, so that nothing shifts
 * under a reader once it appears.
 * ------------------------------------------------------------------ */

.consent-banner {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  /* One below the support band. Both were on 60, and which of the two came
   * out on top was decided by document order alone -- while the band's own
   * comment says it deliberately rides along onto public pages, so the two
   * can stand at once. The band is the one that must never be hidden. */
  z-index: 59;
  padding: var(--space-4);
  background-color: var(--color-surface);
  border-top: var(--border-hairline) solid var(--color-hairline);
  box-shadow: var(--shadow-overlay);
}

.consent-banner__inner {
  max-width: var(--content-max);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.consent-banner p { margin-bottom: 0; }

.consent-banner__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.consent-banner__categories {
  border: 0;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.consent-banner__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* Refusing has to be as easy as accepting: the three buttons are the same
 * class, the same size and the same weight, and they share the row equally.
 * A reject button that is smaller, paler or further out is not an equivalent
 * choice, and that is the defect this one rule exists to prevent. */
.consent-banner__actions .btn { flex: 1 1 12rem; }

/* ------------------------------------------------------------------ *
 * The appearance controls of N-10 — theme and font size. They are icon
 * buttons on a square tinted track, which is the shape the site gives a
 * group of small controls: its own filter bar is the same thing one size
 * larger.
 * ------------------------------------------------------------------ */

.appearance {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1);
  border-radius: var(--radius-track);
  background-color: var(--color-surface-tinted);
}

.appearance__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-width: var(--control-height-sm);
  min-height: var(--control-height-sm);
  padding-inline: var(--space-2);
  border: 0;
  border-radius: var(--radius-control);
  background-color: transparent;
  color: var(--color-muted);
  font-size: var(--text-2xs);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}

.appearance__button:hover {
  background-color: var(--color-surface);
  color: var(--color-heading);
}

/* The chosen button fills, exactly as the chosen segment one component over
 * does, and for the same reason. What stood here was a white fill with a
 * shadow and an inset ring: the fill alone measured 1.1:1 against its track,
 * the label alone was one gold against one grey at almost the same luminance,
 * and the shadow was there to be discarded by forced colours. A near-black
 * fill under an ivory label states the pressed one in every mode, in
 * greyscale, and to a reader who tells no hues apart. The forced-colours
 * block at the end still draws it an edge, because a fill is a colour and
 * that mode replaces every one of them. */
.appearance__button[aria-pressed="true"] {
  background-color: var(--color-primary-fill);
  color: var(--color-on-primary);
}

.appearance__button[aria-pressed="true"]:hover {
  background-color: var(--color-primary-fill);
  color: var(--color-on-primary);
}

.appearance__button:disabled {
  color: var(--color-disabled);
  cursor: not-allowed;
}

/* Bootstrap's spinner is round at `50%`. It renders the same as the pill
 * token and is pointed at it so that the set of radii in the delivered pages
 * is the set the design declares. */
.spinner-border,
.spinner-grow { border-radius: var(--radius-pill); }

/* Alpine hides what it has not initialised yet. Without this rule the drawer
 * scrim is briefly visible on every page load. */
[x-cloak] { display: none !important; }

/* The loading state of an HTMX call. A button that simply does nothing on a
 * slow server is worse than an error message. */
.htmx-indicator {
  opacity: 0;
  /* `visibility` and not only `opacity`: at rest the indicator must be out of
   * the accessibility tree and out of the tab order, not merely transparent.
   * HTMX used to supply this half from its own injected stylesheet, which the
   * content security policy refuses -- so it is written here, where the rest
   * of the component already lives. */
  visibility: hidden;
  transition: opacity var(--motion-fast) var(--motion-ease);
}

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

.htmx-request[aria-busy="true"] { cursor: progress; }

/* The enrolment code of the second factor. Sized in `rem` like everything
 * else, so that raising the font size raises it too — a code somebody cannot
 * see is a code they cannot scan. The white ground is unconditional: a dark
 * theme would otherwise invert it, and an inverted QR code does not scan. */
.qr-code {
  width: 12rem;
  height: 12rem;
  max-width: 100%;
  background-color: var(--white);
  padding: var(--space-2);
  border-radius: var(--radius-control);
  border: var(--border-hairline) solid var(--color-hairline);
}

.qr-code path { fill: var(--black); }

/* ------------------------------------------------------------------ *
 * Prose and pairs
 * ------------------------------------------------------------------ */

.prose {
  max-width: var(--prose-max);
}

.prose h2 { margin-top: var(--space-10); }
.prose h3 { margin-top: var(--space-8); }
.prose ul, .prose ol { margin-bottom: var(--space-4); }
.prose li { margin-bottom: var(--space-2); }
.prose address { margin-bottom: var(--space-4); font-style: normal; }

/*
 * A table in prose is the one block that cannot be made narrower. Its columns
 * are as wide as their widest cell, and an article on the
 * Grundstueckgewinnsteuer carries cells like "abzueglich Handaenderungssteuer
 * und Gebuehren beim Erwerb": measured at 380 px against a 320 px telephone,
 * so the page scrolled sideways instead of the table. The table becomes its
 * own scroll container.
 *
 * Three declarations and each one is load-bearing. `width: max-content` keeps
 * the table shrink-wrapped the way an auto-layout table was, so nothing moves
 * at the widths where it already fitted -- without it a block-level table
 * fills the reading column and the narrow example table would grow from 562
 * to 640 px for no reason. `contain: paint` keeps the overflowing content out
 * of the document's scroll width in Chromium, which is the same effect the
 * segmented control was found to have. And `display: block` is what makes the
 * scroll container possible at all.
 */
.prose table {
  display: block;
  width: max-content;
  max-width: 100%;
  margin: var(--space-6) 0 var(--space-8);
  overflow-x: auto;
  contain: paint;
  border-collapse: collapse;
}

/* The cells of a table in running text. Without these the browser's own one
 * pixel stood between two columns, and a guide article read "beim Verkaufin
 * der Regel ja" across a row with no rule under it. */
.prose th,
.prose td {
  padding: var(--space-2) var(--space-5) var(--space-2) 0;
  border-bottom: var(--border-hairline) solid var(--color-brand-line-soft);
  text-align: left;
  vertical-align: top;
}

.prose th {
  font-weight: var(--font-weight-medium);
  border-bottom-color: var(--color-brand-line);
}

.prose th:last-child,
.prose td:last-child {
  padding-right: 0;
}

.prose caption {
  caption-side: top;
  padding: 0 0 var(--space-2);
  font-size: var(--text-sm);
  text-align: left;
  color: var(--color-muted);
}

/* The name-and-value lists of the legal pages: a register number, a retention
   period, a processing purpose. A definition list rather than a table,
   because the pairs are read one after another rather than compared down a
   column — and a table of two columns is announced as a table, which a
   screen reader then has to navigate as one. */
.pairs {
  display: grid;
  margin-bottom: var(--space-4);
  grid-template-columns: 1fr;
  gap: 0;
}

.pairs dt {
  font-weight: var(--font-weight-medium);
  color: var(--color-heading);
}

.pairs dd { margin: 0 0 var(--space-3); }

/* One column first and two from the small breakpoint up: at 320 px a term of
   twenty characters and its value would share four words a line. Written
   mobile first, like every other breakpoint in this project.
 *
 * The value column carries a minimum of its own, and that is what makes this
 * survive a narrow container. The query asks the window, not the box the list
 * stands in, so a list inside the 423 px panel of a property page is laid out
 * as though it had the whole 1440 px: the term column then grows to its
 * 20 rem limit before the value column — flexible, and therefore served last
 * — gets what is left, which was 29 px. At that width every value breaks
 * character by character down the page, and „SRE-DEMO-001" is read as a
 * column of single letters. With a floor of its own the value column is
 * served first and the term column takes what remains, so the two share the
 * panel instead. The floor is capped at 45 % so that it can never push the
 * grid wider than its container. */
@media (min-width: 640px) {
  .pairs {
    grid-template-columns:
      minmax(min(10rem, 100%), 20rem) minmax(min(14rem, 45%), 1fr);
    gap: var(--space-2) var(--space-6);
  }
  .pairs dd { margin-bottom: 0; }
}

/* The same pairs where they stand on their own across the full width: the
 * label over its value, and as many pairs a line as the room allows. A grid
 * cannot do this from a flat definition list -- the terms and the values are
 * siblings, so a grid puts the second term where the first value's neighbour
 * belongs. Columns flow them in pairs instead, which is what a register entry
 * of eight fields needs to stop being 470 px of a 1184 px page. */
.pairs--wide {
  display: block;
  column-width: 15rem;
  column-gap: var(--space-8);
}

.pairs--wide dt {
  /* A term is a label, not prose: set in tracked capitals and hyphenated
   * by the prose around it, it broke as "IDENTIFIKATI-ONSNUMMER". */
  hyphens: manual;
  break-after: avoid;
  font-size: var(--text-2xs);
  line-height: var(--text-2xs-line);
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-muted);
}

.pairs--wide dd {
  break-inside: avoid;
  margin: 0 0 var(--space-5);
  padding-top: var(--space-1);
  border-top: var(--border-hairline) solid var(--color-hairline);
  color: var(--color-heading);
}

/* The lead paragraph is the one piece of running text the site sets in the
 * serif: it is read as language rather than as interface, and it stands
 * directly under a heading in the same family. 1.5 is its own leading and not
 * `--display-line` — a paragraph of three lines at 1.12 is a block, not
 * prose — and it is a ratio rather than one of the `--text-*-line` lengths,
 * because the size it applies to is a clamp. */
.lead {
  font-family: var(--font-family-display);
  font-variant-numeric: lining-nums;
  font-size: var(--display-sm);
  line-height: 1.5;
  color: var(--color-text);
  font-weight: var(--font-weight-regular);
}

.muted { color: var(--color-muted); }

.stack > * + * { margin-top: var(--space-4); }

/* A list set as a stack is a list of records, not of points: no browser
 * bullet and no indent, so each entry starts on the card's own edge and an
 * empty state in it reads as a sentence rather than as the first point. */
ul.stack,
ol.stack {
  list-style: none;
  padding-left: 0;
}

/* ------------------------------------------------------------------ *
 * Key figures. A tile is a figure, its name, and a glyph that says which
 * kind of thing is being counted — which is what turns a row of numbers
 * into a row a person can scan.
 * ------------------------------------------------------------------ */

.metric-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
  gap: var(--space-4);
}

/* Six figures are two rows of three. Left to the rule above, a wide screen
 * sets five in a row and the sixth alone under them. */
@media (min-width: 64rem) {
  .metric-grid:has(> :nth-child(6):last-child) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 110rem) {
  .metric-grid:has(> :nth-child(6):last-child) {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
}

.metric {
  position: relative;
  background-color: var(--color-surface);
  border: var(--border-hairline) solid var(--color-hairline);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: var(--space-5);
  /* So that the amount below can be sized against this tile rather than
   * against the window: six tiles in a row are 168 px of text each whatever
   * the screen is, and a media query cannot see that. */
  container-type: inline-size;
}

.metric__glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-8);
  height: var(--space-8);
  margin-bottom: var(--space-3);
  border-radius: var(--radius-control);
  background-color: var(--color-tone-amber-soft);
  color: var(--color-on-tone-amber);
}

/* The figure is a serif and its name is not, which is the site's own fact
 * bar: a value read as language over a label read as interface. `tabular-nums`
 * stays because a column of figures has to line up; where Cormorant holds no
 * tabular set the browser ignores the line and nothing is lost. */
.metric__value {
  display: block;
  font-family: var(--font-family-display);
  font-size: var(--text-4xl);
  line-height: var(--display-line);
  font-weight: var(--font-weight-regular);
  color: var(--color-heading);
  /* Lining figures: the display face's old-style 0 reads as an o and 15 as I5. */
  font-variant-numeric: lining-nums tabular-nums;
}

/* An amount in a tile, which is the one value that does not fit.
 * „CHF 1'680'000.00" needs 255 px at the size above and the tile gives 168 px,
 * so until 2026-09-18 the turnover figure on the report page stood broken
 * across three lines — „CHF", „1'680'000.", „00" — because the project's
 * `overflow-wrap: break-word` applies to a number as readily as to a word.
 * Two things are needed and both are here: the figure is sized against its
 * own tile, and a number is never broken inside itself. Where even the
 * smallest step is too narrow the line breaks after the currency, which is
 * the one place a reader loses nothing. */
.metric__value--money {
  font-size: clamp(var(--text-xl), 10.5cqi, var(--text-4xl));
  overflow-wrap: normal;
  word-break: keep-all;
}

.metric__label {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-2xs);
  line-height: var(--text-2xs-line);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-muted);
  /* German compounds in tracked capitals outgrow a narrow tile. Hyphenated
   * at a syllable they read; broken by `overflow-wrap` they came out as
   * "UNZUSTELLBARKEIT / EN". */
  hyphens: auto;
  overflow-wrap: normal;
}

/* A note under a figure: what the number means, or what the threshold is.
 * Quieter than the label, because it is read second. */
.metric__note {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  line-height: var(--text-xs-line);
  color: var(--color-muted);
}

/* A figure that is also a way into the list behind it. It keeps the muted
 * label rather than taking the link colour, because the whole tile is the
 * target and a gold block of text reads as several links. */
a.metric {
  display: block;
  text-decoration: none;
  color: inherit;
  min-height: var(--target-primary);
  transition: border-color var(--motion-fast) var(--motion-ease),
              box-shadow var(--motion-fast) var(--motion-ease);
}

a.metric:hover {
  border-color: var(--color-border-control);
  box-shadow: var(--shadow-raised);
  color: inherit;
}

a.metric:hover .metric__value { color: var(--color-link); }

/* ------------------------------------------------------------------ *
 * The bar list — a row per value, with a bar as long as the value is large.
 *
 * A bar is the difference between a figure somebody has to read and a shape
 * somebody can see. The bar itself is
 * an inline SVG, because the content security policy of phase 10 forbids an
 * inline style attribute and an SVG geometry attribute is not CSS. The track
 * is square like everything else, which also settles what used to be a
 * difficulty: a rectangle stretched from a hundred units to a column's width
 * turns any corner radius into an oval.
 * ------------------------------------------------------------------ */

.bar-list {
  list-style: none;
  padding-left: 0;
  margin: 0;
  display: grid;
  gap: var(--space-3);
}

.bar-row {
  display: grid;
  grid-template-columns: minmax(min(6rem, 100%), 10rem) minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-sm);
  line-height: var(--text-sm-line);
}

.bar-row__label {
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bar-row__track {
  display: block;
  height: var(--space-2);
  border-radius: var(--radius-track);
  background-color: var(--color-surface-tinted);
  overflow: hidden;
}

.bar-row__track svg {
  display: block;
  width: 100%;
  height: 100%;
  color: var(--chart-1);
}

.bar-row__track rect { fill: currentColor; }

.bar-row__value {
  color: var(--color-heading);
  font-weight: var(--font-weight-medium);
  font-variant-numeric: tabular-nums;
  min-width: var(--space-8);
  text-align: right;
}

/* One series, one colour. The rows used to cycle through gold, green, red
 * and graphite to be countable, and on a list of colleagues or of reasons a
 * red and a green bar read as a verdict on that row. The label and the
 * figure are what make a row findable. */

/* On a telephone the label and the figure share the first line and the bar
 * runs under both. Placed by area, because in source order the figure comes
 * after the bar and was pushed onto a third line of its own. */
@media (max-width: 479px) {
  .bar-row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "label value" "track track";
    row-gap: var(--space-1);
  }
  .bar-row__label { grid-area: label; }
  .bar-row__value { grid-area: value; }
  .bar-row__track { grid-area: track; }
}

/* ------------------------------------------------------------------ *
 * Building blocks both shells use. They live here rather than in
 * `public.css` because the working area loads `app.css` instead, and a
 * form that lost its buttons behind the login would be the kind of defect
 * nobody sees until somebody tries to save.
 * ------------------------------------------------------------------ */

/* The unit table of a building. Q-02: every row is a flat with its own
 * status. */
.unit-list {
  list-style: none;
  padding-left: 0;
  margin-bottom: 0;
  display: grid;
  gap: var(--space-2);
}

.unit-list a {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--target-primary);
  padding: var(--space-3) var(--space-4);
  border: var(--border-hairline) solid var(--color-hairline);
  border-radius: var(--radius-control);
  text-decoration: none;
  color: var(--color-heading);
  background-color: var(--color-surface);
  font-size: var(--text-sm);
}

/* The site's own row list turns its hairline full gold under the pointer.
 * The tinted ground is kept beside it, because a border colour alone is not
 * enough of an answer on a row that is 44 px tall. */
.unit-list a:hover {
  border-color: var(--color-brand-edge);
  background-color: var(--color-brand-wash);
}

/* The property file's list carries a second link per flat, the way to take
 * it off again. The flat's own row keeps the width; the second link is as
 * wide as its words and no taller or shorter than the row beside it. */
.unit-list li {
  display: flex;
  gap: var(--space-2);
}

.unit-list li > a:first-child { flex: 1 1 auto; }

.unit-list a.unit-list__remove {
  flex: 0 0 auto;
  justify-content: center;
  color: var(--color-muted);
}

/* A flat on its own line under its building in the property list. The
 * indent says whose line it is, and the smaller type that it is the second
 * rank of the table. */
.table-subrow > td {
  font-size: var(--text-sm);
  background-color: var(--color-surface-subdued);
}

.table-subrow__lead { padding-left: var(--space-6); }

/* The QR code of a property beside what it is for. */
.qr-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-5);
}

.qr-panel > div { flex: 1 1 16rem; }

/* A group of boxes to tick takes the whole width of the form, like a text
 * area: twelve options in half a line wrap into a column nobody reads. */
.form-grid > .field:has(div.choice-group) { grid-column: 1 / -1; }

.form-panel {
  background-color: var(--color-surface);
  border: var(--border-hairline) solid var(--color-hairline);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: var(--space-6);
}

/* ------------------------------------------------------------------ *
 * The field grid.
 *
 * A form fills the column it stands in, and what keeps a field from becoming
 * a 1100 px line is this grid rather than a cap on the form: two short fields
 * on a line, and the full width for the ones that need it. Capping the form
 * instead is what left a 544 px panel with 640 px of empty ground beside it
 * on six pages.
 *
 * Which field is wide is asked of the field rather than declared on it, so
 * that `includes/form.html` stays one loop: a text area, a check box, a
 * switch and anything a field marks itself `--wide` take the whole row.
 * ------------------------------------------------------------------ */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(var(--field-min), 100%), 1fr));
  column-gap: var(--space-6);
}

.form-grid > .field--wide,
.form-grid > .field:has(textarea),
.form-grid > .field:has(.form-check),
.form-grid > .field:has(.switch) {
  grid-column: 1 / -1;
}

/* One column wherever the grid is only as wide as one field. `auto-fit` does
 * this on its own; the rule is here for the browser without `:has()`, where
 * every field would otherwise sit in a half-width track. */
@supports not selector(:has(*)) {
  .form-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ------------------------------------------------------------------ *
 * Panels in columns.
 *
 * A row of a grid is as tall as its tallest cell, so a short panel beside a
 * long one is a box with an empty bottom -- measured on the settings page,
 * where "Zweiter Faktor" was 214 px of content in a 560 px frame. Columns
 * pack instead of aligning: each panel is its own height and the next one
 * follows directly under it.
 * ------------------------------------------------------------------ */
.panel-columns > .panel {
  break-inside: avoid;
  margin-bottom: var(--space-5);
  /* The hidden labels inside (`.visually-hidden`, positioned absolutely)
   * belong to their panel. Without this Safari placed them against the
   * column box, and the value lists page ended in 3400 px of nothing. */
  position: relative;
}

.panel-columns > .panel:last-child { margin-bottom: 0; }

@media (min-width: 62rem) {
  .panel-columns {
    column-count: 2;
    column-gap: var(--space-5);
  }
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

.form-actions .btn { flex: 1 1 auto; }

/* Full width on a phone, its own width from the small breakpoint up. The rule
 * used to stand in `public.css` alone, so the working area never got it and
 * every settings panel ended in a filled bar the width of the panel. */
@media (min-width: 640px) {
  .form-actions .btn { flex: 0 0 auto; }
}

/* ------------------------------------------------------------------ *
 * Forced colours.
 *
 * In this mode the operating system replaces every colour, and anything whose
 * meaning rests on a colour loses it. Most of this design survives untouched,
 * because it draws real hairlines instead of faking edges with backgrounds --
 * measured, panels, fields, buttons and cards all keep a 1 px system-coloured
 * border and only the shadows fall away, which costs nothing where a border
 * already separates.
 *
 * What is caught here are the state markers whose loss makes something
 * unreadable rather than merely plainer: a badge that stops looking like a
 * badge, a switch whose on and off become the same picture, and the two
 * controls where "which one is chosen" is the whole point. Nothing is rescued
 * with `forced-color-adjust: none`: that switch exists to defeat the mode, and
 * defeating it is the opposite of supporting it.
 *
 * This block is not the whole of what the mode costs, and it should not be
 * read as such. Hover feedback on the four card types and on a table row is
 * carried by a border colour and a shadow, and both go; a checked
 * `.form-check-input` relies on a background image with a white tick baked
 * in. Those are recorded in the run's report rather than patched here --
 * feedback to a pointer is not information, and the checkbox needs its
 * marker rebuilt rather than an edge added. An earlier version of this
 * comment claimed the mode cost exactly three things, which is the kind of
 * sentence that closes a subject by asserting it is closed.
 * ------------------------------------------------------------------ */

@media (forced-colors: active) {
  /* A badge carries no border of its own, and its tone is its whole shape.
   * Measured: all pills came back the same Canvas white, so a state badge was
   * indistinguishable from the running text around it. The word inside still
   * carries the meaning -- that part was never colour alone -- but the badge
   * stopped looking like one. */
  .pill { border: var(--border-hairline) solid; }

  /* The switch is the real failure of the two. Measured: track and knob both
   * came back white in both states, so on and off were the same picture --
   * information carried by colour alone, which is WCAG 2.2 AA 1.4.1. Giving
   * the knob and the track an edge makes the knob's POSITION visible, and
   * position is what a switch is supposed to be read by anyway. */
  .switch__control,
  .switch__control::after { border: var(--border-control) solid; }

  /* The chosen segment is marked by a white pill and a lift, and forced
   * colours take both: measured, the active segment came back opaque Canvas
   * on a Canvas page with the same text colour as its neighbours, so which
   * view was switched on was not readable at all. An edge survives where a
   * fill and a shadow do not. */
  .segmented [aria-current="page"],
  .segmented [aria-selected="true"] { border: var(--border-control) solid; }

  /* The appearance control marks its chosen button with a fill, a text colour
   * and an inset ring -- and the ring is a `box-shadow`, which this mode
   * discards along with the two colours. Measured: nothing was left, and
   * which of light, dark and system was switched on stood only in
   * `aria-pressed`. The comment on that rule names forced colours as one of
   * the three reasons for the ring; the ring does not survive them, so the
   * edge is drawn here where it does. */
  .appearance__button[aria-pressed="true"] { border: var(--border-control) solid; }
}

/* ------------------------------------------------------------------ *
 * The support band — phase 08.
 *
 * Shown on every page while somebody from the operator is signed in under
 * another person's name. It is in this file rather than in `app.css`
 * because a support session can walk onto a public page too, and a band
 * that disappears there would be a band that disappears exactly where
 * somebody forgets they are still in it.
 * ------------------------------------------------------------------ */

.impersonation-band {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background-color: var(--color-danger-fill);
  color: var(--color-on-danger);
  font-size: var(--text-sm);
  line-height: var(--text-sm-line);
}

.impersonation-band strong { color: inherit; }

.impersonation-band form { margin: 0; margin-inline-start: auto; }

/* The button on the band is outlined rather than filled: the band is
 * already the loudest thing on the page, and a second fill on it would
 * compete with the one main action of whatever page is underneath. */
.impersonation-band .btn {
  border-color: currentColor;
  background-color: transparent;
  color: inherit;
  min-height: var(--control-height-sm);
}

.impersonation-band .btn:hover {
  background-color: var(--color-on-danger);
  color: var(--color-danger-fill);
}

/* ------------------------------------------------------------------ *
 * Motion.
 *
 * Everything a pointer can act on eases between its states rather than
 * snapping, on the site's own curve and at the site's own figure: 300ms for a
 * colour, a link or a border, which is `--motion-base`. The curve is not a
 * stock one — it starts fast and settles long, which is what makes a change
 * read as an answer rather than as a spring. The buttons, the fields, the
 * switch and the cards declare their own transitions where they are defined;
 * this is the rest of the interactive surface, gathered in one place rather
 * than repeated.
 *
 * It is declared plainly and not inside the no-preference query, because the
 * reduce query at the end of this file sets every duration in the document to
 * one millisecond — that is what actually switches it off, and one rule that
 * cannot be forgotten is worth more than a dozen that can.
 * ------------------------------------------------------------------ */

a,
.wordmark,
.pill,
.appearance__button,
.app-nav a,
.app-search,
.site-nav a,
.phase-tabs a,
.chip-row a,
.chip-row a[aria-current="page"],
.segmented a,
.segmented button,
.pagination-bar a,
.unit-list a,
.board__card,
.calendar-day,
.table tbody tr,
.property-card__title a,
.article-card__title a {
  transition: background-color var(--motion-base) var(--motion-ease),
              border-color var(--motion-base) var(--motion-ease),
              box-shadow var(--motion-base) var(--motion-ease),
              color var(--motion-base) var(--motion-ease);
}

/* ------------------------------------------------------------------ *
 * The query a person who asked for less movement gets.
 * ------------------------------------------------------------------ */

@media (prefers-reduced-motion: no-preference) {
  .skip-link { transition: transform var(--motion-fast) var(--motion-ease); }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

/* ------------------------------------------------------------------ *
 * Print. Invoices, owner reports and articles are the pages that get
 * printed; the navigation, the footer and the banners are not.
 * ------------------------------------------------------------------ */

@media print {
  .app-sidebar,
  .app-topbar,
  .site-header,
  .site-footer,
  .consent-banner,
  .skip-link,
  .appearance,
  .impersonation-band,
  .no-print {
    display: none !important;
  }

  body {
    background: none;
    color: var(--black);
  }

  /* A block that fades in on scrolling is printed whether or not it has been
   * scrolled to: a printed property page had its gallery, its units and its
   * enquiry heading as empty space. */
  :root[data-reveal="on"] .reveal {
    opacity: 1 !important;
    transform: none !important;
  }

  .app-main,
  .app-content,
  .site-main {
    margin: 0;
    padding: 0;
    max-width: none;
  }

  .panel,
  .card,
  .metric,
  .table-responsive {
    border: var(--border-hairline) solid var(--grey-control-light);
    box-shadow: none;
    break-inside: avoid;
  }

  /* A printed page cannot be clicked, so the target is written out. */
  .prose a[href^="http"]::after,
  .prose a[href^="/"]::after {
    content: " (" attr(href) ")";
    font-size: var(--text-xs);
    word-break: break-all;
  }

  h1, h2, h3 { break-after: avoid; }
  tr, img { break-inside: avoid; }
}

/* ------------------------------------------------------------------ *
 * Corrections measured in the browser. Each names what it measured before.
 * ------------------------------------------------------------------ */

/* A link inside a system message is written in the message's own tone, not in
 * the page's link colour. Measured: "Zweiten Faktor einrichten" was
 * `--color-link` on `--color-tone-red-soft` at 4.32:1, which misses AA by a
 * fifth — the gold is measured against the three page grounds and a tinted
 * ground is not one of them.
 *
 * The underline stays, so the link is still a link without the colour. A
 * link drawn as a button stays a button: it came out as red underlined text
 * inside an edge. */
.notice a:not(.btn),
.notice--problem a:not(.btn),
.notice--warning a:not(.btn),
.notice--info a:not(.btn),
.notice--positive a:not(.btn) {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* A link that is the whole content of its cell or list row is a target rather
 * than a word in a sentence, so WCAG 2.5.8's inline exception does not reach
 * it. Measured between 16 and 137 pixels wide at 19 pixels high, on five
 * screens of the working area.
 *
 * `inline-block` with vertical padding rather than a height: a height on an
 * inline box does nothing, and the padding grows the hit area without moving
 * the text off its baseline or the row off its rhythm. */
:where(.app-main td > a:only-child,
       .app-main li > a:only-child,
       .app-main dd > a:only-child,
       .app-main p > a:only-child) {
  /* At zero specificity, so that a button or a unit row keeps its own box:
   * as a plain selector this overrode their flex and padding, which pushed a
   * button's label to its top edge and the glyph against it. */
  display: inline-block;
  padding-block: var(--space-1);
  /* And wide enough. A two-digit lead score is the whole content of its cell
   * and measured 14 pixels across — the row gave it height and nothing gave
   * it width. `min-width` rather than horizontal padding, so that a
   * right-aligned column keeps its alignment. */
  min-width: var(--target-min);
}


/* The shortcut list of N-09, and the one native `dialog` in this project.
 *
 * Native rather than a div with a backdrop: Escape closes it, the focus is
 * trapped inside it and the rest of the page is inert, all three without a
 * line of JavaScript of our own. The `::backdrop` is the browser's own
 * element and is the reason no overlay div appears anywhere in the markup. */
.dialog {
  max-width: 32rem;
  width: calc(100vw - var(--space-8));
  padding: var(--space-6);
  border: var(--border-hairline) solid var(--color-hairline);
  border-radius: var(--radius-card);
  background-color: var(--color-surface);
  color: var(--color-body);
  box-shadow: var(--shadow-card);
}

.dialog::backdrop {
  background-color: rgb(0 0 0 / 45%);
}

.dialog__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.dialog__head h2 { margin: 0; }

/* The key names read as keys: the same face as the rest, in the regular
 * weight rather than the medium the term of a pair carries elsewhere. `kbd`
 * is not used here because the list is built as a definition list — the term
 * *is* the key, and wrapping it again would put an element inside a `dt` for
 * the sake of a border. There is no monospace token in this design and
 * inventing one for seven rows would be a sixth family nobody chose. */
.dialog .pairs dt {
  font-weight: var(--font-weight-regular);
  letter-spacing: 0.04em;
}

/* ------------------------------------------------------------------
 * A long list with its search field (`data-filter-select`, app.js)
 *
 * The two stand on each other as one control: the field on top, the
 * list under it, one hairline between them instead of two borders.
 * ------------------------------------------------------------------ */
.select-filter {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}

.select-filter__input {
  border-bottom-width: 0;
  font-size: var(--text-sm);
  min-height: var(--control-height-sm, 2rem);
  padding-block: var(--space-1);
}

.select-filter__input:focus,
.select-filter__input:focus-visible {
  position: relative;
  z-index: 1;
}

/* ------------------------------------------------------------------
 * The card index on a telephone (contacts/_list_phone_line.html)
 *
 * Only the name column stands below the tablet width, and the number and
 * the place go under the name (2026-10-16): the number was a sideways
 * scroll away. The number is a button that looks like a link and is a
 * finger's height, so it is hit and not the name above it.
 * ------------------------------------------------------------------ */
.contact-row__phone-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  margin-top: var(--space-1);
  white-space: normal;
}

.contact-row__phone-line .btn-link {
  min-height: 2.75rem;
  display: inline-flex;
  align-items: center;
}

/* ------------------------------------------------------------------
 * The mailbox on a telephone (2026-10-16)
 *
 * A message page: the actions beside "Antworten" stand in the row on a
 * wide screen and behind "Weitere Aktionen" below the tablet width
 * (`mailbox/message.html`), where eight buttons took the first screen.
 * A write form: "Kopie" and "Blindkopie" behind one disclosure
 * (`mailbox/_write_fields.html`).
 * ------------------------------------------------------------------ */
.mail-actions-wide { display: contents; }
.mail-actions-narrow { display: none; }

@media (max-width: 47.99rem) {
  .mail-actions-wide { display: none; }
  .mail-actions-narrow { display: block; }
  .mail-actions-narrow > summary { list-style: none; }
  .mail-actions-narrow > summary::-webkit-details-marker { display: none; }
  .mail-actions-narrow__list {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
    margin-top: var(--space-2);
  }
  .mail-actions-narrow__list .btn,
  .mail-actions-narrow__list form { width: 100%; }
  .mail-actions-narrow__list .form-select { width: 100% !important; }
}

.write-copies { margin-block: var(--space-2) var(--space-4); }
.write-copies__toggle {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  cursor: pointer;
  color: var(--color-link, inherit);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
.write-copies[open] > .write-copies__toggle { margin-bottom: var(--space-2); }

/* The board returns to the card that was moved (`#eintrag-...`, 2026-10-16).
 * The address puts the card at the top of the window, where the sticky bar
 * covers its first lines; the margin keeps its title below the bar, which
 * wraps to two rows on a telephone. */
.board__card { scroll-margin-top: calc(2 * var(--topbar-height) + var(--space-4)); }
