/* =====================================================================
   Sandbox — Look der Editorfläche (docs/AUFTRAG-SANDBOX.md, Auftrag B).
   Nutzt die --pp-Tokens, die auf body liegen (Muster lab.css) — die
   Fläche sieht damit in hell und dunkel richtig aus.
   ===================================================================== */

.sb-page { display: flex; flex-direction: column; min-height: 0; }

/* ---------------- Vollfläche (Auftrag E, 11.08.2026) ----------------
   Die Detailseite füllt den Inhaltsbereich randlos: kein Karten-/
   Kastenrahmen mehr um die Fläche, das Raster läuft bis an die Kanten
   (volle Höhe bis zum Fensterrand unter der 54-px-Portal-Kopfzeile,
   volle Breite neben der Seitenleiste). Die Kopfzeile der Sandbox
   schwebt als schmale Leiste darüber, statt die Fläche einzurücken.
   Seitenleiste und Portal-Kopfzeile bleiben — es geht um den Eindruck
   „Fläche statt Fenster", nicht um ein Overlay über der ganzen App. */
.pp-page.sb-page.sb-randlos {
  max-width: none; margin: 0; padding: 0;
  height: calc(100vh - 54px);
}
.sb-randlos .sb-kopf {
  margin: 0; padding: 6px 14px 3px; flex: none;
  border-bottom: 1px solid var(--pp-line);
  background: var(--pp-surface);
}
.sb-randlos .sb-ki { margin: 10px 14px 0; }
.sb-randlos .sb-canvas {
  height: auto; flex: 1; min-height: 0;
  border: 0; border-radius: 0;
}

/* ---------------- Kopfzeile ---------------- */
/* DREI ZEILEN nach Jonas' Entwurfsvorlage (W5, 23.08.2026), wörtlich das
   Muster des Schaltlabors (.lab-top + .lab-ribbon in css/lab.css):
     1. .sb-top    — „‹ Meine Inhalte", Flächenname, Speicherpunkt, Marke
     2. .sb-tabs   — Start · Einfügen · Anordnen · Ansicht · KI
     3. .sb-panel  — das Band des aktiven Reiters in benannten Gruppen
   Bis dahin war es EINE Zeile, in der die Reiter rechts neben dem Namen
   klebten und die Gruppennamen nur als aria-label existierten: Für eine
   sichtbare Beschriftungsebene unter den Knöpfen war schlicht kein Platz.
   Die Regeln unten setzen deshalb ausdrücklich flex-direction: column —
   `.sb-kopf` erbt sonst das `align-items: center` von früher, und das
   staucht jede Zeile auf ihre Inhaltsbreite. */
.sb-kopf {
  display: flex; flex-direction: column; align-items: stretch; gap: 0;
  margin-bottom: 12px;
}
/* Zeile 1. Der Speicherpunkt steht MITTIG: zwei `margin-left: auto`
   (an ihm und an der Marke) teilen den freien Platz zu gleichen Teilen —
   ohne feste Breiten, die bei langem Flächennamen brächen. */
.sb-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.sb-top .sb-status { margin-left: auto; }
/* --pp-text-2, NICHT --pp-text-3 (WebKit-Abnahme 23.08.2026): Die Marke
   ist 11 px klein und gesperrt gesetzt, und mit --pp-text-3 stand sie bei
   3,04 : 1 im hellen und 3,99 : 1 im dunklen Theme — beides unter den
   4,5 : 1, die WCAG AA für Text dieser Größe verlangt. Sie sagt, WO man
   ist; das ist keine Zierde. Mit --pp-text-2: 6,11 bzw. 7,04 : 1. */
.sb-marke {
  margin-left: auto; font-size: var(--pp-fs-mini); font-weight: 600;
  letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--pp-text-2); white-space: nowrap;
}
.sb-name {
  font: inherit; font-weight: 600; font-size: var(--pp-fs-gross);
  color: var(--pp-text); background: transparent;
  border: 1px solid transparent; border-radius: 7px; padding: 6px 8px;
  min-width: 140px; flex: 0 1 260px;
}
.sb-name:hover, .sb-name:focus { border-color: var(--pp-line-2); background: var(--pp-surface); }
/* Die Kurswahl im Kopf der Fläche (Plan 10 F): darf schrumpfen. */
.sb-kurs { flex: 0 1 auto; max-width: 200px; min-width: 0;
  border: 1px solid var(--pp-line-feld); background: var(--pp-surface); color: var(--pp-text);
  border-radius: 6px; padding: 6px 28px 6px 8px; font-size: var(--pp-fs-klein); font-family: inherit; min-height: 32px;
  /* WebKit malt ein natives Auswahlfeld in fester Höhe (21 px) und ohne
     Theme; ohne appearance greifen Höhe und Farben. Der Pfeil sind zwei
     Verläufe in der Textfarbe. */
  -webkit-appearance: none; appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--pp-text-2) 50%), linear-gradient(135deg, var(--pp-text-2) 50%, transparent 50%);
  background-position: calc(100% - 14px) 50%, calc(100% - 9px) 50%; background-size: 5px 5px; background-repeat: no-repeat; }
.sb-kurs:focus-visible { border-color: var(--pp-accent); }
.sb-status { font-size: var(--pp-fs-klein); white-space: nowrap; }
.sb-status.ok { color: var(--pp-ok); }
.sb-status.lauft { color: var(--pp-text-2); }
.sb-status.fehler { color: var(--pp-err); }

/* Zeile 3: das Band des aktiven Reiters. Gruppen NEBENEINANDER, jede eine
   Spalte aus Knopfreihe und Name darunter (Entwurfsvorlage W5). */
.sb-tools {
  display: flex; flex-direction: row; align-items: stretch;
  /* DAS BAND WISCHT WAAGERECHT, ES BRICHT NICHT UM (26.09.2026, Jonas:
     „die Menüleiste soll waagerecht scrollbar sein, damit die Symbole
     nicht ungeordnet untereinander stehen"). Bis dahin stand hier
     `flex-wrap: wrap` — auf der ganzen Seite ab 1100 px zwei Zeilen, in
     der 560-px-Spalte des Arbeitsfensters drei bis vier, und die Gruppen
     lagen dann als Treppe untereinander. Jetzt gilt ÜBERALL, was die
     Telefonregel unten seit dem 24.08.2026 für 860 px vorsah: eine Reihe,
     Bildlauf in der Leiste, der Wisch bleibt in ihr (overscroll-behavior).
     Die Aufklapper des Reiters „Einfügen" müssen deshalb auch am Laptop
     aus dem Kasten heraus — die Regel dafür steht bei .sb-add-wrap. Das
     Zeilenraster darunter bleibt: Es bemisst die HÖHE einer Spalte, nicht
     die Breite des Bandes. */
  flex-wrap: nowrap; gap: 0; padding-top: 5px;
  overflow-x: auto; overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;

  /* ---- Das ZEILENRASTER des Bandes (24.08.2026) ----
     Jonas' Befund: „Bandhöhe angleichen" — beim Reiterwechsel sprang die
     Zeichenfläche um 26 px (WebKit gemessen: Start/Einfügen/Anordnen
     99 px, KI 83, Ansicht 73).

     Gemessen, BEVOR etwas geändert wurde, und die naheliegende Erklärung
     war falsch: „Ansicht" ist nicht niedriger, weil ihm große Knöpfe
     fehlten — es hat zwei (je 48 px). Die Bandhöhe ist
     `5 px Innenabstand + höchste .sb-items + 18 px Gruppenname`, und die
     höchste .sb-items entscheidet die SPALTE der kleinen Einträge:
     drei Einträge = 76 px (Start, Einfügen, Anordnen), zwei = 50 px
     (Ansicht), ein großer zweizeiliger Knopf = 60 px (KI). Es hängt also
     daran, wie viele kleine Einträge ein Reiter zufällig hat.

     Deshalb steht hier ein RASTER, kein Sockel aus einer gegriffenen
     Zahl: Ein Menüband ist nach KLEIN_PRO_SPALTE Zeilen gebaut (Muster
     jeder Textverarbeitung), und sbGrp in js/sandbox.js schneidet nach
     genau so vielen eine zweite Spalte. --sb-raster ist damit die
     RECHNERISCHE OBERGRENZE dessen, was eine Spalte werden kann — nicht
     ein Wunschmaß. Die drei Zahlen stehen je EINMAL und werden unten
     von .sb-b.klein und .sb-kl mitbenutzt; wer eine ändert, ändert das
     Raster mit, statt zwei Werte auseinanderlaufen zu lassen.

     --sb-zeilen MUSS gleich KLEIN_PRO_SPALTE in js/sandbox.js sein;
     verify-sandbox-bandhoehe.js liest beide Dateien und vergleicht.

     Warum das NICHT die Falle des Einstellungsfensters ist (CLAUDE.md
     Abschnitt 5, 17.08.2026 — „ein min-height in Pixeln schlägt jedes
     max-height"): Dort kämpfte der Sockel gegen ein max-height und
     drückte das Raster über den Fensterrand. Hier gibt es in der ganzen
     Kette .sb-page > .sb-kopf > .sb-tools > .sb-grp > .sb-items KEIN
     max-height und keine Höhe, die sich am Fenster misst — die Seite ist
     `height: calc(100vh - 54px)`, die Fläche darunter `flex: 1;
     min-height: 0` und gibt nach. Ein Boden kann hier also nichts
     abschneiden und nichts über den Fensterrand drücken; er nimmt
     ausschließlich der Zeichenfläche Platz weg, und die gibt nach.

     WAS ER KOSTET, in WebKit A/B gemessen (dieselbe Sitzung, Raster zur
     Laufzeit aus- und eingeschaltet):
       1440 px  99/99/99/99/99  — Sprung 26 px -> 0, drei Reiter
                                  pixelgleich wie vorher
       1100 px  193/193/99/99/99 — die drei einzeiligen Reiter stimmen
                                  überein; Start und Einfügen brechen
                                  dort in ZWEI Zeilen um, und die zweite
                                  wird ebenfalls eine Rasterzeile:
                                  +28 bzw. +26 px, die Fläche verliert
                                  576 -> 548 px
        390 px  unverändert      — dort greift der Boden nicht (siehe
                                  die Telefonregel unten)
     Der Preis steht also bei EINER Fensterbreite und bei zwei Reitern,
     und er kauft dort, dass beide Bandzeilen gleich hoch sind statt um
     zwei Pixel verschieden (vorher 165 gegen 167). Wo das Band umbricht,
     macht KEINE Höhenregel die Reiter gleich hoch — sie haben dann
     verschieden viele Zeilen. Das ist die ehrliche Grenze dieses
     Auftrags, nicht ein übersehener Fall. */
  --sb-zeile: 24px;   /* Höhe einer kleinen Knopfzeile (.sb-b.klein) */
  --sb-lueft: 2px;    /* Abstand zwischen zwei kleinen Knöpfen (.sb-kl) */
  --sb-zeilen: 3;     /* = KLEIN_PRO_SPALTE in js/sandbox.js */
  /* Die Fallbacks stehen auch HIER, obwohl die drei Zeilen darüber
     stehen: Ein `var()` ohne Fallback wird ungültig, sobald jemand die
     Rechnung einmal auf einen anderen Knoten kopiert — und dann fiele
     min-height wortlos auf 0. Dieselbe Regel wie unten an den Knöpfen. */
  --sb-raster: calc(var(--sb-zeilen, 3) * var(--sb-zeile, 24px)
                    + (var(--sb-zeilen, 3) - 1) * var(--sb-lueft, 2px));
}
/* Der Fallback für alles, was NICHT über sbBtn() gebaut ist (die Einträge
   der Aufklapp-Menüs). Die Bandknöpfe selbst regelt .sb-b weiter unten.

   `:not(.sb-b)` ist kein Beiwerk, sondern die Reparatur eines
   Spezifitätsfehlers (WebKit-Abnahme 23.08.2026): `.sb-tools button` ist
   eine Klasse PLUS ein Elementname und schlug damit jede
   Ein-Klassen-Regel von `.sb-b`. Der Kommentar darüber sagte „die
   Bandknöpfe regelt .sb-b" — sie taten es nicht. Gemessen am gerenderten
   Knopf: 13 px statt 11,5 px Schrift, 7/10 px statt 5/8/4 px Innenabstand,
   7 px statt 6 px Ecken, --pp-text statt --pp-text-2 und beim aktiven
   Werkzeug --pp-accent statt --pp-accent-2. Sichtbar wurde es nicht als
   „falsche Farbe", sondern als BREITE: Das Band brauchte im Reiter
   „Start" 1390 px und brach bei 1440 px in zwei Zeilen um.
   `.gross`/`.klein` (zwei Klassen) kamen durch, alles an `.sb-b` allein
   nicht — ein Stylesheet, das zur Hälfte gilt, sieht aus wie ein
   Entwurfsfehler und ist keiner. */
.sb-tools button:not(.sb-b) {
  font: inherit; font-size: var(--pp-fs-klein); padding: 7px 10px; border-radius: 7px;
  border: 1px solid transparent; background: transparent;
  color: var(--pp-text); cursor: pointer; white-space: nowrap;
}
.sb-tools button:not(.sb-b):hover { border-color: var(--pp-accent-line); color: var(--pp-accent-2); }
.sb-tools button:not(.sb-b).on { border-color: var(--pp-accent); color: var(--pp-accent-2); background: var(--pp-accent-tint); }

/* ---- Bandknöpfe (W5) ----
   Groß: Symbol ÜBER dem Text, der Text darf zweizeilig werden.
   Klein: Symbol LINKS neben einer Zeile Text.
   Beide Formen sind randlos — die Fläche unter dem Zeiger ist die
   Rückmeldung, nicht ein Kasten um jeden Knopf (Muster button.rb im
   Labor). Ein aktives Werkzeug trägt die Akzentfläche. */
.sb-b {
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid transparent; border-radius: 5px;
  background: transparent; color: var(--pp-text-2);
  font: inherit; font-size: var(--pp-fs-mini); line-height: var(--pp-lh-eng);
  cursor: pointer; padding: 3px 8px 3px 6px; text-align: left;
}
/* Die Vorlage stellt große Einträge auf ein festes Rastermaß von 60 px.
   Gemessen passen zwei deutsche Beschriftungen dort nicht: „Funktions-
   plot" braucht 62 px und „Drucken/PDF" 60 px — beide liefen bei
   `width: 60px` über ihren Knopf hinaus in den Nachbarn. Deshalb 60 px
   als MINDESTmaß (das Raster bleibt sichtbar) und 76 px als Deckel: Was
   länger ist, bricht um, statt auszubrechen. */
.sb-b.gross {
  flex-direction: column; justify-content: flex-start; text-align: center;
  gap: 4px; min-width: 60px; max-width: 76px; white-space: normal;
  font-size: var(--pp-fs-mini); line-height: var(--pp-lh-knapp); padding: 6px 4px 5px; border-radius: 6px;
}
/* `min-height` bleibt: Die Vorlage kommt mit 3 px Innenabstand auf rund
   20 px Knopfhöhe, und das ist auf dem Telefon ein zu kleines Ziel — das
   Band IST dort die Werkzeugleiste. 24 px kosten in der Spalte 4 px je
   Zeile und sonst nichts (align-items: center zentriert den Inhalt). */
/* Der Wert steht als --sb-zeile oben: Aus ihm rechnet sich das Raster
   des Bandes, und zwei Zahlen für dieselbe Zeilenhöhe liefen beim ersten
   Nachbessern auseinander. Fallback 24px, weil ein `var()` ohne ihn
   ungültig wird, sobald ein Knopf einmal außerhalb von .sb-tools steht
   (CLAUDE.md: nie ein Token ohne Definition und ohne Fallback). */
.sb-b.klein { white-space: nowrap; min-height: var(--sb-zeile, 24px); }
/* Hover auf --pp-surface-2, NICHT --pp-surface: Die Kopfzeile steht
   selbst auf --pp-surface, und --pp-bg-2 ist byteweise dieselbe Farbe
   (Notiz bei .ki-msg.ki in prepp.css) — der Knopf hätte sich beim
   Überfahren nur über seinen Rand abgehoben. */
.sb-b:hover { background: var(--pp-surface-2, var(--pp-surface)); color: var(--pp-text); border-color: var(--pp-line); }
/* Die Beschriftung des AKTIVEN Knopfes steht auf --pp-accent-2, nicht auf
   --pp-accent (WebKit-Abnahme 23.08.2026): Der Akzent liegt hier auf der
   Akzentfläche und nicht auf --pp-surface, und dort kam er im hellen
   Theme nur auf 4,03 : 1 — bei 11,5 px verlangt WCAG AA 4,5. Ausgerechnet
   das Werkzeug, das GERADE GILT, war damit der einzige Text des Bandes
   unter der Grenze. --pp-accent-2 ist der dunklere Zwilling desselben
   Blaus (hell #175bc7, dunkel #7db1fa): 5,44 : 1 hell und 7,86 : 1 dunkel,
   Fläche und Rand bleiben unangetastet. */
/* Der eingesetzte Balken ist KEINE Zierde, sondern der Ersatz für das
   „✓ ", das mit den Zeichen weggefallen ist (WebKit-Abnahme 24.08.2026,
   Befund B1). Vorher trug „Aufräumen andersherum" seinen Zustand als
   Haken IM Text; seit die Symbole Grafiken sind, geht das nicht mehr,
   und übrig blieb allein die Farbe.

   Gemessen war das zu wenig: In Graustufen unterscheiden sich EIN und
   AUS nur um Fläche 1,15 : 1, Rand 1,53 : 1 und Textfarbe 1,02 : 1 —
   WCAG 1.4.11 verlangt 3 : 1 für eine Zustandsanzeige, und 1.4.1
   verlangt, dass Farbe nicht das einzige Mittel ist. Wer nur Helligkeit
   sieht, sah nichts.

   Ein Balken statt eines Hakens, aus drei Gründen: Er ist eine FORM
   (vorhanden gegen nicht vorhanden, also auch ohne Farbe lesbar), er
   verschiebt NICHTS (inset box-shadow liegt außerhalb des Layouts —
   ein Häkchen in der Ecke stieße bei den kleinen Knöpfen in den Text,
   die stehen auf `white-space: nowrap`), und er trägt currentColor,
   also dieselbe Farbe wie Text und Symbol: --pp-accent-2 auf
   --pp-accent-tint sind 5,44 : 1 (hell) und 7,86 : 1 (dunkel), beide
   klar über den geforderten 3 : 1.

   Er gilt für JEDEN gedrückten Knopf, nicht nur für den Umschalter:
   Die drei Werkzeuge hatten dieselbe Lücke schon vorher (nur fiel sie
   nicht auf, weil sie nie einen Haken hatten). */
.sb-b.on {
  background: var(--pp-accent-tint); border-color: var(--pp-accent-line);
  color: var(--pp-accent-2); box-shadow: inset 3px 0 0 currentColor;
}
/* Das Symbolfeld hat eine feste BOX, keine Schriftgröße mehr (24.08.2026).
   Solange die Symbole Zeichen waren, steuerte `font-size` ihre Größe —
   auf ein <svg> wirkt das nicht, und ohne feste Maße wären die
   Bandzeilen ungleich hoch. `flex: none`, damit ein langer Text den
   Kasten nicht zusammendrückt. */
.sb-b .sb-ico { display: inline-flex; align-items: center; justify-content: center; flex: none; }
.sb-b.gross .sb-ico { width: 19px; height: 19px; }
.sb-b.klein .sb-ico { width: 14px; height: 14px; }
/* Die gezeichneten Symbole (SB_SYM in js/sandbox.js): currentColor erbt
   Ruhe-, Hover- und Aktivfarbe des Knopfes — deshalb steht hier keine
   Farbe. Sie füllen ihre Box; das 24er-Raster der viewBox rechnet der
   Browser darauf um. Das frühere `vertical-align: -2px` ist entfallen:
   Es war der Notnagel, mit dem die zwei damals schon gezeichneten SVGs
   auf die Grundlinie der ZEICHEN daneben gezwungen wurden — Zeichen
   stehen jetzt keine mehr da, und die Box zentriert von allein. */
.sb-tico { display: block; width: 100%; height: 100%; }
/* Der Aufklapper hängt NICHT im wischenden Band (26.09.2026 — vorher nur
   auf dem Telefon, siehe die 860-px-Regel unten): Ein Kasten mit
   `overflow-x: auto` schneidet auch senkrecht ab, ein Menü nach unten
   wäre weg. Ohne `position: relative` am Wrap ist der nächste
   positionierte Vorfahr die Kopfzeile, und die scrollt nicht; das Menü
   steht dann unter dem Band, linksbündig zum Kopf (left statt right — am
   rechten Rand des Kopfes stünde es sonst neben dem Knopf im Leeren). */
.sb-kopf { position: relative; }
.sb-add-wrap { position: static; display: inline-flex; }
.sb-add-menu {
  position: absolute; top: 100%; left: 8px; z-index: 60;
  display: flex; flex-direction: column; min-width: 170px;
  border: 1px solid var(--pp-line); border-radius: 10px;
  background: var(--pp-surface); box-shadow: 0 10px 28px rgba(0, 0, 0, .18);
  padding: 6px; gap: 2px;
}
.sb-add-menu button { border: 0; background: none; text-align: left; }
/* DAS war „lässt sich nicht ein- und ausblenden" (Auftrag E, 11.08.2026).
   Das JavaScript setzte hidden völlig richtig — nur nützt das nichts:
   [hidden] { display: none } ist eine Regel des BROWSERS, und jede
   Klassenregel mit display aus einem Autoren-Stylesheet schlägt sie. Das
   Menü trug also das Attribut und stand trotzdem da.

   Zwei Lehren, die über diese Zeile hinausgehen:
   - Wer ein Element per hidden umschaltet UND ihm per Klasse ein display
     gibt, braucht IMMER die Gegenregel. Deshalb steht sie unten auch für
     .sbd-gruppe, die denselben Fehler hatte.
   - jsdom rechnet kein Layout: Der bestehende Test sah hidden === true und
     war zufrieden, während im Browser nichts verschwand. Ein Test, der
     „das Attribut steht" prüft, prüft nicht „man sieht es nicht mehr". */
.sb-add-menu[hidden] { display: none; }

/* ---------------- Reiterband der Kopfzeile (W-C/W4, 22.08.2026) ----------------
   Fünf Reiter (Start · Einfügen · Anordnen · Ansicht · KI), je ein Panel.
   EIGENE Klassen statt rb-tabs aus lab.css: Das Labor-Stylesheet gilt nur
   im Labor. Seit W5 (23.08.2026) steht die Reiterzeile in einer EIGENEN
   Zeile über dem Band statt rechts neben dem Flächennamen — erst damit
   passen die sichtbaren Gruppennamen des Entwurfs darunter. */
/* Die Reiterregeln haengen an `.sb-tabs .sb-tab`, nicht am blanken
   `.sb-tab`: Der Name war seit dem Stresstest (Z24, 12.08.2026) schon
   an die Wahrheitstabelle der Tabellenkarte vergeben (`<table
   class="sb-tab">`, weiter unten). Gleiche Spezifitaet, spaetere Regel
   gewinnt — im echten WebKit gemessen trug die TABELLE danach
   `cursor: pointer`, `padding: 6px 12px`, einen 2-px-Unterstrich,
   6 px Eckenradius und `--pp-text-2` statt der Textfarbe, und bei
   Hover haette sie sich accent-blau eingefaerbt; die Reiterknoepfe
   bekamen umgekehrt die 12,5 px der Tabelle statt ihrer 13 px.
   Umbenennen waere Markup-Arbeit in js/sandbox.js — die Eingrenzung
   loest es an EINER Stelle und laesst beide Namen stehen. */
.sb-tabs {
  display: flex; gap: 14px; align-items: flex-end;
  padding: 4px 4px 0; border-bottom: 1px solid var(--pp-line);
}
.sb-tabs .sb-tab {
  /* 13 px bleiben (nicht die 12 des Labors): Die Sandbox sitzt im Portal,
     nicht in einem Overlay — und verify-sandbox-tabname.js hält den Wert
     als Anker dagegen, dass die Tabellenregel ihn wieder überschreibt. */
  font: inherit; font-size: var(--pp-fs-klein); font-weight: 500; line-height: var(--pp-lh-knapp);
  padding: 5px 2px 6px; cursor: pointer;
  border: 0; border-bottom: 2px solid transparent; background: none;
  color: var(--pp-text-2); border-radius: 0; margin-bottom: -1px;
}
.sb-tabs .sb-tab:hover { color: var(--pp-text); background: none; }
.sb-tabs .sb-tab.on { color: var(--pp-accent-2); border-bottom-color: var(--pp-accent); font-weight: 600; }
/* Die Panels wechseln über hidden — .sb-tools bringt ein eigenes display
   mit, also braucht es die Gegenregel (die dokumentierte Falle von
   .sb-add-menu oben gilt hier wörtlich). */
.sb-kopf .sb-panel[hidden] { display: none; }
/* Gruppen im Band (W5): der Entwurf zeichnet benannte Gruppen (WERKZEUG,
   BEARBEITEN …) — Knopfreihe oben, Name darunter in kleiner grauer
   Sperrschrift, dazwischen ein senkrechter Trennstrich. Der Name steht im
   Markup gewöhnlich geschrieben und wird HIER großgestellt: So bleibt er
   für Vorleseprogramme und Tests lesbar. */
.sb-grp {
  display: flex; flex-direction: column; align-items: center;
  padding: 0 12px; border-right: 1px solid var(--pp-line); flex: 0 0 auto;
  /* max-width ist kein Beiwerk: `flex: 0 0 auto` misst die Gruppe an
     ihrem Inhalt, und `.sb-items` misst sich an max-content — bei 390 px
     war die Gruppe KARTEN 610 px breit und schob die GANZE SEITE 234 px in
     den waagerechten Bildlauf (WebKit gemessen, 23.08.2026; auf „Start"
     113 px). Die Knöpfe brechen längst um, es fehlte nur die Erlaubnis,
     schmaler zu werden als der eigene Inhalt. Dieselbe Klasse Fehler wie
     beim Menüband des Labors — dort schnitt es ab, hier schob es. */
  /* Seit das Band selbst wischt (26.09.2026) bleibt die Gruppe so breit
     wie ihr Inhalt: Der Bildlauf liegt in der Leiste, die Seite schiebt
     er nicht mehr — und eine gedeckelte Gruppe würde gestaucht (die
     Telefonregel unten sagt dasselbe seit dem 24.08.2026). */
  max-width: none;
}
.sb-grp:last-child { border-right: none; }
/* Die Reihe der Gruppe: links die großen Einträge nebeneinander
   (.sb-gr), rechts daneben die kleinen untereinander (.sb-kl) — die
   Gestalt der Entwurfsvorlage, seit dem 24.08.2026 auch im Markup
   (sbGrp in js/sandbox.js).

   Bis dahin lagen große und kleine Einträge in EINER Reihe. Das war
   nicht nur eine andere Optik, sondern zu breit: Der Reiter „Einfügen"
   brauchte gemessen 1373 px und „Start" 1254 px, während bei 1440 px
   Fenster und ausgeklappter Seitenleiste 1176 px zur Verfügung stehen —
   das Band brach also im Regelfall in zwei Zeilen um. Mit der Spalte
   sind es 1055 bzw. 906 px.

   Bewusst KEIN `flex-flow: column wrap` mit fester Höhe für die Spalte:
   Solche Container rechnen in WebKit ihre Breite nicht zuverlässig aus
   den umbrochenen Spalten. Wo mehr als KLEIN_PRO_SPALTE Einträge
   anfallen, schneidet sbGrp im Markup eine zweite .sb-kl.

   `align-items: flex-start`, nicht `center` (WebKit-Abnahme 24.08.2026):
   Zentriert man die Reihe gegen die Spalte, sitzt der große Eintrag einer
   Gruppe mit drei kleinen Nachbarn tiefer als der große Eintrag der
   Gruppe daneben — gemessen 15 px zwischen „Text" (Gruppe KARTE) und
   „Auswahl" (Gruppe WERKZEUG). Oben liegen die großen Symbole ALLER
   Gruppen auf einer Linie, und genau das lässt ein Menüband wie ein
   Menüband aussehen (nachgemessen: alle bei 142 px). */
/* `min-height` ist das Zeilenraster von oben und damit der Grund, warum
   die Bandhöhe beim Reiterwechsel steht: Die Reihe jeder Gruppe ist
   mindestens so hoch wie eine volle Spalte kleiner Einträge, also genau
   so hoch, wie das Band in Start, Einfügen und Anordnen ohnehin ist.
   Ein Boden, kein Deckel — was höher werden WILL (ein dreizeiliger
   großer Knopf in einer anderen Sprache), wird höher; abgeschnitten
   werden kann hier nichts. */
.sb-items { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap;
  min-height: var(--sb-raster, 76px); }
.sb-gr { display: flex; align-items: flex-start; gap: 3px; flex-wrap: wrap; }
.sb-kl { display: flex; flex-direction: column; align-items: stretch; gap: var(--sb-lueft, 2px); }
/* Die eine Ausnahme davon: Eine Gruppe OHNE großen Eintrag (REIHENFOLGE
   und FLÄCHE im Reiter „Anordnen") hat gar keine Symbolreihe, an der sich
   etwas ausrichten müsste. Sie steht senkrecht mittig, statt oben zu
   kleben — bei EINEM kleinen Eintrag in einer 99 px hohen Zeile sähe
   „oben" nach einem Layoutfehler aus. */
/* `align-items: center` gehört zum Zeilenraster und ist keine Zutat: Die
   Mitte kam bisher daher, dass ZWEI `margin-top: auto` (hier und am
   Gruppennamen) sich den freien Platz zu gleichen Teilen teilten — bei
   einem kleinen Eintrag 26 px oben und 26 px unten. Seit .sb-items einen
   Boden hat, ist kein freier Platz mehr übrig, den sie teilen könnten,
   und ohne diese Zeile klebte der Eintrag oben: genau die Stellung, die
   der Absatz darüber als Layoutfehler beschreibt.

   `center` ist dabei nicht ungefähr dasselbe, sondern zeichengleich —
   die halbe Restlücke im 76-px-Raster ist dieselbe Zahl wie die halbe
   Restlücke in der Gruppe. In WebKit nachgemessen (Reiter „Anordnen",
   Gruppe FLÄCHE): vorher wie nachher 26 px unter der Gruppenoberkante.
   `margin-top: auto` bleibt trotzdem stehen — es greift weiter, wo eine
   Gruppe in einer umbrochenen Zeile höher gestreckt wird als ihr Raster. */
.sb-grp.nur-klein .sb-items { margin-top: auto; align-items: center; }
/* Der Gruppenname steht auf der GRUNDLINIE DER ZEILE, nicht dicht unter
   seinen eigenen Knöpfen (WebKit-Abnahme 23.08.2026). `.sb-tools` streckt
   alle Gruppen einer Zeile auf dieselbe Höhe (align-items: stretch), der
   Inhalt der Spalte blieb aber oben — im Reiter „Anordnen" saß der Name
   von AUSWAHL (ein großer Knopf) 43 px tiefer als die Namen von
   REIHENFOLGE und FLÄCHE daneben, und der senkrechte Trennstrich lief
   dieselben 43 px unter der Beschriftung ins Leere. Bei 1100 px waren es
   im Reiter „Einfügen" 19 px. `margin-top: auto` schiebt den Namen ans
   untere Ende seiner Spalte: Alle Namen einer Zeile stehen damit auf
   derselben Höhe, und der Trennstrich endet mit ihnen.

   --pp-text-2 statt --pp-text-3 aus demselben Anlass: 10,5 px in
   Sperrschrift lagen bei 3,04 : 1 (hell) bzw. 3,99 : 1 (dunkel) unter den
   4,5 : 1 von WCAG AA. Der Gruppenname ist die Beschriftung, an der man
   ein Werkzeug SUCHT — er darf nicht der blasseste Text der Seite sein. */
/* Schrift und Sperrung nach der Entwurfsvorlage (24.08.2026): IBM Plex
   Mono, gesperrt, großgestellt — das Band bekommt damit die Anmutung
   einer Werkbank-Beschriftung statt einer zweiten Textzeile. Die Vorlage
   nennt 9 px; hier stehen 10, weil css/lab.css für den Zwilling im Labor
   ausdrücklich festhält, dass unter 10,5 px eine Beschriftung „im
   Vorbeigehen" nicht mehr gelesen wird — und Sperrsatz in Versalien
   frisst zusätzlich Lesbarkeit. Kontrast rettet Größe nicht. */
.sb-gname {
  font-family: var(--pp-mono, ui-monospace, monospace);
  font-size: var(--pp-fs-mini); font-weight: 500;
  color: var(--pp-text-2); letter-spacing: 1.2px;
  /* Die letzten zwei Angaben der nächsten Zeile bleiben WÖRTLICH so
     stehen (Reihenfolge und Wert): verify-sandbox-band-spezifitaet.js
     baut seine Gegenprobe aus genau dieser Zeichenfolge — und ein
     Kommentar, der sie zitiert, hätte sie ihm weggenommen (die Regex
     ist faul und träfe das Zitat zuerst; genau so passiert). */
  text-transform: uppercase; margin-top: auto; padding-top: 3px;
  white-space: nowrap;
}

/* ---------------- Statusleiste (W-B, EN19, 22.08.2026) ----------------
   Ganz unten, über die volle Breite: Werkzeug · Karten · Verbindungen ·
   Auswahl · Raster · Zoom. Reine Anzeige aus vorhandenem Zustand
   (js/sandbox.js, statusNeu) — Zwilling der .lab-statusbar, bis auf das
   Feld „Verbindungen", das es im Labor nicht gibt. Mono wie dort; KEIN
   min-height in Pixeln (Lehre vom 17.08.2026 — das Minimum gewinnt).

   --pp-text-2, und im Labor genauso (WebKit-Abnahme 23.08.2026): Beide
   Leisten standen mit --pp-text-3 bei 3,04 : 1 (hell) und 3,99 : 1
   (dunkel) — 11 px Mono, also klar unter WCAG AA. Die Zahlen dort sind
   die EINZIGE Auskunft über Kartenzahl, Auswahl, Raster und Zoom; sie
   sind keine Zierde. Nachgezogen wurde die Zwillingsregel in css/lab.css
   im selben Zug — eine der beiden Leisten heller zu machen als die andere
   hieße, den Zwilling aufzugeben. Die Trennpunkte bleiben auf
   --pp-line-2: Sie sind pure Dekoration (die Felder stehen ohnehin
   getrennt) und dürfen nicht so laut werden wie die Zahlen. */
.sb-statusbar {
  flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
  padding: 4px 14px; border-top: 1px solid var(--pp-line);
  background: var(--pp-bg-2); color: var(--pp-text-2);
  font-family: var(--pp-mono); font-size: var(--pp-fs-mini); letter-spacing: .2px;
  white-space: nowrap; overflow: hidden;
}
.sb-statusbar .sbs-sep { color: var(--pp-line-2); }
/* Das aktive Werkzeug hebt sich weiterhin um EINE Stufe ab — als die
   Leiste auf --pp-text-2 stieg, wäre es sonst farbgleich mit dem Rest
   geworden und die Rangfolge (was gilt gerade? / was steht da sonst?)
   hinge allein am Fettdruck. */
.sb-statusbar [data-sbs="werkzeug"] { color: var(--pp-text); font-weight: 600; }

/* ---------------- Fläche ---------------- */
/* `touch-action: none` (17.09.2026): Ohne die Zeile nimmt der Browser
   jeden Wisch für sich — er scrollt die Seite, zoomt sie bei zwei Fingern
   und meldet PrepPs Geste als abgebrochen (pointercancel). Karten
   verschieben, verbinden, einrahmen, schieben und die Zange wären auf dem
   Telefon damit nicht bedienbar, sondern zufällig. */
.sb-canvas {
  position: relative; flex: 1; min-height: 420px;
  height: calc(100vh - 190px);
  border: 1px solid var(--pp-line); border-radius: 12px;
  background: var(--pp-bg-2); overflow: hidden;
  touch-action: none;
  /* Punktraster wie ein Arbeitsblatt — rein dekorativ. */
  background-image: radial-gradient(var(--pp-line) 1px, transparent 1px);
  background-size: 24px 24px;
}
/* … UND DIE AUSNAHMEN, die dazugehören. `touch-action` vererbt sich zwar
   nicht, wirkt aber über die Vorfahren: Ein Finger auf einem Bildlaufkasten
   IN einer Karte schlüge ohne diese Regel gegen das `none` der Fläche, und
   der Kasten ließe sich nicht mehr rollen. Aufgezählt ist, was in einer
   Karte wirklich rollt oder gezogen wird — die Formularkästen, der
   Quelltext einer Textkarte, die Geo-Listen, das Mini-Labor im iframe, die
   Regler und Felder (dieselbe Menge, die sbIstBedien in js/sandbox.js vom
   Flächenzug abzweigt) sowie das Rechtsklickmenü, das ebenfalls in der
   Fläche hängt. Wer einen weiteren Bildlaufkasten in eine Karte baut,
   trägt ihn hier ein — sonst ist er auf dem Telefon tot. */
.sb-canvas input, .sb-canvas textarea, .sb-canvas select,
.sb-canvas [contenteditable], .sb-canvas iframe, .sb-canvas [data-sb-bedien],
.sb-canvas .sb-tabwrap, .sb-canvas .sb-form, .sb-canvas .sb-rasterwrap,
.sb-canvas .sb-wahl-liste, .sb-canvas .sb-geo-def,
.sb-canvas .sb-live, .sb-canvas .sb-ctx {
  touch-action: auto;
}
/* Die freie Fläche ist zum Schieben da (Auftrag C, 12.08.2026) — der
   Zeiger sagt es, bevor man es probiert. Während des Zugs gilt die
   geschlossene Hand für ALLES darunter: Sonst wechselte der Cursor,
   sobald man beim Schieben über eine Karte fährt. */
/* Der Zeiger sagt, was der Linkszug taete. „grab" lag bis zum 13.08.2026
   pauschal auf der Flaeche — seit „Auswahl" einrahmt statt zu schieben, waere
   das eine Falschaussage; die offene Hand gibt es jetzt nur beim Werkzeug
   „Bewegen" und bei gehaltener Leertaste (Klasse „hand", gesetzt in
   zeigerNeu). */
.sb-canvas.hand { cursor: grab; }
.sb-canvas.schiebt, .sb-canvas.schiebt * { cursor: grabbing; }

/* ---------------- Die Ebenen der Fläche (Auftrag A, 12.08.2026) ----------------
   Unten die Kanten als SVG (#sb-svg), darüber die Karten als
   gewöhnliches HTML (#sb-welt), ganz oben das flüchtige Overlay der
   Gesten (#sb-ovl). Alle drei tragen dieselbe Transformation aus
   buehneTransform() — EINE Rechnung.

   Bis dahin lagen die Karten als foreignObject IM SVG; das ist der
   Fall, in dem WebKit HTML in einer eigenen Schicht malt — Formeln,
   iframes und Regler landeten am SVG-Ursprung, also in der Ecke oben
   links (docs/SANDBOX.md Abschnitt 2a).

   Kein will-change, keine 3D-Transformation: Ein eigener
   Kompositionslayer brächte Unschärfe und Tastaturprobleme zurück.
   transform-origin: 0 0 genügt.

   Die Regel gilt NUR den beiden Flächen-SVGs, nicht jedem SVG darin:
   Seit die Karten HTML sind, liegen Plot- und Schaltbild-SVGs ebenfalls
   unter .sb-canvas — ein Sammelselektor (.sb-canvas svg) würde sie
   absolut positionieren und aus ihrer Karte heben. */
#sb-svg, #sb-ovl {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; user-select: none;
  /* Im SVG hört nur der Kanten-Fühler zu (pointer-events: stroke weiter
     unten) — sonst läge eine unsichtbare Scheibe über der Fläche und
     schluckte den Rahmenzug; das Overlay hört nie zu, sonst schluckte
     es die eigene Geste. */
  pointer-events: none;
}
#sb-welt {
  position: absolute; inset: 0;
  transform-origin: 0 0;
  /* Die freie Fläche bleibt frei: nur die Karten und die
     Kantenbeschriftungen holen sich den Zeiger einzeln zurück. */
  pointer-events: none;
}
/* Die beiden Behälter der Kartenebene: Beschriftungen unter Karten.
   Sie liegen deckungsgleich über der Fläche, damit left/top ihrer Kinder
   dieselbe Weltecke meinen. */
#sb-kantenlabel, #sb-karten { position: absolute; inset: 0; }


/* ---------------- Karten ---------------- */
.sb-node {
  position: absolute; pointer-events: auto;
  box-sizing: border-box;
  background: var(--pp-surface);
  border: 1.5px solid var(--pp-line); border-radius: 10px;
}
.sb-node.sel { border-color: var(--pp-accent); border-width: 2px; }
.sb-griff {
  position: absolute; right: 0; bottom: 0; width: 14px; height: 14px;
  cursor: nwse-resize; border-bottom-right-radius: 8px;
}
.sb-node.sel .sb-griff { background: var(--pp-accent-tint); border: 1px solid var(--pp-accent-line); }
.sb-karte {
  width: 100%; height: 100%; box-sizing: border-box;
  padding: 10px 12px; overflow: hidden;
  color: var(--pp-text); font-size: var(--pp-fs-text); line-height: var(--pp-lh-text);
  cursor: grab;
}
/* display: flow-root ist die REPARATUR von A3 (03.09.2026, Jonas: „bei
   dem Textfeld ohne Rand erweitert sich die Box bei jeder Eingabe um
   eine Zeile"). .sb-md hat weder Rand noch Polster; die Margin ihres
   ersten Blockkindes (.ki-h1 8 px, .ki-liste 3 px — css/prepp.css)
   kollabierte deshalb nach AUSSEN und schob die ganze Box nach unten.
   In Bearbeitung trägt .sb-md zusätzlich min-height: 100% und wächst
   damit MIT der Karte — der Überhang blieb also bei jeder neuen Höhe
   genau so groß, und karteWachsen legte bei jedem Tastendruck eine
   Rasterzeile drauf. In WebKit gemessen (Karte 260x180, Text „# Titel …"):
   scrollHeight 205 gegen clientHeight 197, zehn Tastendrücke 180 -> 380 px;
   mit flow-root 180 -> 180. docs/abnahme/sandbox-tabellenformular/BERICHT.md.

   Ein eigener Block-Formatierungskontext fängt JEDES Blockkind ab, auch
   eine sechste Form, die mdBloecke später bekommt — die zwei Regeln
   darunter sind nur noch Kosmetik (dieselbe Konvention wie
   .ki-msg > .ki-h1:first-child in prepp.css) und kein Verlass. */
.sb-karte .sb-md { white-space: pre-wrap; word-break: break-word; display: flow-root; }
.sb-karte .sb-md > :first-child { margin-top: 0; }
.sb-karte .sb-md > :last-child { margin-bottom: 0; }

/* ---------------- Text ohne Kasten (Auftrag F, 12.08.2026) ----------------
   DIESELBE Karte wie „Text" — der ganze Unterschied steht hier: kein
   Rahmen, keine Fläche, kein Schatten. Die Fläche scheint durch, das ist
   der Sinn der Sache.

   Sichtbar wird der Kasten trotzdem, wenn man ihn braucht: beim
   Überfahren und bei Auswahl ein zurückhaltender gestrichelter Rand.
   Ohne das könnte man eine LEERE Notiz nicht mehr treffen — und eine
   Karte, die man nicht anfassen kann, lässt sich auch nicht löschen. */
/* „Text ohne Kasten" (Auftrag F): Der Rahmen wird im STYLESHEET
   weggenommen, nicht im Markup — der Knoten bleibt als Trefferfläche
   stehen (sonst gäbe es an einer leeren Notiz nichts zum Anfassen) und
   zeigt beim Überfahren und bei Auswahl eine gestrichelte Kante.
   Seit Auftrag A trägt den Rahmen der KNOTEN selbst statt eines
   <rect> daneben — deshalb greifen die drei Regeln hier direkt an
   .sb-node, nicht mehr an .sb-rahmen. */
.sb-node.sb-typ-note { background: none; border-color: transparent; }
.sb-node.sb-typ-note:hover {
  border-color: var(--pp-line-2); border-style: dashed; border-width: 1px;
}
.sb-node.sb-typ-note.sel {
  border-color: var(--pp-accent); border-style: dashed; border-width: 1.5px;
}
.sb-karte.sb-typ-note { padding: 4px 6px; }

/* ---------------- Form-Karte (W-C): Standardformen ohne Kasten ----------------
   Dieselben drei Regeln wie bei „Text ohne Kasten": kein Rahmen, beim
   Überfahren und bei Auswahl eine gestrichelte Kante als Trefferhilfe.
   Das SVG füllt die Karte und streckt sich mit dem Größen-Griff
   (preserveAspectRatio="none" steht im Markup). */
.sb-node.sb-typ-form { background: none; border-color: transparent; }
.sb-node.sb-typ-form:hover {
  border-color: var(--pp-line-2); border-style: dashed; border-width: 1px;
}
.sb-node.sb-typ-form.sel {
  border-color: var(--pp-accent); border-style: dashed; border-width: 1.5px;
}
.sb-karte.sb-typ-form { padding: 0; }
.sb-form-karte { width: 100%; height: 100%; }
.sb-form-karte svg { width: 100%; height: 100%; display: block; }

/* ---------------- Gruppe (N4, 13.10.2026) ----------------
   Ein Rahmen HINTER den Karten (zeichne() setzt ihn an den Anfang der
   Kartenebene). Der Knoten selbst nimmt KEINEN Zeiger — ein Zug in seinem
   Inneren ist ein Zug auf der freien Fläche (Auswahlrahmen) und trifft
   die Kanten darunter; gefasst wird er am Kopf, gezogen am Größengriff.
   Der Grund ist zart (8 %), damit Kanten und Karten lesbar bleiben;
   ohne Farbe trägt er die Linienfarbe des Portals (hell/dunkel). */
.sb-node.sb-typ-gruppe {
  pointer-events: none; background: none; border: 0; border-radius: 12px;
}
.sb-karte.sb-typ-gruppe { padding: 0; overflow: visible; cursor: default; }
.sb-gruppe {
  position: absolute; inset: 0; box-sizing: border-box; border-radius: 12px;
  border: 2px solid var(--sb-gruppe-farbe, var(--pp-line-2));
  background: var(--sb-gruppe-grund, color-mix(in srgb, var(--pp-text) 4%, transparent));
}
.sb-node.sb-typ-gruppe.sel .sb-gruppe { box-shadow: 0 0 0 2px var(--pp-accent); }
.sb-gruppe-kopf {
  pointer-events: auto; cursor: grab;
  position: absolute; left: 10px; top: -1px; transform: translateY(-100%);
  max-width: calc(100% - 20px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 2px 10px; border-radius: 8px 8px 0 0;
  border: 1.5px solid var(--sb-gruppe-farbe, var(--pp-line-2)); border-bottom: 0;
  background: var(--sb-gruppe-farbe, var(--pp-surface-2));
  color: var(--sb-gruppe-schrift, var(--pp-text));
  font-size: var(--pp-fs-klein); font-weight: 600; line-height: var(--pp-lh-knapp);
}
.sb-gruppe-leer { font-weight: 400; opacity: .85; font-style: italic; }
.sb-node.sb-typ-gruppe .sb-griff { pointer-events: auto; }
.sbp-gruppe { font-weight: 600; font-size: 1.05em; border-bottom: 1px solid currentColor; padding-bottom: 2px; }

/* ---------------- Formelkarte (M5/EN18, 22.08.2026) ----------------
   Ein VERWEIS auf eine Formel des Formelraums, kein zweiter Formelraum.
   Die Sorte trägt derselbe 3-px-Farbstrich wie die Formelzeile im
   Material (css/material.css .mt-strich): blau „auf dem Blatt", rot „in
   den Kopf" — eine Farbsprache, an zwei Orten dieselbe. Der Wortlaut
   steht als title an der Karte, nie nur die Farbe (M4, Barrierefreiheit).

   Die Formel darf waagerecht scrollen (overflow-x), statt über die Karte
   hinauszulaufen: Eine lange Formel ist der Normalfall, und .sb-karte hat
   overflow:hidden — ohne min-width:0 schnitte sie stumm ab (dieselbe
   Kette wie .mt-blind-aufgedeckt .mt-tex in M3). */
.sb-formel {
  display: flex; flex-direction: column; gap: 4px;
  width: 100%; height: 100%; min-width: 0; box-sizing: border-box;
  border-left: 3px solid var(--pp-line-2); padding-left: 9px;
}
.sb-formel.blatt { border-left-color: var(--pp-accent); }
.sb-formel.kopf { border-left-color: var(--pp-err); }
.sb-formel.leer, .sb-formel.fehlt { justify-content: center; border-left-style: dashed; }
.sb-formel-kopf {
  font: 500 10.5px/1.2 var(--pp-mono, monospace); letter-spacing: .06em;
  text-transform: uppercase; color: var(--pp-text-3);
}
.sb-formel-tex { min-width: 0; overflow-x: auto; overflow-y: hidden; }
.sb-formel-zeichen, .sb-formel-notiz { font-size: var(--pp-fs-klein); color: var(--pp-text-2); min-width: 0; }
.sb-formel-notiz { font-style: italic; }
.sb-formel-fund { font-size: var(--pp-fs-mini); color: var(--pp-text-3); margin-top: auto; }

/* Das Auswahlfeld des Formulars (Formfeld-Art „wahl"): dieselbe Optik wie
   die Textfelder daneben — sonst sähe eine Gruppe aus zwei Bauteilen aus. */
.sb-form select {
  width: 100%; box-sizing: border-box;
  padding: 6px 8px; border: 1px solid var(--pp-line-feld); border-radius: 6px;
  background: var(--pp-bg); color: var(--pp-text); font: inherit; font-size: var(--pp-fs-klein);
}

/* ---------------- Flüchtige Flächenmeldung (Z1/Z2) ----------------
   Unten mittig auf der Fläche, verschwindet von selbst (sbMeldung).
   --pp-Tokens, damit sie in hell und dunkel lesbar ist. */
.sb-meldung {
  position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
  max-width: min(680px, 92%); z-index: 70;
  background: var(--pp-surface); color: var(--pp-text);
  border: 1px solid var(--pp-line); border-radius: 10px;
  padding: 8px 14px; font-size: var(--pp-fs-klein); line-height: var(--pp-lh-text);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .18);
}

/* Obsidian-Canvas (Paket R1): „In Obsidian geändert — neu laden?" bzw. die
   Hinweise eines Ladens. Oben statt unten — unten steht die flüchtige
   .sb-meldung, und beide können zugleich da sein. Bleibt, bis man klickt. */
.sb-cv-hinweis {
  position: absolute; left: 50%; top: 12px; transform: translateX(-50%);
  max-width: min(680px, calc(100% - 32px)); z-index: 70;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  background: var(--pp-surface); color: var(--pp-text);
  border: 1px solid var(--pp-warn, var(--pp-line)); border-radius: 10px;
  padding: 8px 12px; font-size: var(--pp-fs-klein); line-height: var(--pp-lh-text);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .18);
}
.sb-cv-hinweis ul { margin: 0; padding-left: 20px; flex-basis: 100%; }
.sb-cv-hinweis .pp-btn { padding: 3px 10px; }

.sb-leer { color: var(--pp-text-2); font-style: italic; }
.sb-tot { color: var(--pp-text-2); }
.sb-karte textarea {
  width: 100%; height: 100%; box-sizing: border-box; resize: none;
  font: inherit; color: inherit; background: var(--pp-bg);
  border: 1px solid var(--pp-accent-line); border-radius: 6px; padding: 6px;
}
/* ---------------- Bedienbox (Auftrag B, 12.08.2026) ----------------
   Die Box IST die Karte: Sie füllt sie aus und wächst NIE darüber
   hinaus — zu langer Inhalt bekommt eine Bildlaufleiste, keine neue
   Größe. Damit springt die Karte beim Öffnen und Schließen nicht. */
.sb-box {
  display: flex; flex-direction: column; gap: 4px;
  width: 100%; height: 100%; box-sizing: border-box; min-height: 0;
}
.sb-box > textarea { flex: 1 1 auto; min-height: 0; height: auto; overflow: auto; }
.sb-box > input, .sb-box > .sb-fehler { flex: none; }
.sb-box > .sb-html-name { margin-bottom: 0; }

/* ---------------- Live-Vorschau der Textkarte (Auftrag B, 11.08.2026) ----------------
   Der Bearbeitungszustand darf NICHTS verschieben: Der Text steht danach
   an derselben Stelle und in derselben Größe wie die Anzeige. Deshalb
   keine eigene Schrift, kein Kasten um das Feld, nur ein zurückhaltender
   Rahmen an der Karte — sichtbar bleibt der Zustand über den Cursor. */
.sb-live {
  outline: none; min-height: 100%;
  border-radius: 6px;
  box-shadow: 0 0 0 1px var(--pp-accent-line) inset;
  padding: 2px 4px; margin: -2px -4px;
  box-sizing: border-box;
  cursor: text;
}
/* Eine leere Zeile bleibt anklickbar — ohne Mindesthöhe fiele sie auf
   null zusammen und wäre mit der Maus nicht mehr zu treffen. */
.sb-zeile { min-height: 1.5em; }
/* Die aktive Zeile ist Quelltext — OHNE eigene Optik (24.08.2026,
   Jonas: „Das die Leiste blau markiert wird und der Text anders wird
   … kann entfernt werden"): kein Farbbalken, kein Schriftwechsel mehr,
   der Text bleibt beim Umschalten an Ort und Stelle. Nur pre-wrap ist
   FUNKTION, keine Optik: Ein Block-Mathe-Stück trägt seine Umbrüche im
   Innern (Plan 3 B4), und pre-wrap zeigt sie, statt sie zu einem
   Leerzeichen zu verschlucken. */
.sb-zeile.roh { white-space: pre-wrap; }
/* Warnbox-Zeile „> [!warn] …" (Plan 3 B4, für die Poster-Vorlagen):
   ein Kasten an Ort und Stelle — das Muster der A3-Übersichten. Das ⚠
   steht im Inhalt, nicht hier (Druck und Vorleseprogramm). */
.sb-warnbox {
  border: 1px solid var(--pp-warn); border-left-width: 4px;
  background: var(--pp-warn-tint);
  border-radius: 6px; padding: 3px 8px; margin: 2px 0;
  white-space: normal;
}
/* Der ziehbare Anfangspunkt des Richtungsfelds (Plan 3 B3). */
.sb-plot-bild [data-anf] { cursor: move; }

/* Die Widget-Bibliothek in der leeren html-Karte (Plan 3 C5). */
.sb-widget-liste { display: flex; flex-direction: column; gap: 6px; padding: 4px 0; }
.sb-widget-wahl {
  display: flex; flex-direction: column; gap: 1px; align-items: flex-start;
  text-align: left; padding: 6px 10px; cursor: pointer;
  background: var(--pp-bg); color: var(--pp-text);
  border: 1px solid var(--pp-line); border-radius: 8px; font: inherit; font-size: var(--pp-fs-klein);
}
.sb-widget-wahl:hover { border-color: var(--pp-accent-line); background: var(--pp-accent-tint); }
.sb-widget-wahl span { font-size: var(--pp-fs-mini); color: var(--pp-text-2); }
/* Gesetzte Fragmente nehmen keine Eingaben an (sonst liefe der Cursor in
   ein KaTeX-Fragment); der Klick daneben trifft trotzdem die Zeile. */
.sb-gesetzt { display: block; }
.sb-live .ki-liste { margin: 0; padding-left: 1.2em; }

/* ---------------- Dieselbe Vorschau als freistehendes Feld (23.08.2026) ----------------
   PreppSandbox.liveFeld baut sie in einen fremden Knoten (heute: das
   Notizfenster in js/app.js). Auf der Karte trägt .sb-live absichtlich
   KEINEN Kasten — dort ist die Karte der Kasten. Ein freistehendes Feld
   braucht einen: Ohne Rahmen sähe man nicht, wo man hineinschreiben kann.
   Deshalb hier eine eigene Klasse statt einer Änderung an .sb-live —
   sonst bekäme die Karte den Kasten mit, und der Bearbeitungszustand
   verschöbe den Text (die Regel, aus der .sb-live entstanden ist). */
.sb-live.sb-feld {
  min-height: 4.5em; max-height: 40vh; overflow-y: auto;
  padding: 7px 9px; margin: 0;
  border: 1px solid var(--pp-line-2); box-shadow: none;
  background: var(--pp-bg); color: var(--pp-text);
  white-space: pre-wrap; word-break: break-word;
  font-size: var(--pp-fs-text); line-height: var(--pp-lh-text);
}
.sb-live.sb-feld:focus-within { border-color: var(--pp-accent-line); }
.sb-live.sb-feld.dunkel { background: var(--pp-surface); }
/* Der Platzhalter liegt als Attribut am Knoten, nicht als Text im Feld:
   Im Feld wäre er nach dem ersten Tippen Teil der Notiz. Er kommt weg,
   sobald ein Zeichen dasteht (js/sandbox.js, phPflegen) — nicht schon
   beim Fokus, sonst stünde ein leeres Feld ohne jede Auskunft da. */
.sb-live.sb-feld[data-leer]::before {
  content: attr(data-ph); color: var(--pp-text-3);
  pointer-events: none; display: block; margin-bottom: -1.5em;
}

/* ---------------- Das Textfenster der Textkarte (24.08.2026) ----------------
   Der Doppelklick auf eine text-/note-Karte öffnet dieses Fenster am
   rechten Rand der Fläche (docs/SANDBOX-TEXTKARTE-2026-08-24.md); die
   Karte darunter bleibt die gesetzte Anzeige und rendert bei jedem
   Tastendruck neu — SIE ist die Live-Vorschau.

   Die Größe ist Jonas' Vorgabe: „immer genauso groß … wie die Box der
   Notiz im Training, wenn man sie nach links oder rechts anheftet,
   vergrößert" — also 380 px breit (der tutorKlemmW-Standard der
   Trainingsfenster in js/app.js) und die volle Höhe der Fläche, so wie
   das angedockte Fenster seine Spalte füllt. Zu langer Quelltext
   scrollt im Feld (Bildlauf statt Wachsen, dieselbe Regel wie die
   Bedienboxen); die Schrift bleibt die der Karte — kein Schriftwechsel
   beim Bearbeiten, das war Teil des Befunds.

   z-index ÜBER Karten und Gesten-Overlay, aber UNTER dem
   Rechtsklickmenü (.sb-ctx, 60) und der Flächenmeldung (.sb-meldung,
   70): Ein Menü oder eine Meldung, die unter dem Fenster verschwände,
   wäre für den Nutzer einfach weg. */
/* Die Breite steht als Custom Property, damit ein Kartentyp sie über
   `breite` weiten kann (A4) — width-Inline schlüge die 85-%-Klemme,
   die das Fenster auf schmalen Flächen davon abhält, die ganze Fläche
   zu belegen (Muster --ein-task-w). */
.sb-editfenster {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: min(var(--sb-sf-w, 380px), 85%);
  display: flex; flex-direction: column; min-height: 0;
  background: var(--pp-surface); border-left: 1px solid var(--pp-line);
  box-shadow: -10px 0 26px rgba(0, 0, 0, .12);
  z-index: 59;
}
/* Das Fenster nimmt beim Öffnen selbst den Fokus, damit Escape sofort
   wirkt (C31, tabindex -1). Einen Rahmen bekommt es dafür nicht: Es ist
   kein Bedienelement, und über die Tastatur ist es gar nicht
   erreichbar — der Ring stünde also nur da, wenn ihn niemand gesucht
   hat. Die FELDER darin behalten ihren (sie sind tabbar). */
.sb-editfenster:focus { outline: none; }
.sb-editfenster-kopf {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border-bottom: 1px solid var(--pp-line);
}
.sb-editfenster-titel {
  font: 600 10.5px/1.2 var(--pp-mono, monospace); letter-spacing: .08em;
  text-transform: uppercase; color: var(--pp-text-2); margin-right: auto;
}
.sb-editfenster-x {
  border: none; background: none; cursor: pointer;
  color: var(--pp-text-2); font-size: var(--pp-fs-text); line-height: var(--pp-lh-eng);
  padding: 3px 7px; border-radius: 6px;
}
.sb-editfenster-x:hover { color: var(--pp-text); background: var(--pp-bg-2); }
/* (.sb-editfeld — das Quelltextfeld der Textkarte im Fenster — ist am
   27.09.2026 entfallen: Text und Notiz schreiben seitdem IM Block,
   .sb-blockfeld unten.) */
/* Die Heft-Karte: Verweis plus Knopf (der Doppelklick fehlt dem Finger). */
.sb-heft-fuss { margin-top: 8px; }
.sb-md.sb-bind-heft { cursor: pointer; }
.sb-editfenster-fuss {
  padding: 6px 12px; border-top: 1px solid var(--pp-line);
  font-size: var(--pp-fs-mini); color: var(--pp-text-3);
}
/* Die Karte, deren Text gerade im Fenster steht: ein dünner
   Akzentrahmen sagt, WOHIN getippt wird — mehr Marke braucht es nicht,
   die Karte selbst bleibt die gewöhnliche Anzeige. */
/* Die Karte, die gerade im Seitenfenster bearbeitet wird (A4): derselbe
   duenne Akzentrahmen wie an der Textkarte, nur am KNOTEN — er traegt
   den Rahmen seit Auftrag A. Mehr Marke braucht es nicht, die Karte
   bleibt die gewoehnliche Anzeige. */
.sb-node.sb-in-arbeit { border-color: var(--pp-accent-line); }
.sb-md.sb-in-arbeit {
  box-shadow: 0 0 0 1px var(--pp-accent-line) inset;
  border-radius: 6px; min-height: 100%; box-sizing: border-box;
}

/* ---------------- Schreiben IM Block (27.09.2026) ----------------
   Jonas: „wirklich alles mit in dem Block schreiben". Die Text- und
   Notizkarte zeigt im Schreibzustand an Stelle ihrer gesetzten Anzeige
   ein <textarea> mit dem Quelltext (js/sandbox.js, textBlockStart) —
   in DERSELBEN Schrift, Zeilenhöhe und am selben Ort wie die Anzeige:
   Das Feld hat keinen Innenabstand und keinen Rahmen, es erbt Schrift
   und Farbe der Karte. So springt der Text beim Betreten nicht.

   Die Marke „hier wird geschrieben" ist ein OUTLINE statt des inneren
   Schattens der alten Marke: Ein Innenschatten läge direkt am ersten
   Buchstaben (das Feld hat kein Polster), ein Outline liegt im Polster
   der Karte und verschiebt nichts. Die Offsets passen in beide Polster
   (Karte 10/12 px, „ohne Kasten" 4/6 px).

   Die Kette der Höhen: .sb-md füllt die Karte (height 100 %, Flex-
   Spalte), der Wirt nimmt den Rest nach Kopf und Chips, das Feld füllt
   den Wirt. `min-height: 0` an jedem Glied, sonst gäbe der Wirt nie
   unter seine Inhaltshöhe nach und die Karte liefe unten über. Wird der
   Text länger als das Feld, wächst die KARTE (textBlockWachsen) — die
   Bildlaufleiste des Feldes ist nur das Netz für den Fall, dass die
   Gegenprobe das Wachsen zurücknimmt. */
.sb-karte .sb-md.sb-block-edit {
  display: flex; flex-direction: column;
  height: 100%; min-height: 0; box-sizing: border-box;
  white-space: normal;
  box-shadow: none; border-radius: 4px;
  outline: 1px solid var(--pp-accent-line); outline-offset: 3px;
  cursor: text;
}
.sb-block-wirt { flex: 1 1 auto; min-height: 0; display: flex; }
.sb-karte .sb-blockfeld {
  flex: 1 1 auto; width: 100%; min-height: 2.6em; box-sizing: border-box;
  margin: 0; padding: 0; border: 0; border-radius: 0; outline: none;
  resize: none; overflow-x: hidden; overflow-y: auto;
  background: transparent; color: inherit; caret-color: var(--pp-accent);
  font: inherit; line-height: inherit; letter-spacing: inherit;
  white-space: pre-wrap; word-break: break-word;
  cursor: text;
}
.sb-karte .sb-blockfeld::placeholder { color: var(--pp-text-2); font-style: italic; opacity: 1; }
/* Kopf und Chips eines Befunds bleiben in der Karte und geben nie nach —
   nachgeben darf nur das Feld (Fallstrick 41, die Frage „wer darf?"). */
.sb-block-edit > .sb-bind-kopf { flex: none; }
.sb-block-edit > .sb-bind-chips { flex: none; margin-top: 6px; padding: 8px 0 0; }

/* ---------------- Gekoppelte Karten (r2-09, 26.09.2026) ----------------
   docs/ARBEITSFENSTER-2026-09-26.md 2.3. Die Notiz-Karte und die
   Befund-Karten der Arbeitsflaeche tragen einen KOPF (was sie sind:
   Herkunft, Datum, Fehlerart — dieselben Woerter wie die Vorschau im
   Arbeitsfenster) und einen Akzentbalken links; sonst sind sie
   Textkarten. Die Ursache-Chips stehen im Textfenster unter dem Feld. */
.sb-md.sb-gekoppelt { border-left: 3px solid var(--pp-accent-line); padding-left: 8px; }
.sb-bind-kopf {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 6px; padding-bottom: 4px; border-bottom: 1px dashed var(--pp-line);
  font-size: var(--pp-fs-mini); line-height: var(--pp-lh-knapp); color: var(--pp-text-2);
}
.sb-bind-titel { font-weight: 700; letter-spacing: .6px; text-transform: uppercase; }
.sb-bind-wann { font-family: var(--pp-mono); }
.sb-bind-ursache { font-style: normal; color: var(--pp-accent-2); }
.sb-bind-ursache b { font-family: var(--pp-mono); margin-right: 3px; }
.sb-bind-chips {
  flex: none; padding: 8px 12px 10px; border-top: 1px solid var(--pp-line);
  display: flex; flex-direction: column; gap: 6px;
}
.sb-bind-frage { font-size: var(--pp-fs-mini); color: var(--pp-text-2); }
.sb-bind-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.sb-bind-chip {
  border: 1px solid var(--pp-line); background: var(--pp-surface); color: var(--pp-text-2);
  font: inherit; font-size: var(--pp-fs-mini); line-height: var(--pp-lh-eng);
  padding: 3px 8px; border-radius: 999px; cursor: pointer;
}
.sb-bind-chip b { font-family: var(--pp-mono); margin-right: 3px; }
.sb-bind-chip:hover { background: var(--pp-accent-tint); color: var(--pp-text); }
/* EIN als Form (Muster .sb-b.on). */
.sb-bind-chip.on { color: var(--pp-text); border-color: var(--pp-accent-line); box-shadow: inset 3px 0 0 var(--pp-accent); }
@media (hover: none) { .sb-bind-chip { min-height: 44px; } }
/* Die Kurswahl im Kopf (Plan 10 F) — hier, nicht an ihrer Grundregel:
   verify-sandbox-ansicht.js liest den Teil vor dem ersten @media. */
@media (hover: none) { .sb-kurs { min-height: 44px; } }

/* ---------------- Kanten ---------------- */
.sb-kante .fuehler { fill: none; stroke: transparent; stroke-width: 14; cursor: pointer; pointer-events: stroke; }
.sb-kante .strich { fill: none; stroke: var(--pp-text-2); stroke-width: 1.6; pointer-events: none; }
.sb-kante.sel .strich { stroke: var(--pp-accent); stroke-width: 2.4; }

/* ---------------- Kantenarten (Stresstest UML2, 12.08.2026) ----------------
   Die Markierungen liegen als <marker> in den defs von #sb-svg. Ein
   marker erbt die Farbe des referenzierenden Pfades NICHT (currentColor
   loest dort gegen den eigenen Kontext auf) — deshalb gibt es jede Form
   zweimal und die Farbe steht hier, einmal je Zustand.

   hohl heisst: gefuellt mit der FLAECHENfarbe, nicht mit „nichts". Ein
   ungefuelltes Vererbungsdreieck liesse die Kante durchscheinen, und
   genau daran unterscheidet man in UML ▷ von ▶. */
.sb-kante .strich { transition: none; }
.sb-kante.gestrichelt .strich { stroke-dasharray: 7 5; }
#sb-svg marker .mk { stroke: var(--pp-text-2); stroke-width: 1.6; stroke-linejoin: miter; }
#sb-svg marker .mk.offen { fill: none; }
#sb-svg marker .mk.hohl  { fill: var(--pp-bg); }
#sb-svg marker .mk.voll  { fill: var(--pp-text-2); }
#sb-svg marker .mk.sel        { stroke: var(--pp-accent); }
#sb-svg marker .mk.voll.sel   { fill: var(--pp-accent); }

/* ---------------- UML-Klassenkasten (Stresstest UML1, 12.08.2026) ----------------
   Drei Faecher uebereinander, getrennt durch eine Linie: Kopf, Attribute,
   Methoden. Der Kasten fuellt die Karte ganz aus — die Karte IST der
   Klassifizierer, sie bekommt deshalb kein zusaetzliches Innenpolster
   (.sb-typ-uml setzt das der .sb-karte zurueck).

   Mitglieder in --pp-mono: In UML ist die Signatur ein Bezeichner, kein
   Fliesstext, und untereinander stehende Sichtbarkeitszeichen (+ - #)
   liest man nur in einer Festbreitenschrift als Spalte. */
.sb-karte.sb-typ-uml { padding: 0; }
.sb-uml {
  width: 100%; height: 100%; box-sizing: border-box;
  display: flex; flex-direction: column; overflow: auto;
}
.sb-uml-kopf {
  padding: 8px 10px; text-align: center;
  border-bottom: 1px solid var(--pp-line-2);
}
.sb-uml-stereo {
  font-size: var(--pp-fs-mini); color: var(--pp-text-2); line-height: var(--pp-lh-knapp);
}
.sb-uml-name { font-weight: 600; font-size: var(--pp-fs-text); word-break: break-word; }
.sb-uml-name.abstrakt { font-style: italic; }
.sb-uml-fach {
  padding: 6px 10px; font-family: var(--pp-mono); font-size: var(--pp-fs-klein); line-height: var(--pp-lh-text);
  border-bottom: 1px solid var(--pp-line-2);
}
.sb-uml-fach:last-child { border-bottom: none; }
.sb-uml-zeile { white-space: pre-wrap; word-break: break-word; }
.sb-uml .sb-leer { display: block; padding: 10px 12px; }

/* ---------------- Tabelle (Stresstest Z24, 12.08.2026) ----------------
   Wahrheits- und Quine-McCluskey-Tabellen. Der Rahmen scrollt, statt die
   Karte zu sprengen — dieselbe Regel wie bei jeder anderen Karte: zu
   viel Inhalt bekommt eine Bildlaufleiste, keine neue Groesse. */
/* `table.sb-tab` statt `.sb-tab`: derselbe Klassenname traegt oben das
   Reiterband der Kopfzeile (W-C). Der Elementname macht die Regel
   spezifischer als das blanke `.sb-tab` und trifft trotzdem nie einen
   Knopf — Begruendung und Messwerte oben am Reiterband. */
.sb-tabwrap { width: 100%; height: 100%; overflow: auto; }
table.sb-tab { border-collapse: collapse; font-size: var(--pp-fs-klein); }
table.sb-tab th, table.sb-tab td {
  border: 1px solid var(--pp-line-2); padding: 3px 8px;
  text-align: center; white-space: nowrap;
}
table.sb-tab th { background: var(--pp-surface-2, var(--pp-surface)); font-weight: 600; }
table.sb-tab td { font-family: var(--pp-mono); }
/* Die Beschriftung einer Verbindung (Auftrag G) — HTML in der
   Kartenebene, mit dem Flächenhintergrund hinterlegt, damit sie über der
   Linie lesbar bleibt. Leer heißt unsichtbar: Ohne Text gibt es den
   Knoten gar nicht (js/sandbox.js, kantenLabelHtml). */
.sb-kante-label {
  position: absolute; transform: translate(-50%, -50%);
  pointer-events: auto; cursor: text;
  max-width: 240px; box-sizing: border-box;
  padding: 2px 6px; border-radius: 6px;
  border: 1px solid transparent;
  background: var(--pp-bg-2); color: var(--pp-text-2);
  font-size: var(--pp-fs-klein); line-height: var(--pp-lh-knapp);
}
.sb-kante-label:hover { border-color: var(--pp-line-2); color: var(--pp-text); }
.sb-kante-label.bearbeitet {
  min-width: 140px; background: var(--pp-surface); color: var(--pp-text);
  border-color: var(--pp-accent-line);
}
/* Im Druck-SVG (Anordnung) steht sie wieder als schlichter Text — SVG
   kann kein KaTeX, der Lesefluss druckt die gesetzte Fassung. */
.sbp-kante-label { fill: #444; }
.sb-vorschau { fill: none; stroke: var(--pp-accent); stroke-width: 1.6; stroke-dasharray: 6 4; pointer-events: none; }
.sb-rahmenwahl { fill: var(--pp-accent-tint); stroke: var(--pp-accent-line); stroke-width: 1; }

/* ---------------- Kern-Karten (Auftrag C) ---------------- */
.sb-task { display: flex; flex-direction: column; gap: 4px; height: 100%; }
/* Abgehakt heißt ERLEDIGT, nicht VERSCHWUNDEN (Auftrag E, 11.08.2026).
   Vorher: opacity .55 plus Durchstreichung — auf der dunklen Fläche las
   sich das wie „weg", und Jonas' Befund war folgerichtig „sie wird
   ausgeblendet". Beides ist raus:
   - Gedämpft wird über die FARBE (--pp-text-2), nicht über Deckkraft.
     Die bleibt bei .8 und damit weit über der Lesbarkeitsgrenze; unter
     ~.75 wird auf dunklem Grund aus „ruhig" schnell „unlesbar".
   - Keine Durchstreichung: Durchgestrichener Text heißt „gestrichen,
     gilt nicht mehr" — hier gilt das Gegenteil, die Aufgabe ist
     verstanden. Der Titel muss lesbar bleiben, man sucht die Karte ja
     später wieder.
   Der Haken steht als ✓ IM Kartenkopf (nicht nur im Knopf) und trägt
   die Bestätigungsfarbe. */
.sb-task.sb-done { opacity: .8; }
.sb-task.sb-done .sb-task-titel,
.sb-task.sb-done .sb-task-exam { color: var(--pp-text-2); }
.sb-task-haken { color: var(--pp-ok); font-weight: 700; margin-right: 2px; }
.sb-task-exam { font-size: var(--pp-fs-mini); color: var(--pp-text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-task-titel { font-weight: 600; }
.sb-task-meta { display: flex; align-items: center; gap: 8px; font-size: var(--pp-fs-klein); color: var(--pp-text-2); }
.sb-chip {
  display: inline-block; font-size: var(--pp-fs-mini); padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--pp-line-2); color: var(--pp-text-2);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sb-task-fuss { margin-top: auto; display: flex; gap: 8px; align-items: center; }
.sb-task-fuss button {
  font: inherit; font-size: var(--pp-fs-klein); padding: 4px 8px; border-radius: 6px;
  border: 1px solid var(--pp-line-2); background: var(--pp-surface);
  color: var(--pp-text); cursor: pointer;
}
.sb-task-fuss button:hover { border-color: var(--pp-accent-line); color: var(--pp-accent-2); }
.sb-task-done { border-color: transparent; }
.sb-done .sb-task-done { color: var(--pp-ok); }

.sb-bild { width: 100%; height: 100%; overflow: hidden; }
.sb-bild svg { width: 100%; height: 100%; }
/* Foto-Karte (13.10.2026, docs/FOTOS-HEFT-FLAECHE-2026-10-13.md): das
   Foto füllt die Karte, ohne verzerrt zu werden — die Karte darf ein
   anderes Seitenverhältnis bekommen als das Bild. */
.sb-foto { width: 100%; height: 100%; overflow: hidden; display: flex; align-items: center; justify-content: center; }
.sb-foto-bild { display: block; max-width: 100%; max-height: 100%; object-fit: contain; user-select: none; -webkit-user-select: none; -webkit-user-drag: none; pointer-events: none; }
/* „Noch eins" in der Meldung (Entscheidung F3): Kamera direkt wieder —
   ein echtes Tippziel (44 px), auch am Rechner nicht kleiner. */
.sb-foto-noch {
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 14px; margin-left: 6px;
  font: inherit; font-weight: 600; color: var(--pp-text); background: var(--pp-accent-tint);
  border: 1px solid var(--pp-accent-line); border-radius: 8px; cursor: pointer;
}
.sb-foto-noch svg { width: 18px; height: 18px; }
.sb-foto-noch:focus-visible { outline: 2px solid var(--pp-accent); outline-offset: 2px; }

/* ---------------- Weißes Papier (Auftrag G, 11.08.2026) ----------------
   Schaltbild und Funktionsplot zeichnen nach DIN/IEC bzw. im Plotstil
   SCHWARZ AUF WEISS. Auf der dunklen Sandbox-Fläche stünden ihre Linien
   und Achsenzahlen unlesbar auf dunklem Grund. Sie bekommen deshalb
   dieselbe helle Fläche, die auch die Prüfungsansicht ihnen gibt — ein
   Blatt Papier auf einem dunklen Tisch (dasselbe Muster wie .tr-kachel
   im Training).

   NICHT umgefärbt wird die Zeichnung selbst: Eine CSS-Invertierung oder
   eine andere Linienfarbe zöge den PNG/JPG-Export und die
   Prüfungsansicht mit — genau die Trennung, die verify-lab-maus.js in
   beide Richtungen absichert. Im hellen Theme fällt das Blatt nicht auf,
   weil die Kartenfläche ohnehin fast weiß ist. */
.sb-papier {
  background: #fff; border: 1px solid var(--pp-line-2);
  border-radius: 8px; padding: 4px; box-sizing: border-box;
}
.sb-fehler { color: var(--pp-err); font-size: var(--pp-fs-klein); margin-top: 4px; }

/* ---------------- Zeichnung (Stufe 2, Paket 04b, 23.09.2026) ----------------
   docs/ZEICHNUNG-WIRTE-2026-09-23.md. Das Bild liegt wie Schaltbild und
   Graph auf weißem Papier (.sb-papier); beim Bearbeiten wird die Karte
   selbst zur Zeichenfläche: Der Wirt füllt sie, die Fläche des Editors
   füllt den Wirt, und die viewBox passt die Zeichnung ein (Rand oben und
   unten statt abgeschnittener Striche). Die Werkzeugleiste steht im
   Seitenfenster.

   Zwei Regeln der Karte würden den Editor sonst überstimmen, und beide
   stehen hier ausdrücklich zurückgesetzt — die Warnung der 04a-Doku
   („ein Wirt, der button/svg/textarea pauschal gestaltet"):
   - `.sb-karte textarea` (0,1,1) schlägt `.zg-textfeld` (0,1,0): Das
     Textfeld der Zeichnung bekäme die dunkle Kartenfläche, einen
     Akzentrahmen, 100 % Breite und Höhe und die Zeilenhöhe der Karte —
     die Grundlinie des getippten Textes läge dann nicht mehr dort, wo
     er gezeichnet wird.
   - `.sb-bild svg` gilt nur im BILD (.sb-zg-bild), nie im Editor: Der
     Wirt trägt die Klasse deshalb nicht. */
.sb-zg-bild { display: block; }
.sb-zg-wirt { width: 100%; height: 100%; min-height: 0; cursor: default; }
.sb-zg-wirt > .zg-editor { height: 100%; gap: 0; }
.sb-zg-wirt .zg-flaeche { flex: 1 1 auto; min-height: 0; }
.sb-zg-wirt .zg-svg { width: 100%; height: 100%; }
.sb-karte .zg-textfeld {
  width: auto; height: auto; box-sizing: content-box;
  padding: 0 2px; border: 0; border-radius: 0;
  background: transparent; line-height: 1.25em;
}
/* Das Fenster: Leiste, Meldezeile, ein Satz dazu. Die Leiste folgt dem
   Theme (css/zeichnung.css), der Satz ist Hinweis und bleibt leise. Drei
   Klassen, weil `.sb-editfenster-leib > .sb-sf-editor` weiter unten das
   Polster mit zweien setzt und sonst gewänne. */
.sb-editfenster-leib > .sb-sf-editor.sb-zg-fenster { gap: 8px; padding-top: 10px; }
.sb-zg-hinweis { font-size: var(--pp-fs-klein); line-height: var(--pp-lh-knapp); color: var(--pp-text-2); }

/* Aufgaben-Wähler in der task-Karte. */
.sb-wahl { display: flex; flex-direction: column; gap: 6px; height: 100%; }
.sb-wahl input {
  font: inherit; font-size: var(--pp-fs-klein); padding: 5px 8px; border-radius: 6px;
  border: 1px solid var(--pp-line-feld); background: var(--pp-bg); color: var(--pp-text);
}
.sb-wahl input:focus { border-color: var(--pp-accent-line); }
.sb-wahl-liste { overflow-y: auto; min-height: 0; flex: 1 1 auto; display: flex; flex-direction: column; }
/* flex-shrink: 0 ist die REPARATUR von P1 (11.10.2026, Jonas' Screenshot:
   Zeilenbrei unter „Titel filtern …"). Die Zeile trägt overflow: hidden
   (für die Auslassungspunkte) — damit ist ihr automatisches min-height 0
   statt der Inhaltshöhe, und in der Flex-Spalte mit fester Höhe teilte
   flex-shrink: 1 den fehlenden Platz auf alle Zeilen auf. Gemessen in
   einer 240 x 120-Karte mit acht Kursen: Liste 60 px hoch, jede Zeile
   8 px (nur noch das Polster) statt 27,4 px. Verwandt mit Fallstrick 41.
   Ohne Schrumpfen rollt die Liste, wie sie soll. */
.sb-wahl-liste > * { flex-shrink: 0; }
.sb-wahl-zeile {
  font: inherit; font-size: var(--pp-fs-klein); text-align: left; padding: 4px 6px;
  border: 0; border-radius: 5px; background: none; color: var(--pp-text); cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Aufgabenzeile: Nummer | Titel | Punkte. Der Titel kürzt mit „…" —
   min-width: 0 braucht er dafür (Fallstrick 41), sonst schöbe er die
   Punkte aus der Zeile. */
.sb-wahl-item { display: flex; align-items: baseline; gap: 6px; }
.sb-wahl-nr { flex: none; min-width: 1.6em; text-align: right; color: var(--pp-text-3); font-size: var(--pp-fs-mini); }
.sb-wahl-titel { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.sb-wahl-pkt { flex: none; color: var(--pp-text-3); font-size: var(--pp-fs-mini); }
/* Kopf einer Klausurgruppe im Filterlauf: der Pfad „Kurs › Klausur",
   klein und gedämpft — er ordnet, die Aufgaben darunter sind das Ziel.
   Klickbar: führt auf die Klausur-Ebene. */
.sb-wahl-gruppe {
  font-size: var(--pp-fs-mini); color: var(--pp-text-2); font-weight: 600;
  margin-top: 4px; padding-top: 3px; padding-bottom: 2px;
}
.sb-wahl-art { color: var(--pp-text-3); font-size: var(--pp-fs-mini); margin-right: 2px; }
.sb-wahl-alle { color: var(--pp-accent-2); font-weight: 600; }
.sb-wahl-mehr { padding: 4px 6px; font-size: var(--pp-fs-mini); }
.sb-wahl-zeile:hover { background: var(--pp-bg-2); color: var(--pp-accent-2); }
/* Anzahl je Quelle im Schaltbild-Wähler — beim Wählen ist die Zahl die
   nützlichere Angabe als der Name allein (Muster der Trainings-Sammlung). */
.sb-wahl-zahl { color: var(--pp-text-3); font-size: var(--pp-fs-mini); margin-left: 6px; }

/* ---------------- Rechen-Karten (Auftrag D) ---------------- */
.sb-plot { display: flex; flex-direction: column; gap: 4px; height: 100%; }
/* Der Plot zeichnet ebenfalls schwarz auf weiß — dasselbe Papier wie das
   Schaltbild (Auftrag G). Ohne das stünden Achsenzahlen und Beschriftung
   im dunklen Theme unlesbar da. */
.sb-plot-bild { flex: 1; min-height: 0; overflow: hidden;
  background: #fff; border: 1px solid var(--pp-line-2);
  border-radius: 8px; padding: 4px; box-sizing: border-box; }
.sb-plot-bild svg { width: 100%; height: 100%; }
.sb-plot-param {
  display: flex; align-items: center; gap: 8px; font-size: var(--pp-fs-klein);
  color: var(--pp-text-2);
  /* Rechts Platz in Knopfbreite (Auftrag B3, 11.08.2026): Am rechten
     Anschlag lag der Reglerknopf vorher halb auf dem Größengriff der
     Karte und war dort kaum zu fassen. */
  padding-right: 12px;
}
.sb-plot-param input[type="range"] { flex: 1; min-width: 0; }
.sb-plot-pname { flex: none; }
/* Der Zahlenwert ist ein Feld, kein Text: Wer über den Reglerbereich
   hinaus will, tippt den Wert ein und die Grenzen weiten sich. */
.sb-plot-pwert {
  flex: none; width: 68px; box-sizing: border-box;
  font: inherit; font-variant-numeric: tabular-nums;
  color: var(--pp-text); background: var(--pp-bg);
  border: 1px solid var(--pp-line-2); border-radius: 5px;
  padding: 1px 4px; text-align: right;
}
.sb-plot-pwert:focus { border-color: var(--pp-accent-line); }
.sb-plot-warn { font-size: var(--pp-fs-mini); color: var(--pp-text-3); }
.sb-cir { display: flex; flex-direction: column; gap: 6px; height: 100%; }
.sb-cir .sb-bild { flex: 1; min-height: 0; }
.sb-cir-lab {
  align-self: flex-start;
  font: inherit; font-size: var(--pp-fs-klein); padding: 4px 8px; border-radius: 6px;
  border: 1px solid var(--pp-line-2); background: var(--pp-surface);
  color: var(--pp-text); cursor: pointer;
}
.sb-cir-lab:hover { border-color: var(--pp-accent-line); color: var(--pp-accent-2); }
/* Fußzeile der gefüllten Karte (23.09.2026): Labor öffnen · andere Schaltung
   wählen — nebeneinander, auf schmalen Karten umbrechend. */
.sb-cir-fuss { display: flex; flex-wrap: wrap; gap: 6px; }
.sb-cir-fuss .sb-cir-lab { align-self: auto; }
.sb-json { font-family: var(--pp-mono, monospace); font-size: var(--pp-fs-mini); }

/* ---------------- Formularbox (docs/AUFTRAG-SANDBOX-FORMULARE.md) ----------------
   Vier Baender: Kopf, scrollender Feldbereich, Fehlerzeile, Fuss. Nur der
   Feldbereich darf schrumpfen — Fehler und Fuss sind `flex: none` und
   behalten ihren Platz auch in einer kleinen Karte. Ohne das schnitte
   `.sb-karte { overflow: hidden }` genau die Rueckmeldung ab, auf die man
   im Formular angewiesen ist (Auftrag A3). */
.sb-formbox { gap: 6px; }
.sb-fkopf { display: flex; align-items: center; gap: 8px; flex: none; }
.sb-ftitel { font-size: var(--pp-fs-mini); letter-spacing: .04em; text-transform: uppercase; color: var(--pp-text-3); }
.sb-fumschalt {
  margin-left: auto; font: inherit; font-size: var(--pp-fs-mini); font-family: var(--pp-mono, monospace);
  border: 1px solid var(--pp-line-2); background: transparent; color: var(--pp-text-2);
  border-radius: 5px; padding: 2px 7px; cursor: pointer;
}
.sb-fumschalt:hover { border-color: var(--pp-accent-line); color: var(--pp-accent-2); }
.sb-form { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 10px; }
.sb-fgruppe { display: flex; flex-direction: column; gap: 4px; }
.sb-flabel { font-size: var(--pp-fs-mini); color: var(--pp-text-2); }
.sb-fgruppe > input, .sb-fzeile > input {
  font: inherit; font-size: var(--pp-fs-klein); padding: 4px 7px; width: 100%; box-sizing: border-box;
  border: 1px solid var(--pp-line-feld); border-radius: 5px;
  background: var(--pp-bg); color: var(--pp-text);
}
.sb-fgruppe > input:focus, .sb-fzeile > input:focus, .sb-raster input:focus {
  border-color: var(--pp-accent);
}
.sb-fliste { display: flex; flex-direction: column; gap: 3px; }
.sb-fzeile { display: flex; align-items: center; gap: 4px; }
.sb-fzeile > input { font-family: var(--pp-mono, monospace); font-size: var(--pp-fs-klein); }
.sb-fzeile > button, .sb-raster button {
  flex: none; border: none; background: transparent; color: var(--pp-text-3);
  cursor: pointer; font-size: var(--pp-fs-klein); line-height: var(--pp-lh-eng); padding: 3px 5px; border-radius: 4px;
}
.sb-fzeile > button:hover, .sb-raster button:hover:not([disabled]) { color: var(--pp-err); background: var(--pp-surface-2, var(--pp-surface)); }
.sb-fneu {
  align-self: flex-start; font: inherit; font-size: var(--pp-fs-mini);
  border: 1px dashed var(--pp-line-2); background: transparent; color: var(--pp-text-2);
  border-radius: 5px; padding: 3px 9px; cursor: pointer;
}
.sb-fneu:hover { border-color: var(--pp-accent-line); color: var(--pp-accent-2); }
/* Die Sichtbarkeitszeichen der UML — die halbe Notation, und genau die
   Zeichen, die der Renderer bis zum 12.08.2026 gefressen hat. */
.sb-fhinweis { font-size: var(--pp-fs-mini); color: var(--pp-text-3); line-height: var(--pp-lh-knapp); }
.sb-fehler { flex: none; }
.sb-ffuss { flex: none; display: flex; justify-content: flex-end; gap: 6px; }
.sb-ffuss button {
  font: inherit; font-size: var(--pp-fs-klein); padding: 4px 12px; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--pp-line-2); background: transparent; color: var(--pp-text);
}
.sb-ffuss button.accent { background: var(--pp-accent); border-color: var(--pp-accent); color: #fff; }

/* Das Raster der Tabellenkarte. Es scrollt waagerecht — eine
   Wahrheitstabelle mit acht Spalten passt in keine 320-px-Karte, und die
   Karte soll deswegen nicht wachsen.

   BEFUND vom 03.09.2026 (Jonas: „man sieht nicht die ganze Zeile, obwohl
   noch Platz waere"): Hier stand `.sb-raster input { width: 62px }` —
   eine FESTE Feldbreite. In einem echten WebKit gemessen (Karte 440x340,
   drei Spalten, docs/abnahme/sandbox-tabellenformular/BERICHT.md):
   Zelle 122 px breit, Eingabefeld darin 62 px — 60 px je Zelle standen
   leer, und 11 von 12 Feldern zeigten ihren Inhalt abgeschnitten. Das
   Feld fuellt seine Zelle jetzt aus (width: 100%, min-width: 0 gegen
   Fallstrick 41), und die Spalten teilen sich die Kartenbreite
   (table-layout: fixed + width: 100%).

   Die UNTERGRENZE je Spalte bleibt --sb-rmin — genau die alten 62 px:
   Unterhalb davon wird nicht gequetscht, sondern gescrollt. Die
   Spaltenzahl kann CSS nicht wissen, sie kommt als --sb-rspalten aus
   rasterHtml (js/sandbox.js) — Custom Property, nicht width-Inline
   (Muster --ein-task-w, --tr-task-w). Eine breite Tabelle verhaelt sich
   damit wie vorher, eine schmale nutzt endlich ihren Platz.

   Warum GLEICHE Spalten und nicht nach Inhaltslaenge verteilt: an
   derselben Messung nachgerechnet. Gleich verteilt sind 10 von 12 Zellen
   ganz zu sehen, inhaltsanteilig nur 6 — die anteilige Verteilung gibt
   jeder Spalte weniger als ihr Maximum, sobald die Summe nicht passt. */
.sb-rasterwrap { display: flex; flex-direction: column; gap: 4px; overflow-x: auto; }
.sb-raster {
  --sb-rmin: 62px; --sb-rlabel-w: 30px;
  border-collapse: collapse; table-layout: fixed; width: 100%;
  min-width: calc(var(--sb-rlabel-w) + var(--sb-rspalten, 1) * var(--sb-rmin));
}
.sb-raster th, .sb-raster td { padding: 0; border: 1px solid var(--pp-line-2); }
.sb-raster tr:first-child th { border: none; text-align: center; }
/* table-layout: fixed liest die Breiten aus der ERSTEN Zeile — die
   Beschriftungsspalte bekommt ihre dort, sonst naehme sie sich einen
   gleichen Anteil wie eine Datenspalte. */
.sb-raster tr:first-child th:first-child, .sb-raster .sb-rlabel { width: var(--sb-rlabel-w); }
.sb-raster .sb-rlabel {
  border: none; font-size: var(--pp-fs-mini); font-weight: 400; color: var(--pp-text-3);
  padding: 0 4px 0 0; text-align: right; white-space: nowrap;
}
.sb-raster input {
  font: inherit; font-family: var(--pp-mono, monospace); font-size: var(--pp-fs-klein);
  width: 100%; min-width: 0; box-sizing: border-box; padding: 3px 5px;
  border: none; background: transparent; color: var(--pp-text); text-align: center;
}
.sb-raster input.kopf { font-weight: 600; background: var(--pp-surface-2, var(--pp-surface)); }
.sb-rknoepfe { display: flex; gap: 6px; }
.sb-rknoepfe button {
  font: inherit; font-size: var(--pp-fs-mini); border: 1px dashed var(--pp-line-2);
  background: transparent; color: var(--pp-text-2); border-radius: 5px;
  padding: 3px 9px; cursor: pointer;
}
.sb-rknoepfe button:hover:not([disabled]) { border-color: var(--pp-accent-line); color: var(--pp-accent-2); }
.sb-rknoepfe button[disabled] { opacity: .45; cursor: default; }

/* ---------------- html-Karte (Auftrag E) ---------------- */
.sb-html { display: flex; flex-direction: column; height: 100%; gap: 0; }
.sb-html-leiste {
  display: flex; align-items: center; gap: 6px; flex: none;
  padding-bottom: 6px; border-bottom: 1px solid var(--pp-line);
  margin-bottom: 6px;
}
.sb-html-titel {
  font-weight: 600; font-size: var(--pp-fs-klein); margin-right: auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sb-html-leiste button {
  font: inherit; font-size: var(--pp-fs-klein); padding: 2px 7px; border-radius: 5px;
  border: 1px solid var(--pp-line-2); background: var(--pp-surface);
  color: var(--pp-text); cursor: pointer; flex: none;
}
.sb-html-leiste button:hover { border-color: var(--pp-accent-line); color: var(--pp-accent-2); }
/* Gedrückt = das Quelltext-Fenster dieser Karte steht offen (P10a).
   Der EIN-Zustand ist eine FORM, keine Farbe: Der inset-Schatten
   verdoppelt den Rand, ist also auch in Graustufen zu sehen — und er
   liegt AUSSERHALB des Layouts, verschiebt in der engen Leiste also
   nichts (dieselbe Entscheidung wie beim Sandbox-Menüband, wo ein
   Häkchen im Text den Knopf breiter gemacht hätte). */
.sb-html-leiste button.on {
  color: var(--pp-accent-2); border-color: var(--pp-accent-line);
  box-shadow: inset 0 0 0 1px currentColor;
}
.sb-html iframe {
  flex: 1; min-height: 0; width: 100%; border: 0; border-radius: 6px;
  background: #fff;
}
.sb-html-name {
  font: inherit; font-size: var(--pp-fs-klein); padding: 4px 8px; margin-bottom: 6px;
  border: 1px solid var(--pp-line-2); border-radius: 6px;
  background: var(--pp-bg); color: var(--pp-text);
}

/* ---------------- Der KI-Chat der Fläche (30.09.2026) ----------------
   docs/FLAECHEN-CHAT-2026-09-30.md: `#sb-ki-panel` ist der WIRT des
   gewöhnlichen Chat-Fensters (Professor mit der Fläche als Anker) — der
   Rahmen bringt keine eigenen Farben mit. Die Bausteine des früheren
   Lauf-Chats (Vorschlag, „Erneut versuchen", ehrliche Grenze) sind mit ihm
   gegangen; die Vorschlagskarte ist die des Chat-Kerns.

   `.sb-ki` selbst bleibt stehen: Die Werkbank (Klausurgrafik im Editor)
   und das Mini-Labor rendern denselben Kasten ohne Chat. */
.sb-ki {
  display: flex; gap: 8px; align-items: flex-start; flex-wrap: wrap;
  margin-bottom: 10px; padding: 10px; border-radius: 10px;
  border: 1px solid var(--pp-line); background: var(--pp-surface);
}
.sb-ki textarea {
  flex: 1 1 320px; min-width: 0; resize: vertical;
  font: inherit; font-size: var(--pp-fs-klein); padding: 7px 10px; border-radius: 7px;
  border: 1px solid var(--pp-line-feld); background: var(--pp-bg); color: var(--pp-text);
}
.sb-ki textarea:focus { border-color: var(--pp-accent-line); }

/* Vollbild: zwei Klassen gegen die spätere .modal-Basisregel
   (Fallstrick 4 — sonst gewinnt max-width: 480px). */
.modal.sb-modal {
  max-width: min(96vw, 1100px); width: 96vw; height: 90vh;
  display: flex; flex-direction: column; padding: 12px;
}
.sb-modal iframe { flex: 1; min-height: 0; width: 100%; border: 0; background: #fff; }
.sb-voll-bg { z-index: 145; }

/* ---------------- Drucken (Auftrag G) ----------------
   Der Druckcontainer hängt am body; am Bildschirm bleibt er unsichtbar,
   @media print blendet die App aus und ihn ein — der einfachste Weg ohne
   neues Fenster (Popup-Blocker). */
/* Die Ein-/Ausblendregel steht seit Plan 1 C6 EINMAL in css/druck.css und
   hängt dort an .pp-druck — hier stünde sie nur für die Sandbox, und ein
   zweiter Drucker hätte sich selbst ausgeblendet. Der Sandbox-Container
   trägt beide Klassen; alles Folgende ist sein eigenes Druckbild. */
.sbp-titel { font-size: var(--pp-fs-h1); margin: 0 0 12px; }
.sbp-block {
  border: 1px solid #bbb; border-radius: 8px; padding: 10px 14px;
  margin-bottom: 10px; break-inside: avoid;
}
.sbp-kante { font-size: var(--pp-fs-klein); color: #444; margin-top: 6px; }
/* Der Wert, der auf dem Papier an der Stelle eines Bedienelements steht
   (C33, druckKnotenAufbereiten): Reglerstand, Ankreuzfeld, Auswahl. Er
   liest sich als Zahl, nicht als Feld — Mono, damit „a = 1,5" in einer
   Reglerliste untereinander flieht. */
.sbp-wert { font-family: var(--pp-mono, monospace); }
/* „Text ohne Kasten" heißt auch auf dem Papier ohne Kasten (Auftrag F). */
.sbp-block.sbp-frei { border: 0; padding: 0 2px; }
.sbp-html {
  border: 1px dashed #999; border-radius: 8px; padding: 10px 14px;
  color: #444; font-size: var(--pp-fs-klein);
}
/* ---------------- Druckbühne (Auftrag A, 12.08.2026) ----------------
   Seit die Karten HTML sind, kann das Druckbild kein einzelnes SVG mehr
   sein. Es ist jetzt dieselbe Doppelebene wie am Bildschirm, in EINEM
   Maßstab auf das Blatt gerechnet (anordnungBlock): außen ein Kasten in
   der Größe des skalierten Bildes, innen die unskalierte Fläche. */
.sbp-anordnung svg { max-width: 100%; }
.sbp-buehne { position: relative; overflow: hidden; margin: 0 auto; }
.sbp-flaeche { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
.sbp-flaeche > svg { position: absolute; left: 0; top: 0; }
.sbp-flaeche .sb-node { position: absolute; }
/* Auf Papier ist nichts ausgewählt. */
.sbp-flaeche .sb-node.sel { border-color: var(--pp-line); border-width: 1.5px; }
.sbp-flaeche .sb-griff { display: none; }
/* Poster (Plan 3 B2): das SVG steht mittig auf GENAU einem Blatt —
   break-inside verhindert, dass der Browser es doch aufteilt. */
.sbp-poster { break-inside: avoid; }
.sbp-poster svg { display: block; margin: 0 auto; }

/* Die Vorlagenwahl beim Anlegen (Plan 3 C4) ist am 22.08.2026 mit ihrem
   Dialog entfallen (Z5) — .sb-vorlagen, .sbv-liste und .sbv-vorlage
   hatten danach kein Markup mehr. Vorlagen und Canvas-Import wohnen im
   Reiter „Einfügen" der offenen Fläche und tragen dessen Knopfstile. */

/* ---------------- Rechtsklickmenü (Auftrag E, 12.08.2026) ----------------
   Aufgebaut wie das des Schaltlabors (.lab-ctx in css/lab.css):
   Abschnitte mit Trennlinien, Kürzel rechtsbündig, Unanwendbares
   ausgegraut statt versteckt. Es hängt in der FLÄCHE, nicht in der
   Kartenebene — dort skalierte es mit dem Zoom mit und wäre bei 60 %
   unlesbar. */
/* Flaeche, Schrift, Polsterung, Trenner, Kuerzel und Hover stehen seit dem
   18.09.2026 in css/listen.css („Die EINE Menuegestalt") — fuer ALLE DREI
   Menueknoten zugleich. Hier bleibt nur, was wirklich nur dieses Menue
   betrifft: seine LAGE in der Flaeche. Wer hier wieder eine Farbe oder
   eine Polsterung hinschreibt, bekommt sie auch (sandbox.css laedt NACH
   listen.css) — und hat damit zwei Gestalten;
   verify-rechtsklick-2026-09-18.js meldet es. */
.sb-ctx {
  position: absolute; z-index: 60; max-height: 74%;
  cursor: default;
}
/* Gegenregel zu jeder eigenen display-Angabe — dieselbe Falle wie bei
   .sb-add-menu und .sbd-gruppe. Sie steht jetzt in css/listen.css bei der
   Gestalt, weil dort das display:flex herkommt. */
/* Ein echtes Untermenü: Es klappt zur Seite auf und ERSETZT das
   Hauptmenü nicht.

   position: FIXED, nicht absolute — und das ist kein Geschmack. Das
   Hauptmenü .sb-ctx trägt max-height + overflow-y:auto, damit ein langes
   Menü scrollen kann. Ein Kasten mit overflow BESCHNEIDET aber alles,
   was herausragt, und zwar auf BEIDEN Achsen: Sobald eine Achse nicht
   `visible` ist, rechnet CSS die andere von `visible` auf `auto` um. Das
   absolut positionierte Untermenü stand damit rechnerisch an der
   richtigen Stelle (DOM sagte display:block, visibility:visible), war
   aber bis auf einen 6-px-Streifen weggeschnitten — „Karte hinzufügen"
   im Rechtsklickmenü tat schlicht nichts. Gefunden am 12.08.2026 beim
   Bedienen im Browser (docs/SANDBOX-STRESSTEST-2026-08-12.md, Befund
   B2); ein jsdom-Test kann das nie finden, er sieht das Attribut und
   nicht die Darstellung — dieselbe Lehre wie bei .sb-add-menu.

   Fixed heißt: Die Lage rechnet das JavaScript (ctxBinde), weil ein
   fixed-Element seinen Bezug nicht mehr vom Elternteil bekommt. */
.sb-ctx-unter { position: relative; }
/* Auch hier nur noch die Lage und das Auf/Zu — das Aussehen kommt aus
   css/listen.css. `display: none` steht bewusst NACH der geteilten Regel
   (sandbox.css laedt spaeter) und gewinnt deshalb im geschlossenen
   Zustand; .offen macht daraus block. */
.sb-ctx-unter .sb-ctx-menu {
  display: none; position: fixed; z-index: 61; max-height: 70vh;
}
.sb-ctx-unter.offen .sb-ctx-menu { display: block; }

/* Der Druckdialog. */
.modal.sb-modal.sb-druckdialog { height: auto; max-width: 460px; width: 92vw; gap: 10px; }
.sbd-gruppe { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; font-size: var(--pp-fs-klein); }
/* Gegenregel zu display: flex — sonst steht die Maßstab-Gruppe trotz
   hidden immer da (dieselbe Falle wie bei .sb-add-menu oben). */
.sbd-gruppe[hidden] { display: none; }
.sbd-opt { display: inline-flex; gap: 5px; align-items: center; cursor: pointer; }
.sbd-hinweis { font-size: var(--pp-fs-klein); color: var(--pp-text-2); }
.sbd-fuss { display: flex; justify-content: flex-end; }

/* Schmale Fenster: EINE lange Reihe zum Wischen statt eines Umbruchs.

   Jonas' Regel (24.08.2026): „Auf dem Handy sieht es manchmal besser
   aus, wenn anstatt ein Zeilenumbruch zu machen, die Box einfach
   verlängert wird und man darin nach rechts swipen muss."

   Gemessen wurde vorher (WebKit, 390x844): Das Band brach in FÜNF
   Reihen um, das Start-Panel war 225 px hoch, die ganze Kopfzeile
   378 px — und der Zeichenfläche blieben von 844 px Fenster ganze
   363 px. Dazu sprang die Fläche beim Reiterwechsel um bis zu 172 px
   (Start 225, Einfügen 249, Anordnen 113, Ansicht 53), weil jeder
   Reiter zufällig anders viele Reihen füllt. Waagerecht gewischt
   bleibt die Höhe EINE Reihe, gleich in jedem Reiter — dieselbe
   Antwort, die die Reiterzeile eine Zeile weiter unten längst gibt.

   Der Preis ist ausdrücklich in Kauf genommen: Was rechts liegt, sieht
   man erst nach dem Wischen. Auf dem Telefon ist das der bessere
   Handel — die Kopfzeile ist dort der teuerste Platz der Seite. */
/* ---- Das Tablet zählt mit (web1014 A1-21, 14.10.2026) ----
   Der Block galt bis dahin nur nach der BREITE. Am iPad quer (1112 × 834,
   Finger) stand deshalb das Laptop-Band — aber mit den 44-px-Einträgen des
   Touch-Blocks, drei untereinander: Band 161 px, Kopf + Band 263 px, dazu
   die zwei Portal-Leisten des Mobil-Menüs (dasselbe Hausmuster
   `(pointer: coarse) and (max-width: 1200px)`, css/prepp.css), und die
   Seite rechnete trotzdem `100vh - 54px` und rollte 118 px. Gemessen
   (WebKit): Leinwand ab y 435 von 834, 48 % des Bildschirms. Ein Tablet
   wird angefasst wie ein Telefon — es bekommt dieselbe wischbare Reihe,
   dieselbe kompakte Namenszeile und dieselbe gemessene Oberkante. Die
   Regel aus MECHANISMEN („Sandbox-Menüband") bleibt wahr: Wo die kleinen
   Einträge in einer REIHE stehen, fällt der Boden `--sb-raster` weg. */
/* WF5 (web1014 D1-03): Zwischen 861 und 1250 px hat die Kopfzeile mit
   KI-Leiste ZWEI Zeilen (css/prepp.css) — gelesen wird die gemessene
   Oberkante (--sb-oben, js/sandbox.js sbObenMessen, auch bei resize);
   ohne Messung bleibt es bei 54 px wie bisher.
   (Steht hinter der Grundregel: verify-sandbox-ansicht.js liest alles vor
   dem ersten @media als Grundregel.) */
@media (min-width: 861px) and (max-width: 1250px) and (pointer: fine), (min-width: 1201px) and (max-width: 1250px) {
  .pp-page.sb-page.sb-randlos { height: calc(100vh - var(--sb-oben, 54px)); }
}
@media (max-width: 860px), (pointer: coarse) and (max-width: 1200px) {
  /* ---- Die Seite endet am Fensterboden (18.09.2026) ----
     Die Grundregel von `.sb-randlos` rechnet `100vh - 54px`: die Höhe
     der Portal-Kopfzeile am Laptop. Auf dem Telefon stehen über der
     Seite ZWEI Leisten übereinander — die Mobil-Leiste `.pp-mtop` und
     die Kopfzeile `.pp-top`. Mit der alten Rechnung ragte die Seite
     72 px unter den Fensterboden, und die STATUSLEISTE lag vollständig
     außerhalb des Bildes: Sie ist auf dem Telefon die einzige Stelle,
     an der Zoom, Kartenzahl und die Rückmeldung der Fläche stehen.
     Aufgefallen ist es erst, als die Warnkarte davor fiel — vorher hat
     diese Seite dort nie jemand gesehen.

     GERECHNET WIRD DIE ZAHL NICHT, SIE WIRD GEMESSEN. Der erste Anlauf
     schrieb die gemessenen 126 px hier hin und lag im nächsten Lauf um
     46 px daneben: Die Kopfzeile ist mal 61 und mal 107 px hoch, je
     nachdem ob die KI-Chat-Leiste Reiter trägt. Eine feste Zahl kann
     das nicht treffen. `--sb-oben` setzt deshalb js/sandbox.js aus der
     wirklichen Oberkante der Seite (sbObenMessen, Muster
     --tr-fenster-h vom 14.09.2026, gegen genau denselben Fehler). Der
     Rückfallwert ist der gemessene Normalfall — ohne JS steht die Seite
     damit so da wie vor dieser Änderung, nur 72 px weniger tief.
     `verify-sandbox-telefon.js` hält Regel und Messer zusammen. */
  .pp-page.sb-page.sb-randlos { height: calc(100vh - var(--sb-oben, 126px)); }

  /* ---- Die kompakte Namenszeile (19.09.2026) ----
     Jonas' Entscheidung: Auf dem Telefon stehen Zurück, Flächenname und
     Speicherpunkt in EINER Zeile; Marke und das Wort am Rückweg fallen
     dort weg. (Bewusst NICHT gewählt: ein einklappbarer Kopf.)

     GEMESSEN, BEVOR etwas geändert wurde (WebKit, 390 × 844):
     `.sb-top` brach in DREI Flex-Zeilen um und war 119,8 px hoch —
     mit dem 6-px-Polster des Kopfes 125,8 von 233,7 px Kopfzeile, also
     mehr als die Hälfte. Die Zeilen waren
       1. „‹ Meine Inhalte"  120,9 × 44
       2. Namensfeld 260 × 38,8  +  „● gespeichert" 80,5 × 19,4
       3. die Marke „ARBEITSFLÄCHE" 105,3 × 17
     Und der Umbruch war KEINE Platznot: In 362 px hätten Knopf und Feld
     nebeneinander gepasst. `flex-wrap` verteilt die Kinder auf Zeilen
     nach ihrer HYPOTHETISCHEN Größe, also VOR dem Schrumpfen — und
     `.sb-name` bringt `flex: 0 1 260px` mit: 120,9 + 10 + 260 = 390,9
     > 362, das Feld ging in Zeile 2. Danach passte der Statuspunkt noch
     dazu, die Marke nicht mehr.

     Deshalb ist die Reparatur die FLEX-BASIS und nicht `nowrap`:
     `flex: 1 1 0` gibt dem Namensfeld die hypothetische Größe 0, alle
     drei Kinder landen in einer Zeile, und das Feld wächst danach auf
     den Rest (gemessen 217,5 px). `flex-wrap: wrap` BLEIBT ausdrücklich
     stehen — der Statuspunkt trägt im Fehlerfall einen freien Satz aus
     dem Speicher (setStatus, `zustand.de`), und der ist beliebig lang.
     Mit `nowrap` schöbe er die Seite in den waagerechten Bildlauf; so
     rutscht er auf eine zweite Zeile und bleibt lesbar. Eine Meldung,
     die man nicht lesen kann, ist schlimmer als eine zweite Zeile.

     `min-width: 0` am Feld ist Pflicht und nicht Zierrat (Fallstrick 41):
     Ein Flex-Kind mit dem Vorgabewert `min-width: auto` kann nie unter
     seine Inhaltsbreite schrumpfen — bei einem `<input>` ist das die
     Breite von `size` (20 Zeichen). Ohne die Zeile frisst das Feld den
     Platz seiner Geschwister. Die Grundregel setzt dort 140 px, deshalb
     muss der Selektor sie schlagen: `#sb-name` ist eine Kennung. */
  .sb-top-seite .sb-zurueck {
    /* Nur das „‹" bleibt; das Wort steht in einem eigenen `<span>`, und
       der Name des Knopfes im `aria-label` (js/sandbox.js). Hausmuster:
       `.sb-gv-x` samt `.sb-gv-xwort` im Geo-Vollbild. */
    min-width: 44px; min-height: 44px;
    padding-left: 0; padding-right: 0;
  }
  .sb-top-seite .sb-zurueck .sb-zurueck-wort { display: none; }
  .sb-top-seite #sb-name {
    flex: 1 1 0; min-width: 0;
    /* R3: Das Namensfeld IST bedienbar (es wird angetippt und getippt) und
       war mit 38,8 px unter den 44. Die zwei Trefferflächen der Zeile
       liegen dabei 10 px auseinander (das `gap` der Zeile) und können
       sich deshalb nicht überlappen — geweitet werden ECHTE Kästen, kein
       unsichtbarer `::before`-Vorsatz (die Falle vom 09.09. und
       16.09.2026, beide Male teuer). */
    min-height: 44px;
    /* Ein langer Name gibt nach, statt die Zeile zu sprengen. Bei einem
       `<input>` wirkt `text-overflow` nur, solange es nicht den Fokus
       hat — beim Tippen scrollt der Browser mit, und das ist dort auch
       richtig. */
    text-overflow: ellipsis;
  }
  /* Der Speicherpunkt gibt nicht nach: Er ist der Zustand der Fläche.
     Sein WORT bleibt stehen — „● gespeichert" ist 80,5 px breit und
     passt, und ohne es trüge der Zustand nur noch eine Farbe (die
     Hausregel „der Zustand ist eine FORM, keine Farbe"). */
  .sb-top-seite .sb-status { flex: none; }
  /* Die Marke sagt auf dem Telefon nichts Neues: Die Seite IST die
     Arbeitsfläche, und der Weg dorthin war ein Klick auf ihren Namen.
     Am Laptop bleibt sie (Entwurfsvorlage W5). */
  .sb-top-seite .sb-marke { display: none; }

  .sb-tools {
    width: 100%; flex-wrap: nowrap;
    overflow-x: auto; overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    /* Der Wisch bleibt IN der Leiste: ohne das zieht er am Ende der
       Reihe die Seite mit (und in Safari die Zurück-Geste). Die Fläche
       darunter kann er nicht erreichen — sie hört nur auf Ereignisse
       an #sb-canvas —, aber die Seite eben schon. */
    overscroll-behavior-x: contain;
  }
  .sb-canvas { height: calc(100vh - 240px); }
  /* Die Reiterzeile darf schmal scrollen statt umzubrechen: Fünf Reiter
     in zwei Zeilen sähen aus wie zwei Ebenen. */
  .sb-tabs { overflow-x: auto; gap: 10px; }
  /* ---- Der Wisch-Hinweis (27.09.2026) ----
     Band und Reiterzeile wischen — aber nichts sagte, dass es rechts
     weitergeht (Reiter „Start" bei 390 px: 1153 px Knöpfe in 362 px).
     js/sandbox.js (wischStand) setzt `sb-wisch-r`, solange rechts noch
     Inhalt liegt, und `sb-wisch-l`, sobald gewischt wurde. Gemalt wird
     an der KANTE: ein klebendes Pseudo-Element (position: sticky im
     Bildlaufkasten — es steht am sichtbaren Rand, gleich wie weit
     gewischt ist) mit einem Verlauf in die Fläche und einem kleinen
     Pfeil. `pointer-events: none`: Es verdeckt keine Trefferfläche, ein
     Tipp auf den halb ausgeblendeten Knopf trifft den Knopf.

     Die Breite des Pseudo-Elements wird mit einem negativen Rand wieder
     abgezogen — samt dem `gap` der Zeile (0 im Band, 10 px bei den
     Reitern): Sonst wüchse die Leiste um den Hinweis, und eine Reihe,
     die eben noch passte, liefe über, WEIL sie einen Hinweis trägt.
     Ohne Klasse steht das Element unsichtbar da (opacity 0) — nichts
     bewegt sich, `prefers-reduced-motion` hat nichts anzuhalten. */
  .sb-kopf .sb-tools::before, .sb-kopf .sb-tools::after,
  .sb-kopf .sb-tabs::before, .sb-kopf .sb-tabs::after {
    content: ""; position: sticky; z-index: 1; flex: 0 0 22px; width: 22px;
    align-self: stretch; pointer-events: none; opacity: 0;
    display: flex; align-items: center; justify-content: center;
    color: var(--pp-text-2); font-size: var(--pp-fs-gross); font-weight: 600; line-height: var(--pp-lh-eng);
  }
  .sb-kopf .sb-tools::before, .sb-kopf .sb-tabs::before {
    left: 0; background: linear-gradient(to left, transparent, var(--pp-surface) 55%);
  }
  .sb-kopf .sb-tools::after, .sb-kopf .sb-tabs::after {
    right: 0; background: linear-gradient(to right, transparent, var(--pp-surface) 55%);
  }
  .sb-kopf .sb-tools::before { margin-right: -22px; }
  .sb-kopf .sb-tools::after { margin-left: -22px; }
  .sb-kopf .sb-tabs::before { margin-right: -32px; }
  .sb-kopf .sb-tabs::after { margin-left: -32px; }
  .sb-kopf .sb-wisch-l::before { content: "‹"; opacity: 1; }
  .sb-kopf .sb-wisch-r::after { content: "›"; opacity: 1; }
  /* sim1016 R9 (A4-11): Im BAND sagt der Hinweis, wie viele Knöpfe noch
     rechts liegen (js/sandbox.js wischStand → data-sb-mehr) — in der
     Akzentfarbe auf deckendem Grund statt eines kleinen grauen Pfeils.
     Breite und negativer Rand wachsen gemeinsam (52 px), damit die Leiste
     nicht um den Hinweis wächst. */
  .sb-kopf .sb-tools.sb-wisch-r[data-sb-mehr]::after {
    content: "+" attr(data-sb-mehr) " ›";
    flex-basis: 52px; width: 52px; margin-left: -52px;
    color: var(--pp-accent-2); font-size: var(--pp-fs-klein); white-space: nowrap;
    background: linear-gradient(to right, transparent, var(--pp-surface) 30%);
  }
  /* Bricht ein Cluster um, sähe der senkrechte Trennstrich aus wie ein
     Rahmen um die zweite Zeile — schmal steht er als Abstand da.
     `max-width: none` und `flex-shrink: 0` nehmen die Deckelung der
     Grundregel zurück: Dort verhindert sie, dass eine Gruppe die SEITE
     in den waagerechten Bildlauf schiebt (23.08.2026, 234 px gemessen)
     — hier ist der Bildlauf gewollt und liegt in der Leiste, und eine
     gedeckelte Gruppe würde stattdessen gestaucht. */
  .sb-grp { border-right: 0; padding: 0 6px; max-width: none; flex: 0 0 auto; }
  /* Auf dem Telefon zählt Breite, nicht die Gestalt der Vorlage: Die
     Spalte der kleinen Einträge ist so breit wie ihr längster Eintrag
     (bis 166 px) und stünde neben einem 60-px-Knopf als schmale Kolonne.
     Schmal fällt sie deshalb in die Reihe zurück — und seit die Leiste
     wischt, ohne Umbruch. */
  /* Und mit der Spalte fällt ihr Raster weg: --sb-raster reserviert die
     Höhe von KLEIN_PRO_SPALTE Zeilen UNTEREINANDER. Liegen die Einträge
     in einer Reihe, reservierte es leeren Platz — auf dem Telefon in der
     Kopfzeile, die dort ohnehin am teuersten ist. Genau hier wäre es der
     Sockel, vor dem CLAUDE.md warnt; deshalb steht er nur oben. */
  .sb-items { gap: 4px; min-height: 0; flex-wrap: nowrap; }
  .sb-gr { flex-wrap: nowrap; }
  .sb-kl { flex-direction: row; flex-wrap: nowrap; }
  /* Und der Gruppenname wandert weg: Er kostet auf dem Telefon eine
     Zeile je Gruppe und sagt neben vier Knöpfen nichts Neues. */
  .sb-gname { display: none; }
  /* Die Aufklapper müssen aus der wischenden Leiste HERAUS.

     Ein Kasten mit `overflow-x: auto` schneidet auch senkrecht ab (die
     andere Achse wird dabei zwangsweise zu `auto`) — das „＋ Karte"-Menü
     und seine drei Geschwister im Reiter „Einfügen" klappen aber nach
     UNTEN auf und wären damit vollständig weggeschnitten. Genau diesen
     Fehler hat die Sandbox schon einmal gehabt (12.08.2026: „Untermenü
     durch overflow weggeschnitten").

     Der Ausweg braucht kein JavaScript: Ein absolut positioniertes
     Element wird von einem Bildlaufkasten nur dann beschnitten, wenn
     dieser in seiner Kette der ENTHALTENDEN BLÖCKE liegt. Nimmt man
     dem Wrap sein `position: relative`, ist der nächste positionierte
     Vorfahr die Kopfzeile — und die scrollt nicht. Das Menü hängt
     seitdem unter der ganzen Kopfzeile und darf über die volle Breite
     gehen; auf dem Telefon ist das ohnehin die bessere Fassung als ein
     170 px schmales Kästchen. */
  .sb-kopf { position: relative; }
  .sb-add-wrap { position: static; }
  .sb-add-menu { top: 100%; left: 8px; right: 8px; min-width: 0; max-height: 60vh; overflow-y: auto; }
  /* Die Leiste ist auf dem Telefon die Auskunft, die sonst nirgends steht
     — sie darf umbrechen statt abzuschneiden. */
  .sb-statusbar { flex-wrap: wrap; white-space: normal; }
}
/* ---- Der Überlauf-Hinweis auch mit der MAUS (sim1016 R9, Nachtrag) ----
   Das Band wischt seit dem 26.09.2026 auf JEDER Breite, der Hinweis stand
   aber nur im Telefon-/Tablet-Block oben. Am Laptop in der Arbeitsumgebung
   (Fläche im 916-px-Bereich) lagen im Reiter „Einfügen" 7 von 22 Knöpfen
   hinter der Kante — ohne Hinweis, und ohne Wischgeste kaum erreichbar.
   Hier dieselben Pseudo-Elemente, aber ANKLICKBAR: Ein Klick auf „+n ›"
   bzw. „‹" rollt das Band eine Seite weiter (js/sandbox.js wischBinden;
   der Klick landet als Ziel auf `.sb-tools` selbst und trifft keinen
   Knopf, weil der Hinweis die Kante deckt). Das Mausrad rollt senkrecht
   gedreht waagerecht. Nur mit feinem Zeiger und über 860 px — darunter
   gilt der Block oben unverändert. */
@media (pointer: fine) and (min-width: 861px) {
  .sb-kopf .sb-tools::before, .sb-kopf .sb-tools::after {
    content: ""; position: sticky; z-index: 1; flex: 0 0 22px; width: 22px;
    align-self: stretch; pointer-events: none; opacity: 0;
    display: flex; align-items: center; justify-content: center;
    color: var(--pp-text-2); font-size: var(--pp-fs-gross); font-weight: 600; line-height: var(--pp-lh-eng);
  }
  .sb-kopf .sb-tools::before { left: 0; margin-right: -22px; background: linear-gradient(to left, transparent, var(--pp-surface) 55%); }
  .sb-kopf .sb-tools::after { right: 0; margin-left: -22px; background: linear-gradient(to right, transparent, var(--pp-surface) 55%); }
  .sb-kopf .sb-tools.sb-wisch-l::before { content: "‹"; opacity: 1; pointer-events: auto; cursor: pointer; }
  .sb-kopf .sb-tools.sb-wisch-r::after { content: "›"; opacity: 1; pointer-events: auto; cursor: pointer; }
  .sb-kopf .sb-tools.sb-wisch-r[data-sb-mehr]::after {
    content: "+" attr(data-sb-mehr) " ›";
    flex-basis: 52px; width: 52px; margin-left: -52px;
    color: var(--pp-accent-2); font-size: var(--pp-fs-klein); white-space: nowrap;
    background: linear-gradient(to right, transparent, var(--pp-surface) 30%);
  }
}
/* Das Tablet quer hat die BREITE, die das Telefon nicht hat (web1014
   A1-21): Namenszeile (Zurück, Name, Kurs, Speicherpunkt ~ 600 px) und die
   fünf Reiter (~ 260 px) passen bei 1112 px nebeneinander — zwei Zeilen
   zu 44 + 48 px werden eine. Nur auf der ganzen Seite (`sb-top-seite`,
   also nicht eingebettet, nicht in der Werkbank, nicht auf der Start-
   Fläche, deren Namenszeile ohnehin fehlt). Das Band bleibt die volle
   Breite darunter. Unter 861 px (iPad hoch, Telefon) bleibt es bei zwei
   Zeilen — dort reicht die Breite nicht. */
@media (pointer: coarse) and (min-width: 861px) and (max-width: 1200px) {
  .sb-kopf:has(> .sb-top-seite) { flex-direction: row; flex-wrap: wrap; align-items: stretch; column-gap: 14px; }
  .sb-kopf:has(> .sb-top-seite) > .sb-top-seite { flex: 1 1 0; min-width: 0; }
  .sb-kopf:has(> .sb-top-seite) > .sb-tabs { flex: none; align-self: flex-end; }
  .sb-kopf:has(> .sb-top-seite) > .sb-panel { flex: 0 0 100%; }
}

/* ---------------- Werkbank (15.08.2026, docs/SANDBOX.md 3k) ----------------
   Die Fläche als Overlay über dem Klausur-Editor — dasselbe Muster wie das
   Schaltlabor (.lab, z 120). modal-bg als Basisklasse, damit keynav
   pausiert (es fragt am DOM nach .modal-bg); zwei Klassen gegen die
   Zentrier-Regel von .modal-bg (Fallstrick 4). Randlos: Die Seite füllt
   das Fenster, nicht den Inhaltsbereich unter der Portal-Kopfzeile —
   deshalb 100vh statt der 54-px-Rechnung von .sb-randlos. */
.modal-bg.sb-werkbank-bg {
  display: block; padding: 0; z-index: 120;
  background: var(--pp-bg, #f4f5f7); backdrop-filter: none; animation: none;
}
.sb-werkbank-bg .pp-page.sb-page.sb-randlos { height: 100vh; }
.sb-werkbank-bg .sb-wb-name {
  font-weight: 600; max-width: 34vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Nur lesen (Notizen teilen A2, 02.10.2026): eine geteilte Fläche ohne
   Reiterband — es gibt nichts zu ändern, also auch keine Knöpfe dafür.
   Kopf bleibt; der Kopierknopf rückt an den rechten Rand. */
.sb-nurlesen .sb-tabs, .sb-nurlesen .sb-kopf .sb-panel { display: none; }
.sb-nurlesen .sb-top #sb-wb-ok { margin-left: auto; }
.sb-nurlesen .sb-top .pp-btn { min-height: 44px; }
.sb-nurlesen .sb-top .sb-status { color: var(--pp-text-2); }

/* ---------------- Eingebettet (06.09.2026, docs/NOTIZ-ARBEITSFLAECHE-2026-09-06.md) ----
   Dieselbe randlose Fläche, nur in einem angedockten Fenster neben einer
   Aufgabe statt im Inhaltsbereich der Seite. Geändert wird deshalb
   GENAU EINE Sache: die Höhe. `.sb-randlos` rechnet sie gegen das
   BROWSERFENSTER (100vh − 54 px Portal-Kopfzeile) — im Fenster ist das
   die falsche Bezugsgröße, dort gibt der Fensterkörper die Höhe vor
   (.ppf-koerper, css/prepp.css). Alles Übrige (Kopfleiste, Reiterband,
   Statusleiste, die drei Ebenen der Fläche) bleibt zeichengleich.

   Zwei Klassen (.sb-randlos.sb-eingebettet), weil die Höhenregel darüber
   ebenfalls zwei trägt (Fallstrick 4). */
.pp-page.sb-page.sb-randlos.sb-eingebettet { height: 100%; min-height: 0; }
/* Im Fenster ist Platz knapp: Die Fläche darf bis auf ihre eigene
   Mindesthöhe schrumpfen, statt ihre 420 px gegen den Fensterkörper
   durchzusetzen und ihn zu sprengen. */
.sb-eingebettet .sb-canvas { min-height: 160px; }

/* ---------------- Geo-Karte (Paket P7, 04.09.2026, docs/GEO.md) ----------------
   Bild auf weißem Papier wie plot und circuit (.sb-papier), darunter die
   Regler wie bei der plot-Karte. Die Fensterklassen (.sb-geo-fenster …)
   stehen im Leib des P1-Seitenfensters (.sb-sf-editor); die Regler
   wandern bei offenem Fenster in dessen Bedienteil (.sb-sf-bedien) —
   dasselbe Markup, derselbe Binder, nur ein anderer Wirt. */
.sb-geo { display: flex; flex-direction: column; gap: 4px; height: 100%; }
/* Der Titel der Karte (C34, 06.09.2026): beim .ggb-Import der
   Dateiname. EINE Zeile, nie zwei — die Höhe ist in geo.js als
   titelHoehe() eingerechnet (12,5 × 1,35 + 4 px Reihenabstand ≈ 21), und ein
   Umbruch schöbe das Bild aus der Karte. */
.sb-geo-titel {
  flex: none; font-size: var(--pp-fs-klein); font-weight: 600; color: var(--pp-text);
  line-height: var(--pp-lh-knapp); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sb-geo-bild { flex: 1; min-height: 0; overflow: hidden; }
.sb-geo-bild svg { width: 100%; height: 100%; display: block; }
/* ---- Grafik 2 (Stufe 2, 23.09.2026, docs/GEO-ZWEI-ANSICHTEN-2026-09-23.md) ----
   Zwei Ansichten derselben Karte in EINEM Bildkasten: je Ansicht ein
   eigener Kasten mit eigenem Rahmen (Papier bzw. Tafel — zwei Blätter,
   keine geteilte Fläche, sonst läsen sich zwei Koordinatensysteme wie
   eines), nebeneinander zu gleichen Teilen; unter 420 px Kartenbreite
   untereinander. Die Klasse sb-geo-unter setzt js/geo.js aus DERSELBEN
   Rechnung, mit der es die Maße jeder Ansicht bestimmt (zweiLage).
   Die Lücke `gap: 6px` ist ZWEI_LUECKE in js/geo.js, und
   verify-geo-zwei-ansichten.js vergleicht beide Zahlen: Rechnete die
   Karte mit einer anderen Lücke, als die Seite malt, stimmte das
   Seitenverhältnis keiner der beiden Ansichten mehr.
   `flex: 1 1 0` mit `min-width`/`min-height: 0` teilt den Platz exakt
   hälftig — das SVG darin (width/height 100 %) kann keinen Kasten über
   seinen Anteil aufziehen (Fallstrick 41). Das gilt auf der Fläche und
   im Vollbild gleich; im Vollbild ist das die feste 50/50-Teilung der
   linken Hälfte (ein Teiler zwischen den Ansichten ist ein eigener
   Schritt, docs 7). */
.sb-geo-bild.sb-geo-zwei { display: flex; gap: 6px; }
.sb-geo-bild.sb-geo-zwei.sb-geo-unter { flex-direction: column; }
.sb-geo-g { flex: 1 1 0; min-width: 0; min-height: 0; overflow: hidden; }
.sb-geo-reglerzeile { display: flex; flex-direction: column; gap: 2px; flex: none; }
.sb-geo-regler { display: flex; align-items: center; gap: 6px; font-size: var(--pp-fs-klein); }
.sb-geo-regler input[type="range"] { flex: 1; min-width: 0; }
.sb-geo-rname { flex: none; font-family: var(--pp-mono, monospace); font-size: var(--pp-fs-mini); }
.sb-geo-rwert {
  width: 64px; font: inherit; font-size: var(--pp-fs-mini); padding: 1px 4px; border-radius: 4px;
  border: 1px solid var(--pp-line-2); background: var(--pp-bg); color: var(--pp-text);
}
.sb-geo-rwert:focus { border-color: var(--pp-accent-line); }
/* ---- Animation der Regler (07.09.2026, docs/GEO-ANIMATION-2026-09-07.md) ----
   Der ▶/⏸-Knopf steht an JEDEM Regler, in der Karte wie im Fenster —
   dasselbe Markup, ein Bauer (reglerHtml). Der EIN-Zustand ist eine
   FORM (das Zeichen wechselt) plus Farbe, nie Farbe allein: Die
   Sandbox-Bandregel gilt hier genauso (CLAUDE.md, „Der EIN-Zustand ist
   eine FORM, keine Farbe"). Die Trefferfläche ist 24 px hoch und damit
   so groß wie die Reglerzeile selbst — größer ginge nur auf Kosten des
   Schiebers, und der ist das Hauptbedienelement. */
/* Zentriert wird mit FLEX, nicht mit einer eigenen Zeilenhöhe: Die vier
   Stufen --pp-lh-* sind die einzigen erlaubten Zeilenhöhen des Portals
   (verify-skala.js 3c), und ein Kasten von 22 px braucht ohnehin keine.
   inline-flex ist hier unbedenklich — der Knopf trägt EIN Zeichen, es
   gibt keinen Zwischenraum, den es verschlucken könnte (die Falle von
   R3 traf einen Knopf mit Symbol UND Wort). */
.sb-geo-rplay {
  flex: none; width: 24px; height: 22px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--pp-line-2); border-radius: 5px;
  background: var(--pp-bg); color: var(--pp-text-2);
  font: inherit; font-size: var(--pp-fs-mini); cursor: pointer;
}
.sb-geo-rplay:hover { color: var(--pp-accent-2); border-color: var(--pp-accent-line); }
.sb-geo-rplay[aria-pressed="true"] { color: var(--pp-accent-2); border-color: var(--pp-accent-line); box-shadow: inset 2px 0 0 currentColor; }
/* Tempo und Art stehen nur im Seitenfenster (die Karte hat den Platz
   nicht) — eine eigene Zeile UNTER dem Regler, damit die Reglerzeile
   ihre Breite behält. */
.sb-geo-ranim { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
  font-size: var(--pp-fs-mini); color: var(--pp-text-2); margin: 0 0 4px 0; padding-left: 6px; }
.sb-geo-ranim label { display: inline-flex; align-items: center; gap: 4px; margin: 0; }
.sb-geo-ranim input[type="number"] { width: 62px; font: inherit; font-size: var(--pp-fs-mini); }
.sb-geo-ranim select { font: inherit; font-size: var(--pp-fs-mini); }
/* Achsenteilung von Hand: Schritt (Text — „pi/2" ist ein Ausdruck) und
   Schreibweise der Marken, je Achse eine Zeile. */
.sb-geo-tick { display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  font-size: var(--pp-fs-klein); margin: 3px 0; }
.sb-geo-tick input[type="text"] { width: 84px; font: inherit; font-size: var(--pp-fs-klein); }
.sb-geo-tick select { font: inherit; font-size: var(--pp-fs-klein); }
.sb-geo-schalter { font-size: var(--pp-fs-klein); display: flex; align-items: center; gap: 6px; }
/* Im dunklen Theme verlässt die Karte das weiße Blatt (S11, Jonas'
   Entscheidung vom 06.09.2026): Die Zeichnung ist seit dem Farbsatz
   themefähig, ein weißes Papier darunter wäre nur noch ein greller Rand.
   Die Fläche selbst malt das SVG (satz.flaeche) — hier steht nur der
   Rahmen, damit die Karte ihre Kante behält. */
.sb-geo-tafel {
  border: 1px solid var(--pp-line); border-radius: 8px; padding: 4px; box-sizing: border-box;
}
/* Den gesetzten Textblock UNTER dem Graphen (S11) gibt es seit dem
   30.09.2026 nicht mehr (Jonas' Auftrag): Gleichungen und Texte stehen
   auf Wunsch als ETIKETT im Graphen (js/geo.js svg2d, „Etiketten") und
   lassen sich dort ziehen. Das Etikett ist ein SVG-<text> mit Halo; der
   Zeiger sagt, dass man es greifen kann, während des Zugs die
   geschlossene Hand. docs/GEO-GLEICHUNG-REGLER-2026-09-30.md. */
.sb-geo [data-geo-etikett] { cursor: move; }
.sb-geo-zieht .sb-geo-bild, .sb-geo-zieht [data-geo-etikett] { cursor: grabbing; }
.sb-geo-hinweis { font-size: var(--pp-fs-mini); color: var(--pp-text-3); flex: none; }
/* Der Grund, warum eine plot-Karte plot BLEIBT (S11) — eine Zeile in der
   Karte, damit der Unterschied zur Graph-Karte nicht geraten wird. */
.sb-plot-alt { color: var(--pp-text-2); }
/* Im Leib des Seitenfensters: das Polster bringt .sb-sf-editor mit
   (0 12px), hier nur oben/unten — sonst stünde es doppelt. Die Zeilen
   scrollen IM Editorbereich, der Bedienteil darüber bleibt stehen. */
.sb-geo-fenster { display: flex; flex-direction: column; gap: 8px; padding: 8px 0; overflow: auto; flex: 1 1 auto; min-height: 0; }
/* Die kleinen Bedienfelder des Fensters tragen die Portal-Tokens —
   EINE Regel für alle (07.09.2026, WebKit-Abnahme der Animation,
   docs/abnahme/geo-animation/BERICHT.md Befund B2). Vorher setzten
   .sb-geo-ranim, .sb-geo-tick und .sb-geo-bereiche je nur `width` und
   `font`: Der Browser malte sie damit in seiner Vorgabe, also WEISS auf
   SCHWARZ — im dunklen Vollbild fünf grelle Kästen zwischen lauter
   dunklen Feldern (gemessen rgb(255,255,255) gegen den Panelgrund
   rgb(22,27,34), während .sb-geo-rwert und .sb-geo-eingabe daneben
   korrekt auf rgb(13,17,23) standen). Lesbar war es (21 : 1), stimmig
   nicht — und die zwei Felder, die es richtig machten, standen in
   derselben Spalte.
   Die Werte sind wörtlich die von .sb-geo-eingabe; ein eigener Ton wäre
   die dritte Fassung derselben Sache. `select` bekommt zusätzlich
   `color-scheme`, sonst malt WebKit die aufgeklappte Optionsliste
   weiterhin hell. Ankreuz- und Radiofelder bleiben ausdrücklich außen
   vor: Sie tragen keine Fläche, die zum Theme passen müsste.
   Genannt werden ZWEI Wirte: `.sb-geo-fenster` (Achsenteilung,
   Bereiche) und `.sb-geo-ranim` (Tempo, Art). Die Animationszeile ist
   KEIN Kind des Fensters — sie kommt aus `bedienung(n)` und steht im
   Vollbild als Geschwister darüber; eine Regel nur am Fenster ließ
   genau die zwei Felder weiß stehen (beim Nachmessen aufgefallen). */
.sb-geo-fenster input[type="text"],
.sb-geo-fenster input[type="number"],
.sb-geo-fenster select,
.sb-geo-ranim input[type="number"],
.sb-geo-ranim select {
  border: 1px solid var(--pp-line-feld); border-radius: 5px;
  background: var(--pp-bg); color: var(--pp-text);
}
.sb-geo-fenster select, .sb-geo-ranim select { color-scheme: inherit; }
.sb-geo-fenster input[type="text"]:focus,
.sb-geo-fenster input[type="number"]:focus,
.sb-geo-fenster select:focus,
.sb-geo-ranim input[type="number"]:focus,
.sb-geo-ranim select:focus { border-color: var(--pp-accent-line); }
/* Gesperrt heißt SICHTBAR gesperrt (das Schritt-Feld bei logarithmischer
   x-Achse): Ohne eine eigene Regel bliebe es nach dem Umfärben oben von
   einem offenen Feld nicht mehr zu unterscheiden — die Browser-Vorgabe
   für :disabled greift nur auf der Vorgabefläche. */
.sb-geo-fenster input:disabled,
.sb-geo-fenster select:disabled {
  background: var(--pp-bg-2); color: var(--pp-text-3); cursor: not-allowed;
}
/* Die Regler im Bedienteil: dieselbe Zeile wie in der Karte, ohne die
   Kartengrenzen — die Reglerspur nutzt die Fensterbreite. */
.sb-sf-bedien .sb-geo-regler,
.sb-sf-bedien .sb-geo-schalter { margin: 0; }
.sb-geo-ansichten { display: flex; flex-wrap: wrap; gap: 4px; }
.sb-geo-ansicht {
  font-size: var(--pp-fs-klein); padding: 3px 8px; border: 1px solid var(--pp-line); border-radius: 6px; cursor: pointer;
  color: var(--pp-text-2);
}
.sb-geo-ansicht.on { border-color: var(--pp-accent-line); color: var(--pp-accent-2); box-shadow: inset 3px 0 0 currentColor; }
.sb-geo-ansicht input { display: none; }
.sb-geo-werkzeuge { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.sb-geo-werkzeuge button {
  font: inherit; font-size: var(--pp-fs-klein); padding: 3px 8px; border: 1px solid var(--pp-line); border-radius: 6px;
  background: var(--pp-bg); color: var(--pp-text); cursor: pointer;
}
/* EIN-Zustand als FORM (Balken), nicht nur als Farbe — die Regel des Bandes. */
.sb-geo-werkzeuge button[aria-pressed="true"] { box-shadow: inset 3px 0 0 currentColor; color: var(--pp-accent-2); }
.sb-geo-wzhinweis { font-size: var(--pp-fs-mini); color: var(--pp-text-3); }
.sb-geo-zeilen { display: flex; flex-direction: column; }
.sb-geo-zeile { border-bottom: 1px solid var(--pp-line); padding: 4px 0; }
.sb-geo-zkopf { display: flex; align-items: center; gap: 4px; }
.sb-geo-eingabe {
  flex: 1; min-width: 0; font: inherit; font-size: var(--pp-fs-klein); font-family: var(--pp-mono, monospace);
  padding: 4px 6px; border: 1px solid var(--pp-line-feld); border-radius: 5px; background: var(--pp-bg); color: var(--pp-text);
}
.sb-geo-eingabe:focus { border-color: var(--pp-accent-line); }
.sb-geo-farbe { width: 22px; height: 22px; padding: 0; border: none; background: none; cursor: pointer; flex: none; }
.sb-geo-sicht { flex: none; }
/* Der Halter des Häkchens (Politur 03.10.2026): mit der Maus so groß wie
   das Kästchen samt seinem Rand — zeichengleich wie vorher, als das
   Kästchen selbst das Flex-Kind war. Auf dem Finger wächst er (unten). */
.sb-geo-sichtfl { flex: none; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
.sb-geo-del { border: none; background: none; color: var(--pp-text-3); cursor: pointer; font-size: var(--pp-fs-klein); padding: 2px 4px; }
.sb-geo-del:hover { color: var(--pp-err); }
/* „Im Graphen zeigen" je Zeile (30.09.2026). Der EIN-Zustand ist eine
   FORM — ein Balken links und eine getönte Fläche —, nie Farbe allein
   (die Regel des Sandbox-Bandes, CLAUDE.md „Der EIN-Zustand ist eine
   FORM"). Gesperrt steht er da, statt zu verschwinden: Sonst spränge
   das Eingabefeld beim Tippen um seine Breite. */
.sb-geo-imgraph {
  flex: none; width: 24px; height: 22px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--pp-line-2); border-radius: 5px;
  background: var(--pp-bg); color: var(--pp-text-2); cursor: pointer;
}
.sb-geo-imgraph:hover:not(:disabled) { color: var(--pp-accent-2); border-color: var(--pp-accent-line); }
.sb-geo-imgraph[aria-pressed="true"] {
  color: var(--pp-accent-2); border-color: var(--pp-accent-line);
  background: var(--pp-accent-tint); box-shadow: inset 3px 0 0 currentColor;
}
.sb-geo-imgraph:disabled { color: var(--pp-text-3); cursor: not-allowed; opacity: 0.55; }
.sb-geo-imgraph svg { display: block; }
/* Die Regler UNTER der Zeile, die sie benutzt — nur im Vollbild (30.09.2026).
   Eingerückt wie die Definition darüber (48 px: Sichtbarkeit + Farbe),
   damit man sieht, zu welcher Zeile sie gehören. Leer nimmt der Kasten
   keinen Platz. */
.sb-geo-zregler { display: flex; flex-direction: column; gap: 2px; padding: 2px 0 0 48px; }
.sb-geo-zregler:empty { display: none; }
.sb-geo-zregler .sb-geo-regler { margin: 0; }
/* Unter der Eingabe: links die GANZE Definition gesetzt, rechts der
   Wert (S11). Zwei Knoten, zwei Fragen — „was steht da?" und „was kommt
   heraus?"; sie stehen nebeneinander und brechen erst um, wenn es eng
   wird. */
.sb-geo-defzeile {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; padding-left: 48px;
}
/* Der Kasten der Definition (Befund B4, 06.09.2026): Er DARF schrumpfen
   (min-width: 0 — ohne das frisst er als Flex-Kind den Platz seiner
   Geschwister, Fallstrick 41), und was dann noch übersteht, rollt IN ihm,
   statt die Spalte zu weiten.

   Das Rollen ist hier das NETZ, nicht der Weg. Umgebrochen wird, wo die
   Zeile es zulässt: Eine Formel bricht zwischen ihren katex-base-Kästen
   von selbst um (KaTeX macht je Relation und Rechenzeichen einen neuen),
   und ein Prosa-Argument aus Text("…") bekommt seine Bruchstellen seit B4
   in js/geo.js (texProsa) — vorher war es EIN nowrap-Kasten mit &nbsp;
   statt Leerzeichen, also 513 px Satz in einer 259 px breiten Spalte, am
   Rand abgeschnitten. Fürs Rollen bleibt, was wirklich nicht brechen
   kann: eine breite Matrix, ein langer Bruch, ein einzelnes sehr langes
   Wort. Die Regel steht bewusst UNGESCHACHTELT — sie gilt im
   Seitenfenster am Flächenrand wie in der rechten Hälfte des Vollbilds
   (derselbe Hook, TYPEN.geo.seitenfenster). */
.sb-geo-def { font-size: var(--pp-fs-klein); color: var(--pp-text); min-width: 0; overflow-x: auto; }
.sb-geo-def:empty { display: none; }
/* Der Wert ist BESCHREIBUNG, keine Eingabe: Portal-Schrift wie die
   Definition daneben (06.09.2026). In Monospace las sich „T = Steigung m
   in A" wie Quelltext, und seit derselben Runde wird er gesetzt — ein
   Text-Objekt traegt seinen eigenen Wortlaut samt Formel. Mono bleibt,
   wo man TIPPT (.sb-geo-eingabe) und beim Reglernamen: der ist der
   Bezeichner, der in genau diesem Feld steht. */
.sb-geo-wert { font-size: var(--pp-fs-klein); color: var(--pp-text-2); }
.sb-geo-fehler { font-size: var(--pp-fs-mini); color: var(--pp-err); padding-left: 48px; }
.sb-geo-fehler:empty, .sb-geo-wert:empty { display: none; }
.sb-geo-defzeile:empty { display: none; }
.sb-geo-fuss { display: flex; gap: 6px; }
.sb-geo-add { font: inherit; font-size: var(--pp-fs-klein); border: 1px dashed var(--pp-line-2); border-radius: 6px; background: none; color: var(--pp-text-2); cursor: pointer; padding: 3px 8px; }
.sb-geo-add:hover { color: var(--pp-accent-2); border-color: var(--pp-accent-line); }
.sb-geo-einst summary, .sb-geo-hilfe summary { font-size: var(--pp-fs-klein); cursor: pointer; color: var(--pp-text-2); }
.sb-geo-bereiche label { display: block; font-size: var(--pp-fs-klein); margin: 3px 0; }
.sb-geo-bereiche input[type="number"] { width: 64px; font: inherit; font-size: var(--pp-fs-klein); }
.sb-geo-bereiche input[type="text"] { width: 120px; font: inherit; font-size: var(--pp-fs-klein); }
/* Bode (06.09.2026): welche Objekte auf der rechten Achse liegen. Eine
   Reihe, die UMBRICHT — bei sechs Funktionen liefe sie sonst aus dem
   420 px breiten Fenster. Die Kästchen stehen als eigene Zeile unter
   den Achsenangaben, nicht je Algebra-Zeile: Ein drittes Bedienelement
   im Zeilenkopf nähme der Eingabe daneben ihren Platz. */
.sb-geo-achswahl { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center;
  font-size: var(--pp-fs-klein); color: var(--pp-text-2); margin: 3px 0; }
.sb-geo-achswahl label { display: inline-flex; align-items: center; gap: 3px; margin: 0;
  font-family: var(--pp-mono, monospace); color: var(--pp-text); }
/* Grafik 2 im Seitenfenster (23.09.2026): das Häkchen „Grafik 2
   anzeigen", darunter die Zuordnung je Objekt — dieselbe Bauart wie
   die Achsenwahl der Bode-Einstellungen direkt darüber: eine Reihe, die
   UMBRICHT, unter der Zeilenliste statt je Algebra-Zeile (ein drittes
   Bedienelement im Zeilenkopf nähme der Eingabe ihren Platz). Der Name
   des Objekts steht in Mono wie dort — er ist der Bezeichner aus der
   Zeile; die Auswahl daneben ist Beschreibung und steht in der
   Portalschrift. Die Marke „(Grafik 2 aus)" steht in der Wertezeile des
   Objekts; leer verschwindet sie ganz. */
.sb-geo-g2 { display: flex; flex-direction: column; gap: 4px; }
.sb-geo-g2an { display: inline-flex; align-items: center; gap: 6px; margin: 0;
  font-size: var(--pp-fs-klein); color: var(--pp-text); cursor: pointer; }
.sb-geo-grafikwahl { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center;
  font-size: var(--pp-fs-klein); color: var(--pp-text-2); }
.sb-geo-grafikwahl label { display: inline-flex; align-items: center; gap: 4px; margin: 0;
  font-family: var(--pp-mono, monospace); color: var(--pp-text); }
.sb-geo-grafikwahl select { font: inherit; font-family: var(--pp-sans, sans-serif); }
.sb-geo-g2aus { font-size: var(--pp-fs-mini); color: var(--pp-text-2); }
.sb-geo-g2aus:empty { display: none; }
.sb-geo-ausgabe { display: block; font-size: var(--pp-fs-klein); margin-top: 6px; }
.sb-geo-hilfetext { font-size: var(--pp-fs-mini); color: var(--pp-text-2); line-height: var(--pp-lh-weit); }
.sb-geo-hilfetext code { font-size: var(--pp-fs-mini); }
/* Zellen, die die Tabellenkopplung schreibt (docs/GEO.md 8) */
.sb-tab .sb-tab-auto { background: var(--pp-accent-tint); }

/* ---------------- Reiterband klappt zu (nur mit `.pp-anim`) ----------------
   Nach drei Sekunden Nichtbenutzung fährt das Panel unter der Reiterzeile
   sanft zu, bis nur noch die Reiternamen stehen; ein Klick auf einen
   Reiter fährt es wieder auf. Die Mechanik steht in js/anim.js
   (PreppAnim.bandZuklapp), die Klasse setzt sie an `.sb-kopf`.

   DAS ZEILENRASTER DARÜBER BLEIBT UNANGETASTET. `--sb-zeile`,
   `--sb-lueft`, `--sb-zeilen` und das daraus gerechnete `--sb-raster` an
   `.sb-items` gelten unverändert — hier steht nur, was im ZUGEKLAPPTEN
   Zustand gilt, und der existiert ohne den Schalter nicht.
   verify-sandbox-bandhoehe.js misst deshalb weiter dasselbe.

   `box-sizing: border-box` ist eine ANGESCHRIEBENE VORAUSSETZUNG, keine
   Korrektur: js/anim.js schreibt die gemessene Höhe in Bildpunkten ins
   Element (`height: auto` lässt sich nicht interpolieren) und rechnet
   dabei mit `offsetHeight`, also einschließlich Polster. In WebKit
   nachgemessen liegt border-box hier ohnehin schon an (die Zeile ändert
   die Ruhehöhen um 0 px — alle fünf Reiter bleiben bei 99 px). Sie steht
   trotzdem da, damit ein späterer Griff an die globale Vorgabe die
   Höhenrechnung nicht stumm um die 5 px des `padding-top` verschiebt.

   `visibility` steht mit in der Übergangsliste, damit die Knöpfe erst am
   ENDE verschwinden — sonst wären sie im ersten Bild weg und man sähe
   einen leeren Streifen zufahren statt des Bandes. Zugleich nimmt es sie
   der Tastaturnavigation (dasselbe Argument wie beim keynav-Vertrag der
   Leisten in prepp.css). */
/* `overflow-y`, NICHT `overflow` (Befund B60, 06.09.2026 — R3).
   Die Zuklapp-Mechanik beschneidet SENKRECHT; die waagerechte Achse
   gehört ihr nicht. Als Kurzform `overflow: hidden` setzte sie beide
   Längsformen — und weil `.pp-anim .sb-kopf .sb-panel` drei Klassen
   trägt, schlug sie die Telefonregel `.sb-tools { overflow-x: auto }`
   eine Klasse tiefer (eine Medienabfrage erhöht die Spezifität NICHT).
   Gemessen in WebKit bei 390 px: Panel 362 px breit, Inhalt 1337 px,
   `overflow-x: hidden` — 975 px der Werkzeugreihe waren mit keiner
   Geste erreichbar, und Animationen sind seit dem 27.08.2026 Standard
   AN. Seit dem 26.09.2026 wischt das Band auf JEDER Breite
   (`.sb-tools` oben) — die Regel trägt damit auch am Laptop; nachgemessen
   am 02.10.2026 mit dem Klickweg (docs/abnahme/band-klick-0930/). */
.pp-anim .sb-kopf .sb-panel {
  box-sizing: border-box; overflow-y: hidden;
  transition: height .22s ease, padding-top .22s ease,
              opacity .16s ease, visibility .22s;
}
.pp-anim .sb-kopf.pp-band-zu .sb-panel {
  height: 0; padding-top: 0; opacity: 0;
  visibility: hidden; pointer-events: none;
}
/* OHNE den Schalter (30.09.2026, docs/BAND-KLICK-ZU-2026-09-30.md): Der
   Tipp auf die Zeichenfläche klappt das Band trotzdem zu — das ist
   Funktion, kein Zierrat —, nur ohne Übergang. `display: none` statt der
   Höhe 0, weil hier nichts fährt; der Selektor schließt `.pp-anim`
   ausdrücklich aus, sonst nähme `display: none` dem Übergang oben sein
   erstes Bild. Ohne Tipp trägt der Wirt die Klasse nie: Der Ruhezustand
   bleibt pixelgleich mit dem Stand vor dem Klickweg. */
body:not(.pp-anim) .sb-kopf.pp-band-zu .sb-panel { display: none; }

/* ---------------- Seitenfenster: Leib und Bedienteil (A4, 03.09.2026) ----------------
   Der Rahmen ist derselbe wie beim Textfenster (.sb-editfenster oben) —
   neu ist nur, dass zwischen Kopf und Fuss ein LEIB steht, in den jeder
   Kartentyp seinen Editor rendert. Er scrollt, das Fenster nicht: Zu
   langer Inhalt bekommt eine Bildlaufleiste, nie eine neue
   Fenstergroesse (dieselbe Regel wie bei den alten Bedienboxen).

   Bis zum 27.09.2026 war das <textarea> der Textkarte der eine
   Leib-Inhalt ohne Polster; seitdem schreibt die Textkarte IM Block. */
.sb-editfenster-leib {
  flex: 1 1 auto; min-height: 0; overflow: auto;
  display: flex; flex-direction: column;
}
.sb-editfenster-leib > .sb-sf-bedien,
.sb-editfenster-leib > .sb-sf-editor { padding: 0 12px; }
.sb-editfenster-leib > .sb-sf-bedien { padding-top: 10px; }
.sb-editfenster-leib > .sb-sf-editor { padding-bottom: 10px; }
/* Der Editor bekommt die Resthoehe; die Bedienelemente (Regler der
   plot-Karte) stehen darueber und behalten ihren Platz. */
.sb-sf-editor { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.sb-sf-bedien { flex: none; display: flex; flex-direction: column; gap: 4px; }
/* Ein Typ ohne Regler (plot ohne params, geo ohne Regler-Zeile) meldet
   einen LEEREN Bedienteil an — der trüge sonst sein Polster als leeren
   Streifen über dem Editor. */
.sb-editfenster-leib > .sb-sf-bedien:empty { display: none; }
.sb-sf-bedien .sb-plot-param { margin: 0; }
/* Im Fenster hat die Box keine Karte mehr, an der sie sich festhaelt —
   sie fuellt den Editorbereich. */
.sb-sf-editor > .sb-box { flex: 1 1 auto; min-height: 0; }
/* Der Quelltext-Editor der html-Karte (P10a, 04.09.2026): dieselbe Box,
   nur mit etwas Luft zwischen Titelfeld, Quelltext und Fuss — sie hat
   im Fenster die ganze Hoehe und nicht mehr die enge Karte. Das
   <textarea> nimmt den Rest (.sb-box > textarea, oben); Titelfeld,
   Fehlerzeile und Fuss sind flex: none und behalten ihren Platz. */
.sb-htmlbox { gap: 6px; }
/* Das Textfeld einer Box IM FENSTER — gefunden bei der WebKit-Abnahme
   des html-Editors (P10a), es traf den JSON-Editor aber schon seit dem
   03.09.2026 mit: Die Regel, die einem Editorfeld sein Aussehen gibt,
   heisst `.sb-karte textarea` und ist damit an die KARTE gebunden. Seit
   P1 A4 wohnen die Editoren im Fenster, und dort griff sie nicht mehr —
   gemessen im dunklen Theme: Grund #fff, Schrift #000, Rand schwarz
   (in der Karte --pp-bg / --pp-text). Ein weisser Kasten im dunklen
   Fenster.
   KEINE Schriftangabe in dieser Regel — die steht eine Regel tiefer und
   nur fuer das Quelltextfeld. */
.sb-editfenster .sb-box textarea {
  box-sizing: border-box; resize: none;
  color: var(--pp-text, #1f2328); background: var(--pp-bg, #f6f8fa);
  border: 1px solid var(--pp-line-2, #c3ccd4); border-radius: 6px; padding: 6px;
}
.sb-editfenster .sb-box textarea:focus {
  border-color: var(--pp-accent-line, #b6d3fa);
}
/* Quelltext in der Schrift, in der man Quelltext liest. `.sb-json`
   bittet seit jeher um --pp-mono — und bekommt sie nirgends: Im Portal
   steht `.pp-root input, .pp-root button, .pp-root select, .pp-root
   textarea { font: inherit }` (css/prepp.css), und (0,1,1) schlaegt
   (0,1,0). Im Browser nachgemessen (P10a): angefordert die Mono-Kette,
   gemalt "IBM Plex Sans"; derselbe Wert als Inline-Stil wirkt sofort —
   es lag also nie am Wert der Variablen, sondern an der Spezifitaet.
   Geheilt wird NUR das Quelltextfeld im Fenster (html-Quelltext und
   JSON), nicht die Portalregel: Die gilt fuer jedes Eingabefeld der
   Seite, und ein Kartentyp hat sie nicht umzustossen. */
.sb-editfenster .sb-box textarea.sb-json {
  font-family: var(--pp-mono, monospace);
}

/* ---------------- Vollbild der Graph-Karte (06.09.2026, Jonas) ----------------
   Der Doppelklick auf eine `geo`-Karte oeffnet ein Overlay ueber der
   ganzen Seite: links der Graph, rechts die Beschreibung (derselbe
   Editor wie im Seitenfenster, dieselben Klassen .sb-sf-bedien /
   .sb-sf-editor), dazwischen ein ziehbarer Teiler. Muster ist das
   Labor-Overlay: ein Knoten am body, ueber allem, mit eigener Flaeche.

   Die z-Ebene liegt ueber dem Seitenfenster (59), dem Rechtsklickmenue
   (60) und der Flaechenmeldung (70) — die gehoeren der Flaeche darunter
   und haben IM Vollbild nichts zu suchen —, aber unter den
   Portal-Dialogen.

   JEDES Token traegt hier einen Fallback (die Regel aus
   verify-professor-leisten.js: `--pp-bg-1` gab es NIE, und ein var()
   ohne Fallback heisst dann „nicht gesetzt", also durchsichtig). Der
   Block steht am Dateiende, und verify-sandbox-telefon.js prueft die
   Regel ab dem Telefon-Hinweis bis dorthin — er hat sie beim Bauen
   dieses Blocks prompt gemeldet. */
/* ---- DER KNOTEN HAENGT AM body UND ERBT DESHALB NICHTS VOM PORTAL ----
   (06.09.2026, Jonas: „Schrift im rechten Fenster wie im Rest von PrepP".)
   `.sb-geovoll` steht ausserhalb von `.pp-root` — das MUSS so sein, sonst
   risse `shell()` das Vollbild bei jeder Navigation weg. Der Preis ist
   derselbe wie beim Chat-Wirt (#kf-wirt, css/ki-fenster.css): Alles
   Vererbte kommt vom `body`, und den beschreibt css/exam.css (Segoe UI,
   14,5 px) — nicht das Portal. Die Beschreibung stand deshalb in einer
   anderen Schrift als jede Uebung daneben.

   Der Knoten holt das Portal nach: genau die Erklaerungen, die `.pp-root`
   in css/prepp.css vererbt. Das ist die HAUSREGEL, keine Ausnahme
   (.pp-modal, .ed-hintwin, #kf-wirt machen es genauso) — wer hier eine
   Eigenschaft ergaenzt, ergaenzt sie dort ebenfalls, und umgekehrt.

   `padding-top` haelt die Kopfzeile frei (js/sandbox.js, geoVollLage):
   Sie liegt darueber und bleibt bedienbar, der Graph beginnt darunter. */
.sb-geovoll {
  position: fixed; inset: 0; z-index: 120;
  box-sizing: border-box; padding-top: var(--gv-oben, 0px);
  display: flex; flex-direction: column; min-height: 0;
  background: var(--pp-bg, #fff); color: var(--pp-text, #1f2328);
  font-family: var(--pp-sans, system-ui, sans-serif);
  font-size: var(--pp-fs-text, 13.5px);
  font-weight: 400;
  line-height: var(--pp-lh-text, 1.55);
  -webkit-font-smoothing: antialiased;
}
.sb-geovoll b, .sb-geovoll strong { font-weight: 600; }
.sb-geovoll input, .sb-geovoll button, .sb-geovoll select, .sb-geovoll textarea { font-family: inherit; }
/* Die Algebra-Zeile bleibt Monospace: Sie ist EINGABE von Quelltext, und
   dort traegt die feste Laufweite die Lesbarkeit (`f(x) = sin(a x)`).
   Die Regel steht hier, weil `.sb-geovoll input` (0,1,1) sonst die
   Klassenregel `.sb-geo-eingabe` (0,1,0) schluege — dieselbe Falle wie
   bei jedem „font-family: inherit" ueber einem Klassenselektor. */
.sb-geovoll .sb-geo-eingabe { font-family: var(--pp-mono, monospace); }
/* Die Kopfzeile des Portals ueber dem Vollbild (js/sandbox.js,
   geoVollLage): Ohne sie waeren die Chat-Reiter, das ＋ und die Suche
   unerreichbar, sobald man einen Graphen gross macht. 136 ist keine neue
   Stufe, sondern die des bestehenden Bandes (`.pp-top.kf-pop-auf` in
   css/ki-fenster.css, B40): Die Kopfzeile steht ueber den Pop-ups
   (130–135) und unter den Dialogen (150). Waere sie niedriger als jene
   Marken, kaeme genau der Befund zurueck, den B40 behoben hat. */
.pp-root.sb-gv-auf .pp-top { z-index: 136; }
/* Kein Fokusring: Das Overlay nimmt den Fokus beim Oeffnen nur, damit
   Escape sofort wirkt (Muster .sb-editfenster) — ueber die Tastatur ist
   es gar nicht erreichbar, der Ring staende also grundlos da. */
.sb-geovoll:focus { outline: none; }
/* ---- Die MARKE in der Ecke neben der Kopfzeile (18.09.2026) ----
   Gemessen (WebKit, 1440 px): Das Overlay deckt `inset: 0` und damit auch
   die Seitenleiste, die selbst keinen z-index traegt; herausgehoben wird
   allein die Kopfzeile (z 136), und die beginnt erst rechts der Leiste.
   Links blieb ein Rechteck von 236 × 54 px leer — Jonas' Befund.

   Sie liegt ABSOLUT im Polsterstreifen und ist damit aus dem Fluss: Der
   Kopf darunter behaelt Zeile fuer Zeile seine Lage, und das Gitter des
   Leibes ruehrt sie nicht an. `--gv-ecke` ist die linke Kante der
   Kopfzeile (js/sandbox.js, geoVollMarkeLage) — also genau der Platz der
   Seitenleiste in JEDER ihrer Lagen; `--gv-oben` die Hoehe, die ohnehin
   schon gemessen wird.

   `overflow: hidden` ist das Netz: Bleibt die Ecke einmal schmaler als
   der Name, wird er abgeschnitten statt die Kopfzeile zu ueberschreiben
   (ueber ihr liegt sie mit z 136 ohnehin nie — das ist die zweite
   Sicherung).

   Die GESTALT kommt aus css/prepp.css: Der Knoten traegt `pp-brand`, und
   weder `.pp-brand` noch `.pp-logo` noch `.pp-brand-name` haengen dort an
   `.pp-root` — sie greifen also auch hier am body, samt der Umschaltung
   des Logos zwischen hell und dunkel. Nachgebaut wird nichts; hier steht
   nur, was das Overlay wirklich unterscheidet. */
.sb-gv-marke {
  position: absolute; left: 0; top: 0;
  width: var(--gv-ecke, 0px); height: var(--gv-oben, 0px);
  box-sizing: border-box; padding: 0 12px; overflow: hidden;
  text-decoration: none; color: var(--pp-text, #1f2328);
  /* DIESELBE Flaeche wie die Kopfzeile rechts daneben (`.pp-top` steht
     auf `--pp-surface`), nicht die des Overlays. Gemessen: ohne diese
     Zeile stand die Marke auf `--pp-bg` — hell rgb(246,248,250) neben
     rgb(255,255,255), dunkel rgb(13,17,23) neben rgb(22,27,34) —, und
     der obere Streifen bekam auf halber Strecke eine Kante. Die Marke
     gehoert optisch zur Kopfzeile; sie liegt nur in einem anderen
     Knoten. */
  background: var(--pp-surface, #ffffff);
  border-bottom: 1px solid var(--pp-line, #d7dde3);
}
/* Die Gegenregel zu `[hidden]`, und sie wird gebraucht: `.pp-brand` setzt
   `display: flex` unbedingt und schluege damit die Browserregel fuer
   `[hidden]` — der Knoten staende in einer zu schmalen Ecke trotzdem da
   (die `.pp-inreq`-Falle vom 13.08.2026, hier ueber eine geliehene
   Klasse). */
.sb-gv-marke[hidden] { display: none; }
.sb-gv-marke:hover .pp-brand-name { color: var(--pp-accent-2, #1f6feb); }
.sb-gv-kopf {
  flex: none; display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border-bottom: 1px solid var(--pp-line, #d7dde3);
  background: var(--pp-surface, #ffffff);
}
/* Der Titel ist ein TITEL und wird wie einer gesetzt (06.09.2026):
   Portal-Schrift, dieselbe Groesse und dasselbe Gewicht wie `.pp-top-here`
   in der Kopfzeile darueber. Vorher stand er in gesperrten Versalien aus
   der Monospace — die Auszeichnung einer Rubrik, nicht die eines Namens,
   und der Name kommt hier aus einem Dateinamen. Er darf eine Formel
   tragen und wird deshalb gesetzt (js/sandbox.js, geoVollBauen). */
.sb-gv-titel {
  font-weight: 600; font-size: var(--pp-fs-text, 13.5px); line-height: var(--pp-lh-knapp, 1.3);
  color: var(--pp-text, #1f2328); margin-right: auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Der Weg HINAUS: abgesetzt und ROT hinterlegt (18.09.2026, Jonas).
   Vorher war er ein 22 × 20 px grosses Zeichen ohne Flaeche — er sah aus
   wie die drei Zoomknoepfe daneben und war doch das Einzige im Kopf, das
   die Ansicht VERLAESST.

   Die Farbe: Flaeche `--pp-err`, Schrift `--pp-accent-contrast`. Das ist
   das vorhandene Muster fuer eine gefuellte Warnflaeche
   (`.pp-ctx-it.gefahr:hover` in css/listen.css) und der einzige Weg, der
   in BEIDEN Themes ueber AA kommt: `--pp-accent-contrast` ist je Theme
   der Ton, der auf einer satten Flaeche lesbar ist (dunkel #0d1117 auf
   #f85149 = 5,65 : 1, hell #ffffff auf #cf222e = 5,36 : 1 — gemessen in
   WebKit an den GEMALTEN Farben, docs/GEO-VOLLBILD-KOPF-2026-09-18.md).

   Bewusst NICHT das Muster des Chat-✕ (`.ki-tutor .ppf-x`): Das traegt
   rote SCHRIFT auf normaler Flaeche, und sein Kommentar haelt fest, dass
   `--pp-err-tint` als Flaeche dort gemessen verworfen wurde (4,47 : 1).
   Jonas' Auftrag lautet „rot hinterlegt" — das ist die andere Bauform,
   und sie braucht deshalb den Kontrastton, nicht die Fehlerfarbe als
   Schrift.

   `margin-left: 6px` setzt ihn von der Zoomgruppe ab; den Abstand zum
   Titel traegt der `gap` der Kopfzeile. */
.sb-gv-x {
  border: 1px solid var(--pp-err, #cf222e); background: var(--pp-err, #cf222e);
  cursor: pointer; margin-left: 6px;
  color: var(--pp-accent-contrast, #ffffff);
  font-size: var(--pp-fs-mini, 12px); line-height: var(--pp-lh-eng, 1);
  display: inline-flex; align-items: center; gap: 6px;
  padding: 0 10px; border-radius: 6px;
  min-height: 30px; font-weight: 600;
}
/* Der Hover dunkelt die Flaeche ueber den Text, nicht ueber eine zweite
   Farbe: `--pp-ki-hover` waere der naheliegende Griff und ist falsch —
   das ist die Farbe der KI-Erstellen-Knoepfe, und der Kommentar an ihrem
   Token sagt ausdruecklich, dass sie NICHT die Fehlerfarbe ist. */
.sb-gv-x:hover, .sb-gv-x:focus-visible { filter: brightness(1.12); }
/* Das Wort faellt weg, wo die Kopfzeile eng wird — das Symbol und das
   `title` bleiben. Die Schwelle ist dieselbe, ab der die beiden Haelften
   des Vollbilds stapeln (unten): Wo der Graph schon untereinander steht,
   ist in der Kopfzeile kein Platz fuer ein Wort. */
.sb-gv-x .sb-gv-xwort { white-space: nowrap; }
/* Die drei Zoomknoepfe (08.09.2026, Jonas' Befund): der SICHTBARE Weg
   neben Rad und Zange. Sie sehen aus wie das ✕ daneben — es ist
   dieselbe Sorte Knopf in derselben Zeile, und zwei Bauformen in einer
   Kopfzeile waeren eine Behauptung ueber einen Unterschied, den es
   nicht gibt. */
.sb-gv-zoomleiste { display: flex; align-items: center; gap: 2px; margin-right: 4px; }
/* „In Arbeitsfläche kopieren" (12.09.2026): der Rueckweg aus dem Vollbild
   einer KLAUSUR-Karte in eine eigene Flaeche — ein Textknopf in der
   Zoomleisten-Bauart, weil er in derselben Zeile steht. Auf der Flaeche
   gibt es ihn nicht (js/sandbox.js, geoVollOeffnen). */
.sb-gv-kopieren {
  border: 1px solid var(--pp-line, #d7dde3); background: none; cursor: pointer;
  color: var(--pp-text-2, #525b65); font-size: var(--pp-fs-mini, 12px); line-height: var(--pp-lh-eng, 1);
  padding: 6px 9px; border-radius: 6px; margin-right: 4px; white-space: nowrap;
  font-family: var(--pp-sans, system-ui, sans-serif);
}
.sb-gv-kopieren:hover { color: var(--pp-text, #1f2328); background: var(--pp-bg-2, #ffffff); }
/* Sie sehen seit dem 18.09.2026 wie KNOEPFE aus (Jonas: „Knöpfe
   deutlicher"): Rand und Flaeche, nicht bloss ein Zeichen. Vorher trugen
   sie `border: none; background: none` — auf dem Bildschirmfoto sind
   drei winzige Zeichen zu sehen, von denen nichts sagt, dass man sie
   druecken kann.

   Das Symbol ist ein gezeichnetes Inline-SVG aus SB_SYM (js/sandbox.js),
   kein Schriftzeichen: ＋ und － sind Vollbreitenzeichen und rendern je
   nach Schrift verschieden breit, und ⟲ trug eine andere Strichstaerke
   als alles daneben. */
.sb-gv-zoom {
  border: 1px solid var(--pp-line, #d7dde3); background: var(--pp-surface, #ffffff);
  cursor: pointer; color: var(--pp-text-2, #525b65);
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border-radius: 6px;
  min-width: 30px; min-height: 30px;
}
/* Die Symbolgroesse im Kopf steht EINMAL fuer alle vier Knoepfe. Sie
   wird gebraucht: `.sb-tico` ist `width: 100%; height: 100%` (es fuellt
   im Menueband seinen Knopf, und der hat dort eine feste Groesse). Ohne
   diese Zeile blaeht das SVG seinen Knopf auf — gemessen am
   Schliessen-Knopf, der beim ersten Anlauf 379 × 302 px gross war. */
.sb-gv-zoom .sb-tico, .sb-gv-x .sb-tico { width: 17px; height: 17px; flex: none; }
.sb-gv-zoom:hover:not(:disabled), .sb-gv-zoom:focus-visible:not(:disabled) {
  color: var(--pp-text, #1f2328); background: var(--pp-bg-2, #ffffff);
  border-color: var(--pp-accent, #1f6feb);
}
/* Ausgegraut heisst AUSGEGRAUT und nicht weg: In 3D und in der
   Wahrscheinlichkeits-Ansicht gibt es keine (x, y)-Ansicht zum Zoomen,
   und der `title` sagt es (js/sandbox.js, geoVollZoomNach). */
.sb-gv-zoom:disabled { opacity: .4; cursor: default; }
/* Die Teilung steht als Custom Property, nie als width-Inline: Ein
   Inline-Stil schluege die Medienabfrage weiter unten, die aus beiden
   Haelften eine Spalte macht (woertlich der Handy-Fehler der
   Seitenleiste). minmax(0, …) in beiden Spalten, damit ein breites Kind
   (eine lange Algebra-Zeile, der Befehlskasten) die Spalte nicht ueber
   ihren Anteil hinaus aufzieht. */
.sb-gv-leib {
  flex: 1 1 auto; min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, var(--gv-links, 62%)) 6px minmax(0, 1fr);
}
.sb-gv-graph {
  min-width: 0; min-height: 0; overflow: hidden;
  padding: 12px; box-sizing: border-box;
}
/* Der Graph fuellt seine Haelfte — .sb-geo bringt seine eigene
   Flex-Spalte mit (Titel, Bild, Texte), das Bild bekommt den Rest. */
.sb-gv-graph > .sb-geo { height: 100%; }
/* Im Vollbild laesst sich die Ansicht schieben (js/geo.js): Die offene
   Hand sagt es, bevor man zieht. Ein Punkt traegt sein `cursor: move`
   als Praesentationsattribut im SVG und gewinnt dagegen — die Geste dort
   ist eine andere. */
.sb-gv-graph .sb-geo-bild { cursor: grab; }
.sb-gv-graph .sb-geo-bild:active, .sb-geo-schiebt .sb-geo-bild { cursor: grabbing; }
/* Und derselbe Zug per FINGER (19.09.2026): Ohne `touch-action: none`
   nimmt der Browser jeden Wisch fuer sich — er scrollt, zoomt bei zwei
   Fingern die ganze Seite und meldet PrepPs Geste als abgebrochen
   (pointercancel). Gemessen war im Vollbild ueberall `auto`, es gab die
   Regel also gar nicht.

   Sie steht GENAU am Bild und nicht am Graph-Kasten: `touch-action`
   vererbt sich nicht, wirkt aber ueber die Vorfahren — ein `none` an
   `.sb-gv-graph` nahme damit auch dem gesetzten Textblock darunter
   (`.sb-geo-texte`, `max-height: 40%; overflow: auto`) sein Rollen, und
   der liegt im selben Kasten. Die Reglerleiste und die Algebra-Liste
   sind im Vollbild ohnehin draussen (rechte Haelfte, reglerAussen).

   In der KARTE auf der Flaeche steht sie nicht: Dort navigiert der Graph
   nicht, dort gehoert der Zug der Flaeche (`.sb-canvas`, seit dem
   17.09.2026) — und `.sb-geo-def`/`.sb-geo-texte` holen sich ihr
   `auto` dort ausdruecklich zurueck (Ausnahmeliste weiter oben). */
.sb-gv-graph .sb-geo-bild { touch-action: none; }
/* Der Teiler ist eine eigene Gitterspalte, keine Ueberlagerung — so kann
   er weder wegscrollen noch von einem Nachbarn verdeckt werden (Muster
   .ein-schiene). Die Trefferflaeche waechst ueber ein Pseudoelement nach
   beiden Seiten, ohne das Gitter zu verschieben. */
.sb-gv-teiler {
  position: relative; cursor: col-resize;
  background: var(--pp-line, #d7dde3); border: none;
}
.sb-gv-teiler::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: -5px; right: -5px;
}
.sb-gv-teiler:hover, .sb-gv-zieht .sb-gv-teiler { background: var(--pp-accent-tint, #e7f0fd); }
.sb-gv-zieht { user-select: none; }
.sb-gv-seite {
  min-width: 0; min-height: 0; overflow: auto;
  display: flex; flex-direction: column;
  background: var(--pp-surface, #ffffff); border-left: 1px solid var(--pp-line, #d7dde3);
}
/* Dieselben Abstaende wie im Leib des Seitenfensters — die Regeln dort
   haengen am Elternknoten (.sb-editfenster-leib > …) und greifen hier
   nicht; wiederholt wird der Abstand, nicht der Editor. */
.sb-gv-seite > .sb-sf-bedien,
.sb-gv-seite > .sb-sf-editor { padding: 0 12px; }
.sb-gv-seite > .sb-sf-bedien { padding-top: 10px; }
.sb-gv-seite > .sb-sf-editor { padding-bottom: 10px; }
.sb-gv-seite > .sb-sf-bedien:empty { display: none; }
/* Das Textfeld einer Box im Vollbild — dieselbe Luecke wie im
   Seitenfenster (P10a): `.sb-karte textarea` haengt an der Karte. */
.sb-geovoll .sb-box textarea {
  box-sizing: border-box; resize: none;
  color: var(--pp-text, #1f2328); background: var(--pp-bg, #f6f8fa);
  border: 1px solid var(--pp-line-2, #c3ccd4); border-radius: 6px; padding: 6px;
}

/* Auf dem Telefon gibt es nichts zu verteilen: Bei 390 px waere jede
   Teilung fuer beide Haelften zu wenig. Der Graph steht oben, die
   Beschreibung darunter, der Teiler faellt weg — angeboten wird das
   Vollbild trotzdem, denn dort hilft es am meisten: Die Karte auf der
   Flaeche ist winzig, und das Seitenfenster deckte sie (85-%-Klemme)
   ohnehin fast ganz zu. */

/* ---------------- Widget der Startseite (Paket P3, 11.10.2026, docs/START-WIDGETS-2026-10-11.md) ----
   Zwei Zonen: der KOPF (Name der Box — hier fasst man die Karte an, wie am
   Rahmen) und der LEIB (data-sb-bedien: Klicks gehören dem Widget). Der
   Leib rollt, wenn der Inhalt höher ist als die Karte — die Karte wächst
   nicht von selbst (Regel „Bedienboxen bleiben in ihrer Karte"). Die Boxen
   der Startseite bringen ihren eigenen Kartenrahmen mit (.pp-card,
   .pp-active); IN einer Karte wäre das ein Rahmen im Rahmen — er fällt. */
.sb-widget { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.sb-widget-kopf {
  flex: none; font-size: var(--pp-fs-mini); letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--pp-text-3); font-weight: 600; padding: 0 0 6px; cursor: grab;
}
.sb-widget-leib { flex: 1 1 auto; min-height: 0; overflow: auto; cursor: auto; }
.sb-widget-leib > .pp-card, .sb-widget-leib > .pp-active {
  border: 0; box-shadow: none; background: transparent; margin-bottom: 0; border-radius: 0;
}
.sb-widget-leib > .pp-card.pad { padding-left: 0; padding-right: 0; }
.sb-widget-leib > .pp-active { padding: 6px 0; }
.sb-widget-leib > .pp-start-lead { margin-bottom: 0; }
.sb-widget-leib .pp-stats { margin-bottom: 0; }
.sb-widget.sb-tot { color: var(--pp-text-3); }
.sb-widget-wahl { justify-content: center; }
.sb-widget-wahl-label { display: flex; flex-direction: column; gap: 6px; font-size: var(--pp-fs-klein); color: var(--pp-text-2); }

@media (max-width: 760px) {
  /* ---- Der Karteneditor wird zum Blatt von unten (18.09.2026) ----
     Als SPALTE ist er 380 px breit, auf schmalen Flächen auf 85 %
     geklemmt — bei 390 px sind das 331 px, und der Fläche blieben
     59 px. Man bearbeitet dann eine Karte, die man nicht mehr sieht;
     genau das war das Argument der alten Warnkarte.

     Dieselbe Antwort wie beim Geo-Vollbild einen Block weiter: Was
     nebeneinander nicht passt, wird GESTAPELT. Das Fenster dockt unten
     an, über die volle Breite, und lässt die obere Hälfte der Fläche
     stehen — dort liegt die Karte, an der man arbeitet, und die
     `karteWachsen`-Regel hält sie sichtbar. `max-height` statt `height`,
     damit ein kurzer Editor (ein Formular mit zwei Feldern) nicht die
     halbe Fläche zudeckt. */
  .sb-editfenster {
    top: auto; left: 0; right: 0; bottom: 0;
    width: auto; max-height: 52%;
    border-left: none; border-top: 1px solid var(--pp-line, #d7dde3);
    box-shadow: 0 -10px 26px rgba(0, 0, 0, .18);
  }
  /* Der Griff zum Schließen ist auf dem Finger der wichtigste Knopf des
     Blattes — und der einzige, den die Fläche darunter nicht ersetzt. */
  .sb-editfenster-x { min-width: 44px; min-height: 44px; }

  .sb-gv-leib {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 45%) minmax(0, 1fr);
  }
  .sb-gv-teiler { display: none; }
  .sb-gv-seite { border-left: none; border-top: 1px solid var(--pp-line, #d7dde3); }
  /* Der Titel im Kopf faellt hier weg (Abnahme W4, Befund B4). Gemessen
     bei 390 px mit dem Kopierknopf einer Klausur-Karte: Zoomleiste 132,
     Kopieren 145, Schliessen 44 — dem Titel blieben 13 px, und 13 px
     sind kein gekuerzter Titel, sondern ein Buchstabe mit Punkt. Er
     GEHT dabei nicht verloren: Die Karte nennt sich unmittelbar darunter
     selbst (.sb-geo-titel im Graphen, bei 390 px 366 px breit und als
     Formel gesetzt) — der Kopf zeigte ihn ein zweites Mal. Auf dem
     Laptop bleibt er, dort ist Platz fuer beides. */
  .sb-gv-titel { display: none; }
  /* Und mit ihm das Wort am Schliessen-Knopf: Wo schon der Titel nicht
     mehr hinpasst, ist fuer ein zweites Wort erst recht kein Platz. Das
     rote Feld und das Symbol bleiben — das ist die Erkennbarkeit, um die
     es Jonas ging; das `title` nennt weiterhin auch Escape. */
  .sb-gv-x .sb-gv-xwort { display: none; }
  .sb-gv-x { padding: 0; min-width: 34px; justify-content: center; }
  /* Die Ecke gibt es hier nicht: Unter 760 px fuellt `.pp-main` die ganze
     Breite, die Kopfzeile beginnt bei 0 und die Seitenleiste ist das
     Mobil-Menue. `geoVollMarkeLage` blendet die Marke dort ohnehin aus
     (die gemessene Breite ist 0) — die Regel ist der Riegel dahinter,
     falls je ein Rahmen eine Ecke MELDET, in der keine ist. */
  .sb-gv-marke { display: none; }
}
/* ---------------- Planungskarten: Lernplan, Kalender, To-dos (P2, 11.10.2026) ----------------
   docs/SB-PLAN-KARTEN-2026-10-11.md. Alle drei sind Bedienkarten wie die
   Aufgabe: ein fester Kopf, darunter EIN rollender Kasten. Er trägt
   data-sb-bedien — damit ist er Bedienung (sbIstBedien) und rollt auf dem
   Telefon (das Attribut steht in der touch-action-Liste oben). Farben nur
   aus --pp-*, damit hell und dunkel von selbst stimmen. Die Kurswahl der
   Lernplan-Karte benutzt den Wähler der Aufgabe (.sb-wahl). */
.sb-plk, .sb-kal, .sb-todo { display: flex; flex-direction: column; gap: 6px; height: 100%; min-height: 0; }
.sb-plk-kopf {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; flex: none;
  font-size: var(--pp-fs-klein);
}
.sb-plk-kopf > b { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-plk-dim { color: var(--pp-text-2); font-size: var(--pp-fs-mini); }
.sb-plk-knopf {
  font: inherit; font-size: var(--pp-fs-mini); padding: 2px 7px; border-radius: 5px;
  border: 1px solid var(--pp-line-2); background: var(--pp-surface); color: var(--pp-text-2);
  cursor: pointer; white-space: nowrap;
}
.sb-plk-knopf:hover { border-color: var(--pp-accent-line); color: var(--pp-accent-2); }
/* EIN-Zustand als Form, nicht nur als Farbe (MECHANISMEN, Sandbox-Menüband). */
.sb-plk-knopf.on { color: var(--pp-accent-2); border-color: var(--pp-accent-line); box-shadow: inset 0 -2px 0 currentColor; }
.sb-plk-kopf .sb-plk-knopf[data-plk-wechsel] { margin-left: auto; }
.sb-plk-liste, .sb-todo-liste { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
.sb-plk-gruppe {
  font-size: var(--pp-fs-mini); letter-spacing: .04em; text-transform: uppercase;
  color: var(--pp-text-2); margin-top: 4px;
}
.sb-plk-gruppe:first-child { margin-top: 0; }
.sb-plk-leer { font-size: var(--pp-fs-klein); color: var(--pp-text-2); padding: 2px 0 2px 24px; }
.sb-plk-zeile, .sb-todo-zeile { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: var(--pp-fs-klein); }
.sb-plk-titel { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-plk-titel.ziel { cursor: pointer; }
.sb-plk-titel.ziel:hover { color: var(--pp-accent-2); text-decoration: underline; }
.sb-plk-verb { color: var(--pp-text-2); }
.sb-plk-meta { flex: none; color: var(--pp-text-2); font-size: var(--pp-fs-mini); }
/* Erledigt heißt erledigt, nicht verschwunden (Muster .sb-task.sb-done):
   gedämpft über die Farbe, nicht durchgestrichen. */
.sb-plk-zeile.erledigt .sb-plk-titel, .sb-todo-zeile.erledigt .sb-todo-text,
.sb-todo-zeile.erledigt .sb-todo-lesen { color: var(--pp-text-2); }
.sb-plk-fuss { flex: none; display: flex; align-items: center; gap: 8px; justify-content: flex-end; }

/* Das Häkchen — gezeichnet, nicht ☑ (Emoji in WebKit). */
.sb-hak {
  flex: none; box-sizing: border-box; width: 18px; height: 18px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--pp-line-2); border-radius: 4px; background: var(--pp-surface);
  color: var(--pp-ok); cursor: pointer;
}
span.sb-hak { cursor: default; }
.sb-hak:hover { border-color: var(--pp-accent-line); }
.sb-hak.an { border-color: var(--pp-ok); }
.sb-hak-ico { width: 14px; height: 14px; }

/* Kalender */
.sb-kal-kopf .sb-kal-nav { display: inline-flex; gap: 2px; margin-left: auto; }
.sb-kal-umsch { display: inline-flex; gap: 2px; }
.sb-kal-kurs {
  font: inherit; font-size: var(--pp-fs-mini); max-width: 140px; padding: 1px 4px; border-radius: 5px;
  border: 1px solid var(--pp-line-2); background: var(--pp-surface); color: var(--pp-text);
}
.sb-kal-woche { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }
.sb-kal-tag {
  display: flex; gap: 8px; align-items: baseline; padding: 3px 4px;
  border-top: 1px solid var(--pp-line); font-size: var(--pp-fs-klein);
}
.sb-kal-tag:first-child { border-top: 0; }
.sb-kal-tag.heute, .sb-kal-zelle.heute { background: var(--pp-accent-tint); border-radius: 5px; }
.sb-kal-datum { flex: none; width: 72px; color: var(--pp-text-2); font-size: var(--pp-fs-mini); }
.sb-kal-inhalt { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; gap: 3px; }
.sb-kal-e {
  display: inline-block; max-width: 100%; box-sizing: border-box;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--pp-fs-mini); padding: 1px 6px; border-radius: 4px;
  border-left: 3px solid var(--pp-plan-a); background: var(--pp-bg-2); color: var(--pp-text);
}
.sb-kal-e.plan-b { border-left-color: var(--pp-plan-b); }
.sb-kal-e.erledigt { color: var(--pp-text-2); }
.sb-kal-e.klausur { border-left-color: var(--pp-err); background: var(--pp-err-tint); font-weight: 600; }
.sb-kal-e.ziel { cursor: pointer; }
.sb-kal-e.ziel:hover { color: var(--pp-accent-2); }
.sb-kal-monat {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  grid-template-rows: auto; grid-auto-rows: minmax(44px, auto); gap: 2px;
}
.sb-kal-wt { font-size: var(--pp-fs-mini); color: var(--pp-text-2); text-align: center; align-self: end; }
.sb-kal-zelle {
  min-width: 0; display: flex; flex-direction: column; gap: 1px; padding: 2px;
  border: 1px solid var(--pp-line); border-radius: 5px;
}
.sb-kal-zelle.fremd .sb-kal-nr { color: var(--pp-text-3); }
.sb-kal-nr { font-size: var(--pp-fs-mini); color: var(--pp-text-2); }
.sb-kal-mehr { font-size: var(--pp-fs-mini); color: var(--pp-text-2); }

/* To-dos */
.sb-todo-titel {
  flex: 1 1 120px; min-width: 0; font: inherit; font-weight: 600; font-size: var(--pp-fs-klein);
  padding: 2px 4px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: var(--pp-text);
}
.sb-todo-titel:hover, .sb-todo-titel:focus { border-color: var(--pp-line-2); background: var(--pp-surface); }
.sb-todo-text, .sb-todo-neu {
  flex: 1 1 auto; min-width: 0; font: inherit; font-size: var(--pp-fs-klein);
  padding: 2px 4px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: var(--pp-text);
}
.sb-todo-text:hover, .sb-todo-text:focus, .sb-todo-neu:focus { border-color: var(--pp-line-2); background: var(--pp-surface); }
.sb-todo-neu { flex: none; margin-left: 44px; border: 1px dashed var(--pp-line-2); }
/* Der Griff zum Sortieren (S2, 11.10.2026): 14 px breit + 6 px gap — das
   Neu-Feld rückt deshalb um 20 px auf 44 px ein und fluchtet weiter mit
   dem Text. touch-action: none, damit der Finger zieht statt zu scrollen.
   Während des Zugs: keine Auswahl, kein Übergang, die Zeile nur leicht
   hinterlegt. */
.sb-todo-griff {
  flex: none; width: 14px; height: 20px; display: inline-flex; align-items: center; justify-content: center;
  color: var(--pp-text-3); cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none;
}
.sb-todo-griff .sb-hak-ico { width: 14px; height: 14px; }
.sb-todo-zeile:hover .sb-todo-griff { color: var(--pp-text-2); }
.sb-todo-ordnet, .sb-todo-ordnet * { user-select: none !important; -webkit-user-select: none !important; cursor: grabbing; }
.sb-todo-zieht { background: var(--pp-accent-tint); border-radius: 4px; transition: none; }
.sb-todo-lesen { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-todo-weg {
  flex: none; width: 20px; height: 20px; padding: 0; border: 0; border-radius: 4px;
  background: none; color: var(--pp-text-2); cursor: pointer; opacity: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.sb-todo-zeile:hover .sb-todo-weg, .sb-todo-weg:focus { opacity: 1; }
.sb-todo-weg:hover { color: var(--pp-err); }
/* Ohne Zeiger kein Hover: Das ✕ bleibt sichtbar. Die 44-px-Treffer
   stehen im EINEN coarse-Block unten (verify-sandbox-telefon.js 8.3). */
@media (hover: none) { .sb-todo-weg { opacity: 1; } }
/* Druck: schlichte Listen, Häkchen als Zeichen. */
.sbp-plan ul, .sbp-kal ul, .sbp-todo ul { margin: 4px 0 6px; padding-left: 16px; }

/* Auf dem Telefon ist die Zange die naheliegende Geste — die Knoepfe
   sind der Weg fuer alle anderen und brauchen dort die volle
   Trefferflaeche (R3, 44 px). Gefragt wird nach dem ZEIGER und nicht
   nach der Breite: Ein Tablet ist breit und wird trotzdem angefasst. */
@media (pointer: coarse) {
  .sb-gv-zoom, .sb-gv-x { min-width: 44px; min-height: 44px; }
  .sb-gv-kopieren { min-height: 44px; }
  .sb-gv-zoomleiste { gap: 0; }

  /* ---- Die kleinen Einträge des Bandes (R3, 18.09.2026) ----
     Gemessen am 18.09.2026 bei 390 × 844: 9 von 20 sichtbaren
     Bandknöpfen waren unter 44 px — alle mit 24 px Höhe. Es sind die
     KLEINEN Einträge (`.sb-kl`), die auf dem Telefon nicht als Spalte,
     sondern als Reihe stehen; in der Höhe blieben sie bei ihrem
     Laptop-Maß.

     Geweitet wird nur die HÖHE, nicht die Breite: Die Einträge stehen
     in einer waagerecht wischenden Reihe und sind zwischen 60 und
     122 px breit — ein unsichtbarer Vorsatz nach dem `::before`-Muster
     würde sie hier ÜBERLAPPEN, und dann läge die Mitte von „Kopieren"
     auf „Einfügen" (dieselbe Falle wie am 09.09. und 16.09.2026, beide
     Male teuer). Die Reihe wächst dadurch um 20 px; das ist die eine
     Stelle, an der das Band auf dem Telefon Platz kostet, und sie ist
     es wert: Ein Knopf, den man nicht trifft, ist kein Knopf. */
  .sb-b.klein { min-height: 44px; }
  /* Der Schaltbild-Wähler (23.09.2026): auf dem Finger 44 px je Zeile und je
     Fußknopf — WebKit maß 24 px in einer 320 x 220-Karte; die Liste rollt
     dafür (overflow-y: auto an .sb-wahl-liste), die Karte wächst nicht. */
  .sb-wahl-zeile, .sb-cir-lab { min-height: 44px; }
  /* Die Reiter sind 44 px HOCH und teils nur 17,4 px breit („KI") — der
     schmalste Treffer der Seite. Der Selektor braucht `.sb-tabs davor:
     Der blanke Name `.sb-tab` ist seit dem Stresstest an die
     Wahrheitstabelle der Tabellenkarte vergeben (siehe oben). */
  .sb-tabs .sb-tab { min-width: 44px; justify-content: center; }
  /* „Im Graphen zeigen" (30.09.2026): WebKit maß bei 390 × 844 mit Touch
     24 × 22 px — der Knopf ist das EINZIGE Ziel für den neuen Zustand,
     es gibt keinen zweiten Weg. Das Eingabefeld daneben (flex: 1,
     min-width: 0) gibt die 20 px Breite ab. */
  .sb-geo-imgraph { width: 44px; height: 44px; }
  /* Die älteren Zeilenknöpfe daneben (Politur 03.10.2026, Befund der
     Graph-Abnahme vom 30.09.): Sichtbarkeit (Kästchen ~14 px), Farbe
     22 × 22, ✕ ~20 px. Gewachsen ist die TREFFERFLÄCHE, nicht das
     Zeichen: Das Kästchen sitzt mittig in seinem 44er Label, das
     Farbfeld bekommt 11 px Polster (das Feld selbst bleibt 22 px), das ✕
     behält seine Schriftgröße. Das Eingabefeld (flex: 1, min-width: 0)
     gibt die Breite ab — kein Umbruch, kein Überlauf (Fallstrick 41). */
  .sb-geo-sichtfl { min-width: 44px; min-height: 44px; }
  .sb-geo-farbe { box-sizing: border-box; width: 44px; height: 44px; padding: 11px; }
  .sb-geo-del { min-width: 44px; min-height: 44px; }
  /* Die Planungskarten (P2, 11.10.2026): Haken, Zeilen und Knöpfe der
     Lernplan-, Kalender- und To-do-Karte treffen mit dem Finger. */
  .sb-hak { width: 28px; height: 28px; }
  .sb-plk-zeile, .sb-todo-zeile { min-height: 44px; }
  .sb-plk-knopf, .sb-todo-weg { min-height: 44px; min-width: 44px; }
  .sb-todo-griff { min-height: 44px; width: 32px; }
  .sb-todo-neu { margin-left: 72px; }   /* 32 Griff + 6 + 28 Haken + 6 */
  /* sim1016 R9 (A4-07): das GRAPH-VOLLBILD am Finger. WebKit maß bei
     390 × 844 und 834 × 1112 mit Touch 27 Ziele unter 32 px — Schieber
     16 px hoch, ▶ 24 × 22, Tempo/Art/Wert ~20 px, Kästchen 12 px. Nur im
     Vollbild (`.sb-geovoll`): Dort ist Platz, in der Karte auf der Fläche
     gilt weiter das Laptop-Maß (siehe .sb-geo-rplay oben). Mindestens
     32 px, die Knöpfe 36. */
  .sb-geovoll .sb-geo-regler input[type="range"] { height: 32px; }
  .sb-geovoll .sb-geo-rplay { width: 36px; height: 36px; }
  .sb-geovoll .sb-geo-rwert,
  .sb-geovoll .sb-geo-eingabe,
  .sb-geovoll .sb-geo-ranim input[type="number"],
  .sb-geovoll .sb-geo-ranim select,
  .sb-geovoll .sb-geo-einst input[type="number"],
  .sb-geovoll .sb-geo-einst input[type="text"],
  .sb-geovoll .sb-geo-einst select,
  .sb-geovoll .sb-geo-add { box-sizing: border-box; min-height: 32px; }
  .sb-geovoll .sb-geo-einst summary,
  .sb-geovoll .sb-geo-hilfe summary { min-height: 32px; display: flex; align-items: center; }
  /* Ein Auswahlmenü nimmt in WebKit keine min-height an, nur `height`. */
  .sb-geovoll .sb-geo-ranim select,
  .sb-geovoll .sb-geo-einst select { height: 32px; }
  .sb-geovoll input[type="checkbox"] { width: 22px; height: 22px; }
  .sb-geovoll .sb-geo-einst label,
  .sb-geovoll label:has(> input[type="checkbox"]) { min-height: 32px; display: inline-flex; align-items: center; gap: 4px; }
}

/* AUSBAU-A1 (14.10.2026): die Karte, die ein Verweis in die Mitte holt. */
@keyframes sb-ziel-ping { 0% { box-shadow: 0 0 0 6px var(--pp-accent-tint, rgba(45, 108, 223, .18)) } 100% { box-shadow: 0 0 0 0 transparent } }
.sb-karte.sb-ziel, [data-karte].sb-ziel { animation: sb-ziel-ping 1.8s ease-out; }

/* ---------------- a1014-G5c: die gemeinsame Fläche der Lerngruppe ----------------
   docs/abnahme/a1014-g5c/BERICHT.md. Zeiger der anderen in Kürzel-Farbe in
   der Weltebene (sie wandern mit Zoom und Verschieben), ihre Auswahl als
   farbiger Rand an der Karte, die Anwesenden oben rechts in der Fläche. */
#sb-gemeinsam { position: absolute; inset: 0; pointer-events: none; }
.fg-zeiger {
  position: absolute; z-index: 5; pointer-events: none; transform: translate(-50%, -50%);
  min-width: 22px; height: 22px; padding: 0 5px; box-sizing: border-box;
  border-radius: 11px; border: 2px solid #fff; color: #fff;
  font: 600 12px/18px var(--pp-sans, system-ui, sans-serif); text-align: center;   /* a1014-fix-lv LV-10: 12 px */
  box-shadow: 0 1px 3px rgba(0, 0, 0, .3); transition: left .12s linear, top .12s linear;
}
.sb-node.fg-fremd { outline: 2px solid var(--fg-farbe, #2563eb); outline-offset: 2px; }
.fg-anwesend {
  position: absolute; right: 12px; top: 8px; z-index: 6;
  display: flex; align-items: center; gap: 4px; pointer-events: none;
}
.fg-kuerzel {
  display: inline-block; min-width: 24px; height: 24px; padding: 0 5px; box-sizing: border-box;
  border-radius: 12px; border: 2px solid #fff; color: #fff;
  font: 600 12px/20px var(--pp-sans, system-ui, sans-serif); text-align: center;   /* LV-10: 12 px */
  box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
}
.fg-anwesend:empty { display: none; }
/* b1015-A: statt des grünen/gelben Punkts nur noch offline ein Wort — als
   kleine Marke auf eigener Fläche, damit es über jedem Hintergrund lesbar ist. */
.fg-offline-hinweis {
  padding: 2px 8px; border: 1px solid var(--pp-line, #30363d); border-radius: 999px; background: var(--pp-surface, #161b22);
  color: var(--pp-text-2, #8b949e); font: 500 var(--pp-fs-mini, 11px)/1.35 var(--pp-sans, system-ui, sans-serif); white-space: nowrap;
}
.fg-nurlesen-hinweis { font: 500 var(--pp-fs-mini, 11px)/1 var(--pp-sans, system-ui, sans-serif); color: var(--pp-text-3, #6b7280); margin-left: 4px; }
/* lg1019-P6: Kürzel UND Name in der Leiste der Fläche — Markup aus
   PreppLive.anwesendLeisteHtml (js/live.js), dieselbe Leiste wie das Heft. */
.fg-anwesend:has(.fg-person) {
  padding: 3px 6px 3px 8px; border: 1px solid var(--pp-line, #30363d); border-radius: 999px; background: var(--pp-surface, #161b22);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .12); max-width: min(70vw, 520px); flex-wrap: wrap;
}
.fg-mit { font: 500 var(--pp-fs-mini, 11px)/1 var(--pp-sans, system-ui, sans-serif); color: var(--pp-text-2, #8b949e); margin-right: 2px; }
.fg-person { display: inline-flex; align-items: center; gap: 5px; }
.fg-name { font: 500 var(--pp-fs-mini, 11px)/1 var(--pp-sans, system-ui, sans-serif); color: var(--pp-text, #e6edf3); white-space: nowrap; }
@media (max-width: 600px) { .fg-name { display: none; } }
.fg-nurlesen #sb-karten, .fg-nurlesen #sb-kanten { pointer-events: none; }
