/* Selbst gehostete Schriften (statt Google Fonts CDN) - vermeidet die
   Uebertragung der Besucher-IP an Google-Server beim Laden der Seite.
   Nur latin/latin-ext-Subsets, da die App ausschliesslich deutsche Texte zeigt. */
@font-face {
  font-family: 'Fraunces'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('/static/fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('/static/fonts/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Fraunces'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('/static/fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('/static/fonts/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Fraunces'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('/static/fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('/static/fonts/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('/static/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('/static/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('/static/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('/static/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('/static/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('/static/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('/static/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('/static/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* Struktur-Tokens (theme-unabhängig) */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px;  --space-4: 16px;
  --space-5: 24px; --space-6: 32px; --space-7: 48px;
  --radius-sm: 6px; --radius-md: 10px; --radius-lg: 16px; --radius-pill: 999px;
  --text-xs: .75rem; --text-sm: .85rem; --text-base: 1rem;
  --text-lg: 1.15rem; --text-xl: 1.4rem; --text-2xl: 2rem;
  --sidebar-width: 264px; --topbar-height: 60px; --content-max: 1100px; --bottom-tab-height: 75px;
  /* Emoji-Schriftarten am Ende explizit auflisten - ohne sie finden manche
     Browser/Systeme (v.a. Linux-Desktops ohne Noto Color Emoji, manche
     WebViews) fuer die App-Icons (📖 📁 🔗 ...) keine passende Schrift und
     zeigen leere Kaestchen statt echter Emoji. */
  --font-sans: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif,
    "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  --font-serif: 'Fraunces', Georgia, "Times New Roman", serif;

  /* Farb-Tokens: Hell (Standard) */
  --bg: #faf9f6;
  /* Dunkleres Beige statt Salbei-Grün - fuehrt die Beige-Reihe von --bg/
     --surface-2/--border (siehe unten) einen Schritt weiter, statt einen
     eigenstaendigen Farbton einzufuehren. */
  --chrome-bg: #d8cebe;
  --surface: #ffffff;
  --surface-2: #efe9df;
  --surface-hover: #f4f0e8;
  /* Hintergrund der Profil-Pille in der Topbar - im Dunkelmodus reicht der
     Ton-Unterschied von --surface-2 zu --chrome-bg nicht (nur ein Helligkeits-
     schritt), deshalb dort auf --surface-hover angehoben (siehe Dark-Bloecke
     unten). Im Hellmodus ist --surface-2 (deutlicher Farbtonwechsel) genug. */
  --topbar-pill-bg: var(--surface-2);
  --topbar-pill-hover-bg: var(--surface-hover);
  --text: #201b15;
  --text-muted: #6e6255;
  --border: #e3dccf;
  --link: #b5562e;
  --link-hover: #8f421f;
  --accent-sage: #4f6a45;
  --accent-sage-bg: rgba(79,106,69,0.12);
  --btn-bg: #8f421f;
  --btn-bg-hover: #742f13;
  --btn-text: #fffaf5;
  --tag-bg: rgba(181,86,46,0.10);
  --tag-text: #8f421f;
  --danger-bg: #fbe4df;
  --danger-text: #a33324;
  --danger-border: #f3c4ba;
  --shadow-sm: 0 1px 2px rgba(32,22,14,.06);
  --shadow-md: 0 8px 24px rgba(32,22,14,.10);
  --shadow-lg: 0 24px 56px rgba(32,22,14,.18);
  --overlay: rgba(32,22,14,.45);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #1b1815;
    --chrome-bg: #26190e;
    --surface: #26190e;
    --surface-2: #2c1e12;
    --surface-hover: #312211;
    --text: #f2e8dc;
    --text-muted: #b7a996;
    --border: #3c332c;
    --link: #d98f5f;
    --link-hover: #eaa876;
    --accent-sage: #93b280;
    --accent-sage-bg: rgba(147,178,128,0.16);
    --btn-bg: #cd7f4f;
    --btn-bg-hover: #dd9366;
    --btn-text: #1b1815;
    --tag-bg: rgba(217,143,95,0.16);
    --tag-text: #e3a878;
    --danger-bg: rgba(198,90,66,0.18);
    --danger-text: #e6947c;
    --danger-border: rgba(198,90,66,0.4);
    --shadow-sm: 0 1px 2px rgba(0,0,0,.35);
    --shadow-md: 0 8px 24px rgba(0,0,0,.45);
    --shadow-lg: 0 24px 56px rgba(0,0,0,.6);
    --overlay: rgba(0,0,0,.55);
    --topbar-pill-bg: var(--surface-hover);
    --topbar-pill-hover-bg: var(--border);
  }
}

:root[data-theme="dark"] {
  --bg: #1b1815;
  --chrome-bg: #26190e;
  --surface: #26190e;
  --surface-2: #2c1e12;
  --surface-hover: #312211;
  --text: #f2e8dc;
  --text-muted: #b7a996;
  --border: #3c332c;
  --link: #d98f5f;
  --link-hover: #eaa876;
  --accent-sage: #93b280;
  --accent-sage-bg: rgba(147,178,128,0.16);
  --btn-bg: #cd7f4f;
  --btn-bg-hover: #dd9366;
  --btn-text: #1b1815;
  --tag-bg: rgba(217,143,95,0.16);
  --tag-text: #e3a878;
  --danger-bg: rgba(198,90,66,0.18);
  --danger-text: #e6947c;
  --danger-border: rgba(198,90,66,0.4);
  --shadow-sm: 0 1px 2px rgba(0,0,0,.35);
  --shadow-md: 0 8px 24px rgba(0,0,0,.45);
  --shadow-lg: 0 24px 56px rgba(0,0,0,.6);
  --overlay: rgba(0,0,0,.55);
  --topbar-pill-bg: var(--surface-hover);
  --topbar-pill-hover-bg: var(--border);
}

* { box-sizing: border-box; }

/* Sicherheitsnetz gegen horizontales Scrollen durch einzelne zu breite Elemente
   (z.B. eine lange Quell-URL ohne Umbruchstellen) - verhindert, dass so etwas
   die ganze Seite seitlich aufreisst. Elemente mit eigenem overflow-x:auto
   (z.B. Tabellen) sind davon unberuehrt, da sie einen eigenen Scroll-Kontext haben. */
html { overflow-x: hidden; }

body {
  margin: 0;
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  transition: background 0.2s ease, color 0.2s ease;
  overflow-x: hidden;
}

/* Globale Link-Basis: bisher gab es KEINE - jeder Textlink musste einzeln
   ueber seine Elternklasse gefaerbt werden (.hint a, .legal-content a,
   .login-card a, .panel-link, ...), was leicht uebersehen wird (genau so kam
   z.B. der .hint-a-Bug zustande: ein Link blieb im Standard-Browser-Blau
   haengen, weil er in keiner der bestehenden Regeln vorkam). Diese Basis-
   Regel faengt ab jetzt JEDEN neuen <a> automatisch im Theme-Link-Farbton
   auf - alle bestehenden spezifischeren Klassen-Regeln (.tab-btn.active,
   .add-btn, .btn-secondary, Buttons-als-Link etc.) bleiben durch hoehere
   Selektor-Spezifitaet unveraendert wirksam und ueberschreiben das wie
   gehabt. Neue Links brauchen dadurch keine eigene Farb-Deklaration mehr,
   nur noch abweichendes Verhalten (z.B. keine Unterstreichung im Footer). */
/* KEIN globales a:hover { text-decoration: underline } - eine :hover-
   Pseudoklasse auf dem blossen Element "a" waere genauso specific wie eine
   einzelne Klasse (z.B. .btn-secondary), wuerde also faelschlich gewinnen
   und Button-gestalteten Links (die text-decoration nur in ihrer :hover-
   Regel gar nicht erst erwaehnen, weil sie sich auf diese Basis-Regel
   verlassen) eine ungewollte Unterstreichung beim Hover verpassen. Hover-
   Verhalten bleibt bewusst Sache der jeweiligen spezifischeren Klasse
   (.panel-link:hover, .site-footer a:hover, ...). */
a { color: var(--link); text-decoration: none; }

h1, h2, .brand, .recipe-card-body h3 { font-family: var(--font-serif); }
/* Browser-Standardabstand fuer h1 (rem-basiert, oft >30px) plus das
   Innenpolster von .container (--space-5) addierten sich bisher zu einer
   auffaellig grossen Luecke zwischen Titelleiste und Seiten-Ueberschrift
   (Nutzer-Feedback, z.B. auf der Bearbeiten-Seite - betraf aber jede Seite
   mit einem einfachen <h1>, nicht nur diese eine). margin-top:0 ueberlaesst
   den Abstand nach oben komplett dem Container-Padding, margin-bottom in
   der Groessenordnung, die andere h1-Regeln im Projekt schon verwenden
   (.login-card/.legal-content h1). Spezifischere h1-Regeln (.detail-header
   h1 etc.) ueberschreiben das wie gehabt per hoeherer Selektor-Spezifitaet. */
h1 { margin: 0 0 var(--space-4); }

.topbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: var(--topbar-height);
  padding: 0 var(--space-5);
  background: var(--chrome-bg);
  border-bottom: 1px solid var(--border);
  /* Echtes "fixed" statt "sticky" - html/body setzen beide overflow-x:hidden
     (siehe oben, noetig gegen seitliches Aufreissen), was implizit
     overflow-y:auto auf beiden erzeugt (CSS-Spec: overflow-x/-y muessen
     zusammen "auto" statt "visible" ergeben, wenn eine Achse eingeschraenkt
     ist). Zwei potenzielle Scroll-Container gleichzeitig sind ein bekannter
     Sticky-Bug v.a. in iOS Safari - dort blieb die Kopfzeile beim Scrollen
     nicht zuverlaessig oben. position:fixed haengt nicht von der
     Scroll-Kette ab, daher robuster. .app-shell bekommt zum Ausgleich
     margin-top, da ein fixed Element keinen Platz im normalen Fluss mehr
     belegt (anders als sticky). */
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 30;
}
.app-shell { margin-top: var(--topbar-height); }

/* App-uebliche Tab-Leiste unten (Experiment) - ersetzt auf schmalen Screens
   die Kopfzeile oben, siehe @media (max-width: 640px) weiter unten fuers
   eigentliche Erscheinungsbild. Default hier: unsichtbar/kein Platzverbrauch
   auf Desktop/Tablet, wo die normale Kopfzeile bleibt. */
.bottom-tab-bar { display: none; }

.menu-toggle {
  display: none;
  background: none;
  border: none;
  color: var(--text);
  font-size: var(--text-xl);
  cursor: pointer;
  padding: var(--space-1);
  border-radius: var(--radius-sm);
  transition: background 0.15s;
}
.menu-toggle:hover { background: var(--surface-2); }

.theme-toggle {
  position: fixed; right: var(--space-5); bottom: var(--space-5); z-index: 50;
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow-md);
  color: var(--text); font-size: var(--text-lg); cursor: pointer;
  transition: background 0.15s, transform 0.15s, box-shadow 0.15s;
}
.theme-toggle:hover { background: var(--surface-hover); transform: translateY(-2px); box-shadow: var(--shadow-lg); }

.brand { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-lg); font-weight: 600; color: var(--text); text-decoration: none; }
.brand-icon { width: 28px; height: 28px; object-fit: contain; }

.topbar-right { margin-left: auto; display: flex; align-items: center; gap: var(--space-3); }

.add-btn {
  background: var(--btn-bg);
  color: var(--btn-text);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  text-decoration: none;
  font-weight: 600;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  box-shadow: var(--shadow-sm);
  transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
}
.add-btn:hover { background: var(--btn-bg-hover); box-shadow: var(--shadow-md); transform: translateY(-1px); }
/* Explizite Hoehe nur fuer den Topbar-Button (nicht fuer .empty-state-add-btn,
   der lebt ausserhalb von .topbar-right) - sonst haengt die tatsaechliche
   Hoehe von Padding+Zeilenhoehe der Schriftart ab, was neben dem
   Umgebungs-Umschalter (Select, andere Metrik) leicht daneben wirkte. */
.topbar-right .add-btn {
  height: 40px; padding: 0 var(--space-4);
  display: inline-flex; align-items: center; justify-content: center;
}

/* Profil-Menü oben rechts in der Kopfzeile (Profilbild + Name + Guthaben) */
.topbar-user { position: relative; }
.topbar-user-toggle {
  display: flex; align-items: center; gap: var(--space-2);
  /* --topbar-pill-bg statt "none" - setzt sich von --chrome-bg der Topbar ab.
     Heller Modus: --surface-2 (deutlicher Farbtonwechsel reicht). Dunkler
     Modus: --surface-hover (--surface-2 waere dort zu nah an --chrome-bg dran,
     nur ein kleiner Helligkeitsschritt), siehe --topbar-pill-bg oben. */
  background: var(--topbar-pill-bg); border: none; cursor: pointer; padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill); font-family: var(--font-sans); color: var(--text);
  transition: background 0.15s;
}
.topbar-user-toggle:hover, .topbar-user-toggle[aria-expanded="true"] { background: var(--topbar-pill-hover-bg); }
.topbar-user-avatar {
  position: relative; flex-shrink: 0; width: 34px; height: 34px; border-radius: 50%;
  background: var(--btn-bg); color: var(--btn-text);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  font-family: var(--font-serif); font-weight: 600; font-size: var(--text-base);
}
.topbar-user-info { display: flex; flex-direction: column; align-items: center; gap: 1px; min-width: 0; }
.topbar-user-name {
  font-weight: 600; font-size: var(--text-sm); max-width: 140px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.topbar-user-credits { color: var(--text-muted); font-size: var(--text-xs); font-weight: 600; }

/* position:fixed statt absolute - das Menue liegt jetzt (siehe base.html)
   ausserhalb von .topbar-user, damit ein display:none auf der Kopfzeile
   (Mobil-Tab-Leisten-Experiment) es nicht mit versteckt. Ohne den
   .topbar-user-Vorfahren als Bezugspunkt reicht "unter der Kopfzeile rechts"
   als feste Koordinate, da der Profil-Toggle in der Kopfzeile immer dort
   sitzt (siehe .topbar-right { margin-left: auto }). */
.topbar-user-menu {
  position: fixed; top: calc(var(--topbar-height) + var(--space-2)); right: var(--space-4); z-index: 40;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md); padding: var(--space-2); min-width: 200px;
  display: flex; flex-direction: column; gap: 2px;
}
.topbar-user-menu[hidden] { display: none; }
.topbar-user-menu .logout-form { display: contents; }
.topbar-user-menu-link {
  color: var(--text); text-decoration: none; font-size: var(--text-sm);
  padding: var(--space-2); border-radius: var(--radius-sm); text-align: right;
  background: none; border: none; font-family: var(--font-sans); cursor: pointer; width: 100%;
}
.topbar-user-menu-link:hover { background: var(--surface-hover); color: var(--link); }
/* Hoehere Spezifitaet noetig als "form button[type=submit]" (die generische
   Formular-Absenden-Optik, grosser Vollton-Button) - sonst gewinnt die und der
   Abmelden-Eintrag sieht nicht wie die anderen Menuepunkte aus. */
.topbar-user-menu form button[type=submit].topbar-user-logout {
  background: none; color: var(--link); border: none; box-shadow: none;
  padding: var(--space-2); border-radius: var(--radius-sm); font-weight: 400;
}
.topbar-user-menu form button[type=submit].topbar-user-logout:hover {
  background: var(--surface-hover); box-shadow: none; transform: none;
}

/* App-Shell: Sidebar + Inhalt */
.app-shell {
  display: flex; align-items: flex-start;
  min-height: calc(100vh - var(--topbar-height));
  /* dvh (dynamic viewport height) statt vh: vh bleibt beim Ein-/Ausblenden der
     Browser-Werkzeugleiste beim erstmals geladenen (meist groesseren) Wert
     stehen, dvh passt sich live an die tatsaechlich sichtbare Hoehe an -
     verhindert, dass Inhalt kurzzeitig unter/ueber der fixierten Tab-Leiste
     endet, waehrend sich die Werkzeugleiste ein-/ausblendet. Alte Browser
     ohne dvh-Unterstuetzung ignorieren die zweite Deklaration und bleiben
     beim vh-Wert direkt darueber (reine Verbesserung, kein Fallback noetig). */
  min-height: calc(100dvh - var(--topbar-height));
}

.sidebar {
  position: relative;
  display: flex;
  flex-direction: column;
  width: var(--sidebar-width);
  flex-shrink: 0;
  align-self: stretch;
  background: var(--chrome-bg);
  border-right: 1px solid var(--border);
  padding: var(--space-4);
  font-family: var(--font-sans);
}
.sidebar-overlay { display: none; }

.sidebar-resize-handle {
  position: absolute; top: 0; right: -3px; bottom: 0; width: 6px;
  cursor: col-resize; z-index: 26;
}
.sidebar-resize-handle:hover, .sidebar-resize-handle.dragging { background: var(--link); opacity: 0.35; }

.sidebar-nav { margin-top: var(--space-5); margin-bottom: var(--space-4); padding-bottom: var(--space-4); border-bottom: 1px solid var(--border); }
.sidebar-nav-header {
  font-size: var(--text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted); margin-bottom: var(--space-2); padding: 0 var(--space-2);
}
.sidebar-nav-link {
  display: block; padding: var(--space-2); border-radius: var(--radius-sm);
  text-decoration: none; color: var(--text); font-weight: 600; font-size: var(--text-sm);
  transition: background 0.12s, color 0.12s;
}
.sidebar-nav-link:hover { background: var(--surface-hover); color: var(--link); }
.sidebar-nav-link.active { background: var(--tag-bg); color: var(--link); }

.category-tree-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: var(--space-3);
}
.cat-add-btn {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  width: 26px; height: 26px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.cat-add-btn:hover { background: var(--link); color: var(--btn-text); border-color: var(--link); }
.category-tree-header-actions { display: flex; gap: var(--space-1); }
.cat-edit-toggle-btn {
  background: var(--surface-2); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-sm);
  width: 26px; height: 26px; line-height: 1; cursor: pointer; font-size: var(--text-sm);
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.cat-edit-toggle-btn:hover { background: var(--surface-hover); }
.cat-edit-toggle-btn.active { background: var(--link); color: var(--btn-text); border-color: var(--link); }

.cat-root { list-style: none; margin: 0; padding: 0; }
.cat-node { position: relative; }
.cat-children {
  list-style: none; margin: var(--space-1) 0 0 8px; padding: 0 0 0 12px;
  border-left: 1px solid var(--border);
}
.cat-node.collapsed > .cat-children { display: none; }

.cat-row { display: flex; align-items: center; gap: var(--space-1); border-radius: var(--radius-sm); transition: background 0.12s; cursor: grab; }
.cat-row:hover { background: var(--surface-hover); }
/* Umbenennen/Loeschen werden bewusst NICHT per :hover eingeblendet (das
   Layout-Aendern beim ersten Tap zwingt iOS/Android sonst zu einem zweiten
   Tap, bevor der eigentliche Link-Klick durchgeht) - stattdessen nur im
   explizit aktivierten Bearbeiten-Modus sichtbar, siehe .cat-edit-toggle-btn. */
.category-tree.edit-mode .cat-delete-btn, .category-tree.edit-mode .cat-rename-btn { display: inline-flex; }
.cat-row.dragging { opacity: 0.4; cursor: grabbing; }
.cat-row.drag-over { background: var(--tag-bg); outline: 2px dashed var(--link); outline-offset: -2px; }

.cat-toggle, .cat-toggle-spacer, .cat-delete-btn, .cat-rename-btn {
  background: none; border: none; color: var(--text-muted); cursor: pointer;
  width: 26px; height: 26px; flex-shrink: 0; text-align: center; font-size: var(--text-sm);
  display: inline-flex; align-items: center; justify-content: center;
}
.cat-delete-btn, .cat-rename-btn { display: none; border-radius: var(--radius-sm); width: 28px; height: 28px; font-size: var(--text-base); }
.cat-delete-btn { margin-right: var(--space-1); }
.cat-rename-btn { margin-left: auto; }
.cat-rename-btn:hover { background: var(--surface-2); color: var(--link); }
.cat-delete-btn:hover { background: var(--danger-bg); color: var(--danger-text); }

.cat-rename-input-inline {
  flex: 1; min-width: 0; font-family: var(--font-sans); font-size: var(--text-sm);
  padding: 1px var(--space-1); border: 1px solid var(--link); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text);
  /* Ohne eigenen Rand fuegt sich ein Link nahtlos zwischen Nachbarn ein, das
     sichtbar umrandete Eingabefeld braucht aber etwas Luft, sonst wirkt es an
     z.B. dem farbig hinterlegten "Alle Rezepte" foermlich festgeklebt. */
  margin: var(--space-1) 0;
}
.cat-rename-input-inline:focus { outline: none; box-shadow: 0 0 0 2px var(--tag-bg); }
.cat-node.collapsed > .cat-row .cat-toggle { transform: rotate(-90deg); display: inline-block; }

.cat-link {
  flex: 1;
  display: block;
  padding: var(--space-2) var(--space-2);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--text);
  font-size: var(--text-base);
  font-weight: 500;
  border-left: 3px solid transparent;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.cat-link:hover { color: var(--link); }
.cat-link.cat-depth-1 { font-weight: 700; }
.cat-link.cat-depth-2 { font-weight: 400; }
.cat-link.cat-depth-3 { font-weight: 400; font-size: var(--text-sm); color: var(--text-muted); }
.cat-link.active { border-left-color: var(--link); background: var(--tag-bg); color: var(--link); font-weight: 700; }

/* display:flex + align-self:stretch, damit .container (wie .sidebar bereits)
   die volle Hoehe von .app-shell ausfuellt - sonst ragt der Footer bei kurzen
   Seiten mitten im Leerraum statt am unteren Bildschirmrand zu kleben. Der
   Footer (letztes Kind, siehe .site-footer margin-top:auto) wird dadurch
   immer an den unteren Rand gedrueckt, unabhaengig von der Inhaltslaenge.
   Zusaetzlich min-height direkt hier, da .container auf public_recipe.html
   (oeffentliche Freigabeseiten) ohne .app-shell/Sidebar direkt unter <body>
   haengt - align-self:stretch alleine haette dort keinen Flex-Elternteil zum
   Strecken. */
.container {
  max-width: var(--content-max); flex: 1; margin: 0 auto; padding: var(--space-5); min-width: 0;
  display: flex; flex-direction: column; align-self: stretch;
  min-height: calc(100vh - var(--topbar-height));
  min-height: calc(100dvh - var(--topbar-height)); /* siehe .app-shell weiter oben */
}

.active-filter-pill {
  display: inline-flex; gap: var(--space-2); align-items: center;
  background: var(--tag-bg); border: 1px solid var(--border);
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-pill);
  margin-bottom: var(--space-4); font-family: var(--font-sans); font-size: var(--text-sm); color: var(--tag-text);
}
.active-filter-pill a { color: var(--text-muted); text-decoration: none; font-weight: 700; }
.active-filter-pill a:hover { color: var(--text); }

.category-pill {
  display: inline-block;
  background: var(--accent-sage-bg); border: 1px solid transparent;
  color: var(--accent-sage); text-decoration: none;
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-pill);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600;
  transition: background 0.12s;
}
.category-pill:hover { background: var(--accent-sage); color: var(--surface); }

/* Eigenes, mit einblendendes Tooltip fuer Kategorie-Chips mit mehreren
   Kategorien (statt des nativen title-Attributs) - eigenes Element statt
   Kind des Chips, weil .recipe-card overflow:hidden hat und ein hier
   verschachteltes Tooltip sonst abgeschnitten wuerde. Position wird per JS
   (app.js) auf Basis der Chip-Position gesetzt. */
.category-tooltip {
  /* Abgeschwaechte Variante des .category-pill-Looks (gleiche Sage-Farbtoene,
     nur als Box statt Pille) statt eines neutralen Popover-Kastens - macht die
     Zugehoerigkeit zum Kategorie-Chip auf den ersten Blick klar. */
  position: fixed; z-index: 50; max-width: 240px;
  /* --accent-sage-bg ist als Pillen-Fuellung recht transparent (12-16%) - dahinter
     eine deckende --surface-Ebene, damit das Tooltip nicht von durchscheinendem
     Karteninhalt schwer lesbar wird, oben drauf weiterhin der Sage-Ton. */
  background: linear-gradient(var(--accent-sage-bg), var(--accent-sage-bg)), var(--surface);
  color: var(--accent-sage);
  border: 1px solid var(--accent-sage);
  border-radius: var(--radius-md); box-shadow: var(--shadow-md);
  padding: var(--space-2) var(--space-3); font-family: var(--font-sans);
  font-size: var(--text-xs); font-weight: 600;
  display: flex; flex-direction: column; gap: 2px;
  opacity: 0; visibility: hidden; transform: translateY(4px);
  transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
  pointer-events: none;
}
.category-tooltip.visible { opacity: 1; visibility: visible; transform: translateY(0); }
/* Gruppiert mehrere Kategorie-Pills als einen Block links, statt sie einzeln
   zwischen den Tags umbrechen zu lassen - dadurch faellt bei Platzmangel der
   GESAMTE Tags-Block als Ganzes in eine eigene Zeile (siehe .detail-meta-
   secondary: flex-wrap + space-between mit .category-pills/.tags als je ein
   Flex-Kind), statt Pills und Tags wild durcheinanderzumischen. */
.category-pills { display: flex; flex-wrap: wrap; gap: var(--space-1); }

.detail-meta-panel {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  margin: var(--space-4) 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.detail-meta-panel .meta-row { margin: 0; }
.detail-meta-secondary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}
.detail-tags.tags { margin-top: 0; justify-content: flex-end; }

.search-bar { display: flex; gap: var(--space-2); margin-bottom: var(--space-5); flex-wrap: wrap; }
.search-bar input[type=text] {
  flex: 1; min-width: 200px; padding: var(--space-3) var(--space-3); border: 1px solid var(--border);
  border-radius: var(--radius-md); font-family: var(--font-sans); background: var(--surface); color: var(--text);
  font-size: var(--text-base); /* verhindert iOS-Auto-Zoom beim Fokussieren */
}
.search-bar select, .search-bar button {
  padding: var(--space-3) var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); color: var(--text); font-family: var(--font-sans); cursor: pointer;
  font-size: var(--text-base);
  transition: background 0.15s, box-shadow 0.15s;
}
.search-bar select:hover, .search-bar input[type=text]:hover { border-color: var(--link); }
.search-bar button { background: var(--btn-bg); color: var(--btn-text); border: none; font-weight: 600; }

.usage-filter-form { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-bottom: var(--space-4); }
.usage-filter-form select {
  width: auto; margin-bottom: 0; padding: var(--space-2) var(--space-3);
  font-size: var(--text-base); /* verhindert iOS-Auto-Zoom beim Fokussieren */
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-md); font-family: var(--font-sans); cursor: pointer;
}
.usage-filter-form .btn-secondary { text-decoration: none; }
.usage-user-filter { flex: 0 1 220px; min-width: 160px; }

.usage-pagination { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-4); }

.usage-detail-text {
  white-space: pre-wrap; word-break: break-word; font-family: var(--font-sans); font-size: var(--text-sm);
  background: var(--surface-2); border-radius: var(--radius-md); padding: var(--space-3);
  max-height: 50vh; overflow-y: auto; margin: 0; color: var(--text);
}
.usage-detail-link { color: var(--link); word-break: break-all; }
/* Macht die Quelle-Zelle selbst antippbar (ersetzt die vorherige eigene
   "Details"-Spalte mit separatem Button) - sieht aus wie der normale
   Icon+Text-Inhalt der Zelle, nur mit Zeiger-Cursor + Hover-Farbe als
   Hinweis, dass ein Klick den Popover mit dem eigentlichen Inhalt oeffnet. */
.usage-source-btn {
  background: none; border: none; padding: 0; margin: 0; font: inherit; color: inherit;
  cursor: pointer; display: inline-flex; align-items: center; gap: var(--space-1);
}
.usage-source-btn:hover { color: var(--link); }
.usage-detail-images { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: var(--space-3); }
.usage-detail-images img { width: 100%; border-radius: var(--radius-md); object-fit: cover; aspect-ratio: 1; }
.search-bar button:hover { background: var(--btn-bg-hover); box-shadow: var(--shadow-sm); }

/* Dezenter Hinweis-Button neben dem Suchfeld (zeigt ein kleines Popover mit
   Beispielen, z.B. der <400kcal-Syntax) - bewusst kein staendig sichtbarer
   Banner/Tooltip, nur auf Klick/Tap, damit es niemanden stoert, der es nicht
   braucht. */
.search-input-row { display: flex; align-items: center; gap: var(--space-2); flex: 1; min-width: 200px; }
/* Kein Rahmen/Hintergrund - nur ein dezentes, gedaempftes Icon statt eines
   vollen Buttons, damit es neben dem Suchfeld nicht mit dem "Suchen"-Button
   konkurriert. */
/* .search-bar button (Zeile weiter unten) ist spezifischer als .search-info-btn
   allein (Klasse+Element schlaegt eine einzelne Klasse) und wuerde sonst den
   Hintergrund/Rahmen des "Suchen"-Buttons durchdruecken - deshalb hier ueber
   .search-bar .search-info-btn gezielt ueberschrieben. */
.search-bar .search-info-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; flex-shrink: 0;
  background: none; border: none; border-radius: 50%;
  color: var(--text-muted); cursor: pointer; transition: color 0.15s, background 0.15s;
}
.search-info-btn .icon-btn { width: 18px; height: 18px; }
.search-bar .search-info-btn:hover { color: var(--link); background: none; }
.search-info-popover {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md); padding: var(--space-4); max-width: min(300px, calc(100vw - var(--space-6)));
  font-family: var(--font-sans); font-size: var(--text-sm); color: var(--text);
}
.search-info-popover::backdrop { background: rgba(0,0,0,.25); }
.search-info-popover p { margin: 0 0 var(--space-2); }
.search-info-popover ul { margin: 0; padding-left: var(--space-4); display: flex; flex-direction: column; gap: var(--space-1); }
.search-info-popover code { background: var(--tag-bg); color: var(--tag-text); padding: 1px 4px; border-radius: 4px; font-size: var(--text-xs); }

.tag-autocomplete { position: relative; flex: 1; min-width: 160px; }
.tag-autocomplete input[type=text] { margin-bottom: 0; }
.tag-suggestions {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 40;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md); list-style: none; margin: 0; padding: var(--space-1) 0;
  max-height: 240px; overflow-y: auto;
}
.tag-suggestions.hidden { display: none; }
.tag-suggestion {
  padding: var(--space-2) var(--space-3); cursor: pointer; font-family: var(--font-sans);
  font-size: var(--text-sm); color: var(--text);
}
.tag-suggestion:hover, .tag-suggestion.active { background: var(--tag-bg); color: var(--tag-text); }
.tag-suggestion-create { color: var(--link); font-weight: 600; border-top: 1px solid var(--border); }

/* Kompakte einzeilige Statistik (Startseite, API-Nutzung) */
.usage-summary {
  font-family: var(--font-sans); font-size: var(--text-sm); color: var(--text-muted);
  background: var(--surface-2); border-radius: var(--radius-md); border: 1px solid var(--border);
  padding: var(--space-2) var(--space-3); margin-bottom: var(--space-4); display: inline-block;
}
.usage-summary strong { color: var(--text); font-weight: 700; }
@media (max-width: 640px) {
  .usage-summary { display: block; }
}

/* API-Nutzung (admin_usage.html): Statistik-Zeile aus einzelnen Spans statt
   reinem Fliesstext, damit sie auf schmalen Screens einzeln untereinander
   stehen koennen statt gequetscht in einer Zeile umzubrechen. */
.usage-summary-item:not(:last-child)::after { content: '·'; margin: 0 var(--space-2); opacity: 0.5; }
@media (max-width: 640px) {
  .usage-summary-item { display: block; }
  .usage-summary-item:not(:last-child) { margin-bottom: var(--space-1); }
  .usage-summary-item:not(:last-child)::after { content: none; }
}

/* Startseite: Suche links, Statistikzeile rechts, in einer Zeile (Desktop).
   Feste, gemeinsame Hoehe fuer Suchfeld/Button und die Statistik-Pille, statt
   sich auf Flex-Stretch zu verlassen (verhaelt sich bei verschachtelten
   Flex-Containern nicht konsistent). */
.home-toolbar { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-5); }
.home-toolbar .usage-summary {
  /* <p> hat vom Browser einen margin-top, den es sonst nirgends braucht - ohne
     margin:0 wird die Box beim vertikalen Zentrieren in .home-toolbar dadurch
     asymmetrisch nach unten verschoben (nicht auf gleicher Hoehe wie die Suche). */
  margin: 0; flex-shrink: 0; white-space: nowrap; box-sizing: border-box;
  height: 44px; padding-top: 0; padding-bottom: 0;
  display: flex; align-items: center; gap: var(--space-2);
}
.usage-stat { white-space: nowrap; }
.usage-sep { opacity: 0.5; }
.usage-stat-link { color: inherit; text-decoration: none; border-radius: var(--radius-sm); transition: color 0.12s; }
.usage-stat-link:hover, .usage-stat-link.active { color: var(--link); }
.usage-stat-link.active strong { color: var(--link); }
.home-toolbar .search-bar { margin-bottom: 0; flex: 1; min-width: 0; order: -1; }
.home-toolbar .search-bar input[type=text],
.home-toolbar .search-bar button { height: 44px; box-sizing: border-box; }
@media (max-width: 900px) {
  .home-toolbar { flex-direction: column; align-items: stretch; }
  .home-toolbar .usage-summary {
    white-space: normal; height: auto; flex-wrap: wrap;
    padding-top: var(--space-2); padding-bottom: var(--space-2); margin-bottom: var(--space-3); order: 0;
  }
  .home-toolbar .search-bar { order: 0; }
  .home-toolbar .search-bar input[type=text], .home-toolbar .search-bar button { height: auto; }
}
@media (max-width: 640px) {
  /* Kleinere Schrift/engere Abstaende speziell in diesem Kasten auf schmalen
     Handybreiten - mit 3 Stats (Rezepte/Geteilt/Unkategorisiert) reicht der
     Platz sonst knapp nicht fuer eine Zeile. space-between verteilt die drei
     Stats gleichmaessig ueber die volle Breite der Box, statt sie (mit viel
     Leerraum rechts) links gedraengt anzuzeigen. */
  .home-toolbar .usage-summary { font-size: var(--text-xs); gap: var(--space-1); justify-content: space-between; }
}

.recipe-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: var(--space-5);
  /* align-items: stretch (Grid-Default) bewusst NICHT ueberschrieben - alle
     Karten in einer Zeile sollen gleich hoch sein. Damit der dadurch
     entstehende Freiraum nicht als sichtbare Luecke unter den Tags haengen
     bleibt, wird er stattdessen ueber Flexbox in .recipe-card/.recipe-card-body
     aufgefangen (Tags werden per margin-top:auto an den unteren Kartenrand
     geschoben, siehe dort) statt die verfuegbare Hoehe zur Laufzeit zu messen. */
}

.recipe-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  text-decoration: none;
  color: var(--text);
  box-shadow: var(--shadow-sm);
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
  display: flex;
  flex-direction: column;
  /* Ohne das kann der Karteninhalt (v.a. .tags, wenn app.js kurzzeitig alle
     Tags einblendet um die verfuegbare Hoehe zu messen) die Karte ueber ihre
     vom Grid gestreckte Hoehe hinaus aufblaehen ("Grid-Blowout" - Flex-/
     Grid-Items haben per Default min-height:auto, das laesst Inhalt die
     eigentlich vorgegebene Groesse ueberschreiben). Das hat vorher dazu
     gefuehrt, dass die Hoehen-Messung in truncateCardTags (app.js) faelschlich
     "unbegrenzt Platz" ergab. Mit min-height:0 + overflow:hidden bleibt die
     Karte zuverlaessig bei ihrer gestreckten Hoehe, ueberschuessiger Inhalt
     wird geclippt statt die Karte (und damit die ganze Grid-Zeile) zu strecken. */
  min-height: 0;
}
.recipe-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); border-color: var(--link); }
.recipe-card { cursor: grab; }
.recipe-card.dragging { opacity: 0.4; cursor: grabbing; }
/* Eigener Wrapper (statt direkt .recipe-card img) noetig, weil applyFocalCrop()
   in app.js das <img> selbst absolut positioniert und per Pixel-Translate
   verschiebt (siehe .focal-img) - der bisherige Hover-Zoom-Effekt sitzt
   deshalb jetzt auf dem Wrapper statt dem Bild, sonst wuerde JS ihn ueberschreiben. */
.recipe-card-img-wrap { position: relative; overflow: hidden; width: 100%; height: 155px; transition: transform 0.35s ease; }
.recipe-card:hover .recipe-card-img-wrap { transform: scale(1.06); }
.no-image {
  position: relative; width: 100%; height: 155px; display: flex; align-items: center; justify-content: center;
  font-size: 2.5rem; background: var(--surface-2);
}
/* Gesamtdauer als Pille auf dem Bild - nur in der mobilen Ansicht (siehe
   @media unten), auf Desktop steht die Dauer weiterhin als Text in
   .recipe-card-meta-row. --overlay ist dieselbe dezente, dunkle
   Overlay-Farbe wie z.B. beim Bilder-Zaehler (.gallery-counter) - bewusst
   nicht transparent, damit die Pille sich vom Foto abhebt. */
.recipe-card-time-badge {
  display: none;
  position: absolute; top: var(--space-2); right: var(--space-2);
  background: var(--overlay); color: #fff; font-size: var(--text-xs); font-family: var(--font-sans);
  padding: 2px var(--space-2); border-radius: var(--radius-pill); white-space: nowrap;
}
/* Nur auf Mobil sichtbar (ersetzt dort die ausgeblendeten Tags), siehe @media
   unten - auf Desktop bleiben stattdessen die Tags wie gehabt sichtbar. */
.recipe-card-calories { display: none; }
/* flex:1 laesst die Body-Flaeche den kompletten, durch das gestreckte
   .recipe-card (align-items:stretch im Grid) entstandenen Freiraum
   einnehmen - die Tags werden weiter unten per margin-top:auto an den
   unteren Rand dieser Flaeche geschoben statt Leerraum stehen zu lassen. */
/* gap statt einzelner margin-bottom/margin-top-Werte an den Kindern - sorgt
   automatisch fuer einheitlichen Abstand egal ob .recipe-card-meta-row
   vorhanden ist oder nicht (wird nur gerendert, wenn total_time_min/
   categories gesetzt sind). */
.recipe-card-body { padding: var(--space-4); display: flex; flex-direction: column; flex: 1; gap: var(--space-2); min-height: 0; }
.recipe-card-body h3 { margin: 0; font-size: var(--text-lg); font-weight: 600; overflow-wrap: break-word; hyphens: auto; }
.meta { font-size: var(--text-sm); color: var(--text-muted); font-family: var(--font-sans); }
.recipe-card-meta-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
.recipe-card-meta-row .meta { flex-shrink: 0; white-space: nowrap; }
/* min-width:0 noetig, damit das Flex-Kind ueberhaupt unter seine Textbreite
   schrumpfen kann (Flex-Items sind sonst standardmaessig min-width:auto) -
   sonst ragt die Pille bei langen Kategorienamen ueber die Kartenbreite
   hinaus und wird vom Karten-Overflow mitten im Wort abgeschnitten, statt
   sauber mit "…" zu kuerzen. */
.recipe-card-meta-row .category-pill { padding: 1px var(--space-2); font-size: var(--text-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }

.tags { display: flex; flex-wrap: wrap; gap: var(--space-1); margin-top: var(--space-2); }
.tag {
  background: var(--tag-bg); color: var(--tag-text); font-size: var(--text-xs); font-weight: 600; padding: 3px var(--space-3);
  border-radius: var(--radius-pill); font-family: var(--font-sans); flex: none;
}
/* Auf der Rezeptkarte in der Uebersicht passen so viele Tags wie in eine
   Zeile passen (statt einer festen Anzahl) - Rest wird per JS (app.js,
   truncateCardTags) ausgeblendet und als "…"-Chip zusammengefasst.
   margin-top:auto schiebt die Tags an den unteren Rand von
   .recipe-card-body (flex:1, siehe oben) - der Mindestabstand nach oben
   kommt vom gap auf .recipe-card-body, margin-top:auto verteilt zusaetzlich
   uebrigen Freiraum aus dem Grid-Stretch, statt ihn irgendwo als Luecke
   stehen zu lassen. Ueberschreibt bewusst das margin-top: var(--space-2)
   der allgemeinen .tags-Regel oben. */
.recipe-card .tags { flex-wrap: wrap; overflow: hidden; margin-top: auto; }
.tag-more { background: var(--surface-2); color: var(--text-muted); }

.empty-state { text-align: center; padding: var(--space-7) var(--space-5); color: var(--text-muted); }
.empty-state.hidden { display: none; }

/* Umgebungs-Umschalter in der Kopfzeile - ein normaler <button> (siehe
   base.html-Kommentar: war ein natives <select>, das trotz height:40px UND
   -webkit-appearance:none in Safari eine groessere Eigenhoehe behielt als
   der "+"-Button daneben - zwei Versuche, beide erfolglos, ein echtes
   Steuerelement war zuverlaessiger nicht auf 40px zu zwingen). Auf Mobil
   (Tab-Leisten-Experiment) ist die ganze Kopfzeile ausgeblendet, Umschalten
   laeuft dort ueber den eigenen Tab bzw. den Menuepunkt "Kammer wechseln"
   im Profil-Dropdown (beide oeffnen denselben #environment-switcher-popover). */
/* Gleicher "Pillen"-Stil wie .topbar-user-toggle (Profil-Button) statt der
   schlichten umrandeten Box vom alten <select> - passt so besser zu den
   anderen beiden Kopfzeilen-Elementen (Pille links vom "+"-Button, Pille
   rechts vom Profilbild). */
.environment-switcher-trigger {
  height: 40px; padding: 0 var(--space-3); border: none; border-radius: var(--radius-pill);
  background: var(--topbar-pill-bg); color: var(--text); font-family: var(--font-sans); font-size: var(--text-sm);
  font-weight: 600; cursor: pointer; max-width: 160px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; transition: background 0.15s;
}
.environment-switcher-trigger:hover { background: var(--topbar-pill-hover-bg); }
/* Eigenes Popover fuer den mobilen Umschalter statt eines nativen <select> -
   dessen Auswahlfenster wird komplett vom Betriebssystem gerendert (grosse
   Touch-Flaechen, kein CSS-Einfluss auf Groesse/Abstaende) und wirkte dadurch
   viel zu gross fuer eine Handvoll Emoji-Optionen. */
.environment-switcher-popover { width: min(300px, calc(100vw - var(--space-6))); }
.environment-switcher-options { display: flex; flex-direction: column; gap: var(--space-2); margin: 0; }
/* ".environment-switcher-options .environment-switcher-option" statt nur
   ".environment-switcher-option" - sonst gewinnt die generische
   "form button[type=submit]"-Regel (1 Attribut-Selektor + 2 Element-
   Selektoren schlagen bei Spezifitaets-Gleichstand 1 Klasse hier). Der
   Name-Text blieb dadurch in --btn-text (fuer eine SOLIDE Copper-Flaeche
   gedacht) sitzen, waehrend background per .selected auf die viel hellere/
   dunklere --tag-bg-Tönung wechselte - je nach Theme fast unlesbar (heller
   Text auf hellem Grund im Lightmode, dunkler auf dunklem im Darkmode). */
.environment-switcher-options .environment-switcher-option {
  display: flex; align-items: center; gap: var(--space-3); width: 100%;
  padding: var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); color: var(--text); font-family: var(--font-sans); font-size: var(--text-base);
  font-weight: 400; box-shadow: none; text-align: left; cursor: pointer;
}
.environment-switcher-options .environment-switcher-option:hover { background: var(--surface-hover); }
.environment-switcher-options .environment-switcher-option.selected { border-color: var(--link); background: var(--tag-bg); color: var(--link); }
.environment-switcher-option-icon { font-size: var(--text-xl); flex-shrink: 0; }
.active-collection-notice {
  padding: var(--space-4); background: var(--tag-bg); border-radius: var(--radius-md);
  margin-bottom: var(--space-4); font-family: var(--font-sans);
}
.active-collection-notice p { margin: 0 0 var(--space-2); }

/* Kammern-Seite (/collections) - jede Kammer/Einladung als eigene, klar
   abgegrenzte Karte statt nur durch eine duenne Trennlinie zu unterscheiden
   (Nutzer-Vorgabe: bei mehreren Kammern mit Icon/Einladen-Feld/Buttons war
   nicht mehr klar erkennbar, wo eine Zeile aufhoert und die naechste
   anfaengt). --bg statt --surface, damit sich die Karte spuerbar vom
   umgebenden .panel (das bereits --surface nutzt) abhebt. */
.collection-row, .collection-invite-row {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: var(--space-3); padding: var(--space-4);
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-md);
  margin-bottom: var(--space-3);
}
.collection-row:last-child, .collection-invite-row:last-child { margin-bottom: 0; }
.collection-row > div:first-child { display: flex; flex-direction: column; gap: 2px; }
.collection-name-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
/* Zeigt nur das aktuelle Icon, oeffnet per popovertarget das Auswahl-Popover
   (siehe .collection-icon-popover) - kein Eingabefeld mehr als primaerer Weg,
   freies Emoji-Tippen ueber die Mobiltastatur ist umstaendlich. */
.collection-icon-trigger {
  width: 32px; height: 32px; padding: 0; flex-shrink: 0;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); font-size: var(--text-lg); line-height: 1; cursor: pointer;
}
.collection-icon-trigger:hover { background: var(--surface-hover); }
.collection-icon-popover { width: min(360px, calc(100vw - var(--space-6))); }
.collection-icon-popover h2 { text-align: left; }
.collection-icon-options { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--space-2); margin-bottom: var(--space-4); }
/* ".collection-icon-options .collection-icon-option" statt nur
   ".collection-icon-option" - gleicher Spezifitaets-Grund wie bei
   .environment-switcher-option oben (generische "form button[type=submit]"-
   Regel gewinnt sonst gegen eine einzelne Klasse). Hier bisher nicht
   sichtbar, weil Emoji-Glyphen nicht auf color reagieren - trotzdem
   konsistent mitgefixt. */
.collection-icon-options .collection-icon-option {
  width: 100%; aspect-ratio: 1; padding: 0; font-size: var(--text-xl); line-height: 1;
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface);
  box-shadow: none; cursor: pointer;
}
.collection-icon-options .collection-icon-option:hover { background: var(--surface-hover); }
.collection-icon-options .collection-icon-option.selected { border-color: var(--link); background: var(--tag-bg); }
.collection-icon-custom-form { margin: 0; }
/* ".collection-icon-custom-form .collection-icon-input" statt nur
   ".collection-icon-input" - die generische "form input[type=text]"-Regel
   hat sonst Vorrang: bei gleicher Spezifitaet in der Klassen-Ebene (1 Klasse
   hier, 1 Attribut-Selektor dort) entscheiden die zwei Element-Selektoren
   (form, input) der generischen Regel den Gleichstand fuer sich, width:44px
   & Co. griffen dadurch nie - das Feld blieb bei voller Breite/grossem
   Padding haengen. */
.collection-icon-custom-form .collection-icon-input {
  width: 100%; margin-bottom: 0; padding: var(--space-2); text-align: center; font-size: var(--text-base);
}
/* Name/Statuszeile bekommt ihre eigene Zeile, Einladen+Verlassen teilen sich
   die naechste - "Verlassen" haengt per margin-left:auto rechts (auch falls
   die Zeile doch mal umbricht, wirkt der Auto-Rand dann auf die eigene
   Zeile). flex:1 0 100% zwingt den Block unter die Namenszeile, gleiches
   Muster wie .collection-pending-emails weiter unten. */
.collection-row-actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; flex: 1 0 100%; }
.collection-leave-form { margin-left: auto; }
/* Eigene volle Zeile (siehe collections.html-Kommentar: <form> ist absichtlich
   leer, das Feld haengt per form="..."-Attribut daran) - konkurriert dadurch
   nicht mehr mit "Einladen"/"Verlassen" um Platz in derselben Zeile. */
/* Volle eigene Formatierung noetig statt sich auf die generische
   "form input[type=text]"-Regel zu verlassen - das Feld haengt seit dem
   "gleiche Zeile wie Einladen/Verlassen"-Umbau nur noch per form="..."-
   Attribut an seinem <form>, liegt also nicht mehr WIRKLICH darin (die
   generische Regel ist eine Nachfahren-Auswahl, prüft die tatsächliche
   DOM-Verschachtelung, nicht das form-Attribut) - ohne dieses eigene Set
   sah das Feld wie ein unformatiertes Standard-Eingabefeld aus. */
.collection-invite-input {
  flex: 1 0 100%; width: 100%; margin-bottom: 0;
  padding: var(--space-3) var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-md);
  font-family: var(--font-sans); background: var(--surface); color: var(--text); font-size: var(--text-base);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.collection-invite-input:hover { border-color: var(--text-muted); }
.collection-invite-input:focus {
  outline: none; border-color: var(--link); box-shadow: 0 0 0 3px var(--tag-bg);
}
/* Gleicher Rotton wie die generische .error-Meldung (z.B. Login-Formular),
   nur kompakter fuers Einladungsfeld pro Kammer-Zeile. Kein eigenes display
   gesetzt - das [hidden]-Attribut (siehe collections.html-Skript) uebernimmt
   sonst die Sichtbarkeit nicht zuverlaessig (Autor-Regeln schlagen die
   native [hidden]-UA-Regel, wenn sie selbst display setzen). */
.collection-invite-error {
  flex: 1 0 100%; margin: 0; padding: var(--space-2) var(--space-3);
  background: var(--danger-bg); color: var(--danger-text); border: 1px solid var(--danger-border);
  border-radius: var(--radius-md); font-family: var(--font-sans); font-size: var(--text-sm);
}
.collection-create-form { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.collection-create-form input[type=text] { flex: 1; min-width: 200px; }
/* Einheitliche Groesse fuer ALLE Buttons + Eingabefelder auf der Kammern-
   Seite. Zwei Bausteine des Problems: (1) .btn-secondary/.btn-danger haben
   ein kleineres Padding+Schriftgroesse als Eingabefelder (generische "form
   input"-Regel) - ohne Angleichung wirken "Einladen"/"Erstellen" (neben
   einem Eingabefeld) spuerbar kleiner als "Verlassen"/"Ablehnen"/"Beitreten"
   (ohne Eingabefeld daneben), obwohl alle dieselben Klassen nutzen (gleiches
   Muster wie bei .search-bar button, das deshalb ebenfalls Input-Padding/
   -Schriftgroesse uebernimmt). (2) Eingabefelder haben per genereller Regel
   ein margin-bottom, das sie in der Flex-Zeile nach oben versetzt. */
.collection-row .btn-secondary, .collection-row .btn-danger,
.collection-invite-row .btn-secondary, .collection-invite-row .btn-danger,
.collection-create-form .btn-secondary {
  padding: var(--space-3) var(--space-4); font-size: var(--text-base);
}
.collection-create-form input[type=text] { margin-bottom: 0; }
/* "Beitreten"/"Ablehnen" waren direkte Kinder von .collection-invite-row
   (display:flex; justify-content:space-between) und wurden dadurch ueber
   die ganze Zeile auseinandergezogen statt nebeneinander zu stehen - jetzt
   in einem gemeinsamen Wrapper, der als EIN Flex-Kind rechts landet. */
.collection-invite-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

/* Ausstehende Einladungen (registriert oder nicht - siehe main.py
   collections_page) - flex-basis:100% zwingt den Block unter die Zeile mit
   Name/Formularen, dank flex-wrap auf .collection-row auch bei schmalen
   Bildschirmen. */
.collection-pending-emails { flex: 1 0 100%; display: flex; flex-wrap: wrap; gap: var(--space-2); }
.collection-pending-email-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface-hover); border: 1px solid var(--border); color: var(--text-muted);
  padding: 2px var(--space-1) 2px var(--space-3); border-radius: var(--radius-pill);
  font-family: var(--font-sans); font-size: var(--text-xs);
}
.collection-pending-email-chip form { margin: 0; }
.collection-pending-email-chip .chip-remove-btn {
  background: none; border: none; color: var(--text-muted); cursor: pointer;
  font-size: var(--text-base); line-height: 1; padding: var(--space-1);
}
.collection-pending-email-chip .chip-remove-btn:hover { color: var(--danger-text); }

/* Einkaufsliste (/shopping-list) */
/* Das Suchfeld steckt in einer eigenen <form> (siehe shopping_list.html) -
   die generischen "form input[type=text]"-Styles greifen dadurch automatisch,
   hier nur das ueberfluessige margin-bottom der Mini-Form/des einzelnen Felds
   entfernen. */
.shopping-search-form { margin-bottom: 0; }
.shopping-search-form input[type=text] { margin-bottom: 0; }
.shopping-recipe-results { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }
.shopping-recipe-results:empty { margin-top: 0; }
.shopping-recipe-result {
  padding: var(--space-2) var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--text); font-family: var(--font-sans); font-size: var(--text-sm);
  cursor: pointer; transition: background 0.15s, box-shadow 0.15s;
}
.shopping-recipe-result:hover { background: var(--surface-hover); box-shadow: var(--shadow-sm); }
.shopping-add-form { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.shopping-add-form input[type=text] { margin-bottom: 0; }
.shopping-add-amount { flex: 0 0 100px; }
.shopping-add-name, .shopping-add-note { flex: 1 1 180px; min-width: 160px; }
.shopping-add-form .btn-secondary { padding: var(--space-3) var(--space-4); font-size: var(--text-base); }
.shopping-list { list-style: none; margin: 0; padding: 0; }
.shopping-list-item {
  display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border); flex-wrap: wrap;
}
.shopping-list-item:last-child { border-bottom: none; }
.shopping-list-item form { margin: 0; }
.shopping-check-btn {
  width: 28px; height: 28px; flex-shrink: 0; border-radius: 50%; border: 1px solid var(--border);
  background: var(--surface); color: var(--accent-sage); cursor: pointer; font-size: var(--text-base);
  display: flex; align-items: center; justify-content: center; padding: 0;
}
.shopping-list-item.checked .shopping-check-btn { background: var(--accent-sage-bg); border-color: var(--accent-sage); }
.shopping-item-text { flex: 1; min-width: 0; font-family: var(--font-sans); word-break: break-word; }
.shopping-item-amount { color: var(--link); margin-right: 4px; }
.shopping-item-note { color: var(--text-muted); font-size: var(--text-sm); font-style: italic; margin-left: 4px; }
.shopping-list-item.checked .shopping-item-text { color: var(--text-muted); text-decoration: line-through; }
.shopping-list-item.checked .shopping-item-amount { color: var(--text-muted); }
.shopping-delete-form { flex-shrink: 0; }
.shopping-delete-form .chip-remove-btn {
  background: none; border: none; color: var(--text-muted); cursor: pointer;
  font-size: var(--text-lg); line-height: 1; padding: var(--space-1);
}
.shopping-delete-form .chip-remove-btn:hover { color: var(--danger-text); }
.shopping-list-panel-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-2); }
.shopping-list-panel-header h2 { margin: 0; }
/* Liegt vorbefuellt im DOM, wird per JS (Bearbeiten-Button oben) fuer die
   GESAMTE Liste ein-/ausgeblendet statt neu geladen - waehrenddessen sind
   Abhaken/Loeschen ausgeblendet, die Felder bekommen die volle Zeilenbreite.
   Bewusst NICHT die .shopping-add-*-Klassen der "Manuell hinzufuegen"-Form
   wiederverwendet - deren grosszuegige min-width passt in einer vollen
   Panel-Breite, reisst aber in der schmalen Listenzeile auf Mobil um.
   flex-wrap:nowrap + min-width:0 auf jedem Input (Inputs haben sonst einen
   eigenen intrinsischen Mindestwert, der flex-shrink ignoriert) zwingt alle
   drei Felder auf eine Zeile, notfalls schrumpfen sie gemeinsam statt
   umzubrechen. */
.shopping-item-edit-fields { display: flex; gap: 4px; flex-wrap: nowrap; flex: 1; min-width: 0; }
/* "display: flex" oben gewinnt sonst IMMER gegen die native [hidden]-Regel
   des Browsers (display:none) - eine Autoren-Regel schlaegt die
   UA-Standardregel unabhaengig von Spezifitaet/Reihenfolge. Ohne diese
   explizite Rueck-Ueberschreibung blieb das Feld dauerhaft sichtbar (auch
   ausserhalb des Bearbeiten-Modus, dadurch wirkten Text + Eingabefelder wie
   "doppelt"), egal ob JS el.hidden auf true oder false setzt. */
.shopping-item-edit-fields[hidden] { display: none; }
.shopping-item-edit-fields input[type=text] {
  margin-bottom: 0; min-width: 0; padding: 4px 6px; font-size: var(--text-base);
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text);
  font-family: var(--font-sans);
}
.shopping-edit-amount { flex: 0 1 15%; }
.shopping-edit-name { flex: 1.4 1 0; }
.shopping-edit-note { flex: 1 1 0; }

/* Zutaten-Auswahl-Popup beim Hinzufuegen zur Einkaufsliste */
.ingredient-popover { width: min(440px, calc(100vw - var(--space-6))); }
.ingredient-popover-list { max-height: 50vh; overflow-y: auto; margin-bottom: var(--space-4); display: flex; flex-direction: column; gap: var(--space-2); }

/* Add page */
/* "Rezept hinzufügen"-Seite etwas grosszuegiger als der App-Standardabstand -
   wirkte mit den knappen space-2/space-4-Werten zwischen Social-Links/Hinweis/
   Tabs/Formular recht gedrungen. */
.social-quick-links { display: flex; gap: var(--space-3); flex-wrap: wrap; margin: var(--space-4) 0 var(--space-5); }
.social-quick-link {
  display: inline-flex; align-items: center; gap: var(--space-2);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: var(--space-3) var(--space-5); font-family: var(--font-sans); font-size: var(--text-sm);
  font-weight: 600; color: var(--text); text-decoration: none; transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.social-quick-link:hover { background: var(--surface-hover); border-color: var(--link); color: var(--link); }
.social-icon { width: 16px; height: 16px; flex-shrink: 0; }
/* 1em statt fester px-Groesse, da die Aktions-Buttons je nach Breakpoint
   unterschiedliche Schriftgroessen haben (Icon-only auf dem Handy vs. Icon+Text
   auf dem Desktop) - so skaliert das Icon automatisch mit. Gemeinsame Klasse
   fuer alle Inline-SVG-Icons in .btn-icon-label-Buttons (Teilen/Bearbeiten/
   Kategorien/Löschen), damit sie einheitlich aussehen. */
.icon-btn { width: 1em; height: 1em; vertical-align: -0.15em; flex-shrink: 0; }
.social-quick-links + .hint { margin-bottom: var(--space-5); }

/* Kategorien-Verwaltung auf der Rezeptseite (Chip-Picker, siehe initCategoryPicker) */
.category-prompt {
  background: var(--tag-bg); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: var(--space-4); margin-top: var(--space-3); margin-bottom: var(--space-5); font-family: var(--font-sans);
}
.category-prompt.hidden { display: none; }
#category-toggle-btn.active { background: var(--link); color: var(--btn-text); border-color: var(--link); }
.category-prompt p { margin: 0 0 var(--space-3); font-size: var(--text-sm); color: var(--text); }
.category-prompt input[type=text] {
  width: 100%; padding: var(--space-2) var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-md);
  font-family: var(--font-sans); background: var(--surface); color: var(--text);
  font-size: var(--text-base); /* verhindert iOS-Auto-Zoom beim Fokussieren */
}
.category-prompt .pantry-chips { margin-top: var(--space-2); margin-bottom: 0; }

.tabs { display: flex; gap: var(--space-3); margin-bottom: var(--space-5); border-bottom: 1px solid var(--border); }
.tab-btn {
  padding: var(--space-3) var(--space-5); border: none; background: none; cursor: pointer;
  font-family: var(--font-sans); font-size: var(--text-base); color: var(--text-muted); border-bottom: 3px solid transparent;
  transition: color 0.15s, border-color 0.15s;
}
.tab-btn:hover { color: var(--text); }
.tab-btn.active { color: var(--link); border-bottom-color: var(--link); font-weight: 700; }
.tab-panel.hidden { display: none; }
.tab-panel > .hint { margin-bottom: var(--space-4); }
.hint { color: var(--text-muted); font-size: var(--text-sm); font-family: var(--font-sans); }
.hint a { color: var(--link); }
.add-manual-hint {
  margin-top: var(--space-6); padding-top: var(--space-5); border-top: 1px solid var(--border);
  color: var(--text-muted); font-size: var(--text-sm); font-family: var(--font-sans);
}
.add-manual-hint a { color: var(--link); font-weight: 600; }

form input[type=url], form input[type=text], form input[type=number], form input[type=password], form input[type=email], form textarea, form input[type=file], form select {
  width: 100%; padding: var(--space-3) var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-md);
  font-family: var(--font-sans); background: var(--surface); color: var(--text); margin-bottom: var(--space-3);
  /* Mindestens 16px, sonst zoomt iOS Safari beim Fokussieren automatisch rein
     (Standardverhalten, wenn ein Eingabefeld eine kleinere Schrift hat). */
  font-size: var(--text-base);
  transition: border-color 0.15s, box-shadow 0.15s;
}
/* Native Auf/Ab-Pfeile bei Zahlenfeldern ausgeblendet - der Browser zeichnet
   die immer in seiner eigenen (meist hellen) Optik, unabhaengig vom Theme der
   Seite, und wirkt dadurch wie ein unpassender grauer Klotz auf dunklem
   Hintergrund. Werden hier nirgends gebraucht (kein Feld ist auf Klick-Stepper
   angewiesen statt Texteingabe). */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
  -webkit-appearance: none; margin: 0;
}
input[type=number] { -moz-appearance: textfield; }
form input:hover, form textarea:hover, form select:hover { border-color: var(--text-muted); }
form input:focus, form textarea:focus, form select:focus {
  outline: none; border-color: var(--link); box-shadow: 0 0 0 3px var(--tag-bg);
}
/* Eigener Dropdown-Pfeil per CSS-Dreieck statt des nativen Browser-Pfeils
   (der immer in der Browser-/OS-eigenen Optik gezeichnet wird, unabhaengig
   vom Theme) - reiner CSS-Trick ohne SVG/Data-URI, damit die Pfeilfarbe
   automatisch mit var(--text-muted) im Hell-/Dunkelmodus mitzieht. Kein
   Ersatz durch ein komplett eigenes Dropdown (siehe environment-switcher-
   trigger in base.html) noetig - dafuer ist eine einfache Zwei-Optionen-
   Sprachauswahl zu klein, das native <select> selbst bleibt bestehen. */
.select-wrap { position: relative; }
.select-wrap select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  padding-right: calc(var(--space-3) * 2 + 10px);
}
.select-wrap::after {
  content: ""; position: absolute; right: var(--space-3); top: 50%; transform: translateY(-65%);
  width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-top: 6px solid var(--text-muted); pointer-events: none;
}
form button[type=submit] {
  background: var(--btn-bg); color: var(--btn-text); border: none; padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-md); cursor: pointer; font-family: var(--font-sans); font-weight: 600;
  box-shadow: var(--shadow-sm); transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
}
form button[type=submit]:hover { background: var(--btn-bg-hover); box-shadow: var(--shadow-md); transform: translateY(-1px); }
form button:disabled { opacity: 0.6; cursor: wait; }

.loading { text-align: center; padding: var(--space-6); }
.loading.hidden { display: none; }
.spinner {
  border: 4px solid var(--border); border-top: 4px solid var(--link); border-radius: 50%;
  width: 36px; height: 36px; animation: spin 0.8s linear infinite; margin: 0 auto var(--space-3);
}
@keyframes spin { to { transform: rotate(360deg); } }
.error { background: var(--danger-bg); color: var(--danger-text); padding: var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--danger-border); font-family: var(--font-sans); margin-top: var(--space-4); }
.error.hidden { display: none; }

/* Hinweis-Dialog (z.B. KptnCook-Import-Warnung) */
.confirm-dialog {
  max-width: 440px; width: calc(100% - var(--space-5) * 2);
  background: var(--surface); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  padding: var(--space-5); font-family: var(--font-sans);
}
.confirm-dialog::backdrop { background: rgba(0, 0, 0, .5); }
.confirm-dialog h3 { margin-top: 0; }
.confirm-dialog p { margin: var(--space-3) 0; }
.confirm-dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-3); margin-top: var(--space-4); }

/* overflow-x:hidden gezielt hier, nicht an .confirm-dialog allgemein (andere
   Dialoge wie die KptnCook-Warnung brauchen bei langem Text weiterhin
   vertikales Scrollen) - vermutlich verursacht der Zoom-Regler die
   horizontale Scrollbar: sein Schieberegler-Griff ("thumb") ragt bei
   min/max-Werten technisch bedingt leicht ueber die eigene Box hinaus
   (bekannte Eigenheit von <input type=range> bei width:100%), was manche
   Browser als echtes Scroll-Overflow werten statt es nur optisch
   abzuschneiden. */
.cropper-dialog { max-width: 400px; overflow-x: hidden; }
.cropper-viewport {
  position: relative; width: min(300px, 100%); aspect-ratio: 1;
  margin: var(--space-3) auto 0; overflow: hidden; border-radius: var(--radius-md);
  background: var(--surface-2); touch-action: none; cursor: grab;
}
.cropper-viewport:active { cursor: grabbing; }
#cropper-img { position: absolute; top: 0; left: 0; user-select: none; pointer-events: none; }
.cropper-zoom-slider { width: 100%; margin-top: var(--space-3); accent-color: var(--link); }

/* Login */
/* Grid statt Flex: Zeile 1 (1fr) nimmt allen freien Platz und zentriert die
   Karte darin, Zeile 2 (auto) haelt den Footer immer am unteren Rand fest -
   bei einer reinen Flex-Column mit justify-content:center wuerden Karte UND
   Footer als ein Block gemeinsam mittig sitzen, statt den Footer an den
   Bildschirmrand zu kleben. */
.login-page { display: grid; grid-template-rows: 1fr auto; min-height: 100vh; min-height: 100dvh; background: var(--bg); }
.login-card {
  align-self: center; justify-self: center;
  width: 100%; max-width: 420px; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md); padding: var(--space-6);
}
.login-card h1 { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xl); margin: 0 0 var(--space-4); }
.login-card .error { margin-bottom: var(--space-3); }
.login-card label { display: block; font-family: var(--font-sans); font-size: var(--text-sm); color: var(--text-muted); margin-bottom: var(--space-1); }
.login-card .hint { margin-bottom: var(--space-3); }
.login-card a { color: var(--link); text-decoration: none; }
.login-card a:hover { color: var(--link-hover); text-decoration: underline; }
.login-card form button[type=submit] { display: block; margin: var(--space-1) auto 0; }
.login-forgot { margin: var(--space-3) 0 0; text-align: center; font-family: var(--font-sans); font-size: var(--text-sm); }

/* Footer mit rechtlichen Links - margin-top:auto schiebt ihn innerhalb von
   .container (display:flex;flex-direction:column, siehe oben) immer an den
   unteren Rand, auch bei kurzen Seiten ohne viel Inhalt. */
.site-footer {
  display: flex; justify-content: center; align-items: center; gap: var(--space-4); flex-wrap: wrap;
  margin-top: auto;
  padding: var(--space-5) var(--space-4); font-family: var(--font-sans); font-size: var(--text-xs); color: var(--text-muted);
}
.site-footer a { color: var(--text-muted); text-decoration: none; }
.site-footer a:hover { color: var(--link); text-decoration: underline; }

/* Sprachschalter-Dropdown (Footer + Legal-/FAQ-Seiten) - ein echter <button>
   + Popover statt eines nativen <select> oder blosser Text-Links, im
   gleichen "Pillen+Popover"-Stil wie der Kammer-Umschalter in der Kopfzeile
   (siehe .environment-switcher-trigger) - passt dadurch optisch zum Rest
   der Seite statt wie ein Fremdkoerper zu wirken. */
.lang-switcher { position: relative; display: inline-flex; }
.lang-switcher-trigger {
  display: inline-flex; align-items: center; gap: var(--space-1); height: 28px;
  padding: 0 var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--text-muted); font-family: var(--font-sans);
  font-size: var(--text-xs); cursor: pointer; transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.lang-switcher-trigger:hover { background: var(--surface-hover); border-color: var(--text-muted); color: var(--text); }
/* Im Footer reicht die Flagge allein (kompakter neben den anderen, knappen
   Footer-Links) - der Sprachname bleibt trotzdem im Markup (fuer Screen-
   reader/falls die Flagge als Buchstaben-Fallback gerendert wird, siehe
   Windows) und erscheint ausserdem wieder ausgeschrieben in den Popover-
   Optionen sowie in der Kopf-Variante auf den Legal-/FAQ-Seiten (dort NICHT
   im Footer, deshalb ohne dieses Ausblenden). */
.site-footer .lang-switcher-name { display: none; }
.lang-switcher-chevron { width: 14px; height: 14px; flex-shrink: 0; }
.lang-switcher-popover {
  width: max-content; min-width: 160px; padding: var(--space-2);
}
/* display NUR im :popover-open-Zustand setzen, nie direkt auf [popover] -
   Autoren-CSS schlaegt die eigene UA-Regel "[popover]:not(:popover-open) {
   display: none }" IMMER, unabhaengig von Spezifitaet (Autoren- vor
   Browser-Herkunft in der Kaskade) - ein direktes "display: flex" auf
   dieser Klasse hatte das Popover dadurch auf jeder Seite dauerhaft
   sichtbar gemacht statt nur nach Klick auf den Trigger. */
.lang-switcher-popover:popover-open { display: flex; flex-direction: column; gap: var(--space-1); }
.lang-switcher-popover .lang-switcher-option {
  display: block; padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
  color: var(--text); text-decoration: none; font-family: var(--font-sans); font-size: var(--text-sm);
}
.lang-switcher-popover .lang-switcher-option:hover { background: var(--surface-hover); }
.lang-switcher-popover .lang-switcher-option.selected { background: var(--tag-bg); color: var(--link); font-weight: 600; }
/* Auf den Legal-/FAQ-Seiten steht der Schalter oben bei der Marke statt im
   Footer - etwas Abstand nach unten, damit er nicht am Seiteninhalt klebt.
   .brand und .lang-switcher sind beide inline-flex, aber OHNE gemeinsamen
   Flex-Elternteil - fliessen dadurch als normale Inline-Boxen nebeneinander
   und werden per Standard an der Text-BASELINE ausgerichtet, was bei
   unterschiedlich hohen Boxen (Logo+Serif-Schrift vs. kompakte Pille)
   sichtbar schief wirkt. vertical-align:middle behebt das ohne die beiden
   erst in einen eigenen Flex-Container packen zu muessen. */
.legal-card > a.brand, .legal-lang-switch-wrap { vertical-align: middle; }
.legal-lang-switch-wrap { margin: 0 0 var(--space-4); }

/* Impressum / Datenschutz */
.legal-page { display: flex; justify-content: center; min-height: 100vh; min-height: 100dvh; background: var(--bg); padding: var(--space-6) var(--space-4); }
.legal-card {
  width: 100%; max-width: 720px; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md); padding: var(--space-6); height: fit-content;
}
.legal-card > a.brand { margin-bottom: var(--space-5); }
.legal-content h1 { font-size: var(--text-2xl); margin: 0 0 var(--space-5); }
.legal-content h2 { font-size: var(--text-lg); margin: var(--space-5) 0 var(--space-2); }
.legal-content p, .legal-content ul { font-family: var(--font-sans); font-size: var(--text-sm); line-height: 1.6; color: var(--text); margin: 0 0 var(--space-3); }
.legal-content ul { padding-left: var(--space-5); }
.legal-content a { color: var(--link); }
.legal-translation-notice {
  font-family: var(--font-sans); font-size: var(--text-xs); color: var(--text-muted);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3); margin: 0 0 var(--space-4);
}

/* Admin: Benutzerverwaltung */
.users-table-scroll { overflow-x: auto; }
.users-table { width: 100%; min-width: 480px; border-collapse: collapse; margin: var(--space-4) 0; font-family: var(--font-sans); }
.users-table th, .users-table td { text-align: left; padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--border); }
.users-table th { color: var(--text-muted); font-weight: 600; font-size: var(--text-sm); }
/* Kompakte Variante: alles direkt sichtbar/editierbar in der Zeile, bewusst
   OHNE <details>/Dropdown-Verstecken - schmalere Zeilen, kleinere Schrift,
   damit trotz vieler Spalten (Prisen, zwei Limits, Passwort, Loeschen) moeglichst
   viel auf einen Blick sichtbar bleibt. */
.users-table-compact th, .users-table-compact td { padding: var(--space-1) var(--space-2); font-size: var(--text-sm); vertical-align: middle; }
.users-table-compact .hint { font-size: var(--text-xs); }
.compact-cell { white-space: nowrap; }
.compact-checkbox {
  display: inline-flex; align-items: center; gap: 2px; font-size: var(--text-xs); color: var(--text-muted);
  cursor: pointer; margin-right: var(--space-1);
}
.compact-checkbox input { width: auto; margin: 0; }
.icon-inline-checkbox { width: 14px; height: 14px; }
/* Die generischen "form input[type=...]"/"form button[type=submit]"-Regeln
   weiter unten sind mit (0,1,2) spezifischer als eine einzelne Klasse (0,1,0)
   und haetten sonst trotz dieser Klassen die volle Standardgroesse erzwungen -
   ueber .users-table-compact zusaetzlich verschachtelt (0,2,x) gewinnt das
   sicher, unabhaengig von der Regel-Reihenfolge im Stylesheet. */
.users-table-compact .compact-number { width: 56px; padding: 3px 6px; margin-bottom: 0; font-size: var(--text-xs); }
.users-table-compact .compact-password { width: 130px; padding: 3px 6px; margin-bottom: 0; font-size: var(--text-xs); }
.inline-icon-form { display: inline-flex; align-items: center; gap: var(--space-1); }
.users-table-compact .btn-icon-only {
  padding: 3px 7px; border-radius: var(--radius-sm); border: none;
  background: var(--btn-bg); color: var(--btn-text); cursor: pointer; font-size: var(--text-sm); line-height: 1;
  transition: background 0.15s;
}
.users-table-compact .btn-icon-only:hover { background: var(--btn-bg-hover); }
.users-table-compact .btn-icon-danger:hover { background: var(--danger-bg); color: var(--danger-text); }
.badge-admin {
  display: inline-block; background: var(--accent-sage-bg); color: var(--accent-sage);
  padding: 2px var(--space-2); border-radius: var(--radius-pill); font-size: var(--text-xs); font-family: var(--font-sans);
}
.badge-unverified {
  display: inline-block; background: var(--danger-bg); color: var(--danger-text);
  padding: 2px var(--space-2); border-radius: var(--radius-pill); font-size: var(--text-xs); font-family: var(--font-sans);
  margin-left: var(--space-1);
}
.admin-users-form { max-width: 420px; }
/* Formulare, die nur eine Checkbox+Label enthalten (kein schmales Textfeld
   noetig), sollen nicht auf 420px eingeschraenkt sein - der Label-Text soll
   in einer Zeile bleiben, solange das Panel breit genug ist. */
.checkbox-only-form { max-width: none; }
.checkbox-row { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); font-family: var(--font-sans); }
.checkbox-row input { width: auto; margin: 0; }
.checkbox-row label { cursor: pointer; }
/* Eigene Checkbox-Optik statt der winzigen Browser-Vorgabe (auf Mobil kaum
   treffsicher antippbar) - appearance:none macht aus der Checkbox eine ganz
   normale Box, auf der (anders als bei den meisten anderen Formularelementen)
   ::after in allen relevanten Browsern zuverlaessig funktioniert, damit sich
   das Haekchen rein per CSS zeichnen laesst (kein Icon-Font/SVG-Overlay noetig). */
.checkbox-row input[type=checkbox] {
  appearance: none; -webkit-appearance: none;
  width: 22px; height: 22px; flex-shrink: 0; margin: 0;
  border: 2px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 0.15s, border-color 0.15s;
}
.checkbox-row input[type=checkbox]:hover { border-color: var(--link); }
.checkbox-row input[type=checkbox]:checked {
  background: var(--btn-bg); border-color: var(--btn-bg);
}
.checkbox-row input[type=checkbox]:checked::after {
  content: "";
  width: 6px; height: 11px;
  border: solid var(--btn-text);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) translate(-1px, -1px);
}
.checkbox-row input[type=checkbox]:focus-visible {
  outline: 2px solid var(--link); outline-offset: 2px;
}

/* Profilbild */
.avatar-settings { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; margin-bottom: var(--space-2); }
.avatar-preview-wrap { position: relative; flex-shrink: 0; }
.avatar-preview {
  position: relative; width: 72px; height: 72px; border-radius: 50%; overflow: hidden;
  background: var(--btn-bg); color: var(--btn-text);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-serif); font-weight: 600; font-size: var(--text-2xl);
}
/* Absolut positioniertes <img> fuer applyFocalCrop() in app.js - object-fit
   hier nur als Fallback, bis JS width/height/transform passend zum Fokuspunkt
   setzt (siehe app.js fuer die Begruendung, warum object-position allein bei
   Punkten abseits von genau 50%/50% nicht exakt zentriert). Elternelement
   braucht dafuer position:relative + overflow:hidden in der Zielgroesse. */
.focal-img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; }
.avatar-edit-btn {
  position: absolute; bottom: -2px; right: -2px; width: 26px; height: 26px; padding: 0;
  border-radius: 50%; border: 2px solid var(--surface); background: var(--btn-bg);
  color: var(--btn-text); display: flex; align-items: center; justify-content: center;
  font-size: 12px; cursor: pointer;
}
.avatar-edit-btn:hover { background: var(--btn-bg-hover); }
.avatar-edit-btn .icon-btn { width: 14px; height: 14px; vertical-align: 0; }
.avatar-actions { display: flex; flex-direction: column; gap: var(--space-2); min-width: 260px; }
.avatar-upload-form { margin: 0; }
.avatar-upload-form input[type=file] { width: 100%; margin-bottom: 0; }
.avatar-button-row { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); }
.avatar-remove-form { margin: 0; }

/* Guthaben */
.credits-balance { font-family: var(--font-serif); font-size: var(--text-2xl); font-weight: 700; color: var(--link); margin: 0 0 var(--space-1); }
/* Einfache Textlinks innerhalb eines .panel (z.B. "Guthaben verwalten & aufladen →")
   - ohne diese Klasse faellt so ein Link sonst auf die Standard-Browserfarbe zurueck,
   da es keine globale a{}-Regel gibt (siehe .login-card a fuer denselben Fall). */
.panel-link { color: var(--link); text-decoration: none; font-weight: 600; }
.panel-link:hover { color: var(--link-hover); text-decoration: underline; }
/* Grid statt flex-wrap: die 3 Pakete bleiben immer in einer Reihe (schrumpfen
   gemeinsam auf schmalen Screens) statt bei wenig Platz umzubrechen. */
.topup-packages { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-2); margin-bottom: var(--space-4); }
.topup-package-btn {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-2); cursor: pointer; font-family: var(--font-sans); color: var(--text);
  transition: background 0.15s, border-color 0.15s;
  min-width: 0; /* erlaubt Schrumpfen im Grid, sonst kann der Inhalt die Spalte sprengen */
}
.topup-package-btn:hover { background: var(--surface-hover); border-color: var(--link); }
.topup-package-btn.active { background: var(--tag-bg); border-color: var(--link); }
.topup-package-btn strong { font-size: var(--text-lg); }
.topup-package-btn span { font-size: var(--text-xs); color: var(--text-muted); }
#topup-selected-hint { margin-bottom: var(--space-4); }
/* Nebeneinander statt untereinander, mit fester Mindesthoehe der Zeile - der
   Apple-Pay/Google-Pay-Button (#stripe-pr-button-container) taucht erst nach
   einem asynchronen canMakePayment()-Check auf/verschwindet wieder (siehe
   guthaben.html), das darf dabei aber keinen der anderen Buttons nach unten
   verschieben, deshalb min-height auf der Zeile statt variabler Hoehe. */
.topup-payment-methods {
  display: flex; flex-direction: row; flex-wrap: wrap; align-items: center;
  gap: var(--space-3); min-height: 44px;
}
#paypal-button-container, #stripe-pr-button-container, #stripe-checkout-btn {
  flex: 1 1 200px; min-width: 200px;
}
#stripe-checkout-btn { min-height: 44px; }


/* Rezept teilen - eigenes Website-Popup (natives [popover]-Attribut: rendert im
   Top-Layer, schliesst automatisch bei Klick daneben/Escape, ganz ohne eigenes JS
   dafuer) statt einer permanent sichtbaren Karte unten auf der Seite. */
.share-popover {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md); padding: var(--space-5) var(--space-4);
  width: min(640px, calc(100vw - var(--space-6))); max-height: min(85vh, 600px); overflow-y: auto;
  /* [popover]-Standardfarbe direkt am Container ueberschreiben statt nur bei
     einzelnen Kind-Elementen (wie bisher nur .share-popover-header h2) -
     siehe ausfuehrlicher Kommentar bei .confirm-popover weiter unten. */
  color: var(--text);
}
.share-popover::backdrop { background: rgba(0,0,0,.45); }
.share-popover-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-4); }
.share-popover-header h2 {
  /* [popover] setzt intern eine eigene Standard-Textfarbe, die die Vererbung
     von body ueberschreibt - ohne dieses Farb-Reset stand die Ueberschrift
     immer in reinem Schwarz statt der Theme-Textfarbe. */
  margin: 0; font-size: var(--text-lg); color: var(--text);
}
.share-popover-close {
  background: none; border: none; cursor: pointer; color: var(--text-muted); font-size: var(--text-xl);
  line-height: 1; padding: var(--space-1); flex-shrink: 0;
}
.share-popover-close:hover { color: var(--link); }

.confirm-popover {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md); padding: var(--space-5) var(--space-4);
  width: min(420px, calc(100vw - var(--space-6)));
  /* [popover] setzt intern eine eigene Standard-Textfarbe (color: CanvasText -
     ein SYSTEM-Farbwert, der dem OS-Farbschema folgt statt dem eigenen
     data-theme-Umschalter der App), die die Vererbung von body ueberschreibt -
     siehe auch .share-popover-header h2 weiter oben, wo genau das schon mal
     einzeln gefixt wurde. Hier direkt am Container gesetzt, damit das nicht
     bei jedem neuen Kind-Element (wie zuletzt .ingredient-text im Zutaten-
     Popup, dort im Darkmode faelschlich schwarz statt weiss) erneut vergessen
     werden kann - h2/p unten bleiben trotzdem bewusst explizit, fuer den
     Kontrast zwischen Titel/Text (--text vs. --text-muted). */
  color: var(--text);
}
.confirm-popover::backdrop { background: rgba(0,0,0,.45); }
.confirm-popover h2 { margin: 0 0 var(--space-2); font-size: var(--text-lg); color: var(--text); }
.confirm-popover p { margin: 0 0 var(--space-4); color: var(--text-muted); }
.confirm-popover-actions { display: flex; justify-content: space-between; gap: var(--space-3); }
/* #alert-popover hat nur einen einzelnen "OK"-Button - space-between wuerde
   ihn ganz nach links draengen (einziges Kind = Zeilenanfang), hier bewusst
   rechtsbuendig statt space-between. */
#alert-popover .confirm-popover-actions { justify-content: flex-end; }
.pwa-ios-guide-popover .confirm-popover-actions { justify-content: flex-end; }
.pwa-ios-guide-step { display: flex; align-items: center; gap: var(--space-3); }
.pwa-ios-guide-step p { margin: 0; color: var(--text); }
.pwa-ios-guide-icon {
  flex-shrink: 0; width: 44px; height: 44px; border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--link);
}
.pwa-ios-guide-icon svg { width: 24px; height: 24px; }
.pwa-ios-guide-arrow { display: flex; justify-content: center; margin: var(--space-2) 0; color: var(--text-muted); }
.pwa-ios-guide-arrow svg { width: 20px; height: 20px; }
.share-panel-columns { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-5); align-items: stretch; }
.share-col {
  /* bewusst kein eigener Rahmen/Padding (keine dritte Verschachtelungsebene) -
     Inhalt bleibt so buendig mit der "Teilen"-Ueberschrift. */
  min-width: 0;
}
.share-col h3 { margin: 0 0 var(--space-3); font-size: var(--text-sm); text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); }
.share-col .hint { margin-top: 0; }
.share-users-form { margin-bottom: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.share-users-form .pantry-chips { margin-bottom: 0; min-height: 0; }
.public-share-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-bottom: var(--space-3); }
.public-share-url {
  flex: 1; min-width: 160px; padding: var(--space-2) var(--space-3); border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--surface); color: var(--text); font-family: var(--font-sans);
  font-size: var(--text-sm);
}
.shared-badge {
  display: inline-block; background: var(--tag-bg); color: var(--tag-text);
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-pill);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600;
}

/* Öffentliche Rezeptansicht */
.public-topbar { justify-content: flex-start; }
.public-badge {
  margin-left: auto; color: var(--text-muted); font-family: var(--font-sans); font-size: var(--text-sm);
}

/* Detail page */
.detail, .panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-5) var(--space-6);
}
.panel + .panel { margin-top: var(--space-5); }
.panel h1, .panel h2 { margin-top: 0; }

/* Konto-Einstellungen: zweispaltig auf Desktop statt einer langen Liste
   einzelner Panels - .panel + .panel-Abstand wird durch grid-gap ersetzt. */
.account-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-5); }
.account-grid .panel + .panel { margin-top: 0; }
.account-panel-wide { grid-column: 1 / -1; }
@media (max-width: 900px) {
  .account-grid { grid-template-columns: 1fr; }
}
.back-link { font-family: var(--font-sans); color: var(--link); text-decoration: none; font-weight: 600; }
.back-link:hover { color: var(--link-hover); }
.detail-image { width: 100%; max-height: 400px; object-fit: cover; border-radius: var(--radius-lg); margin: var(--space-3) 0; box-shadow: var(--shadow-md); }

/* max-width, damit die Box nicht bei breiten Desktop-Fensterbreiten extrem
   querformatig wird (width waechst mit dem Container, height bleibt bei
   .detail-image auf max-height:400px gedeckelt) - genau das liess z.B.
   hochformatige Bilder object-fit:cover viel zu stark zuschneiden ("zu nah
   rangezoomt"), waehrend es auf schmalen Mobilbreiten (Box eher quadratisch)
   kaum auffiel. */
.detail-gallery { margin: var(--space-3) 0; max-width: 560px; margin-left: auto; margin-right: auto; }
.detail-gallery-main { position: relative; overflow: hidden; border-radius: var(--radius-lg); background: var(--surface-2); }
/* object-fit:contain statt cover - zeigt das ganze Bild an, auch wenn es
   nicht exakt ins Seitenverhaeltnis der Box passt (dann mit Rand statt
   Beschnitt), damit z.B. hochformatige Fotos nicht mehr oben/unten
   abgeschnitten werden. */
.detail-gallery-main .detail-image { margin: 0; display: block; border-radius: 0; box-shadow: none; object-fit: contain; }
.gallery-drag-wrap { width: 100%; overflow: hidden; touch-action: pan-y; }
.gallery-track { display: flex; width: 300%; transform: translateX(-33.3333%); }
.gallery-slide { flex: 0 0 33.3333%; }
.gallery-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px; border-radius: 50%; border: none; cursor: pointer;
  background: rgba(0,0,0,.5); color: #fff; font-size: var(--text-xl); line-height: 1;
  display: flex; align-items: center; justify-content: center; transition: background 0.15s;
}
.gallery-nav:hover { background: rgba(0,0,0,.75); }
.gallery-prev { left: var(--space-3); }
.gallery-next { right: var(--space-3); }
.gallery-counter {
  position: absolute; bottom: var(--space-3); right: var(--space-3);
  background: rgba(0,0,0,.55); color: #fff; font-size: var(--text-xs); font-family: var(--font-sans);
  padding: 2px var(--space-2); border-radius: var(--radius-pill);
}
.detail-gallery-thumbs { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-top: var(--space-2); }
.gallery-thumb {
  position: relative; width: 64px; height: 64px; padding: 0; border: 2px solid transparent;
  border-radius: var(--radius-sm); cursor: pointer;
  overflow: hidden; background: none; opacity: 0.6; transition: opacity 0.15s, border-color 0.15s;
}
.gallery-thumb:hover { opacity: 0.85; }
.gallery-thumb.active { opacity: 1; border-color: var(--link); }
.detail-gallery-main .detail-image { cursor: zoom-in; }

.lightbox-overlay {
  display: none; position: fixed; inset: 0; z-index: 1000;
  background: rgba(10,8,6,.92); align-items: center; justify-content: center;
  touch-action: none; overflow: hidden;
}
.lightbox-overlay.active { display: flex; }
.lightbox-drag-wrap { width: 100%; height: 100%; overflow: hidden; position: relative; }
.lightbox-track { display: flex; width: 300%; height: 100%; transform: translateX(-33.3333%); }
.lightbox-slide { flex: 0 0 33.3333%; display: flex; align-items: center; justify-content: center; height: 100%; }
.lightbox-image { max-width: 92vw; max-height: 92vh; object-fit: contain; border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); pointer-events: none; user-select: none; }
.lightbox-close {
  position: absolute; top: var(--space-4); right: var(--space-4); z-index: 10;
  width: 40px; height: 40px; border-radius: 50%; border: none; cursor: pointer;
  background: rgba(0,0,0,.5); color: #fff; font-size: var(--text-xl); line-height: 1;
  display: flex; align-items: center; justify-content: center; transition: background 0.15s;
}
.lightbox-overlay .gallery-nav, .lightbox-overlay .gallery-counter { z-index: 10; }
.lightbox-close:hover { background: rgba(0,0,0,.75); }
body.lightbox-open { overflow: hidden; }

@media (max-width: 640px) {
  .lightbox-overlay { background: #000; }
  .lightbox-image { max-width: 100vw; max-height: 100vh; border-radius: 0; box-shadow: none; }
  .lightbox-prev, .lightbox-next { display: none; }
  /* Kategorie-Pille und Tags untereinander statt links/rechts verteilt, wenn sie umbrechen */
  .detail-meta-secondary { flex-direction: column; align-items: flex-start; }
  .detail-tags.tags { justify-content: flex-start; }
  /* Box selbst per negativem Aussenrand teilweise ueber das Padding von .detail
     hinaus verbreitern (naeher an den Bildschirmrand) und ihr eigenes
     Innenpolster verkleinern, damit Portionen-Box und Kategorie/Tag-Chips
     insgesamt weiter nach aussen ruecken statt durch das grosse Desktop-Padding
     optisch mittig zu wirken. */
  .detail-meta-panel { padding: var(--space-2); margin-left: calc(-1 * var(--space-3)); margin-right: calc(-1 * var(--space-3)); }
  /* Alle Aktions-Buttons auf dem Handy nur als Icon (Text-Labels ausblenden),
     damit Bearbeiten/Teilen/Kategorien/Löschen in einer Reihe passen, statt
     umzubrechen oder den Löschen-Button separat abzusetzen.
     Bei langen Titeln bricht .detail-actions in eine eigene Zeile um, nimmt
     dabei aber ohne width:100% nur die Breite ihres Inhalts ein. */
  .detail-actions { width: 100%; }
  .btn-label, .btn-delete-label { display: none; }
  .btn-icon-label, .btn-delete {
    width: 40px; height: 40px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center; font-size: var(--text-lg);
  }
}
.detail-header { display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: var(--space-3); }
.detail-header h1 { font-size: var(--text-2xl); margin: var(--space-2) 0; overflow-wrap: break-word; hyphens: auto; }
.detail-actions { display: flex; align-items: center; gap: var(--space-2); font-family: var(--font-sans); flex-wrap: wrap; }
/* Hoehere Spezifitaet noetig als "form button[type=submit]" (padding/background/box-shadow),
   sonst sehen <button class="btn-danger/btn-secondary"> in Formularen (z.B. "Löschen") groesser
   und andersfarbig aus als <a class="btn-secondary">-Links wie "Bearbeiten". */
.btn-secondary, .btn-danger, form button.btn-secondary, form button.btn-danger {
  padding: var(--space-2) var(--space-4); border-radius: var(--radius-md); text-decoration: none; font-size: var(--text-sm); cursor: pointer; border: 1px solid var(--border);
  font-family: var(--font-sans); font-weight: 600; box-shadow: none; transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
}
.btn-secondary, form button.btn-secondary { background: var(--surface); color: var(--text); }
.btn-secondary:hover, form button.btn-secondary:hover { background: var(--surface-hover); box-shadow: var(--shadow-sm); transform: translateY(-1px); }
.btn-danger, form button.btn-danger { background: var(--danger-bg); color: var(--danger-text); border-color: var(--danger-border); }
.btn-danger:hover, form button.btn-danger:hover { filter: brightness(0.95); box-shadow: var(--shadow-sm); transform: translateY(-1px); }
.description { color: var(--text-muted); font-size: var(--text-lg); line-height: 1.5; text-align: center; }
.meta-row { display: flex; gap: var(--space-4); font-family: var(--font-sans); color: var(--text-muted); margin: var(--space-3) 0; flex-wrap: wrap; align-items: center; justify-content: space-between; }
.detail-times {
  display: flex; gap: var(--space-4); flex-wrap: wrap; margin-left: auto;
  /* gleicher linker Versatz wie .portion-stepper (1px Rahmen + Padding), damit die
     Icons darunter auf gleicher Hoehe wie das Portionen-Icon beginnen, wenn die
     Zeiten (z.B. auf dem Handy) unter die Portionen umbrechen */
  padding-left: calc(var(--space-2) + 1px);
}
.meta-icon { display: inline-block; width: 1.3em; text-align: center; }

.nutrition-panel { margin-top: var(--space-5); }
.nutrition-panel h2 { font-size: var(--text-xl); margin-bottom: 0; }
.nutrition-panel h2 .hint { font-size: var(--text-sm); font-weight: 400; margin-left: var(--space-2); }
.nutrition-panel-header {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3);
  flex-wrap: wrap; margin-bottom: var(--space-3);
}
.edit-page-header {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  flex-wrap: wrap;
}
.nutrition-stale-hint { display: flex; align-items: center; gap: var(--space-1); color: var(--danger-text); margin: calc(-1 * var(--space-2)) 0 var(--space-3); }
.nutrition-stale-hint .icon-btn { flex-shrink: 0; }
/* Grid statt flex-wrap: erzwingt immer genau 3 Ringe pro Reihe (unabhaengig von
   Viewport-Breite), statt dass die Anzahl von Ring-Breite+Luecke abhaengt und auf
   schmalen Handys nur 2 passen. Ring selbst prozentual/responsiv statt fester
   Pixelbreite, damit er die jeweilige Spaltenbreite ausfuellt (SVG-Naehrwert-Mathe
   in _recipe_view.html ist ueber viewBox ohnehin groessenunabhaengig). */
.nutrition-rings { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4) var(--space-3); }
@media (min-width: 641px) {
  /* Desktop hat genug Platz fuer alle Naehrwerte in einer Reihe statt der
     mobil erzwungenen 3 Spalten (siehe Commit zur "3 pro Reihe"-Anfrage). */
  .nutrition-rings { grid-template-columns: repeat(6, 1fr); }
}
.nutrition-ring { position: relative; width: 100%; max-width: 96px; margin: 0 auto; text-align: center; }
.nutrition-ring svg { display: block; width: 100%; height: auto; aspect-ratio: 1; transform: rotate(-90deg); }
.ring-track { fill: none; stroke: var(--surface-2); stroke-width: 6; }
.ring-fill { fill: none; stroke-width: 6; stroke-linecap: round; } /* Farbe kommt als stroke-Attribut pro Naehrwert, siehe _recipe_view.html */
/* Grid statt "alle 3 Zeilen als Block zentrieren": erste/dritte Zeile gleich
   gross (1fr), dadurch landet die mittlere Zeile (Einheit) exakt im
   Ring-Zentrum, mit Wert darueber und Prozent darunter symmetrisch angeordnet -
   ein einfaches justify-content:center haette stattdessen den GESAMTEN
   3-zeiligen Block zentriert, dessen Mitte wegen der groesseren Wert-Zeile
   nicht mit der Einheit zusammenfaellt. */
.ring-center {
  /* aspect-ratio:1 statt height:100% - .nutrition-ring ist durch das Label
     darunter hoeher als der Kreis selbst, height:100% wuerde die Mitte dieser
     Box also unter die tatsaechliche Kreismitte verschieben. */
  position: absolute; top: 0; left: 0; width: 100%; aspect-ratio: 1;
  /* grid-template-columns: 1fr ist Pflicht hier - ohne explizite Spalte packt
     die Grid-Auto-Platzierung alle 3 Spans nebeneinander in Zeile 1 statt sie
     auf die 3 Zeilen zu verteilen (genau das Layout-Problem im Screenshot). */
  display: grid; grid-template-columns: 1fr; grid-template-rows: 1fr auto 1fr; justify-items: center;
}
.ring-value { align-self: end; font-weight: 700; font-size: var(--text-base); line-height: 1.15; }
.ring-unit { align-self: center; font-size: var(--text-xs); color: var(--text-muted); line-height: 1.15; }
.ring-pct { align-self: start; font-size: var(--text-xs); font-weight: 600; line-height: 1.15; margin-top: 2px; }
.ring-label { margin-top: var(--space-2); font-size: var(--text-sm); line-height: 1.3; }

.detail-columns { display: grid; grid-template-columns: 1fr 2fr; gap: var(--space-6); margin-top: var(--space-5); }
.ingredients h2, .steps h2 { font-size: var(--text-xl); }
.ingredients ul { list-style: none; padding: 0; }
.ingredients li { padding: var(--space-2) 0; border-bottom: 1px dashed var(--border); }
.ingredient-row { display: flex; align-items: flex-start; gap: var(--space-2); cursor: pointer; }
.ingredient-row input[type=checkbox] { flex-shrink: 0; margin-top: 4px; }
.ingredient-text { flex: 1; min-width: 0; }
.ingredients .ing-amount { color: var(--link); }
.steps ol { padding-left: var(--space-5); }
.steps li { margin-bottom: var(--space-4); line-height: 1.6; }
.source-line { margin-top: var(--space-6); font-family: var(--font-sans); font-size: var(--text-sm); color: var(--text-muted); }
.source-line a { color: var(--link); overflow-wrap: anywhere; word-break: break-word; }
.ai-notes { font-style: italic; }

/* Portionsrechner */
.portion-stepper {
  display: inline-flex; align-items: center; gap: var(--space-2);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-1) var(--space-1) var(--space-2);
}
.portion-btn {
  width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text); cursor: pointer; font-size: var(--text-base);
  line-height: 1; display: inline-flex; align-items: center; justify-content: center;
  transition: background 0.12s, color 0.12s;
}
/* @media (hover: hover) statt einfachem :hover, sonst bleibt der Button auf
   Touch-Geraeten nach dem Antippen "haengen" (Mobilbrowser simulieren :hover
   beim Tap und loesen es erst beim naechsten Tap woanders wieder). */
@media (hover: hover) {
  .portion-btn:hover { background: var(--link); color: var(--btn-text); border-color: var(--link); }
}
/* :active statt :hover fuers Tastendruck-Feedback auf dem Handy - faerbt sich beim
   Antippen kurz ein und geht dank der obigen transition beim Loslassen sanft mit
   minimaler Verzoegerung zurueck, bleibt aber (anders als :hover) nie haengen. */
.portion-btn:active { background: var(--link); color: var(--btn-text); border-color: var(--link); }
#portion-count { font-weight: 700; color: var(--text); }

/* Edit form */
.edit-form label { display: block; margin: var(--space-4) 0 var(--space-1); font-family: var(--font-sans); font-weight: 600; font-size: var(--text-sm); }
.row3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--space-3); }
/* 2 statt 3 Spalten wie .row3 - die Naehrwerte-Bezeichnungen ("Kohlenhydrate
   (g)") sind laenger als "Portionen"/"Vorbereitung", bei 3 Spalten waeren die
   Felder auf schmalen Bildschirmen zu schmal. */
.nutrition-edit-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
/* ".nutrition-edit-grid .nutrition-edit-label" statt nur ".nutrition-edit-label" -
   sonst gewinnt die generische ".edit-form label"-Regel (1 Klasse + 1
   Element schlaegt bei Spezifitaets-Gleichstand 1 Klasse hier) und der
   ungewollte obere Abstand von dort bleibt bestehen. */
.nutrition-edit-grid .nutrition-edit-label { display: block; margin: 0 0 var(--space-1); font-family: var(--font-sans); font-weight: 600; font-size: var(--text-sm); }
.form-actions { display: flex; gap: var(--space-3); margin-top: var(--space-4); align-items: center; flex-wrap: wrap; }

/* Zutaten-/Schritte-Editor: Zeilen mit Drag-Griff, einzelnen Feldern und Entfernen-Button */
.ingredient-edit-list, .step-edit-list { display: flex; flex-direction: column; gap: var(--space-1); }
.sortable-row {
  display: flex; align-items: center; gap: var(--space-2);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 4px var(--space-2);
}
.drag-handle {
  flex-shrink: 0; cursor: grab; color: var(--text-muted); font-size: var(--text-base);
  padding: 0 var(--space-1); user-select: none; touch-action: none;
}
.drag-handle:active { cursor: grabbing; }
.ingredient-edit-row input[type=text], .step-edit-row textarea {
  margin-bottom: 0; padding: 6px var(--space-2);
}
.ingredient-edit-row input[name=ingredient_amount] { flex: 0 0 70px; }
.ingredient-edit-row input[name=ingredient_unit] { flex: 0 0 90px; }
.ingredient-edit-row input[name=ingredient_name] { flex: 1; min-width: 0; }
.step-edit-row .step-number {
  flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%; background: var(--tag-bg); color: var(--tag-text);
  display: flex; align-items: center; justify-content: center; font-family: var(--font-sans); font-size: var(--text-xs); font-weight: 700;
}
.step-edit-row textarea {
  flex: 1; min-width: 0; resize: none; overflow: hidden; line-height: 1.4;
}
.row-remove-btn {
  flex-shrink: 0; background: none; border: none; color: var(--text-muted); cursor: pointer;
  font-size: var(--text-lg); line-height: 1; padding: 0 var(--space-1); transition: color 0.15s;
}
.row-remove-btn:hover { color: var(--danger-text); }
.add-row-btn {
  display: block; width: 100%; margin-top: var(--space-2); padding: var(--space-2);
  background: none; border: 1px dashed var(--border); border-radius: var(--radius-md);
  color: var(--text-muted); font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600;
  cursor: pointer; transition: border-color 0.15s, color 0.15s;
}
.add-row-btn:hover { border-color: var(--link); color: var(--link); }

@media (max-width: 640px) {
  .ingredient-edit-row input[name=ingredient_amount] { flex: 0 0 56px; }
  .ingredient-edit-row input[name=ingredient_unit] { flex: 0 0 64px; }
}

/* Bild-Picker (Edit-Modus) */
.image-picker { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-2); margin-bottom: var(--space-3); font-family: var(--font-sans); }
.image-picker-item {
  position: relative; padding: 0; border: 2px solid transparent; border-radius: var(--radius-sm);
  overflow: hidden; background: var(--surface-2); transition: border-color 0.15s, transform 0.15s;
}
/* Nur auf Geraeten mit echtem Hover anheben - sonst "hovert" ein Finger beim
   Vorbeischieben ueber Nachbarkacheln waehrend des Ziehens und der Hub flackert. */
@media (hover: hover) {
  .image-picker-item:hover { transform: translateY(-2px); }
}
.image-picker-item.selected { border-color: var(--link); }
/* aspect-ratio statt fester Pixelhoehe, damit die Vorschau exakt dem 4:3-
   Zuschnitt entspricht, den der Bild-Cropper fuer Rezeptbilder nutzt (siehe
   openImageCropper-Aufrufe in edit.html) - eine feste Hoehe unabhaengig von
   der (durch die Spaltenzahl variierenden) Breite hatte hier ein anderes
   Seitenverhaeltnis gezeigt als tatsaechlich zugeschnitten wird. Sitzt auf
   dem Button (statt dem <img> wie frueher), weil applyFocalCrop() in app.js
   das Bild selbst absolut positioniert (siehe .focal-img) und deshalb keine
   eigene Groesse mehr in den normalen Layout-Fluss einbringt. */
.image-picker-select {
  position: relative; display: block; width: 100%; aspect-ratio: 4 / 3;
  padding: 0; border: none; background: none; cursor: pointer; overflow: hidden;
}
.cover-badge {
  position: absolute; bottom: 0; left: 0; right: 0; background: rgba(0,0,0,.6); color: #fff;
  font-size: var(--text-xs); text-align: center; padding: 2px 0;
}
.cover-badge-hint { opacity: 0; transition: opacity 0.15s; }
.image-picker-item:hover .cover-badge-hint { opacity: 1; }
.image-picker-remove {
  position: absolute; top: 4px; right: 4px; z-index: 2; width: 22px; height: 22px; line-height: 20px;
  border: none; border-radius: 50%; background: rgba(0,0,0,.6); color: #fff; font-size: var(--text-sm);
  cursor: pointer; padding: 0; text-align: center;
}
.image-picker-remove:hover { background: var(--link); }
.image-picker-edit {
  position: absolute; top: 4px; right: 30px; z-index: 2; width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 50%; background: rgba(0,0,0,.6); color: #fff; font-size: 11px;
  cursor: pointer; padding: 0;
}
.image-picker-edit .icon-btn { vertical-align: 0; }
.image-picker-edit:hover { background: var(--link); }
.image-drag-handle {
  position: absolute; top: 4px; left: 4px; z-index: 3; width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center; border-radius: 50%;
  background: rgba(0,0,0,.6); color: #fff; font-size: var(--text-md); cursor: grab;
  /* verhindert, dass der Browser die Geste als Seiten-Scroll interpretiert -
     sonst kommt auf Touch-Geraeten kein pointermove fuers Ziehen an */
  touch-action: none;
  user-select: none;
}
/* Waehrend des Ziehens wird das ECHTE Element schwebend positioniert (nicht ein
   separat gestylter Klon) - so sieht es beim Bewegen exakt so aus wie sonst auch.
   scale+shadow als kleines "Anheben"-Feedback, sobald der Griff aktiv ist -
   left/top werden waehrend des Ziehens per JS gesetzt (siehe makePointerSortable),
   transform ist dafuer frei und kollidiert nicht damit. */
.dragging-live {
  position: fixed; z-index: 1000; pointer-events: none; margin: 0;
  transform: scale(1.04);
  box-shadow: var(--shadow-lg);
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.drag-placeholder { background: var(--surface-2); border: 1px dashed var(--border); box-sizing: border-box; }
/* .drag-handle selbst hatte schon user-select:none - das reicht auf Touch-
   Geraeten aber nicht, sobald der Zeiger beim Ziehen ueber ANDERE Zeilen
   wandert: deren Text wird trotz Pointer-Capture vom Browser als eigene,
   parallele Touch-Geste markiert (fuehlte sich dadurch "klebrig"/unsauber an,
   obwohl das Umsortieren selbst funktionierte). Waehrend .dragging-active
   (wird in makePointerSortable beim Start/Ende des Ziehens gesetzt) daher
   fuer den kompletten Container inkl. aller Kind-Elemente gesperrt - Eingabe-
   felder bleiben davon unberuehrt, Browser nehmen <input>/<textarea> von
   einer per Vorfahren gesetzten user-select-Sperre grundsaetzlich aus. */
.sortable-container.dragging-active,
.sortable-container.dragging-active * {
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
.image-upload-row { display: flex; gap: var(--space-2); align-items: center; margin-bottom: var(--space-5); flex-wrap: wrap; font-family: var(--font-sans); }
.image-upload-row input[type=file] { width: auto; margin-bottom: 0; flex: 1; min-width: 200px; }

/* "Was kann ich kochen?" */
.pantry-input { display: flex; gap: var(--space-2); margin-bottom: var(--space-3); flex-wrap: wrap; }
.pantry-input .tag-autocomplete { flex: 1; min-width: 200px; }

.pantry-quantity-picker {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: var(--space-3); margin-bottom: var(--space-4); font-family: var(--font-sans); font-size: var(--text-sm);
}
.pantry-quantity-picker.hidden { display: none; }
#pantry-quantity-label { font-weight: 600; }
.pantry-quantity-chips { display: flex; gap: var(--space-1); flex-wrap: wrap; }
.pantry-qty-chip, .pantry-quantity-picker button {
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-pill); border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text); cursor: pointer; font-family: var(--font-sans); font-size: var(--text-sm);
  transition: background 0.12s, color 0.12s;
}
.pantry-qty-chip:hover { background: var(--link); color: var(--btn-text); border-color: var(--link); }
#pantry-quantity-custom {
  width: auto; margin-bottom: 0; flex: 1; min-width: 120px;
  padding: var(--space-2) var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-md);
  font-family: var(--font-sans); background: var(--surface); color: var(--text);
  font-size: var(--text-base); /* verhindert iOS-Auto-Zoom beim Fokussieren */
}
#pantry-quantity-custom:focus { outline: none; border-color: var(--link); box-shadow: 0 0 0 3px var(--tag-bg); }

.pantry-chips { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-bottom: var(--space-5); }
.pantry-chip {
  display: inline-flex; align-items: center; gap: var(--space-1);
  background: var(--tag-bg); color: var(--tag-text); border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600;
}
.pantry-chip button {
  background: none; border: none; color: inherit; cursor: pointer; font-size: var(--text-base);
  line-height: 1; padding: 0 var(--space-1);
}
.pantry-match { color: var(--accent-sage); font-weight: 600; }
.pantry-missing { display: block; font-size: var(--text-xs); color: var(--text-muted); margin-top: var(--space-1); }

/* Responsive */
@media (max-width: 960px) {
  .menu-toggle { display: inline-flex; align-items: center; justify-content: center; }
  .sidebar {
    position: fixed; top: var(--topbar-height); bottom: 0; left: -100%;
    width: 80vw; max-width: 320px; z-index: 25;
    box-shadow: var(--shadow-lg); transition: left .2s ease; overflow-y: auto;
  }
  .sidebar.open { left: 0; }
  .sidebar-resize-handle { display: none; }
  .sidebar-overlay.visible {
    display: block; position: fixed; inset: 0; top: var(--topbar-height);
    background: var(--overlay); z-index: 20;
  }
  /* PayPal-Button (und der Apple-Pay/Google-Pay-Button) auf /guthaben laufen
     in einem iframe, das PayPal/Stripe intern mit einem sehr hohen z-index
     versieht - das ueberlagert sonst die mobile Seitenleiste, egal welchen
     z-index die Seitenleiste selbst bekommt. Zuverlaessiger, die
     Zahlungs-Buttons waehrend die Seitenleiste offen ist (body.sidebar-open-
     mobile, siehe app.js) einfach auszublenden, statt gegen fremde z-index-
     Werte anzutreten. */
  body.sidebar-open-mobile .topup-payment-methods { visibility: hidden; }
}

@media (max-width: 640px) {
  /* Weniger Grid-Abstand und Karten-Innenpolster auf schmalen Screens, damit
     Kategorie-Pille und Tags in der 2-spaltigen Mobilansicht mehr nutzbare
     Breite haben (die dynamische Tag-Kuerzung in app.js zeigt dann automatisch
     mehr Tags an, statt frueher auf "…" zu kuerzen). */
  .recipe-grid { grid-template-columns: 1fr 1fr; gap: var(--space-3); }
  .recipe-card-body { padding: var(--space-3); }
  /* Gesamtdauer zieht auf Mobil als Pille aufs Bild um (siehe .recipe-card-
     time-badge) - Text-Variante in der Meta-Zeile ausblenden, sonst stuende
     sie doppelt da. Kategorie-Pille bleibt dort und rutscht jetzt an den
     rechten Rand, wo vorher (mit space-between) die Dauer links stand. */
  .recipe-card-time-badge { display: inline-block; }
  .recipe-card-meta-row .meta { display: none; }
  .recipe-card-meta-row { justify-content: flex-start; }
  /* Feste Kartenhoehe auf Mobil statt Grid-Stretch (align-items:stretch haengt
     sonst von der jeweils hoechsten Nachbarkarte in derselben Zeile ab - bei
     unterschiedlich langen Titeln/Tag-Mengen fuehrte das zu den Problemen der
     letzten Versuche). Tags werden auf Mobil komplett ausgeblendet (dafuer
     laesst sich der Titel jetzt auf 3 statt 2 Zeilen kappen) - dadurch bleibt
     die Karte auch ohne Tags/dynamisches Nachmessen zuverlaessig fix, kein
     app.js-Zutun mehr noetig. Kleinere Kartenhoehe als vorher (kein
     Tag-Bereich mehr, dafuer eine Titelzeile mehr). */
  .recipe-card { height: 300px; }
  .recipe-card-body h3 {
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .recipe-card .tags { display: none; }
  /* Weniger Innenabstand unten als oben/seitlich - weniger Leerraum am
     Kartenende. */
  .recipe-card-body { padding-bottom: var(--space-1); }
  /* Ersetzt die ausgeblendeten Tags - grobe KI-Schaetzung, siehe
     Recipe.nutrition. Kleinerer Abstand zur Kategorie-Zeile darueber. */
  .recipe-card-calories { display: block; font-size: var(--text-sm); margin-top: var(--space-1); }
  .recipe-card-calories .hint { font-size: var(--text-xs); }
  .detail, .panel { padding: var(--space-4); }
  .share-popover { padding: var(--space-4) var(--space-3); width: calc(100vw - var(--space-4)); }
  .share-panel-columns { gap: var(--space-6); }
  .tabs { flex-wrap: wrap; gap: 0; }
  .tab-btn { padding: var(--space-2) var(--space-2); font-size: var(--text-sm); }
  .detail-columns { grid-template-columns: 1fr; }
  .share-panel-columns { grid-template-columns: 1fr; gap: var(--space-5); }
  .row3 { grid-template-columns: 1fr; }
  .search-bar { flex-direction: column; }
  .topbar { padding: 0 var(--space-3); }
  .container { padding: var(--space-4); }
  .image-picker { grid-template-columns: repeat(3, 1fr); }
  /* Auf Mobil gibt es dafuer den eigenen "Kammer"-Tab in der Tab-Leiste
     unten (siehe bottom-tab-bar) - der Menuepunkt im Profil-Dropdown waere
     hier nur Dopplung. */
  .environment-switcher-dropdown-item { display: none; }
  .theme-toggle {
    position: static;
    margin-left: auto;
    width: 40px; height: 40px;
    box-shadow: none;
  }
  /* Nur das Profilbild zeigen, Name/Guthaben ausblenden - spart Platz in der
     schmalen Kopfzeile, das Dropdown mit allen Einstellungen bleibt per Klick
     aufs Profilbild trotzdem erreichbar. */
  .topbar-user-info { display: none; }
  .topbar-user-toggle { padding: 0; }
  .topbar-user-avatar { width: 40px; height: 40px; }
  /* Zentriert (Desktop) wuerde auf schmalen Screens leicht rechts ueber den
     Viewport-Rand hinausragen - hier stattdessen fixed am Bildschirmrand statt
     relativ zum (eingerueckten) Profilbild-Toggle, buendig mit right:0. Vom
     UNTEREN Rand aus verankert (bottom statt top) - der Profil-Trigger sitzt
     jetzt in der Tab-Leiste unten, nicht mehr oben (siehe Experiment unten). */
  .topbar-user-menu {
    position: fixed; min-width: 180px;
    top: auto; bottom: calc(var(--bottom-tab-height) + 1px + var(--space-2));
    left: auto; right: var(--space-3); transform: none;
  }

  /* App-uebliche Tab-Leiste unten ergaenzt die Kopfzeile oben (nicht mehr
     komplett ausgeblendet wie im urspruenglichen Experiment - Nutzer-Feedback:
     Marke/Logo soll ganz oben sichtbar bleiben). Menu-Toggle/Theme-Toggle/
     topbar-right (Kammer-Umschalter, "+"-Button, Profil) bleiben trotzdem
     ausgeblendet, weil es fuer alle drei bereits ein Aequivalent unten in der
     Tab-Leiste bzw. im Profil-Dropdown gibt - sonst stuenden Profilbild/"+"
     doppelt da. Uebrig bleibt nur .brand (Logo + Name) - zentriert statt
     linksbuendig (Nutzer-Wunsch), da es jetzt das einzige Element der Zeile ist. */
  .menu-toggle, .theme-toggle, .topbar-right { display: none; }
  .topbar { justify-content: center; }
  .container {
    min-height: calc(100vh - var(--bottom-tab-height));
    min-height: calc(100dvh - var(--bottom-tab-height)); /* siehe .app-shell weiter oben */
    padding-bottom: calc(var(--space-4) + var(--bottom-tab-height) + 1px);
  }
  .bottom-tab-bar {
    display: flex; align-items: stretch; justify-content: space-around;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    /* Fester Wert statt + env(safe-area-inset-bottom): auf Android Chrome
       (Nutzer-Screenshot-Vergleich) waechst dieser Wert sichtbar, sobald die
       browsereigene Werkzeugleiste unten einklappt (mehr sichtbare Hoehe ->
       groesserer Inset), wodurch unsere Leiste dann TIEFER reicht als die
       Trennstriche (die nur bis --bottom-tab-height gehen) - sichtbar als
       "leerer" Streifen, der je nach Werkzeugleisten-Zustand unterschiedlich
       gross ist. Fester 12px-Puffer bleibt in beiden Zustaenden gleich (genau
       der Wunsch: Leiste endet immer an derselben Stelle wie im Bild mit
       sichtbarer Browser-Werkzeugleiste). Auf iPhones mit Home-Indicator ist
       das etwas knapper als der "echte" Geraete-Inset (oft ~34px) - falls das
       dort zu eng wirkt, einfach Bescheid geben. */
    height: calc(var(--bottom-tab-height) + 1px);
    /* Links/rechts unveraendert dynamisch: betrifft die Rundung an den
       Bildschirm-ECKEN (iPhone im Querformat), nicht das Werkzeugleisten-
       Ein-/Ausblenden unten, daher von diesem Problem nicht betroffen. */
    padding-bottom: 1px;
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
    background: var(--chrome-bg); border-top: 1px solid var(--border);
  }
  .bottom-tab-link {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px; flex: 1; padding: 0; padding-bottom: 5px; color: var(--text-muted); text-decoration: none;
    background: none; border: none; font-family: var(--font-sans); cursor: pointer;
  }
  /* Duenne Trennstreifen zwischen den Tabs fuer klarere Abgrenzung - vor dem
     ersten Tab keiner (dort wuerde er direkt auf der Bildschirmkante sitzen). */
  .bottom-tab-link:not(:first-child) { border-left: 1px solid var(--border); }
  .bottom-tab-link.active { color: var(--link); }
  /* .bottom-tab-icon sitzt jetzt direkt auf dem <svg> (statt frueher auf
     einem <span> um ein Emoji-Zeichen) - ohne explizite Breite/Hoehe wuerde
     ein <svg> ohne eigene width/height-Attribute auf seine CSS-Default-
     Groesse (300x150) aufblasen. flex-shrink:0 aus demselben Grund wie beim
     "+"-Button (schmale Flex-Zelle liesse es sonst in der Breite schrumpfen). */
  .bottom-tab-icon { width: 22px; height: 22px; flex-shrink: 0; }
  .bottom-tab-label { font-size: 10px; font-weight: 600; }
  .bottom-tab-label { font-size: 10px; font-weight: 600; }
  /* "+"-Kachel etwas angehoben/prominenter als die anderen vier - uebliches
     Muster in App-Tab-Leisten fuer die primaere Aktion. Der Ring dahinter ist
     ein ::before am AEUSSEREN Link (nicht am Icon selbst) - ein negativer
     z-index auf einem Pseudo-Element des Icons faellt per Spezifikation immer
     VOR (ueber) den eigenen Hintergrund des Elements, das haette das Icon
     selbst eingefaerbt statt nur einen Ring dahinter zu legen. Als Geschwister
     am Link reicht die normale DOM-Reihenfolge (::before zuerst = unten, das
     echte Icon-Element danach = oben), kein z-index noetig. */
  .bottom-tab-add { position: relative; }
  .bottom-tab-add::before {
    content: "";
    position: absolute; top: calc(50% - 10px); left: 50%;
    transform: translate(-50%, -50%);
    width: 75px; height: 75px; border-radius: 50%;
    background: var(--chrome-bg);
  }
  .bottom-tab-add-icon {
    position: relative;
    width: 44px; height: 44px; margin-top: -10px;
    display: flex; align-items: center; justify-content: center;
    background: var(--btn-bg); color: var(--btn-text); border-radius: 50%;
    font-size: 26px; font-weight: 700; line-height: 1; box-shadow: var(--shadow-md);
  }
  .bottom-tab-add-icon svg { width: 24px; height: 24px; }
  /* Ohne das faerbt der Browser beim Antippen per Standard-Tap-Highlight die
     GANZE Link-Flaeche (die komplette Tab-Zelle, nicht nur den runden
     Button) grau ein - hier stattdessen deaktiviert und durch eine gezielte
     :active-Regel nur auf dem runden Icon selbst ersetzt. */
  .bottom-tab-add { -webkit-tap-highlight-color: transparent; }
  .bottom-tab-add:active .bottom-tab-add-icon { background: var(--btn-bg-hover); }
  .bottom-tab-avatar {
    /* position:relative noetig als Verankerung fuer .focal-img (position:
       absolute; width/height:100%) - ohne die haette sich das Bild relativ
       zur naechsten positionierten Vorfahren-Box aufgeblaeht (hier die
       fixed positionierte .bottom-tab-bar), statt nur den 24x24-Kreis zu
       fuellen. */
    position: relative;
    width: 24px; height: 24px; border-radius: 50%; overflow: hidden;
    display: flex; align-items: center; justify-content: center;
    background: var(--btn-bg); color: var(--btn-text); font-family: var(--font-serif);
    font-weight: 600; font-size: var(--text-sm);
  }
}
