/* WatchTower – Stylesheet.
 *
 * Handgeschriebenes CSS mit Custom Properties, wie in allen thaessler-Apps:
 * kein Tailwind, kein Build, kein Bundler. Der Cachebuster kommt aus
 * asset() (filemtime), deshalb darf die .htaccess das hier ein Jahr cachen.
 *
 * Dunkel als Vorgabe. Die Akzentfarbe steht doppelt — hier und als
 * APP_ACCENT in config/app.php, weil die eingebauten Fehlerseiten ihr CSS
 * inline mitbringen muessen. Bei einem Wechsel bitte beide anfassen.
 */

:root {
    /*
     * DAS ERSTE, WAS DER BROWSER UEBER DIESE SEITE ERFAEHRT.
     *
     * Ohne `color-scheme` nimmt er Hellschrift auf Weiss an, bis das
     * Stylesheet steht — in der installierten App ist das bei langsamer
     * Verbindung eine weisse Flaeche von mehreren Sekunden, und gemeldet
     * wurde sie genau so (#138). Die Zeile faerbt auch Scrollbalken,
     * Formularfelder und den Bereich hinter dem Gummiband-Scrollen.
     *
     * Sie steht hier UND in der Huelle (`pages/offline.php`): die traegt ihr
     * CSS inline und kommt an diese Datei nicht heran.
     */
    color-scheme: dark;

    --bg: #0e1726;
    --surface: #162032;
    --surface-hi: #1d2a40;

    /* Der Untergrund von Erklaerkaesten. Eigener Name statt --surface-hi
       direkt: was hier gemeint ist, ist "abgesetzte Flaeche", nicht
       "Hover" — und wenn eins davon sich aendert, soll das andere
       stehen bleiben. */
    --surface-note: var(--surface-hi);
    --border: #26344d;
    --text: #e6ecf5;
    --text-muted: #97a5bd;
    --accent: #f2545b;
    --accent-quiet: rgba(242, 84, 91, 0.12);
    --ok: #3dd68c;
    --bad: #f2545b;
    /* Dritter Zustand: braucht einen Menschen, nicht Geduld. Ein
       abgelehntes Schul-Login behebt sich nicht von selbst, eine
       nicht antwortende Quelle vielleicht schon. */
    --warn: #f0b429;

    /* Die ruhigen Untergruende der Zustandspillen (#86).
     *
     * --bad-quiet hat denselben Wert wie --accent-quiet, weil --bad und
     * --accent dieselbe Farbe sind. Trotzdem ein eigener Name: waere die
     * Pille an --accent-quiet gehaengt, faerbte ein spaeterer Wechsel der
     * Akzentfarbe stillschweigend die Ausfall-Kennzeichnung mit um. Was hier
     * gemeint ist, ist "Ausfall", nicht "Akzent". */
    --bad-quiet: rgba(242, 84, 91, 0.12);
    --warn-quiet: rgba(240, 180, 41, 0.14);

    --radius: 10px;

    /*
     * DIE HOEHE DES KLEBENDEN TEILS, hergeleitet und nicht geraten.
     *
     * Sie wird gebraucht, seit der Kopf steht (sticky): `.tagsticky` im Plan
     * ist ebenfalls sticky und muss GENAU darunter einrasten. CSS kann die
     * Hoehe eines anderen Elements nicht erfragen — stuende hier `top: 0`,
     * verschwaende die Tagesleiste beim Scrollen unter dem Kopf, und man
     * saehe nicht mehr, welcher Tag gilt. Das ist die eine Frage, fuer die es
     * den Plan gibt.
     *
     * SEIT DEM 20.09.2026 KLEBT NUR NOCH DIE NAVIGATION (Marke und
     * Abmeldeweg scrollen weg), die Groesse ist also entsprechend kleiner:
     *
     *   0,35rem Innenabstand oben          (.nav__item)
     *   0,35rem Innenabstand unten         (.nav__item)
     *   0,9rem  Innenabstand unten         (.nav)
     *   -------
     *   1,6rem
     *   + 27px  Zeilenhoehe der Navigation (16px x 1,6, aufgerundet) plus
     *           die 1px-Kante unten
     *
     * REM UND PX GEMISCHT, und das ist Absicht: die Abstaende stehen in rem
     * und wachsen mit der Schriftgroesse des Browsers mit, der Text der
     * Navigation erbt dagegen die festen 16px aus `body`. Eine reine
     * rem-Rechnung driftete, sobald jemand seine Schrift hochdreht.
     *
     * DIE SAFE AREA IST WIEDER DRIN. Sie steckt seit dem 20.09.2026 im
     * Innenabstand der NAVIGATION: `top: 0` ist in der installierten App der
     * Fensterrand unter der Statusleiste, und ohne den Abstand klebte die
     * Navigation unter der Uhr. Damit ist der klebende Kasten um den Inset
     * hoeher, und alles, was sich darunter einordnen soll, muss es
     * mitrechnen.
     *
     * WER EINEN DIESER WERTE AENDERT, zieht diese Zeile mit. Sonst klafft im
     * Plan eine Luecke oder die Tagesleiste steckt halb unter dem Kopf — und
     * das sieht man nur beim Scrollen auf einem schmalen Schirm.
     */
    --kopf-hoehe: calc(1.6rem + 27px + env(safe-area-inset-top));
}

* { box-sizing: border-box; }

/*
 * DIE ZURUECK-GESTE DES BROWSERS, soweit sie sich abstellen laesst.
 *
 * Am 20.09.2026 am Geraet gemeldet: beim Wischen im Plan funkt die
 * Zurueck-/Vorwaerts-Geste dazwischen. `overscroll-behavior-x: contain` steht
 * an der Wischbahn selbst schon — das haelt den Ueberhang IN der Bahn, hilft
 * aber nicht, wenn die Geste am Bildschirmrand beginnt.
 *
 * `none` am Wurzelelement nimmt der Seite die waagerechte Kette ganz. Damit
 * verschwindet sie in Chrome und Firefox zuverlaessig.
 *
 * EHRLICHE GRENZE: auf iOS-Safari laesst sich die Kantenwischgeste von einer
 * Webseite aus NICHT sicher abschalten — sie gehoert dem System. In der
 * INSTALLIERTEN PWA gibt es sie gar nicht erst, weil dort keine Adressleiste
 * und kein Verlauf im selben Sinne existieren. Wer den Plan viel wischt, ist
 * mit "Zum Home-Bildschirm" also nicht nur schneller unterwegs.
 */
html, body { overscroll-behavior-x: none; }

/*
 * IOS BLAEHT TEXT AUF, DEN ES FUER ZU KLEIN HAELT — und zwar NACH unserer
 * Angabe, nicht statt ihr.
 *
 * Die Textvergroesserung von WebKit ("text autosizing") greift bei einem
 * Block, dessen Inhalt breiter ist als der Bildschirm: die Annahme ist, dass
 * man dort ohnehin hineinzoomen muss, also wird die Schrift vorab
 * hochgerechnet. Sie wirkt je Block und damit UNGLEICHMAESSIG — schmale
 * Zellen bleiben, die eine breite waechst.
 *
 * GENAU SO SAH ES IN DER PWA AUS (am 21.09.2026 gemeldet): in der
 * Fehlertabelle unter Status stand die zweite Spalte groesser da als alles
 * daneben. Sie traegt eine Ablaufspur mit Pfad und Zeilennummer, laeuft dank
 * `.tbl { white-space: nowrap }` auf eine einzige lange Zeile und ist damit
 * der mit Abstand breiteste Block der Seite. Dieselbe Falle lauert beim
 * Mailprotokoll und beim Meldungseingang.
 *
 * ZWEI VORHERIGE ANLAEUFE HABEN DESHALB NICHT GEHOLFEN: `.tbl__log` und
 * spaeter `.tbl__zeit` setzen beide 0.78rem, und gemessen kommt das auch an
 * (12,48 px in WebKit am Schreibtisch). Die Vergroesserung rechnet auf
 * diesen Wert OBEN DRAUF — wer die Zahl weiter senkt, verschiebt das
 * Ergebnis, ohne die Ursache zu treffen.
 *
 * `100%` und nicht `none`: `none` schaltet in manchen Engines auch die
 * Textvergroesserung des Nutzers ab, und das ist eine Barriere. `100%` sagt
 * nur "nimm die Groesse, die dasteht" — genau das, was jeder andere Browser
 * ohnehin tut.
 *
 * AM SCHREIBTISCH UND IN PLAYWRIGHT IST DAS NICHT ZU SEHEN, auch nicht in
 * WebKit: die Textvergroesserung gibt es nur auf dem Geraet. Dieselbe Lage
 * wie bei der Safe Area.
 */
html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

/*
 * Der Streifen unter der Statusleiste.
 *
 * In der installierten App reicht die Seite wegen `viewport-fit=cover` bis
 * unter die Uhr. Die klebende Navigation setzt deshalb bei
 * `env(safe-area-inset-top)` an — ohne diesen Streifen scrollte der Plan
 * darueber hinweg und man laese den Stundenplan durch die Uhrzeit hindurch.
 *
 * `fixed` und nicht am Kopf befestigt: er soll immer da sein, auch wenn der
 * Kopf laengst weggescrollt ist. Auf dem Schreibtisch ist der Inset 0 und
 * damit auch dieser Streifen — er kostet dort nichts.
 */
body::before {
    content: '';
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: env(safe-area-inset-top);
    background: var(--surface);
    z-index: 11;
}

/*
 * [hidden] durchsetzen.
 *
 * Die eingebaute Regel des Browsers ist [hidden] { display: none } — und die
 * hat dieselbe Spezifitaet wie eine Klasse. Jede eigene Regel mit `display`
 * gewinnt deshalb gegen sie: `.btn { display: inline-block }` liess den
 * Anzeigen-Knopf sichtbar, obwohl das Skript ihn per `hidden` versteckt hatte.
 * Ein Fehler, der nur am Geraet auffaellt und im Quelltext voellig richtig
 * aussieht.
 */
[hidden] { display: none !important; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font: 16px/1.6 system-ui, -apple-system, "Segoe UI", sans-serif;
}

a { color: var(--accent); }
a:hover { text-decoration: none; }

code {
    background: rgba(255, 255, 255, 0.07);
    padding: 0.1rem 0.35rem;
    border-radius: 4px;
    font-size: 0.9em;
}

/* --- Kopf und Navigation ------------------------------------------------ */

/*
 * Zwei Zeilen: oben Marke und Abmeldeweg, unten die Navigation.
 *
 * Vorher war es eine Reihe mit flex-wrap. Auf 393 px brach die Navigation
 * mit sechs Punkten um, der Kopf wurde 151 px hoch — bei 762 px nutzbarer
 * Hoehe ein Fuenftel des Bildes, bevor die erste Stunde beginnt. Zweizeilig
 * sind es 120 px, und der Platz rechts neben der Marke (rund 217 px, bisher
 * leer) traegt jetzt den Abmeldeweg.
 *
 * Auch auf dem PC zweizeilig. Einreihig waere er dort 77 px hoch, das waere
 * eine Media Query wert — aber es waeren zwei Umsetzungen desselben Kopfes,
 * und Hoehe ist auf 1280 px nicht knapp.
 */
/*
 * NUR DIE NAVIGATION BLEIBT STEHEN — Marke und Abmeldeweg scrollen weg.
 *
 * Zuerst klebte der ganze Kopf. Am Geraet war das zu viel: bei 393 px sind es
 * rund 120 px, dauerhaft, bei 762 px nutzbarer Hoehe. Am 20.09.2026
 * gewuenscht: "watchtower und logout mit rausschieben lassen".
 *
 * Es kostet auch inhaltlich nichts. Beim Lesen eines Plans ist die Marke
 * keine Funktion, und der Abmeldeweg wird einmal am Tag gebraucht — die
 * Navigation dagegen dauernd. Uebrig bleiben rund 50 statt 120 px.
 *
 * Die klebende Regel steht deshalb an `.nav` und nicht mehr hier; siehe
 * dort, auch wegen des z-index.
 */
.topbar {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
    gap: 0.6rem;
    /* padding-top zusaetzlich um die Safe Area: in der installierten App auf
       iOS reicht die Seite unter die Statusleiste (viewport-fit=cover), und
       ohne das laege die Marke darunter. */
    padding: calc(0.9rem + env(safe-area-inset-top)) 1.25rem 0.9rem;
    background: var(--surface);
}

/*
 * EIN SPRUNGZIEL DARF NICHT UNTER DEM KOPF LANDEN.
 *
 * Ein Anker scrollt sein Ziel an den oberen Rand des Fensters — und genau
 * dort liegt jetzt der Kopf. Betroffen ist der Weg, der am haeufigsten
 * gegangen wird und am wenigsten geprueft: die Push zeigt auf
 * `verlauf#eintrag-123` (Notifications::url()). Wer sie antippt, saehe den
 * aufgeklappten Eintrag halb unter der Navigation.
 *
 * Als `:target` und nicht am Verlaufseintrag: die Regel gilt fuer JEDEN
 * Anker, auch fuer den naechsten, den jemand einbaut, ohne an den Kopf zu
 * denken. Das halbe rem obendrauf ist Luft, damit das Ziel nicht direkt an
 * der Kante klebt.
 */
:target { scroll-margin-top: calc(var(--kopf-hoehe) + 0.5rem); }

.topbar__zeile {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

/*
 * min-height: 2.75rem — das sind 44 px.
 *
 * Der Markenlink war 26 px hoch (gemessen) und damit unter dem Mindestmaß
 * fuer einen Tippbereich. Er fuehrt zur Uebersicht, ist also ein Weg und
 * kein Schmuck.
 */
.brand {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    gap: 0.6rem;
    color: var(--text);
    font-weight: 600;
    letter-spacing: 0.01em;
    text-decoration: none;
}

/* Kleines Leuchtfeuer statt eines Bildes – spart einen Request. */
/*
 * Feste Masse in Pixeln, nicht in rem: das Logo hat ein festes
 * Seitenverhaeltnis, und eine mitwachsende Schriftgroesse wuerde es
 * verzerren oder die Kopfzeile sprengen.
 */
.brand__mark {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
}

/*
 * Eine Zeile, notfalls schiebbar.
 *
 * Fuenf Punkte brauchen auf 393 px rund 8 px mehr, als da sind — aber nur
 * mit dem Admin-Punkt "Status". Fuer alle anderen sind es vier, und die
 * passen. Das Schieben ist also die Sicherung, nicht der Normalfall; ein
 * Umbruch auf zwei Zeilen waere es fuer alle.
 */
.nav {
    /*
     * SIE IST DER TEIL DES KOPFES, DER STEHEN BLEIBT (siehe .topbar).
     *
     * SIE LIEGT DAFUER AUSSERHALB VON <header> (siehe includes/header.php):
     * ein sticky klebt nur innerhalb seines Elternteils, und `.topbar` ist
     * nur so hoch wie ihr Inhalt. Darin klebte die Navigation gar nicht.
     *
     * Innenabstand und Untergrund traegt sie jetzt selbst — ohne sie scrollte
     * der Inhalt beim Kleben sichtbar hinter den Punkten durch.
     *
     * `z-index: 10` liegt ueber der Tagesleiste (2) und unter der
     * Feedback-Bubble (20/21/22): sie soll den Plan ueberdecken, aber nicht
     * ein Panel, das modal sein will.
     */
    /*
     * SIE KLEBT UNTER DER STATUSLEISTE, NICHT DAHINTER.
     *
     * `top: 0` ist in der installierten App der obere FENSTERRAND — dorthin
     * reicht die Seite wegen `viewport-fit=cover`, unter die Uhr. Bei rund
     * 50 px Inset und rund 50 px Navigation war sie damit praktisch
     * unsichtbar; am Schreibtisch und im Emulator ist der Inset 0, dort sah
     * alles richtig aus. Am 20.09.2026 am Geraet gemeldet.
     *
     * DER INSET GEHOERT IN `top`, NICHT IN DEN INNENABSTAND. Ein
     * `padding-top` waere der naheliegende Weg und der falsche: es wirkt
     * IMMER, auch wenn die Seite ganz oben steht — und dort klaffte dann ein
     * 50 px hoher Spalt zwischen Marke und Navigation. Auf dem Schreibtisch
     * unsichtbar, weil der Inset 0 ist.
     *
     * Den Streifen ueber ihr fuellt `body::before`; ohne den scrollte der
     * Plan hinter der Uhr durch.
     */
    position: sticky;
    top: env(safe-area-inset-top);
    z-index: 10;

    padding: 0 1.25rem 0.9rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);

    display: flex;
    flex-wrap: nowrap;
    gap: 0.25rem;
    overflow-x: auto;
    max-width: 100%;
    scrollbar-width: none;
    /* Die Ausnahme zu touch-action: pan-y auf .wrap — hier DARF waagerecht
       geschoben werden. Die Kopfzeile liegt ausserhalb von .wrap, deshalb
       braucht sie ihre eigene Angabe. */
    touch-action: pan-x pan-y;
}

.nav::-webkit-scrollbar { display: none; }
.nav__item { flex: 0 0 auto; }

.nav__item {
    padding: 0.35rem 0.7rem;
    border-radius: var(--radius);
    color: var(--text-muted);
    text-decoration: none;
}

.nav__item:hover { background: var(--surface-hi); color: var(--text); }

.nav__item--active {
    background: var(--accent-quiet);
    color: var(--accent);
}

/*
 * Der Abmeldeweg in der ersten Kopfzeile.
 *
 * Zeichen UND Wort: das Zeichen allein spart 47 px, aber ein Tuerzeichen ist
 * nicht selbsterklaerend — und das ist der eine Weg, den man im Zweifel
 * finden muss.
 *
 * KEIN opacity, anders als beim frueheren .nav__item--quiet. Geschachtelte
 * Deckkraft multipliziert sich (0,7 am Link mal 0,8 am Zeichen = 0,56) und
 * drueckt das Glyph auf 2,99:1 — unter die 3:1-Grenze fuer grafische
 * Elemente. --text-muted bei 0,7 ergaebe ausserdem 3,9:1 und damit unter AA.
 * Voll gesetzt sind es 6,55:1, genau so viel wie die Navigationspunkte
 * daneben. Leise bleibt es durch die Farbe, nicht durch Transparenz.
 */
.abmelden {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    min-width: 2.75rem;
    min-height: 2.75rem;
    padding: 0 0.7rem;
    border-radius: var(--radius);
    color: var(--text-muted);
    font-size: 0.9rem;
    text-decoration: none;
}

.abmelden:hover { background: var(--surface-hi); color: var(--text); }
.abmelden:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- Inhalt ------------------------------------------------------------- */

.wrap {
    max-width: 60rem;
    margin: 0 auto;
    /* Unten zusaetzlich die Safe Area: in der installierten App auf iOS
       liegt sonst die letzte Stunde unter dem Home-Indikator (34 px). */
    padding: 1.5rem 1.25rem calc(3rem + env(safe-area-inset-bottom));

    /*
     * Die Seite selbst laesst sich nicht waagerecht schieben.
     *
     * Ohne das haengt der ganze Bildschirm am Finger, sobald irgendwo etwas
     * einen Pixel zu breit ist — und es fuehlt sich an, als sei die Seite
     * kaputt. Waagerecht scrollen darf nur, was es ausdruecklich soll: eine
     * zu breite Tabelle in ihrer eigenen Huelle.
     *
     * Stand hier urspruenglich fuer eine Wischgeste zum Tageswechsel. Die
     * ist wieder raus (siehe .tagnav), die Regel bleibt aus eigenem Recht.
     */
    touch-action: pan-y;
}

h1 { font-size: 1.5rem; margin: 0.5rem 0 1rem; }
h2 { font-size: 1.15rem; margin: 2rem 0 0.75rem; }

/*
 * H3 BRAUCHT EINE EIGENE GROESSE, sonst gibt es keine Stufe.
 *
 * Ohne Regel nimmt der Browser seine Vorgabe (rund 1,17rem) — und die liegt
 * ueber der h2 dieses Stylesheets. In den Einstellungen standen "Meldungen"
 * (h2) und "Wann" (h3) dadurch gleich gross da, die Gliederung war nur im
 * Quelltext zu sehen. Am 22.09.2026 beim Durchspielen gemeldet.
 *
 * Kleiner ALS der Fliesstext waere falsch: eine Ueberschrift, die man
 * uebersieht, ist keine. Dieselbe Groesse plus Fettung und weniger Abstand
 * nach oben ergibt die Stufe, ohne dass es gedraengt wirkt.
 */
h3 {
    font-size: 1rem;
    font-weight: 600;
    margin: 1.4rem 0 0.5rem;
}

.lead { color: var(--text-muted); max-width: 42rem; }
.muted { color: var(--text-muted); }
.ok { color: var(--ok); }
.bad { color: var(--bad); }
.warn { color: var(--warn); }

/*
 * Kein Akzentstreifen mehr an der linken Kante.
 *
 * Bei einem Produkt, das fast jeden Absatz begruendet, steht auf jeder
 * zweiten Seite ein Erklaerkasten — und der Streifen liest sich dann wie ein
 * Alarm, obwohl fast nie einer gemeint ist. Der Kasten setzt sich jetzt
 * ueber die Flaeche ab.
 *
 * Der Streifen an geaenderten PLAN-Zeilen bleibt: dort ist er die
 * Kennzeichnung, die nicht allein an Farbe haengen darf.
 */
.note {
    margin: 1.25rem 0;
    padding: 0.9rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-note);
    color: var(--text-muted);
}

.note strong { color: var(--text); }

/* --- Kennzahlen --------------------------------------------------------- */

.cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: 0.75rem;
    margin: 1.5rem 0;
}

.card {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

.card__num { font-size: 1.75rem; font-weight: 600; line-height: 1.1; }
.card__label { color: var(--text-muted); font-size: 0.85rem; }

/* --- Tabellen ----------------------------------------------------------- */

/*
 * NUR .tablewrap scrollt, nicht die Tabelle selbst.
 *
 * Vorher hatten beide `overflow-x: auto` — innen scrollte es, aussen scrollte
 * es, und der Browser schob zusaetzlich die Seite. Zwei geschachtelte
 * Scroll-Container auf derselben Achse fuehlen sich immer falsch an: der
 * Finger weiss nicht, welchen er gerade bewegt.
 */
.tbl {
    display: block;
    overflow-x: auto;
    width: 100%;
    border-collapse: collapse;
    white-space: nowrap;
}

.tbl th,
.tbl td {
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--border);
    text-align: left;
}

.tbl th { color: var(--text-muted); font-weight: 500; font-size: 0.85rem; }

/*
 * Protokollzeilen in einer Tabelle — Fehlerlog und Mailversand unter Status.
 *
 * SIE STANDEN GROESSER DA ALS IHRE EIGENE SPALTENUEBERSCHRIFT: `.tbl td` setzt
 * keine `font-size`, erbt also das 1rem des Rumpfes, waehrend `.tbl th` auf
 * 0.85rem steht. Bei einem Stacktrace ueber zwei Zeilen sah die Tabelle
 * dadurch aus, als waere die Ablaufspur die Hauptsache. Am 20.09.2026
 * gemeldet.
 *
 * Dicktengleich ist hier kein Schmuck: in `…/pages/quellen.php:41` liest man
 * Pfad und Zeilennummer in einer Monospace deutlich schneller, und untereinander
 * stehende Zeilen lassen sich vergleichen.
 */
.tbl__log {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.78rem;
    color: var(--text-muted);
}

/*
 * Und der Zeitstempel DANEBEN gehoert dazu.
 *
 * Erst war nur die Protokollzeile klein gesetzt — und damit stand ein
 * Zeitstempel in voller Groesse neben dem, worum es eigentlich geht. Die
 * Zeile wirkte dadurch nicht kleiner, sondern schief: das Unwichtigere war
 * das Groessere. Am 20.09.2026 gemeldet, nachdem die erste Haelfte des Fixes
 * schon lief.
 *
 * Dieselbe Groesse wie die Protokollzeile, aber `nowrap`: ein umgebrochenes
 * Datum kostet mehr Platz, als es spart, und `.tbl` schiebt waagerecht.
 */
.tbl__zeit {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.78rem;
    color: var(--text-muted);
    white-space: nowrap;
}

/* --- Einzelseiten ohne Layout ------------------------------------------- */

/*
 * EINE SPALTE UND KEIN GITTER, seit die Einzelseiten einen Fuss haben.
 *
 * `place-items: center` zentrierte genau ein Kind. Mit dem Fuss sind es zwei,
 * und beide waeren nebeneinander in dieselbe Gitterzelle gerutscht. Als
 * Spalte nimmt das Panel den freien Platz (`margin: auto`) und der Fuss
 * schliesst darunter ab — er gehoert zur Seite, nicht zum Inhalt.
 *
 * Das Innenabstands-Verhalten bleibt: der Fuss bringt seinen eigenen mit,
 * einschliesslich `env(safe-area-inset-bottom)`.
 */
body.centered {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    padding: 1.25rem;
}

body.centered > .panel {
    flex: 0 0 auto;
    /* Nimmt den freien Platz oben UND unten — das Panel steht damit mittig,
       solange der Platz reicht, und rueckt sonst nach oben statt zu
       ueberlaufen. */
    margin: auto;
}

/*
 * Der Fuss der Einzelseite traegt den seitlichen Abstand schon vom Rumpf und
 * braucht oben keinen Abstand mehr: `margin: auto` am Panel hat den Raum
 * bereits verteilt.
 */
body.centered > .foot {
    width: 100%;
    margin-top: 0;
    padding-left: 0;
    padding-right: 0;
}

.panel {
    width: 100%;
    max-width: 26rem;
    padding: 1.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    text-align: center;
}

.panel h1 { margin-top: 0; }
.panel .note { text-align: left; }

/* --- Schaltflaeche ------------------------------------------------------ */

/*
 * Als <a> gedacht, nicht als <button>: der Anmeldeweg ist eine Navigation,
 * kein Formular. Deshalb display:inline-block und eine ausdrueckliche
 * Textfarbe — ein Link erbt sie sonst vom Akzent und wird auf dem Akzent
 * unlesbar.
 */
.btn {
    display: inline-block;
    padding: 0.7rem 1.4rem;
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    background: var(--accent);
    color: #fff;
    font-weight: 500;
    text-decoration: none;
}

/*
 * DER ZEIGER GEHOERT AN JEDEN KNOPF.
 *
 * `.btn` hatte ihn nicht, `.linkish` schon — am Rechner fuehlten sich die
 * Hauptknoepfe dadurch an wie Text. Am 22.09.2026 beim Durchspielen
 * gemeldet ("dass alle buttons den normalen cursor haben ist merkwuerdig").
 *
 * AM ELEMENT UND NICHT NUR AN DER KLASSE: ein <button> ohne .btn gibt es in
 * dieser App auch (der Schliessknopf der Feedback-Bubble), und der naechste
 * kommt bestimmt.
 *
 * `:disabled` ist ausgenommen und behaelt seinen eigenen Zeiger — beim
 * Push-Umschalter ist `progress` richtig, weil dort gewartet und nicht
 * verboten wird.
 */
button:not(:disabled),
.btn { cursor: pointer; }

.btn:hover,
.btn:focus-visible { background: transparent; color: var(--accent); }

/* Sichtbarer Fokus, und zwar abgesetzt: die Anmeldeseite ist fuer manche
   Nutzer der einzige Weg hinein, und ein unsichtbarer Fokus sperrt sie aus. */
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- Formulare ---------------------------------------------------------- */

/*
 * Untereinander statt nebeneinander: die Einrichtung wird ueberwiegend am
 * Handy gemacht, und dort ist alles einspaltig. Ein Raster braeuchte einen
 * Umbruchpunkt, ohne dass die Breite je etwas gewaenne.
 */
.stack {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    max-width: 28rem;
}

.stack label {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    color: var(--text-muted);
    font-size: 0.9rem;
}

/*
 * NICHT die Kaestchen. Die Regel beschreibt ein EINGABEFELD — Innenabstand,
 * Rahmen, eigene Flaeche —, und ein Kontrollkaestchen ist keines: es zeichnet
 * der Browser selbst. Ohne den Ausschluss bekamen die fuenf Kaestchen in
 * pages/einstellungen.php (drei Meldezeiten, zwei Meldewege) `padding` und
 * `background` aufgedrueckt.
 *
 * Und das faellt je Browser verschieden aus: Blink verwirft den Innenabstand
 * an einem Kaestchen (gemessen `padding: 0px`), WebKit ist nachgiebiger. Das
 * ist der wahrscheinlichste Grund, warum die Kaestchen am iPhone anders
 * aussahen als am PC — ein Unterschied, den man am eigenen Rechner nie sieht.
 */
.stack input:not([type="checkbox"]),
.stack select {
    padding: 0.6rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg);
    color: var(--text);
    font: inherit;
    /* 16px verhindert, dass iOS beim Antippen hineinzoomt. */
    font-size: 1rem;
}

/* Der Fokusring gilt weiter fuer ALLE, auch die Kaestchen: er sagt, wo man
   gerade ist, und das ist keine Frage der Feldart. */
.stack input:focus-visible,
.stack select:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.stack .btn { align-self: flex-start; }
.stack .note { font-size: 0.9rem; }

.stack p.muted { margin: -0.5rem 0 0; font-size: 0.85rem; }

form.inline { display: inline; }

/*
 * Ein Knopf, der wie ein Link aussieht. Bewusst ein <button> in einem
 * Formular und kein <a>: "Login entfernen" veraendert etwas und gehoert
 * deshalb hinter POST samt CSRF-Pruefung, nicht hinter einen Link, den ein
 * Vorlader auch ungefragt anfassen koennte.
 */
.linkish {
    padding: 0;
    border: 0;
    background: none;
    color: var(--accent);
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}

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

/* --- Lockup auf der Anmeldeseite ---------------------------------------- */

.brand__lockup {
    margin: 0 0 0.5rem;
    /* Die <h1> traegt nur noch ein Bild — die Schriftgroesse wuerde sonst
       eine Zeilenhoehe erzeugen, die niemand sieht, aber jeder misst. */
    line-height: 0;
}

.brand__lockup img {
    width: 100%;
    /* Nicht groesser als noetig: das Lockup ist 300x72, darueber hinaus
       gestreckt wirkt es plakativ statt ruhig. */
    max-width: 15rem;
    height: auto;
}

/* --- Kontrollkaestchen --------------------------------------------------- */

/*
 * Derselbe Weg wie beim <select> weiter oben: `appearance: none` nimmt dem
 * Element nur die ZEICHNUNG. Das echte <input> bleibt stehen —
 * Tastaturbedienung, Screenreader-Ansage und Formularverhalten unveraendert.
 * Ein aus <span> nachgebauter Schalter ist das ausdruecklich NICHT; den lehnt
 * dieses Projekt an drei Stellen ab, und aus gutem Grund.
 *
 * WARUM UEBERHAUPT. Das Systemkaestchen ist gemessen 13 x 13 px — neben
 * 16-px-Text und in einer 44-px-Kurskachel der kleinste Bedienteil der ganzen
 * App, und seine Farben kommen aus dem Betriebssystem, nicht von hier. Der
 * Umschalter setzte deshalb schon immer 1.2rem mit der Begruendung, das
 * Standardkaestchen sei "am Handy kaum zu treffen" — in Kanalwahl, Kursliste
 * und Abo-Filter stand dieselbe Begruendung nicht, und dort waren es die
 * 13 px.
 *
 * DIESE REGELN STEHEN VOR DEN SONDERREGELN (.kanalwahl input,
 * .kursliste__kurs input), und das ist keine Stilfrage: `input[type=…]` und
 * `.kanalwahl input` haben BEIDE die Spezifitaet (0,1,1). Bei Gleichstand
 * entscheidet die Quellreihenfolge — stuenden sie weiter unten, verloere die
 * Kanalwahl ihre Ausrichtung.
 */
input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    display: inline-grid;
    place-content: center;
    flex: 0 0 auto;
    width: 1.25rem;
    height: 1.25rem;
    margin: 0;
    padding: 0;
    border: 1px solid var(--border);
    /* 4px wie bei `code`, nicht var(--radius): 10 px waeren auf 20 px fast
       ein Kreis, und ein runder Schalter liest sich wie ein Radioknopf. */
    border-radius: 4px;
    background: var(--bg);
    cursor: pointer;
}

/*
 * Der Haken aus zwei Raendern und nicht als Datei: kein data-URI, keine
 * CSP-Frage, und er folgt der Farbe, die hier gesetzt wird.
 *
 * `opacity` statt `display`, damit das Kaestchen beim Anhaken nicht springt —
 * das Pseudo-Element belegt seinen Platz auch ungehakt.
 */
input[type="checkbox"]::after {
    content: "";
    width: 0.62rem;
    height: 0.34rem;
    border: 2px solid #fff;
    border-top: 0;
    border-right: 0;
    border-radius: 1px;
    transform: translateY(-0.08rem) rotate(-45deg);
    opacity: 0;
}

input[type="checkbox"]:checked {
    background: var(--accent);
    border-color: var(--accent);
}

input[type="checkbox"]:checked::after { opacity: 1; }
input[type="checkbox"]:hover { border-color: var(--accent); }

input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/*
 * Gesperrt heisst hier "wird gerade geprueft" — der Umschalter auf der
 * Uebersicht wartet auf die Push-Erlaubnis des Browsers. Deshalb
 * `cursor: progress` und nicht `not-allowed`: es ist kein Verbot, sondern
 * eine Wartezeit.
 */
input[type="checkbox"]:disabled { cursor: progress; background: var(--surface); }

input[type="checkbox"]:disabled:checked {
    background: var(--text-muted);
    border-color: var(--text-muted);
}

/* --- Umschalter ---------------------------------------------------------- */

/*
 * Ein gewoehnliches Kontrollkaestchen mit Beschriftung, nur groesser. Bewusst
 * KEIN nachgebauter Schieberegler aus <span>-Elementen: der verliert die
 * Tastaturbedienung und die Ansage fuer Screenreader, und beides braucht man
 * hier — der Schalter entscheidet, ob jemand ueberhaupt etwas erfaehrt.
 */
.switchrow { margin: 1rem 0 0.25rem; }

.switch {
    display: inline-flex;
    align-items: center;
    gap: 0.65rem;
    cursor: pointer;
}

/*
 * Groesse und Farbe kommen seit dem Handoff von oben — hier standen einmal
 * `width: 1.2rem`, `height: 1.2rem` und `accent-color`. Die Begruendung
 * ("am Handy kaum zu treffen") galt an jedem Kaestchen der App und steht
 * deshalb jetzt an der allgemeinen Regel, statt an genau einer Stelle.
 */
.switch input { cursor: pointer; }

.switch input:disabled + span { color: var(--text-muted); }

.switch input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- Freigabe-Schalter (#154) ------------------------------------------- */

/*
 * EIN SCHIEBESCHALTER — und das ist kein Widerspruch zum Absatz darueber.
 * Der lehnt einen Regler aus <span>-Elementen ab, weil der Tastatur und
 * Screenreader verliert. `.kipp` ist ein echter <button> mit
 * `role="switch"` und `aria-checked`: er ist fokussierbar, loest mit
 * Leertaste und Eingabe aus und wird als Schalter angesagt. Gezeichnet wird
 * nur die Bahn, und die gehoert in die Pseudo-Elemente.
 *
 * KEINE `transition`. Der Tipp schickt ein Formular ab, und die Seite laedt
 * neu — es gibt keinen Zustand, zwischen dem sich etwas bewegen koennte
 * (siehe die Regel zu Bewegung in CLAUDE.md).
 *
 * 44 px HOCH ALS TIPPZIEL, die Bahn selbst ist nur 1,5rem: der Knopf ist
 * groesser als das, was man sieht.
 */
.kipp {
    position: relative;
    display: inline-flex;
    align-items: center;
    width: 2.75rem;
    min-height: 44px;
    padding: 0;
    border: 0;
    background: none;
    vertical-align: middle;
    cursor: pointer;
}

/* Die Bahn. Aus: nur ein Rahmen, damit "aus" nicht wie "gesperrt" aussieht. */
.kipp::before {
    content: '';
    width: 100%;
    height: 1.5rem;
    border: 2px solid var(--text-muted);
    border-radius: 999px;
}

/* Der Knopf. Links heisst aus, rechts heisst an — wie auf jedem Telefon. */
.kipp::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 0.3rem;
    width: 0.9rem;
    height: 0.9rem;
    margin-top: -0.45rem;
    border-radius: 50%;
    background: var(--text-muted);
}

.kipp[aria-checked="true"]::before {
    border-color: var(--ok);
    background: var(--ok);
}

.kipp[aria-checked="true"]::after {
    left: calc(100% - 0.9rem - 0.3rem);
    background: var(--bg);
}

.kipp:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 999px; }

/* Nach dem Tipp sperrt absenden.js den Knopf; das sieht man am Zeiger. */
.kipp:disabled { cursor: progress; }

.freigabe {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.6rem;
}

.freigabe__wann { font-size: 0.85rem; }

/* --- Abos, Plan, Status -------------------------------------------------- */

.panelbox {
    margin: 1.25rem 0;
    padding: 1.25rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

/* h3, seit die Abo-Seite bei mehreren Schulen eine h2-Ueberschrift je Schule
   darueber setzt — die Klasse ist dann eine Ebene tiefer. */
.panelbox h2,
.panelbox h3 { margin-top: 0; font-size: 1.15rem; }
.panelbox h2 .muted,
.panelbox h3 .muted { font-weight: 400; font-size: 0.85rem; }

/* Nebeneinander, solange Platz ist — darunter untereinander. Ohne festen
   Umbruchpunkt: die Felder entscheiden selbst, wann es zu eng wird. */
.row {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: flex-end;
}

.row label { flex: 1 1 8rem; }

.filterleiste { margin-bottom: 1.5rem; }

/*
 * Breite Tabellen scrollen IN SICH, nicht die Seite. Sonst wandert bei 320 px
 * die ganze Seite waagerecht — samt Navigation und Ueberschrift —, und man
 * verliert die Orientierung.
 */
.tablewrap {
    overflow-x: auto;
    /* Die Ausnahme zu `touch-action: pan-y` auf .wrap: hier DARF waagerecht
       geschoben werden, sonst kaeme man an eine zu breite Tabelle nicht ran. */
    touch-action: pan-x pan-y;
    /* Sichtbar machen, dass es weitergeht: ohne Rand sieht ein abgeschnittener
       Tabellenrand aus wie das Ende der Tabelle. */
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/* In der Huelle scrollt die Tabelle nicht mehr selbst — sonst waeren es zwei
   Scroller uebereinander. */
.tablewrap .tbl { margin: 0; overflow-x: visible; }

.plan td, .plan th { white-space: nowrap; }

/*
 * Geaenderte Stunden: ein Streifen links UND Farbe in der betroffenen Zelle.
 * Nicht nur Farbe — rund acht Prozent der Maenner sehen Rot-Gruen schlecht,
 * und in der Sonne sieht es ohnehin niemand. Ein Ausfall traegt zusaetzlich
 * das Wort "faellt aus".
 */
.plan__row--geaendert td:first-child {
    border-left: 3px solid var(--accent);
    padding-left: calc(0.75rem - 3px);
}

/* --- Die Zustandspille in Zeile 1 (#86) ---------------------------------- */

/*
 * "Ausfall" bzw. "Änderung" als Pille statt als farbiger Text.
 *
 * Vorher trug ein rotes, fettes "fällt aus" die Aussage, und eine blosse
 * Aenderung hatte ueberhaupt kein Wort — nur den Streifen links und die
 * eingefaerbte Lehrkraft. Die Pille benennt beide Faelle gleich und ist auf
 * einen Blick als Kennzeichen zu erkennen statt als Teil des Satzes.
 *
 * DAS WORT TRAEGT DIE AUSSAGE, die Farbe setzt sie nur ab: rund acht Prozent
 * der Maenner sehen Rot-Gruen schlecht, und im hellen Sonnenlicht sieht die
 * Faerbung ohnehin niemand. Genau deshalb konnte mit diesen Pillen auch die
 * Legende unter der Tabelle entfallen — sie erklaerte eine Farbe, die jetzt
 * nichts mehr allein traegt.
 *
 * Dieselbe Form wie .wochenpille (voller Halbkreis, ruhiger Untergrund,
 * 0.75rem): es soll EINE Formensprache bleiben und nicht zwei Sorten Pillen
 * auf derselben Seite geben. Nur die Farbe unterscheidet sie, und die folgt
 * dem Zustand.
 */
.zustandspille {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    padding: 0.05rem 0.45rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: 1.4;
    /* Ein Umbruch mitten in "Änderung" saehe aus wie zwei Pillen. */
    white-space: nowrap;
}

.zustandspille--ausfall {
    background: var(--bad-quiet);
    color: var(--bad);
}

.zustandspille--aenderung,
.zustandspille--angefragt {
    background: var(--warn-quiet);
    color: var(--warn);
}

/*
 * In der Zeilenform ganz nach rechts.
 *
 * ANS ENDE UND NICHT NACH VORN: eine Pille vor dem Fach schoebe jede
 * betroffene Zeile ein Stueck nach rechts, und die Fachnamen fluchteten nicht
 * mehr untereinander — bei einem Plan, den man ueberfliegt, ist genau diese
 * Flucht die Lesehilfe.
 *
 * `margin-left: auto` statt einer festen Position: bricht .s__zeile um (lange
 * Klartextnamen, schmales Geraet), wandert die Pille mit in die naechste
 * Zeile, statt dort zu kleben, wo vorher Platz war.
 *
 * `:not(:only-child)` faengt den Fall, in dem sie ALLEIN in der Zeile steht:
 * in Jahrgang 12 liefert Indiware bei einem Ausfall zuweilen gar nichts mehr
 * — kein Fach, kein Lehrer, kein Raum (52 Slots in einer gemessenen Woche),
 * und `fachAusInfo()` findet dann auch im Freitext keines. Ohne die
 * Einschraenkung schoebe `auto` die einzige Pille der Zeile an den rechten
 * Rand, wo sie ins Leere zeigt.
 */
.s__zeile .zustandspille:not(:only-child) { margin-left: auto; }

/*
 * AUF SCHMALEN SCHIRMEN EINE EIGENE ZEILE GANZ OBEN, LEISE (04.10.2026).
 *
 * Auf 393 px passt die Pille nicht mehr in die erste Zeile. Ohne Zutun
 * bricht sie um und wird von `margin-left: auto` an den rechten Rand
 * gedrueckt — ein Farbklecks allein zwischen Stunde und Hinweis, gemeldet
 * als "liegt komisch dazwischen".
 *
 * DER ERSTE ANLAUF (#141) NAHM NUR DAS `auto` WEG. Das half, aber nicht
 * weit genug: die Pille fliesst dann einfach weiter und landet neben dem
 * RAUM — gemessen 39 px rechts vom Fach, und "110 ÄNDERUNG" liest sich wie
 * ein zusammenhaengender Raumname. Dieselbe Kollision wie seinerzeit bei
 * "Geschichte 5ETH6". Die Browsermessung dazu behauptete von Anfang an
 * Linksbuendigkeit und war von Anfang an rot; gemerkt hat es niemand, weil
 * die Messungen nicht in der CI laufen.
 *
 * DREI SACHEN, DIE ZUSAMMENGEHOEREN:
 *
 * 1. `flex: 0 0 100%` — eine eigene Zeile. Das macht `margin-left: auto`
 *    zugleich wirkungslos (bei voller Breite bleibt kein freier Platz, den
 *    ein auto-Rand verteilen koennte), deshalb steht hier keine
 *    Gegenregel mehr. Eine, die nichts tut, waere schlimmer als keine.
 * 2. `order: -1` — ganz nach oben. Der Zustand ist die Ueberschrift der
 *    Stunde: erst "ist hier was los", dann Fach, Lehrkraft, Raum, Hinweis.
 *    Das Gegenargument aus #141 ("eine Pille vor dem Fach schoebe jede
 *    betroffene Zeile nach rechts, und die Fachnamen fluchten nicht mehr")
 *    galt fuer eine Pille INLINE vor dem Fach. Auf einer eigenen Zeile
 *    faellt es weg, und die Flucht bleibt — am Geraet nachgesehen.
 * 3. Ohne Flaeche, dafuer als kleine Versalien in ihrer Farbe. Ein
 *    gefuelltes Abzeichen zieht mehr Aufmerksamkeit als die Stunde
 *    daneben — auf einer eigenen Zeile erst recht.
 *
 * WEGGELASSEN WIRD SIE NICHT, und das war die naheliegende Idee: "die Farbe
 * steht ja schon links an der Kiste". Der Streifen hat aber genau EINE
 * Farbe (`.s--geaendert`, `--accent`) und steht bei einem Ausfall, einer
 * Aenderung UND bei einer Stunde mit blossem Freitext. Ohne Pille waeren
 * "faellt aus" und "Fototermin 08.00-08.45 Uhr" auf dem Telefon nicht mehr
 * zu unterscheiden — fuer jeden, nicht nur fuer die rund acht Prozent der
 * Maenner, die Rot-Gruen schlecht sehen. Rund 130 der 1170 gemessenen
 * Freitexte sind genau dieser harmlose Fall.
 *
 * Am breiten Schirm bleibt alles, wie es war: dort passt sie in die Zeile
 * und steht rechts.
 */
@media (max-width: 40rem) {
    .s__zeile .zustandspille {
        order: -1;
        flex: 0 0 100%;
        padding: 0;
        background: none;
        font-size: 0.72rem;
        text-transform: uppercase;
        letter-spacing: 0.06em;
    }
}
/* --- Auswahlfelder im Hausstil ------------------------------------------- */

/*
 * Ein <select> sieht in jedem Betriebssystem anders aus und passt zu keinem.
 * appearance:none nimmt ihm das Systemaussehen; der Pfeil kommt als
 * eingebettetes SVG, weil die CSP externe Bilder ausschliesst und ein
 * zusaetzlicher Verweis hier ohnehin Unsinn waere.
 *
 * Die AUFKLAPPLISTE selbst laesst sich nicht gestalten — die zeichnet das
 * Betriebssystem. Das ist kein Versaeumnis, sondern die Grenze; sie
 * nachzubauen hiesse, Tastaturbedienung und Screenreader-Ansage neu zu
 * erfinden, und beides ginge zuverlaessig schief.
 */
select {
    appearance: none;
    -webkit-appearance: none;
    padding: 0.6rem 2.2rem 0.6rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background-color: var(--bg);
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%2397a5bd' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.7rem center;
    background-size: 0.7rem;
    color: var(--text);
    font: inherit;
    /* 16px: darunter zoomt iOS beim Antippen hinein. */
    font-size: 1rem;
    cursor: pointer;
}

select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
select:hover { border-color: var(--accent); }

/* --- Tageswechsel --------------------------------------------------------- */

/*
 * Die Tagesleiste: zurueck, Tag, vor — in dieser Reihenfolge, immer.
 *
 * Vorher standen hier drei beschriftete Knoepfe ("<- Freitag, 04.09.2026"),
 * die zusammen breiter waren als ein Handy. Ausserdem gab es eine
 * Wischgeste; die ist raus, weil sie sich nie sauber von einem schraegen
 * Scrollen trennen liess. Jetzt tragen zwei Pfeile das Blaettern und die
 * Mitte den Tag, um den es gerade geht.
 */
.tagnav {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 1.25rem;
}

.tagnav__tag {
    flex: 1 1 auto;
    text-align: center;
    font-weight: 500;
    /* Der Tag ist die Orientierung auf dieser Seite - er darf nicht umbrechen
       und dabei die Pfeile verschieben. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tagnav__pfeil {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Mindestens 2.75rem: kleiner trifft man auf einem Handy nicht
       zuverlaessig, und danebentippen heisst hier "falscher Tag". */
    min-width: 2.75rem;
    min-height: 2.75rem;
    flex: 0 0 auto;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    font-size: 1.1rem;
    line-height: 1;
    text-decoration: none;
}

.tagnav__pfeil:hover { border-color: var(--accent); color: var(--accent); }
.tagnav__pfeil:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Kein Nachbar in dieser Richtung: sichtbar, aber erkennbar tot. Den Pfeil
   wegzulassen liesse die Zeile bei jedem Tag anders aussehen — und der Tag
   in der Mitte spraenge beim Blaettern hin und her. */
.tagnav__pfeil--aus {
    color: var(--text-muted);
    border-style: dashed;
    cursor: default;
    opacity: 0.6;
}

/* Der Heute-Knopf steht ueber der Zeile und erscheint nur, wenn er etwas
   bewirkt: wenn man gerade woanders steht UND heute im Bestand ist. */
.tagnav__heute { margin: 0 0 0.6rem; text-align: center; }

/* Ein Knopf, der nicht der Hauptweg ist: gleiche Form, weniger Gewicht. */
.btn--leise {
    padding: 0.4rem 1rem;
    background: transparent;
    color: var(--accent);
    font-size: 0.9rem;
}

.btn--leise:hover,
.btn--leise:focus-visible { background: var(--accent); color: #fff; }

/* --- Stunde, Uhrzeit und Hinweis in der Plan-Tabelle --------------------- */

/*
 * vertical-align: baseline gegen die Voreinstellung middle des Browsers.
 *
 * Mit middle richtet sich jede Zelle an der MITTE der Zeile aus — und weil
 * die Stundenspalte zwei bis drei Zeilen hoch ist, waechst der Versatz mit
 * ihr: gemessen 8,7 px in einer Zeile ohne Mittagsband und 16,2 px mit. Das
 * Fach steht dann nicht neben seiner Stundennummer, sondern zwischen ihr und
 * der Uhrzeit.
 *
 * Nur .plan und nicht .tbl allgemein: die anderen Tabellen haben einzeilige
 * Zellen, dort aendert middle nichts — und eine Regel, die nichts aendert,
 * gehoert nicht in fremde Seiten.
 */
.plan td { vertical-align: baseline; }

/*
 * Stunde und Uhrzeit stehen untereinander in EINER Spalte.
 *
 * Als eigene Spalte war die Zeit mit Abstand die breiteste ("12:25–13:05")
 * und schob auf dem Handy Raum und Hinweis aus dem Bild. Unter der
 * Stundennummer kostet sie gar keine Breite: die Zahl laesst rechts ohnehin
 * Platz stehen.
 */
.plan__std { white-space: normal; line-height: 1.3; }

.plan__nr {
    display: block;
    /* Damit einstellige und zweistellige Stunden untereinander fluchten. */
    font-variant-numeric: tabular-nums;
}

.plan__zeit {
    display: block;
    color: var(--text-muted);
    font-size: 0.78rem;
    white-space: nowrap;
}


/*
 * Der Hinweis als eigene Zeile ueber die volle Breite.
 *
 * Vorher war er ein aufklappbares (i) in der letzten Spalte. Das half nicht:
 * auf einem 360-px-Schirm lag der Aufklapper selbst ausserhalb des Bildes,
 * der Text war also nur zu finden, wenn man wusste, dass es ihn gibt. Eine
 * eigene Zeile braucht keine Breite, keinen Knopf und kein JavaScript.
 */
.plan__stunde--mitinfo td { border-bottom: none; }

.plan__info td {
    /* Einzelwerte statt der padding-Kurzform: die Kurzform wuerde das
       padding-left ueberschreiben, mit dem der Aenderungsstreifen links
       Platz macht. */
    padding-top: 0;
    padding-bottom: 0.6rem;
    white-space: normal;
    color: var(--text-muted);
    font-size: 0.85rem;
}

.plan__info-marke {
    display: inline-flex;
    align-items: center;
    justify-content: center;

    /*
     * flex: none — ohne das wird der Kreis zum Oval.
     *
     * In der Zeilenform ist .s__info ein Flex-Container und die Marke damit
     * ein Flex-Element. `width` ist dort nur die AUSGANGSBREITE; mit dem
     * voreingestellten flex-shrink: 1 gibt sie nach, sobald der Hinweistext
     * Platz braucht — und der braucht ihn, sobald er umbricht. Auf einem
     * iPhone gemessen (393 px, dreizeiliger Hinweis): 7,05 px breit bei
     * unveraenderten 18,39 px hoch. Die Hoehe bleibt, weil align-items:
     * baseline nicht dehnt — deshalb sieht es aus, als waere das Zeichen in
     * die Laenge gezogen.
     *
     * Dieselbe Zeile steht aus demselben Grund an .ico. In der Tabelle
     * schadet sie nicht: dort ist die Marke kein Flex-Element, und `flex`
     * wirkt nur auf solche.
     */
    flex: none;

    width: 1.15rem;
    height: 1.15rem;
    margin-right: 0.35rem;
    border: 1px solid var(--accent);
    border-radius: 50%;
    color: var(--accent);
    font-size: 0.7rem;
    font-style: italic;
    font-weight: 600;
    line-height: 1;
    vertical-align: text-bottom;
}

/* Nur fuer Screenreader. */
.visuell-versteckt {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* --- Zustandsbloecke auf der Status-Seite -------------------------------- */

.blocks {
    display: flex;
    gap: 2px;
    margin: 0.35rem 0 1rem;
}

.blocks__b {
    flex: 1 1 auto;
    height: 1.75rem;
    border-radius: 2px;
    background: var(--border);
}

.blocks__b--ok   { background: var(--ok); }
.blocks__b--auth { background: var(--warn); }
.blocks__b--fail { background: var(--bad); }
/* 'none' behaelt das Grau von .blocks__b — kein Eintrag ist kein Fehler. */

/* --- Fuss --------------------------------------------------------------- */

/*
 * Der Fuss als Signatur (#56).
 *
 * Vorher eine zentrierte Zeile direkt am Inhalt — im Aufbau nicht von einem
 * Absatz zu unterscheiden. Die Luft darueber macht daraus einen Abschluss;
 * ohne sie las man den Fuss als letzten Satz der Seite.
 *
 * env(safe-area-inset-bottom) MUSS BLEIBEN: ohne die Zeile liegt der Fuss in
 * der installierten App unter dem Home-Indikator. Steht seit #46 hier.
 */
.foot {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.2rem;
    margin-top: 3rem;
    padding: 1.5rem 1.25rem calc(1.5rem + env(safe-area-inset-bottom));
    border-top: 1px solid var(--border);
    color: var(--text-muted);
    font-size: 0.85rem;
    text-align: center;
}

.foot__marke {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--text);
    letter-spacing: 0.02em;
}

/* Blasser als der Name: die Marke ist die Signatur, die Adresse der
   Nachweis dahinter — und nicht das Gleichrangige neben ihr. */
.foot__herkunft {
    color: var(--text-muted);
    font-size: 0.8rem;
}

/*
 * Die dritte Zeile: Rechtliches und Betrieb.
 *
 * Hier gehoeren Impressum und Datenschutz hin — sie stehen noch nicht darin,
 * weil es die Seiten nicht gibt und noch nicht entschieden ist, ob sie
 * hierher oder auf thaessler.de kommen. Ein Link ins Leere waere schlechter
 * als keiner. Der PLATZ ist damit vorbereitet: zwei <a> daneben haengen, und
 * weder Markup noch Stile muessen angefasst werden.
 *
 * LEISER ALS DIE HERKUNFT: --text-muted und 0.8rem. Es sind Wege, die man
 * sucht, wenn man sie braucht — keine Angebote.
 *
 * Und der Fuss bleibt DREIZEILIG. Keine Sitemap, kein Kontakt, keine Hilfe,
 * solange es die Seiten nicht gibt: ein Fuss voller toter Links ist
 * schlechter als ein knapper.
 */
.foot__rechtlich {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.25rem 0.9rem;
    margin-top: 0.4rem;
}

.foot__rechtlich a {
    color: var(--text-muted);
    font-size: 0.8rem;
    text-decoration: underline;
}

.foot__rechtlich a:hover { color: var(--text); }

/* --- Die Schulen als Karten ---------------------------------------------- */

/*
 * `.tbl` ist `display: block` mit `white-space: nowrap` — richtig fuer die
 * dichten Tabellen in pages/health.php, hier falsch: vier Spalten, davon eine
 * ein ganzer Satz ("Login entfernen"). Weggeschoben wurde dabei ausgerechnet
 * die Spalte mit der AKTION, und die ist der haeufigste Grund, diese Seite
 * ueberhaupt zu oeffnen.
 *
 * Karten fuer ALLE Breiten und nicht erst unter 40 rem: die Liste ist kurz,
 * ein bis zwei Eintraege. Ein zweiter Umbruchpunkt waere mehr Aufwand als
 * Gewinn. Die Tabellen im Status bleiben unberuehrt.
 */
.schulliste {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin: 0.7rem 0 0;
    padding: 0;
    list-style: none;
}

.schule {
    padding: 0.7rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

.schule__kopf {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.5rem;
}

.schule__name { font-weight: 500; }

/* Die Schulnummer ist ein technischer Wert — Monospace wie bei `code`. */
.schule__nr {
    color: var(--text-muted);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85rem;
}

.schule__zeile {
    margin: 0.2rem 0 0;
    color: var(--text-muted);
    font-size: 0.9rem;
}

/*
 * Zustand und Aktion in EINER Zeile: der Knopf steht bei der Begruendung, auf
 * die er sich bezieht. In der Tabelle lagen drei Spalten dazwischen.
 *
 * `flex-wrap`, weil bei einem abgelehnten Login zusaetzlich die Fehlermeldung
 * der Quelle danebensteht — die kann lang werden.
 */
.schule__fuss {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.5rem;
}

.schule__fuss form { margin-left: auto; }

/* --- Hinweise zum ganzen Tag --------------------------------------------- */

/*
 * Der Freitext aus <ZusatzInfo>. Nicht Beiwerk: am 07.09.2026 stand dort die
 * einzige Ankuendigung, dass in dieser Woche voellig andere Blockzeiten
 * gelten — im Stundenteil derselben Datei standen die normalen.
 */
.hinweise {
    margin: 1.5rem 0 0;
    padding: 0.9rem 1.1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-note);
}

.hinweise__titel {
    margin: 0 0 0.5rem;
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/*
 * pre-wrap: die Schule setzt den Text von Hand, mit Einrueckungen und
 * Trennlinien. Ihre Gliederung ist die Aussage — sie einzuebnen macht aus
 * einer Blockzeitentabelle einen Fliesstext.
 *
 * Bewusst KEIN monospace: es ist kein Quelltext, sondern eine Ansage an
 * Eltern und Schueler.
 */
.hinweise__text {
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: break-word;
    line-height: 1.5;
}

/* --- Plan: Kopfbereich, Wochenstreifen, Stand ---------------------------- */

/*
 * Ueberschrift und Auswahlfelder in EINER Zeile.
 *
 * Vorher war die Filterleiste drei beschriftete Felder untereinander
 * (Schule, Klasse, Tag) — auf 393 px drei Zeilen, bevor der Plan anfing. Der
 * Tag ist in den Wochenstreifen gewandert, Schule und Klasse tragen ihre
 * Beschriftung nur noch fuer Screenreader: was in einem Feld mit "7b" steht,
 * erklaert sich neben der Ueberschrift "Plan" von selbst.
 */
.planhead {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin: 0.5rem 0 1rem;
}

.planhead h1 { margin: 0; flex: 0 0 auto; }

.planhead__form {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
}

.planhead__feld { flex: 0 1 7.5rem; min-width: 0; }
.planhead__feld select,
.planhead__feld input { width: 100%; }

/*
 * Der Wochenstreifen ersetzt Tagesfeld, Blaetterpfeile und Heute-Knopf.
 *
 * Fuenf Kacheln fuer Montag bis Freitag, dazu je ein Wochenpfeil. Warum
 * nicht alle beobachteten Tage: davon liegen bis zu 21 im Bestand, sie
 * wuerden schieben, und der gewaehlte Tag muesste beim Laden herangescrollt
 * werden — eine Bewegung, die niemand ausgeloest hat.
 */
.tagstreifen {
    display: flex;
    align-items: stretch;
    gap: 0.4rem;
    margin: 0 0 1rem;
}

/*
 * Die fuenf Kacheln der gezeigten Woche.
 *
 * KEINE SCROLLBAHN MEHR (#105). Bis dahin lagen hier drei Wochen
 * nebeneinander und der Streifen selbst war der Wischbereich. Jetzt wischt
 * der Plan darunter tageweise, und der Streifen zeigt nur die Woche dazu.
 *
 * `flex: 1 1 auto` mit `min-width: 0` und NICHT `flex: 0 0 100%`: er ist das
 * mittlere von drei Geschwistern, links und rechts stehen die Wochenpfeile.
 * Mit 100 % schob er den rechten Pfeil aus dem Bild — gemessen 57 px ueber
 * die Breite hinaus, und die Seite liess sich waagerecht schieben. Am
 * 20.09.2026 von der Browser-Pruefung gefunden, nicht von Hand.
 *
 * Die fuenf Kacheln teilen sich den Platz (`.tagchip` hat `flex: 1 1 0`), es
 * gibt also nichts zu scrollen. Und nach dem, was das `overflow-x` der
 * Plan-Bahn die Datumszeile gekostet hat, wird hier kein Scrollport
 * eingebaut, den niemand braucht.
 */
.tagstreifen__woche {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: stretch;
    gap: 0.4rem;
}

.tagchip {
    flex: 1 1 0;
    min-width: 0;
    /* 44 px. Darunter trifft man auf einem Handy nicht zuverlaessig, und
       danebentippen heisst hier "falscher Tag". */
    min-height: 2.75rem;
    padding: 0.4rem 0.25rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    text-align: center;
    text-decoration: none;
    line-height: 1.25;
}

.tagchip:hover { border-color: var(--accent); }
.tagchip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.tagchip__tag {
    font-size: 0.72rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.tagchip__d {
    font-size: 0.95rem;
    font-weight: 500;
    /* Damit "07." und "17." untereinander fluchten. */
    font-variant-numeric: tabular-nums;
}

/*
 * Ein Punkt, keine Zahl.
 *
 * Er sagt "hier gibt es etwas zu sehen", nicht wie viel. Mit "4 geaendert"
 * passen fuenf Kacheln nicht in die 351 px Inhaltsbreite eines Handys.
 *
 * Tage ohne Aenderung tragen einen DURCHSICHTIGEN Punkt derselben Groesse,
 * damit die Kacheln fluchten — ohne ihn saessen Wochentag und Datum in jeder
 * zweiten Kachel ein paar Pixel tiefer.
 */
.tagchip__punkt {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--accent);
}

.tagchip__punkt--leer { background: transparent; }

.tagchip--hier {
    background: var(--accent-quiet);
    border-color: var(--accent);
}

.tagchip--hier .tagchip__d { color: var(--accent); }

/* Kein beobachteter Tag: die Kachel bleibt stehen, damit jede Woche gleich
   aussieht, fuehrt aber nirgendwohin. */
.tagchip--aus {
    color: var(--text-muted);
    border-style: dashed;
    opacity: 0.6;
    cursor: default;
}

.wochenpfeil {
    flex: 0 0 auto;
    min-width: 2rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    text-decoration: none;
    font-size: 1rem;
}

.wochenpfeil:hover { border-color: var(--accent); color: var(--accent); }
.wochenpfeil:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.wochenpfeil--aus {
    color: var(--text-muted);
    border-style: dashed;
    opacity: 0.6;
    cursor: default;
}

/*
 * Die klebende Tageszeile.
 *
 * Sie liegt IM .wrap und zieht sich per negativem Rand ueber dessen
 * Innenabstand hinaus bis an die Kanten — so bleibt die Seite ein Stueck
 * Inhalt und der Front-Controller unveraendert.
 *
 * Zweck: beim Scrollen sichtbar halten, welcher Tag gilt und ob an ihm etwas
 * ausfaellt. Ein Vertretungsplan, bei dem man nach oben scrollen muss, um zu
 * sehen, welcher Tag es ist, beantwortet die Frage nicht, fuer die es ihn
 * gibt.
 */
.tagsticky {
    position: sticky;
    /*
     * NICHT `top: 0`, seit der Kopf selbst steht. Sonst rastet die
     * Tagesleiste am oberen Rand des Fensters ein — also UNTER dem Kopf, der
     * dort schon liegt — und ist beim Scrollen nicht mehr zu sehen.
     * Woraus sich --kopf-hoehe zusammensetzt, steht ganz oben bei :root.
     */
    top: var(--kopf-hoehe);
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin: -1.5rem -1.25rem 1rem;
    padding: 0.5rem 1.25rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    font-size: 0.85rem;
}

/* Datum und Wochenpille gehören zusammen und wandern gemeinsam nach links;
   die Bilanz bleibt rechts außen. */
.tagsticky__datum {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
}

.tagsticky__tag { font-weight: 500; }
.tagsticky__n { color: var(--accent); }

/*
 * Die Wochenkennung als Pille neben dem Datum.
 *
 * Sie stand bis hierher links am Wochenstreifen und war dort ein Zeichen ohne
 * Bezug: der Streifen zeigt fünf Tage, und ein „A" davor las sich wie ein
 * weiterer Pfeil. Neben „Di 08.09." ist sie das, was sie ist — eine
 * Eigenschaft dieses Tages.
 *
 * Rund und in der Akzentfarbe, nicht grau: sie ist eine Angabe und kein
 * Bedienteil, und in der klebenden Zeile steht sonst nur noch die Bilanz.
 * Sie soll sich vom Datum absetzen, ohne mit ihm um Aufmerksamkeit zu
 * ringen — deshalb der ruhige Untergrund und kein voller Akzent.
 */
.wochenpille {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.35rem;
    padding: 0.1rem 0.4rem;
    /* Voller Halbkreis statt --radius: eine Pille unterscheidet sich damit
       auf einen Blick von den eckigen Kacheln des Streifens darunter. */
    border-radius: 999px;
    background: var(--accent-quiet);
    color: var(--accent);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: 1.4;
}

/*
 * Wie alt ist der Stand?
 *
 * In display:standalone gibt es keine Adressleiste und keinen
 * Neu-laden-Knopf. Eine von iOS wiederbelebte Seite kann Stunden alt sein,
 * ohne dass man es sieht — und genau dann glaubt man ihr am meisten.
 */
.stand {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem 0.75rem;
    margin: 0 0 1rem;
    color: var(--text-muted);
    font-size: 0.85rem;
}

/*
 * AUSKUNFT LINKS, HANDLUNGEN RECHTS (#56).
 *
 * Vorher war das eine Punktkette: "Stand 11:10 Uhr · Heute · aktualisieren".
 * Zwei Glieder lesen sich als Angabe mit Zusatz, drei als Aufzaehlung — und
 * zwei davon sind gar keine Angaben, sondern Knoepfe.
 *
 * flex-wrap, weil es eng wird: auf 393 px stehen "Stand 11:10 Uhr" (~105 px),
 * "Heute" (~62 px) und "⟳ aktualisieren" (~135 px) nebeneinander. Das passt
 * in die 351 px Inhaltsbreite — aber nur knapp, und mit groesserer Schrift
 * nicht mehr. Dann rutschen die Knoepfe in die zweite Zeile, statt die Zeile
 * seitwaerts zu schieben.
 */
.stand__zeit { display: inline-flex; align-items: baseline; gap: 0.3rem; }

/*
 * Ein Stand, der im Poll-Fenster ueber zwei Stunden alt ist.
 *
 * ZEICHEN UND FARBE, nicht Farbe allein: rund acht Prozent der Maenner sehen
 * Rot-Gruen schlecht, und auf einem Handy in der Sonne sieht die Farbe
 * ohnehin niemand. Dieselbe Regel wie beim Punkt im Wochenstreifen.
 *
 * Gelb und nicht rot — es braucht einen Menschen, aber es ist kein Fehler:
 * vielleicht antwortet die Schule gerade nicht.
 */
.stand__zeit--alt { color: var(--warn); }

/* Das Alter steht nur dann da, wenn es zu alt ist — sonst waere es Ballast
   neben einer Uhrzeit, die man selbst lesen kann. Der Trennpunkt macht aus
   den beiden eine Angabe mit Zusatz und nicht zwei gleichrangige Glieder. */
.stand__alter::before { content: "· "; }

.stand__tun {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    /* Nach rechts, auch wenn die Zeile umbricht: dann steht die Auskunft
       oben und die Knoepfe darunter rechtsbuendig, statt links am Rand zu
       kleben, wo sie mit der Uhrzeit eine Spalte bildeten. */
    margin-left: auto;
}

/*
 * Die beiden Handlungen als Knoepfe, aber leise.
 *
 * Sie sind Wege und keine Aufforderungen — ein Plan, dessen auffaelligstes
 * Element "aktualisieren" heisst, sagt dem Nutzer, er koenne dem Gezeigten
 * nicht trauen.
 */
.standknopf {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.25rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text-muted);
    text-decoration: none;
    /* Fingergroesse ohne die Zeile aufzublasen: der Kasten bleibt flach,
       die Trefferflaeche waechst nach oben und unten. */
    min-height: 32px;
}

.standknopf:hover { background: var(--surface-hi); color: var(--text); }
.standknopf:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Das Zeichen sitzt optisch zu hoch, wenn es auf der Grundlinie steht — es
   ist ein Kreis, kein Buchstabe. */
.standknopf .ico { flex: 0 0 auto; }

/* --- Plan: Stundenzeilen (schmale Schirme) ------------------------------- */

/*
 * Unter 40 rem Zeilen statt Tabelle.
 *
 * Vier Spalten mit white-space: nowrap passen auf 393 px nur, solange alles
 * ein Kuerzel bleibt. Mit einer Kursgruppe in der Fachspalte (RU-7/3) und
 * einem ausgeschriebenen Raum ("Turnhalle 2") sind es gemessen 357 px Inhalt
 * in 349 px Huelle — der Plan schiebt seitwaerts, und weggeschoben wird
 * ausgerechnet der Raum.
 */
.stunden {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

/*
 * align-items: baseline, nicht das voreingestellte stretch.
 *
 * Die linke Spalte traegt line-height 1.3 (Nummer und Zeit sollen dicht
 * stehen), die rechte erbt die 1.6 des Rumpfes. Bei stretch beginnen beide
 * Kaesten oben buendig — der halbe Zeilendurchschuss ist dann aber
 * verschieden gross (2,4 px gegen 4,8 px), und das Fach sitzt gemessen 3 px
 * TIEFER als seine Stundennummer. Auf einem Handy sieht das aus, als sei die
 * Zeile verrutscht.
 *
 * baseline richtet die erste Grundlinie beider Spalten aneinander aus, statt
 * die Kastenkanten: gemessen 0 px Versatz, unabhaengig davon, ob rechts noch
 * ein Hinweis und links noch das Mittagsband steht.
 */
.s {
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--border);
}

.s:last-child { border-bottom: none; }

/* Derselbe Streifen wie in der Tabelle. Farbe allein traegt die Aussage
   nicht — rund acht Prozent der Maenner sehen Rot-Gruen schlecht, und in der
   Sonne sieht es ohnehin niemand. */
.s--geaendert {
    border-left: 3px solid var(--accent);
    padding-left: calc(0.75rem - 3px);
}

/*
 * Feste Breite — und zwar wirklich fest.
 *
 * Ein Flex-Element hat standardmaessig `min-width: auto` und WAECHST ueber
 * seine Basis hinaus, wenn der Inhalt breiter ist. `.s__zeit` traegt
 * zusaetzlich `white-space: nowrap`, kann also nicht umbrechen. Ergebnis:
 * die Spalte war mal 67 px breit (nur die Stundennummer), mal 69 px (mit
 * Uhrzeit) — und die Fachkuerzel daneben fluchteten nicht.
 *
 * Nachgemessen mit den Zeichenbreiten von system-ui:
 *
 *   Basis 4.2rem              67,0 px
 *   "11:40–12:25" bei .78rem  68,7 px   <- 1,7 px zu breit
 *   "Mittagsband" bei .72rem  65,3 px
 *
 * `min-width: 0` allein wuerde die Zeit ueber den Rand laufen lassen und in
 * das Fach daneben ragen. Deshalb BEIDES: die Basis auf 4.9rem (78 px), also
 * rund 9 px Luft ueber dem breitesten Inhalt, damit auch eine andere
 * Systemschrift noch hineinpasst — und min-width: 0, damit sie unter keinen
 * Umstaenden wachsen kann.
 */
.s__std {
    flex: 0 0 4.9rem;
    min-width: 0;
    line-height: 1.3;
}
.s__nr { display: block; font-variant-numeric: tabular-nums; }

.s__zeit {
    display: block;
    color: var(--text-muted);
    font-size: 0.78rem;
    white-space: nowrap;
}

/*
 * Die Mittagspause als eigene Zeile.
 *
 * Sichtbar anders als eine Stunde: kein Fach in Halbfett, keine Nummer,
 * gestrichelte Trennung. Sie soll sich beim Ueberfliegen als Unterbrechung
 * lesen und nicht als weiterer Eintrag.
 *
 * Sie steht nur da, wenn in dem Slot KEIN Unterricht liegt. Beides
 * gleichzeitig zu zeigen war der Fehler vorher: die Stunde trug die
 * Beschriftung "Mittagsband", obwohl Fach, Lehrer und Raum danebenstanden.
 */
.s--pause,
.plan__pause td {
    color: var(--text-muted);
    background: var(--surface);
}

.s--pause { border-bottom-style: dashed; }
.plan__pause td { border-bottom-style: dashed; }

.s__pause {
    font-size: 0.85rem;
    letter-spacing: 0.02em;
}

.plan__pause td:last-child { font-size: 0.85rem; letter-spacing: 0.02em; }

.s__inhalt { flex: 1 1 auto; min-width: 0; }

/* Umbrechend: hier darf eine lange Raumbezeichnung in die naechste Zeile,
   statt die Zeile zu sprengen. Genau das kann die Tabelle nicht. */
.s__zeile {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.15rem 0.55rem;
}

.s__fach { font-weight: 600; }
.s__ku2 { color: var(--text-muted); font-size: 0.85rem; }

/*
 * Der Kurs, wenn er hinter dem Freitext steht (#23.09.2026).
 *
 * Er erbt die Groesse seiner Zeile und tritt nur farblich zurueck: in der
 * Hinweiszeile ist er die Nebenangabe ("was ersetzt wurde" ist die
 * Hauptsache), auf der Tabellenzeile steht er am selben Ort.
 */
.s__kurs-hinten { color: var(--text-muted); }
.s__w { color: var(--text-muted); }
.s__w--neu { color: var(--warn); }

.s__info {
    display: flex;
    align-items: baseline;
    gap: 0.35rem;
    margin-top: 0.15rem;
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* --- Plan: zwei Spalten auf breiten Schirmen ----------------------------- */

/*
 * Die Hinweise der Schule stehen NEBEN dem Plan, nicht darunter.
 *
 * Am 07.09.2026 stand dort die einzige Ansage ueber voellig geaenderte
 * Blockzeiten — unterhalb der Tabelle war sie ausserhalb des Bildes.
 */
/*
 * UNTER 40 rem STEHT ALLES UNTEREINANDER — und zwar sichtbar.
 *
 * Hier stand einmal `.plancols__seite { display: none }`, gedacht als "die
 * Spalte gibt es erst ab 40 rem". Das war ein Fehler mit Ansage: die
 * Hinweise der Schule waren damit auf dem Handy UEBERHAUPT nicht zu sehen —
 * auf der Plattform, fuer die diese App gebaut ist, und ausgerechnet die
 * Angabe, die am 07.09.2026 als einzige die geaenderten Blockzeiten nannte.
 *
 * Deshalb ist der schmale Zustand jetzt der Grundzustand: beide Bereiche
 * fliessen untereinander. Die Media Query darunter macht daraus zwei
 * Spalten, mehr nicht. Ein Fehler in der Media Query kostet dann Eleganz,
 * keine Information.
 */
.plancols { display: block; }
.plancols__seite { display: block; }

/*
 * Der eine Umbruchpunkt: darunter Zeilen, darueber Tabelle.
 *
 * Einer und nicht zwei. Bei 560 px traegt die Tabelle noch, gewinnt aber nur
 * Dichte — und Dichte ist auf einem Geraet in der Hand kein Wert.
 */
.tablewrap { display: none; }

@media (min-width: 40rem) {
    .stunden { display: none; }
    .tablewrap { display: block; }

    .plancols {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 20rem;
        gap: 1.5rem;
        align-items: start;
    }

    /* In der Spalte klebt der Block oben am Plan, statt einen Abstand zu
       tragen, den er nur untereinander braucht. */
    .plancols__seite .hinweise { margin: 0; }
}

/* --- Symbole ------------------------------------------------------------- */

/*
 * Inline im Markup, nie als <img> — dort faellt currentColor auf Schwarz
 * zurueck und das Zeichen verschwindet auf dunklem Grund. Siehe icon() in
 * includes/functions.php.
 */
.ico { display: block; flex: none; }

/* --- Verlauf ------------------------------------------------------------- */

.verlauf {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.verlauf__eintrag {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

/* Der angetippte Eintrag. Er ist ausserdem offen — beides zusammen, damit
   man nach dem Antippen einer Push nicht sucht, sondern liest. */
.verlauf__eintrag--ziel { border-color: var(--accent); }

.verlauf__kopf {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.75rem;
    padding: 0.7rem 0.9rem;
    cursor: pointer;
    /* Der eingebaute Dreiecks-Marker sitzt in jedem Browser anders und
       bricht die Zeile um. Das Aufklappen zeigt der Rahmen selbst. */
    list-style: none;
}

.verlauf__kopf::-webkit-details-marker { display: none; }
.verlauf__kopf:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.verlauf__titel { font-weight: 500; }

.verlauf__meta {
    margin-left: auto;
    color: var(--text-muted);
    font-size: 0.85rem;
    white-space: nowrap;
}

/* --- Der Filterblock je Abo (eingeklappt) -------------------------------- */

/*
 * Gemessen ist ein offenes Abo 1241 px hoch, bei 762 px nutzbarer Hoehe. Bei
 * zwei Abos begann "Klasse abonnieren" erst bei 2625 px — dreieinhalb
 * Bildschirmhoehen tief. Eingeklappt sind es 218 px je Abo, und der Weg zum
 * Abonnieren ist ohne Scrollen sichtbar.
 *
 * Dieselbe Form wie der Kursfilter am Plan: <details> mit Zusatz im Kopf. Es
 * ist keine neue Form, nur dieselbe an der zweiten Stelle.
 */
.abofilter {
    margin: 1rem 0 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.abofilter__auf {
    display: flex;
    /*
     * UMBRECHEND, und das ist keine Vorsichtsmassnahme, sondern behebt einen
     * gemessenen Fehler: der Zusatz traegt `white-space: nowrap` und kann
     * lang werden ("nur Ausfälle · Std 1–6 · 4 Kurse aus"). Ohne Umbruch lief
     * die Zeile ueber — die Beschriftung quetschte sich auf drei Zeilen, der
     * Zusatz blieb starr, und der Pfeil wurde AUS DEM RAHMEN geschoben.
     *
     * In der iOS-PWA gesehen, am breiten Schirm nie: dort ist genug Platz.
     */
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 0.9rem;
    /* 44 px: der Kopf ist der Bedienteil. */
    min-height: 2.75rem;
    cursor: pointer;
    list-style: none;
    font-size: 0.9rem;
}

/*
 * Auf schmalen Schirmen bekommt der Zusatz eine eigene Zeile — dieselbe
 * Technik wie im Verlaufskopf: `flex-basis: 100%` erzwingt den Umbruch,
 * `order` sorgt dafuer, dass er UNTER der Zeile mit Beschriftung und Pfeil
 * landet und nicht dazwischen.
 *
 * Ohne das schrumpft die Beschriftung auf drei Zeilen, damit der starre
 * Zusatz danebenpasst — und lesbar ist dann keins von beidem.
 */
@media (max-width: 39.999rem) {
    .abofilter__auf > span:first-child {
        order: 1;
        flex: 1 1 auto;
        min-width: 0;
    }

    .abofilter__auf .aufklapp-pfeil {
        order: 2;
        margin-left: auto;
    }

    .abofilter__zahl {
        order: 3;
        flex: 0 0 100%;
        margin-left: 0;
        /* Ueber die volle Breite darf er notfalls umbrechen, statt
           hinauszuragen. */
        white-space: normal;
    }
}

.abofilter__auf::-webkit-details-marker { display: none; }
.abofilter__auf:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/*
 * Der Zusatz nennt, WAS gesetzt ist ("nur Ausfälle · 3 Kurse aus") und nicht
 * nur, dass etwas gesetzt ist. Sonst klappt man auf, um nachzusehen, und hat
 * nichts gewonnen.
 */
.abofilter__zahl {
    margin-left: auto;
    padding: 0 0.45rem;
    border-radius: var(--radius);
    background: var(--accent-quiet);
    color: var(--accent);
    font-size: 0.75rem;
    white-space: nowrap;
}

.abofilter__inhalt {
    padding: 0 0.9rem 0.9rem;
    border-top: 1px solid var(--border);
}

/* --- Der Pfeil an den Aufklappern ---------------------------------------- */

/*
 * WOZU. `.kursfilter__auf`, `.verlauf__kopf`, `.namen__auf` und
 * `.abofilter__auf` nehmen alle `list-style: none` samt
 * `::-webkit-details-marker { display: none }` — mit guter Begruendung: der
 * eingebaute Dreiecks-Marker sitzt in jedem Browser anders und bricht die
 * Kopfzeile um.
 *
 * Ohne Ersatz fehlt aber das Zeichen, DASS sich hier etwas oeffnet: ein
 * zugeklappter Kursfilter sieht aus wie eine Ueberschrift. Der Pfeil rechts
 * ist dieser Ersatz — an einer Stelle fuer alle Aufklapper, damit nicht der
 * naechste wieder ohne dasteht.
 */
.aufklapp-pfeil {
    display: flex;
    flex: 0 0 auto;
    margin-left: auto;
    color: var(--text-muted);
}

/* Steht schon ein Zaehler oder eine Uhrzeit rechts, traegt DIE das
   `margin-left: auto` — der Pfeil bekommt dann nur noch Abstand. */
.kursfilter__zahl ~ .aufklapp-pfeil,
.namen__zahl ~ .aufklapp-pfeil,
.abofilter__zahl ~ .aufklapp-pfeil,
.verlauf__meta ~ .aufklapp-pfeil {
    margin-left: 0.5rem;
}

/*
 * Offen zeigt er nach oben. OHNE Uebergang: es gibt genau eine Bewegung in
 * diesem Stylesheet, und die macht der Browser beim Seitenwechsel allein.
 */
details[open] > summary .aufklapp-pfeil .ico {
    transform: rotate(180deg);
}

/*
 * AM HANDY SOLL DIE ZEIT UNTER DEN TITEL — und der Pfeil trotzdem oben rechts
 * neben dem Titel stehen.
 *
 * KEIN `position: absolute` DAFUER, und das ist eine Lehre aus der ersten
 * Fassung: dort bekam `.verlauf__kopf` ein `position: relative` und der Pfeil
 * ein `absolute` dagegen. Am PC sah das richtig aus, in der iOS-PWA sass der
 * Pfeil AUSSERHALB der Karte.
 *
 * Der Grund: `.verlauf__kopf` ist ein <summary>, und WebKit macht daraus
 * keinen verlaesslichen Bezugsrahmen. Das absolut gesetzte Kind sucht sich
 * dann den naechsten positionierten Vorfahren — und der ist irgendwo weiter
 * oben im Dokument.
 *
 * Statt dessen bleibt alles im Flusslayout: der Kopf ist ohnehin eine
 * umbrechende Flex-Zeile. Die Zeit bekommt `flex-basis: 100%` und faellt
 * damit auf eine eigene Zeile; `order` sorgt dafuer, dass sie UNTER der Zeile
 * mit Titel und Pfeil landet und nicht dazwischen.
 *
 * Die Vorlesereihenfolge bleibt die des Markups (Titel, Zeit) — der Pfeil ist
 * `aria-hidden`, seine Position im Bild geht einer Vorlesehilfe nichts an.
 */
@media (max-width: 39.999rem) {
    /*
     * `.verlauf__meta` hat `margin-left: auto` und `white-space: nowrap`. Auf
     * 393 px brechen ALLE Beispieltitel um, und die Zeit sass dann
     * rechtsbuendig an einer je nach Titellaenge anderen Stelle — die
     * Eintraege fluchteten nicht.
     *
     * Gemessen kostet die Umstellung am Handy nichts (72 statt 74 px), weil
     * der Umbruch ohnehin stattfindet. Am PC bleibt die einzeilige Form: dort
     * bricht der Titel selten, und nebeneinander ist sie dichter.
     */
    .verlauf__kopf { align-items: center; }

    /* min-width: 0 laesst einen langen Titel schrumpfen statt den Pfeil aus
       der Zeile zu schieben. */
    .verlauf__titel {
        order: 1;
        flex: 1 1 auto;
        min-width: 0;
    }

    .verlauf__kopf .aufklapp-pfeil {
        order: 2;
        margin-left: auto;
    }

    .verlauf__meta {
        order: 3;
        flex: 0 0 100%;
        margin-left: 0;
        white-space: normal;
    }
}

/*
 * "neu" als WORT und nicht nur als Farbe. Rund acht Prozent der Maenner
 * sehen Rot-Gruen schlecht, und in der Sonne sieht es ohnehin niemand — die
 * Regel gilt hier genauso wie im Plan.
 */
.verlauf__neu {
    display: inline-block;
    margin-left: 0.35rem;
    padding: 0 0.4rem;
    border-radius: var(--radius);
    background: var(--accent-quiet);
    color: var(--accent);
    font-size: 0.75rem;
}

.verlauf__inhalt {
    padding: 0 0.9rem 0.8rem;
    border-top: 1px solid var(--border);
}

.verlauf__liste {
    margin: 0.7rem 0 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.verlauf__zeile {
    display: flex;
    gap: 0.6rem;
    align-items: baseline;
}

/*
 * Umbrueche erhalten (#63).
 *
 * Ein Tageshinweis traegt die Gliederung der Schule mit: Ueberschriften,
 * Blockzeiten untereinander, Trennlinien aus Bindestrichen. HTML faltet
 * Umbrueche sonst zu Leerzeichen — und ausgerechnet die Blockzeiten, die
 * Angabe wegen der es die Tageshinweise gibt, werden dann zu einer Zeile.
 *
 * Dieselbe Regel wie bei .hinweise__text im Plan. Ein gewoehnlicher
 * Meldungssatz hat keine Umbrueche und merkt davon nichts.
 */
.verlauf__text {
    white-space: pre-wrap;
    overflow-wrap: break-word;
    min-width: 0;
}

.verlauf__std {
    /* min-width: 0 aus demselben Grund wie bei .s__std: ein Flex-Element
       waechst sonst ueber seine Basis hinaus, und die Texte daneben
       fluchten nicht mehr. Hier passt "12." zwar hinein, aber die Regel
       soll nicht davon abhaengen, dass niemand eine 13. Stunde hat. */
    flex: 0 0 1.6rem;
    min-width: 0;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.verlauf__weiter { margin: 0.8rem 0 0; font-size: 0.9rem; }

/* Die Zahl neben "Verlauf" in der Navigation. */
.nav__zahl {
    display: inline-block;
    min-width: 1.15rem;
    margin-left: 0.35rem;
    padding: 0 0.3rem;
    border-radius: var(--radius);
    background: var(--accent);
    color: #fff;
    font-size: 0.72rem;
    line-height: 1.4;
    text-align: center;
}

/*
 * Was vorher dort stand.
 *
 * Durchgestrichen und leiser als der neue Wert: die Aussage ist "das gilt
 * nicht mehr", nicht "das gilt auch". Ohne die Durchstreichung stuenden zwei
 * Lehrerkuerzel gleichberechtigt nebeneinander, und man muesste raten,
 * welches jetzt kommt.
 */
.s__alt {
    color: var(--text-muted);
    font-size: 0.8rem;
    opacity: 0.8;
    text-decoration: line-through;
    white-space: nowrap;
}

/*
 * Ueberschrift je Schule auf der Abo-Seite.
 *
 * Steht nur da, wenn jemand mehr als eine Schule hat — sonst unterschiede sie
 * nichts, und die Klasse steht ohnehin gross darunter.
 */
.abos__schule {
    margin: 2rem 0 0.75rem;
    font-size: 1rem;
    font-weight: 500;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Die erste braucht den Abstand nach oben nicht — darueber steht schon die
   Seitenueberschrift. */
.abos__schule:first-of-type { margin-top: 1rem; }

/* Eine Klasse, die man schon abonniert hat: sichtbar, aber erkennbar tot.
   Browser graufaerben ein disabled option unterschiedlich stark, manche gar
   nicht — deshalb steht der Grund als Wort dahinter und nicht nur als Farbe. */
option:disabled { color: var(--text-muted); }

/* --- Kursfilter (#35) ---------------------------------------------------- */

/*
 * Parallele Kurse ausblenden — im Plan als aufklappbares Panel, in den Abos
 * als Feldgruppe. Zwei Stellen, eine Liste: was hier ausgeht, geht auch aus
 * den Meldungen.
 *
 * <details> ohne JavaScript, wie beim Verlauf. Aufklappen, Haken setzen,
 * Speichern.
 */
.kursfilter {
    margin: 0 0 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

.kursfilter__auf {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 0.9rem;
    cursor: pointer;
    /* Wie beim Verlauf: der eingebaute Dreiecks-Marker sitzt in jedem
       Browser anders und bricht die Zeile um. */
    list-style: none;
    font-size: 0.9rem;
}

.kursfilter__auf::-webkit-details-marker { display: none; }
.kursfilter__auf:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/*
 * Wie viele Kurse gerade aus sind — als ZAHL im Kopf und nicht erst hinter
 * dem Aufklappen. Ein Plan, dem Stunden fehlen, muss das sagen, ohne dass
 * man danach sucht: sonst haelt man ihn fuer vollstaendig.
 */
.kursfilter__zahl {
    margin-left: auto;
    padding: 0 0.45rem;
    border-radius: var(--radius);
    background: var(--accent-quiet);
    color: var(--accent);
    font-size: 0.75rem;
    white-space: nowrap;
}

.kursfilter__form {
    padding: 0 0.9rem 0.9rem;
    border-top: 1px solid var(--border);
}

.kursfilter__form > p { margin: 0.7rem 0; }

.kursfeld {
    margin: 0;
    padding: 0.2rem 0.9rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.kursfeld > legend { padding: 0 0.4rem; font-size: 0.9rem; }
.kursfeld > p { margin: 0.6rem 0; }

/*
 * Der Preis der Entscheidung steht an der Entscheidung.
 *
 * Nicht als .note weiter oben und nicht in einer Hilfe: wer hier Haken
 * wegnimmt, schaltet Meldungen ab, die er sich selbst bestellt hat. Gelb
 * und nicht rot — es ist kein Fehler, sondern eine Warnung.
 */
.warnhinweis {
    padding: 0.55rem 0.7rem;
    border-left: 3px solid var(--warn);
    border-radius: 0 var(--radius) var(--radius) 0;
    background: var(--surface-hi);
    color: var(--text-muted);
    font-size: 0.85rem;
}

.warnhinweis strong { color: var(--text); }

/*
 * Die Kurse als Kacheln, nicht als Liste untereinander: es sind meistens
 * drei bis sieben kurze Kuerzel, und untereinander waeren das sieben fast
 * leere Zeilen.
 */
.kursliste {
    margin: 0.7rem 0 1rem;
    padding: 0;
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.kursliste__kurs {
    display: flex;
    /*
     * DIE RICHTUNG MUSS AUSDRUECKLICH DASTEHEN, sonst erbt sie die Kachel aus
     * der Umgebung — und dieselbe Teilansicht sieht an zwei Stellen
     * verschieden aus.
     *
     * In den Abos liegt die Liste in einem `form.stack`, und
     * `.stack label { flex-direction: column }` hat Spezifitaet (0,1,1) gegen
     * `.kursliste__kurs` mit (0,1,0). Die Kachel war dort gemessen 55 x 89 px
     * mit dem Kaestchen UEBER dem Kuerzel, am Plan 44 px waagerecht.
     *
     * Genau dagegen gibt es includes/kursliste.php als gemeinsame
     * Teilansicht: damit Plan und Abos nicht auseinanderlaufen. Ueber die
     * Kaskade taten sie es trotzdem.
     */
    flex-direction: row;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-hi);
    cursor: pointer;
    /* Fingergroesse: eine Kachel unter 44 px trifft man auf einem Handy
       nicht zuverlaessig. */
    min-height: 44px;
}

.kursliste__kurs:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }

/*
 * DIE GEHAKTE KACHEL TRAEGT IHREN ZUSTAND.
 *
 * Ab zwoelf Kursen (Kurse::LANG) liegt die Liste in einer eigenen
 * Bildlaufbahn ueber 40vh. Man sieht dann immer nur einen Ausschnitt, und
 * ohne Farbe muss man die Haken einzeln absuchen — in Jahrgang 11 sind es 95
 * Kacheln. Die Richtung sagt der Satz, der schon ueber der Liste steht:
 * "Ohne Haken: weg aus Plan und Meldungen."
 *
 * Dieselben zwei Werte wie `.tagchip--hier` im Wochenstreifen: Akzentrahmen
 * und `--accent-quiet`. Es ist keine neue Sprache, nur dieselbe an einer
 * zweiten Stelle.
 *
 * `:has()` ohne Rueckfall, und das ist vertretbar: fehlt die Unterstuetzung,
 * bleibt die Hervorhebung aus. Der Haken selbst steht weiterhin da, die
 * Auskunft geht also nicht verloren — sie ist nur muehsamer zu finden.
 *
 * NICHT UEBERNEHMEN FUER .kanalwahl. Dort waeren im Normalfall vier von fuenf
 * Zeilen hervorgehoben, und eine Hervorhebung, die der Regelfall ist, hebt
 * nichts hervor: sie macht aus der einen NICHT gehakten Zeile das
 * auffaelligste Element der Seite.
 */
.kursliste__kurs:has(input:checked) {
    border-color: var(--accent);
    background: var(--accent-quiet);
}

.kursliste__kurs:has(input:checked) .kursliste__fach { color: var(--accent); }

/* --- Kurslisten: Stapelknoepfe und lange Listen (#64) -------------------- */

/*
 * "Speichern" zuerst, die Stapelknoepfe dahinter.
 *
 * Nicht nur der Optik wegen: der ERSTE Absendeknopf eines Formulars ist der,
 * den die Eingabetaste ausloest. Staende "Alle ausblenden" vorn, blendete ein
 * Enter im Faecherfeld saemtliche Kurse aus.
 */
.kursfilter__knoepfe {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}

/*
 * "alle aus" ist ein Zustand, keine Einstellung — deshalb in der Warnfarbe
 * und nicht in der ruhigen Akzentfarbe wie eine Zahl.
 */
.kursfilter__zahl--alle {
    background: rgba(240, 180, 41, 0.15);
    color: var(--warn);
}

/*
 * Eine lange Liste bekommt eine eigene Bahn.
 *
 * Jahrgang 11 dieser Schule hat 95 Kurse. Als offene Kachelwand schoebe die
 * den Plan darunter vom Schirm — man klappt den Filter auf und findet den
 * Plan nicht wieder. Die Grenze liegt bei zwoelf, weil bis dahin alles ohne
 * Scrollen nebeneinander passt (Median der Klassen sind sechs).
 */
.kursliste--lang {
    max-height: 40vh;
    overflow-y: auto;
    /* Sonst klebt die letzte Reihe an der Kante und sieht aus, als waere die
       Liste zu Ende. */
    padding-bottom: 0.2rem;
    overscroll-behavior-y: contain;
}

/*
 * Das Fach neben dem Kurskuerzel. "5bRS" sagt niemandem etwas, "5bRS · RE"
 * schon — und ohne die Angabe muesste man erst im Plan nachsehen, welcher
 * Kurs gemeint ist, bevor man ihn ausblendet.
 */
.kursliste__fach {
    color: var(--text-muted);
    font-size: 0.85rem;
}

.kursliste__fach::before { content: "· "; }

/* --- Bereiche auf der Konto-Seite (#154) -------------------------------- */

/*
 * Je Konnektor ein Aufklapper. Derselbe Kasten wie jede andere Karte
 * (`.panelbox`), derselbe Kopf wie die Namen-Bloecke darin — nur eine Stufe
 * groesser, weil er eine Ueberschrift ersetzt.
 */
.bereich__auf {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    margin: -1.25rem;
    padding: 1.25rem;
    font-size: 1.15rem;
    font-weight: 600;
    cursor: pointer;
    /* Wie bei allen Aufklappern: der eingebaute Marker sitzt in jedem
       Browser anders. Der Pfeil rechts ersetzt ihn. */
    list-style: none;
}

.bereich__auf::-webkit-details-marker { display: none; }
.bereich__auf:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Offen braucht der Inhalt Abstand zum Kopf; zu ragt der Kopf ueber den
   ganzen Kasten (negativer Rand), damit die ganze Flaeche tippbar ist. */
.bereich[open] > .bereich__auf { margin-bottom: 0.25rem; }

.bereich h3 { margin-top: 1.25rem; }
/* Die erste Ueberschrift direkt unter dem Kopf: dort addierten sich der
   Innenabstand des Kopfes und ihr eigener Rand zu einer Luecke von fast drei
   Zeilen. */
.bereich > summary + h3 { margin-top: 0.25rem; }
.bereich__schule + .bereich__schule { margin-top: 1.5rem; }

/*
 * AUSGEGRAUT, ABER LESBAR. Die Ueberschrift nimmt die gedaempfte Textfarbe
 * statt Deckkraft: `opacity` daempfte auch den Knopf darunter, und der ist
 * der einzige Weg hinaus.
 */
.bereich--gesperrt { background: transparent; border-style: dashed; }
.bereich--gesperrt h3 { margin-top: 0; color: var(--text-muted); }

/* --- Klartextnamen (#51) ------------------------------------------------- */

.namen__block {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-hi);
}

.namen__auf {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.6rem 0.9rem;
    cursor: pointer;
    /* Wie bei Verlauf und Kursfilter: der eingebaute Dreiecks-Marker sitzt
       in jedem Browser anders und bricht die Zeile um. */
    list-style: none;
}

.namen__auf::-webkit-details-marker { display: none; }
.namen__auf:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* "4 von 16" — zugeklappt die einzige Auskunft darueber, ob hier noch
   Arbeit liegt. */
.namen__zahl {
    margin-left: auto;
    color: var(--text-muted);
    font-size: 0.85rem;
    white-space: nowrap;
}

/*
 * KEINE TABELLE, sondern eine Liste.
 *
 * Zwei Spalten (Kuerzel | Eingabe) liessen auf 393 px rund 200 px fuer das
 * Feld — genug fuer "Biologie", nicht fuer "Frau Hoffmann-Weber". Das
 * Kuerzel steht deshalb ueber dem Feld, und das Feld nimmt die ganze Breite.
 */
.namen__liste {
    margin: 0;
    padding: 0 0.9rem 0.9rem;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    /* Bis zu 67 Lehrer in einer Klasse: ohne eigene Bahn schoebe die Liste
       den Speichern-Knopf unerreichbar weit nach unten. */
    max-height: 60vh;
    overflow-y: auto;
    overscroll-behavior-y: contain;
}

.namen__zeile label { display: block; }

.namen__kuerzel {
    display: block;
    margin-bottom: 0.15rem;
    font-weight: 500;
    font-size: 0.9rem;
}

/* --- Plan-Tabelle mit ausgeschriebenen Namen ----------------------------- */

/*
 * DIE TABELLE ERSCHEINT AUCH AUF EINEM HANDY.
 *
 * Der Umbruchpunkt liegt bei 40 rem = 640 px; ein iPhone im QUERFORMAT hat
 * rund 850. Mit Kuerzeln passte alles nebeneinander, mit "Biologie" und
 * "Frau Hoffmann-Weber" nicht mehr — und `white-space: nowrap` auf allen
 * Zellen (Zeile 516) schoebe die Tabelle dann seitwaerts, statt umzubrechen.
 *
 * Deshalb duerfen Fach, Lehrer und Raum umbrechen. Die Stundenspalte NICHT:
 * "07:30–08:15" in zwei Zeilen zu brechen macht aus einer Angabe zwei
 * Raetsel, und ihre Breite steht seit #53 ohnehin fest.
 *
 * .tablewrap mit overflow-x bleibt als letztes Netz darunter.
 */
.plan td:not(.plan__std),
.plan th:not(:first-child) { white-space: normal; }

/*
 * Die Zeilenform braucht nichts: .s__zeile bricht seit jeher um, und genau
 * das war ihr Zweck ("hier darf eine lange Raumbezeichnung in die naechste
 * Zeile"). Ein ausgeschriebener Name verhaelt sich dort wie eine lange
 * Raumbezeichnung.
 */

/* --- Suche in einer langen Kursliste (#64) ------------------------------- */

/*
 * Sie steht nur bei langen Listen im Markup und ist dort `hidden`, bis das
 * Skript sie aufdeckt — ein Suchfeld, in dem ohne JavaScript nichts
 * passiert, waere schlimmer als keines.
 */
.kurssuche {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0.7rem 0 0;
}

.kurssuche__feld { flex: 1 1 auto; min-width: 0; }

.kurssuche__eingabe {
    width: 100%;
    padding: 0.45rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-hi);
    color: var(--text);
    font: inherit;
    font-size: 0.9rem;
}

.kurssuche__eingabe:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* "6 von 95" — nur waehrend einer Suche belegt, deshalb ohne feste Breite.
   nowrap, damit die Zahl nicht mitten im "von" umbricht. */
.kurssuche__zahl {
    flex: 0 0 auto;
    color: var(--text-muted);
    font-size: 0.85rem;
    white-space: nowrap;
}

/*
 * "Kein Treffer" MUSS dastehen. Eine Liste, die beim Tippen lautlos leer
 * wird, sieht aus wie "diese Klasse hat keine Kurse" — also wie eine
 * Auskunft statt wie ein Suchergebnis.
 */
.kurssuche__leer {
    margin: 0.7rem 0 1rem;
    color: var(--text-muted);
    font-size: 0.9rem;
}

/* --- Wochenkennung im Streifen (#63) ------------------------------------- */

/*
 * A oder B. Sie gilt der ganzen Woche, steht deshalb am Streifen und nicht
 * in der klebenden Tageszeile — dort staende sie bei jedem Tag neu, obwohl
 * sie sich nur montags aendert.
 *
 * Vor dem Zurueck-Pfeil und nicht dahinter: der Streifen liest sich von
 * links, und die Woche ist die Ueberschrift ueber seinen fuenf Kacheln.
 */
/* Die Wochenkennung steht seit dem Umbau als Pille neben dem Datum in der
   klebenden Zeile — siehe .wochenpille weiter oben. */

/* --- Erinnerung zum Schuljahreswechsel (#43) ----------------------------- */

/*
 * Der Balken bleibt, bis jemand ihn erledigt.
 *
 * Eine Push wischt man morgens im Halbschlaf weg. Die Frage "stimmt deine
 * Klasse noch?" ist aber genau eine, die man vertagt und dann vergisst — und
 * ein Abo auf die alte Klasse meldet danach ein Jahr lang ueber fremde
 * Kinder, ohne dass irgendetwas kaputt aussieht.
 *
 * Gelb und nicht rot: es ist kein Fehler, sondern eine Frage.
 */
.note--warn {
    border-left: 3px solid var(--warn);
    background: var(--surface-hi);
}

.note--warn strong { color: var(--text); }

/* Der Knopf steht in derselben Zeile wie der Text und nicht darunter: er
   gehoert zur Frage und ist ihre Antwort. */
.note--warn .inline { display: inline-block; margin-left: 0.35rem; }

/* --- Konto-Abschnitt der Einstellungen (#62) ----------------------------- */

/*
 * Name und E-Mail als reine Anzeige.
 *
 * <dl> und nicht zwei Absaetze: es sind Paare aus Bezeichnung und Wert, und
 * genau dafuer gibt es das Element. Ein Screenreader liest sie dann als
 * Paare vor statt als vier zusammenhanglose Zeilen.
 */
.konto {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.2rem 0.9rem;
    margin: 0 0 1rem;
    font-size: 0.9rem;
}

.konto dt { color: var(--text-muted); }
.konto dd { margin: 0; overflow-wrap: anywhere; }

/*
 * Die Wege zum zentralen Login.
 *
 * Als Liste und nicht als Absatz mit Trennpunkten: es sind vier
 * gleichrangige Ziele, und eine Punktkette liest sich ab drei Gliedern wie
 * eine Aufzaehlung — dann kann sie auch eine sein. Dieselbe Ueberlegung wie
 * bei der Stand-Zeile, nur andersherum entschieden.
 */
.kontolinks {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}

.kontolinks a {
    display: block;
    /* Fingergroesse: vier Links untereinander mit 1,4 Zeilen Abstand trifft
       man auf einem Handy nicht zuverlaessig. */
    padding: 0.5rem 0;
    min-height: 44px;
}

/* --- Übergang beim Seitenwechsel (#45) ----------------------------------- */

/*
 * DIE ERSTE BEWEGUNG IM GANZEN STYLESHEET, und sie ist mit Bedacht diese.
 *
 * Bis hierher gab es keine einzige `transition` und keine Keyframes — der
 * Design-Handoff bittet ausdruecklich darum, und der Grund traegt: die Seite
 * wird serverseitig gerendert und laedt bei jedem Wechsel neu. Es gibt gar
 * keinen Zustand, zwischen dem man animieren koennte.
 *
 * @view-transition ist der eine Weg, der das nicht aendert. Der Browser
 * fotografiert die alte Seite, holt die neue und blendet dazwischen — ohne
 * JavaScript, ohne Client-State, ohne dass diese App etwas anderes wuerde,
 * als sie ist. Wo ein Browser die Regel nicht kennt, ignoriert er sie
 * folgenlos; es gibt kein Fallback zu bauen.
 *
 * Und sie VERZOEGERT NICHTS: die Ueberblendung liegt ueber dem Ladevorgang,
 * sie verlaengert ihn nicht. Genau daran waere eine Einblendung per
 * Keyframes gescheitert — die kostet ihre Dauer bei jedem Aufruf, auch bei
 * dem, mit dem man morgens nur kurz nachsieht, ob die erste Stunde ausfaellt.
 *
 * Unterstuetzt: Chrome und Edge ab 126, Safari ab 18.2 (macOS und iOS),
 * Firefox bisher nicht. Am 05.09.2026 nachgesehen.
 */
@view-transition {
    navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 250ms;
}

/*
 * Der Wochenstreifen macht sie NICHT mit.
 *
 * Er ist eine echte Scrollbahn, die plan.js nach dem Laden an die richtige
 * Stelle rueckt und deren Nachbarwochen erst dort aufgedeckt werden. Eine
 * Ueberblendung zweier Zustaende, von denen einer noch nicht fertig
 * eingerichtet ist, kann sichtbar springen — und ein springender Streifen
 * ueber dem Plan ist genau das Gegenteil dessen, was diese Bewegung
 * erreichen soll.
 *
 * Seit #105 ist der Streifen selbst keine Scrollbahn mehr, aber die Ausnahme
 * bleibt: die Bahn liegt jetzt eine Ebene tiefer (.plantage), und `plan.js`
 * rueckt SIE nach dem Laden zurecht. Eine Ueberblendung zweier Zustaende,
 * von denen einer noch nicht fertig ist, springt genauso.
 *
 * Der eigene Name hebt ihn aus dem Wurzel-Schnappschuss heraus; die drei
 * Regeln darunter sind das uebliche Rezept fuer "dieses Element wechselt
 * hart": die alte Aufnahme wird nicht gezeigt, die neue nicht animiert.
 */
.tagstreifen__woche {
    view-transition-name: wochenband;
}

/* Dieselbe Ausnahme fuer die Tages-Wischbahn: plan.js springt nach dem Laden
   an die richtige Stelle, und bis dahin zeigt sie den Montag. */
.plantage {
    view-transition-name: plantage;
}

::view-transition-group(plantage) { animation: none; }
::view-transition-old(plantage)   { display: none; }
::view-transition-new(plantage)   { animation: none; }

::view-transition-group(wochenband) { animation: none; }
::view-transition-old(wochenband)   { display: none; }
::view-transition-new(wochenband)   { animation: none; }

/*
 * NICHT VERHANDELBAR: wer Bewegung abbestellt hat, bekommt keine.
 *
 * Fuer manche Menschen ist eine Ueberblendung nicht Zierrat, sondern loest
 * Schwindel oder Uebelkeit aus. Das Betriebssystem fragt danach, der Browser
 * gibt es weiter — es zu ignorieren waere eine Entscheidung gegen den
 * Nutzer, und zwar eine, die er selbst schon getroffen hat.
 *
 * animation: none und nicht bloss eine kurze Dauer: "fast keine Bewegung"
 * ist immer noch Bewegung.
 */
@media (prefers-reduced-motion: reduce) {
    ::view-transition-old(root),
    ::view-transition-new(root) {
        animation: none;
    }
}

/* --- Meldeweg (#19) ------------------------------------------------------ */

/*
 * Push, E-Mail oder beides.
 *
 * Als Auswahl mit Erklärung je Zeile und nicht als drei nackte Wörter: die
 * drei Wege unterscheiden sich in dem, was sie NICHT können — Push kommt auf
 * dem iPhone nur in der installierten App an, E-Mail nicht sofort. Wer das
 * nicht dazuschreibt, lässt raten.
 */
.kanalliste {
    margin: 0 0 0.5rem;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.kanalwahl {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.7rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-hi);
    cursor: pointer;
}

.kanalwahl:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Der Radioknopf sitzt sonst auf der Grundlinie der ersten Zeile und wirkt
   dadurch zu hoch, wenn die Erklärung zweizeilig wird. */
.kanalwahl input { margin-top: 0.2rem; flex: 0 0 auto; }

.kanalwahl > span { display: flex; flex-direction: column; gap: 0.1rem; }
.kanalwahl .muted { font-size: 0.85rem; }

/* --- Die Probe (#82) ----------------------------------------------------- */

/*
 * Ein eigenes Formular unter dem Speichern-Knopf, abgesetzt durch eine Linie:
 * die beiden Knoepfe speichern nichts, und ohne die Trennung saehen sie aus
 * wie zwei weitere Absendeknoepfe desselben Formulars.
 */
.probe {
    margin-top: 1.2rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
}

.probe .muted { margin: 0 0 0.7rem; font-size: 0.85rem; }

/*
 * Nebeneinander, solange Platz ist — und umbrechend, sobald nicht. Auf 393 px
 * stehen die beiden Beschriftungen zusammen bei rund 340 px, das wird knapp,
 * sobald die Schriftgroesse hochgedreht ist.
 */
.probe__knoepfe {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

/* --- Die Feedback-Bubble (#99) ------------------------------------------- */

/*
 * Der schwebende Knopf. Uebernommen aus thaessler.tipparena, dort .fb-fab.
 *
 * OHNE BESCHRIFTUNG, und das ist die eine Ausnahme von "ein Zeichen ersetzt
 * nie ein Wort": er steht auf JEDER Seite. Ein Wort daneben waere ein
 * dauerhafter Aufkleber ueber dem Plan. Die Aussage traegt das aria-label,
 * und im geoeffneten Panel steht alles ausgeschrieben.
 *
 * env(safe-area-inset-bottom) MUSS MIT: ohne die Zeile klebt der Knopf in der
 * installierten App auf dem Home-Indikator — genau dort, wo iOS die
 * Wischgeste abfaengt. Der Knopf waere dann auf einem iPhone nicht drueckbar,
 * und zwar ohne jede Fehlermeldung.
 */
.fb-knopf {
    position: fixed;
    right: 1rem;
    bottom: calc(1rem + env(safe-area-inset-bottom));
    z-index: 20;

    display: grid;
    place-items: center;
    width: 3rem;
    height: 3rem;
    border: 1px solid var(--border);
    border-radius: 50%;
    background: var(--surface-hi);
    color: var(--text);

    /* Ohne Schatten verschwindet ein runder Knopf auf einer Karte gleicher
       Farbe. Das ist keine Bewegung, sondern eine Kante. */
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}

.fb-knopf:hover,
.fb-knopf:focus-visible { border-color: var(--accent); color: var(--accent); }
.fb-knopf:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.fb-schleier {
    position: fixed;
    inset: 0;
    z-index: 21;
    background: rgba(0, 0, 0, 0.6);
}

/*
 * Das Panel. Auf dem Handy ein Blatt von unten ueber die ganze Breite, am
 * breiten Schirm eine Spalte rechts.
 *
 * `max-height` UND `min-height: 0` am Verlauf: ohne beides waechst die Liste
 * der Blasen aus dem Bild heraus, statt zu scrollen — der Eingabeteil rutscht
 * dann unter den Rand, und man kann nicht mehr antworten.
 */
.fb-panel {
    position: fixed;
    z-index: 22;
    right: 0;
    bottom: 0;
    left: 0;
    max-height: min(32rem, 88vh);

    display: flex;
    flex-direction: column;

    border-top: 1px solid var(--border);
    border-radius: var(--radius) var(--radius) 0 0;
    background: var(--surface);
    padding-bottom: env(safe-area-inset-bottom);
}

@media (min-width: 40rem) {
    .fb-panel {
        left: auto;
        right: 1rem;
        bottom: 1rem;
        width: 24rem;
        border: 1px solid var(--border);
        border-radius: var(--radius);
    }
}

.fb-panel__kopf {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.9rem 1rem;
    border-bottom: 1px solid var(--border);
}

.fb-panel__marke { display: flex; color: var(--accent); }

/* flex: 1 schiebt das Kreuz an den rechten Rand — ohne absolute Position.
   Ein `position: absolute` in einer Kopfzeile hat in WebKit schon einmal den
   falschen Bezugsrahmen gefunden; steht in CLAUDE.md. */
.fb-panel__text { flex: 1; display: flex; flex-direction: column; gap: 0.1rem; }
.fb-panel__text .muted { font-size: 0.85rem; }

.fb-panel__zu {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 1.9rem;
    height: 1.9rem;
    border: 0;
    border-radius: var(--radius);
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
}

.fb-panel__zu:hover,
.fb-panel__zu:focus-visible { color: var(--text); }

.fb-verlauf {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 1rem;
}

/*
 * Die Blasen. `max-width: 85%` statt einer festen Breite: eine einzeilige
 * Antwort soll kurz aussehen, ein Fehlerbericht darf lang werden.
 *
 * pre-wrap, weil ein Fehlerbericht Zeilenumbrueche traegt — dieselbe
 * Ueberlegung wie bei den Tageshinweisen (#63).
 */
.fb-blase {
    max-width: 85%;
    padding: 0.5rem 0.75rem;
    border-radius: var(--radius);
    font-size: 0.92rem;
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.fb-blase--app {
    align-self: flex-start;
    background: var(--surface-hi);
    border-bottom-left-radius: 3px;
}

.fb-blase--ich {
    align-self: flex-end;
    background: var(--accent-quiet);
    border: 1px solid var(--border);
    border-bottom-right-radius: 3px;
}

.fb-eingabe {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.9rem 1rem;
    border-top: 1px solid var(--border);
}

/* Die Auswahlknoepfe im Panel. Linksbuendig und zweizeilig — sie tragen eine
   Erklaerung, und eine zentrierte Erklaerung liest niemand. */
.fb-wahl {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    width: 100%;
    padding: 0.6rem 0.8rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-hi);
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

/*
 * EIN RING, NICHT ZWEI.
 *
 * Hier stand `border-color` unter :hover UND :focus-visible, dazu die
 * Outline. Der fokussierte Knopf trug damit beides: 1px Rahmen in
 * Akzentfarbe und 2px Outline mit 2px Abstand — zwei Ringe umeinander.
 * Sichtbar wurde es bei "welche Seite", weil feedback.js dort nach dem Klick
 * den ersten Knopf fokussiert. Am 20.09.2026 gemeldet.
 *
 * Der Rahmen bleibt dem Hover, die Outline dem Fokus. Dieselbe Trennung hat
 * .kanalwahl von Anfang an.
 */
.fb-wahl:hover { border-color: var(--accent); }
.fb-wahl:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.fb-wahl .muted { font-size: 0.85rem; }

.fb-feld {
    width: 100%;
    padding: 0.5rem 0.65rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg);
    color: var(--text);
    font: inherit;
    resize: vertical;
}

.fb-feld:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/*
 * Die Tastenzeile. `flex-wrap` ist Pflicht, kein Feinschliff: bei 393 px und
 * hochgedrehter Schriftgroesse stehen "Ueberspringen" und "Weiter" zusammen
 * breiter als das Panel, und ohne Umbruch wird der rechte aus dem Rahmen
 * geschoben — am breiten Schirm nie zu sehen. Derselbe Fall wie im Kopf des
 * Abo-Filters.
 */
.fb-tasten {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem;
}

/*
 * DER HAUPTKNOPF UEBER DIE GANZE ZEILE, wie ueberall sonst in der App.
 *
 * Er war nicht zu klein, er war als einziger nicht breit: `.stack` ist eine
 * Flex-Spalte mit `align-items: stretch`, jeder Absendeknopf einer Seite wird
 * dadurch ueber die volle Spalte gezogen. Im Panel lag er als schmaler Chip
 * rechts aussen — derselbe Knopf, zwei Groessen, je nachdem wo man ihn
 * antrifft. Am 20.09.2026 gemeldet.
 *
 * `order` haelt ihn oben, obwohl das Skript den leisen Knopf zuerst einhaengt
 * (dort steht "Ueberspringen" links von "Weiter", wie in einer Zeile ueblich).
 * Umgestellt wird also die Anzeige, nicht die Tabulator-Reihenfolge: wer sich
 * durchtabbt, erreicht weiter erst das Ausweichen und dann den Hauptweg.
 */
.fb-tasten .btn {
    flex: 1 1 100%;
    order: 0;
    text-align: center;
}

.fb-tasten .btn--leise {
    flex: 0 1 auto;
    order: 1;
}

/* --- Die Einrichtung in vier Schritten ----------------------------------- */

/*
 * Sie steht auf der Uebersicht ANSTELLE der Zahlenkacheln, solange etwas
 * fehlt — drei Kacheln mit Nullen darin sind keine Auskunft, sondern eine
 * Verzierung. Und sie verschwindet, sobald alle vier stehen: sie ist
 * Einrichtung und kein Dauermoebel.
 */
.einrichtung {
    margin: 0 0 1.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

.einrichtung__kopf {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.9rem 1rem 0;
}

.einrichtung__kopf h2 { margin: 0; font-size: 1.05rem; }

/* Die Zaehlung sagt, dass es ein Ende gibt. Sie kann nicht schrumpfen —
   deshalb `gap` und `baseline` statt eines absoluten Rechtsrands, und
   deshalb ist der Kopf eine Flex-Zeile mit Luft. */
.einrichtung__zahl {
    flex: 0 0 auto;
    color: var(--text-muted);
    font-size: 0.85rem;
    white-space: nowrap;
}

.einrichtung__liste {
    margin: 0;
    padding: 0.6rem 0 0;
    list-style: none;
    counter-reset: none;
}

/*
 * Ein Raster und keine Flex-Zeile: die drei Teile (Zustand, Text, Weg) sollen
 * ueber alle vier Schritte UNTEREINANDER stehen. In einer Flex-Zeile
 * bestimmte jeder Schritt seine Spaltenbreite selbst, und die Haken saessen
 * treppenfoermig.
 */
.einrichtung__schritt {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: start;
    gap: 0.25rem 0.7rem;
    padding: 0.7rem 1rem;
    border-top: 1px solid var(--border);
}

/*
 * Bei 393 px hat der Text keine 12rem mehr, wenn der Weg daneben liegt.
 * Er bricht deshalb unter den Text, nicht neben ihn — `grid-column` setzt
 * ihn in die Textspalte, damit er nicht unter dem Zeichen klebt.
 */
@media (max-width: 30rem) {
    .einrichtung__schritt { grid-template-columns: auto 1fr; }
    .einrichtung__weg { grid-column: 2; justify-self: start; }
}

/* Die Grundlinie des Zeichens gegen die der Ueberschrift — `align-items:
   start` setzt sonst beide Kastenkanten buendig, und bei verschiedenen
   Zeilenhoehen sitzt das Zeichen dann sichtbar zu hoch. */
.einrichtung__lage {
    display: flex;
    padding-top: 0.15rem;
    color: var(--text-muted);
}

.einrichtung__schritt--erledigt .einrichtung__lage { color: var(--ok); }
.einrichtung__schritt--ruht .einrichtung__lage { color: var(--warn); }
.einrichtung__schritt--offen .einrichtung__lage { color: var(--accent); }

.einrichtung__text {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}

.einrichtung__text .muted { font-size: 0.88rem; }

/* Ein erledigter Schritt tritt zurueck, ohne zu verschwinden — man soll sehen
   koennen, was man schon hat, ohne dass es um Aufmerksamkeit mit dem
   konkurriert, was noch fehlt. Kein `opacity`: geschachtelte Deckkraft
   multipliziert sich und druecke den Text unter die Kontrastgrenze. */
.einrichtung__schritt--erledigt .einrichtung__text strong {
    color: var(--text-muted);
    font-weight: 500;
}

.einrichtung__weg {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    /* 44px-Tippziel, ohne die Zeile hoeher zu machen als noetig. */
    min-height: 2.75rem;
    margin: -0.6rem 0;
    padding: 0 0.2rem;
    color: var(--accent);
    font-size: 0.9rem;
    white-space: nowrap;
    text-decoration: none;
}

.einrichtung__weg:hover { text-decoration: underline; }
.einrichtung__weg:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- Der Aufklapper fuer eine Begruendung (#Textdiaet) ------------------- */

/*
 * WOFUER. Zwei Texte sind richtig und wichtig, beantworten aber eine Frage,
 * die man an der Stelle noch nicht hat: das Bedrohungsmodell der
 * Passwort-Verschluesselung und die Geraetegrenze gelesener Push-Meldungen.
 * Beide standen als voller Absatz da, zusammen 82 Woerter.
 *
 * GELOESCHT WIRD NICHTS. Wer sein Schul-Login hier eintraegt, hat ein Recht
 * darauf, die Grenze zu erfahren — sie steht jetzt einen Klick entfernt statt
 * mitten im Weg.
 *
 * <details> und kein eigener Aufklapper: ohne JavaScript bedienbar, mit der
 * Tastatur erreichbar, und ein Screenreader sagt ihn als auf-/zuklappbar an.
 * Die CSP verbietet hier ohnehin jedes Inline-Skript. Dieselbe Wahl wie beim
 * Verlaufseintrag und beim Kursfilter.
 */
.begruendung {
    margin: 0.6rem 0 0;
    font-size: 0.9rem;
}

.begruendung > summary {
    cursor: pointer;
    color: var(--accent);
    /* 44px-Tippziel, ohne die Zeile optisch aufzublasen. */
    padding: 0.5rem 0;
}

.begruendung > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Der Absatz darunter tritt zurueck — er ist die Begruendung, nicht die
   Aussage. */
.begruendung > p {
    margin: 0 0 0.3rem;
    color: var(--text-muted);
}

/* --- Meldungen und ihre Antwort ------------------------------------------- */

/*
 * KARTEN STATT TABELLENZEILEN, seit es eine Antwort gibt (22.09.2026).
 *
 * In eine Tabellenzeile passt kein Antwortfeld: ein <textarea> in einer
 * Zelle sprengt die Spaltenbreiten, und der Hinweis darunter braeuchte
 * wieder ein colspan — genau die Falle, die im Kopf von PlanSeiteTest als
 * Grund steht. Dieselben Karten tragen unter "Melden" die eigenen
 * Meldungen.
 */
.meldungen {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-bottom: 0.8rem;
}

.meldung {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.7rem 0.8rem;
}

/* Die Kopfzeile traegt dieselben Angaben wie die alten Spalten. `wrap`, weil
   sie auf 393 px sonst den Rahmen sprengt — dieselbe Lehre wie beim Kopf des
   Abo-Filters. */
.meldung__kopf {
    display: flex;
    flex-wrap: wrap;
    gap: 0 0.35rem;
    margin: 0 0 0.4rem;
    font-size: 0.9rem;
}

/* Der gemeldete Text traegt die Umbrueche des Melders — er hat sie gesetzt,
   und aus einer Aufzaehlung wird sonst eine Zeile. Dieselbe Regel wie bei
   den Tageshinweisen. */
.meldung__text,
.meldung__antwort > p:last-child {
    margin: 0;
    white-space: pre-wrap;
}

.meldung__wunsch { margin: 0.3rem 0 0; }

/* Die Antwort abgesetzt: sie ist nicht Teil der Meldung, sondern die
   Reaktion darauf. Ein Balken links statt eines zweiten Rahmens — der
   sonst wie eine eigene Karte in der Karte aussieht. */
.meldung__antwort {
    margin-top: 0.6rem;
    padding-left: 0.7rem;
    border-left: 3px solid var(--accent);
}

.meldung__antwort > p:first-child { margin: 0 0 0.2rem; font-size: 0.85rem; }

.meldung__formular { margin-top: 0.5rem; }

.meldung__formular > summary {
    cursor: pointer;
    color: var(--accent);
    /* 44px-Tippziel, ohne die Zeile optisch aufzublasen. */
    padding: 0.5rem 0;
}

.meldung__formular > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.meldung__formular textarea { width: 100%; }

/* --- Als App hinzufuegen --------------------------------------------------- */

/*
 * Die Anleitung steht an zwei Stellen: als Aufklapper neben dem
 * Push-Schalter und als eigene Seite /installieren. Dasselbe Markup, deshalb
 * dieselben Regeln — die Teilansicht bringt nur `.installieren` mit, der
 * Kasten drumherum kommt von der einbindenden Stelle.
 */
.installieren > p { margin: 0 0 0.8rem; }

/* Drei Wege untereinander, jeder ein Aufklapper. Der Rahmen macht sichtbar,
   dass es drei GLEICHRANGIGE sind und nicht eine Abfolge — eine Liste ohne
   Trennung las sich wie "erst iPhone, dann Android". */
.installieren__weg {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    margin-bottom: 0.5rem;
}

.installieren__weg > summary {
    cursor: pointer;
    /* 44px-Tippziel: die Zeile ist der einzige Weg in die Anleitung, und auf
       einem Telefon wird sie mit dem Daumen getroffen. */
    padding: 0.7rem 0.8rem;
    font-weight: 500;
}

.installieren__weg > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.installieren__weg > ol {
    margin: 0;
    padding: 0 0.8rem 0.3rem 2rem;
}

.installieren__weg > ol > li { margin-bottom: 0.35rem; }

/* Der Satz unter den Schritten ist die Fussnote dazu, nicht der naechste
   Schritt — deshalb tritt er zurueck und steht ausserhalb der Nummerierung. */
.installieren__weg > p {
    margin: 0.2rem 0 0;
    padding: 0 0.8rem 0.7rem;
    font-size: 0.9rem;
    color: var(--text-muted);
}

/*
 * Der Kasten auf der Uebersicht. Er traegt dieselbe Teilansicht, ist dort
 * aber selbst zugeklappt: wer laengst installiert hat, soll keine Anleitung
 * vor sich haben, die er nie wieder braucht.
 */
.installieren__box {
    margin-top: 0.8rem;
}

.installieren__box > summary {
    cursor: pointer;
    color: var(--accent);
    padding: 0.5rem 0;
}

.installieren__box > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- Die Woche als Wischbahn (#105) -------------------------------------- */

/*
 * Fuenf Tagesseiten nebeneinander, je eine Bildbreite. Wischen blaettert
 * tageweise und ohne Seitenladen; ueber den Rand hinaus ist zugleich
 * Wochengrenze, und dort laedt die Seite wirklich neu.
 *
 * EINE ECHTE SCROLLBAHN UND KEINE NACHGEBAUTE GESTE. Hier stand einmal eine
 * von Hand gebaute Touch-Geste ueber dem ganzen Plan; die liess sich nie
 * sauber vom schraegen Scrollen trennen, und der Bildschirm fuehlte sich an,
 * als haenge er am Finger. Der Browser kann genau das: er sperrt die Achse,
 * sobald er die Richtung erkannt hat, und rastet mit der Kurve des Systems
 * ein. Dasselbe Prinzip trug schon das Wochenwischen.
 *
 * Die Bahn zieht sich per negativem Rand ueber den Innenabstand von .wrap
 * hinaus bis an die Kanten — eine Seite muss die GANZE Breite haben, sonst
 * sieht man beim Wischen von zwei Tagen je die Haelfte.
 */
.plantage {
    display: flex;
    overflow-x: auto;

    scroll-snap-type: x mandatory;

    /*
     * PFLICHT, KEIN FEINSCHLIFF: ohne das loest ein Wisch am Rand der Bahn
     * auf iOS die Zurueck-Geste des Browsers aus, und man landet auf der
     * vorigen SEITE statt am Nachbartag.
     */
    overscroll-behavior-x: contain;

    /*
     * Die Ausnahme zu `touch-action: pan-y` auf .wrap. Dort steht es, damit
     * nicht der ganze Bildschirm am Finger haengt, sobald irgendwo etwas
     * einen Pixel zu breit ist — hier DARF waagerecht geschoben werden.
     * Dieselbe Ausnahme hat die Navigation.
     */
    touch-action: pan-x pan-y;

    margin: 0 -1.25rem;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.plantage::-webkit-scrollbar { display: none; }

/*
 * flex-basis: 100% und nicht width — die Seite muss exakt so breit sein wie
 * der Inhaltsbereich der Bahn, sonst rastet sie zwischen zwei Tagen ein.
 *
 * scroll-snap-stop: always haelt jeden Tag an. Ohne das fliegt ein schneller
 * Wisch ueber drei Tage hinweg, und man weiss nicht mehr, wo man ist.
 */
.plantage__seite {
    /*
     * BEZUGSRAHMEN FUER DIE EIGENEN KINDER.
     *
     * `.visuell-versteckt` ist `position: absolute` ohne Offsets — es bleibt
     * an seinem Platz im Fluss, ist aber aus dem Fluss heraus. Ohne einen
     * positionierten Vorfahren sucht es sich das Wurzelelement, und dann
     * klammert die Bahn es NICHT mit ein: seine statische Stelle liegt bei
     * der zweiten Tagesseite jenseits der Bildbreite, und die Seite liess
     * sich waagerecht schieben. Gemessen 123 px zu breit, verursacht von zwei
     * Spans mit dem Wort "Hinweis:", die niemand sieht.
     *
     * Mit `relative` gehoert das Kind wieder seiner Seite, und die Bahn
     * scrollt es mit.
     */
    position: relative;

    flex: 0 0 100%;
    min-width: 0;
    padding: 0 1.25rem;
    scroll-snap-align: start;
    scroll-snap-stop: always;
}

/*
 * DIE DATUMSZEILE WANDERT MIT — UND KLEBT DAFUER NICHT MEHR.
 *
 * Das ist ein bewusster Tausch, und er hat eine Messung gekostet. Sie liegt
 * IN der Tagesseite, damit sie beim Wischen mit dem Finger geht, ohne dass
 * irgendetwas synchronisiert werden muesste.
 *
 * Der Preis: `overflow-x: auto` macht die Bahn zum Scrollport in BEIDEN
 * Achsen — die andere faellt von `visible` automatisch auf `auto`. Ein
 * `position: sticky` darin klebt deshalb an der Bahn und nicht am Fenster,
 * und die Bahn scrollt vertikal mit. Gemessen am 20.09.2026 mit Playwright:
 * 108 px Luecke am Schreibtisch, 42 px am Handy. `overflow-y: clip` half
 * nicht; mit `overflow-x: visible` war die Luecke 0 — der Scrollport ist
 * also nachweislich die Ursache.
 *
 * `position: static` steht hier deshalb AUSDRUECKLICH da. Ohne die Zeile
 * bliebe ein `sticky` stehen, das nie klebt: es sieht im Stylesheet nach
 * einer Zusage aus, die der Browser nicht einloest, und der naechste Leser
 * suchte den Fehler woanders.
 *
 * Wer das Kleben zurueckholen will, muss die Zeile AUS der Bahn nehmen und
 * ihren Text beim Einrasten tauschen. Am 20.09.2026 gegen das Mitwandern
 * abgewogen und so entschieden.
 */
.plantage__seite > .tagsticky {
    position: static;

    /*
     * MARGIN-TOP AUF 0, und das ist die zweite Hälfte derselben Geschichte.
     *
     * `.tagsticky` traegt `margin: -1.5rem …` — ein negativer oberer Rand aus
     * der Zeit, als die Zeile direkt unter dem Kopf sass und dort anschliessen
     * sollte. In der Bahn ist dieser Rand aber nicht mehr harmlos: `.plantage`
     * ist wegen `overflow-x: auto` ein Scrollport in BEIDEN Achsen (siehe
     * oben), und was oben herausragt, wird nicht ueberlappt, sondern
     * ABGESCHNITTEN.
     *
     * Am 23.09.2026 am Geraet gemeldet ("wieso ist noch eine Leiste hinter
     * dem Teil mit Filter?") und nachgemessen: die Zeile begann 24 px ueber
     * der Bahn, sichtbar blieben rund 15 der 39 px. Uebrig war ein
     * Streifen, der wie ein zweites, kaputtes Bedienelement aussah.
     *
     * Der seitliche negative Rand bleibt — er zieht die Zeile bis an die
     * Kanten, und auf der waagerechten Achse ist das gewollt.
     */
    margin-top: 0;
    margin-left: -1.25rem;
    margin-right: -1.25rem;
}

/* --- Die Rueckmeldung des Selbstspeichers (#102) -------------------------- */

/*
 * Sie steht dort, wo vorher der Knopf stand — das Skript blendet ihn aus und
 * setzt sie davor. Damit springt beim Laden nichts: der Platz war schon
 * belegt.
 *
 * `aria-live="polite"` sagt sie an, ohne zu unterbrechen. Ohne das erfuehre
 * jemand, der die Seite vorlesen laesst, ueberhaupt nicht, dass gespeichert
 * wurde — bei einem Knopf war das Druecken die Bestaetigung, hier gibt es
 * keine.
 */
/*
 * Sie haengt sich an das Kaestchen oder Feld, an dem gerade gearbeitet wird.
 *
 * KEIN `nowrap`: das stand hier und schob eine laengere Fehlermeldung aus der
 * Karte heraus — am 20.09.2026 am Geraet gemeldet. Eine Meldung, die nicht
 * umbrechen darf, ist auf 393 px eine Meldung, die man nicht lesen kann.
 */
.autosave {
    display: inline-block;
    margin-left: 0.5rem;
    font-size: 0.85rem;
    overflow-wrap: anywhere;
}

/* Ein Fehlschlag darf nicht wie ein Erfolg aussehen: er faerbt sich UND holt
   den Knopf zurueck (siehe assets/js/autosave.js). */
.autosave.ok  { color: var(--ok); }
.autosave.bad { color: var(--bad); }

/* =========================================================================
   Verträge (#154)
   ========================================================================= */

/*
 * DIE ZEILE MIT DEN TATEN DARF UMBRECHEN.
 *
 * In ihr stehen bis zu vier Knöpfe nebeneinander, und „Doch nicht gekündigt"
 * kann nicht schrumpfen. Ohne `flex-wrap` schiebt ein solcher Knopf bei
 * 393 px seine Nachbarn aus dem Rahmen — am breiten Schirm nie zu sehen, weil
 * dort Platz genug ist. Genau so ist es beim Kopf des Abo-Filters passiert.
 */
.vertrag__taten {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 1rem;
    margin-top: 0.8rem;
}

/* Zeichen und Name auf einer Grundlinie — nicht an der Kastenkante
   ausgerichtet, sonst sitzt das Symbol je nach Zeilenhöhe schief. */
.vertrag__kopf {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0 0 0.3rem;
}

.vertrag__kopf .ico { align-self: center; }

.vertrag__frist { margin: 0 0 0.3rem; }

.vertrag__notiz {
    /* Eine Notiz trägt die Gliederung mit, die jemand hineingetippt hat —
       dieselbe Überlegung wie bei den Tageshinweisen. */
    white-space: pre-wrap;
    margin: 0.5rem 0 0;
}

.vertrag__nummern { margin-top: 0.5rem; }

/*
 * Zahl und Einheit, Betrag und Intervall: zwei Felder, die zusammengehören.
 * Das erste nimmt den Platz, das zweite bleibt so breit wie sein Inhalt.
 */
.feldpaar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.feldpaar > input { flex: 1 1 8rem; min-width: 0; }
.feldpaar > select { flex: 0 1 auto; }

.kategorie { margin-bottom: 1rem; }
