/* komponenten.css - Paddy's Mealplan
 *
 * Komponenten: Rezeptraster, Zutatenzeilen, Meal-Ansicht, Bottom-Sheet, Modal, Picker, Listen, Vorkochen, Toast, Profil, Intro/Rechner, Druck.
 *
 * Ausgeschnitten aus index.html (vormals Zeile 1302-2400), unveraendert.
 * Regeln fuer diesen Ordner: css/CLAUDE.md
 */
  /* ---------- Recipe grid ---------- */
  .catgroup { margin-bottom: 22px; }
  .cathead {
    display: flex; align-items: center; gap: 8px; width: 100%; margin: 0 0 12px;
    padding: 6px 2px; background: none; border: none; border-bottom: 1px solid var(--border);
    color: var(--text); font: inherit; cursor: pointer; text-align: left;
  }
  .cathead:hover { border-bottom-color: var(--border-strong); }
  /* hitSlop wie .toast-undo::after: Trefferflaeche auf 44px (Apple HIG), Aussehen
     unveraendert. Gemessen am 16.09.2026 mit tools/abnahme-mobil.py. */
  .cathead { position: relative; }
  .cathead::after { content: ""; position: absolute; inset: -6px 0; }
  /* Waehrend der Suche ist die Kopfzeile nur Beschriftung, kein Knopf - siehe paintRecipeGroups(). */
  .cathead.static { cursor: default; padding-left: 25px; }
  .cathead.static:hover { border-bottom-color: var(--border); }
  .cathead .caret { width: 15px; height: 15px; flex: none; color: var(--text-muted); transition: transform var(--dur-fast); }
  .cathead .caret.open { transform: rotate(90deg); }
  .cathead .cname { font-size: 14.5px; font-weight: 650; letter-spacing: .01em; display: inline-flex; align-items: center; gap: 7px; }
  .cathead .cname .cicon { width: 16px; height: 16px; flex: none; color: var(--text-muted); }
  /* Dieselben Farben wie die Slot-Symbole im Wochenplan, damit Plan und Bibliothek
     dieselbe Bildsprache sprechen. Dieselben Klassen wie die Kategorie-Chips (CAT_CLASS):
     mehr kennt das Theme nicht, und mehr soll es auch nicht kennen - Snack, Dessert,
     Beilage, Getraenk und Favoriten bleiben grau. */
  .cathead .cname .cicon.cat-fr { color: var(--fr); }
  .cathead .cname .cicon.cat-ha { color: var(--mi); }
  .cathead .ccount {
    font-size: 11.5px; font-weight: 650; color: var(--text-muted);
    background: var(--surface-2); border: 1px solid var(--border);
    padding: 1px 8px; border-radius: 999px;
  }
  /* Suchfeld im Meals-Reiter, eigene Zeile unter der .section-head (die ist flex-wrap und
     streckt auf 720px .btn per flex:1 - ein drittes Element wuerde mitgestreckt). */
  .recipe-search { position: relative; margin: -6px 0 18px; }
  /* Filter-Chips im Meals-Reiter (B7). Gleiche Bauform wie die Merkmale im Meal-Sheet
     (.mtags) - es ist dieselbe Auswahl, nur einmal setzend und einmal filternd.
     Die Reihe BRICHT UM und scrollt nicht waagerecht. Der erste Entwurf hatte
     `overflow-x: auto`, und die Messung hat zweierlei gezeigt: Die Regel griff gar nicht
     (`.mtags` steht weiter unten im Stylesheet und gewinnt bei gleicher Spezifitaet mit
     `flex-wrap: wrap`) - und sie war auch inhaltlich falsch. Ein waagerechter Scroller
     ueber der Meal-Liste ist genau die Gestenfalle aus CLAUDE.md §11, und er versteckt
     Filter am rechten Rand. In der Praxis ist die Reihe meist einzeilig: gezeigt wird nur,
     was im Bestand vorkommt. */
  .rfilters { margin: -8px 0 16px; }
  /* hitSlop wie .toast-undo::after: Trefferflaeche auf 44px (Apple HIG), Aussehen
     unveraendert. Gemessen am 16.09.2026 mit tools/abnahme-mobil.py. */
  .rfilters button { position: relative; }
  .rfilters button::after { content: ""; position: absolute; inset: -3px 0; }
  .recipe-search svg { position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
    width: 16px; height: 16px; color: var(--text-muted); pointer-events: none; }
  /* Attributselektor, nicht nur "input": input[type="search"] (Zeile ~1113) hat dieselbe
     Spezifitaet und steht spaeter - dessen Kurzform "padding: 9px 11px" loeschte sonst das
     padding-left und die Lupe laege auf dem Platzhaltertext. */
  .recipe-search input[type="search"] { padding-left: 34px; }
  .recipes { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; }
  /* Rezeptbuch: dieselbe Karte wie die eigenen Meals, nur mit einem Fuss statt der
     Bearbeiten-Aktionen. Bewusst keine eigene Kartenoptik - es ist derselbe Gegenstand,
     nur noch nicht der eigene. */
  /* position + z-index sind hier PFLICHT, nicht Kosmetik: .rcard-open::after spannt als
     Stretched Link mit z-index:1 ueber die ganze Karte. Ein statisch positioniertes Element
     kann z-index gar nicht anwenden - der Link lag deshalb ueber dem Uebernehmen-Knopf und
     fing jeden Klick ab, die Ansicht ging auf statt zu uebernehmen (TROUBLESHOOTING 90). */
  .cb-foot { margin-top: 10px; position: relative; z-index: 2; }
  .cb-foot .btn { width: 100%; }
  /* "In deinen Meals" ist eine Feststellung, kein Knopf - deshalb ohne Rahmen und in der
     gedaempften Schrift. Gleiche Hoehe wie der Knopf daneben, damit die Karten in einer
     Reihe nicht unterschiedlich hoch werden. */
  .cb-drin { display: flex; align-items: center; justify-content: center; gap: 6px; min-height: 34px;
             color: var(--text-muted); font-size: 13px; font-weight: 600; }
  .rcard {
    position: relative;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    box-shadow: var(--shadow); padding: 0; display: flex; flex-direction: column; overflow: hidden;
    transition: transform var(--dur-fast) ease, box-shadow var(--dur-fast) ease, border-color var(--dur-fast) ease;
  }
  .rcard::before {
    content: ""; position: absolute; top: 0; left: 0; right: 0; height: 4px; z-index: 2;
    background: linear-gradient(90deg, var(--accent), var(--accent-strong));
  }
  .rcard:hover {
    transform: translateY(-3px);
    border-color: color-mix(in srgb, var(--accent) 42%, var(--border));
    box-shadow: 0 12px 30px color-mix(in srgb, var(--accent) 20%, transparent);
  }
  /* Ganze Karte als Drop-Ziel fuer ein Foto aus dem Dateisystem (Desktop) - im
     Ruhezustand unsichtbar, nur waehrend des Ziehens ein Rahmen als Rueckmeldung.
     Mobil fehlt echtes OS-Datei-Drag ohnehin, daher keine eigene Handhabung noetig. */
  .rcard.dragover-file { outline: 2px dashed var(--accent); outline-offset: -2px; background: color-mix(in srgb, var(--accent) 8%, var(--surface)); }

  /* Zutaten-Zeilen im Meal-Formular */
  /* Akzentbalken oben wie bei den Meal-Karten (.rcard::before) — grenzt die einzelnen
     Zutaten sichtbar voneinander ab; etwas schmaler, weil hier mehrere untereinander stehen */
  /* overflow bleibt sichtbar, damit die Vorschlagsliste der Zutaten-Suche aus der Zeile
     herausragen darf; der Akzentbalken rundet sich dafuer selbst oben mit ab. */
  .ing-row { position: relative; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 14px 12px 11px; margin-bottom: 9px; transition: border-color var(--dur-fast), box-shadow var(--dur-fast); }
  .ing-row::before {
    content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; z-index: 2;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    background: linear-gradient(90deg, var(--accent), var(--accent-strong));
  }
  .ing-row:focus-within { border-color: color-mix(in srgb, var(--accent) 45%, var(--border-strong)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent); }
  /* Die offene Vorschlagsliste hebt ihre Zeile ueber die darunterliegenden Zeilen */
  .ing-row.ac-open { z-index: 5; }
  /* Antwort des Barcode-Scanners, direkt in der Zeile statt nur unten im Toast
     (ingMsg() in index.html). Bewusst KEIN Rot und kein Warnsymbol: Ein Produkt, das die
     Datenbank nicht kennt, ist kein Fehler des Nutzers. Der Akzentstreifen macht den Satz
     auffindbar, der Ton bleibt ruhig. */
  .ing-msg {
    margin: 9px 0 0; padding: 7px 10px;
    border-left: 3px solid var(--accent);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    background: color-mix(in srgb, var(--accent) 9%, transparent);
    color: var(--text); font-size: 13px; line-height: 1.45;
  }
  .ing-top { display: flex; align-items: center; gap: 8px; }
  /* Name und Barcode-Knopf bleiben zusammen — auch wenn die Zeile mobil umbricht */
  .ing-nrow { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }
  .ing-nwrap { position: relative; flex: 1; min-width: 0; }
  .ing-nwrap .ing-name { width: 100%; }
  /* Zutaten-Suche: Vorschlaege aus der eingebauten Naehrwerttabelle (ARIA-Listbox) */
  .ing-ac {
    position: absolute; z-index: 30; top: calc(100% + 4px); left: 0; right: 0;
    margin: 0; padding: 4px; list-style: none; max-height: 236px; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm); box-shadow: var(--shadow);
  }
  .ing-ac-item {
    display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
    min-height: 40px; padding: 9px 10px; border-radius: 7px; cursor: pointer;
    transition: background-color var(--dur-fast), color var(--dur-fast);
  }
  .ing-ac-item .fname { font-weight: 600; color: var(--text); }
  .ing-ac-item .fnut { flex: none; font-size: 11px; font-weight: 700; color: var(--text-muted); font-variant-numeric: tabular-nums; }
  .ing-ac-item:hover, .ing-ac-item.on { background: color-mix(in srgb, var(--accent) 14%, var(--surface-2)); }
  .ing-ac-item:hover .fnut, .ing-ac-item.on .fnut { color: var(--accent); }
  /* Mengen-Feld mit Einheiten-Auswahl (g / ml / Stueck) */
  .ing-gwrap { display: flex; gap: 6px; flex: none; width: 158px; }
  .ing-gwrap .ing-g { flex: none; width: 74px; text-align: right; }
  .ing-gwrap .ing-u { flex: 1; min-width: 0; padding: 8px 6px; }
  .ing-ic { flex: none; width: 34px; height: 34px; border: 1px solid var(--border); background: var(--surface); color: var(--text-muted); border-radius: var(--radius-sm); cursor: pointer; display: grid; place-items: center; font-size: 14px; line-height: 1; padding: 0; transition: color var(--dur-fast), border-color var(--dur-fast), background-color var(--dur-fast); }
  .ing-ic svg { width: 17px; height: 17px; }
  .ing-ic:disabled, .ing-barcode:disabled { opacity: .5; cursor: default; }
  /* Beschriftet statt nur bebildert: auf Touch gibt es keine Tooltips, ein reines
     Symbol waere dort unauffindbar. Deshalb traegt der Knopf seinen Namen sichtbar. */
  .ing-barcode {
    flex: none; display: inline-flex; align-items: center; gap: 6px; height: 34px;
    padding: 0 11px; border: 1px solid var(--border); background: var(--surface);
    color: var(--text-muted); border-radius: var(--radius-sm); cursor: pointer;
    font-family: var(--font-body); font-size: 11px; font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase; white-space: nowrap;
    transition: color var(--dur-fast), border-color var(--dur-fast), background-color var(--dur-fast);
  }
  .ing-barcode svg { width: 17px; height: 17px; flex: none; }
  .ing-barcode:hover { color: var(--accent); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--surface)); }
  .ing-ic.busy, .ing-barcode.busy { animation: syncpulse 1s ease-in-out infinite; }
  .ing-del:hover { color: var(--accent); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--surface)); }
  /* Naehrwerte zugeklappt. Wer die Zutat aus der Vorschlagsliste waehlt, bekommt sie
     automatisch gefuellt und muss sie gar nicht sehen — sichtbar kosteten die vier
     Felder 93 von 225 px Zeilenhoehe (gemessen). Zum Nachbessern aufklappbar, ueber
     <details> statt eigenem Zustand: nativ per Tastatur und Screenreader bedienbar. */
  .ing-nut { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border); }
  /* Zugeklappt braucht der Trennstrich weniger Luft — die Zeile traegt sich selbst. */
  .ing-nut:not([open]) { margin-top: 5px; padding-top: 4px; }
  .ing-nsum {
    display: flex; align-items: center; gap: 8px; min-height: 44px; cursor: pointer;
    list-style: none; /* Firefox */
  }
  .ing-nsum::-webkit-details-marker { display: none; } /* Safari/Chrome */
  .ing-nsum:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 26%, transparent); border-radius: var(--radius-sm); }
  .ing-nsum::after {
    content: "▾"; margin-left: auto; flex: none; color: var(--text);
    font-size: 14px; line-height: 1; opacity: .7; transition: transform var(--dur-fast);
  }
  .ing-nut[open] .ing-nsum::after { transform: rotate(180deg); }
  /* Feststehendes Label in der Kopfzeile (inkl. Bezugsgroesse .ing-per als Kind) -
     bricht nicht um, die Makro-Kurzfassung daneben bekommt den ganzen restlichen Platz. */
  .ing-ntitle { flex: none; font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--text-muted); white-space: nowrap; }
  /* Bezugsgroesse steht als Inline-Zusatz hinter "Makros" in der Kopfzeile - normale
     Schrift statt der Versalien von .ing-ntitle, damit sie als Nebeninfo lesbar bleibt. */
  .ing-per { font-size: 11px; font-weight: 400; color: var(--text-muted); text-transform: none; letter-spacing: 0; }
  .ing-brief { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; text-align: right; gap: 10px; margin-left: auto; font-size: 11.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; min-width: 0; }
  /* Aufgeklappt stehen dieselben Werte schon unten in den Feldern - die Kurzfassung
     in der Kopfzeile waere doppelt, deshalb nur zugeklappt sichtbar. */
  .ing-nut[open] .ing-brief { display: none; }
  .ing-brief .t-kcal { --tc: var(--accent); }
  /* Die drei Makros stehen als EINE Gruppe (.ing-brief-macros, macroLineHtml()). Die Farbe
     traegt dort der Punkt (.mv::before), nicht der Text - dasselbe Signal wie bei .ing-lbl
     unten und in den .nutfacts-Kacheln. */
  .ing-vals { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; padding-bottom: 3px; }
  .ing-nut label { display: flex; flex-direction: column; gap: 4px; font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--text-muted); text-align: center; }
  /* Farbpunkt vor dem Feldnamen, gleiches Muster wie .nutfacts .k und .mv (nur der
     Punkt traegt Farbe, der Text bleibt --text-muted) - so uebernimmt sich die bekannte
     Kontrastschwaeche von Amber/Blau auf hellem Grund nicht in ein neues Textelement. */
  .ing-lbl { display: inline-flex; align-items: center; justify-content: center; gap: 5px; }
  .ing-lbl::before { content: ""; width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--tc, var(--text-muted)); }
  .ing-vals label.t-kcal { --tc: var(--accent); }
  .ing-vals label.t-carb { --tc: var(--carb); }
  .ing-vals label.t-prot { --tc: var(--prot); }
  .ing-vals label.t-fat  { --tc: var(--fat); }
  .ing-nut input { width: 100%; padding: 7px 4px; text-align: center; }
  /* #f-ing-add ist der alte "+ Weitere Zutat"-Knopf aus dem fruehren openRecipeForm -
     updateMacroSum() schaltet weiterhin sein hidden-Attribut (Funktion unveraendert
     uebernommen, siehe openMealSheet()), der Knopf selbst bleibt aber dauerhaft
     unsichtbar: der sichtbare Einstieg der neuen Ansicht ist #ms-ing-add. */
  #f-ing-add { display: none !important; }

  /* ---------- Zutaten-Ruhezustand (openMealSheet, Vorbild Fotos/Ansicht.jpeg) ----------
     .ing-row traegt im Bearbeiten-Zustand (.editing) unveraendert die Formular-Optik von
     oben (Karte mit Akzentbalken); im Ruhezustand wird nur .ing-view gezeigt, als schlichte
     Liste mit duennen Trennlinien statt Kacheln. */
  .ing-row:not(.editing) { background: none; border: 0; padding: 0; margin-bottom: 0; }
  .ing-row:not(.editing)::before { display: none; }
  /* Die Zeile traegt tabindex="-1" und wird beim Aufklappen programmatisch fokussiert
     (siehe Klick-Handler in addIngRow). Das ist ein reiner Fokus-Anker, kein Bedienelement -
     deshalb kein Fokusring. */
  .ing-row:focus { outline: none; }
  .ing-row.editing .ing-view { display: none; }
  .ing-row:not(.editing) .ing-top, .ing-row:not(.editing) .ing-nut, .ing-row:not(.editing) .ing-done-row { display: none; }
  .ing-view {
    display: grid; grid-template-columns: 58px 1fr auto 34px; column-gap: 12px;
    align-items: start; padding: 11px 2px; border-bottom: 1px solid var(--border);
    position: relative;
  }
  .ing-view.ro { grid-template-columns: 58px 1fr auto; }
  /* Seit der Stift weg ist, deutet sonst nichts an, dass die Zeile aufklappt. Der Rahmen liegt
     etwas ausserhalb des Innenabstands, damit die Flaeche die ganze Zeile umfasst und nicht
     nur den Text. hover NUR in @media (hover: hover): auf Touch bliebe der Zustand sonst nach
     dem Antippen kleben. Fuer Touch traegt der :active-Press-State die Rueckmeldung - dort
     lernt man beim ersten Antippen, dass die Zeile reagiert. */
  @media (hover: hover) {
    .ing-view:not(.ro):hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }
  }
  .ing-view:not(.ro):active { background: color-mix(in srgb, var(--accent) 9%, transparent); }
  /* Kein border-radius: die Trennlinie ist der border-bottom derselben Zeile, die wuerde an
     den Enden mitrunden. Die Liste bleibt dadurch eine Liste, kein Stapel Kacheln. */
  /* padding-left liegt mit in der Transition: auf dem Handy entsteht der Einzug erst mit
     dem Sortierzustand (.ings-sorting) und darf nicht springen. */
  .ing-view:not(.ro) { transition: background var(--dur-fast) var(--ease-out), padding-left var(--dur-fast) var(--ease-out); }
  @media (prefers-reduced-motion: reduce) { .ing-view:not(.ro) { transition: none; } }
  .ing-view-qty { grid-column: 1; grid-row: 1; font-variant-numeric: tabular-nums; color: var(--text-muted); font-size: 13px; padding-top: 2px; }
  /* Stretched-Link-Muster (Nachtrag Abnahme Phase 1, gleiches Vorbild wie .rcard-open in
     Schritt 3): der Stift ist weg, der Name uebernimmt seine Aufgabe UND macht die Zeile
     per ::after als Tastaturziel fokussierbar - .ing-view ist ein <div> ohne eigenen Fokus. */
  /* KEIN position:relative hier - sonst bezieht sich das ::after unten auf den Namen selbst
     und nur der Text waere klickbar (bei "Reis" ein winziges Ziel, der Rest der Zeile tot).
     Der Bezugspunkt muss .ing-view sein, genau wie .rcard-open sich auf .rcard bezieht. */
  .ing-view-name { all: unset; grid-column: 2; grid-row: 1; font-weight: 600; align-self: center; }
  /* Nur die bearbeitbare Zeile ist ein Knopf - die Nur-Lese-Zeile (.ro) nutzt dasselbe
     Markup als reinen <span>, ohne Zeigerkurs oder unsichtbare Klickflaeche. */
  .ing-view:not(.ro) .ing-view-name { cursor: pointer; }
  .ing-view:not(.ro) .ing-view-name::after { content: ""; position: absolute; inset: 0; }
  .ing-view:not(.ro) .ing-view-name:focus-visible { outline: none; }
  .ing-view:not(.ro) .ing-view-name:focus-visible::after { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
  .ing-view-kcal { grid-column: 3; grid-row: 1; text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; padding-top: 2px; }
  .ing-view-macros {
    grid-column: 2 / span 2; grid-row: 2; text-align: right; margin-top: 2px;
    font-size: 11.5px; color: var(--text-muted); font-variant-numeric: tabular-nums;
  }
  .ing-view-del {
    grid-column: 4; grid-row: 1 / span 2; align-self: start; position: relative; z-index: 2;
    width: 34px; height: 34px; border: 0; background: none; color: var(--text-muted);
    cursor: pointer; border-radius: var(--radius-sm); display: grid; place-items: center; font-size: 15px;
  }
  /* Die 44px-Trefferflaeche kommt aus dem gemeinsamen Mechanismus in css/basis.css
     (TROUBLESHOOTING #38). Hier bleibt nur, was wirklich nur hier gilt: z-index haelt das
     ✕ ueber dem ::after der Anzeigezeile, das sonst die ganze Zeile ueberdeckt. */
  .ing-view-del:hover { color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }

  /* ---------- Zutaten sortieren (Paket 3) ----------
     Der Anfasser kostet im Ruhezustand KEINEN Platz: er liegt absolut im linken Rand, und
     die Zeile rueckt nur dort ein, wo dafuer Platz ist - am Rechner dauerhaft
     (hover: hover), auf dem Handy erst waehrend des Sortierens (.ings-sorting). Bei 360 px
     hat die Zeile keine fuenfte Spalte uebrig: Menge, Name, kcal und Makros stehen dort
     bereits gedraengt (plans/Neun_Befunde_Alltag.MD, Paket 3). */
  .ing-grip {
    position: absolute; left: 0; top: 7px; z-index: 2;
    width: 20px; height: 30px; display: grid; place-items: center;
    border: 0; padding: 0; background: none; color: var(--text-muted);
    opacity: 0; pointer-events: none; cursor: grab; touch-action: none;
    transition: opacity var(--dur-fast) var(--ease-out);
  }
  /* 44 px Tastflaeche in der SENKRECHTEN Achse (TROUBLESHOOTING 38: die Enge gilt nur in
     einer Achse). Waagerecht ist der Anfasser nicht das einzige Ziel - auf dem Handy nimmt
     das Halten die ganze Zeile auf. */
  .ing-grip::after { content: ""; position: absolute; inset: -7px -12px; }
  .ing-grip svg { width: 14px; height: 14px; }
  .ing-view.ro .ing-grip { display: none; }
  @media (hover: hover) {
    /* Nur auf Zeigegeraeten: dort ist das Fenster breit genug fuer den Einzug, und nur dort
       gibt es ueberhaupt ein Ueberfahren, das den Anfasser zeigen koennte. */
    .ing-view:not(.ro) { padding-left: 22px; }
    .ing-view:not(.ro):hover .ing-grip, .ing-grip:focus-visible { opacity: .8; pointer-events: auto; }
  }
  .ings-sorting .ing-view:not(.ro) { padding-left: 22px; }
  .ings-sorting .ing-view:not(.ro) .ing-grip { opacity: .8; pointer-events: auto; }
  /* Waehrend der Geste darf nichts markiert werden - sonst zieht iOS beim Halten die Lupe
     auf, statt die Zeile aufzunehmen. */
  .ings-sorting { user-select: none; -webkit-user-select: none; }
  .ing-view:not(.ro) { -webkit-touch-callout: none; }
  .ing-row.dragging { position: relative; z-index: 6; }
  .ing-row.dragging .ing-grip { cursor: grabbing; opacity: 1; }
  /* Die aufgenommene Zeile hebt sich ab: Flaeche, Schatten, Rundung. Die Trennlinie
     verschwindet dabei - eine schwebende Karte hat keinen Nachbarn, von dem sie trennt. */
  .ing-row.dragging .ing-view {
    background: var(--surface-2); box-shadow: var(--shadow);
    border-radius: var(--radius-sm); border-bottom-color: transparent;
  }
  /* .shift traegt nur die Ausweichbewegung der NICHT gezogenen Zeilen. Die gezogene Zeile
     folgt dem Finger 1:1 und darf keine Transition haben. */
  .ing-row.shift { transition: transform var(--dur-fast) var(--ease-out); }
  @media (prefers-reduced-motion: reduce) {
    .ing-grip, .ing-row.shift { transition: none; }
  }
  .ms-ing-add { width: 100%; justify-content: center; margin-top: 10px; }
  /* "Fertig" am Ende der aufgeklappten Zeile (Nachtrag Abnahme Phase 1) - bewusst NICHT
     neben dem ✕ im .ing-top: Bestaetigen und Loeschen duerfen nicht nebeneinander liegen. */
  .ing-done-row { display: flex; justify-content: flex-end; margin-top: 4px; }
  @media (max-width: 560px) {
    .ing-view-macros { grid-column: 1 / span 3; text-align: left; }
  }

  /* ---------- Meal-Ansicht (openMealSheet) ---------- */
  .mealsheet { position: relative; }
  .mealsheet .modal-head {
    position: absolute; top: 0; left: 0; right: 0; z-index: 3;
    border: 0; background: none; padding: 12px; justify-content: flex-end;
  }
  .mealsheet .modal-head .icon-gh.on-photo {
    color: #fff; border-radius: 50%;
    background: color-mix(in srgb, var(--photo-scrim) 50%, transparent);
    box-shadow: var(--shadow-float);
  }
  .mealsheet .modal-head .icon-gh.on-photo:hover { color: var(--on-photo); background: color-mix(in srgb, var(--photo-scrim) 68%, transparent); }
  /* "Bearbeiten" im Kopf des Nur-Lese-Zweigs (Nachtrag Abnahme Phase 2) - nur sichtbar,
     wenn canEdit() wahr ist; jedes bestehende Meal oeffnet inzwischen zuerst lesend, egal
     aus welchem Reiter. margin-right:auto schiebt sie an den linken Rand, Teilen/Schliessen
     bleiben rechts. */
  .mealsheet .modal-head .ms-edit-btn { margin-right: auto; }
  .ms-photo { position: relative; }
  .ms-photo img { width: 100%; height: 220px; object-fit: cover; display: block; }
  .ms-photo .ms-cover-sym { width: 100%; height: 220px; }
  .ms-photobar { position: absolute; right: 10px; bottom: 10px; display: flex; gap: 8px; }
  .ms-photobtn {
    font-family: var(--font-body); padding: 7px 13px; border-radius: 999px; border: 0; cursor: pointer;
    font-size: 12.5px; font-weight: 600; color: #fff;
    background: color-mix(in srgb, var(--photo-scrim) 55%, transparent); box-shadow: var(--shadow-float);
  }
  .ms-photobtn:hover { background: color-mix(in srgb, var(--photo-scrim) 72%, transparent); }
  .ms-photobtn[hidden] { display: none; }
  /* Der Herkunftshinweis unter dem Foto. Leise gehalten - er ist eine Fussnote, keine
     Aussage ueber das Gericht - aber mit --text-muted noch klar lesbar, weil er eine
     Offenlegung traegt und keine Verzierung ist. */
  .ms-bildhinweis {
    margin: 0 0 10px; font-size: 11.5px; line-height: 1.4;
    color: var(--text-muted); letter-spacing: .01em;
  }
  .ms-head { display: flex; align-items: center; gap: 10px; margin: 14px 0; }
  .ms-title { font-family: var(--font-display); font-size: 21px; font-weight: 700; margin: 0; flex: 1; min-width: 0; }
  .ms-name {
    flex: 1; min-width: 0; font: inherit; font-family: var(--font-display); font-size: 21px;
    font-weight: 700; color: var(--text); border: 1px solid transparent; background: transparent;
    padding: 6px 8px; border-radius: var(--radius-sm);
  }
  .ms-name:hover { border-color: var(--border-strong); background: var(--bg); }
  .ms-name:focus { outline: none; border-color: var(--accent); background: var(--bg); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
  .ms-cat { flex: none; width: auto; font-size: 16px; padding: 7px 8px; }
  .ms-ings .sec-h { margin: 22px 0 4px; }   /* Schrift kommt aus .sec-h */
  /* ---------- Merkmale eines Meals (B6) ----------
     Mehrfachauswahl mit aria-pressed, gleiche Bauform wie .tdays im Rechner. Bewusst
     nicht .seg: das ist eine Einfachauswahl (genau ein .on) und wuerde hier falsche
     Erwartungen wecken. (Ein .seg fuer den "Aufwand" stand hier einen Tag lang darunter -
     das Feld ist am 13.08.2026 entfallen, weil es niemand auswertete.) */
  .ms-meta .sec-h { margin: 22px 0 8px; }
  .mtags { display: flex; flex-wrap: wrap; gap: 6px; }
  .mtags button {
    font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
    border: 1px solid var(--border); background: var(--surface-2); color: var(--text-muted);
    padding: 9px 13px; min-height: 40px; border-radius: 999px;
    transition: color var(--dur-fast), background var(--dur-fast), border-color var(--dur-fast), box-shadow var(--dur-fast);
  }
  .mtags button:hover { color: var(--text); border-color: var(--border-strong); }
  .mtags button[aria-pressed="true"] {
    color: var(--accent-contrast); border-color: transparent;
    background: linear-gradient(135deg, var(--accent-solid), var(--accent-solid-strong));
    box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 32%, transparent);
  }
  /* Nur-Lese-Zweig: dieselben Merkmale, aber als ruhige Marken ohne Knopf-Optik -
     hier ist nichts umschaltbar, also darf auch nichts wie ein Schalter aussehen. */
  .mflags { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 0; }
  .mflags span {
    font-size: 11.5px; font-weight: 600; letter-spacing: .02em; padding: 5px 10px;
    border-radius: 999px; color: var(--text-muted); background: var(--surface-2);
    border: 1px solid var(--border); white-space: nowrap;
  }
  .mflags span.on { color: var(--accent-strong); background: color-mix(in srgb, var(--accent) 12%, var(--surface)); border-color: color-mix(in srgb, var(--accent) 30%, transparent); }
  /* Fusszeile der Meal-Ansicht (Nachtrag Abnahme Phase 1): "Meal loeschen"/"Verwerfen" und
     "Fertig" stehen jetzt zusammen im modal-foot statt getrennt im Body. space-between statt
     eines festen 8px-gap gibt der destruktiven Aktion spuerbaren Abstand zur bestaetigenden -
     kein Fehlklick zwischen beiden. ms-danger bleibt transparent (kein .btn-Standardsurface),
     damit sie als ghost neben dem gefuellten "Fertig" leichter wirkt, nicht schwerer. */
  .mealsheet .modal-foot { justify-content: space-between; gap: 16px; }
  .ms-danger { background: transparent; }
  /* ---------- Handy: die Meal-Ansicht ist ein Bottom-Sheet fester Hoehe ----------
     Als normales, inhaltshohes Modal sprang die Groesse von Meal zu Meal: eines ohne
     Zutaten war ein kurzer Kasten, eines mit acht Zutaten hoeher als der Bildschirm - dann
     musste man erst zum Foto hoch- und zum "Schliessen" runterscrollen. Auf dem Handy
     steht das Sheet deshalb immer gleich hoch am unteren Rand, Foto oben und Fusszeile
     unten fest, gescrollt wird nur in der Mitte.
     Die Klasse .sheet-overlay setzt openMealSheet() nach openModal() - bewusst eine echte
     Klasse und nicht :has(.mealsheet), damit alle anderen Modals (Einstellungen, Teilen,
     Rechtstexte) garantiert unberuehrt bleiben.
     Zum Raster: .mealsheet .modal-head ist position:absolute (siehe oben) und damit KEIN
     Grid-Item - die drei Reihen treffen genau .ms-photo, .modal-body und .modal-foot, in
     beiden Zweigen von openMealSheet(). min-height:0 auf dem Body ist zwingend, sonst
     waechst die 1fr-Reihe ueber ihren Inhalt hinaus statt zu scrollen. */
  @media (max-width: 560px) {
    .overlay.sheet-overlay { padding: 0; place-items: end center; }
    .overlay.sheet-overlay .mealsheet {
      max-width: none;
      height: 92vh; height: 92dvh;   /* vh als Rueckfall fuer Browser ohne dvh */
      border-radius: 18px 18px 0 0; border-bottom: 0;
      display: grid; grid-template-rows: auto 1fr auto;
    }
    .overlay.sheet-overlay .modal-body { overflow-y: auto; min-height: 0; overscroll-behavior: contain; }
    /* Flacher als die 220px am Rechner: unter dem festen Fuss soll genug Scrollflaeche
       fuer lange Zutatenlisten bleiben, das Foto aber weiterhin traegt. */
    .overlay.sheet-overlay .ms-photo img,
    .overlay.sheet-overlay .ms-photo .ms-cover-sym { height: 28vh; min-height: 130px; max-height: 200px; }
    .overlay.sheet-overlay .modal-foot { padding-bottom: calc(14px + env(safe-area-inset-bottom)); }
  }
  /* Makro-Ueberschrift: dieselbe Abschnittsform wie "Zutaten" statt eigener Hervorhebung
     mit rotem Rahmen - die Gliederung leisten die Ueberschriften, kein Block sticht mehr
     ohne Grund heraus (Nachtrag Abnahme Phase 1). Die Schrift kommt jetzt aus .sec-h;
     hier bleibt nur, was diese Ueberschrift von den anderen unterscheidet: sie traegt
     zusaetzlich einen Hinweis und einen Knopf und muss dafuer eine Reihe sein. */
  .nut-total > .sec-h {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
    margin: 22px 0 4px;
  }
  .nut-total > .sec-h .hint { font-family: var(--font-body); text-transform: none; letter-spacing: 0; font-weight: 400; font-size: 12px; }
  .ms-nut { margin-bottom: 15px; }
  .ms-nut .nut-total { margin-bottom: 0; }
  /* Makro-Kacheln im Bearbeiten-Zweig (Nachtrag Abnahme Phase 2, 08.08.2026, loest den
     antippbaren Ruhezustand samt eigenem "Fertig"-Knopf wieder ab): dieselbe .nutfacts-
     Kachel wie im Nur-Lese-Zweig (mealStatsHtml) - Farbpunkte/Reihenfolge kommen bereits
     ueber .nutfacts .nf.t-kcal/.t-carb/.t-prot/.t-fat (siehe oben), hier nur der Umbau von
     Text zu Eingabefeld. Die 16px stehen hier BEWUSST direkt in der Regel und nicht in einer
     Breiten-Media-Query: die allgemeine iOS-Zoom-Regel greift nur bis 560px, im Querformat und
     auf Tablets waere der Zoom sonst zurueck (CLAUDE.md §26). */
  .nutfacts-edit .v { display: flex; align-items: baseline; gap: 3px; }
  /* Eingabe-Affordanz wie .ms-name: unauffaellig bis zur Beruehrung (kein Dauer-Rahmen, der
     die ruhige Kachel-Optik stoert), aber mit Hover-/Fokusrahmen erkennbar als Eingabefeld,
     nicht als reiner Text. */
  .nutfacts-edit .v input {
    all: unset; box-sizing: border-box; width: 4.5ch; flex: none;
    font-weight: 700; font-size: 16px; font-variant-numeric: tabular-nums; color: var(--text);
    border: 1px solid transparent; border-radius: var(--radius-sm); padding: 1px 3px; margin: -1px -3px;
    transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
    -moz-appearance: textfield;
  }
  /* Ohne Spinner-Pfeile unterdrueckt (gleiches Muster wie .onb-num): Chromium reserviert
     sonst intern Breite fuer sie und schneidet bei 4,5ch dreistellige Werte wie "520" ab. */
  .nutfacts-edit .v input::-webkit-outer-spin-button,
  .nutfacts-edit .v input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
  .nutfacts-edit .v input:hover { border-color: var(--border-strong); }
  .nutfacts-edit .v input::placeholder { color: var(--text-muted); font-weight: 400; }
  .nutfacts-edit .v input:focus-visible {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
  }
  /* ---------- Ladezustand fuer Meal-Bilder ----------
     Das Layout sprang nie: .rimg hat eine feste Hoehe, das Bild liegt absolut darin.
     Was fehlte, war die Ueberbrueckung - bis das Foto da ist, stand eine leere Flaeche.
     Auf einem langsamen Mobilnetz sind das beim Scrollen durchs Rezeptbuch zwei Dutzend
     graue Loecher.

     Die Animation laeuft NUR, solange das Bild fehlt: `bild-da` kommt von einem einzigen
     Listener (document 'load' mit Capture), nicht von einem Inline-Handler - die CSP
     dieser Seite und die Bauart der App verbieten beides. Ein Shimmer, der unter jedem
     laengst geladenen Bild weiterlaeuft, kostet auf einer Liste mit 30 Karten dauerhaft
     Rechenzeit und Akku, ohne dass ihn je jemand sieht. */
  .rimg { position: relative; height: 116px; display: grid; place-items: center; overflow: hidden; background: var(--surface-2); }
  .rimg:not(.bild-da)::before {
    content: ""; position: absolute; inset: 0; z-index: 0;
    background: linear-gradient(100deg,
      transparent 20%,
      color-mix(in srgb, var(--text) 7%, transparent) 42%,
      transparent 64%);
    background-size: 220% 100%;
    animation: pm-schimmer 1.25s linear infinite;
  }
  @keyframes pm-schimmer { from { background-position: 120% 0; } to { background-position: -120% 0; } }
  /* Ohne Bewegung bleibt die getoente Flaeche - sie sagt dasselbe, nur ruhig. */
  @media (prefers-reduced-motion: reduce) {
    .rimg:not(.bild-da)::before { animation: none; background: none; }
  }
  .rimg::after { content: ""; position: absolute; inset: 0; background: radial-gradient(130px 66px at 78% 16%, rgba(255,255,255,.30), transparent); pointer-events: none; }
  .rimg.has-photo::after { display: none; }
  .rphoto { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
  /* Makro-Hinweis oben links: .good (Proteinreich/Low Carb) nutzt --ok, das schon als
     "positiv"-Signal existiert (Sync verbunden). .fat nutzt dasselbe Blau wie die
     Naehrwert-Kacheln (--mc/--tc fuer Fett) - kein neuer Fett-Ton.
     Bis zu zwei Badges (Haupt-Makro + "Low Carb"), daher Flex mit Umbruch: auf
     schmalen Karten rutscht das zweite in die naechste Zeile statt aus dem Bild. */
  .rimg .mbadges { position: absolute; left: 11px; top: 10px; z-index: 1; display: flex; flex-wrap: wrap; gap: 5px; max-width: calc(100% - 22px); }
  .rimg .mbadge { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; color: var(--on-photo); background: rgba(16,18,13,.62); backdrop-filter: blur(4px); padding: 3px 9px 3px 7px; border-radius: 999px; }
  .rimg .mbadge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--mbc); flex: none; }
  .rimg .mbadge.good { --mbc: var(--ok); }
  .rimg .mbadge.fat { --mbc: var(--fat); }
  .rbody { padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; flex: 1; }
  .rcard .top { display: flex; align-items: flex-start; gap: 10px; }
  .rcard :is(h2, h3) { font-family: var(--font-display); font-size: 18px; font-weight: 600; margin: 0; flex: 1; line-height: 1.25; word-break: break-word; }
  /* Stretched-Link-Muster (plans/MealAnsicht.MD Schritt 3): der Titel oeffnet die ganze
     Karte, ohne dass ein <button> um die ganze Karte liegen muss ("kein Button im Button").
     all:unset gibt Schrift/Farbe an das umgebende <h3> zurueck (beides vererbte
     Eigenschaften) - nur display faellt auf inline zurueck, was hier passt. .rcard ist
     bereits position:relative, das ::after braucht keinen eigenen Anker. */
  .rcard-open { all: unset; cursor: pointer; }
  .rcard-open::after { content: ""; position: absolute; inset: 0; z-index: 1; border-radius: var(--radius); }
  .rcard-open:focus-visible { outline: none; }
  .rcard-open:focus-visible::after { outline: 2px solid var(--accent); outline-offset: 2px; }
  .chip { font-size: 11px; font-weight: 650; letter-spacing: .03em; text-transform: uppercase; padding: 3px 9px; border-radius: 999px; white-space: nowrap; }
  .chip.cat-fr { color: var(--fr); background: color-mix(in srgb, var(--fr) 14%, var(--surface)); }
  .chip.cat-ha { color: var(--mi); background: color-mix(in srgb, var(--mi) 14%, var(--surface)); }
  .chip.cat-x { color: var(--accent-strong); background: color-mix(in srgb, var(--accent) 14%, var(--surface)); }
  .rcard .meta { display: flex; gap: 14px; flex-wrap: wrap; color: var(--text-muted); font-size: 12.5px; }
  .rcard .meta span { display: inline-flex; align-items: center; gap: 5px; }
  .rcard .meta svg { width: 14px; height: 14px; color: var(--accent); opacity: 1; }
  /* Statistik-Zeile der Karte (cardStatsHtml) - margin-top:auto haelt sie unten, damit
     unterschiedlich hohe Karten trotzdem auf gleicher Grundlinie enden. .meta (Person-Icon
     aus recipeAuthorHtml, siehe oben) steht rechts darunter statt in derselben Zeile wie
     die Kartenstatistik. */
  .rcard .rstats { display: flex; flex-direction: column; gap: 6px; margin-top: auto; padding-top: 4px; }
  .rcard .rstats .meta { justify-content: flex-end; }
  .rcard .rstats .leer { padding: 0; }
  /* Kartenstatistik (cardStatsHtml, Nachtrag Abnahme Phase 1) - eine ruhige Zeile statt
     .nutfacts-Kacheln: kcal traegt das Gewicht, die drei Makros folgen als schmale farbige
     Kuerzel. Keine eigene 560px-Regel noetig - flex-wrap faengt sehr schmale Karten auf. */
  .cstats { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 4px 10px; font-variant-numeric: tabular-nums; }
  .cstats .cs-kcal { font-weight: 700; font-size: 15px; color: var(--text); flex: none; }
  .cstats .cs-kcal .u { font-weight: 500; font-size: 11px; color: var(--text-muted); }
  /* Zusammenhalten statt einzeln umbrechen (Nachtrag Abnahme Phase 1) - bei sehr schmalen
     Karten soll die Makrogruppe als Ganzes nach unten rutschen, nicht KH/P/F einzeln. */
  .cstats .cs-macros { font-size: 12.5px; color: var(--text-muted); white-space: nowrap; }

  /* ---------- Empty state ---------- */
  .empty { text-align: center; padding: 60px 24px; border: 1.5px dashed var(--border-strong); border-radius: var(--radius); color: var(--text-muted); background: var(--surface); }
  .empty .em-ico { width: 52px; height: 52px; margin: 0 auto 14px; color: var(--accent); opacity: .85; }
  .empty :is(h2, h3) { font-family: var(--font-display); color: var(--text); font-size: 20px; margin: 0 0 6px; }
  .empty p { margin: 0 auto 18px; max-width: 42ch; }
  /* Der Knopf im leeren Zustand ist oft der einzige Weg nach vorne - er muss sicher zu
     treffen sein. `.btn` kommt hier auf 38 px, unter dem Mindestmass von Apple HIG und
     WCAG 2.5.5. Gefunden am 20.09.2026 von abnahme-mobil.py, an "Zum Wochenplan" in der
     Einkaufsliste; es betrifft ebenso "Erstes Meal anlegen" im Rezeptbuch, das bis dahin
     nur nie gemessen wurde (die Abnahme lief immer mit vorhandenen Meals). */
  .empty .btn { min-height: 44px; }

  /* ---------- Modal ---------- */
  /* Seitliche Notch-Aussparung auch hier (D6): Das Overlay liegt fixiert ueber der ganzen
     Flaeche, also greift das Padding von .wrap nicht. Betrifft alle Modals — und besonders
     den Barcode-Scanner, der ausdruecklich fuers QUERFORMAT gebaut ist, wo die Aussparung
     erst wirklich breit wird. */
  .overlay { position: fixed; inset: 0; z-index: 60; background: color-mix(in srgb, var(--photo-scrim) 55%, transparent); display: grid; place-items: start center; padding: 5vh calc(16px + env(safe-area-inset-right, 0px)) 16px calc(16px + env(safe-area-inset-left, 0px)); overflow-y: auto; color: var(--text); font-family: var(--font-body); }
  .modal {
    background: var(--surface); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 24px 70px rgba(0,0,0,.35);
    width: 100%; max-width: 540px; overflow: hidden;
    animation: pop var(--dur-fast) var(--ease-out); /* Fallback ohne @starting-style-Support */
    opacity: 1; transform: none;
    transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
  }
  @starting-style { .modal { opacity: 0; transform: translateY(8px) scale(.99); } }
  /* Ausblenden beim Schliessen. Gegenstueck zu `pop`, nur kuerzer: Ein Menue, das man
     bewusst schliesst, soll aus dem Weg sein - nicht noch einmal vorgefuehrt werden.
     `.menu-zu` traegt der Knoten ab dem Moment, in dem close() laeuft; er ist dann fuer
     jede Suche unsichtbar und faengt keine Klicks mehr (index.html, attachMenuDismiss). */
  .menu.menu-zu { pointer-events: none; animation: menu-weg var(--dur-fast) var(--ease-out) forwards; }
  @keyframes menu-weg { to { opacity: 0; transform: translateY(-4px) scale(.985); } }
  @media (prefers-reduced-motion: reduce) {
    .menu.menu-zu { animation: none; opacity: 0; }
  }

  @keyframes pop { from { opacity: 0; transform: translateY(8px) scale(.99);} to { opacity: 1; transform: none; } }
  /* Meal-Ansicht mit eigener WAAPI-Bewegung geoeffnet (openMealSheet, plans/MealAnsicht.MD
     Schritt 4): die eigene pop-Animation/-Transition wuerde sonst dagegen kaempfen - gegen
     flipIn() am Rechner ebenso wie gegen das Hochfahren des Bottom-Sheets am Handy (der
     Klassenname stammt aus der FLIP-Zeit und gilt fuer beides). Bewusst ohne !important -
     unter prefers-reduced-motion greift
     weiterhin der globale, wichtigere Block weiter unten (reiner Fade statt Transform),
     dorthin kommt diese Klasse aber ohnehin nie: unter reducedMotion() wird sie gar nicht
     erst gesetzt, siehe openMealSheet(). */
  .modal.flip-anim { animation: none; transition: none; }
  /* Live-Barcode-Scanner. Eigene Ebene ueber dem Modal-Overlay (60), aber unter dem
     Toast (90) - der Scanner wird AUS dem Meal-Formular geoeffnet, das darunter offen
     bleiben muss. Deshalb bewusst nicht ueber openModal(), das modalRoot leert.
     Farben (Paket D6, gepruefte Entscheidung, kein Uebersehen): bewusst FEST dunkel in
     beiden Themes, nicht auf --bg/--text-Tokens gemappt. Ein Kamera-Sucher ist wie ein
     Fotoapp-Viewfinder immer dunkel, unabhaengig vom Erscheinungsbild der App - genau wie
     bei nativen Kamera-UIs verbessert das feste Dunkel den Kontrast zum Kamerabild UND
     zum hellen Sucherrahmen (.scanframe, --accent). Im Hell-Theme waeren --bg/--text hier
     falsch (heller Sucher blendet, Rahmen ginge im hellen Hintergrund unter). */
  .scanwrap { position: fixed; inset: 0; z-index: 80; background: var(--photo-scrim); display: grid; place-items: center; padding: 16px; color: var(--text); font-family: var(--font-body); }
  .scanbox { width: 100%; max-width: 480px; display: flex; flex-direction: column; gap: 14px; }
  .scanhead { display: flex; align-items: center; gap: 12px; color: #F4EFEE; }
  .scanhead :is(h2, h3) { font-family: var(--font-display); font-size: 20px; margin: 0; flex: 1; letter-spacing: .3px; }
  .scanhead .btn { flex: none; }
  /* Buehne = Format des Kamerabilds. --scan-ar ist Breite/Hoehe als reine Zahl und wird von
     scanBarcodeLive() aus track.getSettings() gesetzt, sobald der Stream laeuft; der Startwert
     4:3 reserviert vorher den Platz (kein Springen).

     Vorher stand hier fest 4/3 und unter 560px fest 3/4, WAEHREND die Kamera weiter quer
     lieferte. Mit object-fit: cover schnitt das hochkant links und rechts so viel weg, dass
     ein Barcode nicht mehr in den Rahmen passte - man konnte nur quer scannen. Jetzt stimmen
     Buehne und Bild ueberein, cover ist damit deckungsgleich mit contain und schneidet nichts.

     Die width-Rechnung ist die Hoehenbremse: aspect-ratio zusammen mit einem blossen
     max-height wuerde bei width: 100% kippen (die Breite ist dann definit und gewinnt) und
     wieder beschneiden. Ueber die Breite gerechnet bleibt das Verhaeltnis erhalten und die
     Buehne trotzdem nicht hoeher als der Platz, der nach Kopfzeile, Hinweis und Foto-Ausweg
     uebrig ist.

     --scan-h ist genau dieser Platz und wird von syncStage() gemessen, nicht geschaetzt. Der
     Startwert 62svh gilt nur, bis die Messung steht. Vorher stand hier fest 62svh, und das war
     an beiden Enden falsch: im Hochformat blieb die Buehne 64px schmaler als moeglich (die
     Grenze griff, obwohl Platz war), im Querformat ragte der Kasten 1px aus dem Bildschirm und
     der Foto-Knopf war abgeschnitten - der Wert kannte die Hoehe der Bedienelemente nicht. */
  .scanvid { --scan-ar: 1.3333; --scan-h: 62svh; position: relative; width: 100%; width: min(100%, calc(var(--scan-h) * var(--scan-ar))); margin-inline: auto; aspect-ratio: var(--scan-ar); background: #000; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--border-strong); }
  .scanvid video { width: 100%; height: 100%; object-fit: cover; display: block; }
  /* Sucherrahmen: Ecken statt Vollrahmen, damit der Barcode sichtbar bleibt */
  .scanframe { position: absolute; left: 8%; right: 8%; top: 27%; bottom: 27%; pointer-events: none; }
  .scanframe::before, .scanframe::after { content: ""; position: absolute; width: 34px; height: 34px; border: 3px solid var(--accent); }
  .scanframe::before { left: 0; top: 0; border-right: 0; border-bottom: 0; border-radius: 6px 0 0 0; }
  .scanframe::after { right: 0; bottom: 0; border-left: 0; border-top: 0; border-radius: 0 0 6px 0; }
  .scanhint { margin: 0; text-align: center; font-size: 14px; color: #A69B9D; min-height: 21px; }
  .scanhint.err { color: var(--accent); font-weight: 600; }
  .scanfoot { display: flex; flex-direction: column; gap: 8px; }
  .scanfoot .btn { width: 100%; justify-content: center; }
  /* Gedrehtes Handy: Bild NEBEN die Bedienelemente statt darunter.
     Eine Hochformat-Sperre gibt es im Web auf dem iPhone nicht (Safari setzt weder
     "orientation" aus dem Manifest um noch screen.orientation.lock), und den ganzen Sucher
     gegenzudrehen wuerde das Kamerabild kippen - iOS dreht den Stream naemlich schon selbst
     mit. Also wird das Querformat nicht bekaempft, sondern benutzt: uebereinander bleiben im
     Querformat nur ~240px Bildhoehe uebrig, nebeneinander sind es ~358 - mehr als die doppelte
     Bildflaeche fuer denselben Barcode. Der Aufbau (Kopf, Bild, Hinweis, Fuss) bleibt dabei
     derselbe, nur die Anordnung wechselt. */
  @media (orientation: landscape) and (max-height: 560px) {
    .scanbox {
      max-width: none; height: 100%; display: grid; align-items: center;
      grid-template-columns: auto minmax(150px, 240px); grid-template-rows: auto 1fr auto;
      gap: 10px 16px;
    }
    .scanvid { grid-column: 1; grid-row: 1 / -1; width: auto; height: 100%; max-width: 100%; }
    .scanhead { grid-column: 2; grid-row: 1; }
    .scanhint { grid-column: 2; grid-row: 2; align-self: center; }
    .scanfoot { grid-column: 2; grid-row: 3; }
  }
  .modal-head { display: flex; align-items: center; gap: 12px; padding: 16px 18px; border-bottom: 1px solid var(--border); }
  .modal-head :is(h2, h3) { font-family: var(--font-display); font-size: 19px; margin: 0; flex: 1; }
  .modal-head .kicker { font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); }
  /* hitSlop wie .toast-undo::after: Trefferflaeche auf 44px (Apple HIG), Aussehen
     unveraendert. Gemessen am 16.09.2026 mit tools/abnahme-mobil.py. */
  .modal-head .btn.ghost { position: relative; }
  .modal-head .btn.ghost::after { content: ""; position: absolute; inset: -8px -6px; }
  .modal-body { padding: 18px; }
  .modal-foot { display: flex; gap: 8px; justify-content: flex-end; padding: 14px 18px; border-top: 1px solid var(--border); background: var(--surface-2); flex-wrap: wrap; }

  .field { margin-bottom: 15px; }
  .field:last-child { margin-bottom: 0; }
  .field label { display: block; font-size: 12.5px; font-weight: 600; margin-bottom: 6px; color: var(--text); letter-spacing: .01em; }
  .field .hint { font-weight: 400; color: var(--text-muted); }
  input[type="text"], input[type="number"], input[type="email"], input[type="password"], input[type="search"], select, textarea {
    font: inherit; font-size: 14px; width: 100%; color: var(--text);
    background: var(--bg); border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
    padding: 9px 11px; transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
  }
  textarea { resize: vertical; min-height: 74px; line-height: 1.5; }
  input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
  .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
  .imgfield { display: flex; gap: 14px; align-items: center; }
  .imgprev { width: 156px; min-height: 96px; flex: none; border-radius: 10px; border: 1.5px dashed var(--border-strong); background-color: var(--surface-2); background-size: cover; background-position: center; overflow: hidden; display: grid; place-items: center; text-align: center; color: var(--text-muted); cursor: pointer; transition: border-color var(--dur-fast), background-color var(--dur-fast); }
  .imgprev.has-img { border-style: solid; border-color: var(--border); }
  .imgprev:hover { border-color: var(--accent); }
  .imgprev.dragover { border-color: var(--accent); background-color: color-mix(in srgb, var(--accent) 13%, var(--surface-2)); }
  .imgprev:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .imgprev-hint { display: flex; flex-direction: column; align-items: center; gap: 5px; font-size: 11px; line-height: 1.3; padding: 8px; pointer-events: none; }
  .imgprev-hint svg { opacity: .8; }
  .imgprev-hint b { color: var(--text); font-weight: 600; }
  .imgbtns { display: flex; flex-direction: column; gap: 7px; align-items: flex-start; }
  .imgbtns label.btn { cursor: pointer; }

  /* Symbolflaeche statt Foto (.msym)
     ---------------------------------------------------------------
     Schnelleintraege und gescannte Produkte zeigen ein Symbol, kein geratenes
     Stichwortbild (siehe mealIcon() in index.html). Die Flaeche uebernimmt die MASSE des
     Fotos, das sie ersetzt - Breite, Hoehe und Radius stehen deshalb weiter bei .r-thumb,
     .pthumb, .bc-thumb, .hm-sym und .ms-cover-sym, hier steht nur das Aussehen.

     Der Rahmen liegt als inset-Schatten an und nicht als border: Ohne globales
     box-sizing:border-box wuerde ein Rahmen die geerbten Masse um 2 px aufblasen und die
     Symbolzeile gegen die Fotozeile daneben verschieben. */
  /* overflow als Sicherheitsnetz: Ein Foto wird von object-fit beschnitten, ein SVG nicht -
     ohne das raegte ein Symbol aus einer geschrumpften Flaeche heraus. */
  /* box-sizing: Der Startreiter-Deckel setzt der Flaeche height:100%, und sie traegt dort
     ein padding-bottom in Hoehe des Textschleiers. Ohne border-box kaeme das Padding oben
     drauf, statt den Platz UEBER dem Text abzugrenzen - die Flaeche waere hoeher als der
     Deckel und das Symbol saesse wieder hinter dem Text. */
  .msym { display: grid; place-items: center; overflow: hidden; box-sizing: border-box;
    background: var(--surface-2); color: var(--text-muted);
    box-shadow: inset 0 0 0 1px var(--border); }
  .msym svg { width: 62%; height: 62%; }
  /* Grosse Flaechen (Home-Deckel, Meal-Blatt). Die Obergrenze ist fest, der Prozentwert
     faengt den flachen Fall ab: Die Startreiter-Karte schrumpft ueber flex, und die Flaeche
     war auf einem 390x556-Geraet nur 119 px hoch - ein starres 132-px-Symbol haette dort
     nicht hineingepasst. `min()` nimmt, was kleiner ist, und `width: auto` haelt das
     quadratische Seitenverhaeltnis.
     Die Strichstaerke geht mit der Groesse bewusst zurueck - 1,5 auf dem 24er-Raster waere
     bei 132 px ein 8-px-Balken und damit fetter als jedes andere Icon der App. */
  .msym-big svg { width: auto; height: min(88px, 62%); stroke-width: 1.1; }

  /* Picker list */
  .picker-search { display: flex; gap: 8px; margin-bottom: 12px; }
  .picker-search input { flex: 1; min-width: 0; }
  .plist { display: flex; flex-direction: column; gap: 4px; max-height: 46vh; overflow-y: auto; margin: 0 -4px; padding: 0 4px; }
  .pitem { display: flex; align-items: center; gap: 11px; text-align: left; width: 100%; font: inherit; cursor: pointer;
    background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm); padding: 9px 11px; color: var(--text); }
  .pitem:hover { background: var(--surface-2); border-color: var(--border); }
  .pitem .pdot { width: 9px; height: 9px; border-radius: 3px; flex: none; }
  /* Gilt fuer beides: das Foto (img) und die Symbolflaeche (span.msym) an derselben Stelle -
     deshalb ohne img-Praefix. object-fit wirkt nur auf das Bild und ist am span wirkungslos. */
  .pitem .pthumb { width: 38px; height: 28px; border-radius: 5px; object-fit: cover; flex: none; }
  .pitem .pn { font-size: 14px; font-weight: 550; flex: 1; }
  .pitem .pc { font-size: 11.5px; color: var(--text-muted); }
  .pitem .pfav { display: inline-flex; flex: none; color: var(--accent); }
  .pitem .pfav svg { width: 13px; height: 13px; fill: currentColor; }
  .pitem.suggest { border-color: color-mix(in srgb, var(--accent) 40%, transparent); background: color-mix(in srgb, var(--accent) 6%, transparent); }
  .pitem.offcat .pn, .pitem.offcat .pc { color: var(--text-muted); }
  .pitem.offcat .pthumb { opacity: .55; }
  /* Press-State wie bei .shop-item und .ing-view: auf Touch gibt es kein Hover, die
     Rueckmeldung traegt der Antipp-Zustand. Bewusst ein Farbanflug und kein scale() -
     das ist die Sprache, die die Listenzeilen der App schon sprechen. */
  .pitem:active { background: color-mix(in srgb, var(--accent) 9%, transparent); }
  .pmore {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    margin-top: 8px; padding-top: 9px; border-top: 1px dashed var(--border);
    font-size: 12px; color: var(--text-muted);
  }
  /* Die kleine Form fuer "hier ist nichts" - eine Zeile im laufenden Inhalt.
     Loest .pempty, .wch-empty, .wl-empty und .ms-empty-ings ab (20.09.2026). Uebernommen
     wurde das Verhalten von .pempty: flex mit gap, damit ein Knopf daneben Platz hat,
     ohne dass jede Fundstelle das neu erfindet. Die Abstaende kommen vom Ort, nicht von
     hier - deshalb `margin: 0` und die Feinjustierung je Umgebung weiter unten. */
  .leer { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 0; padding: 8px 4px; font-size: 13px; color: var(--text-muted); }
  /* hitSlop wie .toast-undo::after: Trefferflaeche auf 44px (Apple HIG), Aussehen
     unveraendert. Gemessen am 16.09.2026 mit tools/abnahme-mobil.py. */
  .pmore .btn { position: relative; }
  .pmore .btn::after { content: ""; position: absolute; inset: -8px 0; }
  /* Schnelleintrag (zaehlbare Lebensmittel). Die Ueberschrift traegt .sec-h, hier steht
     nur der Abstand - Ort-abhaengig ist der Abstand, nie die Schrift (siehe .sec-h). */
  .pq-h { margin: 15px 4px 5px; }
  /* Aufklapper-Variante derselben Kopfzeile (Schnell-Bereich). Nur Knopf-Reset und Caret -
     Groesse, Gewicht und Farbe kommen weiter aus .sec-h, sonst entstuende eine zweite
     Abschnittsform (CLAUDE.md, Ziffer 8). Der Abstand wandert dabei in ein padding, damit
     die Trefferflaeche wie bei .cathead die ganze Zeile ist und trotzdem NICHT ueber den
     Rand des Scroll-Containers .plist ragt. */
  button.pq-h {
    display: flex; align-items: center; gap: 6px; margin: 9px 4px 0; padding: 6px 0 5px;
    background: none; border: none; font-family: inherit; text-align: left; cursor: pointer;
  }
  /* Wie .cathead.static: waehrend der Suche ist die Kopfzeile nur Beschriftung. Der Einzug
     haelt "Schnell" auf derselben Kante wie mit Caret (13 px Caret + 6 px gap), sonst
     springt das Wort beim zweiten getippten Zeichen sichtbar nach links. */
  .pq-h.static { padding-left: 19px; }
  /* hitSlop wie .toast-undo::after: Trefferflaeche auf 44px (Apple HIG), Aussehen
     unveraendert. Gemessen am 16.09.2026 mit tools/abnahme-mobil.py. */
  button.pq-h { position: relative; }
  button.pq-h::after { content: ""; position: absolute; inset: -9px 0; }
  /* Suchzeile des Pickers: Ein <input> kann kein Pseudoelement tragen - der
     hitSlop-Weg steht hier nicht offen. Deshalb echte Hoehe, und zwar fuer BEIDE
     Elemente der Zeile: 44px nur am Feld liesse den Barcode-Knopf daneben 3px
     niedriger stehen, was man sieht. Die 16px Schriftgroesse bleiben (iOS-Zoom). */
  .picker-search input, .picker-search .btn { min-height: 44px; }
  button.pq-h .caret { width: 13px; height: 13px; flex: none; transition: transform var(--dur-fast); }
  button.pq-h .caret.open { transform: rotate(90deg); }
  @media (prefers-reduced-motion: reduce) { button.pq-h .caret { transition: none; } }
  /* Die 38px halten die Namen der Schnellzeilen auf derselben Kante wie die Meal-Zeilen
     mit Foto (.pthumb) - ohne Platzhalter saehe die Liste zweispaltig aus. */
  .pitem .pqi { flex: none; width: 38px; height: 28px; display: grid; place-items: center; color: var(--text-muted); }
  .pitem .pqi svg { width: 19px; height: 19px; }
  .pitem .pqb { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
  .pitem .pqb .pn { flex: none; }
  .pitem .pqs { font-size: 11.5px; color: var(--text-muted); }
  .pitem .pqn { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; font-variant-numeric: tabular-nums; }
  .pitem .pqk { font-size: 13px; font-weight: 600; }
  .pitem .pqm { font-size: 11px; color: var(--text-muted); }

  /* Detail / shopping. .dbanner/dl gehoerten zum fruehreren openRecipeDetail (siehe
     plans/MealAnsicht.MD) und sind mit .ms-photo/.nutfacts in openMealSheet() entfallen;
     .dsec/ul/.steps leben in dessen Nur-Lese-Zweig weiter (Makro-Ueberschrift, Zubereitung). */
  .detail .dsec { margin-top: 14px; }
  .detail .dsec .sec-h { margin: 0 0 6px; }   /* Schrift kommt aus .sec-h */
  .detail ul { margin: 0; padding-left: 18px; line-height: 1.6; }
  .detail .steps { white-space: pre-wrap; line-height: 1.6; font-size: 14px; }
  .shop-head { display: flex; align-items: center; gap: 13px; }
  .shop-head .cart { flex: none; width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center; color: var(--accent-contrast); background: linear-gradient(150deg, var(--accent-solid), var(--accent-solid-strong)); box-shadow: var(--shadow); }
  /* Personenanzahl: Flaeche, Rahmen und Rundung bewusst identisch zum Listenfenster
     darunter, damit die beiden als Paar lesen - oben die Einstellung, unten das Ergebnis.
     Gleiches Vorgehen wie bei .slots/.day-bal im Wochenplan. Vorher war sie im Stil einer
     einzelnen .shop-item-Kachel gehalten; seit die Zeilen darunter randlose Listenzeilen
     sind, gibt es diese Kachelform nicht mehr, an die sie sich haette anlehnen koennen.
     44px-Knoepfe sind Pflicht (Touch) und geben der Zeile ihre Hoehe. */
  .shop-persons { display: flex; align-items: center; justify-content: space-between; gap: 10px; background: color-mix(in srgb, var(--surface-2) 92%, var(--bg)); border: 1px solid var(--border); border-radius: var(--radius); padding: 4px 4px 4px 14px; margin-bottom: 13px; }
  .shop-persons .lbl { font-size: 14.5px; }
  .shop-persons .step { display: flex; align-items: center; flex: none; }
  .shop-persons .pnum { min-width: 96px; text-align: center; font-size: 14.5px; font-weight: 650; font-variant-numeric: tabular-nums; }
  .shop-persons .pbtn {
    width: 44px; height: 44px; flex: none; display: grid; place-items: center; padding: 0;
    border: 1px solid var(--border-strong); background: var(--surface); color: var(--text);
    border-radius: 10px; cursor: pointer; touch-action: manipulation;
    transition: transform var(--dur-fast) var(--ease-out), border-color var(--dur-fast), background var(--dur-fast), opacity var(--dur-fast);
  }
  .shop-persons .pbtn svg { width: 17px; height: 17px; }
  .shop-persons .pbtn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-strong); }
  /* Rueckmeldung im Moment des Drucks, nicht erst beim Loslassen */
  .shop-persons .pbtn:active:not(:disabled) { transform: scale(.94); }
  /* Am Anschlag ausgegraut statt versteckt: ein verschwindender Knopf laesst den zweiten
     springen und nimmt dem Nutzer die Orientierung, wo die Grenze liegt. */
  .shop-persons .pbtn:disabled { opacity: .35; cursor: default; }
  @media (prefers-reduced-motion: reduce) { .shop-persons .pbtn { transition: none; } .shop-persons .pbtn:active:not(:disabled) { transform: none; } }
  .shop-progress { display: flex; align-items: center; gap: 12px; margin-bottom: 15px; }
  .shop-progress .bar { flex: 1; height: 7px; border-radius: 99px; background: var(--surface-2); overflow: hidden; }
  .shop-progress .bar > i { display: block; height: 100%; width: 0; border-radius: 99px; background: linear-gradient(90deg, var(--accent), var(--accent-strong)); transition: width var(--dur-slow) ease; }
  .shop-progress .ptext { font-size: 12.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
  /* Aussehen: .btn.link. Hier bleibt nur, was der Ort verlangt. */
  .shop-progress .toggle-all { white-space: nowrap; }
  /* ---------- Listen-Optik statt Kacheln ----------
     Dieselbe Form wie der Zutaten-Ruhezustand der Meal-Ansicht (.ing-view) und die
     Mahlzeiten im Wochenplan auf dem Handy: ein Fenster, darin Abschnitte mit
     Ueberschrift, darin randlose Zeilen mit duennen Trennlinien.

     Hier gilt das auf BEIDEN Geraeten, anders als im Wochenplan. Dort standen am Rechner
     sieben Tage nebeneinander und brauchten ihre Abgrenzung - diese Liste steht in einem
     Modal und ist auf jedem Bildschirm einspaltig untereinander. Es gab also nie einen
     Grund fuer die Kachelform, sie war nur nie angefasst worden.

     EIN Fenster um die ganze Liste, nicht eins pro Kategorie: das waere die Kachel-Optik
     zurueck, nur groesser (gleiche Ueberlegung wie bei .slots im Wochenplan).
     max-height/overflow bleiben - der Rahmen steht dann fest und der Inhalt scrollt darin. */
  .shop-list {
    display: flex; flex-direction: column; gap: 0; max-height: 46vh; overflow-y: auto;
    padding: 4px 12px 8px;
    background: color-mix(in srgb, var(--surface-2) 92%, var(--bg));
    border: 1px solid var(--border); border-radius: var(--radius);
  }
  /* Zeile statt Kachel: die Trennlinie ist der border-bottom der Zeile selbst, deshalb
     kein Radius und seitlich kein eigener Abstand - der sitzt an .shop-list, damit die
     Linien so weit laufen wie die Liste. 23px-Box + 2x11px ergeben 45px Trefferflaeche. */
  .shop-item { display: flex; align-items: center; gap: 13px; padding: 11px 0; border-bottom: 1px solid var(--border); background: none; cursor: pointer; transition: background var(--dur-fast); }
  /* Hover NUR bei echter Maus - auf Touch bliebe der Zustand nach dem Antippen kleben.
     Wortgleich zu den Slots im Wochenplan (680px-Block), die dieselbe Zeilenform haben.
     .ing-view loest dasselbe Problem mit nur (hover: hover), ohne (pointer: fine) - das
     ist die aeltere Stelle und bleibt hier unangetastet; die Mehrheit im Projekt und die
     direkten Nachbarn dieser Liste fragen das Zeigegeraet mit ab. */
  @media (hover: hover) and (pointer: fine) {
    .shop-item:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }
  }
  .shop-item:active { background: color-mix(in srgb, var(--accent) 9%, transparent); }
  /* Abgehakt traegt sich ohne Flaechentoenung: durchgestrichenes graues Label, die in
     --accent gefuellte Box und die gedaempfte Mengen-Pille sagen es dreifach. Eine Toenung
     ueber die volle Zeilenbreite las sich ohne Rahmen als Auswahlbalken statt als erledigt. */
  .shop-check { position: absolute; opacity: 0; width: 1px; height: 1px; }
  .shop-item .box { flex: none; width: 23px; height: 23px; border-radius: 7px; border: 2px solid var(--border-strong); display: grid; place-items: center; color: #fff; transition: background var(--dur-fast), border-color var(--dur-fast); }
  .shop-item .box svg { width: 13px; height: 13px; opacity: 0; transform: scale(.5); transition: opacity var(--dur-fast), transform var(--dur-fast); }
  .shop-check:checked + .box { background: var(--accent); border-color: var(--accent); }
  .shop-check:checked + .box svg { opacity: 1; transform: none; }
  .shop-check:focus-visible + .box { outline: 2px solid var(--accent); outline-offset: 2px; }
  .shop-item .lbl { flex: 1; font-size: 14.5px; line-height: 1.35; word-break: break-word; }
  .shop-check:checked ~ .lbl { text-decoration: line-through; color: var(--text-muted); }
  .shop-item .qty { flex: none; font-size: 11.5px; font-weight: 650; color: var(--accent-strong); background: color-mix(in srgb, var(--accent) 14%, var(--surface)); border-radius: 999px; padding: 3px 9px; font-variant-numeric: tabular-nums; }
  .shop-check:checked ~ .qty { opacity: .5; }
  /* Der leere Zustand im Dialog: dieselbe Form wie `.empty`, nur kompakter und ohne
     gestrichelten Rahmen - im Modal ist der Kasten schon der Rahmen. Loest `.shop-empty`
     ab (20.09.2026); die Werte sind von dort uebernommen, nicht neu erfunden. */
  .empty.im-dialog { padding: 20px 10px 4px; border: 0; }
  .empty.im-dialog .em-ico { width: 46px; height: 46px; margin-bottom: 12px; }
  .empty.im-dialog h2 { font-size: 17px; }
  .empty.im-dialog p { max-width: 34ch; margin-bottom: 14px; }
  .shop-group { display: flex; flex-direction: column; gap: 0; }
  /* 18px Luft ueber jedem Abschnitt, wie im Wochenplan: die Ueberschrift ist nur wenig
     groesser als die Zeilen darunter und dazu grau - mit weniger Raum eroeffnet sie keinen
     neuen Abschnitt, sie schwimmt mit. Der erste bleibt eng, er steht an der Fensterkante. */
  .shop-group:not(:first-child) { margin-top: 18px; }
  /* Die letzte Zeile braucht keine Trennlinie - sie laege direkt auf dem Rahmen. */
  .shop-group:last-child .shop-item:last-child { border-bottom: 0; }
  /* Schrift und Farbe bewusst NICHT auf .sec-h angeglichen (siehe Kommentar dort);
     ortsabhaengig ist nur der Abstand. Der Einzug von 2px seitlich faellt weg: er hielt
     die Ueberschrift frueher auf einer Linie mit dem Text IN den Kacheln, die ihren
     eigenen Innenabstand hatten. Die Zeilen darunter sind jetzt randlos und beginnen am
     Fensterrand - mit 2px staende die Ueberschrift als einzige eingerueckt. Dafuer 3px
     statt 1px darunter, weil keine Kachelkante mehr den Abstand zur ersten Zeile macht. */
  .shop-cat { display: flex; align-items: center; gap: 8px; margin: 0 0 3px; font-family: var(--font-display); font-size: 12.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); }
  .shop-cat-ic { font-size: 15px; line-height: 1; }
  /* Ohne Rahmen und mit --surface: die Pille sass vorher auf der helleren Modalflaeche,
     auf der Fensterflaeche waeren --surface-2 plus --border fast dasselbe Grau in Grau. */
  .shop-cat-n { margin-left: auto; font-family: var(--font-body); font-size: 11px; font-weight: 650; letter-spacing: 0; color: var(--text-muted); background: var(--surface); border-radius: 999px; padding: 1px 8px; }

  /* ---------- Vorkochen (C3) ----------
     Bewusst dieselbe Zeilenbauweise wie die Slot-Karte im Wochenplan (Bild links, Text
     mittig, Zahl rechts) statt eines dritten Listenmusters - es sind dieselben Meals, nur
     nach Gericht gebuendelt. Die Portionszahl steht rechts und traegt die Display-Schrift:
     sie ist die Zahl, nach der man am Sonntag den Topf waehlt. */
  .bc-list { display: flex; flex-direction: column; gap: 8px; }
  .bc-item { display: flex; align-items: center; gap: 12px; padding: 10px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); }
  .bc-thumb { width: 48px; height: 48px; flex: none; border-radius: var(--radius-sm); object-fit: cover; }
  .bc-body { flex: 1; min-width: 0; }
  .bc-name { font-size: 14.5px; font-weight: 600; line-height: 1.3; word-break: break-word; }
  .bc-days { font-size: 11.5px; color: var(--text-muted); margin-top: 2px; letter-spacing: .02em; }
  /* Bewusst NICHT in --accent: Rot ist in dieser App der Akzent fuer Aktionen und den
     aktiven Zustand. Drei rote Zeilen untereinander konkurrieren mit der Portionszahl
     rechts, die hier die eigentliche Hauptzahl ist (im Rendering gegengeprueft). */
  .bc-runs { font-size: 11.5px; color: var(--text); margin-top: 2px; font-weight: 600; }
  .bc-qty { flex: none; text-align: right; }
  .bc-qty b { display: block; font-family: var(--font-display); font-size: 20px; font-weight: 700; line-height: 1; color: var(--text); font-variant-numeric: tabular-nums; }
  .bc-qty small { font-size: 10.5px; color: var(--text-muted); }
  @media (max-width: 560px) {
    .bc-thumb { width: 40px; height: 40px; }
    .bc-item { gap: 10px; }
  }

  /* Toast */
  #toast { position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%) translateY(20px); z-index: 90;
    background: var(--text); color: var(--bg); padding: 10px 18px; border-radius: 999px; font-size: 14px; font-weight: 550;
    box-shadow: var(--shadow); opacity: 0; pointer-events: none; transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out); }
  #toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
  /* Undo-Variante (Paket B1): waehrend sie steht, darf der Knopf getroffen werden - der
     normale Text-Toast bleibt bewusst pointer-events:none, damit er nie etwas verdeckt. */
  #toast.has-undo.show { pointer-events: auto; }
  .toast-undo {
    position: relative;
    margin-left: 12px; background: transparent; border: 1px solid currentColor; color: inherit;
    font: inherit; font-weight: 700; font-size: 13px; padding: 4px 12px; border-radius: 999px;
    cursor: pointer; transition: background var(--dur-fast) var(--ease-out);
  }
  /* Zeitkritisch (5 s Fenster) am Daumen - die sichtbare Pille bleibt kompakt, die Trefferflaeche
     erreicht trotzdem 44px, gleiches hitSlop-Muster wie .btn.icon-gh::after. */
  .toast-undo::after { content: ""; position: absolute; inset: -10px; }
  .toast-undo:hover { background: color-mix(in srgb, currentColor 15%, transparent); }

  .visually-hidden { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0 0 0 0); border:0; }
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

  /* ---------- Profil (Login) & Teilen ---------- */
  .app.authing .tabs, .app.authing #profile-slot { display: none; }
  .profile { position: relative; display: flex; align-items: center; }
  .profile-btn { display: flex; align-items: center; gap: 9px; font: inherit; cursor: pointer; background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 4px 12px 4px 4px; color: var(--text); transition: border-color var(--dur-fast); }
  .profile-btn:hover { border-color: var(--accent); }
  /* hitSlop wie .toast-undo::after: Trefferflaeche auf 44px (Apple HIG), Aussehen
     unveraendert. Gemessen am 16.09.2026 mit tools/abnahme-mobil.py. */
  .profile-btn { position: relative; }
  .profile-btn::after { content: ""; position: absolute; inset: -4px 0; }
  .avatar { width: 30px; height: 30px; border-radius: 50%; flex: none; display: grid; place-items: center; font-size: 12.5px; font-weight: 700; color: var(--accent-contrast); background: linear-gradient(150deg, var(--accent-solid), var(--accent-solid-strong)); overflow: hidden; }
  .avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }
  .profile-btn .pname { font-size: 13.5px; font-weight: 550; max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* Mitglieder der Gruppe, links neben dem eigenen Avatar. Ueberlappend statt in einer
     Reihe: das signalisiert "gehoert zusammen" und spart im Kopf den knappen Platz.
     Der Rand in --bg trennt die Kreise optisch voneinander (nicht --surface: der Stack
     sitzt auf dem Seitenhintergrund, nicht auf einer Karte). */
  .mstack { display: flex; align-items: center; flex: none; padding-right: 10px; }
  .mstack > * { margin-right: -9px; border: 2px solid var(--bg); box-sizing: content-box; }
  /* Ohne eigenes Foto neutral statt im Akzent-Verlauf: drei rote Kreise nebeneinander
     verschwimmen zu einem Klumpen, in dem niemand mehr zu erkennen ist. Rot bleibt dem
     eigenen Avatar - so liest der Stack sofort als "die anderen" plus "ich". */
  .mstack .avatar { width: 26px; height: 26px; font-size: 11px; background: var(--surface-2); color: var(--text-muted); }
  .mstack .avatar:has(img) { background: none; }
  /* "+3", wenn mehr Mitglieder da sind als Platz. Neutral gehalten - der Akzent gehoert
     dem eigenen Avatar, nicht der Zaehlung. */
  .mstack .more { width: 26px; height: 26px; border-radius: 50%; flex: none; display: grid; place-items: center; font-size: 10.5px; font-weight: 700; color: var(--text-muted); background: var(--surface-2); }
  /* Ab hier ist der Name schon ausgeblendet (siehe .pname unten) - dann muss auch der
     Stack schrumpfen, sonst draengt er die Reiter aus dem Kopf. */
  @media (max-width: 560px) {
    .mstack .avatar, .mstack .more { width: 23px; height: 23px; font-size: 10px; }
    .mstack > * { margin-right: -9px; }
  }
  .profile-btn .caret { color: var(--text-muted); }
  .menu { position: absolute; right: 0; top: calc(100% + 8px); z-index: 40; min-width: 216px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-pop); padding: 6px;
           /* Waechst aus dem Knopf, nicht aus der eigenen Mitte: Das Menue haengt oben
              rechts unter seinem Ausloeser, also liegt dort auch sein Ursprung. */
           transform-origin: top right; animation: pop var(--dur-fast) var(--ease-out); }
  /* Nach oben gekippt, wenn unten kein Platz war (index.html, openMenu). Der Ursprung
     wandert mit - sonst waechst das Menue aus einer Ecke, an der es nicht haengt. */
  .menu.menu-oben { top: auto; bottom: calc(100% + 8px); transform-origin: bottom right; }
  .menu .mhead { padding: 8px 11px 11px; border-bottom: 1px solid var(--border); margin-bottom: 6px; }
  .menu .mhead .mn { font-weight: 650; font-size: 14px; }
  /* Pro-Marke (D1). Gold ist in diesem Projekt fuer Premium reserviert und traegt im
     Light-Theme nicht als Flaeche - deshalb Gold nur als Schrift und Rand auf ruhigem
     Grund, nicht als gefuellte Pille. Bis es etwas zu kaufen gibt, sieht diese Marke
     ohnehin nur, wer serverseitig eine Berechtigung eingetragen bekommen hat. */
  .pro-badge {
    display: inline-block; vertical-align: 1px; margin-left: 6px;
    font-size: 10.5px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
    padding: 1px 7px; border-radius: 999px;
    color: var(--gold-ink); background: color-mix(in srgb, var(--gold) 22%, var(--surface));
    border: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
  }
  .menu .mhead .me { font-size: 12px; color: var(--text-muted); word-break: break-all; }
  /* Gegenstueck zu .mhead am Menuefuss: traegt den Copyright-Hinweis, der frueher in der
     Fusszeile stand. Bewusst kein Knopf - es gibt nichts anzutippen. */
  .menu .mfoot { padding: 9px 11px 4px; margin-top: 4px; border-top: 1px solid var(--border); font-size: 11.5px; color: var(--text-muted); }
  /* min-height 44px: die Eintraege kamen mit ihrem Innenmass auf 39px, unter der
     Mindestgroesse von Apple HIG und WCAG 2.5.5. Faellt besonders ins Gewicht, seit das
     Menue auf dem Handy der Weg zu Impressum und Datenschutz ist. */
  .menu button { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; font: inherit; font-size: 13.5px; cursor: pointer; background: transparent; border: 0; border-radius: var(--radius-sm); padding: 9px 11px; min-height: 44px; color: var(--text); }
  .menu button:hover { background: var(--surface-2); }
  .menu button svg { width: 16px; height: 16px; color: var(--text-muted); flex: none; }
  /* Abmelden ist nicht destruktiv – es geht nichts verloren, und der Klick fuehrt
     ohnehin in einen Bestaetigungsdialog. Daher neutral wie die uebrigen Eintraege;
     abgesetzt wird es durch die Trennlinie darueber, die – anders als eine
     Hover-Faerbung – auch auf dem Touchscreen sichtbar ist. Rot bleibt echten
     Loeschaktionen vorbehalten. */
  .menu button.danger:hover { background: color-mix(in srgb, var(--accent) 10%, var(--surface)); color: var(--accent); }
  .menu button.danger:hover svg { color: var(--accent); }
  /* Konto/Daten loeschen IST destruktiv und unwiderruflich – anders als Abmelden
     durchgehend rot, nicht erst im Hover, plus eigene Trennlinie zur Abgrenzung
     von den ungefaehrlichen Eintraegen (destructive-emphasis). */
  .menu button.destructive, .menu button.destructive svg { color: var(--accent); }
  .menu button.destructive:hover { background: color-mix(in srgb, var(--accent) 12%, var(--surface)); }
  .menu hr { border: 0; border-top: 1px solid var(--border); margin: 6px 4px; }
  .sync-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; background: var(--border-strong); flex: none; }
  .sync-dot.ok { background: var(--ok); }
  .sync-dot.busy { background: var(--fr); animation: syncpulse 1s ease-in-out infinite; }
  .sync-dot.err { background: var(--danger); }
  .sync-dot.off { background: var(--border-strong); }
  .av-wrap { position: relative; display: inline-flex; }
  .sync-dot.chip { position: absolute; right: -1px; bottom: -1px; width: 11px; height: 11px; border: 2px solid var(--surface); box-sizing: border-box; }
  .menu .msync { display: flex; align-items: center; gap: 7px; margin-top: 9px; font-size: 12px; color: var(--text-muted); }
  @keyframes syncpulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }

  /* Profilbild-Vorschau im "Profil bearbeiten"-Formular - gleiche Gradient/Initialen-
     Optik wie der Header-Avatar, nur groesser. Rund-Button zum Aendern in derselben
     Gradient-Optik wie .wch-add, hier relativ zum Vorschau-Kreis positioniert. */
  .avatar-edit-row { display: flex; align-items: center; gap: 16px; margin-bottom: 18px; }
  .avatar-edit { position: relative; width: 76px; height: 76px; border-radius: 50%; flex: none; display: grid; place-items: center; font-size: 24px; font-weight: 700; color: var(--accent-contrast); background: linear-gradient(150deg, var(--accent-solid), var(--accent-solid-strong)); }
  .avatar-edit img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }
  .avatar-edit-btn {
    position: absolute; bottom: -2px; right: -2px; width: 28px; height: 28px; border: 2px solid var(--surface);
    border-radius: 50%; cursor: pointer; display: grid; place-items: center; padding: 0;
    color: var(--accent-contrast); background: linear-gradient(135deg, var(--accent-solid), var(--accent-solid-strong));
    box-shadow: var(--shadow-float); transition: transform var(--dur-fast) ease;
  }
  /* Sichtbar bleiben 28px - auf 44x44 erweitert der gemeinsame Mechanismus in
     css/basis.css (TROUBLESHOOTING #38). */
  .avatar-edit-btn:hover { transform: scale(1.12); }
  .avatar-edit-btn svg { width: 14px; height: 14px; }

  /* ---------- Profilbild-Zuschnitt (Kreis-Crop) ---------- */
  .crop-stage { position: relative; width: 260px; height: 260px; max-width: 100%; aspect-ratio: 1; margin: 0 auto; overflow: hidden; border-radius: var(--radius-sm); background: var(--surface-2); cursor: grab; touch-action: none; }
  .crop-stage.dragging { cursor: grabbing; }
  .crop-stage img { position: absolute; top: 0; left: 0; transform-origin: 0 0; user-select: none; -webkit-user-drag: none; pointer-events: none; }
  /* Kreis-Fenster per riesigem box-shadow statt SVG-Maske - dieselbe Abdunklung wie
     .overlay, damit ausserhalb des Kreises sichtbar "verworfen" wirkt. */
  .crop-guide { position: absolute; inset: 0; margin: auto; width: 100%; height: 100%; border-radius: 50%; pointer-events: none; box-shadow: 0 0 0 9999px color-mix(in srgb, var(--photo-scrim) 55%, transparent); border: 2px solid rgba(255,255,255,.7); box-sizing: border-box; }
  .crop-zoom { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 16px; }

  .auth { min-height: calc(100vh - 170px); display: grid; place-items: center; padding: 16px 0 60px; }

  .row3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
  /* Segmented control (Geschlecht/Ziel) im Stil der Wochen-Umschalter */
  .seg { display: flex; background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px; padding: 3px; gap: 2px; }
  .seg button { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; border: 0; background: transparent; color: var(--text-muted); padding: 9px 8px; border-radius: 999px; white-space: nowrap; transition: color var(--dur-fast), background var(--dur-fast), box-shadow var(--dur-fast); }
  .seg button:hover { color: var(--text); }
  .seg button.on { color: var(--accent-contrast); background: linear-gradient(135deg, var(--accent-solid), var(--accent-solid-strong)); box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 32%, transparent); }
  .seg .seg-hint { font-weight: 500; font-size: 10.5px; opacity: .82; }
  /* Aktivität als vertikale Knopf-Liste (Label links, Hinweis rechts) */
  /* Trainingstage: sieben Umschalter, Mehrfachauswahl. Bewusst NICHT .seg, obwohl es
     so aussieht — .seg ist eine Einfachauswahl (genau ein .on). Gleiche Optik, aber
     eigene Klasse, damit niemand die Seg-Logik darauf loslaesst. */
  .tdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 4px; }
  .tdays button {
    font: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer; border: 0;
    background: transparent; color: var(--text-muted); padding: 9px 2px; min-height: 44px;
    border-radius: var(--radius-sm); transition: color var(--dur-fast), background var(--dur-fast), box-shadow var(--dur-fast);
  }
  .tdays button:hover { color: var(--text); }
  /* Im RECHNER ist die Auswahl rot wie jede andere Auswahl im Formular. Das Blau ist der
     Statusfarbe im Wochenplan vorbehalten, wo es einen Tag als Trainingstag markiert —
     hier waere es nur ein zweiter Akzent ohne eigene Bedeutung. */
  .tdays button[aria-pressed="true"] {
    color: var(--accent-contrast); background: linear-gradient(135deg, var(--accent-solid), var(--accent-solid-strong));
    box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 32%, transparent);
  }
  .tdays-note { font-size: 11.5px; color: var(--text-muted); margin: 8px 0 15px; line-height: 1.45; }
  .tdays-note b { color: var(--text); font-weight: 600; }
  /* Eine Zeile je Trainingstag: Kuerzel, Stufe, Dauer, Aufschlag.
     Abfragebasis ist die Breite des FELDES, nicht die des Bildschirms — wie bei den
     Makro-Beschriftungen der Tageskarten. Eine Bildschirm-Query war hier nachweislich
     falsch: im zweispaltigen Rechner-Layout ist die Formularspalte auch auf einem
     breiten Monitor schmal, die Zeile lief dann um 67px ueber ihren Container hinaus. */
  #c-tdetail { container-type: inline-size; }
  .trow {
    /* Frueher standen hier pro Zeile zusaetzlich ein Minutenfeld, der Kalorien-Aufschlag
       und ein „auf alle"-Knopf — bei sieben Tagen ein Feld aus Zahlen und roten Flaechen.
       Die Dauer gilt jetzt einmal fuer alle (.tmin darueber), der Aufschlag steht im
       Ergebnis, wo er hingehoert. Bleibt: Tag, Stufe, Beispiel. */
    display: grid; grid-template-columns: 30px 1fr;
    grid-template-areas: "d lv" "def def";
    align-items: center; gap: 8px; row-gap: 6px;
    padding: 8px 0; border-top: 1px solid var(--border);
  }
  .trow-d { grid-area: d; } .trow-lv { grid-area: lv; }

  /* Eine Dauer fuer alle Einheiten. Steht ueber den Tageszeilen, weil sie fuer alle
     gilt — als Feld IN einer Tageszeile haette sie faelschlich nach „nur dieser Tag"
     ausgesehen. */
  .tmin {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 10px 12px; margin-bottom: 4px;
    background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  }
  .tmin label { font-size: 12.5px; font-weight: 600; color: var(--text); }
  .tmin-f { display: flex; align-items: center; gap: 6px; }
  .tmin-f span { font-size: 11.5px; color: var(--text-muted); }
  .tmin-f input {
    width: 66px; text-align: center; padding: 8px 4px; font-size: 16px; /* 16px gegen iOS-Zoom */
    font-variant-numeric: tabular-nums; color: var(--text);
    background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  }
  /* Umschalter fuer eine gemeinsame Intensitaetszeile statt einer je Trainingstag -
     bei vielen gewaehlten Tagen sonst zu viele Segmented-Controls auf einer Karte. */
  .tsame {
    display: flex; align-items: center; gap: 9px; min-height: 36px;
    padding: 2px 2px 10px; font-size: 12.5px; font-weight: 600; color: var(--text);
    cursor: pointer;
  }
  .tsame input { width: 18px; height: 18px; accent-color: var(--accent); flex: none; cursor: pointer; }
  .tmin-f input:focus-visible { outline: 2px solid var(--train); outline-offset: 1px; }
  /* Beispiele zur GEWAEHLTEN Stufe. Stehen sichtbar in der Zeile und nicht nur im
     title-Attribut: auf dem Handy gibt es keine Tooltips, dort waeren sie unsichtbar —
     und genau dort wird die App benutzt. */
  .trow-def {
    grid-area: def; font-size: 11px; line-height: 1.45; color: var(--text-muted);
    padding-left: 38px; display: flex; align-items: flex-start; justify-content: space-between; gap: 10px;
  }
  .trow-def > span { flex: 1; }
  @container (max-width: 379px) { .trow-def { padding-left: 0; } }
  .trow:first-child { border-top: 0; }
  /* Das Tageskuerzel war rot. Es ist aber nur die Beschriftung der Zeile, keine
     Auswahl und keine Warnung — Rot hat hier nichts zu sagen und nahm dem Akzent
     seine Bedeutung. Jetzt normale Textfarbe. */
  .trow-d { font-family: var(--font-display); font-size: 13px; font-weight: 700; color: var(--text); }
  /* Die gewaehlte Stufe ist BLAU, nicht rot: Training ist im Wochenplan bereits blau
     (--train, siehe .day.training), und im Rechner stand dafuer bisher Rot — dieselbe
     Sache in zwei Farben. Zugleich war Rot pro Zeile mehrfach vertreten und trug damit
     keine Aussage mehr. Rot bleibt jetzt den Tages-Chips darueber, wo tatsaechlich
     ausgewaehlt wird. Erkennbar bleibt die Stufe an Flaeche UND Text, nie an Farbe
     allein — das Beispiel darunter nennt sie zusaetzlich in Worten. */
  .trow-lv { padding: 2px; gap: 1px; }
  .trow-lv button.on {
    color: var(--train-contrast);
    background: linear-gradient(135deg, var(--train), var(--train-strong));
    box-shadow: 0 2px 8px color-mix(in srgb, var(--train) 32%, transparent);
  }
  /* min-height, damit die Stufen mit dem Daumen zu treffen sind: ohne sie waren die
     Buttons 26px hoch, waehrend die Wochentag-Umschalter direkt darueber 44px haben.
     Bei sieben Trainingstagen stehen 21 dieser Buttons gleichzeitig auf dem Schirm. */
  .trow-lv button { padding: 8px 4px; font-size: 11.5px; min-height: 40px; }
  /* Vorschau je Trainingstag im Ergebnis */
  .tprev { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 5px 0; }
  .tprev + .tprev { border-top: 1px dashed var(--border); }
  .tprev-d { font-size: 12px; font-weight: 700; color: var(--text); }
  .tprev-d span { font-weight: 400; color: var(--text-muted); margin-left: 6px; font-size: 11px; }
  .tprev-v { font-size: 12px; color: var(--text-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
  .tprev-v b { color: var(--text); font-size: 13px; }
  .tprev-v span { margin-left: 8px; }
  /* Ruhetag-Werte im Ergebnis: zweite Makro-Zeile, klar als Ableitung erkennbar. */
  .rest-macros { border-top: 1px dashed var(--border); margin-top: 12px; padding-top: 12px; }
  .rest-macros .rm-h { display: flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); margin-bottom: 8px; }
  .rest-macros .rm-h svg { width: 13px; height: 13px; flex: none; color: var(--accent-strong); }

  .seg-col { flex-direction: column; gap: 3px; border-radius: var(--radius-sm); padding: 4px; }
  .seg-col button { flex-direction: row; justify-content: space-between; align-items: center; border-radius: var(--radius-sm); padding: 11px 14px; }
  .macros { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
  .calc-note { max-width: var(--maxw); font-size: 12px; line-height: 1.55; color: var(--text-muted); margin: 20px 0 0; padding-top: 6px; border-top: 1px solid var(--border); }
  .calc-note > summary {
    display: flex; align-items: center; gap: 9px; cursor: pointer;
    min-height: 44px; /* Tippflaeche */
    list-style: none; font-size: 12px;
  }
  .calc-note > summary::-webkit-details-marker { display: none; }
  .calc-note > summary:hover { color: var(--text); }
  .calc-note .ci { width: 15px; height: 15px; flex: none; color: var(--accent); }
  .calc-note .caret { width: 14px; height: 14px; flex: none; margin-left: auto; transition: transform var(--dur-fast) ease; }
  .calc-note[open] .caret { transform: rotate(90deg); }
  @media (prefers-reduced-motion: reduce) { .calc-note .caret { transition: none; } }
  .calc-note .cn-body { padding: 0 0 12px 24px; }
  .calc-note strong { color: var(--text); font-weight: 600; }
  .breakdown { margin-top: 18px; text-align: left; }
  .bd-row { display: flex; justify-content: space-between; align-items: center; font-size: 13.5px; padding: 8px 2px; border-top: 1px solid var(--border); color: var(--text-muted); }
  .bd-row span:last-child { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
  .bd-row.bd-mid { color: var(--text); font-weight: 600; }
  .bd-row.bd-mid span:last-child { font-weight: 700; }
  .auth-card { width: 100%; max-width: 430px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 34px 30px 26px; text-align: center; }
  .auth-card .mark { width: 116px; height: 116px; border-radius: 50%; margin: 0 auto 20px; overflow: hidden; background: center / cover no-repeat var(--logo); }
  .auth-card h2 { font-family: var(--font-display); font-size: 25px; font-weight: 600; margin: 0 0 6px; text-wrap: balance; }
  .auth-card .lead { color: var(--text-muted); font-size: 14.5px; margin: 0 0 22px; line-height: 1.5; text-wrap: balance; }
  /* hitSlop wie .toast-undo::after: "Mit Konto anmelden" ist ein .btn und damit 41px
     hoch. Auf die Karte begrenzt - Begruendung bei .section-head .btn.primary. */
  .auth-card .btn { position: relative; }
  .auth-card .btn::after { content: ""; position: absolute; inset: -3px 0; }
  .auth-card .field { text-align: left; }
  .auth-card .btn.primary { width: 100%; justify-content: center; padding: 12px; font-size: 15px; margin-top: 4px; }
  .auth-switch { margin: 14px 0 0; font-size: 12.5px; color: var(--text-muted); }
  .auth-note { font-size: 12px; color: var(--text-muted); margin: 16px 0 0; line-height: 1.5; display: flex; gap: 8px; align-items: flex-start; text-align: left; border-top: 1px solid var(--border); padding-top: 14px; }
  .auth-note svg { width: 15px; height: 15px; flex: none; margin-top: 1px; color: var(--accent); }
  .auth-card .field + .field { margin-top: 10px; }
  .auth-err { color: var(--danger); font-size: 12.5px; margin: 12px 0 0; text-align: left; line-height: 1.45; min-height: 0; }
  /* Nur noch Ort und Ausrichtung - das Aussehen kommt von .btn.link (css/basis.css). */
  .auth-forgot { display: block; text-align: right; margin: 8px 0 0 auto; }
  .auth-or { display: flex; align-items: center; gap: 12px; margin: 18px 0 16px; color: var(--text-muted); font-size: 12px; }
  .auth-or::before, .auth-or::after { content: ""; flex: 1; height: 1px; background: var(--border); }
  .btn.social { width: 100%; justify-content: center; gap: 10px; padding: 11px; font-size: 14.5px; font-weight: 550; background: var(--surface); border: 1px solid var(--border-strong); color: var(--text); }
  .btn.social:hover { border-color: var(--accent); background: var(--surface); }
  .btn.social + .btn.social { margin-top: 9px; }
  .btn.social svg, .btn.social img { width: 18px; height: 18px; flex: none; }
  .btn.social.apple { background: #000; border-color: #000; color: #fff; }
  .btn.social.apple:hover { background: #111; border-color: #111; }
  .verify-icon { width: 62px; height: 62px; border-radius: 50%; margin: 0 auto 18px; display: grid; place-items: center; background: var(--surface-2); color: var(--accent-strong); }
  .verify-icon svg { width: 30px; height: 30px; }
  .auth-mail { font-weight: 650; color: var(--text); word-break: break-all; }
  .auth-busy { opacity: .6; pointer-events: none; }

  .segbar { display: flex; gap: 4px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 4px; margin-bottom: 16px; }
  .segbar button { flex: 1; font: inherit; font-size: 13.5px; font-weight: 550; cursor: pointer; border: 0; background: transparent; color: var(--text-muted); padding: 8px; border-radius: 7px; }
  .segbar button[aria-selected="true"] { background: var(--surface); color: var(--accent-strong); box-shadow: var(--shadow); }
  .share-hint { font-size: 13px; color: var(--text-muted); line-height: 1.55; margin: 0 0 12px; }
  .share-hint em { color: var(--text); font-style: normal; font-weight: 550; }
  .share-code { width: 100%; font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 11.5px; line-height: 1.4; min-height: 92px; resize: vertical; word-break: break-all; }
  .share-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
  .share-sub { display: block; font-size: 12px; font-weight: 650; letter-spacing: .01em; margin: 2px 0 6px; }
  .link-row { display: flex; gap: 8px; }
  .share-link { flex: 1; font-size: 12.5px; }
  .share-mini { font-size: 12px; color: var(--text-muted); margin: 9px 0 0; line-height: 1.5; }
  .share-mini.warn { color: var(--danger); }
  .share-more { margin-top: 16px; }
  .share-more summary { cursor: pointer; font-size: 13px; font-weight: 550; color: var(--accent-strong); }
  .share-more summary:hover { text-decoration: underline; }
  .share-more .share-code { margin-top: 10px; }

  @media (max-width: 560px) { .profile-btn .pname { display: none; } }

  /* ---------- PDF / Druck ---------- */
  #print-area { display: none; }
  #print-area .pt-doc { font-family: var(--font-body); color: #1a1a1a; }
  /* Feste Hex-Werte statt --accent: Diese Ansicht ist fuers Papier gedacht (siehe #1a1a1a/
     #666 daneben) und darf nicht das grelle Dark-Theme-Neonrot uebernehmen, falls gerade
     das dunkle Theme aktiv ist. */
  #print-area .pt-head { border-bottom: 3px solid #DC2626; padding-bottom: 12px; margin-bottom: 20px; display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
  #print-area h1 { font-family: var(--font-display); font-size: 30px; font-weight: 600; margin: 0; color: #1a1a1a; }
  #print-area .pt-meta { color: #666; font-size: 13px; text-align: right; }
  #print-area .pt-table { width: 100%; border-collapse: collapse; }
  #print-area .pt-table th, #print-area .pt-table td { border: 1px solid #cfcfc7; padding: 11px 13px; text-align: left; font-size: 13px; vertical-align: top; }
  /* Kopfzeile war frueher gruen (#eef3ec/#2A5A3B) - ein Rest aus der Zeit vor der
     Rot-Umstellung der App. Jetzt Rot passend zur Marke, gemessen 6,09:1 (vorher 7,11:1),
     beides ueber der 4,5:1-Schwelle fuer Normaltext. */
  #print-area .pt-table thead th { background: #FBE9E9; color: #B01218; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
  #print-area .pt-table tbody th { background: #f6f6f1; font-weight: 600; width: 96px; font-size: 14px; }
  #print-area .pt-empty { color: #bcbcbc; }
  #print-area .pt-foot { margin-top: 18px; color: #999; font-size: 11px; text-align: right; }

  @media print {
    html, body { background: #fff !important; }
    .app, #modal-root, #toast { display: none !important; }
    #print-area { display: block !important; padding: 8px; }
    @page { margin: 16mm; }
  }

  /* ---------- Hinweis: ein title=, das auch auf Touch ankommt ----------
     Natives `title=` erscheint nur beim Verweilen mit der Maus. Auf einem Telefon gibt es
     kein Verweilen - die Erklaerung war dort schlicht nicht erreichbar. Betroffen sind die
     Stellen, an denen der sichtbare Text die Zahl traegt und das title= die HERKUNFT
     erklaert ("x3" in der Einkaufsliste, das Trainings-Abzeichen).

     Bewusst EIN geteiltes Element statt eines Popovers je Ausloeser: Es gibt nie zwei
     offene Hinweise gleichzeitig, und 30 Listenzeilen erzeugen so nicht 30 Knoten. */
  .hinweis-traeger { cursor: help; text-decoration: underline dotted currentColor; text-underline-offset: 3px; border-radius: 4px; }
  /* Die App setzt Fokusringe je Selektor, es gibt keine Grundregel - ein Traeger ist aber
     per tabindex erreichbar und braucht deshalb seinen eigenen (WCAG 2.4.7). */
  .hinweis-traeger:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  /* Auf Touch gibt es kein Hovern: Die gepunktete Linie allein sagt dort nicht, dass man
     tippen kann. Beim Tippen antwortet der Traeger deshalb sichtbar. */
  .hinweis-traeger:active,
  .hinweis-traeger[aria-describedby] { background: color-mix(in srgb, var(--accent) 12%, transparent); }
  .hinweis-pop {
    position: fixed; z-index: 95; max-width: min(260px, calc(100vw - 24px));
    background: var(--surface); color: var(--text);
    border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
    box-shadow: var(--shadow); padding: 8px 11px;
    font-size: 13px; line-height: 1.4; pointer-events: none;
    opacity: 0; transition: opacity var(--dur-fast) var(--ease-out);
  }
  .hinweis-pop.auf { opacity: 1; }

  /* ---------- Spotlight: EIN Hinweis, genau einmal ----------
     Nach dem Onboarding steht ein neues Konto vor einem leeren Plan. Die vier
     "+ Meal waehlen" sagen, was man tun KANN - sie sagen nicht, dass die Woche
     sich auch von selbst planen laesst. Genau das ist der Punkt, an dem die App
     am meisten Zeit spart, und er steckt hinter einem Symbol.

     Bewusst KEINE mehrstufige Tour: Der Rest des Bildschirms erklaert sich seit
     dem 20.09.2026 selbst (leerZustand mit naechster Aktion). Eine Tour haette
     erklaert, was ohnehin dasteht - gegen CLAUDE.md 5 und 6.

     Das Loch im Dunkel entsteht ueber einen sehr grossen Schattenring statt ueber
     eine Maske: So bleibt das hervorgehobene Element ein ganz normales Element,
     bleibt klickbar und behaelt seinen Fokusring. Vorbild ist das gaengige
     Verfahren aus Shepherd.js / Intro.js; die Dialogrolle folgt dem
     WAI-ARIA-Muster "Dialog (Modal)". */
  .spot-dim { position: fixed; inset: 0; z-index: 90; background: transparent; }
  .spot-ring {
    position: fixed; z-index: 91; border-radius: 999px; pointer-events: none;
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, .72), 0 0 0 3px var(--accent);
    transition: opacity var(--dur-base) var(--ease-out);
  }
  .spot-card {
    position: fixed; z-index: 92; max-width: min(320px, calc(100vw - 32px));
    background: var(--surface); color: var(--text);
    border: 1px solid var(--border-strong); border-radius: var(--radius);
    box-shadow: var(--shadow); padding: 16px;
    animation: spot-in var(--dur-base) var(--ease-out) both;
  }
  .spot-card h3 { margin: 0 0 4px; font-family: var(--font-display); font-size: 17px; }
  .spot-card p { margin: 0 0 12px; color: var(--text-muted); font-size: 14px; line-height: 1.45; }
  .spot-actions { display: flex; gap: 8px; }
  .spot-actions .btn { flex: 1; justify-content: center; }
  @keyframes spot-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

  /* Weniger statt null: Bewegung (Transform/Keyframes) aus, aber Opacity-Ueberblendungen
     bleiben, weil sie Zustandswechsel (Modal/Toast/Onboarding) verstehbar halten. */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; }
    .modal, #toast, .onb-stage, .onb-opt, .onb-bf-o, .spot-card { transition: opacity var(--dur-fast) ease !important; transform: none !important; animation: none !important; }
    * { transition-property: opacity, color, background-color, border-color, box-shadow !important; }
  }
