/* ===========================================================================
   kajing.io - Designsystem
   ===========================================================================

   Abgeleitet aus zwei Referenzoberflaechen (Dashboard hell / Mobile-App mit
   Limette und Schwarz). Die Regeln, die dort ohne Ausnahme gelten und hier
   uebernommen sind:

     1  Nur Pillen und Kreise. Radius ist 999px oder gross (20-28px).
        Es gibt kein Element mit 6px Radius.
     2  Eine Bedienreihe hat EINE Hoehe. Kreis, Pille, Datumswaehler sind
        gleich hoch und nur unterschiedlich breit.
     3  Zahlen werden gesetzt, nicht getippt: Waehrung klein und hochgestellt,
        Ganzzahl gross, Dezimalstellen und Einheit kleiner und leichter.
     4  Label ueber der Zahl, Veraenderung unter der Zahl. Immer.
     5  Dunkel bedeutet handeln. Limette bedeutet hinsehen.
        Es gibt keinen limettenen Knopf - das ist die wichtigste Regel des
        Systems und der Grund, warum die Akzentfarbe nie abstumpft.
     6  Im Diagramm hat nur der bemerkenswerte Wert Farbe.
     7  Hinweisfahnen sind dauerhaft sichtbar, nicht erst bei Hover, und
        haengen an einer gestrichelten Lotlinie.
     8  Schraffur heisst "noch nicht". Gestrichelter Umriss heisst "Vergleich".
     9  Jeder Widgetkopf traegt rechts ein bis zwei Kreisknoepfe.
    10  Schwebende Kreisknoepfe ueberlappen Kartenkanten. So entstehen Ebenen
        ohne einen einzigen Schatten.
    11  Karten duerfen Karten enthalten und eine ganze Flaeche annehmen.

   Die Referenz ist rein hell. Dunkel ist hier trotzdem vollstaendig
   ausgefuehrt, weil das Geraet in Wohnzimmern steht und die Oberflaeche
   abends aufgerufen wird. Limette funktioniert auf beiden Gruenden - das ist
   in der Mobile-Referenz nachgewiesen, dort liegt sie auf Schwarz.
   ========================================================================= */


/* -------------------------------------------------------------- Schrift
   Space Grotesk, selbst ausgeliefert statt von Googles Servern. Der Aufruf
   dorthin war die einzige externe Abhaengigkeit der Oberflaeche - ein selbst
   gehostetes Backend soll ohne Internetzugang laufen, und Googles Server
   bekommen sonst die IP-Adresse jedes Besuchers. Lizenz OFL 1.1, siehe
   static/fonts/LIESMICH.txt.

   Variabler Schnitt: ein Aufruf deckt 400 bis 700 ab. `swap` sorgt dafuer,
   dass die Seite sofort in der Systemschrift steht statt kurz leer zu sein. */
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/fonts/space-grotesk-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                 U+FFFD;
}
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/fonts/space-grotesk-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                 U+2113, U+2C60-2C7F, U+A720-A7FF;
}


/* ------------------------------------------------------------------ Farben
   Aus den Screens abgenommen. Die Limette ist ein gruenstichiges Gelb, kein
   Neongruen; das Schwarz ist leicht gruenstichig, kein reines #000.        */

:root {
  color-scheme: light;

  /* Gruende */
  --plane:      #E9ECF1;   /* Seite hinter dem App-Rahmen */
  --frame:      #FFFFFF;   /* der App-Rahmen selbst */
  --surface:    #FFFFFF;   /* Karte */
  --surface-2:  #F3F5F8;   /* zurueckgesetzte Karte, Reglerbahn */
  --surface-3:  #E7EBF0;   /* Hover, Bahn, leerer Balken */

  /* Linien */
  --hair:       rgba(17, 23, 31, .07);
  --hair-2:     rgba(17, 23, 31, .14);

  /* Schrift. Drei Stufen, alle drei mit mindestens 4.5:1 gegen die Karte -
     auch die dritte, denn sie traegt `.t-label`, `.muted` und den Delta-Satz,
     also 11-13px Text. Die Referenz setzt hier ein sehr helles Grau; das ist
     der eine Punkt, an dem sie nicht uebernommen wurde. Die Abstufung kommt
     stattdessen aus Groesse und Strichstaerke. Nachpruefbar mit
     tools/check_palette.py. */
  --ink:        #14181D;
  --ink-2:      #4F5963;
  --ink-3:      #687077;

  /* Handeln - maximaler Kontrast, nie die Akzentfarbe */
  --act:        #14181D;
  --act-ink:    #FFFFFF;
  --act-hover:  #242B33;

  /* Hinsehen - Datenakzent und Heldenflaeche */
  --lime:       #D8F441;
  --lime-deep:  #869A14;   /* Linien auf hellem Grund - 3:1 als Grafikobjekt */
  --lime-ink:   #171F06;   /* Schrift auf Limette */

  /* Drei Farben, die dem Thema BEWUSST nicht folgen - sie beschreiben keine
     Oberflaeche, sondern Gegenstaende: Das Panel ist schwarzes Glas, ein
     QR-Code braucht weissen Grund, ein Etikett wird auf Papier gedruckt.
     Deshalb stehen sie nur hier und nicht in den Dunkelmodus-Bloecken. */
  --panel-black: #0C0F13;  /* Glas vor dem Display */
  --paper:       #FFFFFF;  /* bedrucktes Papier, QR-Grund */
  --paper-ink:   #111111;  /* Tinte darauf */

  /* Flaechenfarben fuer Heldenkarten, je eine Schriftfarbe dazu. Mint ist
     gruener als in der Referenz: Bei deren Ton lagen Mint und Lavendel nur
     dE 5 auseinander und saehen nebeneinander wie ein Druckfehler aus. */
  --mint:       #9AD7B0;  --mint-ink:  #10352A;
  --lavender:   #DCE2F2;  --lav-ink:   #1A2340;
  --teal:       #4FB0B8;  --teal-ink:  #06272A;

  /* Zustaende. Abgedunkelt gegenueber den ueblichen Ampelfarben, weil sie im
     Delta-Satz bei 12.5px stehen und dort 4.5:1 brauchen. */
  --up:         #0E8542;
  --down:       #D63B40;
  --alarm:      #D63B40;
  --warn:       #9F6A0A;

  /* Rueckmeldungen. NICHT `--up` und `--down`: Die sind fuer Daten da, wo
     Gruen und Rot eine Konvention sind, an der niemand ruetteln sollte. Auf
     einer Meldung neben der Marke schlagen sie sich aber.

     Die Marke sitzt bei rund 72 Grad Farbwinkel (Gelbgruen) und mit sehr
     hoher Buntheit. Ein gesaettigtes Gruen liegt daneben und streitet mit ihr
     um dieselbe Ecke des Farbkreises; ein Feuerwehrrot bei 25 Grad liegt auf
     ihrer warmen Seite und flimmert dagegen.

     Was passt, steht auf dem Farbkreis gegenueber oder in den geteilten
     Gegenfarben - rund 190 Grad und rund 350 Grad. Also:

       Bestaetigung   die Limette selbst, mit ihrer dunklen Schrift. Sie sagt
                      ohnehin "hier schauen", und genau das ist eine
                      Bestaetigung. Kein zweites Gruen daneben.
       Fehler         ein Rosenrot bei 350 Grad statt Rot bei 25 Grad. Es
                      liegt in der geteilten Gegenfarbe, alarmiert genauso und
                      streitet nicht.

     Nachgerechnet: Weiss auf #D6455E ergibt 4,27:1, das Feld selbst gegen
     helles Glas 3,66:1 und gegen dunkles 3,97:1 - beide ueber den 3:1 fuer
     Grafikobjekte. */
  --toast-ok:      #D8F441;
  --toast-ok-ink:  #171F06;
  --toast-err:     #D6455E;
  --toast-err-ink: #FFFFFF;

  /* Das Dock. Es folgt dem Thema wie jede andere Flaeche - eine fest dunkle
     Leiste stand im Hellmodus als schwarzer Block in einer hellen Oberflaeche.

     Das Leuchten des aktiven Kaestchens ist WEISS, in beiden Themen. Auf
     Dunkel ist das ein Schein, auf Hell wird das Kaestchen heller als seine
     Nachbarn und der Schein hebt es aus der Glasleiste heraus - beide Male
     dasselbe Mittel, beide Male sichtbar. */
  --dock-tile:     #F3F5F8;   /* ruhendes Kaestchen */
  --dock-tile-on:  #FFFFFF;   /* das aktive, heller als die Nachbarn */
  --dock-ink:      #4F5963;
  --dock-ink-on:   #14181D;
  --dock-rand:     rgba(17, 23, 31, .08);

  /* Der Raum hinter dem Geraet - ein einziger Wandverlauf ueber die ganze
     Karte. Er soll das Produkt tragen, nicht selbst auffallen - deshalb
     liegen beide Toene innerhalb eines knappen Helligkeitsbandes. Sobald
     die Wand als Flaeche gelesen wird, steht das Geraet davor statt darin. */
  --wall-1:  #F2F5F9;   /* oben, wo das Licht herkommt */
  --wall-2:  #D7DEE7;   /* unten */
  --wall-hi: #FFFFFF;   /* der helle Fleck oben links */

  /* Radien - nur diese vier */
  --r-pill: 999px;
  --r-card: 26px;
  --r-in:   20px;   /* Karte in der Karte */
  --r-chip: 14px;

  /* Bedienhoehen - Regel 2 */
  --h:    42px;
  --h-sm: 34px;
  --h-xs: 28px;

  /* Bewegung */
  --ease: cubic-bezier(.32, .72, 0, 1);

  /* Schraffur - "noch nicht" */
  --hatch: repeating-linear-gradient(-45deg,
            var(--hair-2) 0 1px, transparent 1px 5px);
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --plane:      #090C0F;
  --frame:      #11151A;
  --surface:    #171C22;
  --surface-2:  #1E252C;
  --surface-3:  #283239;
  --hair:       rgba(255, 255, 255, .07);
  --hair-2:     rgba(255, 255, 255, .15);
  --ink:        #EDF1F6;
  --ink-2:      #ABB9C7;
  --ink-3:      #7E8E9F;
  --act:        #EDF1F6;
  --act-ink:    #10151A;
  --act-hover:  #FFFFFF;
  --lime:       #D8F441;
  --lime-deep:  #D8F441;
  --lime-ink:   #171F06;
  --mint:       #0A5040;  --mint-ink:  #C4E7D5;
  --lavender:   #232B45;  --lav-ink:   #D6DDF2;
  --teal:       #2C7A80;  --teal-ink:  #D3EFF1;
  --up:         #35D07F;
  --down:       #FF6B6F;
  --alarm:      #FF6B60;
  --warn:       #E8A93A;

  /* Limette und Rosenrot tragen auf hellem wie dunklem Glas -
     deshalb hier dieselben Werte. */
  --toast-ok:      #D8F441;
  --toast-ok-ink:  #171F06;
  --toast-err:     #E4566E;
  --toast-err-ink: #23050C;

  --dock-tile:     #14181D;
  --dock-tile-on:  #1D242B;
  --dock-ink:      #FFFFFF;
  --dock-ink-on:   #FFFFFF;
  --dock-rand:     rgba(255, 255, 255, .08);

  --wall-1:  #171D24;
  --wall-2:  #0C1015;
  --wall-hi: #232B34;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --plane:#090C0F; --frame:#11151A; --surface:#171C22;
    --surface-2:#1E252C; --surface-3:#283239;
    --hair:rgba(255,255,255,.07); --hair-2:rgba(255,255,255,.15);
    --ink:#EDF1F6; --ink-2:#ABB9C7; --ink-3:#7E8E9F;
    --act:#EDF1F6; --act-ink:#10151A; --act-hover:#FFFFFF;
    --lime-deep:#D8F441;
    --mint:#0A5040;  --mint-ink:#C4E7D5;
    --lavender:#232B45; --lav-ink:#D6DDF2;
    --teal:#2C7A80;  --teal-ink:#D3EFF1;
    --up:#35D07F; --down:#FF6B6F; --alarm:#FF6B60; --warn:#E8A93A;
    --toast-ok:#D8F441; --toast-ok-ink:#171F06;
    --toast-err:#E4566E; --toast-err-ink:#23050C;
    --dock-tile:#14181D; --dock-tile-on:#1D242B;
    --dock-ink:#FFFFFF; --dock-ink-on:#FFFFFF;
    --dock-rand:rgba(255,255,255,.08);
    --wall-1:#171D24; --wall-2:#0C1015; --wall-hi:#232B34;
  }
}


/* --------------------------------------------------------------- Grundlage */

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--plane);
  color: var(--ink);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui,
        "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
  letter-spacing: -.006em;
}

a { color: inherit; text-decoration: none; }
svg { display: block; }


/* ------------------------------------------------------- Schriftgroessen
   Vier Groessen fuer Zahlen, vier fuer Text. Mehr braucht das System nicht. */

/* Seitentitel in der Markenschrift. Space Grotesk laeuft auf grossen Graden
   ruhig und hat die markanten Formen, die eine Marke traegt; auf 13px waere
   sie schlechter lesbar als die Systemschrift. Deshalb nur hier. */
.t-display {
  font-family: "Space Grotesk", -apple-system, BlinkMacSystemFont, system-ui,
               sans-serif;
  font-size: 28px; font-weight: 600; letter-spacing: -.035em;
  line-height: 1.06; margin: 0;
}
.t-title   { font-size: 15px; font-weight: 600; letter-spacing: -.012em; }
.t-body    { font-size: 14.5px; }
.t-micro   { font-size: 12.5px; color: var(--ink-3); }

/* Label ueber der Zahl - Regel 4 */
.t-label {
  font-size: 11px; font-weight: 640; letter-spacing: .075em;
  text-transform: uppercase; color: var(--ink-3);
}

@media (max-width: 760px) { .t-display { font-size: 21px; } }


/* --------------------------------------------------------- Zahlensatz
   Regel 3. Die Waehrung sitzt klein und hochgestellt, die Dezimalstellen
   und die Einheit sind kleiner und leichter. Erzeugt wird das von den
   Makros `money()` und `amount()` in _ui.html - nie per Hand.             */

.num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: -.03em;
  font-weight: 680;
  line-height: 1.02;
  display: inline-block;
  white-space: nowrap;
}
.num-xl { font-size: 34px; }
.num-lg { font-size: 26px; }
.num-md { font-size: 20px; }
.num-sm { font-size: 16px; }

/* Waehrungszeichen, Dezimalstellen und Einheit sind Anteile der Zahlengroesse.
   Die Untergrenzen sind noetig, weil dieselben Anteile bei `num-xl` (34px)
   passen und bei `num-sm` (16px) zu einem unlesbaren 7-px-Zeichen fuehren -
   ein Prozentzeichen, das niemand mehr entziffert, kann man auch weglassen. */
.num > .cur {
  font-size: max(.58em, 12px); font-weight: 600; vertical-align: .52em;
  margin-right: .06em; letter-spacing: 0; color: inherit;
}
.num > .dec {
  font-size: max(.66em, 13px); font-weight: 620; letter-spacing: -.01em;
}
/* Einheit: h, %, px, dBm, KB */
.num > .un {
  font-size: max(.42em, 11px); font-weight: 560; color: var(--ink-3);
  letter-spacing: 0; margin-left: .18em; vertical-align: .06em;
}

/* Veraenderung unter der Zahl - als Satz, nicht als nackte Zahl. Die
   Mobile-Referenz schreibt sie aus ("that's $132,569.46 more than last
   month"), und das ist der Grund, warum man sie ueberhaupt liest.        */
.delta {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 12.5px; margin-top: 7px; color: var(--ink-3);
}
.delta b { font-weight: 600; font-variant-numeric: tabular-nums; }
.delta.up b   { color: var(--up); }
.delta.down b { color: var(--down); }
.delta svg { width: 13px; height: 13px; flex: 0 0 auto;
             transform: translateY(2px); }
.delta.up svg   { color: var(--up); }
.delta.down svg { color: var(--down); }
.delta.flat svg { display: none; }


/* ================================================================= Huelle
   Der App-Rahmen liegt als gerundete Flaeche auf der Seite und beruehrt den
   Fensterrand nicht. Das ist der Griff, mit dem die Referenz aus einer
   Webseite ein Geraet macht.                                              */

.app { min-height: 100vh; padding: 22px; display: flex; }
.frame {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  background: var(--frame); border-radius: 34px;
  /* Unten Platz fuer das Dock: 68 px Schale, 18 px Abstand zum Rand, Rest
     Luft. Ohne das laege die letzte Karte darunter. */
  padding: 18px 26px calc(112px + env(safe-area-inset-bottom));
}
.frame > .body { display: flex; flex: 1; min-height: 0; }
.frame > .body > main { flex: 1; min-width: 0; }

@media (max-width: 900px) {
  .app { padding: 0; }
  .frame { border-radius: 0;
           padding: 14px 16px calc(112px + env(safe-area-inset-bottom)); }
}


/* --------------------------------------------------------------- Anmeldung
   Beim Laden schiebt sich alles von oben herein und blendet sich dabei ein -
   Marke zuerst, Karte kurz danach, Fusszeile zuletzt. Versetzt statt
   gleichzeitig: Gleichzeitig sieht nach "aufgeblendet" aus, versetzt nach
   "aufgebaut".

   `both` haelt den Anfangszustand VOR dem Start - ohne das blitzt der Inhalt
   an seiner Endstelle auf, bevor die Verzoegerung greift. */
.anmelden { width: 100%; }
.anmelden-marke { display: flex; justify-content: center; margin-bottom: 26px; }
.anmelden-sprachen {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  margin-top: 14px;
}
.anmelden-sprachen a {
  padding: 3px 2px; color: var(--ink-3); font-size: 12px;
  font-weight: 650; letter-spacing: .08em; line-height: 1;
  text-decoration: none;
  transition: color .16s var(--ease), opacity .16s var(--ease);
}
.anmelden-sprachen a:hover,
.anmelden-sprachen a:focus-visible { color: var(--ink); }
.anmelden-sprachen a.aktiv {
  color: var(--ink-2); text-decoration: underline;
  text-decoration-color: var(--hair-2); text-underline-offset: 5px;
}
.anmelden-fuss  { text-align: center; margin-top: 14px; }

.anmelden-marke,
.anmelden-karte,
.anmelden-sprachen,
.anmelden-fuss {
  animation: rein .52s cubic-bezier(.16, .84, .44, 1) both;
}
.anmelden-marke { animation-delay: .02s; }
.anmelden-karte { animation-delay: .10s; }
.anmelden-sprachen { animation-delay: .18s; }
.anmelden-fuss  { animation-delay: .24s; }

@keyframes rein {
  from { opacity: 0; transform: translateY(-22px); }
  to   { opacity: 1; transform: none; }
}

/* Bewegung ist hier Schmuck - wer sie abbestellt hat, sieht die Anmeldung
   sofort und vollstaendig. */
@media (prefers-reduced-motion: reduce) {
  .anmelden-marke, .anmelden-karte, .anmelden-sprachen,
  .anmelden-fuss { animation: none; }
}


/* Eine Spalte aus Kaertchen statt einer langen Karte mit Trennlinien: Jede
   Kategorie steht fuer sich, aber alle liegen in EINEM Formular - ein Klick
   auf Speichern reicht weiterhin fuer alles. */
.kartenspalte { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
/* Zwischentitel ueber einer Gruppe von Einstellungskarten. Acht Karten in
   einer Reihe liessen nicht erkennen, was zusammengehoert; drei Gruppen
   ordnen sie nach dem, worauf sie wirken: Geraet, Matrixdisplay, Runddisplay.
   Bewusst KEINE eigene Karte und kein zweites Akkordeon - der Titel ist eine
   Beschriftung, kein Bedienelement. `akkordeon.js` fragt `:scope > .card`
   und laesst ihn deshalb unbehelligt. */
/* Eingerueckt auf die Kante der Kartentitel darunter statt auf einen frei
   gegriffenen Wert - so steht die ganze Spalte auf einer Fluchtlinie. 23px,
   nicht 22: Die Karte hat neben ihrem Innenabstand noch einen 1px-Rahmen,
   und ohne den Ausgleich stehen die Titel um genau dieses Pixel versetzt.
   Die Rangordnung kommt ohnehin aus Groesse und Farbe von `.t-label`,
   nicht aus dem Einzug. */
.einstellungs-gruppe { padding: 10px 23px 0; }
.einstellungs-gruppe:first-child { padding-top: 0; }
.einstellungs-gruppe .t-label { display: block; }
.einstellungs-gruppe .muted { display: block; margin-top: 3px; }

/* ------------------------------------------------------------- Akkordeon
   Ein klassisches Akkordeon: alle fuenf Ueberschriften stehen immer da, ein
   Klick oeffnet eine Kategorie und schliesst dabei jede andere. Zwei
   fruehere Anlaeufe (Skalieren nach reactbits/scroll-stack, dann ein
   Sticky-Register mit Scroll-Vorhang) verlangten beide, dass Kategorien erst
   scrollend zum Vorschein kommen - genau das Gegenteil von "man sieht alle
   Kategorien".

   `.head` wird zum Knopf: die ganze Zeile klickbar, nicht nur ein Pfeilchen
   daneben. Der Kategorie-Kreis bleibt dabei ein <span> statt eines zweiten,
   verschachtelten Knopfs - er hat nie eine eigene Funktion gehabt. */
button.head {
  width: 100%; background: none; border: 0; padding: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
  border-radius: var(--r-in);
  /* `.head` bringt sonst `margin-bottom: 16px` mit - Abstand zum Koerper
     darunter. Zugeklappt ist der Koerper aber null Pixel hoch, und der
     Rand bliebe trotzdem stehen: 20px Kartenpolster oben, aber 36px (16 +
     20px Kartenpolster unten) darunter - die Ueberschrift sass dadurch
     sichtbar zu hoch statt mittig in der Kachel. Der Abstand wandert
     stattdessen als `padding-top` in den Koerper (naechste Regel): Zu
     verschwindet er mit ihm, offen erscheint er wieder. */
  margin-bottom: 0;
}
button.head:focus-visible { outline: 2px solid var(--act); outline-offset: 3px; }

.stapel-pfeil {
  display: grid; place-items: center; color: var(--ink-3);
  transition: transform .25s var(--ease);
}
.card.offen .stapel-pfeil { transform: rotate(180deg); }

/* Der Koerper animiert per gemessener `height`, gesetzt von `akkordeon.js` -
   nicht per `grid-template-rows: 0fr -> 1fr`. Der Grid-Kniff sah auf dem
   Papier eleganter aus (keine Messung noetig), scheiterte hier aber
   zweimal auf verschiedene Art: Eine Kachel, deren `offen`-Klasse
   synchron beim Laden gesetzt wurde, blieb bei `0px` haengen; eine Kachel,
   die als einzige ohne begleitende Gegenbewegung wieder zuklappte, blieb
   bei ihrer offenen Hoehe haengen. Beide Male loeste sich der Uebergang
   nicht auf. Gemessene Pixelwerte (0 -> `scrollHeight`, `scrollHeight` -> 0)
   sind langweiliger, aber verlaesslich - dieselbe Technik, die Akkordeons
   schon vor CSS Grid benutzt haben. */
.stapel-koerper {
  overflow: hidden;
  height: 0px;
  transition: height .35s var(--ease), visibility 0s linear .35s;
  /* Zu, ohne sichtbar zu sein - sonst bliebe ein Radio oder ein Regler in der
     zugeklappten Kategorie mit der Tabulatortaste erreichbar, unsichtbar und
     bedienbar zugleich. Verzoegert auf 0s nach der Klappzeit: Beim Oeffnen
     greift `.offen` sofort (keine eigene Verzoegerung dort), beim Schliessen
     erst, wenn die Flaeche wirklich auf null ist. */
  visibility: hidden;
}
.card.offen .stapel-koerper { visibility: visible; }
.stapel-koerper-innen { padding-top: 16px; }

@media (prefers-reduced-motion: reduce) {
  .stapel-koerper, .stapel-pfeil { transition: none; }
}

/* ------------------------------------------------------------------- Dock
   Die Werkzeugleiste stand links am Rand und rutschte unter 900 px an den
   unteren. Zwei Anordnungen fuer dieselbe Sache; jetzt ist es eine.

   Aussen Glas wie Modale und Toasts, innen dunkle Kaestchen mit limettenen
   Zeichen. Regel 3 bleibt gueltig: Handeln ist dunkel, Limette sagt "hier
   schauen". Ein limettenes Kaestchen waere ein limetteter Knopf - den gibt es
   nicht.

   Die Vergroesserung nach Mausnaehe rechnet `dock.js`; hier steht nur, was
   ohne Skript gilt. */
.dock {
  position: fixed; z-index: 90;
  left: 50%; transform: translateX(-50%);
  bottom: max(18px, env(safe-area-inset-bottom));
  pointer-events: none;                /* nur die Schale faengt ab */
}

/* Die Hoehe der Schale ist FEST. In der Vorlage waechst nur eine unsichtbare
   Huelle darueber mit, damit nichts abgeschnitten wird - die Schale selbst
   behaelt `panelHeight`, und die Kaestchen wachsen oben aus ihr heraus. Waechst
   die Schale mit, hebt und senkt sich der ganze Balken beim Zeigen, und das
   ist nicht die Bewegung, um die es geht.

   Kontur wie bei den Inhaltskacheln (`--hair`) statt einer eigenen, staerkeren
   - es soll dieselbe Oberflaeche sein. Und weniger Unschaerfe als bei Modalen:
   Die liegen ueber dem Inhalt und sollen ihn zuruecknehmen, das Dock liegt
   daneben und soll ihn durchscheinen lassen. */
.dock-panel {
  pointer-events: auto;
  display: flex; align-items: flex-end; gap: 9px;
  height: 56px;
  padding: 0 10px 8px;
  border-radius: 20px;
  background: color-mix(in srgb, var(--surface) 74%, transparent);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  border: 1px solid var(--hair);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18),
              0 16px 36px -20px rgba(0, 0, 0, .5);
}

.dock-item {
  position: relative;
  display: block; flex: 0 0 auto;
  text-decoration: none;
  /* Die Groesse kommt vom Skript. Ohne Skript bleibt der Grundwert stehen -
     dann ist es ein gewoehnliches Dock ohne Lupe, aber vollstaendig
     bedienbar. */
  width: var(--dock-size, 40px); height: var(--dock-size, 40px);
}

/* Eine seiteneigene Handlung (etwa "Ans Gerät senden") lebt in derselben
   Schale wie die Navigation, sieht aber bewusst anders aus: ein breites
   Pill mit Text statt eines Kaestchens mit Zeichen. Handeln und Navigieren
   sollen sich unterscheiden lassen, ohne dass man den Unterschied erst
   lesen muss. Ausdruecklicher Sonderfall zu Regel 5 (dunkel, niemals
   limetten): Dieser eine Knopf im Dock ist limette, auf Wunsch.

   Feste 40px statt `var(--dock-size)`: Die Groesse kommt nur bei den
   `.dock-item`-Kaestchen vom Skript (siehe dock.js), dieser Knopf ist kein
   `.dock-item` und bekommt nie eine eigene `--dock-size` gesetzt - die
   Variable stuende hier immer auf ihrem Ersatzwert, ein Name ohne Zweck.
   Keine eigene Marge: Der Abstand zum Nachbarn kommt allein aus dem `gap`
   von `.dock-panel`. */
.dock-action {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 8px; flex: 0 0 auto;
  height: 40px; padding: 0 18px;
  border: 0; border-radius: 999px;
  background: var(--lime); color: var(--lime-ink);
  font: inherit; font-size: 13.5px; font-weight: 640; white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 6px 14px -10px rgba(0, 0, 0, .5);
  transition: filter .16s var(--ease), transform .16s var(--ease);
}
.dock-action:hover { filter: brightness(.95); }
.dock-action:active { transform: scale(.96); }
.dock-action svg { width: 16px; height: 16px; flex: 0 0 auto; }

/* --------------------------------------------------------- Sprachumschalter
   Kein sichtbares natives Select: Dessen Systempfeil liegt je nach Browser
   trotz `appearance:none` ueber dem Kuerzel. Der eigene Kreis oeffnet ein
   kleines, semantisches Menue; die eigentliche Wahl bleibt ein normales
   Formular und funktioniert deshalb auch ohne JavaScript. */
.language-menu {
  position: relative; width: var(--h); height: var(--h);
  flex: 0 0 var(--h);
}
.language-menu summary {
  display: grid; place-items: center; width: var(--h); height: var(--h);
  padding: 0; border: 0; border-radius: 50%; list-style: none;
  background: var(--surface); color: var(--ink); cursor: pointer;
  font-size: 11.5px; font-weight: 680; line-height: 1;
  box-shadow: inset 0 0 0 1px var(--hair-2);
  transition: background .16s var(--ease), transform .12s var(--ease);
}
.language-menu summary::-webkit-details-marker { display: none; }
.language-menu summary::marker { content: ""; }
.language-menu summary:hover { background: var(--surface-2); }
.language-menu summary:active { transform: scale(.96); }
.language-menu summary:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 1px var(--hair-2),
              0 0 0 2px var(--surface), 0 0 0 4px var(--ink);
}
.language-options {
  position: absolute; z-index: 80; top: calc(100% + 8px); right: 0;
  display: flex; gap: 4px; padding: 6px;
  border: 1px solid var(--hair-2); border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: 0 6px 14px -10px rgba(0, 0, 0, .5);
}
.language-options button {
  display: grid; place-items: center; width: var(--h-sm); height: var(--h-sm);
  padding: 0; border: 0; border-radius: 50%; cursor: pointer;
  background: transparent; color: var(--ink-2); font: inherit;
  font-size: 11px; font-weight: 680;
}
.language-options button:hover { background: var(--surface-2); color: var(--ink); }
.language-options button.aktiv { background: var(--act); color: var(--act-ink); }

/* -------------------------------------------------------- Guide / Neuigkeiten
   Nummern und Verbindungslinie folgen dem React-Bits-Stepper, Farben,
   Radien und Schriften bleiben vollstaendig im kajing-System. */
.guide-modal { z-index: 260; }
/* Hoehere Spezifitaet statt `!important`: Dieselbe Wirkung gegen
   `.modal .sheet-card`, ohne eine Ausnahme von der eigenen Regel. */
.modal .sheet-card.guide-card { max-width: 620px; overflow: hidden; }
/* Punktleiste links, Schliesskreuz rechts - dieselbe Anordnung wie im
   Kopf der uebrigen Modale. */
.guide-top { display: flex; align-items: center; gap: 12px; }
.guide-top .guide-steps { flex: 1; }
.guide-steps { display: flex; align-items: center; justify-content: space-between;
  position: relative; margin-bottom: 26px; }
.guide-steps::before { content:""; position:absolute;
  left:calc(var(--h-sm) / 2); right:calc(var(--h-sm) / 2);
  top:calc(var(--h-sm) / 2 - 1px); height:2px; background:var(--surface-3); }
.guide-step {
  position: relative; z-index: 1; display: grid; place-items: center;
  width: var(--h-sm); height: var(--h-sm); flex: 0 0 var(--h-sm);
  border-radius: 50%;
  background: var(--surface-3); color: var(--ink-3);
  font-size: 12px; font-weight: 700; transition: .2s var(--ease);
}
.guide-step.done, .guide-step.current {
  background: var(--act); color: var(--act-ink);
}
.guide-step.done::before { content: "✓"; }
.guide-step.done { font-size: 0; }
.guide-step.done::before { font-size: 13px; }
.guide-pages { min-height: 210px; position: relative; }
.guide-page { display: none; }
.guide-page.active { display: block; animation: guide-in .3s var(--ease) both; }
.guide-page.from-left { --guide-x: -24px; }
.guide-page.from-right { --guide-x: 24px; }
.guide-page h2 { margin: 8px 0 12px; }
.guide-page p { color: var(--ink-2); line-height: 1.65; max-width: 52ch; }
.guide-actions { display: flex; align-items: center; gap: 12px;
  margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--hair); }
.guide-actions [data-guide-count] { flex: 1; text-align: center; }
.guide-leaving { opacity: 0; transition: opacity .2s; }
@keyframes guide-in { from { opacity: 0; transform: translateX(var(--guide-x,24px)); }
  to { opacity: 1; transform: none; } }

/* ----------------------------------------------------------- Meme-Galerie */
.toggle-control {
  position: relative; display: inline-flex; align-items: center; gap: 9px;
  min-height: var(--h-sm); cursor: pointer; user-select: none;
}
.toggle-control input {
  position: absolute; width: 1px; height: 1px; margin: 0;
  opacity: 0; pointer-events: none;
}
.toggle-track {
  position: relative; width: 48px; height: 28px; flex: 0 0 auto;
  border: 1px solid var(--hair-2); border-radius: var(--r-pill);
  background: var(--surface-3);
  transition: background .18s var(--ease), border-color .18s var(--ease);
}
.toggle-track::after {
  content: ""; position: absolute; width: 22px; height: 22px;
  top: 2px; left: 2px; border-radius: 50%; background: var(--surface);
  box-shadow: 0 1px 4px var(--hair-2);
  transition: transform .18s var(--ease), background .18s var(--ease);
}
.toggle-control input:checked + .toggle-track {
  border-color: var(--act); background: var(--act);
}
.toggle-control input:checked + .toggle-track::after {
  transform: translateX(20px); background: var(--act-ink);
}
.toggle-control input:focus-visible + .toggle-track {
  box-shadow: 0 0 0 4px var(--hair);
}
.toggle-state { min-width: 82px; color: var(--ink-2); font-size: 13.5px;
  font-weight: 620; white-space: nowrap; }
.toggle-control input:checked ~ .toggle-state .toggle-off,
.toggle-control input:not(:checked) ~ .toggle-state .toggle-on { display: none; }

.meme-grid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr));
  gap: 10px; margin-top: 12px; }
.meme-choice { position: relative; cursor: pointer; }
.meme-choice input { position: absolute; opacity: 0; pointer-events: none; }
.meme-tile { display: block; padding: 8px; border: 1px solid var(--hair-2);
  border-radius: var(--r-in); background: var(--surface-2);
  transition: transform .16s var(--ease), box-shadow .16s, border-color .16s; }
.meme-tile img { width: 100%; aspect-ratio: 1; display: block;
  border-radius: 50%; object-fit: cover; background: var(--panel-black); }
.meme-tile b { display: block; margin-top: 7px; text-align: center;
  font-size: 12px; font-weight: 650; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.meme-choice input:checked + .meme-tile {
  border-color: var(--act); box-shadow: inset 0 0 0 1px var(--act); }
.meme-choice input:focus-visible + .meme-tile {
  box-shadow: 0 0 0 4px var(--hair-2); }
.meme-admin-item { display: grid; gap: 8px; justify-items: start; }
.meme-customer-item { display: grid; gap: 8px; align-content: start; }
.meme-customer-item .meme-choice { width: 100%; }
.meme-upload { display: grid;
  grid-template-columns: minmax(150px,.8fr) minmax(240px,1.3fr) auto;
  gap: 12px; align-items: end; }
.round-meme { position: absolute; inset: 0; z-index: 4; display: none;
  border-radius: 50%; overflow: hidden; background: var(--panel-black); }
.round-meme img { width: 100%; height: 100%; object-fit: cover; display: block; }
.round-meme.playing { display: block; }

/* ------------------------------------------------------ Provisioning */
.page-intro { max-width: 64ch; margin: 6px 0 0; color: var(--ink-2); line-height: 1.5; }
.admin-tabs {
  display: flex; gap: 8px; margin: -4px 0 18px; padding-bottom: 2px;
  overflow-x: auto; scrollbar-width: none;
}
.admin-tabs::-webkit-scrollbar { display: none; }
.admin-tabs .pill { flex: 0 0 auto; }
.notice-card {
  display: flex; align-items: center; gap: 12px; margin-bottom: 14px;
  padding: 14px 16px; border: 1px solid var(--hair);
  border-radius: var(--r-in); background: var(--surface-2);
}
.notice-card .chip { flex: 0 0 auto; white-space: nowrap; }
.provision-progress {
  display: grid; grid-template-columns: repeat(4, minmax(0,1fr));
  gap: 0; margin: 0 0 14px; padding: 16px 18px;
  border: 1px solid var(--hair); border-radius: var(--r-card);
  background: var(--surface); list-style: none;
}
.provision-progress li {
  position: relative; display: flex; align-items: center; gap: 9px;
  min-width: 0; color: var(--ink-3); font-size: 12.5px;
}
.provision-progress li:not(:last-child)::after {
  content: ""; position: absolute; z-index: 0; left: 34px; right: 8px;
  top: 14px; height: 1px; background: var(--hair-2);
}
.provision-progress li span {
  position: relative; z-index: 1; display: grid; place-items: center;
  width: 28px; height: 28px; flex: 0 0 28px; border-radius: 50%;
  background: var(--surface-3); color: var(--ink-2); font-weight: 700;
}
.provision-progress li b { position: relative; z-index: 1; padding-right: 8px; background: var(--surface); }
.provision-progress li.current { color: var(--ink); }
.provision-progress li.current span { box-shadow: inset 0 0 0 1px var(--ink); }
.provision-progress li.done span { background: var(--act); color: var(--act-ink); }
.provision-flow { display: grid; gap: 14px; }
.provision-step { display: grid; grid-template-columns: 36px minmax(0,1fr); gap: 16px; }
/* `--surface-2` statt einer Mischung aus `--surface` und `--frame`:
   Im Hellmodus sind beide #FFFFFF, die Mischung ergab dort exakt Weiss
   und der Zustand "noch nicht dran" war nicht zu sehen. `--surface-2`
   ist genau der Token fuer eine zurueckgesetzte Flaeche. */
.provision-step.pending { background: var(--surface-2); }
.step-number {
  display: grid; place-items: center; width: 36px; height: 36px;
  border-radius: 50%; background: var(--act); color: var(--act-ink);
  font-size: 13px; font-weight: 720;
}
.provision-step.pending .step-number { background: var(--surface-3); color: var(--ink-3); }
.step-content { min-width: 0; }
.step-content .head { align-items: flex-start; margin-bottom: 16px; }
.step-content .head p, .provisioned-devices .head p,
.admin-two-column .head p, .card > .head p {
  margin: 5px 0 0; color: var(--ink-2); font-size: 13px; line-height: 1.5;
}
.selected-device-summary {
  display: grid; grid-template-columns: minmax(140px,1.2fr) repeat(2,minmax(140px,1fr)) auto;
  gap: 14px; align-items: end; padding: 15px 16px;
  border-radius: var(--r-in); background: var(--surface-2);
}
.selected-device-summary span, .selected-device-summary b,
.selected-device-summary code { display: block; }
.provision-capture-form {
  display: grid; grid-template-columns: minmax(220px,1fr) minmax(220px,1fr) auto;
  gap: 14px; align-items: end;
}
.field small { display: block; margin: 6px 4px 0; color: var(--ink-3); font-size: 11.5px; line-height: 1.4; }
.step-wait, .empty-state {
  padding: 16px; border: 1px dashed var(--hair-2); border-radius: var(--r-in);
  color: var(--ink-3); background: var(--hatch);
}
.empty-action, .step-action, .connection-result, .wifi-guide {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 16px; border-radius: var(--r-in); background: var(--surface-2);
}
.empty-action p, .step-action p, .connection-result p, .wifi-guide p {
  margin: 5px 0 0; color: var(--ink-2); font-size: 13px; line-height: 1.5;
}
.firmware-assign-form {
  display: grid; grid-template-columns: minmax(240px,1fr) auto;
  gap: 12px; align-items: end; margin-bottom: 12px;
}
.step-action { flex-wrap: wrap; }
.step-action > div { flex: 1 1 320px; }
.step-action [data-flash-note] { flex: 1 1 100%; }
.step-hint { margin: 10px 2px 0; }
.qr-step {
  display: grid; grid-template-columns: 92px minmax(180px,1fr) auto;
  gap: 16px; align-items: center; padding: 16px; border-radius: var(--r-in);
  background: var(--surface-2);
}
.provision-qr.large { width: 92px; height: 92px; }
.setup-code.big { margin-top: 5px; font-size: 22px; }
.qr-step p { margin: 6px 0 0; color: var(--ink-2); font-size: 13px; }
.wifi-guide { display: block; margin-bottom: 10px; }
.wifi-guide ol { margin: 10px 0 0; padding-left: 20px; color: var(--ink-2); font-size: 13px; line-height: 1.65; }
.connection-result { justify-content: flex-start; }
.connection-result > div { flex: 1; }
/* Drei Schweregrade, jeder mit dem Token, der ihn im ganzen System tragt:
   `--up` fuer "geprueft" (wie `.state.on` und `.delta.up`), `--warn` fuer
   "sieh nochmal hin", `--ink-3` fuer "wartet noch". Limette blieb hier
   aussen vor: Sie ist der Akzent fuer "hier schauen", nicht fuer Erfolg.
   Der Punkt daneben nimmt dieselbe Farbe wie der Balken - vorher war der
   Balken rot und der Punkt bernstein bei derselben Aussage. */
.connection-result.success { box-shadow: inset 3px 0 0 var(--up); }
.connection-result.warning { box-shadow: inset 3px 0 0 var(--warn); }
.connection-result.waiting { box-shadow: inset 3px 0 0 var(--ink-3); }
.status-dot { width: 10px; height: 10px; flex: 0 0 10px; border-radius: 50%;
              background: var(--warn); }
.connection-result.waiting .status-dot { background: var(--ink-3); }
.provisioned-devices { margin-top: 14px; }
.provision-device-list, .release-list { display: grid; gap: 8px; }
.provision-device-item, .release-list article {
  display: grid; grid-template-columns: minmax(180px,1.4fr) auto minmax(150px,.7fr) auto;
  gap: 12px; align-items: center; padding: 12px 14px;
  border: 1px solid var(--hair); border-radius: var(--r-in);
}
.provision-device-item.selected { border-color: var(--hair-2); background: var(--surface-2); }
.admin-two-column { display: grid; grid-template-columns: minmax(280px,.8fr) minmax(360px,1.2fr); gap: 14px; }
.media-admin-layout { grid-template-columns: minmax(280px,.65fr) minmax(420px,1.35fr); }
.release-list article { grid-template-columns: minmax(0,1fr) auto; }
.release-list article p { margin: 7px 0 0; color: var(--ink-2); font-size: 13px; }
.firmware-digest { overflow-wrap: anywhere; }
.access-card { max-width: 680px; margin: 64px auto; }
.access-card h1 { margin: 18px 0 10px; }
.access-card p { max-width: 58ch; margin: 0 0 24px; color: var(--ink-2); }
/* Hardware-ID und Kopplungscode auf der Einrichtungsseite. Eigene Klasse
   statt eines Inline-Rands, damit der Abstand hier derselbe bleibt wie bei
   den uebrigen inneren Karten. */
.setup-facts { margin: 18px 0; }
.update-version { max-width: 180px; }
.admin-language-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 12px; }
.admin-language-grid fieldset { min-width: 0; margin: 0; }
.admin-language-grid legend { padding: 0 4px; }
.admin-language-grid .field + .field { margin-top: 12px; }
.provision-list { display: grid; gap: 10px; }
.provision-row { display: grid; grid-template-columns: 72px minmax(180px,1.4fr)
  minmax(160px,1fr) auto; gap: 14px; align-items: center; padding: 12px;
  border: 1px solid var(--hair); border-radius: var(--r-in); }
.provision-qr { width: 64px; height: 64px; border-radius: var(--r-chip);
  background: var(--paper); padding: 4px; }
.setup-code { letter-spacing: .18em; font-weight: 700; }
.label-sheet { display: grid; grid-template-columns: repeat(3,1fr); gap: 12px; }
/* Das Etikett wird GEDRUCKT und folgt deshalb bewusst nicht dem Thema:
   Papier ist weiss, die Tinte dunkel, in beiden Modi. Deshalb hier die
   Papier-Tokens statt der Flaechen-Tokens der Oberflaeche. */
.device-label { border: 1px dashed color-mix(in srgb, var(--paper-ink) 55%, var(--paper));
  padding: 14px; min-height: 180px;
  display: grid; grid-template-columns: 1fr 92px; gap: 10px; align-items: center;
  break-inside: avoid; background: var(--paper); color: var(--paper-ink); }
.device-label img { width: 92px; height: 92px; }
.device-label .wordmark { color: var(--paper-ink); }
.label-hint { margin-top: 14px; font-size: 12px; }
.label-code { display: block; margin-top: 4px; font-size: 20px; }
.label-id { margin-top: 8px; display: block; font-size: 10px; }
@media (max-width: 760px) {
  .provision-progress { grid-template-columns: 1fr; gap: 9px; }
  .provision-progress li:not(:last-child)::after { left: 14px; right: auto; top: 28px; bottom: -9px; width: 1px; height: auto; }
  .provision-step { grid-template-columns: 1fr; }
  .selected-device-summary, .provision-capture-form,
  .firmware-assign-form, .qr-step, .provision-device-item { grid-template-columns: 1fr; }
  .empty-action, .step-action, .connection-result { align-items: flex-start; flex-direction: column; }
  .admin-two-column, .media-admin-layout, .admin-language-grid { grid-template-columns: 1fr; }
  .meme-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .meme-upload { grid-template-columns: 1fr; }
  .provision-row { grid-template-columns: 64px 1fr; }
  .provision-row > :nth-child(n+3) { grid-column: 1 / -1; }
  .label-sheet { grid-template-columns: 1fr; }
}
@media print {
  body.print-labels .app { padding: 0; }
  body.print-labels .frame { padding: 0; border-radius: 0; }
  body.print-labels .bar, body.print-labels .dock, body.print-labels .crumbs { display:none !important; }
  .label-sheet { grid-template-columns: repeat(3,1fr); gap: 6mm; }
}

/* Die Kaestchen folgen dem Thema. Fest dunkel standen sie im Hellmodus als
   schwarzer Block in einer hellen Oberflaeche - die Leiste soll dazugehoeren
   und nicht darauf liegen. */
.dock-box {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  border-radius: 30%;
  background: var(--dock-tile);
  color: var(--dock-ink);
  box-shadow: inset 0 0 0 1px var(--dock-rand);
  transition: background .16s var(--ease), box-shadow .16s var(--ease),
              color .16s var(--ease);
}
.dock-box svg {
  width: 46%; height: 46%;
  stroke-width: 1.8;
}
.dock-item:hover .dock-box { background: var(--dock-tile-on); }

/* Die aktuelle Seite leuchtet - weiss, in beiden Themen. Auf Dunkel ist das
   ein Schein; auf Hell wird das Kaestchen heller als seine Nachbarn und der
   Schein hebt es aus der Glasleiste heraus. Dasselbe Mittel, beide Male
   sichtbar - und kein limettenes Kaestchen, das wie ein Knopf aussaehe. */
.dock-item.on .dock-box {
  background: var(--dock-tile-on);
  color: var(--dock-ink-on);
  /* Knapp und leise: ein Saum und ein Schein, der kaum ueber das Kaestchen
     hinausreicht. Weiter aussen und heller sah es aus, als waere etwas
     kaputt - der Schein soll sagen "hier stehst du", nicht "Achtung". */
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22),
              0 0 8px -3px rgba(255, 255, 255, .45);
}
/* Der Punkt darunter traegt die Aussage, wenn der Schein nicht ankommt - etwa
   auf einem Bildschirm mit wenig Kontrast. Deshalb `--ink` und nicht Weiss:
   Er muss sich in beiden Themen von der Leiste abheben. */
.dock-item.on::after {
  content: ""; position: absolute; left: 50%; bottom: -7px;
  width: 4px; height: 4px; margin-left: -2px;
  border-radius: 50%; background: var(--ink);
}

/* Die Beschriftung steht UEBER dem Kaestchen und nur beim Zeigen. Im Dock ist
   kein Platz fuer Text, und ein Zeichen allein ist nicht immer eindeutig. */
.dock-label {
  position: absolute; left: 50%; bottom: calc(100% + 10px);
  transform: translateX(-50%);
  padding: 5px 10px; border-radius: var(--r-chip);
  font-size: 12px; line-height: 1; white-space: nowrap;
  color: var(--ink);
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  opacity: 0; pointer-events: none;
  transition: opacity .14s var(--ease);
}
.dock-item:hover .dock-label,
.dock-item:focus-visible .dock-label { opacity: 1; }

.dock-item:focus-visible { outline: none; }
.dock-item:focus-visible .dock-box {
  box-shadow: inset 0 0 0 1px var(--dock-rand),
              0 0 0 2px var(--surface), 0 0 0 4px var(--ink);
}

@media (prefers-reduced-motion: reduce) {
  .dock-box, .dock-label { transition: none; }
}


/* ================================================ Kreise und Pillen
   Regel 1 und 2. Alles hier ist entweder ein Kreis oder eine Pille, und
   alles in einer Reihe ist gleich hoch.                                   */

.circle, .pill {
  display: inline-flex; align-items: center; justify-content: center;
  height: var(--h); border: 0; cursor: pointer;
  font: inherit; font-size: 14px; font-weight: 540;
  color: var(--ink); background: var(--surface-2);
  border-radius: var(--r-pill);
  transition: background .16s var(--ease), color .16s var(--ease),
              transform .12s var(--ease);
}
.circle { width: var(--h); flex: 0 0 auto; padding: 0; }
.pill   { gap: 9px; padding: 0 17px; white-space: nowrap; }

.circle svg, .pill svg { width: 18px; height: 18px; flex: 0 0 auto; }

/* Umrissene Variante - der Ruhezustand der Referenz: weiss mit Haarlinie */
.circle.line, .pill.line {
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--hair-2);
}
.circle.line:hover, .pill.line:hover { background: var(--surface-2); }
.circle:hover, .pill:hover { background: var(--surface-3); }
.circle:active, .pill:active { transform: scale(.96); }

/* Handeln. Regel 5: dunkel, niemals limetten. */
.circle.act, .pill.act {
  background: var(--act); color: var(--act-ink); box-shadow: none;
}
.circle.act:hover, .pill.act:hover { background: var(--act-hover); }

/* Aktiver Navigationszustand - dieselbe dunkle Flaeche wie Handeln */
.pill.on { background: var(--act); color: var(--act-ink); box-shadow: none; }

.circle.sm, .pill.sm { height: var(--h-sm); font-size: 13.5px; }
.circle.sm { width: var(--h-sm); } .pill.sm { padding: 0 14px; gap: 7px; }
.circle.sm svg, .pill.sm svg { width: 16px; height: 16px; }

.circle.xs, .pill.xs { height: var(--h-xs); font-size: 12.5px; }
.circle.xs { width: var(--h-xs); } .pill.xs { padding: 0 11px; gap: 6px; }
.circle.xs svg, .pill.xs svg { width: 14px; height: 14px; }

.circle.danger { color: var(--down); }
.circle.danger:hover { background: color-mix(in srgb, var(--down) 14%, transparent); }

/* Meldepunkt auf einem Kreis */
.circle .badge {
  position: absolute; width: 8px; height: 8px; border-radius: 50%;
  background: var(--alarm); transform: translate(13px, -12px);
  box-shadow: 0 0 0 2px var(--surface);
}
.circle { position: relative; }

/* Icon im gerundeten Quadrat vor dem Pillentext - so sitzt es in der
   Kopfnavigation der Referenz. */
.pill .box {
  width: 22px; height: 22px; border-radius: 7px; margin-left: -6px;
  display: grid; place-items: center; flex: 0 0 auto;
  background: color-mix(in srgb, currentColor 12%, transparent);
}
.pill.on .box, .pill.act .box {
  background: color-mix(in srgb, var(--act-ink) 18%, transparent);
}

/* Pille mit angehaengtem Kreis - ein Element, wie "Learn more ›●" in der
   Referenz. Der Kreis ueberlappt die Pille um 6px. */
.pill-act { display: inline-flex; align-items: center; }
.pill-act .pill { padding-right: 26px; }
.pill-act .circle { margin-left: -20px; }


/* ================================================================ Karten */

.card {
  background: var(--surface); border-radius: var(--r-card);
  padding: 20px 22px; min-width: 0;
  border: 1px solid var(--hair);
  position: relative;
}
.card.flat { background: var(--surface-2); border-color: transparent; }
.card.inner { border-radius: var(--r-in); padding: 16px 18px; }

/* Streckt eine Karte, die neben einer hoeheren steht: Der Inhalt in `.fill`
   sitzt mittig im uebrigen Platz, die Unterkarte bleibt unten. Ohne das
   klebt alles oben und darunter steht eine leere weisse Flaeche. */
.card.column { display: flex; flex-direction: column; }
/* Flex, nicht `grid` mit `place-items:center`: Dort schrumpft das Kind auf
   seinen Inhalt, und der Panelrahmen misst sich dann an der Leinwand, die er
   selbst enthaelt - eine Rueckkopplung, die die Leinwand bei jedem Bild
   wachsen laesst. Mit Flex und einer festen Breite ist die Messung stabil. */
.card.column > .fill {
  flex: 1; min-height: 0; width: 100%;
  display: flex; align-items: center; justify-content: center;
}

/* Regel 11: eine Karte darf eine ganze Flaeche annehmen. Jede Flaechenfarbe
   bringt ihre Schriftfarbe mit, damit nichts einzeln nachgefaerbt wird. */
.card.lime     { background: var(--lime);     color: var(--lime-ink); border-color: transparent; }
.card.contrast     { background: var(--act);      color: var(--act-ink);  border-color: transparent; }
.card.mint     { background: var(--mint);     color: var(--mint-ink); border-color: transparent; }
.card.lavender { background: var(--lavender); color: var(--lav-ink);  border-color: transparent; }

/* Auf einer Flaechenkarte richten sich Label und Sekundaertext nach der
   Schriftfarbe der Flaeche, nicht nach den globalen Grautoenen. */
.card.lime  .t-label, .card.contrast .t-label,
.card.mint  .t-label, .card.lavender .t-label,
.card.lime  .t-micro, .card.contrast .t-micro,
.card.mint  .t-micro, .card.lavender .t-micro,
.card.lime  .delta,   .card.contrast .delta,
.card.mint  .delta,   .card.lavender .delta,
.card.lime  .num > .un, .card.contrast .num > .un,
.card.mint  .num > .un, .card.lavender .num > .un {
  color: color-mix(in srgb, currentColor 62%, transparent);
}
.card.contrast .circle, .card.lime .circle,
.card.mint .circle, .card.lavender .circle {
  background: color-mix(in srgb, currentColor 12%, transparent);
  color: inherit; box-shadow: none;
}
.card.contrast .circle:hover, .card.lime .circle:hover,
.card.mint .circle:hover, .card.lavender .circle:hover {
  background: color-mix(in srgb, currentColor 20%, transparent);
}

/* Regel 9: der Widgetkopf. Titel links, ein bis zwei Kreise rechts. Dieses
   eine Muster gibt allen Karten denselben Takt.                          */
.head {
  display: flex; align-items: center; gap: 10px; margin-bottom: 16px;
}
.head > .t-title { flex: 1; min-width: 0; }
.head .tools { display: flex; gap: 7px; flex: 0 0 auto; }

/* Regel 10: schwebender Kreis auf der Kartenkante. Ohne Schatten - die
   Ueberlappung allein erzeugt die Ebene. Der Ring blendet ihn gegen den
   Kartenrand ab.                                                          */
.float {
  position: absolute; z-index: 5;
  box-shadow: 0 0 0 5px var(--frame);
}
.float.left   { left: -21px;   top: 50%; margin-top: -21px; }
.float.right  { right: -21px;  top: 50%; margin-top: -21px; }
.float.bottom { bottom: -21px; left: 50%; margin-left: -21px; }

/* Konzentrische Linienzeichnung als Textur auf Flaechenkarten - in der
   Referenz sitzt sie in der Ecke der Limetten- und der Schwarzkarte. */
.texture {
  position: absolute; inset: 0; overflow: hidden; pointer-events: none;
  border-radius: inherit; opacity: .5;
}
.texture svg { position: absolute; right: -14%; bottom: -34%;
               width: 74%; height: auto; color: currentColor; }


/* ------------------------------------------------------------------ Raster
   Zwoelf Spalten. Die Spannweite einer Karte sagt, wie wichtig sie ist.   */

.grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 14px; }
.s2 { grid-column: span 2 } .s3 { grid-column: span 3 }
.s4 { grid-column: span 4 } .s5 { grid-column: span 5 }
.s6 { grid-column: span 6 } .s7 { grid-column: span 7 }
.s8 { grid-column: span 8 } .s9 { grid-column: span 9 }
.s12 { grid-column: span 12 }
.tall { grid-row: span 2 }

@media (max-width: 1180px) {
  .s2, .s3 { grid-column: span 4 }
  .s4, .s5 { grid-column: span 6 }
  .s7, .s8, .s9 { grid-column: span 12 }
}
@media (max-width: 700px) {
  .grid > * { grid-column: span 12 !important; grid-row: auto !important; }
}


/* ------------------------------------------------------------- Seitenkopf */

.bar {
  display: flex; align-items: center; gap: 9px;
  margin-bottom: 20px; flex-wrap: wrap;
}
/* Der Titel darf nicht unter seine Breite schrumpfen, sonst schieben sich
   die Chips darueber. Auf schmalen Geraeten nimmt er eine eigene Zeile. */
.bar .grow { flex: 1 1 260px; min-width: 0; }
@media (max-width: 760px) {
  .bar .grow { flex-basis: 100%; }
  .bar { gap: 8px; }
}

/* Auf schmalen Geraeten traegt das Dock die Navigation - der Geraete-
   Umschalter oben kostet dort nur Platz. */
@media (max-width: 900px) {
  .bar .stack { display: none; }
}

.crumbs { display: flex; align-items: center; gap: 8px;
          font-size: 12.5px; color: var(--ink-3); margin-bottom: 9px; }
.crumbs svg { width: 14px; height: 14px; }
.crumbs .sep { opacity: .5; }

/* Wortmarke kajing - die gelieferte Originaldatei, eingebettet in `_ui.html`.
   Sie ist NICHT gesetzt: Der Balken ueber `j` und `i` ist gezeichnet. Space
   Grotesk bleibt fuer die Seitentitel, damit die Ueberschriften zur Marke
   passen; der Fliesstext bleibt Systemschrift. */
.brand { display: flex; align-items: center; gap: 10px; }
.brand .mark { flex: 0 0 auto; display: grid; place-items: center; }

/* Die fuenf Punkte der Marke: der Reihe nach WAECHST ein Balken zwischen
   der Mitte und einem Arm - `rotate(...)` je Element legt die Richtung
   fest (siehe `mark()` in _ui.html). Zwei Verbindungen wachsen von der
   Mitte nach aussen (Ost, Nord), zwei von einem Arm ZURUECK zur Mitte
   (Sued, West) - nicht alle vier vom selben Punkt, sonst wirkt die Mitte
   wie der einzige Ursprung. Volle Breite deckt beide Punkte UND den Steg
   dazwischen in derselben Farbe ab - optisch wird eine Kapsel daraus,
   genau wie der durchgehende Strich ueber „i" und „j" im Schriftzug aus
   zwei Punkten entstand.

   Gewachsen wird ueber die BREITE, nicht ueber eine Transform-Skalierung:
   Damit bleibt der Drehpunkt exakt am Ankerpunkt (SVG `rotate(a,cx,cy)`
   auf dem umschliessenden `<g>`), ohne die Unschaerfen von
   `transform-origin` in verschiedenen Browsern.

   Vier Elemente teilen sich EIN Zeitraster von 24,8 s (vier Abschnitte a
   6,2 s), jedes mit eigener negativer Startverzoegerung - so laeuft immer
   nur eine Verbindung gleichzeitig. Reihenfolge Ost (Mitte)/Sued (Arm)/
   Nord (Mitte)/West (Arm) - abwechselnd Ursprung Mitte und Ursprung Arm,
   nicht im Uhrzeigersinn. Je Abschnitt: 0,6 s wachsen, 2 s stehen, 0,6 s
   einziehen, 3 s Pause - das sind die Sekundenwerte, um die es ging, nur
   auf Prozente der Gesamtlaufzeit umgerechnet. */
.brand-grow { width: 0; }
@media (prefers-reduced-motion: no-preference) {
  .brand-grow-mid, .brand-grow-west {
    animation-duration: 24.8s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
  }
  .brand-grow-mid  { animation-name: brand-grow-mid; }
  .brand-grow-west { animation-name: brand-grow-west; }
  .brand-link-ce { animation-delay: 0s; }
  .brand-link-sc { animation-delay: -6.2s; }
  .brand-link-cn { animation-delay: -12.4s; }
  .brand-link-wc { animation-delay: -18.6s; }
}
/* Mitte zu Aussenpunkt, Abstand 27,22 + zweimal Radius 9,97 = 47,16 -
   dieselbe Zahl beim Wachsen (rechte Kante wandert) UND beim Einziehen
   (linke Kante wandert, rechte bleibt stehen): Sonst zoege sich der Balken
   beim Einziehen von der falschen Seite zusammen und wirkte wie ein zweites
   Wachsen statt einem Rueckzug. Deshalb wandern hier ZWEI Eigenschaften
   gleichzeitig (`x` und `width`), nicht nur eine.

   Zwei Keyframes statt einem: Sued teilt sich `brand-grow-mid` mit Ost/Nord
   (derselbe `x`-Ankerwert 215,44 - nur die static `y` am Element selbst
   unterscheidet die Richtung), West braucht einen eigenen Ankerwert
   (188,22), weil sein Ankerpunkt auf der anderen Seite der Mitte liegt. */
@keyframes brand-grow-mid {
  0%, 100% { x: 215.44px; width: 0; }
  2.42%    { x: 215.44px; width: 47.16px; }
  10.48%   { x: 215.44px; width: 47.16px; }
  12.90%   { x: 262.60px; width: 0; }
}
@keyframes brand-grow-west {
  0%, 100% { x: 188.22px; width: 0; }
  2.42%    { x: 188.22px; width: 47.16px; }
  10.48%   { x: 188.22px; width: 47.16px; }
  12.90%   { x: 235.38px; width: 0; }
}
/* Der Rahmen der Marke schliesst die Unterlaenge von `j` und `g` ein. Mittig
   ausgerichtet saesse die Schrift dadurch optisch zu hoch. Die Verschiebung
   nach unten um die halbe Unterlaenge (10 von 89,97 Einheiten = 11,1 %,
   davon die Haelfte) rueckt das Versalband in die Mitte der Kachel, ohne die
   Unterlaenge ganz zu ignorieren. */
.wordmark {
  flex: 0 0 auto; display: block;
  color: var(--ink);
  transform: translateY(5.56%);
}

/* Avatarstapel - Referenz setzt ihn mittig in die Kopfleiste */
.stack { display: flex; align-items: center; }
.stack > * { margin-left: -10px; box-shadow: 0 0 0 2.5px var(--frame);
             border-radius: 50%; }
.stack > *:first-child { margin-left: 0; }
.avatar { width: 34px; height: 34px; border-radius: 50%; flex: 0 0 auto;
          display: grid; place-items: center; font-size: 12.5px;
          font-weight: 620; background: var(--surface-3); color: var(--ink-2);
          object-fit: cover; }
.avatar.more { background: var(--act); color: var(--act-ink); }


/* ------------------------------------------------ Einstellungen als Liste
   Bezeichnung links, Bedienelement rechts, Trennlinie nur dazwischen - der
   Bau nativer Systemeinstellungen. Kein Formular mit Beschriftungen ueber
   den Feldern.                                                            */

.rows { display: flex; flex-direction: column; }
.rows > .row {
  display: flex; align-items: center; gap: 16px; padding: 14px 0;
  border-bottom: 1px solid var(--hair);
}
.rows > .row:last-child { border-bottom: 0; }
.rows > .row:first-child { padding-top: 4px; }
.row .name { flex: 1; min-width: 0; }
.row .name b { display: block; font-weight: 540; font-size: 14.5px; }
.row .name span { color: var(--ink-3); font-size: 12.5px; }
/* `max-width` begrenzt die hypothetische Groesse eines Flex-Kindes schon VOR
   der Verteilung, unabhaengig von `flex-shrink` - genau deshalb reicht sie
   hier allein. Ohne sie beanspruchte ein `.seg` mit vielen Optionen (etwa
   die zehn Bestellansichten) seine volle Inhaltsbreite, und weil `.name`
   nebenan mit `flex-basis: 0%` am Verteilkampf gar nicht teilnimmt, ging
   sein gesamter Platz verloren - der Name stand bei 0px Breite. `.seg`
   scrollt ohnehin selbst (`overflow-x: auto`), es braucht die volle Breite
   nicht. */
.row .ctl { flex: 0 0 auto; display: flex; align-items: center; gap: 12px;
            max-width: min(340px, 60%); }
.row .ctl.wide { flex: 1 1 300px; max-width: 340px; }

/* Eine Zeile mit vielen Optionen (die zehn Bestellansichten, die acht
   Globusarten) bekommt eine eigene Zeile fuer ihre Pillen, statt sie neben
   dem Namen in einen 340px schmalen Streifen zu zwaengen. Dort brach das
   letzte Wort mitten ab, mit nichts, das nach "hier geht's weiter" aussah -
   ein Scrollstreifen ist nur dann selbsterklaerend, wenn eine ganze Pille
   sichtbar bleibt, die zur naechsten locken kann.

   `flex: 1 1 100%` auf beiden Kindern reicht: In einer Reihe, die umbrechen
   darf, verlangt jedes Kind die volle Breite, also bleibt fuer keine zwei
   nebeneinander Platz - Name und Bedienelement fallen von selbst auf zwei
   Zeilen, ganz ohne `flex-direction: column`. */
.row.mehrzeilig { flex-wrap: wrap; align-items: flex-start; }
.row.mehrzeilig .name { flex: 1 1 100%; }
.row.mehrzeilig .ctl { flex: 1 1 100%; max-width: none; }
.row.mehrzeilig .seg { flex-wrap: wrap; overflow: visible; }

/* Kennzahlenzeile mit Kreis rechts - Screen 3 der Mobile-Referenz */
.metric-row {
  display: flex; align-items: center; gap: 16px; padding: 15px 0;
  border-bottom: 1px solid var(--hair);
}
.metric-row:last-child { border-bottom: 0; }
.metric-row .body { flex: 1; min-width: 0; }

/* Drei Kennzahlen im Kartenfuss, durch Haarlinien getrennt */
.footstats { display: flex; margin-top: 18px;
             border-top: 1px solid var(--hair); }
.footstats > div { flex: 1; padding: 14px 14px 0;
                   border-left: 1px solid var(--hair); }
.footstats > div:first-child { border-left: 0; padding-left: 0; }


/* --------------------------------------------------------------- Eingaben */

input:not([type]), input[type=text], input[type=email], input[type=password],
input[type=number], select {
  width: 100%; height: var(--h); padding: 0 16px;
  font: inherit; font-size: 15px; color: var(--ink);
  background: var(--surface-2); border: 1px solid transparent;
  border-radius: var(--r-pill);
  transition: background .15s, border-color .15s, box-shadow .15s;
}
textarea {
  width: 100%; min-height: 88px; padding: 12px 16px;
  font: inherit; font-size: 15px; line-height: 1.45; color: var(--ink);
  background: var(--surface-2); border: 1px solid transparent;
  border-radius: var(--r-in); resize: vertical;
  transition: background .15s, border-color .15s, box-shadow .15s;
}
/* Eigener Pfeil statt des nativen: Auf macOS zeichnet der Browser sonst ein
   Auf-Ab-Pfeilpaar wie bei einem Zahlenfeld, nicht das einzelne Dreieck, das
   sonst im System fuer "Auswahl" steht - `chev-d` aus `ui.icon()`, nur als
   Datei-URI, weil ein natives Auswahlfeld kein eingebettetes SVG-Element
   annimmt. Zwei Fassungen noetig, weil `currentColor` in einem Hintergrundbild
   nicht die Elementfarbe uebernimmt - siehe Theme-Bloecke darunter. */
select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23687077' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6.4 9.4 12 15l5.6-5.6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 15px;
}
:root[data-theme="dark"] select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237E8E9F' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6.4 9.4 12 15l5.6-5.6'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237E8E9F' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6.4 9.4 12 15l5.6-5.6'/%3E%3C/svg%3E");
  }
}
input:focus, select:focus, textarea:focus {
  outline: none; background: var(--surface);
  border-color: var(--ink); box-shadow: 0 0 0 4px var(--hair);
}
input::placeholder, textarea::placeholder { color: var(--ink-3); }

/* Feld mit Beschriftung darueber - nur dort, wo ein echtes Formular ist */
.field .t-label { display: block; margin-bottom: 7px; }

/* Schieberegler: dicke Bahn, grosser Griff mit Greifpunkten. Der gefuellte
   Teil ist limetten, der Rest schraffiert - "noch nicht", Regel 8. Die
   Fuellung kommt aus --p, das JS beim Ziehen setzt.                       */
input[type=range] {
  -webkit-appearance: none; appearance: none; width: 100%; height: 28px;
  margin: 0; background: transparent; --p: 50%;
}
input[type=range]::-webkit-slider-runnable-track {
  height: 10px; border-radius: 999px;
  background:
    linear-gradient(var(--lime), var(--lime)) 0 / var(--p) 100% no-repeat,
    var(--hatch), var(--surface-3);
}
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 26px; height: 26px; margin-top: -8px;
  border: 0; border-radius: 50%; background: var(--act); cursor: grab;
  box-shadow: 0 0 0 3px var(--surface), 0 2px 8px rgba(0, 0, 0, .22);
}
input[type=range]::-webkit-slider-thumb:active { cursor: grabbing; }
input[type=range]::-moz-range-track {
  height: 10px; border-radius: 999px;
  background: var(--hatch), var(--surface-3);
}
input[type=range]::-moz-range-progress {
  height: 10px; border-radius: 999px; background: var(--lime);
}
input[type=range]::-moz-range-thumb {
  width: 26px; height: 26px; border: 0; border-radius: 50%;
  background: var(--act); box-shadow: 0 0 0 3px var(--surface);
}

/* Die Zahl neben dem Regler ist eine PILLE, kein rechtsbuendiger Text. Sie
   ist anklickbar - ein Regler ist gut zum Suchen und schlecht zum Treffen,
   wer weiss dass er 47 will soll 47 tippen koennen -, und was man anklicken
   kann, braucht eine sichtbare Flaeche mit einer Mitte.

   Zahl und Einheit stehen zusammen mittig darin: `inline-flex` mit
   `justify-content: center` UND `align-items: center`, damit der Block als
   Ganzes auch in der Hoehe der Pille sitzt - nicht an ihrer Grundlinie, die
   durch die Schriftmetrik oberhalb der wirklichen Mitte liegt. Die kleine
   Einheit sitzt auf der Linie der grossen Zahl durch `.zw`s eigenes
   Zentrieren (siehe dort), nicht durch die Ausrichtung hier. Die feste
   Mindestbreite haelt die Pille ruhig, waehrend das Zaehlwerk Stellen
   dazunimmt. */
.readout {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 1px;
  min-width: 92px; height: var(--h-sm);
  padding: 0 12px;
  border-radius: var(--r-pill);
  white-space: nowrap;
  background: var(--surface-2);
}
/* Die Zahl selbst mittig - das Zaehlwerk ist inline-flex und wuerde sonst am
   linken Rand seiner Zelle kleben. */
.readout .num { justify-content: center; }

.readout-tipp { cursor: text; transition: background .14s var(--ease),
                                          box-shadow .14s var(--ease); }
.readout-tipp:hover { background: var(--surface-3); }
.readout-tipp:focus-visible { outline: none;
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--ink); }
.readout-tipp.tippt .num, .readout-tipp.tippt .un { visibility: hidden; }

/* Das Feld legt sich ueber die Pille statt daneben - so springt nichts, und
   die Mitte bleibt die Mitte. Auf dem Handy oeffnet `type=number` mit
   `inputmode=numeric` die Zifferntastatur des Systems; ein Nachbau davon
   waere immer schlechter. */
.readout-feld {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  padding: 0 12px; margin: 0;
  border: 0; border-radius: var(--r-pill);
  text-align: center;
  font: inherit; font-variant-numeric: tabular-nums;
  color: var(--ink); background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--hair-2), 0 0 0 2px var(--surface),
              0 0 0 4px var(--lime-deep);
}
.readout-feld:focus { outline: none; }
/* Die Pfeilchen des Systems weg - der Regler daneben macht dasselbe besser. */
.readout-feld::-webkit-outer-spin-button,
.readout-feld::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}
.readout-feld { -moz-appearance: textfield; appearance: textfield; }


/* ------------------------------------------------------- Regler mit Gummiband
   Nachgebaut aus reactbits.dev/components/elastic-slider. Die Bahn dehnt sich,
   wenn man ueber das Ende hinauszieht, und schnellt beim Loslassen zurueck.
   Die Rechnung steht in elastic.js; hier steht nur, wie es aussieht.

   Die Eingabe BLEIBT ein `<input type="range">` und liegt unsichtbar darueber:
   Sie traegt Tastaturbedienung, Formularwert und Vorlesbarkeit. Die Vorlage
   baut dafuer einen `<div>` nach - damit waere all das weg. */
.es {
  position: relative; flex: 1; min-width: 0;
  display: flex; align-items: center;
  height: 28px;
  opacity: .7;                       /* 0.7 -> 1 beim Zeigen, wie die Vorlage */
}
.es-bahn-huelle {
  width: 100%; height: 6px;          /* 6 -> 12 px beim Zeigen */
  display: block;
}
.es-bahn {
  display: block; width: 100%; height: 100%;
  border-radius: 999px; overflow: hidden;
  background: var(--surface-3);
}
.es-fuell {
  display: block; height: 100%;
  border-radius: 999px; background: var(--lime);
}

/* Die Eingabe liegt ueber der gezeichneten Bahn. Unsichtbar wird nur ihre
   BAHN - der Daumen bleibt stehen: Der runde Griff ist das, woran man den
   Regler erkennt und anfasst. Die Vorlage hat keinen; hier bleibt er.

   Damit teilt sich die Darstellung auf: Die dehnbare Bahn kommt aus den
   Elementen darunter, der Griff aus dem Bedienelement selbst. Beide sind
   mittig im 28 Pixel hohen Feld, also stehen sie uebereinander. */
.es input[type=range] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; cursor: grab; background: transparent;
}
.es input[type=range]:active { cursor: grabbing; }

.es input[type=range]::-webkit-slider-runnable-track {
  height: 28px; background: transparent; border-radius: 0;
}
.es input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 26px; height: 26px; margin-top: 1px;
  border: 0; border-radius: 50%; background: var(--act);
  box-shadow: 0 0 0 3px var(--surface), 0 2px 8px rgba(0, 0, 0, .22);
}
.es input[type=range]::-moz-range-track {
  height: 28px; background: transparent;
}
.es input[type=range]::-moz-range-progress { background: transparent; }
.es input[type=range]::-moz-range-thumb {
  width: 26px; height: 26px; border: 0; border-radius: 50%;
  background: var(--act);
  box-shadow: 0 0 0 3px var(--surface), 0 2px 8px rgba(0, 0, 0, .22);
}

.es input[type=range]:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 3px var(--surface), 0 0 0 5px var(--ink);
}
.es input[type=range]:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 3px var(--surface), 0 0 0 5px var(--ink);
}

/* Das rollende Zaehlwerk daneben, nach
   reactbits.dev/components/counter: je Stelle eine Walze mit den Ziffern 0..9,
   bewegt wird die Walze.

   `align-items: center` statt Grundlinie: Ziffern und Einheit sind
   Flex-Kinder dieses Containers, und `vertical-align` (das die Einheit
   frueher auf die Grundlinie zog) wirkt auf Flex-Kinder ueberhaupt nicht -
   totes CSS. Zentrieren haelt die kleinere Einheit optisch auf der Zahl,
   ohne von der Schriftmetrik abzuhaengen. */
.zw { display: inline-flex; overflow: hidden; align-items: center;
      justify-content: center; }
/* Nur ein Host fuer `Zaehlwerk.baue()` zum Leeren - im Layout unsichtbar,
   damit `.zw`s Zentrieren die Walzen weiter wie direkte Kinder behandelt. */
.zw-ziffern { display: contents; }
.zw-stelle {
  position: relative; display: block;
  width: 0.62em;                     /* Ziffernbreite, damit nichts springt */
  overflow: hidden;
  transition: opacity .18s var(--ease), width .18s var(--ease);
}
.zw-ziffer {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  will-change: transform;
}

/* Farbwaehler: ein runder Fleck. Das Systemfeld liegt unsichtbar darueber,
   weil Browser-Farbfelder in jedem System anders aussehen.                */
.swatch {
  position: relative; width: 36px; height: 36px; border-radius: 50%;
  flex: 0 0 auto; cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .3),
              0 0 0 1px var(--hair-2);
}
.swatch input { position: absolute; inset: 0; width: 100%; height: 100%;
                opacity: 0; padding: 0; border: 0; cursor: pointer; }
.hex { font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
       color: var(--ink-3); min-width: 68px; text-transform: uppercase; }

/* --------------------------------------------------------------- Modal
   Glaseffekt. Regel: Glas gehoert auf schwebende Ebenen, nicht auf flaechige
   Karten - dort erzeugt es Unruhe. Ein Modal ist der Ort, an dem er richtig
   ist: Es liegt ueber dem Inhalt, und dass der durchscheint, sagt genau das.

   Das Blatt selbst ist bewusst NICHT durchsichtig genug, um Farben dahinter
   zu beurteilen - deshalb schliesst die Farbwahl das Modal, statt es offen zu
   lassen. Ein halbtransparentes Fenster vor der Vorschau waere die
   schlechteste beider Welten.                                            */

.modal { display: none; position: fixed; inset: 0; z-index: 200; }
.modal.open { display: grid; place-items: center; padding: 20px; }
/* Rueckwand: gerade so dunkel, dass das Blatt vorne steht. Bei .55 lag die
   ganze Seite im Halbdunkel und man wusste nicht mehr, wo man war. */
.modal .back {
  position: absolute; inset: 0;
  background: rgba(9, 13, 18, .34);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
}
:root[data-theme="dark"] .modal .back { background: rgba(2, 4, 7, .52); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .modal .back { background: rgba(2, 4, 7, .52); }
}
.modal .sheet-card {
  position: relative; width: 100%; max-width: 470px;
  border-radius: var(--r-card); padding: 22px 24px;
  background: color-mix(in srgb, var(--surface) 74%, transparent);
  backdrop-filter: blur(26px) saturate(190%);
  -webkit-backdrop-filter: blur(26px) saturate(190%);
  border: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35),
              0 30px 60px -28px rgba(0, 0, 0, .6);
  animation: sheet-in .22s var(--ease);
}
@keyframes sheet-in {
  from { opacity: 0; transform: translateY(14px) scale(.98); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .modal .sheet-card { animation: none; }
}
/* Schriftfarbe auf Glas: Der Grund scheint durch, deshalb braucht der Text
   den vollen Kontrast und nicht die zweite Stufe. */
.modal .sheet-card .muted { color: var(--ink-2); }


/* ------------------------------------------------------- Logo einpassen
   Die Buehne zeigt das Bild so, wie das runde Display es zeigen wird: rund
   beschnitten, auf schwarzem Grund. Was ausserhalb des Kreises liegt, ist
   abgedunkelt statt weggeschnitten - sonst sieht man beim Verschieben nicht,
   was gleich hereinkommt. */
.logo-stage {
  position: relative; width: 240px; max-width: 100%; aspect-ratio: 1;
  margin: 0 auto; border-radius: 50%; overflow: hidden;
  background: #0C0F13; cursor: grab; touch-action: none;
  box-shadow: inset 0 0 0 1px var(--hair-2);
}
.logo-stage:active { cursor: grabbing; }
.logo-stage canvas { display: block; width: 100%; height: 100%; }
.logo-stage.empty::after {
  content: attr(data-empty); position: absolute; inset: 0;
  display: grid; place-items: center;
  color: var(--ink-3); font-size: 13px;
}

/* Der Systemdialog fuer Dateien laesst sich nicht gestalten - also verstecken
   und das Label als Pille davorsetzen. */
.filepick { position: relative; overflow: hidden; display: inline-flex; }
.filepick input[type=file] {
  position: absolute; inset: 0; opacity: 0; cursor: pointer;
  width: 100%; height: 100%;
}


/* Farbpalette. Nur Farben, die das Panel wirklich zeigen kann, und jedes Feld
   in der Farbe, in der das Panel sie zeigt - nicht in der gewaehlten. Ohne
   diese Unterscheidung waehlt man einen zarten Farbstich und bekommt Weiss. */
.pal { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 4px;
       align-items: center; }
.pal label { position: relative; width: 32px; height: 32px; flex: 0 0 auto;
             cursor: pointer; }
.pal label input { position: absolute; inset: 0; width: 100%; height: 100%;
                   opacity: 0; margin: 0; padding: 0; cursor: pointer; }
.pal label > span {
  display: grid; place-items: center; width: 100%; height: 100%;
  border-radius: 50%; color: rgba(0, 0, 0, .55);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22),
              0 0 0 1px var(--hair-2);
  transition: transform .12s var(--ease);
}
.pal label:hover > span { transform: scale(1.12); }
.pal label input:checked + span {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--ink);
}
.pal label.own > span { color: rgba(0, 0, 0, .6); }

/* „Ohne Untergrund" beim Logo: das schwarze Glas des Rundmoduls, mit einem
   Schraegstrich als Zeichen fuer „keine Farbe". Ohne den Strich waere es von
   einem schwarzen Untergrund nicht zu unterscheiden - und genau der
   Unterschied ist hier der Punkt. */
.pal .swatch.glass {
  background: #0C0F13;
  background-image: linear-gradient(48deg, transparent calc(50% - 1px),
                    rgba(255, 255, 255, .55) calc(50% - 1px),
                    rgba(255, 255, 255, .55) calc(50% + 1px),
                    transparent calc(50% + 1px));
}
/* Das Systemfeld sitzt direkt neben „Eigene" und ist absichtlich klein: Es
   ist der Ausweg, nicht der Hauptweg. */
.pal .own-pick {
  width: 32px; height: 32px; padding: 0; border: 0; border-radius: 50%;
  background: none; cursor: pointer; flex: 0 0 auto; opacity: .55;
}
.pal .own-pick:hover { opacity: 1; }

/* Das Farbfeld in der Zeile ist jetzt ein Knopf: Es oeffnet das Modal. Als
   `<button>` bekommt es Tastaturfokus und wird von Screenreadern als
   bedienbar angekuendigt - ein `<div>` mit Klickhandler nicht. */
button.swatch { border: 0; padding: 0; cursor: pointer; }
button.swatch:focus-visible { outline: none;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .3),
              0 0 0 2px var(--surface), 0 0 0 5px var(--ink); }

/* Segmentkontrolle. Die Referenz laesst sie sichtbar ueber den Rand laufen,
   damit man merkt, dass es weitergeht - deshalb kein Umbruch.             */
.seg {
  display: flex; gap: 6px; overflow-x: auto; padding: 3px 0;
  scrollbar-width: none; -ms-overflow-style: none;
}
.seg::-webkit-scrollbar { display: none; }

/* Als Formularfeld: Die Auswahl steckt in verborgenen Radios, sichtbar ist
   die Pille daneben. So speichert das Formular auch ohne JavaScript - ein
   Klickschalter aus <div>-Elementen kann das nicht. */
.seg label { position: relative; display: inline-flex; cursor: pointer; }
.seg label input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; margin: 0; padding: 0; cursor: pointer;
}
.seg label input:checked + .pill {
  background: var(--act); color: var(--act-ink); box-shadow: none;
}
.seg label input:focus-visible + .pill { box-shadow: 0 0 0 4px var(--hair); }

/* Ein Knopf im Auswahlband, der kein Radio ist (der Sekundenzeiger folgt der
   Ruheanzeige, siehe device.html). Er muss sich anfuehlen wie die anderen -
   sonst sieht der Zustand aus wie ein Fremdkoerper statt wie eine Wahl. */
.seg button.pill[aria-pressed="true"] { background: var(--act); color: var(--act-ink); }

/* Ein Knopf im Auswahlband, der gerade NICHT zur Wahl steht: ausgegraut und
   nicht klickbar. Das ist etwas anderes als `.dim` (greift nicht, bleibt aber
   bedienbar) - hier gibt es die Moeglichkeit im Moment gar nicht, weil eine
   andere Einstellung sie ausschliesst (Ruheanzeige Wanduhr -> kein Globus im
   Ruhezustand; Ruheanzeige Globus -> kein Sekundenzeiger). Ein angehaktes,
   aber abgeschaltetes Radio darf dabei nicht dunkel erscheinen - sonst
   behaupten zwei Knoepfe, sie seien dran. */
.seg label input:disabled + .pill,
.seg label input:disabled:checked + .pill,
.seg button.pill:disabled {
  background: transparent; color: var(--ink-3); opacity: .45;
  pointer-events: none; cursor: not-allowed; box-shadow: none;
}

/* Zeile, deren Einstellung gerade nicht greift. Sie bleibt sichtbar und
   bedienbar - ausgrauen und sperren wuerde verbergen, welcher Wert beim
   Umschalten gilt.

   Der Grund (`.dim-grund`) wird bewusst NICHT mit abgeblendet - er ist das
   Einzige, was die abgeblendete Zeile noch zu sagen hat. Deshalb steht die
   Deckkraft auf den Geschwistern statt auf `.name` als Ganzem: Deckkraft
   eines Elternknotens laesst sich von einem Kind nicht wieder aufheben. */
.row.dim .name > b,
.row.dim .name > span:not(.dim-grund),
.row.dim .ctl { opacity: .42; transition: opacity .2s var(--ease); }

.dim-grund { display: none; margin-top: 6px; font-size: 12px; line-height: 1.45;
  padding-left: 10px;
  /* Kraeftigste Textfarbe statt der zurueckgenommenen: Der Grund ist keine
     Nebenbemerkung, sondern das Einzige, was die tote Zeile noch zu sagen
     hat. `--ink` ist im hellen Thema nahezu Schwarz und im dunklen nahezu
     Weiss - hart kodiertes Schwarz waere im dunklen Thema unlesbar. */
  color: var(--ink); font-weight: 450;
  /* Roter Strich: Er soll beim Ueberfliegen der Seite ins Auge fallen. */
  border-left: 2px solid var(--alarm); }
.row.dim .dim-grund { display: block; }


/* ------------------------------------------------------------- Zustaende */

/* Regel: Zustand nie allein ueber Farbe. Jeder Punkt traegt ein Wort. */
.state { display: inline-flex; align-items: center; gap: 8px;
         font-size: 13.5px; font-weight: 540; white-space: nowrap; }
.state .dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.state.on   { color: var(--up); }
.state.off  { color: var(--down); }
.state.idle { color: var(--ink-3); }
.state.warn { color: var(--warn); }
.state.on   .dot { background: var(--up); animation: breathe 2.6s var(--ease) infinite; }
.state.off  .dot { background: var(--down); }
.state.idle .dot { background: var(--ink-3); }
.state.warn .dot { background: var(--warn); }
@keyframes breathe {
  0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--up) 26%, transparent); }
  50%      { box-shadow: 0 0 0 8px color-mix(in srgb, var(--up) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .state.on .dot { animation: none; } }

/* Chip. Gestrichelt bedeutet Vergleichszeitraum, gefuellt bedeutet jetzt -
   Regel 8, direkt aus Screen 3 der Mobile-Referenz.                       */
.chip {
  display: inline-flex; align-items: center; gap: 7px; height: var(--h-sm);
  padding: 0 14px; border-radius: var(--r-pill); font-size: 13px;
  font-weight: 540; background: var(--surface-2); color: var(--ink-2);
}
.chip.dashed { background: transparent; color: var(--ink-3);
               border: 1.5px dashed var(--hair-2); }
.chip.now    { background: var(--act); color: var(--act-ink); }
.chip.lime   { background: var(--lime); color: var(--lime-ink); }
.chip.crown  { background: var(--lime); color: var(--lime-ink);
               font-weight: 620; }

/* ------------------------------------------------------------------- Toasts
   Rueckmeldungen zu einer Handlung, die schon passiert ist. Sie standen
   frueher als Streifen ueber dem Inhalt und schoben beim Laden die ganze
   Seite nach unten - ausgerechnet die Stelle, auf die man gerade geklickt
   hatte, sprang weg. Als schwebende Meldung passiert das nicht.

   Dieselbe Glasformel wie die Modale, damit es eine Oberflaeche bleibt und
   nicht zwei: `blur(26px) saturate(190%)` ueber 74 % Flaeche. */
.toasts {
  position: fixed; z-index: 300;
  right: max(18px, env(safe-area-inset-right));
  top: max(18px, env(safe-area-inset-top));
  width: min(428px, calc(100vw - 36px));
  display: flex; flex-direction: column; gap: 11px;
  /* Der Stapel selbst faengt nichts ab - nur die Meldungen darin. Sonst laege
     ein unsichtbares Rechteck ueber der rechten unteren Ecke der Seite. */
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  /* Mittig, nicht oben ausgerichtet: Die allermeisten Meldungen sind
     einzeilig, und dann standen Zeichen, Text und Schliesskreuz auf drei
     verschiedenen Hoehen. Bei zwei Zeilen ruecken Zeichen und Kreuz in die
     Mitte des Blocks - das liest sich besser als an der ersten Zeile
     klebend. */
  display: flex; align-items: center; gap: 13px;
  /* Etwas mehr Luft um denselben Text: Die Schrift bleibt bei 13,5 px, die
     Meldung bekommt nur Rand. Groessere Schrift haette sie zur Ueberschrift
     gemacht, und das ist sie nicht. */
  padding: 17px 16px 17px 18px;
  border-radius: var(--r-in);
  font-size: 13.5px; line-height: 1.45;
  color: var(--ink);
  background: color-mix(in srgb, var(--surface) 74%, transparent);
  backdrop-filter: blur(26px) saturate(190%);
  -webkit-backdrop-filter: blur(26px) saturate(190%);
  border: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35),
              0 22px 44px -22px rgba(0, 0, 0, .55);
  animation: toast-in .3s cubic-bezier(.16, .84, .44, 1);
}
.toast.gone { animation: toast-out .22s var(--ease) forwards; }

/* Von OBEN herein, weil sie jetzt oben stehen. Eine Meldung, die von unten
   an ihren Platz oben faehrt, kommt aus dem Nichts. */
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-16px) scale(.97); }
  to   { opacity: 1; transform: none; }
}
@keyframes toast-out {
  to { opacity: 0; transform: translateY(-8px) scale(.98); }
}

/* Das Zeichen traegt die Farbe, nicht der Text: Gruene oder rote Schrift auf
   Glas kaempft mit dem durchscheinenden Grund, ein gefuelltes Rondell nicht. */
.toast .sign {
  flex: 0 0 auto; width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center;
}
.toast .sign svg { width: 13px; height: 13px; stroke-width: 2.4; }
.toast.ok  .sign { background: var(--toast-ok);  color: var(--toast-ok-ink); }
.toast.err .sign { background: var(--toast-err); color: var(--toast-err-ink); }

.toast .text { flex: 1; min-width: 0; overflow-wrap: break-word;
               padding: 1px 0; }
.toast .shut {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  border: 0; padding: 0; background: none; cursor: pointer;
  display: grid; place-items: center; color: var(--ink-3);
  transition: background .14s var(--ease), color .14s var(--ease);
}
.toast .shut:hover { background: color-mix(in srgb, var(--ink) 9%, transparent);
                     color: var(--ink); }
.toast .shut svg { width: 13px; height: 13px; }

@media (prefers-reduced-motion: reduce) {
  .toast, .toast.gone { animation: none; }
  .toast.gone { opacity: 0; }
}


/* ------------------------------------------------------------- Anteilsbalken
   Der Prozentwert steht INNERHALB der Fuellung, wie bei "Dollar 72%" in der
   Referenz. Passt er nicht hinein, rutscht er daneben.                    */

.share { display: flex; align-items: center; gap: 14px; padding: 7px 0; }
.share .name { flex: 0 0 78px; font-size: 13.5px; color: var(--ink-2); }
.share .track {
  flex: 1; height: 34px; border-radius: var(--r-pill);
  background: var(--surface-2); position: relative; overflow: hidden;
}
.share .fill {
  position: absolute; inset: 0 auto 0 0; border-radius: var(--r-pill);
  background: var(--surface); box-shadow: 0 0 0 1px var(--hair);
  display: flex; align-items: center; padding-left: 14px;
  min-width: 62px;
}
.share .fill.lime { background: var(--lime); box-shadow: none;
                    color: var(--lime-ink); }


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

table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
th {
  text-align: left; padding: 0 12px 11px 0; border-bottom: 1px solid var(--hair);
  font-size: 10.5px; font-weight: 640; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: .07em;
}
td { padding: 13px 12px 13px 0; border-bottom: 1px solid var(--hair);
     vertical-align: middle; }
tr:last-child td { border-bottom: 0; }

/* `word-break: break-all` trennte mitten im Wort - aus `product` wurde in
   einer schmalen Tabellenspalte `produc t`. `overflow-wrap: anywhere` bricht
   nur, wenn es wirklich nicht anders geht, und laesst kurze Bezeichner
   zusammen. Lange Webhook-Adressen brechen trotzdem. */
code {
  font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--surface-2); color: var(--ink-2);
  border-radius: 7px; padding: 3px 9px;
  overflow-wrap: break-word; word-break: normal;
}

/* Codeblock. Waagerecht scrollbar statt umbrechend: Eine umbrochene
   Befehlszeile laesst sich nicht kopieren, ohne sie zu reparieren. */
/* `color: inherit`, nicht `--ink`: Der Block steht auch auf einer
   Flaechenkarte, und dort ist die Schriftfarbe die der Flaeche. Mit `--ink`
   war er auf der dunklen Karte dunkel auf dunkel. */
pre.code {
  font: 12.5px/1.65 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--surface-2); color: inherit;
  border-radius: var(--r-in); padding: 16px 18px; margin: 0;
  overflow-x: auto; white-space: pre; tab-size: 2;
}
.card.contrast pre.code, .card.lime pre.code,
.card.mint pre.code, .card.lavender pre.code {
  background: color-mix(in srgb, currentColor 10%, transparent);
}
pre.code .c { color: color-mix(in srgb, currentColor 55%, transparent); }
pre.code .s { color: var(--up); }
pre.code .k { color: inherit; font-weight: 600; }

/* Zeile mit Wert und Kopierknopf - fuer Schluessel und Adressen. */
.copyline {
  display: flex; align-items: center; gap: 10px;
  background: var(--surface-2); border-radius: var(--r-pill);
  padding: 5px 6px 5px 16px;
}
.copyline code { background: none; padding: 0; flex: 1; min-width: 0;
                 color: var(--ink); overflow-x: auto; white-space: nowrap;
                 scrollbar-width: none; }
.copyline code::-webkit-scrollbar { display: none; }


/* -------------------------------------------------------------- Diagramme
   Regel 6: nur der bemerkenswerte Wert hat Farbe. Regel 7: die Fahne ist
   dauerhaft sichtbar und haengt an einer gestrichelten Lotlinie.          */

.chart { width: 100%; display: block; overflow: visible; }
.chart .bar      { fill: var(--surface-3); }
.chart .bar.peak { fill: var(--lime); }
.chart .bar.zero { fill: none; stroke: var(--hair-2); stroke-width: 1;
                   stroke-dasharray: 2 3; }
.chart .axis     { fill: var(--ink-3); font-size: 10px;
                   font-variant-numeric: tabular-nums; }
.chart .axis.on  { fill: var(--ink); font-weight: 620; }
.chart .grid     { stroke: var(--hair); }
.chart .drop     { stroke: var(--hair-2); stroke-width: 1;
                   stroke-dasharray: 3 4; }
.chart .flag     { fill: var(--lime); }
.chart .flag-t   { fill: var(--lime-ink); font-size: 11px; font-weight: 650;
                   font-variant-numeric: tabular-nums; }
.chart .line     { fill: none; stroke: var(--ink); stroke-width: 2;
                   stroke-linejoin: round; stroke-linecap: round; }
.chart .line.lime { stroke: var(--lime-deep); }
.chart .prev     { fill: none; stroke: var(--ink-3); stroke-width: 1.5;
                   stroke-dasharray: 5 5; opacity: .7; }
.chart .knot     { fill: var(--surface); stroke: var(--ink); stroke-width: 2; }
.chart .knot.peak { fill: var(--lime); stroke: var(--lime-ink); }
.chart .hit      { fill: transparent; }
.chart .hit:hover + .knot { r: 6; }

/* Ueberlappende, durchscheinende Kreise - "Contract Type" der Referenz.
   Die Groesse traegt die Aussage, die Ueberlappung die Tiefe. */
.bubbles { position: relative; height: 150px; }
.bubbles .b {
  position: absolute; border-radius: 50%; display: grid; place-items: center;
  font-variant-numeric: tabular-nums; font-weight: 680; letter-spacing: -.03em;
}
.bubbles .b.teal { background: color-mix(in srgb, var(--teal) 72%, transparent);
                   color: var(--teal-ink); }
.bubbles .b.lime { background: color-mix(in srgb, var(--lime) 88%, transparent);
                   color: var(--lime-ink); }
.bubbles .b.grey { background: var(--surface-3); color: var(--ink-2); }


/* =========================================================== Das Geraet
   Nicht "ein Rahmen um die Vorschau", sondern das Produkt in seinen echten
   Verhaeltnissen. Alle Prozentwerte kommen aus Millimetern:

     Matrixpanel   64 Pixel x 3 mm Raster = 192 mm Blendenoeffnung
     Rundes Display 1,28"                 =  32,4 mm Glas
     Gehaeuse                              = 225,2 x 225,2 mm, quadratisch

   Die Frontplatte ist QUADRATISCH - die zwei Rundelemente brauchen keine
   zusaetzliche Hoehe, sie sitzen in den Ecken, die der Blendenkreis frei
   laesst.

   Die Gehaeusegroesse ist nicht gewaehlt, sondern ausgerechnet: Ein
   Rundelement soll zur Blende denselben Abstand haben wie zur Gehaeusekante,
   naemlich 10 mm. Sein Mittelpunkt liegt auf der Diagonale bei (g+r, S-g-r),
   der Abstand zum Blendenmittelpunkt ist also sqrt(2)*(S/2 - g - r) und muss
   R + r + g ergeben:

       S = 2 * ( (R + r + g)/sqrt(2) + g + r )
         = 2 * ( (96 + 16,2 + 10)/1,4142 + 26,2 )  =  225,22 mm

   Damit bleiben rund um die Blende 16,61 mm Frontplatte stehen. Bei 212 mm
   (der vorigen Groesse) lag das Rundelement 0,65 mm von der Blende und 10 mm
   von der Kante entfernt - sichtbar ungleich.                            */

/* Alle Kinder sind ABSOLUT gesetzt, und das ist kein Stilmittel: Prozentuales
   `padding` bezieht sich in CSS auf die Breite des ELTERNBLOCKS, nicht auf das
   Element selbst. Weil `.device` durch `max-width` schmaler ist als sein
   Elternblock, war das Polster zu gross und die Blende kam auf 181,8 statt
   192 mm - die ganze Millimeterrechnung war damit hinfaellig. Bei absolut
   gesetzten Kindern beziehen sich die Prozente auf den Polsterrahmen von
   `.device`, also auf seine eigene Breite. */
/* ------------------------------------------------------------------- Raum
   Wand hinter dem Geraet, Boden davor. Das Geraet haengt an einer Wand - ohne
   sie schwebt es auf der Karte und man sieht ein Bild statt eines Produkts.

   Der Raum ist Deko und traegt keine Bedienung: `aria-hidden` im Markup,
   `pointer-events: none` hier.

   Er ist der Hintergrund der GANZEN Karte, nicht ein Kasten darin - ein
   zweiter Rahmen ums Geraet waere eine Kante zu viel. Die Karte beschneidet
   und bildet den Stapelraum, `isolation` haelt die Mischart des Korns darin. */
.card.room { position: relative; isolation: isolate; overflow: hidden; }
.device-scene {
  position: relative;
  width: 100%;
  padding: 30px 0 0;
}

/* Die Wand haengt an der KARTE, nicht an der Szene, und deckt sie GANZ ab
   (`inset: 0`) - kein Boden mehr darunter, nur der eine Verlauf ueber das
   ganze Element. Das Licht kommt von oben links - dieselbe Richtung, aus
   der auch das Gehaeuse beleuchtet ist und in die sein Schatten faellt.
   Stimmten die nicht ueberein, saehe es aus wie zwei uebereinandergelegte
   Bilder. */
.card.room::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(112% 62% at 24% -6%,
                    var(--wall-hi) 0%, transparent 64%),
    linear-gradient(180deg, var(--wall-1) 0%, var(--wall-2) 100%);
}

/* Korn auf beidem. Eine Wand ist nicht glatt, und ohne Struktur sehen die
   Verlaeufe nach Farbfeld aus statt nach Raum.

   Die Mischart haengt am Thema: `multiply` dunkelt und wirkt auf der hellen
   Wand, `screen` hellt auf und wirkt auf der dunklen. Nimmt man nur eine, ist
   das Korn im anderen Thema nicht zu sehen - `overlay` faellt in beiden
   Extremen aus, weil es zur Basis hin rechnet. */
/* Ebenfalls an der Karte, und als `::after` NACH dem Inhalt im Baum - damit
   liegt es bei gleichem `z-index` ueber der Wand UND ueber dem Boden. Waere es
   ein `::before`, koerne nur die Wand. */
.card.room::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='w'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.72' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23w)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
  opacity: .055;
  mix-blend-mode: multiply;
}
:root[data-theme="dark"] .card.room::after {
  opacity: .085; mix-blend-mode: screen;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .card.room::after {
    opacity: .085; mix-blend-mode: screen;
  }
}

/* Das Geraet als eigener Block. `.card.column > .fill` streckt sich, wenn
   nebenan eine hoehere Karte steht, und zentriert seinen Inhalt. Feste
   Breite statt Inhaltsbreite, damit die Messung der Leinwand stabil bleibt
   (siehe `.card.column > .fill`). */
.device-stack { position: relative; z-index: 1; width: 100%; }

/* Buehne fuer das Geraet allein. Sie traegt die Breite, damit das Kabel
   dasselbe Bezugsmass hat wie das Gehaeuse und mitwaechst - deshalb darf der
   Knopf NICHT darin liegen: `top` des Kabels ist ein Prozentwert dieser Hoehe,
   und mit dem Knopf darin ruckte es aus dem Gehaeuse heraus. `z-index` steht
   ausdruecklich: Ohne die Stapelung laege das Kabel VOR dem Gehaeuse. */
.device-shot {
  position: relative;
  width: 100%; max-width: 430px; margin: 0 auto;
}

.device {
  position: relative; z-index: 1;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 8%;             /* 18 mm Radius */
  background:
    radial-gradient(120% 90% at 22% 4%, #333A43 0%, transparent 55%),
    linear-gradient(158deg, #23282F, #12151A 68%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .10),
    inset 0 0 0 1px rgba(255, 255, 255, .05),
    0 26px 52px -26px rgba(0, 0, 0, .9);
}

/* Feines Rauschen auf der Gehaeuseflaeche. Mattes PLA ist nicht glatt, und
   ohne Struktur sieht der Verlauf nach Bildschirm aus statt nach Kunststoff.

   Erzeugt statt geladen: `feTurbulence` in einer Daten-URL kostet keine
   Anfrage, keine Datei und keine externe Adresse - und skaliert mit jeder
   Aufloesung, was ein Rauschbild nicht taete.

   `screen`, nicht `overlay`: Auf einer fast schwarzen Flaeche rechnet
   `overlay` mit `2 x Basis x Auflage` und drueckt damit alles zurueck Richtung
   Schwarz - das Rauschen war schlicht nicht zu sehen. `screen` hellt auf, und
   genau das tut eine matte Oberflaeche: Sie streut Licht, statt es zu
   spiegeln. Die Koerner sind winzige Lichtpunkte, keine Schmutzflecken.

   Es liegt im Hintergrund von `.device`; Blende und Rundmodul sind absolut
   gesetzt und decken es dort ab - Glas soll glatt bleiben. */
.device::before {
  content: ""; position: absolute; inset: 0;
  border-radius: inherit; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.86' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
  opacity: .13;
  mix-blend-mode: screen;
}

/* Die runde Blende. Die Leinwand blendet selbst alles ausserhalb des Kreises
   aus - hier kommt nur die Fase dazu, die die Oeffnung als Einlassung in der
   Frontplatte zeigt. Kein Polster: Sonst misst sich die Leinwand daran und
   frisst es seitlich auf. */
.device .lens {
  position: absolute; left: 7.374%; top: 7.374%;
  width: 85.252%; height: 85.252%;   /* 192 mm von 225,2 mm */
  border-radius: 50%; background: #04060A;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .07),
    inset 0 2px 5px rgba(0, 0, 0, .9),
    0 0 0 1px rgba(0, 0, 0, .5);
}
.device .lens canvas {
  display: block; border-radius: 50%;
  transform: rotate(var(--display-rotation, 0deg));
}

/* Das Rundelement in der unteren rechten Ecke. Absolut gesetzt, nicht in einer
   Reihe darunter: Es liegt in der Flaeche, die der Blendenkreis frei laesst.
   14,386 % = 32,4 mm bei 225,2 mm Breite, 4,44 % = 10 mm Abstand zur Kante -
   und derselbe Abstand zur Blende, dafuer ist das Gehaeuse so gross.

   Frueher waren es zwei: links das Display, rechts eine Einlassung fuer ein
   magnetisches Batch. Das Batch ist entfallen - ein Display kann ein Logo
   auch anzeigen, und dann ist es umschaltbar statt aufgeklebt. */
.device .round {
  position: absolute; bottom: 4.44%; right: 4.44%;
  width: 14.386%; aspect-ratio: 1; border-radius: 50%;
}

/* Das Stoffkabel - ein freigestelltes Foto des echten Neonkabels, kein
   gezeichneter Strich. Es beginnt bei 76 % Hoehe, also HINTER dem Gehaeuse,
   und laeuft darunter weiter, bis die Deckung im Bild selbst auf null geht.
   Dadurch kommt es unter dem Geraet hervor, statt an seiner Kante zu kleben,
   und der Schlagschatten des Gehaeuses faellt darauf.

   2,9 % = 6,5 mm bei 225,2 mm Gehaeusebreite. Das Seitenverhaeltnis stammt
   aus der Datei (40 x 634), sonst wuerde das Geflecht verzerren.

   Wie weit es unten heraussteht, haengt an `top`: Bei 76 % stecken 24 % der
   Geraetebreite hinter dem Gehaeuse, sichtbar bleiben 22 %. Das muss unter
   den 106 px bleiben, die bis zur Legende Platz sind (50 Abstand + 42 Knopf +
   14 Rand) - und diese 106 px sind FEST, waehrend das Kabel mitwaechst. Der
   enge Fall ist deshalb das groesste Geraet: 430 px x 22 % = 95 px, also
   11 px Luft. Schmaler wird die Luft groesser, nie kleiner. */
.device-cable {
  position: absolute; z-index: 0;
  left: 13.2%; top: 76%;
  width: 2.9%; aspect-ratio: 40 / 634;
  pointer-events: none; user-select: none;
}

/* Das runde Display: schwarzes Glas mit Fase. Darin dreht sich die Kugel -
   oder es zeigt das hochgeladene Logo des Kunden.
   Der leichte Glanz oben ist die Woelbung des Glases. */
/* Der schwache blaue Schein aussen ist die Hintergrundbeleuchtung, die im
   dunklen Gehaeuse sichtbar wird - ohne ihn wirkt das Modul wie ein Aufkleber
   und nicht wie ein eingeschaltetes Display. */
.device .oled {
  background: #05070B; overflow: hidden;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .09),
    inset 0 1px 4px rgba(0, 0, 0, .9),
    0 0 0 1px rgba(0, 0, 0, .55),
    0 0 14px -2px rgba(96, 168, 255, .38);
}
.device .oled canvas {
  display: block; width: 100%; height: 100%;
  transform: rotate(var(--display-rotation, 0deg));
}
.device .oled::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: linear-gradient(155deg, rgba(255, 255, 255, .16) 0 18%,
                              transparent 46%);
  pointer-events: none;
}

/* Das Logo im runden Display. Es liegt unter dem Glanz und wird auf den
   Kreis beschnitten - genau wie die Kugel. */
.device .oled > img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; border-radius: 50%;
  transform: rotate(var(--display-rotation, 0deg));
}

/* Die Verkaufsanimation ist ebenfalls Displayinhalt. Sie muss dieselbe Lage
   wie Karte, Globus und Logo haben, nicht die Lage des Gehaeuses. */
.device .round-meme {
  transform: rotate(var(--display-rotation, 0deg));
}

/* ------------------------------------------------------------------ Hilfen */

.muted { color: var(--ink-3); font-size: 12.5px; }
.stack-v { display: flex; flex-direction: column; gap: 14px; }
.row-h { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Zwei Gruppen auseinanderziehen statt sie mit einem Inline-Stil zu trennen -
   kam in `setup.html` und den Adminseiten mehrfach vor. */
.row-h.between { justify-content: space-between; gap: 16px; }
/* Bedienfeld als Block ueber die ganze Zeilenbreite - fuer Inhalte, die
   keine Reihe sind (Kachelraster, Uploadblock). */
.ctl.block { display: block; width: 100%; }
/* Ein Feld, das die uebrige Breite einer Zeile nimmt (Name neben Knopf). */
.field.grow { flex: 1; min-width: 0; }
.hatch { background: var(--hatch); }
.sep { height: 1px; background: var(--hair); margin: 18px 0; }


/* ------------------------------------------------- Einrichtung (Geraet)

   Ein Blatt am rechten Rand, kein Vorhang ueber der ganzen Seite. Die
   Einrichtung stellt Dinge ein, deren Wirkung man in der Panelvorschau
   sofort sieht - und die zugleich live ans Geraet gehen. Ein Modal in der
   Mitte haette genau das verdeckt, was es einzustellen gilt.

   Die Zeilen im Blatt sind die ECHTEN Formularzeilen, dorthin verschoben
   (siehe konfig-guide.js). Sie bringen ihr eigenes Aussehen aus `.row` mit;
   hier steht nur, was sich im engeren Blatt anders verhaelt. */
.kg-hinter {
  position: fixed; inset: 0; z-index: 87;
  background: rgba(9, 13, 18, .22);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}
.kg-hinter[hidden] { display: none; }

.konfig-guide {
  position: fixed; z-index: 88;                /* unter dem Dock (90) */
  right: max(18px, env(safe-area-inset-right));
  top: max(18px, env(safe-area-inset-top));
  /* Unten Platz fuers Dock lassen, sonst liegt der Fuss darunter. */
  bottom: max(96px, calc(env(safe-area-inset-bottom) + 96px));
  width: min(480px, calc(100vw - 36px));
  display: flex; flex-direction: column; gap: 14px;
  padding: 18px 20px 16px;
  border-radius: 22px;
  border: 1px solid var(--hair);
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18),
              0 26px 60px -28px rgba(0, 0, 0, .6);
}
.konfig-guide[hidden] { display: none; }

.kg-kopf { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.kg-punkte { display: flex; gap: 5px; margin-left: auto; }
.kg-punkt {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--hair-2);
  transition: background .18s ease, transform .18s ease;
}
.kg-punkt.done { background: var(--lime-deep); }
.kg-punkt.current { background: var(--lime-deep); transform: scale(1.5); }

/* Der Inhalt scrollt, nicht das Blatt: Kopf und Fuss sollen stehen bleiben,
   damit "Weiter" immer am selben Platz ist. */
.kg-seiten { position: relative; flex: 1 1 auto; overflow-y: auto; margin: 0 -4px; padding: 0 4px; }
.kg-schritt { display: none; }
.kg-schritt.active { display: block; animation: kg-auf .22s ease both; }
.kg-schritt h3 { margin: 0 0 6px; }
.kg-schritt p { margin: 0 0 10px; font-size: 13px; line-height: 1.5; }
.kg-schritt p.muted { font-size: 12.5px; }
@keyframes kg-auf {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .kg-schritt.active { animation: none; }
}

/* Die Skizze der Montagelagen. Hier hilft die Vorschau nicht - es geht um
   das Gehaeuse an der Wand, nicht um den Bildinhalt. */
.kg-skizze {
  width: 100%; max-width: 260px; height: auto;
  margin: 2px 0 12px; color: var(--ink-3);
}

/* Die hereingeholten Formularzeilen. Im Blatt ist es enger als in der
   Kartenspalte, deshalb duerfen Name und Bedienelement hier untereinander
   stehen statt nebeneinander. */
.kg-regler {
  display: flex; flex-direction: column; gap: 2px;
  border-top: 1px solid var(--hair); margin-top: 4px;
}
.kg-regler .row { padding: 13px 0; border-bottom: 1px solid var(--hair); }
.kg-regler .row:last-child { border-bottom: 0; }
@media (max-width: 560px) {
  .kg-regler .row { flex-direction: column; align-items: flex-start; gap: 10px; }
  .kg-regler .row .ctl { width: 100%; }
}
/* Eine abgeblendete Zeile im Blatt bleibt bedienbar - sie ist hier ja das
   Thema des Schrittes. Die Daempfung erklaert nur, dass sie gerade nicht
   greift (z.B. Sammelfenster bei "Einzeln"). */
.kg-regler .row.dim { opacity: .55; }

/* Worauf man in der Vorschau achten soll. Ein Satz statt eines
   eingefrorenen Bildes - daneben steht das lebende Original. */
.kg-schau {
  flex: 0 0 auto;
  display: flex; gap: 8px; align-items: flex-start;
  padding: 9px 11px; border-radius: 12px;
  background: color-mix(in srgb, var(--lime) 14%, transparent);
  font-size: 12.5px; line-height: 1.4; color: var(--ink-2);
}
.kg-schau::before { content: "\2190"; font-size: 14px; line-height: 1.2; }
.kg-schau[hidden] { display: none; }

.kg-fuss { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.kg-fuss [data-kg-count] { white-space: nowrap; }
.kg-fuss [data-kg-next] { margin-left: auto; }

/* Waehrend der Einrichtung bleibt die Vorschau im Blick. Erst ab 1181px -
   darunter liegt das Blatt ueber der Seite und die Vorschau waere ohnehin
   verdeckt; dort scrollt man eben. */
@media (min-width: 1181px) {
  body.kg-laeuft .room.s5 {
    position: sticky; top: 18px; align-self: start;
  }
}

/* --------------------------------------------------------------- Vorlagen
   Ein gespeicherter Einstellungssatz je Zeile: Name links, Handeln rechts.
   Die Liste steht im `.ctl` einer mehrzeiligen Zeile und muss deshalb selbst
   die volle Breite nehmen - `.ctl` ist sonst nur so breit wie sein Inhalt. */
.feld-mit-knopf { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.feld-mit-knopf input[type=text] { flex: 1 1 180px; min-width: 0; }

.vorlagen-liste { display: flex; flex-direction: column; gap: 8px; width: 100%; }

.vorlagen-eintrag {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 12px;
  border: 1px solid var(--hair-2); border-radius: 12px;
  background: var(--frame);
}
.vorlagen-eintrag > b { font-size: 13.5px; font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vorlagen-knoepfe { display: flex; gap: 8px; flex: 0 0 auto; }

/* Fusszeile eines Modals: Abbrechen links, Handeln rechts. Bisher hatten die
   Farbmodale keine - sie schliessen sich beim Klick auf eine Farbe von
   selbst. Das Vorlagenmodal braucht eine, weil das Benennen erst mit dem
   Absenden fertig ist. */
.modal-aktionen {
  display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px;
}

/* ------------------------------------------------------------- Protokoll
   Die Ferndiagnose zeigt breite Tabellen - zwoelf Spalten Hardwarezustand
   passen auf kein Notebook. Sie scrollen in ihrem eigenen Rahmen, damit die
   Seite selbst nicht seitwaerts laeuft. */
.table-scroll { overflow-x: auto; margin: 0 -4px; padding: 0 4px; }
.table-scroll table { min-width: 760px; }

.nowrap { white-space: nowrap; }
.mono { font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; }

/* Die Filterleiste. Bewusst eine Zeile, die umbricht: Bei einer Stoerung
   schraenkt man nacheinander ein, und dann sollen die vorigen Einstellungen
   sichtbar bleiben statt hinter einem Aufklapper zu verschwinden. */
.protokoll-filter {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end;
  margin: 4px 0 18px;
}
.protokoll-filter .field { flex: 0 0 auto; min-width: 130px; }
.protokoll-filter .field.grow { flex: 1 1 200px; }

/* Fehler und Warnungen faerben die ganze Zeile schwach ein - beim
   Ueberfliegen einer langen Liste sieht man so sofort, wo es klemmt, ohne
   die Spalte "Art" lesen zu muessen. */
tr.grad-fehler  td { background: color-mix(in srgb, var(--down) 8%, transparent); }
tr.grad-warnung td { background: color-mix(in srgb, var(--warn) 9%, transparent); }


/* --------------------------------------- Warten auf die Uebertragung

   Der Balken laeuft, statt einen Fortschritt in Prozent zu behaupten: Das
   Geraet meldet erst, WENN es fertig ist, nicht wie weit es ist. Ein
   Prozentbalken waere hier gelogen. */
.uebertragung-karte { max-width: 380px; text-align: left; }
.uebertragung-lauf { height: 3px; border-radius: 3px; overflow: hidden;
  background: var(--hair); margin: 0 0 16px; }
.uebertragung-lauf::after { content: ""; display: block; height: 100%;
  width: 40%; border-radius: 3px; background: var(--lime-deep);
  animation: uebertragung-wandern 1.1s var(--ease) infinite; }
@keyframes uebertragung-wandern {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}
@media (prefers-reduced-motion: reduce) {
  .uebertragung-lauf::after { animation: none; width: 100%; opacity: .55; }
}

/* Gesperrt, weil gerade uebertragen wird. Anders als `.dim` ("greift nicht,
   ist aber bedienbar") heisst das hier: Finger weg, sonst bricht der Abruf
   ab. Deshalb zusaetzlich `inert` im Skript - und `pointer-events` als
   Rueckfall fuer Browser, die `inert` noch nicht kennen. */
.uebertraegt { opacity: .38; pointer-events: none;
  transition: opacity .2s var(--ease); }

.uebertraegt-hinweis { margin: 0 0 14px; font-size: 12.5px; line-height: 1.45;
  color: var(--ink); font-weight: 450; padding-left: 10px;
  border-left: 2px solid var(--alarm); }
