/* ============================================================
   MDB News — "retro future" Terminal-Theme (Blade Runner / Fallout).
   Near-black + Graustufen, BERNSTEIN nur als Akzent. IBM Plex Mono ueberall.
   FARBWELT in :root — hier einmal aendern = alles umfaerben.
   ============================================================ */

:root {
    /* DAS KACHELRASTER HAENGT AN DREI ZAHLEN, und sie stehen hier, weil sie sonst
       ueber Zeilenhoehe, Bildhoehe, Stapelhoehe und cards.php verstreut waeren.
       Roger, 03.08.2026, im Wechsel: erst "immer ein band hoeher alles", dann "wir
       machen die baender schmaler", dann "dann muessten wir die baender kleiner
       machen und auf 4 erhoehen und 2 dem bild geben, mal testen". Genau dafuer sind
       es Variablen: Probieren kostet eine Zahl, keinen Umbau.
         --news-band          Hoehe EINES Bandes
         --news-baender       Baender je Kachel  = die Zeilenhoehe
         --news-baender-bild  davon fuers Bild, der Rest ist Text
       n Baender sind n*band + (n-1)*16, weil zwischen zwei Baendern eine Luecke
       liegt - dieselbe Rechnung wie im Dashboard (--mpx).
       DER STAND: 113 / 4 / 2. Die Zeile bleibt damit bei rund 500px, das Bild wird
       242px statt 156 (auf einer 277px breiten Kachel also fast quadratisch statt
       16:9) und dem Text bleiben 242px, das sind etwa vier bis fuenf Anrisszeilen.
       WAS DIE ZAHLEN GEGENEINANDER TUN, gerechnet fuer die Standardkachel (277px
       breit bei 5 Spalten auf der 1450px-Buehne):
         Bild 16:9 waere 156px  -> mit 2 Baendern also band = 70, Zeile dann 328
         9 Anrisszeilen brauchen 319px Text -> zusammen mit 16:9 rund 500px Zeile
       Mehr Bild und mehr Text zugleich gibt es nur ueber eine hoehere Zeile; die
       Zahlen sind ein Tausch, kein Regler.
       WENN cards.php NICHT MITZIEHT, STEHT EIN LOCH IM RASTER: die bildlose Kachel
       ist EIN Band hoch, also muessen --news-baender Stueck davon gestapelt werden.
       Die Zahl steht dort als $proStapel und muss dieser hier folgen.
       NICHT DASSELBE WIE DIE 175 DES DASHBOARDS: dessen Bausteine rechnen ihre Hoehe
       aus --mh * 175, und die Stream-Ansicht hat ihr eigenes --band. Beide bleiben,
       wo sie sind - ein Baustein ist keine Kachel, und die Stream-Kachel ist dreimal
       so breit, also gilt dort ein anderes Verhaeltnis. */
    --news-band:    113px;
    --news-baender: 4;
    --news-baender-bild: 2;
    --news-bg:      #0a0b09;   /* CRT-Schwarz (leicht warm) */
    --news-bg-dark: #0e0f0c;   /* Panels / Karten / inaktive Flaechen */
    --news-panel:   #101310;   /* Dropdown-Panel */
    --news-line:    #23282a;   /* Hairlines / Rahmen */
    --news-accent:  #ffb52a;   /* Bernstein (Akzent: aktiv, Prompt, Links, Hotkeys) */
    --news-glow:    rgba(255,181,42,0.55);
    /* ZWEITER Akzent: Phosphor-Gruen der alten Gruenmonitore (P1). Klassisch waere
       #33ff33 — eine Spur abgedunkelt, damit es das Bernstein nicht ueberstrahlt.
       Bedeutung: BERNSTEIN = Struktur/aktiv, GRUEN = frisch reingekommen ("new"). */
    /* Scanlines auf den Bildern: Staerke = Deckkraft der dunklen Linie,
       Gap = Abstand (kleiner = dichter). 0.14 / 3px ist bewusst dezent. */
    --news-scan-strength: 0.10;
    --news-scan-gap: 1.5px;
    --news-green:   #3ce06b;
    --news-green-glow: rgba(60,224,107,0.45);
    /* ZWEI DATENFARBEN FUER DEN WETTERKOPF (02.08.2026, Issue #9). Sie sind KEIN
       dritter Akzent: Bernstein bleibt Struktur, Gruen bleibt "frisch reingekommen",
       und diese beiden treten ausschliesslich INNERHALB der Wetter-Diagramme auf,
       wo Regen und Sonne sich unterscheiden muessen. Das Blau ist bewusst das der
       Kategorie News (#4ca1ff aus config.php) — ein zweites Blau daneben waere ein
       Unterschied, den niemand als Bedeutung liest. */
    --news-regen:   #4ca1ff;   /* Niederschlag: Balken und mm-Werte */
    --news-sonne:   #ffd75e;   /* Sonnenschein: Balken und Stunden  */
    --news-text:    #c7cbc4;   /* Grau (Fliesstext) */
    --news-dim:     #6f7468;   /* Dim / Meta */
    --news-bright:  #eef0e6;   /* Hell (Ueberschriften / aktiv) */
    --news-font:    "ibm-plex-mono", "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
    /* DIE SIMULIERTE TERMINALEINGABE BLEIBT IMMER MONO (31.07.2026, Roger:
       "es gibt 2 stellen wo ein terminalinput simuliert wird"). --news-font
       wechselt mit dem Theme, --news-mono nie: eine nachgebaute Eingabe haelt
       nur zusammen, wenn jedes Zeichen gleich breit ist, und Code ebenso. */
    --news-mono:           "ibm-plex-mono", "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;

    /* Buehne: maximale INHALTSbreite (ohne die 40px Seitenluft), zentriert.
       Das Raster ist repeat(auto-fill, minmax(270px,1fr)) mit 16px Gap, also
       gilt: n Spalten brauchen n*270 + (n-1)*16. 5 Spalten = 1414 .. 1699px,
       1450px liegt sauber darin (Kachel dann ~277px). Fuer 6 Spalten: 1700px. */
    --news-stage:   1450px;

    /* ECKENRADIEN, EINE LEITER AUS FUENF STUFEN (31.07.2026, Issue #6).
       ANLASS: Papier hat keine weichen Ecken. Der Radius stand an 54 Stellen als
       Literal, das E-Ink-Theme erbte also die 7px des CRT — der letzte Bruch im
       Papier-Look, denn eine abgerundete Kachel liest sich als Bildschirm-Widget.
       BENANNT NACH DER STUFE, NICHT NACH DER ROLLE. Verlockend waeren Namen wie
       -card / -button gewesen, aber die Rollen decken sich nicht: Eingabefelder
       im Admin sind 5px, die Selects im Portal 4px. Ein Rollenname waere an
       diesen Stellen schlicht falsch. Nach der ZAHL zu benennen (--news-r-7)
       waere schlimmer — im E-Ink wird aus der 7 eine 2, und der Name luege noch
       lauter. Eine Leiter benennt man nach der Sprosse.
       KEIN WERT IST HIER VERAENDERT, nur benannt; geprueft durch Rueckersetzen
       gegen das Stylesheet von vorher, byteweise identisch. Dieselbe Methode wie
       beim Farbdurchgang weiter unten.
       NICHT ALLES IST EIN RADIUS: die beiden 0 (Flip-Karte, gezoomtes Bild) sind
       Struktur — das Element fuellt seinen Rahmen — und die 2px am Ablegebalken
       beim Ziehen gehoeren zu einer Linie, nicht zu einer Flaeche. Die drei
       stehen weiter als Literal da und sollen es bleiben. */
    --news-r-xl:    7px;   /* Kacheln, Dashboard-Bausteine, Lightbox */
    --news-r-l:     6px;   /* Knoepfe, Menues, Panels, Admin-Karten */
    --news-r-m:     5px;   /* innere Kacheln, Admin-Felder, Flash, Reiter */
    --news-r-s:     4px;   /* kleine Bedienelemente, Chips, Selects */
    --news-r-xs:    3px;   /* Labels, Badges, Code, die Aufmacherflaeche */

    /* --- AB HIER: was bis zum 31.07.2026 fest im Stylesheet stand -------------
       Anlass ist das zweite Theme (E-Ink, Issue #6): eine Farbe, die als Literal
       an 14 Stellen steht, laesst sich nicht umschalten. Es ist KEIN Wert
       veraendert worden, nur benannt — das Gerenderte ist identisch.
       Der Umbau lohnt auch ohne Theme: die Rot-/Fehlerfamilie stand an zehn
       Stellen ohne jede Variable, und ein Fehlerrot an zehn Stellen ist ein
       Fehlerrot, das irgendwann an neun Stellen stimmt. */

    /* TEXT AUF EINER AKZENTFLAECHE. Im CRT ist das CRT-Schwarz auf Bernstein;
       im E-Ink-Theme wird der Akzent zur Tinte und der Text muss PAPIER werden —
       genau deshalb reicht hier kein Literal. */
    --news-on-accent:      #0a0b09;

    /* GEFAHR / LOESCHEN. Vier Werte, die zusammengehoeren und es bisher nicht
       zeigten: Textfarbe, Flaeche, Rand und der Text darauf. */
    --news-danger:         #d38b8b;
    --news-danger-bg:      #6e3a3a;
    --news-danger-line:    #a85454;
    --news-danger-on:      #f2d4d4;

    /* Graustufen, die es noch nicht gab. */
    --news-line-hover:     #3a4042;   /* Rand bei Hover, Scrollbalken           */
    --news-placeholder:    #3f4642;   /* Platzhaltertext in Eingabefeldern      */
    --news-img-bg:         #15181a;   /* Flaeche hinter einem ladenden Bild     */

    /* Der Akzent mit Deckkraft — dreimal dieselbe Farbe, drei Rollen. */
    --news-accent-fill:    rgba(255,181,42,0.10);   /* leise Fuellung           */
    --news-accent-line:    rgba(255,181,42,0.45);   /* Rand eines Chips         */
    --news-accent-halo:    rgba(255,181,42,0.22);   /* Schein am 3x2t-Rahmen    */

    /* ZWEI HALBDURCHSICHTIGE FLAECHEN AUS DEM HINTERGRUND. Sie standen bis zum
       31.07.2026 als `rgba(var(--news-bg-rgb), A)` da, mit dem Hintergrund als
       RGB-TRIPEL — und genau daran waeren die E-Ink-Regler gescheitert: dessen
       Papierton kommt aus hsl(), und CSS kann ein hsl() nicht in ein Tripel
       zurueckrechnen. Statt das Tripel in PHP auszurechnen ist es ganz weg; jede
       der zwei Flaechen ist jetzt ihre eigene Variable und wird im E-Ink direkt
       aus --papier-* gemischt, folgt den Reglern also von selbst.
       BENANNT NACH DER STELLE, weil es genau zwei sind und sie verschiedene
       Deckkraft haben — ein gemeinsamer Name mit Zahl (--news-scrim-82) waere ein
       Name, der die Zahl doppelt sagt. */
    --news-sheet:          rgba(10,11,9,0.82);      /* Flaeche unter dem Aufmachertext */
    --news-strip:          rgba(10,11,9,0.72);      /* Titelstreifen im Bildslider     */
    --news-scrim:          rgba(6,7,6,0.92);        /* Vorhang der Bildlupe     */

    /* Schatten. Ein reflektierendes Panel wirft keine — im E-Ink-Theme gehen
       beide auf transparent. */
    --news-shadow:         rgba(0,0,0,0.6);
    --news-textshadow:     rgba(0,0,0,0.7);
    --news-tint-fallback:  rgba(255,255,255,0.12);  /* wenn color-mix fehlt     */
    /* --- Aus dem <style>-Block von admin.php geholt (31.07.2026) --------------
       Der Admin brachte seine eigenen vierzehn Farbwerte mit. Sie gehoeren in
       dieselbe Palette wie alles andere, sonst hat das zweite Theme zwei Orte,
       an denen es Farben setzen muss — und zwei Orte laufen auseinander. */
    --news-accent-focus:   rgba(255,181,42,0.25);   /* Fokusring                */
    --news-error-line:     #c0605f;                 /* Rand einer Fehlermeldung */
    --news-error-text:     #e6b3b2;                 /* Text dazu                */
    --news-dim-soft:       #4a524c;                 /* leiser als --news-dim    */
    --news-badge-line:     rgba(255,181,42,0.4);    /* aktives Abzeichen        */
    --news-badge-fill:     rgba(255,181,42,0.08);
    --news-shadow-strong:  rgba(0,0,0,0.55);        /* Schatten am Aufklapper   */
    --news-info:           #4ec9d9;                 /* der ruhige Zustand im Log */
    /* GRUEN FUER "vorhanden/ok". Stand als #5fbf87 in einem PHP-STRING in
       admin.php und war deshalb die einzige Farbe, die der Variablen-Umbau
       nicht gefunden hat - er raeumte den <style>-Block ab, nicht die Strings. */
    --news-ok:             #5fbf87;
}

body {
    margin: 0;
    background-color: var(--news-bg);
    overflow: hidden;
    color: var(--news-text);
    font-family: var(--news-font);
}
@media (pointer:none), (pointer:coarse) { body { overflow: auto; } }

::-webkit-scrollbar { width: 16px; height: 16px; }
::-webkit-scrollbar-track { background: var(--news-bg); }
::-webkit-scrollbar-thumb { background: var(--news-line); border: 4px solid var(--news-bg); }
::-webkit-scrollbar-thumb:hover { background: var(--news-line-hover); }

.maincontainer {
    position: absolute; display: flex; flex-direction: column;
    top: 0; left: 0; width: 100%; height: 100vh;
    background-color: var(--news-bg); z-index: 0;
}
@media (pointer:none), (pointer:coarse) { .maincontainer { display: block; height: auto; } }

/* --- HEADER: Terminal-Kopf (Prompt + Cursor), kein Banner/Logo mehr --- */
.headercontainer {
    position: relative; flex: 0 0 auto;
    width: 100%;
    box-shadow: 0 1px 0 0 var(--news-line);
    background-color: var(--news-bg); z-index: 100;
}
@media (pointer:none), (pointer:coarse) { .headercontainer { box-shadow: none; padding-bottom: 10px; } }

/* Kopf laeuft mit der Buehne mit, damit die Prompt-Zeile mit den Kacheln fluchtet
   (die 40px Seitenluft steckt jetzt im Padding, nicht mehr im Margin). */
.term-header { position: relative; margin: 0 auto; padding: 20px 40px 16px; max-width: var(--news-stage); }
.term-bar { display: flex; align-items: center; gap: 14px; font-size: 15px; color: var(--news-dim); }
.term-prompt { flex: 1; display: flex; align-items: center; gap: 7px; min-width: 0; white-space: nowrap; margin: 0; }
/* Suchfeld: sieht aus wie getippter Terminal-Text, kein Kasten */
.term-input {
    flex: 1; min-width: 60px; max-width: 460px;
    background: none; border: none; outline: none; padding: 0;
    font-family: var(--news-font); font-size: 15px; color: var(--news-bright);
    caret-color: var(--news-accent);
}
.term-input::placeholder { color: var(--news-placeholder); }
/* Beim Tippen uebernimmt der echte Text-Cursor — der Block blinkt dann nicht mehr */
.term-prompt:focus-within .term-cursor { display: none; }
.term-clear { color: var(--news-dim); text-decoration: none; font-size: 13px; }
.term-clear:hover { color: var(--news-accent); }
.term-prompt .amb { color: var(--news-accent); text-shadow: 0 0 8px var(--news-glow); }
/* "12 muted" in der Suchkopfzeile: eine Randnotiz, kein Ergebnis — deshalb dim und
   ohne Leuchten. Es soll beim Suchen NICHT ins Auge fallen, sondern die Frage
   beantworten, die man erst stellt, wenn ein Treffer fehlt. */
.term-prompt .dim { color: var(--news-dim); }
.term-cursor { display: inline-block; width: 9px; height: 16px; background: var(--news-accent); box-shadow: 0 0 8px var(--news-glow); vertical-align: -3px; margin-left: 3px; animation: blink 1s steps(2) 10; }   /* blinkt 10x1s = 10 s, danach ruhiger Block */
@keyframes blink { 50% { opacity: 0; } }
.term-status { color: var(--news-dim); white-space: nowrap; font-size: 14px; }
/* Admin + Logout: rechts oben im Terminal-Kopf */
.term-actions { display: inline-flex; align-items: center; gap: 8px; flex-shrink: 0; }
/* Ansichtsumschaltung: rechts in der NAV-ZEILE, also genau unter der Knopfleiste.
   margin-left:auto schiebt sie ans Ende der Flex-Zeile — bricht die Navigation um,
   wandert sie mit ans Ende der letzten Zeile statt zu ueberlappen. Sie gilt fuers
   ganze Portal und steht deshalb nicht in der Filterzeile, die immer nur die eine
   Ansicht beschreibt, die man gerade sieht. */
.viewswitch { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; flex-shrink: 0; }
/* Die Theme-Wahl sitzt rechts neben dem Ansichts-Umschalter. Fehlt der (Dashboard,
   Admin), muss sie selbst ans Zeilenende schieben - die Klasse .allein setzt
   header.php, weil PHP dort ohnehin weiss, ob der Umschalter gerendert wurde.
   Ein :has() waere die CSS-Loesung, aber eine Bedingung, die PHP schon kennt,
   gehoert nicht in den Selektor. */
.navtheme { margin-left: 8px; flex-shrink: 0; }
.navtheme.allein { margin-left: auto; }
/* RECHTS VERANKERT wie das saved-Menue - und mit derselben Spezifitaet (0,3,1),
   sonst gewinnt die Basisregel mit ihrem left: -18px und das Menue haengt ueber
   den rechten Rand hinaus. Genau diese Falle steht bei .navsaved dokumentiert. */
.nav-item-wrapper.has-dropdown.navtheme .nav-dropdown-menu { min-width: 150px; left: auto; right: -6px; }
.navtheme .portal-bridge { position: absolute; top: 100%; right: 0; width: 100%; height: 12px;
                           background: transparent; z-index: 1001; pointer-events: none; }
.navtheme:hover .portal-bridge { pointer-events: auto; }

/* DIE ZWEI E-INK-REGLER, "hell" und "warm" (31.07.2026, Issue #6).
   DIESES MENUE IST EINE SPALTE, kein Raster. Die geteilte Regel schaltet auf
   grid-auto-flow: column mit --rows Zeilen — richtig fuer die gespeicherten Suchen,
   die in die Breite wachsen sollen, falsch hier: der Reglerblock landete damit als
   dritter "Eintrag" in einer zweiten SPALTE neben den Themes. Flex-Spalte statt
   dessen, mit demselben 8px-Abstand.
   SPEZIFITAET IST HIER PFLICHT, nicht Zierde: die Basisregel steht als
   `.nav-item-wrapper.has-dropdown:hover .nav-dropdown-menu` (0,4,0) da, ein blosses
   `.navtheme:hover .nav-dropdown-menu` (0,3,0) verliert dagegen. Dieselbe Falle ist
   bei .navsaved dokumentiert und stand dort schon einmal ungenutzt herum. */
.nav-item-wrapper.has-dropdown.navtheme:hover .nav-dropdown-menu,
.nav-item-wrapper.has-dropdown.navtheme.haelt .nav-dropdown-menu {
    display: flex; flex-direction: column; gap: 8px;
}
/* WAEHREND DES ZIEHENS OFFEN HALTEN. Das Menue geht per Hover auf, ein Regler wird
   aber GEZOGEN — rutscht die Maus dabei heraus, klappte es mitten in der Bewegung
   zu. header.php setzt .haelt zwischen pointerdown und pointerup.
   NICHT auf Klick umgestellt: Portal-Umschalter, "more", "saved" und das
   Dashboard-Plus gehen alle auf Hover auf, eine Ausnahme waere der groessere Bruch.
   Das ANIMIEREN entfaellt in diesem Fall — das Menue ist ja schon offen, ein
   fadeIn beim Anfassen des Reglers waere ein Blinken. */
.nav-item-wrapper.has-dropdown.navtheme.haelt .nav-dropdown-menu { animation: none; }
/* IM CRT SIND SIE WEG — aber per CSS, nicht per PHP (Roger, 31.07.2026: "im crt
   koennen die regler ausgeblendet werden"). Ich hatte sie erst in ein
   `if ($themeJetzt === 'eink')` gepackt, und DAS WAR FALSCH: das Theme schaltet OHNE
   Seitenaufbau um, das JavaScript tauscht nur die Klasse am <html>. Das Markup
   stammt also weiter vom letzten Aufbau, und die Regler blieben nach dem Wechsel auf
   CRT stehen. Genau dieselbe Ueberlegung wie bei den zwei Icon-Saetzen, die aus
   demselben Grund beide im Markup liegen — ich hatte die Regel hier nicht
   angewendet. Sie steuern im CRT ohnehin nichts: --hell/--warm werden nur innerhalb
   von .theme-eink gelesen. */
.theme-crt .einkregler { display: none; }
.einkregler { display: flex; flex-direction: column; gap: 7px;
              border-top: 1px solid var(--news-line); padding-top: 9px; margin-top: 1px; }
.einkregler label { display: flex; align-items: center; gap: 9px;
                    font-family: var(--news-font); font-size: 12px; color: var(--news-dim); }
.einkname { flex: 0 0 34px; }
/* DIE STUFE 0-10 (Roger: "und die anzeige eines wertes zwischen 0-10"). Feste
   Breite und tabellarische Ziffern, sonst zappelt der Regler daneben, sobald die
   Zahl von 9 auf 10 springt — zwei Stellen sind breiter als eine. */
.einkwert { flex: 0 0 16px; text-align: right; color: var(--news-text);
            font-variant-numeric: tabular-nums; }

/* Regler im Admin-Reiter "Design". Gleiche Machart wie die zwei im Palette-Menue:
   accent-color faerbt Schiene und Knopf, die Bedienung bleibt die des Browsers. */
.dreg { width: 100%; accent-color: var(--news-accent); cursor: pointer; margin: 4px 0 0; }
.dwert { float: right; color: var(--news-accent); font-variant-numeric: tabular-nums; }
.dhint { color: var(--news-dim); font-size: 12px; line-height: 1.6; margin-top: 5px; }
/* Der Regler traegt sein Aussehen selbst: accent-color faerbt Schiene und Knopf in
   EINER Zeile und laesst dem Browser die Bedienung (Tastatur, Touch, Schrittweite).
   Ein nachgebauter Regler waere hier das, was rule 8 meint - es gibt ihn schon. */
.einkregler input[type=range] { flex: 1 1 auto; width: 96px; accent-color: var(--news-accent);
                                cursor: pointer; margin: 0; }

.term-rule { border-top: 1px solid var(--news-line); margin: 14px 0 12px; }
.headernavigation { display: flex; align-items: center; flex-wrap: wrap; }

/* Terminal-Nav: kein Pill mehr, nur Text; Hotkey in Bernstein; Hover/aktiv hell. */
.navlink {
    /* Gleiche Helligkeit wie die Eintraege im "more"-Dropdown (--news-text):
       beides sind Nav-Punkte und stehen direkt nebeneinander. Das Dropdown erbt
       die Optik des Portal-Switchers, die schon immer heller war — vorher fiel
       das nicht auf, weil es keine Nav-Eintraege darin gab. */
    font-family: var(--news-font); font-size: 17px; letter-spacing: 0.3px;
    color: var(--news-text); text-decoration: none;
    background: none; border: 1px solid transparent; border-radius: var(--news-r-s);
    padding: 3px 9px; margin-right: 14px;
    white-space: nowrap; transition: background-color 0.12s ease, color 0.12s ease;
}
.navlink .hk { color: var(--news-accent); }
/* KATEGORIE-FARBE (config.php -> category_colors) als eckiger BLOCKCURSOR hinter
   dem Hotkey — dieselbe Idee wie .term-cursor, nur sitzt der Block auf dem
   Buchstaben statt neben ihm. KEIN border-radius: ein Terminal-Cursor ist ein
   Rechteck. Der negative Seitenrand hebt das Padding wieder auf, damit die
   Laufweite der Nav-Leiste durch die Kodierung nicht wandert.
   GETOENT statt voll deckend (Roger, 27.07.2026), damit der Buchstabe BERNSTEIN
   bleiben kann wie jeder andere Hotkey: die Farbe ist die Kategorie-Kennung, das
   Amber ist die Bedienungs-Kennung — beide sollen lesbar bleiben. Auf voller
   Deckung geht das nicht, dafuer liegen Amber und ein saturiertes Blau zu nah
   beieinander. Der Kasten ist deshalb ein aufgehellter Untergrund in der
   Kategoriefarbe, wie die .fchip-Pillen es auch machen.
   KEIN eigenes 'color' hier — so bleibt der Buchstabe bernstein und wird auf dem
   aktiven Amber-Balken von der Regel darunter automatisch dunkel. Genau deshalb
   ist die Regel auch NICHT auf die Navigation eingeschraenkt: der erste Buchstabe
   einer Schlagzeile (cards.php) traegt denselben Block und erbt dort die Farbe der
   Ueberschrift. Der negative Seitenrand haelt dabei die Textkante buendig — der
   Block ragt nach links heraus, die Zeile selbst rutscht nicht. */
.hkbox {
    background: var(--news-tint-fallback);   /* Rueckfall, falls color-mix fehlt */
    background: color-mix(in srgb, var(--hkc) 34%, transparent);
    padding: 1px 3px; margin: 0 -3px;
    box-shadow: 0 0 9px color-mix(in srgb, var(--hkc) 45%, transparent);
}
/* Hover + aktiv = Amber-Block (Terminal-"Selektion"), dunkler Text inkl. Hotkey */
.navlink:hover, .navlink_active {
    background-color: var(--news-accent); color: var(--news-on-accent);
    box-shadow: 0 0 10px var(--news-glow);
}
.navlink:hover .hk, .navlink_active .hk { color: var(--news-on-accent); }
/* Der aktive Punkt flackert sporadisch — wie eine Roehre, die ihre hellste
   Flaeche nicht ganz ruhig haelt. Langer Zyklus mit wenigen, kurzen und UNGLEICH
   verteilten Aussetzern (steps(1) = harter Sprung, kein weiches Faden), sonst
   wirkt es wie ein regelmaessiges Pulsieren. Beim Hovern aus: da soll die
   Rueckmeldung eindeutig sein. */
@keyframes navflicker {
    0%, 22.4%, 23.1%, 56.7%, 57.1%, 57.6%, 84.2%, 100% { opacity: 1; }
    22.7% { opacity: 0.62; }
    56.9% { opacity: 0.78; }
    57.3% { opacity: 0.45; }
    84.5% { opacity: 0.70; }
}
.navlink_active { animation: navflicker 12s steps(1, end) infinite; }
.navlink_active:hover { animation: none; }
@media (prefers-reduced-motion: reduce) { .navlink_active { animation: none; } }

/* Basis-Button (nur noch vom Portal-Switcher genutzt) */
.navigationbutton {
    border: 1px solid var(--news-line); border-radius: var(--news-r-l); padding: 10px;
    background-color: var(--news-bg-dark);
    display: inline-flex; align-items: center; justify-content: center;
    margin: 0 5px; font-size: 21px; height: 25px; color: var(--news-text);
    font-family: var(--news-font); text-decoration: none; white-space: nowrap;
}
.navigationbutton:hover { background-color: var(--news-accent); color: var(--news-on-accent); }

/* --- Portal-Switcher: App-Launcher (inline im Terminal-Kopf) --- */
.portal-switcher { position: relative; display: inline-flex; z-index: 130; }
/* Hover-Flaeche = genau das Icon (24x24), nicht mehr */
.portal-switcher .navigationbutton { background: none; border: none; box-shadow: none; padding: 0; width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; }
/* Unsichtbare Bruecke Icon -> Dropdown: nur so breit wie der Weg dorthin, und
   erst aktiv, WENN das Menue schon offen ist (sonst wuerde sie es selbst oeffnen). */
.portal-switcher .portal-bridge { position: absolute; top: 100%; left: -18px; width: 56px; height: 12px; background: transparent; z-index: 1001; pointer-events: none; }
.portal-switcher .nav-item-wrapper:hover .portal-bridge { pointer-events: auto; }
.portal-switcher .navigationbutton i { font-size: 18px; color: var(--news-text); text-shadow: 0 1px 2px var(--news-textshadow); }
.portal-switcher .navigationbutton:hover { background: none; }
.portal-switcher .navigationbutton:hover i { color: var(--news-accent); text-shadow: 0 0 8px var(--news-glow); }

/* "more"-Dropdown der Nav (Rest der Eintraege, wenn nav_max_items greift).
   Eigene Bruecke wie beim Portal-Switcher: haelt das Menue beim Runterfahren
   offen, oeffnet es aber nie selbst (pointer-events erst im Hover). */
.navmore { vertical-align: middle; }
.navmore .navlink { cursor: default; }
/* DER WETTER-EINTRAG TRAEGT SEIT DEM 02.08.2026 SELBST EIN KLAPPMENUE (Issue #22,
   Roger: "unter weather zum aufklappen") - die Orte haengen darunter statt als
   zweiter Eintrag daneben zu stehen. Er ist damit .navmore UND ein echter Link auf
   die Kategorie, anders als "more", dessen Ausloeser nirgends hinfuehrt. Deshalb
   der Zeiger zurueck: er verspricht hier zu Recht etwas. */
.navorte > .navlink { cursor: pointer; }
.navmore .nav-dropdown-menu { left: 0; min-width: 190px; }
.navmore .portal-bridge { position: absolute; top: 100%; left: 0; width: 100%; height: 12px; background: transparent; z-index: 1001; pointer-events: none; }
.navmore:hover .portal-bridge { pointer-events: auto; }
.navmore .nav-dropdown-item { font-size: 16px; }
.navmore .nav-dropdown-item .hk { color: var(--news-accent); }
.navmore .nav-dropdown-item:hover .hk { color: var(--news-on-accent); }
.navmore .nav-dropdown-item.active { background-color: var(--news-accent); color: var(--news-on-accent); }
.navmore .nav-dropdown-item.active .hk { color: var(--news-on-accent); }
/* "saved"-Dropdown: gleiche Mechanik wie "more", nur etwas breiter (Namen) */
/* Waechst nach LINKS statt nach rechts: "saved" ist der letzte Nav-Eintrag, und
   die zweite Spalte liefe sonst aus dem Fenster (gemessen: 42px daneben).
   DER LANGE SELEKTOR IST NOETIG, nicht Zierde: Die Basisregel setzt left: -18px
   als `.nav-item-wrapper.has-dropdown .nav-dropdown-menu` (0,3,1). Ein blosses
   `.navsaved .nav-dropdown-menu` (0,2,0) verliert dagegen — der Anker kam nie an,
   das Menue blieb links verankert und wuchs nach rechts aus dem Bild.
   (Dieselbe Falle steht ungenutzt bei .navmore: left/min-width dort wirken nicht.) */
.nav-item-wrapper.has-dropdown.navsaved .nav-dropdown-menu { min-width: 220px; left: auto; right: -18px; }
.navmanage { border-top: 1px solid var(--news-line); color: var(--news-dim) !important; font-size: 13px !important; }

/* Dropdown */
.nav-item-wrapper.has-dropdown { position: relative; display: inline-block; z-index: 1000; }
.nav-item-wrapper.has-dropdown .nav-dropdown-menu {
    display: none; position: absolute; top: calc(100% + 10px); left: -18px;
    background-color: var(--news-panel);
    /* width: max-content, NICHT auto (29.07.2026): Bei einem RECHTS verankerten
       Menue reicht die verfuegbare Breite nur bis zum Anker — also ueber die Breite
       des Nav-Eintrags. "auto" schrumpfte damit auf die min-width, und die zweite
       Spalte lag hinter dem overflow: hidden. max-content misst den Inhalt und
       waechst nach links. */
    min-width: 160px; width: max-content; max-width: min(92vw, 720px);
    box-shadow: 0 8px 24px 0 var(--news-shadow);
    z-index: 1002; border-radius: var(--news-r-l); border: 1px solid var(--news-line);
    /* GLEICHE ABSTAENDE UEBERALL (Roger, 29.07.2026: "ich bin grosser Freund von
       Symmetrie"). Vorher trug JEDER Eintrag margin: 8px — am Rand ergab das 8px,
       zwischen zwei Eintraegen aber 8+8 = 16px, und mit der zweiten Spalte fiel es
       auf. Jetzt: 8px Polster am Menue, 8px Abstand im Raster, kein Rand am Eintrag.
       Eine Zahl, ueberall dieselbe. */
    padding: 8px; margin-top: 0; overflow: hidden;
}
/* MEHRSPALTIG: FUENF EINTRAEGE JE SPALTE, dann beginnt die naechste (Roger,
   29.07.2026). Die gespeicherten Suchen waren als eine Spalte laenger als der halbe
   Bildschirm und wuchsen mit jeder neuen Suche weiter.
   grid-auto-flow: column mit fuenf festen Zeilen braucht dafuer KEINE Zahl im PHP:
   Die Liste darf wachsen, das Menue wird breiter statt laenger, ganz von selbst. */
.nav-item-wrapper.has-dropdown:hover .nav-dropdown-menu {
    /* --rows kommt aus header.php und ist min(5, Anzahl). FEST FUENF WAERE FALSCH:
       Bei weniger Eintraegen bleiben die restlichen Zeilen als 0px stehen, ihre
       Luecken zaehlen aber weiter — der Portalumschalter mit EINEM Eintrag bekam so
       32px Leerraum unter sich. */
    display: grid; grid-auto-flow: column; grid-template-rows: repeat(var(--rows, 5), auto);
    gap: 8px; animation: fadeIn 0.2s ease-in-out;
}
/* Im Raster bestimmt die Spalte die Breite — die alte Prozentbreite waere ein
   Zirkelschluss (Spalte misst den Eintrag, Eintrag misst die Spalte) und schnitte
   den laengsten Namen ab. */
.nav-dropdown-menu .nav-dropdown-item { width: auto; }
.nav-dropdown-item {
    border-radius: var(--news-r-m); padding: 10px 16px; background-color: var(--news-bg-dark);
    margin: 0; font-size: 18px; color: var(--news-text);   /* Abstaende macht das Raster */
    font-family: var(--news-font); text-decoration: none; white-space: nowrap;
    display: flex; align-items: center; width: calc(100% - 16px); box-sizing: border-box;
}
.nav-dropdown-item i { margin-right: 10px; width: 20px; text-align: center; font-size: 16px; flex-shrink: 0; color: var(--news-accent); }
.nav-dropdown-item:hover { background-color: var(--news-accent); color: var(--news-on-accent); }
.nav-dropdown-item:hover i { color: var(--news-on-accent); }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* --- CONTENT (scrollt unter dem Header) --- */
/* DIE SCROLLRINNE STEHT AUF BEIDEN SEITEN (Roger, 31.07.2026, Issue #15: "Header hat
   einen Versatz zum Content").
   URSACHE, gemessen: .headercontainer ist ein GESCHWISTER dieses Scrollers, der
   Inhalt und der Footer liegen DARIN. Die Scrollbar ist 16px breit (siehe
   ::-webkit-scrollbar oben), der Inhaltskasten also 16px schmaler als das Fenster —
   und `margin: 0 auto` zentriert die 1450px-Buehne darin, 8px links von der Buehne
   des Kopfes. Sichtbar an beiden Enden: die Karten standen 8px weiter links, die
   Icons oben rechts ragten 8px ueber ihre Kante hinaus.
   both-edges reserviert die Rinne SYMMETRISCH, damit der Inhaltskasten mittig
   bleibt. Keine geratene Zahl und kein JS — die Breite der Scrollbar ist in CSS
   nicht auslesbar, hier muss sie es auch nicht sein.
   PREIS: 32px weniger Breite unterhalb der 1450px-Grenze, also erst bei Fenstern
   schmaler als ~1482px. Es gilt AUCH auf Seiten, die gar nicht scrollen — die
   fluchteten vorher zufaellig. Gleich aussehen schlaegt manchmal-richtig.
   Auf Touch stellt die Medienabfrage unten overflow-y auf hidden; dort ist das
   Element kein Scrollcontainer und die Eigenschaft greift ohnehin nicht. */
/* NACH DEM ANSCHLAG SOFORT WIEDER ZURUECK (Roger, 31.07.2026: "da es kein nachfedern
   gibt kann man nach einem runterscrollen bis zum rand nicht wieder direkt
   hochscrollen ohne den finger vom pad zu nehmen").
   URSACHE IST SCROLL-LATCHING, nicht das fehlende Gummiband: erreicht dieser Container
   sein Ende, reicht der Browser die Geste an den ELTERNSCROLLER weiter. Der ist body
   mit overflow: hidden, kann also nichts bewegen - aber die Geste bleibt bis zum
   Loslassen DORT verhaftet, und Weiterdrehen in die Gegenrichtung erreicht diesen
   Container nicht mehr. Am Mausrad faellt das kaum auf, am Trackpad sofort: dort ist
   eine Geste sekundenlang.
   contain unterbindet nur die WEITERGABE nach aussen, nicht das Scrollen selbst - die
   Geste bleibt an diesem Container haengen, und die Umkehr wirkt sofort.
   NICHT `none`: das wuerde zusaetzlich den plattformeigenen Nachfeder-Effekt
   unterdruecken, und der ist hier gar nicht das Problem.
   NEBENWIRKUNG, hier erwuenscht: am oberen Anschlag loest auf dem Handy kein
   Pull-to-Refresh mehr aus. In einem Nachrichtenportal, dessen Seiten ohnehin nur
   lesen, ist ein versehentlicher Neuaufbau nichts, was jemand vermisst.
   NUR HIER, NICHT AUF DEN BAUSTEINEN DES DASHBOARDS: die scrollen innen (.dashlatest,
   .dsstrip), und dort ist die Weitergabe nach aussen richtig - wer am Ende einer
   Bausteinliste ankommt, will, dass die SEITE weiterscrollt. contain wuerde ihn darin
   gefangen halten. */
.contentscroller {
    position: relative; flex: 1 1 auto; min-height: 0;
    width: 100%;
    overflow-y: auto; scrollbar-gutter: stable both-edges;
    overscroll-behavior: contain;
    background-color: var(--news-bg); z-index: 2;
}
@media (pointer:none), (pointer:coarse) { .contentscroller { overflow-y: hidden; height: auto; } }
.contentcontainer {
    position: relative; display: block;
    margin: 0 auto 40px; padding: 0 40px; max-width: var(--news-stage);
    height: auto; width: auto;
    background-color: var(--news-bg); z-index: 10;
}

/* Zurueck nach oben: schwebt unten rechts, faded ein sobald gescrollt wurde.
   pointer-events aus, solange unsichtbar — sonst faengt er Klicks auf Kacheln ab. */
.totop {
    position: fixed; right: 28px; bottom: 28px; z-index: 200;
    opacity: 0; pointer-events: none; transform: translateY(6px);
    transition: opacity 0.18s ease, transform 0.18s ease, color 0.12s ease, border-color 0.12s ease;
}
.totop.show { opacity: 1; pointer-events: auto; transform: none; }
/* DER PFEIL BLEIBT, WO ER IST — auf jeder Seite gleich (28.07.2026, zweiter Anlauf).
   Zwischendurch sollte er auf dem Dashboard nach oben ausweichen, damit das + seine
   Ecke bekommt: `:root:has(.dashbar) .totop { bottom: 78px }`. Bei Roger ist die
   Regel nicht angekommen — der Pfeil lag ueber dem +, und weil er darueber liegt,
   liess sich das Menue nicht mehr oeffnen. Woran es genau lag, habe ich nicht
   weiterverfolgt: Eine Anordnung, die von einer Bedingung abhaengt, ist hier die
   falsche Loesung. Jetzt weicht wieder das + aus (.dashbar bottom: 78px), und das
   haengt an nichts. */

/* --- FOOTER --- */
/* z-index BEWUSST NIEDRIGER als .contentcontainer (korrigiert 28.07.2026): beide
   standen auf 10, und bei Gleichstand entscheidet die Dokumentreihenfolge — der
   Footer kommt spaeter, lag also oben. Ein aufgeklapptes Filtermenue (.qmenu,
   z-index 60) verschwand dadurch dahinter, sobald die Seite kurz genug war: sein
   z-index gilt nur INNERHALB des Stapelkontexts, den .contentcontainer mit seiner
   10 aufmacht, und kann diesen nie verlassen. Der Footer ist Beiwerk und hat
   keinen Grund, ueber dem Inhalt zu liegen. */
.footercontainer {
    position: relative; display: flex; flex-direction: column;
    margin: 0 auto 40px; padding: 0 40px; max-width: var(--news-stage);
    justify-content: center; align-items: center;
    background-color: var(--news-bg); z-index: 1;
}
.footertext {
    font-family: var(--news-font);
    font-size: 15px; color: var(--news-dim); text-align: center; line-height: 1.6;
}
.sponsorlogok { border-radius: var(--news-r-s); margin-left: 2px; }
.sponsorlogok:hover { opacity: 50%; }

/* --- Buttons: EINE gemeinsame Klasse fuer alle Seiten-Buttons (ausser .navlink).
       Ruhezustand outline, Hover = Amber-Fuellung (dunkler Text). Modifier:
       .mini (kleiner), .icon (quadratischer Icon-Button), .danger (rot). --- */
/* box-sizing GEHOERT HIERHER (03.08.2026, Roger: "unterschiedlich gross", mit einem
   Strich unter die Knopfzeile einer Kachel). Ohne sie rechnet ein <a> den 1px-Rand
   ZUM festen Mass dazu und wird 26px, waehrend ein <button> ihn hineinrechnet und
   24px bleibt - der Browser setzt bei Formularelementen border-box von sich aus.
   In der Knopfzeile stehen beide nebeneinander: "Oeffnen" ist ein Link, "Teilen" und
   der Stern sind Knoepfe. Das fiel erst auf, als der dritte dazukam.
   NUR DIE FESTEN GROESSEN aendern sich dadurch (.icon, .icon.large); alle anderen
   .btn haben keine Breite, dort rechnet border-box dasselbe aus. */
.btn {
    box-sizing: border-box;
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    font-family: var(--news-font); font-size: 14px; line-height: 1;
    padding: 10px 18px; border-radius: var(--news-r-l);
    border: 1px solid var(--news-line); background: var(--news-bg-dark); color: var(--news-text);
    text-decoration: none; cursor: pointer; white-space: nowrap;
    transition: color 0.12s ease, border-color 0.12s ease;   /* Rahmen animiert, Fuellung springt (wie Admin-Button) */
}
.btn:hover { background: var(--news-accent); border-color: var(--news-accent); color: var(--news-on-accent); }
.btn.mini { padding: 6px 12px; font-size: 13px; }
.btn.icon { width: 24px; height: 24px; padding: 0; font-size: 15px; }   /* STANDARD-Icon-Button */
.btn.icon.large { width: 42px; height: 42px; font-size: 17px; }         /* falls mal groesser gebraucht */
.btn.danger { border-color: var(--news-line); color: var(--news-danger); }
.btn.danger:hover { background: var(--news-danger-bg); border-color: var(--news-danger-line); color: var(--news-danger-on); }
.btn.danger.arm { background: var(--news-danger-bg); border-color: var(--news-danger-line); color: var(--news-danger-on); }   /* "geschaerft": 2. Klick loescht */
/* aktiver Zustand (z.B. gesetzter Favoriten-Stern): Amber gefuellt */
.btn.icon.active { background: var(--news-accent); border-color: var(--news-accent); color: var(--news-on-accent); }

/* --- News-Karten (My Feed + Kategorien) --- */
.feedhead { font-size: 13px; color: var(--news-dim); margin: 22px 0 16px;
            display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; }
.feedhead .amb { color: var(--news-accent); }
.fhtext { min-width: 0; padding-top: 4px; }

/* --- Suchseite (search.php): grosses Prompt-Feld + gespeicherte Suchen --- */
.searchbox {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    margin: 26px 0 14px; font-size: 17px; color: var(--news-dim);
}
/* SEIT DEM 04.08.2026 EIN <textarea> und kein Textfeld mehr (Roger: "oups die zeile
   ist zuende ... koennen wir einen umbruch machen?"). Ein einzeiliges Steuerelement
   bricht per Definition nie um, das ist keine Frage des Stylesheets - ein textarea
   kann es. Die Optik bleibt dieselbe Unterstreichung wie vorher.
   box-sizing: border-box IST HIER PFLICHT und nicht Ordnung: das Skript setzt die
   Hoehe auf scrollHeight, und der ENTHAELT das Polster - im content-box-Modell kaeme
   es bei jedem Lauf oben drauf und das Feld wuechse bei jedem Tastendruck.
   resize: none, weil es sich selbst misst; overflow: hidden, damit waehrend des
   Messens keine Leiste erscheint und die Breite aendert.
   height: auto ueberschreibt das Skript inline - hier steht nur der Startwert fuer
   den Fall, dass kein JavaScript laeuft: dann ist es ein einzeiliges Feld, das
   scrollt, also genau der Zustand von gestern. */
.searchinput {
    flex: 1; min-width: 220px; box-sizing: border-box;
    background: none; border: none; border-bottom: 1px solid var(--news-line);
    outline: none; padding: 6px 2px; resize: none; overflow: hidden;
    font-family: var(--news-font); font-size: 17px; line-height: 1.35;
    color: var(--news-bright);
    caret-color: var(--news-accent);
}
.searchinput:focus { border-bottom-color: var(--news-accent); }
.searchinput::placeholder { color: var(--news-placeholder); }


.savedbar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
            padding-bottom: 16px; border-bottom: 1px solid var(--news-line); }
.savedlabel { font-size: 11px; letter-spacing: 0.6px; text-transform: uppercase; color: var(--news-dim); }
.savedempty, .savedhint { font-size: 12px; color: var(--news-dim); }
.savedchip {
    display: inline-flex; align-items: center; gap: 6px;
    border: 1px solid var(--news-line); border-radius: var(--news-r-s); padding: 3px 6px 3px 10px;
    background: var(--news-bg-dark);
}
.savedchip a { color: var(--news-text); text-decoration: none; font-size: 13px; }
.savedchip:hover { border-color: var(--news-accent); }
.savedchip:hover a { color: var(--news-bright); }
.savedchip.on { border-color: var(--news-accent); background: var(--news-accent-fill); }
.savedchip.on a { color: var(--news-accent); }
.savedchip form { display: inline; margin: 0; }
/* UMSORTIEREN (29.07.2026): Der Greifzeiger ist der einzige Hinweis, dass die Chips
   ziehbar sind — ein eigener Griff waere in einem so kleinen Element mehr im Weg als
   er hilft. Der gezogene Chip bleibt sichtbar, nur blass: Er wandert waehrend des
   Ziehens ja schon an seine neue Stelle, und ganz verschwinden hiesse, die Luecke
   zu verlieren, an der man sich orientiert. */
.savedchip[draggable="true"] { cursor: grab; }
.savedchip.ziehe { opacity: 0.4; cursor: grabbing; }
.savedx { background: none; border: none; cursor: pointer; padding: 0 2px;
          color: var(--news-dim); font-size: 14px; line-height: 1; font-family: var(--news-font); }
.savedx:hover { color: var(--news-danger); }
/* DAS SPEICHERN-FORMULAR BEKOMMT EINE EIGENE ZEILE (31.07.2026, Issue #2).
   Vorher stand hier margin-left: auto. Das ist richtig, SOLANGE alles in eine Zeile
   passt — bei zwoelf gespeicherten Suchen bricht .savedbar aber um, und dann schob
   das auto das Formular am rechten Rand der ZWEITEN Zeile ab: der Knopf einsam
   rechts, davor eine bildschirmbreite Luecke. Genau die beiden Stellen hat Roger im
   Screenshot markiert, und es war eine einzige Zeile, nicht zwei Fehler.
   flex-basis: 100% erzwingt die eigene Zeile bei JEDER Chip-Anzahl - dieselbe Stelle,
   egal wie lang die Liste wird. Der Preis ist bewusst: auch bei drei Chips sitzt es
   dann unten statt rechts daneben. Es ist ein Bedienelement, kein weiterer Chip. */
.saveform { display: inline-flex; align-items: center; gap: 8px;
            flex-basis: 100%; margin-left: 0; }
/* eigenes Styling, NICHT .ainp — das lebt nur im <style> von admin.php */
.savename {
    width: 170px; font-size: 12px; padding: 6px 9px;
    background: var(--news-bg); border: 1px solid var(--news-line); border-radius: var(--news-r-s);
    color: var(--news-bright); font-family: var(--news-font); outline: none;
}
.savename:focus { border-color: var(--news-accent); }

/* Schnellfilter der Suchseite: drei Selects direkt unter dem Feld */
.quickfilter { display: flex; flex-wrap: wrap; align-items: center; gap: 18px; margin: 14px 0 22px; }
.quickfilter label { display: inline-flex; align-items: center; gap: 8px;
                     font-size: 11px; letter-spacing: 0.6px; text-transform: uppercase; color: var(--news-dim); }
.quickfilter select {
    font-family: var(--news-font); font-size: 13px; text-transform: none; letter-spacing: 0;
    background: var(--news-bg-dark); color: var(--news-text);
    border: 1px solid var(--news-line); border-radius: var(--news-r-s); padding: 5px 8px; outline: none; cursor: pointer;
}
.quickfilter select:hover, .quickfilter select:focus { border-color: var(--news-accent); color: var(--news-bright); }
/* Mehrfachauswahl: <details>-Klappmenue mit Checkboxen statt eines <select>.
   Gleiche Mechanik wie das "more"-Menue der Navigation. */
.qdrop { position: relative; }
/* Der Knopf sieht aus wie das <select> daneben — nur so wirken beide Achsen
   als dasselbe Bedienelement (Vorbild: .msel-btn der MDB-Suchmaske). */
.qgroup { display: inline-flex; align-items: center; gap: 8px; }
.qdrop > summary {
    list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 8px;
    font-family: var(--news-font); font-size: 13px; text-transform: none; letter-spacing: 0;
    background: var(--news-bg-dark); color: var(--news-text);
    border: 1px solid var(--news-line); border-radius: var(--news-r-s); padding: 5px 8px; min-width: 130px;
}
.qdrop > summary::-webkit-details-marker { display: none; }
.qdrop > summary::after { content: "▾"; color: var(--news-dim); font-size: 11px; }
.qdrop[open] > summary, .qdrop > summary:hover { border-color: var(--news-accent); color: var(--news-bright); }
.qlab { font-size: 11px; letter-spacing: 0.6px; text-transform: uppercase; color: var(--news-dim); }
.qmenu { position: absolute; z-index: 60; top: calc(100% + 6px); left: 0;
         min-width: 240px; max-height: 320px; overflow-y: auto;
         background: var(--news-panel); border: 1px solid var(--news-line);
         border-radius: var(--news-r-l); padding: 8px; display: flex; flex-direction: column; gap: 2px; }
.qmenu label { display: flex; align-items: center; gap: 9px; font-size: 13px; color: var(--news-text);
               padding: 5px 7px; border-radius: var(--news-r-xs); cursor: pointer; white-space: nowrap; }
.qmenu label:hover { background: var(--news-bg-dark); color: var(--news-bright); }
.qmenu input[type="checkbox"] { accent-color: var(--news-accent); cursor: pointer; }
.qn { margin-left: auto; color: var(--news-dim); font-size: 11px; }
.qtoggles { display: inline-flex; align-items: center; gap: 16px; }
.qtoggle { cursor: pointer; }
.qtoggle input { cursor: pointer; }
.qtoggle:hover { color: var(--news-text); }

/* Aktive Filter als Chips unter der Kopfzeile */
.fchips { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: -12px 0 20px; }
.fchip {
    display: inline-flex; align-items: center; gap: 8px; text-decoration: none;
    font-size: 12px; color: var(--news-accent); background: var(--news-accent-fill);
    border: 1px solid var(--news-accent-line); border-radius: var(--news-r-xs); padding: 3px 8px;
}
.fchip:hover { background: var(--news-accent); color: var(--news-on-accent); }
.fchip .x { opacity: 0.7; }
/* SEIT DEM 04.08.2026 GIBT ES NUR NOCH EINEN .fclear, den in der Filterzeile - das
   "clear all" unter den Chips ist an dem Tag entfallen, weil es exakt dasselbe tat
   (siehe den Grabstein in results.php). Die Grundregel bleibt trotzdem stehen: sie
   traegt das text-decoration, das die Regel unten nicht wiederholt. Ihr :hover ist
   dagegen tot, weil .quickfilter .fclear:hover es schlaegt - stehengelassen, damit
   ein zweiter Fundort nicht ohne Hover dasteht, falls je wieder einer dazukommt. */
.fclear { font-size: 12px; color: var(--news-dim); text-decoration: none; }
.fclear:hover { color: var(--news-accent); }
/* DAS "clear" DER FILTERZEILE IST DER MARKER, DASS UEBERHAUPT GEFILTERT WIRD
   (04.08.2026, Roger: "dass ein filter eingestellt ist (wird ja mit gespeichert) kann
   man manchmal schlecht sehen, daher bitte das clear etwas groesser und inverse").
   ES STAND SCHON IMMER NUR DANN DA ($hasFilter in results.php), seine blosse
   Anwesenheit WAR also bereits die Aussage - sie war nur zu leise dafuer: 12px in
   --news-dim, neben zwei Auswahlfeldern und einem Haekchen. Und weil die Feed-Seiten
   ihre Filter MERKEN (settings.view_filters, je Ansicht), trifft man den Zustand auch
   Tage spaeter wieder an, ohne ihn eingestellt zu haben.
   INVERS IST DIE SPRACHE, DIE DAS THEME SCHON SPRICHT: wo der CRT einen Bernsteinblock
   setzt, setzt das E-Ink einen Tintenblock - der aktive Nav-Eintrag und die NEW-Pille
   sind genau das, und --news-on-accent gibt es dafuer seit dem ersten Tag. Damit
   stimmt es in BEIDEN Themes, ohne einen eigenen Theme-Block.
   NUR IN DER ZEILE, nicht am "clear all" unter den Chips: dieselbe Klasse, andere
   Aufgabe (dort raeumt man alles weg, hier ist es die Anzeige). Zwei gefuellte
   Bloecke uebereinander waeren zwei Rufe statt eines. Getrennt ueber den VORFAHREN
   und nicht ueber eine neue Klasse - das Markup muss dafuer nichts wissen. */
.quickfilter .fclear {
    font-size: 13px; padding: 3px 10px; border-radius: var(--news-r-s);
    background: var(--news-accent); color: var(--news-on-accent);
}
.quickfilter .fclear:hover { background: var(--news-bright); color: var(--news-bg); }

/* --- TOP-THEMEN-Band: Reiter wie im Admin, aber OHNE Seitenneuladen.
       Versteckte Radiobuttons + <label>; :checked ~ schaltet die Tafeln um.
       Kein JS, gleiche Linie wie die <details>-Klappelemente. --- */
/* KEIN eigener Unterrand mehr (28.07.2026): Die 26px stammen aus der Zeit, als
   das Band im Feed ueber dem Kachelraster sass und den Abstand selbst setzen
   musste. Auf dem Dashboard ist es ein Baustein unter anderen — den Abstand
   macht dort die 16px-Luecke des Rasters, und beides zusammen war zu viel. */
/* DAS BAND STEHT UEBER DEM RASTER (30.07.2026, Roger: "breite und hoehe sind fix,
   breite ist volle buehnenbreite, die hoehe ist fest eingestellt so dass die 5
   meldungen reinpassen, das grid beginnt erst unter dem top-band ... und somit
   nicht schiebbar").
   .dashband ist der Rahmen dafuer: ein Block VOR .dashgrid, volle Breite von selbst.
   Er traegt KEINE .dashmod-Klasse, und genau daran haengt alles Weitere — Ziehen,
   Verschieben und Groessenziehen suchen .dashmod, das Band ist damit ohne eine
   einzige Ausnahme im Skript unbeweglich. Nur das X bleibt.
   VORGESCHICHTE, damit niemand es zurueckbaut: Das Band war der einzige Baustein mit
   inhaltsabhaengiger Hoehe und hat als einziges das 175px-Bandraster gebrochen. Zwei
   Reparaturen IM Raster sind gescheitert (Zeilenspannen rissen Loecher; Aufrunden per
   ResizeObserver war Mechanik gegen ein Symptom). Draussen stellt sich die Frage nicht. */
.dashband { position: relative; margin-top: 16px; }
.topband { margin: 0; border: 1px solid var(--news-line); border-radius: var(--news-r-l); }
/* FESTE INHALTSHOEHE, damit "fix" auch fix ist: Ohne sie richtete sich das Band nach
   dem laengsten Anriss des gerade offenen Reiters — ein Reiterwechsel aenderte die
   Hoehe der ganzen Seite. 340px tragen fuenf Meldungen samt "all N"-Zeile.
   Dazu die Klemmung der Schlagzeilen weiter unten: Erst beides zusammen macht die
   Hoehe unabhaengig vom Text.
   DIE HOEHE HAENGT AM AUFMACHER, NICHT AN DER LISTE (berichtigt 30.07.2026). Vorher
   stand 340px auch an .tside — und weil die linke Spalte aus .ttopic PLUS Aufmacher
   besteht (rund 372px), war die rechte 32px kuerzer als die Rasterzeile. Sichtbar
   wurde das an zwei Stellen, die Roger beide angestrichen hat: Die Trennlinie ist
   der linke Rahmen von .tside und endete deshalb zu frueh, und margin-top:auto an
   der "all N"-Zeile schob sie nur an das Ende dieser 340px statt an den Rand.
   Ohne eigene Hoehe DEHNT SICH .tside auf die Zeile (align-items: stretch) — Linie
   bis unten, Link buendig damit, beides ohne eine zusaetzliche Regel. Die Hoehe
   bleibt fest, weil der Aufmacher sie setzt. */
.dashband .tlead { min-height: 0; height: 340px; }
.dashband .tside { overflow: hidden; }
.topband > .ttog { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
.ttabs { display: flex; align-items: flex-end; flex-wrap: wrap; gap: 6px;
         padding: 10px 14px 0; border-bottom: 1px solid var(--news-line); }
/* IM DASHBOARD RECHTS PLATZ FUER DIE WERKZEUGE (Roger, 29.07.2026: "Button
   ueberlagert Inhalt", und "es ist auch nicht in jedem Widget").
   Das Band ist der einzige Baustein OHNE Kopfzeile — sein Inhalt beginnt ganz oben,
   und die Reiter liefen bis 14px an den Rand, also unter das X und den Griff. Die
   Bausteine mit Filterzeile haben dieselbe Aussparung laengst (.dashfilter), nur
   das Band kam ohne sie aus, solange es im Feed lief und keine Werkzeuge hatte.
   72px = 2 Knoepfe zu 24 + 3 Luecken zu 8, dieselbe Rechnung wie dort.
   NUR im Baustein: Im Feed/Stream gibt es die Werkzeuge nicht, dort waere die
   Aussparung eine Luecke ohne Grund. */
.dashmod .ttabs { padding-right: 72px; }
.ttitle { font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
          color: var(--news-dim); margin-right: 10px; padding-bottom: 11px; }
.ttab { padding: 7px 14px; font-size: 13px; color: var(--news-dim); cursor: pointer;
        border: 1px solid transparent; border-bottom: none;
        border-radius: var(--news-r-s) var(--news-r-s) 0 0; margin-bottom: -1px; }
.ttab:hover { color: var(--news-text); }
.tcount { color: var(--news-green); font-size: 11px; }
.tpane { display: none; gap: 22px; padding: 18px;
         grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
/* Bis zu drei Themen — aktiver Reiter und zugehoerige Tafel. */
#tp0:checked ~ .ttabs .ttab:nth-of-type(1),
#tp1:checked ~ .ttabs .ttab:nth-of-type(2),
#tp2:checked ~ .ttabs .ttab:nth-of-type(3) {
    color: var(--news-bright); border-color: var(--news-line); background: var(--news-bg);
}
/* Der Reiter eines "breaking"-Themas traegt den Rahmen in Phosphor-Gruen —
   dieselbe Bedeutung wie die Pille darunter. Inaktiv reicht die Schriftfarbe. */
.ttab.breaking { color: var(--news-green); }
#tp0:checked ~ .ttabs .ttab:nth-of-type(1).breaking,
#tp1:checked ~ .ttabs .ttab:nth-of-type(2).breaking,
#tp2:checked ~ .ttabs .ttab:nth-of-type(3).breaking {
    border-color: var(--news-green); box-shadow: 0 -2px 12px var(--news-green-glow);
}
#tp0:checked ~ .tpanes > .tpane:nth-child(1),
#tp1:checked ~ .tpanes > .tpane:nth-child(2),
#tp2:checked ~ .tpanes > .tpane:nth-child(3) { display: grid; }

.tlead-wrap { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
/* Der von der KI formulierte Themensatz — steht ueber dem Aufmacher, nicht im
   Reiter: dort ist er gekuerzt, hier vollstaendig. */
.ttopic { font-size: 13px; line-height: 1.5; color: var(--news-accent); }
.tflash {
    display: inline-block; font-size: 10px; letter-spacing: 0.8px; text-transform: uppercase;
    /* Phosphor-Gruen: "gerade passiert". War frueher mit dem NEU-Marker geteilt,
       der ist seit dem 29.07.2026 weg — hier bleibt die Farbe die Aussage. */
    color: var(--news-on-accent); background: var(--news-green); border-radius: var(--news-r-xs);
    box-shadow: 0 0 10px var(--news-green-glow);
    padding: 3px 7px; margin-right: 6px; vertical-align: 1px;
}
/* Aufmacher: Bild formatfuellend, Text unten drauf. Ohne Bild bleibt es eine
   normale Textflaeche — dann greift .hasbg nicht. */
.tlead { position: relative; display: flex; align-items: flex-end;
         min-height: 300px; min-width: 0; overflow: hidden;
         border-radius: var(--news-r-m); text-decoration: none; background: var(--news-bg-dark); }
.tbg { position: absolute; inset: 0; }
.tbg img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Nur noch die Scanlines wie auf den Kacheln. Der VERLAUF, der hier von unten
   hochlief, ist am 31.07.2026 einer Flaeche gewichen — siehe .tlead.hasbg .ttext
   ein Stueck weiter unten. */
.tbg::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background:
      repeating-linear-gradient(to bottom,
        rgba(0,0,0,var(--news-scan-strength)) 0 1px,
        rgba(0,0,0,0) 1px var(--news-scan-gap));
}
/* KEIN z-index hier (28.07.2026): positioniert reicht, um ueber dem absoluten .tbg
   zu liegen — das entscheidet die Dokumentreihenfolge. Ein z-index wuerde einen
   Stapelkontext aufmachen, und dann kaeme der Quellen-Link darin nie ueber die
   Klickflaeche .tlead-hit, egal wie hoch sein eigener z-index waere. */
.ttext { position: relative; display: flex; flex-direction: column;
         gap: 7px; min-width: 0; padding: 18px 20px; }
/* KLICKFLAECHE des Aufmachers: transparent ueber allem, damit die ganze Kachel zum
   Artikel fuehrt, ohne dass ein <a> das andere umschliesst. */
.tlead-hit { position: absolute; inset: 0; z-index: 3; }
/* Der Quellen-Link muss DARUEBER liegen, sonst frisst die Flaeche seinen Klick. */
.tmeta .nmlink { position: relative; z-index: 4; }
/* DER VERLAUF UEBER DEM AUFMACHER IST EINE FLAECHE (Roger, 31.07.2026, zuerst nur
   fuers E-Ink-Theme: "der verlauf im dashboard bitte durch eine helle durchsichtige
   flaeche ersetzen auf die der text steht", am selben Tag dann auch fuers CRT).
   WARUM DER VERLAUF FALSCH WAR: Er dunkelt nach unten ab, damit Text auf einem
   beliebigen Motiv traegt — und faerbt dabei das halbe Bild ein, um eine Textzeile
   lesbar zu machen. Eine begrenzte Flaeche tut dasselbe fuer die Stelle, an der der
   Text wirklich steht: ein Zettel, der auf dem Foto liegt, statt eines Schattens,
   der hineinlaeuft. Im E-Ink kam das Argument von der anderen Seite (dunkler Text
   auf Papier braucht eine HELLE Auflage, keine Abdunklung) — dieselbe Regel, in
   beiden Themes richtig, deshalb steht sie hier und nicht im Theme-Block.
   Die Farbe kommt aus --news-bg-rgb und folgt damit dem Theme von selbst.
   NUR BEI .hasbg: ohne Bild IST .ttext schon die Kachelflaeche, eine zweite Flaeche
   darauf waere ein Kasten im Kasten. Die Scanlines bleiben auf dem BILD (.tbg::after)
   und laufen nicht ueber den Text.
   .tlead gibt es nur in includes/topband.php und includes/modules/pinned.php, das
   hier ist also von sich aus aufs Dashboard begrenzt — kein Selektor noetig. */
.tlead.hasbg .ttext {
    background: var(--news-sheet);
    border-radius: var(--news-r-xs);
    margin: 0 12px 12px;
    padding: 14px 16px;
}
.tlead:not(.hasbg) .ttext { padding: 0; }
.tmeta { font-size: 11px; color: var(--news-dim); }
.thl { font-size: 19px; line-height: 1.3; color: var(--news-bright); }
.tlead:hover .thl { color: var(--news-accent); }
.tsnip { font-size: 12px; color: var(--news-dim); line-height: 1.45; }

.tside { display: flex; flex-direction: column; gap: 11px; min-width: 0;
         border-left: 1px solid var(--news-line); padding-left: 18px; }
/* Nebeneintrag: die Schlagzeile IST der Link und zieht ihre Klickflaeche per
   ::after ueber die ganze Zeile — so bleibt die Zeile komplett anklickbar, ohne
   dass der Quellen-Link in der Meta-Zeile in einem <a> steckt. */
.titem { display: block; position: relative; }
/* ZWEI ZEILEN, DANN ELLIPSE (30.07.2026). Vorher lief eine lange Schlagzeile auf
   drei oder vier Zeilen und schob die Liste — bei fester Bandhoehe fiel dann die
   letzte Meldung oder die "all N"-Zeile heraus. Dieselbe Technik wie im
   Durchscroller (.dshl) und in der Titelzeile des Bildsliders: Die Zeilenhoehe einer
   Liste muss berechenbar sein, sonst kann man keine feste Hoehe dafuer angeben. */
.titem .tit { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
              overflow: hidden;
              font-size: 13px; line-height: 1.35;
              color: var(--news-text); text-decoration: none; }
.titem .tit::after { content: ""; position: absolute; inset: 0; }
.titem:hover .tit { color: var(--news-accent); }
/* DER LISTENKASTEN DES "pinned"-BAUSTEINS (03.08.2026). Er nimmt, was nach der
   "all N"-Zeile uebrig ist - so fuellt die Liste jede Hoehe, ohne dass irgendwo eine
   Zeilenzahl steht. Zieht Roger den Baustein hoeher, erscheinen weitere Zeilen
   sofort: Ziehen aendert nur --mh am Element, die Seite wird nicht neu gebaut, und
   eine gerechnete Zahl waere danach falsch.
   min-height: 0, sonst weigert sich ein Flex-Kind, unter seine Inhaltshoehe zu
   schrumpfen, und der Kasten waechst ueber den Baustein hinaus.
   NUR DIE RECHTE SPALTE, NIE DAS BILD (Roger: "aber nur die rechte seite soll
   scrollen nicht das bild natuerlich"): dieser Kasten steht ausschliesslich in
   .tside, und der Aufmacher ist .tlead in der linken Spalte - die beiden sind
   Geschwister im 2fr/1fr-Raster und teilen nichts als die Zeilenhoehe.
   NUR HIER: das Themenband teilt sich .tside mit diesem Baustein, nimmt seine Hoehe
   aber vom Inhalt - dort gibt es nichts zu scrollen, und die Regel greift nicht,
   weil topband.php diesen Kasten nicht ausgibt.
   SIE SCROLLT, STATT ABZUSCHNEIDEN (Roger, 03.08.2026: "der pinned passt eigentlich,
   nur bei einer kleineren groesse schneidet er falsch, wuerde es was einfacher machen
   wenn wir die rechte seite scrollbar machen? vertikal"). Ja - und es macht die Sache
   kleiner, nicht groesser: mit overflow-y: auto ist die Frage "wie viele Zeilen passen"
   gar nicht mehr zu beantworten noetig. Abgeschnitten wurde vorher bei jeder Hoehe, die
   kein Vielfaches einer Zeilenhoehe ist; jetzt holt man die angeschnittene Zeile
   einfach herunter.
   DAS MUSTER GIBT ES SCHON: .dashlatest-list macht genau dasselbe (flex 1 1 auto,
   min-height 0, overflow-y auto) - der Latest-Baustein scrollt seit dem 28.07. in
   seiner Hoehe, und "das ist, wofuer h da ist".
   KEINE RADFALLE: die gefuerchtete Konstruktion ist overflow AUF EINER Achse hidden
   und auf der anderen auto (.dsstrip, 31.07.) - dort verschluckt die Box das Rad fuer
   eine Richtung, in die sie sich nicht bewegen kann. Hier ist die scrollbare Achse
   genau die des Rades, das ist der Normalfall und gewollt. */
.tliste { display: flex; flex-direction: column; gap: 11px; flex: 1 1 auto;
          min-height: 0; overflow-y: auto; overflow-x: hidden; }
.tall { margin-top: auto; padding-top: 4px; font-size: 12px;
        color: var(--news-dim); text-decoration: none; }
.tall:hover { color: var(--news-accent); }
@media (max-width: 900px) {
    .tpane { grid-template-columns: 1fr; }
    .tside { border-left: none; padding-left: 0;
             border-top: 1px solid var(--news-line); padding-top: 14px; }
}
@media (max-width: 640px) {
    .feedhead { flex-direction: column; gap: 10px; }
    .quickfilter { flex-direction: column; align-items: flex-start; gap: 10px; }
}
/* BAND-Raster. EIN BAND IST 175px - so nennt Roger es, und so heisst es seit dem
   03.08.2026 auch hier. Vorher hiess die ZEILE "Band" und die 175px "halbes Band";
   das hat einen ganzen Abend Aneinandervorbeireden gekostet ("ich hatte letztens
   gelernt dass es keine halben baender gibt, daher nenne ich das jetzt auch ein
   band"). Die Kacheltypen zaehlen genau diese Baender: 1x2 war 1 Spalte x 2 Baender.
   DIE ZEILE IST 3 BAENDER HOCH (3*175 + 2*16 = 557px), seit 03.08.2026, vorher 2
   (366px) - Roger: "aus 1x2 mach 1x3, also immer ein band hoeher alles". Innen
   bekommt das BILD ein Band und der TEXT zwei ("bei drei baendern wuerde ich sagen
   1 band bild, 2 band text"), was rund 11 Anrisszeilen statt 3 ergibt.
   JEDE KACHEL FUELLT GENAU EINE ZEILE, keine springt darueber. Die Kachel OHNE Bild
   bleibt EIN Band ("die lassen wir so"), also stapeln sich jetzt DREI davon zu einer
   Zeile statt zwei: 175 + 16 + 175 + 16 + 175 = 557. Die Buendelung steht in
   cards.php, das Stapel-CSS musste nichts lernen - eine Flex-Spalte mit gap 16
   traegt drei genauso wie zwei. */
.newsgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
            grid-auto-rows: calc(var(--news-baender) * var(--news-band) + (var(--news-baender) - 1) * 16px);
            gap: 16px; grid-auto-flow: dense; }
.ncard { display: flex; flex-direction: column; grid-row: span 1; background: var(--news-bg-dark); border: 1px solid var(--news-line); border-radius: var(--news-r-xl); overflow: hidden; text-decoration: none; color: inherit; transition: border-color 0.12s ease, box-shadow 0.12s ease; }
.ncard:hover { border-color: var(--news-line-hover); box-shadow: 0 0 16px var(--news-accent-fill); }
/* DAS BILD IST GENAU EIN BAND HOCH (03.08.2026). Es hatte `flex: 1 1 auto` und nahm
   sich damit alles, was der Text uebrig liess - bei einer 366px-Zeile ergab das rund
   160px Bild. Mit der 3-Band-Zeile haette dieselbe Regel die ganzen 191px zusaetzlich
   ins BILD gelegt und den Text bei drei Zeilen stehen lassen; gewollt war das
   Gegenteil. Jetzt steht die Bildhoehe fest und der Text bekommt den Rest.
   2x2s UEBERSCHREIBT DAS WEITER: dort liegt das Bild seitlich, `flex` steuert also
   die BREITE (44 %) und nicht die Hoehe - zwei Klassen schlagen die eine hier.
   Stream und Flip setzen ihre eigenen Werte, beide bleiben unberuehrt. */
.nthumb { position: relative; display: block; min-height: 0; background: var(--news-img-bg); overflow: hidden;
          flex: 0 0 calc(var(--news-baender-bild) * var(--news-band) + (var(--news-baender-bild) - 1) * 16px); }
/* Das Bild oeffnet die Lightbox, nicht den Artikel — der Cursor sagt es vorher an. */
.nthumb[data-full] { cursor: zoom-in; }
.nthumb img { width: 100%; height: 100%; object-fit: cover; display: block; position: relative; }
/* PLATZHALTER FUER ARTIKEL OHNE BILD (03.08.2026, Roger: "sollen wir den bildlosen
   einen platzhalter geben?"). Er ersetzt die halbe Textkachel 1x1 samt Stapelei -
   seitdem ist jede Kachel gleich gebaut, so wie es Flipboard auch macht.
   ER LIEGT IMMER IM MARKUP, auch unter einem vorhandenen Bild: das <img> steht
   spaeter im Dokument und ist positioniert, deckt ihn also zu. Faellt das Bild aus,
   raeumt das onerror nur das <img> weg und der Platzhalter kommt zum Vorschein -
   ohne Klassen umzuschreiben.
   KEIN ERFUNDENES FOTO, sondern eine Flaeche mit dem Bild-Symbol und dem
   Quellennamen: dieser Host hat weder GD noch Imagick, und ein generisches Motiv
   waere eine Behauptung ueber den Artikel. Das Symbol kommt aus dem eigenen
   Icon-Satz (beide Familien, also auch im E-Ink), nicht von einem fremden Server -
   MDB verlinkt fuer denselben Zweck auf TMDBs Glyph.
   DIE GROESSE IST FEST UND SKALIERT NICHT MIT: ein 32px-Zeichen auf eine 242px hohe
   Flaeche gezogen waere ein verwaschener Riese. Es bleibt klein und mittig, der Rest
   ist Flaeche. */
.nleer { position: absolute; inset: 0; display: flex; flex-direction: column;
         align-items: center; justify-content: center; gap: 10px;
         color: var(--news-dim); font-size: 28px; opacity: 0.7; }
/* EIGENE KLASSE FUER DEN QUELLENNAMEN, kein blosses <i> - newsIcon() gibt seine
   Symbole als <i> aus (zwei je Icon, eins je Theme), eine Regel auf `i` haette also
   das Zeichen mitgeschrumpft. Beim ersten Versuch genau so passiert. */
.nleer .nleerq { font-size: 12px; letter-spacing: 0.06em; }
/* DER AUSSCHNITT SITZT EIN VIERTEL HOEHER ALS DIE MITTE (Roger, 29.07.2026).
   Anlass: Auf den Kacheln wurden Koepfe abgeschnitten — bei Pressefotos steht das
   Motiv fast immer im oberen Drittel, die Bildmitte ist dort selten der Inhalt.
   WARUM 25% UND NICHT 0%: "ganz oben" schneidet bei Landschaften und Totalen das
   Wichtige unten weg und laesst Koepfe trotzdem knapp am Rand kleben. Ein Viertel
   trifft beide Faelle besser als jedes Extrem — eine Motiverkennung haben wir nicht,
   und eine Bild-API waere fuer einen Nachrichtenticker aus dem Verhaeltnis.
   Gilt fuer ALLE beschnittenen Bilder: Karten, Themen-Band, Bildslider, Bilderwand.
   Die Lightbox bleibt aussen vor, die zeigt mit `contain` ohnehin das ganze Bild. */
.tbg img, .nthumb img, .dsslide .dsimg img, .galtile img { object-position: 50% 25%; }
.nthumb::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: var(--news-accent); opacity: 0.55; z-index: 1; }
/* CRT-Scanlines ueber den Bildern (TEST 27.07.2026: erstmal auf ALLEN).
   Zum Drehen reichen die zwei Variablen in :root; ganz raus = diese Regel loeschen.
   pointer-events: none — die Kachel ist ein Link, der Klick muss durchgehen. */
.nthumb::after {
    content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
    background: repeating-linear-gradient(
        to bottom,
        rgba(0,0,0,var(--news-scan-strength)) 0 1px,
        rgba(0,0,0,0) 1px var(--news-scan-gap)
    );
}
/* DER TEXTKASTEN NIMMT DEN REST DER ZEILE, seit die Zeile drei Baender hoch ist
   (03.08.2026). Er stand auf `flex: 0 0 auto`, war also genau so hoch wie sein
   Inhalt - deshalb konnte includes/snipfit.php dort nie mehr Zeilen ausrechnen als
   ohnehin standen. Mit einer festen Hoehe misst das Skript echten freien Platz und
   macht aus drei Anrisszeilen rund elf. min-height: 0, sonst weigert sich ein
   Flex-Kind, unter seine Inhaltshoehe zu schrumpfen. */
.nbody { padding: 12px 14px 14px; display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.nmeta { font-size: 12px; color: var(--news-dim); margin-bottom: 7px; }
.nmeta .amb { color: var(--news-accent); }
/* Quelle und Kategorie fuehren in die Suche (search.php?q=*&src=/cats=). Dasselbe
   dezente Muster wie .term-clear und .tall: im Ruhezustand nicht von der Meta-Zeile
   zu unterscheiden, auf Hover bernstein. Die Zeile soll eine ANGABE bleiben und
   nicht zu einer Knopfleiste werden — auffindbar ist es ueber den Mauszeiger. */
.nmlink { color: inherit; text-decoration: none; }
.nmlink:hover { color: var(--news-accent); }
/* HIER STAND .isnew und .grn — der NEU-Marker und sein Zaehler (29.07.2026
   entfallen, siehe cards.php). --news-green bleibt: .tflash benutzt es. */
.nhl { font-size: 15px; font-weight: 500; line-height: 1.35; color: var(--news-bright); margin: 0 0 8px; text-decoration: none;
       display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.nhl:hover { color: var(--news-accent); }
/* DIE ZEILENZAHL WIRD GEMESSEN, NICHT GERATEN (03.08.2026, Roger: "hier ist der text
   noch beschnitten obwohl platz ist", dazu "ich wuerde immer die dashboard mechanik
   nutzen. evtl. bekommen wir ja auch mal groessere boxen in den normalen seiten").
   includes/snipfit.php rechnet je Kasten aus, wie viele Zeilen hineinpassen.
   DIE 3 IST DER RUECKFALL ohne JavaScript, nicht die Vorgabe.
   WO ES ETWAS AENDERT: nur auf 2x2s (Bild LINKS, Text rechts). Dort ist das Bild auf
   44 % Breite festgelegt, die freie Hoehe unter dem Text verfiel - gerechnet 158px,
   also acht weitere Zeilen. Bei allen anderen Typen liegt das Bild OBEN, .nthumb
   nimmt sich per flex alles, was .nbody uebrig laesst, und .nbody ist so hoch wie
   sein Inhalt: dort liefert dieselbe Rechnung genau die Zeilen, die schon dastehen.
   Mehr Text hiesse dort ein kleineres BILD - eine Gestaltungsfrage, keine Rechnung. */
/* DER ANRISS NIMMT DEN FREIEN PLATZ, NICHT DER RAND DARUNTER (03.08.2026, sofort nach
   Issue #34 nachgereicht). Mit `margin-top: auto` allein an der Knopfzeile war der Text
   ueberall auf EINE Zeile geschrumpft, und der Grund ist unangenehm genau:
   includes/snipfit.php rechnet den freien Platz aus, indem es die Aussenhoehe der
   anderen Kinder abzieht - und getComputedStyle loest ein auto-Margin in PIXEL auf.
   Das Skript zog also den ganzen freien Platz als "Rand" ab und kam auf eine Zeile.
   MIT flex: 1 1 auto AM ANRISS geht der Platz an den Anriss, das auto-Margin darunter
   loest sich zu 0 auf, und die Rechnung stimmt wieder. Die Knopfzeile steht trotzdem
   unten - sie folgt ja direkt auf einen Kasten, der bis dorthin reicht.
   BEIDES ZUSAMMEN, nicht eines davon: ohne das auto-Margin saesse die Zeile bei einer
   Meldung OHNE Anriss wieder am Text (dann waechst nichts), ohne das flex bliebe die
   Fehlrechnung. */
/* Der Deckel wie bei .dssnip - die lange Begruendung steht dort. Die Kachel hat
   dieselbe Bauweise und damit denselben Fehler, auch wenn er hier seltener auffaellt:
   ihre Kaesten sind niedriger, der Rest geht oefter auf. */
.nsnip { font-size: 12.5px; --zeile: 1.5; line-height: var(--zeile); color: var(--news-text); margin: 0 0 8px; opacity: 0.85;
         flex: 1 1 auto; min-height: 0;
         display: -webkit-box; -webkit-line-clamp: var(--snip-zeilen, 3);
         -webkit-box-orient: vertical; overflow: hidden;
         max-height: calc(var(--snip-zeilen, 3) * var(--zeile) * 1em); }
/* SENKRECHT AUSGERICHTET, und das war der Rest des Knopf-Problems (03.08.2026,
   Roger: "in den favs haengen die buttons noch unterschiedlich").
   OHNE align-items GILT stretch, und in dieser Zeile stehen ungleiche Dinge: zwei
   Links, ein Knopf, ein <form> um den Loeschknopf (auf der Favoritenseite) und der
   Text "saved 3d ago". Das FORM ist dabei ein eigenes Flex-Element, das sich ueber
   die ganze Zeilenhoehe streckt, waehrend sein Knopf oben darin klebt - und der
   Text bringt seine eigene Zeilenhoehe mit. Damit haengt jedes Element woanders.
   DAS FORM WIRD SELBST ZU EINEM FLEX-KASTEN, sonst gibt es innen nichts, das seinen
   Knopf mittig haelt. Es traegt display:inline im Attribut - das gewinnt gegen eine
   Klassenregel, deshalb steht die Rueckname hier mit derselben Spezifitaet plus
   Kontext. */
/* DIE KNOPFZEILE ANKERT UNTEN, NICHT AM TEXT (03.08.2026, Issue #34, Roger mit einem
   Screenshot dreier Kacheln, deren Briefknoepfe auf drei verschiedenen Hoehen stehen:
   "Action Icons immer am unteren Ende der Kachel", dann "ueberall unten ankern").
   Sie hing am Anriss, und der ist je Meldung verschieden lang - bei n-tv sass der
   Knopf 130px tiefer als bei WDR daneben. Eine Reihe gleich hoher Kacheln mit
   Knoepfen auf ungleicher Hoehe liest sich als Unruhe, nicht als Rhythmus.
   ES GEHT ERST SEIT HEUTE ABEND: margin-top: auto braucht freien Platz zum
   Verteilen, und .nbody war bis zur 3-Band-Zeile so hoch wie sein Inhalt. Jetzt hat
   der Kasten eine feste Hoehe (flex: 1 1 auto), also gibt es etwas zu verteilen.
   Flipboard macht es genauso - das war in Rogers Vergleichsbild zu sehen. */
.nactions { display: flex; align-items: center; gap: 8px; margin-top: auto; padding-top: 8px; }
.nactions form { display: flex !important; align-items: center; }
/* WAEHREND includes/snipfit.php MISST, SIND DIESE AUTO-RAENDER STILLGELEGT
   (04.08.2026). Sie und die Rechnung kommen sonst nicht mehr zusammen, seit der
   Anriss einen Deckel hat:
     ohne Deckel  nimmt der Anriss den freien Platz, das auto-Margin wird 0, die
                  Rechnung sieht den Platz - so lief es bis heute.
     mit Deckel   bleiben unter dem Anriss bis zu 16px uebrig, das auto-Margin
                  schluckt sie (gemessen: 13,8px), und die Rechnung zieht sie als
                  "Rand" ab. Vier Zeilen fielen so beim naechsten Lauf auf drei.
   SCHLIMMER ALS DIE EINE ZEILE WAERE DER STILLSTAND: waechst der Baustein, geht der
   ganze Zuwachs ins auto-Margin, und die Zahl bliebe fuer immer stehen.
   NUR FUER DIE DAUER DER MESSUNG, im selben Frame wieder weg - man sieht es nie. Die
   Knopfzeile bleibt also unten verankert (Issue #34), auch mit Deckel: die uebrigen
   Pixel liegen ueber ihr, nicht unter ihr.
   DIE KLASSE SITZT AM KASTEN (.nbody/.dstext), NICHT AN <html>, und das ist kein
   Geschmack: an der Wurzel haengt in snipfit.php ein MutationObserver auf dem
   class-Attribut, der bei jedem Themenwechsel neu rechnet. Die erste Fassung setzte
   die Klasse genau dort - Beobachter weckt Messung, Messung setzt Klasse, Seite laedt
   endlos. Roger hat es live gesehen, keine drei Minuten nach dem Hochladen. */
.snipmessung .nactions, .snipmessung .dsactions { margin-top: 0; }
.feedempty { color: var(--news-dim); font-size: 14px; padding: 20px 0; }
/* FILTER VOR DER SUCHE — eigene Zeile unter dem Suchfeld, aber IM selben Formular
   (.searchbox hat flex-wrap, die 100% schieben die Zeile also sauber darunter).
   Nur auf der leeren Suchseite; sobald Treffer da sind, uebernimmt .quickfilter. */
.searchpre {
    flex: 0 0 100%; display: flex; align-items: center; gap: 14px;
    font-size: 14px; padding-top: 2px;
}
.searchpre > .qlab { color: var(--news-accent); }
.searchpre .qmenu { max-height: 320px; overflow-y: auto; }

.searchsyntax { color: var(--news-dim); font-size: 12px; padding: 0 0 20px; line-height: 1.9; }
/* DIE BEISPIELE ALS RASTER (29.07.2026). Der Hinweis stand vorher als Fliesstext
   mit Trennpunkten; mit zehn Formen statt vier liest das niemand mehr.
   max-content fuer die erste Spalte: '(a OR b) c' und '*' sind zu verschieden
   lang fuer eine feste Breite. */
.ssgrid { display: grid; grid-template-columns: max-content 1fr; gap: 5px 14px;
          max-width: 660px; margin-bottom: 10px; }
.ssgrid > span { padding-top: 1px; }
.ssex { color: var(--news-text); }
.searchsyntax code { color: var(--news-text); background: var(--news-bg-dark);
                     border: 1px solid var(--news-line); border-radius: var(--news-r-xs); padding: 2px 6px; }
.feedempty .amb { color: var(--news-accent); }

/* --- KACHELTYPEN. Benannt nach ihrer GROESSE IN RASTERBAENDERN (30.07.2026,
   Roger: "einheitliche bezeichnungen fuer die boxen ... 2x1 3x1 1x1"), und ein Band
   ist dieselbe Einheit wie im Dashboard: 175px. Eine normale Kachel ist also 1 Spalte
   x 2 BAENDER, nicht x 1 Zeile — die 366px Zeilenhoehe des Rasters SIND 2*175+16.
       3x2   drei Spalten                      (frueher "hero")
       2x2   zwei Spalten, Bild oben           (frueher "wide")
       2x2s  zwei Spalten, Bild seitlich       (frueher "split")
       1x2   Standard, keine eigene Klasse     (frueher "card")
       1x1   halbe Hoehe, ohne Bild            (frueher "text")
   DAS b IM KLASSENNAMEN ist kein Geschmack: Ein CSS-Klassenname darf nicht mit einer
   Ziffer beginnen, ".3x2" waere ungueltig. Der ctype in PHP heisst dagegen glatt
   "3x2" — so tippt Roger es auch in die Box-Regeln, cards.php setzt das b davor.
   1x2 BEKOMMT KEINE KLASSE, weil es der Normalfall ist: .ncard allein IST 1x2. */
.ncard.b2x2 { grid-column: span 2; }                    /* 2 breit */
.ncard.b3x2, .ncard.b3x2t { grid-column: span 3; }      /* 3 breit */
.ncard.b3x2 .nhl, .ncard.b3x2t .nhl { font-size: 20px; -webkit-line-clamp: 3; }
/* Auch hier die gemessene Zahl mit derselben 3 als Rueckfall - sonst haette diese
   Regel (zwei Klassen) die Basisregel geschlagen und der Aufmacher waere der einzige
   Kacheltyp ohne die Rechnung. Heute aendert sie dort nichts (Bild oben), aber
   "evtl. bekommen wir ja auch mal groessere boxen". */
.ncard.b3x2 .nsnip, .ncard.b3x2t .nsnip { -webkit-line-clamp: var(--snip-zeilen, 3); }
/* 3x2t = derselbe Aufmacher, aber MARKIERT (Roger, 30.07.2026: "so wie der 3x2
   aber mit orangenem rahmen"). Gedacht fuer die Box-Regeln des Admins: eine
   Eilmeldung soll sich nicht nur durch GROESSE abheben, sondern auch durch Farbe —
   gross allein ist auf einer Seite mit mehreren grossen Kacheln kein Signal.
   Das t steht fuer top und ist Teil des ctype, den Roger in die Regel tippt.
   DER AUTOMATISMUS VERGIBT DIESEN TYP NIE: newsAssignCardTypes() kennt nur 3x2.
   3x2t entsteht ausschliesslich durch eine Regel — die Farbe ist eine Aussage, und
   die soll ein Mensch treffen, nicht eine Punktzahl.
   !important ist NICHT noetig, obwohl .ncard:hover die Rahmenfarbe setzt: Diese
   Regel steht spaeter in der Datei UND ist mit zwei Klassen spezifischer. Die
   Hover-Zeile darunter haelt die Farbe fest, sonst wuerde der Rahmen beim
   Ueberfahren grau. */
/* DER SONDERRAHMEN HAENGT AN DER REGEL, NICHT AN DER GROESSE (31.07.2026, Roger:
   "macht ja sinn der spezialrahmen immer dann dran ist"). Er stand vorher auf
   .b3x2t, also nur auf der groessten Kachel - eine Regel `Neuromancer -> 1x1` machte
   den Artikel damit zwar "seinen", liess ihn aber aussehen wie jeden anderen Text.
   Die Klasse setzt cards.php aus dem Kennzeichen 'boxregel'.
   3x2t IST DAMIT UEBERFLUESSIG und nur noch als Altlast gueltig: es ist dasselbe wie
   3x2, den Rahmen bringt die Regel mit. */
.ncard.nregel { border-color: var(--news-accent); }
/* IM E-INK REICHT DER RAHMEN NICHT (Roger, 31.07.2026: "hast du eine idee wie man
   die tops (die mit dem rahmen) in der eink theme noch besser kenntlich machen
   kann?" - Vorschlag von ihm: "der titel in inverse?").
   WARUM ER DORT SCHWAECHER IST: im CRT ist der Rahmen BERNSTEIN in einer sonst
   farblosen Umgebung, er sticht durch den FARBTON heraus. Im E-Ink wird der Akzent
   zur Tinte, der Rahmen also zu einer dunklen Linie - der Wertunterschied ist
   riesig (#100f0e gegen Papier #dedcd8), aber es ist EIN Pixel, und ein Pixel liest
   sich als Struktur, nicht als Betonung. Auf Papier gibt es keinen Farbton zum
   Herausstechen; es gibt FLAECHE und GEWICHT.
   DIE UMKEHRUNG IST DIE SPRACHE, DIE DAS THEME SCHON SPRICHT: wo im CRT ein
   Bernsteinblock steht, steht hier ein Tinteblock - aktiver Nav-Punkt und
   NEU-Pille sind genau das, und --news-on-accent existiert seit dem ersten Tag
   dafuer.
   DREI STELLEN MUSSTEN MIT, sonst verschwindet Text auf Text:
     - :hover faerbt die Schlagzeile sonst auf den Akzent, also Tinte auf Tinte;
     - der Blockcursor auf dem ersten Buchstaben (.hkbox) ist eine getoente Flaeche
       mit Schein - auf der Tinte ein Fleck, also hier ohne Fuellung;
     - .nsnip bleibt unberuehrt: zwei umgekehrte Bloecke uebereinander waeren keine
       Hervorhebung mehr, sondern eine schwarze Kachel. */
.theme-eink .ncard.nregel .nhl {
    background: var(--news-accent); color: var(--news-on-accent);
    padding: 3px 7px; margin: 0 -7px; border-radius: var(--news-r-xs);
}
.theme-eink .ncard.nregel .nhl:hover { color: var(--news-on-accent); }
.theme-eink .ncard.nregel .nhl .hkbox { background: none; box-shadow: none; }
.ncard.nregel:hover { border-color: var(--news-accent);
                     box-shadow: 0 0 18px var(--news-accent-halo); }
/* split = 2 breit: HOHES Bild LINKS, Text RECHTS */
.ncard.b2x2s { grid-column: span 2; flex-direction: row; }
.ncard.b2x2s .nthumb { width: 44%; flex: 0 0 44%; align-self: stretch; }
.ncard.b2x2s .nbody { flex: 1 1 auto; }
/* GRABSTEIN: 1x1 UND .nstack WERDEN NICHT MEHR ERZEUGT (03.08.2026). Ein Artikel
   ohne Bild bekommt seit dem Platzhalter oben dieselbe Kachel wie jeder andere;
   newsAssignCardTypes vergibt den Typ nicht mehr, cards.php stapelt nicht mehr und
   newsBoxRules nimmt ihn nicht mehr an. Die Regeln bleiben vorerst stehen, damit ein
   noch im Umlauf befindliches Teilstueck aus dem AJAX-Nachladen nicht nackt
   dasteht - sie koennen bei der naechsten Aufraeumrunde weg, zusammen mit den
   b1x1-Regeln in den Stream- und Flip-Bloecken.
   WAS SIE MAL WAREN: halbes Band (175px), Amber-Kante oben, Knoepfe unten. */
.ncard.b1x1 { position: relative; }
.ncard.b1x1::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: var(--news-accent); opacity: 0.55; }
.ncard.b1x1 .nbody { flex: 1 1 auto; padding-top: 15px; }
.ncard.b1x1 .nhl { -webkit-line-clamp: 3; }
.ncard.b1x1 .nsnip { display: none; }
.ncard.b1x1 .nactions { margin-top: auto; }
/* Zwei Text-Kacheln teilen sich EIN Band (175 + 16 + 175 = 366) */
.nstack { grid-row: span 1; display: flex; flex-direction: column; gap: 16px; }
.nstack .ncard.b1x1 { flex: 0 0 var(--news-band); height: var(--news-band); box-sizing: border-box; }
/* --- FLIP-ANSICHT: eine Meldung je BILDSCHIRM, senkrecht gewischt ------------
   Roger, 30.07.2026: "eine reine wischansicht, also ein artikel pro seite der
   vertikal gewischt wird (funktioniert dann auch super aufm handy) ... die breite
   ist dann die maxbreite und hoehe auch, also was sichtbar ist".

   WIE DIE ANDEREN BEIDEN: reines CSS ueber demselben Markup. cards.php,
   results.php und die Kacheltypen bleiben unangetastet — nur die Klasse an <html>
   entscheidet. Das ist der Grund, warum eine dritte Ansicht so billig ist.

   ES WIRD NICHT GESCROLLT. Die erste Fassung war ein Scroller mit scroll-snap;
   warum daraus ein Blaetterer wurde, steht weiter unten bei .ncard.
   DIE HOEHE KOMMT VOM VIEWPORT, nicht aus dem Layout und nicht aus dem Skript:
   position:fixed; inset:0 auf dem Raster, 100% x 100% auf der Kachel. Siehe die
   Begruendung bei .vm-flip .newsgrid. Ein --flip-h gibt es nicht mehr. */
/* KOPFZEILE UND FILTERZEILE WEG (Roger, 30.07.2026: "den kopf bitte rausnehmen").
   In einer Wischansicht ist der Bildschirm der Artikel; eine Zaehl- und Filterzeile
   darueber nimmt Hoehe und gehoert zu einer Listenansicht, nicht hierher. Die Filter
   selbst bleiben natuerlich wirksam — sie sind nur nicht sichtbar, und ein Wechsel
   zurueck auf Raster oder Stream zeigt sie sofort wieder. */
.vm-flip .feedhead,
.vm-flip .quickfilter,
.vm-flip .pager,
.vm-flip .footercontainer,
.vm-flip .totop { display: none; }
/* NICHTS DARF SCROLLEN (Roger, 30.07.2026: "er schiebt es noch immer hoch und
   raus"). Der Grund war nicht die Kachelhoehe, sondern alles, was UNTER ihr stand:
   Fussleiste, Blaetterleiste und der Hochscroll-Knopf machten die Seite laenger als
   den Bildschirm, und der Browser scrollte. In einer Wischansicht ist Scrollen aber
   genau die Geste, die etwas anderes bedeutet — die beiden kamen sich in die Quere.
   Jetzt ist die Seite exakt bildschirmhoch und die Kachel das Einzige darin.
   Roger wollte auch die Blaetterknoepfe weg ("den ganzen footer auch raus mit den
   blaettericons auch"): Wischen, Pfeiltasten und Mausrad reichen. */
html.vm-flip, .vm-flip body { overflow: hidden; height: 100%; }
/* DAS RASTER LIEGT FEST UEBER DEM BILDSCHIRM (Roger, 30.07.2026: "kannst du ich
   einfach das raster vergesse.. volle breite volle hoehe").
   DIE HOEHE AUS DEM LAYOUT ABZULEITEN FUNKTIONIERT HIER NICHT - vier Anlaeufe:
   fester Abzug, gemessene Rasterlage, Nachkorrektur an der Unterkante, zuletzt eine
   Flex-Kette vom .maincontainer abwaerts. Der letzte gab der Kachel ein Drittel des
   Bildschirms, weil ein Prozentwert sich zu NULL aufloest, sobald eine Ebene
   darueber keine feste Hoehe hat. Dazu kommt, was eine Rechnung im Skript nie
   wissen kann: .contentcontainer bringt margin-bottom: 40px mit, und auf
   Touchgeraeten stellt eine Medienabfrage .maincontainer auf height:auto.
   position:fixed haengt an gar nichts - der Viewport IST die Bezugsgroesse. Damit
   kann keine Regel weiter oben die Hoehe kaputtmachen.
   DER KOPF BLEIBT DARUEBER BEDIENBAR (z-index): Ohne ihn kaeme man aus der Ansicht
   nicht mehr heraus - der Umschalter sitzt in der Nav-Zeile. */
/* DER KOPF FAEHRT HOCH UND KOMMT AUF KNOPFDRUCK ZURUECK (Roger, 01.08.2026,
   Issue #19). Bis dahin holte ihn eine unsichtbare HOVER-ZONE ueber dem oberen
   Viertel des Schirms (Issue #4) - und genau die war der Fehler: ein Viertel ist so
   viel Flaeche, dass der Zeiger beim Blaettern staendig darin liegt, und der Kopf
   erschien ungefragt. Ein Knopf wird gedrueckt oder nicht.
   NICHT GEMESSEN wurde, warum er dabei nur KURZ erschien und nicht stehenblieb, wie
   Hover es tut. Der Ausloeser war die Zone, und die ist weg; falls das Flackern je
   wiederkommt, ist die Frage damit noch offen.
   ER WIRD GESCHOBEN, NICHT AUSGEBLENDET. Eine erste Fassung nahm display: none, und
   damit hatte er keine FLAECHE mehr - es gab nichts, worueber man fahren koennte.
   translateY laesst ihn im Dokument stehen und trotzdem verschwinden.
   position: fixed, weil er sonst als erstes Element die Seite oben um seine Hoehe
   verkuerzt, waehrend er selbst nach oben herausgeschoben ist.
   ACHTUNG, DAS IST DER NOTAUSGANG: .viewswitch UND die Theme-Palette sitzen IM Kopf
   (geprueft, #headercontainer schliesst weit darunter). Ohne ihn gibt es keinen Weg
   aus Flip heraus. Wer hier etwas aendert, prueft ZUERST, dass man wieder
   herauskommt. */
/* AUCH AUF DEM HANDY OHNE FLIP (02.08.2026, Roger auf dem Geraet: "die navigation
   klappt aber nicht ein und die seite ist nicht scrollbar"). Das war der bis dahin
   als "noch nicht beurteilt" gefuehrte Nebeneffekt aus Issue #18, und er ist
   groesser als er klang: .handy macht aus der Navigation ein einspaltiges PANEL,
   das Einfahren hing aber allein an .vm-flip. Auf der Wetterseite - der ersten
   Seite mit .handy und OHNE Flip, die man scrollen will - stand der Kopf damit
   dauerhaft offen. GEMESSEN bei 375 px: .headernavigation 1680 px hoch, der
   Wetterkopf begann bei y = 1723, und weil .headercontainer als Flex-Element
   flex: 0 0 auto ist, blieb fuer .contentscroller eine Hoehe von NULL bei 1638 px
   Inhalt. Nicht "unschoen", sondern nicht bedienbar.
   position: fixed LOEST BEIDES: der Kopf ist aus dem Fluss, verkuerzt die Seite
   nicht mehr um seine eigene Hoehe, und der Inhalt beginnt oben.
   EIN PANEL, DAS DEN GANZEN SCHIRM FUELLT, MUSS ZUKLAPPBAR SEIN - das gilt in
   jeder Ansicht, nicht nur in Flip. Dashboard und Admin bekommen es damit auch,
   und das ist die Absicht: EIN Kopfverhalten auf dem Handy statt zwei.
   DER NOTAUSGANG BLEIBT GEPRUEFT (siehe oben): der Knopf ist der Weg zurueck, und
   footer.php musste dafuer mit - sein Handler stieg bis heute bei !flip() aus. */
.vm-flip .headercontainer {
    position: fixed; top: 0; left: 0; right: 0; z-index: 20;
    transform: translateY(-100%); transition: transform 0.2s ease;
}
/* DER KNOPF (Markup in header.php) SITZT IM KOPF, auf top: 100% also direkt unter
   dessen Unterkante - und faehrt damit von allein mit: ist der Kopf um seine eigene
   Hoehe nach oben geschoben, steht der Knopf exakt am oberen Bildschirmrand; ist der
   Kopf unten, steht er darunter. Eine Bewegung, keine zweite Animation.
   HIER DARF KEIN WEITERES transform DAZU - translateX(-50%) ist das Mitten, und ein
   zweiter Wert wuerde es ersetzen, nicht ergaenzen.
   Er liegt im Stapelkontext des Kopfes (z-index 20) und damit ueber dem Raster (5);
   abgeschnitten wird er nicht, .headercontainer hat kein overflow.
   .btn.icon SIND ZWEI KLASSEN, also braucht das Ausblenden auch zwei - sonst zeigte
   sich der Knopf auf jeder Seite ausserhalb von Flip. */
.kopfknopf.btn { display: none; }
.vm-flip .kopfknopf.btn {
    display: inline-flex; position: absolute; top: 100%; left: 50%;
    transform: translateX(-50%);
}
/* DER ZUSTAND STEHT AN <html>, NICHT AM KNOPF: dieselbe Klasse schaltet den Kopf und
   das Zeichen, die beiden koennen also nicht auseinanderlaufen. Beide Zeichen stehen
   im Markup, wie bei den zwei Icon-Saetzen - Umschalten kostet keinen Seitenaufbau. */
.kopfknopf .kk-auf,
.kopfknopf .kk-x { display: none; }
.vm-flip.kopf-an .kopfknopf .kk-zu  { display: none; }
.vm-flip.kopf-an .kopfknopf .kk-auf { display: inline; }
/* AUF DEM HANDY IST DAS OFFENE ZEICHEN EIN X (Roger, 01.08.2026: "ganz oben ueber der
   headline ein x button waere das beste"). Der Pfeil nach oben beschreibt eine
   Bewegung - am Desktop faehrt der Kopf sichtbar ein; auf dem Handy ist das Panel der
   ganze Schirm, und da heisst der Knopf schlicht "zu". */
.handy.kopf-an .kopfknopf .kk-auf { display: none; }
.handy.kopf-an .kopfknopf .kk-x   { display: inline; }
/* Gedrueckt bleibt gedrueckt (footer.php setzt .kopf-an). Ein Klick DANEBEN schliesst
   weiterhin: der zweite Weg zurueck, falls der Knopf einmal nicht getroffen wird. */
.vm-flip.kopf-an .headercontainer { transform: none; }

/* DASSELBE AUF EINEM ECHTEN TOUCHGERAET, AUCH OHNE FLIP (02.08.2026).
   ANLASS UND KORREKTUR IN EINEM: Roger sah den Kopf am Laptop einfahren und sagte
   "der soll doch bei flip einfahren". Richtig - ich hatte es an .handy gehaengt, und
   .handy heisst nur SCHMAL. Bei ihm reicht dafuer schon Browser-Zoom: 4K-Schirm,
   1,5-fach skaliert, bei 200 % Zoom sind aus 1280 echten Pixeln 640 CSS-Pixel.
   WARUM ES TROTZDEM MEHR ALS FLIP BRAUCHT: auf dem Handy macht .handy aus der
   Navigation ein Vollbild-Panel (Issue #18), und auf der Wetterseite ist Flip
   ABGESCHALTET. Ohne Einfahren stuenden dort 1680 px Navigation ueber dem Inhalt -
   gemessen. Die Bedingung ist also nicht "Flip", sondern "das Panel ist im Weg und
   es gibt keinen Mauszeiger, mit dem man daran vorbeikaeme".
   (pointer: coarse) IST EINE ANDERE ACHSE ALS DIE BREITE und dupliziert die 640
   deshalb nicht - die stehen weiterhin genau einmal, im Skript in header.php.
   Auf Rogers Geraet gemessen: coarse=true, fine=false, hover=false, 411x783. */
@media (pointer: coarse) {
    .handy .headercontainer {
        position: fixed; top: 0; left: 0; right: 0; z-index: 20;
        transform: translateY(-100%); transition: transform 0.2s ease;
    }
    .handy .kopfknopf.btn {
        display: inline-flex; position: absolute; top: 100%; left: 50%;
        transform: translateX(-50%);
    }
    .handy.kopf-an .kopfknopf .kk-zu { display: none; }
    .handy.kopf-an .headercontainer { transform: none; }
}

/* === HANDY-ANSICHT (Roger, 01.08.2026, Issue #18) ===========================
   ALLES HAENGT AN DER KLASSE .handy, NICHT AN EINER @media-ABFRAGE. Die Breite
   (640px) steht genau einmal, im Skript in header.php, das die Klasse setzt - eine
   Medienabfrage hier waere dieselbe Zahl ein zweites Mal, und zwei Zahlen driften.
   DER KOPF IST HIER KEINE LEISTE MEHR, SONDERN EIN PANEL. Das geht erst, seit er in
   Flip ein Overlay auf Knopfdruck ist (Issue #19): er nimmt der Kachel keinen Platz
   mehr weg, also darf er hoch sein - und damit loest sich das eigentliche Problem
   des Issues von selbst. Neun Eintraege nebeneinander brechen auf einem Handy
   haesslich um; neun Eintraege in einem 3x3-Raster brauchen gar keinen Umbruch, und
   Untermenues (Rogers "nicht praktikabel") ebenfalls nicht. */

/* Was auf dem Handy gar nicht erst erscheint. .term-bar nimmt drei Dinge auf
   einmal mit: Portalwechsel, Suchfeld und die Knoepfe Suche/Favoriten/Admin/Logout. */
/* ZWEI DAVON BRAUCHEN VIER KLASSEN, und das ist keine Kosmetik: die Groessenregel
   weiter unten hat DREI (.handy .headernavigation .nav-dropdown-item) und setzt
   display: flex. Mit zwei Klassen verliert das Ausblenden dagegen - genau so stand
   "manage …" trotz dieser Regel im Panel (Roger, 01.08.2026). Die Dashboard-Zeile
   kommt ueber ihr Attribut schon auf vier, die more-Zeile ueber das :not(). */
.handy .term-bar,
.handy .viewswitch,
.handy .headernavigation .navlink[data-slug="dashboard"],
.handy .navmore:not(.navsaved) > .navlink,
.handy .headernavigation .nav-dropdown-item.navmanage,
.handy .headernavigation .navsaved > .navlink,
.handy .portal-bridge,
.handy .einkregler,
.handy .navtheme > .btn { display: none; }
/* AUSGENOMMEN: DER WETTER-EINTRAG (02.08.2026, Issue #22). Seit die Orte unter ihm
   haengen, traegt er .navmore - und die Zeile darueber blendet jeden .navmore-
   Ausloeser aus, weil "more" und die Themewahl auf dem Handy nichts zu suchen haben.
   Dieser hier ist aber eine KATEGORIE und muss im Panel stehen bleiben, sonst waere
   Weather auf dem Handy gar nicht mehr erreichbar.
   VIER KLASSEN, weil die Ausblendregel ueber ihr :not() auf drei kommt - dieselbe
   Arithmetik, die diese Datei schon bei .navsaved, .navmore und "manage …" bezahlt
   hat. display: flex, damit die Kachelform der uebrigen Panel-Eintraege gilt. */
.handy .headernavigation .navorte.navmore > .navlink { display: flex; }

/* EINE SPALTE, NICHT DREI (Roger, 01.08.2026, nachdem er es am Geraet gesehen hat:
   "die navigation ist aufm handy auch zu breit ... es waere besser nur eine spalte zu
   haben"). Die Spezifikation vorher lautete 3x3; das Geraet hat entschieden.
   ZU BREIT WAR ES AUS EINEM GRUND, DER SICH WIEDERHOLEN KANN: Rasterzellen haben
   min-width: auto, also drueckt eine nicht umbrechende Beschriftung ("Movie and TV")
   die Spalte ueber ihr Drittel hinaus und schiebt das ganze Panel ueber den Rand.
   min-width: 0 an den Eintraegen nimmt dem den Boden - unabhaengig von der Spaltenzahl.
   Die Reihenfolge kommt aus dem DOM und stimmt von allein: My Feed vorn, die drei
   Eintraege aus dem more-Menue hinten. */
.handy .headernavigation {
    display: grid; grid-template-columns: 1fr; gap: 8px;
    /* Unten Platz fuer den schwebenden Kopfknopf, sonst deckt er die letzte Zeile zu.
       border-box, damit max-height weiter unten die Abstaende MITZAEHLT - es gibt in
       diesem Stylesheet kein globales border-box, nur einzelne Deklarationen. */
    box-sizing: border-box; padding: 10px 12px 60px;
}
/* DIE EINTRAEGE AUS DEN KLAPPMENUES MUESSEN INS RASTER, und display: contents ist der
   Griff dafuer: der Behaelter verschwindet als Kasten, seine Kinder werden Kinder des
   Rasters. Genau das macht .nstack in Flip mit den gestapelten Textkacheln.
   VIER KLASSEN, WEIL DIE GRUNDREGEL DREI HAT: `.nav-item-wrapper.has-dropdown
   .nav-dropdown-menu` gewinnt sonst - dieselbe Falle wie bei .navsaved und .navmore,
   die in diesem Stylesheet schon zweimal Zeit gekostet hat. */
.handy .headernavigation .navmore,
.handy .headernavigation .navtheme,
.handy .headernavigation .nav-item-wrapper.navmore .nav-dropdown-menu,
.handy .headernavigation .nav-item-wrapper.navtheme .nav-dropdown-menu {
    display: contents;
}
/* ALLE EINTRAEGE GLEICH GROSS UEBER DIE GANZE BREITE (Roger, 01.08.2026). Eine
   einzige Regel fuer Kategorien, gespeicherte Suchen und Themes - jede Ausnahme
   waere sofort eine Zeile, die anders hoch ist.
   46px: fingergross, unter etwa 44 trifft man daneben.
   min-width: 0 IST DAS GEGENMITTEL GEGEN "zu breit" - ohne das haelt eine Rasterzelle
   ihre Mindestbreite am Inhalt fest und schiebt das Panel ueber den Schirmrand.
   Ein Name, der nicht passt, wird abgeschnitten statt umzubrechen: eine umbrechende
   Zeile waere hoeher als die anderen, und genau das soll hier nicht passieren. */
.handy .headernavigation .navlink,
.handy .headernavigation .nav-dropdown-item {
    display: flex; align-items: center; justify-content: center; text-align: center;
    min-width: 0; min-height: 46px; padding: 8px 10px; font-size: 15px; line-height: 1.2;
    border: 1px solid var(--news-line); border-radius: var(--news-r-m);
    background: var(--news-bg-dark);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    /* ZWEI ERBSTUECKE AUS DER LEISTE, beide gemessen, nachdem Roger sah, dass die
       Knoepfe ungleich sind ("die buttons sind nicht gleich breit und hoch"):
       margin: .navlink traegt margin-right: 14px fuer den Abstand in der Leiste - in
         einer Spalte macht das die Zeile schlicht 14px schmaler als die aus den
         Klappmenues, die keinen haben.
       box-sizing: .nav-dropdown-item ist border-box, .navlink nicht. Dieselbe
         min-height bedeutete damit zweierlei - 46px inklusive Polster gegen 46 plus
         Polster plus Rahmen, also 46 gegen 64.
       BEIDES MUSS HIER STEHEN, weil die Grundregeln je EINE Klasse haben und sonst
       nur zufaellig gleich wirken. */
    margin: 0; box-sizing: border-box;
}
/* DER AKTIVE EINTRAG MUSS SEINE FUELLUNG BEHALTEN (Roger, 01.08.2026: "der aktive
   button ist jedoch schwarz"). Die Regel darueber setzt background fuer JEDEN Eintrag
   und schlaegt mit drei Klassen die eine von .navlink_active - die Fuellung war weg,
   die dunkle Schrift dafuer blieb: dunkel auf dunkel. Vier Klassen holen beides
   zurueck. Der Hotkey-Buchstabe wird von allein dunkel, weil .hkbox keine eigene
   Farbe setzt - dieselbe Mechanik wie in der Leiste am Desktop. */
.handy .headernavigation .navlink.navlink_active,
.handy .headernavigation .nav-dropdown-item.active {
    background: var(--news-accent); border-color: var(--news-accent);
    color: var(--news-on-accent);
}
/* ZWISCHENUEBERSCHRIFTEN (header.php, .navrubrik). Sie stehen immer im Markup und
   sind ueberall ausser auf dem Handy unsichtbar. KEINE Knoepfe - "alle gleich gross"
   gilt fuer die Eintraege, eine Ueberschrift darf und soll anders aussehen. */
.navrubrik { display: none; }
.handy .headernavigation .navrubrik {
    display: block; padding: 14px 4px 2px;
    font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--news-dim);
}

/* AUF DEM HANDY SCROLLT DAS DOKUMENT, KEIN KASTEN DARIN (02.08.2026, Roger: "das
   scrollt noch immer nicht aufm handy" - der dritte Anlauf, und diesmal wird die
   Konstruktion umgestellt statt geflickt).
   DER FEHLER IST EINE HALBE UMSTELLUNG, und sie steht seit dem 26.07. so da: die
   Touch-Medienabfrage setzt .maincontainer auf display: block und height: auto,
   nimmt aber `position: absolute` aus der Grundregel NICHT zurueck. Der Container
   bleibt damit AUS DEM FLUSS - der Body ist 0 Pixel hoch (gemessen), und ob das
   Dokument trotzdem scrollt, haengt daran, wie die Engine den Ueberlauf eines
   absolut positionierten Kastens in die Scrollflaeche des Viewports einrechnet.
   Auf dem Desktop faellt das nie auf, weil dort .contentscroller selbst scrollt.
   ES WAR VORHER UNSICHTBAR, weil auf dem Handy bis zum 02.08. IMMER Flip lief, und
   Flip scrollt gar nicht (position: fixed, overflow: hidden). Die Wetterkategorie
   ist die erste Seite ohne Flip, die man auf dem Handy scrollen will - sie hat den
   Fehler nicht gemacht, sie hat ihn aufgedeckt.
   position: static STELLT DEN FLUSS WIEDER HER: der Body bekommt eine echte Hoehe
   und das Dokument scrollt, wie es das auf jeder gewoehnlichen Seite tut. Kein
   verschachtelter Scroller mehr, an dem eine Geste haengenbleiben kann.
   AN .handy GEHAENGT, nicht an die Medienabfrage: die Breite steht genau einmal (im
   Skript in header.php), und die Abfrage gilt auch fuer Tablets und Touch-Laptops,
   deren Flex-Aufbau heute funktioniert. */
.handy .maincontainer { position: static; display: block; height: auto; }
.handy .contentscroller { overflow: visible; height: auto; }
/* DAS PANEL DARF NICHT UNTER DEN SCHIRMRAND RAGEN, und genau das tat es: .term-header
   bringt 20px oben und 16px unten mit, die zur Hoehe der Navigation DAZUKOMMEN - die
   letzte Zeile lag damit ausserhalb. Auf dem Handy sind die 40px seitliche Polsterung
   ohnehin zu viel; die 16px hier sind derselbe Wert, den der Abzug unten benutzt. */
.handy .term-header { padding: 8px 8px 8px; }
/* DAS PANEL MUSS SCROLLEN KOENNEN. Neun Kacheln, dreizehn gespeicherte Suchen und die
   Themewahl sind zusammen hoeher als ein Handyschirm. dvh statt vh, weil vh auf
   Mobilbrowsern die eingeklappte Adressleiste mitrechnet und damit zu gross ist.
   DAS SCROLLEN SITZT AUF DER NAVIGATION, NICHT AUF .headercontainer, und das ist
   nicht kosmetisch (Roger, 01.08.2026: "aufm handy fehlt der knopf zur navigation").
   Ein overflow ausser `visible` macht das Element zum CLIPPING-KONTEXT - und der
   Kopfknopf haengt per top: 100% AUSSERHALB des Kopfes. Er war also nicht weg,
   sondern abgeschnitten. Am Desktop faellt das nie auf: dort hat der Kopf kein
   overflow. WER HIER JE overflow AUF .headercontainer SETZT, nimmt den Knopf mit -
   und damit in Flip den Notausgang.
   HIER STAND EIN ABZUG VON 64px, damit unter dem Panel Platz fuer den Knopf bleibt.
   ER HAT NICHT GEREICHT und die Rechnung war der Fehler (Roger: "wie kann ich die
   navigation schliessen?"): ohne border-box zaehlt max-height die Innenabstaende
   NICHT mit, dazu kamen die des Kopfes - das Panel wurde hoeher als der Schirm und
   der Knopf lag darunter. Jetzt border-box und volle Hoehe, und der Knopf haengt im
   offenen Zustand gar nicht mehr am Panel (siehe unten). Keine Rechnung mehr. */
.handy .headernavigation {
    max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px);
    overflow-y: auto;
}
/* GESCHLOSSEN schwebt er am oberen Rand: fingergross statt 24px, das ist die
   Reservegroesse .icon.large, die es fuer genau solche Faelle schon gibt. */
.handy:not(.kopf-an) .kopfknopf.btn { width: 42px; height: 42px; font-size: 17px; }
/* AUF TOUCH KLEBT DER HOVER-ZUSTAND (Roger, 01.08.2026: "wenn man die navigation ueber
   den button schliesst ist der navi-oeffnen button auf einmal schwarz"). Nach dem
   Tippen behaelt der Knopf :hover, und .btn:hover fuellt mit dem Akzent - im E-Ink ist
   der Akzent TINTE, der Knopf also ein schwarzer Block. Genau diese Falle hat schon
   die alte Hover-Zone in @media (hover: hover) gezwungen.
   HIER BLEIBT ER DESHALB IN RUHESTELLUNG. Was er gerade tut, sagt sein ZEICHEN - der
   Pfeil beziehungsweise das X -, dafuer braucht es keine Fuellung. Nur dieser Knopf:
   die Kategoriezeilen navigieren weg, dort kann nichts kleben. */
.handy .kopfknopf.btn:hover,
.handy .kopfknopf.btn:focus {
    background: var(--news-bg-dark); border-color: var(--news-line);
    color: var(--news-text);
}
/* OFFEN IST ER EINE ZEILE WIE JEDE ANDERE, und zwar die ERSTE, ueber der ersten
   Ueberschrift (Roger, 01.08.2026: "ganz oben ueber der headline ein x button waere
   das beste"). Zwei Fassungen davor liegen dazwischen und beide sind aus dem Weg:
   fest unten am Schirm schwebend (ein zweites Bedienmuster fuer denselben Knopf), und
   als LETZTE Zeile - da war er praktisch unerreichbar, weil das Panel um die
   Polsterung von .term-header unter den Schirmrand ragte.
   OBEN BRAUCHT SCHLIESSEN KEIN SCROLLEN. Das ist der Grund, nicht der Geschmack.
   transform MUSS ZURUECK: das translateX(-50%) mittet den schwebenden Knopf und
   wuerde die stehende Zeile um ihre halbe Breite nach links schieben. */
.handy.kopf-an .kopfknopf.btn {
    position: static; transform: none; width: auto; height: auto;
    min-height: 46px; padding: 8px 10px; font-size: 17px;
    border-radius: var(--news-r-m); box-sizing: border-box;
}
/* GESCHLOSSEN DARF DAS PANEL NICHT SCROLLEN, und das ist kein Schoenheitsfehler: ein
   overflow ausser `visible` schneidet den Knopf ab, der auf top: 100% AUSSERHALB
   haengt - genau der Fehler von vorhin, eine Ebene tiefer. Zu ist der Kopf ohnehin
   vom Schirm geschoben, also braucht er weder Deckel noch Rollbalken; ohne max-height
   ist er genau so hoch wie sein Inhalt und verschwindet damit vollstaendig. */
.handy:not(.kopf-an) .headernavigation { max-height: none; overflow: visible; }
/* AUF DEM HANDY STEHT DER TEXT HOEHER (Roger, 01.08.2026: "den text etwas hoeher
   setzen, der sitzt zu weit unten"). NUR HIER, nicht global: die 20 % sind Rogers
   eigene Korrektur am grossen Schirm ("wir sollten 80% bild 20% text"), und die bleibt
   stehen. Auf einem Handy ist derselbe Anteil weniger absolute Hoehe, und Schlagzeile
   plus Anriss ruecken damit an die Unterkante.
   30 % IST SEINE MARKIERUNG, nicht geschaetzt: im Screenshot liegt die rote Linie bei
   knapp einem Drittel der Schirmhoehe. Eine Zahl, beide Haelften - das Bild endet bei
   bottom: var(--flip-strip). */
.handy.vm-flip .newsgrid { --flip-strip: 30%; }
/* UND DIE SCHRIFT DARIN ETWAS GROESSER (Roger, 01.08.2026, am echten Geraet). Sie
   stand auf 17/13px aus der Medienabfrage fuer schmale Schirme - eine Groesse, die
   fuer den 20-%-Streifen gerechnet war. Mit 30 % ist der Platz da: zwei Zeilen
   Schlagzeile zu 20px und zwei Zeilen Anriss zu 15px brauchen rund 100px von etwa
   215px nutzbarer Streifenhoehe.
   FUENF KLASSEN, weil die Grundregel und die Medienabfrage je VIER haben. Die
   Zeilenbegrenzung bleibt bei 2 + 2 - sie ist der Grund, warum das ueberhaupt passt,
   und mehr Text ist Issue #5, nicht diese Schraube. */
.handy.vm-flip .newsgrid .ncard .nhl   { font-size: 20px; }
.handy.vm-flip .newsgrid .ncard .nsnip { font-size: 15px; }
.handy.vm-flip .newsgrid .ncard .nmeta { font-size: 13px; }
.vm-flip .newsgrid {
    /* grid-auto-rows zuruecknehmen: in Flip gibt es kein Raster, und die 366px
       Zeilenhoehe wuerde die feste Kachel wieder daran binden. */
    display: block; grid-auto-rows: auto;
    position: fixed; inset: 0; z-index: 5;   /* der Viewport ist das Mass, siehe oben */
    margin: 0; padding: 0;
    /* DOPPELT SO HOCH WIE ZUERST (Roger, 30.07.2026: "der textbalken doppelt so
       hoch"). 92px trugen Meta, Schlagzeile und Anriss nur, solange alle drei
       einzeilig blieben - im Screenshot war der Anriss unten abgeschnitten. Mit
       184px passt eine ZWEIZEILIGE Schlagzeile, und das ist der Normalfall. */
    /* 60 % BILD, 40 % TEXT (Roger, 31.07.2026, Issue #4: "Bild text Verhaeltnis
       aendern: 60% Bild 40% Text (damit der Text nicht so weit unten steht)").
       EIN ANTEIL, KEINE PIXEL - und das ist der eigentliche Gewinn: 184px waren auf
       einem Handy ein Fuenftel des Schirms und auf einem grossen Monitor ein
       Zwanzigstel, derselbe Wert also je nach Geraet etwas anderes. Ein Prozentsatz
       bedeutet ueberall dasselbe, weshalb die eigene Zahl fuer schmale Schirme
       ersatzlos entfallen konnte.
       ES REICHT EINE ZAHL, weil beide Haelften daran haengen: das Bild endet bei
       bottom: var(--flip-strip), der Streifen ist genau so hoch.
       ZUERST WAREN ES 40 %, seit demselben Tag 20 % (Roger, nachdem er es gesehen
       hat: "wir sollten 80% bild 20% text"). Das Bild ist der Inhalt dieser
       Ansicht - der Streifen sagt nur, WAS man sieht. */
    --flip-strip: 20%;
}
/* NUR EINE KACHEL IST DA. Roger, 30.07.2026: "ich meinte nicht dass das ein endlos
   scroller ist, sondern wirklich artikel (nur dieser) wisch -> neuer artikel".
   Die erste Fassung war ein Scroller mit scroll-snap — sieht aehnlich aus, ist aber
   etwas anderes: Dort liegen alle Kacheln untereinander und man bewegt sich durch
   sie hindurch. Hier gibt es zu jedem Zeitpunkt EINE, und Wischen tauscht sie.
   Der Unterschied ist nicht nur Gefuehl: Er loest das Bildproblem. Ein Blaetterer
   kann eine Kachel UEBERSPRINGEN, bevor sie zu sehen war; ein Scroller kann sie nur
   nachtraeglich ausblenden, und dann verschwindet sie unter dem Finger. */
.vm-flip .newsgrid .ncard { display: none; }
.vm-flip .newsgrid .ncard.flip-an {
    /* Alle Kacheltypen werden hier gleich: 3x2, 2x2, 2x2s, 1x2, 1x1 sagen etwas
       ueber ein RASTER, und in Flip gibt es keins. Die Spannweiten muessen deshalb
       ausdruecklich zurueckgesetzt werden. */
    display: flex; grid-column: auto; grid-row: auto;
    width: 100%; height: 100%;               /* = der Viewport, siehe .vm-flip .newsgrid */
    margin: 0; border: none; border-radius: 0; box-sizing: border-box;
    position: relative; flex-direction: column;
    animation: flipRein 0.18s ease-out;
}
/* Ein kurzer Aufblendschritt, damit ein Wisch als Wechsel LESBAR ist. Ohne ihn
   springt der Inhalt hart um und man ist sich nicht sicher, ob etwas passiert ist. */
@keyframes flipRein { from { opacity: 0; transform: translateY(10px); } }
.vm-flip .newsgrid.rueckwaerts .ncard.flip-an { animation-name: flipReinAbwaerts; }
@keyframes flipReinAbwaerts { from { opacity: 0; transform: translateY(-10px); } }
/* HIER STANDEN DIE REGELN DER BEDIENLEISTE (.flipbar) - Zaehler und zwei
   Blaetterknoepfe, am 30.07.2026 gebaut und am selben Tag wieder entfernt.
   Roger: "die steuerelemente unten brauchen wir nicht, koennen auch raus."
   Der Grund ist nicht Geschmack: In einer Vollbild-Wischansicht ist JEDER Pixel
   unter der Kachel einer, der der Kachel fehlt - und die Leiste wiederholte nur,
   was Finger, Rad und Pfeiltasten ohnehin tun.
   Kacheln, deren Bild zu klein ist, werden vom Blaetterer uebersprungen - die
   Klasse dafuer setzt das Skript in footer.php. In den anderen Ansichten bleiben
   sie sichtbar: dort stoert ein kleines Bild nicht, es ist ja klein dargestellt. */
/* Der Stapel ist in Flip gegenstandslos: seine zwei halben Kacheln sind hier zwei
   einzelne Artikel. display:contents loest ihn auf, ohne cards.php anzufassen —
   und wichtiger: dadurch sind seine Kinder fuer den Blaetterer normale Kacheln. */
.vm-flip .newsgrid .nstack { display: contents; }
/* TEXT NEBEN DEM BILD, NICHT DARAUF (Roger, 30.07.2026: "der verlauf faengt zu frueh
   an und nimmt zu viel vom bild weg, ich wuerde den text eher ausserhalb des bilds
   drunter setzen in einem schmalen streifen").
   Die erste Fassung kopierte den Aufmacher des Themen-Bandes: Bild formatfuellend,
   Verlauf darueber, Text drin. Auf einer Kachel von 300px traegt das; auf einem
   ganzen Bildschirm frisst derselbe Verlauf das untere Drittel des Motivs. Jetzt
   endet das Bild ueber dem Streifen, und beides bleibt ganz. */
.vm-flip .newsgrid .ncard .nthumb {
    position: absolute; left: 0; right: 0; top: 0; bottom: var(--flip-strip);
    width: auto; height: auto; flex: none; z-index: 0;
}
.vm-flip .newsgrid .ncard .nbody {
    position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
    height: var(--flip-strip); box-sizing: border-box;
    /* TEXT DIREKT UNTER DAS BILD, NICHT MITTIG (Roger, 31.07.2026). Zentriert
       haengt die Schlagzeile bei kurzem Anriss in der Luft und der Abstand zum Bild
       waechst genau dann, wenn weniger dasteht - der Text soll aber am Bild kleben,
       zu dem er gehoert. flex-start heisst: oben buendig, und was uebrig bleibt,
       bleibt unten uebrig. */
    display: flex; flex-direction: column; justify-content: flex-start; gap: 3px;
    padding: 10px 22px; background: var(--news-bg-dark);
    border-top: 1px solid var(--news-line);
}
/* ZWEI PLUS ZWEI ZEILEN, gerechnet gegen den 20-%-Streifen: auf einem 800px hohen
   Handyschirm sind das 160px, abzueglich Polsterung 140px nutzbar. Schlagzeile
   2 x 27px plus Anriss 2 x 19px plus Abstand sind rund 96px - es passt mit Luft.
   Bei drei und vier Zeilen (kurz so, als der Streifen noch 40 % hatte) wuerde der
   Text auf kleinen Schirmen mitten in einer Zeile abgeschnitten, weil die Hoehe
   fest ist. Mehr Anriss ist Issue #5, nicht diese Stellschraube. */
.vm-flip .newsgrid .ncard .nhl { font-size: 21px; line-height: 1.3; -webkit-line-clamp: 2; }
/* CLAMP UND DECKEL AUS EINER ZAHL (Issue #37, 04.08.2026, Roger mit einer Flip-Kachel,
   in der die Ellipse mitten im Text sass und darunter noch eine Zeile weiterlief).
   Seit dem Deckel von gestern rechnet .nsnip ZWEI Eigenschaften aus derselben Zahl -
   den Clamp und die max-height. Diese Regel ueberschrieb nur den CLAMP: die Ellipse kam
   nach Zeile 2, der Deckel stand aber weiter auf --snip-zeilen und liess bis Zeile 3
   malen. Derselbe Fehlertyp wie Issue #36 am selben Tag: eine Groesse, zwei Stellen,
   verschiedene Masse.
   EIGENE VARIABLE UND NICHT --snip-zeilen, und das ist der Kern: snipfit.php setzt
   --snip-zeilen INLINE, und ein Inline-Wert schlaegt jede Klassenregel. Haette diese
   Regel ihn nur gesetzt, waere Flip von "fest 2" auf "gemessen" umgestellt worden -
   eine dokumentierte Entscheidung, die eine Fehlerbehebung nicht nebenbei kippen darf.
   Mit --flip-zeilen bleibt die Zahl hier, und beide Eigenschaften folgen ihr.
   WARUM FLIP UEBERHAUPT EINE FESTE ZAHL HAT (unveraendert): die Kachel ist hier der
   ganze Schirm und der Textstreifen ein ANTEIL davon (--flip-strip), der Wert ist also
   gegen diesen Anteil gerechnet und nicht gegen einen gemessenen Kasten. */
.vm-flip .newsgrid .ncard .nsnip { font-size: 13px; display: -webkit-box; --flip-zeilen: 2;
    -webkit-line-clamp: var(--flip-zeilen); max-height: calc(var(--flip-zeilen) * var(--zeile) * 1em); }
/* Die Knopfleiste der Kachel wuerde den Streifen doppelt so hoch machen; in Flip
   fuehrt der Klick auf die Schlagzeile zum Artikel, das reicht. */
.vm-flip .newsgrid .ncard .nactions { display: none; }
/* DAS BILD IST HIER UNEMPFINDLICH (31.07.2026, Roger: "wenn es einfach ist link nur
   auf die headline"). Es fuellt 80 % des Schirms, das obere Viertel liegt also mitten
   darin - ein Klick aufs Bild und das Tippen fuer den Kopf waren derselbe Griff.
   pointer-events statt eines Handlers, weil es damit BEIDE Wege schliesst: den zum
   Artikel und den zur Lupe. Die Lupe ist in Flip seit dem 30.07. bewusst aus, und sie
   haette sich sonst durch die Hintertuer zurueckgeholt, sobald der Bild-Handler
   entfaellt - ihr eigener Handler haengt am document und fragt nur nach .nthumb.
   Der Weg zum Artikel ist die Schlagzeile, die ohnehin ein <a> ist.
   KEIN cursor: pointer mehr - der Zeiger darf nichts versprechen. */
.vm-flip .newsgrid .ncard .nthumb { pointer-events: none; }
/* DIE LUPE HAT EINEN EIGENEN KNOPF (Roger, 02.08.2026, Issue #20). Das Bild bleibt
   unempfindlich, denn es ist als Ziel zu gross: 80 % des Schirms, und jeder Wisch,
   der zu ruhig beginnt, waere ein Klick. Ein 42px-Knopf kann man nicht aus Versehen
   treffen - dieselbe Lehre wie bei der 25vh-Hoverzone aus Issue #4.
   DER KNOPF STEHT AUF JEDER KACHEL IM MARKUP und wird nur hier sichtbar: die
   Ansicht wechselt ohne Seitenaufbau, per PHP weggelassen fehlte er nach dem
   Umschalten (wie die zwei Icon-Saetze und die E-Ink-Regler).
   UNTEN MITTIG UEBER DEM TEXTSTREIFEN: --flip-strip IST dessen Hoehe, der Knopf
   haengt also an derselben einen Zahl wie das Bild darueber und rutscht auf dem
   Handy (30 %) automatisch mit. Damit sind es oben und unten zwei gleiche runde
   Knoepfe - der Kopfknopf und dieser.
   z-index 3, weil .ncard .nbody in Flip positioniert ist und sonst darueber liegt. */
.nlupe { display: none; }
.vm-flip .newsgrid .ncard .nlupe {
    display: inline-flex; position: absolute; z-index: 3;
    left: 50%; transform: translateX(-50%);
    bottom: calc(var(--flip-strip) + 12px);
}
/* 1x1 hat kein Bild - da gibt es nichts zu vergroessern. */
.vm-flip .newsgrid .ncard.b1x1 .nlupe { display: none; }
/* AUCH HIER KLEBT DER HOVER AUF TOUCH, dieselbe Falle wie beim Kopfknopf einen Tag
   zuvor: nach dem Tippen behaelt der Knopf :hover, .btn:hover fuellt mit dem Akzent,
   und im E-Ink IST der Akzent Tinte - der Knopf bliebe als schwarzer Block stehen,
   waehrend die Lightbox darueber liegt. Er navigiert nicht weg, kann also kleben.
   Ruhestellung wie dort; was er tut, sagt sein Zeichen. */
.handy .nlupe.btn:hover,
.handy .nlupe.btn:focus {
    background: var(--news-bg-dark); border-color: var(--news-line);
    color: var(--news-text);
}
/* 1x1 hat kein Bild — dann gehoert die ganze Flaeche dem Text, mittig. */
.vm-flip .newsgrid .ncard.b1x1 .nbody {
    position: relative; height: auto; margin: auto; border-top: none;
    background: none; padding: 30px; justify-content: center;
}
.vm-flip .newsgrid .ncard.b1x1 .nhl { font-size: 24px; -webkit-line-clamp: 4; }
/* Nur die Zahl - Clamp und Deckel haengen eine Regel hoeher daran (Issue #37). */
.vm-flip .newsgrid .ncard.b1x1 .nsnip { --flip-zeilen: 4; }
/* --flip-strip STAND HIER FRUEHER AUF 150px, weil 184px auf einem schmalen Schirm
   zu viel waren. Mit dem Anteil erledigt sich das von selbst - 40 % sind ueberall
   40 %. Schriftgrad und Polsterung bleiben, die haengen an der BREITE. */
@media (max-width: 700px) {
    .vm-flip .newsgrid .ncard .nbody { padding: 12px 16px; }
    .vm-flip .newsgrid .ncard .nhl { font-size: 17px; }
}

/* --- STREAM-ANSICHT: eine Meldung pro Zeile, zum Durchscrollen ---------------
   Die BUEHNE bleibt unveraendert (Roger, 27.07.2026) — die Kachel nimmt drei der
   fuenf Rasterspalten ein und steht zentriert. Deshalb wird die Breite aus den
   beiden Zahlen GERECHNET statt als Prozentwert geraten: --cols ist die Zahl der
   Spalten, die das Raster auf der Buehne bildet, --span die Breite einer Kachel.
   Passt die Aufteilung nicht, sind es genau diese zwei Zahlen.
   Markup und cards.php bleiben unangetastet: die Kacheltypen behalten ihre
   Klassen, hier werden nur ihre Spannen neutralisiert. */
.vm-stream .newsgrid {
    --gap:  16px;
    /* BAND = das halbe Rasterband, also die Hoehe einer Text-Kachel. Genau diese
       Einheit hat das Raster ohnehin schon: zwei Text-Kacheln plus die Luecke
       ergeben eine normale Zeile (175 + 16 + 175 = 366). */
    --band: 175px;
    --cols: 5;       /* so viele Spalten bildet das Raster auf der Buehne */
    --span: 3;       /* Kachelbreite in Spalten */
    --rows: 3;       /* Kachelhoehe in Baendern */
    --img:  2;       /* davon entfallen auf das Bild */
    grid-template-columns: minmax(0, 1fr);
    grid-auto-rows: auto;
    max-width: calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols) * var(--span) + (var(--span) - 1) * var(--gap));
    margin-left: auto; margin-right: auto;
}
/* Hoehe wie im Raster gerechnet: N Baender plus die Luecken, die zwischen ihnen
   laegen. Das Bild bekommt davon --img Baender, der Text den Rest. */
.vm-stream .newsgrid .ncard {
    grid-column: auto;
    height: calc(var(--rows) * var(--band) + (var(--rows) - 1) * var(--gap));
}
.vm-stream .newsgrid .ncard .nthumb {
    flex: 0 0 calc(var(--img) * var(--band) + (var(--img) - 1) * var(--gap));
}
/* Die Paerchen aus zwei Text-Kacheln loesen sich auf — in einer Spalte stehen sie
   ohnehin untereinander, sollen aber jede fuer sich zaehlen. */
.vm-stream .newsgrid .nstack { display: contents; }
.vm-stream .newsgrid .ncard.b3x2 .nhl,
.vm-stream .newsgrid .ncard.b3x2t .nhl,
.vm-stream .newsgrid .ncard .nhl { font-size: 20px; }
/* split trug das Bild links; hier liegen alle Kacheln gleich, sonst wechselt der
   Aufbau beim Scrollen staendig. */
.vm-stream .newsgrid .ncard.b2x2s { flex-direction: column; }
.vm-stream .newsgrid .ncard.b2x2s .nthumb { width: 100%; flex: 1 1 auto; align-self: auto; }
.vm-stream .newsgrid .ncard.b2x2s .nbody { flex: 0 0 auto; }
/* Ohne Bild braucht es keine 366px Hoehe — dafuer ist hier Platz fuer den Anriss,
   den das enge Raster ausblendet. */
.vm-stream .newsgrid .ncard.b1x1 { height: auto; min-height: 175px; }
.vm-stream .newsgrid .ncard.b1x1 .nsnip { display: -webkit-box; }
@media (max-width: 1200px) { .vm-stream .newsgrid { max-width: 100%; } }

/* --- BILD-LIGHTBOX (footer.php) ------------------------------------------------
   Liegt ueber ALLEM, auch ueber dem Zurueck-nach-oben-Knopf. Der Hintergrund ist
   dunkel statt schwarz, damit der Roehren-Look nicht bricht. */
.imglb { position: fixed; inset: 0; z-index: 2000; background: var(--news-scrim);
         display: flex; align-items: center; justify-content: center; padding: 40px; }
.imglb[hidden] { display: none; }
.imglb-stage { position: relative; width: 100%; height: 100%; display: flex;
               align-items: center; justify-content: center; }
.imglb-img { max-width: 100%; max-height: 100%; object-fit: contain; display: block;
             border-radius: var(--news-r-xl); border: 1px solid var(--news-line); }
/* DIE LUPE AM ZEIGER NUR, WENN ES ETWAS ZU VERGROESSERN GIBT (Roger, 02.08.2026:
   "koennen wir bei kleineren bildern ohne vergroesserung einfach den lupenmauszeiger
   wegmachen?"). Sie stand vorher unbedingt an .imglb-img und versprach damit auch
   dort etwas, wo das Bild schon in voller Groesse liegt. .kanngross setzt footer.php
   aus einer Messung - derselbe Schalter, der den Knopf zeigt. */
.imglb.kanngross .imglb-img { cursor: zoom-in; }
.imglb.gross .imglb-img { cursor: zoom-out; }
/* Gezoomt: echte Bildgroesse, Position kommt per transform aus dem Schwenk. */
.imglb-img.zoom { max-width: none; max-height: none; object-fit: fill;
                  position: absolute; left: 0; top: 0; border-radius: 0; cursor: zoom-out; }
.imglb-close { position: absolute; top: 18px; right: 22px; z-index: 1; }
/* HIER STAND EIN ZOOM-KNOPF (.imglb-zoom, expand/compress), fuer wenige Stunden am
   02.08.2026 - und die Begruendung fuer sein Verschwinden ist die interessantere:
   er wurde gebraucht, SOLANGE der Klick aufs Bild schloss. Seit der Klick
   vergroessert, tat er dasselbe wie die Flaeche unter ihm und lag dabei auf dem Foto
   (Roger: "der kann generell weg weil fullscreen macht ja klick jetzt").
   Was er zusaetzlich leistete - den ZUSTAND zeigen - macht jetzt der Zeiger
   (zoom-in / zoom-out), und geschlossen wird ueber das X. */
/* ORIGINALGROESSE, DER BROWSER SCHIEBT. Ohne die max-Masse rendert ein <img> in
   seiner natuerlichen Groesse; die Buehne wird zum Scroll-Container und Finger wie
   Pinch macht der Browser selbst - deshalb steht hier kein Zeilchen JavaScript.
   flex-start IST NOTWENDIG: ein zentriertes Flex-Kind, das groesser ist als sein
   Container, laesst sich ueber die ANFANGSKANTE nicht zurueckscrollen - der obere
   und linke Rand des Bildes waeren unerreichbar.
   UND margin: auto AM BILD, weil flex-start allein das kleine Bild in die ECKE legt
   (Roger, 02.08.2026, am Laptop: "da packt er das bild nur in die ecke ... das ist
   weil es nicht groesser ist"). Automatische Aussenabstaende SAUGEN den freien Platz
   auf: ist welcher da, mitten das Bild; ist keiner da - also bei Ueberlauf - werden
   sie zu 0 und die Anfangskante bleibt erreichbar. Beides mit derselben Zeile, ohne
   Messung im JavaScript und ohne `safe center`, das aeltere Browser nicht kennen. */
.imglb.gross .imglb-stage {
    overflow: auto; align-items: flex-start; justify-content: flex-start;
    touch-action: pan-x pan-y pinch-zoom;
    /* KEINE BALKEN (Roger, 02.08.2026: "da ist besser als die scrollbalken"). Das
       ZIEHEN bleibt, nur die Leisten verschwinden - auf dem Handy sind sie Zierat
       ueber dem Foto, und mit Maus schwenkt ohnehin der Zeiger. */
    scrollbar-width: none;
}
.imglb.gross .imglb-stage::-webkit-scrollbar { display: none; }
.imglb.gross .imglb-img { max-width: none; max-height: none; border-radius: 0; margin: auto; }
.imglb-load { color: var(--news-dim); font-size: 14px; }
/* Hintergrund nicht mitscrollen, solange die Lightbox offen ist */
body.imglb-open #contentscroller { overflow: hidden; }

/* responsiv */
@media (max-width: 900px) { .ncard.b3x2, .ncard.b3x2t { grid-column: span 2; } }
@media (max-width: 640px) {
    .ncard.b2x2, .ncard.b3x2, .ncard.b3x2t, .ncard.b2x2s { grid-column: span 1; }
    .ncard.b2x2s { flex-direction: column; }
    .ncard.b2x2s .nthumb { width: 100%; flex: 1 1 auto; }
    .ncard.b2x2s .nbody { flex: 0 0 auto; }
}

/* Pagination */
.pager { display: flex; align-items: center; justify-content: center; gap: 18px; margin: 30px 0 12px; }
.pager-info { font-size: 13px; color: var(--news-dim); font-family: var(--news-font); }
.btn.loadmore { min-width: 150px; }

/* --- DASHBOARD: Raster aus BAUSTEINEN (28.07.2026) -------------------------
   Dieselbe Rastersprache wie das Kachelraster: fuenf Spalten, 16px Luecke. Die
   BREITE eines Bausteins kommt als --mw aus dem gespeicherten Layout, nicht aus
   dem Markup — deshalb ist Verschieben spaeter eine Datenaenderung und kein Umbau.
   Die HOEHE (--mh) wird mitgefuehrt, aber noch nicht erzwungen: Solange ein Modul
   allein steht, waere eine feste Hoehe nur Leerraum unter ihm. Sobald mehrere
   nebeneinander liegen und buendig sein sollen, wird daraus ein grid-row: span.
   Kein eigener Rahmen: die Bausteine bringen ihre Optik selbst mit (das Band hat
   seinen Rahmen bereits), zwei Rahmen ineinander sahen nur nach Kasten im Kasten aus. */
/* Meldung ueber den Feed-Abruf DIESES Aufrufs. Steht ueber dem Raster in der
   Sprache der Prompt-Zeile, klein und zurueckgenommen: Es ist eine Auskunft,
   keine Nachricht. Erscheint erst, wenn ingest.php geantwortet hat.
   .dashstatus + .dashgrid nimmt dem Raster seinen Abstand nach oben wieder ab,
   sonst stuende die Zeile in einer eigenen leeren Etage. */
.dashstatus { margin-top: 14px; font-size: 12px; color: var(--news-dim); }
.dashstatus .amb { color: var(--news-accent); }
.dashstatus + .dashgrid { margin-top: 10px; }

.dashgrid {
    display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
    /* HIER STANDEN KURZ grid-auto-rows UND EIN grid-row-SPAN (30.07.2026, wieder
       entfernt). Ich hatte "er beruecksichtigt halbe bloecke" als Rasterfehler
       gelesen und die Zeilen auf 175px-Baender festgelegt. Rogers Klarstellung:
       "das mit dem grid war bisher immer OK, nur das breaking band hat sich nicht
       dran gehalten, alle anderen widgets ja."
       Es war also kein Rasterfehler, sondern ein einzelner Baustein, der seine Hoehe
       aus dem Inhalt nahm — und mein Eingriff hat stattdessen ein LOCH erzeugt: mit
       Zeilenspannen schiebt die Auto-Platzierung einen Baustein, der nicht mehr in
       die restlichen Spalten passt, in die naechste Zeile und laesst die Luecke
       stehen. Die Zeilen bleiben deshalb inhaltsbestimmt.
       DIE REGEL, die daraus folgt: Ein Baustein haelt das Raster, indem er seine
       Hoehe aus --mpx nimmt (mh*175 + (mh-1)*16) — nicht, indem das Raster ihn
       zwingt. Genau das macht jeder Baustein ausser dem Themen-Band. */
    gap: 16px; align-items: start;
    /* Oben derselbe Abstand wie ZWISCHEN den Bausteinen: das Raster soll auch
       zur Kopfzeile hin atmen, sonst klebt der erste Baustein am Trennstrich. */
    margin-top: 16px;
}
/* --mpx = die Hoehe des Bausteins in Pixeln, aus seinen Baendern gerechnet.
   Als Variable und nicht als feste Hoehe: Ein Modul, das von sich aus kuerzer
   ist (das Band), soll nicht kuenstlich gestreckt werden; eines mit Liste
   (Latest) greift sie ab und scrollt darin. */
/* DIE BANDHOEHE DES DASHBOARDS IST SEIT 03.08.2026 EINSTELLBAR (Issue #33), deshalb
   --dash-band statt der festen 175. Der Wert kommt aus settings.dash_band und wird
   von header.php ans <html> geschrieben; die 175 hier ist der Rueckfall, falls die
   Einstellung fehlt.
   ES IST NICHT --news-band: das Kachelraster des Feeds und das Baustein-Raster des
   Dashboards sind zwei Flaechen mit zwei Aufgaben - eine Kachel traegt eine Meldung,
   ein Baustein eine ganze Liste. Sie hatten historisch dieselbe Zahl, das war aber
   ein Zufall und kein Zusammenhang.
   DIE ZAHL STEHT NOCH AN EINER ZWEITEN STELLE: dashboard.php rechnet beim Ziehen in
   Baendern und liest sie dafuer aus getComputedStyle - sie ist also auch dort nicht
   fest verdrahtet. */
.dashmod { position: relative; grid-column: span var(--mw, 5); min-width: 0;
           --mpx: calc(var(--mh, 1) * var(--dash-band, 175px) + (var(--mh, 1) - 1) * 16px); }
/* Baustein ohne Inhalt — z.B. das Band, bevor der erste KI-Lauf durch ist. */
.dashempty {
    border: 1px dashed var(--news-line); border-radius: var(--news-r-l); padding: 22px 20px;
    color: var(--news-dim); font-size: 14px;
}
.dashempty .amb { color: var(--news-accent); }
/* Dashboard ohne einen einzigen Baustein — ueber die volle Breite, sonst saesse
   der Hinweis in einer Spalte von fuenf. */
.dashnone {
    grid-column: 1 / -1;
    border: 1px dashed var(--news-line); border-radius: var(--news-r-l); padding: 26px 20px;
    color: var(--news-dim); font-size: 14px; text-align: center;
}
.dashnone .amb { color: var(--news-accent); }
.dashnone i { color: var(--news-accent); margin: 0 2px; }
@media (max-width: 900px) { .dashmod { grid-column: 1 / -1; } }

/* --- BAUSTEIN "Latest": Filterkopf + scrollende Liste -----------------------
   Nimmt genau die Hoehe, die im Layout steht (--mpx) — nur deshalb kann die Liste
   20 Eintraege fuehren, ohne den Baustein in die Laenge zu ziehen. */
.dashlatest {
    display: flex; flex-direction: column; height: var(--mpx);
    background: var(--news-bg-dark); border: 1px solid var(--news-line);
    border-radius: var(--news-r-xl); overflow: hidden;
}
/* RECHTS PLATZ FUER DIE WERKZEUGE (Roger, 28.07.2026): Sie sitzen oben rechts im
   Baustein und lagen sonst auf dem zweiten Auswahlfeld. Die Felder sind
   flex: 1 1 0, sie schrumpfen also von allein, sobald hier Luft reserviert wird —
   keine feste Breite noetig.
   72px = 2 Knoepfe zu 24 + 3 Luecken zu 8, siehe .dashtools. Die erste Fassung
   stand auf 38 und war fuer EINEN Knopf gerechnet (nur den Griff) — mit dem X
   daneben lag der zweite auf dem Auswahlfeld. Wer ein drittes Werkzeug ergaenzt,
   rechnet n*24 + (n+1)*8 und zieht diese Zahl mit. */
.dashfilter { display: flex; gap: 8px; padding: 9px 72px 9px 10px; flex: 0 0 auto;
              border-bottom: 1px solid var(--news-line); }
/* DIE KLAPPMENUES DER ERGEBNISZEILE, im Baustein (Roger, 28.07.2026: "der ist
   schoener und halt multiselect"). Sie bringen ihre Optik selbst mit, hier steht
   nur, was sich im engen Kopf eines Bausteins anders verhaelt:
   Sie fuellen die Breite (ein <select> tat das ueber flex: 1 1 0), das Menue haengt
   sich an die Breite des Bausteins statt an feste 240px, und es klappt NACH RECHTS
   auf, wenn es sonst aus dem Baustein liefe. */
.dashfilter .qgroup { flex: 1 1 0; min-width: 0; }
.dashfilter .qdrop { flex: 1 1 0; min-width: 0; }
/* box-sizing: border-box IST HIER DER GANZE WITZ (Roger, 29.07.2026: "Button
   ueberlagert Inhalt ... es ist nur in Latest richtig"). Gemessen im Browser:
   Die Zusammenfassung ragte in JEDEM Suchen-Baustein 7px unter das X und den Griff.
   Ursache: width: 100% + padding 5/7 + 1px Rahmen ergibt bei content-box 100% + 16px,
   also 16px mehr als die Zeile hergibt — die Aussparung von 72px war nie das Problem.
   WARUM LATEST HEIL BLIEB: Dort steht ein <select>, und Formularelemente sind im
   Browser von Haus aus border-box. Ein <summary> ist es nicht. */
.dashfilter .qdrop > summary { width: 100%; box-sizing: border-box; justify-content: space-between; padding: 5px 7px; font-size: 12px; }
.dashfilter .qval { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dashfilter .qmenu { min-width: 0; width: max-content; max-width: min(320px, 80vw); right: 0; left: auto; }

/* Gleiche Optik wie die Auswahlfelder der Filterzeile — es ist dieselbe Sache. */
.dashfilter select {
    flex: 1 1 0; min-width: 0;
    font-family: var(--news-font); font-size: 12px;
    background: var(--news-bg); color: var(--news-text);
    border: 1px solid var(--news-line); border-radius: var(--news-r-s);
    padding: 5px 7px; outline: none; cursor: pointer;
}
.dashfilter select:hover { border-color: var(--news-accent); color: var(--news-bright); }
.dashlatest-list { flex: 1 1 auto; min-height: 0; overflow-y: auto;
                   display: flex; flex-direction: column; gap: 10px; padding: 10px; }
.dlitem { display: block; }
.dlhl { display: block; margin-top: 2px; font-size: 13px; line-height: 1.35;
        color: var(--news-text); text-decoration: none; }
.dlitem:hover .dlhl { color: var(--news-accent); }

/* --- BAUSTEIN "Saved search": waagerechter Durchscroller ---------------------
   Ein Blatt je Meldung, Bild oben und Text unten zu gleichen Teilen (Roger:
   "bild 1hoch text 1hoch"). Die Hoehe kommt wie beim Latest aus --mpx, damit der
   Baustein exakt seine Baender fuellt statt sich am Inhalt aufzublaehen. */
/* DER BAUSTEIN KLIPPT NICHT MEHR (Issue #51, 07.08.2026, Roger mit einem Screenshot:
   die Liste der gespeicherten Suchen war an der Blockkante abgeschnitten, mitten im
   Wort "Polizei Koeln").
   DIE URSACHE WAR overflow: hidden HIER, nicht das Menue: .qmenu ist position: absolute
   mit z-index: 60 - und ein z-index hilft gegen overflow ueberhaupt nicht. Ein Kind
   kann nicht aus einem klippenden Vorfahren herausragen, so hoch es auch gestapelt ist.
   Das ist dieselbe Verwechslung wie bei den Stapelkontexten in 4: sichtbar ist nicht
   dasselbe wie oben.
   ZWEI WEGE GAB ES, Roger hat den zweiten gewaehlt: das Menue nach OBEN aufklappen
   lassen (kleiner, aendert nichts am Rahmen) oder den Rahmen aufmachen. Sein Wort:
   "herauslassen aber die hoehe des dropdown halbieren".
   WAS ES KOSTET: die abgerundeten Ecken klippen ihre Kinder nicht mehr. Der Streifen
   darin hat sein eigenes overflow-x, klippt sich also selbst - was hier wegfaellt, war
   die zweite Sicherung. Nachmessen laesst sich das nicht, das braucht Augen: wenn an
   den unteren Ecken etwas uebersteht, ist DAS die Stelle.
   DIESELBE ZEILE AUCH AN .dashgal UND .dashpinned - alle vier Bausteine mit
   Suchen-Waehler (saved, imgslide, gallery, pinned) haengen daran. */
.dashsaved {
    display: flex; flex-direction: column; height: var(--mpx);
    background: var(--news-bg-dark); border: 1px solid var(--news-line);
    border-radius: var(--news-r-xl); overflow: visible;
}
/* HALBE HOEHE, ABER NUR IM DASHBOARD (Rogers zweite Haelfte desselben Satzes). Die
   320px sind die Vorgabe von .qmenu, und die gilt auch fuer die FILTERZEILE des
   Portals - dort ist sie richtig und wird nicht angefasst. Ein Menue, das aus einem
   Baustein herausragt, soll dagegen nicht den halben Nachbarn verdecken. */
.dashmod .qmenu { max-height: 160px; }
.dsview { position: relative; flex: 1 1 auto; min-height: 0; }
/* scroll-snap statt freiem Scrollen: Der Streifen bleibt nie zwischen zwei
   Blaettern stehen, egal ob per Knopf, Trackpad oder Tastatur bewegt.
   scrollbar-width: none — die Leiste wuerde ein halbes Band Hoehe fressen. */
.dsstrip {
    display: flex; height: 100%; overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory; scroll-behavior: smooth;
    scrollbar-width: none;
}
.dsstrip::-webkit-scrollbar { display: none; }
/* position: relative, damit der Artikel-Link des Bildsliders am BLATT haengt und
   nicht am Streifen — sonst laegen alle Blaetter-Links uebereinander auf derselben
   Stelle des Rahmens. */
/* DIE FOLIE DARF SENKRECHT NIEMALS UEBERLAUFEN (31.07.2026, Roger: "wenn ich ueber
   eine horizontalescroll box befinde kann ich nicht mehr vertikal scrollen nur noch
   horizontal in der box", und "das fuehrt zu den scrollrucklern").
   WARUM DAS SO WEHTUT: .dsstrip traegt overflow-x: auto und overflow-y: hidden.
   Laeuft der Inhalt auch nur um einen Bruchteil eines Pixels senkrecht ueber, wird
   die Box AUCH IN Y zum Scrollcontainer - sie verschluckt das Mausrad, kann sich
   wegen hidden aber nicht bewegen. Das Rad kommt dann weder bei ihr noch bei der
   Seite an, und beim Runterscrollen bleibt man auf jedem Bildslider haengen.
   WARUM ES NUR IM E-INK AUFTRAT: es gibt in diesem Stylesheet KEIN globales
   box-sizing: border-box (nur sechs einzelne Stellen). .dstext rechnet deshalb im
   content-box-Modell, seine Polsterung kommt zur Hoehe DAZU, und die Folie steht
   dauerhaft auf der Kante. Ob sie kippt, entscheidet ein Bruchteil eines Pixels -
   und genau den verschiebt eine andere Schrift. Das Theme war der Ausloeser, nicht
   die Ursache; im CRT stand dieselbe Konstruktion knapp auf der anderen Seite.
   max-height und border-box nehmen der Sache die Kante: die Folie kann nicht mehr
   ueber ihren Rahmen wachsen, egal welche Schrift darin steht. */
.dsslide {
    position: relative;
    flex: 0 0 100%; width: 100%; height: 100%; max-height: 100%; scroll-snap-align: start;
    display: flex; flex-direction: column; min-width: 0; min-height: 0;
    box-sizing: border-box;
}
/* Bild und Text teilen sich die Hoehe. Ohne Bild bekommt der Text alles — eine
   leere Flaeche als Platzhalter waere nur ein Loch. */
/* Zwei Klassen tragen das Bild: .dsimg fuer die Form, .nthumb damit die Bild-
   Lightbox aus footer.php es findet (die delegiert auf .nthumb[data-full]).
   Deshalb hier eine Stufe spezifischer — sonst haengt es an der Dateireihenfolge,
   welche der beiden Regeln die Hoehe setzt. */
.dsslide .dsimg { flex: 1 1 50%; min-height: 0; overflow: hidden; cursor: zoom-in; display: block; }
.dsslide .dsimg img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* .noimg WIRD SEIT DEM 04.08.2026 NICHT MEHR GESETZT (Issue #38): eine Folie ohne Bild
   bekommt den Platzhalter und behaelt damit ihre Bildhaelfte, statt dem Text die volle
   Hoehe zu geben. Die Regel bleibt als Grabstein stehen - sie kostet nichts und
   beschreibt, was der Zustand VORHER war. Wer sie entfernt, sollte vorher pruefen, ob
   irgendein Baustein die Klasse wieder vergibt. */
.dsslide.noimg .dstext { flex: 1 1 100%; }
/* BILDSLIDER: dasselbe Blatt, nur ohne Text — das Bild nimmt die ganze Hoehe.
   Deshalb teilt er sich Streifen, Einrasten und Knoepfe mit dem Durchscroller und
   braucht nur diese eine Regel. */
.dsslide.nurbild .dsimg { flex: 1 1 100%; }
/* TITELZEILE UEBER DEM BILD (Roger, 29.07.2026, mit Entwurf): schmaler dunkler
   Streifen am unteren Rand, Text mittig.
   LINKS UND RECHTS 38px LUFT, keine 8: In den Ecken sitzen der Artikel-Link und der
   Blaetterknopf — ohne die Aussparung liefe der Titel unter beide.
   EINE ZEILE MIT ELLIPSE statt Umbruch: Ein dreizeiliger Streifen verdeckt das Bild,
   das der Baustein ja zeigen soll. Die volle Schlagzeile steht weiterhin im Tooltip
   der Bildflaeche. */
.dsslide .dstitel {
    position: absolute; left: 0; right: 0; bottom: 0;
    padding: 7px 38px; box-sizing: border-box;
    background: var(--news-strip);
    color: var(--news-bright); font-size: 12px; line-height: 1.5; text-align: center;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Der Artikel-Link sitzt hier LINKS unten: rechts sitzt der Blaetterknopf. */
/* MITTIG ZUR TITELZEILE (Roger, 29.07.2026): Der Streifen ist 32px hoch
   (7 Polster + 18 Zeile + 7), der Knopf 24 — also (32-24)/2 = 4px vom Rand.
   Roger hat es nachgemessen: die Rechnung stimmt, ein Pixel tiefer war zu tief.
   Vorher standen dort 8px aus der Zeit ohne Titelzeile, wodurch der Knopf oben an
   ihrer Kante klebte. Wer Polsterung oder Schriftgroesse des Streifens aendert,
   rechnet mit: (Streifenhoehe - 24) / 2. */
.galopen.slideopen { left: 8px; right: auto; bottom: 4px; width: 24px; height: 24px; font-size: 11px; }
.dsslide:hover .galopen.slideopen { opacity: 0.85; }
/* border-box, sonst kommen die 2x10px Polsterung zur 50-%-Hoehe DAZU und die Folie
   laeuft senkrecht ueber - siehe die lange Begruendung an .dsslide. */
.dstext {
    flex: 1 1 50%; min-height: 0; overflow: hidden; box-sizing: border-box;
    display: flex; flex-direction: column; gap: 5px; padding: 10px 12px;
}
.dshl { font-size: 15px; line-height: 1.3; color: var(--news-bright); text-decoration: none;
        display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.dshl:hover { color: var(--news-accent); }
/* DIE ZEILENZAHL HAENGT AN DER HOEHE DES BAUSTEINS (Roger, 03.08.2026: "wir muessen
   die textzeilen berechnen auf grund der hoehe"), gerechnet in
   includes/snipfit.php und hier nur eingesetzt.
   WARUM NICHT EINE FESTE ZAHL: Sie ist immer nur fuer EINE Bausteinhoehe richtig.
   Drei Zeilen waren fuer h:2 zu viel - .dstext ist die halbe Folie und hat dort rund
   138px, in denen Meta 14 + Schlagzeile 2x19.5 + Anriss 3x16.8 + Knopfzeile 32 +
   drei Zwischenraeume a 5 = 150 standen. .dstext hat overflow: hidden, also staucht
   die Flex-Spalte Schlagzeile und Anriss und schneidet beide MITTEN IN DER ZEILE ab
   (Roger hat genau das rot eingerahmt). Zwei Zeilen passen dort - und sind bei einem
   Baustein auf h:5 wieder falsch, weil dann Platz fuer sieben ist.
   DIE 2 IM var() IST DER RUECKFALL, nicht die Vorgabe: ohne JavaScript bleibt es bei
   dem Wert, der fuer die kleinste benutzte Hoehe gerechnet ist. Zu wenig Text ist
   ein Verlust, abgeschnittene Zeilen sind ein Fehler.
   .dssnip gibt es nur in modules/saved.php - gemessen, nicht vermutet, deshalb
   braucht die Regel keine Eingrenzung auf den Baustein. */
/* flex: 1 1 auto aus demselben Grund wie bei .nsnip - siehe dort. */
/* DER DECKEL IST DER ZWEITE TEIL DER SACHE, und ohne ihn war die Rechnung oben
   wirkungslos (Roger, 04.08.2026, mit Foto: vier Zeilen, Ellipse, und darunter noch
   anderthalb Zeilen, die letzte quer durchgeschnitten).
   -webkit-line-clamp SETZT NUR DIE ELLIPSE, ES BEGRENZT DEN KASTEN NICHT. Gemessen an
   genau diesem Baustein: freier Platz 81px, Zeile 16,8 - der Anriss steht auf 81px
   (flex: 1 1 auto zieht ihn dorthin), sein scrollHeight ist 185. Blink legt also ALLE
   Zeilen an, setzt die Ellipse hinter Zeile N und malt weiter bis zur Kante des
   Kastens. Alles zwischen N*16,8 und 81 ist die halbe Zeile.
   ES FIEL ERST JETZT AUF, weil Roger die Bandhoehe im Admin auf 140 gestellt hat:
   81/16,8 = 4,82, und dieser Rest von 0,82 Zeilen ist der Schnitt. Bei anderen
   Bandhoehen ging der Rest fast auf - der Fehler war trotzdem schon da.
   1em IST HIER DIE ZEILE, nicht die Schriftgroesse: em rechnet gegen die eigene
   font-size, also 1.4 * 12px. --zeile steht deshalb genau einmal da und wird von
   line-height UND vom Deckel benutzt; zwei Zahlen waeren zwei Wahrheiten. */
.dssnip { flex: 1 1 auto; min-height: 0;
          font-size: 12px; --zeile: 1.4; line-height: var(--zeile); color: var(--news-dim);
          display: -webkit-box; -webkit-line-clamp: var(--snip-zeilen, 2);
          -webkit-box-orient: vertical; overflow: hidden;
          max-height: calc(var(--snip-zeilen, 2) * var(--zeile) * 1em); }
/* Blaetterknoepfe liegen UEBER dem Blatt und erscheinen erst beim Ueberfahren —
   dieselbe Zurueckhaltung wie Griff und X. Sie sitzen auf halber Bildhoehe, also
   im oberen Viertel des Bausteins, damit sie den Text nicht verdecken. */
/* DIESELBEN KNOEPFE WIE IM WETTERKOPF (03.08.2026, Roger: "mir ist grade
   aufgefallen, dass die blaetterpfeile im weather viel schoener sind als die auf dem
   dashboard in den widgets"). Der Knopf IST jetzt der Standard - .btn.icon mit den
   Icons beider Familien - und hier steht nur noch, WO er sitzt und dass er erst beim
   Ueberfahren auftaucht.
   ENTFALLEN SIND eine eigene Groesse, Flaeche, Rundung, Schriftgroesse und ein
   eigener Hover. Sie haben genau das nachgebaut, was .btn schon kann, nur anders:
   26px statt 24, --news-r-s statt --news-r-l, und ein Hover, der bloss den RAND
   faerbte, waehrend jeder andere Knopf im Portal sich fuellt.
   DIE EINBLENDUNG BLEIBT: ueber dem Baustein sichtbar, sonst nicht. Sie ist der
   Grund, warum diese Regel ueberhaupt noch existiert. */
.dsnav {
    position: absolute; top: 25%; transform: translateY(-50%); z-index: 4;
    opacity: 0; transition: opacity 0.15s ease, color 0.12s ease, border-color 0.12s ease;
}
.dsview:hover .dsnav { opacity: 0.85; }
.dsview:hover .dsnav:hover { opacity: 1; }
/* IM BILDSLIDER MITTIG (03.08.2026, Roger: "koenntest du bei diesem widgetmodul die
   pfeile vertikal zentrieren").
   WARUM DIE 25 % OBEN TROTZDEM RICHTIG SIND, und deshalb bleiben sie fuer "saved":
   dort ist eine Folie BILD OBEN, TEXT DARUNTER, das Bild also die obere Haelfte -
   25 % der Buehne sind die Mitte des BILDES. Im Bildslider fuellt das Bild die ganze
   Folie (.dsslide.nurbild), und da ist die Mitte des Bildes die Mitte der Buehne.
   Der Knopf zeigt in beiden Faellen auf dieselbe Stelle: aufs Bild. */
.dashshots .dsnav,
.dashwetter .dsnav { top: 50%; }
/* Teilen im saved-Baustein: unter dem Text, nicht in der Bildecke - dort sitzt beim
   Bildslider schon der Weg zum Artikel.
   AN DER UNTERKANTE, UND DAS DREIMAL AN EINEM ABEND (03.08.2026). Die Reihenfolge,
   damit niemand sie noch einmal durchlaeuft: erst hing der Knopf am Text, dann schob
   ihn ein margin-top: auto nach unten, dann nahm Roger das zurueck ("ich habe gesehen
   dass bei den anderen artikeln die buttons immer unterm text stehen") - und zuletzt
   entschied er es fuer ALLE Stellen andersherum (Issue #34: "Action Icons immer am
   unteren Ende der Kachel", dann "ueberall unten ankern").
   DER MASSSTAB WAR IMMER DERSELBE: die Folie soll aussehen wie eine Kachel. Nur ist
   die Kachel inzwischen mitgewandert, also wandert die Folie mit. Die Begruendung
   steht bei .nactions.
   flex: 0 0 auto bleibt, es gehoert nicht zur Lage: ein Flex-Kind wird sonst
   mitgestaucht, sobald es eng wird. */
.dsactions { display: flex; gap: 6px; padding-top: 8px; flex: 0 0 auto; margin-top: auto; }
/* Teilen im Bildslider: dieselbe Ecke wie der Artikel-Link, einen Knopf weiter. */
.galopen.galshare { left: 38px; right: auto; bottom: 4px; width: 24px; height: 24px; font-size: 11px; }
.dsslide:hover .galopen.galshare { opacity: 0.85; }
/* Und der Stern noch einen weiter (issue #54, 07.08.2026). 30px Schritt wie zwischen
   den beiden davor - 24px Knopf plus 6px Luft, dieselbe Luecke, die .dsactions als
   gap benutzt. Rechts unten sitzt der Blaetterknopf, deshalb waechst die Reihe nach
   RECHTS aus der linken Ecke und nicht der Ecke entgegen. */
.galopen.galfav { left: 68px; right: auto; bottom: 4px; width: 24px; height: 24px; font-size: 11px; }
.dsslide:hover .galopen.galfav { opacity: 0.85; }
.dsnav.prev { left: 8px; }
.dsnav.next { right: 8px; }

/* --- TEILEN-FENSTER (Issue #7, 03.08.2026) -----------------------------------
   Ein Fenster fuer alle Seiten, das Markup steht in footer.php. Es folgt der Lupe:
   feste Lage ueber allem, gedaempfter Grund, Panel in der Mitte. KEIN <dialog>:
   das Element bringt eigenes Verhalten und einen eigenen Grund mit, und beides
   muesste hier zurechtgebogen werden. */
.shbox {
    position: fixed; inset: 0; z-index: 90; display: flex;
    align-items: center; justify-content: center; padding: 20px;
    background: rgba(0,0,0,.55);
}
.shbox[hidden] { display: none; }
.shpanel {
    width: 420px; max-width: 100%; box-sizing: border-box;
    background: var(--news-panel); border: 1px solid var(--news-line);
    border-radius: var(--news-r-xl); padding: 16px 18px 14px;
    box-shadow: 0 18px 50px var(--news-shadow-strong);
}
.shkopf { display: flex; align-items: center; justify-content: space-between; gap: 10px;
          font-family: var(--news-mono); font-size: 12px; letter-spacing: .12em;
          text-transform: uppercase; color: var(--news-accent); }
/* Die Ueberschrift des Artikels, damit man sieht, WAS man teilt - zwei Zeilen
   reichen, der Rest waere Zitat statt Kontext. */
.shthema {
    margin: 10px 0 14px; color: var(--news-bright); font-size: 14px; line-height: 1.4;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.shlabel { display: block; font-family: var(--news-mono); font-size: 11px;
           letter-spacing: .08em; text-transform: uppercase; color: var(--news-dim);
           margin: 0 0 5px; }
.shlabel span { text-transform: none; letter-spacing: 0; opacity: .8; }
.shinput {
    width: 100%; box-sizing: border-box; margin: 0 0 12px;
    background: var(--news-bg); border: 1px solid var(--news-line);
    border-radius: var(--news-r-s); padding: 9px 11px;
    font-family: var(--news-font); font-size: 14px; color: var(--news-text);
}
.shinput:focus { outline: none; border-color: var(--news-accent); }
.shnote { resize: vertical; min-height: 62px; font-size: 14px; }
.shfuss { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.shstatus { font-size: 12px; color: var(--news-dim); min-height: 1em; }
.shstatus.gut    { color: var(--news-ok); }
.shstatus.fehler { color: var(--news-danger); }

/* --- BAUSTEIN "Weather": heute prominent, dahinter fuenf Tage ----------------
   (03.08.2026, Roger). KEINE LINIEN - Kachelzeile, ein Band hoch. Der Streifen ist
   .dsview/.dsstrip wie bei "saved" und dem Bildslider, also erben Einrasten,
   Blaetterknoepfe und die Radweitergabe von dort. */
/* RAHMEN WIE BEI DEN ANDEREN BAUSTEINEN (03.08.2026, Roger: "einen rahmen drum").
   Dieselben vier Zeilen wie .dashsaved, und das ist auch der Grund, warum die Doku-
   Zeile "Bausteine haben keinen eigenen Rahmen" nie so gemeint war: Sie sagt, dass
   nicht ZWEI ineinander stehen sollen. Diese Flaeche IST der Rahmen des Bausteins.
   height: var(--mpx) STATT 100 %, und das war der sichtbare Fehler davor: ein
   Prozentwert braucht eine gesetzte Hoehe am Elternteil, die .dashmod nicht hat -
   die Kachelzeile stand deshalb kurz ueber dem unteren Rand statt ihn zu fuellen. */
.dashwetter {
    /* Breite der klebenden now-Spalte. EINE Zahl, zwei Leser: .dwheute nimmt sie als
       flex-basis, .dsstrip als scroll-padding - sonst rastet der Streifen neben etwas
       anderem ein, als die Spalte breit ist. */
    --dwnow: 150px;
    display: flex; flex-direction: column; min-height: 0; height: var(--mpx);
    background: var(--news-bg-dark); border: 1px solid var(--news-line);
    border-radius: var(--news-r-xl); overflow: hidden;
}
/* Der Ortsname, wo es nichts zu waehlen gibt. Er TRAEGT .dashfilter mit, also kommen
   Polster, Trennlinie und die 72px Reserve fuer die Werkzeuge von dort - hier steht
   nur, dass es Text ist und kein Bedienelement. Kein eigenes padding: das wuerde
   genau die Reserve ueberschreiben, unter der X und Griff sitzen. */
.dwort { color: var(--news-bright); font-size: 13px; align-items: center; }
/* ZWEI BAENDER WOLLEN GEFUELLT SEIN (03.08.2026, Roger: "nutze den platz gut").
   space-evenly STATT center: die Kachel verteilt ihre Zeilen ueber die ganze Hoehe,
   statt sie als Block in die Mitte zu legen und oben wie unten Luft zu lassen. Bei
   einem Band sieht das genauso richtig aus - der Abstand schrumpft einfach mit. */
/* SIE TEILEN SICH DIE BREITE, statt feste Pixel zu tragen (03.08.2026, Roger: "ein
   wenig schmaler damit es bei 2x2 nicht scrollt"). Mit festen Werten haette ich die
   Blockbreite ausrechnen muessen - und die haengt an der Buehne, an der Spaltenzahl
   und an seinem w. flex: 1 1 0 laesst die sechs Kacheln den Rahmen genau ausfuellen,
   bei w:2 also ohne Scrollen; heute bekommt mit dem groesseren Faktor seinen
   Vorsprung, ohne dass eine Zahl im Stylesheet steht.
   DIE min-width IST DIE SCHWELLE ZUM SCROLLEN: schmaler als 68px wird nicht
   gequetscht, dann wandert der Streifen - genau dafuer ist er da. */
/* DIE TAGE UND IHRE KURVE SIND EINE SPALTE (03.08.2026). Sie scrollen zusammen, und
   die Grafik ist exakt so breit wie die Kachelreihe darueber - nur deshalb steht der
   Punkt eines Tages ueber der Kachel desselben Tages. */
/* SIE IST SO BREIT WIE IHRE KACHELREIHE, NIEMALS SCHMALER (korrigiert 03.08.2026,
   Roger: "es stimmt was mit den linien nicht wenn ich es kleiner ziehe in der
   breite"). Vorher stand hier flex: 1 1 auto mit min-width: 0 - die Spalte durfte
   damit schrumpfen, die Reihe darin lief ueber, und das SVG mit width: 100 % bezog
   sich auf die GESCHRUMPFTE Box. Die Kurve endete deshalb mitten im Streifen.
     width: min-content  = die Summe der Kachel-Untergrenzen (10 x 62px)
     flex-shrink 0       = darunter geht es nicht, dann scrollt der Streifen
     flex-grow 1         = ist mehr Platz da, fuellt sie ihn aus
   Damit sind Reihe und Grafik IMMER gleich breit, egal wie schmal der Baustein
   gezogen ist - und genau das ist die Bedingung dafuer, dass ein Punkt ueber seiner
   Kachel steht. */
.dwfolge { flex: 1 0 auto; width: min-content; display: flex; flex-direction: column; }
.dwreihe { display: flex; flex: 1 1 auto; min-height: 0; }
.dwgraf { flex: 0 0 auto; padding: 0 0 8px; }
.dwgraf svg { display: block; width: 100%; height: 68px; }

/* DER INHALT STEHT OBEN, nicht ueber die Hoehe verteilt (04.08.2026, Issue #42,
   Roger: "und alles wandert etwas nach oben"). Mit space-evenly wuchsen die ABSTAENDE
   mit dem Baustein - bei h:4 stand zwischen Kopfzeile und Symbol eine halbe Kachel
   Luft. Jetzt stehen Kopf und Symbol oben, und die uebrige Hoehe geht an .dwwerte,
   das sie unter den Zeilen darunter GLEICH verteilt (Roger, direkt danach: "die
   zeilen unter den icons in der hoehe gleichverteilen").
   overflow: hidden, weil die zwei neuen Zeilen IMMER stehen (Roger: "sollen immer
   stehen da die hoehe ja jetzt in px verstellbar ist") und die Bandhoehe seit dem
   03.08. frei einstellbar ist: bei einem sehr flachen Baustein wird unten
   abgeschnitten, statt in die Grafik darunter zu laufen. */
.dwkachel {
    scroll-snap-align: start; flex: 1 1 0; min-width: 62px; box-sizing: border-box;
    display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
    gap: 9px; padding: 12px 4px; overflow: hidden;
    border-right: 1px solid var(--news-line);
}
/* DAS KOPFBAND DER TAGE (04.08.2026, Roger: "die tagesheader och grau hinterlegen").
   Es muss bis an die Kachelkanten reichen, also nimmt es das 12/4px-Polster der Kachel
   per negativem Rand zurueck und bringt sein eigenes mit. align-self: stretch, weil
   die Kachel ihre Kinder mittig zentriert und das Band sonst nur so breit waere wie
   das Wort "WED".
   NUR IN DER TAGESREIHE: HEUTE hat keinen Kopf, es hat eine Ueberschrift - und drei
   Klassen schlagen die zwei der Grundregel darunter, ohne !important.
   color-mix STATT ZWEIER THEME-VARIABLEN, und das ist hier der Gewinn: Tinte in die
   Flaeche gemischt DUNKELT das Papier und HELLT die Roehre auf - eine Regel, beide
   Themes richtig. Fehlt color-mix, bleibt die Flaeche schlicht ungetoent; ein
   Rueckfallwert koennte nur fuer eines der beiden Themes stimmen, und die Information
   steckt ohnehin auch im Wochentag. */
.dwreihe .dwkachel > .dwtag {
    align-self: stretch; margin: -12px -4px 0; padding: 8px 4px 7px;
    background: color-mix(in srgb, var(--news-text) 8%, transparent);
}
/* DAS WOCHENENDE, ueber die GANZE Kachel und leiser als das Kopfband (Roger: "und das
   wochenende"). Gemessen an seinem Mockup: Papier 242, Kopfband 222, Wochenende 233 -
   und IM Kopfband addieren sich beide auf 216. Genau deshalb liegt die Toenung auf der
   KACHEL und nicht auf dem Band: die Summe faellt dann von selbst heraus, statt als
   dritter Wert gepflegt zu werden. */
.dwkachel.dwwe { background-color: color-mix(in srgb, var(--news-text) 4%, transparent); }
/* NASSE TAGE BLAU, wie die nassen Spalten des Wetterkopfs - dieselbe Mischung
   (--news-regen 12%) und dieselbe Bedingung (regen > 0), damit beide Seiten dasselbe
   meinen (04.08.2026, Roger: "bitte dort die spalten mit regen auch so blau
   hinterlegen wie auf der wetterseite, am wochenende ueberlagern sie das grau
   zusaetzlich").
   NUR DIE KACHEL, nicht die Spalte bis unter die Kurve - seine Antwort auf genau
   diese Frage war "so wie die wochenendbalken", und die enden an der Kachel.
   BLAU ALS background-image, GRAU ALS background-color, und das ist der ganze Trick:
   zwei Eigenschaften auf EINEM Element stapeln sich von selbst, das Blau liegt ueber
   dem Grau. Ein gemischter Grau-Blau-Wert waere ein DRITTER Wert, der falsch wird,
   sobald jemand eine der beiden Toenungen anfasst - genau die Falle, die das
   Wochenende schon einmal umgangen hat, als es auf die Kachel statt aufs Kopfband
   gelegt wurde. Deshalb steht in der Regel darueber auch background-COLOR: die
   Kurzform haette das Bild wieder geloescht.
   IM KOPFBAND ADDIEREN SICH DANN DREI Toenungen (Band 8 %, Wochenende 4 %, Regen
   12 %) - dieselbe Rechnung wie bisher, nur mit einem Summanden mehr. */
.dwkachel.dwnass {
    background-image: linear-gradient(color-mix(in srgb, var(--news-regen) 12%, transparent),
                                      color-mix(in srgb, var(--news-regen) 12%, transparent));
}
/* DIE ZEILEN UNTER DEM SYMBOL TEILEN SICH DIE RESTHOEHE (04.08.2026, Roger: "die
   zeilen unter den icons in der hoehe gleichverteilen").
   ZWEI EBENEN SIND DAFUER NOETIG, und das ist der ganze Grund fuer diesen Kasten:
   justify-content gilt immer fuer ALLE Kinder einer Flexbox, "die ersten zwei oben,
   der Rest verteilt" laesst sich in EINER Spalte nicht ausdruecken. Also setzt die
   Kachel oben an und der Kasten verteilt, was uebrig bleibt.
   width: 100%, weil die Kachel ihre Kinder mittig zentriert - ohne das waere der
   Kasten nur so breit wie seine laengste Zeile, und ein Wechsel von "20 km/h" auf
   "9 km/h" rueckte die ganze Spalte.
   GILT FUER HEUTE UND DIE TAGE GLEICH: dieselbe Klasse, dieselbe Regel. Ein
   Sonderfall waere hier nur eine zweite Stelle, die man spaeter vergisst. */
.dwwerte {
    flex: 1 1 auto; min-height: 0; width: 100%;
    display: flex; flex-direction: column; align-items: center;
    justify-content: space-evenly; gap: 2px;
}
.dwkachel:last-child { border-right: none; }
/* HEUTE IST BREITER UND TRAEGT MEHR: gemessene Temperatur, gefuehlte, Spanne, Wort,
   dazu Wind und Feuchte. Die anderen fuenf sind Modellwerte - der Unterschied
   rechtfertigt die Flaeche UND entscheidet, was auf ihnen NICHT steht. */
/* Heute steht fest, die zehn Tage teilen sich den Rest: eine feste Breite hier ist
   richtig, weil diese Kachel MEHR traegt (gefuehlt, Spanne, Wort, Wind, Feuchte) und
   nicht mit den anderen mitschrumpfen darf. */
/* DIE "now"-SPALTE BLEIBT BEIM BLAETTERN STEHEN (04.08.2026, Roger: "und beim
   scrollen sollte sie stehenbleiben"). Sie ist der Bezugspunkt der ganzen Reihe -
   scrollt sie weg, vergleicht man die Prognosetage mit nichts mehr.
   SIE BRAUCHT EINE DECKENDE FLAECHE, sonst scheinen die Tageskacheln durch: die
   Toenungen dieses Bausteins sind alle color-mix mit transparent, also durchsichtig
   gebaut. --news-bg-dark ist die Flaeche des Bausteins selbst (.dashwetter), damit
   die Spalte im Stand aussieht wie vorher. Das Blau bleibt sichtbar, weil .dwnass es
   als background-IMAGE darueberlegt.
   KEIN scroll-snap-align: eine klebende Kachel liegt immer am aktuellen Scrollpunkt,
   und mit `mandatory` waere ihr Rastpunkt der, auf den der Streifen zurueckspringt -
   er liesse sich dann kaum vom Fleck bewegen.
   KEIN z-index: sie ist POSITIONIERT (sticky), die Tageskacheln sind es nicht, und
   positionierte Elemente malen ohnehin ueber statischen. Dokumentreihenfolge statt
   eines Stapelkontexts - dieselbe Regel wie bei .ttext ueber .tbg. */
.dwheute {
    flex: 0 0 var(--dwnow);
    position: sticky; left: 0;
    background-color: var(--news-bg-dark);
    scroll-snap-align: none;
}
/* DIE RASTUNG MUSS NEBEN DER KLEBENDEN SPALTE ANFANGEN, NICHT DARUNTER (04.08.2026,
   Roger unmittelbar nach dem sticky: "das einrasten ist jetzt komisch beim scrollen").
   scroll-snap-align: start richtet die Kachel am Anfang des SCROLLPORTS aus, und der
   ist die linke Kante des Streifens - die liegt seit dem sticky hinter der
   now-Spalte. Der Tag, auf den man rastet, verschwindet also genau darunter, und weil
   die Rastung `mandatory` ist, passiert das schon im Ruhezustand.
   scroll-padding-left ruecKT den Anfang des Scrollports um die Breite dieser Spalte
   ein - danach heisst "start" fuer eine Tageskachel: direkt rechts neben der
   now-Spalte. Kein JavaScript, keine Sonderbehandlung je Kachel.
   EINE ZAHL FUER BEIDES, deshalb --dwnow: die Spaltenbreite und der Einzug MUESSEN
   gleich sein, und zwei Literale waeren beim ersten Verstellen auseinander.
   NUR IM WETTERBAUSTEIN: .dsstrip teilen sich auch der saved-Baustein und der
   Bildslider, und dort gibt es keine klebende erste Kachel. */
.dashwetter .dsstrip { scroll-padding-left: var(--dwnow); }
/* Der Wochentag ist die Ueberschrift der Kachel und wird gelesen, nicht geprueft
   (Roger, 03.08.2026: "den tag hervorheben"). Das DATUM darunter bleibt leise. */
.dwkachel > .dwtag { color: var(--news-bright); font-size: 12px; }
.dwkachel > .dwtag i { color: var(--news-dim); }
.dwtag { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
         color: var(--news-dim); text-align: center; }
.dwtag i { display: block; font-style: normal; font-size: 10px; letter-spacing: 0; }
.dwheute .dwtag { color: var(--news-accent); }
.dwsym { font-size: 30px; color: var(--news-text); line-height: 1; }
.dwheute .dwsym { font-size: 40px; }
/* DIE HIMMELSSYMBOLE TRAGEN FARBE (04.08.2026, Issue #42, Roger: "die wetter icons in
   gedaempften farben"). Sie standen in Tinte, wie jedes andere Symbol im Portal.
   ES IST DIE GESTATTETE AUSNAHME und keine neue: --news-regen und --news-sonne sind
   seit dem 02.08. genau dafuer da, weil dort die FARBE das Etikett ist - und beide
   sind im E-Ink bereits gedaempft hinterlegt (#41608c / #735f25 gegen #4ca1ff /
   #ffd75e). Ueber die Variablen zu faerben heisst also: CRT kraeftig, E-Ink gedaempft,
   ohne einen einzigen Theme-Block hier.
   GEWITTER BEKOMMT --news-danger, NICHT Bernstein: Bernstein ist im CRT die Struktur
   (aktiv, Prompt, Links), und ein Wettersymbol in der Strukturfarbe laese sich als
   Bedienelement. Warnfarben bleiben ohnehin farbig, das ist die aeltere Ausnahme.
   DIE WOLKE BLEIBT GRAU, absichtlich: bei einer Wolke IST Grau die Farbe, und ein
   Symbol, das an fuenf von sieben Tagen dasteht, soll die anderen nicht uebertoenen.
   UEBER DIE FA-KLASSE, nicht ueber eine eigene: newsIcon() setzt fa-<name> an beide
   <i> (eins je Theme), also trifft eine Regel automatisch beide. */
.dwsym .fa-sun { color: var(--news-sonne); }
.dwsym .fa-droplet, .dwsym .fa-umbrella, .dwsym .fa-snowflake { color: var(--news-regen); }
.dwsym .fa-bolt { color: var(--news-danger); }
/* Die zwei neuen Zeilen der Tageskachel: nackter Wert, die EINHEIT sagt, was er ist -
   ein Wort passt auf 62px nicht, und HEUTE traegt seine Beschriftung weiter, weil dort
   Platz ist. Leiser als die Tiefsttemperatur: sie werden geprueft, nicht gelesen. */
.dwwert { font-size: 11px; color: var(--news-dim); line-height: 1; }
/* Die Regenwahrscheinlichkeit unter ihrem Balken. DIESELBE FLEX-GEOMETRIE WIE .dwreihe
   (je Tag eine Zelle, flex: 1 1 0, dieselbe Mindestbreite) - und weil .dwgraf genau so
   breit ist wie die Kachelreihe, steht die Zahl damit unter IHREM Tag und nicht unter
   dem Nachbarn. Kein Rand noetig: .dwkachel ist border-box, die Trennlinie liegt also
   INNERHALB der Spaltenbreite und verschiebt nichts. */
.dwwahr { display: flex; }
.dwwahr > span { flex: 1 1 0; min-width: 62px; text-align: center;
                 font-size: 10px; color: var(--news-regen); line-height: 1; }
.dwjetzt { font-size: 34px; color: var(--news-bright); line-height: 1; }
.dwmax { font-size: 21px; color: var(--news-bright); line-height: 1; }
/* Wind und Feuchte: Wort plus Wert, leise wie jede Meta-Angabe im Portal. Das Wort
   traegt die Sperrung der Kachelkoepfe, damit die Zeile als Beschriftung liest und
   nicht als Satz. */
.dwzeile { display: flex; align-items: baseline; gap: 6px; font-size: 11px; color: var(--news-dim); }
.dwzeile i { font-style: normal; font-size: 10px; letter-spacing: 0.06em;
             text-transform: uppercase; color: var(--news-dim-soft); }
/* Die Nacht steht leiser: man liest das Hoch und PRUEFT das Tief. */
.dwnacht { font-size: 13px; color: var(--news-dim); line-height: 1; }
.dwspanne { font-size: 12px; color: var(--news-text); }
.dwmeta, .dwwort { font-size: 11px; color: var(--news-dim); text-align: center; }
.dwwort { text-transform: lowercase; }

/* --- BAUSTEIN "Images": nur die Bilder einer gespeicherten Suche -------------
   Wie beim Latest kommt die Hoehe aus --mpx, damit der Baustein genau seine
   Baender fuellt und die Wand DARIN scrollt statt die Seite zu verlaengern. */
.dashgal {
    display: flex; flex-direction: column; height: var(--mpx);
    background: var(--news-bg-dark); border: 1px solid var(--news-line);
    /* overflow: visible statt hidden - Begruendung an .dashsaved (Issue #51). Die
       Groessen-Containment aus container-type klippt nicht, das waere contain: paint;
       das Menue kommt also heraus. */
    border-radius: var(--news-r-xl); overflow: visible;
    /* CONTAINER FUER DIE KACHELGROESSE (Issue #52) - die Begruendung steht an
       .dashgal-wall. Hier steht es, weil ein Element sich nicht selbst abfragen
       kann: die Wand rechnet in cqw/cqh, also muss der Container ihr ELTERNTEIL
       sein. Beide Masse sind von aussen bestimmt (Hoehe --mpx, Breite aus dem
       Raster), also kostet die Groessen-Containment hier nichts. */
    container-type: size;
}
/* auto-fill statt fester Spaltenzahl: Derselbe Baustein soll bei 2 Spalten Breite
   genauso vernuenftig aussehen wie bei 5, und nach dem Groessenziehen sofort. */
/* DIE KACHELGROESSE FOLGT DEM BAUSTEIN (Issue #52, 06.08.2026, Roger: "Bildgroesse
   abhaengig von Widgetgroesse machen", mit einem Screenshot: elf Bilder in zwei Reihen
   und darunter der halbe Block leer).
   WAS FALSCH WAR, gemessen an der alten Regel: die Spaltenbreite hing laengst an der
   Breite (auto-fill plus 1fr), die ZEILENHOEHE aber an einer festen Zahl - 84px, egal
   wie hoch der Baustein gezogen war. Ein hoeherer Block bekam deshalb nicht groessere
   Bilder, sondern mehr Leerraum.
   BEIDE MASSE ZAEHLEN (Rogers Nachfrage am selben Abend: "koennen wir die breite des
   widgets auch in die groessenberechnung einfliessen lassen?"), und die KLEINERE Grenze
   gewinnt: vier Reihen hoch (24cqh) oder drei Spalten breit (30cqw), je nachdem, was
   zuerst eng wird. Ohne das min() bekaeme ein flacher breiter Baustein Riesenkacheln
   und ein schmaler hoher solche, die ueber die Kante stehen.
   UEBER CONTAINER-QUERIES UND NICHT UEBER --mw, und das ist der Grund fuer die neue
   Bauform: --mw zaehlt RASTERSPALTEN, keine Pixel. Eine Spalte ist nur bei voller
   Buehnenbreite ~277px, darunter schmaler - jede Rechnung daraus waere eine Schaetzung,
   die bei jedem Fensterwechsel danebenliegt. cqw/cqh sind die ECHTEN Masse des
   Bausteins. Erste Container-Query in diesem Stylesheet.
   DIE KOPFZEILE IST NICHT ABGEZOGEN: der Container ist der ganze Baustein, vom dem der
   Suchen-Waehler noch abgeht. 24cqh sind deshalb etwas mehr als ein Viertel der WAND -
   gefragt war "groesser, wenn der Baustein groesser ist", nicht "randlos gefuellt", und
   die Wand scrollt ohnehin.
   DIE 84px BLEIBEN ALS UNTERGRENZE. Ohne sie wuerde ein flacher Baustein (h:1) die
   Bilder auf Briefmarken schrumpfen - max() haelt das ab, und bei der alten
   Standardhoehe aendert sich dadurch nichts. */
.dashgal-wall {
    --gal-kachel: max(84px, min(24cqh, 30cqw));
    flex: 1 1 auto; min-height: 0; overflow-y: auto;
    display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--gal-kachel), 1fr));
    grid-auto-rows: var(--gal-kachel); gap: 6px; padding: 8px;
}
.galtile {
    position: relative; display: block; overflow: hidden; border-radius: var(--news-r-m);
    background: var(--news-bg); cursor: zoom-in;
}
.galtile img { width: 100%; height: 100%; object-fit: cover; display: block;
               transition: transform 0.18s ease, opacity 0.18s ease; }
.galtile:hover img { transform: scale(1.06); opacity: 0.75; }
/* Der Artikel-Link sitzt in der Ecke, damit die Flaeche der Lightbox bleibt. */
/* DIE KLASSE BESCHREIBT DAS AUSSEHEN, NICHT DAS ELEMENT (korrigiert 03.08.2026,
   Roger: "hups, bitte einheitliche buttons"). Sie war fuer <a> geschrieben; der
   Teilen-Knopf ist ein <button> und brachte dessen Standardgestaltung mit - Rand,
   Innenabstand, eigene Schrift, content-box. Nebeneinander sah das aus wie zwei
   verschiedene Bedienelemente. Die vier Ruecknahmen kosten nichts und machen die
   Klasse fuer jedes Element gueltig - dieselbe Lehre wie heute frueh bei .dsnav. */
.galopen {
    position: absolute; right: 3px; bottom: 3px; z-index: 2;
    width: 20px; height: 20px; border-radius: var(--news-r-s);
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--news-bg); color: var(--news-text); font-size: 10px;
    text-decoration: none; opacity: 0; transition: opacity 0.15s ease, color 0.12s ease;
    border: 0; padding: 0; box-sizing: border-box; font-family: inherit; cursor: pointer;
}
.galtile:hover .galopen { opacity: 0.85; }
.galopen:hover { opacity: 1; color: var(--news-accent); }

/* --- BAUSTEIN "Pinned topic": der Aufbau des Themen-Bandes, ohne Reiter -------
   ES GIBT HIER ABSICHTLICH FAST NICHTS. Der Aufmacher und die Liste laufen ueber
   die Klassen des Bandes (.tlead, .tbg, .ttext, .tside, .titem, .tall), die weiter
   oben stehen und nicht an .topband haengen — "Aufbau genau wie das Band" (Roger,
   30.07.2026) heisst woertlich dieselben Regeln. Was fehlte, war nur der Rahmen
   und das Raster, denn .tpane ist an die Reiter gebunden (display:none, bis ein
   #tpN:checked es einschaltet). */
/* HOEHE AUS --mpx, WIE JEDER ANDERE BAUSTEIN (30.07.2026, Endstand). Vier Fassungen
   waren es bis hierhin, und die Kette gehoert aufgeschrieben, weil jede die naechste
   erklaert:
     1. height: --mpx, 24 Eintraege mit Scrollen -> Bild und vierter Eintrag
        abgeschnitten ("er passt sich in der hoehe nicht richtig an").
     2. Hoehe frei nach Inhalt, wie das Themen-Band -> lag nicht mehr im Bandraster.
     3. height: --mpx mit vier Eintraegen -> schnitt mitten in eine dreizeilige
        Schlagzeile (Screenshot).
     4. min-height, wachsen erlaubt -> "es ist ein halbes band zu hoch".
   WARUM ES JETZT AUFGEHT und in Fassung 3 nicht: Die Schlagzeilen der Liste sind
   seit dem Umbau des Themen-Bandes auf ZWEI ZEILEN GEKLEMMT (.titem .tit). Erst
   damit ist die Zeilenhoehe berechenbar — 15px Meta + 2 x 17,5px Titel = 50px je
   Eintrag — und erst mit einer berechenbaren Zeile darf man eine feste Hoehe
   angeben. Fassung 3 scheiterte genau an der fehlenden Klemmung, nicht an der Zahl.
   Gerechnet: 366px minus Filterkopf (~48) minus Innenabstand (28) = 290px Platz.
   VIER Eintraege zu 50px plus drei Luecken zu 11px plus die "all N"-Zeile ergeben
   255px — passt mit Luft. Fuenf waeren 316px und damit das halbe Band zu hoch, das
   Roger gesehen hat.
   FUENF WURDEN ZWEIMAL VERSUCHT und zweimal zurueckgenommen, das zweite Mal, indem
   ich Luecke und Innenabstand enger stellte, um 20px zu sparen. Rogers Urteil:
   "sorry beides undo, passt doch nicht." Die Lehre steht hier, weil die Versuchung
   wiederkommt: Abstaende zusammenzuschieben, damit eine Zeile mehr hineinpasst,
   kauft eine Zeile und verkauft die Rechnung — beim naechsten Eingriff an Schrift
   oder Polster bricht sie. Freiraum unten ist normal, Latest macht es genauso. */
.dashpinned {
    display: flex; flex-direction: column; height: var(--mpx);
    background: var(--news-bg-dark); border: 1px solid var(--news-line);
    /* overflow: visible statt hidden - Begruendung an .dashsaved (Issue #51). */
    border-radius: var(--news-r-xl); overflow: visible;
}
/* ZWEI SPALTEN BILD ZU EINER SPALTE LISTE — Rogers Aufteilung. Das Band steht auf
   1.35fr zu 1fr, weil es die ganze Buehne hat; hier sind es nur drei Spalten von
   fuenf, und bei 1.35 zu 1 waere die Liste zu schmal fuer eine zweizeilige
   Schlagzeile. min-height:0 auf dem Koerper, sonst kann die Liste nicht scrollen:
   ein Grid-Element waechst sonst ueber seine Zeile hinaus. */
/* DIE ZEILE DARF NICHT MITWACHSEN (03.08.2026, nachgereicht). .dpbody hatte
   flex: 1 1 auto und min-height: 0, hatte damit also selbst die richtige Hoehe - aber
   seine GRID-ZEILE stand auf `auto` und richtete sich nach dem hoechsten Kind. Solange
   die Liste vier Zeilen hatte, fiel das nie auf; mit zwoelf wuchs die Zeile ueber den
   Baustein hinaus, .dashpinned schnitt ab, und die Liste stand mitten in einer Zeile
   abgeschnitten da - ohne die "all N"-Zeile. Genau so hat Roger es fotografiert.
   minmax(0, 1fr) heisst: die Zeile nimmt die verfuegbare Hoehe und KEINE mehr.
   DAZU min-height: 0 AUF .tside, weil ein Grid-Kind sonst nicht unter seine
   Inhaltshoehe schrumpft - dieselbe Voreinstellung, die schon .dpbody selbst brauchte.
   Erst mit beidem kann .tliste ueberhaupt schneiden: ein overflow: hidden wirkt nur in
   einem Kasten, dessen Hoehe feststeht. */
.dpbody {
    flex: 1 1 auto; min-height: 0;
    display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    gap: 18px; padding: 14px;
}
/* Der Aufmacher gibt hier KEINE Hoehe vor (im Band sind es 300px, weil er dort
   allein steht): Als Grid-Element fuellt er seine Zeile, und die bestimmt die
   Liste daneben — bzw. das Mindestmass des Bausteins, wenn die Liste kurz ist. */
.dashpinned .tlead { min-height: 0; }
/* Siehe die Begruendung an .dpbody: ohne das schrumpft die Spalte nicht unter ihren
   Inhalt, und .tliste haette nichts, worin es schneiden koennte. NUR im Baustein -
   das Themenband teilt sich .tside und nimmt seine Hoehe vom Inhalt. */
.dashpinned .tside { min-height: 0; }
.dashpinned .tside { padding-left: 16px; }
/* Schmal: untereinander statt nebeneinander. Sonst nichts — die Hoehe stimmt
   hier von allein, weil sie nirgends festgeschrieben ist. */
@media (max-width: 900px) {
    .dpbody { grid-template-columns: 1fr; }
}

/* --- BAUSTEINE VERSCHIEBEN ---------------------------------------------------
   Der Griff liegt oben rechts IM Baustein und ist im Ruhezustand fast unsichtbar:
   ein Dashboard soll nach Inhalt aussehen, nicht nach Werkzeugkasten. Beim
   Ueberfahren des Bausteins wird er sichtbar — das ist die Einladung. */
/* Werkzeuge oben rechts im Baustein: X zum Entfernen, Griff zum Verschieben.
   Beide zusammen in einer Zeile, damit sie sich nicht ueberlagern.
   GLEICHE ABSTAENDE (Roger, 28.07.2026), und zwar strukturell statt geschaetzt:
   Beide Knoepfe sind exakt 24x24, und EINE Zahl bestimmt alle Luecken —
       8px zum Rand  +  8px zwischen den Knoepfen  +  8px zum Auswahlfeld.
   Daraus folgt der Freiraum der Filterzeile zwingend: 2*24 + 3*8 = 72px.
   Vorher hing die Breite an den Icon-Metriken (padding 3/6 auf unterschiedlich
   breite Glyphen), und ein X ist nun einmal schmaler als drei Balken — deshalb
   sassen sie ungleich, obwohl im CSS ueberall dieselbe 2px-Luecke stand.
   Wer hier ein drittes Werkzeug ergaenzt, rechnet: n*24 + (n+1)*8. */
/* FREIER TEXT ALS BAUSTEIN (Issue #43, 04.08.2026, Roger: "ohne rahmen aber etwas
   dunkler hinterlegt als der hintergrund").
   EINE color-mix-TOENUNG, KEINE ZWEITE FARBE, und das ist dasselbe Mittel wie die
   Tagesleiste und die Wochenendtoenung im Wetterbaustein: Tinte in die Flaeche
   gemischt DUNKELT PAPIER AB und HELLT DIE ROEHRE AUF. Eine Regel, beide Themes -
   im E-Ink also genau das Gewuenschte, im CRT hebt es sich nach oben statt nach
   unten ab. Ein fester Wert braeuchte zwei Theme-Bloecke und wuerde den E-Ink-
   Reglern nicht folgen.
   KEIN RAHMEN, aber ein Radius: der ist keine Umrandung, und eine Flaeche mit
   scharfer Kante liest sich im Raster als Fehler. */
/* EINE ZONE MIT INNENABSTAND. Der KOPFSTREIFEN, der hier vom 04.08.2026 nachmittags
   bis abends stand ("die obere linie schwarz und der text etwas darunter"), ist mit
   Issue #48 wieder weg - Rogers eigene Einordnung: "der abstand nach oben war nur
   eine kruecke weil das von mir eingestellte band zu hoch fuer die headline ist".
   Er kostete 40px, und die Ueberschrift hat jetzt ihre eigene Hoehe.
   DIE ZWEI VARIABLEN SIND DER GRUND, WARUM DIE HOEHE STIMMT: die L-Groesse und die
   Zeilenhoehe stehen JE EINMAL und werden zweimal gelesen - als Schriftgroesse des
   Feldes und als Hoehe einer Ueberschrift. Als Literale waeren es vier Zahlen, und
   die erste Aenderung machte zwei davon zur Luege. */
.dashtext {
    --dt-l: 20px; --dt-zeile: 1.45;
    position: relative; height: var(--mpx); box-sizing: border-box;
    padding: 10px 14px; overflow: hidden;
    background: color-mix(in srgb, var(--news-text) 6%, transparent);
    border-radius: var(--news-r-m);
}
/* DIE UEBERSCHRIFT IST GENAU EINE ZEILE HOCH UND LIEST --mpx NICHT (Issue #48,
   Roger: "dann wuerde ich da eine fixe hoehe machen fuer eine headline mit L
   ... welches nur in der breite verstellt werden kann").
   height: auto AM KASTEN, feste Hoehe AM FELD - so faellt die Bausteinhoehe aus
   Zeile plus Innenabstand VON SELBST heraus. Die Summe (29 + 20 = 49px) steht
   deshalb nirgends: wer den Innenabstand oben aendert, aendert sie mit.
   DAS RASTER KANN DAS, ohne dass etwas nachgezogen werden muesste: die Zeilen von
   .dashgrid sind `auto`, jeder Baustein erzwingt seine Hoehe selbst - dieser
   erzwingt eben eine kleinere. Das ist NICHT der Fall des Themenbands vom
   30.07.2026, das aus dem Raster musste: dort war ein Baustein HOEHER als sein Band
   und zog die ganze Zeile mit, hier ist er niedriger und steht allein in seiner.
   ZWEI KLASSEN, weil die Basisregel darueber eine hat - sonst entscheidet die
   Reihenfolge im Stylesheet, und das ist in dieser Datei schon viermal schiefgegangen. */
.dashtext.dashfest { height: auto; }
.dashtext.dashfest .dtfeld { height: calc(var(--dt-l) * var(--dt-zeile)); }
/* DAS FELD SIEHT NICHT WIE EIN FELD AUS: keine Flaeche, kein Rahmen, kein Zuggriff,
   und es erbt die Portalschrift. Es FUELLT den Baustein, statt mitzuwachsen - die
   Hoehe zieht Roger am Baustein selbst, dafuer ist er "komplett ziehbar".
   height: 100% MISST GEGEN DIE INHALTSHOEHE des Kastens, der Innenabstand ist also
   schon abgezogen. Die Ueberschrift ueberschreibt genau diese Zeile. */
.dtfeld {
    width: 100%; height: 100%; box-sizing: border-box; display: block;
    margin: 0; padding: 0; border: none; outline: none; resize: none;
    background: none; color: var(--news-text);
    font-family: var(--news-font); line-height: var(--dt-zeile);
    caret-color: var(--news-accent);
}
.dtfeld::placeholder { color: var(--news-dim); }
/* DIE DREI GROESSEN DES PORTALS, nachgemessen und nicht erfunden: 12,5px ist der
   Anriss (.nsnip), 15px die Schlagzeile (.nhl), 20px die der grossen Kachel
   (.ncard.b3x2 .nhl). Groesser wird es im Raster nirgends. */
.dt-s .dtfeld { font-size: 12.5px; }
.dt-m .dtfeld { font-size: 15px; }
.dt-l .dtfeld { font-size: var(--dt-l); }
/* NUR BEIM UEBERFAHREN, UND UEBER DEM TEXT (Roger, Issue #48: "die controls liegen
   dann bei mouseover ueber dem widget kein abstand oder so"). Sie haengen wieder
   absolut am Baustein, wie vor dem Kopfstreifen; right: 72px ist die Reservierung
   von X und Ziehgriff (2*24 + 3*8), die eine Regel tiefer steht. */
.dtgroessen {
    position: absolute; top: 10px; right: 72px; z-index: 5;
    display: inline-flex; gap: 4px;
    opacity: 0; transition: opacity 0.15s ease;
}
.dashmod:hover .dtgroessen, .dtgroessen:focus-within { opacity: 1; }
/* UND WEG, SOLANGE MAN TIPPT (Issue #49, 06.08.2026). Roger mit einem Screenshot, auf
   dem drei Woerter unter den Knoepfen verschwunden sind: "Dadfsd asdfs ist e[S][M][L]t
   ...r weil" - und zwar genau beim Schreiben, weil die Maus dann ueber dem Baustein
   liegt und die Knoepfe seit dem Wegfall des Kopfstreifens (#48) wieder absolut in der
   ERSTEN ZEILE haengen. Der Streifen kommt nicht zurueck, das war Rogers eigene Ansage:
   "die controls liegen dann bei mouseover ueber dem widget kein abstand oder so". Also
   verschwinden sie in dem einen Moment, in dem sie stoeren.
   :has() UND KEIN GESCHWISTER-SELEKTOR: die Knoepfe stehen im Markup VOR dem Feld, und
   CSS kann nicht rueckwaerts. Die Reihenfolge zu drehen waere die groessere Aenderung -
   sie haenge an der Tab-Reihenfolge.
   :focus-within TAUGT HIER NICHT, obwohl es eine Zeile hoeher steht: die Knoepfe liegen
   selbst in .dashtext, ein Klick auf S/M/L wuerde sie also unter dem eigenen Zeiger
   ausblenden. Gefragt ist der Fokus DES FELDES, und genau das sagt :has(.dtfeld:focus).
   DIE ZWEITE REGEL IST NICHT KOSMETIK: ohne sie waere die Schriftgroesse bei
   fokussiertem Feld gar nicht mehr erreichbar - unsichtbar und trotzdem klickbar ist
   schlimmer als sichtbar. Wer den Zeiger in die Ecke fuehrt, meint sie.
   OHNE :has()-UNTERSTUETZUNG fallen beide Regeln weg und es bleibt beim Verhalten von
   gestern - kein Bruch, nur keine Verbesserung. */
.dashtext:has(.dtfeld:focus) .dtgroessen { opacity: 0; }
.dashtext:has(.dtfeld:focus) .dtgroessen:hover { opacity: 1; }
.dtgr {
    width: 22px; height: 22px; padding: 0; cursor: pointer;
    background: var(--news-bg-dark); color: var(--news-dim);
    border: 1px solid var(--news-line); border-radius: var(--news-r-s);
    font-family: var(--news-font); font-size: 11px; line-height: 1;
}
.dtgr:hover { color: var(--news-bright); border-color: var(--news-accent); }
/* Gefuellt wie der aktive Nav-Eintrag und das "clear" der Filterzeile - im CRT
   bernstein, im E-Ink Tinte, aus denselben zwei Variablen und ohne Theme-Block. */
.dtgr.active { background: var(--news-accent); color: var(--news-on-accent); border-color: var(--news-accent); }
.dashtools { position: absolute; top: 10px; right: 8px; z-index: 5;
             display: inline-flex; align-items: center; gap: 8px;
             /* Der Uebergang steht auf dem ELEMENT und nicht in der Zustandsregel
                darunter, sonst blendete nur eine der beiden Richtungen weich. Er
                kostet nichts, solange keine Regel die Deckkraft anfasst - und das
                tut ausser der naechsten keine. */
             transition: opacity 0.15s ease; }
/* X UND ZIEHGRIFF GEHEN MIT (Issue #49, Nachtrag am selben Abend, Roger: "fuer das X
   und den hamburger muss auch die neue regel gelten"). Sie sassen bis dahin OHNE jede
   Sichtbarkeitsregel dauerhaft in der ersten Zeile - also schlimmer als die
   Groessenknoepfe, die immerhin nur beim Ueberfahren erschienen.
   AN .dashmod UND NICHT AN .dashtext, weil die Werkzeuge GESCHWISTER des Bausteins
   sind und nicht in ihm liegen. Eine Einschraenkung auf den Textbaustein braucht es
   trotzdem nicht: .dtfeld gibt es nur dort (und in der Headline, die dasselbe Problem
   hat), also kann die Bedingung anderswo gar nicht zutreffen.
   ZURUECK BEIM UEBERFAHREN, aus demselben Grund wie bei den Groessenknoepfen: ein
   unsichtbarer, aber klickbarer Loeschknopf waere schlechter als ein sichtbarer, und
   einen Ziehgriff kann man nicht treffen, den man nicht sieht. */
.dashmod:has(.dtfeld:focus) .dashtools { opacity: 0; }
.dashmod:has(.dtfeld:focus) .dashtools:hover { opacity: 1; }
.dashx, .dashgrip {
    width: 24px; height: 24px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
}
.dashx {
    border-radius: var(--news-r-s); text-decoration: none;
    color: var(--news-dim); font-size: 12px; opacity: 0;
    transition: opacity 0.15s ease, color 0.12s ease;
}
.dashmod:hover .dashx { opacity: 0.75; }
.dashx:hover { color: var(--news-danger); opacity: 1; background: var(--news-bg); }
/* Scharf gestellt: der zweite Klick entfernt wirklich. */
.dashx.armed { opacity: 1; color: var(--news-bg); background: var(--news-danger); }

.dashgrip {
    border-radius: var(--news-r-s); cursor: grab;
    color: var(--news-dim); font-size: 12px; opacity: 0;
    transition: opacity 0.15s ease, color 0.12s ease;
}
.dashmod:hover .dashgrip { opacity: 0.75; }
.dashgrip:hover { color: var(--news-accent); opacity: 1; background: var(--news-bg); }
.dashgrip:active { cursor: grabbing; }
/* KEINE LADEANZEIGE am Baustein (Roger, 28.07.2026). Es gab kurz einen blinkenden
   Cursor im leergeraeumten Kasten — gemessen war er aber nie laenger als ein
   Drittel einer Sekunde zu sehen: Das Nachladen eines Bausteins dauert 0,15-0,33s,
   auch das mit einer Sternsuche (die Bausteine arbeiten mit einem Fenster, anders
   als die Suchseite mit ihren 4,6s). Er hat also nur geflackert und gestoert.
   Die LANGE Phase ist der Feed-Abruf davor, und der gehoert in die Statuszeile
   ueber dem Raster, nicht in die Kaesten. */

/* GROESSENGRIFF unten rechts: zwei Kanten statt eines Icons — dieselbe Zeichensprache
   wie die Ecke eines Fensters, und sie braucht keinen Glyph aus dem Kit (die
   Chevrons haben gezeigt, dass das nicht immer da ist).
   Bei hlock-Bausteinen (Band) nur waagerecht — der Zeiger sagt es vorher. */
.dashsize {
    position: absolute; right: 3px; bottom: 3px; z-index: 6;
    width: 14px; height: 14px; cursor: nwse-resize; opacity: 0;
    border-right: 2px solid var(--news-dim); border-bottom: 2px solid var(--news-dim);
    border-bottom-right-radius: 5px;
    transition: opacity 0.15s ease, border-color 0.12s ease;
}
.dashmod:hover .dashsize { opacity: 0.55; }
.dashsize:hover { opacity: 1; border-color: var(--news-accent); }
.dashmod[data-hlock="1"] .dashsize { cursor: ew-resize; }
/* Waehrend des Ziehens nichts markieren und den Zeiger nicht wechseln lassen —
   sonst flackert er, sobald man den Griff verlaesst (und das tut man sofort). */
body.dashresize,   body.dashresize *   { user-select: none; cursor: nwse-resize !important; }
body.dashresize-x, body.dashresize-x * { user-select: none; cursor: ew-resize   !important; }

/* Der gezogene Baustein bleibt sichtbar, aber zurueckgenommen. */
.dashmod.drag { opacity: 0.4; }
/* EINFUEGEMARKE statt Umrandung: ein Balken an der Kante, an der der Baustein
   landen wird. Eine Umrandung sagte nur 'hier irgendwo', der Balken sagt
   'davor' oder 'dahinter' — und die Seite bleibt dabei ruhig stehen, weil
   erst beim Loslassen umsortiert wird. */
.dashmod.over-vor::before,
.dashmod.over-nach::after {
    content: ""; position: absolute; top: 0; bottom: 0; width: 3px;
    background: var(--news-accent); box-shadow: 0 0 10px var(--news-glow);
    border-radius: 2px; z-index: 6;
}
.dashmod.over-vor::before  { left: -10px; }
.dashmod.over-nach::after  { right: -10px; }
/* Das Ziehbild am Mauszeiger: klein, beschriftet, aus dem Sichtfeld geparkt.
   display:none ginge nicht — der Browser nimmt nur an, was gerendert ist. */
.dashghost {
    position: fixed; top: -1000px; left: -1000px;
    font-family: var(--news-font); font-size: 12px;
    background: var(--news-accent); color: var(--news-on-accent);
    padding: 5px 10px; border-radius: var(--news-r-s); white-space: nowrap;
}

/* --- BAUSTEIN HINZUFUEGEN ----------------------------------------------------
   EIN SCHWEBENDES + UNTEN RECHTS (Roger, 28.07.2026), genau wie der
   Hochscroll-Pfeil: dieselbe Ecke, dieselbe Groesse (.btn icon large), derselbe
   Abstand von 28px zum Rand. Vorher war es eine Leiste ueber dem Raster, die eine
   ganze Zeile fuer einen einzigen Knopf verbrauchte.
   ES STAPELT SICH UEBER DEM PFEIL (bottom 78 = 28 + 42 Knopfhoehe + 8 Luft).
   Hin und her am 28.07.: erst darueber, dann auf die Position des Pfeils gesetzt
   (der sollte per :has() ausweichen), dann wieder darueber. Grund fuer den
   Rueckweg: Bei Roger wich der Pfeil nicht aus, lag ueber dem + und BLOCKIERTE
   dessen Menue. Der Pfeil erscheint erst ab 400px Scroll — deshalb sah die Ecke
   beim Bauen frei aus und der Fehler zeigte sich erst Stunden spaeter.
   Die Lehre steckt in der Reihenfolge: Wer ausweicht, sollte nicht davon abhaengen,
   dass eine Regel greift. Das + weicht immer aus, ohne Bedingung.
   IMMER SICHTBAR, anders als der Pfeil: Der blendet sich erst ab 400px Scroll ein,
   was dort richtig ist (vorher gibt es nichts, wohin man zurueck wollte). Einen
   Baustein hinzufuegen will man aber gerade oben, also von Anfang an.
   ES OEFFNET BEIM UEBERFAHREN (Roger, 28.07.2026), genau wie das saved-Dropdown
   in der Navigation — dieselbe Mechanik, dieselbe Bruecke, kein Skript. Vorher war
   es ein <details> mit Klick, das dann auch einen Zuklapp-Handler brauchte. */
.dashbar { position: fixed; right: 28px; bottom: 78px; z-index: 200; }
.dashadd { position: relative; display: inline-block; }
.dashadd > .btn { cursor: pointer; }
/* Der Knopf bleibt hervorgehoben, solange man IM Menue ist — sonst sieht es aus,
   als gehoerte die offene Liste zu nichts. */
.dashadd:hover > .btn { background: var(--news-accent); border-color: var(--news-accent); color: var(--news-on-accent); }
/* Die Bruecke deckt die 10px zwischen Knopf und Menue ab. pointer-events erst im
   Hover, sonst hielte sie das Menue von sich aus offen (wie .portal-bridge). */
.dashbridge { position: absolute; bottom: 100%; right: 0; width: 100%; height: 10px;
              background: transparent; z-index: 209; pointer-events: none; }
.dashadd:hover .dashbridge { pointer-events: auto; }
/* Das Menue klappt NACH OBEN auf: Unter dem Knopf sind nur noch 78px Luft, dort
   waere die Liste abgeschnitten.
   NUR EINE display-Angabe in dieser Regel — hier stand kurzzeitig 'none' am Anfang
   UND 'flex' am Ende, und die spaetere gewinnt: das Menue waere dauerhaft offen
   gewesen. Das Aufklappen macht allein die :hover-Regel darunter. */
/* DAS + BENUTZT DAS MENUE DER NAVIGATION (31.07.2026, Roger: "kannst du das +menu
   im dashbaords so bauen wie die navigation?"). Hier standen vorher eigene Regeln
   fuer Panel, Eintraege und Hover - dieselbe Sache ein zweites Mal beschrieben. Was
   bleibt, sind die drei Stellen, an denen dieses Menue wirklich anders ist.
   SPEZIFITAET IST PFLICHT, nicht Zierde: die Basisregel steht als
   `.nav-item-wrapper.has-dropdown .nav-dropdown-menu` (drei Klassen). Ein blosses
   `.dashadd .nav-dropdown-menu` (zwei) verliert und die Ueberschreibung kaeme nie
   an. Dieselbe Falle ist bei .navsaved dokumentiert - und steht ungenutzt bei
   .navmore, wo left/min-width seit jeher wirkungslos sind. */
.nav-item-wrapper.has-dropdown.dashadd .nav-dropdown-menu {
    /* NACH OBEN: unter dem Knopf sind nur 78px, das Menue hat dort keinen Platz. */
    top: auto; bottom: calc(100% + 10px);
    /* RECHTS VERANKERT: der Knopf schwebt in der rechten unteren Ecke. */
    left: auto; right: 0;
}
/* RECHTSBUENDIG (Issue #14). Der Nav-Eintrag ist ein Flexkasten, deshalb entscheidet
   hier justify-content und nicht text-align: die Beschriftung soll unter dem Knopf
   stehen, unter dem das Menue haengt, nicht am gegenueberliegenden Rand. */
.dashadd .nav-dropdown-item { justify-content: flex-end; }
/* HIER STANDEN .dashmenu a, .dashmenu a:hover UND DIE EIGENE HOVER-REGEL
   (31.07.2026 entfallen). Sie beschrieben Aussehen und Aufklappen des Menues ein
   ZWEITES Mal, neben den Nav-Dropdowns, die dasselbe schon konnten. Jetzt traegt
   das Menue deren Klassen und alles davon kommt von dort - inklusive der
   Mehrspaltigkeit ueber --rows, die es vorher gar nicht hatte.
   .dashmenu-size war schon am 28.07.2026 gegangen: der Eintrag traegt nur noch
   seinen Namen, siehe dashboard.php. */
.dashmenu-leer { padding: 10px 16px; color: var(--news-dim); font-size: 13px; text-align: right; }
/* KEIN Sonderabstand mehr nach der Leiste: Seit der Knopf schwebt, steht sie
   nicht mehr im Fluss und nimmt keine Hoehe weg — das Raster behaelt seine
   eigenen 16px zur Kopfzeile. Die alte Regel haette sie auf 12 gedrueckt. */
/* ============================================================================
   THEME: E-INK - ein Panel aus Papier statt eines leuchtenden Schirms
   ============================================================================
   Roger, 31.07.2026: "Wir stellen es so dar als waere es ein E-Ink Display."
   Die BILDER BLEIBEN IN FARBE - Farb-E-Ink gibt es wirklich (Kaleido, Gallery 3),
   dort sind Fotos gedaempft und koernig, das Papier bleibt Papier.

   WARUM ES EIN THEME IST UND KEIN HELLMODUS, gemessen: Bernstein auf Weiss kommt
   auf 1,77:1, Phosphorgruen auf 1,74:1 - beide weit unter der 4,5er-Grenze fuer
   Fliesstext. Ein heller Modus mit dieser Palette ist unmoeglich. Hier ist der
   Akzent deshalb keine FARBE mehr, sondern ein WERT: wo im CRT ein Bernsteinblock
   steht, steht ein Tinteblock. Genau deshalb traegt die Struktur 1:1 herueber und
   keine einzige Regel musste umgebaut werden.

   DIE RAMPE HAENGT AN DREI ZAHLEN. Papier, Kachel, Panel und Linie kommen aus EINEM
   Ton, die Abstaende dazwischen sind fest - so bleibt sie stimmig, egal wo man
   landet. In Schritt 3 werden --papier-l und --papier-s die zwei Regler des
   Kindle-Vorbilds ("hell" und "warm").
   ========================================================================= */
.theme-eink {
    /* DIE DREI ZAHLEN, AUS DENEN DAS PAPIER FAELLT. -s und -l sind die zwei REGLER
       im Palette-Menue; header.php schreibt ihren gespeicherten Wert als inline-
       style ans <html> und der schlaegt diese Zeilen hier, wie jeder inline-style
       jede Klassenregel schlaegt. Was hier steht, ist also der RUECKFALL fuer den
       Fall, dass keiner geschrieben wurde — und zugleich die Voreinstellung, die
       newsSettingsDefaults() nennt. Die beiden muessen uebereinstimmen.
       -h HAT KEINEN REGLER: der Farbton entscheidet, WELCHES Papier es ist, nicht
       wie hell oder warm es liegt. Zwei Regler sind eine Einstellung, drei sind
       ein Farbwaehler. */
    --papier-h:  45;        /* warm, Richtung Papierweiss                     */

    /* HIER UND NUR HIER WERDEN DIE STUFEN ZU PROZENT (31.07.2026). --hell und
       --warm sind nackte Zahlen 0-10; header.php schreibt sie ans <html>, das
       JavaScript setzt sie beim Ziehen, und WEDER DAS EINE NOCH DAS ANDERE KENNT
       PROZENTE. Mit Prozentwerten haette dieselbe Formel in PHP und in JS gestanden
       - genau die Doppelung, an der --news-bg-rgb gescheitert ist.
       DIE STUFENWEITE IST AUS ROGERS WERTEN GERECHNET, nicht andersherum: seine
       90 % sind Stufe 9 (54 + 9*4), seine 6 % sind Stufe 1 (1*6). Wer die Spanne
       aendert, verschiebt damit auch, was seine Einstellung bedeutet - warm lief
       zuerst bis 30 % in 3er-Schritten, und das Anheben auf 60 % (Roger, 31.07.2026)
       hat seinen Wert von Stufe 2 auf Stufe 1 verschoben. Solche Aenderungen
       brauchen also eine Umstellung der gespeicherten Zahl, nicht nur der Formel.
       DIE GROEBERE STUFE KOSTET NICHTS SICHTBARES: bei hell 9 laeuft die ganze
       Spanne von #e6e6e6 bis #f5edd6, die 6er-Schritte sind dort kaum zu
       unterscheiden. Deutlich wird warm erst auf DUNKLEM Papier (bei hell 2 von
       grau nach bernstein) - genau da, wo die groessere Spanne etwas bringt.
       Die Rueckfallwerte in var() muessen zu newsSettingsDefaults() passen. */
    --hell: 9;
    --warm: 1;
    /* BASIS UND SCHRITT SIND SEIT 31.07.2026 IM ADMIN EINSTELLBAR (Reiter Design)
       und kommen als inline-style vom <html>. Was hier steht, ist der Rueckfall und
       muss zu newsSettingsDefaults() passen.
       ACHTUNG: sie sind keine Anzeige, sie sind die BEDEUTUNG der Stufe - hell = 9
       heisst 90 %, aber nur bei Basis 54 und Schritt 4. Der Admin rechnet die
       gespeicherte Stufe beim Verschieben der Grenzen deshalb um. */
    --hell-basis:   54%;
    --hell-schritt:  4%;
    --warm-schritt:  6%;
    --papier-l:  calc(var(--hell-basis) + var(--hell) * var(--hell-schritt));
    --papier-s:  calc(var(--warm) * var(--warm-schritt));

    --news-bg:             hsl(var(--papier-h), var(--papier-s), var(--papier-l));
    --news-bg-dark:        hsl(var(--papier-h), var(--papier-s), calc(var(--papier-l) + 3.5%));
    --news-panel:          hsl(var(--papier-h), var(--papier-s), calc(var(--papier-l) + 5%));
    --news-line:           hsl(var(--papier-h), calc(var(--papier-s) - 3%), calc(var(--papier-l) - 17%));
    --news-line-hover:     hsl(var(--papier-h), calc(var(--papier-s) - 3%), calc(var(--papier-l) - 30%));
    --news-img-bg:         hsl(var(--papier-h), var(--papier-s), calc(var(--papier-l) - 7%));
    --news-placeholder:    hsl(var(--papier-h), var(--papier-s), calc(var(--papier-l) - 22%));

    /* DIE TINTE WANDERT MIT (31.07.2026). Sie stand als vier feste Hexwerte da, und
       DAS war die eigentliche Grenze der Regler - nicht der Regler selbst: bei
       hell = 70 % fiel der Kontrast der Meta-Zeile auf 2,6:1, weil das Papier sich
       bewegte und die Schrift nicht. Deshalb ging der Regler nur von 82 bis 97.
       DASS ES UEBERHAUPT GEHT, ist gemessen und war die Ueberraschung: die vier Werte
       liegen auf DEMSELBEN Farbton und DERSELBEN Saettigung wie das Papier (H 40-48,
       S ~6 %) - sie waren immer schon Teil dieser Leiter, nur als Literale
       geschrieben. Es ist also kein Umbau, sondern das Zuendedenken dessen, was ueber
       diesen Zeilen schon steht. Die Abstaende sind exakt die gemessenen, bei Stufe 9
       kommen deshalb genau die alten Farben heraus.
       ERGEBNIS, gemessen ueber die ganze Leiter: Fliesstext 6,7 bis 11,5:1, Meta 3,9
       bis 6,2:1 - vorher brach die Meta-Zeile unterhalb von 82 % weg.
       NICHT MITGEWANDERT sind die rgba(26,25,23,...)-Fuellungen weiter unten: das
       sind Tintenlasuren fuer Chips und Fokusringe, also Beiwerk, kein Text. Auf sehr
       dunklem Papier werden sie blass. Wenn das je stoert, ist es dieselbe Rechnung
       wie hier - aber es ist keine Lesbarkeitsfrage. */
    --news-text:           hsl(var(--papier-h), var(--papier-s), calc(var(--papier-l) - 74%));
    --news-dim:            hsl(var(--papier-h), var(--papier-s), calc(var(--papier-l) - 49%));
    --news-dim-soft:       hsl(var(--papier-h), var(--papier-s), calc(var(--papier-l) - 38%));
    --news-bright:         hsl(var(--papier-h), var(--papier-s), calc(var(--papier-l) - 80%));

    /* DER AKZENT IST DIE TINTE. Und damit MUSS --news-on-accent Papier werden:
       14 Stellen schreiben Text auf eine Akzentflaeche, dunkel auf dunkel waere
       unlesbar. Das ist der Grund, warum die Variable in Schritt 1 ueberhaupt
       angelegt wurde - als Literal haette sie hier nicht folgen koennen. */
    --news-accent:         hsl(var(--papier-h), var(--papier-s), calc(var(--papier-l) - 80%));
    --news-on-accent:      hsl(var(--papier-h), var(--papier-s), calc(var(--papier-l) + 3.5%));
    --news-accent-fill:    rgba(26,25,23,0.08);
    --news-accent-line:    rgba(26,25,23,0.45);
    --news-accent-focus:   rgba(26,25,23,0.25);
    --news-badge-line:     rgba(26,25,23,0.35);
    --news-badge-fill:     rgba(26,25,23,0.06);

    /* KEIN LEUCHTEN. Ein reflektierendes Panel gibt kein Licht ab und wirft keinen
       Schatten - alles, was glueht oder schattet, geht auf durchsichtig. */
    --news-glow:           rgba(0,0,0,0);
    --news-green-glow:     rgba(0,0,0,0);
    --news-accent-halo:    rgba(0,0,0,0);
    --news-shadow:         rgba(0,0,0,0);
    --news-shadow-strong:  rgba(0,0,0,0);
    --news-textshadow:     rgba(0,0,0,0);
    --news-scan-strength:  0;   /* Scanlines aus, dafuer die Papierkoernung    */

    /* "NEU/BREAKING" war Phosphorgruen. Auf einem Graustufen-Panel gibt es kein
       Gruen, also ist es Tinte - die Pille faellt weiter auf, weil sie eine
       FLAECHE ist und nicht weil sie eine Farbe hat. */
    --news-green:          #1a1917;

    /* REGEN UND SONNE BLEIBEN FARBIG, nur gedaempft (Roger, 02.08.2026: "ich glaube
       wir duerfen im eink theme dafuer gedaempfte farben nutzen"). Sie stehen damit
       in derselben Familie wie --news-danger, --news-info und --news-ok direkt
       darunter, und die Begruendung ist dieselbe: Farbe traegt hier BEDEUTUNG. In
       einem Diagramm ist "das Blaue ist der Regen" die Beschriftung — zwei
       Grauwerte muesste man jedes Mal neu zuordnen, und die Reihen stehen mit vier
       Zeilen Abstand untereinander.
       FESTE WERTE, keine aus --papier-* gemischten: die drei Farben darunter machen
       es genauso. Die Tinte MUSS den Reglern folgen, weil sie Text traegt; eine
       Kennfarbe muss vor allem sie selbst bleiben.
       DIE WERTE SIND AUF DIE FAMILIE GEMESSEN, nicht geschaetzt: danger, info und ok
       liegen bei Rogers Papier (98 %) auf 5,7 bis 5,9:1, und diese zwei ebenso. Das
       erste Ocker (#8a7433) kam auf 4,3:1 und waere damit blasser gewesen als die
       drei daneben - eine Kennfarbe, die leiser ist als eine Fehlermeldung.
       AUF SEHR DUNKLEM PAPIER FALLEN SIE AB, gemessen 1,9:1 bei hell 0 - genau wie
       danger/info/ok, denn das ist der Preis eines festen Wertes. Wer das aendert,
       muss alle fuenf anfassen, nicht diese zwei. */
    --news-regen:          #41608c;   /* gedaempftes Blau  (CRT: #4ca1ff) */
    --news-sonne:          #735f25;   /* gedaempftes Ocker (CRT: #ffd75e) */

    /* WARNFARBEN BLEIBEN FARBIG. Das ist die eine bewusste Ausnahme von der
       Graustufenregel: eine Warnung, die man nicht als solche erkennt, ist
       schlimmer als ein Bruch im Konzept. Gedaempft, damit sie nicht schreit. */
    --news-danger:         #9a4a4a;
    --news-danger-bg:      #e6d2d2;
    --news-danger-line:    #b98a8a;
    --news-danger-on:      #5a2626;   /* auf Papier dunkel auf hell - umgekehrt */
    --news-error-line:     #b06a6a;
    --news-error-text:     #6a2f2f;
    --news-info:           #3f6b70;
    --news-ok:             #3f6b52;   /* auf Papier gedaempft */

    /* Die zwei halbdurchsichtigen Flaechen, direkt aus dem Papierton gemischt —
       sie FOLGEN DEN REGLERN damit von selbst. Vorher stand hier ein festes
       rgb-Tripel (231,230,228), das genau das nicht konnte; siehe die Begruendung
       an --news-sheet oben in :root.
       Die Schraegstrich-Schreibweise hsl(h s l / a) braucht einen aktuellen
       Browser — dieselbe Generation wie scrollbar-gutter weiter oben. */
    --news-sheet:  hsl(var(--papier-h) var(--papier-s) var(--papier-l) / 0.82);
    --news-strip:  hsl(var(--papier-h) var(--papier-s) var(--papier-l) / 0.72);
    --news-scrim:          rgba(26,25,23,0.88);   /* Vorhang der Bildlupe      */
    --news-tint-fallback:  rgba(0,0,0,0.10);      /* auf Papier abdunkeln      */

    /* Literata, eine Lese-Serif. Der Kit-Name ist "literata-variable" - der
       Anzeigename in Adobe Fonts taeuscht hier. Variabel, 200-900. */
    --news-font: "literata-variable", Literata, Georgia, "Times New Roman", serif;

    /* PAPIER HAT KEINE WEICHEN ECKEN. Die Leiter faellt von 7/6/5/4/3 auf 2/2/1/1/1
       - der letzte sichtbare Rest des Bildschirms im Papier-Look.
       WARUM NICHT AUF 0: eine Kante ohne jeden Radius liest sich nicht als Papier,
       sondern als ungestaltet - Schnittkanten sind nie mathematisch scharf, und die
       Bedienflaechen eines E-Readers sind es auch nicht. 1-2px ist der Unterschied
       zwischen "geschnitten" und "gerendert".
       DIE STUFEN RUECKEN ZUSAMMEN, und das ist Absicht: bei 7px trennt eine
       Abstufung sichtbare Formen, bei 2px waeren fuenf verschiedene Werte eine
       Unterscheidung, die niemand sehen kann. Zwei reichen. */
    --news-r-xl:    2px;
    --news-r-l:     2px;
    --news-r-m:     1px;
    --news-r-s:     1px;
    --news-r-xs:    1px;

    /* Papierkoernung, Rogers Werte aus der Vorschau. Das Rauschen ist
       UNREGELMAESSIG (feTurbulence), kein Gitter: ein regelmaessiges Raster ist
       das Farbfilter eines Kaleido, ein Paperwhite zeigt bei 300 ppi gar keins. */
    /* Kachelgroesse; die zwei STAERKEN kommen als inline-style vom <html>,
       weil sie im Admin einstellbar sind (Reiter Design). Die Rueckfallwerte
       hier muessen zu newsSettingsDefaults() passen. */
    --korn-groesse: 255px;
    --korn-dunkel:  0.10;
    --korn-hell:    0.14;
    --rauschen-dunkel: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='300'%20height='300'%3E%3Cfilter%20id='n'%20color-interpolation-filters='sRGB'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.45'%20numOctaves='3'%20stitchTiles='stitch'%20seed='7'/%3E%3CfeColorMatrix%20values='0%200%200%200%200%200%200%200%200%200%200%200%200%200%200%20-0.4252%20-1.4304%20-0.1444%200%201.0000'/%3E%3C/filter%3E%3Crect%20width='300'%20height='300'%20filter='url(%23n)'/%3E%3C/svg%3E");
    --rauschen-hell: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='300'%20height='300'%3E%3Cfilter%20id='n'%20color-interpolation-filters='sRGB'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.45'%20numOctaves='3'%20stitchTiles='stitch'%20seed='7'/%3E%3CfeColorMatrix%20values='0%200%200%200%201%200%200%200%200%201%200%200%200%200%201%200.4252%201.4304%200.1444%200%20-1.0000'/%3E%3C/filter%3E%3Crect%20width='300'%20height='300'%20filter='url(%23n)'/%3E%3C/svg%3E");
}

/* KEINE UEBERGAENGE: ein Panel schaltet, es gleitet nicht. */
.theme-eink * { transition: none !important; }

/* KEIN FLACKERN, KEIN BLINKEN (Roger, 31.07.2026, Issue #17). Beides sind
   ROEHRENGESTEN: der aktive Navigationspunkt flackert wie eine Roehre, die ihre
   hellste Flaeche nicht ruhig haelt, und der Blockcursor blinkt wie ein Terminal.
   Ein E-Ink-Panel kann das physisch nicht - es zeichnet ein Bild und laesst es
   stehen; jede Aenderung kostet einen sichtbaren Refresh. Ein blinkender Cursor
   waere dort kein Cursor, sondern ein Defekt.
   BEIDE RUHEN AUF opacity: 1 (navflicker bei 0 %, blink ausserhalb der 50 %), das
   Abschalten laesst also genau den richtigen Zustand stehen: einen ruhenden
   Tinteblock. Kein zweiter Wert noetig.
   NUR DIESE ZWEI, nicht `* { animation: none }`: fadeIn an den Menues und die
   Einblendung der Flip-Karte sind gewoehnliche Oberflaechenbewegung und keine
   Roehrengeste - die waeren eine eigene Entscheidung. */
.theme-eink .navlink_active,
.theme-eink .term-cursor { animation: none; }

/* Die Koernung liegt als EINE Schicht ueber allem - sie ist eine Eigenschaft der
   OBERFLAECHE, nicht der einzelnen Kachel. Fest positioniert: Papier scrollt nicht
   mit. overlay statt multiply, weil multiply nur ABDUNKELN kann und dann als
   Grauschleier erscheint statt als Koernung (gemessen 31.07.2026). */
/* ZWEI SCHICHTEN STATT EINER MISCHUNG (31.07.2026). Hier stand mix-blend-mode:
   overlay.
   ES WURDE AUS DEM FALSCHEN GRUND ENTFERNT, und das gehoert hierhin, damit es
   niemand als Beleg missversteht: Roger meldete ruckelndes Scrollen und tote
   Bausteinscroller, NUR im E-Ink, und diese Schicht war das einzige Element, das
   es nur dort gibt. Der Verdacht lag nahe und war falsch. Die Ursache war .dsstrip,
   das senkrecht ueberlief und deshalb das Mausrad verschluckte - die Begruendung
   steht dort. Auch die Bilddaempfung war eine Stunde lang verdaechtig und ist es
   nicht gewesen.
   DER MECHANISMUS STIMMT TROTZDEM, nur war er hier nicht am Werk: eine
   bildschirmfuellende, FEST positionierte Schicht mit mix-blend-mode mischt sich mit
   allem darunter, und weil sich das beim Scrollen staendig aendert, muss der ganze
   Sichtbereich in jedem Bild neu verrechnet werden. pointer-events: none hilft
   dagegen nichts - das Problem waere nicht das Treffen, sondern das Zeichnen. Wer
   das je wieder einbaut, sollte es wissen; hier ist es kein Grund zur Rueckkehr,
   weil die zwei Schichten den unbestrittenen Vorteil haben, EINSTELLBAR zu sein.
   WAS STATTDESSEN: Die Turbulenz steuert jetzt das ALPHA, nicht die Helligkeit, und
   es gibt zwei Kacheln - dunkle Koerner und helle Koerner. Normal uebereinander
   gelegt, ohne jede Mischung. Nachgerechnet ist overlay auf hellem Grund ohnehin
   fast dasselbe: bei Papier .90 kommt 0.8 + 0.2*Rauschen heraus, also eine
   symmetrische Auf- und Abhellung um den Papierton.
   ZWEI STAERKEN UND NICHT EINE, und das ist keine Bequemlichkeit: ueber Weiss ist
   nach oben kein Platz mehr. Dieselbe Aufhellung braeuchte rund neunmal so viel
   Deckkraft wie die Abdunklung, und das waeren auf einem dunklen Foto weisse Punkte.
   Die helle Haelfte ist deshalb gedaempft - im Admin einstellbar, weil der richtige
   Wert Ansichtssache ist.
   BEIDE STAERKEN SIND REINE DECKKRAFT, damit der Regler im Admin sie verstellen
   kann, ohne dass irgendein SVG neu erzeugt werden muesste. */
/* FREIGESPROCHEN DURCH AUSSCHLUSS (31.07.2026). Koernung und Bilddaempfung waren
   beide draussen, dann kam die Koernung allein zurueck - Roger: "es geht mit
   koernung dann muss es die daempfung sein". Also bleibt sie, und der Filter geht.
   DAS IST DER UNTERSCHIED, UM DEN ES GEHT: eine Flaeche mit Deckkraft kostet beim
   Scrollen nichts, sie wird einmal gerastert und danach nur verschoben. Ein
   CSS-filter dagegen ist Rasterarbeit JE BILD und je Vollbild - und ein Bildstreifen
   zieht genau diese Bilder durchs Bild.
   NICHT ZU VERWECHSELN mit der frueheren Fassung dieser Schicht, die
   mix-blend-mode trug: DIE waere teuer gewesen, aus einem dritten Grund (Mischung
   mit dem staendig wechselnden Untergrund). Sie ist aus einem falschen Verdacht
   gefallen und aus einem richtigen Grund nicht zurueckgekommen. */
.theme-eink body::before,
.theme-eink body::after {
    content: ""; position: fixed; inset: 0; z-index: 90; pointer-events: none;
    background-size: var(--korn-groesse) var(--korn-groesse);
}
.theme-eink body::before { background-image: var(--rauschen-dunkel); opacity: var(--korn-dunkel); }
.theme-eink body::after  { background-image: var(--rauschen-hell);   opacity: var(--korn-hell); }

/* KEINE UNSCHAERFE MEHR (Roger, 31.07.2026: "die unschaerfe bei den bildern bitte
   rausnehmen"). Sie stand hier als blur(.58px) plus scale(1.03), das zweite nur, um
   die ausgefransten Kanten des ersten zu verdecken - mit dem blur faellt auch das
   weg. Was bleibt, ist die Daempfung: Farb-E-Ink zeigt Farbe, aber zurueckgenommen.
   FALLS ES JEMAND ZURUECKHOLEN WILL: auf die BILDER, nie auf den Text. E-Ink-Schrift
   ist gestochen scharf, weichgezeichneter Text liest sich als kaputter Bildschirm. */
/* DIE BILDDAEMPFUNG IST RAUS (31.07.2026), und sie war ein echter Fehler - kein
   Verdacht. Die Beweisfuehrung steht in Rogers eigenen Meldungen:
       Daempfung raus  -> "horizontal scrollen auf dashboard jetzt gut"
       Daempfung drin  -> "die box scrollt auch horizontal nicht gut im eink"
   WARUM: ein CSS-filter zwingt den Browser, jedes betroffene Bild beim Scrollen neu
   zu rastern. Ein Bildstreifen zieht genau diese Bilder durchs Bild, also faellt es
   dort am staerksten auf. Das trifft alle Themes gleich - nur hatte bloss das E-Ink
   ueberhaupt einen Filter.
   ICH HABE SIE ZU FRUEH ZURUECKGEHOLT. Rogers "daran lag es nicht" galt dem
   SENKRECHTEN Klemmen, und dafuer stimmt es auch; die Daempfung war die Ursache des
   WAAGERECHTEN Ruckelns. Zwei Fehler, die ich als einen behandelt habe.
   ES WAR OHNEHIN MEINE ZUTAT, nicht Rogers Vorgabe: "ah die bilder weiter in farbe",
   "es gibt ja auch farbige e-inks" (31.07.2026). Farb-E-Ink zeigt Farbe.
   FALLS SIE JEMAND ZURUECKHOLT: nicht als filter auf jedem Bild. Ein Filter im
   Scrollweg ist Rasterarbeit je Bild und je Vollbild.

   GEGENPROBE LAEUFT (31.07.2026): die Zeile ist WIEDER DRIN, damit der Verdacht
   nicht nur auf dem Ausschluss ruht. Ruckelt es jetzt erneut, ist die Daempfung
   ueberfuehrt statt bloss verdaechtig - und dann fliegt sie endgueltig. Ruckelt es
   NICHT, war der Ausschluss vorhin ein Zufall und die Suche geht weiter. */
.theme-eink .nthumb img,
.theme-eink .tbg img { filter: saturate(.62) contrast(.92) brightness(1.06); }

/* DIE FLAECHE STATT DES VERLAUFS stand bis zum 31.07.2026 hier, als E-Ink-Regel.
   Sie gilt seit demselben Tag fuer BEIDE Themes und ist deshalb zu .tlead.hasbg
   .ttext hochgewandert; --news-bg-rgb faerbt sie ohnehin pro Theme. Die zweite
   Regel (.theme-eink .tbg::after) ist mit dem Verlauf weggefallen: was der ::after
   noch traegt, sind die Scanlines, und die stehen hier auf --news-scan-strength: 0. */

/* MONO BLEIBT, WO ZEICHEN ZAEHLEN (Roger, 31.07.2026). Die zwei simulierten
   Terminaleingaben, dazu alles was Code ist: die Syntaxbeispiele unter der leeren
   Suche, die Logzeilen im Admin und die zwei Textfelder, in die Suchausdruecke
   getippt werden. Formularelemente erben die Schrift NICHT, deshalb stehen die
   Felder ausdruecklich mit drin. */
.theme-eink .term-input,
.theme-eink .searchinput,
.theme-eink code,
.theme-eink textarea[name="box_rules"],
.theme-eink textarea[name="mute_keywords"],
.theme-eink .logcard .tinfo { font-family: var(--news-mono); }
/* ZWEI ICON-SAETZE STEHEN IM MARKUP, EINER IST SICHTBAR (31.07.2026, Issue #6).
   Der Grund steht bei newsIcon() in news_functions.php: das Theme wechselt ohne
   Seitenaufbau, also muss auch der Iconsatz sofort wechseln koennen. */
/* !important, UND ZWAR NOETIG (31.07.2026, Roger: "links oben ist noch ein
   fehler"): Der Portal-Umschalter gibt seinem Icon ein inline-style mit
   display:inline-block mit, und ein inline-style schlaegt JEDE Klassenregel. Damit
   stand das ausgeblendete Icon doch da - beide Saetze uebereinander, sichtbar als
   verdoppeltes Raster oben links. Es ist die einzige Stelle mit einem display im
   Attribut, aber die Regel muss gegen jedes kuenftige gewinnen: sie entscheidet
   ueber Sichtbarkeit, nicht ueber Aussehen. */
.theme-crt .ico-eink,
.theme-eink .ico-crt { display: none !important; }

/* HIER STAND EINE DUOTONE-REGEL (--fa-secondary-opacity: 0.35), entfallen am
   31.07.2026 mit dem Wechsel auf fa-sharp fa-light. Die Icons faerben sich aus
   currentColor, und das ist der Grund, warum sie in jeder Umgebung stimmen: auf
   Papier, auf einer Tinteflaeche beim Hover, in einem Warnknopf - jede setzt ihre
   eigene Schriftfarbe schon. Feste Werte muessten all das nachbilden. */


/* ===========================================================================
   WETTERKOPF (02.08.2026, Issue #9) — includes/weatherhead.php
   Steht ueber dem Kachelraster, nur bei cat=weather. Alles hier traegt ein w-
   Praefix, damit keine dieser Regeln je eine Kachel erwischt.
   =========================================================================== */
.wetter { margin: 0 0 22px; }

/* DER GEZEIGTE ORT, INVERS (02.08.2026). Dieselbe Sprache wie ueberall im Portal:
   eine Akzentflaeche mit --news-on-accent heisst "das hier ist aktiv" - der aktive
   Nav-Punkt, der aktive Menueeintrag, im E-Ink die Schlagzeile mit Boxregel. Und
   damit traegt es im E-Ink von selbst, wo der Akzent zur TINTE wird: aus dem
   Bernsteinblock wird ein Tintenblock, der Text darauf Papier. */
.wortkopf { margin: 0 0 12px; }
.wortaktiv {
    display: inline-block; padding: 3px 12px; border-radius: var(--news-r-s);
    background-color: var(--news-accent); color: var(--news-on-accent);
    font-size: 14px; letter-spacing: 0.06em;
    box-shadow: 0 0 10px var(--news-glow);
}

/* DER ERSTE UEBERBLICK, NUR AUF SCHMALEN SCHIRMEN (issue #55, 07.08.2026). Die
   Begruendung steht am Markup in weatherhead.php; hier steht nur, was CSS entscheidet.
   AUSGEBLENDET IST DER NORMALFALL und .handy schaltet es ein - nicht umgekehrt: die
   Klasse kommt aus dem Skript in header.php, und das laeuft im <head>, bevor
   irgendetwas sichtbar wird. Ein @media hier waere die 640 px ein zweites Mal.
   DIE ZEILE IST EIN FLEX-ROW und die Reihenfolge im Bild ist die der Wichtigkeit:
   Tag, Symbol, Regen, Temperatur ganz rechts, wo das Auge sie an allen fuenf Zeilen
   uebereinander findet. */
.wuebersicht { display: none; }
.handy .wuebersicht {
    display: block; margin: 0 0 14px;
    background: var(--news-bg-dark); border: 1px solid var(--news-line);
    border-radius: var(--news-r-xl); overflow: hidden;
}
.wujetzt {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 11px; border-bottom: 1px solid var(--news-line);
}
.wujetzt .wusym { width: auto; font-size: 30px; line-height: 1; }
.wugrad { font-size: 30px; color: var(--news-bright); line-height: 1; }
.wumeta { color: var(--news-dim); font-size: 11.5px; line-height: 1.35; }
.wumeta b {
    display: block; color: var(--news-text); font-weight: 400;
    font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase;
}
.wuzeile {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 11px; border-bottom: 1px solid var(--news-line);
}
.wuzeile:last-child { border-bottom: 0; }
/* Wochenende und Regen wie im Baustein: die eine als Hintergrund-FARBE, die andere
   als Hintergrund-BILD, damit sie sich an einem Sonntag mit Regen von selbst
   uebereinanderlegen statt als dritter, handgemischter Wert zu existieren. */
.wuwe { background-color: color-mix(in srgb, var(--news-text) 4%, transparent); }
.wunass {
    background-image: linear-gradient(color-mix(in srgb, var(--news-regen) 12%, transparent),
                                      color-mix(in srgb, var(--news-regen) 12%, transparent));
}
.wutag {
    flex: 0 0 58px; color: var(--news-bright);
    font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase;
}
.wutag i { display: block; color: var(--news-dim); font-style: normal; font-size: 10.5px; letter-spacing: 0.02em; }
.wusym { flex: 0 0 22px; display: flex; justify-content: center; font-size: 18px; color: var(--news-text); line-height: 1; }
/* Dieselben drei Farben wie an den Kacheln (issue #42) - die Wolke bleibt grau,
   fuer eine Wolke IST Grau die Farbe. */
.wusym .fa-sun { color: var(--news-sonne); }
.wusym .fa-droplet, .wusym .fa-umbrella, .wusym .fa-snowflake { color: var(--news-regen); }
.wusym .fa-bolt { color: var(--news-danger); }
/* Die mittlere Zelle traegt je nach Tag Regen oder Sonne (07.08.2026) - deshalb heisst
   sie nach ihrer LAGE und nicht nach einem der beiden Inhalte. Die Farbe ist das Etikett:
   dieselben zwei, die auch die Symbole und die Diagramme benutzen. */
.wuwert { margin-left: auto; color: var(--news-dim); font-size: 11.5px; white-space: nowrap; }
.wuwert.nass   { color: var(--news-regen); }
.wuwert.sonnig { color: var(--news-sonne); }
.wutemp { flex: 0 0 78px; text-align: right; font-size: 15px; color: var(--news-bright); white-space: nowrap; }
.wutemp span { color: var(--news-dim); font-size: 12.5px; }

/* DIE KAPITEL SIND <details>. Das Browser-Dreieck ist ersetzt durch einen Pfeil,
   der sich beim Oeffnen dreht — dieselbe Geste wie am Kopfknopf: eine Bewegung,
   kein zweites Bild. */
.wkap { margin: 20px 0 0; }
.wkap > summary {
    font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase;
    color: var(--news-accent); font-weight: 600; cursor: pointer;
    border-bottom: 1px solid var(--news-line); padding: 0 0 6px 20px;
    margin-bottom: 12px; list-style: none; position: relative; user-select: none;
}
.wkap > summary::-webkit-details-marker { display: none; }
.wkap > summary::before {
    content: "\25B8"; position: absolute; left: 2px; top: -1px; font-size: 13px;
    display: inline-block; transition: transform 0.12s ease;
}
.wkap[open] > summary::before { transform: rotate(90deg); }
.wkap > summary:hover { color: var(--news-bright); }
/* Der Sprung ins Radar sitzt in der Karte von Kapitel 1, rechtsbuendig (Roger,
   03.08.2026, mit einem Pfeil genau dorthin). .wjetzt ist ohnehin eine Flex-Zeile,
   margin-left: auto schiebt ihn ans Ende; mittig statt unten, weil die Zeile an der
   Unterkante ausgerichtet ist und ein 42px-Knopf dort abrutschen wuerde. */
.wrsprung { margin-left: auto; flex: 0 0 auto; align-self: center; }
.wkap > summary small {
    color: var(--news-dim); text-transform: none; letter-spacing: 0;
    font-weight: 400; font-size: 12px; margin-left: 10px;
}

.wkarte {
    background-color: var(--news-bg-dark); border: 1px solid var(--news-line);
    border-radius: var(--news-r-xl); padding: 15px 17px;
}

/* DIE WARNUNG STEHT UEBER ALLEN KAPITELN und in keinem Aufklapper. Warnfarben
   bleiben auch im E-Ink farbig — die eine bewusste Ausnahme von der
   Graustufenregel (siehe --news-danger). */
.wwarn {
    border: 1px solid var(--news-danger-line); background-color: var(--news-accent-fill);
    border-radius: var(--news-r-m); padding: 9px 13px; margin-bottom: 4px;
}
.wwarn b { color: var(--news-danger); letter-spacing: 0.05em; font-weight: 600; }
.wstufe { color: var(--news-dim); font-size: 11px; margin-left: 8px; letter-spacing: 0.06em; }
.wwtext { color: var(--news-dim); font-size: 12px; margin-top: 3px; }
/* JEDE WEITERE WARNUNG IM SELBEN KASTEN, durch eine Haarlinie getrennt (03.08.2026,
   Roger: "ja bitte alle ausgeben"). Ein eigener Kasten je Warnung waere die andere
   Moeglichkeit und ist es nicht: sie gelten fuer denselben Landkreis und stehen fuer
   dieselbe Stunde, das ist EINE Meldung mit mehreren Zeilen. Der Rand ist derselbe
   wie der des Kastens, damit die Trennung nicht lauter ist als seine Umrandung. */
.wwmehr { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--news-danger-line); }

.wjetzt { display: flex; gap: 34px; align-items: flex-end; flex-wrap: wrap; }
.wgrad { font-size: 52px; line-height: 0.95; color: var(--news-bright); font-weight: 600; }
.wgrad i { font-style: normal; font-size: 15px; color: var(--news-dim); font-weight: 400; margin-left: 8px; }
/* Himmelssymbol neben der Gradzahl. 40px wie im "heute"-Feld des Dashboard-Bausteins
   (.dwheute .dwsym), damit dasselbe Wetter an beiden Stellen gleich gross aussieht;
   line-height: 1, sonst traegt die Zeilenhoehe der Schrift den Kasten hoeher als das
   Zeichen und .wjetzt richtet die Reihe an der falschen Unterkante aus. */
.wjsym { font-size: 40px; color: var(--news-text); line-height: 1; }
/* EIN GITTER, KEINE &nbsp;-KETTE: --news-font wechselt mit dem Theme (Literata im
   E-Ink), und eine Ausrichtung ueber Leerzeichen haelt nur, solange jedes Zeichen
   gleich breit ist. */
.wkv { display: grid; grid-template-columns: auto auto; gap: 2px 12px; margin: 0; font-size: 13px; }
.wkv dt { color: var(--news-dim); }
.wkv dd { color: var(--news-text); margin: 0; }

/* SEITLICH SCROLLBAR — UND overflow-y BLEIBT HIER NICHT visible, auch wenn nichts
   davon dasteht. Das ist die CSS-Regel, die ich hier am 02.08.2026 falsch
   aufgeschrieben hatte: ist EINE Achse nicht visible, rechnet der Browser die
   andere auf auto. Gemessen an der ausgelieferten Seite: overflow-x auto,
   overflow-y AUTO. Jeder dieser Rahmen ist damit ein senkrechter Scroll-Container,
   und er liegt genau dort, wo man zum Blaettern hintippt. Er meldet zwar 0 px
   senkrechten Ueberlauf, aber ein Geraet mit anderer Pixeldichte rundet anders -
   und "ueberlaeuft um einen Bruchteil eines Pixels" war genau die Radfalle des
   Dashboards (.dsstrip, 31.07.2026).
   HIER STAND touch-action: pan-x pinch-zoom, EINGEBAUT UND WIEDER RAUS AM 02.08.2026
   - und der Irrtum ist die Zeile wert, weil er naheliegt: ich hatte es als "dieser
   Kasten nimmt waagerechte Gesten, senkrechte gehoeren der Seite" beschrieben.
   touch-action REICHT ABER NICHTS WEITER. Es sagt dem Browser, welche Gesten fuer
   eine Beruehrung ueberhaupt erlaubt sind, die IN diesem Kasten beginnt - pan-x
   verbietet das senkrechte Schieben also GANZ, statt es dem Elternteil zu geben.
   Mit den Kapiteln 1 und 2 offen liegen diese Rahmen ueber dem halben Handyschirm,
   also genau dort, wo der Finger aufsetzt: die Seite war damit unscrollbar, sobald
   man sie an der Grafik anfasste. Roger hat es gemeldet, ich hatte es gebaut.
   OHNE ANGABE gilt touch-action: auto, und der Browser entscheidet richtig: die
   waagerechte Geste bekommt der Rahmen, die senkrechte reicht er nach aussen, weil
   er senkrecht nichts zu scrollen hat. */
/* BLAETTERKNOEPFE AN DEN RAHMEN (03.08.2026, Issue #27). Sie entstehen im Browser,
   siehe includes/weathernav.php - hier steht nur, wie sie aussehen und wann.
   .wnav-an SITZT AM WRAPPER und wird vom Skript gesetzt: sichtbar nur, wo wirklich
   etwas ueberlaeuft. Deshalb sind sie am PC in Kapitel 3 und 4 da und im Bike-Block
   nicht, ohne dass eine Regel das aufzaehlen muesste. */
.wnavwrap { position: relative; }
.wnav { position: absolute; top: 50%; transform: translateY(-50%); display: none; z-index: 2; }
.wnav-an > .wnav { display: inline-flex; }
/* SIE STEHEN NEBEN DER KARTE, NICHT DARIN (Roger, 03.08.2026: "ich weiss wir haben
   eine maximalbreite aber koennte man fuer diesen fall die pfeile ausserhalb der box
   platzieren?"). Es geht, und die Bahn ist GERECHNET:
     .wkarte  17px Polster + 1px Rand   = 18px bis zur Kartenkante
     .contentcontainer  padding 0 40px  = 40px bis zum Buehnenrand
   Dazwischen liegt nichts weiter - .wetter und .wkap bringen kein Polster mit. Die
   40px gibt es bei JEDER Fensterbreite, kein Media-Query fasst sie an; deshalb
   braucht diese Loesung keine zweite fuer schmale Fenster.
   DARAUS FOLGT DIE KNOPFGROESSE: 24px (der Portal-Standard) plus 8px Abstand zur
   Karte laesst 8px zum Buehnenrand - symmetrisch. Ein 42px-Knopf passt nicht, und
   das ist der Grund, warum weathernav.php nicht .icon.large nimmt.
   -50px = 18 (Karte) + 8 (Luft) + 24 (Knopf). */
.wprev { left: -50px; }
.wnext { right: -50px; }
/* AM ANSCHLAG BLASS STATT WEG: verschwaende der Knopf, ruckte der andere unter dem
   Zeiger weg und man muesste ihn zum Zurueckblaettern neu suchen. Der Hover muss
   mitgenommen werden - .btn faerbt sich sonst auch dann noch amber (im E-Ink TINTE),
   wenn der Knopf gar nichts mehr tut. */
.wnav:disabled { opacity: .3; cursor: default; }
.wnav:disabled:hover { background: var(--news-bg-dark); border-color: var(--news-line); color: var(--news-text); }

/* --- REGENRADAR, Kapitel 6 (Issue #9, 03.08.2026) ---------------------------
   Gezeichnet wird im Browser auf einem <canvas>: dieser Host hat weder GD noch
   Imagick, PHP kann hier also gar kein Bild erzeugen. Das Raster ist 101x101 Punkte
   a 1 km; das Canvas traegt genau diese Groesse und wird per CSS hochskaliert - so
   entscheidet der Browser ueber die Glaettung und wir muessen nichts rechnen.
   QUADRATISCH ueber aspect-ratio, damit ein Kilometer waagerecht so lang ist wie
   senkrecht. Ohne das waere die Karte verzerrt und jede Entfernung darauf gelogen. */
/* DER GANZE BLOCK IST HALB SO BREIT (Roger, 03.08.2026: "ich meinte den ganzen block
   halb so gross"). Erst hatte ich nur die KARTE halbiert - dann standen Zeitleiste,
   Skala und Hinweis weiter ueber die volle Buehne und die Karte klebte links in einem
   viel zu weiten Rahmen. Halbiert man den Kasten, halbiert sich alles darin mit, und
   die Karte darf wieder seine volle Breite nehmen.
   min-width, damit auf schmalen Fenstern nicht die Legende breiter wird als das Bild.
   ZIEHBAR MIT DEM EINGEBAUTEN GRIFF (Roger, 03.08.2026: "oder kannst du diese kachel
   ziehbar machen?"). `resize: horizontal` ist eine CSS-Eigenschaft - der Browser malt
   den Griff unten rechts und zieht selbst, es braucht kein Zieh-Skript. Bedingung ist
   ein overflow ausser `visible`, deshalb steht hier hidden; die Karte darin haengt an
   aspect-ratio, die Hoehe folgt der Breite also von allein.
   DIE GEZOGENE BREITE MERKT SICH DER BROWSER, nicht der Server - und das ist Rogers
   eigenes Argument: "ist ja immer abhaengig davon wo ich schaue". Eine Pixelzahl vom
   Laptop waere auf dem Handy Unsinn, und settings gilt fuer alle Geraete. Das ist die
   bewusste Ausnahme von der Hausregel, dass Einstellungen in die Datenbank gehoeren. */
.wradar { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
/* HALBE BREITE UND ZIEHGRIFF NUR IM WETTERKOPF - dort traegt die Karte zusaetzlich
   .wkarte. Auf dem Dashboard (.dashradar) bestimmt der Baustein die Groesse, und zwei
   Ziehmechanismen an einem Kasten waeren einer zu viel. */
/* box-sizing: border-box IST HIER DIE FEHLERBEHEBUNG (Issue #36, 04.08.2026, Roger:
   "mit jeden reload wird die box groesser" - und zwar um genau 36px je Reload).
   .wkarte hat padding 15/17 und einen 1px-Rand, und dieses Stylesheet hat KEIN
   globales border-box. Damit rechneten die zwei Stellen, die sich die gemerkte Breite
   zuwerfen, in verschiedenen Massen:
     radarjs.php SPEICHERT getBoundingClientRect().width  -> die RANDbox
     radarjs.php SETZT     style.width                    -> im content-box-Modell die
                                                             INHALTSbreite
   Jeder Reload legte also 2*17 + 2*1 = 36px drauf, und der naechste speicherte den
   neuen, groesseren Wert. Mit border-box meinen beide dasselbe.
   NICHT IM SKRIPT GEHEILT (etwa durch Abziehen der Polster): eine Zahl, die an zwei
   Stellen umgerechnet werden muss, ist genau die Bauart, die diesen Fehler erzeugt
   hat. Preis: die Karte ist einmalig 36px schmaler als vorher, weil die 50 % jetzt
   die Aussenbreite sind - das ist die richtige Lesart von "halbe Buehne".
   DER BAUSTEIN IST NICHT BETROFFEN: das Merken haengt an `kap` und laeuft nur im
   Wetterkopf; auf dem Dashboard zieht man am Baustein. */
.wkarte.wradar { width: 50%; min-width: 300px; max-width: 100%; box-sizing: border-box;
                 resize: horizontal; overflow: hidden; }
/* ALS BAUSTEIN: der Rahmen kommt vom Baustein, die Buehne nimmt die Resthoehe und
   richtet ihre BREITE danach (aspect-ratio wirkt in beide Richtungen). So bleibt die
   Karte quadratisch, egal wie Roger den Baustein zieht, und steht mittig. */
.dashradar { display: flex; flex-direction: column; height: var(--mpx);
             background: var(--news-bg-dark); border: 1px solid var(--news-line);
             border-radius: var(--news-r-xl); overflow: hidden; }
.dashradar .wradar { flex: 1 1 auto; min-height: 0; padding: 10px 12px 12px; }
.dashradar .wrbuehne { flex: 1 1 auto; min-height: 0; width: auto; height: auto;
                       margin: 0 auto; max-width: 100%; }
.wrkopf { display: flex; align-items: center; gap: 12px; }
.wrzeit { font-size: 20px; color: var(--news-bright); font-weight: 600; }
/* "measured" oder "forecast" - der Unterschied ist der ganze Witz des Nowcasts. */
.wrlage { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--news-dim); }
.wrfc .wrlage { color: var(--news-accent); }
.wrkopf .wrplay { margin-left: auto; }
/* DIE ZEITLEISTE (Roger, 03.08.2026: "kann man die zeit auch auf eine zeitlinie
   setzen oben?"). Ein Feld je Bild, gleich breit - die Schritte liegen ja auch
   gleich weit auseinander, eine Leiste mit ungleichen Feldern wuerde eine
   Ungleichheit behaupten, die es nicht gibt.
   DIE UHRZEIT STEHT AN JEDEM ZWEITEN FELD: bei acht Bildern passen acht Zeiten
   nebeneinander nicht, bei dreien schon - deshalb blendet die Regel unten die
   ungeraden aus, sobald es eng wird, statt eine Zahl zu erfinden.
   VORHERSAGEFELDER SIND HOHL: derselbe Unterschied wie der gestrichelte Rahmen um
   die Karte, an derselben Stelle noch einmal - man liest die Leiste, bevor man
   die Karte liest. */
.wrleiste { display: flex; gap: 3px; }
.wrtick { flex: 1 1 0; min-width: 0; background: none; border: 0; padding: 0;
          cursor: pointer; font-family: inherit; color: var(--news-dim); }
.wrtick i { display: block; height: 4px; border-radius: 2px; background: var(--news-line); }
.wrtick.fc i { background: none; box-shadow: inset 0 0 0 1px var(--news-line); }
.wrtick span { display: block; font-size: 9px; letter-spacing: .02em; padding-top: 3px;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wrtick:hover i { background: var(--news-dim); }
.wrtick.on i { background: var(--news-accent); box-shadow: none; }
.wrtick.on span { color: var(--news-bright); }
@media (max-width: 1100px) { .wrtick:nth-child(even) span { visibility: hidden; } }
/* Die Karte nimmt die volle Breite IHRES Kastens - und der ist halb so breit wie die
   Buehne, siehe .wradar. Ueber die ganze Buehne wuerde sie durch aspect-ratio 1/1
   auch voll HOCH: bei 1370px waere das ein Bildschirm nur fuer das Radar.
   ew-resize sagt vorher an, dass hier waagerecht die Zeit liegt. */
/* cursor: ew-resize IST RAUS (Issue #39): er versprach den Ziehgriff des
   Rahmens, aber IN der Buehne schiebt man seit der Karte den Ausschnitt.
   Leaflet setzt dort seinen eigenen Zeiger. */
.wrbuehne { position: relative; width: 100%; aspect-ratio: 1 / 1;
            border-radius: var(--news-r-m); overflow: hidden; background: var(--news-bg); }
/* HIER STAND .wrcanvas - die Buehne WAR ein Canvas. Seit dem 04.08.2026 (Issue #39)
   ist sie eine Leaflet-Karte, und das Radar eine Ebene darauf. */
.wrmap { position: absolute; inset: 0; background: var(--news-bg); }
/* DIE KACHELN FOLGEN DEM THEME - hell im E-Ink, dunkel im CRT - und werden im
   E-Ink genauso gedaempft wie jedes andere Bild (siehe 5. DESIGN). Ohne das
   staende eine bunte Karte unter einem gedaempften Portal. */
.theme-eink .wrmap img { filter: saturate(0.62); }
/* Leaflets eigene Bedienelemente in die Portalsprache: klein, kantig, leise.
   Die Attribution ist Pflicht (OSM-Lizenz, CARTO-Bedingungen) und bleibt lesbar,
   nur eben nicht laut. */
/* LEAFLETS BEDIENELEMENTE IN DIE PORTALLEITER (04.08.2026, nachgereicht). Sie kamen
   mit ihren eigenen Massen: 26px (auf Touch 30) und 4px Eckenradius, beides fremd in
   einem Portal, dessen Knoepfe 24px sind und dessen Radien eine Leiter bilden, die
   das E-Ink auf 2/1px zusammenzieht. Ueberschrieben statt umgebaut: Leaflet setzt
   die Werte je Kind (:first-child/:last-child), also muessen die Regeln dieselben
   Stellen treffen. Die Touch-Regeln tragen eine Klasse mehr und brauchen sie auch.
   HOVER FUELLT, wie jeder andere Knopf hier - im CRT bernstein, im E-Ink Tinte. */
.wrmap .leaflet-bar a, .leaflet-touch .wrmap .leaflet-bar a {
    width: 24px; height: 24px; line-height: 24px;
    background: var(--news-bg-dark); color: var(--news-text);
    border-bottom-color: var(--news-line); }
.wrmap .leaflet-bar { border: 1px solid var(--news-line); border-radius: var(--news-r-s); }
.wrmap .leaflet-bar a:first-child, .leaflet-touch .wrmap .leaflet-bar a:first-child {
    border-radius: var(--news-r-s) var(--news-r-s) 0 0; }
.wrmap .leaflet-bar a:last-child, .leaflet-touch .wrmap .leaflet-bar a:last-child {
    border-radius: 0 0 var(--news-r-s) var(--news-r-s); }
.wrmap .leaflet-bar a:hover, .wrmap .leaflet-bar a:focus {
    background: var(--news-accent); color: var(--news-on-accent); }
.wrmap .leaflet-bar a.leaflet-disabled { background: var(--news-bg-dark); color: var(--news-dim); }
/* DIE LEAFLET-FLAGGE IST WEG (Roger sah sie im Bild). Das gelb-blaue Faehnchen ist
   das einzige Bunte auf der Seite ausser dem Radar, und in dieser Palette darf Farbe
   nur stehen, wo sie ETIKETT ist. Die NENNUNG von OpenStreetMap und CARTO bleibt -
   die ist Lizenzpflicht; das Faehnchen ist es nicht. */
.wrmap .leaflet-attribution-flag { display: none; }
/* EIN ORT OHNE RADARDATEN ZEIGT NUR DIE KARTE (04.08.2026, Roger fuer Auxais:
   "koennen wir fuer diese faelle denn immerhin die karte anzeigen mit dem ort
   zentriert", und "bei orten ohne radardaten faellt dann halt die timeline weg").
   MIT DER LEISTE GEHEN ABSPIELKNOPF, ZEITANGABE UND FARBSKALA: es gibt keine
   Bilder zum Blaettern, keine Zeit zum Anzeigen, und die Skala erklaert
   Radarfarben - ohne Radar erklaert sie nichts. Was bleibt, ist die Karte mit
   dem Ortspunkt und die Hinweiszeile, die sagt warum.
   UEBER EINE KLASSE AM KASTEN und nicht im Markup weggelassen: ob es Daten gibt,
   weiss erst der Abruf - beim Seitenaufbau steht das noch nicht fest. */
.wrleer .wrleiste, .wrleer .wrplay, .wrleer .wrzeit,
.wrleer .wrlage, .wrleer .wrskala { display: none; }
/* Der Kopf traegt dann nichts mehr - ohne das bliebe eine leere Zeile stehen. */
.wrleer .wrkopf { display: none; }
/* DIESELBEN REGELN GELTEN SEIT DEM 04.08.2026 (abends) AUCH FUER DEN TROCKENEN FALL,
   und zwar ohne eine einzige Zeile hier (Roger: "wenn die zeitleiste zu sehen ist, dann
   weiss ich dass dort auch irgendwo niederschlag ist"). radarjs.php setzt .wrleer jetzt
   auch, wenn im Fenster kein Niederschlag liegt - damit ist die blosse ANWESENHEIT der
   Leiste die Aussage, dass dort etwas zu sehen ist.
   ICH HATTE HIER EINE ZWEITE, GLEICHLAUTENDE REGEL ERGAENZT, weil ein selbst
   geschriebenes Suchmuster den Selektor oben uebersehen hatte - er geht ueber zwei
   Zeilen, das Muster verlangte die Klammer in derselben. Wieder entfernt. */
.wrmap .leaflet-control-attribution { background: var(--news-bg-dark); color: var(--news-dim);
     font-size: 9px; font-family: var(--news-font); }
.wrmap .leaflet-control-attribution a { color: var(--news-dim); }
/* GESTRICHELT, SOLANGE ES VORHERSAGE IST: wer ein fortgeschriebenes Bild fuer eine
   Messung haelt, vertraut ihm zu weit. */
.wrfc .wrbuehne { outline: 1px dashed var(--news-accent); outline-offset: -1px; }
/* Der eigene Ort. Die Lage kommt aus latlon_position und steht in Prozent am
   Element - eine Pixelzahl waere bei jeder anderen Kartengroesse falsch. */
/* .wrmitte IST ENTFALLEN (Issue #39): der Ort sass als Punkt in PROZENT auf der
   starren Buehne. Mit einer schieb- und zoombaren Karte muss er am ORT kleben,
   nicht am Kasten - Leaflet zeichnet ihn jetzt als circleMarker. */
/* DIE .wrzoom-REGELN SIND ENTFALLEN (04.08.2026): das Portal-Paar +/- gibt es
   nicht mehr, Leaflet bringt sein eigenes mit. Siehe radarjs.php. */
.wrskala { display: flex; gap: 0; font-size: 10px; color: var(--news-dim); }
.wrskala span { flex: 1 1 0; padding: 3px 0 0; border-top: 3px solid; text-align: left; }
/* Dieselben Farben wie im Skript - die Skala IST die Beschriftung, siehe
   --news-regen: eine Graustufenleiter muesste man bei jedem Blick neu zuordnen. */
.wrskala span[data-s="1"] { border-color: #a8d8ff; }
.wrskala span[data-s="2"] { border-color: #4ca1ff; }
.wrskala span[data-s="3"] { border-color: #2f78e0; }
.wrskala span[data-s="4"] { border-color: #facc15; }
.wrskala span[data-s="5"] { border-color: #f97316; }
.wrskala span[data-s="6"] { border-color: #dc2626; }
/* Waehrend ein neuer Radius geholt wird, bleibt das alte Bild stehen und wird blass -
   ein leerer Kasten fuer 0,4 s sieht nach Fehler aus. */
.wrlaedt .wrbuehne { opacity: .45; }
.wrhinweis { font-size: 11px; color: var(--news-dim); }

.wscroll { overflow-x: auto; padding-bottom: 6px; }
.wscroll::-webkit-scrollbar { height: 8px; }
.wscroll::-webkit-scrollbar-thumb { background-color: var(--news-line); border-radius: var(--news-r-s); }
.wscroll::-webkit-scrollbar-track { background: transparent; }
.wscroll svg { display: block; }

/* Reihenbeschriftung ueber jeder Grafik. */
/* DER REGENSTREIFEN LAEUFT DURCH ZWEI REIHEN (Issue #45, 04.08.2026, Roger: "die
   streifen sollen durchgehen nicht unterbrechen"). Er kann deshalb kein <rect> im SVG
   sein: jede Reihe ist ein EIGENES SVG, dazwischen steht eine .wlg-Zeile - zwei
   Rechtecke haetten die Luecke und die Stundenachse dazu.
   EIN ELEMENT JE NASSER SPALTE, absolut ueber dem Kasten der drei Reihen. Die SVGs
   stehen mit width="$B" viewBox="0 0 $B ..." 1:1 in Pixeln, also trifft die absolute
   Position in px genau dieselbe Spalte - weatherhead.php rechnet left/width aus
   derselben $px-Funktion, aus der auch die Balken kommen.
   KEIN z-index: der Streifen ist das ERSTE Kind, die Reihen danach sind POSITIONIERT
   und malen dadurch darueber. Dokumentreihenfolge statt Stapelkontext - dieselbe
   Regel, mit der .ttext ueber .tbg liegt (siehe STACKING CONTEXTS in der Doku).
   WIE WEIT ER REICHT, ENTSCHEIDET DAS MARKUP und nicht diese Regel: was in
   .wnassreihen liegt, bekommt ihn. Heute sind das die Temperaturkurve und die
   Regenzeile; SUNSHINE und WIND stehen ausserhalb, so wie Roger es mit zwei roten
   Linien abgesteckt hat. */
.wnassreihen { position: relative; }
.wnassreihen > .wlg, .wnassreihen > svg { position: relative; }
/* ER FAENGT NICHT GANZ OBEN AN (Roger: "fast", mit einer roten Linie knapp unter der
   Tagesbeschriftung). Ganz oben liefe er durch die Zeile "Wed 05.08.", die zum
   DIAGRAMM gehoert und nicht zur Stunde. Den Abstand gibt weatherhead.php mit, weil
   die Zahl dort schon existiert - es ist der obere Rand der Temperaturkurve. */
.wnassband {
    position: absolute; top: var(--wnass-top, 0); bottom: 0; pointer-events: none;
    background: color-mix(in srgb, var(--news-regen) 12%, transparent);
}
.wlg { color: var(--news-dim); font-size: 11px; letter-spacing: 0.04em; margin: 9px 0 2px; }
.wlg span { margin-left: 8px; }
.wlg .wregen { color: var(--news-regen); }
.wlg .wsonne { color: var(--news-sonne); }

/* Beschriftungen IN den Grafiken. SVG faerbt ueber fill, nicht ueber color —
   deshalb sind das eigene Regeln und nicht dieselben wie oben. */
.wax { fill: var(--news-dim); font-family: var(--news-font); font-size: 12px; text-anchor: middle; }
.wax.whell { fill: var(--news-bright); font-size: 13px; font-weight: 600; }
.wax.whr { fill: var(--news-dim); font-size: 10px; letter-spacing: 0.06em; }
text.wax.wregen { fill: var(--news-regen); font-size: 11px; }

/* Tageszeilen (Block 4). Der Balken ist die Spanne des Tages gegen die aller
   gezeigten Tage. */
.wtz {
    display: flex; align-items: center; gap: 11px; padding: 5px 0;
    border-bottom: 1px solid var(--news-line); font-size: 13px;
}
.wtz:last-child { border-bottom: none; }
.wtz.wheute .wtg { color: var(--news-accent); }
/* WOCHENWECHSEL (02.08.2026). Ueber vierzehn Zeilen verliert man sonst, welcher Tag
   in welche Woche faellt. Ein Strich in der Akzentfarbe statt der Haarlinie, plus
   etwas Luft darueber - die Trennung soll man sehen, ohne dass sie die Zeile
   uebertoent, die daneben "today" heisst. */
.wtz.wwoche { border-top: 1px solid var(--news-accent-line); margin-top: 6px; padding-top: 10px; }
.wtg { width: 84px; color: var(--news-bright); }
.wti { width: 96px; color: var(--news-dim); font-size: 12px; }
.wtlo { width: 34px; text-align: right; color: var(--news-dim); }
.wthi { width: 36px; color: var(--news-bright); }
.wtb { flex: 1 1 auto; height: 4px; background-color: var(--news-line); border-radius: 2px; position: relative; min-width: 70px; }
.wtb i { position: absolute; top: 0; bottom: 0; border-radius: 2px;
         background: linear-gradient(90deg, var(--news-regen), var(--news-accent)); }
.wtsun { width: 46px; text-align: right; color: var(--news-sonne); font-size: 11px; }
.wtrg { width: 108px; text-align: right; color: var(--news-regen); font-size: 11px; }

/* PENDELWOCHE (02.08.2026) — eine Zeile je Werktag, zwei Zeitfenster darin.
   EIN GITTER UND KEINE TABELLE, damit die Spalten untereinander stehen und die
   Zeile auf dem Handy trotzdem umbrechen darf. */
.wpend { display: flex; flex-direction: column; }
/* JE WOCHE EINE UEBERSCHRIFT (02.08.2026). Ohne sie liefe die Liste von Mittwoch bis
   zum uebernaechsten Dienstag durch, und "welche Tage diese Woche" waere daraus nicht
   zu lesen. Die zweite Woche bekommt eine Linie darueber - derselbe Gedanke wie der
   Wochenwechsel in der Tagesliste. */
.wpwoche { display: flex; align-items: baseline; gap: 10px; padding: 2px 10px 6px;
           font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
           color: var(--news-accent); }
.wpwoche i { font-style: normal; letter-spacing: 0; text-transform: none;
             color: var(--news-dim); }
.wpwoche.wpwzweite { border-top: 1px solid var(--news-accent-line); margin-top: 12px; padding-top: 12px; }
/* DIE ZEILE BRICHT NICHT MEHR UM, sie SCROLLT (02.08.2026). Umgebrochen standen die
   zwei Fenster untereinander, und damit war die Spalte hin - der ganze Nutzen liegt
   darin, dass "07-10" aller Tage untereinander steht und man senkrecht vergleichen
   kann. width: max-content laesst die Reihe so breit werden, wie sie sein will;
   min-width: 100% haelt sie am Desktop trotzdem ueber die volle Breite. */
.wpend { width: max-content; min-width: 100%; }
.wpz {
    display: flex; align-items: center; gap: 10px; flex-wrap: nowrap;
    padding: 8px 10px; border-bottom: 1px solid var(--news-line);
    border-left: 2px solid transparent; font-size: 13px;
    background-color: var(--news-bg-dark);   /* fuer den mitlaufenden Tagesnamen */
}
.wpz:last-child { border-bottom: none; }
/* DER BUEROTAG IST DIE ANTWORT DER GANZEN REIHE, also traegt ihn die ZEILE und
   nicht ein Abzeichen darin: ein linker Balken plus eine leise Fuellung.
   DIE FUELLUNG IST GEMISCHT, NICHT GELEGT (02.08.2026, Roger: "die tage die stehen
   bleiben sind opaque, man sieht durch sie hindurch das weggescrollte"). Genau so
   war es: --news-accent-fill ist rgba(...,0.10), der mitlaufende Tagesname erbte
   diese Deckkraft, und beim Wischen lief der Inhalt sichtbar dahinter durch -
   gemessen an vier von fuenf Zeilen, weil nur die Buerotage die Fuellung tragen.
   color-mix ergibt DENSELBEN Ton, nur deckend, also kann der Tagesname ihn erben.
   Die Zeile davor ist der Rueckfall, falls color-mix fehlt - dieselbe Machart wie
   bei .hkbox. --news-accent-fill bleibt unangetastet: es haengt an einem Dutzend
   anderer Stellen, wo die Lasur richtig ist. */
.wpz.wbuero {
    border-left-color: var(--news-accent);
    background-color: var(--news-bg-dark);
    background-color: color-mix(in srgb, var(--news-accent) 10%, var(--news-bg-dark));
}
/* DER TAG BLEIBT STEHEN, waehrend die Fenster daneben wegscrollen - sonst weiss man
   nach zwei Zentimetern Wischen nicht mehr, welche Zeile man liest.
   background-color: inherit und NICHT ein fester Wert: die Buerotag-Zeile hat ihre
   eigene Fuellung, ein gesetzter Ton wuerde sie an dieser Stelle uebermalen. */
.wptag { flex: 0 0 150px; color: var(--news-bright);
         position: sticky; left: 0; background-color: inherit; padding-right: 6px; }
.wptag i { display: block; font-style: normal; font-size: 11px; color: var(--news-dim); }

/* EIN FENSTER. Feste Breiten, damit die Zahlen der Tage untereinander stehen -
   sonst muss das Auge je Zeile neu suchen, und genau das soll es nicht. */
.wpf {
    display: flex; align-items: center; gap: 8px;
    padding: 5px 9px; border: 1px solid var(--news-line); border-radius: var(--news-r-m);
    background-color: var(--news-bg);
}
.wpf b { color: var(--news-dim); font-weight: 400; font-size: 11px; width: 40px; }
/* GETOENT WIRD DIE ZELLE, NICHT DIE ZAHL: man soll hinsehen muessen, nicht lesen.
   ZWEI STAERKEN: ab 0,5 mm wird man nass (voller Rand), darunter ist Regen bloss
   angesagt (gestrichelt). Der Unterschied ist keine Kosmetik - in Rogers Woche sind
   0,1 mm vorhergesagt und trotzdem 87 % gemeldet, und eine Zelle, die beides gleich
   laut anzeigt, kostet ihn einen Buerotag fuer nichts. */
.wpf.wnass { border-color: var(--news-regen); background-color: var(--news-accent-fill); }
.wpf.wnass .wpp { color: var(--news-regen); }
.wpf.wvielleicht { border-style: dashed; border-color: var(--news-line-hover); }
.wpf.wvielleicht .wpp { color: var(--news-text); }
.wpi { width: 18px; text-align: center; color: var(--news-dim); }
.wpt.cold { color: var(--news-regen); }
.wpt.mild { color: var(--news-text); }
.wpt.hot  { color: var(--news-danger); }
.wpgrad { width: 66px; color: var(--news-text); }
.wpmm   { width: 58px; text-align: right; color: var(--news-dim); }
.wpp    { width: 40px; text-align: right; color: var(--news-dim); }
.wpurteil { flex: 0 0 auto; margin-left: auto; font-size: 11px; letter-spacing: 0.06em;
            text-transform: uppercase; color: var(--news-dim); }
.wpz.wbuero .wpurteil { color: var(--news-accent); }

/* HIER STAND ".handy .wpz { flex-direction: column }" - die Fenster untereinander.
   ENTFALLEN am 02.08.2026: gestapelt passte zwar alles auf den Schirm, aber die
   Spalte war hin, und die ist der ganze Nutzen (Roger sieht "07-10" aller Tage
   untereinander). Jetzt scrollt die Reihe seitlich, der Tagesname bleibt stehen. */
.handy .wptag { flex: 0 0 120px; }
.handy .wpurteil { margin-left: 12px; }

/* HINWEIS STATT LEERER KACHELWAND (02.08.2026). Die Wetter-Kategorie hat keine
   eigenen Quellen - ihre Nachrichten kommen aus einer gespeicherten Suche je Ort.
   Ist keine zugeordnet, steht hier, warum es leer ist und wo man das aendert. */
.wleer { margin-top: 20px; color: var(--news-dim); font-size: 13px; line-height: 1.7; }
.wleer a { color: var(--news-accent); }

/* Handy: die Zeile traegt sonst mehr Spalten, als der Bildschirm hat. Der Balken
   ist das erste, was gehen kann — er ist Form, die Zahlen sind die Aussage. */
.handy .wtb, .handy .wti { display: none; }
.handy .wjetzt { gap: 20px; }
.handy .wgrad { font-size: 40px; }
.handy .wkarte { padding: 12px 13px; }

