/*
 * Pages and sheet contents: Today (cistern, notice, running now, system,
 * upcoming runs, weather), the zone picker, the zone run sheet, the notice
 * and refill sheets, and the placeholder pages.
 *
 * Same rules as components.css: tokens only, transform and opacity are the
 * only animated properties, hover only for fine pointers.
 */
@layer pages {
  /* ------------------------------------------------------------------
     Shared pieces
     ------------------------------------------------------------------ */
  /* Fill backwards only: a finished enter animation must not pin the
     opacity (hideWithFlip fades the same card out with inline styles).
     The class is removed on animationend (motion/flip.js enterWithFade). */
  .page.is-entering,
  .settings-view.is-entering,
  .card.is-entering {
    animation: page-enter var(--duration-base) ease-out 1 backwards;
  }

  /* A card that is fading in ignores taps: its buttons just appeared where
     something else was. */
  .card.is-entering {
    pointer-events: none;
  }

  @keyframes page-enter {
    from {
      opacity: 0;
    }

    to {
      opacity: 1;
    }
  }

  .list-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    min-height: max(52px, var(--touch-min));
  }

  .list-row + .list-row {
    border-top: 1px solid var(--hairline);
  }

  /* A hidden row (the system card shows deviations only) leaves no line above the next. */
  .list-row[hidden] + .list-row {
    border-top: 0;
  }

  .list-row-text {
    min-width: 0;
  }

  .list-row-title {
    font-size: var(--type-body-size);
    line-height: var(--type-body-line);
  }

  .list-row-sub {
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-tertiary);
  }

  .list-row-value {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: none;
    font-size: var(--type-body-size);
    color: var(--text-secondary);
    white-space: nowrap;
  }

  .list-row-value.is-live {
    color: var(--accent);
    font-weight: var(--weight-medium);
  }

  .list-row-value[data-tone="warning"] {
    color: var(--warning);
  }

  .list-row-value[data-result="leak"] {
    color: var(--danger);
  }

  .list-row-icon {
    display: grid;
    color: var(--success);
  }

  /* A pause set in Settings takes the line under the automation row (system card). */
  .list-row-sub[data-tone="warning"] {
    color: var(--warning);
  }

  .list-row-icon.is-tone {
    color: inherit;
  }

  /* A row that leads on (the system card's rows, the cistern's refill
     row; plan of 2026-10-10, B): the whole row is a button with a
     chevron, pressed it fills like a row of "Nächste Läufe". Its fill
     reaches a little beyond the text; the hairline between rows keeps
     the text's width. */
  button.list-row {
    position: relative;
    width: calc(100% + 2 * var(--space-2));
    margin-inline: calc(-1 * var(--space-2));
    padding-inline: var(--space-2);
    border-radius: var(--radius-md);
    text-align: left;
  }

  button.list-row + button.list-row {
    border-top: 0;
  }

  button.list-row + button.list-row::before {
    content: "";
    position: absolute;
    top: 0;
    right: var(--space-2);
    left: var(--space-2);
    height: 1px;
    background: var(--hairline);
  }

  button.list-row[hidden] + button.list-row::before {
    display: none;
  }

  button.list-row .list-row-text {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
  }

  .list-row-chevron {
    flex: none;
    margin-left: calc(-1 * var(--space-1));
    color: var(--text-tertiary);
  }

  :where(:root:not([data-drag-scroll])) button.list-row:active {
    background: var(--surface-2);
  }

  .hint-dot {
    position: absolute;
    top: 50%;
    left: 4px;
    width: 6px;
    height: 6px;
    margin-top: -3px;
    border-radius: 50%;
    background: var(--warning);
  }

  /* ------------------------------------------------------------------
     Page head (phones) and placeholder pages
     ------------------------------------------------------------------ */
  .page-date {
    color: var(--text-secondary);
  }

  .page-title {
    margin-top: 2px;
    font-size: var(--type-title1-size);
    line-height: var(--type-title1-line);
    letter-spacing: var(--type-title1-tracking);
    font-weight: var(--weight-semibold);
  }

  .page-status {
    margin-top: var(--space-1);
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    color: var(--text-secondary);
  }

  /* Phones: the status sentence under "Heute" leads where the header's
     parts do (shell/status-line.js). Each part is a 44 px target that takes
     one text line in the layout (the "Noch N Hinweise" pattern); a detail
     that does not fit beside the message takes a line of its own, whole,
     the targets' gap keeping their reach apart. Wide screens show the
     sentence in the header (layout.css hides the page head's). */
  @container app (max-width: 699px) {
    /* The parts' reach stays clear of the title above; the head keeps its height. */
    .page-status {
      display: flex;
      margin-top: var(--space-3);
    }

    .page-today .page-head {
      padding-bottom: var(--space-3);
    }

    .page-status .status-line-text {
      flex: 1 1 auto;
      row-gap: calc(var(--touch-min) - var(--type-headline-line));
    }

    /* The reach above and below the words, whatever lines they take; the
       dot stays with the first line of a message that wraps. */
    .page-status .status-part {
      align-items: flex-start;
      min-height: 0;
      padding-block: calc((var(--touch-min) - var(--type-headline-line)) / 2);
      margin-block: calc((var(--type-headline-line) - var(--touch-min)) / 2);
    }

    .page-status .status-part-text {
      white-space: normal;
    }

    .page-status .status-dot {
      margin-top: calc((var(--type-headline-line) - 8px) / 2);
    }

    .page-status.is-stacked .status-part[data-part="detail"] {
      flex-basis: 100%;
    }
  }

  .page-placeholder {
    display: grid;
    place-items: center;
    min-height: 100%;
    text-align: center;
  }

  .placeholder .icon {
    margin: 0 auto;
    color: var(--text-tertiary);
  }

  .placeholder-title {
    margin-top: var(--space-4);
    font-size: var(--type-title3-size);
    line-height: var(--type-title3-line);
    letter-spacing: var(--type-title3-tracking);
    font-weight: var(--weight-semibold);
  }

  .placeholder-text {
    max-width: 34ch;
    margin: 6px auto 0;
    color: var(--text-secondary);
  }

  /* ------------------------------------------------------------------
     Today grid
     ------------------------------------------------------------------ */
  .page-today {
    height: 100%;
  }

  /* The cistern's column grows with the gauge's words: at the display size's smaller step they keep 12 physical
     px (13.3 px, tokens.css), and in 272 px "Nach dem Gießen morgen" broke into two lines (measured 2026-10-11);
     the factor is 13.3 / 13 there and 1 at the other steps. */
  .today-grid {
    position: relative;
    height: 100%;
    display: grid;
    grid-template-columns: calc(272px * max(1, 12 / 13 / var(--display-zoom))) minmax(0, 1fr) minmax(0, 1.24fr);
    gap: var(--space-3);
  }

  /* Today laid out as at the standard display size, for one measurement: below standard the upcoming runs show the
     rows standard shows (Stephan 2026-10-11; runs-card.js snapToRows() sets and removes it, never drawn). A zoom
     scope (tokens.css: the touch targets and type worked out at 1), without the room the step adds to the screen:
     100vw / zoom − 100vw wide, 100dvh / zoom − 100dvh high (the viewport units are zoomed like any length, base.css).
     So its columns, cards and rows have standard's own px. */
  .today-grid[data-zoom-scope] {
    width: calc(100% - (100vw / var(--page-zoom) - 100vw));
    height: calc(100% - (100dvh / var(--page-zoom) - 100dvh));
  }

  .today-col {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    min-height: 0;
    /* A card gliding into place (FLIP) must not make the content area
       scroll for a moment: clip at the column, without a scroll container. */
    overflow: clip;
    overflow-clip-margin: var(--space-1);
  }

  /* ---------- Cistern ---------- */
  .cistern-card {
    flex: 1;
    display: flex;
    flex-direction: column;
  }

  .cistern-hero {
    margin-top: 10px;
  }

  .hero {
    font-size: var(--type-display-l-size);
    line-height: var(--type-display-l-line);
    letter-spacing: var(--type-display-l-tracking);
    font-weight: var(--weight-light);
  }

  .hero .display-value-unit {
    font-size: var(--type-hero-unit-size);
    color: var(--text-secondary);
  }

  .cistern-state {
    margin-top: 6px;
    color: var(--text-secondary);
  }

  .cistern-state[data-tone="warning"] {
    color: var(--warning);
  }

  /* 120 px at the least: on the kiosk the 24 hours' causes may take two lines under the change (cistern-card.js). */
  .gauge {
    flex: 1;
    min-height: 120px;
    margin-top: 18px;
    padding-bottom: 22px;
    display: grid;
    grid-template-columns: 60px minmax(0, 1fr);
  }

  .vessel {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-lg);
    background: var(--surface-2);
  }

  .vessel > * {
    position: absolute;
    left: 0;
    right: 0;
  }

  .water {
    bottom: 0;
    height: var(--level, 0%);
    background: var(--water);
  }

  .water-use {
    bottom: var(--from, 0%);
    height: calc(var(--to, 0%) - var(--from, 0%));
    background: var(--water-use);
  }

  .water-edge {
    bottom: var(--level, 0%);
    height: 2px;
    margin-bottom: -1px;
    background: var(--water-edge);
  }

  .water-reserve {
    bottom: 0;
    height: var(--h, 0%);
    background: repeating-linear-gradient(135deg, var(--water-reserve) 0 3px, var(--water) 3px 6px);
  }

  .mark {
    bottom: var(--at, 0%);
    height: 1px;
  }

  .mark-after {
    background-image: linear-gradient(90deg, var(--text-primary) 60%, transparent 0);
    background-size: 6px 1px;
    opacity: 0.8;
  }

  .mark-reserve {
    background: var(--text-secondary);
    opacity: 0.7;
  }

  .gauge-labels {
    position: relative;
  }

  .gauge-label {
    position: absolute;
    left: 14px;
    right: 0;
    display: flex;
    flex-direction: column;
  }

  .gauge-label::before {
    content: "";
    position: absolute;
    left: -14px;
    width: 10px;
    height: 1px;
    background: var(--text-tertiary);
  }

  /* The gauge's words keep the annotation size on the kiosk too (tokens.css). */
  .gauge-cap {
    top: 0;
    font-size: var(--type-caption-compact-size);
    line-height: var(--type-caption-compact-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-tertiary);
  }

  .gauge-cap::before {
    top: 0;
  }

  .gauge-at {
    top: calc(100% - var(--at, 0%));
    transform: translateY(-9px);
  }

  .gauge-at::before {
    top: 9px;
  }

  /* Crowded (gaugeLabelPlacement in cistern-card.js): the level after the
     next run is too close to the reserve for both labels on their marks.
     The after label sits above its mark, the reserve label below its own,
     each tick on its mark; the after level is never below the reserve. */
  .gauge-at.is-above {
    transform: translateY(-100%);
  }

  .gauge-at.is-above::before {
    top: auto;
    bottom: 0;
  }

  .gauge-at.is-below {
    transform: none;
  }

  .gauge-at.is-below::before {
    top: 0;
  }

  .gauge-key {
    font-size: var(--type-caption-compact-size);
    line-height: var(--type-caption-compact-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
  }

  .gauge-value {
    font-size: var(--type-body-size);
    line-height: var(--type-body-line);
    color: var(--text-primary);
  }

  .gauge-note {
    font-size: var(--type-caption-compact-size);
    line-height: var(--type-caption-compact-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-tertiary);
  }

  /* The change of the 24 hours takes the rest of the row: its mini curve sits beside the value. */
  .cistern-stats {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--space-3) var(--space-4);
    margin-top: 14px;
  }

  .stat-key {
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
  }

  .stat-value {
    font-size: var(--type-title3-size);
    line-height: var(--type-title3-line);
    letter-spacing: var(--type-title3-tracking);
    font-weight: var(--weight-medium);
    white-space: nowrap;
  }

  /* "34 L": at 20 px the narrow space of running text disappears; the
     unit keeps the size and gets a word space. */
  .stat-display .display-value-unit {
    margin-inline-start: 0.25em;
    font-size: 1em;
    font-weight: inherit;
    letter-spacing: inherit;
    color: inherit;
  }

  /* The 24 hours (cistern-card.js): its key with the info button, the mini curve beside the change, the causes under it. */
  .stat-key-row {
    display: flex;
    align-items: center;
    gap: 6px;
  }

  /* The hit area stays 48 px; the key line keeps its height. */
  .stat-key-row > .info-button {
    margin-block: calc(-16px - var(--info-grow));
  }

  .cistern-change-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }

  .cistern-curve {
    position: relative;
    flex: 1;
    min-width: 40px;
    height: 28px;
  }

  .cistern-curve-lines {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
  }

  .cistern-curve-area {
    fill: var(--accent);
    opacity: 0.12;
  }

  .cistern-curve-line {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
  }

  .cistern-curve-now {
    position: absolute;
    width: 6px;
    height: 6px;
    margin: -3px 0 0 -3px;
    border-radius: 50%;
    background: var(--accent);
  }

  .cistern-causes {
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--space-3);
    margin-top: 4px;
    font-size: var(--type-caption-compact-size);
    line-height: var(--type-caption-compact-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
  }

  .cistern-cause {
    white-space: nowrap;
  }

  .cistern-cause b {
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
  }

  /* The level, its state and the gauge are one tap region (cistern-card.js);
     the gauge keeps the free room of the column. */
  .cistern-level {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
  }

  /* An info button at a card's title stays above a region under it (its
     hit area reaches into the region's). */
  .page-today .card-title-group > .info-button {
    z-index: 2;
  }

  .cistern-refill {
    margin-top: 6px;
    border-top: 1px solid var(--hairline);
  }

  .cistern-refill-button {
    margin-top: var(--space-2);
  }

  /* The refill row's value: "Läuft" over its time left, as the title over "Automatik an" beside it. */
  .cistern-refill-value {
    flex-direction: column;
    align-items: flex-end;
    gap: 0;
  }

  .cistern-refill-remaining {
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
  }

  /* ---------- Notice deck (pages/today/hint-card.js) ----------
     Every open notice is its own card, stacked (Stephan 2026-10-17): the
     cards lie side by side in one row that snaps card by card, each as
     wide as the deck, and the ones behind the front card show as edges
     under it. The row scrolls sideways only; 3 px of room around the cards
     keep their edge (--card-edge) from being cut. The cards share the
     tallest one's height, so the deck keeps its height while it is swiped
     and the buttons line up. */
  .hint-deck {
    position: relative;
    flex: none;
    min-width: 0;
  }

  .hint-track {
    position: relative;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 100%;
    gap: var(--space-2);
    margin: -3px;
    padding: 3px;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: 3px;
    scrollbar-width: none;
  }

  .hint-track::-webkit-scrollbar {
    display: none;
  }

  /* The cards behind the front one: one or two edges under it, each in the
     surface of its notice (amber, or plain for info), the second narrower.
     Each edge starts 10 px up, behind the card, and shows 6 px. They let
     the page through a little, so they recede in both themes (the strong
     amber was lighter than the card at night and read as in front), and a
     hairline in the notice's colour draws their edge: amber, red for an
     alarm, blue for info. A tap on them brings the next card, as "1/4 ›"
     does (that line is the control; the edges only add to it). */
  .hint-stack {
    position: relative;
    z-index: 0;
    display: grid;
    justify-items: center;
    margin-top: -10px;
    cursor: pointer;
  }

  .hint-stack-edge {
    --hint-edge-line: color-mix(in srgb, var(--warning) 45%, transparent);
    width: calc(100% - 24px);
    height: 16px;
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    background: var(--surface-warning);
    box-shadow: var(--card-edge), inset 0 -1px 0 var(--hint-edge-line);
    opacity: 0.85;
  }

  .hint-stack-edge + .hint-stack-edge {
    width: calc(100% - 52px);
    margin-top: -10px;
    opacity: 0.55;
  }

  .hint-stack-edge[data-tone="critical"] {
    --hint-edge-line: color-mix(in srgb, var(--danger) 55%, transparent);
  }

  .hint-stack-edge[data-tone="info"] {
    --hint-edge-line: color-mix(in srgb, var(--info) 45%, transparent);
    background: var(--surface-1);
  }

  .hint-card {
    display: flex;
    flex-direction: column;
    scroll-snap-align: start;
    scroll-snap-stop: always;
    background: var(--surface-warning);
  }

  .hint-card > .hint-open {
    flex: 1 0 auto;
  }

  .hint-eyebrow {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-weight: var(--weight-medium);
    color: var(--warning);
  }

  .hint-card[data-tone="critical"] .hint-eyebrow {
    color: var(--danger);
  }

  /* "1/4 ›" brings the next card. In the layout it is a caption at the
     eyebrow's end, 35 px: on the kiosk the eyebrow has 211 px beside its
     icon and "Hinweis · heute 10:17" takes 158 (measured 2026-10-17; an
     older "gestern 23:59", 172, wraps). Its box is a 48 px target: it
     reaches 14 px above and below the line and into the card's padding on
     the right, and lies above the tap region's hit button, as "Noch N
     Hinweise" did. */
  .hint-next {
    --hint-target: max(48px, var(--touch-min));
    position: relative;
    z-index: 1;
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: flex-end;
    gap: 1px;
    min-width: var(--hint-target);
    min-height: var(--hint-target);
    margin: calc((20px - var(--hint-target)) / 2) calc(-1 * var(--space-4)) calc((20px - var(--hint-target)) / 2) auto;
    padding: 0 12px 0 4px;
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--text-secondary);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    font-weight: var(--weight-medium);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  .hint-next-chevron {
    flex: none;
  }

  /* An info notice (an emergency stop with nothing running) reads calm:
     the plain card, the eyebrow in the info colour, as its count on
     Settings (Stephan 2026-10-06, 22:00). */
  .hint-card[data-tone="info"] {
    background: var(--surface-1);
  }

  .hint-card[data-tone="info"] .hint-eyebrow {
    color: var(--info);
  }

  .hint-card[data-tone="info"] .hint-actions > .button {
    background: var(--surface-2);
  }

  .hint-title {
    margin-top: var(--space-2);
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-semibold);
  }

  .hint-text {
    margin-top: var(--space-1);
    color: var(--text-secondary);
    text-wrap: pretty;
  }

  .hint-actions {
    display: flex;
    gap: 10px;
    margin-top: 14px;
  }

  .hint-actions > .button {
    flex: 1;
    min-width: 0;
    padding: 0 var(--space-3);
    background: var(--surface-warning-strong);
  }

  /* ---------- Running now ----------
     It fills its column, so all three columns end on one line with or
     without a notice above it. */
  .run-card {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 176px;
  }

  /* The head keeps its words whole (Stephan 2026-10-11: on the kiosk "Läuft / gerade" stood beside "Fertig
     um / 14:10"): where the title and the end time do not fit side by side, the end time takes the line
     below, as one unit. */
  .run-card > .card-head {
    flex-wrap: wrap;
    row-gap: 0;
  }

  .run-end {
    color: var(--text-secondary);
    white-space: nowrap;
    opacity: 0;
    transition: opacity var(--duration-base) ease;
  }

  .run-card[data-state="live"] .run-end {
    opacity: 1;
  }

  .run-stack {
    display: grid;
    flex: 1;
    margin-top: 6px;
  }

  .run-stack > * {
    grid-area: 1 / 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    transition: opacity var(--duration-base) ease, transform var(--duration-base) ease;
  }

  .run-card:not([data-state="idle"]) .run-idle,
  .run-card:not([data-state="live"]) .run-live,
  .run-card:not([data-state="manual"]) .run-manual {
    opacity: 0;
    transform: translate3d(0, 6px, 0);
    pointer-events: none;
  }

  /* "Nichts" is a word, not a number: calm and secondary in the title-3 size, the state of the card under
     its title (type audit 2026-10-10: in the 32 px light numeral style of "+12,4 mm" it was the loudest
     word on Today, two steps above Apple's empty states on a phone). */
  .run-none {
    font-size: var(--type-title3-size);
    line-height: var(--type-title3-line);
    letter-spacing: var(--type-title3-tracking);
    font-weight: var(--weight-regular);
    color: var(--text-secondary);
  }

  .run-next {
    margin-top: 2px;
    padding-bottom: var(--space-4);
    color: var(--text-secondary);
  }

  /* "Nächster Lauf ...", "Nichts geplant": a link at the size of the line
     (run-card.js), its box reaching above and below it to the touch
     height (the inline link row), so the card keeps its height. */
  .run-next-link.is-inline {
    margin-block: calc((var(--type-body-line) - var(--touch-min)) / 2);
    font-size: var(--type-body-size);
    line-height: var(--type-body-line);
    letter-spacing: var(--type-body-tracking);
  }

  /* What runs, from its title to its meta line: one tap region
     (run-card.js). It takes the free room, so the progress bar stays at
     its foot and the actions follow under it. */
  .run-live-open {
    display: flex;
    flex: 1 0 auto;
    flex-direction: column;
    min-width: 0;
  }

  .run-water-now {
    margin-top: auto;
  }

  .run-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    font-size: var(--type-title3-size);
    line-height: var(--type-title3-line);
    letter-spacing: var(--type-title3-tracking);
    font-weight: var(--weight-semibold);
  }

  .run-title-text {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  /* What runs, as its icon, in the live colour. */
  .run-kind-icon {
    color: var(--accent);
  }

  .run-countdown {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    margin-top: 2px;
  }

  .run-countdown-label {
    font-size: var(--type-headline-size);
    color: var(--text-secondary);
  }

  .run-countdown-label:empty {
    display: none;
  }

  .run-countdown-time {
    font-size: var(--type-display-m-size);
    line-height: var(--type-display-m-line);
    letter-spacing: var(--type-display-m-tracking);
    font-weight: var(--weight-light);
  }

  .run-since {
    margin-top: 2px;
    color: var(--text-secondary);
  }

  /* Measured by the water meter (E13); a hint keeps the text colour, a notice of the engine its tone. */
  .run-flow {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin-top: 2px;
    color: var(--text-secondary);
  }

  .run-flow-icon {
    display: grid;
    flex: none;
    padding-top: 3px;
  }

  .run-flow[data-tone="warning"] {
    color: var(--warning);
  }

  .run-flow[data-tone="critical"] {
    color: var(--danger);
  }

  .progress {
    height: 6px;
    margin-top: auto;
    overflow: hidden;
    border-radius: 3px;
    background: var(--surface-3);
  }

  .progress-fill {
    display: block;
    height: 100%;
    background: var(--accent);
    transform-origin: 0 50%;
    transform: scaleX(0);
  }

  .run-meta {
    margin-top: var(--space-2);
    color: var(--text-secondary);
  }

  /* Ends only the running establishment session (E14), or only the run by
     hand or the leak test ("Beenden", F3). At the card's foot like "Jetzt
     bewässern" and the manual actions, under the region above it, which
     takes the free space (its progress bar at its foot; a leak test
     without a known end has none). */
  .run-live-action {
    margin-top: auto;
    padding-top: var(--space-3);
  }

  /* Manual control (E6): no banner on Today, the card says it. */
  .run-manual-icon {
    color: var(--accent);
  }

  .run-manual[data-tone="warning"] .run-manual-icon,
  .run-manual[data-tone="warning"] .run-manual-line {
    color: var(--warning);
  }

  .run-manual-line {
    margin-top: 2px;
    color: var(--text-secondary);
  }

  /* Its title may take two lines: "Handsteuerung beendet" is wider than
     the kiosk column (review 2026-10-06), and the state is the one that
     must be read whole. The icon stays on the first line: (26 − 20) / 2. */
  .run-manual .run-title {
    align-items: flex-start;
  }

  .run-manual .run-manual-icon {
    margin-top: 3px;
  }

  .run-manual .run-title-text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    white-space: normal;
  }

  .run-manual-actions {
    display: flex;
    gap: var(--space-2);
    margin-top: auto;
    padding-top: var(--space-3);
  }

  .run-manual-actions > .button {
    flex: 1 1 0;
    min-width: 0;
  }

  /* ---------- System ---------- */
  .system-card {
    padding-top: 2px;
    padding-bottom: 2px;
  }

  /* ---------- Upcoming runs ---------- */
  .runs-card {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  .runs-list {
    min-height: 0;
    margin: 0 -8px;
    padding: 0 8px;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* The list scrolls and clips: the focus ring goes inside the row. */
  .runs-list :focus-visible {
    outline-offset: -2px;
  }

  .runs-empty {
    margin-top: var(--space-2);
    color: var(--text-secondary);
  }

  /* "Nichts geplant" leads to the schedule line in Settings (plan of 2026-10-10, rule 8): the same calm
     words, a row with a chevron. */
  .runs-empty-link {
    margin-top: var(--space-1);
    color: var(--text-secondary);
  }

  .runs-empty-link .link-row-label {
    font-weight: var(--weight-regular);
  }

  .runs-day {
    margin: 6px 0 2px;
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-tertiary);
  }

  .run-row {
    display: grid;
    grid-template-columns: 48px minmax(0, 1fr) 20px;
    column-gap: 10px;
    align-items: start;
    width: calc(100% + 16px);
    min-height: max(52px, var(--touch-min));
    margin: 0 -8px;
    padding: 5px 8px;
    border-radius: var(--radius-md);
    text-align: left;
  }

  .run-row.is-static {
    grid-template-columns: 48px minmax(0, 1fr);
  }

  .run-row > .icon {
    align-self: center;
    color: var(--text-tertiary);
  }

  .run-row-time {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-medium);
    color: var(--text-secondary);
  }

  .run-row-text {
    min-width: 0;
  }

  /* The name has priority: when name and badge ("Tropfwerk speist vorher
     nach") do not fit one line, the badge moves to a line of its own
     instead of squeezing the name to "G…" (the name says what "Lauf
     auslassen" skips). */
  .run-row-title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: var(--space-2);
    row-gap: 0;
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-medium);
  }

  /* Inter's tabular figures widen the hyphen of hyphenated names; names are words. */
  .run-row-name,
  .wave-zone-name,
  .zone-row-name,
  .hint-title,
  .run-title-text {
    font-variant-numeric: normal;
  }

  /* The run's type: a quiet icon under its time, level with the details
     line, so the time column says when and what. */
  .run-row-icon {
    display: grid;
    margin-top: 2px;
    color: var(--text-secondary);
  }

  .run-row-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .run-row-meta {
    display: block;
    color: var(--text-secondary);
  }

  /* On a line of its own the badge may still be wider than the row
     ("Wasser reicht nicht, Nachspeisung ist aus"): then it wraps. */
  .run-water {
    display: inline-flex;
    /* The icon (18 px, one caption line) stays level with the first line. */
    align-items: flex-start;
    gap: var(--space-1);
    flex: 0 1 auto;
    min-width: 0;
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    font-weight: var(--weight-regular);
    color: var(--text-secondary);
  }

  .run-water-icon {
    display: grid;
    flex: none;
  }

  .run-water[data-water="refill"] .run-water-icon {
    color: var(--accent);
  }

  .run-water[data-water="short"] {
    color: var(--warning);
  }

  .wave-zones {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin: var(--space-1) 0 0 58px;
  }

  .wave-zone {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 20px;
    align-items: center;
    gap: 10px;
    width: calc(100% + 8px);
    min-height: max(52px, var(--touch-min));
    margin-right: -8px;
    padding: 6px 8px 6px 14px;
    border-radius: var(--radius-md);
    background: var(--surface-2);
    text-align: left;
  }

  .wave-zone-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }

  .wave-zone-name {
    overflow: hidden;
    font-weight: var(--weight-medium);
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .wave-zone-meta {
    color: var(--text-secondary);
    white-space: nowrap;
  }

  .wave-zone > .icon {
    color: var(--text-tertiary);
  }

  /* "Skip run" under a waiting establishment run, aligned with its name. */
  .run-skip-row {
    display: flex;
    margin: 2px 0 var(--space-2) 58px;
  }

  /* ---------- Weather ---------- */
  .weather-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }

  .weather-now {
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-medium);
    white-space: nowrap;
  }

  .weather-stale {
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--warning);
  }

  .weather-rain {
    display: grid;
    grid-template-columns: auto repeat(3, minmax(max-content, 1fr));
    align-items: end;
    gap: var(--space-3);
  }

  .weather-rain-lead {
    display: flex;
    align-items: center;
    gap: 6px;
    padding-bottom: 1px;
    color: var(--text-secondary);
  }

  .weather-rain dt {
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
  }

  /* The phone's headline size on the kiosk too: three totals beside the lead fill the card (tokens.css). */
  .weather-rain dd {
    font-size: var(--type-headline-compact-size);
    line-height: var(--type-headline-compact-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-medium);
    white-space: nowrap;
  }

  /* ------------------------------------------------------------------
     Sheet contents
     ------------------------------------------------------------------ */
  .sheet-subtitle[data-tone="warning"] {
    color: var(--warning);
  }

  .sheet-subtitle[data-tone="critical"] {
    color: var(--danger);
  }

  .tiles {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-3);
  }

  .tile {
    padding: 14px var(--space-4) var(--space-4);
    border-radius: var(--radius-tile);
    background: var(--surface-2);
  }

  .tile-key,
  .tile-text {
    color: var(--text-secondary);
  }

  .tile-value {
    margin-top: var(--space-1);
    font-size: var(--type-display-s-size);
    line-height: var(--type-display-s-line);
    letter-spacing: var(--type-display-s-tracking);
    font-weight: var(--weight-light);
    white-space: nowrap;
  }

  /* The "Jetzt bewässern" suggestion as a button: a tap takes it (press state, no chevron: it stays here). */
  .tile.is-link {
    display: block;
    width: 100%;
    color: inherit;
    text-align: left;
  }

  .tile.is-link > span {
    display: block;
  }

  .tile-value.is-quiet {
    font-size: var(--type-title3-size);
    line-height: var(--type-display-s-line);
    letter-spacing: var(--type-title3-tracking);
    font-weight: var(--weight-medium);
  }

  .tile-display .display-value-unit,
  .stepper-display .display-value-unit {
    font-size: var(--type-headline-size);
  }

  .tile-text {
    margin-top: var(--space-1);
  }

  .duration-block {
    margin-top: var(--space-4);
  }

  .duration-label {
    color: var(--text-secondary);
  }

  .stepper {
    display: grid;
    grid-template-columns: 56px minmax(0, 1fr) 56px;
    align-items: center;
    gap: var(--space-3);
    margin-top: 6px;
  }

  .stepper-readout,
  .refill-readout {
    min-width: 0;
    text-align: center;
  }

  .refill-readout {
    margin-top: 6px;
  }

  .stepper-value {
    font-size: var(--type-stepper-size);
    line-height: var(--type-stepper-line);
    letter-spacing: var(--type-display-m-tracking);
    font-weight: var(--weight-light);
  }

  .stepper-display .display-value-unit {
    font-size: var(--type-title3-size);
  }

  .stepper-text {
    color: var(--text-secondary);
  }

  .duration-presets {
    margin-top: var(--space-3);
  }

  .segment-unit {
    margin-inline-start: 0.25em;
    font-size: var(--type-caption-size);
    letter-spacing: var(--type-caption-tracking);
    font-weight: var(--weight-regular);
  }

  .sheet-info {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-4);
    color: var(--text-secondary);
  }

  .sheet-info[data-tone="warning"] {
    color: var(--warning);
  }

  /* Refill by amount over the room: why Start waits, and the amount that fits. */
  .refill-room-limit-text {
    flex: 1 1 auto;
    min-width: 0;
  }

  .refill-room-apply {
    flex: none;
  }

  /* Refill by amount (refill-sheet.js): the label carries the engine's range. */
  .refill-amount-label {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
  }

  /* The amount is the run sheet's readout made tappable (the keypad opens). */
  .refill-amount-readout {
    display: block;
    width: 100%;
    min-height: var(--control-height);
    padding: var(--space-1) var(--space-2);
    border: 0;
    border-radius: var(--radius-control);
    background: var(--surface-2);
    color: var(--text-primary);
    font: inherit;
    /* A spinbutton, not a button (stepper.js): the button behaviour by hand. */
    cursor: pointer;
    touch-action: manipulation;
    user-select: none;
    -webkit-user-select: none;
  }

  .refill-amount-readout > span {
    display: block;
  }

  .refill-amount-readout.is-invalid {
    box-shadow: inset 0 0 0 2px var(--danger);
  }

  .refill-amount-readout.is-editing {
    box-shadow: inset 0 0 0 2px var(--accent);
  }

  .refill-amount-error {
    margin-top: var(--space-1);
    text-align: center;
  }

  /* "1.000 L" needs the whole segment on a 320 px phone. */
  @container app (max-width: 359px) {
    .refill-amount-presets .segmented-option {
      padding-inline: 0;
    }
  }

  /* An inline error takes the place of the info line (the run sheet's
     "one zone at a time" says the same), so the sheet does not outgrow
     the screen. */
  .sheet:has(> .sheet-error:not([hidden])) .sheet-info {
    display: none;
  }

  /* ... and the duration block closes up a little, so even a two-line
     error keeps the presets in view on the 600 px kiosk. */
  .sheet:has(> .sheet-error:not([hidden])) .duration-block {
    margin-top: var(--space-2);
  }

  .sheet:has(> .sheet-error:not([hidden])) .stepper {
    margin-top: 2px;
  }

  .sheet:has(> .sheet-error:not([hidden])) .duration-presets {
    margin-top: var(--space-2);
  }

  .sheet:has(> .sheet-error:not([hidden])) .sheet-foot {
    padding-top: var(--space-3);
  }

  .zone-list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-2) var(--space-3);
    padding-bottom: var(--space-5);
  }

  /* "Soil water" over the value column of each list column. */
  .zone-list-head {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 var(--space-3);
    padding-bottom: var(--space-1);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
    text-align: right;
  }

  .zone-list-head > span {
    padding-right: var(--space-3);
  }

  /* Fixed bar and value columns: the bars and their zero marks line up
     from row to row, and the name keeps the same width in every row. */
  .zone-row {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 36px 74px;
    align-items: center;
    gap: 10px;
    min-height: 56px;
    padding: 6px var(--space-3) 6px var(--space-4);
    border-radius: var(--radius-lg);
    background: var(--surface-2);
    text-align: left;
  }

  .zone-row.is-off {
    color: var(--text-tertiary);
  }

  /* "Run establishment now": the picker's first row, across both columns. */
  .establishment-row {
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--space-3);
    width: 100%;
    margin-bottom: var(--space-4);
  }

  .establishment-row > .icon:first-child {
    color: var(--accent);
  }

  .establishment-row > .icon:last-child {
    color: var(--text-tertiary);
  }

  .zone-row .hint-dot {
    left: 6px;
  }

  .zone-row-text {
    min-width: 0;
  }

  .zone-row-name {
    display: block;
    overflow: hidden;
    font-weight: var(--weight-medium);
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .zone-row-tag {
    display: flex;
    align-items: flex-start;
    gap: var(--space-1);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
  }

  .zone-row-tag > .meaning-icon {
    margin-top: 1px;
  }

  .zone-row-tag > .meaning-icon > [data-meaning="notice"] {
    color: var(--warning);
  }

  .zone-row.is-off .zone-row-tag {
    color: var(--text-tertiary);
  }

  /* At its column's end: a value wider than the column („+16,9 mm“ on the kiosk, 77 of 74 px) reaches into the gap
     before it, not 3 px past the others' end (the layout probe's `spilled`, 2026-10-11). */
  .zone-row-value {
    justify-self: end;
    text-align: right;
    white-space: nowrap;
  }

  .bucket-bar {
    position: relative;
    height: 6px;
    border-radius: 3px;
    background: var(--surface-3);
  }

  .bucket-bar::after {
    content: "";
    position: absolute;
    top: -3px;
    bottom: -3px;
    left: 28.6%;
    width: 1px;
    background: var(--text-tertiary);
  }

  .bucket-bar > i {
    position: absolute;
    top: 0;
    bottom: 0;
    border-radius: 3px;
  }

  .bucket-bar > i.is-positive {
    background: var(--accent);
  }

  .bucket-bar > i.is-negative {
    background: var(--text-secondary);
  }

  .facts {
    padding: 0 var(--space-4);
    border-radius: var(--radius-tile);
    background: var(--surface-2);
  }

  .facts > div {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    min-height: 48px;
  }

  .facts > div + div {
    border-top: 1px solid var(--hairline);
  }

  .facts dt {
    color: var(--text-secondary);
  }

  .facts dd {
    font-weight: var(--weight-medium);
    text-align: right;
  }

  .prose {
    margin-top: var(--space-4);
    font-size: var(--type-headline-size);
    line-height: 24px;
    letter-spacing: var(--type-headline-tracking);
  }

  .prose-sub {
    margin-top: var(--space-2);
    color: var(--text-secondary);
  }

  /* A notice's way to its History event, under its text (hint-sheet.js; plan of 2026-10-10, group E). */
  .hint-sheet-history {
    margin: var(--space-4) 0 var(--space-2);
  }

  /* ------------------------------------------------------------------
     Run, "Standort und Zeit", "Verbindung", "Status" (sheets/run-sheet.js,
     location-sheet.js, connection-sheet.js, status-sheet.js; plan of
     2026-10-10): facts and link lists in one rhythm, a section key over
     each group, a note right under what it is about.
     ------------------------------------------------------------------ */
  .run-sheet,
  .location-sheet,
  .connection-sheet,
  .status-sheet {
    display: grid;
    gap: var(--space-4);
    padding-bottom: var(--space-2);
  }

  :is(.run-sheet, .location-sheet, .connection-sheet, .status-sheet) > :is(.prose, .prose-sub) {
    margin-top: 0;
  }

  :is(.run-sheet, .connection-sheet) section {
    display: grid;
    gap: var(--space-2);
  }

  :is(.run-sheet, .connection-sheet) section > .prose-sub {
    margin-top: 0;
  }

  /* The water badge belongs to the facts above it. */
  .run-sheet-water {
    margin-top: calc(var(--space-2) - var(--space-4));
  }

  .run-sheet .facts > div[data-tone="warning"] dd {
    color: var(--warning);
  }

  .location-sheet .prose-sub {
    margin-top: calc(var(--space-2) - var(--space-4));
  }

  .connection-checks {
    display: grid;
    gap: var(--space-1);
  }

  .connection-checks > li {
    position: relative;
    padding-left: var(--space-4);
    text-wrap: pretty;
  }

  .connection-checks > li::before {
    content: "";
    position: absolute;
    left: 4px;
    top: 0.6em;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--text-secondary);
  }

  /* ------------------------------------------------------------------
     Shared by the zone pieces
     ------------------------------------------------------------------ */
  .section-key {
    color: var(--text-secondary);
  }

  .attention-dot {
    width: 8px;
    height: 8px;
    flex: none;
    border-radius: 50%;
    background: var(--warning);
  }

  /* Words, not figures: Inter's tabular figures widen the hyphen. */
  .zone-tile-name,
  .zone-name,
  .zone-row-name,
  .form-label,
  .form-hint,
  .balance-label,
  .section-key {
    font-variant-numeric: normal;
  }

  .sheet-info.is-warning {
    color: var(--warning);
  }

  /* ------------------------------------------------------------------
     Zones page
     ------------------------------------------------------------------ */
  .page-zones {
    display: flex;
    flex-direction: column;
  }

  /* Sticky over the tiles when there are more zones than fit. */
  .zones-head {
    position: sticky;
    top: calc(-1 * var(--space-1));
    z-index: 2;
    display: flex;
    align-items: center;
    gap: var(--space-4);
    min-height: 56px;
    margin-bottom: var(--space-3);
    background: var(--surface-0);
  }

  .zones-title-group {
    flex: none;
    padding-left: var(--space-1);
  }

  .zones-title {
    flex: none;
    font-size: var(--type-title2-size);
    line-height: var(--type-title2-line);
    letter-spacing: var(--type-title2-tracking);
    font-weight: var(--weight-semibold);
  }

  .zones-establishment {
    flex: none;
    margin-left: auto;
  }

  /* The bar's meaning in one line: a sample track (deficit grey to the
     left, reserve blue to the right of the middle mark) and the words. */
  .zones-legend {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: calc(-1 * var(--space-2)) 0 var(--space-3) var(--space-1);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
  }

  .zones-legend-bar {
    position: relative;
    flex: none;
    width: 32px;
    height: 6px;
    border-radius: 3px;
    background:
      linear-gradient(to right,
        var(--surface-3) 0 20%, var(--text-secondary) 20% 50%,
        var(--accent) 50% 75%, var(--surface-3) 75% 100%);
  }

  .zones-legend-bar::after {
    content: "";
    position: absolute;
    top: -3px;
    bottom: -3px;
    left: 50%;
    width: 1px;
    background: var(--text-tertiary);
  }

  /* Tiles sized to their content, rows aligned to the top. Every tile has
     room for the name and one deviation line; the bar sits at the bottom,
     so the bars of a row line up and a tile without a line keeps the same
     calm gap above its bar. */
  .zones-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    grid-auto-rows: auto;
    align-content: start;
    gap: var(--space-3);
  }

  /* Kiosk: when the zones fit the screen, the rows share the free height
     (no empty band under the grid); with more zones the page scrolls and
     the rows keep their content height. */
  :root[data-device="kiosk"] .zones-grid {
    flex: 1 0 auto;
    align-content: stretch;
  }

  .zone-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-width: 0;
    min-height: 112px;
    padding: 14px var(--space-4) var(--space-4);
    overflow: hidden;
    border-radius: var(--radius-card);
    background: var(--surface-1);
    box-shadow: var(--card-edge);
    text-align: left;
  }

  /* The zone's kind left of the name (T6); in the DOM after name and line,
     so a screen reader names the zone first. Only the name line shares its
     width with the symbol: the second line runs under both at the tile's
     full width, so it breaks where it broke without the symbol (review
     2026-10-08: "Anwässerung 22:00", "Tomorrow 05:10" on one line at
     1024 × 600). The row gap leaves the watering mark, which sits on the
     square's corner, clear of that line. */
  .zone-tile-top {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "head"
      "line";
    align-items: center;
    gap: var(--space-1) 10px;
    min-width: 0;
  }

  .zone-tile-top:has(> .zone-kind:not([hidden])) {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "kind head"
      "line line";
    row-gap: 6px;
  }

  .zone-tile-top > .zone-kind {
    grid-area: kind;
  }

  .zone-tile-text {
    display: contents;
  }

  .zone-tile-top .zone-tile-head {
    grid-area: head;
  }

  .zone-tile-top .zone-tile-line {
    grid-area: line;
  }

  /* Name and second line wrap to two lines; the soil water stays at the
     bottom (margin-top: auto). */
  .zone-tile-head {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    min-width: 0;
  }

  /* The notice dot sits on the name's first line: (22 px line − 8 px) / 2. */
  .zone-tile-head > .attention-dot {
    margin-top: 7px;
  }

  .zone-tile-name {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    min-width: 0;
    overflow: hidden;
    overflow-wrap: break-word;
    /* The phone's headline size on the kiosk too, so a name breaks where it broke without the symbol (tokens.css). */
    font-size: var(--type-headline-compact-size);
    line-height: var(--type-headline-compact-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-semibold);
  }

  /* The second line: only a deviation from the normal case (model line),
     led by the icon of what it means, aligned to its first text line. */
  .zone-tile-line {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    min-width: 0;
    font-size: var(--type-body-size);
    line-height: var(--type-body-line);
    letter-spacing: var(--type-body-tracking);
    color: var(--text-secondary);
  }

  .zone-tile-line-text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    min-width: 0;
    overflow: hidden;
    overflow-wrap: break-word;
  }

  .zone-tile-line > .meaning-icon {
    margin-top: 1px;
  }

  .zone-tile-line[data-tone="live"] {
    color: var(--accent);
    font-weight: var(--weight-medium);
  }

  .zone-tile-line[data-tone="warning"] {
    color: var(--warning);
  }

  /* Soil water at the bottom of every tile, so the bars line up in a row. */
  .zone-tile-soil {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-3);
    min-height: 24px;
    margin-top: auto;
    padding-top: var(--space-3);
  }

  .zone-tile-bar {
    display: block;
    min-width: 0;
  }

  .zone-tile-value {
    grid-column: 2;
    font-weight: var(--weight-medium);
    white-space: nowrap;
  }

  /* The value decides no watering run (establishment watering, manual
     only): bar muted, value quiet; the line says why. */
  .zone-tile-soil.is-muted .zone-tile-value {
    font-weight: var(--weight-regular);
    color: var(--text-secondary);
  }

  /* Switched off: dimmed, not hidden. The tile drops to the page colour
     with an outline; name, line and value stay at text-secondary (at
     least 4.5:1 on the page in both themes), only the bar fades. */
  .zone-tile[data-mode="disabled"] {
    background: var(--surface-0);
    box-shadow: inset 0 0 0 1px var(--surface-3);
  }

  .zone-tile[data-mode="disabled"] .zone-tile-name,
  .zone-tile[data-mode="disabled"] .zone-tile-value {
    color: var(--text-secondary);
  }

  .zone-tile[data-mode="disabled"] .zone-tile-bar {
    opacity: 0.4;
  }

  /* The watering mark is a coin of the tile's colour. */
  .zone-tile[data-mode="disabled"] .zone-kind-mark {
    background: var(--surface-0);
  }

  .zone-tile-progress {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    background: var(--surface-3);
  }

  .zone-tile-progress-fill {
    display: block;
    height: 100%;
    background: var(--accent);
    transform-origin: 0 50%;
  }

  /* ------------------------------------------------------------------
     Settings page (pages/settings/): groups as cards; "This device" first
     ------------------------------------------------------------------ */
  .settings-head {
    display: flex;
    align-items: center;
    max-width: 760px;
    min-height: 56px;
    margin: 0 auto var(--space-3);
  }

  .settings-title {
    padding-left: var(--space-1);
    font-size: var(--type-title2-size);
    line-height: var(--type-title2-line);
    letter-spacing: var(--type-title2-tracking);
    font-weight: var(--weight-semibold);
  }

  .settings-groups {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    max-width: 760px;
    /* Centred like a reading column, not left with an empty strip on the right. */
    margin-inline: auto;
  }

  .settings-group {
    padding: var(--space-4) var(--space-5) var(--space-2);
  }

  .settings-group-title {
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-semibold);
  }

  /* Every choice the same width, so the controls line up. */
  .settings-row .settings-segmented {
    width: 390px;
  }

  /* Six choices in that width (the kiosk's display sleep): narrow gaps keep every segment 48 px wide
     or more and "30 min" whole. */
  .settings-segmented-dense {
    --segmented-gap: var(--space-1);
  }

  .settings-segmented-dense .segmented-option {
    padding: 0 2px;
  }

  /* Overview: the sections as rows, each a link to its sub-page. */
  .settings-nav-group {
    padding-top: 0;
    padding-bottom: 0;
  }

  .settings-nav-row {
    text-decoration: none;
  }

  /* The count of open notices on the Service row (F1), before the chevron. */
  .settings-nav-row:has(> .settings-nav-badge:not([hidden])) {
    grid-template-columns: minmax(0, 1fr) auto auto;
  }

  .settings-nav-row .form-hint {
    display: block;
  }

  /* Sub-page: Back above the section's title, the content below. */
  .settings-subhead {
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
  }

  .settings-back {
    margin-left: calc(-1 * var(--space-3));
  }

  .settings-subhead-title {
    display: flex;
    align-items: center;
    gap: var(--space-1);
  }

  /* The page title names the section and its region; the card's own
     heading is not shown twice (and names no region there, settings-page.js
     createSubPage). */
  .settings-subpage .settings-group > .settings-group-title {
    display: none;
  }

  .settings-subpage .settings-group {
    padding-top: var(--space-2);
  }

  .settings-title:focus {
    outline: none;
  }

  /* ------------------------------------------------------------------
     Explanation sheet (sheets/help-sheet.js): short paragraphs to read,
     then "How Tropfwerk decides" on a quiet panel, a tip, the term
     ------------------------------------------------------------------ */
  .help {
    padding-bottom: var(--space-2);
  }

  /* Prose: words, not figures (Inter's tabular figures widen the hyphen). */
  .help-text p,
  .help-decides-list li {
    font-variant-numeric: normal;
  }

  .help-text p {
    line-height: 22px;
    text-wrap: pretty;
  }

  .help-text p + p {
    margin-top: 10px;
  }

  .help-decides {
    margin-top: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-tile);
    background: var(--surface-2);
  }

  .help-decides-title {
    font-size: var(--type-body-size);
    line-height: var(--type-body-line);
    letter-spacing: var(--type-body-tracking);
    font-weight: var(--weight-semibold);
    color: var(--text-secondary);
  }

  .help-decides-list li {
    margin-top: var(--space-2);
    text-wrap: pretty;
  }

  /* The question an explanation answers, when it stands alone (role 'lead'). */
  .help-text p.help-lead {
    font-weight: var(--weight-semibold);
  }

  /* A figure under the question (ui/illustrations/help-figures.js): a drawing on a quiet panel, as wide as
     the sheet allows up to 340 px; its text stays at 13 px or more down to a 320 px phone. Colours from the
     tokens only, so day and night need no second drawing. Static. */
  .help-figure {
    display: flex;
    justify-content: center;
    margin: var(--space-3) 0;
    padding: var(--space-3);
    border-radius: var(--radius-tile);
    background: var(--surface-2);
  }

  .help-figure-art {
    display: block;
    width: 100%;
    max-width: 340px;
    height: auto;
  }

  .help-figure-art .figure-body {
    fill: var(--surface-3);
    stroke: var(--text-secondary);
    stroke-width: 1.75;
  }

  .help-figure-art .figure-measure {
    fill: none;
    stroke: var(--text-secondary);
    stroke-width: 1.75;
  }

  .help-figure-art .figure-water {
    fill: var(--water-edge);
  }

  .help-figure-art text {
    font-variant-numeric: normal;
  }

  .help-figure-art .figure-print {
    fill: var(--text-primary);
    font-weight: var(--weight-semibold);
  }

  .help-figure-art .figure-label {
    fill: var(--text-secondary);
    font-weight: var(--weight-medium);
  }

  /* An optional tip, led by the word "Tip"; the technical term as a footnote. */
  .help-tip {
    margin-top: var(--space-4);
    line-height: 22px;
    font-variant-numeric: normal;
    text-wrap: pretty;
  }

  .help-tip-label {
    font-weight: var(--weight-semibold);
  }

  .help-term {
    margin-top: var(--space-3);
    padding-top: var(--space-2);
    border-top: 1px solid var(--hairline);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
    font-variant-numeric: normal;
  }

  /* ------------------------------------------------------------------
     Zone sheet (sheets/zone-sheet.js): tall and wide, tabs in the head
     ------------------------------------------------------------------ */
  .sheet.zone-sheet {
    width: min(880px, 100%);
    height: calc(100dvh / var(--display-zoom) - var(--header-height) - var(--space-2));
  }

  .zone-tabs {
    width: 420px;
  }

  /* In the head, the mode in the subtitle leads to "Betrieb", "Anwässerung"
     to its tab (plan of 2026-10-10, group C): a link in the line, in the
     subtitle's size; its touch height reaches above and below the line. */
  .sheet-subtitle .zone-subtitle-link {
    margin-block: calc((1lh - var(--touch-min)) / 2);
    font-size: inherit;
    line-height: inherit;
    letter-spacing: inherit;
  }

  /* The kind's symbol leads to its pickers: the touch size around the 44 px symbol. */
  .zone-kind-link {
    display: grid;
    place-items: center;
    min-width: var(--touch-min);
    min-height: var(--touch-min);
    margin: calc((44px - var(--touch-min)) / 2);
    border-radius: var(--radius-tile);
  }

  .zone-tabs .segmented-option {
    padding: 0 var(--space-3);
  }

  .zone-sheet .sheet-foot {
    justify-content: flex-end;
  }

  .zone-sheet .sheet-foot > .button {
    flex: 0 1 auto;
    min-width: 280px;
  }

  /* Pushed over another sheet (plan of 2026-10-10): "Zurück" first, as
     wide as its label, the view's own actions stay at the end. */
  .zone-sheet .sheet-foot > .sheet-action-back {
    min-width: max-content;
    margin-right: auto;
  }

  .zone-views {
    padding-bottom: var(--space-5);
  }

  /* ---------- Overview ---------- */
  .zone-overview {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
    gap: var(--space-6);
  }

  .zone-overview-main,
  .zone-overview-side,
  .zone-overview-status {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    min-width: 0;
  }

  /* What runs, and beside it its own end where the engine can end just this run (F3). */
  .zone-running {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-tile);
    background: var(--surface-2);
  }

  /* On the block's own surface a secondary button would not stand out: the sheet's surface. */
  .zone-running > .button {
    flex: none;
    background: var(--surface-1);
  }

  .zone-running-title {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    color: var(--accent);
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-semibold);
  }

  /* The zone's notice in one line as before (regular weight, closer icon), now a row with a chevron. */
  .zone-link-hint {
    gap: var(--space-2);
  }

  .zone-link-hint .link-row-label {
    font-weight: var(--weight-regular);
  }

  /* On the kiosk the chevron hangs into the gap between the columns, so the line keeps its width. */
  @container app (min-width: 760px) {
    .zone-link-hint .link-row-chevron {
      margin-inline-end: calc(-1 * var(--space-3));
    }
  }

  /* A place that is a link only while it leads somewhere (zone/links.js linkOrPlain()): no box of its own. */
  .link-holder,
  .facts > .link-holder {
    display: contents;
  }

  .zone-soil-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
  }

  .zone-soil-reading {
    min-width: 0;
  }

  .zone-water-now {
    flex: none;
  }

  .zone-soil-value {
    margin-top: 2px;
    font-size: var(--type-display-s-size);
    line-height: var(--type-display-s-line);
    letter-spacing: var(--type-display-s-tracking);
    font-weight: var(--weight-light);
  }

  .zone-soil-bar .soil-bar {
    margin: var(--space-2) 0;
  }

  .zone-soil-text {
    color: var(--text-secondary);
  }

  /* "Durchfluss unbekannt" and the like: rows that lead to the field or explanation. */
  .zone-attention {
    margin-top: var(--space-2);
  }

  /* Next and last run side by side, each value under its label; the
     establishment program spans both. A fact that leads somewhere is a
     button with a chevron; one that does not reads the same without it. */
  .facts.zone-facts {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--space-4);
  }

  .zone-fact {
    display: flex;
    align-items: center;
    gap: 2px;
    min-width: 0;
    min-height: var(--touch-min);
    padding: var(--space-2) 0;
    border-radius: var(--radius-row);
    color: inherit;
    text-align: left;
  }

  .zone-fact.is-wide {
    grid-column: 1 / -1;
    border-top: 1px solid var(--hairline);
  }

  .zone-fact-text {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
  }

  .zone-fact-label {
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
    white-space: nowrap;
  }

  .zone-fact .fact-title {
    font-weight: var(--weight-medium);
  }

  /* The chevron sits in the gap beside the column (and in the surface's padding on the right), so
     the facts keep their width and wrap as before. */
  .zone-fact-chevron {
    flex: none;
    margin-inline-end: calc(-1 * var(--space-4));
    color: var(--text-tertiary);
  }

  /* The run type before the next and the last run. */
  .fact-title {
    display: flex;
    align-items: flex-start;
    gap: 6px;
  }

  .fact-title > .meaning-icon {
    margin-top: 1px;
    color: var(--text-secondary);
  }

  .fact-meta {
    font-weight: var(--weight-regular);
    color: var(--text-secondary);
  }

  .zone-load-error {
    color: var(--danger);
  }

  @container app (min-width: 760px) {
    .zone-overview .soil-chart {
      --plot-height: 96px;
    }

    /* The notice line is a 48 px row now (plan of 2026-10-10): the plot gives up what it adds, so the
       overview of a zone with a notice still fits the kiosk sheet. */
    .zone-overview-main:has(.zone-link-hint:not([hidden])) .soil-chart {
      --plot-height: 80px;
    }

    .zone-overview-main,
    .zone-overview-status {
      gap: var(--space-3);
    }

    .zone-overview .balance-row {
      padding: 6px 0;
    }

    .zone-overview .balance-row.is-link {
      padding: 2px 0;
    }
  }

  /* ---------- Soil water chart ---------- */
  .soil-chart {
    --plot-height: 132px;
    margin: 0;
  }

  .chart-frame {
    display: grid;
    grid-template-columns: 56px minmax(0, 1fr);
    margin-top: var(--space-3);
  }

  .chart-y-labels {
    position: relative;
    height: var(--plot-height);
  }

  .chart-y {
    position: absolute;
    right: var(--space-2);
    transform: translateY(-50%);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-tertiary);
    white-space: nowrap;
  }

  .chart-y.is-zero {
    color: var(--text-secondary);
  }

  .chart-limit-label {
    position: absolute;
    left: var(--space-1);
    padding-bottom: 2px;
    transform: translateY(-100%);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
    white-space: nowrap;
    pointer-events: none;
  }

  .chart-limit-label.is-below {
    padding: 2px 0 0;
    transform: none;
  }

  .chart-main {
    min-width: 0;
  }

  .chart-plot {
    position: relative;
    height: var(--plot-height);
  }

  .chart-lines {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
  }

  .chart-area {
    fill: var(--accent);
    opacity: 0.14;
  }

  .chart-line {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
  }

  .chart-zero {
    stroke: var(--text-tertiary);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
  }

  .chart-limit {
    stroke: var(--text-tertiary);
    stroke-width: 1;
    stroke-dasharray: 4 4;
    vector-effect: non-scaling-stroke;
  }

  /* A zone on its roots: the deficit Tropfwerk waters at, „Hier gießt Tropfwerk“ (its label above the line). */
  .chart-watering {
    stroke: var(--accent);
    stroke-width: 1;
    stroke-dasharray: 2 3;
    vector-effect: non-scaling-stroke;
  }

  /* The plot as the balance's day slider: the day shown is a calm band over its column. */
  .chart-plot.is-day-slider {
    border-radius: var(--radius-xs);
  }

  .chart-day {
    position: absolute;
    top: 0;
    bottom: 0;
    border-radius: 2px;
    background: color-mix(in srgb, var(--accent) 14%, transparent);
    pointer-events: none;
  }

  .chart-current {
    position: absolute;
    width: 8px;
    height: 8px;
    margin: -4px 0 0 -4px;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 0 2px var(--surface-1);
  }

  .chart-caption {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
  }

  /* Calm: the accent is for what can be tapped, and the value stands large above the chart (plan of 2026-10-10, F). */
  .chart-now {
    color: var(--text-secondary);
    font-weight: var(--weight-medium);
    white-space: nowrap;
  }

  .chart-lane {
    position: relative;
    height: 16px;
    border-radius: var(--radius-xs);
    background: var(--surface-2);
  }

  /* The run lane with the day labels under it: one target for the runs of
     a day while there are runs (markers are 8 px), the touch height with
     the gap above the lane. */
  .chart-runs {
    border-radius: var(--radius-xs);
  }

  .chart-runs[role="button"] {
    min-height: var(--touch-min);
    padding-top: var(--space-2);
  }

  .chart-marker {
    position: absolute;
    top: 50%;
    width: 8px;
    height: 8px;
    margin: -4px 0 0 -4px;
    border-radius: 50%;
    background: var(--accent);
  }

  .chart-marker[data-kind="establishment"] {
    background: transparent;
    box-shadow: inset 0 0 0 2px var(--accent);
  }

  .chart-marker[data-kind="manual"] {
    border-radius: 2px;
    background: var(--text-secondary);
  }

  .chart-marker[data-kind="other"] {
    background: var(--text-tertiary);
  }

  .chart-marker.is-legend {
    position: static;
    display: inline-block;
    margin: 0;
  }

  .chart-x-labels {
    position: relative;
    height: var(--type-caption-line);
    margin-top: var(--space-1);
  }

  .chart-x {
    position: absolute;
    top: 0;
    transform: translateX(-50%);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-tertiary);
    white-space: nowrap;
  }

  .chart-x:first-child {
    transform: none;
  }

  .chart-x.is-end {
    transform: translateX(-100%);
  }

  .chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-4);
    margin: var(--space-2) 0 0 56px;
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
  }

  .chart-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
  }

  .chart-message {
    margin-top: var(--space-2);
    color: var(--text-secondary);
  }

  /* ---------- Water balance ---------- */
  .balance-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
  }

  .balance-title {
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-semibold);
  }

  .balance-nav {
    display: flex;
    gap: var(--space-2);
  }

  .balance-verdict {
    margin-top: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-tile);
    background: var(--surface-2);
  }

  /* An establishment verdict leads to the program (its tab). */
  .balance-verdict.is-link {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    color: inherit;
    text-align: left;
  }

  .balance-verdict-body {
    flex: 1 1 auto;
    min-width: 0;
  }

  .balance-verdict.is-link .balance-chevron {
    margin: 0;
  }

  .balance-verdict-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-semibold);
  }

  .tone-dot {
    width: 8px;
    height: 8px;
    flex: none;
    border-radius: 50%;
    background: var(--text-tertiary);
  }

  .balance-verdict[data-tone="warning"] .tone-dot {
    background: var(--warning);
  }

  .balance-verdict[data-tone="ok"] .tone-dot {
    background: var(--success);
  }

  .balance-verdict[data-tone="info"] .tone-dot {
    background: var(--accent);
  }

  .balance-verdict-text {
    margin-top: 2px;
  }

  .balance-basis {
    margin-top: 2px;
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
  }

  .balance-rows,
  .balance-verdict {
    transition: opacity var(--duration-fast) ease;
  }

  .balance.is-pending .balance-rows,
  .balance.is-pending .balance-verdict {
    opacity: 0.45;
  }

  .balance-rows {
    margin-top: var(--space-2);
  }

  .balance-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding: 7px 0;
  }

  .balance-row + .balance-row {
    border-top: 1px solid var(--hairline);
  }

  .balance-label-group {
    min-width: 0;
  }

  .balance-value {
    white-space: nowrap;
  }

  /* A change that leads where it comes from (zone-links.js): the touch height, a chevron. */
  .balance-row.is-link {
    align-items: center;
    width: 100%;
    min-height: var(--touch-min);
    padding: 2px 0;
    color: inherit;
    text-align: left;
  }

  .balance-row.is-link .balance-label-group {
    flex: 1 1 auto;
  }

  .balance-chevron {
    flex: none;
    margin-inline-start: calc(-1 * var(--space-2));
    color: var(--text-tertiary);
  }

  .balance-row.is-level .balance-label,
  .balance-row.is-level .balance-value {
    font-weight: var(--weight-semibold);
  }

  .balance-row.is-change .balance-label {
    color: var(--text-secondary);
  }

  /* Changes that are zero, in one line (waterBalanceView().visibleRows). */
  .balance-row.is-note .balance-label {
    color: var(--text-secondary);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
  }

  .balance-note {
    display: block;
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-tertiary);
  }

  .balance-why {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    min-height: var(--touch-min);
    margin-top: var(--space-1);
    color: var(--accent);
    font-weight: var(--weight-medium);
  }

  .balance-why .icon {
    transition: transform var(--duration-base) var(--ease-out);
  }

  .balance-why[aria-expanded="true"] .icon {
    transform: rotate(180deg);
  }

  @media (prefers-reduced-motion: reduce) {
    .balance-why .icon {
      transition: none;
    }
  }

  .balance-why-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    color: var(--text-secondary);
    text-wrap: pretty;
  }

  .balance-message {
    margin-top: var(--space-3);
    color: var(--text-secondary);
  }

  .balance-retry {
    margin-top: var(--space-2);
  }

  /* ---------- Forms (establishment, settings, establishment now) ---------- */
  .form-list {
    display: flex;
    flex-direction: column;
  }

  .form-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-2) var(--space-4);
    padding: var(--space-3) 0;
  }

  .form-list > * + * {
    border-top: 1px solid var(--hairline);
  }

  .form-row.is-stacked {
    grid-template-columns: minmax(0, 1fr);
  }

  .form-row-text {
    min-width: 0;
  }

  .form-label {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--space-2);
    font-weight: var(--weight-medium);
  }

  .form-range,
  .form-sub {
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    font-weight: var(--weight-regular);
    color: var(--text-tertiary);
  }

  .form-hint {
    margin-top: 2px;
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
    text-wrap: pretty;
  }

  .form-control {
    justify-self: end;
    min-width: 0;
  }

  .form-row.is-stacked .form-control {
    justify-self: stretch;
  }

  .form-error {
    grid-column: 1 / -1;
    color: var(--danger);
  }

  .form-row.is-name .text-field {
    width: 340px;
  }

  .form-row.is-stacked.is-name .text-field {
    width: 100%;
  }

  .form-row-extra {
    padding: 0 0 var(--space-3);
  }

  .form-list > .form-row-extra {
    border-top: 0;
  }

  .establishment-presets {
    margin-top: 0;
  }

  /* The top padding keeps the hit area of the info button at its end
     inside the scrolling body. */
  .establishment-intro {
    margin: 0 0 var(--space-2);
    padding-top: 6px;
  }

  /* The program's explanation at the end of the intro, while there is no program. */
  .establishment-intro .intro-info {
    margin-left: 6px;
  }

  .establishment-end {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3) 0;
  }

  .establishment-summary {
    margin-top: var(--space-3);
    color: var(--text-secondary);
  }

  .settings-section {
    margin-top: var(--space-5);
  }

  .settings-section > .section-key,
  .settings-section > .section-key-row {
    margin-bottom: var(--space-2);
  }

  .flow-value {
    margin-left: auto;
  }

  .flow-offer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2) var(--space-3);
    padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
    border-radius: var(--radius-md);
    background: var(--surface-warning);
  }

  .flow-offer > .button {
    background: var(--surface-warning-strong);
  }

  .flow-log {
    display: grid;
    grid-template-columns: auto auto minmax(0, 1fr);
    gap: 2px var(--space-4);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
  }

  .flow-log > li {
    display: contents;
  }

  .flow-log-status {
    justify-self: end;
  }

  .flow-log > li[data-status="unusual"] .flow-log-status {
    color: var(--warning);
  }

  .maintenance {
    display: flex;
    flex-direction: column;
  }

  .maintenance-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-2) var(--space-4);
    padding: var(--space-3) 0;
  }

  .maintenance-item + .maintenance-item {
    border-top: 1px solid var(--hairline);
  }

  .maintenance-text {
    min-width: 0;
  }

  .valve-live,
  .confirm-panel {
    grid-column: 1 / -1;
  }

  .valve-live-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--accent);
    font-weight: var(--weight-medium);
  }

  .valve-live .progress {
    margin-top: var(--space-2);
  }

  .confirm-panel {
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    background: var(--surface-warning);
  }

  .confirm-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-3);
    margin-top: var(--space-3);
  }

  .confirm-actions > .button[data-variant="secondary"] {
    background: var(--surface-warning-strong);
  }

  /* ------------------------------------------------------------------
     Pressed and hover states (the pressed tint lets go while the
     showroom's mouse drags or holds the page, base.css)
     ------------------------------------------------------------------ */
  :where(:root:not([data-drag-scroll])) button.run-row:active {
    background: var(--surface-2);
  }

  :where(:root:not([data-drag-scroll])) .wave-zone:active,
  :where(:root:not([data-drag-scroll])) button.zone-row:active {
    background: var(--surface-3);
  }

  :where(:root:not([data-drag-scroll])) .zone-tile:active,
  :where(:root:not([data-drag-scroll])) .balance-why:active {
    background: var(--surface-2);
  }

  @media (hover: hover) and (pointer: fine) {
    button.run-row:hover,
    button.list-row:hover {
      background: var(--surface-2);
    }

    .wave-zone:hover,
    button.zone-row:hover {
      background: var(--surface-3);
    }

    .zone-tile:hover {
      background: var(--surface-2);
    }

    .hint-actions > .button:not(:disabled):hover {
      background: var(--surface-warning-hover);
    }

    .hint-card[data-tone="info"] .hint-actions > .button:not(:disabled):hover {
      background: var(--fill-hover);
    }
  }

  /* ------------------------------------------------------------------
     Tablet portrait (700–959 px): two columns, the content area scrolls
     ------------------------------------------------------------------ */
  @container app (min-width: 700px) and (max-width: 959px) {
    .page-today,
    .today-grid {
      height: auto;
    }

    .today-grid {
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
      grid-template-areas: "a b" "a c";
    }

    .today-col-a {
      grid-area: a;
      align-self: start;
    }

    .today-col-b {
      grid-area: b;
    }

    .today-col-c {
      grid-area: c;
    }

    .gauge {
      flex: none;
      height: 300px;
    }

    .runs-list {
      overflow: visible;
    }

    .today-col {
      overflow: visible;
    }
  }

  /* ------------------------------------------------------------------
     Narrow sheets (< 760 px): the zone sheet in one column
     ------------------------------------------------------------------ */
  @container app (max-width: 759px) {
    .zone-overview {
      grid-template-columns: minmax(0, 1fr);
    }

    .zone-tabs {
      width: 100%;
    }

    .zone-sheet .sheet-foot > .button {
      flex: 1 1 auto;
      min-width: max-content;
    }
  }

  /* ------------------------------------------------------------------
     Phones (< 700 px): one column in order of urgency
     ------------------------------------------------------------------ */
  @container app (max-width: 699px) {
    /* Zones: title and "establishment now", one row per zone: name and
       value, under them the line (deviations only) and the bar. */
    .zones-head {
      position: static;
      flex-wrap: wrap;
      gap: var(--space-2) var(--space-3);
      padding: var(--space-3) 0 0;
    }

    /* Title, its info button and "Establishment now" keep one row at
       390 px in both languages: a small button (zones-page.js), narrow here. */
    .zones-establishment {
      padding: 0 var(--space-3);
    }

    .zones-title {
      padding-left: 0;
      font-size: var(--type-title1-size);
      line-height: var(--type-title1-line);
      letter-spacing: var(--type-title1-tracking);
    }

    .settings-head {
      min-height: 0;
      padding: var(--space-3) 0 0;
    }

    .settings-title {
      padding-left: 0;
      font-size: var(--type-title1-size);
      line-height: var(--type-title1-line);
      letter-spacing: var(--type-title1-tracking);
    }

    .settings-group {
      padding: var(--space-3) var(--space-4) var(--space-1);
    }

    /* The choice under its label, across the width. */
    .settings-row {
      grid-template-columns: minmax(0, 1fr);
    }

    .settings-row .form-control {
      justify-self: stretch;
    }

    .settings-row .settings-segmented,
    .settings-row .settings-action {
      width: 100%;
    }

    .settings-segmented .segmented-option {
      padding: 0 2px;
    }

    .zones-grid {
      grid-template-columns: minmax(0, 1fr);
      grid-auto-rows: auto;
      gap: var(--space-2);
    }

    .zone-tile {
      display: grid;
      grid-template-columns: minmax(0, 1fr) auto;
      grid-template-areas:
        "head value"
        "line bar";
      align-items: center;
      gap: 2px var(--space-3);
      min-height: 0;
      padding: var(--space-3) var(--space-4);
    }

    .zone-tile-head {
      grid-area: head;
    }

    .zone-tile-line {
      grid-area: line;
    }

    /* The kind spans both lines, left of the name (T6). */
    .zone-tile-top,
    .zone-tile-text {
      display: contents;
    }

    .zone-tile:has(> .zone-tile-top > .zone-kind:not([hidden])) {
      grid-template-columns: auto minmax(0, 1fr) auto;
      grid-template-areas:
        "kind head value"
        "kind line bar";
    }

    .zone-tile-top > .zone-kind {
      grid-area: kind;
      align-self: center;
    }

    .zone-tile-soil {
      display: contents;
    }

    .zone-tile-soil > .zone-tile-bar {
      grid-area: bar;
      width: 96px;
    }

    .zone-tile-value {
      grid-area: value;
      justify-self: end;
    }

    .sheet.zone-sheet {
      height: calc(100dvh / var(--display-zoom) - var(--header-height) - var(--space-2));
    }

    .form-row.is-name .text-field {
      width: 100%;
    }

    /* Maintenance: the text across the width, its button under it. */
    .maintenance-item {
      grid-template-columns: minmax(0, 1fr);
      justify-items: start;
    }

    /* Duration presets and the mode choice: tighter segments so the labels fit. */
    .establishment-presets .segmented-option,
    .settings-choice .segmented-option {
      padding: 0 2px;
    }

    .establishment-presets .segment-unit {
      margin-inline-start: 0.15em;
    }

    /* Number rows: the stepper under its label, across the full width. */
    .form-row:has(> .form-control > .form-stepper) {
      grid-template-columns: minmax(0, 1fr);
    }

    .form-row:has(> .form-control > .form-stepper) .form-control {
      justify-self: stretch;
    }

    .form-row .form-stepper {
      grid-template-columns: var(--control-height) minmax(0, 1fr) var(--control-height);
    }

    /* The info button's hit area reaches 12 px below its label: keep the
       stepper under it out of that area. */
    .form-row:has(> .form-control > .form-stepper):has(.form-label > .info-button) {
      row-gap: 14px;
    }

    .zone-tabs .segmented-option {
      padding: 0 var(--space-1);
    }

    .chart-frame {
      grid-template-columns: 48px minmax(0, 1fr);
    }

    .chart-legend {
      margin-left: 48px;
    }

    .page-today,
    .today-grid {
      height: auto;
    }

    .today-grid {
      display: flex;
      flex-direction: column;
    }

    .today-col {
      display: contents;
      overflow: visible;
    }

    .hint-deck {
      order: 1;
    }

    .run-card {
      order: 2;
    }

    /* The card grows with its content: air between the measured line and the progress bar. */
    .run-flow {
      margin-bottom: var(--space-2);
    }

    .cistern-card {
      order: 3;
      flex: none;
    }

    .runs-card {
      order: 4;
    }

    .weather-card {
      order: 5;
    }

    .system-card {
      order: 6;
    }

    .gauge {
      flex: none;
      height: 230px;
    }

    .runs-list {
      overflow: visible;
    }

    .page-placeholder {
      min-height: calc(100dvh / var(--display-zoom) - 220px);
    }

    .zone-list,
    .zone-list-head {
      grid-template-columns: 1fr;
    }

    .zone-list-head-second {
      display: none;
    }

    .tile {
      padding: var(--space-3) 14px 14px;
    }

    .weather-rain {
      grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    .weather-rain-lead {
      grid-column: 1 / -1;
    }
  }

  /* Small phones (< 380 px, e.g. 360 px): the three-part choices take the
     room between and inside their segments, so the longest label (German
     "automatic") still fits at body size. */
  @container app (max-width: 379px) {
    .settings-segmented {
      --segmented-gap: var(--space-1);
    }

    .settings-segmented .segmented-option {
      padding: 0;
    }

    /* The zone sheet's tabs and its mode, the same way (a 390 px phone at the display size's larger step is 355 px):
       „Anwässerung“ ran 4 px past its tab, „Establishment“ 6.7 px, „Nur von Hand“ 1.8 px (review 2026-10-11). The
       tabs need every px between them: no gap, the thumb still marks the one shown. */
    .zone-tabs {
      --segmented-gap: 0px;
    }

    .settings-choice {
      --segmented-gap: var(--space-1);
    }

    .zone-tabs .segmented-option,
    .settings-choice .segmented-option {
      padding: 0;
    }
  }

  /* ------------------------------------------------------------------
     Reduced motion and more contrast
     ------------------------------------------------------------------ */
  @media (prefers-reduced-motion: reduce) {
    .run-stack > * {
      transform: none !important;
    }
  }

  @media (prefers-contrast: more) {
    .tile,
    .zone-row,
    .wave-zone,
    .facts {
      box-shadow: inset 0 0 0 1px var(--border-strong);
    }

    .hint-card {
      box-shadow: inset 0 0 0 1px var(--warning);
    }

    .hint-card[data-tone="info"] {
      box-shadow: inset 0 0 0 1px var(--info);
    }

    .mark-after,
    .mark-reserve {
      opacity: 1;
    }
  }
}
