/*
 * Components: shared primitives (card, buttons, segmented control, switch,
 * sheet, toast) and the shell pieces (header, navigation).
 *
 * Rules: colours, radii, spacing, type and durations come from tokens.css.
 * Only transform and opacity animate. Hover styles exist only for fine
 * pointers. Do not set `transition` on buttons here: base.css owns the
 * press feedback transition and a component rule would replace it.
 */
@layer components {
  /* ------------------------------------------------------------------
     Card
     ------------------------------------------------------------------ */
  .card {
    position: relative;
    z-index: var(--z-content);
    padding: var(--space-4);
    border-radius: var(--radius-card);
    background: var(--surface-1);
    box-shadow: var(--card-edge);
  }

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

  /* A heading, not an eyebrow (type audit 2026-10-10): at 15 px in the secondary colour "Zisterne" and
     "Nächste Läufe" were smaller than the rows they head ("Gemüsebeet", 17 px medium). Headline
     semibold, like Apple's card headers. The other way, the rows at regular weight under a 15 px title,
     would have kept the title the smallest line of its card. Measured: the kiosk's Today still fits
     1024 × 600 (every column ends at 584 px) with the cistern gauge's words at 13 px (tokens.css). */
  .card-title {
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
  }

  /* ------------------------------------------------------------------
     Display value: large number, smaller unit (components/display-value.js)
     ------------------------------------------------------------------ */
  .display-value {
    white-space: nowrap;
  }

  .display-value-unit {
    margin-inline-start: 0.22em;
    font-size: 0.5em;
    font-weight: var(--weight-regular);
    letter-spacing: var(--type-headline-tracking);
    color: var(--text-secondary);
  }

  /* ------------------------------------------------------------------
     Buttons
     ------------------------------------------------------------------ */
  .button,
  .icon-button {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--surface-2);
    color: var(--text-primary);
    font-weight: var(--weight-medium);
    white-space: nowrap;
  }

  .button {
    min-height: var(--control-height);
    min-width: var(--control-height);
    padding: 0 var(--space-5);
    border-radius: var(--radius-control);
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
  }

  .button[data-size="sm"] {
    min-height: var(--touch-min);
    min-width: var(--touch-min);
    padding: 0 var(--space-4);
    font-size: var(--type-body-size);
    line-height: var(--type-body-line);
    letter-spacing: var(--type-body-tracking);
  }

  .button[data-size="lg"] {
    min-height: var(--control-height-lg);
    padding: 0 var(--space-6);
    font-weight: var(--weight-semibold);
  }

  .button-block {
    display: flex;
    width: 100%;
  }

  .icon-button {
    width: var(--control-height);
    height: var(--control-height);
    flex: none;
    border-radius: var(--radius-full);
  }

  .icon-button[data-size="sm"] {
    width: var(--touch-min);
    height: var(--touch-min);
  }

  .icon-button[data-size="lg"] {
    width: var(--control-height-lg);
    height: var(--control-height-lg);
  }

  .button-label {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-width: 0;
    transition: opacity var(--duration-fast) ease;
  }

  .button-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .button-busy {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    opacity: 0;
    pointer-events: none;
  }

  .button-busy-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.6;
  }

  [data-busy] > .button-label {
    opacity: 0;
  }

  [data-busy] > .button-busy {
    opacity: 1;
  }

  .button[data-variant="primary"],
  .icon-button[data-variant="primary"] {
    background: var(--accent);
    color: var(--on-accent);
    font-weight: var(--weight-semibold);
  }

  .button[data-variant="ghost"],
  .icon-button[data-variant="ghost"] {
    background: transparent;
    color: var(--accent);
  }

  .button[data-variant="danger"],
  .icon-button[data-variant="danger"] {
    background: var(--danger);
    color: var(--on-danger);
    font-weight: var(--weight-semibold);
  }

  /* Every other button that triggers the emergency stop (Stop in
     "Running now", in the manual control banner) is red like the header's
     (Stephan 2026-10-06, 22:00): the same tokens, so the contrast checked
     for them holds (tests/theme-tokens.test.js). A press darkens it. */
  .button[data-variant="stop"],
  .icon-button[data-variant="stop"] {
    background: var(--stop-fill);
    color: var(--on-stop);
    font-weight: var(--weight-semibold);
  }

  .button[data-variant="stop"].is-pressed:not(:disabled),
  .icon-button[data-variant="stop"].is-pressed:not(:disabled) {
    background: var(--stop-fill-pressed);
  }

  /* Dimmed by colour, not by opacity: the label keeps at least 4.5:1. */
  .button:disabled,
  .icon-button:disabled,
  .button[aria-disabled="true"],
  .icon-button[aria-disabled="true"] {
    background: var(--surface-2);
    color: var(--text-tertiary);
  }

  /* ------------------------------------------------------------------
     Segmented control
     ------------------------------------------------------------------ */
  .segmented {
    --segmented-gap: var(--space-2);
    --segmented-pad: var(--space-1);
    position: relative;
    display: grid;
    grid-template-columns: repeat(var(--segment-count, 2), minmax(0, 1fr));
    gap: var(--segmented-gap);
    padding: var(--segmented-pad);
    border-radius: var(--radius-xl);
    background: var(--surface-2);
  }

  /* One size on every page and at every width: the body size (type audit 2026-10-10 found 17 px on the
     History's period and the kiosk's zone sheet, 15 px on Weather, the phone's zone sheet and Settings).
     The phone's 390 px needs the body size; Apple's segment label is smaller still. */
  .segmented-option {
    position: relative;
    z-index: 1;
    min-height: var(--control-height);
    padding: 0 var(--space-2);
    border-radius: var(--radius-md);
    font-size: var(--type-body-size);
    line-height: var(--type-body-line);
    letter-spacing: var(--type-body-tracking);
    font-weight: var(--weight-medium);
    text-align: center;
    white-space: nowrap;
    color: var(--text-secondary);
  }

  .segmented-option[aria-checked="true"],
  .segmented-option[aria-selected="true"] {
    color: var(--text-primary);
  }

  .segmented-option:disabled {
    color: var(--text-tertiary);
    cursor: default;
  }

  .segmented-thumb {
    position: absolute;
    z-index: 0;
    top: var(--segmented-pad);
    bottom: var(--segmented-pad);
    left: var(--segmented-pad);
    width: calc((100% - 2 * var(--segmented-pad) - (var(--segment-count, 2) - 1) * var(--segmented-gap)) / var(--segment-count, 2));
    border-radius: var(--radius-md);
    background: var(--fill-thumb);
    box-shadow: var(--shadow-thumb);
    opacity: 0;
    transition: opacity var(--duration-fast) ease;
    will-change: transform;
  }

  /* ------------------------------------------------------------------
     Switch
     ------------------------------------------------------------------ */
  .switch {
    display: inline-grid;
    place-items: center;
    min-width: 64px;
    min-height: var(--touch-min);
    flex: none;
    border-radius: var(--radius-full);
  }

  .switch-track {
    position: relative;
    width: 52px;
    height: 32px;
    border-radius: var(--radius-full);
    background: var(--switch-track-off);
  }

  /* The "on" colour is a layer that fades in: opacity, not background-color. */
  .switch-track::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: var(--accent);
    opacity: 0;
    transition: opacity var(--duration-base) ease;
  }

  .switch-thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--switch-thumb);
    box-shadow: var(--shadow-thumb);
    transition: transform var(--duration-base) var(--ease-out);
  }

  .switch[aria-checked="true"] .switch-track::before {
    opacity: 1;
  }

  .switch[aria-checked="true"] .switch-thumb {
    transform: translate3d(20px, 0, 0);
  }

  /* Reduced motion: the thumb jumps; the track's colour still fades. */
  @media (prefers-reduced-motion: reduce) {
    .switch-thumb {
      transition: none;
    }
  }

  .switch:disabled {
    opacity: 0.45;
  }

  /* ------------------------------------------------------------------
     Header
     ------------------------------------------------------------------ */
  .header-wordmark {
    justify-self: center;
    font-size: var(--type-body-size);
    line-height: var(--type-body-line);
    letter-spacing: -0.01em;
    font-weight: var(--weight-semibold);
    color: var(--text-secondary);
  }

  /* The status sentence in parts (shell/status-line.js; plan of 2026-10-10,
     group A): each part a button of the touch height that reads as the
     sentence (no underline, no chevron, the house press state). Sentence
     and dot fade together. The message may shrink to an ellipsis when it
     stands alone; the detail never shrinks: it shows whole or not at all. */
  .status-line {
    align-items: center;
    min-width: 0;
    transition: opacity var(--duration-fast) ease;
  }

  .status-line-text {
    display: flex;
    align-items: center;
    min-width: 0;
    max-width: 100%;
  }

  .status-line.is-wrapping .status-line-text {
    flex-wrap: wrap;
  }

  .status-part {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    min-height: var(--touch-min);
    border-radius: var(--radius-sm);
    text-align: left;
  }

  .status-part[data-part="detail"] {
    flex: none;
  }

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

  .status-part-sep {
    flex: none;
    white-space: pre;
  }

  .header-status {
    display: flex;
    min-width: 0;
    padding-left: var(--space-1);
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-medium);
  }

  .status-dot {
    width: 8px;
    height: 8px;
    flex: none;
    border-radius: 50%;
    background: transparent;
  }

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

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

  .status-dot[data-tone="danger"] {
    background: var(--danger);
  }

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

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

  .header-end {
    display: flex;
    align-items: center;
    gap: var(--space-5);
    padding: 0 var(--space-3) 0 var(--space-4);
  }

  /* The connection leads to "Verbindung", the clock to "Standort und
     Zeit": buttons of the touch height, their words where they were
     (negative margins keep the gaps of the row as before). */
  .connection {
    position: relative;
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-width: var(--touch-min);
    min-height: var(--touch-min);
    margin: 0 calc(-1 * var(--space-2));
    padding: 0 var(--space-2);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    white-space: nowrap;
  }

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

  .connection[data-state="connecting"] .connection-dot {
    background: var(--warning);
  }

  .connection[data-state="offline"] .connection-dot {
    background: var(--danger);
  }

  .connection[data-state="offline"] {
    color: var(--text-primary);
  }

  .header-clock {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    min-width: var(--touch-min);
    min-height: var(--touch-min);
    margin: 0 calc(-1 * var(--space-2));
    padding: 0 var(--space-2);
    border-radius: var(--radius-sm);
    font-size: var(--type-title3-size);
    line-height: 24px;
    letter-spacing: var(--type-title3-tracking);
    font-weight: var(--weight-medium);
  }

  /* The phone's status chip (shell/status-line.js createStatusChip()):
     wide screens show the sentence instead. */
  .status-chip {
    display: none;
  }

  /* The pill keeps its place in the header at every display size, 48 of its 56 px. At the smaller step the touch
     target's compensation (--touch-min, 54 px on the kiosk) made it as tall as the header, flush with the screen's
     top and under an open alarm's red edge (review 2026-10-11): the finger's reach grows past the pill instead
     (::after), as an info button's does. */
  .stop-button {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    height: 48px;
    min-width: var(--control-height);
    padding: 0 var(--space-5) 0 var(--space-4);
    border-radius: var(--radius-full);
    background: var(--surface-2);
    color: var(--text-primary);
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-semibold);
  }

  /* Above and below the pill where the touch target is taller than it (the smaller step); none otherwise. */
  .stop-button::after {
    content: "";
    position: absolute;
    inset: min(0px, calc((48px - var(--touch-min)) / 2)) 0;
  }

  /* At rest a neutral header button, still tappable (a stop then switches
     off as a precaution); red in both themes while something runs or is
     on, or that is not known (Stephan 2026-10-07, operatingState(); F4:
     Stop is the emergency stop, the everyday end of a run is "Beenden" in
     "Running now"). A press darkens the red. No colour transition: the Pi
     animates transform and opacity only. */
  .stop-button.is-active {
    background: var(--stop-fill);
    color: var(--on-stop);
  }

  .stop-button.is-active.is-pressed {
    background: var(--stop-fill-pressed);
  }

  /* While the stop request runs the label says so; the button stays
     usable and readable. */
  .stop-button[aria-busy="true"] .icon {
    opacity: 0.6;
  }

  /* ------------------------------------------------------------------
     Navigation (rail on wide screens, tab bar on phones)
     ------------------------------------------------------------------ */
  .nav-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    width: 100%;
    min-height: 68px;
    border-radius: var(--radius-xl);
    color: var(--text-tertiary);
    font-size: var(--type-nav-size);
    line-height: 16px;
    letter-spacing: var(--type-caption-tracking);
    font-weight: var(--weight-medium);
    white-space: nowrap;
    text-align: center;
  }

  .nav-item[aria-current="page"] {
    background: var(--surface-1);
    color: var(--text-primary);
    box-shadow: var(--card-edge);
  }

  .nav-item[aria-current="page"] .icon {
    color: var(--accent);
  }

  /* The icon carries the count badge at its top right (Settings: open
     notices, F1). */
  .nav-item-icon {
    position: relative;
    display: grid;
  }

  /* A count: 13 px at least, in the colour of the weightiest notice. */
  .nav-badge {
    display: inline-grid;
    place-items: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: var(--radius-full);
    background: var(--warning);
    color: var(--on-warning);
    font-size: var(--type-caption-size);
    line-height: 1;
    letter-spacing: 0;
    font-weight: var(--weight-semibold);
    font-variant-numeric: tabular-nums;
  }

  .nav-badge[data-tone="critical"] {
    background: var(--danger);
    color: var(--on-danger);
  }

  /* Only info notices open: calm (review 2026-10-06). */
  .nav-badge[data-tone="info"] {
    background: var(--info);
    color: var(--on-info);
  }

  .nav-badge[hidden] {
    display: none;
  }

  .nav-item-icon > .nav-badge {
    position: absolute;
    top: -7px;
    left: 14px;
  }

  /* ------------------------------------------------------------------
     Sheet
     ------------------------------------------------------------------ */
  .sheet-scrim {
    position: fixed;
    inset: var(--header-height) 0 0 0;
    z-index: var(--z-scrim);
    background: var(--scrim);
    opacity: calc(var(--sheet-progress, 0) * var(--scrim-opacity));
    pointer-events: none;
  }

  .sheet-scrim.is-active {
    pointer-events: auto;
  }

  .sheet {
    position: fixed;
    z-index: var(--z-sheet);
    left: 0;
    right: 0;
    bottom: 0;
    width: min(var(--sheet-max-width), 100%);
    max-height: calc(100dvh / var(--display-zoom) - var(--header-height) - var(--space-2));
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
    background: var(--surface-1);
    box-shadow: var(--shadow-sheet);
    transform: translate3d(0, 110%, 0);
    will-change: transform;
  }

  .sheet:focus {
    outline: none;
  }

  /* A pushed sheet (an explanation from the zone sheet) and the sheet it
     returns to swap their content in the same panel: the new content
     fades in, opacity only, while the height springs (sheet.js). */
  @keyframes sheet-swap {
    from {
      opacity: 0;
    }
  }

  .sheet.is-swapping .sheet-lead,
  .sheet.is-swapping .sheet-head-text,
  .sheet.is-swapping .sheet-toolbar,
  .sheet.is-swapping > .sheet-body,
  .sheet.is-swapping > .sheet-foot {
    animation: sheet-swap var(--duration-base) ease-out;
  }

  /* The surface continues below the screen edge, so pulling the sheet up
     (rubber band) never opens a gap. */
  .sheet::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    height: 480px;
    background: var(--surface-1);
  }

  /* No text selection in the grab area: a selection would start a native
     drag on the next pull (pointercancel). */
  .sheet-grab {
    flex: none;
    padding: var(--space-2) var(--space-4) var(--space-1) var(--space-6);
    touch-action: none;
    cursor: grab;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
  }

  .sheet-grab:active {
    cursor: grabbing;
  }

  .sheet-grabber {
    width: 40px;
    height: 5px;
    margin: 0 auto var(--space-1);
    border-radius: 3px;
    background: var(--grabber);
  }

  .sheet-head {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: max(52px, var(--touch-min));
  }

  .sheet-head-text {
    flex: 1;
    min-width: 0;
  }

  /* A zone's kind (T6, ui/icons/zone-kinds.js): the plant in a calm rounded
     square, how it is watered as a small mark at its lower right edge, a
     coin of the surface's colour with a fine ring. No motion. */
  .zone-kind {
    position: relative;
    display: inline-flex;
    flex: none;
    width: 32px;
    height: 32px;
  }

  .zone-kind[hidden] {
    display: none;
  }

  /* The plant sits a little up and left of the middle: the mark at the corner leaves it whole. */
  .zone-kind-square {
    display: grid;
    place-items: center;
    width: 100%;
    height: 100%;
    padding: 0 3px 3px 0;
    border-radius: var(--radius-md);
    background: var(--kind-fill);
    color: var(--kind-ink);
  }

  /* The coin sits on the square's corner and stays within 5 px of it: the tile's second line
     keeps its distance (pages.css .zone-tile-top row gap). */
  .zone-kind-mark {
    position: absolute;
    right: -5px;
    bottom: -5px;
    display: grid;
    place-items: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--surface-1);
    box-shadow: 0 0 0 1px var(--kind-mark-ring);
    color: var(--kind-mark-ink);
  }

  .zone-kind.is-sheet {
    width: 44px;
    height: 44px;
  }

  /* A symbol before the title (the zone sheet's kind). */
  .sheet-lead {
    flex: none;
    display: flex;
  }

  .sheet-lead[hidden] {
    display: none;
  }

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

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

  /* A sheet with views: its tabs sit in the head (under the title on narrow screens). */
  .sheet-toolbar {
    flex: none;
    min-width: 0;
  }

  .sheet-body {
    flex: 1 1 auto;
    min-height: 0;
    padding: var(--space-2) var(--space-6) 0;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* A control scrolled into view by keyboard focus keeps its focus ring clear of the edge. */
    scroll-padding-block: var(--space-2);
  }

  /* Content that fits: the body drags the sheet (sheet.js markStaticBody). */
  .sheet-body.is-static {
    touch-action: none;
  }

  /* Touch screens scroll by touch; no classic scrollbar on the kiosk. */
  :root[data-device="kiosk"] .sheet-body,
  :root[data-device="kiosk"] .runs-list {
    scrollbar-width: none;
  }

  .sheet-error {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin: var(--space-2) var(--space-6) 0;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    background: var(--surface-2);
    color: var(--text-primary);
    text-wrap: pretty;
  }

  .sheet-error .icon {
    color: var(--danger);
  }

  .sheet-foot {
    flex: none;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-6) calc(var(--space-5) + var(--safe-bottom));
  }

  /* Buttons share the row but never truncate: if the labels do not fit,
     the row wraps. */
  .sheet-foot > .button {
    flex: 1 1 0;
    min-width: max-content;
  }

  .sheet-foot > .sheet-action-primary {
    flex-grow: 1.6;
  }

  /* The field being edited stays above the on-screen keyboard. */
  .sheet-body.has-keyboard {
    padding-bottom: var(--keyboard-overlap, 0);
    scroll-padding-bottom: var(--keyboard-overlap, 0);
  }

  /* ------------------------------------------------------------------
     Form stepper and text field (components/stepper.js, sheets/zone/fields.js)
     ------------------------------------------------------------------ */
  .form-stepper {
    display: grid;
    grid-template-columns: var(--control-height) 128px var(--control-height);
    align-items: center;
    gap: var(--space-2);
  }

  .stepper-field {
    display: grid;
    place-items: center;
    min-width: 0;
    min-height: var(--control-height);
    padding: 0 var(--space-3);
    border-radius: var(--radius-control);
    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;
  }

  .stepper-field-text {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* A value with free entry (a spinbutton, not a button: stepper.js) looks
     and behaves like the tappable field it is. */
  .stepper-field.is-editable {
    background: var(--surface-2);
    cursor: pointer;
    touch-action: manipulation;
    user-select: none;
    -webkit-user-select: none;
  }

  .text-field {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: var(--control-height);
    padding: 0 var(--space-4);
    border: 0;
    border-radius: var(--radius-control);
    background: var(--surface-2);
    color: var(--text-primary);
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-variant-numeric: normal;
    text-align: left;
  }

  .text-field:focus {
    outline: 2px solid var(--focus-ring);
    outline-offset: 0;
  }

  .text-field-value {
    min-width: 0;
    overflow: hidden;
    white-space: pre;
    text-overflow: ellipsis;
  }

  /* Invalid and being edited: a ring, the inline error says why. */
  .text-field[aria-invalid="true"],
  .form-stepper.is-invalid .stepper-field {
    box-shadow: inset 0 0 0 2px var(--danger);
  }

  .is-editing .text-field,
  .is-editing .stepper-field {
    box-shadow: inset 0 0 0 2px var(--accent);
  }

  /* ------------------------------------------------------------------
     Info button (components/info-button.js): a 20 px circle-i with a
     48 × 48 px hit area; the negative margin keeps the hit area out of
     the layout, so a heading keeps its height. At the display size's
     smaller step 48 px are 43 on the screen: the box grows to the touch
     target (--touch-min) and its margins by as much (--info-grow), so it
     still takes 20 px in the layout and no heading moves.
     ------------------------------------------------------------------ */
  .info-button {
    --info-target: max(48px, var(--touch-min));
    /* How much more than 48 px on each side: 0 at the standard and the larger step. */
    --info-grow: calc((var(--info-target) - 48px) / 2);
    position: relative;
    display: inline-grid;
    place-items: center;
    flex: none;
    width: var(--info-target);
    height: var(--info-target);
    margin: calc(-14px - var(--info-grow));
    padding: 0;
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--text-secondary);
    vertical-align: middle;
  }

  /* Pressed: a quiet disc behind the icon (opacity only). */
  .info-button::before {
    content: "";
    position: absolute;
    inset: calc(8px + var(--info-grow));
    border-radius: var(--radius-full);
    background: var(--surface-3);
    opacity: 0;
    transition: opacity var(--duration-fast) ease;
  }

  .info-button.is-pressed::before {
    opacity: 1;
  }

  .info-button > .icon {
    position: relative;
  }

  .info-button:focus-visible {
    outline: none;
  }

  .info-button:focus-visible::before {
    opacity: 1;
    box-shadow: inset 0 0 0 2px var(--focus-ring);
  }

  /* A heading and its info button. */
  .card-title-group,
  .section-key-row,
  .balance-title-group,
  .zones-title-group {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
  }

  .form-label > .info-button {
    align-self: center;
  }

  /* An icon slot of a meaning (icons/semantic.js meaningIconSlot). */
  .meaning-icon {
    display: inline-grid;
    flex: none;
  }

  /* ------------------------------------------------------------------
     Soil water bar (components/soil-bar.js): zero in the middle
     ------------------------------------------------------------------ */
  .soil-bar {
    position: relative;
    display: block;
    height: 6px;
    border-radius: 3px;
    background: var(--surface-3);
  }

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

  .soil-bar.is-wide {
    height: 8px;
  }

  .soil-bar-fill {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 50%;
    border-radius: 3px;
    background: var(--accent);
    transform-origin: 0 50%;
  }

  .soil-bar[data-side="deficit"] .soil-bar-fill {
    background: var(--text-secondary);
  }

  .soil-bar[data-band="dry"] .soil-bar-fill {
    background: var(--warning);
  }

  /* Muted: the value decides no watering run (establishment watering,
     manual only, off). Same position, but only outlined: no normal state
     is hollow, so muted reads apart from a grey deficit too. */
  .soil-bar[data-muted] .soil-bar-fill {
    background: transparent;
    box-shadow: inset 0 0 0 1.5px var(--text-tertiary);
  }

  /* ------------------------------------------------------------------
     On-screen keyboard (components/keyboard.js)
     ------------------------------------------------------------------ */
  .keyboard {
    --key-gap: 6px;
    position: fixed;
    z-index: var(--z-keyboard);
    left: 0;
    right: 0;
    bottom: 0;
    max-width: 1024px;
    margin: 0 auto;
    /* env() as it is: zoomed back to 1 (below), the keyboard's px are the device's. */
    padding: var(--space-2) var(--space-3) calc(var(--space-3) + env(safe-area-inset-bottom));
    border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
    background: var(--keyboard-bg);
    box-shadow: var(--shadow-sheet);
    transform: translate3d(0, 100%, 0);
    will-change: transform;
    user-select: none;
    -webkit-user-select: none;
    touch-action: manipulation;
    /* The keyboard keeps its size at every display size, as a device's own keyboard does (Stephan 2026-10-11,
       decided): zoomed back to 1 by the page's zoom, and a zoom scope (keyboard.js, tokens.css), so its keys,
       buttons and captions are worked out at 1 too (at "Kleiner" they inherited the page's 54 px targets). At the
       larger step on the kiosk a keyboard 10 % taller left 27 px of a sheet above it, and the edited field under
       it (measured 2026-10-11); its keys are a finger's size at every step anyway. keyboard.js measures it in its
       own zoom. */
    zoom: calc(1 / var(--page-zoom));
  }

  .keyboard-bar {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-3);
    min-height: 56px;
    padding-bottom: var(--space-2);
  }

  .keyboard-cancel,
  .keyboard-done {
    min-width: 112px;
  }

  .keyboard-cancel {
    background: var(--key-special);
  }

  .keyboard-entry {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }

  /* The range stays on one line (it wrapped between the buttons on phones). */
  .keyboard-hint {
    white-space: nowrap;
  }

  .keyboard-label,
  .keyboard-hint {
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    color: var(--text-secondary);
  }

  /* Right-to-left box: a long value shows its end (where the caret is). */
  .keyboard-value {
    display: block;
    min-height: var(--type-title3-line);
    overflow: hidden;
    direction: rtl;
    text-align: left;
    white-space: pre;
    font-size: var(--type-title3-size);
    line-height: var(--type-title3-line);
    letter-spacing: var(--type-title3-tracking);
    font-weight: var(--weight-medium);
    font-variant-numeric: normal;
  }

  .keyboard-value-text {
    direction: ltr;
    unicode-bidi: isolate;
  }

  .keyboard-value-text::after {
    content: "";
    display: inline-block;
    width: 2px;
    height: 1.1em;
    margin-left: 1px;
    vertical-align: -0.15em;
    background: var(--accent);
  }

  /* A selected value (numbers open that way): the first key replaces it. */
  .keyboard-value-text.is-selected {
    border-radius: var(--radius-xs, 4px);
    background: var(--selection);
  }

  .keyboard-value-text.is-selected::after {
    display: none;
  }

  .keyboard-keys {
    display: flex;
    flex-direction: column;
    gap: var(--key-gap);
  }

  .keyboard-row {
    display: flex;
    justify-content: center;
    gap: var(--key-gap);
  }

  /* Width in key units: twelve units fill a row. */
  .keyboard-key {
    display: grid;
    place-items: center;
    flex: 0 1 calc(var(--key-width, 1) * (100% - 11 * var(--key-gap)) / 12 + (var(--key-width, 1) - 1) * var(--key-gap));
    min-width: 0;
    min-height: max(52px, var(--touch-min));
    border-radius: var(--radius-md);
    background: var(--key-fill);
    box-shadow: var(--shadow-thumb);
    color: var(--text-primary);
    font-size: var(--type-key-size);
    line-height: var(--type-key-line);
    font-weight: var(--weight-regular);
  }

  .keyboard-key.is-special {
    background: var(--key-special);
  }

  .keyboard-key[aria-pressed="true"] {
    background: var(--accent);
    color: var(--on-accent);
  }

  .keyboard-key.is-pressed {
    background: var(--key-special);
  }

  .keyboard[data-layout="numeric"] .keyboard-keys {
    max-width: 440px;
    margin: 0 auto;
  }

  .keyboard[data-layout="numeric"] .keyboard-key {
    flex: 1 1 0;
    min-height: 56px;
    font-size: var(--type-title2-size);
  }

  /* ------------------------------------------------------------------
     Toast
     ------------------------------------------------------------------ */
  .toast {
    position: fixed;
    z-index: var(--z-toast);
    left: var(--rail-width);
    right: 0;
    bottom: var(--space-5);
    width: max-content;
    max-width: calc(100% - var(--rail-width) - var(--space-8));
    min-height: max(52px, var(--touch-min));
    margin: 0 auto;
    padding: var(--space-3) var(--space-5) var(--space-3) var(--space-4);
    display: flex;
    align-items: center;
    gap: var(--space-2);
    border-radius: var(--radius-full);
    background: var(--surface-elevated);
    color: var(--text-primary);
    box-shadow: var(--shadow-2);
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    letter-spacing: var(--type-headline-tracking);
    font-weight: var(--weight-medium);
    opacity: 0;
    transform: translate3d(0, 72px, 0);
    pointer-events: none;
    will-change: transform, opacity;
  }

  /* Above an open sheet's actions, so a sheet that stays open keeps them in
     reach; in their band when none can be tapped (toast.js placement()), so
     it does not cover the content it reports on. */
  :root[data-sheet-open] .toast {
    bottom: 104px;
  }

  :root[data-sheet-open] .toast[data-place="foot"] {
    bottom: calc(var(--space-5) + var(--safe-bottom));
  }

  :root[data-keyboard-open] .toast {
    bottom: calc(var(--keyboard-height, 360px) + var(--space-3));
  }

  .toast[data-state="visible"] {
    pointer-events: auto;
    cursor: pointer;
  }

  .toast[data-state="hidden"] {
    visibility: hidden;
  }

  .toast-icon {
    display: grid;
    flex: none;
    color: var(--accent);
  }

  /* Tinted by kind (Stephan 2026-10-06, F1): done and info calm, a
     warning amber, an error red; the text stays primary (4.5:1 on every
     tint, tests/theme-tokens.test.js). */
  .toast[data-tone="info"],
  .toast[data-tone="success"] {
    background: var(--toast-info);
  }

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

  .toast[data-tone="error"] {
    background: var(--toast-error);
  }

  .toast[data-tone="success"] .toast-icon {
    color: var(--success);
  }

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

  .toast[data-tone="error"] .toast-icon {
    color: var(--danger);
  }

  .toast-text {
    min-width: 0;
    text-wrap: pretty;
  }

  /* A toast that leads to what it names (toast.js onOpen, plan of
     2026-10-10, rule 9): a chevron at its end, a calm press state (the
     spring owns its transform, so the press dims instead of scaling). */
  .toast-chevron {
    display: grid;
    flex: none;
    margin-right: calc(-1 * var(--space-2));
    color: var(--text-secondary);
  }

  .toast.is-pressed > * {
    opacity: 0.6;
  }

  /* ------------------------------------------------------------------
     Link rows (components/link-row.js; Stephan 2026-10-10, everything
     sensibly tappable): a whole row leads somewhere, with a chevron and
     the press state; no underline, no accent colour on a row.
     ------------------------------------------------------------------ */
  .link-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    min-height: var(--touch-min);
    padding: var(--space-2) 0;
    border-radius: var(--radius-row);
    text-align: left;
    color: inherit;
  }

  .link-row-icon {
    flex: none;
    color: var(--text-secondary);
  }

  /* Short of room, the label gives way first, down to its longest word and not below it (min-width: auto); then
     the value wraps. Squeezed under its word's width, „Zustand“ ran under the value beside it („Verbunden · letzter
     Wert …“; review 2026-10-11: a 390 px phone at the larger display size, a 360 px one at standard). */
  .link-row-text {
    display: flex;
    flex: 1 1000 auto;
    flex-direction: column;
    min-width: auto;
  }

  .link-row-label {
    font-weight: var(--weight-medium);
    text-wrap: pretty;
  }

  .link-row[data-tone="warning"] .link-row-label,
  .link-row[data-tone="warning"] .link-row-icon {
    color: var(--warning);
  }

  .link-row[data-tone="critical"] .link-row-label,
  .link-row[data-tone="critical"] .link-row-icon {
    color: var(--danger);
  }

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

  .link-row-value {
    flex: 0 1 auto;
    max-width: 55%;
    color: var(--text-secondary);
    text-align: right;
  }

  .link-row-chevron {
    flex: none;
    color: var(--text-tertiary);
  }

  /* Disabled it leads nowhere just now: no chevron (plan rule 11). */
  .link-row:disabled {
    cursor: default;
  }

  .link-row:disabled .link-row-label {
    color: var(--text-tertiary);
  }

  .link-row:disabled .link-row-chevron {
    display: none;
  }

  /* A link that waits for the History before it opens (ui/navigate.js):
     dimmed once the wait is noticeable, one opacity transition, back at
     once when it opens. */
  [data-link-wait] {
    opacity: 0.55;
    transition: opacity var(--duration-base) var(--ease-standard) 150ms, transform var(--duration-press) ease-out;
  }

  /* In a line of a card: one caption line in the layout, a touch target
     that reaches above and below it (the "Noch N Hinweise" pattern, F1),
     so a kiosk card keeps its height. A link in running text: accent. */
  .link-row.is-inline {
    position: relative;
    z-index: 1;
    display: inline-flex;
    gap: 2px;
    width: auto;
    margin: calc((var(--type-caption-line) - var(--touch-min)) / 2) 0
      calc((var(--type-caption-line) - var(--touch-min)) / 2) calc(-1 * var(--space-2));
    padding: 0 var(--space-2);
    color: var(--accent);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
  }

  .link-row.is-inline .link-row-chevron {
    color: inherit;
  }

  /* Rows of a group on one surface, hairlines between them (as the facts of a sheet). */
  .link-list {
    padding: 0 var(--space-4);
    border-radius: var(--radius-tile);
    background: var(--surface-2);
  }

  /* Between the rows shown: a hidden row (one that shows only sometimes) draws no line above the next. */
  .link-list > :not([hidden]) ~ :not([hidden]) {
    border-top: 1px solid var(--hairline);
  }

  .link-list > .link-row {
    border-radius: 0;
  }

  /* A region that leads somewhere (components/tap-region.js): one button
     over a few lines of a card, a little beyond their box; pressed, a
     quiet tint over them (opacity only). An info button inside stays
     above it. Isolated, so the button's layer stays inside the region. */
  .tap-region {
    position: relative;
    isolation: isolate;
  }

  .tap-region-hit {
    position: absolute;
    inset: calc(-1 * var(--space-1)) calc(-1 * var(--space-2));
    z-index: 1;
    border-radius: var(--radius-md);
  }

  .tap-region-hit::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: color-mix(in srgb, var(--text-primary) 7%, transparent);
    opacity: 0;
    transition: opacity var(--duration-fast) ease;
  }

  :where(:root:not([data-drag-scroll])) .tap-region-hit:active::before,
  .tap-region-hit.is-pressed::before {
    opacity: 1;
  }

  .tap-region .info-button {
    z-index: 2;
  }

  /* ------------------------------------------------------------------
     Chart read-out (components/chart-readout.js; plan of 2026-10-10,
     as Apple Health): a finger reads a chart. The rule, band, dot and
     the read-out move by transform only; the read-out stays inside the
     chart by clamping its own translation (no measuring per frame).
     ------------------------------------------------------------------ */
  .readout-surface {
    position: relative;
    touch-action: pan-y;
    user-select: none;
    -webkit-user-select: none;
    cursor: crosshair;
  }

  .readout-surface:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
  }

  .readout-layer {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
  }

  .readout-rule {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 1px;
    margin-left: -0.5px;
    background: var(--text-secondary);
  }

  .readout-band {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    border-radius: var(--radius-xs);
    background: color-mix(in srgb, var(--accent) 16%, transparent);
  }

  .readout-dot {
    position: absolute;
    top: -5px;
    left: -5px;
    width: 10px;
    height: 10px;
    border: 2px solid var(--surface-1);
    border-radius: 50%;
    background: var(--accent);
  }

  .readout {
    position: absolute;
    bottom: calc(100% + 6px);
    left: 0;
    /* It stands where the card's head gave way: a tap on it takes it away (chart-readout.js), not what is under it. */
    pointer-events: auto;
    cursor: default;
    display: flex;
    flex-direction: column;
    max-width: min(260px, var(--readout-w, 260px));
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-xs);
    background: var(--surface-elevated);
    box-shadow: var(--shadow-1);
    color: var(--text-primary);
    font-size: var(--type-caption-size);
    line-height: var(--type-caption-line);
    letter-spacing: var(--type-caption-tracking);
    white-space: nowrap;
    translate: clamp(0px, calc(var(--readout-x, 0px) - 50%), calc(var(--readout-w, 100%) - 100%)) 0;
  }

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

  .readout-value {
    font-size: var(--type-headline-size);
    line-height: var(--type-headline-line);
    font-weight: var(--weight-semibold);
  }

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

  .readout-lines {
    display: flex;
    flex-direction: column;
  }

  /* While a chart is read, its card's head gives way to the read-out (as
     Apple Health puts the reading in place of the summary): it fades out
     and takes no tap, back with one fade when the read-out goes. */
  .readout-yield {
    /* With a pressable's own transform (base.css), which this rule would replace. */
    transition: opacity var(--duration-fast) var(--ease-standard), transform var(--duration-press) ease-out;
  }

  [data-readout] .readout-yield {
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--duration-fast) var(--ease-standard), visibility 0s linear var(--duration-fast);
  }

  /* A place a link led to (ui/reveal.js highlightOnce()): a tint over it
     that fades out once, opacity only, on its own pseudo-element. */
  .is-revealed::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: var(--reveal-tint);
    opacity: 0;
    pointer-events: none;
  }

  @media (hover: hover) and (pointer: fine) {
    .link-row:not(:disabled):hover .link-row-chevron {
      color: var(--text-secondary);
    }

    .link-row.is-inline:not(:disabled):hover .link-row-chevron {
      color: inherit;
    }
  }

  /* ------------------------------------------------------------------
     Theme cross-fade overlay (src/ui/theme.js)
     ------------------------------------------------------------------ */
  .theme-fade {
    position: fixed;
    inset: 0;
    z-index: var(--z-theme-fade);
    opacity: 0;
    pointer-events: none;
  }

  /* ------------------------------------------------------------------
     Display sleep on the kiosk (src/ui/shell/sleep-screen.js): above
     everything, the header Stop included; it catches the waking tap.
     ------------------------------------------------------------------ */
  .sleep-screen {
    position: fixed;
    inset: 0;
    z-index: var(--z-sleep);
    background: var(--sleep-screen);
    opacity: 0;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
  }

  /* ------------------------------------------------------------------
     The red frame of an open alarm (src/ui/shell/alarm-frame.js,
     Stephan 2026-10-09): above the page, sheets and toasts, under the
     theme fade and the black screen; it never takes a tap.
     The display's corner radius is not known to CSS. A phone with a
     rounded display has safe-area insets (iPhone: 47 to 59 px at the
     notch or island, 34 px at the home indicator, 21 px in landscape;
     its corners are 47 to 55 px). Each corner is rounded on its own,
     and only where the viewport's top or bottom edge reaches the
     display's, which an inset on that side shows: in a browser tab the
     top sits under the browser's bar (inset 0, square corners) while
     the bottom reaches the home indicator once the toolbar collapses.
     The radius is that inset (the bottom one scaled to the corner) or
     the side's, whichever is larger, at most 56 px; the `* 100` term is
     0 without an inset and out of the way with one. The kiosk, a
     desktop window and a phone without insets get square corners.
     Landscape with the toolbar hidden reaches the display's top with
     an inset of 0 too: square there (the display rounds them off).
     ------------------------------------------------------------------ */
  .alarm-frame {
    /* The insets and the cap in the device's px at every display size (tokens.css --safe-*). */
    --frame-top: var(--safe-top);
    --frame-bottom: var(--safe-bottom);
    --frame-left: var(--safe-left);
    --frame-right: var(--safe-right);
    --frame-cap: calc(56px / var(--display-zoom));
    position: fixed;
    inset: 0;
    z-index: var(--z-alarm);
    border-top-left-radius: min(var(--frame-cap), calc(var(--frame-top) * 100), max(var(--frame-top), var(--frame-left)));
    border-top-right-radius: min(var(--frame-cap), calc(var(--frame-top) * 100), max(var(--frame-top), var(--frame-right)));
    border-bottom-right-radius: min(var(--frame-cap), calc(var(--frame-bottom) * 100), max(calc(var(--frame-bottom) * 1.6), var(--frame-right)));
    border-bottom-left-radius: min(var(--frame-cap), calc(var(--frame-bottom) * 100), max(calc(var(--frame-bottom) * 1.6), var(--frame-left)));
    pointer-events: none;
  }

  /* The edge and its inward glow are painted once; only the layer's opacity breathes (the compositor alone).
     The one endless animation the app allows: src/ui/motion/loops.js. */
  .alarm-frame-edge {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow:
      inset 0 0 0 var(--alarm-edge-width) var(--alarm-edge),
      inset 0 0 var(--alarm-glow-blur) var(--alarm-edge-width) var(--alarm-glow);
    will-change: opacity;
    animation: alarm-breathe 3s ease-in-out infinite;
  }

  @keyframes alarm-breathe {
    0%,
    100% {
      opacity: 0.6;
    }

    50% {
      opacity: 1;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .alarm-frame-edge {
      animation: none;
      opacity: 1;
    }
  }

  /* ------------------------------------------------------------------
     Hover: fine pointers only
     ------------------------------------------------------------------ */
  @media (hover: hover) and (pointer: fine) {
    .button[data-variant="secondary"]:not(:disabled):hover,
    .icon-button[data-variant="secondary"]:not(:disabled):hover {
      background: var(--fill-hover);
    }

    .stop-button:hover {
      background: var(--fill-hover);
    }

    .stop-button.is-active:hover,
    .button[data-variant="stop"]:not(:disabled):hover,
    .icon-button[data-variant="stop"]:not(:disabled):hover {
      background: var(--stop-fill-pressed);
    }

    .button[data-variant="primary"]:not(:disabled):hover,
    .icon-button[data-variant="primary"]:not(:disabled):hover {
      background: var(--accent-hover);
    }

    .button[data-variant="ghost"]:not(:disabled):hover,
    .icon-button[data-variant="ghost"]:not(:disabled):hover {
      background: var(--surface-2);
    }

    .button[data-variant="danger"]:not(:disabled):hover,
    .icon-button[data-variant="danger"]:not(:disabled):hover {
      background: var(--danger-hover);
    }

    .segmented-option:hover,
    .nav-item:not([aria-current="page"]):hover {
      color: var(--text-primary);
    }
  }

  /* Narrow screens: a sheet's tabs move under its title. */
  @container app (max-width: 759px) {
    .sheet-head {
      flex-wrap: wrap;
    }

    .sheet-toolbar {
      order: 3;
      flex: 1 1 100%;
      padding: var(--space-1) var(--space-2) var(--space-2) 0;
    }
  }

  /* ------------------------------------------------------------------
     Phones
     ------------------------------------------------------------------ */
  @container app (max-width: 699px) {
    .header-wordmark {
      justify-self: start;
    }

    .header-status {
      display: none;
    }

    .header-wordmark,
    .status-chip {
      grid-column: 1;
      grid-row: 1;
    }

    .header-end {
      grid-column: 2;
      grid-row: 1;
      gap: var(--space-3);
      padding: 0 var(--space-2);
    }

    /* On the pages without the sentence a short word stands where the
       wordmark is, while something needs attention or runs: next to the
       connection's and the clock's 44 px targets and Stop there is no room
       for both at 390 px. */
    .status-chip {
      display: inline-flex;
      justify-self: start;
      align-items: center;
      gap: var(--space-2);
      max-width: 100%;
      min-height: var(--touch-min);
      margin-left: calc(-1 * var(--space-2));
      padding: 0 var(--space-2);
      border-radius: var(--radius-sm);
      font-weight: var(--weight-semibold);
    }

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

    .app-header:has(.status-chip:not([hidden])) .header-wordmark {
      visibility: hidden;
    }

    /* The dot alone in a 44 px target; the clock's target right beside it. */
    .connection {
      margin: 0 calc(-1 * var(--space-3)) 0 calc(-1 * var(--space-2));
      padding: 0;
    }

    .header-clock {
      margin: 0;
      padding: 0 var(--space-1);
    }

    /* Dot only; the label stays for screen readers (visually hidden,
       never clipped on screen: no box, no paint). */
    .connection-label {
      position: absolute;
      width: 1px;
      height: 1px;
      margin: -1px;
      padding: 0;
      overflow: hidden;
      clip: rect(0 0 0 0);
      clip-path: inset(50%);
      border: 0;
      white-space: nowrap;
    }

    .header-clock {
      font-size: var(--type-headline-size);
      line-height: var(--type-headline-line);
      letter-spacing: var(--type-headline-tracking);
    }

    .nav-item {
      flex: 1 1 0;
      min-width: max-content;
      min-height: 56px;
      gap: 2px;
      border-radius: var(--radius-lg);
    }

    .nav-item[aria-current="page"] {
      background: transparent;
      box-shadow: none;
      color: var(--accent);
    }

    .sheet {
      width: 100%;
      border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
    }

    .sheet-grab {
      padding-left: var(--space-4);
      padding-right: var(--space-2);
    }

    .sheet-body,
    .sheet-foot {
      padding-left: var(--space-4);
      padding-right: var(--space-4);
    }

    .sheet-error {
      margin-left: var(--space-4);
      margin-right: var(--space-4);
    }

    .toast {
      left: 0;
      max-width: calc(100% - var(--space-8));
      bottom: calc(var(--tabbar-height) + var(--safe-bottom) + var(--space-3));
    }

    /* Room for the label and the range between Cancel and Done. */
    .keyboard-cancel,
    .keyboard-done {
      min-width: 0;
    }
  }

  /* A narrow phone (a 390 px phone at the display size's larger step is 355 px): Stop's words take less air, so
     the header's short word keeps its room („Ein Hinweis“ was cut to „Ein Hin…“ there, measured 2026-10-11).
     The gaps stay: closer, the connection's target ran into the clock's. */
  @container app (max-width: 379px) {
    .stop-button {
      padding: 0 var(--space-3);
    }
  }

  /* ------------------------------------------------------------------
     Signal bars (components/signal-bars.js): four steps, the filled
     ones in the secondary text colour
     ------------------------------------------------------------------ */
  .signal-bars {
    display: inline-flex;
    flex: none;
    align-items: flex-end;
    gap: 2px;
    height: 14px;
  }

  .signal-bars > i {
    width: 3px;
    border-radius: 1px;
    background: var(--surface-3);
  }

  .signal-bars > i:nth-child(1) { height: 30%; }
  .signal-bars > i:nth-child(2) { height: 55%; }
  .signal-bars > i:nth-child(3) { height: 80%; }
  .signal-bars > i:nth-child(4) { height: 100%; }

  .signal-bars[data-bars="1"] > i:nth-child(-n + 1),
  .signal-bars[data-bars="2"] > i:nth-child(-n + 2),
  .signal-bars[data-bars="3"] > i:nth-child(-n + 3),
  .signal-bars[data-bars="4"] > i {
    background: var(--text-secondary);
  }

  .signal-bars[data-bars="0"] {
    display: none;
  }

  /* ------------------------------------------------------------------
     More contrast: visible edges on every control and surface
     ------------------------------------------------------------------ */
  @media (prefers-contrast: more) {
    .card,
    .button[data-variant="secondary"],
    .button[data-variant="ghost"],
    .icon-button[data-variant="secondary"],
    .icon-button[data-variant="ghost"],
    .stop-button,
    .segmented,
    .nav-item[aria-current="page"],
    .switch-track,
    .sheet-error {
      box-shadow: inset 0 0 0 1px var(--border-strong);
    }

    .toast {
      box-shadow: inset 0 0 0 1px var(--border-strong), var(--shadow-2);
    }

    .sheet {
      box-shadow: inset 0 0 0 1px var(--border-strong), var(--shadow-sheet);
    }
  }
}
