/* ================================================
   Schulprozess – Global Stylesheet
   Ausgelagert aus layout.php
   ================================================ */

/* [hidden] heisst verborgen — auch wenn eine Klasse display setzt. Ohne diese
   Regel schlaegt z. B. .form-group{display:flex} das hidden-Attribut, und
   ein Feld, das per JavaScript verborgen sein soll, steht sichtbar da. Seit
   dem 29.09.2026 ersetzt hidden die Inline-Angabe display:none. */
[hidden] { display: none !important; }

/* Visually hidden but accessible (for file inputs the server strips display:none from) */
.visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;border:0!important;}

/* --- Manrope + Inter: lokal gehostet (DSGVO) --- */
@font-face {
    font-family: 'Manrope';
    font-style: normal;
    font-weight: 400 800;
    font-display: swap;
    src: url('fonts/manrope-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 600;
    font-display: swap;
    src: url('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;
}

/* --- Material Symbols: lokal gehostet --- */
@font-face {
    font-family: 'Material Symbols Outlined';
    font-style: normal;
    font-weight: 100 700;
    font-display: block;
    src: url('fonts/material-symbols-outlined.woff2') format('woff2');
}

.material-symbols-outlined {
    font-family: 'Material Symbols Outlined';
    font-weight: normal;
    font-style: normal;
    font-size: 24px;
    line-height: 1;
    letter-spacing: normal;
    text-transform: none;
    display: inline-block;
    white-space: nowrap;
    word-wrap: normal;
    direction: ltr;
    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-feature-settings: 'liga';
    font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}

:root {
    --maske: #000;   /* nur Deckkraft fuer mask-image, keine sichtbare Farbe */
    --feld-mindest: 0px;   /* unter Beruehrung 16px (app/beruehrung.css) — iOS zoomt darunter */
    /* --- Design-System: Digital Curator --- */
    /* Primary */
    --primary: #005394;
    --primary-hover: #004881;
    --primary-light: #d3e4ff;
    --primary-container: #2b6cb0;
    /* Schrift auf dem Akzent. Steht seit jeher in DESIGN.md, fehlte hier —
       deshalb schrieben die Rubriken #fff hin, wo dieses Token gemeint war.
       Daran entscheidet sich das dunkle Thema: colorsDark setzt den Wert auf
       #0a2540. Ohne das Token waere die Schrift auf einem hellblauen Knopf
       dann weiss auf hell. Nachgetragen 25.09.2026, keine Erweiterung der
       Palette. */
    --on-primary: #ffffff;

    /* Surfaces (Tonal Layering) */
    --bg: #F7F9FD;
    --card: #ffffff;
    --surface-low: #f2f4f8;
    --surface-container: #eceef2;
    --surface-high: #e6e8ec;

    /* Borders: Ghost-Borders statt harter Linien */
    --border: rgba(193, 199, 210, 0.35);
    --border-light: rgba(193, 199, 210, 0.25);

    /* Text: kein reines Schwarz */
    --text: #181c1f;
    --text-dark: #181c1f;
    --text-mid: #2D3748;
    --text-muted: #414750;
    /* 4.5:1 auf Weiss und auf gedaempfter Flaeche. Der frueher hier stehende
       Ton #727782 erreichte 4.49:1 auf Weiss und nur 4.08:1 im Tabellenkopf —
       das ist der Grund, warum Labels und Spaltenkoepfe schwer lesbar waren. */
    --text-light: #676b75;

    /* Backgrounds */
    --bg-subtle: #F7F9FD;
    --bg-light: #f2f4f8;

    /* Shadows: ambient, weich */
    --shadow-sm: 0 1px 3px rgba(24, 28, 31, 0.04);
    --shadow-md: 0 4px 16px rgba(24, 28, 31, 0.06);
    --shadow-lg: 0 24px 48px rgba(24, 28, 31, 0.06);

    /* Radii */
    --radius: 8px;
    --radius-lg: 12px;
    --radius-xl: 16px;

    /* Der kraeftige Rand: Fussbalken von Feldern, Ankreuzfelder. DESIGN.md
       fuehrt ihn seit dem 24.09.2026 im Vorspann („line-strong"), das
       Stylesheet kannte ihn nicht — wer ihn brauchte, schrieb die Zahl hin. */
    --line-strong: #7f858f;

    /* --- Rollen, die DESIGN.md fuehrt und die hier fehlten (28.09.2026) ---
       Im hellen Thema tragen sie dieselben Werte wie Token weiter oben. Das
       ist kein Grund, sie wegzulassen: IM DUNKLEN THEMA GEHEN SIE AUSEINANDER.
       colorsDark setzt --hero-from auf #0d4478, waehrend --primary dort zu
       #8dbdf5 wird. Wer den Verlauf der Kopfzone heute aus --primary baut,
       bekommt spaeter eine hellblaue Kopfzone mit weisser Schrift darauf.
       Dasselbe gilt fuer die Schrift auf dem Verlauf (--on-hero) und fuer den
       Knopf darin (--hero-btn-ink), der im Dunklen die Blau-Beziehung
       mitdreht. */
    --focus: #005394;
    --hero-from: #005394;
    --hero-to: #2b6cb0;
    --on-hero: #ffffff;
    --hero-btn-ink: #005394;

    /* Die Markierung „das betrifft Sie" und die Modulfarbe der Kopfzone —
       beide in DESIGN.md als eigene Rollen benannt, damit sie sich spaeter
       unabhaengig von Achtung bzw. Akzent bewegen koennen. */
    --marker: #fef9c3;
    --marker-ink: #854d0e;
    --mod: #d3e4ff;
    --mod-ink: #005394;

    /* --- Statusfarben, normativ ---
       DESIGN.md erklaert den Satz OHNE Praefix fuer massgebend, aber Status
       gab es bisher nur mit Praefix — der normative Satz konnte einen Zustand
       gar nicht ausdruecken, und wer einen brauchte, schrieb die Zahl hin.
       Die Namen sind die aus DESIGN.md (Waschung + Tinte). Kein Wert aendert
       sich.
       24.09.2026, beim Aufraeumen des Kalenders. */
    /* DIE WASCHUNGEN SIND AM 28.09.2026 AUSGETAUSCHT. Hier standen vier
       Toene, die die Anwendung fast nirgends benutzte (#fee2e2, #dcfce7,
       #fef9c3, #dbeafe: 4- bis 15-mal), waehrend ein zweiter, weicherer Satz
       16- bis 38-mal im Code stand. Uebernommen ist jetzt der weiche Satz —
       die Flaeche ist das, was man sieht — mit den dunkleren Tinten von hier.
       Der weiche Satz brachte bei Erfolg eigene 4,49:1 mit und verfehlte
       4,5:1 um einen Hundertstel; die neue Paarung liegt bei 6,36 bis 7,77.
       Begruendung in DESIGN.md, Abschnitt „Status". */
    --info-wash: #e8f3ff;
    --info-ink: #1e40af;
    --success-wash: #e7f6ec;
    --success-ink: #166534;
    --caution-wash: #fff6df;
    --caution-ink: #854d0e;
    --danger-wash: #fdecec;
    --danger-ink: #991b1b;

    /* DIE DRITTE STUFE (28.09.2026): der Rand einer getoenten Flaeche. Er
       stand an 33 Stellen in sechs verschiedenen Toenen im Code, ohne Namen.
       Nicht zu verwechseln mit --border: Die Ghost-Border trennt NEUTRAL
       (Tabellenzeilen, Karten); dieser Rand gehoert zur Aussage der Flaeche
       und kommt nie ohne die zugehoerige Waschung darunter vor. */
    /* KATEGORIEFARBEN (30.09.2026) — die Kaesten auf „Anträge & Formulare".
       Zehn Paare aus Grund und Farbe, waehlbar je Kategorie
       (app/forms/form_kategorie_aussehen.php). Die Werte sind die, die dort
       bis heute fest im Code standen; neu ist nur, dass sie Namen haben und
       ein dunkles Thema sie an EINER Stelle umstellen kann. Kein Status —
       sie unterscheiden Kategorien, nichts sonst. */
    --kat-blau-grund: #dbeafe;    --kat-blau-farbe: #1e40af;
    --kat-tuerkis-grund: #e0f2fe; --kat-tuerkis-farbe: #075985;
    --kat-gruen-grund: #dcfce7;   --kat-gruen-farbe: #166534;
    --kat-gelb-grund: #fef3c7;    --kat-gelb-farbe: #92400e;
    --kat-orange-grund: #fff7ed;  --kat-orange-farbe: #9a3412;
    --kat-rot-grund: #fee2e2;     --kat-rot-farbe: #991b1b;
    --kat-rosa-grund: #fce7f3;    --kat-rosa-farbe: #9d174d;
    --kat-lila-grund: #ede9fe;    --kat-lila-farbe: #5b21b6;
    --kat-indigo-grund: #e0e7ff;  --kat-indigo-farbe: #3730a3;
    --kat-grau-grund: #f1f5f9;    --kat-grau-farbe: #475569;

    --success-border: #a6d8ab;
    --caution-border: #f1dfae;
    --danger-border: #f0c2c2;
    --info-border: #cfe3f7;
    /* Radius (B: größer) */
    --r-sm: 8px;
    --r-md: 12px;
    --r-lg: 16px;
    --r-pill: 999px;
    /* Abstände */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    /* Typo */
    --text-sm: .85rem;
    --text-base: 1rem;
    --text-lg: 1.2rem;
}

/* ================================================
   BASE
   ================================================ */
body {
    font-family: 'Manrope', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    margin: 0;
    background: var(--bg);
    color: var(--text);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6 {
    font-family: 'Manrope', system-ui, sans-serif;
    letter-spacing: -0.01em;
    line-height: 1.3;
    color: var(--text-dark);
}

/* .seitentitel — das h1 einer Seite ohne Kopfzone: die Einzelseiten ohne
   Layout (.einzelseite: Kurzlink-Sackgasse, abgewiesene Uebernahme, „bereits
   angemeldet“, abgelaufenes Formular), der oeffentliche Formularrahmen, das
   Abo, Meine Faecher. Bis zum 01.10.2026 ein h2 und die Seite ohne
   Hauptueberschrift. Groesse und Abstaende sind die des bisherigen h2, damit
   nichts springt. Impressum, Datenschutz und AGB trugen es bis zum
   11.10.2026; sie haben seitdem eine Kopfzone aus page_hero(). */
.seitentitel { font-size: 1.5em; margin: 0.83em 0; }

a,
a:visited {
    color: var(--primary);
}

/* ================================================
   LINK-FARBEN FÜR BUTTONS (verhindert visited-Override)
   ================================================ */
.button-primary,
.button-primary:visited,
.button-primary-small,
.button-primary-small:visited {
    color: var(--on-primary) !important;
}

.button-secondary,
.button-secondary:visited,
.button-secondary-small,
.button-secondary-small:visited,
.button-ghost-small,
.button-ghost-small:visited {
    color: var(--text-mid) !important;
}

.button-danger-small,
.button-danger-small:visited {
    color: var(--danger-ink) !important;
}

/* DER GEWAEHLTE KNOPF EINER FILTERREIHE (aria-current="page") traegt den
   Akzent — er markiert den Standort. Die Farbe braucht !important, weil die
   Regel darueber die Schrift der Knoepfe mit !important festhaelt. Bis zum
   30.09.2026 stand dieser Zustand nur in test_runs.css, ohne !important:
   Der aktive Filter „Alle" war dunkelgrau auf Blau (rund 1,9 : 1). */
.button-secondary[aria-current="page"],
.button-secondary-small[aria-current="page"] {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--on-primary) !important;
}

/* Buttons auf farbigem Kopfzonen-Grund: weiß statt text-mid.
   Frueher stand hier [class*="-hero"] — ein Teilstring-Selektor, der JEDE
   Klasse mit der Zeichenfolge "-hero" traf, auch eine am <body>. Eine
   einzige solche Klasse faerbte damit saemtliche Aktionsbuttons der Seite
   weiss auf weiss. Jetzt stehen die Kopfzonen ausdruecklich da. */
.page-hero .button-secondary-small,
.page-hero .button-secondary-small:visited,
.page-hero .button-ghost-small,
.page-hero .button-ghost-small:visited{
    color: color-mix(in srgb, var(--on-hero) 90%, transparent) !important;
}

/* ================================================
   HEADER
   ================================================ */
header {
    background: color-mix(in srgb, var(--card) 85%, transparent);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--border-light);
    padding: 12px 24px 10px;
    box-shadow: var(--shadow-sm);
}

header .role {
    font-size: 13px;
    color: var(--text-light);
    margin-top: 2px;
}

/* ================================================
   SEITENINHALT
   ================================================ */
/* Keine Hoechstbreite (10.10.2026, DESIGN.md „Die Volle-Breite-Regel"): Bis
   hierher 1600px — ab 1856px Fensterbreite blieb rechts alles leer. */
main {
    padding: 1.25rem 1.25rem;
    width: 100%;
    box-sizing: border-box;
}

@media (max-width: 640px) {
    main {
        padding: 1.25rem 1rem;
    }
}

.card {
    background: var(--card);
    /* Einmal-Erheben-Regel (DESIGN.md): Rand ODER Schatten, nicht beides. */
    border: none;
    border-radius: var(--radius-xl);
    padding: 1.75rem;
    box-shadow: var(--shadow-sm);
    transition: box-shadow 0.25s ease;
}
/* Margin nur wenn Card NICHT in einem Grid-Container ist */
main > .card {
    margin-bottom: 1.5rem;
}

/* Den Rand einer Karte macht ihre Polsterung. Absaetze und Ueberschriften am
   Anfang oder Ende bringen ihren Browser-Abstand nicht mit hinein — bis zum
   29.09.2026 glich das jede Seite inline aus (margin:0 0 0.5rem, margin:0 …). */
.card > :first-child { margin-top: 0; }
.card > :last-child { margin-bottom: 0; }

.card:hover {
    box-shadow: var(--shadow-md);
}

.hint {
    color: var(--text-light);
    font-size: 0.875rem;
    line-height: 1.5;
}

/* ================================================
   BUTTONS — Stitch Design System
   ================================================ */
.button-primary {
    min-height: var(--knopf-mindest, 0px);   /* am Finger 44px (app/beruehrung.css) */
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background: var(--primary);
    color: var(--on-primary);
    padding: 0.65rem 1.5rem;
    border-radius: var(--radius-lg);
    text-decoration: none;
    font-size: 0.9rem;
    font-weight: 600;
    border: 1px solid transparent;
    transition: all 0.2s ease;
    cursor: pointer;
    line-height: 1.4;
    white-space: nowrap;
}

.button-primary:hover {
    background: var(--primary-hover);
    border-color: transparent;
    box-shadow: 0 4px 16px color-mix(in srgb, var(--primary) 25%, transparent);
    transform: translateY(-1px);
}

.button-primary:active {
    transform: translateY(0);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--primary) 15%, transparent);
}

.button-secondary {
    min-height: var(--knopf-mindest, 0px);   /* am Finger 44px (app/beruehrung.css) */
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    background: var(--surface-high);
    color: var(--text-mid);
    padding: 0.65rem 1.5rem;
    border-radius: var(--radius-lg);
    border: none;
    text-decoration: none;
    font-size: 0.9rem;
    font-weight: 600;
    transition: all 0.2s ease;
    cursor: pointer;
    line-height: 1.4;
    white-space: nowrap;
}

.button-secondary:hover {
    background: var(--surface-container);
    box-shadow: var(--shadow-sm);
}

/* GESPERRTE KNOEPFE (02.10.2026). Dafuer gab es keine Regel: Ein Knopf mit
   disabled sah aus wie ein benutzbarer — im Medienwaehler stand „Einfügen"
   in vollem Blau, bevor eine Datei gewaehlt war. */
.button-primary:disabled,
.button-secondary:disabled,
.button-danger:disabled,
.button-primary-small:disabled,
.button-secondary-small:disabled,
.button-danger-small:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    box-shadow: none;
    transform: none;
}

/* LANGE KNOEPFE AM HANDY (01.10.2026). Mit white-space:nowrap war
   „Ausgewählte globale Fächer hinzufügen" 351px breit — in einer Karte von
   302px. Die Seite liess sich seitwaerts schieben (Faecher +5px,
   Schuljahreswechsel). Am Handy darf der Text umbrechen; kurze Knoepfe
   bleiben einzeilig, weil sie gar nicht umbrechen muessen. */
@media (max-width: 640px) {
    .button-primary,
    .button-secondary,
    .button-danger {
        white-space: normal;
        max-width: 100%;
        text-align: center;
        justify-content: center;
    }
}

.button-link {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--primary);
    text-decoration: none;
    font-size: 0.875rem;
    font-weight: 600;
    transition: color 0.15s;
}

.button-link:hover {
    text-decoration: underline;
}
/* Ein Textknopf, der nur ein Symbol traegt (etwa „…" im Kartenkopf der
   Startseite), mass 18×18px. Dieselbe Loesung wie in der Kopfzone. */
.button-link[aria-label]:has(> .material-symbols-outlined:only-child) { padding: 4px; margin: -4px; }

/* Der Knopf, mit dem man eine Karte in einem Baukasten waehlt oder aufklappt.
   Er steht NEBEN dem Ziehgriff, nie um ihn herum: Bis zum 08.10.2026 trug die
   ganze Karte role="button" (oder tabindex="0" ohne Rolle) und hielt den
   Griff — selbst ein Knopf — samt Werkzeugleiste in sich. Verschachtelte
   Bedienelemente liest ein Vorleseprogramm nicht verlaesslich vor (DESIGN.md,
   die Nebeneinander-statt-ineinander-Regel). Der Knopf sieht aus wie sein
   Inhalt; den Fokusrahmen bringt :focus-visible mit. */
.kartenknopf { appearance: none; display: block; width: 100%; margin: 0; padding: 0; border: 0; background: none; font: inherit; color: inherit; text-align: inherit; cursor: inherit; }
/* Wo der Inhalt selbst Felder oder Diagramme traegt (Formular-, Test- und
   Auswertungs-Baukasten), kann der Knopf ihn nicht umschliessen. Er liegt
   dann als Flaeche ueber der ganzen Karte; Griff und Werkzeugleiste liegen
   darueber (z-index 2 in der Moduldatei). */
.kartenknopf.flaeche { position: absolute; inset: 0; z-index: 1; width: auto; border-radius: inherit; }

/* Das Symbol im Textknopf folgt der Schrift, nicht der 24px-Vorgabe der
   Symbolschrift — sonst ueberragt es das Wort daneben. */
.button-link .material-symbols-outlined {
    font-size: 16px;
}

/* Icon innerhalb von Buttons. DESIGN.md: 20–22px neben einem Wort; die
   kleinen Knoepfe (Text 0,8rem) tragen die Kompakt-Stufe 18px — dieselbe wie
   .karten-titel--kompakt. Bis zum 29.09.2026 stand die Groesse ~270-mal
   inline in den Seiten, meist 14px mit vertical-align:-2px. Das Ausrichten
   besorgt inline-flex der Knoepfe; ein Versatz von Hand ist nicht noetig. */
.button-primary .material-symbols-outlined,
.button-secondary .material-symbols-outlined,
.button-danger .material-symbols-outlined {
    font-size: 20px;
}
.button-ghost-small .material-symbols-outlined,
.button-secondary-small .material-symbols-outlined,
.button-primary-small .material-symbols-outlined,
.button-danger-small .material-symbols-outlined {
    font-size: 18px;
}

/* ===============================
   SMALL ACTION BUTTONS (GLOBAL)
   =============================== */
.button-ghost-small,
.button-secondary-small,
.button-primary-small,
.button-danger-small {
    min-height: var(--knopf-mindest, 0px);   /* am Finger 44px (app/beruehrung.css) */
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 44er-Regel (DESIGN.md), aber nur in der Hoehe erzwungen.
       Eine feste Mindestbreite sprengt dichte Aktionsspalten: in der
       Schulverwaltung stehen sechs Icon-Buttons nebeneinander, die Spalte
       wuchs dadurch um 72px und schob sich in einer 11-spaltigen Tabelle
       aus dem Bild. Reine Icon-Buttons in dichten Tabellen bleiben damit
       schmaler als 44px — bewusster Kompromiss, siehe DESIGN.md. */
    min-height: 44px;
    box-sizing: border-box;
    gap: 0.35rem;
    font-size: 0.8rem;
    padding: 0.4rem 0.85rem;
    border-radius: var(--radius);
    text-decoration: none;
    white-space: nowrap;
    font-weight: 500;
    transition: all 0.15s ease;
    cursor: pointer;
    line-height: 1.4;
}

.button-ghost-small {
    min-height: var(--knopf-mindest, 0px);   /* am Finger 44px (app/beruehrung.css) */
    box-sizing: border-box;
    border: none;
    background: var(--surface-low);
    color: var(--text-mid);
}

.button-ghost-small:hover {
    background: var(--surface-high);
}

.button-secondary-small {
    min-height: var(--knopf-mindest, 0px);   /* am Finger 44px (app/beruehrung.css) */
    box-sizing: border-box;
    border: none;
    background: var(--surface-low);
    color: var(--text-mid);
}

.button-secondary-small:hover {
    background: var(--surface-high);
}

.button-primary-small {
    min-height: var(--knopf-mindest, 0px);   /* am Finger 44px (app/beruehrung.css) */
    box-sizing: border-box;
    border: none;
    background: var(--primary);
    color: var(--on-primary);
}

.button-primary-small:hover {
    background: var(--primary-hover);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--primary) 20%, transparent);
}

.button-danger-small {
    border: none;
    background: var(--danger-wash);
    color: var(--danger-ink);
}

.button-danger-small:hover {
    background: var(--danger-wash);
}

/* Gesperrter Zustand: Die Aktion bleibt sichtbar an ihrem Platz, ist aber
   erkennbar nicht ausfuehrbar. Vorher stand hier das blosse Wort
   "geschuetzt" — es brach die Icon-Reihe und nannte den Grund nicht. */
.button-danger-small.is-blocked,
.button-secondary-small.is-blocked {
    opacity: 0.4;
    cursor: not-allowed;
    background: var(--surface-low);
    color: var(--text-muted) !important;
}

/* button-small: kleiner Hilfsbutton ohne eigene Farbe */
.button-small {
    min-height: var(--knopf-mindest, 0px);   /* am Finger 44px (app/beruehrung.css) */
    box-sizing: border-box;
    font-size: 0.8rem;
    padding: 0.4rem 0.85rem;
}

/* button-danger: größerer Danger-Button */
.button-danger {
    min-height: var(--knopf-mindest, 0px);   /* am Finger 44px (app/beruehrung.css) */
    box-sizing: border-box;
    border: none;
    color: var(--danger-ink);
    background: var(--danger-wash);
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.65rem 1.5rem;
    border-radius: var(--radius-lg);
    text-decoration: none;
    font-size: 0.9rem;
    font-weight: 600;
    transition: all 0.2s ease;
    cursor: pointer;
}

.button-danger:hover {
    background: var(--danger-ink);
    color: var(--card);
    box-shadow: 0 4px 12px color-mix(in srgb, var(--danger-ink) 20%, transparent);
}

/* ================================================
   MODAL
   ================================================ */
.modal-backdrop {
    display: none;
    position: fixed;
    inset: 0;
    background: color-mix(in srgb, var(--text) 30%, transparent);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    z-index: 1000;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}
.modal-backdrop.open { display: flex; }
/* Der Bestaetigungsdialog (layout_footer.php) liegt ueber jedem anderen Dialog. */
.modal-backdrop.modal-backdrop--oben { z-index: 9998; }
/* Die Ablage der Rueckmeldungen oben rechts (layout_footer.php). */
.toast-behaelter { position: fixed; top: 1rem; right: 1rem; z-index: 9999; display: flex; flex-direction: column; gap: 0.5rem; pointer-events: none; max-width: 400px; }
/* Die Einblendung selbst (layout_footer.php: showToast). Bis zum 30.09.2026
   eine style-Zeichenkette mit per Skript geholten Token-Werten. Waschung und
   Tinte tragen den Status, der Rahmen bleibt neutral (DESIGN.md). */
.toast {
    pointer-events: auto;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.75rem 1rem;
    border-radius: 8px;
    border: 1px solid var(--border);
    font-size: 0.88rem;
    box-shadow: 0 4px 16px color-mix(in srgb, var(--text) 10%, transparent);
    cursor: pointer;
    opacity: 0;
    transform: translateX(40px);
    transition: opacity 0.3s, transform 0.3s;
}
.toast.sichtbar { opacity: 1; transform: translateX(0); }
.toast--success { background: var(--success-wash); color: var(--success-ink); }
.toast--error   { background: var(--danger-wash);  color: var(--danger-ink); }
.toast--info    { background: var(--info-wash);    color: var(--info-ink); }
.toast-symbol { font-weight: 700; font-size: 1.1rem; line-height: 1; }
@media (prefers-reduced-motion: reduce) { .toast { transition: opacity 0.3s; transform: none; } }

/* Eine Zeile, die nach erfolgreichem Loeschen ausblendet (ajaxAction). */
.wird-entfernt { opacity: 0; transition: opacity 0.3s; }

/* Hinweise der gemeinsamen Bearbeitung (layout_footer.php) — bis zum
   30.09.2026 als style-Zeichenketten. */
.nebenlaeufig-warnung,
.nebenlaeufig-anwesend {
    padding: 0.6rem 1rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 0.88rem;
    margin-bottom: 1rem;
}
.nebenlaeufig-warnung {
    background: var(--danger-wash);
    color: var(--danger-ink);
    font-weight: 600;
    position: sticky;
    top: 0;
    z-index: 100;
}
.nebenlaeufig-anwesend { background: var(--info-wash); color: var(--info-ink); }

/* Offene Seitenleiste auf dem Handy: die Seite dahinter scrollt nicht mit. */
body.sidebar-offen { overflow: hidden; }
/* Eine schlichte Seite ohne Portalrahmen (Subdomains: „Diese Adresse gibt es nicht“). */
body.seite-schlicht { display: flex; min-height: 100dvh; align-items: center; justify-content: center; padding: var(--space-4); box-sizing: border-box; }
/* Dasselbe fuer eine offene Lade im Baukasten (app/baukasten.css, „Die Lade"). */
body.lade-offen { overflow: hidden; }
.modal-body .gcm-text { margin: 0; font-size: 0.9rem; line-height: 1.5; color: var(--text); }
.modal-dialog {
    background: var(--card);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-lg);
    width: 100%;
    max-width: 480px;
    /* dvh, nicht vh: Auf iOS rechnet vh die Adressleiste NICHT ein. Ein
       Dialog mit 90vh ragte dadurch unter die Leiste, und die unterste
       Zeile — meist der Knopf, auf den es ankommt — war nicht zu sehen.
       dvh misst, was wirklich sichtbar ist (08.10.2026). */
    max-height: 90dvh;
    overflow-y: auto;
    animation: modalIn 0.2s ease;
}
.modal-dialog-sm  { max-width: 380px; }
.modal-dialog-lg  { max-width: 700px; }
@keyframes modalIn {
    from { opacity:0; transform:scale(0.95) translateY(8px); }
    to   { opacity:1; transform:scale(1)    translateY(0);   }
}
.modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1.25rem 1.5rem 1rem;
    border-bottom: 1px solid var(--border-light);
}
.modal-title { margin:0; font-size:1.15rem; font-weight:700; color:var(--text-dark); letter-spacing:-0.01em; }
/* Ein Symbol im Dialogtitel steht neben dem Wort, in der Akzentfarbe — wie im
   Kartentitel. Bis zum 29.09.2026 setzte das jeder Dialog inline. */
.modal-title:has(> .material-symbols-outlined) { display: flex; align-items: center; gap: 0.5rem; }
.modal-title > .material-symbols-outlined { font-size: 22px; color: var(--primary); }
.modal-close {
    background:none; border:none; font-size:1.1rem; color:var(--text-light);
    cursor:pointer; padding:0.4rem; border-radius:var(--radius); line-height:1;
    transition: background 0.15s;
    /* Mindestens 32×32 (01.10.2026): gemessen 22×29, am Handy zu schmal.
       Am Finger 44×44 ueber das Token (app/beruehrung.css, 13.10.2026). */
    min-width: var(--schliessen-mass, 32px); min-height: var(--schliessen-mass, 32px);
    display: inline-flex; align-items: center; justify-content: center;
}
.modal-close:hover { background:var(--surface-low); color:var(--text-dark); }
.modal-body { padding: 1.25rem 1.5rem; }
.modal-body .form-input { max-width:100%; width:100%; }
.modal-footer {
    padding: 1rem 1.5rem 1.25rem;
    border-top: 1px solid var(--border-light);
    display: flex; justify-content: flex-end; gap: 0.75rem; align-items: center;
}
.modal-confirm-text { font-size:0.85rem; color:var(--text-light); margin-top:0.4rem; }
/* Die Dialoge des Bausteinkastens (baukasten.js: dialog(), linkDialog()).
   Bis zum 30.09.2026 trugen sie ihre ganze Optik inline; jetzt sind sie das
   gemeinsame Modal, und nur das Eigene steht hier. */
.modal-body .gcm-text.bk-dialog-text { margin: 0 0 1.1rem; }
.bk-dialog-mehrfach {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.25rem 0.6rem;
    max-height: 12rem;
    overflow-y: auto;
}
.bk-dialog-mehrfach .ankreuz { min-height: 36px; }
/* „Link entfernen" steht links, abgesetzt von Abbrechen/Übernehmen. */
.modal-footer .bk-dialog-links { margin-right: auto; }

/* ================================================
   ALERT-BOXEN — Stitch Design System
   ================================================ */
/* Auch .alert-error usw. allein sind vollwertige Meldungen. Nur .alert
   ergaenzt den oberen Abstand und setzt die uebrigen Raender zurueck.
   Deshalb die Grundwerte teilen, ohne bestehendes Markup zu veraendern. */
.alert,
.alert-error,
.alert-success,
.alert-warning,
.alert-info {
    padding: 0.85rem 1.1rem;
    border-radius: var(--radius-lg);
    font-size: 0.875rem;
    line-height: 1.5;
}

.alert {
    margin-top: 0.75rem;
    border: none;
}

/* Eine Liste in einer Meldung (mehrere Fehler auf einmal) setzt keinen
   eigenen Aussenabstand. Stand in den Seiten ~10-mal als margin:0;padding-left. */
.alert ul, .alert-error ul, .alert-success ul, .alert-warning ul, .alert-info ul {
    margin: 0;
    padding-left: 1.1rem;
}

/* .seiten-meldung — eine Meldung ueber dem Seiteninhalt, nicht in einer
   Karte. Die Meldungen selbst haben keinen Aussenabstand (sie stehen auch in
   Karten und Dialogen); hier stand er bis zum 30.09.2026 von Hand. */
.seiten-meldung { margin-bottom: 1rem; }

/* .meldung-mit-knopf — eine Meldung mit Symbol, Satz und einem Weg
   („Zu den Fragen"). Der Satz nimmt den Platz, der Knopf bricht bei Enge um. */
.meldung-mit-knopf { display: flex; align-items: center; flex-wrap: wrap; gap: 0.75rem; }
.meldung-mit-knopf > .material-symbols-outlined { font-size: 20px; }
.meldung-mit-knopf > .meldung-text { flex: 1; min-width: 220px; }
/* Ein Knopf in einer eigenen Zeile unter dem Satz einer Meldung. */
.meldung-knopfzeile { margin: 0.6rem 0 0; }

/* Mehrere Absaetze in einer Meldung: Abstand dazwischen, keiner davor. */
.alert p, .alert-error p, .alert-success p, .alert-warning p, .alert-info p { margin: 0.5rem 0 0; }
.alert p:first-child, .alert-error p:first-child, .alert-success p:first-child,
.alert-warning p:first-child, .alert-info p:first-child { margin-top: 0; }

/* .ankreuz--oben: bei mehrzeiligem Text steht das Kaestchen an der ersten Zeile. */
.ankreuz--oben { align-items: flex-start; }
.ankreuz--oben input[type="checkbox"], .ankreuz--oben input[type="radio"] { margin-top: 0.2rem; }

/* Varianten bestimmen nur die Statusfarben; der linke Rand nutzt die Tinte. */
.alert-error {
    background: var(--danger-wash);
    border-left: 3px solid var(--danger-ink);
    color: var(--danger-ink);
}

.alert-success {
    background: var(--success-wash);
    border-left: 3px solid var(--success-ink);
    color: var(--success-ink);
}

.alert-warning {
    background: var(--caution-wash);
    border-left: 3px solid var(--caution-ink);
    color: var(--caution-ink);
}

/* .alert-info trug den AKZENT (--primary). Der markiert laut DESIGN.md
   „Handlung und Standort — nichts sonst"; eine Hinweismeldung ist keines
   von beiden. Es gibt eine Statusfarbe dafuer. */
.alert-info {
    background: var(--info-wash);
    border-left: 3px solid var(--info-ink);
    color: var(--info-ink);
}

/* ================================================
   SCHNELLAKTIONEN
   .page-header, .page-header-main und die graue Seitenspalte
   .page-header-side sind seit dem 11.10.2026 fort: Ihr letzter Nutzer waren
   die AGB, deren Kopf jetzt aus page_hero() kommt (Eine-Kopfzone-Regel).
   ================================================ */
/* 2-Spalten-Grid (z.B. Kalender mit Toggle-Aktionen) */
.quick-actions-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.35rem;
}

.quick-action {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    width: 100%;
    font-size: 0.8rem;
    font-weight: 500;
    text-align: center;
    padding: 0.5rem 0.75rem;
    border-radius: var(--radius);
    text-decoration: none;
    border: none;
    background: var(--surface-low);
    color: var(--text-mid);
    box-sizing: border-box;
    white-space: normal;
    transition: all 0.15s ease;
}

.quick-action:hover {
    background: var(--primary-light);
    color: var(--primary);
}

.quick-action--active {
    background: var(--primary);
    color: var(--on-primary);
}

.quick-action:visited { color: var(--text-mid); }
.quick-action--active:visited { color: var(--on-primary); }

/* ================================================
   RESPONSIVE
   ================================================ */
@media (max-width: 900px) {
    .quick-actions-grid {
        grid-template-columns: 1fr;
    }
}

/* ================================================
   STATUS & FEEDBACK
   ================================================ */
.error {
    color: var(--danger-ink);
    font-size: 14px;
}

.success {
    color: var(--success-ink);
    font-size: 14px;
}

.footer {
    margin-top: 2.5rem;
    padding: 1.25rem 2.5rem;
    font-size: 0.8rem;
    color: var(--text-light);
    border-top: 1px solid var(--border-light);
}

.footer a {
    color: var(--text-light);
    text-decoration: none;
}

.footer a:hover {
    color: var(--primary);
}

/* ================================================
   FORMULAR-CONTROLS — Stitch Design System
   ================================================ */
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="time"],
/* datetime-local fehlte bis zum 01.10.2026: 19 Felder im Portal, und wo die
   Klasse form-input nicht stand, kam das Feld im Browser-Standard (weisser
   Grund, schwarzer Rand) — gesehen im Dialog „MTA-Meeting bearbeiten". */
input[type="datetime-local"],
input[type="month"],
input[type="week"],
input[type="tel"],
input[type="search"],
input[type="url"],
select,
textarea {
    font-family: inherit;
    font-size: max(var(--feld-mindest, 0px), 0.9rem);
    color: var(--text);
    background: var(--surface-low);
    border: 1px solid transparent;
    border-bottom: 2px solid var(--border);
    border-radius: var(--radius);
    padding: 0.6rem 0.9rem;
    box-sizing: border-box;
    transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
}

/* PLATZHALTER (02.10.2026). Ohne Regel nimmt der Browser sein eigenes Grau
   (#757575) — auf der Feldflaeche (--surface-low) sind das 4,18:1, in fast
   jedem Formular des Portals. --text-light kommt dort auf 4,84:1. */
::placeholder { color: var(--text-light); opacity: 1; }

input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="number"]:focus,
input[type="date"]:focus,
input[type="time"]:focus,
input[type="datetime-local"]:focus,
input[type="month"]:focus,
input[type="week"]:focus,
input[type="tel"]:focus,
input[type="search"]:focus,
input[type="url"]:focus,
select:focus,
textarea:focus {
    outline: none;
    background: var(--card);
    border-color: transparent;
    border-bottom-color: var(--primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 6%, transparent);
}

input[type="checkbox"],
input[type="radio"] {
    accent-color: var(--primary);
    width: 1.1rem;
    height: 1.1rem;
    cursor: pointer;
}

input[type="hidden"] {
    display: none !important;
}

/* Nicht jedes Feld im Bestand traegt .form-input — viele stehen ohne Klasse
   oder mit einer seitenlokalen. Sie fielen deshalb weiter unter die
   Daumengrenze, obwohl die Klasse repariert war. Ankreuzfelder und
   Auswahlknoepfe bleiben aussen vor: dort ist das Quadrat die Marke, und die
   Trefferflaeche gehoert an das zugehoerige Label. */
input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
select,
textarea {
    min-height: 44px;
    box-sizing: border-box;
}

.form-input {
    /* Ohne Mindesthoehe traegt nur die Zeilenhoehe das Feld — ein <select> hat
       keine und fiel deshalb auf 25px zusammen, obwohl es dieselbe Klasse
       traegt wie seine Nachbarn. */
    min-height: 44px;
    font-family: inherit;
    font-size: max(var(--feld-mindest, 0px), 0.9rem);
    color: var(--text);
    background: var(--surface-low);
    border: 1px solid transparent;
    border-bottom: 2px solid var(--border);
    border-radius: var(--radius);
    padding: 0.6rem 0.9rem;
    box-sizing: border-box;
    transition: border-color 0.15s, box-shadow 0.15s;
    width: 100%;
}

.form-input:focus {
    outline: none;
    background: var(--card);
    border-bottom-color: var(--primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 8%, transparent);
}

.form-group {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin-bottom: 1.1rem;
}

/* KEINE VERSALIEN AN FELDBESCHRIFTUNGEN (25.09.2026).
   DESIGN.md fuehrt Versalien ausschliesslich unter „Label" und nennt dort
   Tabellenkoepfe, Badges, Metazeilen und den Markenzusatz — Feldbeschriftungen
   stehen nicht dabei. Die Vorlage setzt sie in Manrope 700 bei 13,5px in der
   Tinte (Designvorschlag/komponenten.css: `.field label`); genau das steht
   jetzt hier. Mit Inter faellt auch die Sperrung weg: Inter tritt laut
   DESIGN.md nur bei Kleinstgraden und in Versalien an.

   Was die alte Regel angerichtet hat: „WAS SOLL IMPORTIERT WERDEN?",
   „E-MAIL-DOMAIN (OPTIONAL)" — und, wo eine Beschriftung einen Personennamen
   trug, „DIETER BOHLEN". Bewacht von docs/tests/test_design_dokument.php. */
/* .field > label: die Beschriftung im Formularraster (.form-grid .field).
   Bis zum 29.09.2026 ohne Regel — 16px in normaler Schrift, anders als jede
   andere Feldbeschriftung. */
.form-group label,
.field > label,
.feld-label {
    font-size: 0.84rem;
    line-height: 1.25rem;
    font-weight: 700;
    color: var(--text);
}

/* GEMEINSAME BAUSTEINE STATT INLINE-STILEN (29.09.2026).
   Rund 2.150 style="…" standen in den Seiten, die meisten als dieselben
   wenigen Muster, jedes Mal abgeschrieben — und dabei von DESIGN.md
   abgewichen. Diese Klassen ersetzen sie; die Seiten ziehen Rubrik fuer
   Rubrik nach (Ablauf: Notiz „Inline-Stile").

   .feld-label — die Beschriftung ueber einem Feld, wo kein .form-group
   darum steht. Stand ~80-mal als „0,78rem, grau, 600"; DESIGN.md will
   0,84rem, Tinte, 700 — dieselbe Beschriftung wie in .form-group. */
.feld-label {
    display: block;
    margin-bottom: 0.3rem;
}

/* .ankreuz — ein Ankreuz- oder Auswahlfeld mit seinem Wort, als ganze
   Zeile anklickbar. Stand ~20-mal mit wechselnden Groessen (0,82–0,88rem).
   label.ankreuz, damit die Feldbeschriftung (.form-group label, .field > label,
   fett) den Satz neben dem Kaestchen nicht mit erfasst. */
.ankreuz,
label.ankreuz {
    font-weight: 400;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 44px;   /* 44er-Regel (DESIGN.md): die ganze Zeile ist das Ziel */
    font-size: 0.875rem;
    color: var(--text);
    cursor: pointer;
}
.ankreuz input[type="checkbox"],
.ankreuz input[type="radio"] { flex-shrink: 0; margin: 0; }

/* .form-inline — ein Formular, das nur einen Knopf in einer Zeile traegt
   (Loeschen, Umschalten). Stand ~60-mal als display:inline;margin:0. */
/* Die Einstellungen-Karten eines Objekts oder einer Rubrik (Bedienlogik,
   Spezifikation 1.2): gestapelte Karten in fester Reihenfolge, die letzte
   traegt Archivieren und Loeschen. Eine Regel fuer Formulare, Konzepte und
   Konzeptvorlagen — bis zum 14.10.2026 hiess sie .fm-einstellungen und
   stand in app/forms/form_uebersicht.css. */
.einstellungs-karten { display: flex; flex-direction: column; gap: 1.25rem; max-width: 960px; }

.form-inline {
    display: inline;
    margin: 0;
}

/* .zeilen-aktionen — die Knoepfe einer Tabellenzeile nebeneinander, dicht
   (DESIGN.md, 44er-Regel: Symbolreihen in Listen bleiben schmal). */
.zeilen-aktionen {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

/* .form-actions — die Knopfreihe unter einem Formular. Stand 43-mal im
   Markup, aber es gab keine Regel: jede Seite gab ihr den Abstand inline
   (oder, im Treffpunkt, in der eigenen Stildatei). */
.form-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.75rem;
}

/* .karten-kopf — oben in einer Karte: Text links, Handlung rechts. */
.karten-kopf {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 1rem;
}

/* .liste-detail — links eine Liste zum Auswaehlen, rechts das Gewaehlte
   (Raumverwaltung, Bildungsgaenge je Lehrkraft). Stand bis zum 29.09.2026
   zweimal inline nachgebaut, mit zwei verschiedenen Auswahlfarben. Der
   gewaehlte Eintrag traegt dieselbe Markierung wie der aktive Eintrag der
   Seitenleiste. */
.liste-detail {
    display: grid;
    grid-template-columns: 300px 1fr;
    gap: 1.25rem;
    align-items: start;
}
.liste-detail > * { min-width: 0; }
.card.liste-spalte { padding: 0; }
.liste-kopf {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--border);
}
.liste-kopf .karten-titel { margin: 0; }
.liste-scroll { max-height: 560px; overflow-y: auto; }
.liste-fuss { padding: 0.5rem 1rem; border-top: 1px solid var(--border); }
.liste-fuss .button-secondary-small { width: 100%; }
.liste-zeile {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 1rem;
    color: var(--text);
    text-decoration: none;
    border-bottom: 1px solid var(--border-light);
}
.liste-zeile:hover { background: color-mix(in srgb, var(--primary) 5%, transparent); }
.liste-zeile.is-active {
    color: var(--primary);
    background: color-mix(in srgb, var(--primary) 8%, transparent);
    font-weight: 600;
    box-shadow: inset 3px 0 0 var(--primary);
}
.liste-zeile > .material-symbols-outlined { font-size: 20px; color: var(--text-light); }
.liste-zeile.is-active > .material-symbols-outlined { color: var(--primary); }
.liste-zeile-name {
    flex: 1;
    min-width: 0;
    font-size: 0.875rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
@media (max-width: 640px) {
    .liste-detail { grid-template-columns: 1fr; }
}

/* .unterzeile — eine zweite, leise Zeile unter einem Namen (E-Mail,
   „angelegt von …", Ticketnummer). */
.unterzeile {
    display: block;
    font-size: 0.8rem;
    color: var(--text-light);
}

/* .symbol-im-text — ein Symbol mitten im Fliesstext oder in einer Meldung.
   Die Groesse folgt der Schrift, in der es steht; stand bis zum 29.09.2026
   als 14px mit vertical-align:-2px von Hand daneben. */
.symbol-im-text {
    font-size: 1.15em;
    vertical-align: -0.2em;
}

/* .avatar — Profilbild oder Kuerzel einer Person (render_avatar() in
   app/config.php). Bis zum 01.10.2026 eine Stilzeile je Avatar. Die Groesse
   kommt als --avatar-groesse mit, die Grundfarbe als --avatar-grund.
   Die Schrift ist 35 % der Groesse, aber nie kleiner als 0,6rem (DESIGN.md):
   Bei 20px ergab die alte Rechnung 7px. */
.avatar {
    width: var(--avatar-groesse, 34px);
    height: var(--avatar-groesse, 34px);
    border-radius: 50%;
    flex-shrink: 0;
}
.avatar--bild { object-fit: cover; }
.avatar--kuerzel {
    background: var(--avatar-grund, var(--primary));
    color: var(--on-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: max(0.6rem, calc(var(--avatar-groesse, 34px) * 0.35));
    font-weight: 700;
    line-height: 1;
}

/* .tabelle-scroll — der Rahmen, in dem eine breite Tabelle waagerecht
   scrollt, statt die Seite zu sprengen. Stand viermal unter vier Namen
   da (zwei davon ohne jede Regel, nur mit Inline-Stil). */
.tabelle-scroll { overflow-x: auto; }

/* Das Band waehrend einer Support-Uebernahme (layout.php): Satz links,
   „Beenden" rechts; die Liste vergangener Uebernahmen mit Abstand darunter. */
.uebernahme-band { display: flex; flex-wrap: wrap; gap: 0.6rem 1rem; align-items: center; justify-content: space-between; }
.uebernahme-anlass { display: block; font-size: 0.85rem; margin-top: 0.15rem; }
.alert-info ul.uebernahme-liste { margin: 0.4rem 0 0.6rem 1.1rem; padding: 0; }

/* Der Satz zur Verantwortung unter einem ausgefuellten Formular
   (form_submit.php). Angemeldet gilt diese Regel; auf der oeffentlichen Seite
   ueberschreibt form_public.css sie mit der Schulfarbe. */
.fp-verantwortung { font-size: 0.82rem; color: var(--text-light); margin: 0.9rem 0 0; }

/* Der Datenschutzsatz unter einem Gastformular (guest_form_privacy_note). */
.gast-datenschutz { font-size: 0.82rem; color: var(--text-light); margin: 0.75rem 0 0; }

/* .mit-avatar — ein Name mit render_avatar() davor, in einer Zeile.
   Ueberall, wo ein Name steht, steht das Avatar davor (Vorgabe seit 04/2026). */
.mit-avatar { display: inline-flex; align-items: center; gap: 0.35rem; }
/* Steht unter dem Namen eine .unterzeile (E-Mail), fasst .mit-avatar-zeilen
   beide Zeilen rechts neben dem Avatar zusammen. */
.mit-avatar-zeilen { min-width: 0; text-align: left; }
/* „angelegt von [Avatar] Name" in einer .unterzeile: an der Grundlinie stand der
   Name 1,5px tiefer als der Text davor (gemessen am 02.10.2026). */
.unterzeile .mit-avatar { vertical-align: bottom; }

/* .uebernahme-form — „Ansicht uebernehmen" mit Anlass (Support-Vorgaenge,
   Benutzer einer Schule). Stand zweimal inline. */
.uebernahme-form { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 0.45rem; }
.uebernahme-form .form-input { min-width: 14rem; flex: 1; width: auto; font-size: max(var(--feld-mindest, 0px), 0.8rem); }

/* .details-leise — aufklappbarer Rohtext unter einem Eintrag (Kontext einer
   Meldung, SQL einer Migration). */
.details-leise { margin-top: 0.4rem; }
.details-leise summary { font-size: 0.78rem; color: var(--text-light); cursor: pointer; }
.details-leise pre { margin: 0.35rem 0 0; font-size: 0.74rem; color: var(--text-mid); white-space: pre-wrap; word-break: break-word; }

/* .zahl-rechts — Zahlen in einer Tabellenspalte, rechtsbuendig und mit
   gleich breiten Ziffern, damit Stellen untereinander stehen. */
.zahl-rechts { text-align: right; font-variant-numeric: tabular-nums; }
/* Feste Spaltenanteile, wo eine Tabelle sie braucht (Klassen der Lehrkraft). */
.spalte-30 { width: 30%; }
.spalte-35 { width: 35%; }

/* .card--schmal — eine Karte fuer ein einzelnes kurzes Formular (Anmelden,
   Passwort vergessen, neues Passwort). Bis zum 29.09.2026 hatten zwei der
   drei Seiten inline 420px, die Anmeldung war voll breit. */
.card.card--schmal { max-width: 420px; }
/* .card--formular — ein einzelnes laengeres Formular (Kontakt, Rueckmeldung). */
.card.card--formular { max-width: 720px; }

/* .wahl-kachel — eine Auswahl als Flaeche: Radio oder Ankreuzfeld, Titel,
   leise Erklaerung darunter. Die ganze Kachel ist das Ziel (44er-Regel).
   Die gewaehlte Kachel traegt den Akzent als Rand. */
.wahl-kachel {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.75rem 0.9rem;
    margin-bottom: 0.6rem;
    cursor: pointer;
    background: var(--card);
}
.wahl-kachel:hover { background: var(--surface-low); }
.wahl-kachel:has(input:checked) { border-color: var(--primary); box-shadow: inset 0 0 0 1px var(--primary); }
.wahl-kachel input[type="radio"],
.wahl-kachel input[type="checkbox"] { margin: 0.2rem 0 0; width: 18px; height: 18px; flex-shrink: 0; }
.wahl-kachel-text { display: block; font-size: 0.8rem; color: var(--text-light); }

/* Ein fieldset als Gruppe von Auswahlkacheln, ohne Browser-Rahmen. */
.auswahl-gruppe { border: none; padding: 0; margin: 0 0 1.25rem; }
/* .symbolkachel — ein Symbol, das eine Sache vertritt (ein Modul, ein Weg),
   auf einem abgerundeten Quadrat in der blauen Waschung (DESIGN.md, Symbole).
   Stand bis zum 30.09.2026 in keiner gemeinsamen Datei; die Seiten bauten es
   nach, in der Testbibliothek in drei Farben. */
.symbolkachel {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 42px; height: 42px; border-radius: 12px;
    background: var(--primary-light); color: var(--primary);
}
.symbolkachel .material-symbols-outlined { font-size: 22px; }
.symbolkachel--klein { width: 32px; height: 32px; border-radius: 9px; }
.symbolkachel--klein .material-symbols-outlined { font-size: 18px; }

/* Mehrere .ankreuz nebeneinander (Radiogruppe „Anonym / Personalisiert").
   Die Legende darueber traegt .feld-label. */
.ankreuz-reihe { display: flex; flex-wrap: wrap; gap: 0 1.25rem; }
.auswahl-gruppe > legend.feld-label { padding: 0; }

/* .einzelseite — eine Seite ohne Layout, die nur eine Karte zeigt
   (Sackgasse eines Kurzlinks, abgewiesene Uebernahme). Stand zweimal inline. */
.einzelseite { max-width: 560px; margin: 4rem auto; padding: 0 1rem; }

/* .text-vorformatiert — Text mit erhaltenen Zeilenumbruechen in der
   normalen Schrift (Aenderungsprotokoll). */
.text-vorformatiert { white-space: pre-wrap; word-break: break-word; margin: 0; font-family: inherit; font-size: 0.95rem; }

/* .spalte-auswahl — die schmale Ankreuzspalte vorn in einer Liste. */
.spalte-auswahl { width: 26px; }

/* .name-mit-knopf — ein Name mit seinem Bearbeiten-Knopf direkt dahinter. */
.name-mit-knopf {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

/* .filter-zeile — eine schmale Auswahl ueber einer Liste („Schuljahr: …"),
   ohne die Flaeche der .filter-bar. Beschriftung wie dort. */
.filter-zeile {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 0.6rem 0 1rem;
}
.filter-zeile label {
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--text-muted);
    white-space: nowrap;
}
/* Ein Auswahlfeld mit eigenem Formular (Startseite: Anzeige, Frist) steht
   mit seiner Beschriftung in der Zeile (15.10.2026). */
.filter-zeile form { display: flex; align-items: center; gap: 0.35rem; margin: 0; }
.filter-zeile .form-input,
.filter-zeile select {
    width: auto;
    min-width: 9rem;
}
/* Ein Suchfeld-Aufsatz (app/typeahead.js) ist sonst 100 % breit und
   stuende allein in einer Zeile (14.10.2026). */
.filter-zeile .ta-wrap {
    width: auto;
    min-width: 14rem;
}

.form-row {
    display: flex;
    gap: 1.25rem;
    flex-wrap: wrap;
    align-items: flex-end;
    margin-bottom: 1.1rem;
}

.form-row .form-group {
    flex: 1;
    min-width: 160px;
    margin-bottom: 0;
}

/* ================================================
   PAGINIERUNG + LISTENSUCHE
   ================================================ */
.pg-search {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 0.85rem;
    position: relative;
}
.pg-search > .material-symbols-outlined {
    position: absolute;
    left: 0.65rem;
    font-size: 20px;
    color: var(--text-light);
    pointer-events: none;
}
.pg-search input[type="search"] {
    flex: 1 1 260px;
    min-width: 0;
    max-width: 360px;
    padding-left: 2.35rem;
}

.pg-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem 1.25rem;
    margin-top: 0.85rem;
    padding-top: 0.85rem;
    box-shadow: 0 -1px 0 var(--border-light);
    font-size: 0.85rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.pg-info b { color: var(--text); font-weight: 700; }
.pg-size {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-left: auto;
}
.pg-size label {
    font-size: 0.8rem;
    color: var(--text-light);
    white-space: nowrap;
}
.pg-size select { padding: 0.35rem 0.5rem; font-size: max(var(--feld-mindest, 0px), 0.85rem); }
.pg-nav { display: inline-flex; align-items: center; gap: 0.5rem; }
.pg-pos { white-space: nowrap; }

@media (max-width: 640px) {
    .pg-bar { flex-direction: column; align-items: stretch; }
    .pg-size { margin-left: 0; justify-content: space-between; }
    .pg-nav { justify-content: space-between; }
    .pg-search input[type="search"] { max-width: none; }
}

/* ================================================
   BADGES / CHIPS — Stitch Design System
   ================================================ */
/* .pill ist seit dem 29.09.2026 dieselbe Marke wie .badge. Die Pille stammte
   aus Welle 0 (0,85rem, normale Schreibweise) und stand an rund 60 Stellen in
   Schul- und Plattformverwaltung und Treffpunkt — neben den Badges der
   uebrigen Rubriken, fuer dieselbe Aussage. DESIGN.md kennt nur die Badge-Form.
   Die Pille ohne Variante ist die neutrale Marke (DESIGN.md: gedaempfte
   Flaeche, gedaempfte Tinte); .badge allein bleibt ohne Flaeche. */
.badge,
.pill {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-family: 'Inter', 'Manrope', system-ui, sans-serif;
    font-size: 0.68rem;
    font-weight: 600;
    padding: 0.3rem 0.75rem;
    border-radius: 999px;
    white-space: nowrap;
    border: none;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.badge-gray,
.pill         { background: var(--surface-low); color: var(--text-muted); }
.badge-green,
.pill-success { background: var(--success-wash); color: var(--success-ink); }
.badge-yellow { background: var(--marker); color: var(--caution-ink); }
.pill-warn    { background: var(--caution-wash); color: var(--caution-ink); }
.badge-red,
.pill-danger  { background: var(--danger-wash); color: var(--danger-ink); }
.badge-blue,
.pill-info    { background: var(--info-wash); color: var(--info-ink); }
.badge-orange { background: var(--caution-wash); color: var(--danger-ink); }

/* Chip-Variante (für Tags, Kategorien) */
.chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.75rem;
    font-weight: 500;
    padding: 0.35rem 0.85rem;
    border-radius: 999px;
    background: var(--surface-low);
    color: var(--text-muted);
    white-space: nowrap;
    transition: background 0.15s;
}

.chip:hover {
    background: var(--surface-high);
}

.chip.is-active {
    background: var(--primary);
    color: var(--on-primary);
}

/* Als Filter (14.10.2026): a.chip in einer .filter-zeile ueber der Liste —
   die eine Filterform des Portals (DESIGN.md, Filter-sind-keine-Reiter-Regel). */
a.chip { text-decoration: none; }

/* ================================================
   LIST-TABLE — Stitch Design System
   ================================================ */
.list-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-xl);
    background: var(--card);
    margin-top: 1rem;
    box-shadow: var(--shadow-sm);
}

.list-table thead th {
    background: var(--surface-low);
    color: var(--text-light);
    font-family: 'Inter', 'Manrope', system-ui, sans-serif;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 0.85rem 1.1rem;
    border-bottom: 1px solid var(--border-light);
    text-align: left;
    white-space: nowrap;
    /* Hier stand „width: auto" — der Normalwert, aber spezifischer als jede
       Klasse. Solange Spaltenbreiten inline standen, fiel das nicht auf; seit
       sie Klassen sind (.cal-spalte-datum, .spalte-30 …), schluckte die Zeile
       sie alle. Entfernt am 30.09.2026. */
    position: sticky;
    top: 0;
    z-index: 10;
}
.list-table thead th:first-child { border-top-left-radius: var(--radius-xl); }
.list-table thead th:last-child { border-top-right-radius: var(--radius-xl); }
.list-table tbody tr:first-child th:first-child { border-top-left-radius: var(--radius-xl); }
.list-table tbody tr:last-child th { border-bottom-left-radius: var(--radius-xl); }
.list-table tbody tr:last-child td:last-child { border-bottom-right-radius: var(--radius-xl); }

.list-table tbody th,
.list-table th:not(thead th) {
    background: var(--bg-light);
    font-weight: 600;
    color: var(--text-mid);
    text-align: left;
    width: 25%;
    padding: 0.85rem 1.1rem;
    border-bottom: 1px solid var(--border-light);
    vertical-align: top;
}

.list-table tbody tr {
    transition: background 0.15s;
}

.list-table tbody td {
    padding: 0.85rem 1.1rem;
    border-bottom: 1px solid var(--border-light);
    font-size: 0.875rem;
    color: var(--text-mid);
    vertical-align: middle;
}

.list-table tbody tr:last-child td,
.list-table tbody tr:last-child th {
    border-bottom: none;
}

.list-table input[type="text"],
.list-table input[type="email"],
.list-table input[type="password"],
.list-table input[type="number"],
.list-table input[type="date"],
.list-table select,
.list-table textarea {
    width: 100%;
    box-sizing: border-box;
}

/* ================================================
   TOOLBAR / FILTER-LEISTE — Stitch Design System
   ================================================ */
.toolbar {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
    align-items: center;
    margin-bottom: 1.5rem;
}

/* ================================================
   LEERE ZUSTÄNDE — Stitch Design System
   ================================================ */
.empty-state {
    text-align: center;
    padding: 3.5rem 1.5rem;
    color: var(--text-light);
    background: var(--surface-low);
    border-radius: var(--radius-xl);
}

.empty-state-icon {
    font-size: 3rem;
    margin-bottom: 0.75rem;
    opacity: 0.4;
    color: var(--primary);
}

.empty-state p {
    margin: 0.35rem 0;
    font-size: 0.9rem;
    max-width: 400px;
    margin-left: auto;
    margin-right: auto;
    line-height: 1.5;
}

/* Der Titel traegt die Aussage, der Satz darunter die Begruendung.
   Ohne diesen Unterschied liest sich der leere Zustand wie eine Fehlermeldung. */
.empty-state-title {
    font-weight: 600;
    font-size: 1rem !important;
    color: var(--text);
}

/* Der Weg nach vorn. Ein leerer Zustand ohne Aktion ist eine Sackgasse —
   siehe app/empty_state.php. */
.empty-state-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    justify-content: center;
    margin-top: 1.25rem;
}

/* ================================================
   ERSTE SCHRITTE — die Einrichtungskarte auf der Startseite
   Siehe app/onboarding_checklist.php
   ================================================ */
.home-onboarding {
    margin-bottom: 1rem;
}
.home-onboarding .ob-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}
/* Seit dem 07.10.2026 ein h2 mit .karten-titel. Hier stand `margin: 0`, aber
   als h3 gewann `.card h3` (die Tag-Regel, bis 11.10.2026) den Abstand: Er war
   immer 0 0 1rem. Der Wert kommt deshalb aus .karten-titel. */
.home-onboarding h2 {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.home-onboarding .ob-sub {
    margin: 0.35rem 0 0;
    font-size: 0.85rem;
    color: var(--text-light);
    max-width: 62ch;
}
.home-onboarding .ob-dismiss {
    margin: 0;
    flex-shrink: 0;
}
.home-onboarding .ob-progress {
    margin: 0.9rem 0 0.25rem;
    font-size: 0.82rem;
    color: var(--text-light);
    font-variant-numeric: tabular-nums;
}
.home-onboarding .ob-list {
    list-style: none;
    margin: 0.5rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.home-onboarding .ob-item {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.7rem 0.25rem;
    border-top: 1px solid var(--border-subtle, var(--border-light));
}
.home-onboarding .ob-item:first-child {
    border-top: 0;
}
.home-onboarding .ob-item-icon {
    font-size: 22px;
    color: var(--primary);
    flex-shrink: 0;
}
.home-onboarding .ob-item--done .ob-item-icon {
    color: var(--success-ink);
}
.home-onboarding .ob-item-body {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    flex: 1;
    min-width: 0;
}
.home-onboarding .ob-item-title {
    font-weight: 600;
    font-size: 0.92rem;
    display: flex;
    align-items: center;
    gap: 0.45rem;
    flex-wrap: wrap;
}
/* Erledigtes wird leiser, nicht durchgestrichen: der Schritt bleibt lesbar,
   weil man ihn spaeter noch einmal braucht. */
.home-onboarding .ob-item--done .ob-item-title,
.home-onboarding .ob-item--done .ob-item-text {
    color: var(--text-light);
}
.home-onboarding .ob-item-text {
    font-size: 0.82rem;
    color: var(--text-light);
}
.home-onboarding .ob-optional {
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--text-light);
    background: var(--surface-low);
    border-radius: 999px;
    padding: 0.1rem 0.5rem;
}
.home-onboarding .ob-done-label {
    font-size: 0.78rem;
    color: var(--success-ink);
    font-weight: 600;
    flex-shrink: 0;
}
.home-onboarding .ob-item .button-primary,
.home-onboarding .ob-item .button-secondary {
    flex-shrink: 0;
    padding: 0.4rem 1.1rem;
    font-size: 0.82rem;
}

@media (max-width: 560px) {
    .home-onboarding .ob-item {
        flex-wrap: wrap;
    }
    .home-onboarding .ob-item .button-primary,
    .home-onboarding .ob-item .button-secondary {
        width: 100%;
        justify-content: center;
        margin-left: 37px;   /* auf die Textspalte ausrichten */
    }
}

/* Ein Suchergebnis ist kein Erstkontakt: hier fehlt nichts, es passt nur
   gerade nichts. Deshalb leiser und ohne Einladung zum Anlegen. */
.empty-state--filtered {
    padding: 2.25rem 1.5rem;
    background: transparent;
}
.empty-state--filtered .empty-state-icon {
    font-size: 2.25rem;
}

/* ================================================
   Typeahead / Autocomplete
   ================================================ */

.ta-wrap {
    position: relative;
    display: inline-block;
    width: 100%;
}

.ta-input {
    width: 100%;
    padding: 0.35rem 2rem 0.35rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    font-size: max(var(--feld-mindest, 0px), 0.9rem);
    box-sizing: border-box;
    background: var(--card);
    color: var(--text);
}

.ta-input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--focus) 15%, transparent);
}

.ta-arrow {
    position: absolute;
    right: 0.5rem;
    top: 50%;
    transform: translateY(-50%);
    pointer-events: none;
    color: var(--text-light);
    font-size: 0.7rem;
}
.ta-arrow::after { content: '▾'; }

.ta-list {
    position: absolute;
    z-index: 999;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--card);
    border: 1px solid var(--border);
    border-top: none;
    border-radius: 0 0 6px 6px;
    max-height: 260px;
    overflow-y: auto;
    list-style: none;
    margin: 0;
    padding: 0.25rem 0;
    box-shadow: 0 6px 16px color-mix(in srgb, var(--text) 12%, transparent);
}

.ta-item {
    padding: 0.4rem 0.7rem;
    font-size: 0.9rem;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ta-item:hover,
.ta-item.ta-active {
    background: var(--primary-light);
    color: var(--primary);
}

.ta-item mark {
    background: none;
    font-weight: 700;
    color: var(--primary);
}

.ta-empty {
    padding: 0.4rem 0.7rem;
    font-size: 0.85rem;
    color: var(--text-light);
    font-style: italic;
}

/* Raumwahl (app/raumwahl.js): dieselbe Trefferliste unter einem freien
   Ortsfeld. Anders als beim Typeahead haengt sie nicht an einer eigenen
   Eingabe, sondern steht mit Abstand unter dem Feld. Zustand in Wort,
   Symbol und Tinte — nie in Farbe allein (29.09.2026; vorher Inline-Stile
   mit sechs festen Farben im Footer). */
.raumwahl { position: relative; }
.raumwahl .ta-list {
    margin-top: 4px;
    border-top: 1px solid var(--border);
    border-radius: var(--radius);
    /* Ein Ortsfeld steht oft in einer halben Spalte (Pinnwand: 135px). Name
       und Zustand brauchen mehr: die Liste darf breiter sein als das Feld. */
    right: auto;
    min-width: 100%;
    width: max-content;
    max-width: min(24rem, calc(100vw - 2rem));
}
.raumwahl .ta-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}
.raum-name { display: inline-flex; align-items: center; gap: 0.4rem; min-width: 0; }
.raum-name .material-symbols-outlined { font-size: 18px; color: var(--text-light); }
.raum-status { display: inline-flex; align-items: center; gap: 0.2rem; font-size: 0.72rem; font-weight: 600; flex-shrink: 0; }
.raum-status .material-symbols-outlined { font-size: 14px; }
.raum-status--frei     { color: var(--success-ink); }
.raum-status--belegt   { color: var(--danger-ink); }
.raum-status--gesperrt { color: var(--text-light); }

/* Filter-Eingabe für Multi-Select */
.ta-multi-filter {
    display: block;
    width: 100%;
    padding: 0.3rem 0.5rem;
    margin-bottom: 0.3rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    font-size: 0.85rem;
    box-sizing: border-box;
    background: var(--card);
}

.ta-multi-filter:focus {
    outline: none;
    border-color: var(--primary);
}

/* ================================================
   SHARED: scope-pill (forms, test_library,
   form_template_edit)
   ================================================ */
.scope-pill {
    display: inline-block;
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--surface-low);
    color: var(--text-mid);
    font-size: 12px;
    font-weight: 600;
}
.scope-pill.scope-global { background:var(--info-wash); border-color:var(--info-border); color:var(--info-ink); }
.scope-pill.scope-provider { background:var(--success-wash); border-color:var(--success-border); color:var(--success-ink); }
.scope-pill.scope-school { background:var(--caution-wash); border-color:var(--caution-border); color:var(--caution-ink); }

/* ================================================
   SHARED: Zahlkasten — offene Rechnung mit Zahlungsdaten
   (Abo-Seite der Schule, Vertragsseite des Trägers; bis
   16.10.2026 je eine Abschrift in beiden Modul-Stildateien)
   ================================================ */
.zahlkasten { margin-top: 1rem; padding: 1rem; border: 1px solid var(--border); border-radius: 10px; background: var(--surface-low); }
.zahlkasten-titel { margin: 0 0 0.4rem; font-size: 1rem; }

/* ================================================
   SHARED: stack layout (gremien, gremien_meeting)
   ================================================ */
.stack { display:flex; flex-wrap:wrap; gap:0.35rem; align-items:center; }

/* ================================================
   SHARED: tight table cells (gremien, gremien_meeting)
   ================================================ */
.tight th, .tight td { padding:0.45rem 0.6rem; }

/* ================================================
   SHARED: muted text (gremien, gremien_meeting)
   ================================================ */
.muted { color:var(--text-light); font-size:0.82rem; }

/* ================================================
   SHARED: rich-toolbar / rich-btn
   (gremien_meeting, project_view)
   ================================================ */
.rich-toolbar {
    display:flex;
    gap:0.35rem;
    flex-wrap:wrap;
    margin:0.35rem 0;
}
.rich-btn {
    border:1px solid var(--border);
    background:var(--bg);
    padding:0.2rem 0.5rem;
    border-radius:4px;
    font-size:0.85rem;
    cursor:pointer;
}

/* ================================================
   SHARED: actions-cell (test_library, test_runs)
   ================================================ */
.cell-actions {
    white-space:nowrap;
}
.actions-cell {
    display:flex;
    flex-wrap:nowrap;
    gap:0.25rem;
}

/* ================================================
   === page: home.php ===
   ================================================ */

.home-mini-calendar {
    width: 320px;
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 0.55rem 0.65rem;
    background: var(--bg);
}
.mini-cal-title {
    font-size: 0.85rem;
    font-weight: 600;
    margin-bottom: 0.4rem;
}
.mini-cal-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
    font-size: 0.72rem;
}
.mini-cal-dow {
    text-align: center;
    color: var(--text-light);
    font-weight: 600;
    padding: 2px 0;
}
.mini-cal-day {
    min-height: 26px;
    border: 1px solid var(--border);
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    background: var(--card);
}
.mini-cal-day a {
    text-decoration: none;
    color: var(--text);
    width: 100%;
    text-align: center;
}
.mini-cal-dot {
    position: absolute;
    right: 3px;
    bottom: 3px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
}
.mini-upcoming {
    margin-top: 0.5rem;
    border-top: 1px solid var(--border);
    padding-top: 0.4rem;
}
.mini-upcoming a {
    text-decoration: none;
}

.mini-list {
    margin:0;
    padding-left:1rem;
}
.mini-list li {
    margin-bottom:0.4rem;
}
.mini-muted {
    color:var(--text-light);
    font-size:0.82rem;
}
.mini-status {
    display:inline-block;
    padding:0.1rem 0.35rem;
    border-radius:999px;
    font-size:0.72rem;
    margin-left:0.35rem;
    background:var(--surface-high);
    color:var(--text);
}
.todo-list {
    display:flex;
    flex-direction:column;
    gap:0.55rem;
}
.todo-item {
    border:1px solid var(--border);
    border-radius:8px;
    padding:0.55rem 0.7rem;
    background:var(--card);
    display:grid;
    grid-template-columns: minmax(0,1fr) auto;
    gap:0.55rem;
}
.todo-title { font-weight:600; color:var(--text); }
.todo-meta { font-size:0.8rem; color:var(--text-light); }
.todo-actions {
    display:flex;
    flex-wrap:wrap;
    gap:0.35rem;
    align-content:flex-start;
    justify-content:flex-end;
}
.todo-prio {
    display:inline-block;
    padding:0.08rem 0.45rem;
    border-radius:999px;
    font-size:0.72rem;
    font-weight:600;
    margin-left:0.35rem;
}
.todo-prio-hoch { background:var(--danger-wash); color:var(--danger-ink); }
.todo-prio-mittel { background:var(--marker); color:var(--caution-ink); }
.todo-prio-niedrig { background:var(--info-wash); color:var(--info-ink); }
.todo-status-rueckfrage { background:var(--surface-container); color:var(--text-muted); }
.todo-status-neu { background:var(--success-wash); color:var(--success-ink); }
.todo-status-offen { background:var(--surface-high); color:var(--text); }
/* .todo-manual-* steht in app/startseite.css (29.09.2026: die Kopie hier war
   wirkungslos, startseite.css ueberschrieb sie). */

@media (max-width: 720px) {
    .home-mini-calendar {
        width: 100%;
    }
    .todo-item {
        grid-template-columns:1fr;
    }
}

/* ================================================
   === page: calendar.php ===
   ================================================ */
.calendar-event-link { text-decoration: none; color: var(--text); }
.calendar-event-link:hover { text-decoration: none; color: var(--primary); }

/* ================================================
   === page: forms.php ===
   ================================================ */
.forms-actions { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.forms-actions .button-secondary-small,
.forms-actions .button-danger-small,
.forms-actions .button-primary {
    min-width: 112px;
    justify-content: center;
}

/* -- Inline member panel in sidebar -- */

.member-cb-item:hover{background:var(--surface-low);}

/* ================================================
   === page: schooladmin.php ===
   ================================================ */
.card-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 1rem;
}
.card-grid .card {
    display:flex;
    flex-direction:column;
}
.card-grid .card p {
    flex-grow:1;
}
.card-grid .card .form-actions {
    margin-top:auto;
}
/* ═══════════ DIE EINE KARTENÜBERSCHRIFT (28.09.2026) ═══════════
   Hier stand bis heute nur `margin-top: 0`. Die Folge: Jedes Modul hat sich
   seine eigene Kartenueberschrift gebaut — gemessen waren es ELF
   verschiedene, von 13,5px/700 bis 16px/800. Stephan am 28.09.2026: „Im
   Schulprogramm sind die Cards duenner beschriftet als in den uebrigen
   Modulen. Das Layout sollte ueber alle Seiten hinweg keine Brueche haben."

   Die duennste war `.sp-concept-card-title` mit Gewicht **650** — ein Wert,
   den es sonst nirgends im Portal gibt.

   Jetzt steht die Ueberschrift EINMAL hier. Wer eine Karte baut, bekommt sie
   geschenkt; wer eine andere braucht, nimmt die Kompakt-Variante darunter
   und begruendet es nicht neu. Die Werte sind die, die die Mehrheit der
   Module ohnehin schon benutzte (Kalender, Curricula, Testbibliothek,
   Treffpunkt, Mein Konto, Datenimport, Anträge).

   NUR DIE KLASSE, KEIN TAG (11.10.2026). Hier stand zusaetzlich `.card h3`:
   Jedes h3 in einer Karte wurde Kartentitel, ob es einer war oder nicht.
   Seit die Ueberschriften am 07.10.2026 eine Ebene hoeher rueckten, griff die
   Regel auf Unterueberschriften (Mein Konto, Mail-Vorschau, Einstellungen,
   Raeume), und jede brauchte eine Gegenregel mit mehr Gewicht. Wer einen
   Kartentitel will, schreibt die Klasse (DESIGN.md, Klasse-statt-Tag-Regel). */
.karten-titel {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 1rem;
    font-size: 1rem;
    font-weight: 700;
    color: var(--text);
}

/* EIN KARTENTITEL, DER EIN LINK IST, BLEIBT TINTE. Ohne diese Zeile nimmt
   er die Linkfarbe — genau so standen am 28.09.2026 achtzehn Konzeptnamen
   im Schulprogramm in Blau, wo alle anderen Rubriken schwarz schreiben.
   Blau bleibt dem Symbol daneben und dem Ueberfahren vorbehalten. */
a.karten-titel,
a.karten-titel:visited,
a.karten-titel--kompakt,
a.karten-titel--kompakt:visited,
.karten-titel > a,
.karten-titel > a:visited {
    color: var(--text);
    text-decoration: none;
}

a.karten-titel:hover,
a.karten-titel--kompakt:hover,
.karten-titel > a:hover {
    color: var(--primary);
}

.karten-titel .material-symbols-outlined {
    font-size: 22px;
    color: var(--primary);
}

/* KOMPAKT — fuer Karten, die in einer Reihe stehen und nicht die Seite
   tragen: die Aufgabenkarte im Kanban, der Schritt im Genehmigungsweg, die
   Notiz am Bausteinkasten. Eine zweite Groesse, nicht die siebte. */
.karten-titel--kompakt {
    font-size: 0.85rem;
    margin-bottom: 0.5rem;
}
/* Hier stand bis zum 11.10.2026 `.card .karten-titel--kompakt` mit denselben
   Werten — eine Gegenregel, weil `.card h3` spezifischer war als die Klasse
   (Projekt-Beteiligte, 30.09.2026). Mit der Tag-Regel ist sie entfallen. */
.karten-titel--kompakt .material-symbols-outlined {
    font-size: 18px;
}
@media (max-width: 1400px) {
    .card-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}
@media (max-width: 1100px) {
    .card-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
@media (max-width: 720px) {
    .card-grid {
        grid-template-columns: 1fr;
    }
}

/* ================================================
   === page: account_settings.php ===
   ================================================ */
/* .kat--<farbe> setzt --kat-grund und --kat-farbe aus dem Tokenpaar. Benutzt
   von den Kategorie-Kaesten der Formulare und den Methodenmarken der
   Projekte (30.09.2026). */
.kat--blau{--kat-grund:var(--kat-blau-grund);--kat-farbe:var(--kat-blau-farbe);}
.kat--tuerkis{--kat-grund:var(--kat-tuerkis-grund);--kat-farbe:var(--kat-tuerkis-farbe);}
.kat--gruen{--kat-grund:var(--kat-gruen-grund);--kat-farbe:var(--kat-gruen-farbe);}
.kat--gelb{--kat-grund:var(--kat-gelb-grund);--kat-farbe:var(--kat-gelb-farbe);}
.kat--orange{--kat-grund:var(--kat-orange-grund);--kat-farbe:var(--kat-orange-farbe);}
.kat--rot{--kat-grund:var(--kat-rot-grund);--kat-farbe:var(--kat-rot-farbe);}
.kat--rosa{--kat-grund:var(--kat-rosa-grund);--kat-farbe:var(--kat-rosa-farbe);}
.kat--lila{--kat-grund:var(--kat-lila-grund);--kat-farbe:var(--kat-lila-farbe);}
.kat--indigo{--kat-grund:var(--kat-indigo-grund);--kat-farbe:var(--kat-indigo-farbe);}
.kat--grau{--kat-grund:var(--kat-grau-grund);--kat-farbe:var(--kat-grau-farbe);}

/* .feld-voll — ein Feld ueber die ganze Breite seines Behaelters, wo kein
   .form-group es streckt. Stand im Projektmanagement ~40-mal als style=. */
.feld-voll { width: 100%; box-sizing: border-box; }

/* .feld-hinweis — ein leiser Satz direkt unter einem Feld, wo kein
   .form-group ihn setzt (Feld in einer Tabellenzelle). Stand bis zum
   30.09.2026 als margin-top:0.35rem von Hand. Mit .hint zusammen. */
.feld-hinweis { display: block; margin-top: 0.35rem; font-size: 0.8rem; }
/* Eine Fehlermeldung an derselben Stelle — etwa „Bitte … ausfüllen.“ im Dialog. */
.feld-hinweis--fehler { color: var(--danger-ink); font-weight: 600; }

.form-group small {
    display:block;
    margin-top:0.2rem;
    font-size:0.8rem;
    color:var(--text-muted);
}

/* ================================================
   === page: help_center.php ===
   ================================================ */
.help-step {
    display: grid;
    grid-template-columns: 32px 1fr;
    gap: 0 1rem;
    align-items: start;
    margin-bottom: 1.5rem;
}
.help-step:last-child {
    margin-bottom: 0;
}
.help-step-number {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: var(--primary-container);
    color: var(--on-primary);
    font-weight: 700;
    font-size: 0.9rem;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    margin-top: 2px;
}
.help-step-body h3 {
    margin: 0 0 0.35rem;
    font-size: 0.95rem;
    color: var(--text-mid);
}
.help-step-body p {
    margin: 0 0 0.6rem;
    font-size: 0.875rem;
    line-height: 1.55;
    color: var(--text-mid);
}

/* ================================================
   DRAG HANDLE (SortableJS rows)
   ================================================ */

.sortable-ghost {
    background: var(--info-wash) !important;
    opacity: 0.6;
}

/* ================================================
   RESPONSIVE: Table wrapper
   ================================================ */
/* DIE KURZSCHREIBWEISE HAT DAS SEITWAERTS-ROLLEN GETOETET (bis 27.09.2026).

   Hier stand unter `overflow-x: auto` noch ein `overflow: hidden` — und die
   Kurzschreibweise setzt BEIDE Achsen. Damit tat die Klasse das Gegenteil
   dessen, wofuer es sie gibt: Jede Tabelle, die breiter ist als ihre Karte,
   wurde am rechten Rand abgeschnitten, ohne Bildlaufleiste und ohne jede
   Moeglichkeit, an den Rest zu kommen.

   Das traf NICHT eine Seite, sondern das ganze Portal: layout_footer.php
   legt diese Huelle per JavaScript um jede <table>. Stephan hat es am
   27.09.2026 in der Plattformverwaltung gesehen — Huelle 1160px, Tabelle
   1210px, die Spalte AKTIONEN unerreichbar.

   `overflow-y: hidden` bleibt, weil der border-radius sonst nicht schneidet.
   Auf der X-Achse wird wieder gerollt. */
.table-responsive {
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    border-radius: 8px;
}
/* Eine zu breite Tabelle direkt in einer Karte (layout_footer.php): Die Huelle
   traegt den oberen Abstand, den sonst die Tabelle traegt — so faellt er
   weiter mit dem Abstand des Elements davor zusammen. */
.table-responsive--karte { margin-top: 1rem; }
.table-responsive--karte > .list-table { margin-top: 0; }

/* Sticky header: scrollbare Tabellen mit fixiertem Kopf.
   overflow wird auf auto gesetzt, damit sticky funktioniert. */
.table-responsive.table-sticky {
    max-height: 70dvh;
    overflow: auto;
    border: 1px solid var(--border-light);
}

.table-responsive.table-sticky .list-table {
    border: none;
    border-radius: 0;
    margin-top: 0;
}

/* ================================================
   RESPONSIVE: Mobile breakpoints
   ================================================ */

/* --- Tables (.list-table) --- */
@media (max-width: 768px) {
    .list-table {
        font-size: 0.82rem;
    }
    .list-table th,
    .list-table td {
        padding: 0.4rem 0.5rem;
    }

}

/* --- Navigation --- */
@media (max-width: 768px) {
    header nav {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        white-space: nowrap;
        padding-bottom: 4px;
    }

}

/* --- Forms inside tables --- */
@media (max-width: 768px) {
    .list-table input[type="text"],
    .list-table input[type="email"],
    .list-table input[type="number"],
    .list-table select,
    .list-table textarea {
        min-width: 120px;
    }
}

/* --- Toast container --- */
@media (max-width: 480px) {
    #toast-container {
        left: 1rem;
        right: 1rem;
        max-width: none;
    }
}

/* --- Quick actions grid --- */
@media (max-width: 480px) {
    .quick-actions-grid {
        grid-template-columns: 1fr;
    }
}

/* ================================================
   DRAG-AND-DROP FILE UPLOAD ZONE
   ================================================ */
.file-drop-zone {
    position: relative;
    border: 2px dashed var(--border);
    border-radius: 8px;
    padding: 1.5rem;
    text-align: center;
    transition: border-color 0.2s, background 0.2s;
    cursor: pointer;
}

.file-drop-zone:hover {
    border-color: var(--primary);
    background: var(--primary-light);
}

.file-drop-zone.drag-over {
    border-color: var(--primary);
    background: var(--primary-light);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 15%, transparent);
}

/* Auf der blauen Waschung reicht --text-light nicht mehr: 4,14:1 statt 4,5:1
   (gemessen am 02.10.2026). Der Hinweis nimmt dort die dunklere Schrift. */
.file-drop-zone:hover .drop-hint,
.file-drop-zone.drag-over .drop-hint { color: var(--text-muted); }

.file-drop-zone > input[type="file"] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.file-drop-zone .drop-icon {
    display: block;
    font-size: 28px;
    color: var(--primary);
    margin: 0 auto 0.3rem;
}

.file-drop-zone .file-preview .material-symbols-outlined {
    font-size: 20px;
}

.file-drop-zone .drop-text {
    font-size: 0.88rem;
    color: var(--text-muted);
}

.file-drop-zone .drop-text strong {
    color: var(--primary);
}

.file-drop-zone .drop-hint {
    font-size: 0.78rem;
    color: var(--text-light);
    margin-top: 0.3rem;
}

.file-drop-zone .file-preview {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.75rem;
    padding: 0.5rem 0.75rem;
    background: var(--bg-subtle);
    border-radius: 6px;
    font-size: 0.85rem;
    color: var(--text-mid);
}

.file-drop-zone .file-preview .file-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.file-drop-zone .file-preview .file-remove {
    position: relative;
    z-index: 1;
    display: inline-flex;
    background: none;
    border: none;
    color: var(--text-light);
    cursor: pointer;
    padding: 0 0.3rem;
    line-height: 1;
}

.file-drop-zone .file-preview .file-remove .material-symbols-outlined {
    font-size: 18px;
}

.file-drop-zone .file-preview .file-remove:hover {
    color: var(--danger-ink);
}

/* ================================================
   FLOATING ACTION BUTTON (Mobile)
   ================================================ */
.fab-container {
    display: none;
}

@media (max-width: 768px) {
    .fab-container {
        display: block;
        position: fixed;
        bottom: 1.5rem;
        right: 1.5rem;
        z-index: 900;
    }

    .fab-toggle {
        width: 52px;
        height: 52px;
        border-radius: 50%;
        background: var(--primary);
        color: var(--on-primary);
        border: none;
        font-size: 1.5rem;
        cursor: pointer;
        box-shadow: 0 4px 16px color-mix(in srgb, var(--text) 20%, transparent);
        display: flex;
        align-items: center;
        justify-content: center;
        transition: transform 0.2s, background 0.2s;
    }

    .fab-toggle:hover,
    .fab-toggle:focus {
        background: var(--primary-hover);
    }

    .fab-container.open .fab-toggle {
        transform: rotate(45deg);
    }

    .fab-menu {
        position: absolute;
        bottom: 62px;
        right: 0;
        display: flex;
        flex-direction: column;
        gap: 0.4rem;
        opacity: 0;
        transform: translateY(10px) scale(0.95);
        pointer-events: none;
        transition: opacity 0.2s, transform 0.2s;
    }

    .fab-container.open .fab-menu {
        opacity: 1;
        transform: translateY(0) scale(1);
        pointer-events: auto;
    }

    .fab-action {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        padding: 0.5rem 1rem;
        background: var(--card);
        border: 1px solid var(--border);
        border-radius: 999px;
        font-size: 0.82rem;
        color: var(--text-mid);
        text-decoration: none;
        white-space: nowrap;
        box-shadow: 0 2px 8px color-mix(in srgb, var(--text) 10%, transparent);
        transition: background 0.15s;
    }

    .fab-action:hover {
        background: var(--bg-subtle);
    }

}

/* ===== RICHTEXT CONTENT (Schulprogramm inline media) ===== */

.richtext-content::after {
    content: '';
    display: table;
    clear: both;
}

.richtext-content img {
    max-width: 100%;
    height: auto;
}

.richtext-content video {
    max-width: 100%;
    height: auto;
}

/* ================================================
   APP-SHELL: SIDEBAR NAVIGATION
   ================================================ */
.app-shell {
    display: flex;
    /* dvh, nicht vh: Auf iOS rechnet vh die Adressleiste nicht ein, die
       Shell war dadurch hoeher als das Sichtfeld (08.10.2026). */
    min-height: 100dvh;
}

/* --- Sidebar --- */
.sidebar {
    position: fixed;
    top: 0;
    left: 0;
    width: 256px;
    height: 100dvh;
    background: var(--surface-low);
    display: flex;
    flex-direction: column;
    padding: 1.25rem 0 0;
    overflow: hidden;
    z-index: 100;
    transition: transform 0.25s ease;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0 1.25rem;
    margin-bottom: 1.5rem;
    text-decoration: none;
}

.sidebar-brand-icon {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-lg);
    background: linear-gradient(135deg, var(--hero-from) 0%, var(--hero-to) 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--on-primary);
    flex-shrink: 0;
    box-shadow: 0 4px 12px color-mix(in srgb, var(--primary) 20%, transparent);
}

.sidebar-brand-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.sidebar-brand-name {
    font-family: 'Manrope', system-ui, sans-serif;
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text-dark);
    letter-spacing: -0.01em;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sidebar-brand-claim {
    font-family: 'Inter', 'Manrope', system-ui, sans-serif;
    font-size: 0.6rem;
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 500;
}

/* --- Nav Links --- */
.sidebar-nav {
    flex: 1;
    display: flex;
    flex-direction: column;
    /* Zwischen Gruppen grosszuegig (DESIGN.md: 24-32px), innerhalb eng. */
    gap: 1.5rem;
    padding: 0 0.75rem;
    overflow-y: auto;
    min-height: 0;
}

/* DIE GRUPPEN DER SEITENLEISTE (DESIGN.md, Abschnitt „Navigation").
   „Zwoelf Zeilen am Stueck sind eine Liste, die man absucht; vier Gruppen
   sind eine Ordnung, die man versteht."

   Die Ueberschrift ist eine ABSCHNITTSMARKE, keine Feldbeschriftung —
   Versalien sind hier vorgesehen und genauso gesetzt wie im Tabellenkopf:
   Inter 600 bei 0,7rem in heller Tinte. */
/* Der Kopierknopf in der Kopfzone. Stand an zwei Stellen als
   `style="all:unset;cursor:pointer;color:rgba(255,255,255,0.9);"` im
   Markup — zwei Geisterknöpfe, die kein Waechter je gesehen hat. Die
   Schrift bezieht sich auf den Verlauf darunter, deshalb --on-hero. */
/* Die Zeile „Öffentlicher Link" unter den Zahlen (page_hero_oeffentlicher_link). */
.hero-oeffentlich {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.9rem;
    font-size: 0.78rem;
    color: color-mix(in srgb, var(--on-hero) 90%, transparent);   /* 0.7 ergab 3,53:1 (02.10.2026) */
}
.hero-oeffentlich .material-symbols-outlined { font-size: 16px; }
.hero-oeffentlich .hero-symbollink .material-symbols-outlined,
.hero-oeffentlich .hero-kopierknopf .material-symbols-outlined { font-size: 14px; }

.hero-kopierknopf {
    cursor: pointer;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    line-height: 1;
    color: var(--on-hero);
    opacity: 0.9;
}
.hero-kopierknopf:hover { opacity: 1; }

/* EIN SYMBOL UNTERSTREICHT MAN NICHT. Der Link „öffentliche Seite öffnen"
   in der Kopfzone trug bis zum 28.09.2026 `text-decoration:underline` als
   Inline-Stil — ein Unterstrich unter einem Pfeilsymbol, den es sonst
   nirgends im Portal gibt. Stephan hat ihn am selben Tag am
   Schulprogramm bemerkt. */
.hero-symbollink {
    color: var(--on-hero);
    opacity: 0.9;
    text-decoration: none;
    line-height: 1;
}
.hero-symbollink:visited { color: var(--on-hero); }
.hero-symbollink:hover { opacity: 1; }
/* TREFFERFLAECHE (01.10.2026). Die beiden Symbole massen 14×15px — unter
   den 24px, die DESIGN.md (WCAG 2.5.8) als Untergrenze nennt. Polsterung und
   negativer Rand vergroessern die Flaeche auf 26px, ohne etwas zu verschieben. */
.hero-kopierknopf,
.hero-symbollink { display: inline-flex; padding: 6px; margin: -6px; }

/* Die Zeile „Öffentlicher Link" in der Kopfzone des Lehrplans (29.09.2026).
   Dieselbe Gestalt wie in der Fachübersicht und im Schulprogramm, dort noch
   als style= im Markup. Farbe aus --on-hero, weil sie auf dem Verlauf steht. */
.page-hero-link {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.9rem;
    font-size: 0.78rem;
    color: var(--on-hero);
    opacity: 0.9;   /* 0.75 lag unter 4,5:1 (02.10.2026) */
}
.page-hero-link .material-symbols-outlined { font-size: 16px; }
.page-hero-link .hero-symbollink .material-symbols-outlined,
.page-hero-link .hero-kopierknopf .material-symbols-outlined { font-size: 14px; }

.sidebar-gruppe {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.sidebar-gruppe-titel {
    margin: 0 0 0.35rem;
    padding: 0 0.75rem;
    color: var(--text-light);
    font-family: 'Inter', 'Manrope', system-ui, sans-serif;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.sidebar-link {
    display: flex;
    align-items: center;
    min-height: 44px;
    box-sizing: border-box;
    gap: 0.75rem;
    padding: 0.55rem 0.75rem;
    border-radius: var(--radius);
    text-decoration: none;
    color: var(--text-muted);
    font-size: 0.875rem;
    font-weight: 500;
    transition: all 0.15s ease;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    border-right: 3px solid transparent;
}

.sidebar-link:visited {
    color: var(--text-muted);
}

.sidebar-link:hover {
    color: var(--primary);
    background: color-mix(in srgb, var(--primary) 5%, transparent);
}

.sidebar-link.is-active {
    color: var(--primary);
    background: color-mix(in srgb, var(--primary) 8%, transparent);
    font-weight: 600;
    border-right-color: var(--primary);
}

.sidebar-link.is-active:visited {
    color: var(--primary);
}

.sidebar-link .material-symbols-outlined {
    font-size: 22px;
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* --- Sidebar Footer (User Area) --- */
.sidebar-footer {
    margin-top: auto;
    padding: 0.75rem 0.75rem 1.25rem;
    border-top: 1px solid var(--border-light);
    flex-shrink: 0;
}

.sidebar-user {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.6rem 0.75rem;
    border-radius: var(--radius);
    background: color-mix(in srgb, var(--card) 60%, transparent);
}

.sidebar-user-info {
    min-width: 0;
    flex: 1;
}

.sidebar-user-name {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-dark);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sidebar-user-role {
    font-family: 'Inter', 'Manrope', system-ui, sans-serif;
    font-size: 0.65rem;
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.sidebar-logout {
    display: flex;
    align-items: center;
    min-height: 44px;
    box-sizing: border-box;
    gap: 0.5rem;
    padding: 0.45rem 0.75rem;
    margin-top: 0.35rem;
    border-radius: var(--radius);
    text-decoration: none;
    color: var(--text-light);
    font-size: 0.8rem;
    font-weight: 500;
    transition: all 0.15s ease;
}

/* Abmelden ist ein Knopf (POST mit CSRF), sieht aber aus wie die Links
   darueber. Stand bis zum 30.09.2026 als style= im Layout. */
.sidebar-logout-form { margin: 0; }
button.sidebar-logout { width: 100%; border: none; background: none; cursor: pointer; text-align: left; }

.sidebar-logout:visited {
    color: var(--text-light);
}

.sidebar-logout:hover {
    color: var(--danger-ink);
    background: color-mix(in srgb, var(--danger-ink) 5%, transparent);
}

/* --- Main Content Area (neben Sidebar) --- */
.app-content {
    margin-left: 256px;
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}
/* Seiten ohne Anmeldung haben keine Seitenleiste, also auch keinen Platz für sie. */
.app-content--ohne-leiste {
    margin-left: 0;
}

/* --- Top Bar (kontextuell, in app-content) --- */
.app-topbar {
    position: sticky;
    top: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 56px;
    padding: 0 1.5rem;
    background: color-mix(in srgb, var(--card) 85%, transparent);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--border-light);
    box-shadow: var(--shadow-sm);
}

.app-topbar-left {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 0;
    flex: 1;
}

.app-topbar-title {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text-dark);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* Ein-Titel-Regel: unsichtbar, solange die Kopfzone den Namen traegt.
       Kein display:none und kein Inline-opacity — beides filtert der Server. */
    opacity: 0;
    transform: translateY(4px);
    transition: opacity 0.18s ease, transform 0.18s ease;
    pointer-events: none;
}
.app-topbar-title.is-shown {
    opacity: 1;
    transform: none;
}

/* ================================================
   TOPBAR OHNE EIGENEN PLATZ (Ein-Titel-Regel, zweiter Teil)
   Steht die Kopfzone ganz oben, legt sich die Leiste durchsichtig
   darueber statt 56px weiss zu verbrauchen. Die Glocke sitzt dann
   weiss auf dem Blau. Beim Scrollen wird die Leiste wieder fest.
   Die Klasse setzt das Skript nur, wenn die Kopfzone wirklich das
   erste Element im Inhalt ist — sonst bleibt alles beim Alten.
   ================================================ */
body.hero-leads main {
    /* Die Leiste ist 56px hoch, <main> hat 1.25rem Innenabstand oben.
       Beides muss weg, damit die Kopfzone wirklich am oberen Rand beginnt —
       sonst haengt die Glocke halb ueber der Kante. */
    margin-top: -56px;
    padding-top: 0;
}
.page-hero.is-lead {
    /* Kein Ausgleich fuer die entfallene Leiste — sonst waere nichts gewonnen.
       Die Kopfzone behaelt ihren normalen Innenabstand, der Inhalt rueckt um
       die vollen 76px nach oben. */
    padding-top: 1.25rem;
    /* Beruehrt sie den oberen Rand, sind runde Ecken dort falsch:
       oben gerade, unten weiter 16px. */
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}
/* Rechts oben sitzt die Glocke — die eigenen Aktionen weichen ihr aus,
   statt sich mit ihr zu ueberlagern. */
.page-hero.is-lead .page-hero-top {
    padding-right: 52px;
}
body.hero-leads .app-topbar {
    background: transparent;
    /* Die durchsichtige Leiste liegt ueber der Kopfzone und fing sonst
       jeden Klick ab, der eigentlich einer Kopfzonen-Aktion galt.
       Sie laesst Klicks jetzt durch; ihre eigenen Bedienelemente
       nehmen sie ueber die Regel darunter wieder an. */
    pointer-events: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-bottom: none;
    box-shadow: none;
    transition: background 0.18s ease, border-color 0.18s ease;
}
/* Nur die echten Bedienelemente nehmen Klicks an — nicht die Wrapper-Boxen.
   Die linke Box liegt sonst ueber dem Home-Link der Brotkrume. */
body.hero-leads .app-topbar .topbar-icon-btn,
body.hero-leads .app-topbar .burger-btn,
body.hero-leads .app-topbar .notif-wrap,
body.hero-leads .app-topbar .notif-dropdown {
    pointer-events: auto;
}
/* SOBALD SIE ETWAS VERDECKT, IST SIE DECKEND — auf jeder Seite.
   Voll deckend statt 85 % mit Weichzeichner: Sie traegt beim Scrollen Titel
   UND primaere Aktion, darunter liegt eine dichte Tabelle oder eine farbige
   Kopfzone — durchscheinend wurde beides milchig.

   BIS ZUM 04.10.2026 GALT DAS NUR BEI `body.hero-leads`, also nur dort, wo
   die Kopfzone das erste Element der Seite ist. Auf allen anderen Seiten
   blieb die Leiste beim Scrollen bei 85 Prozent, und die farbige Kopfzone
   schob sich darunter hinweg: Der SEITENTITEL schimmerte blass durch die
   Leiste, waehrend die Leiste denselben Titel schon trug. Zwei Titel
   uebereinander — und damit ein Verstoss gegen die Ein-Titel-Regel
   (DESIGN.md). Gemeldet fuer Schulprogramm, Projektmanagement und die
   Sitzung; es betraf jede Seite mit Kopfzone, die nicht fuehrt.

   Die Entscheidung stand schon im Kommentar der alten Regel. Sie galt nur
   zu eng. */
/* BEIDE SELEKTOREN (15.10.2026): `body.hero-leads .app-topbar` (oben, 0-2-1)
   macht die Leiste durchsichtig und schlug `.app-topbar.is-stuck` (0-2-0).
   Seit dem 04.10.2026 blieb sie deshalb auf jeder Seite mit fuehrender
   Kopfzone beim Scrollen durchsichtig; Seitenname und Glocke lagen ueber dem
   Inhalt. Mit dem zweiten Selektor (0-3-1) gewinnt die deckende Flaeche. */
.app-topbar.is-stuck,
body.hero-leads .app-topbar.is-stuck {
    background: var(--card);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-sm);
}
/* Fuehrt die Kopfzone, liegt die Leiste zunaechst durchsichtig DARUEBER und
   laesst Klicks durch (Regel oben). Deckend nimmt sie sie wieder an — das
   Aussehen steht in der Regel darueber, hier nur, was wirklich nur hier gilt. */
body.hero-leads .app-topbar.is-stuck {
    pointer-events: auto;
}
/* Auf dem Blau muessen Glocke und Burger weiss sein */
/* Auf dem Blau bekommen Glocke und Burger dasselbe Gewand wie die leisen
   Aktionen der Kopfzone — sonst schweben sie als nackte Symbole herum.
   44px, damit die 44er-Regel auch hier gilt. */
body.hero-leads .app-topbar:not(.is-stuck) .topbar-icon-btn,
body.hero-leads .app-topbar:not(.is-stuck) .burger-btn {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    color: var(--on-hero);
    background: color-mix(in srgb, var(--on-hero) 14%, transparent);
    transition: background 0.18s ease;
}
body.hero-leads .app-topbar:not(.is-stuck) .topbar-icon-btn:hover,
body.hero-leads .app-topbar:not(.is-stuck) .burger-btn:hover {
    background: color-mix(in srgb, var(--on-hero) 26%, transparent);
}
body.hero-leads .app-topbar:not(.is-stuck) .topbar-icon-btn:focus-visible,
body.hero-leads .app-topbar:not(.is-stuck) .burger-btn:focus-visible {
    outline: 2px solid var(--on-hero);
    outline-offset: 3px;
}
/* Der rote Zaehler braucht auf Blau einen Rand, sonst verschwimmt er */
body.hero-leads .app-topbar:not(.is-stuck) .notif-badge {
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--focus) 55%, transparent);
}
/* Etwas Luft zum rechten Rand, damit die Glocke nicht an der Kante klebt */
body.hero-leads .app-topbar:not(.is-stuck) {
    padding-right: 1.25rem;
}
@media (max-width: 640px) {
    .page-hero.is-lead { padding-top: 1.25rem; }
    .page-hero.is-lead .page-hero-top { padding-right: 48px; }
}
@media (prefers-reduced-motion: reduce) {
    body.hero-leads .app-topbar { transition: none; }
}
/* Seiten ohne Kopfzone behalten den Titel dauerhaft.
   Der Klassenname darf die Zeichenfolge "-hero" NICHT enthalten — siehe
   die Regel weiter oben, die auf [class*="-hero"] zielt. */
/* Gespiegelte Kopfzonen-Aktion in der klebenden Leiste: dort steht sie auf
   Weiss, also blau statt weiss, und etwas kompakter als in der Kopfzone. */
.app-topbar .topbar-action {
    background: var(--primary);
    color: var(--on-primary) !important;
    border: none;
    border-radius: 10px;
    padding: 0 1rem;
    min-height: 38px;
    font-size: 0.85rem;
    font-weight: 600;
    margin-right: 0.5rem;
    white-space: nowrap;
}
.app-topbar .topbar-action:hover { background: var(--primary-hover); }
.app-topbar .topbar-action .material-symbols-outlined { font-size: 18px; }
@media (max-width: 640px) {
    /* Auf schmalen Geraeten nur das Symbol, sonst draengt es den Titel weg */
    .app-topbar .topbar-action { padding: 0; width: 38px; justify-content: center; }
    .app-topbar .topbar-action .topbar-action-text { display: none; }
}

.heroless .app-topbar-title {
    opacity: 1;
    transform: none;
}
@media (prefers-reduced-motion: reduce) {
    .app-topbar-title { transition: none; }
}

.app-topbar-right {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
}

.topbar-icon-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: none;
    background: transparent;
    color: var(--text-light);
    cursor: pointer;
    transition: all 0.15s ease;
    position: relative;
}

.topbar-icon-btn:hover {
    background: var(--surface-low);
    color: var(--primary);
}

/* --- Burger Button (nur Mobile) --- */
.burger-btn {
    display: none;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: none;
    background: transparent;
    color: var(--text-mid);
    cursor: pointer;
    border-radius: var(--radius);
    transition: background 0.15s;
}

.burger-btn:hover {
    background: var(--surface-low);
}

/* --- Sidebar Overlay (Mobile) --- */
.sidebar-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: color-mix(in srgb, var(--text) 30%, transparent);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    z-index: 99;
}

/* ================================================
   APP-SHELL: RESPONSIVE (Mobile < 1024px)
   ================================================ */
@media (max-width: 1023px) {
    /* visibility dazu (02.10.2026): Die eingeklappte Leiste war nur aus dem
       Bild geschoben — die Tab-Taste lief trotzdem durch ihre 16 unsichtbaren
       Links, bevor sie den Inhalt erreichte. Verborgen heisst jetzt auch fuer
       die Tastatur verborgen; die Verzoegerung laesst das Hinausgleiten zu
       Ende laufen. */
    .sidebar {
        transform: translateX(-100%);
        visibility: hidden;
        transition: transform 0.25s ease, visibility 0s linear 0.25s;
    }

    .sidebar.is-open {
        transform: translateX(0);
        visibility: visible;
        transition: transform 0.25s ease, visibility 0s;
        box-shadow: var(--shadow-lg);
    }

    .sidebar-overlay.is-open {
        display: block;
    }

    .app-content {
        margin-left: 0;
    }

    .burger-btn {
        display: flex;
    }
}

/* Alte Top-Navigation ausblenden wenn App-Shell aktiv */
.app-shell header:not(.app-topbar) {
    display: none;
}

/* === Welle-0-Bausteine === */
/* ================================================
   MODUL-KOPFZONE — Signaturkomponente (DESIGN.md)
   Eine-Kopfzone-Regel: Diese Klasse ist die einzige zugelassene
   Modul-Kopfzone. Den 135-Grad-Verlauf NICHT seitenlokal nachbauen.
   Markup siehe app/page_hero.php.
   ================================================ */
.page-hero{
  /* clip statt hidden (29.09.2026): Die Zierkreise ragen 50px ueber den
     rechten Rand. Mit hidden wird die Kopfzone ein Scrollbereich — ein
     Knopf, der per Tastatur den Fokus bekommt, schob den Inhalt um 50px
     nach links, und Brotkrume und Titel waren angeschnitten. clip schneidet
     genauso ab, laesst sich aber nicht verschieben. hidden bleibt als
     Rueckfall fuer Browser ohne clip. */
  position:relative; overflow:hidden; overflow:clip;
  background:linear-gradient(135deg,var(--hero-from) 0%,var(--hero-to) 100%);
  color:var(--on-hero);
  border-radius:16px;
  /* Kompakter als zuvor (war 1.75rem 2rem): die Kopfzone steht auf jeder
     Modulseite und darf nicht den halben ersten Bildschirm belegen. */
  padding:1.25rem 2rem;
  margin-bottom:1.25rem;
}
/* Angeschnittene Kreise: die einzige zugelassene Zierflaeche im System */
.page-hero::before{
  content:''; position:absolute; width:200px; height:200px;
  top:-70px; right:-50px; border-radius:50%;
  background:color-mix(in srgb, var(--on-hero) 7%, transparent); pointer-events:none;
}
.page-hero::after{
  content:''; position:absolute; width:120px; height:120px;
  bottom:-40px; left:35%; border-radius:50%;
  background:color-mix(in srgb, var(--on-hero) 4%, transparent); pointer-events:none;
}
.page-hero-inner{ position:relative; z-index:1; }

.page-hero-top{
  display:flex; align-items:flex-start; justify-content:space-between;
  gap:1.5rem; flex-wrap:wrap;
}
/* Die Aktionen stehen rechts oben — immer.
   Vorher hatte der Titelblock keine Flex-Angabe und war so breit wie sein
   laengster Text. Damit entschied die zufaellige Laenge des UNTERTITELS,
   ob die Knoepfe rechts blieben oder linksbuendig unter den Titel
   rutschten: „Antraege & Formulare" hatte die breiteste Knopfreihe von
   allen (670px) und blieb trotzdem oben, weil sein Untertitel der
   kuerzeste war — waehrend die Testbibliothek mit 624px umbrach.
   Dieselbe Kopfzone sah dadurch von Modul zu Modul anders aus
   (Stephan, 21.09.2026).
   Jetzt nimmt der Titelblock den Restplatz und sein Untertitel bricht um.
   Erst wenn unter 320px fuer ihn bliebe, weichen die Knoepfe nach unten —
   dann aber wegen des Platzes, nicht wegen einer Textlaenge, und auf jeder
   Seite bei derselben Fensterbreite. */
.page-hero-titlewrap{ min-width:0; flex:1 1 320px; }
.page-hero-titlewrap > .public-brand{ margin-bottom:0.75rem; }

/* KONTRAST IN DER KOPFZONE (02.10.2026). Gemessen auf beta, 62 Seiten: Am
   hellen Ende des Verlaufs (--hero-to, #2b6cb0) reichte halbtransparentes Weiss
   nicht — Brotkrume 3,53:1, Reiter 3,27:1, Unterzeile 4,41:1, Zaehlpillen
   3,26:1, leiser Knopf 4,10:1 (gefordert 4,5:1). Zwei Regeln seitdem:
   1. Text traegt mindestens 0,9 Deckkraft (= 4,73:1 an der hellsten Stelle).
      Die Rangfolge machen Groesse und Gewicht, nicht die Blaesse.
   2. Flaechen IN der Kopfzone dunkeln ab statt aufzuhellen: Eine weisse
      Lasur hebt den Grund und nimmt weissem Text den Kontrast, eine dunkle
      gibt ihm welchen (0,14 schwarz: Weiss 6,80:1). */

.page-hero-crumb{
  font-family:'Inter','Manrope',system-ui,sans-serif;
  font-size:0.72rem; letter-spacing:0.06em;
  color:color-mix(in srgb, var(--on-hero) 90%, transparent); margin-bottom:0.35rem;
}

.page-hero-crumb a{ color:color-mix(in srgb, var(--on-hero) 90%, transparent); text-decoration:none; }

.page-hero-crumb a:hover{ color:var(--on-hero); text-decoration:underline; }
/* Brotkrume und Hilfe-Link in einer Zeile (02.10.2026). Der Link ist ein Nebenweg
   und sieht aus wie die Brotkrume: dieselbe Schrift, dieselbe Tinte, davor das
   Fragezeichen. */
.page-hero-kopfzeile{ display:flex; align-items:center; flex-wrap:wrap; gap:0.2rem 0.9rem; margin-bottom:0.35rem; }
.page-hero-kopfzeile .page-hero-crumb{ margin-bottom:0; }
/* Die aktuelle Seite und der Trenner kamen sonst aus den hellen Standardfarben
   der Brotkrumen: Dunkelblau auf Blau war praktisch unsichtbar (19.09.2026). */

.page-hero h1,.page-hero h2{
  color:var(--on-hero); margin:0;
  font-size:1.6rem; font-weight:800; line-height:1.15; letter-spacing:-0.02em;
  display:flex; align-items:center; gap:0.55rem;
}
.page-hero h1 .material-symbols-outlined,
.page-hero h2 .material-symbols-outlined{ font-size:26px; opacity:0.9; }
.page-hero-sub{
  margin:0.3rem 0 0; max-width:640px;
  font-size:0.92rem; line-height:1.5; color:color-mix(in srgb, var(--on-hero) 92%, transparent);
}

/* Primaere Aktion rechts oben */
.page-hero-actions{ display:flex; gap:0.5rem; flex-wrap:wrap; flex-shrink:0; margin-left:auto; }
.page-hero-actions .button-primary,
.page-hero-action{
  display:inline-flex; align-items:center; justify-content:center; gap:0.5rem;
  min-height:44px; box-sizing:border-box;
  background:var(--card); color:var(--primary) !important;
  border:none; border-radius:12px; padding:0.65rem 1.5rem;
  font-family:'Manrope',system-ui,sans-serif; font-size:0.9rem; font-weight:600;
  text-decoration:none; cursor:pointer; white-space:nowrap;
  transition:background 0.2s ease;
}
.page-hero-actions .button-primary:hover,
.page-hero-action:hover{ background:var(--primary-light); box-shadow:none; transform:none; }

/* Leise Variante: Nebenweg oder Umschalter, nicht die Hauptaktion */
.page-hero-action--quiet{
  background:color-mix(in srgb, var(--text) 14%, transparent);
  color:var(--on-hero) !important;
}
.page-hero-action--quiet:hover{ background:color-mix(in srgb, var(--text) 24%, transparent); }

/* Metazeile: Kontext zur Kopfzone (Ort, Zeit, Gremium) */
.page-hero-meta{
  display:flex; flex-wrap:wrap; gap:1rem;
  margin-top:0.65rem;
  font-size:0.88rem; color:color-mix(in srgb, var(--on-hero) 92%, transparent);
  font-variant-numeric:tabular-nums;
}
.page-hero-meta-item{ display:inline-flex; align-items:center; gap:0.35rem; }
.page-hero-meta-item .material-symbols-outlined{ font-size:18px; opacity:0.8; }

/* Zaehlpillen */
.page-hero-stats{ display:flex; flex-wrap:wrap; gap:0.5rem; margin-top:0.75rem; }
.page-hero-stat{
  display:inline-flex; align-items:baseline; gap:0.4rem;
  background:color-mix(in srgb, var(--text) 14%, transparent); border-radius:10px;
  padding:0.3rem 0.7rem;
  font-variant-numeric:tabular-nums;
}
/* 1.25rem / 0.72rem sind die Werte, die sechs von sieben Kopfzonen vor der
   Vereinheitlichung trugen — der Bestand, nicht eine neue Erfindung. */
.page-hero-stat b{ font-size:1.25rem; font-weight:800; color:var(--on-hero); letter-spacing:-0.02em; }
.page-hero-stat span{
  font-family:'Inter','Manrope',system-ui,sans-serif;
  font-size:0.72rem; font-weight:600; letter-spacing:0.04em;
  text-transform:uppercase; color:color-mix(in srgb, var(--on-hero) 90%, transparent);
}

/* Tab-Leiste am Fuss der Kopfzone */
.page-hero--tabs{ padding-bottom:0; }
.page-hero-tabs{
  position:relative; z-index:1;
  display:flex; gap:0;
  margin:0 -2rem; padding:0 2rem;
  border-top:1px solid color-mix(in srgb, var(--on-hero) 12%, transparent);
  overflow-x:auto;
}
.page-hero-tabs a{
  display:inline-flex; align-items:center; gap:0.35rem;
  min-height:44px; box-sizing:border-box;
  padding:0.75rem 1rem;
  font-size:0.82rem; font-weight:600;
  color:color-mix(in srgb, var(--on-hero) 90%, transparent);
  border-bottom:2px solid transparent;
  text-decoration:none; white-space:nowrap;
  transition:color 0.15s ease, border-color 0.15s ease;
}
.page-hero-tabs a:hover{ color:var(--on-hero); }
.page-hero-tabs a.is-active{ color:var(--on-hero); border-bottom-color:var(--on-hero); }
.page-hero-tabs a .material-symbols-outlined{ font-size:18px; }

/* Rollbare Leisten: Der Rand blendet aus, solange in der Richtung noch etwas
   kommt (app/rollrand.js). Nur Deckkraft — --maske ist schwarz, weil
   mask-image allein den Alphakanal liest. -webkit- fuer iPads vor iOS 15.4. */
[data-rollrand="waagerecht"].rollrand-danach{
  -webkit-mask-image:linear-gradient(to right, var(--maske) calc(100% - 40px), transparent);
          mask-image:linear-gradient(to right, var(--maske) calc(100% - 40px), transparent);
}
[data-rollrand="waagerecht"].rollrand-davor{
  -webkit-mask-image:linear-gradient(to left, var(--maske) calc(100% - 40px), transparent);
          mask-image:linear-gradient(to left, var(--maske) calc(100% - 40px), transparent);
}
[data-rollrand="waagerecht"].rollrand-davor.rollrand-danach{
  -webkit-mask-image:linear-gradient(to right, transparent, var(--maske) 40px, var(--maske) calc(100% - 40px), transparent);
          mask-image:linear-gradient(to right, transparent, var(--maske) 40px, var(--maske) calc(100% - 40px), transparent);
}
[data-rollrand="senkrecht"].rollrand-danach{
  -webkit-mask-image:linear-gradient(to bottom, var(--maske) calc(100% - 48px), transparent);
          mask-image:linear-gradient(to bottom, var(--maske) calc(100% - 48px), transparent);
}
[data-rollrand="senkrecht"].rollrand-davor.rollrand-danach{
  -webkit-mask-image:linear-gradient(to bottom, transparent, var(--maske) 32px, var(--maske) calc(100% - 48px), transparent);
          mask-image:linear-gradient(to bottom, transparent, var(--maske) 32px, var(--maske) calc(100% - 48px), transparent);
}
[data-rollrand="waagerecht"]{ scrollbar-width:none; }
[data-rollrand="waagerecht"]::-webkit-scrollbar{ display:none; }

@media (max-width:640px){
  .page-hero-tabs{ margin:0 -1.25rem; padding:0 1.25rem; }
  .page-hero{ padding:1.25rem; border-radius:12px; }
  .page-hero h1,.page-hero h2{ font-size:1.35rem; }
  .page-hero-sub{ font-size:0.95rem; }
  .page-hero-top{ flex-direction:column; }
  /* AUS BREITE WIRD HOEHE (29.09.2026).
     Oben steht .page-hero-titlewrap{ flex:1 1 320px } — die 320px sind als
     Mindest-BREITE gemeint und sind das in der Zeilenrichtung auch. Die
     Zeile darueber kippt die Richtung aber auf column, und flex-basis gilt
     immer auf der HAUPTACHSE: Aus 320px Breite wurden 320px HOEHE.

     Der Titelblock war damit auf dem Handy 320px hoch, obwohl sein Inhalt
     78px braucht — 242px reine Leere, auf JEDER der 50 Seiten mit Kopfzone.
     Gemessen auf 375x812: Die Kopfzone belegte 74% des Bildschirms, jetzt
     44%; ueber zwoelf Seiten gemessen 174-220px weniger (Stephan, 29.09.).

     `auto` statt einer Zahl: Hier soll der Inhalt die Hoehe bestimmen. */
  .page-hero-titlewrap{ flex:0 1 auto; }
  .page-hero-actions{ width:100%; }
  .page-hero-actions .button-primary,
  .page-hero-action{ width:100%; }
}

/* KOPFZONE AM TELEFON (15.10.2026, DESIGN.md Kopfzone-am-Telefon-Regel).
   Gemessen auf 390×844: Die Kopfzone der Testbibliothek war 478 px hoch, die
   der Projekte 453, des Kalenders 438 — mehr als die halbe Höhe, bevor der
   erste Eintrag kam. Untertitel der Übersicht weg, Zahlen als Textzeile,
   Nebenaktion als Symbol neben der Hauptaktion. */
@media (max-width:640px){
  .page-hero-sub--rubrik{ display:none; }
  .page-hero-stats{ gap:0.2rem 0.9rem; margin-top:0.5rem; }
  .page-hero-stat{ background:none; padding:0; }
  .page-hero-stat b{ font-size:1rem; }
  .page-hero-actions .button-primary,
  .page-hero-action{ width:auto; flex:1 1 auto; }
  .page-hero-action--symbol{ flex:0 0 44px; width:44px; padding:0; }
  .page-hero-action--symbol .page-hero-action-text{ display:none; }
}

/* Die klebende Kopfleiste ist durchsichtig und liegt ueber der Kopfzone — am
   Schreibtisch ist sie leer und stoert dort niemanden. Unterhalb von 1024px
   traegt sie aber den Menue-Knopf, und der landete damit genau auf der ersten
   Zeile der Kopfzone: auf der Startseite auf der Ueberschrift, sonst auf der
   Brotkrume. Fuer die Glocke rechts war das schon geloest (padding-right an
   .page-hero-top), fuer den Knopf links nicht.

   Die Regel steht bewusst hier unten und nennt auch `.is-lead`: die
   Kurzform-`padding`-Regeln weiter oben wuerden ein einzelnes `padding-top`
   sonst ueberstimmen. Gemessen am 09.09.2026. */
@media (max-width:1024px){
  .page-hero,
  .page-hero.is-lead{ padding-top:62px; }
}

/* ================================================
   BROWSER-OBERFLAECHEN — gehoeren auch zum System
   ================================================ */
/* Ohne border-radius (02.10.2026): Die Zeile stand hier und aenderte die
   FORM des Elements, sobald es den Fokus hatte — runde Knoepfe, Schalter
   und Kuerzelkreise wurden zu Rechtecken mit 6 px. Der Rahmen folgt der
   Rundung des Elements von selbst. */
:focus-visible{
  outline:2px solid var(--primary);
  outline-offset:3px;
}
/* TASTATUR (02.10.2026) — vier Ergaenzungen nach der Messung auf beta.

   Kopfzone: Der Rahmen ist Portalblau, die Kopfzone auch — der Fokus war
   dort nicht zu sehen. Auf Blau ist er weiss. */
.page-hero :focus-visible{ outline-color:var(--on-hero); }
/* Sprunglink: ausserhalb des Bildes, bis er den Fokus hat. */
/* Der Schatten erst mit dem Fokus: Versteckt reichte er 20px tief ins Bild —
   im Portal deckte ihn die obere Leiste, auf der öffentlichen Startseite
   (ohne Leiste) lag ein grauer Fleck über der Marke (10.10.2026). */
.sprunglink{
  position:absolute; left:0.75rem; top:-4rem; z-index:10000;
  background:var(--card); color:var(--primary); font-weight:700; font-size:0.9rem;
  padding:0.6rem 1rem; border-radius:8px; text-decoration:none;
}
.sprunglink:focus{ top:0.75rem; box-shadow:var(--shadow-lg); }
/* Das Sprungziel selbst zeigt keinen Rahmen — es ist ein Ort, kein Knopf. */
main#inhalt:focus{ outline:none; }
/* Datei-Ablage: Das eigentliche Feld liegt unsichtbar ueber der Flaeche
   (Deckkraft 0). Der Fokus war deshalb nicht zu sehen — die Flaeche zeigt ihn. */
.file-drop-zone:has(> input[type="file"]:focus-visible){
  outline:2px solid var(--primary); outline-offset:3px; border-color:var(--primary);
}
::selection{ background:var(--info-wash); color:var(--text); }

/* Ziffern-Regel: Zahlen, die untereinander stehen, laufen tabellarisch */
.list-table td,
.list-table th,
.page-hero-stat,
time{ font-variant-numeric:tabular-nums; }

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:0.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:0.01ms !important;
    scroll-behavior:auto !important;
  }
}
.form-grid{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--space-4); }
/* Ein Feld im Raster stapelt und fuellt die Spalte. `align-items` muss hier
   ausdruecklich stehen: traegt dasselbe Element zusaetzlich die aeltere Klasse
   .form-row (die fuer eine waagerechte Feldzeile gedacht ist und
   align-items:flex-end setzt), schrumpfen Label, Eingabe und Liste sonst auf
   Inhaltsbreite und stehen rechtsbuendig. Aufgetreten auf treffpunkt_suggest.php. */
.form-grid .field{ display:flex; flex-direction:column; align-items:stretch; gap:var(--space-1); min-width:0; }
.form-grid .field--full{ grid-column:1 / -1; }
/* Ankreuz- und Auswahlfelder duerfen NICHT auf volle Breite gestreckt werden.
   Ohne diese Ausnahme wird eine Checkbox in einer Personenliste 185px breit,
   schiebt den Namen an den Rand und zwingt ihn in den Umbruch.
   Aufgetreten auf treffpunkt_suggest.php. */
.form-grid .field input:not([type="checkbox"]):not([type="radio"]),
.form-grid .field select,
.form-grid .field textarea{ width:100%; box-sizing:border-box; }
@media (max-width:640px){ .form-grid{ grid-template-columns:1fr; } }
/* Seit dem 09.10.2026 bei 860px statt 640px — dem Band „schmal" der
   Baukaesten (app/baukasten.js, BAENDER). Am iPad im Hochformat (744/820px)
   waren sieben Listen breiter als ihre Karte und liessen sich nur seitwaerts
   wischen; bei 640px griff das Stapeln dort nie. */
/* Seit dem 13.10.2026 per Container-Abfrage: Bei 1024×768 ist das Fenster
   1024px breit, der Inhalt neben der Seitenleiste aber nur 768px — die
   Tabellen blieben Tabellen und schoben die Seite bis zu 546px seitwaerts.
   Die Huelle .tabelle-platz misst, was die Tabelle wirklich hat
   (DESIGN.md, Container-Regel). */
.tabelle-platz{ container-type:inline-size; }
@container (max-width:860px){
  /* Zeilen und Zellen wurden zu Karten umgeformt, die Tabelle selbst blieb eine
     Tabelle — und eine Tabelle waechst auf ihre Mindestinhaltsbreite, statt sich
     der Karte zu fuegen. Auf schooladmin_students.php ragte sie damit 48px ueber
     den Bildschirmrand. Gemessen am 09.09.2026. */
  .list-table.table-cards,
  .list-table.table-cards tbody{ display:block; width:100%; }
  .list-table.table-cards tr{ box-sizing:border-box; }

  .list-table.table-cards thead{ position:absolute; left:-9999px; top:auto; }
  .list-table.table-cards tr{
    display:block; background:var(--card); border:1px solid var(--border);
    border-radius:var(--r-md); box-shadow:var(--shadow-sm);
    margin-bottom:var(--space-3); padding:var(--space-3);
  }
  /* Mehrere Werte in einer Zelle (Stufe: „Kollegium“, „aus der Schatzkiste“,
     „geschützt“) brechen um, rechtsbündig unter dem Wert davor — ohne Umbruch
     quetschte die Zeile die Pillen auf Buchstabenbreite (15.10.2026). */
  .list-table.table-cards td{
    display:flex; justify-content:flex-end; flex-wrap:wrap; gap:var(--space-1) var(--space-3);
    padding:var(--space-1) 0; border:0; text-align:right;
    /* Ein langes Wort ohne Umbruchstelle — in der Praxis eine E-Mail-Adresse —
       setzt die Mindestbreite der Zelle und schiebt sie ueber alle Vorfahren
       bis zur Seite durch. Die Zelle selbst bleibt dabei im Bild, weshalb kein
       Element sichtbar heraussteht und der Fehler schwer zu finden ist.
       Gemessen am 09.09.2026: 48px Ueberlauf auf schooladmin_students.php. */
    min-width:0; overflow-wrap:anywhere;
  }
  .list-table.table-cards td::before{
    content:attr(data-label); font-weight:600; color:var(--text-light);
    text-align:left; flex:0 0 auto; margin-right:auto;
  }
  .list-table.table-cards td .pill,
  .list-table.table-cards td [class*="-pill"]{ overflow-wrap:normal; max-width:100%; }
  /* Sechs Symbolknöpfe (Testbibliothek) sind breiter als die Karte (15.10.2026). */
  .list-table.table-cards .zeilen-aktionen{ flex-wrap:wrap; }
  .list-table.table-cards td:empty{ display:none; }
  /* Aktions-/Formularzellen: Inhalt voll, kein Label-Gegenüber */
  /* white-space: normal — .cell-actions haelt die Knoepfe einer Tabellenzeile
     sonst auf einer Zeile (nowrap). In der Karte schob das ein Formular mit
     Eingabefeld ueber den Rand: Ideen im Design Thinking, 1.180px auf 704px,
     und die ganze Seite lief 484px ueber (09.10.2026). */
  .list-table.table-cards td.cell-actions,
  .list-table.table-cards td.cell-stack{ display:block; text-align:left; white-space:normal; }
  .list-table.table-cards td input:not([type="checkbox"]):not([type="radio"]),
  .list-table.table-cards td select{ max-width:100%; box-sizing:border-box; }
  .list-table.table-cards td.cell-actions::before,
  .list-table.table-cards td.cell-stack::before{ display:block; margin-bottom:var(--space-1); }
}

@media (max-width:640px){
  .modal-backdrop{ align-items:flex-end; }
  .modal-dialog{
    width:100%; max-width:none; max-height:90dvh; overflow-y:auto;
    border-radius:var(--r-lg) var(--r-lg) 0 0;
  }
  .modal-dialog::before{
    content:""; display:block; width:36px; height:4px; border-radius:2px;
    background:var(--border); margin:0 auto var(--space-3);
  }
}

.cluster{ display:flex; flex-wrap:wrap; gap:var(--space-2); align-items:center; }

/* Die farbigen Kaesten und die Bildklassen stehen seit dem 22.09.2026 in
   textbild_css() (app/config.php), nicht mehr hier: Die oeffentliche
   Konzeptseite laedt ueberhaupt kein Stylesheet — sie bringt ihre Regeln
   inline mit, wegen der Theme-Farben je Schule. Eine Regel in style.css
   erreicht sie nie, und genau das fiel bei den Kaesten auf. */

/* 30.09.2026 aus dem Markup: das Logo der Schule in der Markenzeile
   (public_theme_render_brand_html) und der kleine Satz im Datenschutztext
   (legal_texts.php). Beides erscheint oeffentlich UND in der Vorschau im
   Portal — deshalb steht die Regel in public_basis.css und style.css. */
.public-brand-bild{display:block;max-height:64px;max-width:260px;width:auto;height:auto;}
.rechtstext-klein{font-size:0.85rem;}

/* ── Art.-13-Hinweis als Kasten (02.10.2026), angemeldete Ansicht ──
   Siehe form_public.css: Er ist der Standard statt eines Pflichthakens und
   darf deshalb nicht untergehen. */
.fp-verantwortung { display: flex; align-items: flex-start; gap: 0.55rem; font-size: 0.85rem;
  color: var(--text); background: var(--info-wash); border-left: 3px solid var(--primary);
  border-radius: 6px; padding: 0.6rem 0.85rem; margin: 1.1rem 0 0; line-height: 1.5; }
.fp-verantwortung .material-symbols-outlined { font-size: 19px; flex-shrink: 0; }

/* Feld „Adresse“ (Subdomains, app/bereich_kennung.js): belegte oder ungültige Kennung */
.kennung-belegt { color: var(--danger-ink); }
