/* =====================================================================
   ProjectPattex — Stylesheet
   Mobile first. Farben als Custom Properties, Dark Mode automatisch.
   ===================================================================== */

/* --- Reset ---------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; }
img, svg { display: block; max-width: 100%; }
input, button, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
table { border-collapse: collapse; width: 100%; }
/* Kein Unterstrich, nirgends (FH, 2026-08-24). Links tragen die
   Akzentfarbe; beim Überfahren wird sie kräftiger. Ausnahmen gibt es
   nicht - wer eine braucht, setzt sie an der Stelle selbst. */
a { color: var(--accent); text-decoration: none; }
/* Nur Links ohne eigene Klasse - Knopf, Navigation und Namenlink
   bringen ihr eigenes Verhalten mit und sollen es behalten. */
a:not([class]):hover { color: var(--accent-strong); }
/* --- Farbmarken ------------------------------------------------------
   Zwei Achsen, nicht eine (FH, 2026-08-27):

     Schema     die Farbwelt - vier Stueck, gehoert zur Person und steht
                deshalb in der Datenbank; der Server schreibt sie als
                `data-schema` ans <html>.
     Hell/Dunkel  gehoert zum Geraet - abends am Telefon dunkel, tagsueber
                am Rechner hell. Bleibt im localStorage, gesetzt von
                assets/js/theme.js vor dem ersten Zeichnen.

   Deshalb steht hier je Schema **ein** Block mit zwei Saetzen Rohwerten
   (`--h-*` hell, `--d-*` dunkel), und darunter **einmal** die Zuweisung
   fuer die drei Zustaende. Ohne diese Trennung waeren es vier Schemen
   mal drei Zustaende = zwoelf Paletten, von denen man beim Aendern
   immer eine vergisst.

   Ein fuenftes Schema ist ein Block, kein Umbau.
   --------------------------------------------------------------------- */
:root {
  /* Hoehe aller Bedienelemente einer Zeile - Textfeld, Auswahlfeld und
     Knopf holen sie sich hier, damit sie nebeneinander buendig sind. */
  --feldhoehe:       2.875rem;
  --feldhoehe-klein: 2.125rem;

  --radius: 12px;
  --nav-h: 62px;
  /* Die wirkliche Hoehe der Leiste, nicht eine gerundete: `body` haelt
     darunter Platz frei und der klebende Tabellenkopf setzt sich
     darunter. Stand hier 54, waehrend die Leiste 57 mass, verschwanden
     die obersten drei Pixel jeder Seite hinter ihr. bin/test_hoehen.js
     misst nach und faellt um, wenn beides auseinanderlaeuft. */
  --topbar-h: 58px;

  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-num: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;

}

/* --- Sand: das warme Grau des Logos, die Vorgabe --------------------
   Rot bleibt schlechten Werten vorbehalten - so faellt eine
   Hypertonie-Einordnung wirklich auf, statt in einer ohnehin roten
   Oberflaeche unterzugehen. `--accent-ink` ist die Schrift, die auf
   einer Akzentflaeche liegt.

   Die Schemabloecke stehen bewusst auf `[data-schema=…]` und nicht auf
   `:root[data-schema=…]`: das Kaertchen in der Profileinstellung traegt
   sein Schema selbst und soll dieselben Marken bekommen wie die Seite.
   Sand braucht dafuer einen eigenen Block - ohne ihn erbte ein
   Sand-Kaertchen auf einer Tinte-Seite deren Rohwerte.
   --------------------------------------------------------------------- */
:root,
[data-schema="sand"] {
  --h-accent: #605a53;  --h-accent-strong: #4a453f;
  --h-accent-soft: #ebe5db;  --h-accent-ink: #ffffff;
  --h-bg: #f4f1ec;  --h-surface: #ffffff;  --h-surface-2: #ebe6de;
  --h-border: #dcd5c9;  --h-text: #26231e;  --h-muted: #6d665c;

  --d-accent: #b8ab9c;  --d-accent-strong: #d0c4b6;
  --d-accent-soft: #2c281f;  --d-accent-ink: #232019;
  --d-bg: #1a1815;  --d-surface: #232019;  --d-surface-2: #2c281f;
  --d-border: #3c362d;  --d-text: #ece7de;  --d-muted: #968e84;
}

/* --- Schiefer: kuehles Blaugrau ------------------------------------- */
[data-schema="schiefer"] {
  --h-accent: #55606b;  --h-accent-strong: #3f4954;
  --h-accent-soft: #e4e9ee;  --h-accent-ink: #ffffff;
  --h-bg: #f1f3f5;  --h-surface: #ffffff;  --h-surface-2: #e6e9ec;
  --h-border: #d5dade;  --h-text: #1f242a;  --h-muted: #5f6a75;

  --d-accent: #a8b4c0;  --d-accent-strong: #c3cdd7;
  --d-accent-soft: #252b31;  --d-accent-ink: #1c2126;
  --d-bg: #14181c;  --d-surface: #1c2126;  --d-surface-2: #252b31;
  --d-border: #333b43;  --d-text: #e2e7ec;  --d-muted: #8b95a0;
}

/* --- Salbei: gedaempftes Gruen -------------------------------------- */
[data-schema="salbei"] {
  --h-accent: #4f6b58;  --h-accent-strong: #3b5142;
  --h-accent-soft: #e2ebe4;  --h-accent-ink: #ffffff;
  --h-bg: #f1f4f0;  --h-surface: #ffffff;  --h-surface-2: #e4eae2;
  --h-border: #d3dbd0;  --h-text: #1f2620;  --h-muted: #5f6b60;

  --d-accent: #9fbca8;  --d-accent-strong: #bcd2c2;
  --d-accent-soft: #252c26;  --d-accent-ink: #1c221d;
  --d-bg: #141814;  --d-surface: #1c221d;  --d-surface-2: #252c26;
  --d-border: #333c35;  --d-text: #e3e9e3;  --d-muted: #8a958b;
}

/* --- Tinte: tiefes Blau, auch im Grund -------------------------------
   FH, 2026-08-28: "das 2. und 4. Farbschema ist fast gleich". Es war
   fast dasselbe Schema. Gemessen lagen Grund, Flaeche, Rahmen und Text
   zwischen zwei und fuenf von 255 auseinander - unter der Schwelle, ab
   der ein Auge etwas bemerkt. Verschieden war allein der Akzent, und
   der sitzt nur auf Knoepfen und Verweisen; auf einer Seite mit wenigen
   Knoepfen war Tinte von Schiefer nicht zu unterscheiden.

   Tinte traegt sein Blau jetzt bis in den Grund: der Hellgrund geht
   deutlich ins Blaue, der Dunkelgrund ebenso. Schiefer bleibt das
   neutrale Grau, das es immer war - der Unterschied liegt damit auf
   der ganzen Flaeche und nicht mehr auf drei Knoepfen. */
[data-schema="tinte"] {
  --h-accent: #35508a;  --h-accent-strong: #26386d;
  --h-accent-soft: #dfe6f4;  --h-accent-ink: #ffffff;
  --h-bg: #eceff9;  --h-surface: #ffffff;  --h-surface-2: #dde3f2;
  --h-border: #c6cfe6;  --h-text: #1b1f2e;  --h-muted: #5b6480;

  --d-accent: #8fa6de;  --d-accent-strong: #aebfec;
  --d-accent-soft: #232941;  --d-accent-ink: #171b2b;
  --d-bg: #10131f;  --d-surface: #171b2b;  --d-surface-2: #232941;
  --d-border: #333c5c;  --d-text: #e2e6f3;  --d-muted: #8992ad;
}

/* --- Was in jedem Schema gleich bleibt -------------------------------
   Diese Farben **tragen Bedeutung** und werden deshalb vom Schema nicht
   angefasst: Gruen heisst in jeder Farbwelt dasselbe. Wuerde ein Schema
   sie mitdrehen, hiesse derselbe Ton je nach Einstellung etwas anderes.

   Das Gepruefte-Siegel hat aus demselben Grund ein eigenes Blau und
   nicht --good: der Haken sagt "nachgesehen", nicht "guter Wert".
   Bei den Schlagworten fehlt Rot mit Absicht - das bleibt schlechten
   Werten -, und Gruen und Bernstein sind bewusst andere Toene als
   --good und --warn, damit ein Schlagwort nicht wie eine Bewertung
   aussieht.
   --------------------------------------------------------------------- */
:root {
  --good: #1f7a44;
  --warn: #a8690a;
  --bad:  #a71930;
  --siegel: #2f6fb0;
  /* "Zu niedrig" - bisher nur die Untertemperatur (FH, 2026-09-03:
     "Untertemperatur sollte aber blau sein"). Ein eigener Ton neben
     --siegel: das Siegel ist ein Abzeichen, das hier ist ein Messwert,
     und zwei Bedeutungen sollen nicht an derselben Zahl haengen. */
  --kalt: #2b6ca3;

  /* Reihenfarben fuer mehrreihige Diagramme */
  --chart-2: #3c6e8f;
  --chart-3: #96703f;

  --tag-grau:   #6d665c;
  --tag-blau:   #3c6e8f;
  --tag-ocker:  #96703f;
  --tag-salbei: #4f7a63;
  --tag-rost:    #9a5a44;
  --tag-pflaume: #7a5586;
  --tag-petrol:  #2c716b;
  --tag-oliv:    #77762e;

  /* Blutdruck: drei deutlich verschiedene Farbfamilien - Blau, Bernstein,
     warmes Grau. Alle drei Linien durchgezogen, damit "gestrichelt"
     eindeutig fuer eine Messpause steht. */
  --bp-sys: #1f5f85;
  --bp-dia: #a5670f;
  --bp-pul: #6d665c;

  --shadow: 0 1px 2px rgb(0 0 0 / .06), 0 4px 14px rgb(0 0 0 / .05);
}

/* --- Was der Browser selbst zeichnet -----------------------------------
   Datumsauswahl, Bildlaufleisten, Auswahlfelder: die malt der Browser,
   und er richtet sich dabei nach `color-scheme`. Ohne diese vier Zeilen
   steht dort `normal`, und "normal" heisst: nimm das Schema des
   Systems.

   Das geht schief, sobald beides auseinanderlaeuft. Wer im Profil
   **dunkel** waehlt, waehrend das Betriebssystem hell steht, bekommt
   ein dunkles Feld mit einem hell gedachten Kalendersymbol darin -
   dunkelgrau auf dunkelbraun, praktisch unsichtbar. Genau das hat FH
   am 2026-08-31 gemeldet ("bei den routinen habe ich keine kalender
   hilfe beim zeitraum"): die Hilfe war da, man sah sie nur nicht.

   Nachgemessen, nicht vermutet: bei System hell und `data-theme="dark"`
   stand an der Wurzel `color-scheme: normal` und am Feld ein
   Hintergrund von rgb(35, 32, 25).

   Ohne `data-theme` bleibt es beim `<meta name="color-scheme">` im
   Layout - dann folgt die Anwendung ohnehin dem System, und beide
   sagen dasselbe. */
:root[data-theme="dark"]  { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

/* --- Hell: die Vorgabe ----------------------------------------------
   `[data-schema]` steht daneben, damit das Vorschaukaertchen im Profil
   aus seinen eigenen Rohwerten dieselben Marken bildet wie die Seite. */
:root,
[data-schema] {
  --accent:        var(--h-accent);
  --accent-strong: var(--h-accent-strong);
  --accent-soft:   var(--h-accent-soft);
  --accent-ink:    var(--h-accent-ink);
  --bg:        var(--h-bg);
  --surface:   var(--h-surface);
  --surface-2: var(--h-surface-2);
  --border:    var(--h-border);
  --text:      var(--h-text);
  --muted:     var(--h-muted);
}

/* --- Dunkel in zwei Zustaenden ---------------------------------------
   1. Systemeinstellung, solange der Benutzer nichts gewaehlt hat
   2. ausdrueckliche Wahl "dunkel"
   Die Wahl liegt als data-theme am <html>-Element und wird von
   assets/js/theme.js noch vor dem ersten Zeichnen gesetzt.

   Beide Zustaende weisen dasselbe zu; nur die Bedingung davor
   unterscheidet sich. Die Werte selbst stehen weiter oben je Schema.
   --------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]),
  :root:not([data-theme="light"]) [data-schema] {
    --accent:        var(--d-accent);
    --accent-strong: var(--d-accent-strong);
    --accent-soft:   var(--d-accent-soft);
    --accent-ink:    var(--d-accent-ink);
    --bg:        var(--d-bg);
    --surface:   var(--d-surface);
    --surface-2: var(--d-surface-2);
    --border:    var(--d-border);
    --text:      var(--d-text);
    --muted:     var(--d-muted);

    --good: #5aa86f;
    --warn: #d9a441;
    --bad:  #d63a52;
    --siegel: #6aa9e0;
    --kalt: #6fb2e0;

    --chart-2: #8fa9b8;
    --chart-3: #c08f6a;

    --tag-grau:   #a49c92;
    --tag-blau:   #7fb0cd;
    --tag-ocker:  #c9a071;
    --tag-salbei: #86b79c;
    --tag-rost:    #cf9683;
    --tag-pflaume: #b596bf;
    --tag-petrol:  #6fbab2;
    --tag-oliv:    #c6c079;

    --bp-sys: #6fb0d4;
    --bp-dia: #e0a24f;
    --bp-pul: #a49c92;

    --shadow: 0 1px 2px rgb(0 0 0 / .4), 0 4px 14px rgb(0 0 0 / .3);

    /* Im Dunkeln ist `--accent` schon hell; weiter aufzuhellen hiesse,
       das Kaestchen im Grund verschwinden zu lassen. */
    --haken: var(--accent);
    /* Und der Haken darauf muss dunkel sein - `--accent-ink` ist genau
       die Farbe, die auf `--accent` lesbar ist. `--text` waere hier
       hell und der Haken damit unsichtbar. */
    --hakenstrich: var(--accent-ink);
  }
}

:root[data-theme="dark"],
:root[data-theme="dark"] [data-schema] {
    --accent:        var(--d-accent);
    --accent-strong: var(--d-accent-strong);
    --accent-soft:   var(--d-accent-soft);
    --accent-ink:    var(--d-accent-ink);
    --bg:        var(--d-bg);
    --surface:   var(--d-surface);
    --surface-2: var(--d-surface-2);
    --border:    var(--d-border);
    --text:      var(--d-text);
    --muted:     var(--d-muted);

    --good: #5aa86f;
    --warn: #d9a441;
    --bad:  #d63a52;
    --siegel: #6aa9e0;
    --kalt: #6fb2e0;

    --chart-2: #8fa9b8;
    --chart-3: #c08f6a;

    --tag-grau:   #a49c92;
    --tag-blau:   #7fb0cd;
    --tag-ocker:  #c9a071;
    --tag-salbei: #86b79c;
    --tag-rost:    #cf9683;
    --tag-pflaume: #b596bf;
    --tag-petrol:  #6fbab2;
    --tag-oliv:    #c6c079;

    --bp-sys: #6fb0d4;
    --bp-dia: #e0a24f;
    --bp-pul: #a49c92;

    --shadow: 0 1px 2px rgb(0 0 0 / .4), 0 4px 14px rgb(0 0 0 / .3);

    /* Im Dunkeln ist `--accent` schon hell; weiter aufzuhellen hiesse,
       das Kaestchen im Grund verschwinden zu lassen. */
    --haken: var(--accent);
    /* Und der Haken darauf muss dunkel sein - `--accent-ink` ist genau
       die Farbe, die auf `--accent` lesbar ist. `--text` waere hier
       hell und der Haken damit unsichtbar. */
    --hakenstrich: var(--accent-ink);
}

/* Reserviert Platz fuer die Scrollleiste, damit der Inhalt nicht seitlich
   springt, sobald eine Seite laenger oder kuerzer wird. */
html { scrollbar-gutter: stable; }

/* Der Doppeltipp zoomt nicht mehr (FH, 2026-09-02, in der App
   aufgefallen): wer schnell zweimal tippt - zwei Haken, zweimal
   derselbe Knopf - bekam stattdessen die Vergroesserung.
   `manipulation` laesst Wischen und das Zwei-Finger-Zoomen in Ruhe
   und nimmt nur die Doppeltipp-Deutung weg. Am Wurzelelement, weil
   ein Element die Geste nur bekommt, wenn alle Vorfahren sie
   erlauben - so gilt es ueberall, ohne an jedem Feld zu stehen. */
html { touch-action: manipulation; }

body {
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  /* Platz fuer die feste Kopfleiste. */
  padding-top: var(--topbar-h);
  padding-bottom: calc(var(--nav-h) + env(safe-area-inset-bottom));
  -webkit-font-smoothing: antialiased;
}
body.is-guest { padding-top: 0; padding-bottom: 0; display: grid; place-items: center; min-height: 100svh; }

h1 { font-size: 1.4rem; line-height: 1.25; font-weight: 650; }
h2 { font-size: 1.1rem; line-height: 1.3; font-weight: 650; }
h3 { font-size: .95rem; font-weight: 650; }
.muted { color: var(--muted); }
.num { font-variant-numeric: tabular-nums; }

/* --- Topbar --------------------------------------------------------- */
.topbar {
  /* Fest statt sticky: ein sticky-Element wird beim Scrollen vom
     Compositor mitgefuehrt und schleift dabei sichtbar hinterher. Ein
     festes Element wird einmal gezeichnet und bleibt stehen. */
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: .7rem 1rem;
  min-height: var(--topbar-h); box-sizing: border-box;
  /* Bewusst deckend und ohne backdrop-filter: eine unscharfe Ebene ueber
     dem scrollenden Inhalt bekommt eine eigene Compositing-Ebene und
     wackelt in Chrome beim Scrollen um Bruchteile eines Pixels. */
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
/* 1.05rem Luft zwischen Wuerfel und Wortmarke (bis Paket 152: .55rem):
   der taumelnde Koerper ragt bis 10,5 px ueber seine 28-px-Buehne
   hinaus (gemessen ueber den ganzen Umlauf) und stiess mit .55rem an
   das Wort. FH, 11.09.2026: "ProjectPattex im Kopfteil sollte etwas
   weiter rechts stehen." */
.topbar__brand { display: flex; align-items: center; gap: 1.05rem; text-decoration: none; color: var(--text); }
/* Groesser als die 19 x 21 px bis zum 2026-08-29 (FH: "das Pattexlogo
   oben links etwas groesser darstellen"). Daneben steht die Wortmarke
   mit 16 px in Fettschrift - ihre Zeile ist 24 px hoch, das Zeichen war
   also kleiner als die Schrift, die es begleiten soll. 26 x 29 passt
   weiter bequem in die 58 px hohe Leiste und traegt das Verhaeltnis des
   Originals (1512:1701) mit. */
/* Der Koerper in der Kopfzeile: eine Buehne von 28 px, der Wuerfel
   darin steht, taumelt, steht (wuerfel--pausen). */
.topbar__mark {
  --wuerfel-g: 28px; --wuerfel-h: 14px;
  width: var(--wuerfel-g); height: var(--wuerfel-g); perspective: 10rem;
  color: var(--accent); flex: 0 0 auto;
}
.brandmark { width: 40px; height: 46px; margin: 0 auto .7rem; color: var(--accent); display: block; }
/* Auf der Anmeldung als Koerper, der steht, taumelt, steht (FH,
   11.09.2026). Dieselbe Buehne wie im Schleier, nur kleiner. Oben
   1.25rem und unten 1.9rem Luft (Paket 153): der taumelnde Koerper ragt bis 16,3 px
   ueber seine 44-px-Buehne hinaus, und der Kasten hat 1.1rem Rand -
   im unguenstigsten Moment stand er 1 px vor der Kante, und ebenso
   dicht an der Ueberschrift darunter (FH: "dass oberhalb vom wuerfel
   mehr platz ist"). */
.brandmark--koerper {
  --wuerfel-g: 2.75rem; --wuerfel-h: 1.375rem;
  width: var(--wuerfel-g); height: var(--wuerfel-g); perspective: 16rem;
  margin-top: 1.25rem; margin-bottom: 1.9rem;
}
.topbar__name { font-weight: 650; letter-spacing: -.01em; }
.topbar__actions { display: flex; align-items: center; gap: .5rem; }

/* --- Die Sprungsuche (FH, 2026-09-03) ---------------------------------
   Ein Feld für Seiten und Datensätze. Der Knopf steht in der Kopfzeile,
   das Blatt legt sich über die ganze Seite - auf jedem Schirm dasselbe,
   weil in der Kopfzeile eines Telefons kein Feld Platz hat.

   Der Knopf sieht aus wie ein Feld und ist keines: so ist ohne
   Beschriftung klar, was passiert, wenn man ihn drückt. Am Handy
   schrumpft er auf die Lupe. */
.sprung__knopf {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .35rem .6rem; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); color: var(--muted);
  font: inherit; font-size: .88rem; cursor: pointer;
}
.sprung__knopf:hover { border-color: var(--accent); color: var(--text); }
.sprung__knopf:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sprung__knopf svg {
  width: 1.05rem; height: 1.05rem; flex: none;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Die Taste als Verzierung - sie sagt, dass es einen kürzeren Weg gibt. */
.sprung__taste {
  border: 1px solid var(--border); border-radius: 4px;
  padding: 0 .3rem; font-size: .75rem; line-height: 1.3;
}
@media (max-width: 620px) {
  .sprung__wort, .sprung__taste { display: none; }
  .sprung__knopf { padding: .35rem .45rem; }
}

/* Der Schleier. `hidden` allein reicht nicht, sobald hier ein
   display steht - dieselbe Falle wie bei der Kartenlupe. */
.sprung {
  position: fixed; inset: 0; z-index: 120;
  display: flex; justify-content: center;
  padding: max(3rem, 8vh) 1rem 1rem;
  background: color-mix(in srgb, #000 45%, transparent);
}
.sprung[hidden] { display: none; }
body.is-sprung { overflow: hidden; }

.sprung__blatt {
  display: flex; flex-direction: column; width: min(38rem, 100%);
  max-height: min(32rem, 80vh);
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .35);
  overflow: hidden;
}

.sprung__kopf {
  display: flex; align-items: center; gap: .5rem; flex: none;
  padding: .6rem .7rem; border-bottom: 1px solid var(--border);
}
.sprung__lupe {
  width: 1.1rem; height: 1.1rem; flex: none;
  fill: none; stroke: var(--muted); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Randlos war die Absicht: der Rahmen des Blattes ist schon da, ein
   zweiter darin machte aus einer Zeile eine Schachtel.

   **Erreicht wird sie nicht** (gemessen am 06.09.2026, Paket 99).
   `border: 0`, `background: transparent` und das linke Polster
   verlieren gegen die Grundregel fuer Eingabefelder ganz oben: die ist
   als Ausschlussliste geschrieben, neun `:not()` machen Spezifitaet
   0,9,1, und `.sprung__feld` hat 0,1,0. Am Feld stehen deshalb ein
   1 px breiter Rahmen, ein weisser Grund und 12 px Polster links.

   **Das ist FHs Entscheidung und kein Versehen.** Drei Varianten sind
   gebaut und fotografiert worden (A heute, B wirklich randlos, C
   randlos mit Abstand zur Lupe); er hat A gewaehlt. Wer die drei
   Zeilen also wirksam machen will - mit der Ausschlussliste noch
   einmal, wie bei `.field--invalid` in Paket 91 -, aendert damit das
   Aussehen gegen seine Entscheidung.

   Sie bleiben stehen, weil `flex`, `min-width` und die Schrift daneben
   sehr wohl wirken. Dieser Kommentar steht hier, damit der naechste
   Leser sie nicht fuer eine Beschreibung dessen haelt, was auf dem
   Schirm steht. Siehe `docs/offene_punkte.md`. */
.sprung__feld {
  flex: 1 1 auto; min-width: 0;
  border: 0; background: transparent; color: inherit;
  font: inherit; font-size: 1rem; padding: .25rem 0;
}
.sprung__feld:focus { outline: none; }

.sprung__liste { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: .3rem 0; }

.sprung__gruppe {
  padding: .5rem .8rem .2rem; font-size: .72rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}

.sprung__ziel {
  display: flex; align-items: center; gap: .5rem;
  padding: .45rem .8rem; color: inherit; text-decoration: none;
}
/* Hervorgehoben wird über eine Klasse und nicht über :hover: die
   Tastatur soll dieselbe Zeile markieren wie die Maus, und zwei Wege
   zu einer Hervorhebung wären zwei, die auseinanderlaufen. */
.sprung__ziel.is-wahl { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.sprung__ziel .sprung__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Seiten sind Orte, Datensätze sind Dinge - ein Pfeil sagt das ohne
   ein Wort dafür zu verbrauchen. */
.sprung__ziel.is-seite .sprung__name::before { content: "→ "; color: var(--muted); }

.sprung__leer { padding: 1.2rem .8rem; color: var(--muted); }

.sprung__fuss {
  flex: none; margin: 0; padding: .45rem .8rem;
  border-top: 1px solid var(--border);
  font-size: .78rem; color: var(--muted);
}

/* --- Avatare -----------------------------------------------------------
   Ein Bild vom Benutzer, rund - oben rechts, im Profil und hinter dem,
   was jemand selbst angelegt hat. Wer kein Foto hat, bekommt einen
   farbigen Kreis mit seinen Initialen; damit steht ueberall etwas, ohne
   dass jemand erst etwas hochladen muss (FH, 2026-08-27).
   ---------------------------------------------------------------------- */
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; overflow: hidden; border-radius: 50%;
  /* Ein Rahmen um den Kreis (FH, 2026-08-27). Er trennt das Bild vom
     Hintergrund - ein Foto mit hellem Rand verlief sonst in die
     Kopfleiste, und der Kreis mit den Initialen sass ohne Kante da. */
  border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text);
  font-weight: 650; line-height: 1; letter-spacing: .02em;
  /* Die Initialen sind Text und wuerden sich sonst markieren lassen,
     wenn man ueber den Kreis zieht. */
  user-select: none;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

.avatar--klein  { width: 1.4rem;  height: 1.4rem;  font-size: .6rem; }
/* In einer Tabellenzeile steht der kleine Avatar hinter dem Namen und
   soll dort **mittig zur Schrift** sitzen, nicht auf der Grundlinie
   haengen (FH, 2026-08-27) - dasselbe wie beim Siegel daneben. Beide
   sind Zeichen am Namen und sollen gleich aussehen.

   `middle` statt eines em-Werts: der Kreis ist 1,4 rem hoch und damit
   hoeher als die Zeile, ein fester Versatz haette bei jeder anderen
   Schriftgroesse wieder danebengelegen. Gemessen bleibt ein Pixel
   Abstand zur Mitte des Namens (bin/test_avatar.js). */
.avatar--klein { vertical-align: middle; margin-left: .3rem; }
.avatar--mittel { width: 2.125rem; height: 2.125rem; font-size: .78rem; }
.avatar--gross  { width: 5rem;    height: 5rem;    font-size: 1.6rem; }

/* Die Farbe kommt aus dem Benutzernamen (App\Support\Avatar) - und aus
   vier der Variablen, die auch die Schlagworte benutzen, damit nicht
   zwei Farbwelten nebeneinanderstehen und beide im dunklen Bild
   mitziehen. Seit dem 03.09.2026 haben die Schlagworte vier weitere;
   die Avatare bekommen sie bewusst **nicht** - warum, steht in
   App\Support\Avatar.
   Etwas kraeftiger als am Abzeichen (22 statt 16 Prozent): dort steht
   Text darauf, hier tragen zwei Buchstaben die ganze Flaeche. */
.avatar--grau   { background: color-mix(in srgb, var(--tag-grau) 22%, transparent);   color: var(--tag-grau); }
.avatar--blau   { background: color-mix(in srgb, var(--tag-blau) 22%, transparent);   color: var(--tag-blau); }
.avatar--ocker  { background: color-mix(in srgb, var(--tag-ocker) 22%, transparent);  color: var(--tag-ocker); }
.avatar--salbei { background: color-mix(in srgb, var(--tag-salbei) 22%, transparent); color: var(--tag-salbei); }

/* Oben rechts ist der Avatar der ganze Knopf - deshalb bekommt er den
   Rahmen beim Tastaturweg, nicht ein unsichtbares Kaestchen darum. */
.topbar__user { display: inline-flex; border-radius: 50%; }
.topbar__user:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.topbar__user .avatar { transition: box-shadow .12s; }
.topbar__user:hover .avatar { box-shadow: 0 0 0 2px var(--border); }

/* Die Reiter über den Profilseiten. Dieselbe Machart wie die
   segmentierten Schalter, aber über die ganze Breite und umbrechend -
   sechs Wörter passen am Handy nicht in eine Zeile. */
.profilnav {
  display: flex; flex-wrap: wrap; gap: .3rem;
  margin: .2rem 0 1rem; padding: .25rem;
  background: var(--surface-2); border-radius: 10px;
}
.profilnav a {
  padding: .45rem .8rem; border-radius: 7px;
  color: var(--muted); font-size: .9rem; font-weight: 600;
}
.profilnav a:hover { color: var(--text); }
.profilnav a[aria-current="page"] {
  background: var(--surface); color: var(--text); box-shadow: var(--shadow);
}

/* Die zweite Ebene unter der Administration. Sie soll als Unterpunkt zu
   erkennen sein und nicht als zweite Reihe gleichrangiger Reiter:
   deshalb ohne eigene Fläche, kleiner, und ein Stück eingerückt. */
.profilnav--unter {
  background: transparent; padding: 0 0 0 .25rem;
  margin-top: -.6rem; margin-bottom: 1rem;
}
.profilnav--unter a { font-size: .82rem; padding: .3rem .6rem; }
.profilnav--unter a[aria-current="page"] {
  background: var(--surface-2); box-shadow: none;
}

/* Die vier Farbwelten zur Wahl. Jedes Kaertchen traegt das Schema
   selbst (`data-schema`), zeigt also nicht eine gemalte Behauptung,
   sondern die Marken, die die Seite danach wirklich benutzt. */
.schemawahl {
  display: grid; gap: .7rem; margin: .8rem 0;
  grid-template-columns: repeat(auto-fill, minmax(min(7rem, 100%), 1fr));
}
.schema { display: grid; gap: .4rem; cursor: pointer; }
.schema input { position: absolute; opacity: 0; pointer-events: none; }
.schema__bild {
  display: block; padding: .55rem; border-radius: 10px;
  border: 2px solid var(--border); background: var(--bg);
  transition: border-color .12s;
}
/* Innerhalb des Kaertchens gelten die Marken des gewaehlten Schemas -
   deshalb hier bewusst `--surface` und `--accent` statt fester Werte. */
.schema__flaeche {
  display: grid; gap: .3rem; padding: .5rem;
  background: var(--surface); border-radius: 6px;
}
.schema__balken { display: block; height: .55rem; border-radius: 3px; background: var(--accent); }
.schema__zeile  { display: block; height: .35rem; border-radius: 3px; background: var(--surface-2); }
.schema__zeile--kurz { width: 60%; }
/* Das gewaehlte Kaertchen traegt einen doppelten Rand statt eines
   Hakens: ein Haken auf einer Farbflaeche waere in einem der vier
   Schemen immer schlecht zu sehen.

   `is-gewaehlt` kommt vom Server und sagt, was **gespeichert** ist;
   `:has(input:checked)` sagt, was **angeklickt** ist. Bis 2026-08-28 gab
   es nur das erste - wer ein anderes Schema anklickte, sah bis zum
   Speichern nichts (FH). Der Ring haengt jetzt an der Auswahl, nicht am
   gespeicherten Zustand.

   Und er nimmt den Ton der Seite (`--pp-ring`), nicht den des
   Kaertchens: `--accent` gilt im Kaertchen dem Schema, das es zeigt -
   der Ring waere dann bei jedem Kaertchen anders und beim einen oder
   anderen kaum zu sehen. */
.schemawahl { --pp-ring: var(--accent); }
.schema:has(input:checked) .schema__bild {
  border-color: var(--pp-ring);
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--pp-ring);
}
.schema input:focus-visible + .schema__bild { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Die Bereiche zum Anhaken. Ein Kaestchen je Zeile, der Hinweis rechts. */
.modulwahl { list-style: none; display: grid; gap: .1rem; padding: 0; margin: .2rem 0 0; }
/* Dieselbe Schrift wie die Zwischenueberschrift im Menue - es sind
   dieselben Gruppen, und sie sollen auch gleich aussehen. */
.modulwahl__kopf {
  margin: .95rem 0 0; font-size: .68rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.modulwahl:first-of-type + .modulwahl__kopf, .modulwahl__kopf:first-of-type { margin-top: .6rem; }
.modulwahl__zeile {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  padding: .45rem .1rem; border-bottom: 1px solid var(--border);
}
.modulwahl__zeile:last-child { border-bottom: 0; }
.modulwahl__zeile label { display: flex; align-items: center; gap: .55rem; cursor: pointer; }
.modulwahl__zeile input[disabled] + .modulwahl__name { color: var(--muted); }
.modulwahl__name { font-weight: 600; }
.modulwahl__hinweis { margin-left: auto; font-size: .8rem; }

/* Im Profil steht das Bild neben seinem Formular. */
.profilbild {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1rem;
  margin-top: .8rem;
}
.profilbild__form { flex: 1 1 18rem; min-width: 0; }
.topbar__user { color: var(--muted); text-decoration: none; font-size: .9rem; }
.topbar__user:hover { color: var(--text); }

/* --- Navigation: unten auf dem Handy, links am Desktop --------------- */
.nav {
  position: fixed; z-index: 30; inset: auto 0 0 0;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom);
}
/* Am Handy ist die Leiste **eine** Reihe, auch wenn die Punkte seit den
   Zwischenueberschriften in mehreren Listen stehen: `display: contents`
   loest die Listen auf, ihre Punkte werden direkt zu Feldern der Reihe.
   Die Vorlage traegt dafuer `role="list"` - Safari nimmt einer so
   aufgeloesten Liste sonst ihre Bedeutung fuer Vorlesegeraete. */
.nav { display: flex; }
.nav > ul { display: contents; }
.nav ul { list-style: none; padding: 0; margin: 0; }
.nav li { flex: 1; }
.nav a {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; height: var(--nav-h); text-decoration: none;
  color: var(--muted); font-size: .68rem; font-weight: 550;
}
.nav svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.nav a[aria-current="page"] { color: var(--accent); }

/* Die Leiste taucht beim Lesen ab (FH, 2026-09-02): wer nach unten
   scrollt, will Inhalt sehen; wer nach oben scrollt oder unten
   hintippt, will navigieren. Die Klasse setzt app.js und nur am
   Handy - die Regel steht trotzdem unter der Medienabfrage, damit
   ein liegengebliebenes `is-versteckt` die Spalte am Rechner nie
   anfassen kann. 100% nimmt das env()-Polster mit: die Leiste ist
   dann wirklich aus dem Bild, nicht nur bis zum Home-Balken. */
@media (max-width: 899.98px) {
  .nav { transition: transform .22s ease; }
  .nav.is-versteckt { transform: translateY(100%); }
}
@media (prefers-reduced-motion: reduce) {
  .nav { transition: none; }
}

/* --- Layout --------------------------------------------------------- */
.main { padding: 1rem; display: grid; gap: 1rem; min-width: 0; }
.is-guest .main { max-width: 400px; padding: 1.5rem; }

@media (min-width: 900px) {
  body { padding-bottom: 0; }
  /* overflow-y: sonst laufen die elf Punkte auf niedrigen Fenstern unten heraus. */
  .nav { inset: var(--topbar-h) auto 0 0; width: 210px; border-top: 0; border-right: 1px solid var(--border); padding-top: .5rem; overflow-y: auto; }
  .nav { display: block; }
  .nav > ul { display: flex; flex-direction: column; padding: .5rem; gap: .15rem; }
  .nav a { flex-direction: row; justify-content: flex-start; gap: .7rem; height: 42px; padding: 0 .8rem; border-radius: 9px; font-size: .9rem; }
  .nav a[aria-current="page"] { background: var(--accent-soft); }

  /* Die Zwischenueberschrift traegt nach oben mehr Luft als nach unten:
     sie gehoert zu dem, was unter ihr steht, nicht zu dem darueber. */
  .nav__gruppe {
    margin: .9rem 0 .1rem; padding: 0 1.3rem;
    font-size: .68rem; font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--muted);
  }
  .nav > ul:first-of-type + .nav__gruppe { margin-top: .5rem; }
  .nav__leiste { display: none; }
  body:not(.is-guest) .main { margin-left: 210px; }
  .main { padding: 1.5rem; }
  .topbar { padding-left: 1.25rem; }
}

/* Der Behälter um eine Liste darf nicht breiter werden als die Karte.
   Ohne das gilt `min-width: auto`, und eine breite Tabelle schiebt ihn
   auseinander - die Tabelle hat ihren eigenen Rollbalken
   (`.table-wrap`), aber der greift erst, wenn der Behälter darüber
   schmal bleibt. Am Handy war die Messwerteseite dadurch 471 statt
   390 px breit; `bin/vorschau.js` hat es gemeldet, zu sehen war nur ein
   Wackeln beim Wischen. */
[data-liste] { min-width: 0; }

/* --- Karten --------------------------------------------------------- */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 1.1rem; display: grid; gap: .85rem;
  min-width: 0;
}
.card--center { text-align: center; justify-items: center; }

/* Der Kasten, hinter dem etwas endgueltig verschwindet (FH,
   2026-08-30, Konto loeschen). Er sieht aus wie jeder andere, nur der
   Rand nimmt die Warnfarbe auf - kein roter Block, sondern ein Kasten,
   der sich beim Ueberfliegen von den drei darueber unterscheidet. Die
   Ueberschrift bleibt in der Schriftfarbe: rote Ueberschriften lesen
   sich wie ein Fehler, und hier ist noch nichts passiert.

   `color-mix` wie bei `.btn--danger`: der Rand nimmt dieselbe Farbe
   auf und ist damit in jedem Schema stimmig. Browser ohne `color-mix`
   bekommen ueber die erste Zeile den gewoehnlichen Rand - blasser
   waere schoener, sichtbar ist wichtiger. */
.card--gefahr {
  border-color: var(--bad);
  border-color: color-mix(in srgb, var(--bad) 45%, var(--border));
}

/* Was an einem Konto haengt, in Zahlen - vor dem Loeschknopf. Eine
   Liste ohne Punkte: die Zahl am Anfang jeder Zeile ist die Marke, ein
   Punkt davor waere eine zweite. */
.bestandliste { list-style: none; margin: 0; padding: 0; display: grid; gap: .3rem; }
.bestandliste li { font-size: .85rem; color: var(--muted); }
.bestandliste strong { color: var(--text); font-variant-numeric: tabular-nums; }
.card__head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; min-width: 0; }
/* Die Kopfzeile steht auch ausserhalb einer Karte direkt im Raster -
   ohne das darf sie nicht schrumpfen und schiebt die Seite auf. */
.card__head > .btn-row { min-width: 0; }

/* Ein Formular als Kind der Karte ist ein Rasterfeld und wuechse sonst
   mit seinem Inhalt - eine breite Tabelle darin schoebe die ganze Seite
   auf, statt in ihrer .table-wrap zu scrollen. */
.card > form { min-width: 0; }

.grid { display: grid; gap: 1rem; }
@media (min-width: 620px) { .grid--2 { grid-template-columns: 1fr 1fr; } }
@media (min-width: 620px) { .grid--3 { grid-template-columns: repeat(3, 1fr); } }
/* Fuellt die Breite selbstaendig: eine Spalte schmal, danach zwei, drei,
   vier - je nachdem wie viel Platz da ist. Ohne festen Umbruchpunkt. */
.grid--auto { grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); align-items: start; }

/* Eine Reihe aus drei Kacheln: das Bild, daneben der Stapel aus
   Einheiten und Packungen, daneben Herkunft. Unter 900 px stapelt sich
   alles - drei Spalten waeren dort jede fuer sich zu schmal. */
.grid--details { align-items: start; }
@media (min-width: 900px) {
  /* Die Bildspalte richtet sich nach dem Bild (auto), die beiden anderen
     teilen sich den Rest. stretch laesst alle drei Kacheln gleich hoch
     werden - das Bild wird so hoch wie die Herkunftskachel, und seine
     Breite ergibt sich aus dem Seitenverhaeltnis. */
  .grid--details { grid-template-columns: auto minmax(0, 1fr) minmax(0, 1fr); align-items: stretch; }
  .card--bild { max-width: 18rem; padding: .7rem; }
  /* So hoch wie die Nachbarkachel, aber nicht hoeher als 12rem - sonst
     zieht eine lange Herkunftstabelle das Bild ins Riesenhafte. */
  .card--bild .bildknopf { height: 100%; display: grid; place-items: center; }
  .card--bild .produktbild { height: 100%; max-height: 12rem; width: auto; margin: 0; }
}
.stapel { display: grid; gap: 1rem; align-content: start; min-width: 0; }

/* Bildkachel: nur das Bild, mittig, ohne Ueberschrift. */
.card--bild { display: grid; place-items: center; }
.produktbild { display: block; max-width: 100%; max-height: 8rem;
  border-radius: var(--radius); object-fit: contain; }

/* --- Kennzahlen ----------------------------------------------------- */
.stat { display: grid; gap: .15rem; }
.stat__label { font-size: .75rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.stat__value { font-size: 1.75rem; font-weight: 650; font-variant-numeric: tabular-nums; line-height: 1.1; }
.stat__unit { font-size: .95rem; font-weight: 500; color: var(--muted); }
.stat__delta { font-size: .85rem; font-variant-numeric: tabular-nums; }
.is-good { color: var(--good); }
.is-warn { color: var(--warn); }
.is-bad  { color: var(--bad); }
/* "Zu niedrig" bei der Koerpertemperatur - siehe LabRepository::KALT_UNTEN. */
.is-kalt { color: var(--kalt); }

/* --- Formulare ------------------------------------------------------ */
.form { display: grid; gap: .9rem; }
/* `hidden` heisst versteckt - auch bei allem, was von sich aus
   `display: flex` oder `grid` bekommt. Ohne das steht ein verstecktes
   Abzeichen sichtbar da: die Klasse setzt `inline-flex`, und das schlaegt
   den Vorgabewert des Attributs. Aufgefallen an der Filterleiste, wo die
   Zahl neben "Filter" und der Verweis zum Zuruecksetzen zwischen sichtbar
   und versteckt wechseln (FH, 2026-08-27). */
[hidden] { display: none !important; }

.field { display: grid; gap: .3rem; }
/* Ein Feld wächst nicht in die Höhe, nur weil das Feld daneben einen
   Hinweistext trägt. Ohne das verteilt das Raster die freie Höhe auf
   seine Zeilen, und ein Eingabefeld stünde höher da als das gleiche
   Feld eine Spalte weiter. */
.field { align-content: start; }

/* Auch ohne <label>: eine Gruppe wie die Sternwahl hat kein einzelnes
   Feld, auf das sich eine Beschriftung beziehen könnte. */
/* `:not(.btn)` seit dem 2026-08-29: ein <label>, das ein Knopf ist
   (die Ablegeflaeche der Packungsfotos), ist keine Feldbeschriftung.
   Ohne die Ausnahme schlaegt diese Regel mit ihrer hoeheren Spezifitaet
   die Farbe von `.btn` - der Knopf stand gedaempft auf gedaempft und
   war praktisch nicht zu lesen. Im Bild nachgemessen, nicht vermutet. */
.field > label:not(.btn), .field__label { font-size: .82rem; font-weight: 600; color: var(--muted); }
.field__hint { font-size: .78rem; color: var(--muted); }
.field__error { font-size: .8rem; color: var(--bad); }
/* Ein Hinweis, kein Fehler: er haelt das Speichern nicht auf, deshalb
   die Warnfarbe und nicht die des Fehlers - und deshalb bekommt das
   Feld auch kein `field--invalid` (2026-08-29). Wer die Packung vor
   sich hat, weiss mehr als die Anwendung. */
.field__warnung { font-size: .8rem; color: var(--warn); margin-top: .2rem; }

/* Der Satz, der sagt, wohin etwas geht (FH, 2026-08-30, Datenschutz).
   Er steht an vier Knoepfen und muss an allen vieren gefunden werden,
   ohne zu schreien: dieselbe Schrift wie die uebrigen Hinweise unter
   einem Feld, aber ein Strich an der Seite, damit das Auge ihn beim
   Ueberfliegen nicht mit der Erklaerung darueber verwechselt.

   `color-mix` und nicht eine eigene Farbe: der Strich soll in jedem
   Schema die gedaempfte Schrift aufnehmen und sich davon nur so weit
   abheben, wie ein Rand das tut. Browser ohne `color-mix` bekommen
   ueber die erste Zeile die gedaempfte Farbe selbst - blasser waere
   schoener, sichtbar ist wichtiger. */
.ki-hinweis {
  border-left: 2px solid var(--muted);
  border-left-color: color-mix(in srgb, var(--muted) 45%, transparent);
  padding-left: .55rem;
  margin-top: .35rem;
}
.ki-hinweis a { color: inherit; }

/* Kaestchen und Punkte behalten ihre Form vom Betriebssystem - nur die
   Farbe kommt von uns. Ohne das blieben sie im Systemblau stehen, und
   ein gewaehltes Schema haette einen blauen Fleck in jedem Formular
   (FH, 2026-08-27). `accent-color` faerbt nur den Haken selbst; Groesse,
   Rahmen und Bedienung bleiben die gewohnten.

   **Nicht `--accent` selbst** (FH, 2026-08-30: "bei hellem Hintergrund
   auch hell darstellen, sonst wirken sie sehr aggressiv/massiv"). Im
   hellen Schema ist `--accent` fast schwarz; ein Formular mit acht
   Haken hatte danach acht dunkle Kloetze darin, und die zogen den Blick
   staerker an als der Text daneben.

   Gemischt mit der Flaeche, auf der das Kaestchen sitzt: hell genug,
   um ruhig zu sein, und immer noch deutlich gegen die leere Box. Den
   **Haken** darin waehlt der Browser selbst nach Kontrast - auf dem
   hellen Grund zeichnet er ihn dunkel, und genau das ist das Bild, das
   FH wollte.

   Im dunklen Schema bleibt es bei `--accent`: dort ist er ohnehin
   hell, und weiter aufzuhellen hiesse, ihn im Grund verschwinden zu
   lassen. Der Wert steht deshalb als eigene Marke und wird unten im
   dunklen Block zurueckgesetzt, statt hier eine Bedingung zu tragen. */
:root, [data-schema] {
  --haken:       color-mix(in srgb, var(--accent) 30%, var(--surface));
  --kastenrand:  color-mix(in srgb, var(--border) 30%, var(--muted));
  --hakenstrich: var(--text);
}

/* `accent-color` allein reichte nicht (FH, 2026-08-30: „Im nicht
   ausgewählten Zustand sind die Checkboxen weiterhin dunkel", und:
   „Wenn das Theme hell ist, sieht eine dunkle inaktive Box nicht gut
   aus").

   Der Grund: `accent-color` faerbt nur den **gefuellten** Zustand. Die
   leere Box zeichnet das Betriebssystem, und die ist in Chromium ein
   fast schwarzer Rahmen - in einem hellen Schema acht dunkle Punkte in
   jedem Formular.

   Dagegen hilft nur `appearance: none` und selbst zeichnen. Was dabei
   aufgegeben wird, steht hier, damit es niemand suchen muss: die
   Systemform ist weg, also muessen Groesse, Rahmen, Haken, der dritte
   Zustand (`indeterminate`), der gesperrte und der Fokusring von hier
   kommen. Alle sechs stehen unten, und `bin/test_kaestchen.js` misst
   sie **an Bildpunkten** - nicht an der Regel, die davorsteht.

   13 Punkte ist die Groesse, die Chromium bisher gezeichnet hat.
   Absichtlich unveraendert: sie steckt in jeder Zeilenhoehe dieser
   Anwendung, und eine groessere Box haette hundert Layouts um einen
   Punkt verschoben. Die Trefferflaeche kommt ohnehin vom Label
   darum - siehe `.zuordnen__haken`. */
/* **`.eigenform` ist die Ausnahme.** Zwei Stellen verstecken ihre
   Bedienelemente mit Absicht und zeichnen selbst etwas darueber: die
   Sternwahl (zehn Punkte auf 1x1 Punkt, darueber die Sterne) und die
   Schemawahl im Profil (ein Punkt je Farbkaertchen).

   Ohne die Ausnahme gewinnt die Regel hier: `input[type=radio]` ist
   spezifischer als `.sternwahl__radio`, und die versteckten Punkte
   waeren wieder 13 Punkte gross geworden - sie lagen dann ueber den
   Trefferflaechen, und ein Klick auf den ersten Stern traf ins Leere.
   Am 2026-08-30 genau so passiert; `bin/test_sterne.js` hat es
   gefangen.

   Die Klasse steht **ausdruecklich** im Markup und nicht als
   Spezifitaets-Trick im CSS, damit die naechste solche Stelle nicht
   wieder still zerbricht. `bin/test_kaestchen.js` sieht nach, dass
   jedes versteckte Element sie traegt. */
input[type=checkbox]:not(.eigenform), input[type=radio]:not(.eigenform) {
  appearance: none;
  -webkit-appearance: none;
  width: 13px;
  height: 13px;
  flex: 0 0 auto;
  margin: 0;
  padding: 0;
  border: 1px solid var(--kastenrand);
  background: var(--surface);
  /* `grid` statt `block`: der Haken darin wird ueber `place-content`
     mittig gesetzt, ohne absolute Verrenkungen. */
  display: inline-grid;
  place-content: center;
  vertical-align: -2px;
  cursor: pointer;
  transition: background-color .12s, border-color .12s;
}

input[type=checkbox]:not(.eigenform) { border-radius: 3px; }
input[type=radio]:not(.eigenform)    { border-radius: 50%; }

input[type=checkbox]:checked:not(.eigenform),
input[type=checkbox]:indeterminate:not(.eigenform),
input[type=radio]:checked:not(.eigenform) {
  background: var(--haken);
  border-color: color-mix(in srgb, var(--kastenrand) 70%, var(--haken));
}

/* Der Haken: zwei Kanten eines Kastens, um 45 Grad gedreht - der
   klassische Weg, und der einzige, bei dem die Farbe aus einer Marke
   kommen kann. Ein SVG als `background-image` muesste seine Farbe
   eingebacken tragen und braeuchte zwei Fassungen fuer hell und
   dunkel.

   `::after` an einem `<input>` ist laut Norm nicht zugesichert - bei
   `appearance: none` zeichnen es Chromium, Firefox und Safari aber
   alle. Deshalb misst die Pruefung Bildpunkte und nicht das
   Pseudoelement: faellt es in einem Browser doch weg, faellt sie
   durch. */
input[type=checkbox]:not(.eigenform)::after {
  content: "";
  width: 4px;
  height: 8px;
  border: solid var(--hakenstrich);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) translate(-1px, -1px);
  opacity: 0;
}

input[type=checkbox]:checked:not(.eigenform)::after { opacity: 1; }

/* Der dritte Zustand: teilweise angehakt. Seit dem 2026-08-30 zeigt
   ihn das Kaestchen im Tabellenkopf der Rezeptliste - ohne eigene
   Zeichnung saehe es aus wie „leer" und behauptete damit, es sei
   nichts angehakt. */
input[type=checkbox]:indeterminate:not(.eigenform)::after {
  opacity: 1;
  width: 7px;
  height: 0;
  border-width: 0 0 2px 0;
  transform: none;
}

input[type=radio]:not(.eigenform)::after {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--hakenstrich);
  opacity: 0;
}

input[type=radio]:checked:not(.eigenform)::after { opacity: 1; }

/* Gesperrt heisst blass, nicht unsichtbar: die festen Module im Profil
   tragen `disabled` und stehen trotzdem angehakt da. */
input[type=checkbox]:disabled:not(.eigenform), input[type=radio]:disabled:not(.eigenform) {
  opacity: .5;
  cursor: default;
}

/* Alle Eingabefelder ausser Schaltern und Kaestchen - als Ausschluss
   geschrieben, damit ein neuer Feldtyp (tel, url) nicht wieder
   unbehandelt im Formular steht. */
input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=file]):not([type=range]):not([type=color]):not([type=image]),
select, textarea {
  width: 100%; padding: var(--feldpolster, .65rem) .75rem;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 9px;
  transition: border-color .12s, box-shadow .12s;
}

/* Alles, was in einer Zeile nebeneinander stehen kann, ist gleich hoch:
   Textfeld, Auswahlfeld, Knopf. Vorher kam die Hoehe bei jedem aus etwas
   anderem - beim Textfeld aus der Zeilenhoehe, beim Auswahlfeld aus dem
   Zeichensatz, beim Datumsfeld aus dem Kalendersymbol. Das ergab 44, 46,
   47 und 49 Pixel nebeneinander (FH, 2026-08-25). Jetzt kommt die Hoehe
   aus einer einzigen Zahl.

   Zwei Groessen, weil es zwei Zusammenhaenge gibt: das Formular und die
   schmalen Leisten ueber Tabellen und Diagrammen. Innerhalb einer Leiste
   sind wieder alle gleich hoch. */
input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=file]):not([type=range]):not([type=color]):not([type=image]),
select {
  height: var(--feldhoehe); line-height: 1.25;
}
textarea { min-height: calc(2 * var(--feldhoehe)); }
/* Dateifelder sind oben ausgenommen (sie bekommen keinen Rahmen), haben
   aber eine eigenwillige Eigenbreite: Knopf plus "Keine Datei
   ausgewählt". In einem schmalen Feld - etwa im Foto-Klappfeld einer
   Mahlzeit - schob das die ganze Seite auf. */
input[type=file] { max-width: 100%; min-width: 0; font-size: .9rem; }

/* Der Fokusring - und warum er so umstaendlich geschrieben ist.
   ---------------------------------------------------------------------
   Die allgemeine Feldregel oben traegt neun `:not()`-Glieder und hat
   damit die Spezifitaet 0,9,1. Eine schlichte Regel `input:focus-visible
   { border-color: … }` (0,1,1) verliert dagegen - **gemessen** blieb der
   Rahmen bei Fokus unveraendert (Pruefbericht 2026-08-27, Punkt 3).
   Uebrig blieb ein Schatten mit 22 % Deckung: Kontrast 1,38 : 1 hell,
   1,55 : 1 dunkel, gefordert sind 3 : 1. Wer mit der Tastatur durch ein
   Formular ging, sah nicht, wo er war.

   Deshalb steht hier dieselbe Kette noch einmal, dazu `:focus-visible` -
   0,10,1 gegen 0,9,1, die Regel greift. Und der Ring ist jetzt der
   Akzent selbst statt einer Verduennung davon; das `outline` daneben
   traegt den Fall, in dem eine Seite andere Farben hat.
   --------------------------------------------------------------------- */
input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=file]):not([type=range]):not([type=color]):not([type=image]):focus-visible,
select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}
/* Kaestchen, Punkte und Dateifelder sind oben ausgenommen - sie bekommen
   den Ring trotzdem, nur ohne Rahmenfarbe. */
input[type=checkbox]:focus-visible, input[type=radio]:focus-visible,
input[type=file]:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
/* **Ein fehlerhaftes Feld traegt einen roten Rand** - und tat es seit
   jeher nicht (gefunden am 06.09.2026, Paket 91, im Browser gemessen:
   das Feld mit `.field--invalid` hatte dieselbe Randfarbe wie ein
   heiles, `rgb(220,213,201)`).

   Der Grund ist die Grundregel fuer Eingabefelder weiter oben. Sie ist
   als Ausschlussliste geschrieben - `input:not([type=checkbox]):not(…)`
   mit neun `:not()` - und hat damit die Spezifitaet 0,9,1. `.field--invalid
   input` hat 0,1,1 und kommt nicht dagegen an, egal wie weit unten es
   steht. **Beim `select` ging es gut**: in der Grundregel steht `select`
   als eigener Selektor mit 0,0,1, und dagegen gewinnt 0,1,1. Die Regel
   wirkte also zur Haelfte - und das ist der Grund, warum es niemandem
   auffiel.

   Hier steht deshalb dieselbe Ausschlussliste noch einmal. Das ist
   haesslich und hat einen Vorteil: es aendert nichts an allen anderen
   Feldern. Die saubere Loesung waere, die Grundregel in ein
   `input:where(:not(…))` zu fassen - `:where()` zaehlt nicht in die
   Spezifitaet, und alle Klassenregeln koennten wieder darueber. Das
   betrifft aber jedes Formular der Anwendung und gehoert gemessen,
   nicht nebenbei geaendert; siehe docs/offene_punkte.md. */
.field--invalid input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=file]):not([type=range]):not([type=color]):not([type=image]),
.field--invalid select,
.field--invalid textarea { border-color: var(--bad); }
.row { display: grid; gap: .9rem; grid-template-columns: 1fr 1fr; }

/* Ein Feld, das die ganze Zeile nimmt - der Hinweis unter den
   Freigabefeldern etwa: ein Satz in einer halben Spalte bricht nach
   jedem zweiten Wort um. */
.row > .field--breit { grid-column: 1 / -1; }

/* Drei Felder nebeneinander - auf dem Handy bleiben es zwei, sonst wird
   jedes einzelne zu schmal zum Tippen. */
.row--3 { grid-template-columns: 1fr 1fr; }
@media (min-width: 620px) { .row--3 { grid-template-columns: 1fr 1fr 1fr; } }

/* Ein Feld, das **nur am Handy** die ganze Zeile nimmt (FH, 2026-08-29:
   "Am Handy Geburtsdatum als eigene Zeile, Groesse und Gewicht
   nebeneinander in der Zeile darunter").

   Ohne das teilt die Zweispaltigkeit die drei Felder als 2 + 1 auf, und
   das letzte steht allein unten - ausgerechnet das Gewicht, das mit der
   Groesse zusammengehoert. Nimmt das erste Feld die ganze Zeile, ergibt
   sich 1 + 2 von selbst.

   Die Grenze ist die Gegenseite von `row--3` oben: ab 620 px stehen
   ohnehin alle drei nebeneinander, und dann hat die Regel nichts zu
   tun. */
@media (max-width: 619.98px) { .row--3 > .field--schmal-breit { grid-column: 1 / -1; } }

/* Notiz direkt unter dem Wert - schmaler, damit klar ist, was Beiwerk ist. */
.input--note { font-size: .85rem; padding: .4rem .6rem; }

/* Anmeldeformular: Beschriftung und Eingabe stehen mittig, weil die ganze
   Karte mittig auf dem Bildschirm sitzt. Die Eingaben als Ausschluss
   geschrieben wie oben - ein Haken oder Schalter soll nicht
   mitzentriert werden. `.field` ist ein Raster, die Beschriftung nimmt
   darin ohnehin die volle Breite ein; ein `display: block` braucht es
   also nicht. */
.form--mitte .field > label,
.form--mitte .field__label {
  text-align: center;
}
.form--mitte input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]) {
  text-align: center;
}

/* Der Haken "Angemeldet bleiben" zwischen Passwort und Knopf: mittig
   wie der Rest der Karte, aber als eigene Zeile - er gehoert nicht in
   das Raster eines .field, denn er hat keine Beschriftung darueber. */
.merken {
  display: flex; align-items: center; justify-content: center; gap: .55rem;
  font-size: .92rem; color: var(--muted);
}

/* --- Das Fenster ueber der Seite ------------------------------------
   FH, 2026-08-29, zum Teilen in der Rezeptuebersicht: "Nicht unten
   drunter [...] einen neuen Button teilen, die Infos, die aktuell unten
   sind in einem neuen Pop-Up."

   Gebaut aus `<dialog>` und nicht aus einem selbstgebauten Kasten: das
   Element bringt Fokusfalle, Escape und den Hintergrund von selbst mit.
   Nachbauen hiesse, drei Dinge nachzubauen, die der Browser besser
   kann - und das dritte davon (die Fokusfalle) vergisst man.

   **Ohne Skript bleibt es ein Kasten im Fluss.** Die Vorlage schreibt
   `open` mit ins Element; app.js schliesst es beim Laden und macht
   daraus ein Fenster. Wer kein JavaScript hat, sieht das Formular
   weiter unten auf der Seite stehen, so wie bis zum 2026-08-29 - nur
   der Knopf oben fehlt ihm, und der ist deshalb `hidden`, bis das
   Skript ihn zeigt.

   Das offene, nicht-modale <dialog> nimmt vom Browser
   `position: absolute` mit; ohne die Ruecksetzung schwebte der Kasten
   ueber dem Rest der Seite statt darin zu stehen. Alles, was nur fuer
   das Fenster gilt, haengt deshalb an `:modal`. */
.dialog {
  position: static; margin: 0; padding: 0; border: 0;
  width: auto; max-width: none; max-height: none;
  background: none; color: inherit;
}
.dialog:modal {
  position: fixed; inset: 0; margin: auto;
  width: min(46rem, calc(100vw - 1.6rem));
  max-height: calc(100dvh - 3rem);
  overflow-y: auto;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 24px 60px rgb(0 0 0 / .35);
  padding: 1.1rem;
}
.dialog::backdrop { background: rgb(0 0 0 / .45); }
/* Eine Ueberschrift mit Warnsymbol davor. Das Symbol traegt die Farbe
   der Warnung, die Ueberschrift bleibt schwarz - faerbte man beides,
   schriee das Fenster, und es ist nur ein Hinweis (FH, 2026-08-29:
   "im keine aenderung popup sollte noch ein warnungsicon sein"). */
.dialog__warnung { display: flex; align-items: center; gap: .45rem; }
.dialog__warnung .icon { width: 1.15em; height: 1.15em; flex: none; color: var(--warn); }
/* Luft unter dem Satz, bevor die Knoepfe kommen (FH: "etwas mehr Platz
   unterm text"). Im Fluss wie im Fenster - der Kasten steht dort
   mitten in einer Seite und braucht ihn genauso. */
.dialog__text { margin: 0 0 1.3rem; }
/* Die gefundenen Dubletten. Der Grund steht klein hinter dem Namen und
   nicht als Ueberschrift darueber: bei drei Treffern aus drei Gruenden
   waeren das drei Ueberschriften mit je einer Zeile darunter. */
.dubletten { list-style: none; margin: 0 0 1rem; padding: 0; display: grid; gap: .45rem; }
.dubletten li { display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem; }
.dubletten__grund { font-size: .78rem; color: var(--muted); }
/* Die Kopfzeile mit dem Schliessen-Knopf gibt es nur im Fenster - im
   Fluss ist "Schliessen" sinnlos, und die Ueberschrift steht dort
   ohnehin schon im Formular. */
.dialog__kopf { display: none; }
.dialog:modal .dialog__kopf {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; margin-bottom: .85rem;
}
/* Und umgekehrt: die Ueberschrift, die im Fluss den Kasten benennt,
   faellt im Fenster weg - dort steht sie schon oben. */
.dialog:modal .dialog__flusskopf { display: none; }

/* Ueberschrift und ein Knopf, der zu ihr gehoert - als eine Gruppe am
   linken Rand der Kopfzeile. Ohne die Huelle waeren es zwei Kinder des
   Flex-Kopfes, und `space-between` schoebe den Knopf in die Mitte.

   `flex-wrap` und `align-items: center`: am Telefon darf der Knopf unter
   die Ueberschrift rutschen, statt sie zusammenzuquetschen. */
.kopf-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem;
  min-width: 0;
}

/* --- Die Spaltenwahl im Fenster "Ansicht einstellen" (Paket 121) ---
   Eine Spalte je Kaestchen waere bei zwoelf Spalten eine sehr lange
   Liste; zwei bis drei nebeneinander, so viele wie hineinpassen. Kein
   `auto-fit`: bei EINER abwaehlbaren Spalte zoege es das eine Kaestchen
   ueber die ganze Fensterbreite (dieselbe Falle wie bei
   `.filterleiste__felder`).

   `minmax(0, …)` in der Spur, damit ein langer Titel ("Haltungsform")
   sie nicht aufschiebt - siehe Paket 119. */
.spaltenwahl__kopf {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin: 0 0 .5rem;
}
.spaltenwahl__kopf h4 { margin: 0; }
/* Das Kaestchen "alle" traegt seit Paket 122 auch die Ueberschrift -
   so wie im Vorbild ("Columns (16/43)"). Die Ueberschrift daneben ist
   der Rueckfall ohne Skript; eines von beiden steht immer da. */
.spaltenwahl__alle { display: flex; align-items: center; gap: .5rem; font-weight: 600; cursor: pointer; }
.spaltenwahl__alle [data-spaltenzahl] { font-weight: 400; color: var(--muted); }
.spaltenwahl__alle:not([hidden]) + .spaltenwahl__ohne-skript { display: none; }

/* --- Die Reiter im Fenster (Paket 122) ---------------------------------
   Spalten | Sortieren | Gruppieren. Ohne Skript sind sie versteckt und
   die drei Abschnitte stehen untereinander, jeder mit Ueberschrift.
   -------------------------------------------------------------------- */
.reiter {
  display: flex; gap: .2rem; margin: 0 0 .9rem;
  border-bottom: 1px solid var(--border);
}
.reiter__knopf {
  appearance: none; background: none; border: 0; cursor: pointer;
  padding: .5rem .8rem; font: inherit; font-weight: 600; color: var(--muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.reiter__knopf[aria-selected="true"] { color: var(--text); border-bottom-color: var(--accent); }
.dialog__teil { margin-bottom: .3rem; }
.dialog__teil > h4 { margin: 0 0 .6rem; }
/* Ohne Skript stehen die drei Abschnitte untereinander und brauchen
   jeder seine Ueberschrift. Sobald die Reiterleiste da ist, sagt der
   Reiter schon, wo man ist - die Ueberschrift daneben waere dasselbe
   Wort zweimal. */
.reiter:not([hidden]) ~ .dialog__teil > h4,
.reiter:not([hidden]) ~ .dialog__teil .spaltenwahl__ohne-skript { display: none; }

/* Die Sortierstufen im Fenster - je Zeile Spalte und Richtung
   nebeneinander, am Telefon untereinander. Sie hiessen bis Paket 122
   `.filterleiste__stufe`; der Aufbau ist derselbe geblieben. */
.sortierstufen { display: grid; gap: .55rem; }
.sortierstufe {
  display: grid; gap: .6rem;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
}
@media (max-width: 520px) {
  .sortierstufe { grid-template-columns: minmax(0, 1fr); }
}
.dialog__fuss {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: .5rem; margin-top: 1.1rem;
}
.dialog__fuss .btn-row { gap: .4rem; }
.spaltenwahl {
  display: grid; gap: .1rem;
  border: 1px solid var(--border); border-radius: 9px;
  padding: .25rem; margin-bottom: .2rem;
  max-height: 17rem; overflow-y: auto;
}
.spaltenwahl__zeile { padding: .35rem .45rem; border-radius: 6px; }
.spaltenwahl__zeile:hover { background: var(--surface-2, rgb(0 0 0 / .04)); }
.spaltenwahl__zeile {
  display: flex; align-items: center; gap: .45rem;
  min-width: 0;
}
.spaltenwahl__name {
  display: flex; align-items: center; gap: .45rem;
  min-width: 0; flex: 1 1 auto; cursor: pointer;
}
/* Die vier Pfeile stehen im Baum und sind unsichtbar, bis die Maus auf
   der Zeile steht (FH, 08.09.2026). `visibility` und nicht `display`:
   so behalten sie ihren Platz, und die Namen daneben springen beim
   Ueberfahren nicht.

   Am Telefon gibt es kein Ueberfahren - dort stehen sie immer da. Ein
   Bedienelement, das nur die Maus findet, waere sonst unerreichbar. */
.spaltenwahl__pfeile { display: flex; gap: .1rem; flex: none; visibility: hidden; }
.spaltenwahl__zeile:hover .spaltenwahl__pfeile,
.spaltenwahl__zeile:focus-within .spaltenwahl__pfeile { visibility: visible; }
@media (hover: none) {
  .spaltenwahl__pfeile { visibility: visible; }
}
.spaltenwahl__pfeile .btn { padding: .1rem .15rem; }
.spaltenwahl__pfeile .icon { width: 1em; height: 1em; }
.spaltenwahl__pfeile .btn[disabled] { opacity: .25; cursor: default; }
.spaltenwahl__zeile > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Die Pflichtspalten: angehakt, grau, nicht bedienbar. Sie stehen mit
   in der Liste, damit niemand sie dort sucht - und sehen an, dass an
   ihnen nichts zu entscheiden ist. */
.spaltenwahl__zeile--pflicht { color: var(--muted); cursor: default; }

/* Ein Strich im Fenster trennt zwei Entscheidungen ueber dieselbe
   Auswahl: freigeben an ein Konto, und fuer alle sichtbar. Ohne ihn
   liest sich der zweite Block als Fortsetzung des ersten - "fuer alle
   sichtbar" gaelte dann scheinbar fuer das oben gewaehlte Konto. */
.dialog__trenner {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 1.1rem 0 .9rem;
}
/* Am Handy nimmt das Fenster die ganze Breite und sitzt unten - dort
   ist der Daumen. */
@media (max-width: 619.98px) {
  .dialog:modal {
    inset: auto 0 0 0; margin: 0;
    width: 100%; max-height: 88dvh;
    border-radius: var(--radius) var(--radius) 0 0;
    border-bottom: 0;
    padding-bottom: calc(1.1rem + env(safe-area-inset-bottom));
  }
}

/* --- Eine ganze Karte zum Aufklappen --------------------------------
   FH, 2026-08-29, zum Teilen-Kasten am Rezept: "Die Box sollte aber
   aufklappbar sein und per default zugeklappt sein."

   Die Karte selbst ist ein Raster mit `gap` - steckt ihr Inhalt in
   einem <details>, ist das **ein** Rasterfeld, und die Abstaende
   darin waeren weg. Deshalb traegt das <details> dasselbe Raster.

   Die Kopfzeile bleibt eine Kopfzeile: dieselbe Ueberschrift wie in
   `.card__head`, davor der Winkel, der sich beim Aufklappen dreht. Das
   eigene Dreieck des Browsers faellt weg, sobald `summary` ein
   `display` bekommt - genau wie bei `.filterleiste__block`. */
/* Eine aufklappbare Karte. `display: grid` steht **nur im offenen
   Zustand** da.

   Der Grund ist der Abstand: als Raster gerechnet zaehlt das
   `<details>` den `gap` auch dann mit, wenn der Inhalt zugeklappt ist -
   die Karte war 36 px hoch statt 23, und unter der Zusammenfassung
   stand eine leere Zeile von 13,6 px. Der Inhalt selbst klappt richtig
   weg; es ist keine Karte, die offen bleibt, sondern eine, die eine
   Luecke behaelt.

   Gefunden am 2026-08-30, als die Routinenkarte dieses Muster
   uebernahm. Die "Teilen"-Karte am Rezept trug es seit ihrer
   Einfuehrung mit - dort faellt eine leere Zeile weniger auf. */
.karte-klapp { min-width: 0; }
.karte-klapp[open] { display: grid; gap: .85rem; }
.karte-klapp > summary {
  display: flex; align-items: center; gap: .5rem;
  cursor: pointer; list-style: none; min-width: 0;
}
.karte-klapp > summary::-webkit-details-marker { display: none; }
.karte-klapp > summary > .icon {
  width: .8em; height: .8em; flex: none; color: var(--muted);
  transform: rotate(-90deg); transition: transform .12s;
}
.karte-klapp[open] > summary > .icon { transform: rotate(0); }
.karte-klapp > summary:hover > .icon { color: var(--text); }
/* Der Zusatz rechts sagt im zugeklappten Zustand, ob es drinnen etwas
   gibt - sonst muesste man jede Karte einmal aufmachen, um zu sehen,
   dass nichts darin steht. */
.karte-klapp__zahl { margin-left: auto; color: var(--muted); font-size: .85rem; font-weight: 500; }

/* Aufklappbarer Block, z.B. die noch nicht gemessenen Messgrössen. */
.details > summary {
  cursor: pointer; font-size: .85rem; font-weight: 600; color: var(--muted);
  padding: .4rem 0;
}
.details > summary:hover { color: var(--text); }

/* ---------------------------------------------------------------------
   Die Werkzeuge hinter dem Namen einer Mahlzeit im Tagebuch.

   Der Text steht *neben* der Überschrift, das Feld klappt darunter auf -
   und zwar schwebend, damit die Karte nicht bei jedem Klick auseinander-
   springt. Auf schmalen Geräten wird es höchstens so breit wie das
   Fenster, sonst schöbe es die Seite auf.
   ------------------------------------------------------------------ */
.mahlzeit-kopf {
  display: flex; align-items: baseline; gap: .7rem; flex-wrap: wrap;
  flex: 1 1 auto; min-width: 0;
}
.mahlzeit-menue { position: relative; }
/* **Ohne Unterstreichung** (FH, 2026-09-01: "Keine Unterstreichungen").
   Sie stand hier, solange die drei Werkzeuge blosse Woerter waren und
   sonst nichts sie von der Ueberschrift daneben unterschied. Seit jedes
   sein Zeichen vorweg traegt, sagt das Zeichen, dass hier etwas zu
   bedienen ist - und ein unterstrichenes Wort sieht aus wie ein Link,
   der es nie war. */
.mahlzeit-menue > summary {
  display: inline-flex; align-items: center; gap: .3rem;
  cursor: pointer; font-size: .8rem; color: var(--muted);
  list-style: none;
}
.mahlzeit-menue > summary::-webkit-details-marker { display: none; }
.mahlzeit-menue > summary:hover { color: var(--text); }
.mahlzeit-menue > summary .icon { width: 1rem; height: 1rem; flex: none; }
.mahlzeit-menue__inhalt {
  position: absolute; z-index: 20; top: 100%; left: 0; margin-top: .4rem;
  width: 19rem; max-width: calc(100vw - 2.5rem);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: .9rem;
}
.mahlzeit-menue__inhalt .form { gap: .6rem; }

/* Auf dem Handy schwebt nichts: 19 rem passen neben die Überschrift
   nicht mehr aufs Bild, das Feld liefe rechts aus der Seite. Es klappt
   deshalb als eigene Zeile auf und schiebt die Tabelle nach unten -
   sichtbar statt abgeschnitten. */
@media (max-width: 699px) {
  .mahlzeit-menue[open] { flex: 1 0 100%; }
  .mahlzeit-menue__inhalt {
    position: static; width: auto; max-width: none; margin-top: .5rem;
  }
}

/* Am Telefon steht nur noch das Zeichen (FH, 2026-09-01). Drei Woerter
   neben "Mittagessen" brauchen 24 rem; am Telefon sind 22 da, und die
   Zeile brach mitten in der Reihe um - "als Routine" stand allein unter
   der Ueberschrift.

   Das Wort wird **geparkt und nicht entfernt**: dieselben fuenf Zeilen
   wie `.visually-hidden`. Es bleibt damit die Beschriftung des
   Klappfeldes fuer Vorleseprogramme und fuer die Suche im Browser.
   Dafuer waechst das Zeichen und bekommt eine Polsterung: sichtbar
   bleibt ein kleines Zeichen, antippbar sind 34 x 34 px - vorher waren
   es 19 px hohe Woerter (Punkt 15 des Pruefberichts vom 27.08.). Die
   40 px, die dort stehen, sind hier nicht zu haben: drei davon neben
   "Mittagessen" und "1987 kcal" gehen in 390 px nicht auf, und
   Trefferflaechen, die sich ueberlappen, waeren schlimmer als zu
   kleine.

   Der negative Aussenabstand nur waagerecht und nur klein: er holt
   die Polsterung so weit zurueck, dass die Zeichen noch aussehen wie
   eine Reihe - zoege er ganze .5 rem, ueberlappten sich die drei
   Flaechen, denn der Abstand der Reihe ist .7 rem. */
@media (max-width: 620px) {
  .mahlzeit-menue > summary { padding: .5rem; margin: -.5rem -.15rem; }
  .mahlzeit-menue > summary .icon { width: 1.15rem; height: 1.15rem; }
  .mahlzeit-menue__wort {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
}

/* Die Summenzeile einer Mahlzeit bekommt runde Ecken (FH, 2026-08-30) -
   dieselbe Rundung wie der Kasten darueber, damit beide zur selben
   Handschrift gehoeren. Sie ist die letzte Zeile der Tabelle, also
   rundet sie unten. */
table tfoot tr:last-child > th:first-child,
table tfoot tr:last-child > td:first-child { border-bottom-left-radius: .6rem; }
table tfoot tr:last-child > th:last-child,
table tfoot tr:last-child > td:last-child  { border-bottom-right-radius: .6rem; }

/* Der Kasten um eine Routine in der Mahlzeit (FH, 2026-08-30).
   ---------------------------------------------------------------------
   Kein Rahmen, sondern ein abgesetzter Grund: `border-collapse: collapse`
   legt die Rahmen benachbarter Zellen zusammen, und `border-radius`
   bleibt dort wirkungslos - ein Kasten mit eckigen Ecken war aber genau
   das, was nicht gewollt war ("um weicher zu wirken"). Ein Hintergrund
   nimmt den Radius auch bei collapse an.

   Die Rundung sitzt deshalb an den vier Ecken**zellen**: erste und
   letzte Spalte der Kopfzeile oben, dieselben in der letzten Zeile
   unten. */
/* **Die runden Ecken sind zurueck** (FH, 2026-09-02: "Routinen sollten
   oben und unten abgerundete Ecken und einen Rahmen haben. Design am
   iPhone UND im Browser gleichhalten").
   ---------------------------------------------------------------------
   Am 2026-08-30 mussten sie weg: "sonst haben wir oben und unten
   Kanten/Ecken". Der Befund stimmte, die Ursache lag aber nicht an der
   Rundung, sondern daran, dass der Kasten seine Nachbarn beruehrte.
   Jede Tabellenzeile zieht ihre eigene Trennlinie ueber die volle
   Breite - und die lief quer durch die runde Ecke. Was man sah, war
   also nicht eine schlecht gerundete Ecke, sondern eine gerade Linie
   dahinter.

   Deshalb diesmal in zwei Teilen: die Rundung **und** Luft ober- und
   unterhalb, in der die Linien der Nachbarzeilen wegfallen. Am Telefon
   steht der Kasten seit dem 2026-09-01 schon genau so (weiter unten,
   ab "Der Kasten einer Routine am Telefon") - dort ist die Zeile ein
   Flex-Kasten und traegt Rahmen und Radius von sich aus. Beide Seiten
   zeigen jetzt dasselbe Bild; `--routineecke` ist das eine Mass dafuer. */
:root { --routineecke: .6rem; }

.routinekopf > td {
  background: var(--surface-2);
  padding-block: .4rem .1rem;
  border-top-left-radius: var(--routineecke);
  border-top-right-radius: var(--routineecke);
}
tr.is-routine--ende > td:first-child { border-bottom-left-radius: var(--routineecke); }
tr.is-routine--ende > td:last-child  { border-bottom-right-radius: var(--routineecke); }

/* Die Luft. Sie kommt aus den **Nachbarzeilen** und nicht aus einem
   Rand am Kasten selbst: in einer Tabelle mit `border-collapse:
   collapse` gehoert die Kante zwischen zwei Zeilen keiner von beiden
   allein, ein Rand dort waere ein Streit um dieselbe Linie. Ein
   `padding` gehoert dagegen eindeutig einer Zelle.

   `:has(+ tr.routinekopf)` fuer die Zeile davor - CSS kennt keinen
   Blick zurueck, und ein Kasten steht selten als erstes in einer
   Mahlzeit. Nach unten genuegt `tr.is-routine--ende + tr`: die letzte
   Zeile des Kastens bleibt auch dann seine letzte Zeile, wenn er
   zugeklappt ist (dann ist sie `hidden`, aber immer noch die
   Schwester, an der der Nachbar haengt). */
tbody tr:has(+ tr.routinekopf) > th,
tbody tr:has(+ tr.routinekopf) > td { padding-bottom: 1rem; }
tbody tr + tr.routinekopf > td { border-top: 0; }
/* Steht der Kasten als **erstes** in der Mahlzeit, gibt es keine Zeile
   darueber, die Luft hergeben koennte - darueber steht der
   Tabellenkopf, und dessen Trennstrich ist ein `inset`-Schatten an
   seiner eigenen Unterkante (weiter oben, "Der Kopf bleibt stehen"):
   mehr Polster im Kopf schoebe den Strich mit nach unten, statt Platz
   darunter zu schaffen.
   Also hier ein **Rand in der Farbe der Karte** - dieselbe Loesung wie
   bei der Summenzeile. Der aeussere Radius muss dabei um die Randbreite
   groesser sein als der gewuenschte: der Radius gilt der Aussenkante,
   und sichtbar wird die Innenkante. */
tbody tr:first-child.routinekopf > td {
  border-top: .5rem solid var(--surface);
  border-top-left-radius: calc(var(--routineecke) + .5rem);
  border-top-right-radius: calc(var(--routineecke) + .5rem);
}
tbody tr.is-routine--ende + tr > th,
tbody tr.is-routine--ende + tr > td { border-top: 0; padding-top: 1rem; }
/* Die Posten selbst behalten den Grund der Karte (FH, 2026-08-30:
   "weiss bzw. bei einem dunklen Theme dunkel" - `--surface` ist genau
   das, sie wechselt mit dem Thema).

   Ueberlegt war zuerst ein abgesetzter Grund wie beim Kopf. Der Rahmen
   sagt aber schon, wo der Kasten anfaengt und aufhoert; ihn
   einzufaerben sagte dasselbe ein zweites Mal - und die Posten einer
   Routine sind gewoehnliche Eintraege, keine andere Art von Zeile.
   Nebenbei entfaellt damit die Sonderregel fuer die klebende
   Aktionsspalte: die traegt ohnehin diesen Grund, und genau ihr eigener
   riss vorher ein weisses Loch in die rechte Kante. */
tr.is-routine > td { background: var(--surface); }

/* Und ein Rahmen darum (FH, 2026-08-30: "nur einen Rahmen um die
   Routine drum").
   ---------------------------------------------------------------------
   Nicht als `border`: die Tabelle steht auf `border-collapse: collapse`,
   dort legt der Browser die Rahmen benachbarter Zellen zusammen und
   ignoriert `border-radius` - der Rahmen haette also eckige Ecken um
   einen runden Grund. Ein **innen liegender Schatten** folgt dagegen dem
   Radius der Zelle.

   Zusammengesetzt wird er aus den Randzellen: der Kopf traegt oben,
   links und rechts, die Zeilen dazwischen nur links und rechts, die
   letzte zusaetzlich unten. `box-shadow` ist eine Eigenschaft und keine
   Liste, die sich sammelt - jede Regel nennt deshalb alle Kanten, die
   an dieser Zelle liegen.

   Und eine eigene Farbe: `--border` (#dcd5c9) gegen den Grund des
   Kastens (#ebe6de) sind zwei Toene, die wenige Stufen auseinander
   liegen - der Rahmen war da und doch nicht zu sehen. Ein Drittel
   `--muted` dazu gibt ihm gerade so viel Gewicht, dass er den Kasten
   fasst, ohne ihn einzusperren. */
/* Die Farbe haengt an der **Zeile** und nicht mehr an der Zelle: die
   Zellen erben sie ohnehin, und am Telefon (weiter unten) traegt den
   Rahmen die Zeile selbst. */
.routinekopf, tr.is-routine {
  --routinerahmen: color-mix(in srgb, var(--border) 65%, var(--muted));
}
.routinekopf > td {
  box-shadow: inset 0 1px 0 var(--routinerahmen),
              inset 1px 0 0 var(--routinerahmen),
              inset -1px 0 0 var(--routinerahmen);
}
tr.is-routine > td:first-child { box-shadow: inset 1px 0 0 var(--routinerahmen); }
tr.is-routine > td:last-child  { box-shadow: inset -1px 0 0 var(--routinerahmen); }
tr.is-routine--ende > td:first-child {
  box-shadow: inset 1px 0 0 var(--routinerahmen), inset 0 -1px 0 var(--routinerahmen);
}
tr.is-routine--ende > td:last-child {
  box-shadow: inset -1px 0 0 var(--routinerahmen), inset 0 -1px 0 var(--routinerahmen);
}
/* Die Zellen dazwischen brauchen nur den unteren Strich der letzten
   Zeile - links und rechts liegt dort keine Kante. */
tr.is-routine--ende > td { box-shadow: inset 0 -1px 0 var(--routinerahmen); }

/* Zugeklappt besteht der Kasten nur noch aus seinem Kopf - dann rundet
   der unten mit und traegt den Strich, den sonst die letzte Zeile
   traegt. Die Klasse setzt das Skript; ohne Skript klappt nichts, und
   dann steht sie nie da. */
tr.routinekopf.is-zu > td {
  box-shadow: inset 0 1px 0 var(--routinerahmen),
              inset 1px 0 0 var(--routinerahmen),
              inset -1px 0 0 var(--routinerahmen),
              inset 0 -1px 0 var(--routinerahmen);
  padding-block: .4rem;
  border-radius: var(--routineecke);
}

/* Der Schalter ist die ganze Beschriftung, nicht ein Zeichen daneben:
   eine Trefferflaeche von zwei Buchstaben Breite ist am Telefon keine. */
.routinekopf__schalter {
  display: inline-flex; align-items: center; gap: .3rem;
  background: none; border: 0; padding: .15rem .2rem; margin: -.15rem -.2rem;
  cursor: pointer; color: inherit; font: inherit;
}
.routinekopf__schalter .icon { transition: transform .15s ease; }
/* Der Papierkorb sitzt rechts im Kopf. Die Zelle ist eine
   colspan-Zelle ueber die ganze Breite; ohne `float`/Flex staende der
   Knopf direkt hinter dem Namen. `margin-left: auto` in einer
   Flex-Zelle waere sauberer - die Zelle ist aber keine, und sie zu
   einer zu machen hiesse, die Zeilenhoehe der Tabelle aufzugeben. */
.routinekopf > td { position: relative; }
.routinekopf__weg { position: absolute; right: .6rem; top: 50%; transform: translateY(-50%); }

/* Der Papierkorb im Kopf ist **kein** `.btn` (FH, 2026-08-30: "So ist
   der Button zu gross").
   ---------------------------------------------------------------------
   Ein `.btn--sm` bringt `min-height: var(--feldhoehe-klein)` mit, also
   34 px - in einer Kopfzeile, deren Schrift .72 rem misst, ist das
   hoeher als die Zeile selbst. Der Knopf sah aus wie ein Fremdkoerper,
   und die Zeile wuchs an ihm.

   Also derselbe Bau wie beim Klappschalter daneben: durchsichtig, ohne
   Rahmen, in der Groesse seines Zeichens. Die Trefferflaeche kommt aus
   der Polsterung, die ein negativer Aussenabstand wieder herauszieht -
   sichtbar ist ein kleines Zeichen, antippbar eine Flaeche von
   fast 2 rem.

   Gedaempft und erst beim Zeigen rot: der Kasten ist eine Auskunft, kein
   Formular, und ein rotes Zeichen an jeder Routine zoege den Blick auf
   das Loeschen statt auf das, was dasteht. */
.routinekopf__wegknopf {
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; cursor: pointer; line-height: 0;
  padding: .35rem; margin: -.35rem;
  color: var(--muted); transition: color .12s;
}
.routinekopf__wegknopf:hover,
.routinekopf__wegknopf:focus-visible { color: var(--bad); }
.routinekopf__wegknopf .icon { width: .95rem; height: .95rem; }
/* Offen zeigt der Winkel nach unten, zugeklappt nach rechts - dieselbe
   Richtung wie bei den Klappkarten. */
.routinekopf__schalter[aria-expanded="true"] .icon { transform: rotate(0deg); }
/* Keine Linie zwischen Beschriftung und erster Zeile: sie gehoeren
   zusammen. Zwischen den Posten laeuft die gewoehnliche Trennlinie
   weiter - sie ordnet den Kasten, statt ihn in Streifen zu zerlegen. */
tbody tr.routinekopf + tr > td { border-top: 0; }

/* Der Name im Kopf des Kastens: klein, gedaempft, mit dem Winkel davor -
   dasselbe Zeichen, das auch eine Gruppe aufklappt. Es sagt "das hier
   gehoert zusammen", ohne ein eigenes Symbol dafuer zu erfinden. */
.routinemarke {
  display: inline-flex; align-items: center; gap: .3rem;
  font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); font-weight: 600;
}
.routinemarke .icon { width: .9rem; height: .9rem; transform: rotate(-90deg); flex: none; }

/* Das Abzeichen "geaendert" am Kastenkopf (03.09.2026, FHs Antwort b).
   `.badge` bringt seine eigene Grosschreibung und Sperrung mit, und die
   sind hier schon da - zweimal angewendet wuerde daraus ein zu weit
   gesperrtes Wort. `margin-left` faellt weg, weil der Kopf ein Flex mit
   `gap` ist. */
.routinemarke .badge {
  text-transform: none; letter-spacing: 0; margin-left: 0;
  font-size: .68rem; flex: none;
}

/* Ein angetasteter Kasten steht genauso da wie ein unberuehrter - nur
   das Abzeichen sagt den Unterschied. Ein zweiter Rahmen oder ein
   anderer Grund waere eine Warnung, und es ist keine: dass jemand einen
   Posten berichtigt hat, ist der Normalfall und nicht der Fehlerfall.
   Die Klasse steht trotzdem am `<tr>` - fuer die Pruefung, und damit
   der Tag, an dem doch etwas anders aussehen soll, keinen Umbau
   braucht. */

/* Routinen: Name links, Ziel und Knopf rechts. */
.routinen { list-style: none; display: grid; gap: .55rem; }
.routine {
  display: flex; align-items: center; gap: .7rem; flex-wrap: wrap;
  padding: .5rem .1rem; border-top: 1px solid var(--border);
}
.routine:first-child { border-top: 0; }

/* --- Routinen, die von allein kommen (FH, 2026-08-30) --------------- */

/* Was sich beim Aufschlagen eingetragen hat. Keine Fehlerfarbe und
   keine Warnfarbe: es ist eine Auskunft ueber etwas, das genau so
   gewollt war - aber sie muss stehen, sonst faende jemand Posten vor,
   die er nicht eingetragen hat. */
.routinen__gemeldet { margin: 0 0 .3rem; }

/* Die nicht faelligen hinter einem Klick. Dieselbe Machart wie die
   Karte darum herum, nur eine Ebene kleiner - deshalb `.karte-klapp`
   nicht wiederverwendet: zwei ineinander verschachtelte Klappfelder
   mit derselben Groesse saehen aus wie ein Fehler. */
.routinen__weitere { margin-top: .5rem; border-top: 1px solid var(--border); padding-top: .5rem; }
.routinen__weitere > summary {
  display: flex; align-items: center; gap: .4rem; cursor: pointer;
  font-size: .85rem; color: var(--muted); list-style: none;
}
.routinen__weitere > summary::-webkit-details-marker { display: none; }
.routinen__weitere > summary .icon { width: .9rem; height: .9rem; transition: transform .15s; }
.routinen__weitere[open] > summary .icon { transform: rotate(90deg); }
.routinen__weitere > .routinen { margin-top: .4rem; }

/* Die sieben Kaestchen im Routinenformular. Eine Reihe, die umbricht -
   am Telefon stehen dann vier oben und drei darunter, und das ist
   besser als sieben Zeilen untereinander. */
.wochentagwahl {
  list-style: none; margin: .25rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .2rem .9rem;
}
/* Die Abkuerzung traegt den vollen Namen im `title`. Ohne diese Zeile
   malt der Browser eine gepunktete Linie darunter, und sieben davon
   nebeneinander sehen aus wie Fehler. */
.wochentagwahl abbr { text-decoration: none; }
/* `flex: 1 1 12rem` und nicht `margin-right: auto` mit voller Breite:
   die Zeile bricht um (`.routine` ist ein `flex-wrap`), und ohne eine
   Grundbreite waere der Name so breit wie sein laengster Satz - seit
   dort "Heute gelaufen, steht aber nicht mehr im Tag" stehen kann,
   schob er den Loeschknopf auf eine eigene Zeile. Mit Grundbreite
   bricht stattdessen der Satz um, und die Knoepfe bleiben beisammen. */
.routine__name { display: grid; gap: .1rem; flex: 1 1 12rem; min-width: 0; }
.routine__name .muted { font-size: .8rem; }

/* Ob die Routine an diesem Tag schon gelaufen ist (FH, 2026-08-31:
   "koennen wir eine routine resetten?").

   Keine Farbe fuer den Normalfall: dass eine Routine dasteht, ist kein
   Ereignis, sondern der gewollte Zustand - eine gruene Zeile daneben
   waere ein Lob fuer nichts. Der zweite Fall dagegen bekommt --warn und
   kein --bad: nichts ist kaputt, es ist bloss anders, als man denkt.

   `justify-self: start`, weil das Elternteil ein Grid ist: ohne die
   Zeile zoege sich der Rahmen des Zeichens ueber die ganze Spalte, und
   der Haken staende weit links von seinem Satz. */
.routine__stand {
  justify-self: start;
  display: inline-flex; align-items: baseline; gap: .3rem;
  font-size: .78rem; color: var(--muted);
}
.routine__stand::before { content: "\2713"; font-weight: 700; }
.routine__stand--weg { color: var(--warn); }
.routine__stand--weg::before { content: "\0021"; }

/* Feld und Knopf nebeneinander, kleine Feldhoehe. Hiess bis zum
   2026-08-30 `.vorlage__anwenden` und damit nach der Karte, in der sie
   zuerst gebraucht wurde - die Anmeldeseite benutzt dieselbe Anordnung
   fuer die Passkey-Namen, und dort waere der Name schlicht falsch
   gewesen. Eine Anordnung heisst nach dem, was sie tut. */
.feldzeile { display: flex; align-items: center; gap: .4rem; }
.routine__anwenden select { max-width: 10rem; }
/* Der Knopf traegt seit dem 2026-08-31 bis zu zwei Woerter und bleibt
   trotzdem auf einer Zeile: „Nochmal / eintragen" umgebrochen sieht aus
   wie zwei Knoepfe. */
.routine__anwenden button { white-space: nowrap; }

/* Am Telefon bekommt das Paar aus Mahlzeit und Knopf eine eigene Zeile.
   Sonst bliebe fuer das Auswahlfeld daneben so wenig uebrig, dass aus
   „Frühstück" ein „Frühs…" wird - und die Mahlzeit ist das eine Feld,
   das man vor dem Druecken lesen muss. */
@media (max-width: 520px) {
  .routine__anwenden { flex: 1 0 100%; flex-wrap: wrap; }
  .routine__anwenden select { max-width: none; flex: 1 1 100%; }
}

/* Das Formular einer Routine (FH, 2026-08-30: "Vorlagen muessen
   aenderbar sein"). Dieselbe Ausrichtung wie in der
   Schlagwortverwaltung: Kopfzeilen ueber ihren Feldern, weil hier
   Bedienelemente stehen und keine Zahlen - die allgemeine Regel
   "alles ausser der ersten Spalte nach rechts" waere hier falsch. */
.routinentabelle th, .routinentabelle td { text-align: left; }
.routinentabelle th.is-kasten, .routinentabelle td.is-kasten { text-align: center; }
/* Menge und Einheit gehoeren zusammengelesen: "80 g" ist eine Angabe,
   nicht zwei. Das Feld ist deshalb nur so breit, wie eine Menge wird.
   Ueber `max-width` und nicht ueber `width`: die allgemeine Feldregel
   setzt `width: 100%` und hat neun `:not()` im Selektor - dagegen
   kommt keine Klasse an, ohne dass man `!important` schreibt oder eine
   Kette aus zehn Klassen baut. Eine Hoechstbreite kollidiert damit gar
   nicht erst; die Schlagwortverwaltung macht es bei ihren
   Auswahlfeldern genauso. */
.routinenmenge { display: flex; align-items: baseline; gap: .35rem; white-space: nowrap; }
.routinenmenge input { max-width: 5.5rem; text-align: right; }
.routinentabelle input[type="text"] { max-width: 16rem; }

/* Eine Zeile des Kassenbons, so wie sie dastand. Zweierlei Gebrauch:
   unter einem Posten als Herkunft, und für sich allein als das, was
   der Zerleger liegengelassen hat — dann durchgestrichen.

   Monospace, weil ein Kassenbon in Spalten gedruckt ist: mit
   Proportionalschrift rutschen Name und Preis ineinander und man
   erkennt die Zeile nicht wieder. `overflow-wrap: anywhere`, weil eine
   Bonzeile am Handy sonst die Seite aufschiebt. */
.bonzeile {
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  margin: .15rem 0 .55rem; font-size: .78rem; color: var(--muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.bonzeile__nr {
  flex: 0 0 auto; min-width: 1.6rem; text-align: right;
  opacity: .7; font-variant-numeric: tabular-nums;
}
.bonzeile__text { min-width: 0; overflow-wrap: anywhere; white-space: pre-wrap; }
.bonzeile--aus { padding: .25rem 0; border-top: 1px solid var(--border); }
.bonzeile--aus .bonzeile__text { text-decoration: line-through; }
/* Die Herkunft steht **über** ihrem Posten und rückt an ihn heran — sie
   gehört dazu und ist keine eigene Zeile der Liste. */
.bonzeile--her { margin: .5rem 0 .2rem; padding-left: .2rem; }

/* Angefangene Kochvorgänge auf der Startseite. Derselbe Aufbau wie die
   Routinen im Tagebuch: Name und Nebensatz links, die Knöpfe rechts,
   und am Handy bricht die Zeile um. Eine eigene Klasse, weil hier kein
   Auswahlfeld dazwischensteht — sonst wäre es dieselbe. */
.kueche { list-style: none; display: grid; gap: .55rem; }
.kueche__zeile {
  display: flex; align-items: center; gap: .7rem; flex-wrap: wrap;
  padding: .5rem .1rem; border-top: 1px solid var(--border);
}
.kueche__zeile:first-child { border-top: 0; }
.kueche__was { display: grid; gap: .1rem; margin-right: auto; min-width: 0; }
.kueche__was .muted { font-size: .8rem; }

/* ---------------------------------------------------------------------
   Fotos vom Essen, über der Tabelle einer Mahlzeit.

   Klein als Streifen, damit die Zahlen darunter sichtbar bleiben. Gross
   wird ein Bild **auf Klick**, in derselben Lupe wie das Bild eines
   Lebensmittels - nicht beim Vorbeifahren mit der Maus und nicht in
   einem neuen Reiter.

   Der Löschknopf steht unter dem Bild und ist immer da. Vorher lag er
   halbdurchsichtig in der Bildecke und tauchte erst beim Zeigen auf:
   auf dem Bild kaum zu erkennen, auf dem Handy gar nicht zu finden.
   ------------------------------------------------------------------ */
.essensfotos { display: flex; flex-wrap: wrap; gap: .8rem; }
.essensfoto {
  position: relative; margin: 0; width: 8rem;
  display: grid; gap: .3rem; justify-items: center;
  /* Der Knopf steht als letztes und soll bei allen Bildern einer Reihe
     auf derselben Höhe sitzen — auch wenn eines eine Bildunterschrift
     hat und das daneben nicht. Die Kacheln sind im Flex-Streifen ohnehin
     gleich hoch; `space-between` schiebt den Knopf an deren Unterkante.

     Seit dem 2026-08-28 steht unter dem Bild nichts mehr (FH: die
     Unterschrift sagte, was auf dem Bild zu sehen war). Die Regel
     bleibt trotzdem: sie hielt auch vorher schon die Kachel zusammen,
     wenn eine Notiz fehlte - und das war der haeufige Fall. */
  align-content: space-between;
}
.essensfoto .bildknopf {
  border-radius: var(--radius); overflow: hidden;
  border: 1px solid var(--border); background: var(--surface-2);
}
.essensfoto img { display: block; width: 8rem; height: 8rem; object-fit: cover; }

/* Der Löschknopf sitzt oben rechts in der Bildecke: ein kleiner Kreis
   mit einem X, der auftaucht, sobald man über das Foto fährt (FH,
   07.09.2026). Vorher war es ein Mülleimer, und der stand dauerhaft da.

   **Warum das diesmal aufgeht.** Am 2026-08-28 lag hier schon einmal
   ein Knopf, der erst beim Zeigen kam - halbdurchsichtig, "auf dem Bild
   kaum zu erkennen, auf dem Handy gar nicht zu finden". Beide Gründe
   sind abgeräumt: der Kreis ist voll gefüllt und wirft einen Schatten,
   und auf einem Gerät ohne Zeigegerät steht er dauerhaft (siehe unten).

   `opacity` und nicht `display` oder `visibility`: der Knopf bleibt in
   der Tastaturreihenfolge, und `:focus-within` holt ihn hervor. Wer
   ohne Maus arbeitet, käme sonst nie an ihn heran. */
.essensfoto__weg {
  position: absolute; top: .35rem; right: .35rem; z-index: 2;
  opacity: 0; transition: opacity .12s ease-in-out;
}
.essensfoto:hover .essensfoto__weg,
.essensfoto:focus-within .essensfoto__weg { opacity: 1; }
.essensfoto__weg .btn {
  /* "Ein kleiner Kreis": Masse ausgeschrieben statt aus Polsterung
     plus Zeichengroesse zusammengerechnet - `.btn--icon` bringt die
     Feldhoehe mit, und die ist fuer ein Vorschaubild von 8rem zu
     gross (34 gemessene Punkte auf 126, also gut ein Viertel der
     Kante). 1.6rem sind knapp ein Fuenftel und lassen vom Foto
     etwas uebrig. */
  width: 1.6rem; height: 1.6rem; min-height: 0; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px;
  background: var(--surface); color: var(--bad);
  border: 1px solid var(--border); box-shadow: 0 2px 8px rgb(0 0 0 / .35);
}
.essensfoto__weg .btn:hover { background: var(--bad); color: #fff; border-color: var(--bad); }
.essensfoto__weg .icon { width: .8rem; height: .8rem; }

/* Ohne Zeigegerät gibt es kein "sobald man darüberfährt" - dort steht
   der Kreis dauerhaft. Genau daran ist die Fassung vom 2026-08-28
   gescheitert.

   Gemessen wird `hover`, nicht die Breite: ein schmales Fenster am
   Rechner hat eine Maus, ein Tablet im Querformat keine. */
@media (hover: none) {
  .essensfoto__weg { opacity: 1; }
}


/* Die zwei Zahlen zum Abnehmen stehen immer nebeneinander - auch am
   Handy. `auto-fit` laesst eine einzelne Zahl die ganze Breite nehmen;
   `minmax(0, 1fr)` verhindert, dass ein langer Text die Spalte
   aufschiebt. */
.gewicht-bilanz {
  display: grid; gap: .6rem;
  grid-template-columns: repeat(auto-fit, minmax(min(140px, 100%), 1fr));
}

/* --- Meilensteine ----------------------------------------------------
   Eine Zeile je Marke: Symbol, Gewicht, wofür sie steht, wie weit es
   noch ist. Der nächste offene ist hervorgehoben - er ist die einzige
   Zahl, die heute etwas bedeutet. Erreichte werden nicht durchgestrichen:
   sie sind das Ergebnis, kein erledigter Punkt.
   -------------------------------------------------------------------- */
.meilensteine { list-style: none; display: grid; gap: .1rem; }
/* Alles linksbuendig (FH, 2026-08-26): die letzte Spalte nimmt den
   Rest, statt den Stand an den rechten Rand zu schieben. Zwischen
   "169,9 kg" und "seit 07.08.2025" lag sonst eine handbreite Luecke,
   und das Auge musste sie jedes Mal ueberspringen. */
.meilenstein {
  display: grid; align-items: baseline; gap: .1rem .7rem;
  grid-template-columns: auto auto auto 1fr;
  padding: .45rem .55rem; border-radius: 9px; font-size: .9rem;
}
.meilenstein__marke { display: inline-flex; color: var(--muted); align-self: center; }
.meilenstein__marke .icon { width: 1.05rem; height: 1.05rem; }
.meilenstein__wert { font-weight: 650; font-variant-numeric: tabular-nums; white-space: nowrap; }
.meilenstein__text { display: grid; gap: .1rem; min-width: 0; overflow-wrap: anywhere; }
.meilenstein__stand { color: var(--muted); font-size: .82rem; white-space: nowrap; }

.meilenstein.is-erreicht .meilenstein__marke { color: var(--siegel); }
.meilenstein.is-naechster {
  background: var(--surface-2);
  outline: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
}
.meilenstein.is-naechster .meilenstein__stand { color: var(--text); font-weight: 600; }

/* Das Zielgewicht ist der letzte in der Reihe und steht etwas fester da
   als die Zwischenstände - es ist ja der eigentliche Punkt. */
.meilenstein.is-ziel { border-top: 1px solid var(--border); margin-top: .25rem; padding-top: .6rem; }
.meilenstein.is-ziel .meilenstein__wert { color: var(--accent-strong); }

/* Am Handy faellt "Unter 170,0 kg" weg: es sagt dasselbe wie die Zahl
   davor, brauchte aber zwei Zeilen und schob den Stand nach unten. Beim
   Zielgewicht bleibt es stehen - dort steht kein zweites Mal dieselbe
   Zahl, sondern wofuer die Marke ueberhaupt gilt. */
@media (max-width: 620px) {
  .meilenstein:not(.is-ziel) .meilenstein__text { display: none; }
  .meilenstein { grid-template-columns: auto auto 1fr; }

  /* Beim Zielgewicht bleibt der Text - er passt aber nicht neben Wert
     und Stand und wuerde zu "Zielge / wicht" brechen. Also nimmt er
     eine eigene Zeile unter der Zahl. */
  .meilenstein.is-ziel .meilenstein__text { grid-column: 2 / -1; grid-row: 2; }
}

/* Auf der Pflegeseite steht dieselbe Hervorhebung in der Tabellenzeile. */
tr.is-naechster > td { background: var(--surface-2); }

/* Die Rechnung im Profil: links wofür, rechts wie viel. Keine Tabelle -
   es sind Aussagen, keine Spalten. */
.details-block { border-top: 1px solid var(--border); padding-top: .9rem; }

/* Die Werte eines Ad-hoc-Eintrags: am Handy zwei Spalten, ab 620 px
   alle fünf nebeneinander. `auto-fit` mit einer Mindestbreite käme mit
   einem Rest von zwei Feldern in der letzten Zeile heraus; die feste
   Zahl je Stufe hält die Zeile ganz. `minmax(0, 1fr)`, weil ein
   Eingabefeld sonst seine eigene Mindestbreite durchsetzt und das
   Raster aufschiebt. */
.adhoc-werte {
  display: grid; gap: .55rem .7rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
/* Vier Werte, seit das Gewichtsfeld weg ist (FH, 2026-08-28). Mit
   fuenf Spalten blieb die letzte leer und riss ein Loch neben "Fett", in
   dem der Eintragen-Knopf haette stehen koennen. */
@media (min-width: 620px) {
  .adhoc-werte { grid-template-columns: repeat(4, minmax(3.5rem, 1fr)); }
}
/* Zahlen stehen rechtsbündig - wie überall, wo Mengen untereinander
   stehen. Die Beschriftung bleibt links, sonst wanderte sie mit. */
.adhoc-werte input { text-align: right; font-variant-numeric: tabular-nums; }
/* Nur zu lesen: die Zahlen einer KI-Schätzung. Sie sollen als Angabe
   dastehen und nicht als Einladung, daran zu drehen — deshalb gedeckte
   Fläche statt Feldweiss und ein Rahmen, der sich zurücknimmt.

   Absichtlich **kein** `disabled`: ein gesperrtes Feld schickt seinen
   Wert nicht mit, und der Eintrag käme ohne Nährwerte in der Datenbank
   an.

   **Die neun `:not()` sind kein Zierrat.** Die allgemeine Feldregel
   weiter oben trägt sie und kommt damit auf eine Spezifität, die keine
   einzelne Klasse schlägt — die erste Fassung dieser Regel blieb
   deshalb wirkungslos, und die Felder sahen im Bild aus wie jedes
   andere. Hier steht dieselbe Kette noch einmal, damit sie gewinnt.
   Nachgemessen im Browser, nicht angenommen. */
.adhoc-werte input[readonly]:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=file]):not([type=range]):not([type=color]):not([type=image]) {
  background: var(--surface-2);
  border-color: var(--surface-2);
  color: var(--muted);
  cursor: default;
}
.rechnung { list-style: none; display: grid; gap: .3rem; }
.rechnung > li {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; font-size: .9rem;
}
.rechnung > li > span { color: var(--muted); min-width: 0; }
.rechnung > li > strong { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* Linksbuendig statt auseinandergezogen (FH, 2026-08-26, zu "Heute
   gemessen"): Bezeichnung und Wert stehen nebeneinander, die Werte
   beginnen alle an derselben Stelle. Bei acht Zeilen mit kurzen
   Bezeichnungen las sich das Auseinanderziehen wie eine Preisliste. */
.rechnung--links > li {
  display: grid; grid-template-columns: minmax(0, 9rem) auto;
  justify-content: start; gap: .2rem .9rem;
}

/* Das Geprüft-Siegel hinter einem Namen in der Liste. Kein eigenes Grün,
   sondern derselbe Akzent wie beim Abzeichen in der Ansicht - die Farben
   dieser Anwendung sind warmes Grau, und Rot bleibt schlechten Werten
   vorbehalten. */
.siegel {
  display: inline-flex; vertical-align: -.16em; margin-left: .3rem;
  color: var(--siegel);
}
.siegel .icon { width: 1.05em; height: 1.05em; stroke-width: 1.9; }
/* `display: inline-block` und nicht bloss `vertical-align`: svg steht
   global auf `display: block` (ganz oben in dieser Datei), und ein
   Blockkasten im Abzeichen schiebt das Wort in eine zweite Zeile -
   `white-space: nowrap` haelt das nicht auf. Aufgefallen an "von Mira"
   in der Rezeptliste (02.09.); die Abzeichen in Kopfzeilen standen
   richtig, weil dort eine andere Regel das svg schon zurueckgeholt
   hatte. */
.badge .icon { display: inline-block; width: .95em; height: .95em; margin-right: .25em; vertical-align: -.14em; }
/* Ein Abzeichen, das nur aus dem Symbol besteht (die Schlagworte in der
   Rezeptliste): kein Abstand nach rechts, weil dort kein Wort mehr
   folgt, und etwas groesser - es traegt jetzt die ganze Aussage. */
/* Ein Abzeichen, das nur aus dem Symbol besteht (die Schlagworte in der
   Rezeptliste). Groesse in `rem` und nicht in `em`: das Abzeichen setzt
   seine Schrift auf .68rem, und 1.1em davon waeren zwoelf Pixel. Genau
   die hat FH am 29.08.2026 gesehen - "so erkennt man sie gar nicht",
   und das stimmt: eine Schneeflocke aus fuenf Strichen ist dort ein
   Fleck. */
.badge--symbol {
  display: inline-flex; align-items: center; justify-content: center;
  padding: .22rem .36rem;
}
/* Die Verwaltung: Vorschau neben dem Auswahlfeld, und die Schau aller
   Symbole darunter. Feste Breite fuer die Vorschau, damit die Spalte
   nicht springt, wenn jemand "keines" waehlt. */
.schlagwortzeile { display: flex; align-items: center; gap: .5rem; }
.schlagwortzeile__bild {
  flex: none; width: 1.9rem; height: 1.9rem;
  display: inline-flex; align-items: center; justify-content: center;
}
.schlagwortzeile__bild .icon { width: 1.6rem; height: 1.6rem; stroke-width: 1.5; }
/* Die Legende als Tabelle: Symbol, Bezeichnung (FH, 29.08.2026).
   Davor war es eine fliessende Reihe - zwoelf Zeichnungen und zwoelf
   Woerter abwechselnd, und man las nicht mehr, was zu was gehoert -,
   dann eine Liste mit Spiegelstrichen. Zwei Spalten sagen dasselbe
   ohne jedes Zeichen davor: die Zuordnung steht in der Zeile. */
/* Schmal und linksbuendig, gegen zwei Regeln der allgemeinen Tabelle:
   `width: 100%` und "alle Spalten ausser der ersten rechts". Beide sind
   fuer Zahlenspalten gedacht; hier trieben sie Zeichnung und Wort an
   die entgegengesetzten Enden der Karte, und die Zuordnung, um die es
   geht, war quer ueber die Seite zu lesen. */
.symbolschau { width: auto; margin: .4rem 0 0; }
.symbolschau th, .symbolschau td { text-align: left; padding: .25rem 1.2rem .25rem 0; }
.symbolschau td:first-child, .symbolschau th:first-child { width: 1px; padding-right: .8rem; }

/* Fuenf Spalten mit Bedienelementen sind mehr, als eine Tabelle sich
   selbst einteilen kann: ohne Mindestbreiten quetschte sie die
   Auswahlfelder auf "Tropfer" und "- die d" zusammen. Lieber breiter
   als der Bildschirm - `.table-wrap` rollt seitwaerts, und ein
   abgeschnittenes Wort in einem Auswahlfeld tut das nicht. */
/* Seit dem 2026-08-30 stehen drei Spalten mehr in der Zeile (die
   beiden Kaestchen "wo wird es angeboten" und die Zaehlung). Damit lief
   die Tabelle 109 Punkte ueber den Rahmen hinaus - sie scrollt zwar,
   aber gerade die neuen Kaestchen lagen dann ausserhalb. Statt eine
   Spalte wegzulassen sind die Auswahlfelder etwas schmaler: elf
   Zeichen reichen fuer "Milchkanne durc…", und der ganze Name steht im
   Feld, sobald man es aufklappt. */
.schlagworttabelle select { min-width: 8.5rem; max-width: 11rem; }
.schlagworttabelle input[type="text"] { min-width: 9rem; }
.schlagworttabelle input[type="number"] { width: 5rem; }

/* Die Kopfzeilen stehen ueber ihren Feldern (FH, 2026-08-30: "die
   Positionen bei den Schlagwort Spaltenheadern"). Die allgemeine
   Tabellenregel stellt alles ausser der ersten Spalte nach rechts -
   richtig fuer Zahlen, falsch hier: in dieser Tabelle stehen
   Bedienelemente, und die beginnen am linken Rand ihrer Zelle, weil
   sie Blockelemente sind. Kopf und Feld liefen deshalb auseinander,
   und zwar umso weiter, je breiter die Spalte gegenueber ihrem Feld
   war - am auffaelligsten bei "Symbol", wo links noch die Vorschau
   steht, und bei "Reihenfolge", dessen elf Buchstaben die Spalte
   doppelt so breit machen wie das Zahlenfeld darunter. */
.schlagworttabelle th, .schlagworttabelle td { text-align: left; }
/* Zwei Ausnahmen. Die Kaestchen sind schmaler als ihre Ueberschrift;
   dort ist Mitte ueber Mitte das Buendige, links waere das Kaestchen
   unter dem "R" von "Rez." und der Rest der Spalte leer. */
.schlagworttabelle th.is-kasten, .schlagworttabelle td.is-kasten { text-align: center; }
/* "Rezepte" und "Lebensmittel" stehen seit dem 2026-08-30 ausgeschrieben
   da (FH) und machen die Tabelle damit breiter, als der Rahmen ist.
   Das bleibt so: `.table-wrap` rollt seitwaerts, und dafuer ist es da.
   Ein Umbruch waere die schmalere Loesung gewesen, geht hier aber
   nicht - "Lebensmittel" hat keine Trennstelle, `white-space: normal`
   bricht nur an Leerzeichen, und `overflow-wrap: anywhere` zerschnitte
   das Wort mitten im Stamm. Zwei lesbare Woerter sind ein bisschen
   Rollen wert; die Zaehlung ganz rechts ist ohnehin die Spalte, die man
   am seltensten braucht. */
/* Und die Zaehlung ganz rechts ist wirklich eine Zahl - die bleibt
   rechtsbuendig, damit die Schraegstriche untereinander stehen. */
.schlagworttabelle th.is-zahl, .schlagworttabelle td.is-zahl { text-align: right; }
/* `display: block` zurueckgeholt: ein Abzeichen, das **nur** aus dem
   Symbol besteht, hat kein Wort, an dessen Grundlinie sich etwas
   ausrichten muesste. Als inline-block saesse das svg auf der
   Grundlinie und zoege den Unterlaengenraum mit - das Abzeichen wurde
   damit 4 px hoeher (gemessen an den Schlagworten der
   Lebensmittelliste, 02.09.). */
.badge--symbol .icon { display: block; width: 1.4rem; height: 1.4rem; margin-right: 0; stroke-width: 1.5; }

/* Die `zuordnungstabelle` (Paket 58): dieselbe Bauart wie die
   Schlagworttabelle - Bedienelemente stehen links, nur die Zaehlung
   bleibt eine Zahl.

   Gebaut war sie fuer die Angebotskategorien; seit Paket 100 gibt es
   die Seite nicht mehr, und die **Markenpflege** auf /marken ist die
   einzige, die die Klasse noch traegt. Sechs Regeln sind mit der Seite
   gefallen - sie hatten nur dort einen Gegenstand, und eine Regel ohne
   Gegenstand faellt erst auf, wenn jemand sie sucht. */
.zuordnungstabelle th, .zuordnungstabelle td { text-align: left; }
.zuordnungstabelle th.num, .zuordnungstabelle td.num { text-align: right; }
.zuordnungstabelle select { min-width: 10rem; max-width: 18rem; }
/* Am Handy bricht der Name nur an Leerzeichen und das Auswahlfeld wird
   schmaler. So bleibt es ohne Querscrollen bei 390 px (gemessen,
   bin/test_markenlogo.js). */
@media (max-width: 620px) {
  .zuordnungstabelle td.is-name { overflow-wrap: normal; white-space: normal; }
  .zuordnungstabelle select { min-width: 6.5rem; max-width: 7.5rem; }
}

/* Das Kaestchen in der Zuordnungstabelle: die Trefferflaeche ist das
   Label, nicht das 13-px-Quadrat. */
.zuordnen__haken {
  display: flex; align-items: center; justify-content: center;
  min-width: 2.75rem; min-height: 2.75rem; margin: -.55rem -.6rem; cursor: pointer;
}

/* Die Hakenspalte im Kochbuch. Sie soll so schmal sein, wie das
   Kaestchen es zulaesst - der Rest der Breite gehoert dem Namen. Die
   Ueberschrift ist leer, deshalb setzt hier nichts eine Mindestbreite
   ausser dieser Regel. */
th.is-haken, td.is-haken { width: 2.75rem; padding-inline: .35rem; text-align: center; }

/* Die Zeile unter der Liste: "3 angehakt" steht in derselben Reihe wie
   die beiden Knoepfe und soll nicht daneben umbrechen. */
[data-sammelzahl] { font-size: .85rem; white-space: nowrap; align-self: center; }


.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  min-height: var(--feldhoehe); line-height: 1.25;
  padding: .62rem 1rem; border: 1px solid transparent; border-radius: 9px;
  background: var(--accent); color: var(--accent-ink); font-weight: 600; text-decoration: none;
  transition: background .12s, transform .06s;
}
.btn:hover { background: var(--accent-strong); }
.btn:active { transform: translateY(1px); }
.btn--ghost { background: transparent; color: var(--muted); border-color: var(--border); }
.btn--ghost:hover { background: var(--surface-2); color: var(--text); }
.btn--danger { background: transparent; color: var(--bad); border-color: color-mix(in srgb, var(--bad) 40%, transparent); }
.btn--danger:hover { background: color-mix(in srgb, var(--bad) 12%, transparent); }
.btn--sm { min-height: var(--feldhoehe-klein); padding: .35rem .7rem; font-size: .85rem; }
.btn--block { width: 100%; }
.btn-row { display: flex; flex-wrap: wrap; gap: .5rem; }

/* --- Tabellen ------------------------------------------------------- */
/* position: relative, damit absolut gesetzte Kinder (.visually-hidden in
   den Kopfzeilen) im Rahmen bleiben - sonst schieben sie bei einer
   breiten Tabelle die ganze Seite auf und der Kopf laeuft aus dem Bild. */
.table-wrap { position: relative; overflow-x: auto; margin: 0 -1.1rem; padding: 0 1.1rem; min-width: 0; }

/* --- Was rechts noch kommt ------------------------------------------
   Eine breite Tabelle rollt waagerecht, und bis 2026-08-27 sah man das
   nicht: am Handy stand das Tagebuch 672 px breit in einem 356 px
   breiten Behaelter, "Aendern" und "Loeschen" lagen bei x = 611 - also
   ausserhalb -, und mit der Zeile "Zusammen" darunter sah die Karte
   vollstaendig aus. **Am Telefon liess sich ein Eintrag damit weder
   aendern noch loeschen** (Pruefbericht, Punkt 2).

   Zwei Dinge dagegen, beide ohne Skript:

   1. Die Aktionsspalte bleibt stehen (`position: sticky`). Sie ist das,
      wonach man greift; alles andere darf wegrollen.
   2. Ein Schatten an der Kante sagt, dass es weitergeht. Gebaut aus
      zwei Verlaeufen je Seite - einem, der am Inhalt klebt
      (`background-attachment: local`), und einem, der am Rahmen bleibt.
      Der klebende deckt den bleibenden zu, sobald man am Anfang
      beziehungsweise am Ende ist. Deshalb erscheint der Schatten genau
      dann, wenn in dieser Richtung noch etwas liegt.
   --------------------------------------------------------------------- */
.table-wrap {
  background:
    linear-gradient(to right, var(--surface) 30%, transparent) left / 2.2rem 100% repeat-y local,
    linear-gradient(to left,  var(--surface) 30%, transparent) right / 2.2rem 100% repeat-y local,
    linear-gradient(to right, rgb(0 0 0 / .13), transparent)   left / .9rem 100% no-repeat scroll,
    linear-gradient(to left,  rgb(0 0 0 / .13), transparent)   right / .9rem 100% no-repeat scroll;
}
/* `repeat-y` statt `no-repeat` bei den beiden mitrollenden Verlaeufen:
   seit die Tabelle auch senkrecht rollen kann (klebender Kopf, weiter
   unten), waere ein einzelner Verlauf nach ein paar Zeilen oben aus dem
   Bild - und der Rollhinweis waere weg, obwohl rechts noch etwas liegt.
   Gekachelt deckt er die ganze Hoehe. */

/* Die Aktionen bleiben am rechten Rand stehen, waehrend der Rest
   wegrollt. Der Hintergrund ist Pflicht: ohne ihn scheint die Tabelle
   durch. */
table td.is-aktionen, table th.is-aktionen {
  position: sticky; right: 0; z-index: 1;
  background: var(--surface);
  /* Ein weicher Schatten nach links - er sagt, dass die Spalte oben
     liegt und darunter noch etwas wegrollt. Die feine Linie darunter
     traegt den Fall, in dem der Schatten kaum zu sehen ist (dunkel). */
  box-shadow: -7px 0 9px -7px rgb(0 0 0 / .3), -1px 0 0 var(--border);
}
/* --- Der Kopf bleibt stehen -----------------------------------------
   FH, 2026-08-28: "beim Runterscrollen die Spaltennamen über der Tabelle
   hovern". Bei zwanzig Zeilen weiss man sonst in der letzten nicht mehr,
   was die dritte Zahl von links bedeutet.

   Der Haken dabei: `.table-wrap` traegt `overflow-x: auto`, und damit
   rechnet der Browser `overflow-y` auf `auto` hoch - der Rahmen ist also
   ein Rollbehaelter. Ein klebender Kopf klebt immer am naechsten
   Rollbehaelter, hier also am Rahmen und nicht am Fenster. Solange der
   Rahmen so hoch ist wie sein Inhalt, kann der Kopf sich darin nie
   bewegen: er bliebe wirkungslos.

   Deshalb bekommt der Rahmen eine Hoechsthoehe. Sie tut **nur bei
   langen Tabellen** etwas - eine kurze ist niedriger als die Grenze und
   bleibt, wie sie war. Ist die Tabelle laenger, rollt sie in sich, und
   der Kopf steht.

   `dvh` statt `vh`: am Telefon waechst und schrumpft die Adresszeile,
   und `vh` rechnet mit dem grossen Fenster - die letzte Zeile laege dann
   unter der Leiste.
   --------------------------------------------------------------------- */
/* Nur die wirklich breite Tabelle bleibt ein Rollbehaelter und bekommt
   deshalb eine Hoechsthoehe; sonst haette ihr klebender Kopf nichts, an
   dem er kleben koennte. app.js setzt die Klassen (siehe dort). */
.table-wrap.is-breit { max-height: calc(100dvh - 9rem); }

/* Passt die Tabelle hinein, faellt der Rollbehaelter weg - und damit
   klebt der Kopf am Fenster. Die Rollhinweise gehen mit: wo nichts
   rollt, hat ein Hinweis darauf nichts zu suchen. */
.table-wrap.is-schmal { overflow: visible; background: none; }

table thead th {
  /* Unter der Leiste, nicht darunter verschwindend: die Topbar steht
     fest und waere sonst ueber dem Kopf. */
  position: sticky; top: var(--topbar-h); z-index: 3;
  /* Ohne eigenen Grund scheinen die Zeilen durch, sobald sie darunter
     wegrollen. */
  background: var(--surface);
  /* Als Schatten nach innen, nicht als Rand: ein `border-bottom` an
     einer klebenden Zelle loest sich in manchen Browsern ab und rollt
     mit den Zeilen weg. */
  box-shadow: inset 0 -1px 0 var(--border);
}

/* Klebt der Kopf dagegen am Rahmen (breite Tabelle), zaehlt dessen
   oberer Rand - dort gibt es keine Leiste, die im Weg waere. */
.table-wrap:not(.is-schmal) thead th { top: 0; }

/* Die Ecke oben rechts gehoert beiden Regeln - sie muss ueber beide
   liegen. Und sie braucht **beide** Schatten: `box-shadow` ist eine
   Eigenschaft, keine Liste, die sich sammelt - die Regel fuer die
   klebende Aktionsspalte hat den unteren Strich des Kopfes bis zum
   2026-08-29 einfach ersetzt. Sichtbar war das als Trennlinie, die
   kurz vor der letzten Spalte aufhoerte (FH: "geht der Strich oben
   unter der Titelleiste nicht bis nach ganz rechts"). */
table thead th.is-aktionen {
  z-index: 4;
  box-shadow:
    -7px 0 9px -7px rgb(0 0 0 / .3),
    -1px 0 0 var(--border),
    inset 0 -1px 0 var(--border);
}
table th, table td { padding: .55rem .6rem; text-align: right; white-space: nowrap; }
table th:first-child, table td:first-child { text-align: left; }
table thead th {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); font-weight: 600;
}
/* Die Trennlinie zieht durch die ganze Zeile, auch durch eine Zelle, die
   als th die Zeile benennt (Mahlzeit, Kategorie ...). Nur td zu nehmen
   liess die Linie erst ab der zweiten Spalte beginnen (FH, 2026-08-24). */
table tbody tr + tr > th,
table tbody tr + tr > td { border-top: 1px solid var(--border); }
table td.num, table td:not(:first-child) { font-variant-numeric: tabular-nums; }

/* Die Summenzeile ist kein Eintrag (FH, 2026-08-27).
   Im Tagebuch stand "Zusammen" wie eine weitere Zeile der Mahlzeit da -
   gleiche Fläche, gleiche Schrift, nur ohne Knöpfe. Sie wird aber
   gerechnet und lässt sich nicht anfassen. Drei Mittel sagen das:
   abgesetzte Fläche, die Beschriftung in derselben Form wie der
   Tabellenkopf - denn "Zusammen" benennt die Zeile, es ist kein Name
   eines Lebensmittels - und seit dem 2026-09-02 der **Abstand**: die
   Summe steht als eigener Riegel unter der Tabelle, nicht als deren
   letzte Zeile.
   Gilt für jede Tabelle mit tfoot, damit die Summe überall gleich
   aussieht.

   **Der Akzentstrich darüber ist weg** (FH, 2026-09-02: "Der dunkle
   Akzent oben soll verschwinden", zusammen mit "oben und unten
   abgerundete Ecken"). Er hat zwei Aufgaben auf einmal getragen -
   abgrenzen und betonen -, und die erste erledigt der Abstand besser:
   ein Riegel, der neben der Tabelle steht, braucht keinen Strich, der
   sagt, dass er dazugehört. Übrig bleibt die abgesetzte Fläche, und die
   war ohnehin das, was die Zeile lesbar gemacht hat.

   Der Abstand ist ein Rand in der Farbe der Karte und keine Lücke:
   `border-collapse: collapse` kennt keinen Zwischenraum zwischen
   Zeilen, ein `margin` gibt es an einer Tabellenzeile nicht. Der
   gestrichene Akzentstrich stand an genau derselben Stelle - der Rand
   ist also nicht neu, nur anders eingefärbt und breiter. */
table tfoot tr > th,
table tfoot tr > td {
  background: var(--surface-2);
  border-top: .4rem solid var(--surface);
  font-weight: 600;
}
/* Oben rund wie unten (unten steht es weiter oben bei den Mahlzeiten,
   seit dem 2026-08-30). Erste und letzte Zelle der ersten
   tfoot-Zeile - dieselbe Zerlegung wie beim Routinenkasten, aus
   demselben Grund: `border-radius` an der Zeile bliebe bei
   `collapse` wirkungslos. */
table tfoot tr:first-child > th:first-child,
table tfoot tr:first-child > td:first-child { border-top-left-radius: .6rem; }
table tfoot tr:first-child > th:last-child,
table tfoot tr:first-child > td:last-child  { border-top-right-radius: .6rem; }
table tfoot th[scope="row"] {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); font-weight: 600;
}
table.table--sm tfoot th[scope="row"] { font-size: .68rem; }
.table-actions { display: flex; gap: .35rem; justify-content: flex-end; }

/* Drei Knoepfe in einer Zeile brauchen am Telefon ein paar Pixel mehr:
   seit Paket 44 steht an den Angeboten neben Aendern und Loeschen auch
   "auf die Einkaufsliste", und damit schob die Tabelle bei 341 px
   Platz um 4 px quer (gemessen 04.09.2026).

   Genommen werden sie am **Rand der Zelle** und nicht zwischen den
   Knoepfen. Der Abstand dort ist 8 px und das mit Absicht (siehe die
   Regel weiter unten im Handy-Abschnitt): der harmlose und der
   zerstoerende Knopf sollen nicht einen Fingernagel auseinanderliegen.
   Der erste Versuch hat genau daran gedreht - falsche Stelle. */
@media (max-width: 620px) {
  td.is-aktionen { padding-left: .15rem; padding-right: .15rem; }
}

/* Kompakte Variante fuer die Kacheln der Startseite. */
table.table--sm th, table.table--sm td { padding: .3rem .4rem; font-size: .85rem; }
table.table--sm thead th { font-size: .68rem; }

/* --- Meldungen ------------------------------------------------------ */
/* --- Meldungen -------------------------------------------------------
   Sie schweben oben über der Seite und gehen von selbst wieder weg.
   Vorher standen sie im Fluss und schoben nach jedem Speichern den
   ganzen Inhalt ein Stück nach unten - man las die Meldung und suchte
   danach die Zeile wieder, auf die man geschaut hatte.

   Die Fläche ist **deckend** (color-mix gegen --surface, nicht gegen
   transparent): über einer Tabelle wäre eine durchscheinende Meldung
   unlesbar. Der Rahmen der Hülle lässt Klicks durch, die Meldung selbst
   nimmt sie an - so lässt sie sich wegklicken, ohne dass der Streifen
   die Seite darunter blockiert.
   -------------------------------------------------------------------- */
.flashes {
  position: fixed; z-index: 50;
  /* Unter der Kopfleiste, nicht darüber: eine Fehlermeldung bleibt
     absichtlich stehen, bis man sie wegklickt - läge sie über der
     Leiste, wären "Abmelden", der Hell/Dunkel-Schalter und das Profil
     so lange verdeckt, und man hielte sie für kaputt.
     Und unter der Lupe (z-index 110), sonst schwebt eine alte Meldung
     über dem gerade geöffneten Bild. */
  top: calc(var(--topbar-h, 3.5rem) + env(safe-area-inset-top) + .6rem);
  left: 50%; transform: translateX(-50%);
  width: min(34rem, calc(100vw - 1.5rem));
  display: grid; gap: .5rem; pointer-events: none;
}
.flash {
  pointer-events: auto; cursor: pointer;
  padding: .7rem .9rem; border-radius: 9px; font-size: .9rem; border: 1px solid;
  box-shadow: 0 10px 30px rgb(0 0 0 / .18);
  transition: opacity .3s ease, transform .3s ease;
}
.flash.is-weg { opacity: 0; transform: translateY(-.5rem); }
.flash--success { background: color-mix(in srgb, var(--good) 12%, var(--surface)); border-color: color-mix(in srgb, var(--good) 35%, transparent); color: var(--good); }
.flash--error   { background: color-mix(in srgb, var(--bad) 12%, var(--surface));  border-color: color-mix(in srgb, var(--bad) 35%, transparent);  color: var(--bad); }
.flash--info    { background: var(--surface-2); border-color: var(--border); }

.empty { text-align: center; padding: 2rem 1rem; color: var(--muted); }


/* --- Segmentwahl (Tag/Woche/Monat) ----------------------------------
   Passt die Leiste nicht in die Breite - die Stammdaten haben fünf
   Punkte -, schiebt sie sich in sich selbst zur Seite, statt die ganze
   Seite aufzuschieben. */
.segmented {
  display: inline-flex; background: var(--surface-2); border-radius: 9px; padding: 3px; gap: 2px;
  max-width: 100%; overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none; -ms-overflow-style: none;
}
.segmented::-webkit-scrollbar { height: 0; }
/* Derselbe Rollhinweis wie an den Tabellen: ein Verlauf, der am Inhalt
   klebt, deckt den zu, der am Rahmen bleibt - der Schatten erscheint
   also genau dann, wenn in dieser Richtung noch etwas liegt. Auf
   `/zutaten/zuordnen` war die vierte Stufe "Zugeordnet (1059)" zu null
   Pixeln zu sehen, und nichts sagte, dass sie da ist (Pruefbericht,
   Punkt 16). */
.segmented {
  background:
    linear-gradient(to right, var(--surface-2) 30%, transparent) left / 1.6rem 100% no-repeat local,
    linear-gradient(to left,  var(--surface-2) 30%, transparent) right / 1.6rem 100% no-repeat local,
    linear-gradient(to right, rgb(0 0 0 / .16), transparent)     left / .7rem 100% no-repeat scroll,
    linear-gradient(to left,  rgb(0 0 0 / .16), transparent)     right / .7rem 100% no-repeat scroll,
    var(--surface-2);
}
/* In der Filterleiste ist die Segmentleiste ein Rasterfeld und wuechse
   sonst ueber ihr max-width hinaus - drei lange Beschriftungen schoeben
   die Seite auf, statt in der Leiste zu scrollen. */
.filterbar .segmented { min-width: 0; }
.segmented a {
  flex: 0 0 auto; white-space: nowrap;
  padding: .32rem .7rem; border-radius: 7px; font-size: .85rem;
  text-decoration: none; color: var(--muted); font-weight: 550;
}
.segmented a[aria-current="page"] { background: var(--surface); color: var(--text); box-shadow: var(--shadow); }

/* Dieselbe Leiste mit Knoepfen statt Links: der Umschalter fuer die
   Bezugsmenge laedt die Seite nicht neu, da waere ein Link gelogen. */
.segmented button {
  flex: 0 0 auto; white-space: nowrap; cursor: pointer;
  padding: .32rem .7rem; border: 0; border-radius: 7px; font: inherit; font-size: .85rem;
  background: none; color: var(--muted); font-weight: 550;
}
.segmented button[aria-current="true"] { background: var(--surface); color: var(--text); box-shadow: var(--shadow); }

/* In einem Formular ist die Leiste ein Rasterfeld und wuerde sich ueber
   die ganze Breite ziehen - `inline-flex` hilft dort nicht. */
.segmented--eng { justify-self: start; align-self: start; }

/* --- Naehrwerte am Handy: eine Bezugsmenge statt Querscrollen --------
   Am Desktop stehen alle Spalten nebeneinander und die Leiste ist weg.
   Auf dem Handy passt neben die Namen genau eine Spalte: die Leiste
   waehlt sie, alle anderen Zellen fallen aus der Tabelle. Ohne Skript
   bleibt die vom Server gewaehlte stehen - lesbar ist die Tabelle
   dann immer noch.

   Die Namensspalte darf dort umbrechen. "davon mehrfach ungesaettigte
   Fettsaeuren" in einer Zeile waere allein schon breiter als ein
   iPhone; ohne das Umbrechen brauchte man die zweite Spalte gar nicht
   erst auszublenden.
   -------------------------------------------------------------------- */
.bezugzeile { display: none; justify-content: flex-end; margin: 0 0 .6rem; min-width: 0; }

@media (max-width: 767px) {
  .bezugzeile { display: flex; }
  .tabelle--bezug th[scope="row"],
  .tabelle--bezug thead th { white-space: normal; }
  .tabelle--bezug [data-spalte]:not(.is-aktiv),
  .tabelle--bezug [data-nrv]:not(.is-aktiv) { display: none; }
}

/* Am Desktop steht der Prozentsatz nur einmal da - zu der Spalte, auf
   die sich die Angabe des Herstellers bezieht. */
@media (min-width: 768px) {
  .tabelle--bezug [data-nrv]:not(.is-standard) { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* --- Diagramme ------------------------------------------------------
   Die Grafik zieht sich frei in die Breite (preserveAspectRatio="none"),
   die Linien behalten dank vector-effect ihre Staerke. Die Beschriftung
   steht als HTML daneben - im SVG wuerde sie mit der Breite mitwachsen.
   Sie ist bewusst kleiner gesetzt als der Fliess- und Tabellentext, damit
   die Achsen die Grafik nicht ueberstimmen.
   -------------------------------------------------------------------- */
.chart {
  --plot-h: 240px;
  --chart-label: .78rem;
  display: flex;
  flex-direction: column;
  gap: .3rem;
  color: var(--accent);
  margin: 0;
}
@media (min-width: 620px) { .chart { --plot-h: 300px; } }

/* Auf der Startseite steht das Diagramm neben Kennzahlen und Tabelle -
   es darf die Kachel nicht dominieren. */
.chart--sm .chart, .chart--sm.chart { --plot-h: 130px; }
@media (min-width: 620px) { .chart--sm .chart, .chart--sm.chart { --plot-h: 150px; } }
/* In der schmalen Kachel stossen Mitte und Ende der Zeitachse aneinander,
   sobald die Messungen dicht beisammen liegen. */
.chart--sm .chart__xaxis .is-middle { display: none; }

.chart__row { display: flex; gap: .6rem; align-items: flex-start; }

.chart__yaxis {
  flex: 0 0 auto;
  position: relative;
  height: var(--plot-h);
  min-width: 3.4em;
  font-size: var(--chart-label);
}
.chart__yaxis span {
  position: absolute; right: 0;
  transform: translateY(-50%);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* min-width: 0 ist noetig, damit die Spalte in einem Flex-Element
   schrumpfen darf statt die Kachel aufzublaehen. */
.chart__main { flex: 1 1 auto; min-width: 0; }

/* Senkrecht wischen bleibt Scrollen, nicht Ablesen: ohne diese beiden
   Zeilen fing das Diagramm am Handy die Geste ab, markierte nach kurzem
   Halten seine Beschriftung als Text - und danach ging das Ablesen gar
   nicht mehr. */
.chart__plot {
  position: relative; height: var(--plot-h);
  touch-action: pan-y;
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
.chart__svg  { display: block; width: 100%; height: 100%; overflow: visible; }

.chart__xaxis { position: relative; height: 1.5em; margin-top: .3rem; font-size: var(--chart-label); }
.chart__xaxis span {
  position: absolute; top: 0;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.chart__xaxis .is-start  { transform: translateX(0); }
.chart__xaxis .is-middle { transform: translateX(-50%); }
.chart__xaxis .is-end    { transform: translateX(-100%); }

/* Auf schmalen Displays wuerde die mittlere Beschriftung mit den
   aeusseren kollidieren. */
@media (max-width: 560px) { .chart__xaxis .is-middle { display: none; } }

/* Linien behalten ihre Staerke, obwohl die Flaeche ungleich skaliert */
.chart__grid, .chart__goal, .chart__line, .chart__gap {
  vector-effect: non-scaling-stroke;
}
.chart__grid { stroke: var(--border); stroke-width: 1; }
.chart__goal { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 1 3; }
.chart__line { fill: none; stroke: currentColor; stroke-width: 2; stroke-linejoin: round; }
.chart__area { stroke: none; }
/* Der Trend im Gewichtsdiagramm: waagerechte Striche ueber feste
   Fenster, duenn und in Bernstein ueber der Messlinie. Durchgezogen -
   gestrichelt hiesse Messpause.

   Nur waagerecht (FH, 03.09.): senkrechte Verbinder machten daraus eine
   Treppe, und die faehrt quer durch die Messlinie, wo gar nichts
   passiert. Jede Stufe steht fuer sich. */
.chart__trendstufe { fill: none; stroke: var(--warn); stroke-width: 1.4; vector-effect: non-scaling-stroke; }
.chart__mark.is-trend { background: var(--warn); }

/* Die Prozentdifferenz zur vorigen Stufe. Wie Punkt und Stern liegt sie
   als HTML ueber der Grafik - im SVG zoege sie das ungleiche Skalieren
   in die Breite. Sie sitzt mittig ueber ihrer Stufe; der helle Saum
   macht sie auch dort lesbar, wo sie auf der Messlinie liegt.

   Das Feld darum ist genau so breit wie die Stufe und ist ein
   Groessenbezug (`container-type: inline-size`). Damit kann die Regel
   darunter fragen, wie viele **Pixel** diese Stufe wirklich hat, statt
   welchen Anteil der Zeichenflaeche: dreizehn Monatsstufen sind am
   Schreibtisch bequem lesbar und am Telefon ein Knaeuel, und der Anteil
   ist in beiden Faellen derselbe. Wo es nicht reicht, bleibt die Zahl
   weg - im Kaestchen steht sie weiterhin. */
.chart__trendstufe-feld {
  position: absolute;
  top: 0; height: 100%;
  container-type: inline-size;
  pointer-events: none;
}
/* Links unter dem Anfang ihres Strichs (FH, 03.09.). Unter und nicht
   ueber der Linie: darueber lag sie im Weg der Messlinie, die von links
   oben kommt. Linksbuendig und nicht mittig: so beginnt sie dort, wo
   das Fenster beginnt, zu dem sie gehoert - man liest sie in derselben
   Richtung, in der die Zeit laeuft. */
.chart__trendstufe-pct {
  position: absolute;
  left: 0;
  margin-top: 3px;
  font-size: .7rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--warn);
  text-shadow: 0 0 3px var(--surface), 0 0 2px var(--surface), 0 0 2px var(--surface);
}
/* Knapp mehr als die breiteste Zahl ("-10,0 %"). Darunter stiessen die
   Nachbarinnen aneinander. */
@container (max-width: 54px) {
  .chart__trendstufe-pct { display: none; }
}
.chart__band { fill: currentColor; opacity: .07; }

/* Der letzte Punkt liegt als HTML obenauf - ein Kreis im SVG wuerde
   durch das ungleiche Skalieren zur Ellipse. */
.chart__dot {
  position: absolute;
  width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 2px var(--surface);
}

/* Erreichte Meilensteine. Der Stern liegt als HTML ueber der Grafik -
   im SVG zoege ihn das ungleiche Skalieren in die Breite. Er sitzt auf
   der Linie, an dem Tag, an dem die Marke fiel.
   `pointer-events: none`, seit der Text im Kaestchen steht und nicht
   mehr im title (FH, 2026-08-28): der Stern liegt genau auf der Linie,
   und ohne das faengt er die Zeigerbewegung an der einen Stelle ab, an
   der man am ehesten hinfaehrt. */
.chart__stern {
  position: absolute;
  transform: translate(-50%, -50%);
  /* Gruen wie der abnehmende Monatswert: ein gefallener Meilenstein ist
     eine gute Nachricht, keine Warnung. In der Akzentfarbe verschwaende
     er auf der Linie, die dieselbe Farbe traegt. */
  color: var(--good);
  font-size: 1rem;
  line-height: 1;
  pointer-events: none;
  /* Ein heller Saum, damit der Stern auch auf der Linie ablesbar ist. */
  text-shadow: 0 0 3px var(--surface), 0 0 2px var(--surface);
}

/* Messpausen: gestrichelt, damit klar ist, dass dort nicht gemessen
   wurde - und nicht, dass sich nichts veraendert haette. */
.chart__gap {
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-dasharray: 4 4; opacity: .75;
}

/* Neutral statt blau/braun: die Linien tragen seit Paket 17 die
   Stufenfarben, und ein blauer Endpunkt an einer gruenen Linie saehe
   aus wie eine vierte Aussage. Der Puls behaelt seine Farbe - er hat
   keine Stufen. */
.chart__dot.is-sys  { background: var(--muted); }
.chart__dot.is-dia  { background: var(--muted); }
.chart__dot.is-pul  { background: var(--bp-pul); }

/* Wer welche Linie ist, sagt seit Paket 17 das MUSTER, nicht die Farbe
   (FH: "durchgezogen, gestrichelt, gepunktet"): die Systole ist
   durchgezogen, die Diastole lang gestrichelt - deutlich laenger als
   die 4-4-Striche der Messpausen (.chart__gap) -, der Puls gepunktet.
   Die Reihenfarben darunter bleiben als Rueckfall stehen; auf der
   Blutdruckseite ueberdecken die Stufenfarben sie ohnehin. */
.chart__line.is-sys { stroke: var(--bp-sys); }
.chart__line.is-dia { stroke: var(--bp-dia); stroke-dasharray: 9 5; }
.chart__line.is-pul { stroke: var(--bp-pul); }

/* Die Bruecke ueber eine Messpause sagt nur "hier fehlt etwas". Seit
   die Linien die Stufenfarben tragen, waren diese Bruecken die letzten
   Traeger der alten Reihenfarben - und leuchteten blau und orange quer
   durch ein Jahr ohne Messungen (FH, 02.09.: "woher kommt das
   orange?"). Neutral und blass: eine Pause ist keine Aussage ueber
   den Blutdruck. */
.chart__gap.is-sys, .chart__gap.is-dia, .chart__gap.is-pul {
  stroke: var(--muted); opacity: .4;
}

/* Eine Messung ohne Nachbarn bekommt einen Punkt in ihrer Stufenfarbe -
   sonst waere sie nur ein Knick in den blassen Pausenbruecken. */
.chart__punkt { fill: var(--muted); stroke: var(--surface); stroke-width: 1; }
.chart__punkt.is-good { fill: var(--good); }
.chart__punkt.is-warn { fill: var(--warn); }
.chart__punkt.is-bad  { fill: var(--bad); }

/* Paket 17: Linienstuecke in der Farbe ihrer Einstufung; wo die Stufe
   wechselt, malt SvgChart einen Farbverlauf laengs des Stuecks (dessen
   stroke steht als style am Pfad und braucht hier keine Regel). Die
   Stufenklasse steht in dieser Datei HINTER den Reihenregeln und
   gewinnt deshalb gegen is-sys/is-dia. */
/* --- Die Linie nach den Referenzwerten (FH, 2026-09-03) --------------
   Ein Verlauf ueber die Hoehe der Zeichenflaeche; die Halte stehen in
   der Mitte jeder Stufe, gerechnet in App\Support\SvgChart. Die Farben
   kommen ueber Klassen und nicht als feste Werte, damit sie dieselben
   --good/--warn/--bad lesen wie jede Einordnung im Rest der Anwendung -
   und Hell und Dunkel von selbst mitmachen. */
.chart__stufe--good { stop-color: var(--good); }
.chart__stufe--ok   { stop-color: var(--accent); }
.chart__stufe--warn { stop-color: var(--warn); }
.chart__stufe--bad  { stop-color: var(--bad); }
.chart__stufe--kalt { stop-color: var(--kalt); }

.chart__line.is-good { stroke: var(--good); }
.chart__line.is-warn { stroke: var(--warn); }
.chart__line.is-bad  { stroke: var(--bad); }
/* Der Puls ist Beiwerk und tritt deshalb zurueck - aber durchgezogen,
   damit gestrichelt weiterhin nur Messpause bedeutet.

   **Dicker seit dem 03.09.2026** (FH: "Linie fuer Puls zu schmal im
   Diagramm"). Die Punkte sind runde Kappen auf einer Linie der Laenge
   null - ihr Durchmesser IST die Strichstaerke. Bei 1,5 px waren sie
   also 1,5 px gross, und das ist am Telefon ein Staubkorn; die beiden
   anderen Linien daneben sind 2 px breit und durchgezogen. Jetzt
   2,4 px, und der Abstand waechst mit (.1 6 statt .1 4.5), damit aus
   dickeren Punkten keine gestrichelte Linie wird - gestrichelt heisst
   in diesem Diagramm Diastole, und drei Muster muessen drei bleiben.

   `opacity` von .85 auf .95: das Zuruecktreten leistet jetzt das
   Muster, nicht mehr die Blaesse. */
.chart__line.is-pul {
  stroke-width: 2.4; opacity: .95;
  /* Gepunktet (Paket 17): runde Kappen auf Nulllaenge ergeben Punkte. */
  stroke-dasharray: .1 6; stroke-linecap: round;
}

/* --- Anzeige am Zeiger ----------------------------------------------
   Linie, Punkte und Kaestchen liegen als HTML ueber der Grafik: im SVG
   wuerden sie durch das ungleiche Skalieren verzerrt.
   -------------------------------------------------------------------- */
.chart__cursor {
  position: absolute; top: 0; bottom: 0; width: 1px;
  background: var(--muted); opacity: .45;
  pointer-events: none; display: none;
}
.chart__mark {
  position: absolute; width: 8px; height: 8px; margin: -4px 0 0 -4px;
  border-radius: 50%; background: currentColor;
  box-shadow: 0 0 0 2px var(--surface);
  pointer-events: none; display: none;
}
/* Das Kaestchen steht neben der Zeigerlinie, nie darauf - sonst verdeckt
   es genau den Wert, den man ablesen will. */
.chart__tip {
  position: absolute; z-index: 2; display: none; top: 2px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 9px; box-shadow: var(--shadow);
  padding: .35rem .55rem; font-size: .78rem; line-height: 1.35;
  white-space: nowrap; pointer-events: none;
  font-variant-numeric: tabular-nums;
}
.chart__tip.is-rechts { transform: translateX(8px); }
.chart__tip.is-links  { transform: translateX(calc(-100% - 8px)); }
.chart__tip b { display: block; font-weight: 600; color: var(--muted); font-size: .72rem; }
.chart__tip-zeile { display: flex; gap: .9rem; justify-content: space-between; }
.chart__tip-name { color: var(--muted); }

/* Der Meilenstein im Kaestchen: eine Zeile unter dem Wert, abgesetzt
   durch eine Linie - sie sagt etwas anderes als die Zahl darueber. Der
   Stern davor ist derselbe wie in der Grafik, damit klar ist, welcher
   Stern gemeint ist. */
.chart__tip-marke {
  gap: .4rem; justify-content: flex-start;
  margin-top: .25rem; padding-top: .25rem;
  border-top: 1px solid var(--border);
  /* Umbrechen darf sie - aber nicht bei zehn Zeichen. Das Kaestchen ist
     sonst nur so breit wie sein Kopf ("21.08.2026"), und der Meilenstein
     stuende darunter vierzeilig. `min-width` zieht das Kaestchen auf
     eine Breite, in der zwei Zeilen genuegen; `max-width` haelt es davon
     ab, am Handy ueber die Grafik hinauszuwachsen. */
  white-space: normal; min-width: 12rem; max-width: 15rem;
}
.chart__tip-marke + .chart__tip-marke { margin-top: 0; padding-top: 0; border-top: 0; }
.chart__tip-stern { color: var(--good); flex: 0 0 auto; }

.chart__plot.is-aktiv .chart__cursor,
.chart__plot.is-aktiv .chart__tip { display: block; }
/* Die Marken schaltet das Skript einzeln, weil nicht jede Reihe an
   jedem Punkt einen Wert hat. */
.chart__plot:not(.is-aktiv) .chart__mark { display: none !important; }

.chart-legend {
  display: flex; flex-wrap: wrap; gap: .25rem 1rem;
  list-style: none; padding: 0; margin-top: .2rem;
  font-size: var(--chart-label, .78rem); color: var(--muted);
}
.chart-legend li { display: flex; align-items: center; gap: .4rem; }
.chart-legend__dot { width: 12px; height: 3px; border-radius: 2px; background: currentColor; }
/* Die Legende zeigt seit Paket 17 das MUSTER der Linie, nicht mehr
   ihre alte Farbe: die Linien tragen die Stufenfarben, und ein blauer
   Balken neben einer gruen-roten Linie erklaerte nichts mehr. Neutral
   fuer Systole und Diastole; der Puls behaelt Farbe UND Punkte. */
.chart-legend__dot.is-sys { width: 18px; background: var(--muted); }
.chart-legend__dot.is-dia {
  width: 18px; border-radius: 0;
  background: repeating-linear-gradient(90deg, var(--muted) 0 5px, transparent 5px 8px);
}
.chart-legend__dot.is-pul {
  width: 18px; border-radius: 0;
  /* Dieselben Abstaende wie die Linie selbst (2,4 px Punkt, 6 px
     Teilung) - eine Legende, die feiner punktet als das Diagramm,
     erklaert eine andere Linie. */
  background: repeating-linear-gradient(90deg, var(--bp-pul) 0 3px, transparent 3px 7px);
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* --- Bedienelemente über einem Diagramm ------------------------------ */
.chart-controls {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .5rem;
  margin-bottom: .2rem;
}
.chart-controls .segmented { flex: 0 0 auto; }
/* Die Zeitraumwahl steht immer rechts - auch wenn sie das einzige
   Bedienelement ist oder in eine zweite Zeile umbricht. */
.chart-controls .is-end { margin-left: auto; }
@media (max-width: 520px) {
  .chart-controls { gap: .4rem; }
  .segmented a { padding: .32rem .55rem; font-size: .8rem; }
}

/* --- Hell/Dunkel-Umschalter ----------------------------------------- */
/* Ein Knopf, der nur ein Zeichen traegt - der Umschalter fuer hell und
   dunkel und, seit dem 03.09.2026, das Abmelden. Beide stehen in
   derselben Reihe oben rechts und muessen deshalb bis auf den Pixel
   gleich aussehen; eine gemeinsame Regel ist der einzige Weg, auf dem
   das auch nach der naechsten Aenderung noch stimmt. Getrennte
   Klassennamen bleiben, weil das Umschalten der drei Zeichen nur den
   Umschalter angeht. */
.symbolknopf,
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  background: transparent; border: 1px solid var(--border);
  border-radius: 9px; color: var(--muted); cursor: pointer;
}
.symbolknopf:hover,
.theme-toggle:hover { background: var(--surface-2); color: var(--text); }
.symbolknopf svg,
.theme-toggle svg { width: 17px; height: 17px; fill: none; stroke: currentColor;
                    stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
/* Es wird immer nur das Symbol des aktuellen Zustands gezeigt. */
.theme-toggle [data-icon] { display: none; }
:root:not([data-theme]) .theme-toggle [data-icon="system"],
:root[data-theme="light"] .theme-toggle [data-icon="light"],
:root[data-theme="dark"]  .theme-toggle [data-icon="dark"] { display: block; }

/* --- Auswahl der Messgröße ------------------------------------------ */
.marker-picker { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.marker-picker label { font-size: .82rem; font-weight: 600; color: var(--muted); }
.marker-picker select { width: auto; min-width: 14rem; padding: .35rem .6rem; }

/* --- Was sich nicht mehr meldet -------------------------------------
   Eine Liste, kein Alarm: derselbe zurueckhaltende Ton wie eine
   Hinweiszeile, nur mit dem Datum in der Warnfarbe. Ein roter Kasten
   waere hier falsch - eine Messgroesse, die schweigt, ist kein Fehler
   der Anwendung, sondern eine Quelle, die aufgehoert hat.

   Die Zeile bricht am Handy um: Name, Datum und Rhythmus stehen dann
   untereinander statt in drei Spalten, die sich gegenseitig
   zusammenquetschen.
   -------------------------------------------------------------------- */
.stillstand { list-style: none; margin: .4rem 0 0; padding: 0; display: grid; gap: .5rem; }
.stillstand > li {
  display: flex; align-items: baseline; gap: .25rem .7rem; flex-wrap: wrap;
  padding-top: .5rem; border-top: 1px solid var(--border);
}
.stillstand > li:first-child { padding-top: 0; border-top: 0; }
.stillstand > li > span { font-size: .85rem; }

/* Am schmalen Geraet bekommt der Name eine eigene Zeile. Sonst haengt
   es von der Laenge des Namens ab, was noch danebenpasst - und zwei
   Eintraege untereinander haben zwei verschiedene Formen. Gemessen am
   Handy (390 px): "Herzratenvariabilitaet" brach um, "Ruhepuls" nicht.
   Zwei Zeilen fuer jeden sind ruhiger als eine fuer den einen und
   anderthalb fuer den anderen. */
@media (max-width: 620px) {
  .stillstand > li > a { flex: 0 0 100%; }
}

/* Die Zeile unter der Bedienleiste des Diagramms. Sie steht dicht am
   Diagramm, zu dem sie gehoert, und nicht als eigener Absatz darueber. */
.stillstand__zeile { margin: .1rem 0 .3rem; }

/* --- Filterleiste ueber Listen -------------------------------------- */
/* In den schmalen Leisten gilt die kleine Hoehe - fuer Felder und
   Knoepfe gleichermassen. Gesetzt wird sie ueber die Variable und nicht
   ueber eine eigene Hoehenregel: die allgemeine Feldregel steht wegen
   ihrer langen :not()-Kette so hoch, dass eine Regel mit Klasse sie
   nicht ueberschreiben koennte. */
/* Dieselbe Regel gilt fuer jede enge Reihe, in der Feld und Knopf
   nebeneinander stehen (FH, 2026-08-27: "sind die ueberall gleich
   hoch?"). Gemessen wurde es danach: von 184 Reihen mit mehr als einem
   Bedienelement fielen vier aus der Reihe, und immer auf dieselbe Weise -
   ein `btn--sm` neben einem Feld voller Hoehe. Die Klappmenues am
   Tagebuch und die Zeilen der Einkaufsliste sind enge Reihen; sie
   fehlten hier nur. `bin/test_hoehen.js` misst das jetzt nach. */
.filterbar, .datumsleiste, .chart-controls, .table-actions, .feldzeile,
.mahlzeit-menue__inhalt, .liste__posten {
  --feldhoehe: var(--feldhoehe-klein);
  /* Und die Polsterung muss mit (FH, 2026-08-30: "Den Text kann man im
     Dropdown nicht ordentlich lesen").
     ------------------------------------------------------------------
     Die kleine Feldhoehe ist 2,125 rem = 34 px. Davon gehen 2 px
     Rahmen ab und, mit der Polsterung des grossen Feldes, 2 x 10,4 px -
     fuer 16 px Schrift bleiben elf. Der Text wurde unten
     abgeschnitten, und zwar seit es die zweite Groesse gibt: die Hoehe
     wurde damals verkleinert, die Polsterung nicht.

     .3 rem sind 4,8 px; mit Rahmen und einer Zeile von 20 px macht das
     31,6 px in einem 34-px-Feld. Betroffen war jede enge Reihe mit
     einem Auswahlfeld - die Filterleisten, die Datumsleiste, die
     Klappmenues am Tagebuch und die Routinenkarte, in der es FH
     aufgefallen ist.

     `bin/test_hoehen.js` ging dabei gruen durch, weil es Hoehen
     **vergleicht** und nicht prueft, ob der Text hineinpasst. Genau
     das tut es jetzt auch. */
  --feldpolster: .3rem;
}

/* min-width: 0, weil die Leiste als Rasterfeld in der Karte sitzt und
   sonst mit ihrem Inhalt waechst - vier Reiter in der Segmentleiste
   schoeben die Seite auf, statt in der Leiste zu scrollen. */
/* --- Filterleiste, die neue -------------------------------------------
   Drei Abschnitte untereinander statt einer vollgestellten Zeile (FH,
   2026-08-27): oben nur Suchfeld, "Suchen" und "Zuruecksetzen", darunter
   geklappt "Filter" und "Gruppieren". Der Gewinn steckt in der ersten
   Zeile - dort steht jetzt nichts, wonach man suchen muss.
   ---------------------------------------------------------------------- */
.filterleiste {
  --feldhoehe: var(--feldhoehe-klein);
  display: grid; gap: .5rem; min-width: 0; margin-bottom: .3rem;
  /* `minmax(0, 1fr)` statt der Vorgabe `auto` (Paket 119). Eine Spur
     der Vorgabe ist so breit wie der schmalste Inhalt, den sie NICHT
     unterschreiten kann - und die Zusammenfassung neben "Filter" laeuft
     auf einer Zeile. Mit neun gesetzten Filtern war die Spur damit 750
     Pixel breit, das Formular 306, und die ganze Seite schob am Telefon
     quer. `min-width: 0` am Text allein reicht dagegen NICHT: es erlaubt
     dem Text zu schrumpfen, macht aber seinen Beitrag zur
     min-content-Breite der Spur nicht kleiner. Dieselbe Familie wie der
     Fund aus Paket 115. */
  grid-template-columns: minmax(0, 1fr);
}
.filterleiste__kopf {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; min-width: 0;
}
/* Das Suchfeld nimmt, was da ist - aber nicht die ganze Seite: bei einer
   Tabelle mit vier Spalten sieht ein 900 Pixel breites Suchfeld aus wie
   ein Fehler. */
.filterleiste__kopf--leer { justify-content: flex-end; }
/* Das Suchfeld nimmt die ganze Zeile (FH, 2026-08-27) - abzueglich der
   beiden Knoepfe daneben. Es war auf 30 rem gedeckelt: bei einer Tabelle
   mit vier Spalten dachte ich, ein breites Feld saehe verloren aus. Es
   ist umgekehrt - eine halbe Zeile Suchfeld sieht aus, als fehlte
   rechts etwas. */
.filterleiste__suche { flex: 1 1 16rem; min-width: 0; gap: 0; }

/* --- Das X zum Leeren im Suchfeld -----------------------------------
   FH, 2026-08-29: "In den Textfeldern zur Suche ist kein X zum löschen
   mehr drin" - und: "das x soll in der passenden farbe sein".

   Der Browser bringt bei `type="search"` selbst eines mit, aber nicht
   überall (Firefox und iOS Safari zeichnen keins) und immer in seinem
   eigenen Blau, das sich nicht umfärben lässt. Deshalb ein eigenes -
   und das des Browsers weg, sonst stünden zwei nebeneinander.

   Der Knopf liegt **im** Feld, nicht daneben: daneben würde er das Feld
   schmaler machen und in der Leiste eine vierte Höhe einführen. Das
   Feld bekommt dafür rechts Platz, damit der Text nicht darunter
   läuft. */
input[type=search]::-webkit-search-cancel-button,
input[type=search]::-webkit-search-decoration { -webkit-appearance: none; appearance: none; display: none; }

.feld-mit-x { position: relative; display: block; min-width: 0; }
/* **Die Ausschlussliste noch einmal, und das mit Absicht** (Paket 95,
   06.09.2026 - derselbe Handgriff wie bei `.field--invalid` in Paket
   91, siehe dort).

   Die Grundregel fuer Eingabefelder ganz oben ist als Ausschlussliste
   geschrieben; neun `:not()` machen Spezifitaet 0,9,1. `.feld-mit-x >
   input` hat 0,1,1 und kam nicht dagegen an - der Platz fuer das X
   wurde von der Grundregel mit ihren .65rem ueberschrieben.

   Gemessen am 06.09. im Sucher, mit einem langen Namen im Feld: das
   Textfeld endete bei 540 Pixeln, der Knopf begann bei 518. Der Text
   lief also 22 Pixel weit unter das X, und das X schnitt mitten in
   "Packung". Betroffen war jedes Feld mit dem X - der Sucher und das
   Suchfeld der Filterleiste.

   Die saubere Loesung waere ein `:where()` an der Grundregel. Sie ist
   gemessen und bewusst NICHT gemacht: 4962 Felder verglichen, 612
   Unterschiede, davon vier echte - und einer davon (`.sprung__feld`)
   waere danach nur anders falsch. Der Weg dorthin steht in
   `docs/offene_punkte.md`.

   **Nur solange das X wirklich dasteht** (`:has`). Der Platz gehoert
   dem Knopf, und den gibt es erst, wenn etwas im Feld steht - app.js
   nimmt ihm dafuer das `hidden`. Immer freigeraeumt war er zu teuer:
   `bin/test_platzhalter.js` wurde rot, weil "Suchen oder scannen" in
   das schmaler gewordene leere Feld nicht mehr passte. Und ein leeres
   Feld braucht ihn nicht - da steht nur der Platzhalter, und neben ihm
   kein Knopf.

   Der Text springt dabei nicht: er steht links, und nur rechts wird
   Platz frei. Ohne Skript erscheint das X nie, dann bleibt es beim
   gewoehnlichen Polster - richtig, denn dann gibt es auch nichts,
   worunter der Text laufen koennte. */
.feld-mit-x:has(> .feld-x:not([hidden])) > input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=file]):not([type=range]):not([type=color]):not([type=image]),
.feld-mit-x:has(> .feld-x:not([hidden])) > select,
.feld-mit-x:has(> .feld-x:not([hidden])) > textarea { padding-right: 2.4rem; }
.feld-x {
  position: absolute; top: 50%; right: .45rem; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 1.7rem; height: 1.7rem; padding: 0;
  border: 0; border-radius: 50%;
  /* Deckend, nicht durchsichtig: ein Text, der laenger ist als das
     Feld, rollt in Chrome **unter** den rechten Innenabstand. Ohne
     Flaeche stuende das X dann mitten in den Buchstaben (2026-08-29 im
     Sucher gesehen). */
  background: var(--surface); color: var(--muted);
  cursor: pointer;
}
.feld-x:hover { color: var(--text); background: var(--surface-2); }
.feld-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.feld-x .icon { width: .95rem; height: .95rem; }
.filterleiste__zusatz { margin-left: auto; color: var(--muted); font-size: .85rem; }

.filterleiste__block {
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface); padding: .1rem .7rem;
}
.filterleiste__block > summary {
  display: flex; align-items: center; gap: .45rem;
  padding: .45rem .1rem; cursor: pointer;
  font-size: .85rem; font-weight: 600; color: var(--muted);
}
/* Der kleine Pfeil davor (FH, 2026-08-27). Das eigene Dreieck des
   Browsers faellt weg, sobald `summary` ein `display` bekommt - deshalb
   ein eigenes Zeichen, das sich beim Aufklappen dreht. */
.filterleiste__block > summary { list-style: none; }
.filterleiste__block > summary::-webkit-details-marker { display: none; }
.filterleiste__block > summary > .icon {
  width: 1em; height: 1em; flex: none;
  transform: rotate(-90deg); transition: transform .12s;
}
.filterleiste__block[open] > summary > .icon { transform: rotate(0); }
.filterleiste__block[open] > summary { color: var(--text); }
.filterleiste__block > summary .muted { font-weight: 500; }
/* Die Namen der eingestellten Filter (Paket 119). Sie stehen auf EINER
   Zeile und werden abgeschnitten, wenn es zu viele sind: /lebensmittel
   hat zwoelf, und ausgeschrieben schoebe die Zusammenfassung am Telefon
   die Leiste breiter als die Karte. Die Zahl daneben bleibt vollstaendig,
   und aufgeklappt steht ohnehin alles da.

   `min-width: 0` ist dabei das Entscheidende: ein Flex-Element schrumpft
   von sich aus nur bis zu seinem laengsten Wort, und ohne diese Zeile
   griffe das Abschneiden nie. */
.filterleiste__block > summary [data-filtertext] {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* `auto-fill`, nicht `auto-fit`: bei "Gruppieren" steht ein einziges
   Feld im Abschnitt, und `auto-fit` zieht es ueber die ganze Breite -
   ein Auswahlfeld von 950 Pixeln fuer sechs Spaltennamen. */
.filterleiste__felder {
  display: grid; gap: .7rem; margin: .3rem 0 .8rem;
  grid-template-columns: repeat(auto-fill, minmax(min(13rem, 100%), 1fr));
}
/* Dieselbe schmale Polsterung wie in der alten Leiste - die allgemeine
   Feldregel setzt .65rem, und in einem 34 Pixel hohen Feld steht der
   Text davon halb unter dem Rand. */
/* Das Logo einer Kette (Paket 80) oder einer Marke (Paket 82) - seit
   Paket 82 heisst die Klasse `.logo`, weil `partials/kettenlogo` zum
   allgemeinen `partials/logo` geworden ist.

   Eine feste HOEHE und freie Breite:
   Logos sind verschieden geschnitten - Lidl fast quadratisch, REWE
   breit -, und eine feste Breite quetschte das eine und liesse das
   andere winzig. `object-fit: contain` haelt das Seitenverhaeltnis,
   falls doch einmal beides gesetzt ist.

   `vertical-align: -.2em` statt `middle`: die Zeile traegt danach
   Text, und `middle` setzte das Bild auf die halbe x-Hoehe - es
   schwebte ueber der Grundlinie. Die Zahl ist am Bildschirmfoto
   gewaehlt, nicht gerechnet.

   Die Hoehe steht in `em` und nicht in Pixeln: an der Kachel ist die
   Zeile 0,8 rem gross, in der Tabelle 1 rem, und das Logo soll in
   beiden zum Text passen statt einmal zu gross zu sein. */
.logo {
  /* `display: inline-block` gegen die Grundregel `img { display: block }`
     ganz oben - ohne das stuende das Logo in einer eigenen Zeile ueber
     dem Text, und jede Angebotszeile waere eine Zeile hoeher. Am
     Bildschirmfoto gesehen, nicht vermutet. */
  display: inline-block;
  height: 1.15em; width: auto; max-width: 3.2em;
  object-fit: contain; vertical-align: -.2em;
  margin-right: .35em;
}
/* Im Formular, wo es fuer sich steht und niemand daneben liest. */
.logo--kopf { height: 2.6rem; max-width: 12rem; margin: 0; }
.logowahl { margin: .2rem 0 .5rem; }

/* Das Logo des Bonusprogramms steht als einziges HINTER seinem Text
   (Paket 116, FH: "dann kann da der preis stehen und der bonus icon
   dahinter") - der Abstand gehoert also nach links statt nach rechts.
   `:not(.logo--kopf)` laesst die Vorschau im Kettenformular in Ruhe,
   wo das Logo fuer sich steht. */
.logo--treue:not(.logo--kopf) { margin-right: 0; margin-left: .35em; }

/* Die Markenpflege (Paket 82) benutzt die `zuordnungstabelle` - seit
   Paket 100 als einzige. Nur das Textfeld fuer eine neue Marke kommt
   dazu: es steht unter dem Auswahlfeld, damit beide auch bei 390 px
   lesbar bleiben. */
/* Die Breite entscheidet in Wahrheit die Tabellenzelle: das Feld ist
   `width: 100%` davon, und die 18 rem greifen erst am breiten Schirm.
   Am Telefon bleiben rund 120 px, und ein langer Vorschlag steht dort
   angeschnitten - das ist verkraftbar, weil der Vorschlag bis auf die
   Gross-/Kleinschreibung genau das Wort ist, das links in derselben
   Zeile steht. */
.zuordnungstabelle .zuordnung__neu { display: block; margin-top: .35rem; width: 100%; max-width: 18rem; }

/* Ein vorbelegtes Feld (Paket 85) traegt einen Balken in der
   Akzentfarbe an der linken Kante - nicht ausgegraut: der Wert IST
   echt und geht beim Abschicken mit.

   **Ein `border-color` waere hier wirkungslos.** Die Grundregel fuer
   Eingabefelder (Zeile ~997) ist als Ausschlussliste geschrieben -
   `input:not([type=checkbox]):not(...)` mit neun `:not()` - und hat
   damit die Spezifitaet 0,9,1. Eine Klassenregel kommt dagegen nicht
   an; im Browser gemessen, das Feld behielt `rgb(220,213,201)`.
   `box-shadow` setzt die Grundregel dagegen nicht, also traegt es
   hier. (Der Fokusrahmen setzt es auch und gewinnt dann - richtig so:
   welches Feld gerade dran ist, ist die wichtigere Auskunft.)

   Eine Fuellung in `--accent-soft` stand zuerst dabei und ist wieder
   raus: im hellen Schema war sie auf Weiss kaum zu sehen, im dunklen
   ist `--d-accent-soft` fast die Flaechenfarbe selbst. */
.zuordnungstabelle .zuordnung__neu--vorschlag { box-shadow: inset 3px 0 0 var(--accent); }

/* Der Knopf ueber der Tabelle. Er steht dicht am Hinweis, der die Zahl
   nennt - beide gehoeren zusammen. */
.vorschlag__alle { margin: 0 0 1rem; }
.vorschlag__alle .field__hint { margin-bottom: .5rem; }

/* Der Block "Sortieren" (Paket 79): eine Zeile je Stufe, in jeder die
   Spalte und ihre Richtung nebeneinander. Die Spalte bekommt den
   Platz, der uebrig ist - sie traegt die langen Namen, "aufsteigend"
   und "absteigend" sind immer gleich lang.

   Am Telefon untereinander: zwei Auswahlfelder in einer 390 Pixel
   breiten Zeile sind beide zu schmal, um ihren Text zu zeigen. */
.filterleiste__stufen { display: grid; gap: .55rem; margin: .3rem 0 .8rem; }
.filterleiste__stufe {
  display: grid; gap: .7rem;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
}
/* Am Telefon bleiben sie nebeneinander - untereinander waeren es bei
   drei Stufen acht Felder, und der Block fuellte den Bildschirm. Die
   Beschriftung "Richtung" faellt dabei weg: das Feld sagt selbst, was
   es ist, und die Spalte daneben traegt die Zeile. */
@media (max-width: 620px) {
  .filterleiste__stufe { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: .45rem;
    align-items: end; }
  .filterleiste__stufe .field:last-child > label { position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .filterleiste__stufe select { padding-left: .45rem; padding-right: .3rem; }
}
.filterleiste input, .filterleiste select { padding: .45rem .6rem; font-size: .9rem; }
.field__hinweis { font-size: .75rem; color: var(--muted); }

/* Die Zahl der Zeilen steht hinter der Ueberschrift der Tabelle, nicht
   am anderen Ende der Zeile (FH, 2026-08-27): sie sagt etwas ueber
   genau diese Ueberschrift aus. Innerhalb der <h2>, damit sie ihr auch
   dann folgt, wenn rechts noch etwas anderes steht. */
.anzahl {
  margin-left: .35rem; font-size: .85rem; font-weight: 500;
  color: var(--muted); font-variant-numeric: tabular-nums;
}

/* Mehrfachauswahl zum Aufklappen: zugeklappt ein Feld wie jedes andere,
   aufgeklappt eine Liste mit Kaestchen (FH, 2026-08-27). Das `multiple`
   von HTML waere ein Listenkasten - dauernd hoch, und zum Waehlen
   mehrerer Eintraege braeuchte man die Strg-Taste. */
.mehrfach { position: relative; }
.mehrfach__knopf {
  display: flex; align-items: center; justify-content: space-between; gap: .4rem;
  height: var(--feldhoehe); padding: .45rem .6rem; cursor: pointer;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 9px;
  font-size: .9rem; list-style: none;
}
.mehrfach__knopf::-webkit-details-marker { display: none; }
.mehrfach__knopf > span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.mehrfach__knopf > .icon { width: 1em; height: 1em; flex: none; color: var(--muted); }
.mehrfach[open] > .mehrfach__knopf {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}
/* Schwebend, damit das Aufklappen die Leiste nicht auseinanderschiebt
   und die Tabelle darunter nicht springt. */
.mehrfach__liste {
  position: absolute; z-index: 30; top: calc(100% + .3rem); left: 0;
  min-width: 100%; max-width: min(22rem, 90vw); max-height: 15rem; overflow-y: auto;
  padding: .3rem; background: var(--surface);
  border: 1px solid var(--border); border-radius: 9px; box-shadow: var(--shadow);
}
.mehrfach__zeile {
  display: flex; align-items: center; gap: .5rem;
  padding: .35rem .45rem; border-radius: 6px; cursor: pointer;
  font-size: .9rem; white-space: nowrap;
}
.mehrfach__zeile:hover { background: var(--surface-2); }
.mehrfach__zeile input { flex: none; margin: 0; }
/* Das Symbol des Schlagworts zwischen Kaestchen und Wort. Etwas
   gedaempft: es soll das Wort begleiten und nicht mit ihm streiten -
   unter dem Rezeptnamen steht es dann allein und in voller Farbe. */
.mehrfach__zeile .icon { flex: none; width: 1rem; height: 1rem; color: var(--muted); }
/* --- Schlaf: die Nacht als Balken -------------------------------------

   Drei Abschnitte nebeneinander, zusammen 100 % der Nacht. Die Farben
   laufen von dunkel nach hell in derselben Richtung wie der Schlaf
   selbst tiefer wird - Tiefschlaf am dunkelsten. Sie kommen aus den
   Akzenttoenen des Themas und nicht aus eigenen Farbwerten, damit sie
   im hellen wie im dunklen Schema stimmen.

   Die drei `span` stehen im Markup **ohne Leerzeichen** aneinander:
   als Inline-Elemente wuerde jedes Leerzeichen dazwischen als Luecke
   gezeichnet, und die Summe waere breiter als 100 %. */
.schlaf__balken {
  display: flex; width: 100%; min-width: 6rem; height: .8rem;
  border-radius: 999px; overflow: hidden; background: var(--surface-2);
}
.schlaf__teil { display: block; height: 100%; }
.schlaf__teil--tief   { background: var(--accent); }
.schlaf__teil--rem    { background: var(--accent); opacity: .62; }
.schlaf__teil--leicht { background: var(--accent); opacity: .3; }

/* Die Legende in der Kopfzeile: kleine Marken in denselben Farben. */
.schlaf__legende {
  display: inline-flex; align-items: center; gap: .3rem;
  color: var(--muted); font-size: .8rem;
}
.schlaf__marke {
  display: inline-block; width: .7rem; height: .7rem; border-radius: 3px;
  background: var(--accent); margin-left: .5rem;
}
.schlaf__marke:first-child { margin-left: 0; }
.schlaf__marke--rem    { opacity: .62; }
.schlaf__marke--leicht { opacity: .3; }

/* Der Weg zum Geschaeft, unter der Anschrift. Klein und mit der
   Kartennadel davor: er ist eine Handlung mitten in einer Tabelle aus
   Angaben und soll als solche zu erkennen sein, ohne als Knopf
   aufzutragen. `align-items: center` statt `baseline` - die Nadel ist
   hoeher als das Wort und zoege die Zeile sonst auseinander. */
.anschrift__weg {
  display: inline-flex; align-items: center; gap: .3rem;
  margin-top: .35rem; font-size: .9rem;
}
.anschrift__weg .icon { width: 1rem; height: 1rem; }

/* Die Trefferzahl hinter dem Wort (FH, 31.08.). Sie steht in einem
   eigenen span statt im Text - der Knopf ueber der Liste setzt seine
   Zusammenfassung aus `data-text`, und mit der Zahl darin stuende dort
   "Vegan (12), Glutenfrei (7)". Grau und etwas kleiner: sie begleitet
   das Wort, sie ist nicht die Auskunft, wegen der man die Liste
   aufmacht - dieselbe Rolle wie das Symbol daneben. */
.mehrfach__zahl { color: var(--muted); font-size: .82rem; }
.mehrfach__leer { margin: .3rem .45rem; color: var(--muted); font-size: .85rem; }

.filterbar { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; min-width: 0; }
.filterbar .field { flex: 1 1 12rem; gap: 0; }
.filterbar input, .filterbar select { padding: .45rem .6rem; font-size: .9rem; }
.filterbar .is-end { margin-left: auto; }

/* Zwei Segmentleisten nebeneinander gehören zusammen und dürfen nicht
   einzeln umbrechen. Als **ein** Kind der Leiste kann die Zeile sie
   nicht mehr auseinanderreissen - vorher sprang "Aktiv | Mit Archiv"
   allein in die nächste Zeile, sobald der Zurücksetzen-Knopf dazukam. */
@media (max-width: 520px) {
  .filterbar .is-end { margin-left: 0; }
}

/* Blaettern unter langen Listen */
.pager {
  display: flex; align-items: center; justify-content: center;
  gap: .6rem; margin-top: .9rem; font-size: .85rem; color: var(--muted);
}

/* Kleiner Zusatz hinter einem Namen, z.B. "archiviert" */
.badge {
  display: inline-block; margin-left: .35rem;
  font-size: .68rem; font-weight: 600; letter-spacing: .03em; text-transform: uppercase;
  padding: .08rem .38rem; border-radius: 5px;
  background: var(--surface-2); color: var(--muted);
  /* Ein Abzeichen ist ein Wort, kein Absatz. Ohne das erbt es
     `overflow-wrap: anywhere` aus der Handyregel fuer Namensspalten und
     bricht mitten hinein: aus "geraten" wurden auf 390 px drei Zeilen
     "GE / RAT / EN", und die Tabellenzeile wuchs auf 140 px
     (Pruefbericht 2026-08-27, Punkt 16). */
  white-space: nowrap; overflow-wrap: normal;
}

/* Zusatz, der etwas Gutes meldet - der Geprüft-Haken am Lebensmittel. */
/* Das Geprüft-Abzeichen: dasselbe Blau wie das Siegel in der Liste -
   sonst hiesse dieselbe Aussage an zwei Stellen zwei verschiedene
   Farben. `nowrap`, weil sonst zwischen Siegel und Wort umbrochen
   wird; `inline-flex`, damit das Symbol auf der Grundlinie sitzt. */
.badge--gut {
  display: inline-flex; align-items: center; white-space: nowrap;
  background: color-mix(in srgb, var(--siegel) 14%, transparent);
  color: var(--siegel);
}

/* Verderblich am Lebensmittel: Sanduhr plus Wort, in Ocker. Rot bliebe
   schlechten Werten vorbehalten, und verderblich ist kein schlechter
   Wert - es ist ein Hinweis auf die Zeit. Aufbau wie .badge--gut, damit
   Symbol und Wort nicht auseinanderbrechen. */
.badge--verderblich {
  display: inline-flex; align-items: center; white-space: nowrap;
  background: color-mix(in srgb, var(--tag-ocker) 16%, transparent);
  color: var(--tag-ocker);
}
/* Die Sanduhr ist hoch und schmal und wirkt neben dem runden Siegel
   kleiner, als sie ist - ein Zehntel mehr gleicht das aus. */
.badge--verderblich .icon { width: 1.05em; height: 1.05em; }

/* Wasser: Tropfen plus Wort, in Petrol - die Farbe, die im
   Schlagwortkreis noch keinem Zustand gehoert und zu Wasser passt.
   Aufbau wie .badge--verderblich. */
.badge--trinken {
  display: inline-flex; align-items: center; white-space: nowrap;
  background: color-mix(in srgb, var(--tag-petrol) 16%, transparent);
  color: var(--tag-petrol);
}
.badge--trinken .icon { width: 1.05em; height: 1.05em; }

/* Allergene auf der Lebensmittelseite: zwei Saetze untereinander.
   "Enthaelt" traegt Gewicht, "kann Spuren enthalten" ist der leisere
   Hinweis - genau wie auf der Packung. */
.allergene { display: block; }
.allergene--enthaelt { font-weight: 600; }
.allergene--spuren { color: var(--muted); }

/* Geschätzt: der selbst getippte Eintrag im Tagebuch. Dasselbe warme
   Grau wie der Akzent, nicht Ocker und schon gar nicht Rot - der
   Eintrag ist nicht schlecht und läuft auch nicht ab, er ist nur nicht
   nachgeschlagen. Aufbau wie .badge--gut, damit Zeichen und Wort nicht
   auseinanderbrechen. */
.badge--geschaetzt {
  display: inline-flex; align-items: center; white-space: nowrap;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
}
/* Zwei liegende Wellen füllen nur die Mitte ihres Kastens - auf .95em
   blieben davon drei Pixel Höhe. Ein Fünftel mehr, wie bei der
   Sanduhr, und ein etwas dickerer Strich machen sie wieder lesbar. */
.badge--geschaetzt .icon { width: 1.2em; height: 1.2em; stroke-width: 2; }

/* Die beiden Abzeichen eines geschätzten Eintrags: `≈` und `KI`. Sie
   stehen nebeneinander und sagen Verschiedenes.

   **Kein Wort neben dem Zeichen** (FH, 2026-08-28: „≈ GESCHÄTZT" war
   ihm zu lang) — der ganze Satz steht im `title`.

   **Verschiedene Farben** (FH: „evtl. auch farbig gekennzeichnet"):
   `≈` in Grau sagt, **dass** geschätzt wurde — zurückhaltend, es ist
   kein Mangel, nur eine Herkunft. `KI` in Blau sagt, **wer** geschätzt
   hat — kühl und unterscheidbar, ohne wie eine Warnung auszusehen;
   Ocker und Rot sind ohnehin vergeben (verderblich, schlechte Werte).

   **Gleich hoch und mittig** (FH: „auf der gleichen Höhe", „mittig in
   den Kisten"). Beides braucht Nachhilfe, aus zwei verschiedenen
   Gründen:

   - Ein Kasten um ein blosses Symbol fällt kleiner aus als einer um
     Buchstaben. `min-height: 1.6em` gibt beiden dieselbe Höhe — 17 px,
     nachgemessen —, und das Symbol bleibt mit 1.1em darin, statt den
     Kasten aufzublähen.
   - Beim Zentrieren zieht das Symbol nach unten (die Wellen liegen im
     Zeichenquadrat unter der Mitte) und „KI" nach oben (Versalien ohne
     Unterlängen: die Zeile lässt unter ihnen Platz für ein g, das nie
     kommt). Das gleicht ein einseitiges `padding-block` aus.

   Die beiden Werte sind **ausgemessen**, nicht geraten: Bild bei
   sechsfacher Auflösung, Tinte gegen Kastenrand gezählt, nachjustiert.
   Danach steht das ≈ auf 0,2 px genau in der Mitte; bei „KI" bleibt
   ein Pixel, weil der Browser die Zeile auf ganze Pixel rastet — es
   gibt zwischen −1 und +1 keinen Zustand.

   Über `padding-block` und nicht über `transform`, damit die Kästen
   ihre gemeinsame Höhe behalten: `min-height` bestimmt die Höhe, das
   Polster nur, wo der Inhalt darin steht. */
.badge--zeichen, .badge--ki {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 1.6em; line-height: 1.35;
  /* Nach der Mitte ausrichten, nicht nach der Grundlinie: ein Kasten,
     der nur ein Symbol enthält, hat gar keine Grundlinie, und der
     Browser nimmt dann seine Unterkante — die beiden sassen deshalb
     zwei Pixel versetzt. */
  vertical-align: middle;
}
.badge--zeichen { padding-inline: .3rem; padding-block: 0 .08rem; }
.badge--zeichen .icon { width: 1.1em; height: 1.1em; }
.badge--ki { padding-block: .12rem 0; letter-spacing: .06em; }

.badge--geschaetzt.badge--zeichen {
  background: color-mix(in srgb, var(--tag-grau) 18%, transparent);
  color: var(--tag-grau);
}
.badge--ki {
  background: color-mix(in srgb, var(--tag-blau) 18%, transparent);
  color: var(--tag-blau);
}

/* Angefangen: zu diesem Rezept läuft ein Kochvorgang. Bernstein, wie
   überall, wo etwas begonnen und nicht abgeschlossen ist — nicht Rot,
   denn es ist nichts falsch daran, und nicht die Akzentfarbe, die schon
   „geschätzt" trägt. */
.badge--offen {
  display: inline-flex; align-items: center; white-space: nowrap;
  background: color-mix(in srgb, var(--warn) 16%, transparent);
  color: var(--warn);
}
.badge--offen .icon { width: 1.05em; height: 1.05em; }

/* Die Frist am Angebot (FH, 05.09.2026): "Es soll nur der Zeitraum
   dastehen. In der naechsten Zeile soll stehen 'noch x Tage' in einem
   Tag (Kasten, farbig)." Ocker wie `.badge--verderblich` - dieselbe
   Aussage, derselbe Ton: es ist ein Hinweis auf die Zeit und kein
   schlechter Wert. **Eine Farbe fuer alle Fristen** (FH gefragt): der
   letzte Tag sticht nicht heraus, dafuer springt die Zeile nicht die
   Farbe, waehrend die Woche vergeht.

   `display: block` und kein linker Abstand: das Abzeichen steht in
   der naechsten Zeile und buendig unter dem Zeitraum, nicht
   eingerueckt. `width: fit-content`, sonst zoege der Kasten ueber die
   ganze Zellenbreite. */
.badge--frist {
  display: block; width: fit-content;
  margin-left: 0; margin-top: .3rem;
  background: color-mix(in srgb, var(--tag-ocker) 16%, transparent);
  color: var(--tag-ocker);
}

/* Zusatz, der zur Eile mahnt - das Geschäft schließt gleich. */
.badge--warn {
  background: color-mix(in srgb, var(--warn) 16%, transparent);
  color: var(--warn);
}

/* Schlagworte am Rezept: eine Farbe je Kategorie. Dasselbe Muster wie
   oben - getönter Grund, kräftige Schrift -, damit sie sich von einem
   Zustandsabzeichen nicht in der Form unterscheiden, nur im Ton. */
.badge--grau   { background: color-mix(in srgb, var(--tag-grau) 16%, transparent);   color: var(--tag-grau); }
.badge--blau   { background: color-mix(in srgb, var(--tag-blau) 16%, transparent);   color: var(--tag-blau); }
.badge--ocker  { background: color-mix(in srgb, var(--tag-ocker) 16%, transparent);  color: var(--tag-ocker); }
.badge--salbei { background: color-mix(in srgb, var(--tag-salbei) 16%, transparent); color: var(--tag-salbei); }
.badge--rost   { background: color-mix(in srgb, var(--tag-rost) 16%, transparent); color: var(--tag-rost); }
.badge--pflaume { background: color-mix(in srgb, var(--tag-pflaume) 16%, transparent); color: var(--tag-pflaume); }
.badge--petrol { background: color-mix(in srgb, var(--tag-petrol) 16%, transparent); color: var(--tag-petrol); }
.badge--oliv   { background: color-mix(in srgb, var(--tag-oliv) 16%, transparent); color: var(--tag-oliv); }

/* Die Reihe unter dem Namen. Das erste Schlagwort bekommt keinen
   Abstand nach links - sonst stünde die Reihe eingerückt unter dem
   Namen statt bündig. */
/* Die Abzeichenreihe unter einer Ueberschrift - geprueft, verderblich,
   "nur du" auf der Lebensmittelseite. Dieselben Masse wie die
   Schlagwortreihe des Rezepts (FH, 2026-08-29: "gleiche Groesse und
   Darstellung, wie bei den Rezepten"); sie sind hier ausgeschrieben
   und nicht von .schlagworte geerbt, weil das eine Reihe von
   Schlagworten ist und das hier eine von Eigenschaften. Zwei Namen fuer
   zwei Sachen, auch wenn sie im Moment gleich aussehen.

   Leer bleibt sie unsichtbar: `:empty` faengt den Fall ab, dass keines
   der drei zutrifft - sonst stuende unter dem Namen eine Luecke. */
.abzeichen { display: flex; flex-wrap: wrap; gap: .3rem; margin: .35rem 0 0; align-items: center; }
.abzeichen:empty { display: none; }
.abzeichen .badge {
  display: inline-flex; align-items: center; margin-left: 0;
  font-size: .82rem; padding: .22rem .55rem;
}

/* Ein Zeichen hinter einem Namen - dieselbe Form wie das Siegel
   daneben, aber **nicht** dessen Klasse.

   `.siegel` heisst in dieser Anwendung "geprueft": das Auge liest es
   so, und `bin/test_feinschliff.js` fragt danach, um zu pruefen, dass
   das Bild des Anlegers verschwindet, sobald der Haken sitzt. Als die
   Sanduhr am 2026-08-29 kurzzeitig `.siegel` trug, hielt die Pruefung
   ein verderbliches Lebensmittel fuer ein geprueftes - zu Recht, denn
   die Klasse behauptete genau das.

   Eine Klasse ist ein Name fuer eine Bedeutung, nicht fuer ein
   Aussehen. Deshalb hier eine eigene. */
.namenszeichen {
  display: inline-flex; vertical-align: -.16em; margin-left: .3rem;
  color: var(--muted);
}
.namenszeichen .icon { width: 1.05em; height: 1.05em; stroke-width: 1.9; }
.namenszeichen--verderblich { color: var(--tag-ocker); }
.namenszeichen--trinken { color: var(--tag-petrol); }

.schlagworte { display: flex; flex-wrap: wrap; gap: .3rem; margin: .35rem 0 0; align-items: center; }
.schlagworte .badge { margin-left: 0; }
/* In der Liste enger, dort steht sie in einer Tabellenzelle. */
.schlagworte--eng { margin-top: .25rem; gap: .25rem; }

/* Auf der Rezeptseite ist Platz, und dort steht neben dem Symbol auch
   der Name (FH, 29.08.2026: "im Rezeptdetail sollten die Schlagworte
   auch etwas groesser sein"). Die Reihe steht dort allein unter der
   Ueberschrift und nicht in einer Tabellenzelle - sie darf atmen. */
.schlagworte:not(.schlagworte--eng) .badge {
  display: inline-flex; align-items: center;
  font-size: .82rem; padding: .22rem .55rem;
  /* Kein Umbruch zwischen Zeichen und Wort (FH, 29.08.2026): das
     Abzeichen ist **eine** Aussage, und ein Zeichen allein in der
     ersten Zeile sagt gar nichts. Bricht die Reihe, dann zwischen den
     Abzeichen - dafuer ist sie ein `flex-wrap`. */
  white-space: nowrap;
}
.schlagworte:not(.schlagworte--eng) .badge .icon {
  width: 1.3rem; height: 1.3rem; margin-right: .35em; stroke-width: 1.5;
  flex: none;
}

/* Zwischenüberschrift über einer Kästchengruppe im Formular. */
.field__label--sub { margin: .5rem 0 .15rem; font-size: .75rem; }

/* Der Knopf, der den Geprüft-Haken trägt: gesetzt steht er in der
   Akzentfarbe, ungesetzt bleibt er zurückhaltend. */
/* Der Geprüft-Knopf trägt das Blau des Siegels - dieselbe Aussage,
   dieselbe Farbe. Gesetzt ist er gefüllt, ungesetzt nur umrandet: so
   sieht man am Knopf selbst, woran man ist (FH, 2026-08-26). */
.btn--geprueft {
  border-color: color-mix(in srgb, var(--siegel) 55%, transparent);
  color: var(--siegel); background: transparent;
}
.btn--geprueft:hover { border-color: var(--siegel); }
.btn--geprueft.is-an {
  background: color-mix(in srgb, var(--siegel) 14%, transparent);
  border-color: var(--siegel);
}

/* --- Naehrwertfelder: "davon" gehoert unter seinen Hauptwert ---------- */
/* Untereinander, auch am Desktop (FH, 2026-08-26): in zwei Spalten
   sprang das Auge beim Abtippen einer Packung links-rechts-links, und
   die Reihenfolge auf der Verpackung ist eine Spalte. */
.nutrients { display: grid; gap: .9rem; }
.field--sub { position: relative; padding-left: 1.1rem; }
.field--sub::before {
  content: ""; position: absolute; left: .3rem; top: .35rem; bottom: .35rem;
  border-left: 2px solid var(--border);
}
.field--sub > label { font-weight: 500; }
/* Hauptwert und die zugehoerige "davon"-Zeile bleiben eine Einheit. */
.nutrient-group { display: grid; gap: .5rem; align-content: start; }

/* Dieselbe Klammer ausserhalb der Naehrwerte: ein Auswahlfeld und das
   "oder neue ..."-Textfeld, das dazugehoert. Nebeneinander in zwei
   Spalten war beim Ausfuellen nicht zu sehen, welches Textfeld zu
   welchem Auswahlfeld gehoert (FH, 2026-08-25). */
.feldgruppe { display: grid; gap: .5rem; align-content: start; }

/* Dieselbe Einrueckung in der Naehrwerttabelle der Ansicht. */
table th.is-sub { padding-left: 1.4rem; font-weight: 500; color: var(--muted); }
table td.is-sub { color: var(--muted); }

/* Schluessel-Wert-Tabellen (Herkunft, Kennzeichnung): der Wert darf
   umbrechen, sonst schiebt er sich aus der schmalen Kachel heraus. */
/* Feste Spalten (18.11.). Mit der Vorgabe des Browsers misst die
   Tabelle ihre Spalten am laengsten Inhalt, und "Identitaetskennzeichen"
   neben der Sternreihe war auf dem Handy breiter als die Karte: die
   rechte Spalte lief unter den Rand, und "Inhaltsstoffe" war seit dem
   ersten Tag abgeschnitten. Bei festem Massstab teilt sie den Platz
   45/55 und bricht um, was nicht passt - das lange Wort traegt dafuer
   einen weichen Trennstrich in der Vorlage. */
.table--kv { width: 100%; table-layout: fixed; }
.table--kv th[scope="row"] { text-align: left; width: 45%; font-weight: 600; color: var(--muted); }
.table--kv th, .table--kv td { white-space: normal; }
.table--kv td { text-align: left; }

/* --- Wiederholbare Zeilen (Einheiten, Packungen) --------------------- */
.rows { display: grid; gap: .7rem; }
.rows__row { display: grid; gap: .6rem; align-items: end; }
.rows__row--2 { grid-template-columns: 1fr 1fr; }
.rows__row--paket { grid-template-columns: 1fr 1fr; }
@media (min-width: 760px) {
  .rows__row--paket { grid-template-columns: 5rem 6rem 8rem 1fr; }
  /* Beschriftung nur ueber der ersten Zeile - darunter wiederholt sie sich nur. */
  .rows__row:not(:first-child) > .field > label { display: none; }
}
.field--radio input { width: auto; }
.details h3 { margin-bottom: .5rem; }

/* --- Nutri-Score ----------------------------------------------------- */
.nutri {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.5rem; height: 1.5rem; border-radius: 6px;
  font-size: .8rem; font-weight: 700; color: #fff;
}
/* Auf Gelb und Hellgruen ist Weiss nicht lesbar. */
.nutri[data-nutri="B"], .nutri[data-nutri="C"] { color: #1a1815; }

/* --- Symbolknöpfe in Tabellen ---------------------------------------- */
.btn--icon {
  padding: .35rem; flex: 0 0 auto;
  width: var(--feldhoehe-klein); height: var(--feldhoehe-klein);
  min-height: var(--feldhoehe-klein);
}
.icon {
  width: 1.15rem; height: 1.15rem;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Die Namensspalte darf umbrechen - sonst schiebt ein langer Name die
   Knöpfe am Zeilenende aus der Kachel.
   Linksbündig, und zwar ausdrücklich: in Tabellen stehen alle Spalten
   rechts ausser der ersten. Steht die Namensspalte nicht vorn - bei den
   Einkäufen ist das Datum davor -, klebte der Name sonst am rechten
   Rand. */
table th.is-name, table td.is-name { white-space: normal; min-width: 11rem; text-align: left; }

/* Spalten, die nur mitkommen, wenn Platz ist: Zucker und Ballaststoffe
   in der Rezeptübersicht (FH, 2026-08-24). kcal, Fett, KH und Eiweiß
   stehen immer da, der Rest verschwindet, bevor die Tabelle waagerecht
   zu schieben anfängt. Die Gruppenzeile darüber trägt ein colspan, das
   dann zu gross ist - das ist unschädlich, Browser kürzen es. */
@media (max-width: 1199px) {
  table th.spalte-weit, table td.spalte-weit { display: none; }
}

/* Jede Mahlzeit im Tagebuch steht in ihrer eigenen Tabelle. Ohne feste
   Breiten misst jede Tabelle für sich und die Spalten stehen von Karte
   zu Karte woanders. Deshalb feste Breiten; die Namensspalte bekommt den
   Rest (FH, 2026-08-24). */
table.table--mahlzeit { table-layout: fixed; min-width: 42rem; }
table.table--mahlzeit th:nth-child(2), table.table--mahlzeit td:nth-child(2) { width: 6rem; }
table.table--mahlzeit th:nth-child(3), table.table--mahlzeit td:nth-child(3) { width: 5rem; }
table.table--mahlzeit th:nth-child(4), table.table--mahlzeit td:nth-child(4),
table.table--mahlzeit th:nth-child(5), table.table--mahlzeit td:nth-child(5),
table.table--mahlzeit th:nth-child(6), table.table--mahlzeit td:nth-child(6) { width: 5.5rem; }
table.table--mahlzeit th:nth-child(7), table.table--mahlzeit td:nth-child(7) { width: 6rem; }
/* In der Namensspalte darf ein langer Name umbrechen, statt die feste
   Spalte zu überlaufen. */
table.table--mahlzeit td.is-name { overflow-wrap: anywhere; }

/* --- Die Mahlzeit am Telefon: drei Bloecke statt Querscrollen --------
   FH, 2026-08-28: "Zeilen umbrechen - Gericht/Lebensmittel, Nährwerte,
   Button". Sieben Spalten brauchen 42 rem; am Telefon sind 24 da, und
   die Tabelle schob sich bisher seitlich in ihrem Rahmen. Querscrollen
   ist an einer Filterleiste in Ordnung - dort sucht man eine Stufe -,
   aber nicht an dem, was man liest: die Naehrwerte standen rechts
   ausserhalb des Bildes, und der Loeschknopf noch weiter.

   Deshalb wird aus jeder Zeile ein kleiner Block: Name, darunter die
   Zahlen, darunter die Knoepfe. Kein zweites Markup - dieselbe Tabelle,
   nur anders gesetzt. Der Kopf faellt weg, weil er ueber Bloecken
   nichts mehr beschriftet; was welche Zahl ist, sagt `data-kurz` an der
   Zelle selbst.
   -------------------------------------------------------------------- */
@media (max-width: 620px) {
  table.table--mahlzeit { min-width: 0; table-layout: auto; }
  table.table--mahlzeit thead { display: none; }

  table.table--mahlzeit tr {
    display: flex; flex-wrap: wrap; align-items: baseline;
    gap: .1rem .8rem;
    padding: .45rem 0;
  }
  table.table--mahlzeit tbody tr + tr { border-top: 1px solid var(--border); }

  /* Die festen Spaltenbreiten von oben gelten hier nicht mehr - sie
     stammen aus dem Raster, das es gerade nicht mehr gibt. Sie stehen an
     `:nth-child()` und wiegen damit schwerer als eine Regel auf `td`
     allein; deshalb werden sie hier genauso genau wieder aufgehoben.
     Ohne das behielt die Mengenzelle ihre 6 rem und riss eine Luecke
     mitten in die Zahlenzeile. */
  table.table--mahlzeit td,
  table.table--mahlzeit th,
  table.table--mahlzeit th:nth-child(2), table.table--mahlzeit td:nth-child(2),
  table.table--mahlzeit th:nth-child(3), table.table--mahlzeit td:nth-child(3),
  table.table--mahlzeit th:nth-child(4), table.table--mahlzeit td:nth-child(4),
  table.table--mahlzeit th:nth-child(5), table.table--mahlzeit td:nth-child(5),
  table.table--mahlzeit th:nth-child(6), table.table--mahlzeit td:nth-child(6),
  table.table--mahlzeit th:nth-child(7), table.table--mahlzeit td:nth-child(7) {
    display: block; width: auto; border: 0; padding: 0;
  }

  /* Erster Block: der Name, ueber die ganze Breite. */
  table.table--mahlzeit td.is-name,
  table.table--mahlzeit th.is-name { flex: 1 1 100%; text-align: left; }

  /* Zweiter Block: die Zahlen nebeneinander, mit ihrer Beschriftung. */
  table.table--mahlzeit td.num { flex: 0 0 auto; text-align: left; font-size: .85rem; }
  table.table--mahlzeit td.num:empty { display: none; }
  table.table--mahlzeit td.num[data-kurz]::before {
    content: attr(data-kurz) " ";
    color: var(--muted); font-weight: 550;
  }

  /* Dritter Block: die Knoepfe, wieder ueber die ganze Breite.

     Und ohne die klebende Spalte: hier rollt nichts zur Seite, also
     gibt es auch nichts, das oben liegen muesste. Ihr Schatten nach
     links stand als senkrechter Strich links neben den beiden Knoepfen
     - eine Kante, hinter der keine Kante war (gefunden am 2026-09-01
     beim Nachsehen der Striche im Tagebuch). */
  table.table--mahlzeit td.is-aktionen {
    flex: 1 1 100%; margin-top: .3rem;
    position: static; box-shadow: none; background: none;
  }
  table.table--mahlzeit .table-actions { justify-content: flex-start; }

  /* Die Zusammen-Zeile hebt sich als **ein** Block ab, nicht als eine
     Reihe einzelner Kaestchen: die abgesetzte Flaeche liegt sonst je
     Zelle an, und aus der Summe wurden fuenf Fleckchen mit Luecken
     dazwischen. */
  /* Kein Akzentstrich mehr (FH, 2026-09-02) - am Telefon trug ihn die
     Zeile selbst, am breiten Schirm die Zellen; beide fallen. Der
     Abstand nach oben war hier schon da, er waechst nur auf dasselbe
     Mass wie drueben, und der Radius auch. So steht die Summe auf
     beiden Geraeten als derselbe Riegel da. */
  table.table--mahlzeit tfoot tr {
    background: var(--surface-2);
    border-top: 0;
    border-radius: .6rem;
    padding: .45rem .5rem;
    margin-top: .4rem;
    font-weight: 600;
  }
  table.table--mahlzeit tfoot tr > th,
  table.table--mahlzeit tfoot tr > td { background: none; border-top: 0; }
  table.table--mahlzeit tfoot td:empty { display: none; }

  /* --- Der Kasten einer Routine am Telefon --------------------------
     Hier ist die Zeile ein Flex-Kasten und die Zelle ein Block. Damit
     faellt genau das auseinander, was den Kasten weiter oben aus
     Zellkanten zusammensetzt - zweimal, und beides stand in FHs
     Screenshot vom 2026-09-01:

     - Die `colspan`-Zelle des Kopfes ist keine Zelle ueber alle Spalten
       mehr, sondern ein Block so breit wie sein Text. Bei 390 px
       gemessen: 178 von 355 px. Der abgesetzte Grund endete deshalb
       mitten in der Zeile, und der Papierkorb, der mit `right: .6rem`
       an dieser Zelle haengt, sass 33,6 px **im** Namen - im Bild lag
       er im Wort "MORGENS".

     - Der Rahmen ist ein `inset`-Schatten je Zelle. Aus dem einen
       Strich unter der letzten Zeile des Kastens wurden damit sechs:
       einer unter jeder Zelle, also unter jedem Naehrwert. Das sind die
       "Unterstreichungen" an den Postenzeilen; sie waren nie eine
       Textauszeichnung, sondern eine Kante, die sechsmal statt einmal
       gezeichnet wurde.

     Also: die Kopfzelle wieder ueber die ganze Breite, und den Rahmen
     an die Zeile statt an die Zellen. Die Zeile ist hier keine
     Tabellenzeile mehr, sondern ein Flex-Kasten - an ihr wirken
     `border` und `border-radius` ganz gewoehnlich, und `border-collapse`
     redet nicht mehr hinein. Deshalb duerfen die Ecken hier auch rund
     sein, anders als am breiten Schirm. */
  table.table--mahlzeit tr.routinekopf > td { flex: 1 1 100%; }

  table.table--mahlzeit tr.routinekopf > td,
  table.table--mahlzeit tr.routinekopf.is-zu > td,
  table.table--mahlzeit tr.is-routine > td,
  table.table--mahlzeit tr.is-routine > td:first-child,
  table.table--mahlzeit tr.is-routine > td:last-child,
  table.table--mahlzeit tr.is-routine--ende > td,
  table.table--mahlzeit tr.is-routine--ende > td:first-child,
  table.table--mahlzeit tr.is-routine--ende > td:last-child { box-shadow: none; }

  /* Und aus demselben Grund faellt hier auch alles, was am breiten
     Schirm an der **Zelle** haengt: Rundung und der helle Rand, mit dem
     der Kasten sich dort vom Tabellenkopf absetzt. Hier traegt die
     Zeile beides; an der Zelle blieb der helle Rand als weisser Bogen
     mitten im zugeklappten Kasten stehen. */
  table.table--mahlzeit tr.routinekopf > td,
  table.table--mahlzeit tr:first-child.routinekopf > td,
  table.table--mahlzeit tr.routinekopf.is-zu > td,
  table.table--mahlzeit tr.is-routine--ende > td,
  table.table--mahlzeit tr.is-routine--ende > td:first-child,
  table.table--mahlzeit tr.is-routine--ende > td:last-child {
    border-top: 0;
    border-radius: 0;
  }

  /* Der Grund liegt jetzt an der Zeile: die Zelle endet innerhalb der
     Polsterung, ihr Hintergrund haette links und rechts einen Streifen
     freigelassen. */
  table.table--mahlzeit tr.routinekopf,
  table.table--mahlzeit tr.is-routine {
    border-left: 1px solid var(--routinerahmen);
    border-right: 1px solid var(--routinerahmen);
    padding-inline: .5rem;
  }
  table.table--mahlzeit tr.routinekopf { background: var(--surface-2); }
  table.table--mahlzeit tr.is-routine  { background: var(--surface); }
  table.table--mahlzeit tr.routinekopf > td,
  table.table--mahlzeit tr.is-routine > td { background: none; }

  table.table--mahlzeit tr.routinekopf {
    border-top: 1px solid var(--routinerahmen);
    border-radius: var(--routineecke) var(--routineecke) 0 0;
  }
  table.table--mahlzeit tr.is-routine--ende {
    border-bottom: 1px solid var(--routinerahmen);
    border-radius: 0 0 var(--routineecke) var(--routineecke);
  }
  /* Zugeklappt ist der Kopf der ganze Kasten - dann rundet er an allen
     vier Ecken und traegt auch den Strich unten. */
  table.table--mahlzeit tr.routinekopf.is-zu {
    border-bottom: 1px solid var(--routinerahmen);
    border-radius: var(--routineecke);
  }
  /* Zwischen dem Kopf und der ersten Zeile keine zweite Linie: sie
     gehoeren zusammen. Am breiten Schirm steht dieselbe Regel weiter
     oben, dort aber an der Zelle. */
  table.table--mahlzeit tbody tr.routinekopf + tr { border-top: 0; }

  /* Und die Luft um den Kasten - hier als `margin` an der Zeile, weil
     die Zeile am Telefon ein Flex-Kasten ist und die Zellen kein
     Polster tragen duerfen (weiter oben auf 0 gesetzt). Am breiten
     Schirm erledigt dasselbe ein `padding` in den Nachbarzellen; eine
     Tabellenzeile kennt dort keinen Aussenabstand.

     Die Trennlinie der Zeile darunter faellt dabei weg: sie liefe quer
     durch die runde Ecke des Kastens - genau der Strich, wegen dessen
     die Rundung am 2026-08-30 weichen musste. */
  table.table--mahlzeit tbody tr.is-routine--ende + tr {
    border-top: 0;
    margin-top: .7rem;
  }
  table.table--mahlzeit tbody tr:has(+ tr.routinekopf) { margin-bottom: .7rem; }
}

/* Zutatenliste am Rezept: die Menge steht vorn und trotzdem
   rechtsbündig (FH, 2026-08-24), damit die Zahlen untereinander
   fluchten. Sonst gilt in Tabellen die erste Spalte als Textspalte und
   wäre linksbündig. */
.table--zutaten td:first-child { text-align: right; width: 1%; }
.table--zutaten td.is-name { text-align: left; min-width: 0; }
/* Zutaten zuordnen: jede Spalte ist Text, nichts wird gerechnet. */
.table--zuordnen th, .table--zuordnen td { text-align: left; }

/* Kategorien sind lang ("Ketchup, Würz- und Feinkostsoßen, Senf" mit
   Oberkategorie davor). Lieber umbrechen als die Tabelle breit machen -
   FH, 2026-08-25. */
table td.is-kategorie { white-space: normal; max-width: 20rem; }

/* Ein Auswahlfeld darf in einer Knopfreihe stehen - dann bitte so schmal
   und so hoch wie die Knoepfe daneben. */
.btn-row select, .btn-row input[type=text] { padding: .45rem .6rem; font-size: .9rem; max-width: 14rem; }

/* Ein Ziel, zu dem der Server nach einem Handgriff zurueckschickt, darf
   nicht unter der festen Kopfleiste landen (FH, 2026-09-01: nach dem
   Hochladen an derselben Stelle bleiben). `scroll-margin-top` haelt
   genau so viel Abstand, wie die Leiste hoch ist, plus etwas Luft -
   sonst faengt die Mahlzeit hinter ihr an und man scrollt doch wieder.

   Nur an den Karten, die wirklich ein Ziel sind (`[id]`), und nicht an
   allen: eine Regel auf jedes Element mit Kennung waere eine Regel
   ueber Dinge, die mit dem Springen nichts zu tun haben.

   `.sprungziel` ist dasselbe fuer alles, was keine Karte ist - die
   Bewertungszeile am Lebensmittel etwa steht in einer Tabelle (FH,
   2026-09-02). Sie sagt es selbst, statt dass hier eine Liste von
   Kennungen entstuende. */
.card[id], .sprungziel { scroll-margin-top: calc(var(--topbar-h, 3.5rem) + 1rem); }

/* Wer aus einer Einheit in die Liste zurueckspringt, soll die Zeile
   auch **sehen**, an der er war (FH, 2026-09-03). Der Anker bringt ihn
   an die richtige Stelle; ohne ein Zeichen sucht das Auge sie dort
   trotzdem. Ein kurz aufleuchtender Hintergrund, dann ist es vorbei -
   eine dauerhafte Markierung waere eine Behauptung ueber die Zeile
   selbst. */
tr.sprungziel:target > * { animation: sprungziel 2.4s ease-out 1; }
@keyframes sprungziel {
  0%, 45% { background: color-mix(in srgb, var(--accent) 18%, transparent); }
  100%    { background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  tr.sprungziel:target > * { animation: none; background: color-mix(in srgb, var(--accent) 12%, transparent); }
}

/* --- Kopfzeilen und Navigation auf schmalen Schirmen ----------------- */
/* Überschrift und Knopfreihe brechen untereinander um, statt die Knöpfe
   in eine schmale Spalte am rechten Rand zu quetschen. */
@media (max-width: 620px) {
  .card__head { flex-wrap: wrap; align-items: center; gap: .6rem; }
  .card__head > .btn-row { width: 100%; }
}
/* "Lebensmittel" ist das längste Wort in der unteren Leiste - unter
   420px braucht es eine Nummer kleiner, sonst stösst es an den Rand. */
@media (max-width: 420px) {
  .nav a { font-size: .58rem; }
}

/* Die Marke steht hinter dem Namen und tritt zurück - kleiner, leichter,
   in der gedämpften Farbe. Keine Klammern. */
.name__brand { font-size: .85em; font-weight: 450; color: var(--muted); letter-spacing: .01em; }

/* --- Der Lebensmittel-Sucher ----------------------------------------
   Ein Feld für alles: tippen oder scannen, darunter Menge und Einheit,
   darunter die Packungsgrößen zum Antippen. Die Trefferliste liegt über
   dem Inhalt, damit sie in einer Zeile nichts aufschiebt.
   -------------------------------------------------------------------- */
.picker { display: grid; gap: .5rem; min-width: 0; }
.picker__suche { position: relative; display: flex; gap: .4rem; min-width: 0; }
/* Seit dem X im Feld ist die Hülle das Flexkind, nicht mehr das Feld
   selbst - sonst stünde der Knopf ausserhalb und das Feld waere so
   breit wie sein Inhalt. */
.picker__feld { flex: 1 1 auto; min-width: 0; }
.picker__input { width: 100%; min-width: 0; }
/* Der Kameraknopf ist so hoch wie das Suchfeld daneben - die feste Höhe
   der Tabellenknöpfe passt hier nicht (FH, 2026-08-24). */
.picker__suche .btn--icon { height: auto; align-self: stretch; width: 2.9rem; }
/* Derselbe Gedanke in den Zutatenzeilen: der Loeschknopf steht neben
   Textfeldern und ist deshalb so hoch wie sie. */
.rows__row .btn--icon { width: var(--feldhoehe); height: var(--feldhoehe); }

/* Die aufklappende Liste teilen sich der Lebensmittel-Sucher und die
   Suchhilfe an den Auswahlfeldern (`sucher.js`, FH 2026-08-30). Zwei
   Regelsaetze fuer dasselbe Bild liefen frueher oder spaeter
   auseinander - dann sieht dieselbe Liste an zwei Stellen verschieden
   aus, ohne dass jemand das entschieden haette. */
.picker__liste, .sucher__liste {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 40;
  margin: 0; padding: .25rem; list-style: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 9px; box-shadow: var(--shadow);
  max-height: 17rem; overflow-y: auto;
}
.picker__treffer {
  display: flex; align-items: baseline; justify-content: space-between; gap: .8rem;
  width: 100%; padding: .5rem .6rem; border: 0; border-radius: 7px;
  background: transparent; color: var(--text); text-align: left; cursor: pointer;
}
/* Der Treffer ist seit 2026-08-27 die Zeile selbst (`role="option"`),
   kein Knopf darin - eine Auswahlliste erwartet ihre Eintraege
   unmittelbar unter sich. Hervorgehoben wird mit `.is-aktiv`: der Fokus
   bleibt im Suchfeld, sonst verloere das Tippen ihn bei jedem
   Pfeildruck. */
.picker__treffer:hover, .picker__treffer.is-aktiv,
.sucher__eintrag:hover, .sucher__eintrag.is-aktiv { background: var(--surface-2); }
.picker__treffer.is-aktiv,
.sucher__eintrag.is-aktiv { outline: 2px solid var(--accent); outline-offset: -2px; }
.picker__name { min-width: 0; }
.picker__wert { flex: 0 0 auto; color: var(--muted); font-size: .8rem; font-variant-numeric: tabular-nums; }
.picker__leer, .sucher__leer { padding: .6rem; color: var(--muted); font-size: .9rem; }

/* --- Suchhilfe an einem Auswahlfeld --------------------------------
   FH, 2026-08-30: "Lebensmittel Suchhilfe im Dropdown". Marke, Zutat
   und Kategorie sind lange Listen, und bei den Unterkategorien heissen
   mehrere gleich - die trifft man mit dem Auge nicht.

   `position: relative`, damit die Liste sich an dieser Huelle
   ausrichtet und nicht an der naechsten Karte darueber. Das
   Auswahlfeld selbst steht daneben weiter im Formular, nur `hidden` -
   siehe den Kopf von sucher.js. */
.sucher { position: relative; min-width: 0; }
/* Nach oben, wenn unten kein Platz mehr ist (FH, 2026-08-30: "Das
   Dropdown wird nach unten hin abgeschnitten"). Welche Richtung gilt,
   entscheidet `sucher.js` beim Oeffnen und misst dafuer den Platz im
   Fenster; hier steht nur, was die Entscheidung bewirkt.

   Die Hoehe kommt ebenfalls von dort und ueberschreibt die
   `max-height` der gemeinsamen Regel - damit die Liste in keiner
   Richtung ueber den Fensterrand ragt. */
.sucher--oben .sucher__liste { top: auto; bottom: calc(100% + 4px); }
.sucher__feld { width: 100%; min-width: 0; }
.sucher__eintrag {
  padding: .45rem .6rem; border-radius: 7px; cursor: pointer;
  color: var(--text);
}
/* "ueber Rinderfond" hinter dem Namen: das Wort, ueber das der Treffer
   kam (FH, Paket 12). Klein und grau - es ist die Begruendung, nicht
   der Name. Es steht **hinter** dem Namen und nicht darunter: die
   Liste soll nicht bei jedem zweiten Eintrag doppelt so hoch werden. */
.sucher__ueber { margin-left: .4rem; color: var(--muted); font-size: .82rem; }
/* Die Oberkategorie ist Ueberschrift und nicht waehlbar - dasselbe,
   was das Auswahlfeld selbst mit `<optgroup>` tut.

   **Gleiche Schrift wie ein Eintrag, nur fett** (FH, 2026-08-30: "die
   Ueberschriften sollten auch suchbar sein (sind sie), daher gleiche
   schrift, aber fett"). Sie stand zuerst kleiner und gedeckt da, wie
   eine Beschriftung - aber wer "Getraenke" tippt, findet damit die
   Saefte darunter, und was man suchen kann, ist Inhalt und nicht
   Beiwerk. Dass sie sich nicht anklicken laesst, sagen der fehlende
   Zeiger und das fehlende Hervorheben; die Einrueckung darunter sagt,
   was zu ihr gehoert. */
.sucher__gruppe {
  padding: .5rem .6rem .15rem; font-weight: 700;
  color: var(--text); cursor: default;
}
/* Steht ein Eintrag darueber, braucht die Ueberschrift etwas mehr Luft
   nach oben als nach unten - sonst klebt sie an der Zeile davor und
   sieht aus, als gehoere sie noch dazu. Im Bild nachgesehen, nicht
   vermutet (2026-08-30). */
.sucher__eintrag + .sucher__gruppe { padding-top: .7rem; }
/* Die Eintraege einer Gruppe ruecken ein, sonst steht die Ueberschrift
   in derselben Flucht wie das, was unter ihr haengt.

   Eine eigene Klasse und **nicht** `.sucher__gruppe ~ .sucher__eintrag`:
   in der Kategorieliste stehen Oberkategorien ohne Unterkategorien als
   gewoehnliche Eintraege zwischen den Gruppen, und der
   Geschwister-Ausdruck haette die faelschlich mit eingerueckt. */
.sucher__eintrag--gruppiert { padding-left: 1.1rem; }

/* --- "… neu anlegen" -------------------------------------------------
   FH, 2026-09-01: "Wenn wir einen neuen Wert eintippen, sollte dieser
   einfach angelegt werden." Der letzte Eintrag der Liste tut das - er
   steht dort, sobald das Getippte auf keinen Namen genau passt.

   Er soll sich von den Treffern abheben, ohne sie zu ueberstrahlen: er
   ist die Antwort fuer den Fall, dass nichts passt, und nicht der
   naechstbeste Treffer. Deshalb eine Linie darueber statt einer Farbe
   dahinter - das trennt, ohne zu rufen.

   Der Rand nur, wenn Treffer darueber stehen: bei einer leeren Liste
   waere er ein Strich unter nichts. */
.sucher__eintrag--neu { color: var(--muted); }
.sucher__eintrag + .sucher__eintrag--neu,
.sucher__gruppe + .sucher__eintrag--neu {
  margin-top: .25rem; padding-top: .5rem;
  border-top: 1px solid var(--border);
}
/* Der getippte Name traegt die Aussage - "neu anlegen" ist nur das
   Verb dazu. Also er in der Textfarbe und halbfett, der Rest gedaempft
   wie die Zeile. */
.sucher__neuname { color: var(--text); font-weight: 600; }

/* Der Satz unter dem Feld, wenn ein neuer Name eingetragen ist. Er
   sagt, was beim Speichern passiert - ohne ihn saehe ein Name, den es
   in der Liste nicht gibt, aus wie eine Eingabe, die niemand gelesen
   hat. Dieselbe Machart wie `.field__hint` daneben; sie hier zu erben
   ginge nicht, weil der Sucher nicht immer in einem `.field` steht. */
.sucher__neu { margin: .3rem 0 0; font-size: .8rem; color: var(--muted); }
/* Und das Feld selbst bekommt die Farbe des Akzents an den Rand: es
   steht dort etwas, das es noch nicht gibt. */
.sucher--neu .sucher__feld { border-color: var(--accent); }

.picker__menge { display: grid; grid-template-columns: 6.5rem 1fr; gap: .5rem; min-width: 0; }

/* Packungsgrößen: ein Tipp trägt die ganze Packung als Gramm ein. */
.chips { display: flex; flex-wrap: wrap; gap: .35rem; }
.chip {
  padding: .28rem .6rem; border-radius: 999px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--muted); font-size: .82rem; font-weight: 550;
}
.chip:hover { color: var(--text); border-color: var(--accent); }

@media (min-width: 760px) {
  .picker { grid-template-columns: minmax(0, 1fr) 15rem; align-items: start; }
  .picker .chips { grid-column: 1 / -1; }
}

/* Eine Zutatenzeile im Gerichtformular: der Sucher, daneben der Knopf
   zum Wegnehmen. Eine feine Linie trennt die Zutaten voneinander. */
.rows__row--zutat { grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: .5rem; }
.rows__row--zutat + .rows__row--zutat { border-top: 1px solid var(--border); padding-top: .7rem; }

/* Ein Posten im Einkauf: Sucher, daneben Menge, Preis, Angebot und der
   Eimer. Auf dem Handy stapelt es sich - der Sucher braucht die Breite. */
.rows__row--posten { grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: .5rem; }
.rows__row--posten + .rows__row--posten { border-top: 1px solid var(--border); padding-top: .7rem; }

/* Eine Zeile des KI-Vorschlags: Haken ganz links, daneben der Rest. Der
   Haken steht in einer eigenen Spalte und nicht im Fluss, damit die
   Zeilen darunter fluchten — sonst wandert er mit der Länge des Namens.

   **Steht hier unten und nicht bei den anderen neuen Regeln**, weil
   `.rows__row` weiter oben `align-items: end` setzt: bei gleicher
   Spezifität gewinnt die spätere Regel, und der Haken sass in der
   Zeile der Zahlenfelder statt oben. `--zutat` und `--posten` stehen
   aus demselben Grund hier. Im Bild gesehen, nicht gedacht. */
.rows__row--schaetzung {
  display: grid; grid-template-columns: 2rem minmax(0, 1fr); gap: .6rem;
  align-items: start;
}
.schaetzzeile__an { display: flex; justify-content: center; padding-top: .55rem; }
.schaetzzeile__an input { width: auto; margin: 0; }
.schaetzzeile { display: grid; gap: .5rem; min-width: 0; }
.schaetzzeile__kopf { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.schaetzzeile__kopf input { flex: 1 1 12rem; min-width: 0; }
/* Fünf Zahlenfelder: am Handy zu dritt, ab 620 px alle nebeneinander.
   Dasselbe Muster wie bei den Ad-hoc-Werten. */
.schaetzzeile__zahlen {
  display: grid; gap: .4rem .6rem;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (min-width: 620px) {
  .schaetzzeile__zahlen { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
.schaetzzeile__zahlen input { text-align: right; font-variant-numeric: tabular-nums; }
/* Vier Felder seit dem 2026-08-30, nicht mehr drei: Anzahl, Preis,
   Steuersatz, Angebot. Der Satz bekommt 6,5rem - "MwSt. —" ist die
   breiteste Wahl darin, und abgeschnitten hiesse sie "MwSt.", also
   etwas anderes. */
.posten__zahlen { display: grid; grid-template-columns: 5rem 6.5rem 6.5rem auto; gap: .5rem; align-items: center; margin-top: .5rem; }
.posten__steuer { min-width: 0; }
/* Am Telefon in zwei Reihen: Anzahl und Preis oben, Satz und Angebot
   darunter. Zu viert nebeneinander passten sie auf 390 Punkten nicht -
   "Angebot" wurde am rechten Rand abgeschnitten, und der Haken davor
   war nicht mehr zu treffen. Zwei Reihen sind hier besser als vier
   gequetschte Spalten: die Zeile eines Postens ist ohnehin ein Block. */
@media (max-width: 620px) {
  .posten__zahlen { grid-template-columns: 6.5rem minmax(0, 1fr); }
  .posten__steuer  { grid-column: 1 / 2; }
  .posten__angebot { grid-column: 2 / 3; }
}
.posten__angebot { display: flex; align-items: center; gap: .35rem; font-size: .85rem; color: var(--muted); white-space: nowrap; }
.posten__angebot input { width: auto; }
/* Am breiten Bildschirm steht ein Posten wirklich in EINER Zeile:
   Sucher mit Packung, daneben Anzahl, Preis, Angebot, dann der Eimer.
   Vorher rutschten die drei nach unten, obwohl neben dem Suchfeld die
   halbe Breite leer stand (FH, 2026-08-27).
   `display: contents` hebt die drei Felder aus ihrem Kasten in das
   Raster der Zeile - ohne das säßen sie zu dritt in einer Zelle. */
@media (min-width: 1000px) {
  .rows__row--posten > div {
    display: grid; grid-template-columns: minmax(0, 1fr) 5rem 7rem 6.5rem auto;
    gap: .5rem; align-items: start;
  }
  .rows__row--posten .posten__zahlen { display: contents; }
  .rows__row--posten .posten__angebot { height: var(--feldhoehe); }
  .rows__row--posten .posten__steuer { height: var(--feldhoehe); }
  /* Die Packung braucht hier keine 15rem - der Name ist laenger und
     wurde sonst mitten im Wort abgeschnitten. */
  .rows__row--posten .picker { grid-template-columns: minmax(0, 1fr) 9.5rem; }
}
/* Im Packungsmodus steht dort nur das Auswahlfeld - ohne diese Zeile
   sass es in der 6,5rem schmalen ersten Spalte und schnitt "120 Kps"
   nach vier Zeichen ab. */
.picker__menge > select:only-child { grid-column: 1 / -1; }
/* Ein Kassenbeleg ist schmal und hoch - als Vorschau reicht eine Kachel
   in Daumengroesse, die Lupe zeigt ihn ganz. */
.belegbild { display: block; max-width: 100%; max-height: 14rem;
  border-radius: var(--radius); object-fit: contain; }
/* Eine Zutatenzeile im Rezept: Menge, Einheit, Zutat, neue Zutat,
   Notiz und der Eimer. Auf dem Handy stapelt alles untereinander,
   ab Tablet steht die Zeile nebeneinander. */
.rows__row--rzutat { grid-template-columns: minmax(0, 1fr); align-items: start; gap: .5rem; }
.rows__row--rzutat + .rows__row--rzutat { border-top: 1px solid var(--border); padding-top: .7rem; }
@media (min-width: 760px) {
  .rows__row--rzutat { grid-template-columns: 5rem 8rem minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) auto; align-items: center; }
  .rows__row--rzutat + .rows__row--rzutat { border-top: 0; padding-top: 0; }
}

/* Zutatenauswahl und "oder neue Zutat" in EINEM Rasterfeld (03.09.2026,
   FH: "Zutatendropdown, sollte neue Eintraege erzeugen"). Mit Skript
   versteckt sucher.js das Textfeld, und es bleibt beim Auswahlfeld
   allein; ohne Skript stehen beide untereinander, so wie Marke und
   "oder neue Marke" am Lebensmittel. Ein siebtes Kind der Zeile waeren
   sie nicht - die Reihe hat sechs Spalten, und ein verstecktes Kind
   nimmt keine ein: die Zeile rutschte um eine Spalte. */
.rzutat__zutat { display: grid; gap: .35rem; min-width: 0; }

/* Ein Zubereitungsschritt: die Nummer, das Feld, der Eimer. */
.rows__row--schritt { grid-template-columns: 1.6rem minmax(0, 1fr) auto; align-items: start; gap: .5rem; }
.schritt__nr { color: var(--muted); font-size: .85rem; padding-top: .55rem; text-align: right; }

/* Die Zubereitung in der Anzeige - nummeriert, mit Luft dazwischen. */
.schritte { display: grid; gap: .6rem; margin: .2rem 0 0; padding-left: 1.3rem; }
.schritte li { padding-left: .2rem; line-height: 1.5; }

.zutaten { display: grid; gap: .7rem; }
.zutaten h2 { font-size: 1rem; }

/* --- Bewertung: Sterne ----------------------------------------------
   Der Stern ist eine Form aus diesem Stilblatt, kein Schriftzeichen.

   Vorgeschichte: erst lagen die Zeichen ★ frei nebeneinander, mit
   Abstand dazwischen - 70 % der Breite trafen nicht die Mitte des
   vierten Sterns, ein halber Stern sah aus wie zwei Drittel. Dann
   steckte jedes Zeichen in einem Kästchen fester Breite; im Container
   stimmte damit alles auf den Pixel, auf dem Telefon aber nicht: dort
   kommt ★ aus einer anderen Schrift, deren Zeichen im Vorschub anders
   sitzt. Die Trefferfläche lag deshalb eine halbe Stufe neben dem, was
   man sah - für 3,5 musste man auf 4 tippen (FH, 2026-08-25).

   Jetzt zeichnet --stern-maske den Stern, überall gleich und exakt
   mittig im Kästchen. Grau- und Farbfläche werden mit derselben Maske
   ausgestanzt, die Farbfläche zusätzlich auf den Anteil beschnitten.
   Damit stimmt jede Zwischenstufe von selbst. */
:root {
  --stern-maske: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2.4L14.9 8.6 21.6 9.5 16.8 14.2 17.94 21 12 17.8 6.06 21 7.2 14.2 2.4 9.5 9.1 8.6Z'/%3E%3C/svg%3E");

  /* Derselbe Umriss, nur gestrichelt statt gefuellt (FH, 02.09.: "wenn
     noch keiner bewertet hat, sollen 5 strichelte sterne mit (0) da
     stehen"). Eine leere Flaeche und "null von fuenf" saehen sonst
     gleich aus und sind nicht dasselbe: das eine heisst "noch niemand",
     das andere waere ein Urteil.

     Derselbe Pfad wie oben - so sitzt der gestrichelte Stern auf dem
     Punkt dort, wo der gefuellte sitzt. Der Strich liegt auf der
     Kontur, deshalb ist das viewBox-Feld etwas weiter gefasst; sonst
     schnitte die Maske die halbe Strichbreite ab. */
  --stern-maske-strich: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-1 -1 26 26'%3E%3Cpath d='M12 2.4L14.9 8.6 21.6 9.5 16.8 14.2 17.94 21 12 17.8 6.06 21 7.2 14.2 2.4 9.5 9.1 8.6Z' fill='none' stroke='%23000' stroke-width='1.6' stroke-linejoin='round' stroke-dasharray='2.8 2.2'/%3E%3C/svg%3E");
}

.sterne {
  --stern-feld: 1.15em;
  position: relative; display: inline-block; vertical-align: -.22em;
  width: calc(5 * var(--stern-feld)); height: var(--stern-feld);
}
.sterne__leer, .sterne__voll {
  position: absolute; top: 0; left: 0; height: 100%;
  background: currentColor;
  -webkit-mask-image: var(--stern-maske); mask-image: var(--stern-maske);
  -webkit-mask-size: var(--stern-feld) 100%; mask-size: var(--stern-feld) 100%;
  -webkit-mask-repeat: repeat-x; mask-repeat: repeat-x;
}
.sterne__leer { width: 100%; color: color-mix(in srgb, var(--muted) 38%, transparent); }
.sterne__voll { color: var(--accent-strong); }

/* Eingabe: drei Lagen übereinander, alle genau fünf Kästchen breit.
   Die zehn Flächen teilen die Reihe mit "flex: 1 1 0" in zehn gleiche
   Teile - eine Fläche ist damit per Rechnung genau eine Sternhälfte,
   ganz gleich welche Schrift das Gerät hat. */
/* Bewerten in der Ansicht (Paket 8, umgebaut am 02.09.). Eine Zeile:
   die Sterne, die Zahl der Stimmen, sonst nichts. Der Klick auf einen
   Stern speichert - siehe partials/bewerten.

   `gap` klein: Klammer und Sterne gehoeren zusammen und lesen sich als
   ein Ding, nicht als zwei nebeneinander. */
.bewerten { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.bewerten__wert { display: flex; align-items: center; gap: .35rem; flex: 0 0 auto; }
.bewerten .sternwahl { gap: 0; }

/* Etwas kleiner als im Formular: hier steht die Reihe in einer
   Tabellenzelle und hat die Klammer neben sich. Bei 2,3 rem passte
   beides in einer schmalen Spalte nicht mehr nebeneinander, und die
   Klammer rutschte unter die Sterne. Die Trefferflaechen bleiben
   2,6 rem hoch - angetippt wird die Hoehe, nicht die Breite. */
.bewerten .sternwahl__reihe { --stern-feld: 2rem; }
/* Auf dem Handy noch etwas enger: fuenf Sterne zu 2 rem sind mit der
   Klammer breiter als die 55 % der Tabelle neben "Bewertung". */
@media (max-width: 620px) {
  .bewerten .sternwahl__reihe { --stern-feld: 1.7rem; }
}

/* Der Knopf traegt den Fall ohne JavaScript. Sobald app.js den Klick
   uebernimmt, setzt es `is-flink` und der Knopf geht weg - er bleibt
   aber im Formular stehen, damit die Eingabetaste weiter abschickt. */
.bewerten.is-flink .bewerten__knopf {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  /* Ohne diese drei bliebe der Knopf 24 Punkte breit: `width: 1px`
     kann bei `box-sizing: border-box` nicht unter Polsterung und
     Rahmen des `.btn` fallen. */
  padding: 0; border: 0; min-height: 0;
}

/* Der Weg zurueck steht nur da, wenn es etwas zurueckzunehmen gibt.
   Klein und gedeckt: er soll auffindbar sein und nicht einladend -
   dieselbe Ueberlegung wie beim Loeschweg unter den Konten. */
.bewerten__weg {
  border: 0; background: none; padding: .1rem .2rem; cursor: pointer;
  font: inherit; font-size: .82rem; color: var(--muted);
  text-decoration: underline; text-underline-offset: 2px;
}
.bewerten__weg:hover { color: var(--bad); }

.sternwahl { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; }
.sternwahl__reihe {
  --stern-feld: 2.3rem;
  position: relative; flex: 0 0 auto;
  width: calc(5 * var(--stern-feld)); height: 2.6rem;
}
.sternwahl__radio { position: absolute; width: 1px; height: 1px; opacity: 0; }

.sternwahl__bild {
  position: absolute; left: 0; right: 0; top: 50%;
  height: var(--stern-feld); transform: translateY(-50%);
}
.sternwahl__leer, .sternwahl__voll {
  position: absolute; top: 0; left: 0; height: 100%;
  background: currentColor;
  -webkit-mask-image: var(--stern-maske); mask-image: var(--stern-maske);
  -webkit-mask-size: var(--stern-feld) 100%; mask-size: var(--stern-feld) 100%;
  -webkit-mask-repeat: repeat-x; mask-repeat: repeat-x;
}
.sternwahl__leer { width: 100%; color: color-mix(in srgb, var(--muted) 38%, transparent); }

/* Noch niemand: der Umriss statt der Flaeche. Beim Zeigen faellt der
   Strich weg, damit unter der wandernden Fuellung keine zweite Kontur
   mitlaeuft. */
.sternwahl__reihe--strich .sternwahl__leer {
  -webkit-mask-image: var(--stern-maske-strich); mask-image: var(--stern-maske-strich);
  color: color-mix(in srgb, var(--muted) 62%, transparent);
}
.sternwahl__reihe--strich:hover .sternwahl__leer {
  -webkit-mask-image: var(--stern-maske); mask-image: var(--stern-maske);
  color: color-mix(in srgb, var(--muted) 38%, transparent);
}
/* `--grund` ist der Ruhezustand: auf der Ansichtsseite der Durchschnitt
   aller, im Formular gibt es ihn nicht und die Fuellung beginnt bei
   null. Als Eigenschaft und nicht als `width` am Element, damit die
   Regeln fuers Angehakte und fuers Zeigen sie schlagen koennen - ein
   `style="width:…"` schluege umgekehrt jede Regel. */
.sternwahl__voll { width: var(--grund, 0); color: var(--accent-strong); }

/* Die Trefferflächen liegen obenauf und sind durchsichtig. Sie sind
   höher als der Stern: 2,6 rem sind auch mit dem Daumen zu treffen,
   ohne dass die Sterne grösser aussehen. */
.sternwahl__flaechen { position: absolute; inset: 0; display: flex; }
.sternwahl__halb { flex: 1 1 0; height: 100%; cursor: pointer; }

/* Was angehakt ist, füllt die Farbfläche - ein Zehntel je halbem
   Stern. Beim Zeigen mit der Maus wird die Stufe vorab gefüllt. */
.sternwahl__radio[value="0.5"]:checked ~ .sternwahl__bild .sternwahl__voll { width: 10%; }
.sternwahl__radio[value="1"]:checked   ~ .sternwahl__bild .sternwahl__voll { width: 20%; }
.sternwahl__radio[value="1.5"]:checked ~ .sternwahl__bild .sternwahl__voll { width: 30%; }
.sternwahl__radio[value="2"]:checked   ~ .sternwahl__bild .sternwahl__voll { width: 40%; }
.sternwahl__radio[value="2.5"]:checked ~ .sternwahl__bild .sternwahl__voll { width: 50%; }
.sternwahl__radio[value="3"]:checked   ~ .sternwahl__bild .sternwahl__voll { width: 60%; }
.sternwahl__radio[value="3.5"]:checked ~ .sternwahl__bild .sternwahl__voll { width: 70%; }
.sternwahl__radio[value="4"]:checked   ~ .sternwahl__bild .sternwahl__voll { width: 80%; }
.sternwahl__radio[value="4.5"]:checked ~ .sternwahl__bild .sternwahl__voll { width: 90%; }
.sternwahl__radio[value="5"]:checked   ~ .sternwahl__bild .sternwahl__voll { width: 100%; }

.sternwahl__reihe:has(.sternwahl__halb:nth-child(1):hover)  .sternwahl__voll { width: 10%;  color: var(--accent); }
.sternwahl__reihe:has(.sternwahl__halb:nth-child(2):hover)  .sternwahl__voll { width: 20%;  color: var(--accent); }
.sternwahl__reihe:has(.sternwahl__halb:nth-child(3):hover)  .sternwahl__voll { width: 30%;  color: var(--accent); }
.sternwahl__reihe:has(.sternwahl__halb:nth-child(4):hover)  .sternwahl__voll { width: 40%;  color: var(--accent); }
.sternwahl__reihe:has(.sternwahl__halb:nth-child(5):hover)  .sternwahl__voll { width: 50%;  color: var(--accent); }
.sternwahl__reihe:has(.sternwahl__halb:nth-child(6):hover)  .sternwahl__voll { width: 60%;  color: var(--accent); }
.sternwahl__reihe:has(.sternwahl__halb:nth-child(7):hover)  .sternwahl__voll { width: 70%;  color: var(--accent); }
.sternwahl__reihe:has(.sternwahl__halb:nth-child(8):hover)  .sternwahl__voll { width: 80%;  color: var(--accent); }
.sternwahl__reihe:has(.sternwahl__halb:nth-child(9):hover)  .sternwahl__voll { width: 90%;  color: var(--accent); }
.sternwahl__reihe:has(.sternwahl__halb:nth-child(10):hover) .sternwahl__voll { width: 100%; color: var(--accent); }

.sternwahl__reihe:has(.sternwahl__radio:focus-visible) {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 5px;
}

/* Die Ziffernanzeige und der Knopf „keine" standen hier bis zum
   2026-09-02. Sie gehoerten zur Sternwahl im Aenderungsformular, und
   das gibt es nicht mehr: bewertet wird auf der Ansichtsseite, was
   gewaehlt ist sagt die Beschriftung, und zuruecknehmen laesst es sich
   mit `.bewerten__weg`. */

/* Mehrfachauswahl als Kästchen nebeneinander - die Schlagworte am
   Rezept. Ein Auswahlfeld mit multiple wäre auf dem Handy kaum zu
   bedienen und verbirgt, was schon gewählt ist. */
.haken { display: flex; flex-wrap: wrap; gap: .4rem .9rem; margin: .1rem 0 .4rem; }
.haken__eins { display: inline-flex; align-items: center; gap: .4rem; font-size: .9rem; }

/* Das Symbol des Schlagworts steht zwischen Kaestchen und Name. Es ist
   gedaempft und nicht schwarz: den Blick soll das Wort fuehren, das
   Symbol erinnert nur an die Form, die spaeter in der Liste steht.
   `flex: none`, damit ein langer Name es nicht zusammendrueckt. */
.haken__eins .icon { width: 1rem; height: 1rem; flex: none; color: var(--muted); }

/* Die Bilder eines Lebensmittels im Formular. Je Bild zwei
   Entscheidungen: Hauptbild (Radiofeld) und entfernen (Kaestchen).

   Das Bild selbst ist der Knopf fuers Hauptbild - ein Radiofeld daneben
   waere bei vier Miniaturen ein Zielfeld von 13 px, und getroffen
   werden soll das Bild. Das Feld bleibt trotzdem bedienbar:
   `opacity: 0` und nicht `display: none`, sonst nimmt es keinen Fokus
   mehr an. */
.bildwahl { display: flex; flex-wrap: wrap; gap: .8rem; margin: .4rem 0 .6rem; }
.bildwahl__eins { display: grid; gap: .25rem; justify-items: center; }
.bildwahl__bild { position: relative; display: block; cursor: pointer; line-height: 0; }
.bildwahl__bild input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: pointer;
}
.bildwahl__bild img {
  max-height: 6rem; border-radius: var(--radius);
  border: 3px solid transparent; box-sizing: border-box;
}
.bildwahl__bild input:checked + img { border-color: var(--accent); }
.bildwahl__bild input:focus-visible + img { outline: 2px solid var(--accent); outline-offset: 2px; }
.bildwahl__weg {
  display: inline-flex; align-items: center; gap: .3rem;
  font-size: .8rem; color: var(--muted); cursor: pointer;
}
.bildwahl__weg input { width: auto; margin: 0; }

/* Die Ablegeflaeche fuer die Packungsfotos. Gestrichelt, damit sie als
   Flaeche und nicht als Kasten gelesen wird, und erst dann, wenn
   Ziehen ueberhaupt geht - `.ablage--geht` setzt app.js. Ohne das
   staende hier ein Rahmen um einen Knopf, auf den man nichts fallen
   lassen kann. */
/* `position: relative`, damit das mit .visually-hidden weggeraeumte
   Dateifeld an der Flaeche haengt und nicht am naechsten
   positionierten Vorfahren irgendwo weiter oben. Sichtbar wird das
   nur in einem Fall - und der zaehlt: bei `required` zeigt der
   Browser seine Meldung ("Bitte waehlen Sie eine Datei aus") an genau
   diesem 1x1-Punkt. Ohne die Zeile steht sie neben der Flaeche, zu
   der sie gehoert (2026-08-30). */
.ablage { display: grid; gap: .5rem; justify-items: start; position: relative; }

/* **Flach statt klobig** (FH, 2026-08-30: „Die Bilderselektion muss
   sich ändern" - auf Rückfrage: die Fläche ist zu groß).

   Vorher stapelte sie vier Zeilen übereinander und trug 1,1 rem
   Polster: Beschriftung, „hierher ziehen", Knopf, Stand. Auf der
   Tagebuchseite stand damit ein Kasten von über 150 Punkten Höhe da,
   bevor überhaupt etwas darin lag.

   Jetzt eine Reihe: die Beschriftung nimmt ihre eigene Zeile, alles
   andere steht nebeneinander und bricht erst um, wenn es eng wird. Der
   Rahmen ist ein Punkt statt zwei - gestrichelt wirkt dünn leichter,
   und die Fläche soll ein Angebot sein, kein Bauwerk. */
.ablage--geht {
  border: 1px dashed var(--border); border-radius: var(--radius);
  padding: .55rem .7rem;
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .4rem .6rem;
  text-align: left;
}
.ablage--geht.is-drueber { border-color: var(--accent); background: var(--surface-2); }
.ablage__label { font-weight: 600; }
/* Die Beschriftung gehoert dem Feld und nicht der Reihe - sie nimmt
   eine Zeile fuer sich, damit „Bild" nicht neben „Bild wählen" steht. */
.ablage--geht .ablage__label { flex: 1 1 100%; margin: 0; }
.ablage__ziehen { margin: 0; color: var(--muted); }
/* Der Stand nimmt den Rest der Zeile und bricht mit ihr um. `min-width:
   0` ist Pflicht: ein langer Dateiname schoebe die Reihe sonst aus der
   Karte, weil ein Flex-Element nie unter seine Inhaltsbreite geht. */
.ablage__stand {
  margin: 0; font-size: .85rem; color: var(--muted);
  overflow-wrap: anywhere; flex: 1 1 8rem; min-width: 0;
}

/* Am Telefon ist fuer „hierher ziehen" ohnehin kein Platz - und ziehen
   kann man dort auch nicht. */
@media (max-width: 480px) {
  .ablage--geht .ablage__ziehen { display: none; }
}
/* Ein <label>, das wie ein Knopf aussieht, erbt die Knopfregeln - nur
   den Zeiger nicht, weil `.btn` ihn ueber `button, a` setzt. */
label.btn { cursor: pointer; }

/* Die weiteren Bilder unter dem Hauptbild auf der Lebensmittelseite.
   Klein: sie sind Nachschlagewerk, nicht Schaustueck. */
.bildreihe { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .5rem; }
.bildreihe img { max-height: 4.5rem; border-radius: var(--radius); }

/* Linkslauf: der Kreispfeil läuft gegen den Uhrzeigersinn und steht vor
   dem Wort. Am Thermomix rührt das Messer im Linkslauf, statt zu
   zerkleinern - wer das überliest, hat statt Gulasch Hackfleisch. */
/* Der Pfeil sitzt in der Textzeile, nicht darüber: inline-flex würde
   die ganze Auszeichnung an der Unterkante des Symbols ausrichten und
   sie damit über die Grundlinie heben. Deshalb bleibt alles inline und
   das Symbol wird selbst an der Schrift ausgerichtet. */
.linkslauf { font-weight: 600; white-space: nowrap; }
/* svg steht global auf display:block - hier muss es zurück in die
   Zeile, sonst bricht der Text vor und hinter dem Pfeil um. */
.linkslauf .icon {
  display: inline-block; width: 1.15em; height: 1.15em;
  margin-right: .15em; vertical-align: -.24em;
}

/* --- Essensplanung ---------------------------------------------------- */

/* Die Woche hat zwei Gestalten: bis 900 px ein Block je Tag, darueber
   das Raster aus sieben Spalten. Immer nur eine davon ist da - so muss
   niemand auf dem Handy waagerecht scrollen. */
.plan-raster { display: none; }
.plan-tage   { display: grid; gap: 1rem; }

@media (min-width: 900px) {
  .plan-raster { display: block; }
  .plan-tage   { display: none; }
}

.wochenleiste { display: grid; gap: .6rem; }
.wochenleiste__spanne { display: flex; flex-wrap: wrap; gap: .5rem; align-items: baseline; font-weight: 600; }
.wochenleiste__spanne .muted { font-weight: 400; font-size: .85rem; color: var(--muted); }

.plan { table-layout: fixed; }
.plan th, .plan td { vertical-align: top; padding: .5rem .4rem; border-top: 1px solid var(--border); }
.plan thead th { border-top: 0; text-align: left; font-size: .8rem; }
.plan__datum { display: block; font-weight: 400; font-size: .75rem; color: var(--muted); }
.plan__mahlzeit { width: 8.5rem; font-size: .8rem; color: var(--muted); font-weight: 600; text-align: left; }
.plan .is-heute { background: var(--accent-soft); }
.card--heute { outline: 2px solid var(--accent-soft); }

.plan__eintrag {
  display: grid;
  /* Zwei Spalten, nicht drei: Text links, Knoepfe rechts. Bei drei
     Spalten hing die Zahl der Knoepfe davon ab, wo der letzte landete -
     mit Topf rutschte der Papierkorb in eine dritte Zeile an den linken
     Rand (Pruefbericht, Punkt 21). */
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: .2rem .35rem;
  padding: .3rem .4rem;
  margin-bottom: .3rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.plan__titel { grid-column: 1 / -1; font-size: .85rem; line-height: 1.3; overflow-wrap: anywhere; }
.plan__portionen { font-size: .72rem; color: var(--muted); justify-self: start; }
/* Der Knopfkasten sitzt immer am rechten Rand derselben Zeile. */
.plan__werkzeuge {
  display: flex;
  align-items: center;
  /* Am Desktop ist eine Tagesspalte schmaler als drei Knoepfe. Dann
     bricht der Kasten um - aber als Ganzes und weiter rechtsbuendig, so
     dass der Papierkorb der letzte bleibt. */
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: .15rem;
  justify-self: end;
}

/* 2 rem statt 19 px: das war die kleinste Trefferflaeche der Anwendung,
   und drei davon lagen 6 px auseinander. Das Symbol bleibt 1 rem - es
   waechst die Flaeche darum herum, nicht die Zeichnung. */
.plan__aendern,
.plan__weg {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2rem;
  min-height: 2rem;
  color: var(--muted);
  background: none;
  border: 0;
  padding: 0;
  border-radius: var(--radius);
}
.plan__aendern svg,
.plan__weg svg { width: 1rem; height: 1rem; }
.plan__aendern:hover, .plan__weg:hover { color: var(--text); }
.plan__eintrag form { display: contents; }

.plan__plus {
  display: block;
  text-align: center;
  padding: .15rem;
  border-radius: var(--radius);
  color: var(--muted);
  text-decoration: none;
  font-size: .9rem;
  line-height: 1.2;
}
.plan__plus:hover { background: var(--surface-2); color: var(--text); }

.plan__block { margin-bottom: .8rem; }
.plan__block .plan__mahlzeit { width: auto; margin-bottom: .3rem; }

/* Entweder-oder-Felder im Planungsformular. is-aus setzt JavaScript;
   ohne JavaScript stehen alle Felder offen und der Knopf entscheidet. */
.wahl { border: 1px solid var(--border); border-radius: var(--radius); padding: .8rem; display: grid; gap: .4rem; }
.wahl legend { padding: 0 .4rem; font-size: .8rem; color: var(--muted); }
.wahl__punkt { display: flex; align-items: center; gap: .5rem; font-weight: 600; }
.wahl__punkt input { width: auto; }
.wahl__feld { padding-left: 1.6rem; margin-bottom: .4rem; }
.wahl__feld.is-aus { opacity: .45; }

/* --- Einkaufsliste ---------------------------------------------------- */

.liste { list-style: none; padding: 0; display: grid; gap: .1rem; }
.liste--knapp { gap: .25rem; font-size: .9rem; }
.liste--knapp .muted { color: var(--muted); }

/* Fuenf Felder: Haken, Text, Menge, Kette, Eimer. Es waren vier
   Spalten - der Eimer fiel dadurch in eine eigene Zeile unter den
   Posten und sah aus wie ein zweiter Eintrag (2026-08-26 gesehen).
   Die drei Formulare sind wegen `display: contents` selbst keine
   Felder, ihre Knoepfe dagegen schon. */
.liste__posten {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto auto;
  align-items: center;
  gap: .5rem;
  padding: .45rem .1rem;
  border-bottom: 1px solid var(--border);
}
.liste__posten:last-child { border-bottom: 0; }
/* Haken und Eimer sind eigene Formulare - display:contents laesst sie
   trotzdem als Rasterfelder sitzen. Die Kette bleibt ein echtes Feld,
   damit sie auf schmalen Geraeten in die zweite Zeile rutschen kann. */
.liste__posten form { display: contents; }
.liste__posten form.liste__kette { display: block; }

/* Der Haken ist gross genug fuer einen Daumen im Laden. */
.liste__haken {
  width: 1.6rem; height: 1.6rem;
  border: 2px solid var(--border); border-radius: .4rem;
  background: var(--surface);
  display: flex; align-items: center; justify-content: center;
  font-size: .9rem; line-height: 1; color: var(--accent);
}
.liste__haken:hover { border-color: var(--accent); }
.liste__haken.is-an { background: var(--accent-soft); border-color: var(--accent); }

/* Öffnungszeit über einem Kettenblock der Einkaufsliste. */
.ladenzeit { margin: -.2rem 0 .6rem; font-size: .85rem; }
.ladenzeit.is-warn { font-weight: 600; }

/* Zwischenzeile "Frisches" im Kettenblock. Kein eigener Posten - sie
   hat keinen Haken und keine Menge, also auch nicht das Raster der
   Posten, sondern eine schlichte Zeile ueber die ganze Breite. */
.liste__trenner {
  display: flex; align-items: center; gap: .3rem;
  margin: .7rem 0 .2rem;
  font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;
  font-weight: 600; color: var(--tag-ocker);
}
/* Keine eigene Linie: der Posten darueber bringt schon eine mit, zwei
   dicht untereinander sehen aus wie ein Fehler. */
.liste__trenner:first-child { margin-top: 0; }
.liste__trenner .icon { width: .95em; height: .95em; }

/* Eine Zeile der Freigaben: Name links, Eimer rechts. */
.liste__freigabe {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  padding: .3rem 0;
}

/* Stift und Papierkorb einer Freigabe stehen zusammen und rechts -
   sonst wandert der Stift mit der Laenge der Abzeichen, und drei Zeilen
   untereinander haben drei verschiedene Stiftpositionen. */
.freigabe__aktionen { display: flex; align-items: center; gap: .15rem; flex: none; }

.liste__text { display: grid; gap: .1rem; min-width: 0; }
.liste__name { overflow-wrap: anywhere; }
.liste__menge { white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--muted); font-size: .9rem; }
.liste__kette select { font-size: .8rem; padding: .2rem .3rem; max-width: 8rem; }
.liste--erledigt { opacity: .6; }
.liste--erledigt .liste__name { text-decoration: line-through; }

/* Schmal: Haken, Text, Menge, Eimer in der ersten Zeile, die Kette
   darunter. Beide muessen ihren Platz ausdruecklich nennen - die Kette
   steht im Quelltext vor dem Eimer, und der rutschte sonst hinter die
   umbrechende Kette in eine dritte Zeile. */
@media (max-width: 560px) {
  .liste__posten { grid-template-columns: auto minmax(0, 1fr) auto auto; }
  .liste__weg > button { grid-row: 1; grid-column: 4; }
  .liste__kette { grid-row: 2; grid-column: 2 / -1; }
}

/* --- Der aufklappbare Filterblock ---------------------------------------
   Oben bleiben Suche und Gruppierung; alles Weitere liegt darunter und
   klappt auf. Der Block nimmt in der Filterleiste eine eigene Zeile
   (`width: 100%`), sonst stuende er als schmale Saeule neben dem
   Suchfeld.
   ---------------------------------------------------------------------- */

/* Die Naehrwerte unter dem Namen in der Lebensmittelliste. Klein und
   grau: sie sind die Beigabe zum Namen, nicht die Auskunft, wegen der
   man die Liste aufmacht. */
.foodzeile__werte {
  color: var(--muted); font-size: .78rem; margin-top: .15rem;
  font-variant-numeric: tabular-nums;
}
/* --- Die Preisspalte der Angebote -------------------------------------
   Drei Zahlen koennen in einer Zelle stehen: der Preis, der
   Streichpreis und der Grundpreis. Nebeneinander lesen sie sich wie
   eine einzige Reihe - "0,99 EUR statt 3,49 EUR 1,98 EUR/kg" -, und
   man sucht, welche davon der Preis ist. Untereinander beantwortet die
   erste Zeile die Frage, und die beiden darunter erklaeren sie.

   Und sie machen die Spalte schmal: am Telefon nahm die Zelle sonst so
   viel Platz, dass der Name buchstabenweise umbrach (gemessen am
   04.09.2026, 390 px).
   -------------------------------------------------------------------- */
.angebot__preis .foodzeile__werte { display: block; }

/* **Und sie duerfen umbrechen** (Paket 115). `table th, table td` setzt
   `white-space: nowrap` fuer alle Zellen - eine Grundregel, und fuer
   Zahlenspalten richtig. Der Richtpreis erbt sie und zwingt die Spalte
   damit auf die Breite seines laengsten Textes: "4,38 – 5,20 €/kg"
   misst so 101 px, wo "1,98 €/kg" mit 59 auskommt. Am Telefon wuchs
   die Preisspalte dadurch von 84 auf 104 px und schob die ganze Liste
   waagerecht auf (bin/test_schmale_listen.js).

   `overflow-wrap` half nicht und konnte nicht helfen: es entscheidet,
   WO gebrochen wird, wenn gebrochen werden darf - und das darf es bei
   `nowrap` gar nicht. Gemessen, nicht vermutet: die Mindestbreite
   derselben Zeichenkette liegt mit `nowrap` bei 101 px und ohne bei 28. */
.angebot__preis .foodzeile__werte { white-space: normal; }


/* Dieselbe Ueberlegung in der Namensspalte, und aus demselben Grund
   (04.09.2026, Paket 42, am Bild gesehen): unter dem Namen stehen die
   Menge, die Kette und seit Paket 42 auch die Kategorie. Inline lasen
   sie sich als eine einzige Angabe - "je 100 g Kaufland - Fleisch -
   Rindfleisch". Als eigene Zeile beantwortet der Name die erste Frage
   und die Zeile darunter die zweite.

   **`:not(.is-schmal-nur)` ist kein Beiwerk.** `.is-schmal-nur` haelt
   mit `display: none` das zurueck, was nur am Telefon dastehen soll -
   und diese Regel hier ist spezifischer, haette es also am Rechner
   sichtbar gemacht. Genau das ist am 04.09.2026 passiert: die
   Gueltigkeit stand an den Angeboten und die PLZ an den Orten
   doppelt, einmal unter dem Namen und einmal in ihrer Spalte.
   Gefunden hat es ein Bildschirmfoto, keine Pruefung. */
td.is-name .foodzeile__werte:not(.is-schmal-nur) { display: block; }

/* Im Fenster eines Angebots (Paket 59): unter unserer Kategorie klein,
   was die Quelle selbst gesagt hat - "bei PENNY: Fleisch & Wurst".
   Eine eigene Zeile, kein Anhang in Klammern: die erste Zeile ist die
   Antwort, die zweite die Herkunft. */

/* Der Preis mit Kundenkarte (Paket 60): in der Zeile eine Zeile unter
   dem Preis wie der Streichpreis; auf der Kachel und im Fenster klein
   und grau unter der Preiszeile - eine Auskunft, nicht der Preis. */
.kachel__karte, .angebot__karte {
  display: block; color: var(--muted); font-size: .78rem; margin: .1rem 0 0;
  font-variant-numeric: tabular-nums;
}
/* In der Preisspalte der Liste darf der Kartenname umbrechen - sonst
   zieht "mit Kaufland Card XTRA 0,79 EUR" die Spalte am Telefon auf
   200 px und die Tabelle schiebt (gemessen am 05.09.2026, 390 px,
   bin/test_schmale_listen.js). */
.angebot__preis .angebot__karte { white-space: normal; }

/* --- Ein Angebot fuer sich (Paket 51) ---------------------------------
   Auf der Seite /angebote/{id} und im Fenster ueber der Liste dasselbe
   Markup (partials/angebot): links das Bild, rechts Preiszeile und
   Tabelle. Ohne Bild gibt es keine Bildspalte - ein leerer Rahmen saehe
   aus wie ein Fehler, und die Kopie kommt erst mit dem naechsten Lauf.

   Das Bild ist eine Freistellung auf Weiss (so liefert es die Kette);
   deshalb ein weisser Grund auch im dunklen Schema, sonst steht ein
   weisses Rechteck mit Rand da, wo das Bild aufhoert.
   -------------------------------------------------------------------- */
.angebot { display: grid; gap: 1rem; align-items: start; }
.angebot--mit-bild { grid-template-columns: minmax(0, 12rem) minmax(0, 1fr); }
.angebot__bild {
  margin: 0; padding: .5rem;
  background: #fff; border: 1px solid var(--border); border-radius: var(--radius);
  display: grid; place-items: center;
}
.angebot__bild img { display: block; max-width: 100%; max-height: 14rem; object-fit: contain; }
.angebot__daten { min-width: 0; display: grid; gap: .8rem; }
/* Die Preiszeile: der Betrag gross, Streichpreis und Grundpreis in
   der Zeile daneben - hier ist Platz, anders als in der Spalte der
   Liste. `baseline`, damit die kleine Schrift auf der Grundlinie des
   Betrags steht und nicht in seiner Mitte schwebt. */
.angebot__preiszeile {
  margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: .25rem .7rem;
}
.angebot__betrag { font-size: 1.75rem; font-weight: 650; font-variant-numeric: tabular-nums; line-height: 1.1; }
.angebot__statt, .angebot__grund { font-size: .9rem; color: var(--muted); }
.angebot .table--kv { margin: 0; }

/* **Die Marke oben rechts, die Kette unten links** (Paket 83, FH im
   Wortlaut). Beide auf einem hellen, abgerundeten Kaestchen: die Logos
   der Ketten und Marken sind fuer weissen Papierdruck gemacht, und
   `Barilla` in Dunkelblau verschwand im dunklen Farbschema fast ganz -
   am Bildschirmfoto gesehen.

   Das Markenlogo liegt absolut, damit es die Preiszeile nicht
   verschiebt; der Platz dafuer kommt aus dem `padding-right` der
   Daten. Die Kette steht als eigene Zeile ueber die ganze Breite,
   direkt ueber dem Korb-Knopf des Fensters. */
.angebot { position: relative; }
.angebot__marke {
  position: absolute; top: 0; right: 0; z-index: 1;
  display: inline-flex; align-items: center;
  padding: .3rem .5rem; border-radius: var(--radius);
  background: #fff; border: 1px solid var(--border);
}
.angebot__kette {
  grid-column: 1 / -1; margin: 0;
  display: inline-flex; align-items: center; justify-self: start;
  padding: .3rem .5rem; border-radius: var(--radius);
  background: #fff; border: 1px solid var(--border);
}
.angebot__marke .logo, .angebot__kette .logo {
  height: 2.2rem; max-width: 9rem; margin: 0; vertical-align: middle;
}
/* Platz fuer das Kaestchen oben rechts - aber nur in den zwei Zeilen,
   die neben ihm stehen. Der Rand an `.angebot__daten` insgesamt schnitt
   auch die Tabelle darunter ab, und die Beschreibung brach dann mitten
   im Satz um (am Bildschirmfoto gesehen). */
.angebot__preiszeile, .angebot__karte { padding-right: 10rem; }
@media (max-width: 619.98px) {
  /* Am Telefon ist fuer beides nebeneinander kein Platz: das
     Markenkaestchen rueckt ueber das Bild, die Zeilen brauchen dann
     keinen Rand mehr. */
  .angebot__preiszeile, .angebot__karte { padding-right: 0; }
  .angebot__marke { position: static; justify-self: end; }
  .angebot__marke .logo, .angebot__kette .logo { height: 1.8rem; max-width: 7rem; }
}
/* Am Telefon steht das Bild ueber den Daten, in der Mitte, und nicht
   in einer 12rem-Spalte neben einer 8rem-Spalte. */
@media (max-width: 619.98px) {
  .angebot--mit-bild { grid-template-columns: minmax(0, 1fr); }
  .angebot__bild { justify-self: center; max-width: 14rem; }
}
/* Solange die Seite geholt wird, haelt das Fenster seine Hoehe, damit
   es beim Eintreffen nicht springt; `aria-busy` setzt angebote.js. */
.angebot-fenster__inhalt { min-height: 6rem; margin-bottom: 1rem; }
.angebot-fenster__inhalt[aria-busy="true"] { opacity: .5; }

/* --- Die Angebote als Kacheln (Paket 53) ------------------------------
   FH: Kacheln als Vorgabe, gleich hohe Kacheln auch ohne Bild, Gruppen
   als Zwischenueberschriften. Ein Raster je Gruppe (`ul.kacheln`),
   davor die Gruppenzeile als <div> - derselbe Schalter wie in den
   Tabellen klappt das Raster dahinter zu.
   -------------------------------------------------------------------- */
.kachelliste { display: grid; gap: .6rem; }
/* **Ein gemeinsames Zeilenraster ueber die ganze Reihe** (Paket 115).
   FH: "der name, marke, preis, richtpreis (/kg oder /l), dauer,
   geschaeft sollte immer auf der gleichen hoehe auf der kachel sein".

   Jede Kachel spannt elf Zeilen dieses Rasters und gibt sie per
   `subgrid` an ihre Teile weiter. Damit bestimmt in jeder Reihe die
   hoechste Kachel die Hoehe jeder einzelnen Zeile - ein zweizeiliger
   Name macht die Namenszeile aller Nachbarn zweizeilig, und ein
   fehlender Grundpreis laesst seine Zeile leer statt alles darunter
   hochzuziehen.

   Der `row-gap` gilt hier zwischen **allen** Zeilen des Rasters, also
   auch innerhalb einer Kachel - und genau das ist gewollt: er ist der
   Zeilenabstand, den `.kachel__text` vor Paket 115 als eigenes `gap`
   hatte. Den groesseren Abstand zwischen zwei Kachelreihen traegt
   `margin-block-end` an der Kachel.

   **Er stand kurz auf 0**, und dann stiessen Fristabzeichen und
   Kettenlogo zusammen (FH, 07.09.2026: "zwischen noch X Tage und dem
   Ketten Logo sollte etwas Platz sein"). Gemessen waren es null Pixel,
   und zwar zwischen jedem Paar von Zeilen. */
.kacheln {
  list-style: none; margin: 0; padding: 0;
  display: grid; column-gap: .7rem; row-gap: .15rem;
  grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr));
}
.kachel {
  display: grid; grid-template-rows: subgrid; grid-row: span 11;
  min-width: 0; margin-block-end: .7rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden;
}
/* Die Bildflaeche: immer dasselbe Format (4:3), das Bild darin
   eingepasst. Weisser Grund auch im dunklen Schema - die Bilder sind
   Freistellungen auf Weiss (siehe .angebot__bild). Ohne Bild grauer
   Grund mit dem Korb-Umriss: ein Platzhalter, kein Loch. */
.kachel__bild {
  position: relative; display: grid; place-items: center; aspect-ratio: 4 / 3;
  background: #fff; border-bottom: 1px solid var(--border);
  color: inherit; text-decoration: none;
}
/* Das Bild absolut in die Flaeche gelegt, nicht im Fluss: im Fluss
   loeste sich `max-height: 88%` gegen die aspect-ratio-Hoehe nicht auf,
   das Bild kam in seiner eigenen Groesse und zog die Flaeche hoeher
   als die Nachbarn ohne Bild - gemessen von test_angebotskacheln.js
   (166 zu 140 px), nicht am Bildschirmfoto gesehen. */
.kachel__bild img { position: absolute; inset: 6%; width: 88%; height: 88%; object-fit: contain; }
.kachel--ohne-bild .kachel__bild { background: var(--surface-2); color: var(--border); }
.kachel--ohne-bild .kachel__bild .icon { width: 2.4rem; height: 2.4rem; stroke-width: 1.2; }
/* Der Text gibt die neun Zeilen des Rasters an seine Kinder weiter -
   Name, Marke, Menge, Preis, Grundpreis, Kundenkarte, Kategorie,
   Gueltigkeit, Kette. Ohne dieses zweite `subgrid` lieferte die Kachel
   ihre Zeilen nur bis hierher, und der Text machte darin wieder sein
   eigenes Mass. */
.kachel__text {
  display: grid; grid-template-rows: subgrid; grid-row: span 9;
  padding: .6rem .7rem .3rem; min-width: 0;
}

/* Jeder Inhalt oben in seiner Zeile. Ohne das steht ein einzeiliges
   Markenlogo mittig in einer Zeile, die der Nachbar mit Logo UND Text
   zweizeilig gemacht hat - dann sind die Zeilen buendig und die Logos
   darin trotzdem nicht. */
.kachel__text > * { align-self: start; }
.kachel__name { font-weight: 600; line-height: 1.25; overflow-wrap: break-word; hyphens: auto; }
.kachel__menge, .kachel__unten, .kachel__kategorie, .kachel__gueltig {
  font-size: .78rem; color: var(--muted);
}
/* Die Marke unter dem Artikelnamen (Paket 83, FHs Reihenfolge:
   Artikel, Marke, Einheit, Preis, Kategorie, Gueltigkeit, Kette).
   Etwas groesser als die grauen Zeilen darunter - sie steht direkt
   unter dem Namen und gehoert noch zur Ueberschrift. */
/* Beide Zeilen tragen ein Logo UND einen Text. `flex-wrap`, weil der
   Text sonst neben dem Logo aus der Kachel herauslaeuft - „K-Wertschätze
   Bio" stand am ersten Bildschirmfoto zur Haelfte im Nachbarn. Das Logo
   selbst schrumpft nicht (`flex: none`), sonst quetscht ein langer Text
   es zusammen. */
.kachel__marke, .kachel__unten {
  display: flex; align-items: center; gap: .25rem; flex-wrap: wrap; min-width: 0;
}
.kachel__marke { font-size: .82rem; color: var(--muted); }
.kachel__marke .logo, .kachel__unten .logo { flex: none; }

/* **Doppelt so gross wie der Text** (FH, 06.09.2026: „die logos in den
   angeboten sollte etwas groesser sein" - und auf die Rueckfrage:
   doppelt). Aus 1,15 em werden 2,3 em, und nur an den Angeboten: in
   der Markenliste und am Lebensmittel steht das Logo im Namenslink und
   bliebe dort ein Bild in Textgroesse. */
.kachel .logo, .foodzeile__werte .logo, .angebot .logo {
  height: 2.3em; max-width: 6.4em; vertical-align: -.5em;
}

/* **Das Bonus-Icon nicht** (Paket 116). Die doppelte Groesse gilt
   Logos, die einen NAMEN ersetzen - Kette und Marke stehen fuer sich
   und tragen die Zeile. Das Zeichen des Bonusprogramms steht neben
   einer Zahl ("0,79 €"), und doppelt so gross ueberragt es sie um das
   Dreifache: gemessen 80 px neben einem 12-px-Preis, in einer Spalte
   von 95 px. Am Telefon schob es die Liste dadurch waagerecht auf.
   Anderthalbfach ist deutlich sichtbar und bleibt ein Zusatz. */
.kachel .logo--treue, .foodzeile__werte .logo--treue, .angebot .logo--treue {
  height: 1.5em; max-width: 4em; vertical-align: -.3em;
}
.kachel__preis { margin: .2rem 0 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .45rem; }
.kachel__betrag { font-size: 1.25rem; font-weight: 650; font-variant-numeric: tabular-nums; }
.kachel__statt, .kachel__grund { font-size: .78rem; color: var(--muted); }

/* Der Grundpreis steht seit Paket 115 unter dem Preis und **haelt
   seine Zeile auch leer** (FH: "dann die Zeile leer lassen, sodass
   alle auf einer Hoehe stehen"). Jedes vierte Angebot hat keinen, und
   ohne die feste Hoehe ruecken Kategorie, Gueltigkeit und Kette dort
   eine Zeile hoch.

   `line-height` und `min-height` tragen **dieselbe** Zahl. Zuerst
   stand hier nur `min-height: 1.35em`, und gemessen war die leere
   Zeile 17 px hoch und die gefuellte 19 - genau die zwei Pixel, um
   die dann alles darunter verrutschte. Eine Zeile, die leer bleiben
   soll, muss so hoch sein wie dieselbe Zeile voll. */
.kachel__grund { display: block; line-height: 1.5; font-variant-numeric: tabular-nums; }

/* Die Kette steht in der letzten Zeile des Rasters - dafuer braucht
   es kein `margin-top: auto` mehr. In einem Grid schoebe das ohnehin
   nichts: `margin: auto` verteilt dort innerhalb der eigenen Zelle
   und nicht ueber den Restplatz.

   Etwas mehr Luft als zwischen den uebrigen Zeilen bekommt sie
   trotzdem: darueber steht oft das Fristabzeichen, und ein Kasten
   neben einem Bild braucht mehr Abstand als zwei Textzeilen
   voneinander. */
.kachel__unten { margin-block-start: .25rem; }
.kachel__aktionen { display: flex; justify-content: flex-end; gap: .25rem; padding: .3rem .5rem .5rem; }
.kachel__aktionen form { margin: 0; }
/* Die Gruppenzeile ausserhalb einer Tabelle: dieselbe Schrift wie der
   kleine Tabellenkopf (`table tr.gruppenzeile > th`), nur ohne Zelle. */
.gruppenzeile--frei {
  padding: .6rem .2rem .1rem;
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted);
}
.gruppenzeile--frei .gruppenzeile__zusatz { color: var(--muted); font-weight: 600; }
/* Der Umschalter Liste/Kacheln im Listenkopf: zwei Symbolknoepfe, der
   aktive gefuellt - wie ein Segment, nur ohne Schiene. */
.umschalter { display: inline-flex; gap: .25rem; }
.umschalter a[aria-current="page"] { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
/* Am Telefon zwei Kacheln nebeneinander; die Schrift darf dann etwas
   kleiner, sonst bricht jeder Name dreimal um. */
@media (max-width: 619.98px) {
  .kacheln { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem; }
  .kachel__text { padding: .5rem .55rem .2rem; }
  .kachel__name { font-size: .85rem; }
  .kachel__betrag { font-size: 1.1rem; }
}

/* Der Angebotshinweis unter einem Posten der Einkaufsliste (Paket 45).
   Eigene Zeile aus demselben Grund wie oben - er steht neben Menge,
   Gebinde und Notiz, und inline waere er die vierte Angabe in einer
   Reihe. Das Zeichen davor bekommt Luft, sonst klebt es am Namen. */
.nutri--klein {
  font-size: .68rem; padding: 0 .3rem; margin-right: .2rem;
  vertical-align: .05em;
}

/* --- Kochen -------------------------------------------------------------
   Die Seite wird in der Kueche benutzt: das Telefon liegt quer auf der
   Arbeitsplatte, die Haende sind schmutzig, gelesen wird aus einem
   halben Meter. Deshalb grosse Schrift im Schritt, grosse Knoepfe unten
   und ein Balken, der sagt, wo man ist.
   ---------------------------------------------------------------------- */
.kochkopf {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
  justify-content: space-between;
}
.kochkopf__portionen { display: flex; align-items: center; gap: .5rem; }
/* `flex-basis` statt `width`: die allgemeine Feldregel setzt
   `width: 100%` mit neun :not()-Klauseln und schlaegt jede Klasse - in
   einem Flex-Kasten entscheidet aber die Basis. Dieselbe Falle wie bei
   .kochzutat__menge input. Ohne das war das Feld fuer eine Portionszahl
   220 Pixel breit. */
.kochkopf__portionen input { flex: 0 0 4.5rem; }
.kochkopf__stand { font-size: .85rem; margin: 0; }

/* Der Fortschritt als Reihe von Punkten. Jeder ist anklickbar - wer im
   dritten Schritt merkt, dass er den ersten falsch gelesen hat, kommt
   mit einem Griff zurueck. */
.kochbalken {
  list-style: none; display: flex; flex-wrap: wrap; gap: .3rem;
  margin: .8rem 0 0; padding: 0;
}
/* Die Punkte sind Knoepfe eines Formulars, keine Links - sonst gingen
   die Eingaben der Zutatenliste beim Blaettern verloren. Aussehen wie
   vorher. */
.kochbalken li > button { border: 0; cursor: pointer; font-family: inherit; }
.kochbalken li > a,
.kochbalken li > button {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2rem; height: 2rem; padding: 0 .5rem;
  border-radius: 999px; background: var(--surface-2);
  color: var(--muted); font-size: .85rem; font-weight: 600;
}
/* Abgeschlossene Schritte sind gruen (FH, 2026-08-27) - beim Kochen
   will man auf einen Blick sehen, was hinter einem liegt. Gruen heisst
   hier "erledigt", nicht "guter Wert"; die Farbe ist dieselbe, die
   Aussage passt in beiden Faellen. */
.kochbalken li.is-durch > a,
.kochbalken li.is-durch > button {
  background: color-mix(in srgb, var(--good) 16%, transparent);
  color: var(--good);
}
.kochbalken li.is-hier > a,
.kochbalken li.is-hier > button {
  background: var(--accent); color: var(--accent-ink);
  box-shadow: var(--shadow);
}

/* Eine Zutat: Menge, Name, und daneben das Lebensmittel, das man
   wirklich nimmt. Am Handy rutscht die Auswahl unter den Namen. */
.kochzutaten { list-style: none; display: grid; gap: .1rem; padding: 0; margin: .8rem 0 0; }
/* Im Kochen zerfaellt der Sucher in seine Teile: vorn Menge und
   Einheit, daneben das Suchfeld. `display: contents` hebt beide in das
   Raster der Zeile - dieselbe Vorgehensweise wie im Tagebuch. */
.kochzutat__wahl { display: contents; }
.kochzutat__wahl .picker { display: contents; }
.kochzutat .picker__menge { grid-column: 1; grid-row: 1; grid-template-columns: 4.2rem minmax(0, 6.2rem); }
.kochzutat .picker__suche { grid-column: 2; grid-row: 1; }
.kochzutat .kochzutat__echt { grid-column: 2; grid-row: 2; }
.kochzutat .kochzutat__anlegen { grid-column: 2; grid-row: 3; }
.kochzutat > .kochzutat__name { grid-column: 3; grid-row: 1 / span 3; }
.kochzutat .picker__menge input { text-align: right; font-variant-numeric: tabular-nums; }
/* Was von der Zeile wirklich ins Gericht geht - klein unter dem
   Suchfeld. Steht dort "Menge nicht umrechenbar", faellt die Zutat
   beim Fertigkochen heraus; das soll man vorher sehen. */
.kochzutat__anlegen { display: inline-block; font-size: .8rem; margin-top: .2rem; }
.kochzutat__echt {
  display: block; font-size: .78rem; color: var(--muted);
  font-variant-numeric: tabular-nums; margin-top: .15rem; min-height: 1.1em;
}
.kochzutat {
  display: grid; align-items: start; gap: .2rem .7rem;
  /* Vorn die Menge, dann das Lebensmittel, ganz rechts die Zutat aus
     dem Rezept (FH, 2026-08-27). Das Auswahlfeld bekommt die Breite:
     dort steht Marke und Bezug ("Rindergulasch Aldi · 131 kcal/100 g"),
     und bei 14rem war nach "131 k" Schluss. */
  grid-template-columns: 11.2rem minmax(0, 1fr) minmax(0, 11rem);
  padding: .5rem .1rem; border-bottom: 1px solid var(--border);
}
.kochzutat:last-child { border-bottom: 0; }
/* Die Menge ist ein Eingabefeld: was auf der Waage stand, zaehlt.
   Schmal, mit der Einheit daneben - "750" und "g" gehoeren zusammen. */
.kochzutat__menge {
  display: flex; align-items: center; gap: .35rem;
  font-weight: 650; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.kochzutat__menge input {
  /* `flex-basis` statt `width`: die allgemeine Feldregel setzt
     `width: 100%` mit neun :not()-Klauseln und schlaegt jede Klasse -
     in einem Flex-Kasten entscheidet aber die Basis. */
  flex: 0 0 4.5rem; height: var(--feldhoehe-klein);
  font-weight: 650; font-variant-numeric: tabular-nums; text-align: right;
}
.kochzutat__einheit { color: var(--muted); font-weight: 500; }
/* Die Zutat des Rezepts steht rechts und tritt zurueck - gekocht wird
   nach dem Lebensmittel daneben. */
/* Genauso gross wie die Menge davor (FH, 2026-08-27). Sie war auf
   .9rem heruntergesetzt, weil sie "nur der Beleg" ist - beim Kochen
   liest man sie aber aus einem halben Meter, und dann ist klein das
   falsche Wort. Zurueckhaltend bleibt sie ueber die Farbe. */
.kochzutat__name {
  display: grid; gap: .1rem; min-width: 0; overflow-wrap: anywhere;
  text-align: right; color: var(--muted); font-size: 1rem;
  padding-top: .45rem;
}
/* Die Menge, die das Rezept verlangt - der Sollwert neben dem, was
   wirklich in der Schuessel landet. */
.kochzutat__soll { font-weight: 650; color: var(--text); font-variant-numeric: tabular-nums; }
.kochzutat__wahl select { width: 100%; font-size: .85rem; }

@media (max-width: 620px) {
  /* Am Handy passt die Zeile in keine Reihe: bei 355 nutzbaren Pixeln
     blieben fuer das Suchfeld zwei Buchstaben ("Le..."). Deshalb
     untereinander - oben, was im Rezept steht, darunter ueber die ganze
     Breite die Wahl, dann die Menge. Die Reihenfolge im Quelltext bleibt
     davon unberuehrt.

     `> .kochzutat__name`, nicht nur `.kochzutat__name`: die Regel weiter
     oben steht mit demselben Kindzeichen da und waere sonst genauer -
     ein Media-Query zaehlt bei der Gewichtung nicht mit. Die Zutat blieb
     deshalb in einer dritten Spalte stehen, die es hier gar nicht gibt,
     und lag quer ueber dem Suchfeld. */
  .kochzutat { grid-template-columns: minmax(0, 1fr); gap: .35rem; }
  .kochzutat > .kochzutat__name {
    grid-column: 1; grid-row: 1; text-align: left; padding-top: 0;
    display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .4rem;
  }
  .kochzutat .picker__suche { grid-column: 1; grid-row: 2; }
  .kochzutat .picker__menge {
    grid-column: 1; grid-row: 3;
    grid-template-columns: 5rem minmax(0, 8rem); justify-content: start;
  }
  .kochzutat .kochzutat__echt { grid-column: 1; grid-row: 4; margin-top: 0; }
  .kochzutat .kochzutat__anlegen { grid-column: 1; grid-row: 5; }
}

/* "von 3" haengt an der Schrittzahl, nicht am anderen Ende der Zeile
   (FH, 2026-08-27) - es sagt etwas ueber diese Zahl aus. */
.kochschritt__von { color: var(--muted); font-weight: 500; font-size: .85em; }

/* Der Schritt selbst - gross genug fuer den Blick von der Seite. */
.kochschritt {
  font-size: 1.15rem; line-height: 1.6; margin: .8rem 0 0;
  overflow-wrap: anywhere;
}

/* Weiter und Zurueck ganz unten, weit auseinander: der Daumen liegt am
   Rand, und "Weiter" soll man nicht suchen muessen. */
.kochnav {
  display: flex; justify-content: space-between; align-items: center;
  gap: 1rem; margin-top: 1rem;
}
.kochnav .btn { min-width: 8rem; justify-content: center; }

/* Die ganze Zutatenliste vor dem Speichern (FH, 2026-08-27): links die
   Menge, die wirklich hineinging, in der Mitte das Lebensmittel, rechts
   was das Rezept verlangte. Nichts ist hier mehr aenderbar - dies ist
   der Blick zurueck, bevor daraus ein Gericht wird. Zeilen, die es
   nicht ins Gericht schaffen, treten zurueck statt zu verschwinden:
   sonst faellt erst hinterher auf, dass die Bruehe fehlt. */
.kochliste { list-style: none; display: grid; gap: .1rem; padding: 0; margin: .8rem 0; }
.kochliste__zeile {
  display: grid; align-items: baseline; gap: .2rem .7rem;
  grid-template-columns: 7rem minmax(0, 1fr) minmax(0, 11rem);
  padding: .45rem .1rem; border-bottom: 1px solid var(--border);
}
.kochliste__zeile:last-child { border-bottom: 0; }
.kochliste__menge {
  text-align: right; font-weight: 650; font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.kochliste__food { min-width: 0; overflow-wrap: anywhere; }
.kochliste__gramm { display: block; font-size: .78rem; color: var(--muted); }
.kochliste__zutat {
  text-align: right; color: var(--muted); min-width: 0; overflow-wrap: anywhere;
}
/* Kein Durchstreichen: die Menge stimmt ja - es fehlt das Lebensmittel,
   und das steht daneben. Durchgestrichen laese es sich als "diese Menge
   ist falsch". */
.kochliste__zeile.is-raus { opacity: .6; }

@media (max-width: 620px) {
  /* Am Handy untereinander: oben die Menge und das Lebensmittel, darunter
     - eingerueckt - was das Rezept dazu sagte. Rechtsbuendig waere hier
     nur eine Luecke. */
  .kochliste__zeile { grid-template-columns: 5.5rem minmax(0, 1fr); }
  .kochliste__menge { text-align: left; }
  .kochliste__zutat { grid-column: 2; text-align: left; font-size: .85rem; }
}

/* --- Protokoll ---------------------------------------------------------
   Maschinentext, aber lesbar: die Zeilen eines Eintrags bleiben
   eingerueckt, lange Meldungen brechen trotzdem um - am Handy waere die
   Haelfte sonst nicht zu sehen, und genau dort wird es gebraucht.
   ---------------------------------------------------------------------- */
.protokoll { display: grid; gap: .5rem; margin-top: .8rem; }
.protokoll__eintrag {
  margin: 0; padding: .6rem .7rem;
  background: var(--surface-2); border-radius: 8px;
  font: 12.5px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: pre-wrap; overflow-wrap: anywhere;
}

/* --- Tagesziele im Tagebuch ------------------------------------------ */
.ziele { display: grid; gap: .8rem; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
/* **Zwei Zeilen, immer** (FH, 2026-09-01: "ungleiche Hoehen").
   Die zweite Zeile traegt den Balken - oder, wo es kein Ziel gibt, den
   Satz warum. Als *ausdrueckliche* Rasterzeile steht sie auch dann da,
   wenn nichts darin liegt; sonst waren Kacheln mit Balken 36 px hoch
   und Kacheln ohne 24, und die Reihe lief auseinander. `minmax` und
   nicht `1.05rem` fest: bricht der Satz einmal um, darf die Zeile
   wachsen.

   `align-items: center` setzt den 6 px hohen Balken in die Mitte
   dieser Zeile statt an ihren oberen Rand. */
.ziel { display: grid; gap: .35rem; grid-template-rows: auto minmax(1.05rem, auto); align-items: center; }
/* Zahl, Ziel und Name stehen als ein Satz nebeneinander - "861 von 2000
   kcal". Kein space-between: sonst reisst die Spaltenbreite die Zahl von
   ihrem Ziel weg und die Zeile liest sich nicht mehr (FH, 2026-08-24). */
.ziel__kopf { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem; min-width: 0; }
.ziel__wert { font-variant-numeric: tabular-nums; font-weight: 600; }
.ziel__ziel { color: var(--muted); font-size: .9em; font-variant-numeric: tabular-nums; }
/* **Lange Namen brechen um** (FH, 2026-09-01: der
   "ASHWAGANDHAWURZELEXTRAKT schreibt ins Folgewort"). Gemessen am
   2026-09-01: der Name ist 213 px breit, die Kachel bei einem 600 px
   breiten Fenster 163 - er stand 49 px weit in der Nachbarspalte.
   Umbrechen konnte er nicht: ein Wort ohne Leerzeichen hat keine
   Stelle, an der ein Browser von sich aus trennt.

   `hyphens` zuerst, damit die Trennung dort faellt, wo sie im Deutschen
   hingehoert (die Seite ist `lang="de"`); `overflow-wrap: anywhere` ist
   der Rueckfall fuer Browser ohne Trennmuster - und nebenbei das, was
   die Mindestbreite der Kachel von "so breit wie das laengste Wort"
   auf "so breit wie ein Zeichen" senkt. Ohne das zwaenge der Name das
   Raster selbst auseinander. */
.ziel__name {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); min-width: 0; hyphens: auto; overflow-wrap: anywhere;
}
/* Steht kein Balken da, steht dort der Grund. Kleiner als der Name und
   in derselben gedaempften Farbe: es ist eine Fussnote zur Zahl
   darueber, keine zweite Angabe. */
.ziel__ohne { margin: 0; font-size: .7rem; color: var(--muted); }
.ziel__balken { height: 6px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.ziel__balken span { display: block; height: 100%; border-radius: 999px; background: var(--accent); }
/* Über einer Obergrenze: erst Bernstein, ab 110 % Rot. Das ist der Fall,
   für den Rot vorbehalten ist. Über einer Untergrenze - Eiweiß - ist
   das Ziel dagegen erreicht, und das ist gut. */
.ziel__balken.is-ueber span      { background: var(--warn); }
.ziel__balken.is-weit-ueber span { background: var(--bad); }
.ziel__balken.is-erreicht span   { background: var(--good); }

/* --- Datumsleiste über dem Tag --------------------------------------- */
/* Datumsfeld und Knöpfe sind gleich hoch - sonst zieht das Feld die
   Kopfzeile auf und die Karte steht höher als auf den anderen Seiten
   (FH, 2026-08-24). Die feste Höhe ist nötig, weil Chrome dem Datumsfeld
   wegen seines Kalendersymbols eine eigene Mindesthöhe gibt. */
.datumsleiste { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
/* Das Datumsfeld steckt in einem eigenen Formular - ohne diese Zeile ist
   das Formular der Nachbar der Knöpfe, nicht das Feld, und es wächst
   über die Zeile hinaus. */
.datumsleiste form { display: flex; min-width: 0; }
.datumsleiste input[type=date],
.datumsleiste .btn { height: 2.1rem; }
.datumsleiste input[type=date] {
  width: auto; min-width: 0; flex: 0 1 auto;
  padding: 0 .5rem; font-size: .9rem; line-height: 1;
}
.datumsleiste .btn--icon { font-size: 1.2rem; line-height: 1; }

/* Tagebuch, Eintragen: Mahlzeit, Suchfeld, Kamera, Menge, Einheit und
   der Knopf stehen in einer Zeile (FH, 2026-08-25). Der Sucher gibt
   seine Teile mit `display: contents` in dieselbe Reihe; die
   Packungsgrößen bekommen eine eigene Zeile, sie gehören zum Suchfeld
   und nicht in die Bedienreihe. */
.form--eintrag { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem; }
.form--eintrag > .picker { display: contents; }
.form--eintrag .field--mahlzeit { flex: 0 0 auto; }
.form--eintrag .picker__suche { flex: 1 1 16rem; }
.form--eintrag .picker__menge { flex: 0 0 auto; grid-template-columns: 5.5rem 7rem; }
/* order: die Packungsgrößen kommen ans Ende, sonst schöben sie den
   Eintragen-Knopf hinter sich in die nächste Zeile. */
.form--eintrag .chips { order: 1; flex: 1 1 100%; }
.form--eintrag > .btn-row { flex: 0 0 auto; }
/* Die Fehlermeldung nimmt eine eigene Zeile - neben dem Knopf gelesen
   sieht sie aus wie seine Beschriftung. `order: 2` haelt sie hinter den
   Packungsgroessen, also ganz unten. */
.form--eintrag > .field__error { flex: 1 1 100%; order: 2; margin: 0; }
/* Der eigene Eintrag steht in derselben Reihe wie der Sucher: das
   Namensfeld nimmt den Platz, den der Sucher sonst hat, und die fünf
   Werte bekommen eine Zeile für sich. Ohne das stünde das Namensfeld
   auf seiner Vorgabebreite von 220 px, und "Currywurst mit Pommes"
   passte nicht hinein. */
.form--adhoc .field--adhocname { flex: 1 1 16rem; min-width: 0; }
/* Nicht mehr die ganze Breite: der Eintragen-Knopf soll am Ende
   derselben Zeile stehen und nicht darunter (FH, 2026-08-28). Die
   Grundbreite haelt die vier Felder beisammen; wird es enger, bricht
   der Knopf von selbst um. */
.form--adhoc .adhoc-werte      { flex: 1 1 22rem; }
.form--adhoc .field--adhocname input { width: 100%; }
/* Die Knopfreihe steht **am Ende der Zeile**, nicht darunter (FH,
   2026-08-28). `flex: 1 1 auto` mit `margin-left: auto` schiebt sie an
   den rechten Rand, solange dort Platz ist; wird es eng, bricht sie von
   selbst um. Vorher stand sie mit `flex: 1 1 100%` immer allein - was
   den Ueberlauf verhinderte, aber auch am breiten Schirm eine Zeile
   verschenkte.

   Am Telefon bleibt die eigene Zeile: dort ist ohnehin kein Platz
   daneben, und ein Knopf, der halb neben dem letzten Feld klebt, ist
   schlechter zu treffen. */
.form--adhoc > .btn-row,
.form--foto  > .btn-row { flex: 0 0 auto; margin-left: auto; }

@media (max-width: 620px) {
  .form--adhoc > .btn-row,
  .form--foto  > .btn-row { flex: 1 1 100%; margin-left: 0; }
  .form--adhoc > .btn-row .btn,
  .form--foto  > .btn-row .btn { flex: 1 1 auto; }
}

/* --- Ein Feld mit einem Knopf daneben -------------------------------
   Für "Adresse eintragen und auswerten lassen" (Rezept anlegen). Nicht
   `.btn-row`: die deckelt Eingabefelder auf 14 rem, weil sie für
   Filterreihen gedacht ist - eine Adresse braucht die ganze Breite.

   `min-width: 0` steht als Vorsorge da, nicht als Notwendigkeit: bei
   einem `<input>` hängt die Mindestbreite an `size` und nicht am Inhalt,
   ein langer Link bläht es also nicht auf - nachgemessen am 2026-08-30,
   die Gegenprobe ohne die Zeile blieb still. Sobald hier einmal etwas
   Inhaltsbasiertes steht (ein Div, ein Knopf mit langem Text), wird die
   Zeile gebraucht, und dann fehlt sie garantiert.

   Was den Überlauf hier wirklich verhindert, ist `flex-wrap: wrap`: der
   Knopf bricht um, statt sich hinauszuschieben. Genau das misst
   `bin/test_rezeptlink.js` - mit `nowrap` fällt es durch. */
.feldreihe { display: flex; flex-wrap: wrap; align-items: stretch; gap: .5rem; }
.feldreihe > input { flex: 1 1 16rem; min-width: 0; }
.feldreihe > .btn  { flex: 0 0 auto; }

@media (max-width: 480px) {
  .feldreihe > .btn { flex: 1 1 100%; }
}

/* Seit dem 2026-08-30 steht **kein** Dateifeld mehr sichtbar auf einer
   Seite: alle elf gehen durch `partials/ablage`, wo das Feld mit
   `.visually-hidden` unter einem <label class="btn"> liegt. Die
   Umgebungsregeln von `.form--foto .field--datei` (Breite des Feldes,
   Hoehe, Rahmen) sind damit gegenstandslos und hier weggefallen -
   `.field--datei` gibt es nicht mehr.

   Die Regel fuer den Knopf **im** Dateifeld bleibt: sollte je wieder
   eines sichtbar werden - in einem Browser ohne unsere Annahmen, oder
   an einer Stelle, die jemand ohne das Partial baut -, sieht es
   wenigstens nach pattex aus und nicht nach 1998. Sie kostet nichts,
   solange sie niemanden trifft. (FH, 2026-08-29: "den Upload-Button
   bitte im pattex design, aller button darstellen".) */
input[type=file]::file-selector-button {
  margin-right: .6rem;
  padding: .35rem .8rem;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface-2); color: var(--text);
  font: inherit; font-size: .85rem; font-weight: 550;
  cursor: pointer;
}
input[type=file]::file-selector-button:hover {
  background: var(--border);
}
/* Die Notiz ist ein Nebensatz und nimmt sich nicht den ganzen Rest der
   Zeile - sonst steht ein leeres Feld ueber 30 rem da. */
.form--foto .field--fotonotiz { flex: 0 1 12rem; min-width: 0; }

.field--mahlzeit select { max-width: 16rem; }
.form--eintrag .field--mahlzeit select { max-width: 11rem; }

/* Am Telefon nimmt die Mahlzeit die ganze Breite (FH, 2026-08-28). Die
   11 rem sind dafuer da, dass sie am breiten Schirm neben dem Sucher
   Platz laesst - am Telefon steht sie ohnehin allein in ihrer Zeile,
   und ein halbbreites Auswahlfeld ist dort nur schwerer zu treffen. */
@media (max-width: 620px) {
  .form--eintrag .field--mahlzeit { flex: 1 1 100%; }
  .form--eintrag .field--mahlzeit select,
  .field--mahlzeit select { max-width: 100%; width: 100%; }
}

.prosa { line-height: 1.65; }

/* --- Strichcode scannen ----------------------------------------------
   Vollbild über allem, damit das Kamerabild groß genug ist. Die Linie
   zeigt, wohin der Code gehalten werden soll.
   -------------------------------------------------------------------- */
.scanner {
  position: fixed; inset: 0; z-index: 100; display: none;
  flex-direction: column; gap: .5rem;
  background: #0b0a09;
  padding: calc(env(safe-area-inset-top) + .8rem) 1rem calc(env(safe-area-inset-bottom) + 1rem);
}
.scanner.is-offen { display: flex; }
body.is-scannt { overflow: hidden; }

.scanner__rahmen {
  position: relative; flex: 1 1 auto; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; border-radius: var(--radius);
}
.scanner__video { max-width: 100%; max-height: 100%; }
.scanner__linie {
  position: absolute; left: 6%; right: 6%; height: 2px;
  background: rgba(255,255,255,.85);
  box-shadow: 0 0 12px rgba(255,255,255,.5);
}
.scanner__fuss { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.scanner__text { color: #f2efe9; font-size: .9rem; }
.scanner .btn--ghost { color: #f2efe9; border-color: rgba(255,255,255,.35); }
.scanner .btn--ghost:hover { background: rgba(255,255,255,.12); color: #fff; }

/* Das Datum hinter dem Namen eines Gerichts - dieselbe Behandlung wie
   die Marke hinter einem Lebensmittel. */
.name__date { font-size: .85em; font-weight: 450; color: var(--muted); letter-spacing: .01em; }

/* --- "Mehr" in der unteren Leiste --------------------------------------
   Die Leiste hat Platz für vier Symbole. Alles Weitere - Rezepte,
   Gerichte, Planung, Einkaufslisten, Einkäufe, EKG - stand am Handy
   bis hierher **nirgends**: nicht in der Leiste, nicht unter den
   Kacheln auf "Heute". Es war eine Sackgasse, keine Frage der
   Auffindbarkeit.

   Der fünfte Punkt klappt sie über der Leiste auf. Aus <details>
   gebaut: das öffnet ohne Skript und ist mit der Tastatur bedienbar.
   Am Rechner steht ohnehin alles in der Spalte links - dort fällt der
   Punkt weg.
   -------------------------------------------------------------------- */
@media (max-width: 899px) {
  /* Nicht nur `li`: seit den Gruppen faellt hier auch die Ueberschrift
     weg. Vier Symbole nebeneinander vertragen kein fuenftes Element. */
  .nav .is-weit { display: none; }
}

@media (min-width: 900px) {
  .nav__mehr { display: none; }
}

.nav__mehr details { position: static; }

.nav__mehr summary {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; height: var(--nav-h);
  color: var(--muted); font-size: .68rem; font-weight: 550;
  cursor: pointer; list-style: none; user-select: none;
}
.nav__mehr summary::-webkit-details-marker { display: none; }
.nav__mehr summary.is-aktiv,
.nav__mehr details[open] summary { color: var(--accent); }

/* Das Blatt sitzt ueber der Leiste - `absolute` in der `fixed` Leiste.
   Die Breite steht als `100vw` da und nicht als `left: 0; right: 0`:
   ein **zugeklapptes** <details> traegt in Chromium `content-visibility:
   hidden`, und das bringt `contain: layout` mit - damit wird das
   zugeklappte Blatt selbst zum Bezug fuer alles, was absolut darin
   liegt. Das Blatt waere dann so breit wie der Knopf "Mehr" (78 px),
   sein Inhalt liefe rechts heraus, und die Seite bekaeme am Handy eine
   Breite von 567 statt 390 px - bei zugeklapptem Menue, also unsichtbar.
   `bin/vorschau.js` hat genau das gemeldet. Ein Mass, das vom Fenster
   kommt, ist von der Einschliessung nicht betroffen. */
.nav__blatt {
  position: absolute; right: 0; bottom: 100%; width: 100vw;
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: 0 -12px 28px rgba(0,0,0,.18);
  max-height: 60vh; overflow-y: auto;
}
.nav__blatt ul { display: grid; grid-template-columns: 1fr 1fr; gap: .2rem; padding: .5rem; }
.nav__blatt li { flex: none; }
/* Im Blatt gelten dieselben Gruppen wie in der Spalte links - nur ohne
   `is-weit`, denn hier steht ohnehin nur, was unten nicht hinpasst. */
.nav__blatt .nav__gruppe {
  margin: .6rem 0 0; padding: 0 .8rem;
  font-size: .68rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.nav__blatt .nav__gruppe:first-child { margin-top: .2rem; }
.nav__blatt ul + .nav__gruppe { margin-top: .4rem; }
.nav__blatt a {
  flex-direction: row; justify-content: flex-start; gap: .7rem;
  height: 3rem; padding: 0 .8rem; border-radius: 9px; font-size: .92rem;
}
.nav__blatt a[aria-current="page"] { background: var(--accent-soft); }

/* --- Aufgenommene Posten einer Routine ---------------------------------
   FH, 2026-08-31: mehrere Lebensmittel sammeln, statt nach jedem zu
   speichern. Die Liste entsteht erst im Browser (`routineposten.js`);
   ohne Skript steht sie leer da und ist versteckt.

   Sie sieht bewusst aus wie eine Merkliste und nicht wie die Tabelle
   der gespeicherten Posten darueber: was hier steht, ist noch nicht
   gespeichert, und das soll man sehen.
   -------------------------------------------------------------------- */
.neuliste { list-style: none; margin: .6rem 0 0; padding: 0; display: grid; gap: .3rem; }
.neuliste__zeile {
  display: flex; align-items: baseline; gap: .6rem;
  padding: .35rem .5rem; border-radius: 7px;
  background: var(--surface-2);
}
.neuliste__name  { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.neuliste__menge { flex: 0 0 auto; color: var(--muted); font-size: .85rem;
                   font-variant-numeric: tabular-nums; }
.neuliste__weg   { flex: 0 0 auto; padding: 0 .4rem; line-height: 1.2; }
.neuliste__knopf { margin-top: .5rem; }

/* --- Datum und Uhrzeit ------------------------------------------------
   iOS gibt diesen Feldern ein eigenes Aussehen mit eigener Höhe und
   ignoriert die vorgegebene, solange es gilt. Nebeneinander standen am
   iPhone deshalb zwei verschieden hohe Kästen (Geburtsdatum neben
   Grösse), und in der Datumsleiste des Tagebuchs schob sich das Feld
   über den Knopf daneben. Der Text sass zusätzlich mittig statt links.
   -------------------------------------------------------------------- */
input[type=date], input[type=time], input[type=datetime-local], input[type=month] {
  -webkit-appearance: none; appearance: none;
  min-width: 0; max-width: 100%;
}
input[type=date]::-webkit-date-and-time-value,
input[type=time]::-webkit-date-and-time-value,
input[type=datetime-local]::-webkit-date-and-time-value,
input[type=month]::-webkit-date-and-time-value {
  text-align: left; margin: 0;
}
/* Safari legt um das Feld herum noch einen eigenen Behälter. */
input[type=date]::-webkit-datetime-edit,
input[type=time]::-webkit-datetime-edit { padding: 0; }

/* --- Bild groß ansehen ------------------------------------------------
   Ein Klick auf das Produktbild legt es über die ganze Seite. Der
   Knopf drumherum macht das auch mit der Tastatur erreichbar; er sieht
   aus wie nichts und gibt nur den Zeiger und den Fokusrahmen dazu.
   -------------------------------------------------------------------- */
.bildknopf {
  display: block; max-width: 100%; padding: 0;
  border: 0; background: none; cursor: zoom-in;
  border-radius: var(--radius);
}
.bildknopf:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Die Lupe ist ein `<dialog>` (05.09.2026), damit sie ueber einem
   anderen Fenster liegt: `showModal()` hebt sie in die oberste Ebene
   des Browsers, und die schlaegt jeden z-index. Gemessen am
   Angebotsfenster - als `<div>` lag sie mit `z-index: 110` dahinter.

   Der Browser bringt fuer `<dialog>` eigene Werte mit (Rahmen,
   Polsterung, `margin: auto`, `max-width: calc(100% - 6px)`), und die
   muessen alle weg: sonst steht die Lupe als kleiner weisser Kasten in
   der Mitte statt formatfuellend. `inset: 0` allein reicht dafuer
   nicht - `margin: auto` zieht sie wieder zusammen. */
.lupe {
  position: fixed; inset: 0; z-index: 110; display: none;
  align-items: center; justify-content: center;
  margin: 0; border: 0; max-width: none; max-height: none;
  width: 100%; height: 100%;
  color: inherit;
  background: rgba(11, 10, 9, .92); cursor: zoom-out;
  padding: calc(env(safe-area-inset-top) + 1rem) 1rem calc(env(safe-area-inset-bottom) + 1rem);
}
.lupe.is-offen { display: flex; }
/* Der Rueckhalt, falls Klasse und `open` je auseinanderlaufen: zu ist
   zu. Ohne die Zeile stuende eine geschlossene Lupe mit `is-offen`
   ueber der ganzen Seite und liesse sich nicht wegklicken. */
.lupe:not([open]) { display: none; }
/* Der eigene Schatten des Dialogs bleibt leer - die Flaeche der Lupe
   deckt ohnehin alles ab, und zwei uebereinander waeren dunkler als
   gewollt. */
.lupe::backdrop { background: transparent; }
body.is-lupe { overflow: hidden; }

/* **Jedes Bild fuellt die Flaeche** (FH, 02.09.). Davor stand hier
   `width/height: auto`, und jedes Bild stand in seiner eigenen Groesse
   da: beim Blaettern sprang es, weil das eine 192 und das naechste 180
   Punkte breit war. Vergroessert wird also weiterhin.

   **Die Masse rechnet app.js** (`passen()`), seit dem 05.09.2026.
   `object-fit: contain` wuerde dasselbe malen, aber der KASTEN des
   Bildes waere die ganze Flaeche - und dann rundet ein
   `border-radius` Ecken, die niemand sieht, und ein weisser Grund
   waere ein bildschirmgrosses Rechteck statt einer Karte hinter dem
   Foto. Genau das wollte FH nicht.

   Weiss wie am Angebotsbild (`.angebot__bild`), damit ein
   freigestelltes Produktfoto nicht im Dunkeln steht - und weil ein
   Foto mit weissem Rand sonst eine sichtbare Kante haette. */
.lupe__bild {
  display: block;
  /* Der Rueckfall, bis `passen()` gerechnet hat (und falls das Skript
     ausfaellt): nie groesser als die Flaeche. Die endgueltigen Masse
     stehen danach als Stilangabe am Element selbst. */
  max-width: 100%; max-height: 100%;
  background: #fff; border-radius: var(--radius);
}

.lupe__zu {
  position: absolute; top: calc(env(safe-area-inset-top) + .6rem); right: .8rem;
  width: 2.5rem; height: 2.5rem; padding: 0;
  font-size: 1.6rem; line-height: 1; color: #f2efe9;
  background: rgba(255,255,255,.12);
  border: 1px solid rgba(255,255,255,.35); border-radius: 999px;
}
.lupe__zu:hover { background: rgba(255,255,255,.22); color: #fff; }

/* Blaettern in der Lupe (Paket 8). Die Pfeile sitzen mittig am Rand,
   wo der Daumen sie am Telefon ohnehin findet, und der Zaehler unten
   mittig - er sagt, wie viele es sind, und ist keine Bedienung.

   Die Reihe laeuft um (FH, 02.09.), es gibt also kein Ende und keinen
   gesperrten Pfeil mehr. */
.lupe__pfeil {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 2.8rem; height: 2.8rem; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%; cursor: pointer;
  background: rgba(0,0,0,.45); color: #fff; font-size: 2rem;
}
.lupe__pfeil--rueck { left: .6rem; }
.lupe__pfeil--vor   { right: .6rem; }
.lupe__pfeil:hover { background: rgba(255,255,255,.22); }

.lupe__zaehler {
  position: absolute; left: 50%; bottom: .8rem; transform: translateX(-50%);
  margin: 0; padding: .15rem .6rem; border-radius: 999px;
  background: rgba(0,0,0,.45); color: #fff; font-size: .82rem;
}

/* Der Abspielknopf (FH, 07.09.2026: "bei mehreren Bildern eine
   Slideshow"). Er steht oben links, gegenueber dem Schliessen - die
   Pfeile sitzen mittig am Rand und der Zaehler unten, dort ist die
   Ecke die einzige freie Stelle. Dieselbe Form wie `.lupe__zu`, damit
   die zwei Ecken zusammengehoeren; nur das Zeichen ist kleiner, weil
   ▶ und ❚❚ von sich aus breiter bauen als ×.

   Waehrend es laeuft, steht der Knopf heller - er ist gedrueckt, und
   das soll man sehen, ohne auf das Zeichen zu schauen. */
.lupe__lauf {
  position: absolute; top: calc(env(safe-area-inset-top) + .6rem); left: .8rem;
  width: 2.5rem; height: 2.5rem; padding: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 1rem; line-height: 1; color: #f2efe9;
  background: rgba(255,255,255,.12);
  border: 1px solid rgba(255,255,255,.35); border-radius: 999px;
  cursor: pointer;
}
.lupe__lauf:hover { background: rgba(255,255,255,.22); color: #fff; }
.lupe__lauf.is-laeuft { background: rgba(255,255,255,.3); color: #fff; }

/* --- Sortieren und Gruppieren ----------------------------------------
   Der Spaltenkopf ist der Knopf: der ganze Kopf ist anklickbar, deshalb
   wandert die Polsterung vom <th> in den Link. Der Pfeil steht nur an
   der Spalte, nach der gerade sortiert wird; beim Überfahren blitzt er
   blass auf und zeigt, wohin der Klick führt.
   -------------------------------------------------------------------- */
table th.sortierbar { padding: 0; }
table th.sortierbar > a {
  display: flex; align-items: center; justify-content: flex-end; gap: .3rem;
  padding: .55rem .6rem; color: inherit; text-decoration: none;
}
table th.sortierbar:first-child > a { justify-content: flex-start; }
/* Textspalten tragen ihre Überschrift links, wie ihren Inhalt - sonst
   klebt "Name" am rechten Rand einer Spalte, deren Text links anfängt.
   Die Regel darüber ordnet nur der ersten Spalte zu; der Name steht
   aber oft mittendrin.

   `is-notiz` stand hier bis zum 03.09.2026 daneben. Die Notizspalte am
   Einkauf war die einzige, die sie trug, und die ist weg (FH: "Notiz
   nicht in der Tabelle zeigen") - mit ihr die drei Regeln, die nur ihr
   galten. */
table th.sortierbar.is-name > a { justify-content: flex-start; }
/* Und seit Paket 140 wieder eine `is-notiz`, mit anderem Sinn: die
   Notizspalte der Messwerte. Ihr `class="muted" style="font-size:.85rem;
   white-space:normal"` stand bis dahin in der Vorlage; eine Zelle aus
   TableView::zeile() traegt Klassen und keinen Stil. Nur die Zelle,
   nicht der Kopf. */
table td.is-notiz { color: var(--muted); font-size: .85rem; white-space: normal; }
table th.sortierbar > a:hover { background: var(--surface-2); color: var(--text); }
table.table--sm th.sortierbar > a { padding: .3rem .4rem; }

/* In rechtsbündigen Spalten steht der Pfeil links vom Wort. Stünde er
   rechts, schöbe er die Überschrift um 11 px nach innen - sie endete
   dann nicht über ihren Zahlen, und zwar in jeder Liste (gemessen und
   von FH bemerkt, 2026-08-25). Wo die Überschrift links steht, bleibt
   er hinter dem Wort. */
table th.sortierbar > a .sortpfeil { order: -1; }
table th.sortierbar:first-child > a .sortpfeil,
table th.sortierbar.is-name > a .sortpfeil { order: 0; }

.sortpfeil { font-size: .65em; line-height: 1; opacity: 0; }
.sortpfeil::before { content: "\25B2"; }
table th.sortierbar > a:hover .sortpfeil { opacity: .4; }
table th.is-auf .sortpfeil, table th.is-ab .sortpfeil { opacity: 1; }
table th.is-ab .sortpfeil::before { content: "\25BC"; }

/* Zwischenüberschrift einer Gruppe. Sie steht über ihren Zeilen und
   sieht aus wie ein zweiter, kleinerer Tabellenkopf. */
table tr.gruppenzeile > th {
  text-align: left; padding-top: .8rem;
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); background: var(--surface-2);
  border-top: 1px solid var(--border);
}
table tbody tr.gruppenzeile:first-child > th { border-top: 0; padding-top: .55rem; }
/* Rechts in der Gruppenzeile darf eine Zusammenfassung stehen - beim
   Gewicht der Monatsdurchschnitt. Sie ist keine Überschrift mehr,
   deshalb ohne Versalien und in normaler Schriftstärke. */
.gruppenzeile__inhalt { display: flex; flex-wrap: wrap; gap: .2rem 1rem; justify-content: space-between; align-items: center; }
.gruppenzeile__zusatz { text-transform: none; letter-spacing: 0; font-weight: 550; }

/* Der Schalter sieht aus wie die Überschrift, die er ersetzt - er soll
   nicht wie ein Knopf aussehen, sondern wie etwas, das man anklicken
   kann. Ohne Skript bleibt er wirkungslos und stört nicht. */
.gruppenzeile__schalter {
  display: inline-flex; align-items: center; gap: .35rem;
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
}
.gruppenzeile__schalter:hover { color: var(--text); }
.gruppenzeile__schalter .icon { width: 1em; height: 1em; transition: transform .12s ease; }
.gruppenzeile__schalter[aria-expanded="false"] .icon { transform: rotate(-90deg); }

/* Gesperrter Knopf: ausgegraut und schraffiert. Er ist da, aber er geht
   nicht - ein fehlender Knopf sähe aus wie ein Fehler (FH, 2026-08-25). */
.btn.is-gesperrt, .btn.is-gesperrt:hover {
  cursor: not-allowed; color: var(--muted); border-color: var(--border);
  background-color: var(--surface-2);
  background-image: repeating-linear-gradient(-45deg,
      transparent 0 3px, var(--border) 3px 4px);
}
.btn.is-gesperrt .icon { opacity: .5; }

/* In einer Kopfzeile soll die Filterleiste nicht in die Breite ziehen -
   sonst rutschen Knopf und Anzahl in eine zweite Zeile (FH, 2026-08-25). */
.card__head .filterbar .field { flex: 0 0 auto; }

/* Der Name führt in die Ansicht - ohne Unterstrich, sonst wäre eine
   Liste voller Namen eine Liste voller Striche. */
.namenlink { color: inherit; text-decoration: none; }
.namenlink:hover { color: var(--accent); }
.namenlink:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* Das Gruppierungsfeld wächst nicht mit - es hat immer denselben Inhalt. */

/* --- Kacheln anordnen -------------------------------------------------
   Eine kurze Zeile je Kachel statt der Kachel selbst: so lässt sich auch
   am Handy ziehen, ohne halbe Bildschirme zu bewegen.
   -------------------------------------------------------------------- */
.ordnen { list-style: none; padding: 0; margin: .6rem 0 0; display: grid; gap: .3rem; }
/* Die Pfeile im Kachelkopf - klein, aber greifbar. Sie sind der Weg
   fuer Tastatur und Vorlesegeraet; das Ziehen hat keinen. */
.ordnen__pfeil { width: 1.9rem; height: 1.9rem; min-height: 1.9rem; padding: .2rem; font-size: .9rem; }
.ordnen__pfeil[disabled] { opacity: .35; }

.ordnen__zeile {
  display: flex; align-items: center; gap: .6rem;
  padding: .5rem .6rem; border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface);
}
.ordnen__zeile.is-gezogen { border-color: var(--accent); box-shadow: var(--shadow); opacity: .9; }

/* Der Griff fängt den Zeiger ab - deshalb hier kein Scrollen. */
.ordnen__griff {
  cursor: grab; color: var(--muted); font-size: 1.1rem; line-height: 1;
  touch-action: none; user-select: none; padding: .2rem .1rem;
}
.ordnen__zeile.is-gezogen .ordnen__griff { cursor: grabbing; }

.ordnen__name { display: flex; align-items: center; gap: .5rem; flex: 1 1 auto; min-width: 0; }
.ordnen__name input { width: auto; }
.ordnen__pfeile { display: flex; gap: .2rem; }
.ordnen__pfeile .btn--icon { font-size: 1rem; line-height: 1; padding: .25rem .4rem; }
.ordnen__pfeile .btn[disabled] { opacity: .35; cursor: default; }

/* --- Kacheln an Ort und Stelle ordnen ---------------------------------
   Am Desktop wird an den echten Kacheln gezogen, am Handy in der Liste
   darüber. Welcher Weg gilt, entscheidet die Fensterbreite - dieselbe
   Grenze wie bei der Navigation (900 px).
   -------------------------------------------------------------------- */
.ordnen-leiste {
  display: grid; gap: .55rem;
  margin: .8rem 0 0; padding: .6rem .8rem;
  border: 1px solid var(--accent); border-radius: 10px;
  background: var(--accent-soft);
}
.ordnen-leiste__zeile {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem;
  justify-content: space-between;
}
.ordnen-leiste__wort { font-size: .9rem; }

/* Haken und Griff sitzen zusammen rechts im Kachelkopf. */
.kopfleiste { display: inline-flex; align-items: center; gap: .5rem; }
.haken { display: inline-flex; align-items: center; cursor: pointer; }
.haken input { width: auto; margin: 0; }

/* Im Ordnen-Zustand ist die ganze Kachel der Griff. Ihr Inhalt nimmt
   deshalb keine Zeiger mehr an - sonst faengt ein Link im Diagramm den
   Druck ab, und die Kachel bleibt liegen. Der Kopf bleibt lesbar, ist
   aber ebenfalls nur Griff.

   Alles davon steht **hinter der Breitengrenze**: am Handy ordnet die
   Liste, und dort muss die Startseite darunter ganz normal bedienbar
   bleiben. Sonst saehe man gestrichelte Kacheln mit toten Knoepfen und
   haette keinen Weg, sie zu ziehen. */
@media (min-width: 900px) {
  .grid.is-ordnen .card {
    cursor: grab; touch-action: none; user-select: none;
    border-style: dashed; border-color: var(--accent);
  }
  .grid.is-ordnen .card > *:not(.card__head) { pointer-events: none; }
  .grid.is-ordnen .card__head { pointer-events: none; }

  /* Der Haken und die zwei Pfeile sind die Stellen, die noch etwas
     annehmen - alles andere gehoert dem Ziehen. Die Pfeile sind seit
     2026-08-27 der Weg fuer Tastatur und Vorlesegeraet (Punkt 11); ohne
     `pointer-events: auto` waeren sie mit der Maus unerreichbar,
     obwohl sie dastehen. */
  .grid.is-ordnen .card__head .haken,
  .grid.is-ordnen .card__head .ordnen__pfeil { pointer-events: auto; cursor: pointer; }

  /* Ausgeblendet: die Kachel steht an ihrem Platz, aber blass und
     farblos - sie ist da, gilt aber nicht. Der Kopf bleibt lesbar,
     sonst wuesste man nicht, welche Kachel man wieder einschaltet. */
  .grid.is-ordnen .card.is-aus { border-style: dotted; }
  .grid.is-ordnen .card.is-aus > *:not(.card__head) { opacity: .3; filter: grayscale(1); }
  .grid.is-ordnen .card.is-aus .card__head h2 { color: var(--muted); }

  /* Knoepfe im Inhalt sehen sonst bedienbar aus und sind es nicht -
     die Pfeile im Kopf dagegen sind es. */
  .grid.is-ordnen .card .btn { opacity: .4; }
  .grid.is-ordnen .card__head .ordnen__pfeil { opacity: 1; }
  .grid.is-ordnen .card__head .ordnen__pfeil[disabled] { opacity: .35; }

  .grid.is-ordnen .card.is-gezogen {
    cursor: grabbing; border-style: solid;
    box-shadow: var(--shadow); opacity: .92;
  }
}

/* Am Handy ordnet die Liste, und die Startseite darunter bleibt die
   Startseite: eine ausgeblendete Kachel hat dort nichts zu suchen. Blass
   zeigen ginge auch, aber ohne Haken daneben waere sie nur im Weg. */
@media (max-width: 899px) {
  .grid.is-ordnen .card.is-aus { display: none; }
}

/* --- Spalten, die am Handy wegfallen ----------------------------------
   Eine Tabelle mit sechs Spalten passt auf kein Telefon. Statt sie
   seitlich scrollen zu lassen, fallen die Spalten weg, die man unterwegs
   nicht braucht - dieselbe Klasse an Kopf und Zelle. Was bleibt, ist die
   Auskunft, wegen der man die Liste aufmacht.

   Bewusst nicht `.nur-breit` (das faellt schon unter 900 px weg): eine
   Tabelle mit 700 px Platz kommt mit ihren Spalten noch gut zurecht.
   -------------------------------------------------------------------- */
.is-schmal-nur { display: none; }

@media (max-width: 620px) {
  .is-schmal-weg { display: none; }

  /* Das Gegenstueck: was **nur** am Handy dasteht - die Naehrwerte
     unter dem Namen, wo die Spalten dafuer weggefallen sind. */
  .is-schmal-nur { display: block; }

  /* Spalten wegzunehmen reicht nicht: `white-space: nowrap` gilt fuer
     jede Zelle, und "24.08.2026 07:21" oder eine Gruppenzeile mit
     Zusammenfassung schieben die Tabelle allein wieder auf. Die erste
     Spalte und die Gruppenzeile duerfen deshalb umbrechen - dort steht
     Text, keine Zahlenreihe, die fluchten muesste. */
  table td:first-child,
  table th:first-child { white-space: normal; }

  /* Auch die Spaltenkoepfe duerfen umbrechen. Sie sind laenger als das,
     was darunter steht ("Sportart" gegen "Joggen") und schoben die
     Tabelle allein wieder auf - die Zellen waren laengst schmal genug. */
  table thead th { white-space: normal; }
  .gruppenzeile > th { white-space: normal; }
  .gruppenzeile__inhalt { flex-wrap: wrap; }

  /* Die Namensspalte fordert sonst 11rem - auf einem 390-px-Telefon
     sind das zwei Drittel der Breite, und alles andere muss sich den
     Rest teilen. Hier darf sie so schmal werden, wie ihr Inhalt es
     zulaesst. */
  table th.is-name, table td.is-name { min-width: 0; }

  /* `overflow-wrap: anywhere` senkt die **Mindestbreite** einer Zelle
     auf ein Zeichen - `break-word` taete das nicht. Nur damit kann die
     Tabelle unter die Breite ihres laengsten Wortes ("Krafttraining,")
     schrumpfen. Gebrochen wird trotzdem erst, wenn es eng wird. */
  table td.is-name { overflow-wrap: anywhere; }

  /* Engere Zellen: 0,6rem links und rechts sind bei sechs Spalten
     zusammen ein Fingerbreit, den die Zahlen besser gebrauchen. */
  table th, table td { padding: .5rem .35rem; }

  /* Der Sortierpfeil steht nur noch an der Spalte, nach der gerade
     sortiert wird. An den uebrigen ist er ein Angebot, das am Telefon
     kaum jemand annimmt - und er macht jeden Spaltenkopf um ein
     Zeichen breiter, was bei sechs Spalten eine Spalte kostet. */
  table th.sortierbar:not(.is-auf):not(.is-ab) .sortpfeil { display: none; }

  /* Die Aktionsleiste steht ueber **und** unter der Tabelle - am
     Rechner ein kurzer Weg zurueck, am Handy zweimal 268 px, also
     zusammen zwei Drittel des Bildschirms fuer dieselben neun
     Bedienelemente (Pruefbericht, Punkt 16). Unten reicht einmal. */
  .zuordnen__leiste--unten { display: none; }

  /* Die Knoepfe am Zeilenende - wieder groesser, seit die Spalte klebt.
     
     Bis 2026-08-27 waren sie hier auf 2rem gestutzt, um in der Zeile
     Platz zu sparen: die Tabelle musste ins Bild passen. Seit die
     Aktionsspalte am rechten Rand stehen bleibt (Punkt 2), konkurriert
     ihre Breite nicht mehr mit den Datenspalten - der Grund fuer das
     Stutzen ist damit weg.
     
     Gemessen waren es 32 x 32 px mit 4 px Abstand, und rechts davon
     loescht der Knopf (Pruefbericht, Punkt 15). Jetzt 38 px mit 8 px
     Luft dazwischen: der harmlose und der zerstoerende Knopf liegen
     nicht mehr einen Fingernagel auseinander. */
  .table-actions { gap: .5rem; }
  .table-actions .btn--icon {
    width: 2.375rem; height: 2.375rem; min-height: 2.375rem; padding: .35rem;
  }

  /* Am Sport stehen die zwei Knoepfe untereinander (Paket 149, FH:
     "variante c am sport dazu"). Nebeneinander nahmen sie 89 px, und
     die Sportart hatte 111 - "Krafttraining, moderat" brach im Wort.
     Untereinander sind es 43 px, die Sportart bekommt 149, und das
     Wort bleibt ganz. Gemessen an der echten Seite bei 390 px, siehe
     bin/test_schmale_listen.js. Der Abstand bleibt bei .5rem: der
     harmlose und der zerstoerende Knopf liegen auch uebereinander
     nicht einen Fingernagel auseinander. */
  table.table--sport .table-actions { flex-direction: column; align-items: flex-end; }
}

/* Diese beiden stehen **nach** allem, was sie ausblenden sollen.
   Gleiche Spezifitaet entscheidet die Reihenfolge: stuenden sie oben,
   gaebe das spaetere `display: flex` der Leiste den Ausschlag und die
   Leiste stuende auch am Handy da (am 2026-08-26 genau so passiert). */
@media (max-width: 899px) {
  .nur-breit { display: none; }
}
@media (min-width: 900px) {
  .nur-schmal { display: none; }
}

/* --- Öffnungszeiten ---------------------------------------------------
   Im Formular je Wochentag eine Zeile mit zwei Spannen; auf schmalen
   Geräten bricht sie unter den Tagesnamen um.
   -------------------------------------------------------------------- */
.zeiten { display: grid; gap: .45rem; margin-top: .8rem; }
.zeiten__zeile { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .6rem; }
.zeiten__tag { flex: 0 0 6.5rem; font-weight: 600; font-size: .9rem; }
.zeiten__spanne { display: flex; align-items: center; gap: .3rem; }
.zeiten__spanne input[type=time] { width: auto; padding: .35rem .5rem; font-size: .9rem; }
.zeiten__ganz { display: inline-flex; align-items: center; gap: .3rem; font-size: .85rem; }
.zeiten__ganz input { width: auto; }
.zeiten__kopie { font-size: .78rem; padding: .25rem .5rem; }

/* Wird eine Zeile zu schmal für "von – bis" nebeneinander, stehen von
   und bis untereinander (FH, 02.09.) - jede Spanne bricht als Ganzes,
   der Strich dazwischen entfällt. */
@media (max-width: 620px) {
  .zeiten__spanne { flex-direction: column; align-items: stretch; }
  .zeiten__strich { display: none; }
}

/* Der heutige Tag in der Wochentabelle des Geschäfts. */
table tr.is-heute > th, table tr.is-heute > td { background: var(--accent-soft); }

/* --- Wege, Höhenprofil und EKG ----------------------------------------
   Drei Zeichnungen, die alle dasselbe Problem haben: sie sind breiter
   als ein Handy. Der Weg und das Profil passen sich an, das EKG nicht -
   dort ist der Massstab die Aussage (25 mm/s, 10 mm/mV), und eine
   gestauchte Kurve wäre keine mehr. Es scrollt deshalb in seinem
   eigenen Rahmen, wie eine breite Tabelle.
   -------------------------------------------------------------------- */

.stat__note { font-size: .78rem; color: var(--muted); }

/* --- Die Karte unter dem Weg -------------------------------------------
   Die Farben sind bewusst blass: der Weg ist die Aussage, die Karte
   sagt nur, wo. Sie stehen als eigene Merkmale da, damit Hell und
   Dunkel dieselbe Zeichnung mit anderen Tönen füllen können - das
   ginge mit fest eingetragenen Farben im SVG nicht.
   -------------------------------------------------------------------- */
/* Seit Paket 15 kraeftiger als die erste Fassung: die blassen Toene
   waren vom Grund kaum zu trennen (FH: "Farben/Kontrast"). Der Weg
   bleibt trotzdem die Aussage - die Karte ist gesaettigter geworden,
   nicht dunkler. Der Grund selbst bleibt unveraendert, an ihm haengt
   bin/test_karten_theme.js. */
:root {
  --k-wald:    #c9ddba;
  --k-wiese:   #e3ecd3;
  --k-bebauung:#e9e3db;
  --k-haus:    #d3c9bc;
  --k-park:    #bfe0b2;
  --k-wasser:  #b7d7ee;
  --k-autobahn:#eaa96a;
  --k-haupt:   #f3d9a0;
  --k-neben:   #dcd7cf;
  --k-weg:     #cdc6ba;
  --k-bahn:    #b0aca4;
  --k-fluss:   #7cb0d8;
  --k-grund:   #f6f3ec;
  --weg:       #4f46c8;
}

/* Dunkel in denselben zwei Zustaenden wie bei den Seitenfarben oben:
   Systemeinstellung, solange nichts gewaehlt ist, oder ausdrueckliche
   Wahl "dunkel". Bis hierher fehlte die data-theme-Wache - die Karte
   hing allein am System, und wer die Seite auf hell stellte, waehrend
   das System dunkel stand, bekam die dunkle Karte auf heller Seite
   (FH: "Karten sehen im hellen Theme schlecht aus"). */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --k-wald:    #2c3b21;
    --k-wiese:   #2e3323;
    --k-bebauung:#332f2b;
  --k-haus:    #47413a;
    --k-haus:    #47413a;
    --k-park:    #2f4023;
    --k-wasser:  #203548;
    --k-autobahn:#8a6132;
    --k-haupt:   #5d4c2c;
    --k-neben:   #423e37;
    --k-weg:     #4a443b;
    --k-bahn:    #4e4a44;
    --k-fluss:   #4f88b4;
    --k-grund:   #1b1a18;
    --weg:       #9b8dff;
  }
}

:root[data-theme="dark"] {
  --k-wald:    #2c3b21;
  --k-wiese:   #2e3323;
  --k-bebauung:#332f2b;
  --k-park:    #2f4023;
  --k-wasser:  #203548;
  --k-autobahn:#8a6132;
  --k-haupt:   #5d4c2c;
  --k-neben:   #423e37;
  --k-weg:     #4a443b;
  --k-bahn:    #4e4a44;
  --k-fluss:   #4f88b4;
  --k-grund:   #1b1a18;
  --weg:       #9b8dff;
}

/* Der Grund: ohne ihn steht die Karte auf der Kartenfarbe der Seite und
   sieht aus wie ein Diagramm, nicht wie eine Karte. */
.karte__grund { fill: var(--k-grund); }

.kf { stroke: none; }
.kf--g { fill: var(--k-wald); }
.kf--m { fill: var(--k-wiese); }
.kf--u { fill: var(--k-bebauung); }
.kf--p { fill: var(--k-park); }
.kf--W { fill: var(--k-wasser); }
/* Gebäude (2026-09-02): eine Stufe dunkler als die Bebauung, auf der
   sie stehen - sie zeichnen die Häuserzeile, nicht das Viertel. */
.kf--z { fill: var(--k-haus); }

/* --- Die Zeichen am Weg (2026-09-02) ---------------------------------
   Gipfel, Aussichtspunkte, Hütten, Bänke, Parkplätze. Kleine Formen in
   Karteneinheiten - beim Zoomen wachsen sie mit, wie der Punkt eines
   Ortsnamens; erst die Lupe macht Bänke und Parkplätze überhaupt
   sichtbar (Route::AB_METER_FEIN). */
.kz { fill: var(--text); opacity: .78; }
.kz__strich { fill: none; stroke: var(--text); stroke-width: 1.3; stroke-linecap: round; }
.kz__auge { fill: none; stroke: var(--text); stroke-width: 1.2; }
.kz--P { opacity: .9; }
.kz--P rect { fill: #3d7bd0; }
.kz__p {
  fill: #fff; font-size: 7.5px; font-weight: 700;
  text-anchor: middle; dominant-baseline: central;
}
.kz__name {
  font-size: calc(11.4px / var(--zoom));
  fill: var(--text); opacity: .9;
  paint-order: stroke; stroke: var(--k-grund); stroke-width: calc(2.6px / var(--zoom));
}

/* --- Strichstärken und Schriftgrössen beim Hineinzoomen ---------------
   Die Lupe zoomt über das viewBox: ein engerer Ausschnitt, gleiche
   Zeichenfläche. Alles in Karteneinheiten wächst dabei mit - bei
   sechsfach wäre der Weg ein fingerdickes Band und "Nienbrügge" stünde
   in Plakatschrift.

   Deshalb steht hinter jeder Strichstärke, Schriftgrösse und jedem
   Punktdurchmesser ein `/ var(--zoom)`. Das Skript setzt `--zoom` auf
   die aktuelle Vergrösserung; ohne Skript ist es 1, und dann rechnet
   die Zeile nichts. So bleibt am Bildschirm alles gleich dick und
   gleich gross, und es kommt nur mehr Karte dazwischen.
   -------------------------------------------------------------------- */
/* Nur auf dem <svg>: die Kartenebenen darunter tragen selbst die
   Klasse `.karte`, und ein zweiter Startwert dort setzte den Wert
   mitten im Baum wieder auf 1 zurück - Strassen und Ortsnamen wuchsen
   dann weiter mit, Weg und Startpunkt nicht. Genau so sah es aus. */
.weg { --zoom: 1; }

.kl { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.kl--a { stroke: var(--k-autobahn); stroke-width: calc(3.4 / var(--zoom)); }
.kl--h { stroke: var(--k-haupt);    stroke-width: calc(2.6 / var(--zoom)); }
.kl--n { stroke: var(--k-neben);    stroke-width: calc(1.8 / var(--zoom)); }
.kl--d { stroke: var(--k-neben);    stroke-width: calc(1.3 / var(--zoom)); }
.kl--w { stroke: var(--k-weg);      stroke-width: calc(1.2 / var(--zoom));
         stroke-dasharray: calc(4 / var(--zoom)) calc(3.3 / var(--zoom)); }
.kl--b { stroke: var(--k-bahn);     stroke-width: calc(1.6 / var(--zoom));
         stroke-dasharray: calc(6.5 / var(--zoom)) calc(5.2 / var(--zoom)); }
.kl--F { stroke: var(--k-fluss);    stroke-width: calc(2.9 / var(--zoom)); }
.kl--f { stroke: var(--k-fluss);    stroke-width: calc(1.4 / var(--zoom)); }
.kl--K { stroke: var(--k-fluss);    stroke-width: calc(1.8 / var(--zoom)); }

/* Ortsnamen mit hellem Saum, damit sie auch über einer Fläche lesbar
   bleiben. paint-order legt den Saum unter die Schrift statt darüber.

   Der Abstand zum Punkt steht hier und nicht in den Koordinaten: sonst
   wüchse er beim Hineinzoomen mit, und der Name rutschte von seinem
   Punkt weg. */
.ko {
  font-size: calc(12.4px / var(--zoom)); font-weight: 600; fill: var(--muted);
  paint-order: stroke fill; stroke: var(--surface);
  stroke-width: calc(3.3px / var(--zoom));
  stroke-linejoin: round;
  translate: calc(6.5px / var(--zoom)) calc(5px / var(--zoom));
}
.ko--3 { font-size: calc(15.6px / var(--zoom)); fill: var(--text); }
.ko--2 { font-size: calc(14.3px / var(--zoom)); }
.ko__punkt { fill: var(--muted); r: calc(2.6px / var(--zoom)); }

/* Der Massstabsbalken (Paket 15): in Karteneinheiten gezeichnet, damit
   er beim Zoomen ehrlich bleibt; weg.js rechnet ihn dabei neu, sonst
   wüchse er über den halben Schirm. Strich und Schrift teilen durch
   --zoom wie alles andere - am Bildschirm bleibt er gleich dick. */
.karte__massstab line {
  stroke: var(--text); stroke-width: calc(1.6 / var(--zoom));
  stroke-linecap: butt;
}
.karte__massstab-text {
  font-size: calc(8.5px / var(--zoom)); font-weight: 600; fill: var(--text);
  text-anchor: middle;
  paint-order: stroke fill; stroke: var(--k-grund);
  stroke-width: calc(2.5px / var(--zoom)); stroke-linejoin: round;
}

/* Die Quellenangabe gehört zur Karte, nicht unter sie: so wandert sie
   mit, auch ins Vollbild. Klein und blass - sie muss lesbar sein, mehr
   verlangt die Lizenz nicht. */
.karte__quelle {
  font-size: calc(9px / var(--zoom)); fill: var(--muted); text-anchor: end; opacity: .8;
  paint-order: stroke fill; stroke: var(--k-grund); stroke-width: calc(2.6px / var(--zoom));
}

.weg-rahmen { display: flex; justify-content: center; padding: .4rem 0 .2rem; }

/* --- Karte gross ansehen ----------------------------------------------
   Auf einem Handy ist eine Wanderung von hundert Kilometern ein Faden.
   Ein Tippen legt die Karte deshalb über die ganze Seite; dort lässt
   sie sich schieben und zoomen.

   Der Knopf sieht aus wie nichts - er gibt nur den Zeiger und den
   Fokusrahmen dazu, dieselbe Machart wie beim Produktbild. Damit
   kommt man auch mit der Tastatur an die grosse Karte.
   -------------------------------------------------------------------- */
/* Der Knopf legt sich um die Zeichnung, nicht um die Zeile: sonst läge
   der Fokusrahmen weit neben der Karte. */
.weg-knopf {
  display: block; max-width: 100%; min-width: 0;
  padding: 0; border: 0; background: none;
  cursor: zoom-in; line-height: 0; border-radius: var(--radius);
}
.weg-knopf:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Der Kartengrund als Hintergrund, nicht Schwarz wie beim Bild: die
   Karte hat keinen Rand, sie hört einfach auf. Ein dunkles Feld
   drumherum sähe aus wie ein Loch. */
.kartenlupe {
  position: fixed; inset: 0; z-index: 110;
  display: flex; flex-direction: column;
  background: var(--k-grund);
  padding-top: env(safe-area-inset-top);
  padding-bottom: calc(env(safe-area-inset-bottom) + .3rem);
}
/* `hidden` allein reicht nicht, sobald hier `display: flex` steht -
   die eigene Regel gewinnt gegen die des Browsers. */
.kartenlupe[hidden] { display: none; }
body.is-lupe { overflow: hidden; }

.kartenlupe__kopf {
  display: flex; align-items: center; gap: .8rem; flex: none;
  padding: .5rem .9rem; border-bottom: 1px solid var(--border);
}
.kartenlupe__titel {
  font-weight: 600; font-size: .95rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kartenlupe__tasten { display: flex; align-items: center; gap: .3rem; margin-left: auto; flex: none; }
.kartenlupe__tasten [data-zoom] { min-width: 2.1rem; font-size: 1.05rem; line-height: 1; }

/* `touch-action: none` ist der Kern: ohne die Zeile scrollt und zoomt
   der Browser die Seite, und die Finger kommen nie bei uns an. */
.kartenlupe__buehne {
  flex: 1 1 auto; min-height: 0; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  touch-action: none; cursor: grab;
}
.kartenlupe__buehne:active { cursor: grabbing; }

/* Im Grossen wird nicht abgelesen: ein Finger, der zieht, kann nicht
   gleichzeitig verschieben und ablesen. `pointer-events: none` schickt
   deshalb alles an die Bühne, auch das Doppeltippen. */
/* Die Grösse rechnet hier das Skript aus: die Zeichnung soll die Bühne
   so weit ausfüllen, wie ihr Seitenverhältnis es zulässt. Bliebe das
   <svg> so gross wie die Bühne, stünde die Zeichnung wieder mittig
   darin - und die Quellenangabe in ihrer Ecke wieder im Leeren. */
.kartenlupe__buehne .weg {
  max-width: none; max-height: none;
  touch-action: none; pointer-events: none;
}

.kartenlupe__hinweis {
  flex: none; margin: 0; padding: .35rem .9rem 0; font-size: .78rem;
}
/* width und height stehen als Merkmale am <svg>; mit `auto` auf beiden
   Seiten schrumpfen die Grenzen die Zeichnung im Verhältnis, statt sie
   in einen zu breiten Kasten zu stellen. */
.weg {
  /* 38rem statt 32 (Paket 15, FH: "Groesse/Platz"): auf dem Rechner
     blieb rund ein Fuenftel der Karte ungenutzt, und mehr Karte heisst
     hier mehr Lesbarkeit, nicht mehr Deko. Auf dem Handy begrenzt
     ohnehin max-width. */
  width: auto; height: auto; max-width: 100%; max-height: 38rem;
  touch-action: pan-y;
}
/* Eine eigene Farbe, die in der Karte nicht vorkommt - Grün ist Wald,
   Blau ist Wasser, Beige ist Strasse. Der Weg muss auf den ersten Blick
   der Weg sein. */
.weg__linie {
  fill: none; stroke: var(--weg); stroke-width: calc(3.4 / var(--zoom));
  stroke-linejoin: round; stroke-linecap: round;
  /* Der Weg kreuzt sich oft selbst; halbe Deckung zeigt, wo. */
  opacity: .85;
}
.weg__start { fill: var(--good); r: calc(6.5px / var(--zoom)); }
.weg__ende  { fill: var(--bad);  r: calc(6.5px / var(--zoom)); }

/* --- Tempofärbung (FH, 2026-09-02, umgedreht 2026-09-03) --------------
   Fünf Stufen vom eigenen langsamsten Fünftel zum schnellsten.

   Bis zum 03.09. lief die Rampe von Blau über das Weg-Lila nach Rot -
   sie blieb damit in der Farbfamilie des Weges, sagte aber nichts
   darüber, ob eine Stelle gut oder zäh war. FH: "langsam sollte rot
   sein und schnell sollte grün sein." Das ist die Ampel, die jeder
   ohne Legende liest, und sie steht in derselben Richtung wie --bad
   und --good in der übrigen Anwendung.

   Kräftige Mitteltöne, die auf hellem wie dunklem Grund stehen -
   deshalb weiterhin ohne Themen-Weiche. */
.weg__linie--t0 { stroke: #d94a3d; }
.weg__linie--t1 { stroke: #de8433; }
.weg__linie--t2 { stroke: #c9a52a; }
.weg__linie--t3 { stroke: #85ac35; }
.weg__linie--t4 { stroke: #34a05e; }

/* Dieselbe Rampe in der Legende unter der Karte. */
.tempoleiste {
  display: inline-flex; vertical-align: baseline; margin: 0 .15rem 0 .75rem;
  border-radius: 2px; overflow: hidden;
}
.tempoleiste__stufe { width: .55rem; height: .55rem; }
.tempoleiste__stufe--t0 { background: #d94a3d; }
.tempoleiste__stufe--t1 { background: #de8433; }
.tempoleiste__stufe--t2 { background: #c9a52a; }
.tempoleiste__stufe--t3 { background: #85ac35; }
.tempoleiste__stufe--t4 { background: #34a05e; }

/* --- Kilometermarken (FH, 2026-09-02) --------------------------------
   Kleine Scheiben auf dem Weg, die Zahl ist der Kilometer - das "km"
   sagt der Massstabsbalken. In Weg-Farbe, damit sie zur Linie gehören
   und nicht zur Karte. */
.km__punkt {
  fill: var(--surface); stroke: var(--weg);
  stroke-width: calc(1.6 / var(--zoom)); r: calc(7px / var(--zoom));
}
.km__zahl {
  font-size: calc(9.5px / var(--zoom));
  font-weight: 700; fill: var(--text);
  text-anchor: middle; dominant-baseline: central;
}

/* Dieselben zwei Farben noch einmal in der Legende. */
.weg-punkt {
  display: inline-block; width: .55rem; height: .55rem; border-radius: 50%;
  vertical-align: baseline; margin: 0 .15rem 0 .5rem;
}
.weg-punkt--start { background: var(--good); }
.weg-punkt--ende  { background: var(--bad); }

/* Die Marke in der Liste: eine winzige Schlaufe, die sagt "hier ist ein
   Weg aufgezeichnet". Ein Wort stünde in jeder zweiten Zeile. */
.weg-marke {
  display: inline-block; width: .8rem; height: .8rem; margin-left: .35rem;
  vertical-align: -.05rem; border: 2px solid var(--accent); border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg); opacity: .75;
}

/* Der Zeiger, der auf beiden Zeichnungen dieselbe Stelle meint.
   `hidden` allein reicht bei SVG nicht: die Regel des Browsers dafür
   gilt nur für HTML-Elemente. Ohne diese Zeile stand der Zeiger von
   Anfang an in der linken oberen Ecke. */
.weg__zeiger[hidden], .profil__zeiger[hidden] { display: none; }

.weg__zeiger {
  fill: var(--weg); stroke: var(--surface); stroke-width: calc(2.6 / var(--zoom));
  r: calc(7.8px / var(--zoom));
  pointer-events: none;
}
.profil__zeiger { stroke: var(--weg); stroke-width: 1.5; pointer-events: none; }

/* Eine feste Zeile statt eines Kästchens am Zeiger: die kann nicht aus
   dem Bild rutschen und steht auf dem Handy an derselben Stelle. */
.weg-info {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .9rem;
  font-size: .85rem; font-variant-numeric: tabular-nums;
  min-height: 1.4rem; margin-top: .2rem;
}
/* Start und Ziel rutschen ans Ende der Zeile - auf schmalen Geräten in
   die nächste, aber immer hinter die abgelesenen Werte. */
.weg-info__legende { margin-left: auto; color: var(--muted); font-size: .82rem; }
.weg-info__wert { font-weight: 650; }
.weg-info:not(.is-aktiv) .weg-info__wert { color: var(--muted); font-weight: 500; }

.profil-rahmen { padding: .2rem 0; }
.profil { display: block; width: 100%; height: 8rem; touch-action: pan-y; }
.profil__flaeche { fill: color-mix(in srgb, var(--accent) 16%, transparent); stroke: none; }
.profil__linie   { fill: none; stroke: var(--accent); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
/* Die Kilometermarken im Profil - dieselben Stellen wie die Scheiben
   auf der Karte. Sie liegen **unter** der Linie und über der Fläche:
   ein Strich, der die Höhenlinie durchschneidet, führte das Auge weg
   von dem, worum es geht. Ohne non-scaling-stroke würde der Strich
   mit dem gezerrten <svg> mitgezerrt und wäre am Rechner fingerdick. */
.profil__km {
  stroke: var(--weg); stroke-width: 1; opacity: .3;
  stroke-dasharray: 3 4; vector-effect: non-scaling-stroke;
}

.ekg-rahmen { overflow-x: auto; padding: .3rem 0; }
.ekg {
  display: block;
  /* Breiter darf es werden, schmaler nicht: 600 Bildpunkte sind vier je
     Millimeter, darunter verschwimmt das Kästchenmuster. Auf schmalen
     Geräten scrollt der Rahmen. Gestreckt wird in beide Richtungen
     gleich - das Verhältnis 25 mm/s zu 10 mm/mV bleibt also stehen,
     auch wenn ein Millimeter am Bildschirm kein Millimeter ist. */
  width: 100%; min-width: 600px; height: auto;
}
.ekg__fein { stroke: color-mix(in srgb, var(--bad) 22%, transparent); stroke-width: .06; fill: none; }
.ekg__grob { stroke: color-mix(in srgb, var(--bad) 42%, transparent); stroke-width: .12; fill: none; }
.ekg__linie {
  fill: none; stroke: var(--text); stroke-width: .22;
  stroke-linejoin: round; stroke-linecap: round;
}

/* --- Eintragen: Lebensmittel / Direkteingabe / Fotoanalyse -----------
   Die Leiste selbst ist die allgemeine `.segmented`; hier steht nur,
   wie sie in der Karte sitzt. Der Satz darunter gehoert zum gewaehlten
   Bereich und erklaert ihn - er steht deshalb zwischen Leiste und
   Formular und nicht am Kartenkopf. */
/* Anders als die Filterleisten scrollt diese nicht in sich: sie ist
   keine Verfeinerung einer Liste, sondern der Weg zum Formular, und was
   man nicht sieht, findet man nicht. */
.eingabewahl {
  display: inline-flex;
  /* In der Karte ist die Leiste ein Rasterfeld und zoege sich sonst
     ueber die ganze Breite - dieselbe Lage wie bei `.segmented--eng`. */
  justify-self: start; align-self: flex-start;
  margin-bottom: .7rem;
  overflow-x: visible;
  background: var(--surface-2);
}

/* Am Telefon **drei gleiche Spalten ueber die volle Breite** (FH,
   2026-08-28: "am iPhone schlecht"). Umgebrochen sass die Fotoanalyse
   allein in einer zweiten Zeile und sah aus wie ein Nachtrag, nicht wie
   die dritte von drei gleichwertigen Moeglichkeiten. Drei Spalten sind
   ausserdem drei gleich grosse Ziele fuer den Daumen - umgebrochen war
   das obere Paar breit und das untere schmal.

   `1fr` je Spalte statt `auto`: sonst bekaeme "Fotoanalyse" mehr Platz
   als "Direkteingabe", und die Leiste saehe schief aus. Die Schrift
   rueckt dafuer eine Spur zusammen; bei 390 px bleiben je Feld gut
   120 px, und das laengste Wort braucht keine 100. */
@media (max-width: 620px) {
  .eingabewahl {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    width: 100%;
  }
  .eingabewahl a {
    text-align: center;
    padding-inline: .15rem;
    /* Klein genug, dass "Direkteingabe" in eine Zeile passt: bei 390 px
       ist eine Spalte knapp 100 px breit, und das Wort braucht bei
       .72rem rund 78. Eine Stufe groesser brach es mitten im Wort um
       ("Direkteinga-be"), und das liest sich schlechter als klein. */
    font-size: .72rem;
    /* `.segmented a` setzt `nowrap` - dort ist die Leiste scrollbar und
       eine umbrechende Stufe saehe zerrissen aus. Hier gibt es kein
       Scrollen mehr, also darf "Direkteingabe" lieber zweizeilig stehen
       als hinter dem Rand zu verschwinden. */
    white-space: normal;
    overflow-wrap: anywhere;
    hyphens: auto;
    line-height: 1.2;
  }
}

.eingabesatz {
  margin: 0 0 .9rem;
  font-size: .85rem;
  line-height: 1.45;
}

/* Das Zeichen im Satz ist eine Erklaerung und kein Zustand - es steht
   mitten im Fliesstext und darf ihn nicht auseinanderziehen. */
.eingabesatz .badge { margin-inline: .1rem; }

/* --- Welche Kategorien vorgeschlagen werden --------------------------
   Eine Liste zum Abhaken, keine Tabelle: es gibt nur eine Angabe je
   Zeile. Das ganze Feld ist anklickbar, weil ein Kaestchen von 13 px am
   Handy zu klein ist. */
.kategorienfilter { list-style: none; margin: .4rem 0 1rem; padding: 0; }

.kategorienfilter__zeile + .kategorienfilter__zeile { border-top: 1px solid var(--border); }

/* Umbrechend, nicht scrollend: bei 390 px passen "Nahrungsergaenzung"
   und "118 Lebensmittel" nicht in eine Zeile - nebeneinander gezwungen
   schoben sie die Seite um 93 px auf. Umgebrochen rutscht die Zahl unter
   den Namen und bleibt lesbar. */
.kategorienfilter__zeile > label {
  display: flex; align-items: center; flex-wrap: wrap; gap: .3rem .6rem;
  padding: .5rem .2rem; cursor: pointer;
}
.kategorienfilter__zeile > label:hover { background: var(--surface-2); }

.kategorienfilter__zeile input { width: auto; flex: 0 0 auto; }

/* Unterkategorien ruecken ein - der Baum ist genau eine Ebene tief. */
.kategorienfilter__zeile--kind > label { padding-left: 1.8rem; }

.kategorienfilter__name { font-weight: 550; min-width: 0; overflow-wrap: anywhere; }

/* Die Zahl rechts, und am Handy darf sie umbrechen statt zu draengen. */
.kategorienfilter__zahl { margin-left: auto; font-size: .82rem; text-align: right; }

/* Eine Unterkategorie faellt mit ihrer Oberkategorie - ohne diesen
   Hinweis stuende sie ohne Haken da, als sei sie weiter im Rennen. */
.kategorienfilter__erbe {
  flex: 0 0 auto;
  font-size: .75rem; font-weight: 550;
  color: var(--muted);
  background: var(--surface-2);
  border-radius: 999px; padding: .1rem .5rem;
}

/* --- Konten in der Wartung ------------------------------------------
   Ein Konto, das auf seine Freischaltung wartet, ist der Grund, warum
   man diese Liste aufschlaegt - deshalb steht es oben (die Ordnung
   macht UserRepository) und faellt hier zusaetzlich ins Auge.
   -------------------------------------------------------------------- */
.badge--warnung {
  background: color-mix(in srgb, var(--warn) 16%, transparent);
  color: var(--warn);
}

/* Und eines fuer "hier geht gerade nichts" (seit dem 31.08.2026, fuer
   den KI-Zugang). Rot war bis dahin schlechten Messwerten vorbehalten -
   ein Schluessel, der nicht mehr traegt, ist der erste Zustand der
   Anwendung selbst, der dieselbe Farbe verdient: es ist keine Mahnung,
   sondern ein Ausfall. */
.badge--fehler {
  background: color-mix(in srgb, var(--bad) 16%, transparent);
  color: var(--bad);
}

/* Die Befundliste des KI-Zugangs: Kopf und Erklaerung links, das
   Abzeichen rechts. Dieselbe Anordnung wie die Kontenliste daneben -
   zwei Listen auf zwei Unterpunkten derselben Ebene sollen nicht
   verschieden aussehen. */
.befunde { list-style: none; display: grid; gap: .55rem; margin: 0; padding: 0; }
.befund {
  display: flex; align-items: flex-start; gap: .7rem; flex-wrap: wrap;
  padding: .55rem .1rem; border-top: 1px solid var(--border);
}
.befund:first-child { border-top: 0; }
/* Grundbreite statt `margin-right: auto`, aus demselben Grund wie bei
   `.routine__name`: die Zeile bricht um, und ohne sie waere der Text so
   breit wie sein laengster Satz - der hier drei Zeilen lang sein kann. */
.befund__was { display: grid; gap: .15rem; flex: 1 1 16rem; min-width: 0; }
.befund__was .muted { font-size: .85rem; }
/* Das Abzeichen sitzt auf der Zeile des Kopfes und nicht in der Mitte
   des ganzen Blocks: bei einem dreizeiligen Text staende es sonst neben
   der Erklaerung und nicht neben dem, was es bewertet. */
.befund > .badge { margin-top: .1rem; }

/* Der Grund eines gescheiterten Aufrufs steht **unter** seinem
   Abzeichen und nicht daneben: er ist ein ganzer Satz, und neben einem
   Abzeichen gelesen sieht ein Satz aus wie eine zweite Spalte, die es
   nicht gibt. */
.kigrund { display: block; margin-top: .15rem; font-size: .85rem; }

.konten { list-style: none; margin: .6rem 0 0; padding: 0; }

.konto {
  display: flex; align-items: center; flex-wrap: wrap; gap: .3rem .7rem;
  padding: .6rem .2rem;
}
.konto + .konto { border-top: 1px solid var(--border); }

/* Der Name nimmt den Platz, die beiden anderen Bloecke nur das Noetige.
   `min-width: 0`, damit eine lange Adresse den Namen nicht aufschiebt. */
.konto__wer   { flex: 1 1 12rem; min-width: 0; }
.konto__name  { display: block; font-size: .82rem; overflow-wrap: anywhere; }
.konto__stand { flex: 0 0 auto; }
.konto__ich   { flex: 0 0 auto; font-size: .82rem; }

/* Ein wartendes Konto ist kein Fehler, aber eine offene Aufgabe - ein
   Streifen am Rand statt einer roten Flaeche. */
.konto--wartet { border-left: 3px solid var(--warn); padding-left: .5rem; }

/* Sperren und Loeschen stehen zusammen am rechten Rand (FH,
   2026-08-30). Untereinander und nicht nebeneinander: der Loeschweg ist
   ein Klappfeld, und aufgeklappt braucht er die ganze Breite fuer sein
   Bestaetigungsfeld. */
.konto__knoepfe { flex: 0 0 auto; display: grid; gap: .3rem; justify-items: end; }

/* Wo beim letzten Verwalter kein Knopf steht, steht der Grund - gedeckt
   und klein, in derselben Reihe wie die Knoepfe, damit die Zeile nicht
   ploetzlich anders gebaut aussieht als ihre Nachbarn. Der ganze Satz
   haengt im `title`; hier stehen zwei Worte, sonst schoebe eine
   Begruendung die Liste auseinander. */
.konto__riegel { font-size: .82rem; padding: .2rem .1rem; cursor: help; }

/* Der Weg hinaus, zugeklappt. Gedeckt und klein: er soll auffindbar
   sein und nicht einladend. Aufgeklappt steht darunter ein Formular,
   das wieder links ausgerichtet ist - eine Beschriftung, die rechts
   klebt, liest sich nicht. */
.kontoweg > summary {
  cursor: pointer; font-size: .82rem; color: var(--muted);
  list-style: none; padding: .2rem .1rem;
}
.kontoweg > summary::-webkit-details-marker { display: none; }
.kontoweg > summary:hover { color: var(--bad); }
.kontoweg[open] { justify-self: stretch; }
.kontoweg[open] > summary { color: var(--bad); }
.kontoweg .form { justify-items: stretch; text-align: left; margin-top: .4rem; }

/* Am Telefon rutscht der Knopf unter den Namen und nimmt die Breite -
   dort ist er das, was man antippen will. */
@media (max-width: 520px) {
  .konto__wer { flex: 1 1 100%; }
  .konto form { flex: 1 1 100%; }
  .konto form .btn { width: 100%; }
  .konto__knoepfe { flex: 1 1 100%; justify-items: stretch; }
}

/* Die Zahl der Stimmen hinter den Sternen. Gedeckt und klein: sie
   qualifiziert die Sterne, sie konkurriert nicht mit ihnen. */
.sterne__stimmen {
  margin-left: .3rem;
  font-size: .8em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- Der Schleier, wenn etwas dauert ---------------------------------
   FH, 2026-09-01: "ein Schleier ueber die Seite und einen Kreisel
   darueber in der Mitte der Seite".

   Ein <dialog>, kein <div>: `showModal()` hebt das Element in die
   oberste Ebene des Browsers, und die schlaegt jedes z-index. Als
   <div> laege der Schleier hinter jedem offenen Fenster. */
.warten {
  position: static; margin: 0; padding: 0; border: 0;
  width: auto; max-width: none; background: none; color: inherit;
}
.warten:modal {
  position: fixed; inset: 0; margin: auto;
  /* `fit-content`, nicht `auto`: ein <dialog> nimmt sonst die volle
     erlaubte Breite, und dann steht ein Dreiwortsatz in einem Kasten,
     der zu drei Vierteln leer ist. */
  width: fit-content; max-width: min(26rem, calc(100vw - 2rem));
  background: none; border: 0; padding: 0;
  overflow: visible;
}
/* Derselbe Schleier wie am Fenster - er soll aussehen, als gehoere er
   dazu, und nicht wie ein zweites Bauteil. */
.warten::backdrop { background: rgb(0 0 0 / .45); }
.warten__kasten {
  display: flex; flex-direction: column; align-items: center; gap: .8rem;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 24px 60px rgb(0 0 0 / .35);
  padding: 1.4rem 1.6rem; text-align: center;
}
.warten__kreisel {
  /* Seit Paket 150 taumelt hier das Zeichen selbst: der Wuerfel als
     Koerper, ein P auf jeder Seite (partials/wuerfel_taumelt.php). Bis
     dahin ein Ring mit einem betonten Viertel. Die Buehne gibt die
     Perspektive; `--wuerfel-h` ist die halbe Kantenlaenge, um die jede
     Flaeche aus der Mitte rueckt. */
  --wuerfel-g: 3rem; --wuerfel-h: 1.5rem;
  width: var(--wuerfel-g); height: var(--wuerfel-g); color: var(--accent);
  perspective: 16rem;
}
.wuerfel {
  width: var(--wuerfel-g); height: var(--wuerfel-g); position: relative;
  transform-style: preserve-3d;
  /* Neun Sekunden, dann ist er einmal rundherum und steht wieder in der
     Schraegsicht des Zeichens. Die Stationen wechseln die Achse - "der
     wuerfel soll gerne unkoordiniert kreiseln" (FH, 11.09.2026) -, aber
     die Kurve laeuft in einem Zug durch: `linear`, und die Abstaende der
     Stationen sind nach dem Drehwinkel bemessen, damit er zwischen
     ihnen weder anhaelt noch springt (FH: "die bewegungen sollten
     fluessiger sein. ohne stopps."). Bis Paket 151 bremste eine
     cubic-bezier vor jeder Station - das sah nach Halt aus. */
  animation: warten-taumelt 9s linear infinite;
}
.wuerfel__seite { position: absolute; inset: 0; backface-visibility: hidden; }
.wuerfel__seite svg { width: 100%; height: 100%; display: block; }
/* Deckend, nicht durchsichtig: durch eine Flaeche mit Deckkraft saehe
   man die Innenseiten, und dort steht das P spiegelverkehrt. Dieselben
   drei Stufen wie am flachen Zeichen (1, .62, .32), gemischt mit der
   Flaeche des Kastens. */
.wuerfel__flaeche--1 { fill: currentColor; }
.wuerfel__flaeche--2 { fill: color-mix(in srgb, currentColor 62%, var(--surface)); }
.wuerfel__flaeche--3 { fill: color-mix(in srgb, currentColor 32%, var(--surface)); }
/* Y und Z laufen nur vorwaerts, X pendelt - so kommt das Taumeln
   zustande, und am Ende (405 = 45, 360 = 0) steht er wieder wie am
   Anfang, in derselben Drehrichtung wie beim Start: der Umlauf hat
   keine Naht. */
@keyframes warten-taumelt {
  0%    { transform: rotateX(-20deg) rotateY(45deg)  rotateZ(0deg); }
  17.2% { transform: rotateX(-130deg) rotateY(80deg) rotateZ(10deg); }
  31.3% { transform: rotateX(-120deg) rotateY(160deg) rotateZ(40deg); }
  51.2% { transform: rotateX(-20deg) rotateY(230deg) rotateZ(100deg); }
  62.9% { transform: rotateX(30deg) rotateY(300deg) rotateZ(160deg); }
  75.7% { transform: rotateX(60deg) rotateY(360deg) rotateZ(220deg); }
  91.0% { transform: rotateX(-10deg) rotateY(390deg) rotateZ(300deg); }
  100%  { transform: rotateX(-20deg) rotateY(405deg) rotateZ(360deg); }
}
/* Mit Pausen: die ersten 35 % steht er, dann dieselben Stationen wie
   oben, am Ende wieder die Ruhelage - und weil der Umlauf von vorn
   beginnt, steht er danach wieder. 14 Sekunden: rund fuenf stehen,
   neun taumeln. Aus der Ruhe heraus beschleunigt er (ease-in), vor der
   Ruhe bremst er (ease-out); dazwischen linear wie oben - die
   Zeitfunktion an einer Station gilt bis zur naechsten. */
.wuerfel--pausen { animation: warten-taumelt-pausen 14s linear infinite; }
@keyframes warten-taumelt-pausen {
  0%, 35% { transform: rotateX(-20deg) rotateY(45deg)  rotateZ(0deg); animation-timing-function: ease-in; }
  46.2%   { transform: rotateX(-130deg) rotateY(80deg) rotateZ(10deg); }
  55.3%   { transform: rotateX(-120deg) rotateY(160deg) rotateZ(40deg); }
  68.3%   { transform: rotateX(-20deg) rotateY(230deg) rotateZ(100deg); }
  75.9%   { transform: rotateX(30deg) rotateY(300deg) rotateZ(160deg); }
  84.2%   { transform: rotateX(60deg) rotateY(360deg) rotateZ(220deg); }
  94.2%   { transform: rotateX(-10deg) rotateY(390deg) rotateZ(300deg); animation-timing-function: ease-out; }
  100%    { transform: rotateX(-20deg) rotateY(405deg) rotateZ(360deg); }
}
/* `balance`, weil der Satz die Zahl tragen kann ("3 Packungsfotos
   werden gelesen …") und dann umbricht. Ohne die Regel stehen die
   Auslassungspunkte allein in der zweiten Zeile - das sieht aus, als
   waere der Satz abgeschnitten. */
.warten__wort { margin: 0; font-weight: 600; text-wrap: balance; }
.warten__lang { margin: 0; font-size: .9rem; color: var(--muted); }
/* Wer keine Bewegung will, bekommt keine: der Wuerfel steht dann still -
   das ist das Zeichen, wie es auch oben links steht, und sieht nicht nach
   Fehler aus (der Ring von frueher tat das und wurde deshalb zum Punkt).
   Der Satz darunter traegt die Auskunft ohnehin allein. */
@media (prefers-reduced-motion: reduce) {
  .wuerfel { animation: none; transform: rotateX(-20deg) rotateY(45deg); }
}

/* --- Profil -> Naehrstoffe: die eigenen Ziele (Paket 157) -----------
   Das Feld steht rechts in der Tabelle mit seiner Einheit daneben. Die
   Breite bekommt der Rahmen (`inline-grid`), nicht das Feld: die
   Grundregel fuer Eingabefelder (`width: 100%`, Spezifitaet 0,9,1)
   schluege jede Klassenregel am Feld selbst - siehe das Stilblatt-
   Kapitel zu `.feld-mit-x`. Die Zahl steht rechtsbuendig wie jede
   Zahl in einer Tabelle; `text-align` setzt die Grundregel nicht, also
   greift es hier. */
.naehrstoffziel { display: inline-grid; grid-template-columns: 6.5rem auto; align-items: center; gap: .4rem; }
.naehrstoffziel input { text-align: right; }
.naehrstoffziel__einheit { color: var(--muted); text-align: left; min-width: 2rem; }
table.table--naehrstoffziele td { vertical-align: middle; }
table.table--naehrstoffziele td.is-name { white-space: normal; }
table.table--naehrstoffziele td.is-name .field__error { margin: .15rem 0 0; }
table.table--naehrstoffziele tr.is-fehler td { background: color-mix(in srgb, var(--bad) 8%, transparent); }
@media (max-width: 620px) {
  .naehrstoffziel { grid-template-columns: 5.5rem auto; }
  table.table--naehrstoffziele td.is-name .is-schmal-nur { display: block; font-size: .85rem; }
}

/* =====================================================================
   Drucken - und als PDF sichern (Paket 154)
   =====================================================================
   FH, 11.09.2026: das PDF einer Liste kommt aus dem Browser, nicht vom
   Server. Der Knopf "Drucken / als PDF sichern" im Ansichtfenster
   laedt die Liste mit allen Zeilen (`?drucken=1`, siehe
   App\Support\Blaettern) und app.js ruft den Druckdialog; dort steht
   auf dem Mac wie auf dem iPhone "Als PDF sichern".

   Was hier steht, gilt fuer JEDE Seite, nicht nur fuer Listen: wer
   das Tagebuch druckt, bekommt dasselbe - keine Leiste, keine
   Knoepfe, keine Fenster. Auf dem Papier gibt es nichts zu bedienen.

   Die Regeln, in dieser Reihenfolge:

    1. Helle Farben, auch wer dunkel eingestellt hat. Papier ist weiss,
       und die Browser drucken Hintergruende von sich aus nicht - ein
       helles Grau auf Weiss (die dunkle `--text`) waere unlesbar.
       Deshalb die zehn Marken zurueck auf ihre hellen Werte, mit
       genau den Selektoren, die im Dunkeln gelten (siehe oben "Dunkel
       in zwei Zustaenden"), damit sie sie schlagen.
    2. Weg, was bedient: Kopfleiste, Navigation, Filterleiste, alle
       Knoepfe und Verweise als Knoepfe, Fenster, Blaettern, die
       Aktionenspalte, Meldungen, das Suchfeld-X. Die Leiste selbst,
       nicht ihre Karte: beim Gewicht steht die Liste mit in der
       Karte der Leiste - und war beim ersten Bau mit verschwunden.
    3. Die Tabelle ganz: kein Rollen, kein Kleben, jede Zeile am Stueck,
       der Kopf auf jeder Seite noch einmal (`table-header-group`).
    4. Die Druckzeile unter dem Listentitel (`.nur-druck`): Stand und
       Filter - am Bildschirm sagt das die Leiste, auf dem Papier
       sonst niemand.
    5. Verweise wie Text: keine Unterstreichung, keine Adresse dahinter.
   -------------------------------------------------------------------- */
.nur-druck { display: none; }

/* Am Telefon gibt es keinen Export (FH, 11.09.2026: "am iphone brauchen
   wir keinen export. das kann ausgeblendet sein") - dieselbe Grenze wie
   fuer alles, was am Telefon anders ist (620 px, siehe Handy-Tabellen).
   Der Reiter und sein Abschnitt sind im Baum, damit das Fenster ueberall
   dasselbe ist; nur zu sehen sind sie hier nicht. */
@media (max-width: 620px) {
  [data-reiter-knopf="export"], [data-reiter-teil="export"] { display: none !important; }
}

@media print {
  @page { margin: 12mm; }

  :root,
  :root:not([data-theme="light"]),
  :root[data-theme="dark"] {
    color-scheme: light;
    --accent:        var(--h-accent);
    --accent-strong: var(--h-accent-strong);
    --accent-soft:   var(--h-accent-soft);
    --accent-ink:    var(--h-accent-ink);
    --bg:        #ffffff;
    --surface:   #ffffff;
    --surface-2: var(--h-surface-2);
    --border:    var(--h-border);
    --text:      var(--h-text);
    --muted:     var(--h-muted);
    --shadow:    none;
  }

  html, body { background: #fff; }
  body { padding: 0; font-size: 10.5pt; }
  /* Am Schreibtisch rueckt `.main` nach rechts, neben die Leiste
     (`margin-left: 210px`, oben bei 900 px). Die Leiste ist weg - der
     Platz muss es auch sein. */
  body:not(.is-guest) .main, .main { margin-left: 0; padding: 0; gap: .8rem; }

  /* 2. Was bedient, geht weg. */
  .topbar, .nav, .flashes, dialog, .filterleiste,
  .pager, .btn, .btn-row, button,
  .theme-toggle, .chart-controls, .sortpfeil, .feld-x,
  table td.is-aktionen, table th.is-aktionen,
  [data-klappen-alle], [data-zuruecksetzen] { display: none !important; }

  /* Der Schalter der Gruppenzeile ist ein Knopf - aber der traegt den
     Gruppenwert. Er bleibt, als Text; nur der Winkel davor geht. */
  .gruppenzeile__schalter { display: inline !important; border: 0; padding: 0; background: none; font: inherit; color: inherit; }
  .gruppenzeile__schalter .icon { display: none; }

  /* 3. Die Tabelle ganz. */
  .card { box-shadow: none; border: 0; padding: 0; break-inside: auto; }
  .table-wrap { overflow: visible; margin: 0; padding: 0; background: none; }
  table { width: 100%; font-size: 9.5pt; }
  table thead { display: table-header-group; }
  table thead th { position: static; box-shadow: none; border-bottom: 1.5px solid var(--h-text); }
  table tr { break-inside: avoid; }
  table td, table th { white-space: normal; }
  .is-schmal-nur { display: none !important; }
  .is-schmal-weg { display: table-cell !important; }

  /* 4. Die Druckzeile. */
  .nur-druck { display: block; }
  .listenkopf__druckzeile { margin: -.4rem 0 .6rem; font-size: 9pt; color: var(--h-muted); }

  /* 5. Verweise wie Text. */
  a, a:visited { color: inherit; text-decoration: none; }
  a[href]::after { content: none; }
}
