/* upstreem core.css — geteilte Basis für alle Tabellen-/Chart-Komponenten.
   Klassen-Prefix: up-  |  Event-Namen bleiben pro Komponente (uut/udt).
   Enthält: Tokens+Farben, Icon-Buttons, Switcher, Dropdowns/Portal, Tooltips,
   Skeleton, Suche, Sticky-Header-Mechanik, generische Tabellenstruktur. */

/* Der ganze Bereich statt einzelner Staerken: Google liefert dafuer die VARIABLE
   Schnittdatei, und damit sind Zwischenwerte wie 550 wirklich verfuegbar. Mit einer Liste
   einzelner Staerken faellt jeder Zwischenwert auf die naechste vorhandene zurueck -- 550
   waere dann 600, und eine halbe Stufe liesse sich gar nicht setzen.

   400..700 UND NICHT MEHR 400..600 (21.09.). Der Bereich endete bei 600, aber an 15 Stellen
   stand 650, 700 oder 750 -- und ein Wert ausserhalb des geladenen Bereichs wird still auf
   den Rand gezogen. Diese 15 Stellen sahen also aus wie 600, obwohl sie mehr wollten, und
   zwei davon (die Treffermarkierung in Miras Auswahl und die in den Quick Actions) hatten
   sogar ZWEI Stufen -- 700 und 750 -- die beide als 600 herauskamen. Die Unterscheidung, die
   dort gebaut wurde, gab es auf dem Bildschirm nie.
   Kostet keine zweite Datei: es ist dieselbe variable Schnittdatei mit einem breiteren
   Bereich. */
/* Geist kommt von der Website selbst (lib/fremd.ts) */

/* Die Form des Globus als Maske -- EINMAL, damit sie nicht in jeder Regel steht. Dieselben Pfade
   wie UC.icon("globe"): Kreis, Meridian, Aequator. Gebraucht vom Rueckfall der Favicon-Kaesten
   (.up-fav weiter unten) und vom freistehenden Globus (.up-globus-frei).
   An :root und nicht an .up-root: der Rueckfall trifft auch Bilder in Karten, die am <body>
   haengen (Tooltips, Menues) -- dort loest eine Variable von .up-root nicht auf, und eine Maske
   mit unaufloesbarem Wert waere kein Globus, sondern ein volles Rechteck. */
:root {
      --up-globus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20'/%3E%3Cpath d='M2 12h20'/%3E%3C/svg%3E");

  /* ================= DIE MASSE ==========================================================
     AN :root UND NICHT AN .up-root (21.09., nach einem selbst gebauten Fehler). Diese Werte
     haengen an KEINEM Thema und an keiner Akzentfarbe -- eine 12px bleibt eine 12px. Sie
     gehoeren deshalb an die Wurzel des Dokuments, nicht an die der Komponente.
     WARUM DAS WICHTIG IST: mehrere Teile der App haengen bewusst am <body> und damit
     AUSSERHALB von .up-root -- die Massenleiste der Prompts-Tabelle (position: fixed, sie
     muesste sonst gegen jeden transformierten Bubble-Container ankaempfen), Tooltips, Menues.
     Dort loest eine Variable aus .up-root nicht auf. Bei einer FARBE faellt das sofort auf;
     bei einer Kurve in einer transition-KURZSCHREIBWEISE ist die ganze Angabe ungueltig, und
     dann verschwindet nicht die Kurve, sondern die ANIMATION. Genau so sind am 21.09. das
     Ein- und Ausgleiten der Massenleiste und das Aufklappen ihrer Themenauswahl ausgefallen,
     nachdem ich ihre Uebergaenge auf var(--up-ease) umgestellt hatte -- vier Regeln, alle
     still.
     core macht es bei --up-globus direkt darueber aus demselben Grund genauso.
     WAS HIER NICHT STEHT: --up-e-* (die Tiefen). Die aendern sich mit dem Thema und stehen
     deshalb im naechsten Block, der eine dunkle Fassung hat -- dieser hier hat keine.

  /* Schrift. SIEBEN Stufen, und die Zahl ist nachgeschlagen, nicht geschaetzt: Polaris faehrt
     8 Stufen (12/14/16/20/24/28/32/40), Nathan Curtis (EightShapes) empfiehlt "mit wenigen
     anfangen und nur wachsen, wenn eine Komponente es wirklich verlangt", die Ratgeber fuer
     dichte Dashboards nennen 6.
     ERSTE FASSUNG DIESES BLOCKS HATTE SECHS UND WAR FALSCH: sie endete bei 22 und haette
     damit .up-ph-heading (28px, der Kopf ALLER SIEBEN Seiten) auf Dialogtitelgroesse
     gedrueckt -- eine sichtbare Entwertung der obersten Stufe der App. Die Hierarchie WAR
     da, sie war nur je Stufe uneinheitlich; das ist etwas anderes, als keine zu haben.
     KEIN MODULARES VERHAELTNIS. Die Stufen sind von Hand gesetzt, nach dem, was die App
     wirklich braucht -- ein Faktor wie 1.2 erzeugt an dieser Dichte Bruchteile (13.2, 15.8),
     die je Browser anders runden. Genau daher kommen die 216 halben Pixel im Bestand. */
  --up-fs-xs:  11px;   /* Nebenbeschriftung, Einheiten, Zaehler        (52) */
  --up-fs-s:   12px;   /* Tabellenzelle, Chip, Menueeintrag           (176) */
  --up-fs-m:   13px;   /* Fliesstext, Knopfbeschriftung               (164) */
  --up-fs-l:   14px;   /* Zeilentitel, Chart-Ueberschrift             (100) */
  --up-fs-xl:  16px;   /* Abschnittstitel, Seitenbeschreibung          (26) */
  --up-fs-2xl: 22px;   /* Dialogtitel UND die grosse Zahl einer Kennzahl -- in der App
                          heute 20, 21, 22, 23 und 23.5; eine Rolle, fuenf Werte    (22) */
  --up-fs-3xl: 28px;   /* .up-ph-heading, der Kopf jeder Seite              (core:3601) */
  --up-fs-display: 38px; /* DIE EINE grosse Zahl einer Karte -- die Aussage, nicht der
                          Rahmen daneben. Eigene Stufe, weil das eine eigene Rolle ist und
                          keine Ueberschrift: sie steht allein und wird nicht gelesen,
                          sondern abgelesen. Heute nur .upt-kpi-quota-used; ask-mira.css
                          traegt dieselbe Rolle auf 34px und zieht nach, wenn die Datei
                          drankommt.                                     (prompts-table) */
  /* DIE VIER UEBERSCHRIFTEN (20.10., Audit gegen Geist: vorher sieben Varianten von Titel plus Unterzeile, Titel in 12, 14, 15, 16 und
     Unterzeilen in 12, 12.5, 13, 14). Werte nach Geist (vercel.com/geist/typography, gemessen): heading-16 16/24/600/-0.32,
     heading-14 14/20/600/-0.28, copy-13 13/18, label-12 12/16; auf unserer Skala (--up-fs-*). Jede Komponente liest diese Tokens, keine
     eigene Groesse mehr.
       Abschnitt   steht ueber einer Komponente, mit oder ohne Unterzeile oder Zahl, auch in Drawern und Einstellungen (.up-heading, .up-dk-h2, *-sectitle)
       Unterzeile  erklaert den Abschnitt, muted                                                               (.up-sec-sub, .up-dk-unter, *-secsub)
       Block       Titel einer Spalte oder eines Eintrags INNERHALB einer Karte (Kanban-Spalte, Verlaufseintrag)  (.uo-col-title, .upr-history-headline)
       Gruppe      kleine Beschriftung ueber einer Gruppe, kein Titel, keine Versalien                        (.up-blockhead, .up-label, .up-pop-head)
     Abstand Titel zu Unterzeile 4: Geist hat 8 unter einem heading-24; unter 16 sind 4 verhaeltnisgleich. */
  --up-ueb-abschnitt-gr: var(--up-fs-xl); --up-ueb-abschnitt-gew: 600; --up-ueb-abschnitt-zeile: 24px; --up-ueb-abschnitt-spur: -0.32px;
  --up-ueb-unter-gr: var(--up-fs-m);      --up-ueb-unter-gew: 400;     --up-ueb-unter-zeile: 18px;
  --up-ueb-block-gr: var(--up-fs-l);      --up-ueb-block-gew: 600;     --up-ueb-block-zeile: 20px;     --up-ueb-block-spur: -0.28px;
  --up-ueb-gruppe-gr: var(--up-fs-s);     --up-ueb-gruppe-gew: 500;    --up-ueb-gruppe-zeile: 16px;
  --up-ueb-luft: 4px;
  /* AUSSERHALB DER SKALA, mit Grund: neun font-size in clamp() (auth-page, onboarding,
     ask-mira, prompt-research). Das sind Ueberschriften auf ganzseitigen Flaechen, die mit
     dem Fenster mitwachsen -- dieselbe Ueberlegung wie bei der Landingpage: eine ganze Seite
     ist kein Dashboard. .check_skala.py laesst clamp() deshalb ausdruecklich durch. */

  /* Schriftschnitt. Die vier Stufen, die die App traegt -- und alle vier liegen im
     geladenen Bereich (siehe @import oben); das war vor dem 21.09. bei 700 nicht so.
     SIE SIND KEINE AUSSCHLIESSLICHE LISTE. Geist wird als variable Schrift geladen, also
     rendert auch 450 oder 550 wirklich, und an 37 Stellen steht genau das -- als halbe
     Stufe zwischen zwei Nachbarn, bewusst gesetzt. Was NICHT geht, ist ein Wert ausserhalb
     von 400..700: der wird still auf den Rand gezogen und sieht dann aus wie der Nachbar.
     .check_skala.py prueft deshalb den BEREICH und nicht diese vier Zahlen. */
  --up-fw-n:  400;     /* Fliesstext, Nebentext                        (67) */
  --up-fw-m:  500;     /* der Normalfall der App                      (324) */
  --up-fw-h:  600;     /* Ueberschrift, aktiver Zustand               (172) */
  --up-fw-b:  700;     /* echtes Fett: Treffermarkierung, <strong>          */

  /* Radius. SEIT DEM 15.10. DIE STUFEN VON GEIST (vercel.com/geist/materials und die
     Komponentenseiten, gemessen am 15.10.): 4, 6, 8, 12, 16 und rund -- nichts dazwischen.
     Geist ordnet die Stufe nach Rolle und Hoehe, nicht nach Gefuehl:
          4  Checkbox (16px), Knopf 24px, die Knoepfe IN einem Segment
          6  Knopf und Feld 32-36px, Menuezeile (--ds-popover-row-radius), einzeiliger Tooltip
             (material-tooltip), Segment-Kapsel -- die "Alltagsstufe" (material-base/-small)
          8  Knopf und Feld ab 40px, mehrzeiliger Tooltip, Vorschaukachel
         12  Karte (material-medium/-large), Menue (material-menu), Dialog, Toast
         16  ganzseitige Flaeche (material-fullscreen)
     Gegenueber vorher (4/6/8/10/12/16/32) ist das ueberall eine Stufe kleiner: 8 -> 6,
     10 und 12 -> 8, 14 und 16 -> 12, 32 -> 16. Gemessen am 15.10. VOR dem Umbau: 956 Angaben,
     davon 735 mit fester Zahl in 36 Werten (8px allein 160-mal, 9px 48-mal, 12px 71-mal fuer
     Knopf, Feld, Tabelle und Menue zugleich) und nur 152 ueber ein Token. Danach 811 ueber ein
     Token; fest stehen nur noch 0, 50 %, die Haarlinien unter 4 (Flaggen 3, Markierungsstriche
     2 und 1), die em-Werte in Miras Text und drei Skelett- bzw. Command-Menu-Werte, die deren
     eigene Arbeit sind (skeleton.css, .mqa-enter, logoStyle der Tabellenskelette).
     Zwei Stufen tragen denselben Wert, weil zwei alte Rollen auf dieselbe Geist-Stufe fallen:
     s = xs (Chip und Punkt, beide 4) und xl = l (Karte innen und Kachel, beide 8). Die Namen
     bleiben getrennt -- sie sagen, WAS gerundet wird, und eine spaetere Abweichung kostet dann
     eine Zeile statt einer Suche durch 40 Dateien.
     DIE KONZENTRIK BLEIBT: innen = aussen minus Abstand, danach auf die naechste Stufe. Ein Bild
     mit 1px Rahmen in einer 6er-Ecke traegt `calc(var(--up-r-m) - 1px)`, die Menuezeile im
     12er-Menue mit 8 Polster 4 (s), das Bild im Logo-Stapel (Rahmen 1 + Polster 2)
     `calc(var(--up-r-m) - 3px)`. .check_skala.py laesst Basis minus 1 durch und rechnet calc()
     nicht nach.
     ALTE ZAHLEN IN KOMMENTAREN: wo ein Kommentar aelter als der 15.10. einen Radius nennt
     ("32px, Radius 8", "Radius 12 wie .uob-input"), meint er die alte Skala. Die Zuordnung ist
     die oben: 4 -> 4, 6 -> 4, 8 und 9 -> 6, 10 bis 12 -> 8, 14 bis 18 -> 12, 32 -> 16; die Rolle
     (Knopf, Feld, Menue, Karte) kann eine Angabe davon abweichend auf ihre Geist-Stufe gezogen
     haben -- massgeblich ist die Regel, nicht der Kommentar. */
  --up-r-xs:   4px;    /* Punkt, Haken, Checkbox, Plakette, Knopf im Segment               */
  --up-r-s:    4px;    /* Chip, Tag, Knopf bis 24px -- Geist: Knopf 24px (vorher 6)      */
  --up-r-m:    6px;    /* Knopf, Feld 32-36px, Segment, Logoplatte, Menuezeile (vorher 8) */
  --up-r-l:    8px;    /* Knopf und Feld ab 40px, Kachel, Tooltip mehrzeilig (vorher 10)  */
  --up-r-xl:   8px;    /* Karte innen, grosse Logoplatte, Vorschaubild      (vorher 12)   */
  --up-r-2xl: 12px;    /* Karte, Box, Tabelle, Dialog, Toast, Erklaerkarte (vorher 16)    */
  --up-r-3xl: 16px;    /* GROSSE FLAECHE -- Geist material-fullscreen; heute das Anmeldefeld
                          (.uau-panel, vorher 32)                                           */
  --up-r-voll: 999px;  /* alles, was ganz rund ist                                          */
  /* Das Menue (Geist material-menu, 12). Steht seit dem 15.10. AUCH hier an :root und nicht nur
     an .up-root: die Menues von Mira, der Antwortansicht und der Fader haengen teils am <body>,
     und ein unaufgeloestes var() in border-radius ist keine Rundung, sondern eine scharfe Ecke. */
  --up-dd-radius: 12px;

  /* Hoehen der Bedienelemente. Vier Stufen, eine Zeilenhoehe -- und das ist die Leiter, die
     GitHub Primer unabhaengig davon fuehrt (24/28/32/40/48). Dass beide auf dieselben vier
     Zahlen kommen, ist der beste Hinweis darauf, dass sie richtig sind. */
  --up-h-chip: 24px;   /* .up-pille (Tarif, Status), Plaketten         (53) */
  --up-h-seg:  28px;   /* kleines Segment                              (47) */
  --up-h-btn:  32px;   /* Knopf, Eingabe, Werkzeugleiste              (112) */
  --up-h-gross: 40px;  /* Hauptknopf, Suchfeld                         (33) */
  --up-h-row:  55px;   /* Tabellenzeile -- steht so im STYLEGUIDE           */

  /* DIE FESTE SPUR einer Zahl (15.10. gewaehlt, Vorschau "Zahl und Trend", Variante 1): jede Zahl
     in einer Tabellenzelle bekommt eine Mindestbreite fuer ihr FORMAT und steht darin rechts --
     Kopf und Zelle bleiben linksbuendig, aber Komma steht unter Komma, und ein Trend dahinter
     beginnt in jeder Zeile auf demselben Pixel. Seit dem 15.10. spaet steht die Zahl rechts in der
     Zelle und der Trend LINKS davor (Regel "RECHTSBUENDIG, DER TREND LINKS DAVOR"). Gemessen in der Zellschrift (.up-num 14/500,
     Geist, Tabellenziffern), auf ganze Pixel aufgerundet, und zwar fuer den GROESSTEN Wert der
     Skala (100%, Sentiment 100, Rang zweistellig): mit der typischen Breite ("00%") braeche
     jede Zeile mit 100% aus der Flucht. Eine Zelle waehlt ihr Format ueber --up-spur (core.css,
     "DIE FESTE SPUR", Regel an .up-num, .up-rank-group und .up-sent-val in .up-td). */
  --up-spur-pct0:   38px;   /* "100%"    Prozent ohne Stelle, 37.05   */
  --up-spur-pct1:   49px;   /* "100.0%"  Prozent mit einer Stelle, 48.59 */
  --up-spur-rang:   29px;   /* "10.0"    Rang, immer eine Stelle, 28.69 */
  --up-spur-note:   26px;   /* "100"     Sentiment 0-100, 25.71       */
  --up-spur-anzahl: 38px;   /* "0.00k" / "0,000" Anzahl, 37.26        */
  /* Abstand zwischen Trend und Zahl (15.10. spaet: "etwas mehr gap, minimal mehr"): 12 statt der
     10, die jede Zelle zwischen ihren Teilen laesst. */
  --up-spur-abstand: 12px;
  /* Der feste Platz des Trends vor dem Wert (17.10.): Pfeil 16 + Abstand 4 + "100.0%" in 13/600 mit
     Tabellenziffern, gemessen 66.0 -- der breiteste Chip, den trendChip nach CLAUDE.md 2b bauen kann
     (Prozent mit einer Stelle; Rang-Deltas "0.3" sind schmaler). Siehe "RECHTSBUENDIG, DER TREND
     DIREKT VOR DEM WERT". */
  --up-spur-trend: 66px;
  /* Luft zwischen der Unterkante eines offenen Bandes und der Zeile darunter (15.10., Vorbild
     Prompt Insights: Linie der Navigation bis zur Leiste 16, Leiste bis zum Tabellenkopf 16).
     Regel "DIE ZEILE UNTER DEM BAND". */
  --up-band-luft: 16px;

  /* Bewegung. Drei Dauern, zwei Kurven -- nach dem, WAS sich bewegt, nicht wie weit.
     Die zwei Kurven sind die, die in der App schon am haeufigsten ausgeschrieben stehen. */
  --up-t-1: 120ms;     /* Farbe, Deckkraft, Hover -- reiner Zustand   (133) */
  --up-t-2: 200ms;     /* Groesse und Position                        (129) */
  --up-t-3: 260ms;     /* Flaechen: Drawer, Modal, Seitenwechsel       (16) */
  --up-ease:     cubic-bezier(.2, 0, .38, .9);   /* hin und zurueck   (13) */
  --up-ease-auf: cubic-bezier(.22, 1, .36, 1);   /* nur Auftritte      (9) */
  /* Der Auftritt einer ganzen Seite (11.10.): eigene, benannte Stufe AUSSERHALB der UI-Dauern.
     Eine Seite ist keine Schaltflaeche -- sie kommt in einem Zug mit Staffel, und unter 500ms
     liest sich das als Ruck statt als Ankommen. Die Kurve ist easeOutExpo, gemessen auf
     mobbin.com (dem Massstab des Nutzers fuer Bewegung): schnell da, lang auslaufend. Dort sind
     es 1s; in einer App, in der man zehnmal am Tag die Ansicht wechselt, sind 600ms richtig --
     dieselbe Dauer wie die Einfahrt der Diagramme, damit Seite und Chart in einem Zug ankommen. */
  --up-t-auftritt: 600ms;
  --up-ease-expo: cubic-bezier(.19, 1, .22, 1);
  /* easeOutQuart, wie Chart.js es fuer Linie und Ring faehrt (600ms, core.js makeLine und
     makeTypeChart). Fuer alles, was im selben Bild wie ein Diagramm einfaehrt (Balken). */
  --up-ease-quart: cubic-bezier(.25, 1, .5, 1);
  /* DAS LAUFPROTOKOLL (22.10.): Arbeitsschritte als wachsende Liste unter einer Uhr -- Mira (ask-mira.css .am-run) und
     Page Audit (page-audit.css .upa-run). Die Bewegung stand als Zahlen in Miras CSS, Page Audit hatte sie abgeschrieben
     (Lukas 22.10.: "mach es genauso"); seitdem EINE Quelle hier, der Takt dazu steht in core.js (LAUF_TAKT). Die Werte
     sind Miras, unveraendert, und liegen bewusst AUSSERHALB von --up-t-*: eine Zeile, die waechst und deren Spinner
     weich weggeht, braucht mehr als die 260 einer Flaeche, sonst liest sie sich als Ruck. */
  --up-lauf-auf: 420ms;      /* die Zeile waechst auf (grid-template-rows 0fr -> 1fr) */
  --up-lauf-blende: 320ms;   /* ihre Deckkraft; der Spinner blendet und schrumpft beim Haken */
  --up-lauf-platz: 380ms;    /* der Spinner gibt seine Breite ab, die Zeile rueckt an den Rand */
  --up-lauf-farbe: 460ms;    /* Text und Zeichen gehen in die erste Farbe */
  --up-lauf-logo: 340ms;     /* die Logos hinter dem Text kommen nach */
  --up-lauf-kurve: cubic-bezier(.2, .8, .3, 1);
  --up-lauf-dreh: 720ms;     /* eine Umdrehung des Spinners */
  --up-lauf-glanz: 1.56s;    /* ein Durchlauf des Glanzes ueber dem laufenden Text */
  /* Der Lichtlauf ueber Skelett-Platzhaltern (.up-sk-*::after, upskt 1200ms). Als Token, seit ihn
     auch Bausteine ausserhalb von core brauchen (14.10.: die Geister-Chips in Prompt Research) --
     vorher stand der Verlauf nur hier, und jede Kopie waere eine eigene Farbe gewesen. */
  --up-sk-lauf: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.14) 50%, rgba(255,255,255,0) 100%);
  /* DAS SKELETT NACH GEIST (15.10., angefordert: "Skeletons aus dem Design-System holen").
     Gelesen an vercel.com/geist/skeleton, an den berechneten Stilen des Beispiels: ein Verlauf
     gray-100 -> gray-200 -> gray-100 auf einer dreimal so breiten Flaeche, deren Kachel die Haelfte
     davon ist und um eine Kachel nach rechts wandert -- 1.5s ease-in-out, endlos
     (Keyframes loading-skeleton, bei 100 % translate(-50%), rueckwaerts gespielt). Radius 5px
     (rounded-[5px]); voll rund fuer Avatare, eckig fuer Bildkacheln.
     Hier dieselbe Bewegung OHNE ::after: die Kachel ist 150 % breit und faehrt per
     background-position um ihre eigene Breite -- gleiches Bild, aber kein position und kein
     overflow am Element. overflow: hidden an einem inline-block verschoebe dessen Grundlinie, und
     damit jede Tabellenzeile, in der ein Skelettbalken steht.
     Bis heute gab es ZWEI Skelette nebeneinander: das Pulsieren (uutpulse, Deckkraft 1 -> .45) an
     Tabellen und Karten und den Lichtlauf (upskt, weisser Streifen) an den Diagrammen. Beide
     gehen auf diese eine Form; ihre Keyframes bleiben stehen (Vertrag), sie tragen nur nichts
     mehr.
     So nutzt es eine Komponente:
         background: var(--vc-sk) var(--up-sk-verlauf); animation: var(--up-sk-bewegung);
     oder gleich die Klasse .up-skelett. Die Grundfarbe bleibt --vc-sk und wird erst am Element
     aufgeloest; --up-sk-verlauf ist nur der Glanz darueber und haengt am Thema (siehe die Bloecke
     mit den Tiefen). Bei reduzierter Bewegung steht der Verlauf still (Regel nach diesem Block). */
  --up-sk-r: 5px;
  /* Dauer und Kurve einzeln, weil der Schimmer in den Diagramm-Skeletten (svg, .up-sk-wisch) eine
     eigene Strecke faehrt, aber im selben Takt laufen muss. */
  --up-sk-dauer: 1.5s;
  --up-sk-kurve: ease-in-out;
  --up-sk-bewegung: up-sk-geist var(--up-sk-dauer) var(--up-sk-kurve) infinite;
}
/* Ein Token statt einer Regel je Skelett: jede Komponente liest --up-sk-bewegung, also haelt diese
   eine Zeile alle an. Der Verlauf bleibt stehen (Geist: animated={false} zeigt denselben Verlauf
   ohne Bewegung). */
@media (prefers-reduced-motion: reduce) { :root { --up-sk-bewegung: none; } }
/* ================= AUFTRITT VON SEITE UND ANSICHT (11.10.) ==================================
   Eine Seiten-Komponente (makeAuftritt in core.js, Klasse .up-seite) laesst ihren Inhalt warten,
   bis Stylesheets und erste Daten da sind -- hoechstens einige hundert Millisekunden. Bis dahin
   steht er unsichtbar, aber mit seiner vollen Hoehe da (kein Springen des Kopfs).
   Ansichten OHNE Seiten-Komponente bekommen den Auftritt hier, in derselben Kurve. Das ersetzt
   den viewfade aus dem Kopf-Skript (View-System v7): Deckkraft an der Ansicht, der Weg nur am
   Koerper ([id^="viewbody-"]) -- ein transform an der Ansicht selbst wuerde alles darin mit
   position:fixed waehrend der Bewegung verschieben. */
.up-auftritt-zu { opacity: 0; }
[id^="view-"].view-on:not(:has(.up-seite, .uvb-root, [data-up-vorab-seit], .uds-root, .ucs-root, .upi-root, .upf-root, .uop-root, .urs-root, .utp-root, .ubr-root, .uss-root, .upa-root)) { animation: up-ansicht-auf var(--up-t-auftritt) var(--up-ease-expo); }
[id^="view-"].view-on:not(:has(.up-seite, .uvb-root, [data-up-vorab-seit], .uds-root, .ucs-root, .upi-root, .upf-root, .uop-root, .urs-root, .utp-root, .ubr-root, .uss-root, .upa-root)) [id^="viewbody-"] { animation: up-ansicht-weg var(--up-t-auftritt) var(--up-ease-expo); }
/* Die Seiten werden an ihrer WURZEL erkannt (uds, ucs, upi, upf, uop, urs, utp, ubr), nicht nur an
   .up-seite (11.10.): die Wurzel steht von Anfang an im Markup, .up-seite setzt erst die Seite beim
   Einrichten -- rund 40ms nach dem Einblenden. In dieser Luecke lief der Auftritt der ganzen
   Ansicht an und brach dann ab: die Ansicht sprang auf sichtbar, gleich danach ging der Inhalt fuer
   den eigenen Auftritt wieder auf unsichtbar ("flasht ein paar Mal auf und zu"). */
/* Der Platzhalter (.uvb-root, core.js seitenVorab) blendet sich selbst ein, und die Seite danach
   uebernimmt seine Bewegung (data-up-vorab-seit, 13.10. abends) -- ein Einblenden der ganzen
   Ansicht dazu liefe gegen beide, und eine Seite ohne eigenen Auftritt (Mira, Events) blendete
   beim Austausch noch einmal von unsichtbar ein: der Kopf blinkte. */
/* Ansichten MIT Seiten-Komponente: keine Bewegung an der Ansicht -- die Seite bewegt ihre Teile
   selbst, und zwei Auftritte uebereinander laufen gegeneinander. Ueber Spezifitaet (0,3,0 bzw.
   0,4,0 gegen die 0,2,0 bzw. 0,3,0 des Kopf-Skripts), damit es auch gilt, solange der Kopf seine Zeilen noch hat. */
[id^="view-"].view-on:has(.up-seite, .uvb-root, [data-up-vorab-seit], .uds-root, .ucs-root, .upi-root, .upf-root, .uop-root, .urs-root, .utp-root, .ubr-root, .uss-root, .upa-root), [id^="view-"].view-on:has(.up-seite, .uvb-root, [data-up-vorab-seit], .uds-root, .ucs-root, .upi-root, .upf-root, .uop-root, .urs-root, .utp-root, .ubr-root, .uss-root, .upa-root) [id^="viewbody-"] { animation: none; }
@keyframes up-ansicht-auf { from { opacity: 0; } }
@keyframes up-ansicht-weg { from { transform: translateY(10px); } }
@media (prefers-reduced-motion: reduce) {
  [id^="view-"].view-on:not(:has(.up-seite, .uvb-root, [data-up-vorab-seit], .uds-root, .ucs-root, .upi-root, .upf-root, .uop-root, .urs-root, .utp-root, .ubr-root, .uss-root, .upa-root)) [id^="viewbody-"] { animation: none; }
}

/* ================= KEINE ROHE TABELLE (18.10.) ===============================================
   Eine Tabelle, die eine Seite fertig als Markup mitbringt (Prompt Insights: Prompts, Responses,
   Topics), steht vom ersten Bild an im Dokument, richtet sich aber erst ein, wenn ihr Baustein
   gemountet ist (makeMount, Wurzelbeobachter). Wird der Inhalt der Seite vorher freigegeben, sieht
   man den Kopf ohne Raster: Sortierzeichen untereinander, ohne Spalten. Nachgestellt: kamen die
   Stylesheets spaet (700ms), stand die Seite 190ms mit rohem Kopf da (Deckkraft 1, 6512 von 6512
   Bildern), weil Auftritt und Einrichtung nicht aufeinander warten.
   Das Merkmal einer fertigen Tabelle ist data-up-colsig, das makeColumns an Kopf UND Zeilen setzt.
   Solange der Kopf es nicht hat, wird der Kasten weggeschnitten, nicht versteckt: clip-path laesst
   Layout, Messen (istSichtbar, messbar) und Fokus unberuehrt, ein visibility:hidden haette das
   Einrichten selbst aufgehalten. Die Regel braucht kein JS und gilt ab dem ersten Bild.
   FRIST: kommt die Struktur nie (Baustein fehlt, Fehler beim Einrichten), gibt die Animation den
   Kasten nach 1,5 s frei. Leer und kaputt duerfen nicht gleich aussehen, also lieber roh als nie. */
.up-seite .up-box:has(.up-thead:not([data-up-colsig])) { clip-path: inset(100% 0 0 0); animation: up-roh-frist 0s linear 1500ms forwards; }
@keyframes up-roh-frist { to { clip-path: none; } }

/* ================= TIEFEN UND UMGEKEHRTE KARTE, AUCH AUSSERHALB EINER WURZEL ================
   (28.09.) Die Tiefen standen bis heute nur an .up-root. Was am <body> haengt -- Erklaerkarte,
   Tooltip, die Menues und Leisten mit eigener Palette --, erreichte sie nicht: dort loeste
   var(--up-e-2) ins Leere auf, und es galt der Rueckfallwert, der an jeder Stelle ein anderer
   war (.28, .22, .20, .16). Gemessen an der Erklaerkarte: am <body> 0 12px 32px .28, dieselbe
   Karte im Portal von create-with-ai 0 10px 28px .14 -- ein Bauteil, zwei Schatten, je nachdem,
   wo es haengt.
   Deshalb stehen die Werte jetzt HIER, an :root und an .up-root zugleich, dunkel an jedem
   [data-theme="dark"] -- dieselbe Loesung wie bei den vier Schriftstufen (24.09.), aber mit EINER
   Quelle: im Block von .up-root stehen sie nicht noch einmal.
   .up-root[data-theme="dark"] (0,2,0) steht in der dunklen Liste, damit an einer Wurzel die
   Spezifitaet entscheidet und nicht die Reihenfolge -- .up-root aus der hellen Liste ist (0,1,0).
   [data-theme="light"] steht in der hellen Liste, damit ein Element, das sein Thema selbst traegt
   (die Erklaerkarte, ein Menue), auch in einem dunklen Vorfahren hell aufloest statt zu erben. */
:root, .up-root, [data-theme="light"] {
      /* Tiefe. SEIT DEM 15.10. DIE SCHATTEN VON GEIST (vercel.com/geist/materials, die Werte aus
         Vercels eigenem Stylesheet, --ds-shadow-* an :root und an .dark-theme, gemessen am 15.10.).
         Vorher war jede Stufe EINE breite Lage (0 10px 28px .14 und so fort); Geist baut jede aus
         zwei bis drei schmalen Lagen mit negativer Streuung -- die naechste Lage zeichnet die Kante,
         die fernste das Schweben, und keine faerbt den Grund daneben grau. Das ist der Unterschied
         zwischen "liegt etwas hoeher" und "hat einen Hof".
         Geists Werte tragen zusaetzlich einen 1px-Ring als Rahmen (--ds-shadow-border-base). Der
         fehlt hier mit Absicht: jedes Bauteil der App hat seinen Rahmen schon als border, ein Ring
         dazu waere eine zweite Linie um denselben Kasten.
         --up-dd-shadow IST e-2 (var), nicht mehr dieselbe Zahl an einem zweiten Ort.
         DIE ROLLEN (28.09., beim Aufraeumen von 42 Einzelwerten festgelegt) und ihre Geist-Stufe:
             e-1  liegt auf der Flaeche: Knopf, Feld, Regler, Kachel     Geist small
             e-2  haengt an einem Ausloeser: Menue, Popover, Tooltip,    Geist menu (= xl)
                  Erklaerkarte, Hover-Anhub
             e-3  schwebt frei ueber dem Inhalt: Leiste, Kasten          Geist modal (= 2xl)
             e-4  liegt ueber einem Schleier: Dialog, Palette, Toast     Geist modal-elevated */
      --up-e-1: 0 2px 2px rgba(0,0,0,.04);
      --up-e-2: 0 1px 1px rgba(0,0,0,.02), 0 4px 8px -4px rgba(0,0,0,.04), 0 16px 24px -8px rgba(0,0,0,.06);
      --up-e-3: 0 1px 1px rgba(0,0,0,.02), 0 8px 16px -4px rgba(0,0,0,.04), 0 24px 32px -8px rgba(0,0,0,.06);
      --up-e-4: 0 32px 72px -12px rgba(0,0,0,.06), 0 8px 32px -12px rgba(0,0,0,.08), 0 8px 24px -12px rgba(0,0,0,.12);
      /* DIE FUENFTE TIEFE: SEITLICH (23.09.). Die vier Stufen darueber werfen nach UNTEN --
         fuer eine Schublade, die von rechts einfaehrt, ist das die falsche Richtung. Der Wert
         stand bis heute vier Mal einzeln in den Dateien (Opportunity-Karte, Prompt Research,
         Onboarding-Hilfe, Mira als Drawer), zweimal mit .15 und zweimal mit .18 -- vier
         Schubladen, die nebeneinander nie zu sehen sind und deshalb auseinanderliefen.
         Seit dem 15.10. Geists Sheet-Schatten (modal-elevated), um 90 Grad gedreht: dieselben drei
         Lagen, der Versatz auf der x-Achse statt der y-Achse -- Geist selbst legt den Abwaerts-
         Schatten auch an ein Seitenblatt, dort faellt er aber unten aus dem Fenster und die Kante
         links bliebe ohne Tiefe. Die Kante selbst ist der border-left der Drawer. */
      --up-e-drawer: -32px 0 72px -12px rgba(0,0,0,.06), -8px 0 32px -12px rgba(0,0,0,.08), -8px 0 24px -12px rgba(0,0,0,.12);
      /* DIE SECHSTE TIEFE: DER GEHOBENE CHIP (28.09.). Zwei Lagen, und das ist keine Verzierung:
         die dichte Lage zeichnet die Kante, die weiche hebt den Chip ab -- eine Lage kann nur
         eines davon. Der Grund des Chips ist dafuer auf die Haelfte zurueckgenommen
         (quick-actions.css, .mqa-chip): der Schatten TRAEGT die Abgrenzung.
         Der Wert stand achtmal einzeln da (core .up-entchip.is-lifted, quick-actions .mqa-chip und
         .mqa-mini, je hell und dunkel).
         Seit dem 15.10. Geist medium ("further raised"): ebenfalls zwei Lagen, die dichte 2px, die
         weiche 8px mit -8px Streuung, die nur unter dem Chip austritt. */
      --up-e-chip: 0 2px 2px rgba(0,0,0,.04), 0 8px 8px -8px rgba(0,0,0,.04);
      /* DER SCHLEIER DER DRAWER (16.10.): Schwarz zu 20 %, in beiden Themen derselbe Wert -- wie
         die Floating Groups der Bubble-Zeit, aus denen die Drawer kommen (16.10. gemeldet: "wie
         vorher in schwarzer Farbe, mit weniger Opacity"). Vom 11. bis 16.10. stand hier 18 % der
         Schrift (hell) und 45 % von --vc-text-invert (dunkel). Das ist #1f1f1b, also HELLER als
         der dunkle Grund #0a0a0a: gemessen wurde die Seite hinter dem Drawer heller statt dunkler,
         ein grauer Nebel statt eines Schleiers. Schwarz kann nur abdunkeln.
         Unschaerfe 1.5px und die Dauern 160/200ms gehoeren zum Schleier selbst bzw. zum
         Kopf-Skript; hier steht nur die Farbe. Fuer alle Drawer: Kern-Drawer, Seiten-Drawer,
         Opportunity und Prompt Research. Dialoge behalten ihre .30/.35 (STYLEGUIDE 27). */
      --up-schleier: rgba(255,255,255,.68); --up-schleier-blur: 1.5px;
      --vc-drawer-schleier: var(--up-schleier);
      /* DIE UMGEKEHRTE KARTE (28.09.): Grund und Tinte der Erklaerkarte (.up-explain), die sich
         gegen das Thema dreht -- hell eine dunkle Karte, dunkel eine helle. Sie stand fuenfmal
         als Literal da (core, domains-table, visibility-chart, quick-actions, create-with-ai),
         je mit eigener Dunkel-Regel.
         Kein vorhandenes Token trifft sie: --vc-text (#1f1f1b / #e0e0e0) waere als Grund im
         Dunkeln 14 Stufen grauer, --vc-text-invert (#e0e0e0 / #1f1f1b) als Tinte im Hellen 31
         Stufen matter als das Weiss der Ueberschrift. Beides haette die Karte sichtbar veraendert.
         Die helle Beispielplatte IN der Karte ist dieselbe Umkehrung noch einmal: ihr Grund ist
         die Tinte, ihre Schrift der Grund. Dafuer braucht es kein drittes Token. */
      --vc-inverse-bg: #1f1f1b;
      --vc-inverse-ink: #ffffff;
      /* DER TOAST (10.10., Handoff Stil 1a "Inverted"): die umgekehrte Karte wie die Erklaerkarte --
         hell eine dunkle Flaeche, dunkel eine helle (10.10. korrigiert: "invertiert heisst, im
         Darkmode hell"; die erste Fassung blieb nach dem Handoff in beiden Themen dunkel).
         Der Zustand steht nur im Zeichen, und zwar in den Werten des ANDEREN Themas: auf der
         dunklen Flaeche Geist Green 900 und Red 900 der dunklen Skala (seit 15.10., vorher #60d25d
         und #ff7a66) -- gemessen auf #1f1f1b 7.3:1 und 5.6:1. Auf der hellen Flaeche (Dunkel-Thema)
         die der hellen Skala. */
      --vc-toast-bg: var(--vc-inverse-bg);
      /* Die Schrift ist die des ANDEREN Themas (10.10.: "immer in Fontfarbe des je anderen
         Themes, nicht weiss und schwarz"): Titel #e0e0e0 (--vc-text dunkel), Untertitel und
         Zeichen #a0a0a0 (--vc-third dunkel), das X #6c717a (--vc-fourth dunkel). Das dunkle
         Thema unten traegt spiegelbildlich die hellen Werte. */
      --vc-toast-ink: #e0e0e0;
      --vc-toast-sub: #a0a0a0;
      --vc-toast-ok: #62c073;
      --vc-toast-fehler: #ff6166;
      --vc-toast-neutral: #a0a0a0;
      --vc-toast-flaeche: rgba(255,255,255,.10);
      --vc-toast-flaeche-hover: rgba(255,255,255,.18);
      --vc-toast-zu: #6c717a;
      /* Hervorgehobener Teil im Toast (UC.toast opts.hervor): die Hauptschrift des Toasts, nie die
         Akzentfarbe (Nutzer 18.10.); heller als der Rest der Nachricht. */
      --vc-toast-akzent: #e0e0e0;
      /* ---- DIE ZUSTANDSFARBEN NACH GEIST (15.10.) -------------------------------------------
         "Chartfarben, Trend Chips, das Gruen vom Prompt Quota etc.: Nimm fuer das alles bitte das
         Geist Design System, das ist das neue System fuer diese Sachen." Die Werte sind Vercels
         eigene, ausgelesen am 15.10. aus dem Stylesheet von vercel.com/geist/colors (--ds-*-value an
         :root fuer hell, an .dark-theme fuer dunkel; die sRGB-Fassung, die Geist auf Bildschirmen
         ohne P3 zeigt -- die P3-Fassung in oklch() liest Chart.js nicht).
         GEISTS EIGENE ROLLENVERTEILUNG, uebernommen statt nachgebaut ("Colors 1-10"):
             100-300  Grund einer Komponente      400-600  Rahmen
             700-800  kraeftige Flaeche           900      Text und Zeichen (Sekundaer)
         Also: Schrift in Farbe immer Stufe 900, eine Flaeche (Ring, Punkt, Balken) 700, die Notiz
         100 mit Rand 400 -- genau so baut Geist seine Note (geist-new-error: Red 900 auf Red 100,
         Rand Red 400) und seine Gauge (Green 700, Amber 700, Red 800).
         AN :root UND NICHT AN .up-root, aus demselben Grund wie die Tiefen oben: Tooltips, Dialoge
         und Menues haengen am <body>. Bis heute trugen vier davon eigene Kopien von --vt-up/-down
         (performance-radar, settings-brand), weil die Marke an .up-root dort nicht ankam.
         Gemessen (WCAG, Text 12-13px braucht 4.5):
             --vt-up     Green 900 #297a3a   auf Weiss 5.33, auf #fafafa 5.11   (vorher #2ea84a: 3.08)
             --vt-down   Red 900   #cb2a2f   auf Weiss 5.37, auf #fafafa 5.14   (vorher #b0200c: 6.87)
             --vc-warn   Amber 900 #a35200   auf Weiss 5.58, auf Amber 100 5.20 */
      --vt-up: #297a3a;   --vt-down: #cb2a2f;
      /* Die FLAECHE in diesen Farben: der Ring des Prompt-Kontingents, der Punkt "Active", ein
         Balken. Geist Gauge: Green 700 im Normalfall, Red 800 als Fehlerstufe, Amber 700 als
         Warnung. Eine Grafik braucht 3:1 (WCAG 1.4.11): Green 700 auf Weiss 3.10. */
      --vt-up-flaeche: #45a557;   --vt-down-flaeche: #da2f35;   --vt-warn-flaeche: #ffb224;
      /* Eine erreichte Grenze, die nichts verlangt (28.10., Lukas: "wenn die voll sind, mach die eher orange.
         nicht rot. rot sieht aus als bestaende Handlungsbedarf"): das volle Kontingent. Geist Amber 800, eine
         Stufe kraeftiger als die Warnung (Amber 700 liest im 22er Ring gelb, nicht orange). Farbton 35 Grad;
         auf Weiss 2.14, auf dem grauen Kopf 1.91 -- unter den 3:1 einer Grafik, hingenommen: die Grenze sagt
         die Zahl daneben ("100 of 100 prompts"), der Bogen ist Zugabe. Dunkel 8.96. */
      --vt-voll-flaeche: #ff990a;
      /* Die Notiz (Geist Note): Text 900, Grund 100, Rand 400. --vc-pos/--vc-neg standen bis heute
         nirgends -- auth-page, onboarding und .up-formerr lebten von Rueckfallwerten, je mit eigenem
         Rot (#c8412e) und Gruen (#2e9e63). Jetzt loesen sie auf. */
      --vc-pos: var(--vt-up);     --vc-pos-bg: #effbef;
      --vc-neg: var(--vt-down);   --vc-neg-bg: #fff0f0;   --vc-neg-border: #fdd8d8;
      /* Der Hinweis, der kein Fehler ist (settings-brand .usb-stuck, "Speichern bleibt aus"). Bis
         heute ohne Marke: "die Palette hat keinen Warnton". Geist hat einen. */
      --vc-warn: #a35200;         --vc-warn-bg: #fff6e5;  --vc-warn-border: #ffdd8f;
      /* DER GLANZ DES SKELETTS (15.10., Geist gray-100 -> gray-200). Im Hellen eine Spur DUNKLER
         als der Grund, im Dunkeln eine Spur heller -- an vercel.com gemessen 242 -> 235 (hell) und
         26 -> 31 (dunkel). Auf --vc-sk (#eeedef / #28282c) sind das 3 Prozent Schwarz und 2.5
         Prozent Weiss: dieselben 7 und 5 Stufen. Ein Glanz ueber dem Grund statt eines Verlaufs
         zwischen zwei Festfarben, damit die Grundfarbe am Element aufgeloest wird (--vc-sk) und
         dieser Teil trotzdem auch am <body> gilt. Lage, Groesse und Wiederholung stehen mit drin,
         also setzt eine Regel nur noch die Grundfarbe davor (siehe --up-sk-bewegung).
         Steht in BEIDEN Themenbloecken: ein Token, das ein anderes liest, wird dort ausgerechnet,
         wo es deklariert ist -- stuende es nur hier, erbte ein dunkles Portal den hellen Glanz. */
      --up-sk-glanz: rgba(0,0,0,.03);
      --up-sk-verlauf: linear-gradient(90deg, rgba(0,0,0,0) 0%, var(--up-sk-glanz) 50%, rgba(0,0,0,0) 100%) 0 0 / 150% 100% repeat-x;
      /* ---- Komponentenfarben: Prompts-Tabelle, Brands (17.10.) ----
         Die Farben, die prompts-table.css/.js und brands-overview.css/.js bis zum 17.10. als
         Literal fuehrten, Wert fuer Wert uebernommen (gemessen: keine sichtbare Aenderung).
         FESTE WERTE, KEINE THEMENWERTE -- bis auf das Raster der Landschaft ganz unten. Die
         Massenleiste, das Topic-Popup, der Tooltip und das Aktionsmenue haengen am <body> und
         waehlen ihre Fassung ueber ihr EIGENES data-theme (die Leiste sogar umgekehrt zum Thema der
         App, prompts-table.js leistenThema). Ein Themenwert hier wuerde dagegen ueber das Thema des
         Vorfahren entscheiden -- deshalb stehen beide Fassungen nebeneinander (-hell, -dunkel) und im
         dunklen Block nichts davon. An :root, damit sie am <body> ankommen. */
      /* Massenleiste (.upt-bulkbar), helle Fassung -- sie steht im DUNKLEN Thema der App. */
      --vc-upt-leiste-hell-grund: #e0e0e0;
      --vc-upt-leiste-hell-text: #1f1f1b;
      --vc-upt-leiste-hell-muted: #6b6f78;
      --vc-upt-leiste-hell-rand: #e4e2e4;
      --vc-upt-leiste-hell-trenner: #c6c5c7;
      --vc-upt-leiste-hell-flaeche: #f9f8f9;
      --vc-upt-leiste-hell-hover: #f9f8f9;
      --vc-upt-leiste-hell-third: #9e9e9e;
      --vc-upt-leiste-hell-rand-hover: #6f6f6f;
      --vc-upt-leiste-hell-surface: #ffffff;
      --vc-upt-leiste-hell-akzent: #1f1f1b;
      --vc-upt-leiste-hell-akzent-fg: #ffffff;
      --vc-upt-leiste-hell-schalter-aus: #d6d6d4;
      --vc-upt-leiste-hell-fokus: #4A9EFF;
      --vc-upt-leiste-hell-fokus-ring: rgba(74,158,255,0.20);
      /* Massenleiste, dunkle Fassung -- sie steht im HELLEN Thema der App. Die Flaeche der
         Werkzeugknoepfe (-hover) liegt mit Absicht hoeher als -flaeche (prompts-table.css, 23.09.). */
      --vc-upt-leiste-dunkel-grund: #1f1f1b;
      --vc-upt-leiste-dunkel-text: #e0e0e0;
      --vc-upt-leiste-dunkel-muted: #a0a0a0;
      --vc-upt-leiste-dunkel-rand: #3e3e44;
      --vc-upt-leiste-dunkel-trenner: #3e3e44;
      --vc-upt-leiste-dunkel-flaeche: #232326;
      --vc-upt-leiste-dunkel-hover: #33333a;
      --vc-upt-leiste-dunkel-third: #808080;
      --vc-upt-leiste-dunkel-rand-hover: #707070;
      --vc-upt-leiste-dunkel-surface: #232326;
      --vc-upt-leiste-dunkel-akzent: #e0e0e0;
      --vc-upt-leiste-dunkel-akzent-fg: #1b1b1b;
      --vc-upt-leiste-dunkel-schalter-aus: #444444;
      --vc-upt-leiste-dunkel-fokus: #6DB2FF;
      --vc-upt-leiste-dunkel-fokus-ring: rgba(109,178,255,0.28);
      /* Schrift auf dem scharfgestellten Loeschknopf der Leiste: Weiss auf --vt-down, in BEIDEN
         Fassungen -- --up-accent-fg waere in der hellen Leiste fast schwarz. */
      --vc-upt-gefahr-ink: #fff;
      /* Rand des Farbfelds im Topic-Ersteller der Leiste (auf jeder Topic-Farbe gleich). */
      --vc-upt-farbfeld-rand: rgba(0,0,0,.14);
      /* Topic-Zelle der Tabelle (prompts-table.js, eingespritzte Regeln): der Strich einer leeren
         Zelle, hell und mit data-theme="dark" an der Zelle; das Popup der weiteren Topics, hell und
         dunkel. Die dunklen Popup-Werte sind der Rueckfall fuer --up-menu-bg/--up-menu-border, die am
         <body> nur unter html[data-up-dunkel="geist"] stehen. */
      --vc-upt-zelle-leer: #a0a5ad;
      --vc-upt-zelle-leer-dunkel: #555;
      --vc-upt-popup-grund: #fff;
      --vc-upt-popup-rand: #d9dde3;
      --vc-upt-popup-dunkel-grund: #0f0f0f;
      --vc-upt-popup-dunkel-rand: #333333;
      /* Ein Topic ohne eigene Farbe (Zelle, Popup, Gruppenkopf, eigene Gruppe). */
      --vc-upt-topic-ohne-farbe: #6b7280;
      /* Brands: schwebende Schichten am <body> (Tooltip der Landschaft .ubo-mxtip, Aktionsmenue
         .ubo-actmenu), hell. Grund und Rand der dunklen Fassung kommen schon aus --up-menu-bg/-border. */
      --vc-ubo-schicht-text: #1f1f1b;
      --vc-ubo-schicht-muted: #6b6f78;
      --vc-ubo-schicht-rand: #dcdbdd;
      --vc-ubo-schicht-grund: #ffffff;
      --vc-ubo-schicht-kopf: #f9f8f9;
      --vc-ubo-schicht-wahl: #f4f2f4;
      --vc-ubo-schicht-logo: #ffffff;
      --vc-ubo-schicht-third: #9e9e9e;
      --vc-ubo-schicht-dunkel-text: #ededed;
      --vc-ubo-schicht-dunkel-muted: #a1a1a1;
      --vc-ubo-schicht-dunkel-logo: #e0e0e0;
      --vc-ubo-schicht-dunkel-third: #808080;
      /* Landschaft (Canvas): Schatten des ueberfahrenen Logos, Punkt einer Marke ohne Logo und Farbe
         im Markenfilter. */
      --vc-ubo-logo-schatten: rgba(0,0,0,0.14);
      --vc-ubo-marke-ohne-farbe: #9e9e9e;
      /* Das gestrichelte Raster der Landschaft -- als EINZIGES hier ein Themenwert (dunkel im Block
         darunter): das Chart liest es an seiner Wurzel, deren data-theme ist das Thema. */
      --vc-ubo-raster: rgba(0,0,0,0.09);
      /* ---- Komponentenfarben: Quick Actions, Anmeldung, Onboarding (17.10.) ---- */
      /* Bis zum 17.10. standen diese Werte als Literal in quick-actions.css, auth-page.css/.js und
         onboarding-page.css/.js ("alles, was wir an Farben nutzen, nach core"). Wert fuer Wert
         uebernommen, nichts nachgestellt: dieselbe Zeichenkette, damit auch die aufgeloesten
         Variablen der Komponenten (--am-*, --uau-*) gleich bleiben. An :root, weil die Palette der
         Quick Actions am <body> haengt.
         QUICK ACTIONS (Palette --am-* in quick-actions.css). Hell eigene, etwas blaustichige Graue;
         im Dunkeln zeigt die Palette auf die Menue-Token -- die dunklen Werte unten sind genau diese
         Verweise, wie sie quick-actions.css im dunklen Block weiterhin selbst setzt. */
      --vc-qa-rand: #e5e7eb;                 /* --am-border */
      --vc-qa-rand-stark: #d9dde3;           /* --am-border-strong: Hover-Kante des Knopfes, Rollbalken */
      --vc-qa-weich: #f7f8fa;                /* --am-soft: Grund des Knopfes beim Ueberfahren */
      --vc-qa-weich-2: #f3f4f6;              /* --am-soft-2: Chips, Avatare, Tastenkuerzel */
      --vc-qa-karte: #ffffff;                /* --am-card: die offene Palette, voll deckend */
      --vc-qa-knopf: rgba(255,255,255,0.60); /* --am-trigger-bg: nur der geschlossene Knopf, 60 % Weiss */
      --vc-qa-hover: #eef0f3;                /* --am-hover-bg: Zeichenknoepfe (Chip-x, Leeren, Stern) */
      /* Der Schleier hinter der Palette: hell der Grund der Karte zu 80 % (Geist), dunkel Schwarz. */
      --vc-qa-schleier: var(--up-schleier);
      /* Beschriftungs-Chip der zwei Knoepfe im Eingabefeld (.mqa-btip): wie .up-tip in BEIDEN
         Themen die dunkle Karte -- deshalb nicht --vc-inverse-*, das sich im Dunkeln dreht. */
      --vc-qa-tip-bg: #1f1f1b;
      --vc-qa-tip-ink: #ffffff;
      /* Die Taste "/" in der Erklaerkarte der Filter (.mqa-tip-key): hell eine helle Taste auf der
         dunklen Karte; dunkel Grund und Rand der Palette. */
      --vc-qa-taste-bg: #f4f4f5;
      --vc-qa-taste-rand: #e4e2e4;
      /* Ueberschrift des Fehlerzustands (.mqa-error .mqa-empty-title), in beiden Themen derselbe Ton. */
      --vc-qa-fehler: #b4451f;
      /* ANMELDUNG (auth-page). Der Hover des Google-Knopfes und des Themenschalters; dunkel die
         Kopfflaeche, wie auth-page.css sie im dunklen Zweig selbst setzt. */
      --vc-auth-hover: #f7f8fa;
      /* Passwortstaerke: Stufe 2 ("Fair") und 3 ("Good"). 1 und 4 sind --vc-neg und --vc-pos. */
      --vc-auth-staerke-mittel: #d98324;
      --vc-auth-staerke-gut: #b8a02a;
      /* Der Haken im gewaehlten Kaestchen (.uau-check): weiss auf der dunklen Akzentflaeche. */
      --vc-auth-haken: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
      /* Die rechte Spalte: der graue Verlauf der Landingpage (wortgleich .ulh-bg-foto in
         landing-hero.css -- wer eine Seite aendert, aendert die andere mit), die Lichter unten
         (.ulh-bg-foto::after; im Dunkeln dimmt auth-page.css sie auf ein Viertel) und das
         Linienmuster (.ulh-bg-grid). Begruendung der dunklen Fassungen im dunklen Block. */
      --vc-auth-panel-verlauf: linear-gradient(to bottom,
              rgba(246, 247, 248, 0) 16%, #f6f7f8 34%, #e8eaec 60%, #dbdde1 100%);
      --vc-auth-panel-lichter:
    radial-gradient(58% 72% at 44% 100%,
      rgba(255, 255, 255, .34) 0%, rgba(255, 255, 255, .24) 28%,
      rgba(255, 255, 255, .1) 62%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(34% 52% at 61% 97%,
      rgba(255, 255, 255, .16) 0%, rgba(255, 255, 255, .09) 40%,
      rgba(255, 255, 255, .03) 72%, rgba(255, 255, 255, 0) 100%),
    linear-gradient(to right,
      rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .02) 18%, rgba(255, 255, 255, .06) 32%,
      rgba(255, 255, 255, .09) 44%, rgba(255, 255, 255, .1) 50%, rgba(255, 255, 255, .09) 56%,
      rgba(255, 255, 255, .06) 68%, rgba(255, 255, 255, .02) 82%, rgba(255, 255, 255, 0) 100%);
      --vc-auth-panel-raster: repeating-linear-gradient(to right,
              rgba(255, 255, 255, .55) 0, rgba(255, 255, 255, .55) 1px,
              transparent 1px, transparent 8px);
      /* Fremdmarke: das Google-Zeichen im Anmeldeknopf, Googles vier Farben, in jedem Thema gleich. */
      --vc-marke-google-blau: #4285F4;
      --vc-marke-google-gruen: #34A853;
      --vc-marke-google-gelb: #FBBC05;
      --vc-marke-google-rot: #EA4335;
      /* ONBOARDING. Schrift in den Logokacheln (.uob-ident-logo, .uob-item-logo): die Kachel ist in
         BEIDEN Themen hell, also traegt sie in beiden die hellen Schriftfarben (Begruendung in
         onboarding-page.css). */
      --vc-ob-kachel-text: #1f1f1b;
      --vc-ob-kachel-muted: #6b6f78;
      /* Themenfarbe, wenn die Themenpalette aus core fehlt (onboarding-page.js). */
      --vc-ob-thema-rueckfall: #6b7280;
      /* ---- Komponentenfarben: Mira (17.10.) ----
         Alles, was ask-mira.css und ask-mira.js bis heute als Literal trugen ("alles, was wir an
         Farben nutzen -> core"), mit GENAU den Werten von vorher -- die Umstellung aendert keinen
         einzigen Bildpunkt, gemessen per Fingerabdruck ueber vier Themen. Mira liest sie ueber
         ihre --am-* oder direkt; die --am-*-Namen bleiben.
         Hier, an :root, .up-root und [data-theme="light"], und nicht an #ask-mira: das Chat-Menue
         (.am-cm), das Einstellungsfenster (.am-set-backdrop) und das Mini-Menue haengen am <body>.
         GLEICH IN ALLEN VIER THEMEN (hell, grau-hell, dunkel, Geist-dunkel) und deshalb nur hier:
         .up-root trifft auch eine dunkle Wurzel, :root alles am <body>. So standen sie auch in der
         Komponente -- ein Literal ohne Dunkel-Regel. Die Rollen:
           status*            der Punkt "Ready" und sein Puls, der Ring beim Laden
           opp-*              die vier Status einer Opportunity (Punkt im Statusmenue); erledigt
                              auch der Knopf "Add as opportunity" nach dem Anlegen
           thema-rueckfall    ein Topic ohne Farbe (hex_light/hex_dark leer)
           logo-rueckfall     der Buchstabenkasten, wenn ein Logo im Lauf nicht laedt
           auf-farbe          Schrift und Zeichen auf einer Farbflaeche (Buchstabenkasten, Pille
                              "neue Nachricht", gewaehlter Haken hell, Punkte auf der Reglerfuellung)
           lauf-glanz         der Lichtstreifen ueber dem laufenden Schritt
           signal*            das Blau fuer "neue Nachricht" (Pille im Chat, Punkt in der Chatliste)
           regler-*           Daumen und gefuellter Halt des Aufwand-Reglers
           haken-rand         der leere Haken im Einstellungs-Dropdown
           aufnahme-punkt     der rote Punkt des Diktiermodus
           speichern/loeschen Hover der Mini-Knoepfe und "Delete" im Chat-Menue */
      --vc-mira-status: #2ec27e;
      --vc-mira-status-puls: rgba(46,194,126,0.5);
      --vc-mira-status-puls-an: rgba(46,194,126,0.45);
      --vc-mira-status-puls-aus: rgba(46,194,126,0);
      --vc-mira-status-laden: rgba(31,31,27,0.4);
      --vc-mira-opp-offen: #9ca3af;
      --vc-mira-opp-laeuft: #2384E2;
      --vc-mira-opp-erledigt: #15803d;
      --vc-mira-opp-ignoriert: #b4451f;
      --vc-mira-thema-rueckfall: #6b7280;
      --vc-mira-logo-rueckfall: #9ca3af;
      --vc-mira-auf-farbe: #fff;
      /* Der Streifen des Glanzes (.up-glanz). Seit dem 22.10. abends ein Token fuer alle; der Name von Mira
         bleibt fuer Page Audit (page-audit.css liest ihn noch). */
      --vc-glanz: rgba(255,255,255,0.85);
      --vc-mira-lauf-glanz: var(--vc-glanz);
      --vc-mira-signal: #2f6df6;
      --vc-mira-signal-hover: #2b63dd;
      --vc-mira-signal-schein: rgba(47,109,246,0.30);
      /* Die Glut ueber dem Eingabefeld, solange der Agent antwortet (26.10., .am-composer-shell::before).
         Ton: das Blau der App, mit gewaehltem Akzent dessen Tinte (Regel bei den Akzentfarben). Im
         Hellen ein HELLERES Blau (der Ton in oklch zu 40 % mit Weiss gemischt, so bleibt er satt) zu 30 %:
         Lukas, 26.10. abends: "im lightmode ist mir die blaue waber farbe zu dunkel", dann zu 7 % gesenkt: "immernoch
         recht dunkel und kaum erkennbar" -- weniger Deckkraft machte das Blau nur blasser und grauer, nicht heller.
         26.10. spaet "in lightmode etwas heller": Weiss von einem Viertel auf 40 %.
         Die dunkle Fassung steht im dunklen Block. */
      --vc-mira-glut-ton: var(--up-blau);
      --vc-mira-glut: color-mix(in srgb, color-mix(in oklch, var(--vc-mira-glut-ton) 60%, white) 30%, transparent);
      --vc-mira-regler-punkt: rgba(255,255,255,0.85);
      --vc-mira-regler-daumen: #ffffff;
      --vc-mira-haken-rand: #b7bbc2;
      --vc-mira-aufnahme-punkt: #e5484d;
      --vc-mira-speichern: #1a7f4b;
      --vc-mira-loeschen: #c0392b;
      /* JE THEMA VERSCHIEDEN, hier die helle Fassung (grau-hell gleich), die dunkle im naechsten
         Block (Geist-dunkel gleich). Wo die Komponente hell und dunkel je ein Literal fuehrte, wechselt
         jetzt das Token, und ihre Dunkel-Regel ist entfallen. Steht ein var() darin, ist das der Wert,
         den Miras eigene Regel in diesem Thema schon aus einer Marke nahm -- dort bleibt die Regel
         stehen, und die Zeile hier haelt das Token in beiden Themen vollstaendig.
           akzent*            Miras Violett und Blau (die Mini-Knoepfe "an")
           blase-*            die Blase des Nutzers (dunkel #2f2f2f, hell die Kopfflaeche)
           raster             das 44px-Raster hinter der Schale
           positiv/negativ/quelle/unklar  die Markierungen im Antworttext (Grund und Schrift)
           tabelle-streifen   jede zweite Zeile einer Tabelle in der Antwort
           composer-schale    die leise Flaeche um das Eingabefeld
           gewaehlt           gewaehlte Zeile im Modell-Menue und im Einstellungs-Dropdown
           entfernen-hover    das X an einem Zitat und an einem Chip im Picker
           picker-schatten    die Farbe des Schattens nach oben (die Masse stehen in der Regel)
           aufnahme-*         Hover der beiden Knoepfe im Diktiermodus, seine Fehlerzeile
           schleier           ueber der Seite, wenn die Chatliste am Telefon aufliegt
           haken-an           der gewaehlte Haken im Dunkeln (hell traegt er --am-primary)
           skelett-blase-*    die zwei Toene des Skeletts in der Blase des Nutzers */
      --vc-mira-akzent: #7c5cff;
      --vc-mira-akzent-2: #4f8bff;
      --vc-mira-akzent-soft: rgba(124,92,255,0.10);
      --vc-mira-blase-bg: var(--vc-heading-bg);
      --vc-mira-blase-ink: var(--vc-text);
      --vc-mira-raster: rgba(31,31,27,0.022);
      --vc-mira-positiv-bg: rgba(22,163,74,0.09);    --vc-mira-positiv-ink: #166534;
      --vc-mira-negativ-bg: rgba(220,38,38,0.14);    --vc-mira-negativ-ink: #991b1b;
      --vc-mira-quelle-bg: rgba(37,99,235,0.085);    --vc-mira-quelle-ink: #1d4ed8;
      --vc-mira-unklar-bg: rgba(217,119,6,0.10);     --vc-mira-unklar-ink: #92400e;
      --vc-mira-tabelle-streifen: rgba(0,0,0,0.012);
      --vc-mira-composer-schale: #f9f8f9;
      --vc-mira-gewaehlt: #f0f0ef;
      --vc-mira-entfernen-hover: rgba(0,0,0,0.06);
      --vc-mira-picker-schatten: rgba(0,0,0,0.06);
      --vc-mira-aufnahme-weg-hover: #e9e9ec;
      --vc-mira-aufnahme-ok-hover: #585855;
      --vc-mira-aufnahme-fehler: var(--vt-down);
      --vc-mira-schleier: var(--up-schleier);
      --vc-mira-haken-an: var(--vc-text);
      --vc-mira-skelett-blase-a: rgba(255,255,255,0.35);
      --vc-mira-skelett-blase-b: rgba(255,255,255,0.6);
      /* ---- Ende Komponentenfarben: Mira ---- */

      /* ---- Komponentenfarben: übrige Bereiche (17.10.) ---- */
      /* "Alles, was wir an Farben nutzen, nach core" (17.10.). Die Werte sind GENAU die, die bis
         heute als Literal in der Komponente standen -- nichts ist dabei neu gewaehlt, gemessen an
         jeder Flaeche hell, grau-hell, dunkel und Geist-dunkel. Hier und nicht an .up-root, weil die
         Haelfte davon an Flaechen am <body> haengt (Dialoge, Tooltips, Erklaerkarten, Schleier).
         ZWEI ARTEN. Was nur in diesem Block steht, gilt in BEIDEN Themen (die Komponente hatte keine
         dunkle Fassung) -- die dunkle Liste unten setzt es nicht noch einmal. Was in beiden Bloecken
         steht, dreht mit dem Thema; aufgeloest wird es am naechsten [data-theme], also genau dort,
         wo vorher die dunkle Regel der Komponente griff. */
      /* Schrift und Zeichen auf einer kraeftigen Farbflaeche: der rote Knopf und das rote Kopfband
         des Gefahrendialogs (settings-brand), der Haken der farbigen Checkbox (Combo-Chart), das
         Zeichen auf der Ueberblendung des Profilbilds (Preferences). In beiden Themen Weiss. */
      --vc-ink-auf-farbe: #ffffff;
      --vc-ink-auf-farbe-leise: rgba(255,255,255,.82);
      --vc-hover-auf-farbe: rgba(255,255,255,.16);
      /* Der Mischpartner, mit dem ein kraeftiger Knopf beim Ueberfahren dunkler wird
         (color-mix(in srgb, var(--vt-down) 86%, var(--vc-abdunkeln))). */
      --vc-abdunkeln: #000000;
      /* Die feine Kante um Flaggen (Branchen-/Marktwahl, Prompt Research), damit eine weisse
         Fahne nicht im Grund verschwindet. */
      --vc-flagge-rand: rgba(0,0,0,.08);
      /* Der Rand des Farbfelds im Brand Editor, Ruhe und Hover. */
      --vc-farbfeld-rand: rgba(0,0,0,.14);
      --vc-farbfeld-rand-hover: rgba(0,0,0,.28);
      /* Die Ueberblendung auf dem Profilbild (Preferences), beim Zeigen. */
      --vc-bild-ueberblendung: rgba(0,0,0,.45);
      /* Der Schleier hinter der ausgefahrenen Seitenleiste (hint-Zustand). In BEIDEN Themen .30:
         die dunkle Regel in sidebar.css (.45) war schwaecher (0,2,0) als die helle (0,3,0) und hat
         nie gegriffen -- gemessen 17.10. Bleibt so, eine Angleichung waere eine eigene Entscheidung. */
      --vc-seitenleiste-schleier: var(--up-schleier);
      /* Der Fehlerring im Team-Formular: der helle Ring von --vc-fehler-ring, in BEIDEN Themen
         (team-orga hatte nie eine dunkle Fassung). */
      --vc-fehler-ring-fest: rgba(176,32,12,.16);
      /* Fokus des Export-Knopfs (.uex-btn, 12 % der Schrift), in beiden Themen. */
      --vc-fokus-schatten-leise: rgba(31,31,27,.12);
      /* Die Zellen der Performance-Matrix (performance-radar): Ziffer auf dunkler und auf heller
         Flaeche, dazu der Schimmer (Glanz oben, Tiefe unten). Die Flaeche selbst kommt aus der
         Heat-Rampe (--uhm-h0 ...). */
      --vc-matrix-ink: rgba(255,255,255,.94);
      --vc-matrix-ink-hell: rgba(0,0,0,.78);
      --vc-matrix-glanz: rgba(255,255,255,.30);
      --vc-matrix-tiefe: rgba(0,0,0,.07);
      /* Die Volumen-Leiter von Prompt Research, von "kaum gesucht" bis "hohe Nachfrage". Bewusst
         nicht --vt-up/--vt-down: eine eigene Skala. Die Spur dreht mit dem Thema (unten). */
      --vc-volumen-1: #d2865d; --vc-volumen-2: #d9b85d; --vc-volumen-3: #9fd25d; --vc-volumen-4: #60d25d;
      /* Die Spalten der Opportunities (Punkt vor dem Spaltennamen): Statusfarben, nicht die
         Up/Down- oder Zitationsfarben. */
      --vc-status-offen: #9ca3af;
      --vc-status-in-arbeit: #2384E2;
      --vc-status-erledigt: #15803d;
      --vc-status-ignoriert: #b4451f;
      /* Die Farbe einer Topic, die selbst keine traegt (hex_light und hex_dark leer). Die Dateien
         hatten je einen eigenen Ersatz, und der bleibt: Opportunities, Radar, Performance-Detail,
         Topics Manager und die Ergebnistabelle von Prompt Research #6b7280; die Vorschlaege und
         der Editor von Prompt Research #6b6f78; dessen Ladechips #9ca3af; Filterliste und
         Add Prompts #808080. Gelesen per getComputedStyle (die Werte gehen dort in Daten ein). */
      --vc-thema-ersatz: #6b7280;
      --vc-thema-ersatz-vorschlag: #6b6f78;
      --vc-thema-ersatz-lader: #9ca3af;
      --vc-thema-ersatz-filter: #808080;
      /* Die Platte hinter einem Logo ohne Bild (Visibility Chart, Erwaehnungen). */
      --vc-logo-ersatz: #9e9e9e;

      /* AB HIER DREHT ES MIT DEM THEMA (dunkle Werte in der Liste darunter). */
      /* Der Schleier hinter einem Dialog (STYLEGUIDE 27): .30 hell, .35 dunkel -- Gefahrendialog
         der Settings. */
      --vc-dialog-schleier: var(--up-schleier);
      /* Die Schicht am <body> mit eigener Leiter: Gefahrendialog (settings-brand .usb-dlg) und
         Tooltip der Performance-Matrix (.uhm-tip). Ihre Schrift ist eine Stufe leiser als die der
         Wurzel (#6b6f78 als zweite, #9e9e9e als dritte Stufe), ihr Rand der eines Menues. */
      --vc-schicht-text: #1f1f1b;
      --vc-schicht-muted: #6b6f78;
      --vc-schicht-third: #9e9e9e;
      --vc-schicht-border: #dcdbdd;
      --vc-schicht-flaeche: #ffffff;
      --vc-schicht-leise: #f9f8f9;
      --vc-schicht-hover-rand: #6f6f6f;
      --vc-schicht-fokus: #4A9EFF;
      --vc-schicht-fokus-ring: rgba(74,158,255,0.20);
      --vc-schicht-logo-platte: #ffffff;
      /* Die leere Zelle der Performance-Matrix (keine Messung). Im Dunkeln die leise Flaeche der
         Wurzel; im Hellen fest -- auch im grauen Hell, wie bisher. */
      --vc-matrix-leer: #f9f8f9;
      /* Die Spur der Volumen-Leiter (Prompt Research, auch in ihrer Erklaerkarte). */
      --vc-volumen-spur: #e8eaee;
      /* Das Millimeterpapier hinter dem Start von Prompt Research. */
      --vc-raster-linie: rgba(31,31,27,0.025);
      /* Der Kalender (filters/date-range): Hover einer Zeile, gewaehlter Zeitraum, das Band
         zwischen Anfang und Ende und der Endpunkt. Feste Mischungen, nicht color-mix -- Chrome
         rechnet die beim Themenwechsel nicht neu (Begruendung in date-range.css). Der Endpunkt
         traegt im Hellen die Schrift (Regel mit --vc-text), das Token nur im Dunkeln. */
      --vc-kalender-hover: #eaeaea;
      --vc-kalender-wahl: #e2e2e1;
      --vc-kalender-band: #ededed;
      --vc-kalender-ende: #1f1f1b;
      /* Die Karten der Opportunities: die leiseste durchscheinende Flaeche ueber dem Grund (Linear,
         2 % Schwarz hell, 5 % Weiss dunkel). Im grauen Hell und im Geist-Dunkel setzt core die
         Karte ohnehin auf --vc-bg. Dazu der Anfang des Potenzial-Verlaufs. */
      --vc-flaeche-durchscheinend: #00000005;
      --vc-potenzial-anfang: #c4c9d0;
      /* Erklaerkarten: die Beispielplatte zeigt die Balken und Logos so, wie sie im jeweiligen Thema
         auf ihr stehen -- Potenzial-Balken (Opportunities), Platzhalterlogo (Discover Brands),
         Syntaxfarben der Formatvorschau (Create with AI). */
      --vc-erklaer-potenzial-leer: #e4e2e4;
      --vc-erklaer-potenzial-a: #c4c9d0;
      --vc-erklaer-potenzial-b: #1f1f1b;
      --vc-erklaer-logo-platte: #f0f0ef;
      --vc-syntax-auszeichnung: #7c3aed;
      --vc-syntax-attribut: #b45309;
      --vc-syntax-zeichenkette: #15803d;
      /* Seit dem 22.10. auch die Codeproben von Page Audit (.up-explain-code): ein HTTP-Status braucht eine Zahl, JavaScript
         und curl-Zeilen einen Kommentar. Zahl aus derselben Familie wie die drei darueber (Tailwind 700, dunkel 300: Blau),
         Kommentar das Grau der Probenschrift (.up-explain-dot hell, --vc-third dunkel). Auf der Platte (hell Weiss, dunkel
         #1f1f1b) 6,7:1 bzw. 5,0:1, dunkel 9,2:1 bzw. 6,4:1. */
      --vc-syntax-zahl: #1d4ed8;
      --vc-syntax-kommentar: #6f6f6f;
      /* Der aktuelle Treffer einer Suche mit Schritten (.up-hl.is-aktiv, 22.10., Page Audit "View initial
         HTML"): Geist Amber 700 (der Ton von --vt-warn-flaeche) mit dunkler Schrift, in BEIDEN Themen --
         die uebrigen Treffer bleiben das Gelb von .up-hl, wie in der Suche des Browsers. Schrift auf
         Flaeche 10,4:1. */
      --vc-treffer-aktiv: #ffb224;
      --vc-treffer-aktiv-ink: #1f1f1b;
      /* Der Schleier hinter dem Fenster von Create with AI. */
      --vc-ki-schleier: var(--up-schleier);
      /* Fehler im Feld (Add Brand): Fokusring und Flaeche der Meldung. */
      --vc-fehler-ring: rgba(176,32,12,0.16);
      --vc-fehler-flaeche: rgba(176,32,12,0.06);
      /* Der Punkt einer Akzentwahl in den Preferences: die TINTE des Akzents im jeweiligen Thema
         (Begruendung in core bei --up-accent-ink); Standard und Blau sind die Schrift bzw. die
         Chartfarbe. */
      --vc-akzent-punkt-standard: #1f1f1b;
      --vc-akzent-punkt-blau: #3f66b0;
      --vc-akzent-punkt-azur: #0075DE;
      --vc-akzent-punkt-indigo: #5E6AD2;
      --vc-akzent-punkt-terrakotta: #BD5836;
}
[data-theme="dark"], .up-root[data-theme="dark"] {
      /* Die Tiefen im dunklen Thema, wie Geist sie an .dark-theme fuehrt (15.10.): auf dunklem
         Grund traegt ein Schatten kaum, deshalb liegt die Arbeit dort beim helleren Rahmen
         (--vc-border, --up-menu-border). Geist zieht im Dunkeln NUR die flachen Stufen an --
         small und medium, also e-1 und der Chip, von 4 auf 16 bzw. 32 Prozent, damit die Kante
         auf dem dunklen Grund ueberhaupt ankommt. Menue, Modal und Seitenblatt behalten dort
         dieselben Zahlen wie im Hellen; sie stehen hier trotzdem ausgeschrieben, damit ein
         Element mit data-theme="dark" in einer hellen Wurzel nicht still die helle Fassung erbt.
         Die Masse selbst -- Schrift, Radius, Hoehe, Dauer -- stehen NICHT noch einmal hier:
         die aendern sich mit dem Thema nicht, und ein zweiter Satz waere ein zweiter Ort zum
         Auseinanderlaufen. */
      --up-e-1: 0 1px 2px rgba(0,0,0,.16);
      --up-e-2: 0 1px 1px rgba(0,0,0,.02), 0 4px 8px -4px rgba(0,0,0,.04), 0 16px 24px -8px rgba(0,0,0,.06);
      --up-e-3: 0 1px 1px rgba(0,0,0,.02), 0 8px 16px -4px rgba(0,0,0,.04), 0 24px 32px -8px rgba(0,0,0,.06);
      --up-e-4: 0 32px 72px -12px rgba(0,0,0,.06), 0 8px 32px -12px rgba(0,0,0,.08), 0 8px 24px -12px rgba(0,0,0,.12);
      --up-e-drawer: -32px 0 72px -12px rgba(0,0,0,.06), -8px 0 32px -12px rgba(0,0,0,.08), -8px 0 24px -12px rgba(0,0,0,.12);
      --up-e-chip: 0 2px 2px rgba(0,0,0,.32), 0 8px 8px -8px rgba(0,0,0,.16);
      /* Der Schleier der Drawer: derselbe Wert wie hell, mit Absicht (siehe dort). */
      --vc-drawer-schleier: rgba(0,0,0,.2);
      /* Die Erklaerkarte im Dunkeln: heller Grund, die Schrift im Ton der hellen Karte. */
      --vc-inverse-bg: #eeedef;
      --vc-inverse-ink: #1f1f1b;
      /* Der Toast als helle Karte (umgekehrt, siehe oben): dunkle Schrift, Zustandsfarben des
         hellen Themas, die Flaechen der Knoepfe aus Schwarz statt aus Weiss. */
      --vc-toast-bg: var(--vc-inverse-bg);
      --vc-toast-ink: var(--vc-inverse-ink);
      --vc-toast-sub: #6b6f78;
      /* Geist Green 900 und Red 900 der HELLEN Skala, wie --vt-up/--vt-down im hellen Block (15.10.):
         auf #eeedef 4.57:1 und 4.60:1. */
      --vc-toast-ok: #297a3a;
      --vc-toast-fehler: #cb2a2f;
      --vc-toast-neutral: #6b6f78;
      --vc-toast-flaeche: rgba(31,31,27,.08);
      --vc-toast-flaeche-hover: rgba(31,31,27,.14);
      --vc-toast-zu: #80858e;
      --vc-toast-akzent: #1f1f1b;
      /* Die Zustandsfarben nach Geist, dunkle Skala (Begruendung im hellen Block). Gemessen auf der
         Karte #1c1c1f: --vt-up Green 900 7.53, --vt-down Red 900 5.79, --vc-warn Amber 900 8.05
         (vorher #60d25d und #d25d5d). Die Flaechen 700/800 sind bei Geist in beiden Themen dieselben
         Toene; Red 800 dunkel ist #d93036 statt #da2f35. */
      --vt-up: #62c073;   --vt-down: #ff6166;
      --vt-up-flaeche: #45a557;   --vt-down-flaeche: #d93036;   --vt-warn-flaeche: #ffb224;
      --vt-voll-flaeche: #ff990a;
      /* --vc-pos/--vc-neg noch einmal, obwohl es dieselbe Formel ist wie hell: ein var() in einer
         Marke wird dort aufgeloest, wo sie DEKLARIERT ist. Ein dunkles Element am <body>, das keine
         .up-root ist (#ask-mira, der Schleier von settings-brand), erbte sonst den schon
         aufgeloesten hellen Wert von :root. */
      --vc-pos: var(--vt-up);   --vc-pos-bg: #0b2212;
      --vc-neg: var(--vt-down); --vc-neg-bg: #2a1314;   --vc-neg-border: #671e21;
      --vc-warn: #f2a20d;     --vc-warn-bg: #291800;  --vc-warn-border: #573300;
      /* Der Glanz des Skeletts im Dunkeln: Weiss statt Schwarz (Begruendung im hellen Block). */
      --up-sk-glanz: rgba(255,255,255,.025);
      --up-sk-verlauf: linear-gradient(90deg, rgba(255,255,255,0) 0%, var(--up-sk-glanz) 50%, rgba(255,255,255,0) 100%) 0 0 / 150% 100% repeat-x;
      /* ---- Komponentenfarben: Prompts-Tabelle, Brands (17.10.) ----
         Nur das Raster der Landschaft; die festen Werte stehen im hellen Block (Begruendung dort). */
      --vc-ubo-raster: rgba(255,255,255,0.09);
      /* ---- Komponentenfarben: Quick Actions, Anmeldung, Onboarding (17.10.) ---- */
      /* Quick Actions im Dunkeln: die Menue-Token, auf die die Palette schon seit dem 07.09. zeigt
         (Zuordnung im Kopf von quick-actions.css). Ein var() hier wird am Element mit
         data-theme="dark" aufgeloest -- Palette und Overlay tragen das Attribut selbst. */
      --vc-qa-rand: var(--up-menu-border);
      --vc-qa-rand-stark: var(--vc-border);
      --vc-qa-weich: var(--up-sel-bg);
      /* Halbdurchsichtig (Linears --color-bg-translucent): liegt mal auf der Karte, mal auf der
         aktiven Zeile und bleibt auf beiden eine leise Stufe darueber. */
      --vc-qa-weich-2: #ffffff0d;
      --vc-qa-karte: var(--up-menu-bg);
      --vc-qa-knopf: var(--vc-field);
      --vc-qa-hover: var(--up-sel-bg);
      --vc-qa-schleier: rgba(0,0,0,0.55);
      --vc-qa-tip-bg: #1f1f1b;
      --vc-qa-tip-ink: #ffffff;
      --vc-qa-taste-bg: var(--vc-qa-karte);
      --vc-qa-taste-rand: var(--vc-qa-rand);
      --vc-qa-fehler: #b4451f;
      --vc-auth-hover: var(--vc-heading-bg);
      --vc-auth-staerke-mittel: #d98324;
      --vc-auth-staerke-gut: #b8a02a;
      /* Im Dunkeln ist die Akzentflaeche hell, der Haken also dunkel (#1b1b1b). */
      --vc-auth-haken: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231b1b1b' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
      /* Die rechte Spalte der Anmeldung im Dunkeln: dasselbe Grau, gegenlaeufig. Im Hellen wird es
         nach unten DUNKLER (Weiss -11/-28/-43), hier nach unten HELLER, vom alten Seitengrund
         #1c1c1f aus um +5/+13/+21, der Blauanteil waechst mit der Tiefe wie dort. Das Linienmuster
         dunkel statt hell (21.09.: "die line pattern rechts dunkler machen"): weisse Striche
         leuchteten auf dem dunklen Grau als Gitter VOR der Flaeche; Schwarz mit .30 graebt sie ein.
         Die Lichter bleiben dieselben, auth-page.css nimmt sie im Dunkeln auf ein Viertel. */
      --vc-auth-panel-verlauf: linear-gradient(to bottom,
              rgba(28, 28, 31, 0) 16%, #212224 34%, #2a2b2e 60%, #323336 100%);
      --vc-auth-panel-lichter:
    radial-gradient(58% 72% at 44% 100%,
      rgba(255, 255, 255, .34) 0%, rgba(255, 255, 255, .24) 28%,
      rgba(255, 255, 255, .1) 62%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(34% 52% at 61% 97%,
      rgba(255, 255, 255, .16) 0%, rgba(255, 255, 255, .09) 40%,
      rgba(255, 255, 255, .03) 72%, rgba(255, 255, 255, 0) 100%),
    linear-gradient(to right,
      rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, .02) 18%, rgba(255, 255, 255, .06) 32%,
      rgba(255, 255, 255, .09) 44%, rgba(255, 255, 255, .1) 50%, rgba(255, 255, 255, .09) 56%,
      rgba(255, 255, 255, .06) 68%, rgba(255, 255, 255, .02) 82%, rgba(255, 255, 255, 0) 100%);
      --vc-auth-panel-raster: repeating-linear-gradient(to right,
              rgba(0, 0, 0, .30) 0, rgba(0, 0, 0, .30) 1px,
              transparent 1px, transparent 8px);
      --vc-marke-google-blau: #4285F4;
      --vc-marke-google-gruen: #34A853;
      --vc-marke-google-gelb: #FBBC05;
      --vc-marke-google-rot: #EA4335;
      --vc-ob-kachel-text: #1f1f1b;
      --vc-ob-kachel-muted: #6b6f78;
      --vc-ob-thema-rueckfall: #6b7280;
      /* ---- Komponentenfarben: Mira (17.10.) ----
         Die dunklen Fassungen der Mira-Farben, die je Thema verschieden sind (Rollen und
         Begruendung im hellen Block). Geist-dunkel traegt dieselben Werte -- so stand es auch in
         der Komponente: ihre Dunkel-Regeln hingen nur an data-theme, nie an html[data-up-dunkel]. */
      --vc-mira-akzent: #9d83ff;
      --vc-mira-akzent-2: #6ba0ff;
      --vc-mira-akzent-soft: rgba(157,131,255,0.13);
      /* Die Glut dunkel: auf #0a0a0a traegt ein Blau erst ab etwa 15 % ueberhaupt; das Bild aus der
         Bestellung lag weit darueber ("subtiler als das"). 26 % seit 26.10. nachmittags (vorher 18, "etwas
         staerker"), weil die Glut zugleich auf die halbe Hoehe ging; 21 % seit 26.10. spaet ("im darkmode etwas
         abschwaechen"), zwischen beiden. */
      --vc-mira-glut: color-mix(in srgb, var(--vc-mira-glut-ton) 21%, transparent);
      --vc-mira-blase-bg: #2f2f2f;
      --vc-mira-blase-ink: #ececec;
      --vc-mira-raster: rgba(255,255,255,0.022);
      --vc-mira-positiv-bg: rgba(34,197,94,0.12);    --vc-mira-positiv-ink: #86efac;
      --vc-mira-negativ-bg: rgba(248,113,113,0.18);  --vc-mira-negativ-ink: #fecaca;
      --vc-mira-quelle-bg: rgba(96,165,250,0.12);    --vc-mira-quelle-ink: #bfdbfe;
      --vc-mira-unklar-bg: rgba(251,191,36,0.13);    --vc-mira-unklar-ink: #fde68a;
      --vc-mira-tabelle-streifen: rgba(255,255,255,0.022);
      --vc-mira-composer-schale: var(--vc-heading-bg);
      --vc-mira-gewaehlt: var(--up-sel-bg);
      --vc-mira-entfernen-hover: rgba(255,255,255,0.08);
      --vc-mira-picker-schatten: rgba(0,0,0,0.3);
      --vc-mira-aufnahme-weg-hover: var(--up-sel-bg);
      --vc-mira-aufnahme-ok-hover: #a0a0a0;
      --vc-mira-aufnahme-fehler: #e07068;
      --vc-mira-schleier: rgba(0,0,0,.45); --vc-seitenleiste-schleier: rgba(0,0,0,.45);
      --vc-mira-haken-an: #e0e0e0;
      --vc-mira-skelett-blase-a: rgba(255,255,255,0.12);
      --vc-mira-skelett-blase-b: rgba(255,255,255,0.22);
      /* ---- Ende Komponentenfarben: Mira ---- */

      /* ---- Komponentenfarben: übrige Bereiche (17.10.) ---- */
      /* Die dunklen Werte der Marken, die mit dem Thema drehen (Begruendung im hellen Block). Es
         sind die Werte der bisherigen dunklen Regeln der Komponenten, unveraendert. Die Marken,
         die nur oben stehen, gelten in beiden Themen. */
      --vc-dialog-schleier: rgba(0,0,0,.35);
      /* Die Schicht am <body> im Dunkeln (Geist-Leiter, Wert fuer Wert wie bisher). Rand, Flaeche
         und leise Flaeche nehmen dort die Marken der schwebenden Schicht bzw. erben -- das steht
         weiter in der Komponente, hier nur dieselbe Zuordnung. */
      --vc-schicht-text: #ededed;
      --vc-schicht-muted: #a1a1a1;
      --vc-schicht-third: #808080;
      --vc-schicht-border: var(--up-menu-border);
      --vc-schicht-flaeche: var(--up-menu-bg);
      --vc-schicht-leise: var(--vc-heading-bg);
      --vc-schicht-hover-rand: #707070;
      --vc-schicht-fokus: #6DB2FF;
      --vc-schicht-fokus-ring: rgba(109,178,255,0.28);
      --vc-schicht-logo-platte: #e0e0e0;
      --vc-matrix-leer: var(--vc-heading-bg);
      --vc-volumen-spur: #303030;
      --vc-raster-linie: rgba(255,255,255,0.035);
      --vc-kalender-hover: #2d2d2d;
      --vc-kalender-wahl: #383838;
      --vc-kalender-band: #313131;
      --vc-kalender-ende: #e0e0e0;
      --vc-flaeche-durchscheinend: #ffffff0d;
      --vc-potenzial-anfang: #4a4f56;
      --vc-erklaer-potenzial-leer: #23252a;
      --vc-erklaer-potenzial-a: #4a4a4a;
      --vc-erklaer-potenzial-b: #e0e0e0;
      --vc-erklaer-logo-platte: #303030;
      --vc-syntax-auszeichnung: #c4b5fd;
      --vc-syntax-attribut: #fcd34d;
      --vc-syntax-zeichenkette: #86efac;
      --vc-syntax-zahl: #93c5fd;
      --vc-syntax-kommentar: #a1a1a1;
      --vc-ki-schleier: rgba(0,0,0,0.60);
      --vc-fehler-ring: rgba(210,93,93,0.24);
      --vc-fehler-flaeche: rgba(210,93,93,0.10);
      --vc-akzent-punkt-standard: #e0e0e0;
      --vc-akzent-punkt-blau: #83abf4;
      --vc-akzent-punkt-azur: #3D97F0;
      --vc-akzent-punkt-indigo: #7C8AF0;
      --vc-akzent-punkt-terrakotta: #D97757;
}
.up-root {
      /* --vc-muted ist um vier Einheiten dunkler als vorher (#6f737c), und das ist KEINE neue
         Schriftfarbe, sondern der Ausgleich fuer den Grund, den ich verschoben habe: auf Weiss
         stand dieser Ton bei Kontrast 4.75, auf der neuen Karte (#f9f8f9) waren es 4.49 -- einen
         Hundertstel unter dem, was AA verlangt. #6b6f78 ist derselbe Blaustich in derselben
         Helligkeitsfamilie und bringt die 4.75 zurueck, auf der Kopfzeile (#f4f2f4) noch 4.52.
         Wer den alten Wert buchstabengleich zurueckwill, aendert diese eine Stelle -- dann steht
         gedaempfter Text auf Karten aber unter AA.
         Die anderen beiden Schriftfarben sind unveraendert. --vc-third liegt mit 2.53 auf der
         Karte unter 3, war aber schon auf Weiss bei 2.68: das ist Beiwerk (Platzhalter, Rauten,
         Einheiten) und war nie AA-Text. */
      /* ---- DIE VIER SCHRIFTSTUFEN (24.09.) --------------------------------------------------
         Vorher waren es drei, und sie waren keine Reihe: hell ging es 16.5 -> 5.0 -> 2.7, also
         von sehr dunkel mit einem Sprung auf mittelgrau und dann unter die Wahrnehmungsgrenze.
         2.68 gegen Weiss reisst beide Schwellen (4.5 fuer normalen, 3.0 fuer grossen Text) --
         und die Stufe stand nicht auf grossem Text: von 248 Vorkommen lagen 53 auf 12px, 22 auf
         13px, 12 auf 11px. Im Dunkelmodus war dieselbe Stufe mit 4.30 in Ordnung; die zwei
         Themen waren also verschieden gebaut.
         Jetzt vier Sprossen in gleichmaessigen Schritten, nach dem Vorbild von Linear (dort
         14.3 / 10.3 / 5.0 / 3.7 im Hellen und 18.7 / 13.6 / 6.1 / 3.5 im Dunklen -- am 24.09.
         aus der laufenden App ausgelesen, nicht aus einer Token-Liste abgeschrieben, die
         gefundenen Listen geben den Hellmodus falsch wieder):
             --vc-text    16.53 / 12.88    Ueberschriften, Fliesstext
             --vc-muted   10.25 / 10.47    zweite Ebene: Beschriftungen, Werte neben dem Text
             --vc-third    5.04 /  6.50    Metadaten, Hilfstexte -- der alte --vc-muted-Ton
             --vc-fourth   3.71 /  3.47    Zaehler, Zeitstempel, inaktive Zustaende
         Die vierte Stufe unterschreitet 4.5 mit Absicht; bei Linear tut sie das auch. Sie
         traegt nur, was man ueberlesen darf. Was Inhalt ist, steht auf Stufe drei oder hoeher.
         ZWEITE ACHSE, noch offen: der Schnitt. Linear unterscheidet 12px/510 in der zweiten von
         12px/400 in der dritten Stufe -- dieselbe Groesse, zwei Ebenen. Die Schnittskala
         (400-700) gibt es hier auch, sie ist nur noch nicht an die Farbe gekoppelt.
         STAND 18.10. (Hell): die Werte sind seitdem Geists (am Vercel-Dashboard gemessen, reines Grau):
         gray-1000 #171717, gray-900 #4d4d4d, gray-800 #7d7d7d, gray-700 #8f8f8f. Kontrast gegen Weiss
         jetzt 17.93 / 8.45 / 4.12 / 3.23. Die Zahlen darueber sind die der alten Werte. Die dritte Stufe
         liegt damit unter 4.5 (vorher 5.04): Hilfstext in 12px dort ist knapp. Dunkel noch unveraendert. */
      --vc-text: #171717; --vc-muted: #4d4d4d; --vc-border: #e4e2e4;
      /* Die Primaerfarbe des ANDEREN Themas -- also genau der Wert, der zwei Bloecke weiter unten
         als --vc-text steht. Gebraucht fuer Zeichen AUF einer Flaeche in der Primaerfarbe: die
         16px-Plaettchen der Mira-Chips (16.09. angefordert). Kein Weiss und kein --vc-bg: auf
         #1f1f1b soll dasselbe gedaempfte Hell sitzen, das im dunklen Thema die Schrift traegt,
         sonst sticht das Zeichen heller heraus als jeder Text der App. */
      --vc-text-invert: #e0e0e0;
      /* ---- DIE AKZENTFARBE (18.09.) -------------------------------------------------------
         Bis hierher gab es sie nicht: was wie eine Primaerfarbe aussah, war --vc-text, also die
         SCHRIFTFARBE der App. Gezaehlt vor dem Umbau: 578 Vorkommen, davon 475 als color. Ein
         Umdefinieren haette die ganze Schrift umgefaerbt, deshalb ein eigenes Paar.
         --up-accent ist die Flaeche, --up-accent-fg das, was darauf liegt. Die Vorgabe ist
         buchstabengleich das, was vorher dastand (Flaeche --vc-text, Schrift --vc-bg) -- wer
         nichts einstellt, sieht keinen Unterschied.
         WAS NICHT AKZENT IST, und das ist Absicht: die Platte hinter Logos und Favicons
         (--vt-logo-bg), Zaehlerplaketten und Punkte, die Hintergrundgrafik, das Avatarrund, der
         Toast und die leisen Hover-Toene der Fensternavigation. Die bleiben neutral, egal was
         eingestellt ist. */
      --up-accent: #1f1f1b; --up-accent-fg: #ffffff; --up-accent-ink: #1f1f1b;
      --vc-bg: #ffffff; --vc-heading-bg: #f9f8f9; --vc-sk: #eeedef; --up-surface: #ffffff;
      /* ---- DIE HELLE LEITER (07.09.) --------------------------------------------------------
         Dieselbe Quelle und dieselbe Zuordnung wie die dunkle Leiter weiter unten, nur laeuft sie
         hier nach unten statt nach oben. Von linear.app gelesen, helles Thema:
             bg-primary #ffffff  >  bg-secondary #f9f8f9  >  bg-tertiary #f4f2f4
             bg-quaternary #eeedef  >  bg-quinary #e9e8ea
             border-primary #e9e8ea   border-secondary #e4e2e4   border-tertiary #dcdbdd
         IM HELLEN DREHT SICH DIE ERHOEHUNG UM, und das ist der eigentliche Unterschied zum
         Dunkeln: dort ist "liegt hoeher" heller, hier ist die Seite schon Weiss -- ueber Weiss
         gibt es nichts. Die Leiter geht also von oben nach unten, und die oberste Sprosse teilen
         sich Seite, Karte und Menue.
         JEDE ROLLE EINE STUFE HOEHER ALS AM VORMITTAG. Erst standen Karte auf bg-secondary und
         Kopfzeile auf bg-tertiary; gemeldet wurde "die Tabellen, Cards und Kopfzeilen sind mir
         etwas zu dunkel". In Linears hellem Spektrum gibt es dafuer genau einen Schritt, naemlich
         den naechsten nach oben -- zwischen bg-secondary und Weiss liegt nichts mehr. Also:
             Karte #ffffff   Kopfzeile #f9f8f9   Auswahl #f4f2f4   Skelett #eeedef
         Der Preis steht hier, damit ihn niemand spaeter suchen muss: die Karte hat damit
         DENSELBEN Wert wie die Seite und traegt sich allein ueber ihren Rahmen (#e4e2e4, 45
         Helligkeitsstufen unter Weiss -- deutlich sichtbar). Wer die Karte wieder schweben lassen
         will, setzt die Seitenfarbe in Bubble auf #f9f8f9 und --up-surface hier ebenso; dann
         rutscht die ganze Leiter um eine Sprosse und alles andere bleibt, wie es ist.
         WAS DIESER SCHRITT GERETTET HAT: die Kopfzeile jeder Tabelle stand vorher auf einem
         30-Prozent-Wert (auf Weiss #fcfcfc, Kontrast 1.01 zur Karte) und war schlicht nicht da.
         Jetzt ist sie #f9f8f9 auf einer weissen Karte, also 15 Stufen -- leise, aber vorhanden.
         Menues und Tooltips bleiben auf #ffffff. Was oben liegt, muss sich abheben; da hier
         alles Weiss ist, machen das Rahmen und Schatten, so wie es im Hellen ueblich ist.
         Die SCHRIFTFARBEN bleiben unveraendert (#1f1f1b / #6b6f78 / #9e9e9e) -- ausdruecklich so
         angefordert, genau wie im Dunkeln. Linears Toene sind #282a30 / #6f6e77 / #86848d, also
         etwas blaeulicher und in der dritten Stufe deutlich dunkler; das waere eine eigene
         Entscheidung. */
      /* One dropdown panel colour and one selected-row colour for the WHOLE app. Every component
         that draws its own menu reads these instead of picking something close -- that drift is
         exactly why the panels stopped matching each other.
         --up-sel-bg war einmal halbtransparent, damit die Flaeche darunter durchscheint. Das ist
         seit dem 07./08.09. in beiden Themen ein DECKENDER Wert aus Linears Leiter: eine Auswahl
         ist eine eigene Stufe, und zwei uebereinanderliegende Halbtransparenzen ergaben je nach
         Untergrund einen dritten Ton. */
      /* DIE AUSWAHL WAR HELLER ALS DIE KOPFZEILE, und damit lief die helle Leiter rueckwaerts:
         rgba(245,245,245,0.55) ergibt auf einem weissen Menue #fafafa -- Helligkeit 243.8, also
         elf Stufen UEBER --vc-heading-bg (232.8). Eine gewaehlte Zeile war damit heller als der
         Kopf darueber und gegen das weisse Panel praktisch unsichtbar (Kontrast 1.02).
         Jetzt Linears bg-quaternary, dieselbe Rolle und derselbe Wert wie im Dunkeln (#28282c
         dort): eine Stufe UNTER der Kopfzeile, deckend statt halbtransparent. Im Dunkeln stand
         die Auswahl schon am 08.09. deckend da -- hier war es noch der alte Aufbau.
         Gemessen mit _h_helle_leiter.html. */
      --up-menu-bg: #ffffff; --up-sel-bg: #f4f2f4; --up-menu-border: #dcdbdd;
      /* Der Topic-Chip "Dot" (06.10., Topic-Handoff): Grund und Rand je Zustand. Gewaehlt ist
         --up-sel-bg hell (#f4f2f4); die drei uebrigen Stufen gab es noch nicht. */
      --up-chip-rand-hover: #cfcdd0; --up-chip-an: #f4f2f4; --up-chip-an-hover: #eeedef; --up-chip-rand-an-hover: #b9b7bb;
      /* Event-Pins (Design-Handoff 02.10.): der Rand des Pins und die Hilfslinie, die die Vorgabe
         mit eigenen Werten fuehrt -- #CDD1D6 und #9AA1AB (dieselbe Stufe auch fuer die leiseste
         Schrift der Gruppenkarte, #9CA3AF, drei Werte daneben). Alles andere der Vorgabe liegt
         auf vorhandenen Marken, die um ein bis drei Werte abweichen: #1F2328 = --vc-text,
         #3F4652 = --vc-muted, #6B7280 = --vc-third, #E5E7EB = --vc-border, #F5F5F5 = --up-sel-bg. */
      --up-evm-rand: #cdd1d6; --up-evm-linie: #9aa1ab;
      /* Dropdown-Geometrie. Vorher hatte jedes Panel eigene Werte -- 12 vs 14 Radius, 6 vs 8
         Padding, 6 vs 8 Abstand zum Trigger. Keiner davon war eine Design-Entscheidung, es waren
         Kopien die auseinandergelaufen sind. Eine Zahl je Groesse, hier.
         Radius 12 seit dem 15.10. (Geist material-menu, vorher 14) -- derselbe Wert wie an :root
         im Massblock oben, hier noch einmal, weil Komponenten ihn an ihrer Wurzel neu setzen. */
      --up-dd-radius: 12px; --up-dd-pad: 8px; --up-dd-gap: 6px;
      /* Der Abstand NUR zwischen einem Dropdown und seinem Untermenue. Eigener Wert, weil
         --up-dd-gap auch den Abstand vom Trigger zum Panel setzt -- der bleibt 6. */
      --up-sub-gap: 4px;
      /* Der Schatten der Menues IST e-2 (die Tiefe von allem, was an einem Ausloeser haengt).
         Bis zum 15.10. stand hier dieselbe Zahl noch einmal als Literal -- zwei Orte fuer einen
         Wert, die beim naechsten Umbau auseinandergelaufen waeren. */
      --up-dd-shadow: var(--up-e-2);
      --vc-switch-bg: #f9f8f9; --vc-switch-border: #e4e2e4; --vc-third: #7d7d7d; --vc-fourth: #8f8f8f;
      /* Die PILLE auf der Schiene -- das Gegenstueck zu --vc-switch-rail. Sie stand als
         Literal an sieben Stellen, jede mit einer eigenen Dunkel-Regel daneben (#ffffff und
         #28282c). Eine Marke statt vierzehn Werten; der Rahmen kam schon immer aus
         --vc-switch-border. Wer eine eigene Palette aufmacht, muss sie mitfuehren -- das sind
         genau die Geltungsbereiche, die auch --vc-switch-border setzen (fuenf, gezaehlt). */
      --vc-switch-pill: #ffffff;
      /* Die SCHIENE eines Schalters -- ein eigener Token und nicht --vc-switch-bg, der die
         Flaeche eines Segmentumschalters meint. Als TOKEN und nicht als fester Wert in der
         Regel: ein fester Wert erbt nicht ueber eine Attributgrenze, und der Schalter steht
         auch in Menues, die core selbst mit data-theme markiert. Genau daran stand er am
         08.09. hell im dunklen Menue. */
      --up-switch-off: #d6d6d4;
      /* DAS BLAU DER APP und sein Lila. --up-blau ist der Ton des "You"-Chips der Mitgliederliste
         (team-orga.css .uto-you) und des Chips der Landingpage -- also der eine Blauton, den die
         App schon hat, statt eines zweiten daneben.
         --up-lila ist daraus GERECHNET und nicht gewaehlt: in OKLCh liegt das Blau auf
         L=0.6231 C=0.1880 H=259.8; das Lila dreht den Farbton um 40 Grad auf H=299.8 und laesst
         Helligkeit und Saettigung unangetastet. Genau deshalb hat der Verlauf zwischen den
         beiden keinen Knick -- nachgerechnet: die Chroma wird auf dem ganzen Weg nicht
         beschnitten, und bei 40 Grad ist es noch Lila und nicht schon Magenta (das beginnt bei
         etwa H=330).
         Beide Toene sind in hell UND dunkel dieselben. Eine Flaeche in dieser Saettigung traegt
         auf beiden Gruenden, und weiss darauf misst 3.68 bis 3.92 -- ueber den 3.0, die WCAG
         1.4.11 fuer ein Bedienelement verlangt. Ein zweiter Satz Werte waere eine Aussage ohne
         Anlass. */
      --up-blau: #3b82f6;
      --up-lila: #9a65e6;
      /* DIE MITTE DES BOGENS, und sie ist noetig. Ein Verlauf zwischen zwei Farben rechnet der
         Browser in sRGB, und diese Gerade schneidet den Bogen, den OKLCh zwischen ihnen zieht:
         die Mitte landet auf #6a74ee, also L 0.6131 statt 0.6231 und Chroma 0.1809 statt
         0.1880 -- knapp vier Prozent Saettigung weniger, genau in der Mitte, genau dort, wo man
         hinsieht. Mit diesem Stopp bleibt die Abweichung auf dem ganzen Weg unter einem
         Prozent. Derselbe Ton, nur bei H=279.8 statt 299.8. */
      --up-lila-halb: #7473f4;
      /* UND EIN EIGENES PAAR FUER TEXT. Zwei Messskalen, wie ueberall in dieser Bibliothek:
         eine FLAECHE wird nach WCAG 1.4.11 beurteilt (3.0 reicht), ein WORT nach AA (4.5).
         --up-blau und --up-lila sind Flaechentoene und schaffen als Text keine 4.5 -- gerechnet
         auf dem Eingabefeld: hell 3.68 und 3.90, dunkel 3.90 und 3.68.
         Diese hier drehen nur die HELLIGKEIT, der Farbton bleibt (259.8 und 299.9), also ist es
         sichtbar dieselbe Familie. Hell auf #ffffff: 4.72 und 4.63. Dunkel steht weiter unten. */
      --up-blau-text: #286fe1;
      --up-lila-text: #8e58d8;
      /* DAS UPSTREEM-TEAM (28.09.): die Marke an Mitgliedern mit einer @upstreem.ai-Adresse in
         team-orga -- "neutral, aber eindeutig, eine Farbe, die wir so noch nirgends haben". Warmes
         Taupe: kein Signalton (nicht Rot, Gruen, Gelb, Blau), kein Violett oder Indigo (zwei der
         Akzentfarben), und bewusst WARM, damit es nicht mit dem kuehlen Grau der leisen Schrift
         (--vc-third #6b6f78) verwechselt wird. Die Braeune der Typpaletten (seit 13.10. die
         Familie commercial, Schrift #7B461E auf #FFF4EC) sind orange -- das hier ist entsaettigt.
         Gerechnet: Schrift #76634d auf der Flaeche #f2ede6 4.8:1, ueber AA fuer 11px. */
      --vc-staff-ink: #76634d; --vc-staff-bg: #f2ede6;
      /* DIE SCHIENE EINES UMSCHALTERS -- im Hellen ein einziger, DECKENDER Ton, fuer jede Groesse
         und jeden Grund. (Im Dunkeln seit dem 27.09. nicht mehr: dort hebt sie ihren Grund an,
         Begruendung im dunklen Block.) Zwei Fassungen liegen dahinter. Bis zum 07.09. stand hier
         ein aufgehellter Ton, und Aufhellen heisst, sich der Pille zu naehern, denn die Pille ist
         der hellere Teil: auf Weiss 4.1 Stufen, im dunklen Menue 3.0. Gemeldet als "die Switcher in allen
         Dropdowns brauchen andere Farben, im Darkmode sind sie kaum erkennbar". Danach eine
         schwarze Halbtransparenz -- die loeste den Kontrast, brachte aber je Grund einen anderen
         Ton und im Dunkeln eine zweite Fassung nur fuer Menues.
         Seit dem 22.09. ein fester Wert, gemessen gegen die Pille (#ffffff) und gegen die beiden
         hellen Gruende (Flaeche #ffffff, Kopfzeile #f9f8f9), rohe Kanaele mit Augengewichten:
             zur Pille 17 Stufen, zur Flaeche 17, zur Kopfzeile 11.
         Der Wert war erst #efedef (17 Stufen unter Weiss) und ist am 22.09. auf Anforderung
         aufgehellt worden -- "mir sind die so zu dunkel". #f2f2f2 ist GENAU der Ton, den die
         durchscheinende Fassung auf weissem Grund gerendert hat, also der Stand, den die App
         vor dem 22.09. gezeigt hat:
             zur Pille 13 Stufen, zur Flaeche 13, zur Kopfzeile 7.
         Auf der Kopfzeile (#f9f8f9) ist das die schwaechste Stelle -- vorher waren es dort 12,
         weil sich der Ton mit dem Grund mitbewegte. Das ist der Preis des festen Werts und war
         die bewusste Wahl. Noch heller ginge nur bis #f9f8f9 selbst, dann steht die Schiene auf
         der Kopfzeile gar nicht mehr und zur Pille bleiben 6 Stufen. */
      --vc-switch-rail: var(--up-seite-bg);
      /* Die Flaeche eines Eingabefelds, SOLIDE. Es gab sie bisher nur als 60%-Variante
         (--vc-switch-bg im Dunkeln, --up-sel-bg, --vt-row-hover) -- und wo ein Chip auf einer Karte
         liegt, die selbst schon getoent ist, addieren sich zwei Halbtransparenzen zu einem dritten
         Ton. Deshalb ein eigenes Token mit dem Vollwert. */
      --vc-field: #f9f8f9;
      /* DECKEND UND AUF DER MENUESTUFE, wie im Dunkeln (#232326 an allen drei). Vorher waren es
         drei verschiedene Halbtransparenzen von #f5f5f5 -- 30, 75 und ein eigener Vollwert --,
         die auf Weiss #fcfcfc, #f8f8f8 und #f7f7f7 ergaben: drei Toene fuer zwei Rollen, und der
         Tabellenkopf mit 1.01 Kontrast zur Karte praktisch nicht vorhanden. Auf der neuen Karte
         (#f9f8f9) waere er ganz verschwunden.
         --vt-logo-bg bleibt Weiss: die Platte hinter einem Favicon liegt AUF der Karte, ist also
         eine Stufe hoeher -- dasselbe, was im Dunkeln #e0e0e0 dort tut. */
      --vt-head-bg: #f9f8f9; --vt-logo-bg: #ffffff; --vt-row-hover: #f9f8f9;
      /* Der Anfangsbuchstabe AUF dieser Platte, wenn das Logo fehlt (28.09.). Die Platte ist in
         BEIDEN Themen hell (#ffffff / #e0e0e0), der Buchstabe stand aber auf --vc-muted -- im
         Dunkeln #c9cbd0 auf #e0e0e0, gemessen 1.2:1, also unsichtbar. Hier im Hellen bleibt es
         --vc-muted (nichts aendert sich), im Dunkeln unten --vc-text-invert: das ist die Tinte
         fuer Zeichen auf #e0e0e0, genau diese Flaeche. power-dashboard hatte dasselbe schon
         einmal fuer sich allein geflickt (.upw-chip-ic, --vc-bg). */
      --vt-logo-ink: var(--vc-muted);
      --vt-row-hover-solid: #f9f8f9;
      /* --vt-up/--vt-down stehen seit dem 15.10. im Block an :root weiter oben (Geist, "DIE
         ZUSTANDSFARBEN NACH GEIST") -- hier nicht noch einmal, sonst waeren es zwei Orte. */
      /* Die Flaeche eines Gefahr-Knopfs (Loeschen im Dialog, 10.10.: "schoen rot") und die Schrift
         darauf. Seit dem 15.10. Geists eigener Gefahr-Knopf (geist-new-error-fill): Red 800 mit
         weisser Schrift, 4.73:1 (vorher #b0200c, 6.87). Geist fuehrt Red 800 in beiden Themen fast
         gleich (#da2f35 / #d93036), darum steht das Paar weiter nur hier. */
      --vc-danger: #da2f35; --vc-danger-fg: #ffffff;
      /* Markenauszeichnung im Antworttext. Die Farbe ist die der alten Response-Elemente,
         wortgleich uebernommen -- Orange, nicht das Gruen der Trendpfeile: eine Marke im Text ist
         keine Bewertung. Der Hintergrund liegt 10 Prozentpunkte unter dem ersten Ansatz (8 -> 6,
         hover 15 -> 12), damit der Absatz beim Lesen nicht gestreift wirkt. */
      --vt-brand: rgba(255, 165, 0, 1);
      --vt-brand-bg: color-mix(in srgb, var(--vt-brand) 4%, transparent);
      --vt-brand-bg-hover: color-mix(in srgb, var(--vt-brand) 9%, transparent);
      --vt-brand-border: color-mix(in srgb, var(--vt-brand) 55%, transparent);
      --vt-brand-border-hover: var(--vt-brand);
      --vc-hover-border: #6f6f6f;
      /* Fokusfarbe der Eingabefelder. Der Ring ist derselbe Ton, nur transparent -- eine Farbe,
         zwei Deckkraefte. --up-focus-w steht hier und nicht in den Regeln, weil die Ringbreite
         sonst in zehn Dateien einzeln stuende und beim naechsten Mal auseinanderliefe. */
      --up-focus: #4A9EFF; --up-focus-ring: rgba(74,158,255,0.20); --up-focus-w: 2px;
      /* Kaestchen: EIN Timing fuer alle Haken der App. Die Anmeldeseite hatte es zuerst (dort
         --uau-t), die Tabellen und die Dropdown-Haken hatten gar keinen Uebergang und sprangen.
         Zwei Werte an einer Stelle, damit sie nicht wieder auseinanderlaufen. */
      --up-cb-t: 140ms; --up-cb-ease: cubic-bezier(.4, 0, .2, 1);
      /* Sticky-Header: Abstand von oben in px = Hoehe der Floating-Topbar. Wird auch ueber
         data-sticky-top am Root gesetzt (siehe oben bei den dynamischen Werten). */
      --up-sticky-top: 171px; --up-thead-off: 32px;
      display: flex; flex-direction: column; position: relative;
      font-family: Geist, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
      color: var(--vc-text); width: 100%; box-sizing: border-box; -webkit-font-smoothing: antialiased;
    }
/* DIE EINE universelle box-sizing-Regel der App. .up-root selbst traegt es im Block darueber,
   hier stehen seine Nachfahren -- und jede Komponentenwurzel traegt up-root (siehe die Vorlagen
   in bubble/), ebenso jedes Portal (up-root up-portal). Damit ist eine zweite universelle Regel
   je Komponente doppelt, und neun davon sind am 03.09. entfernt worden.
   Warum das zaehlt: eine universelle rechte Seite macht JEDES Element der Seite zum Kandidaten.
   Im Trace des Nutzers 124283 Versuche je Selektor -- ein voller Durchlauf ueber das Dokument,
   je Style-Neuberechnung, rund 8ms pro Regel unabhaengig von den Treffern. 53 unserer Selektoren
   dieser Bauform kosteten zusammen 458 von 1205ms.
   Wer eine neue Komponente baut, braucht hier nichts: up-root an der Wurzel genuegt. */
.up-root * { box-sizing: border-box; }

/* ---------- [data-align] Ausrichtung in einer zu breiten Wirtsbox ----------
   Ein Filter schrumpft auf die Breite seines Triggers. Steckt er in einer Bubble-Box, die
   breiter gezogen ist als dieser Trigger -- und das ist der Normalfall, weil die Box von Hand
   auf eine runde Zahl gezogen wird --, sitzt der Trigger am LINKEN Rand dieser Box. In einer
   rechtsbuendigen Leiste sieht das aus, als haenge der Filter weit links, obwohl er formal
   richtig steht: sichtbar ist der Trigger, gemessen wird die Box.

   data-align="right" klebt die Komponente an die rechte Kante ihrer Wirtsbox, ohne dass die Box
   selbst passend gezogen werden muss. Bewusst opt-in: bestehende Platzierungen stehen links in
   einer Toolbar und sollen genau da bleiben. Hoehe und Umbruchverhalten bleiben unberuehrt, es
   wechselt nur die Ausrichtung im Fluss. */
.up-root[data-align="right"]  { display: block; width: max-content; max-width: 100%; margin-left: auto; }
.up-root[data-align="center"] { display: block; width: max-content; max-width: 100%; margin-left: auto; margin-right: auto; }

/* ---------- [popover] auf UA-Neutral ----------
   Ein Dropdown, das im Drawer in den Top Layer gehoben wird (UC.menuEscape), traegt kurzzeitig
   das Attribut `popover`. Damit greift das UA-Stylesheet mit einem ganzen Satz eigener Vorgaben:
   position/inset/margin/width/height/border/padding/background -- und `overflow: auto`.
   Alles ausser overflow gewinnt unsere eigene Panel-Regel ohnehin; overflow deklariert kaum ein
   Panel selbst, also schlug hier die UA-Vorgabe durch und machte aus jedem Panel einen
   Scroll-Container. Gemessen: `overflow: visible -> auto` beim Oeffnen im Drawer, mit sichtbarer
   Folge fuer die Flex-Panels (der Kalender legte sich neu aus).
   Bewusst nur ein Attribut-Selektor (0,1,0) und ganz oben in der Datei: jede Komponentenregel --
   gleiche Spezifitaet, aber spaeter, oder hoehere -- gewinnt weiterhin. Das hier raeumt nur den
   UA-Default weg, es setzt nichts durch. */
[popover] { overflow: visible; }
/* Scroll-Chaining schuetzen — gegen fremdes Seiten-CSS, das `overscroll-behavior: contain`
   pauschal per `*` auf alles legt (der Bubble-Header dieser App tat/tut genau das ueber
   `[id*="allow_scroll"] *`). `contain` gehoert nur auf einen ECHTEN Scroll-Container, der die
   Seite bewusst nicht mitscrollen lassen soll (z.B. ein Modal). Liegt es dagegen auf allem, trifft
   es auch jedes Element, das nur wegen runder Ecken/Clipping ein `overflow: hidden` hat — und
   `overflow: hidden` macht ein Element bereits zum Scroll-Container. Ergebnis: das Element kann
   selbst nichts scrollen, gibt das Scrollen aber auch nicht an die Seite weiter, und das Scrollen
   versickert genau ueber diesen Flaechen (gemessen an `.tct-table`, `.tcd-box`, `.vt-table`).
   `auto` ist der Browser-Default — das hier stellt also nur wieder her, was ohne die fremde Regel
   ohnehin gaelte, und kann fuer sich genommen nichts kaputt machen.
   Doppeltes `.up-root` nur wegen Spezifitaet: (0,2,0) schlaegt `[id*="..."] *` (0,1,0) unabhaengig
   von der Ladereihenfolge, weil beide Seiten `!important` verwenden.

   AUSGENOMMEN: DIE ROLLFLAECHEN DER POPUPS (15.10.). Gemeldet: "Wenn man auf einem Popup scrollt,
   scrollt irgendwann auch die Hauptseite dahinter." Die Regel oben traf auch die Flaechen, auf
   die `contain` wirklich gehoert -- und weil sie `!important` traegt, war jede solche Angabe
   darunter tot. Gemessen: .up-kdr-koerper deklariert contain und rechnete auto; die Liste von
   Mentioned brands im Filtermenue der Citations (auto) gab das Rad an ihrem Ende an #main weiter,
   #main sprang von 0 auf 300.
   Die Liste hier ist die EINE Stelle dafuer. Nur Flaechen, die selbst rollen (Listen in Menues,
   Koerper und Karten der Dialoge, Koerper der Drawer) -- nie ein Panel oder eine Zeile mit
   overflow: hidden: an einer Zeile mit Auslassungspunkten versickerte das Rad sonst wieder genau
   so wie oben beschrieben, nur innerhalb der Liste.
   :where() traegt keine Spezifitaet bei: die Ruecksetzregel bleibt bei (0,2,0), und die
   contain-Regel bei (0,0,0) -- jede eigene Angabe einer Komponente gewinnt weiterhin. Ein neues
   Popup mit eigener Rollflaeche traegt sich in BEIDE Listen ein (sie sind gleich).
   NICHT HIERHER: eine Liste IM FLUSS einer Dialogkarte (17.10., Sitemap-Baum und Treffer der
   Events). Sie reicht das Rad an die Karte weiter, wie jede Liste in einer Seite, und die Karte
   haelt mit ihrem eigenen contain die Seite still. Mit contain blieb die Karte stehen, solange der
   Zeiger ueber der Liste war -- an ihrem Ende und auch ganz ohne Ueberlauf (gemessen mit echtem
   Rad; gemeldet: "Das Popup kann nicht scrollen, wenn der Zeiger darueber ist"). Traegt die Liste
   eine Klasse von hier (.up-filter-list), setzt die Komponente auto selbst.
   Aus demselben Grund draussen (17.10.): die Themenliste im Fenster "New Grouping"
   (.up-cgm-list/.upt-gm-list, UC.makeGroupingModal), ausgeklappt 240 hoch in einer Karte, die im
   kurzen Fenster selbst rollt (1024x560: 69 zu rollen, die Karte stand ueber der Liste still).
   Ebenso draussen seit dem 17.10.: .uap-list (Add Prompts). Die Liste schrumpfte statt der Karte
   und stand bei 1024x440 auf 4px; jetzt rollt der Rumpf (.uap-rumpf, hier eingetragen) zwischen
   Kopf und Fuss -- die Karte selbst nicht, sie behaelt overflow: visible fuer die Menues im Fuss.
   Geprueft und BLEIBEN: .uap-list-opts (Menue am Ausloeser), .uca-bld-list (einzige
   Rollflaeche des Link-Builders, die Karte hat overflow: hidden),
   .upt-topiclist (Auswahlleiste, kein Dialog; ihr Feld hat overflow: hidden). Die uebrigen sind
   selbst Koerper oder Spalte ihrer Karte. */
.up-root.up-root, .up-root.up-root *:not(:where(
  .up-topicmodal-card, .uex-body, .uex-nav, .uca-body, .uca-bld-list, .uca-how, .ums-aside, .ums-main,
  .am-set-aside, .am-set-main, .uap-rumpf, .uap-list-opts, .upt-topiclist,
  .up-kdr-koerper, .up-sdrawer-body, .uo-m-body,
  .up-filter-list, .utf-list, .umf-list, .umk-list, .upr-dd-list, .combo-filter-menu,
  .am-rep-range-menu, .am-pick-scroll, .up-cols-menu, .usn-menu, .usn-teamlist, .uob-help-body)) { overscroll-behavior: auto !important; }
:where(
  .up-topicmodal-card, .uex-body, .uex-nav, .uca-body, .uca-bld-list, .uca-how, .ums-aside, .ums-main,
  .am-set-aside, .am-set-main, .uap-rumpf, .uap-list-opts, .upt-topiclist,
  .up-kdr-koerper, .up-sdrawer-body, .uo-m-body,
  .up-filter-list, .utf-list, .umf-list, .umk-list, .upr-dd-list, .combo-filter-menu,
  .am-rep-range-menu, .am-pick-scroll, .up-cols-menu, .usn-menu, .usn-teamlist, .uob-help-body) { overscroll-behavior: contain; }
/* DIE SEITENSPERRE (15.10.), gesetzt von core.js (seitenSperreAbgleichen), solange ein modales
   Popup am body offen ist. Sie deckt, was contain nicht kann: das Rad ueber dem Schleier und ueber
   Kopf und Fuss einer Karte, die selbst nicht rollen. Wie die Sperre des Kopf-Skripts fuer seine
   Drawer (drawer-locked) nur overflow -- keine Ebene, keine Lage, nichts, was einen
   Stapelkontext aufmacht. #main davor: die Wirt-Regel fuer #main (overflow-y: auto) traegt eine
   Kennung, und gegen die gewinnt eine Klasse allein nicht.
   Die Rinne nur, wenn vorher eine klassische Leiste stand (core.js misst): sonst sprang der Inhalt
   beim Oeffnen um ihre Breite nach rechts. Ohne scrollbar-gutter (Safari vor 18.2) haelt ein
   Polster in derselben Breite den Platz. */
#main.up-seitensperre { overflow: hidden; }
#main.up-seitensperre.up-sperre-rinne { scrollbar-gutter: stable; }
@supports not (scrollbar-gutter: stable) {
  #main.up-seitensperre.up-sperre-rinne { padding-right: var(--up-sperre-rinne, 0px); }
}
    .up-root[data-theme="dark"] {
      /* ================= DAS DUNKEL NACH GEIST (15.10.) =========================================
         Gemeldet: "Darkmode: Da passen jetzt die Farben nicht wirklich ... sieht sehr off und uebel
         unordentlich aus." Gemessen war es das auch: das Licht stand seit dem Vormittag auf Geist
         (neutrales Grau, Seite #fafafa, Inhalt #fff), das Dunkel noch auf Linears blaustichiger
         Leiter (#101114 / #1c1c1f / #232326 / #28282c / #34343a / #3e3e44) -- und daneben Reste
         aus der Zeit davor (#1a1a1a, #1e1e1e, #242424, #2d2d2d, #383838) in Komponenten. Dazu kam
         der eigentliche Bruch: im Dunkeln lag ALLES auf dem Seitengrund. Tabelle, Kopf, Chart und
         Seitenleiste trugen dasselbe #101114, nur Raender trennten sie -- das Gegenteil des Lichts.
         DIE WERTE SIND VERCELS EIGENE, ausgelesen am 15.10. aus dem Stylesheet von vercel.com/geist
         (.dark-theme, --ds-*-value, die sRGB-Fassung) und an der laufenden Seite gegengeprueft
         (materials, menu, table, input, button, switch, tooltip; und das dunkle Bild zur neuen
         Navigation auf vercel.com/changelog/dashboard-navigation-redesign-rollout):
             Background 1  --ds-background-100  hsl(0 0% 4%)   #0a0a0a  Inhalt, Karten, Menues, Felder
             Background 2  --ds-background-200  hsl(0 0% 0%)   #000000  Seite, Seitenleiste, Kopf
             Gray 100   #1a1a1a  Grund einer Komponente, Hover auf Background 1, Skelett
             Gray 200   #1f1f1f  Hover einer Komponente, aktiv auf Background 1, Skelett-Glanz
             Gray 300   #292929  aktiv einer Komponente
             Gray 400   #2e2e2e  Default border (Tabelle, Karte, Knopf, Feld)
             Gray 500   #454545  Hover border
             Gray 900   #a1a1a1  Secondary text      Gray 1000 #ededed  Primary text
             Gray Alpha 100 #ffffff0f (6 %)  gewaehlter Punkt der Navigation, Hover im Menue
             Gray Alpha 200 #ffffff17 (9 %)  gewaehlter Reiter im Dashboard-Bild
         DIE ROLLEN SIND DIE DES LICHTS, nur mit der dunklen Skala: dort ist der Seitengrund
         Background 2 (#fafafa) und der Inhalt Background 1 (#fff) -- hier ebenso, also ist der
         Inhalt im Dunkeln HELLER als die Seite (#0a0a0a auf #000). Genau so trennt Vercel: im
         Dashboard-Bild Seitenleiste und Kopf #000, die Inhaltsflaeche #0a0a0a mit einer Linie.
         Welche Flaeche Inhalt ist, entscheiden dieselben Regeln wie im Licht (Block "DAS DUNKEL NACH
         GEIST: SEITE UND INHALT" nach dem grauen Hell).
         WAS NICHT GEAENDERT IST: die vier Schriftstufen (wie im Licht, das seine auch behalten hat;
         #a0a0a0 IST Geists Gray 900 bis auf eine Stufe), die Akzentfarbe, die Zustandsfarben (schon
         Geist), Typ- und Chartfarben, die Tiefen (--up-e-*).
         DER RAHMEN: Geists Default border, Gray 400 #2e2e2e -- im Licht steht an derselben Stelle
         Gray 400 der hellen Skala (#ebebeb). Linears #34343a war blaustichig und mit 52 Stufen
         Helligkeit auf #101114 der hellste Rahmen beider Themen; #2e2e2e liegt auf dem Inhalt 36,
         auf der Seite 46 Stufen ueber dem Grund.
         EINE STUFE RUHIGER (15.10. abends). Gemeldet: das Dunkel wirkt "zu dolle". Gefallen hat
         das Dunkel der Design-Leinwand der Benachrichtigungen (Grund, angedeutete Tabellen mit
         Skelett): Seite #0a0a0a, Inhalt #0f0f0f, Linien 8 % Weiss. Beschlossen: "Es bleibt bei
         Geists Stufen, nur eine Stufe ruhiger" -- dieselben Rollen, dieselben ABSTAENDE zwischen
         den Stufen, die ganze Leiter angehoben. Zugeordnet je Rolle, nicht je Wert (#1f1f1f war
         Auswahl und ist jetzt Hover; #454545 ist Rand UND ausgeschalteter Schalter):
             Seite, Seitenleiste, Kopf      #000000 -> #0a0a0a   Geists Background 1 wird Seite
             Inhalt, Menue, Feld, Dialog    #0a0a0a -> #0f0f0f
             Linie, Rand                    #2e2e2e -> #222222   8 % Weiss auf dem Inhalt, DECKEND
                                                                 gerechnet: --vc-border steht auch als
                                                                 Flaeche und in color-mix
             Hover, Kopf- und leise Flaeche #1a1a1a -> #1f1f1f   +16 ueber dem Inhalt wie vorher
             Auswahl, gewaehlter Chip       #1f1f1f -> #242424   +21
             aktiv (Chip ueberfahren)       #292929 -> #2e2e2e   +31
             Rand einer schwebenden Schicht #454545 -> #333333   Geists Ring (14,5 % Weiss) auf
                                                                 dem Inhalt, eine Stufe leiser
             Skelett                        #1a1a1a -> #1e1e1e   wie die Leinwand; Glanz unveraendert
                                                                 2,5 % Weiss, also bis #242424
         UNVERAENDERT: die vier Schriftstufen, der ausgeschaltete Schalter (#454545, er muss als
         Bedienelement lesbar bleiben) und alles Durchscheinende (Gewaehlt 6 %, Schiene, Pille) --
         das zieht mit seinem Grund von selbst mit. Die Linie liegt jetzt auf dem Inhalt 19, auf
         der Seite 24 Stufen ueber dem Grund. */
      --vc-text: #e0e0e0; --vc-muted: #c9cbd0; --vc-border: #222222;
      /* Gegenstueck zum hellen Thema: dort steht hier #e0e0e0, hier der Textton von dort. */
      --vc-text-invert: #1f1f1b;
      /* Inhalt Background 1, Seite Background 2, Kopfzeilen- und Hoverflaeche Gray 100 (im grauen
         Hell steht dort Gray 100 der hellen Skala, #f2f2f2), Skelett Gray 100 -- Geists Skeleton
         laeuft im Dunkeln von Gray 100 nach Gray 200, und genau das ergibt der Glanz oben
         (2,5 % Weiss auf #1a1a1a = #1f1f1f). Seit "eine Stufe ruhiger" je eine Stufe hoeher:
         Inhalt #0f0f0f, Seite #0a0a0a, Kopf/Hover #1f1f1f, Skelett #1e1e1e (Glanz bis #242424). */
      --vc-bg: #0f0f0f; --vc-heading-bg: #1f1f1f; --vc-sk: #1e1e1e; --up-surface: #0a0a0a;
      /* ---- DIE DUNKLE LEITER (08.09.) -- ABGELOEST AM 15.10. DURCH GEIST (siehe oben) ---------
         Bleibt als Geschichte stehen, weil sie erklaert, warum Menue und Auswahl getrennte Stufen
         sind -- das gilt weiter, nur mit Geists Werten.
         An Linears dunklem Thema ausgerichtet, ausgelesen aus deren eigener Seite:
             Grund #08090a  <  Karte/Panel #1c1c1f  <  Menue/Kopfzeile #232326  <  Auswahl #28282c
         Vorher lag der Grund bei #121212 und die Karte bei #1b1b1b -- neun Stufen Abstand. Jetzt
         sind es zwanzig, und genau das ist der Unterschied, den man sieht: die Karten stehen auf
         der Seite statt in ihr zu verschwimmen.
         EINE Umkehrung steckt darin, und sie ist Absicht: das Menue war mit #151515 DUNKLER als
         die Karte, jetzt ist es heller. Ein Menue liegt ueber allem, und was oben liegt, ist bei
         Linear heller -- sonst braucht es den Schatten, um ueberhaupt als eigene Ebene zu gelten.
         Auswahl und Menuegrund muessen auseinanderliegen: mit beiden auf #232326 waere eine
         gewaehlte Zeile im Menue unsichtbar gewesen.
         ALLE Werte sind Linears eigene, ausgelesen von linear.app:
             bg-primary #08090a   bg-secondary #1c1c1f   bg-tertiary #232326
             bg-quaternary #28282c   border-primary #23252a   border-secondary #34343a
         DER GRUND IST EINE SPUR HELLER ALS LINEARS EIGENER. Deren bg-primary ist #08090a, also
         fast Schwarz; angefordert am 08.09. wurde "#0f0f11 oder ein kleines kleines bisschen
         heller, so dass es aber noch super als Page-BG haelt". #101114 ist dieses bisschen: die
         Karte hebt sich weiterhin ab (Kontrast 1.11 statt 1.17 gegen #1c1c1f, also gut ein
         Zehntel weniger Abstand), und der Text bleibt bei 14.3 gegen den Grund -- weit ueber
         allem, was AA verlangt. Alles andere in dieser Leiter ist unveraendert Linears Wert.
         Auch die Auswahlflaeche ist ihre (quaternary) -- ausdruecklich so angefordert: "nimm
         einfach alles aus Linear, also auch die selected bg states".
         Die SCHRIFTFARBEN sind unveraendert (#e0e0e0 / #a0a0a0 / #808080) -- ebenfalls so
         angefordert. Linears Toene sind heller (#f7f8f8 / #d0d6e0 / #8a8f98) und leicht
         blaustichig; wer das auch will, aendert die drei --vc-Werte, aber das ist eine eigene
         Entscheidung und keine Folge dieser hier. */
      /* GEIST (15.10.): Menues, Popover und Dialoge tragen im Dunkeln Background 1 -- dieselbe
         Flaeche wie der Inhalt (material-menu, gemessen: #0a0a0a, Rand 14,5 % Weiss). Abheben tut
         sie der Rahmen, eine Stufe ueber dem Default border wie im Licht (#dcdbdd neben #ebebeb):
         Gray 500, Geists Hover border. Die Auswahl im Menue ist Gray 200 -- Geists "aktiv auf
         Background 1"; der Hover einer Menuezeile (Gray Alpha 100 auf #0a0a0a) ergibt #191919,
         eine Stufe darunter. Menuegrund und Auswahl liegen damit 21 Stufen auseinander.
         Chips: gewaehlt = Auswahl (wie hell), gewaehlt und ueberfahren Gray 300 ("Active
         background"), die Raender Gray 500 und Gray Alpha 500 (#ffffff3d, auf #292929 = #5c5c5c --
         der Ton, den #5a5a62 vorher ohne Blaustich hatte).
         Eine Stufe ruhiger (15.10. abends): Menue #0f0f0f, Auswahl #242424, aktiv #2e2e2e, Rand
         der schwebenden Schicht #333333 statt Gray 500 -- Geists Ring selbst (14,5 % Weiss auf
         #0f0f0f = #323232). Der Hover einer Menuezeile (6 % auf #0f0f0f) ergibt #1d1d1d. */
      --up-menu-bg: #0f0f0f; --up-sel-bg: #242424; --up-menu-border: #333333;
      --up-chip-rand-hover: #333333; --up-chip-an: #242424; --up-chip-an-hover: #2e2e2e; --up-chip-rand-an-hover: rgba(255,255,255,.24);
      --up-dd-shadow: var(--up-e-2);   /* die dunkle e-2, siehe den hellen Block */
      /* Event-Pins im Dunkeln: dieselben Stufen auf der dunklen Leiter -- der Rand eine Stufe
         ueber --up-menu-border, die Linie auf --vc-fourth (#6c717a), damit sie wie hell hinter
         den Daten bleibt und nicht heller als die Achsenschrift wird. */
      --up-evm-rand: #333333; --up-evm-linie: #6c717a;
      /* Die vier Tiefen im dunklen Thema stehen seit dem 28.09. im Block vor .up-root, an
         [data-theme="dark"] -- dort erreichen sie auch, was am <body> haengt.
         Schalterflaeche und -rand eine Stufe ruhiger (15.10. abends) wie Kopf und Linie:
         #1f1f1f und #222222 statt #1a1a1a und #2e2e2e. */
      --vc-switch-bg: #1f1f1f; --vc-switch-border: #222222; --vc-third: #a0a0a0; --vc-fourth: #6c717a;
      /* Die Pille hebt ihre Schiene noch einmal an, statt einen festen Ton zu tragen -- sie muss
         der Schiene folgen, die das seit dem 27.09. auch tut (Begruendung bei --vc-switch-rail).
         Der feste Ton #28282c laege im Menue UNTER der angehobenen Schiene (40 gegen 47).
         Seit dem 15.10. Geists Stufen: Schiene Gray Alpha 100 (6 %), Pille Gray Alpha 200 (9 %)
         darueber -- der gewaehlte Reiter im dunklen Dashboard-Bild von Vercel traegt genau diese
         Stufe (gemessen 22 auf #000). Vorher 5,5 und 7 %. */
      --vc-switch-pill: rgba(255,255,255,.09);
      --up-switch-off: #454545;   /* Gray 500; siehe hell. Bleibt auch "eine Stufe ruhiger": Bedienelement */
      /* Die Textfassung des Blau-Lila-Paares, hier HERAUFgedreht statt herunter -- der Text
         steht auf --vc-field #2a2a2a. Gerechnet: 4.61 und 5.07, beide ueber den 4.5 aus AA.
         Die Flaechentoene --up-blau und --up-lila erben absichtlich aus dem hellen Block: eine
         Flaeche in dieser Saettigung traegt auf beiden Gruenden. Nur der TEXT braucht ein
         zweites Paar, weil er gegen den Grund gelesen werden muss und nicht auf ihm liegt. */
      --up-blau-text: #4c90ff;
      --up-lila-text: #b280ff;
      /* Das upstreem-Team im Dunkeln (Begruendung im hellen Block): dieselbe Farbfamilie
         HERAUFgedreht. #c9b596 auf #2b2620 7.5:1; die Flaeche liegt eine Stufe ueber der Karte
         (#1c1c1f), damit der Chip als Kasten steht und nicht nur als Schrift. */
      --vc-staff-ink: #c9b596; --vc-staff-bg: #2b2620;
      /* DIE SCHIENE HEBT IM DUNKELN IHREN GRUND AN, statt einen festen Ton zu tragen (27.09.).
         Gemeldet: "Im Darkmode sind die Background der ganzen Switcher recht schwer zu erkennen
         auf unserem Page Background." Die Ursache stand hier als benannter Kompromiss: ein
         DECKENDER Ton muss zwischen drei Gruenden liegen, die 18 Stufen auseinander sind (Seite
         #101114 = 17, Karte #1c1c1f = 28, Kopfzeile und Menue #232326 = 35), und #17171a war das
         Maximum dieses Minimums -- auf der Seite 6 Stufen ueber dem Grund, auf der Karte 5
         darunter. Die Seite ist aber genau der Grund der meisten Umschalter (die Werkzeugleisten
         der Tabellen sind durchsichtig), und 6 Stufen bei L* 5 verschwinden auf jedem Bildschirm
         mit etwas Umgebungslicht. Dieselbe Meldung kam am 07.09. schon einmal.
         Ein Ton kann das nicht, ein ANHEBEN kann es: 5,5 Prozent Weiss legt auf JEDEN Grund
         dieselbe Stufe -- Seite +13, Karte +12, Kopfzeile und Menue +12, also so viel wie im
         Hellen (#f2f2f2 auf Weiss: 13). Die Pille geht mit (--vc-switch-pill oben) und steht auf
         jedem Grund 14 bis 15 Stufen ueber der Schiene (vorher 17, hell 13). Gemessen mit
         _h_sw_mess.html, rohe Kanaele mit Augengewichten wie ueberall in dieser Datei.
         Aufgegeben ist damit das Ziel vom 22.09., EIN Ton je Rolle: er hat den haeufigsten Fall
         unsichtbar gemacht. Eine Liste von Flaechen mit eigener Schienenfarbe braucht es auch
         jetzt nicht -- die Stufe haengt am Grund, nicht an der Flaeche. */
      --vc-switch-rail: var(--up-seite-bg);
      /* Eingabefelder wie Geists Input: Background 1 mit Rand (gemessen #0a0a0a, Ring Gray Alpha
         400). Im grauen Hell genauso (--vc-field = --vc-bg, weiss mit Rand). Vorher #232326 --
         ein Feld, das heller war als jedes Menue, in dem es stand. Seit 15.10. abends der Inhalt
         eine Stufe ruhiger, #0f0f0f. */
      --vc-field: #0f0f0f;
      /* Die Kopfleiste jeder Tabelle: eine Stufe HELLER als die Tabellenflaeche (--vc-bg
         #1b1b1b), nicht dunkler. Hier stand #121212 -- die Farbe der SEITE, die Leiste sass
         damit optisch hinter der Karte statt darauf. #242424 steht ohnehin in dieser Palette
         (--vt-row-hover-solid), es bleibt also eine abgeleitete Farbe. Dass eine
         ueberstrichene Zeile fast denselben Wert traegt (Mischwert etwa #232323), kostet
         nichts: die Kante zwischen Leiste und Koerper zeichnet die Kopfzelle selbst mit
         border-bottom 1px --vc-border. */
      /* #242424 war ein Waise: ein Ton aus der Zeit vor Linears Leiter, den der Farbtausch am
         08.09. nicht erfasst hat, weil er in keiner der getauschten Zeichenketten vorkam. Auf
         einer Karte (#1c1c1f) sass damit ein Kopf, der zu keiner Stufe gehoerte -- genau der
         "komische Eindruck", der gemeldet wurde. Jetzt tertiary, also dieselbe Stufe wie jede
         andere Kopfzeile und jeder Hover. */
      /* GEIST (15.10.): der Kopf einer Tabelle traegt keine eigene Flaeche, sondern den Grund der
         Tabelle (Geists Table: Kopf durchsichtig, darunter Gray 400 als Linie). Die flachen
         Tabellen taten das schon (ihr Kopf ist --up-surface); mit #232326 hier standen die
         Kaesten auf dem Dashboard als einzige mit hellem Kopf da. Der Hover einer Zeile ist Gray
         100 -- Geists Hover auf Background 1. Eine Stufe ruhiger (15.10. abends): Kopf = Inhalt
         #0f0f0f, Hover #1f1f1f, derselbe Abstand von 16. */
      --vt-head-bg: #0f0f0f; --vt-logo-bg: #e0e0e0; --vt-row-hover: #1f1f1f;
      --vt-logo-ink: var(--vc-text-invert);
      /* Die Vorgabe der Akzentfarbe im Dunkeln -- wieder buchstabengleich das, was vorher aus
         --vc-text und --vc-bg kam. */
      --up-accent: #e0e0e0; --up-accent-fg: #1b1b1b; --up-accent-ink: #e0e0e0;
      --vt-row-hover-solid: #1f1f1f;
      /* --vt-up/--vt-down dunkel: seit dem 15.10. im dunklen Block an :root (Geist). */
      /* Im Dunkeln ein aufgehellter Ton mit weniger Deckung -- auch das wortgleich aus den alten
         Elementen. Der Hintergrund darf hier etwas mehr tragen, sonst verschwindet er im Grund. */
      --vt-brand: rgba(255, 190, 60, 0.85);
      --vt-brand-bg: color-mix(in srgb, var(--vt-brand) 7%, transparent);
      --vt-brand-bg-hover: color-mix(in srgb, var(--vt-brand) 14%, transparent);
      --vt-brand-border: color-mix(in srgb, var(--vt-brand) 60%, transparent);
      --vt-brand-border-hover: var(--vt-brand);
      --vc-hover-border: #707070;
      /* Auf dunklem Grund braucht der Ring mehr Deckkraft, sonst verschwindet er im Panel. */
      --up-focus: #6DB2FF; --up-focus-ring: rgba(109,178,255,0.28);
    }

    /* ---------- DIE DREI ANDEREN AKZENTFARBEN ----------------------------------------------
       Gewaehlt wird im Einstellungsfenster, core schreibt die Wahl als data-accent an das
       <html> und merkt sie im localStorage.
       Warum html[data-accent] .up-root und nicht html[data-accent] allein: die Vorgabe steht als
       EIGENE Deklaration an .up-root. Ein geerbter Wert vom <html> verliert gegen jede eigene
       Deklaration, egal wie spezifisch er ist -- also muss der Selektor .up-root selbst treffen.

       DIE AKZENTFARBE HAT ZWEI ROLLEN, und nur eine davon kommt mit einem Wert aus:

         --up-accent      die FLAECHE. Was darauf liegt, traegt --up-accent-fg, und die dreht
                          sich mit der Farbe statt mit dem Thema. Ein Wert reicht also.
         --up-accent-ink  die SCHRIFT. Sie liegt auf der Seite, nicht auf einer Flaeche, und
                          konkurriert damit mit dem Grund des THEMAS -- hier braucht es zwei.

       DIE WERTE SIND AN DEN QUELLEN GEPRUEFT (19.09. verlangt: "check ob du wirklich Linear,
       Claude, Notion getroffen hast, mit Quelle"). Zwei von dreien stimmten nicht:

         Indigo      linear.app/brand nennt #5E6AD2 als Brand Indigo, Flaeche der primaeren
                     Schaltflaeche. Getroffen.
         Azur        Notions Palette fuehrt #2EAADC als Akzent, die primaere Schaltflaeche der
                     Anwendung ist aber #0075DE. Hier stand #1A73C7 -- ein von mir abgedunkeltes
                     #2383E2, also ein Wert, den es bei Notion gar nicht gibt. Jetzt #0075DE.
         Terrakotta  Anthropic fuehrt ZWEI Toene: Terracotta #C96442 fuer die Flaeche der
                     primaeren Schaltflaeche und Coral #D97757 fuer Schrift und Verweise auf
                     dunklem Grund. Ich hatte die SCHRIFTfarbe als Flaeche benutzt -- daher die
                     Meldung "schwarzer Text auf Orange wirkt komisch". Er war nicht zu schwach
                     (5.30), aber es war der falsche Ton fuer die Rolle.

       Gemessen, verlangt ist AA (4.5). Flaeche gegen die Schrift darauf, Tinte gegen den
       Seitengrund (hell #ffffff, dunkel #101114):
           Indigo      Flaeche #5E6AD2 weiss 4.70    Tinte hell 4.70   dunkel #7C8AF0 6.07
           Azur        Flaeche #0075DE weiss 4.57    Tinte hell 4.57   dunkel #3D97F0 6.19
           Terrakotta  Flaeche hell #BD5836 weiss 4.55, dunkel #D97757 mit dunkler Schrift 5.30
                       Tinte hell 4.55   dunkel 6.05
       #BD5836 ist Anthropics Terracotta, so weit abgedunkelt, dass weisse Schrift AA traegt
       (#C96442 selbst traegt weder weisse 3.90 noch dunkle 4.24). Im Dunkeln bleibt es bei
       Coral mit dunkler Schrift -- das ist dort der uebliche Bau und Anthropics eigener Ton fuer
       diesen Grund. Terrakotta ist damit die einzige Farbe mit zwei FLAECHEN; die anderen zwei
       brauchen nur zwei Tinten. */
    /* "Standard + Blau" (06.10.): nur die Linienfarbe der Charts (UC.chartInk), die Oberflaeche bleibt
       im Standard. Die Werte sind die erste Stufe von UC.ruhigeFamilie. */
    html[data-chartakzent="blau"] .up-root { --up-chart-ink: #3f66b0; }
    html[data-chartakzent="blau"] .up-root[data-theme="dark"] { --up-chart-ink: #83abf4; }
    /* Die Glut des Agenten folgt einem gewaehlten Akzent; ohne Wahl (Standard, Standard + Blau) bleibt sie
       im Blau der App -- die Standardtinte ist Schwarz bzw. Grau und leuchtet nicht. */
    html[data-accent] .up-root { --vc-mira-glut-ton: var(--up-accent-ink); }
    html[data-accent="indigo"] .up-root { --up-accent: #5E6AD2; --up-accent-fg: #ffffff; --up-accent-ink: #5E6AD2; }
    html[data-accent="indigo"] .up-root[data-theme="dark"] { --up-accent-ink: #7C8AF0; }
    html[data-accent="azur"] .up-root { --up-accent: #0075DE; --up-accent-fg: #ffffff; --up-accent-ink: #0075DE; }
    html[data-accent="azur"] .up-root[data-theme="dark"] { --up-accent-ink: #3D97F0; }
    html[data-accent="terrakotta"] .up-root { --up-accent: #BD5836; --up-accent-fg: #ffffff; --up-accent-ink: #BD5836; }
    html[data-accent="terrakotta"] .up-root[data-theme="dark"] { --up-accent: #D97757; --up-accent-fg: #1f1f1b; --up-accent-ink: #D97757; }

    /* ================= DAS GRAUE HELL ("Light", 15.10.) ===========================================
       DAS EINZIGE HELL DER APP (15.10. abends: "Das Hell klassisch faellt weg als Design"). Die Wahl
       heisst nur noch Light / Dark / System; "Legacy Light" ist als Wahl entfernt, eine gespeicherte
       Vorliebe "legacy" wird beim Lesen zu Light (core.js prefsLesen). Nach Vercel: die SEITE grau --
       Seitengrund, Seitenleiste, Seitenkopf, Reiterzeile --, die INHALTSFLAECHEN weiss -- Tabellen,
       Karten, Charts, Drawer, Menues. core.js setzt data-up-hell="grau" ans <html>, sobald hell
       gemalt wird (hellAnwenden) -- ohne Vorliebe, ohne Bedingung; im Dunkeln steht es nicht.
       WARUM DIE BLOECKE OBEN NICHT SELBST GRAU SIND, sondern dieser hier sie ueberschreibt: ohne das
       Attribut sieht man sie noch an zwei Stellen, und an beiden ist das richtig. (1) Die Landingpage
       laedt core, ist aber kein Dashboard -- hellAnwenden laesst sie aus (__upApp.landing), ihre
       Fenster bleiben weiss wie bisher. (2) Das erste Bild der App, bevor das Kopf-Skript oder core
       das Attribut gesetzt hat. Ueberschrieben werden fuenf Marken (--up-surface, --vc-border,
       --vc-switch-border, --vc-heading-bg, --vc-field) -- alles andere gilt in beiden unveraendert,
       eine zweite helle Palette gibt es nicht.

       DIE WERTE SIND VERCELS EIGENE, ausgelesen am 15.10. aus vercel.com/geist (helles Thema,
       --ds-*-value an :root) und gegengeprueft an zwei Seiten mit App-Rahmen:
           Background 1  --ds-background-100  hsl(0 0% 100%)  #ffffff  "Default element background"
           Background 2  --ds-background-200  hsl(0 0%  98%)  #fafafa  "Secondary background"
           Gray 400      --ds-gray-400        hsl(0 0%  92%)  #ebebeb  "Default border"
       vercel.com/docs: body, Kopf und Seitenleiste #fafafa, Karten #ffffff mit 1px #eaeaea, Radius 12.
       Das Bild zur neuen Navigation (vercel.com/changelog/dashboard-navigation-redesign-rollout,
       26.02.2026): Seitenleiste, Kopf und Reiterzeile #fafafa, darunter die Inhaltsflaeche #ffffff
       mit einer hellen Linie (#e6e6e6) als Kante; der gewaehlte Reiter ein grauer Fleck (#e6e6e6 =
       8 % Schwarz auf #fafafa) OHNE Rahmen.

       WAS SICH AENDERT, und nur das:
         --up-surface  der Seitengrund (#ffffff -> #fafafa). Er ist schon heute genau DAS Token, das
                       "die Farbe der Seite" bedeutet -- Deckstreifen hinter klebenden Leisten,
                       Kopfzeilen von Komponenten, der Grund unter einer flachen Tabelle. Alles, was
                       ihn traegt, wird damit grau, ohne dass eine Regel ihn nennen muss.
         --vc-border   Vercels Default border (#e4e2e4 -> #ebebeb). Die Kante zwischen Weiss und
                       Grau ist die Linie, die Vercel dafuer zieht; die etwas dunklere aus Linears
                       Leiter neben Vercels Grau saehe nach zwei Systemen aus.
       Was NICHT: die Schriftfarben (auf #fafafa gemessen: --vc-text 15.8, --vc-muted 9.8,
       --vc-third 4.8 -- AA haelt; --vc-fourth 3.5 wie im Weiss 3.7, Beiwerk), die Karte (--vc-bg
       bleibt #ffffff), Kopfzeile und Hover in den Tabellen, Menues, Akzent, Tiefen.

       INNERHALB EINER WEISSEN FLAECHE IST DER GRUND WEISS. Ein Drawer, ein Fenster am <body>
       (up-portal) und der Kasten einer flachen Tabelle setzen --up-surface auf --vc-bg zurueck: was
       dort "Seitenfarbe" sagt -- der klebende Tabellenkopf, die Gesamtzeile, die festgehaltene erste
       Spalte --, ist die Farbe DIESER Flaeche. */
    html[data-up-hell="grau"] {
      --up-seite-grau: #fafafa;   /* Vercel Background 2 */
      --up-kante-grau: #ebebeb;   /* Vercel Gray 400, Default border */
      /* Die leise Flaeche (Hover, Kopfflaechen) des grauen Hell, Vercel Gray 100 -- Begruendung
         unten an --vc-heading-bg. Als Marke an <html> und nicht nur an der Wurzel, weil Flaechen mit
         EIGENER Palette sie brauchen: die Dialogkarte (.up-topicmodal-card) setzt ihre leise Flaeche
         selbst, und eine eigene Deklaration schlaegt jeden geerbten Wert der Wurzel -- ihr
         Schliessen-Hover stand bis zum 16.10. auf dem alten #f9f8f9, 6 Stufen unter dem Weiss der
         Karte statt 13. */
      --up-leise-grau: #f2f2f2;
      /* Der Seitengrund als Token (20.10.): Weiss, im grauen Hell das helle Grau, im Dunkeln #0a0a0a (unten). Die Schiene der Umschalter liest ihn. */
      --up-seite-bg: var(--up-seite-grau);
      /* DER GEWAEHLTE ZUSTAND DER NAVIGATION (15.10. abends angefordert: "rgba(0, 0, 0, 0.05) ist die
         Farbe fuer selected states -- Sidebar-selektierte Menues, in den Nav-/Subpage-Navigationen
         die Backgrounds der Container. Und da auch bitte weiterhin eine Border!").
         Der Grund ist Geist Gray Alpha 100 (#0000000d = 5,1 % Schwarz) -- genau der Ton, den Geists
         eigene Seitennavigation fuer den gewaehlten Eintrag traegt (vercel.com/geist, gewaehlter
         Punkt links: bg-gray-alpha-100).
         Der RAND ist Gray Alpha 400 (#00000014 = 8 % Schwarz), Geists "Default border" in der
         durchscheinenden Fassung. Durchscheinend und nicht das feste Gray 400 (#ebebeb), weil der
         Grund durchscheint: auf der Seite (#fafafa) ergibt er #eeeeee, und ein fester Rand #ebebeb
         laege darauf nur 3 Stufen dunkler -- unsichtbar. Der durchscheinende liegt ueber dem Grund
         (background-clip: border-box) und ergibt #dbdbdb, 19 Stufen Abstand; auf einem weissen
         Fenster (Preferences) dasselbe Verhaeltnis. Geist selbst sagt dazu: "Gray-alpha only when
         the background should show through" -- hier tut er es. 1px wie jeder Rand der App.
         15.10. SPAET NACHGEZOGEN ("die selected States ... sind mir zu dunkel, mach das so wie auf
         meinem Screenshot also die Farbe", Screenshot von Geists Seitennavigation): der Grund ist
         genau die Farbe des Screenshots, #ededed, jetzt deckend -- auf #fafafa derselbe Ton wie die
         5 % vorher, auf weissen Fenstern (Preferences) aber nicht mehr heller als in der Leiste.
         Zu dunkel war der RAND: 8 % ueber dem Grund ergaben #dadada, eine harte Kontur, die Geists
         Vorbild nicht hat. Jetzt 3 % ueber #ededed = #e6e6e6 -- ein Hauch dunkler als der Grund, der
         Rand bleibt also (abgenommen: "weiterhin eine Border"), nur nicht mehr als Strich. Die
         Seitenleiste traegt seither gar keinen (sidebar.css). */
      --up-wahl-bg: #ededed;
      --up-wahl-rand: rgba(0,0,0,.03);
      /* DAS GRUNDGRAU AM <html> (17.10., bestaetigt: "also ja"). Die leise Flaeche stand an :root noch
         auf #f9f8f9 (Block "DIE SCHRIFTSTUFEN GELTEN IM GANZEN DOKUMENT") -- jede Flaeche am <body>
         ohne eigene Palette (helles [data-theme], keine .up-root) trug darum im grauen Hell die alte,
         zu blasse leise Flaeche und den alten Knopf-Hover (--up-knopf-hover rechnet aus ihr). An der
         Wurzel steht das Grau schon (unten, .up-root); hier dieselbe Marke fuer alles darueber.
         (0,1,1) schlaegt :root (0,1,0); jede Wurzel, jede eigene Palette und jedes dunkle Element
         setzt sie weiter selbst. Ohne das Attribut (Landingpage, erstes Bild, Dunkel) bleibt es. */
      --vc-heading-bg: var(--up-leise-grau);
    }
    /* Der Grund hinter allem (html/body). Das Kopf-Skript der App malt ihn weiss (kopf.css,
       html[data-up-pagetheme="light"] body, 0,1,2); hier eine Stufe spezifischer (0,2,2) statt
       spaeter geladen. Nur ohne dunkles Seitenthema -- dann steht data-up-hell ohnehin nicht. */
    html[data-up-hell="grau"]:not([data-up-pagetheme="dark"]),
    html[data-up-hell="grau"]:not([data-up-pagetheme="dark"]) body { background-color: var(--up-seite-grau); }
    html[data-up-hell="grau"] .up-root:not([data-theme="dark"]) {
      /* Hell bleibt der Grund eines Popups Weiss (20.10., Nutzer), auch im grauen Hell; nur die Leisten (Fuss) tragen das helle Grau. */
      --up-popup-fuss: var(--up-seite-grau);
      --up-seite-bg: var(--up-seite-grau);   /* auch in Popups: die .up-root des Schleiers wuerde sonst das Weiss von unten tragen */
      --up-surface: var(--up-seite-grau);
      --vc-border: var(--up-kante-grau);
      --vc-switch-border: var(--up-kante-grau);
      /* DIE LEISEN FLAECHEN MUESSEN AUF DEM GRAU NOCH ZU SEHEN SEIN. --vc-heading-bg ist die Flaeche
         fuer Hover und Kopfzeilen (#f9f8f9) -- auf #fafafa liegt sie EINE Stufe darunter, der Hover
         eines Knopfs in der Werkzeugleiste waere unsichtbar. Vercel nimmt dafuer Gray 100 (#f2f2f2,
         "Default background" einer Komponente; dasselbe, was 5 % Schwarz auf Weiss ergibt, Vercels
         Hover in Menues). Auf der Seite 8 Stufen, auf der Karte 13. */
      --vc-heading-bg: var(--up-leise-grau);
      /* Eingabefelder wie bei Vercel weiss mit Rahmen (Background 1): das bisherige #f9f8f9 waere
         auf der grauen Seite vom Grund nicht zu unterscheiden, nur der Rahmen bliebe. */
      --vc-field: var(--vc-bg);
    }
    /* Eine WURZEL IN einer weissen Flaeche setzt den Grund ebenfalls zurueck -- sonst stellte die Regel
       darueber in jeder inneren .up-root wieder das Grau her (gemessen 15.10.: die Einheit
       "Product Movement" im Shopping-Band, eine eigene .up-root, stand grau im weissen Band).
       Drawer, Portale und Bubble-Drawer, die Baender und Kaesten unten. (0,4,1) gegen (0,3,1). */
    html[data-up-hell="grau"] .up-root:is(.up-kdr, .up-portal):not([data-theme="dark"]),
    html[data-up-hell="grau"] :is(.up-kdr, .up-portal, [id^="drawer-"]) .up-root:not([data-theme="dark"]),
    html[data-up-hell="grau"] :is(.up-band, .up-box, .upt-grp-sidepanel) .up-root:not([data-theme="dark"]) {
      --up-surface: var(--vc-bg);
    }
    /* DIE FLACHE TABELLE BLEIBT FLACH UND WIRD WEISS: kein Kasten, keine Rundung, oben und unten die
       Linie, links und rechts bis an die Kante (data-flach, 15.10. so abgenommen) -- nur ihr Grund ist
       jetzt Weiss statt durchsichtig. Das ist auch Vercels Bild: die Inhaltsflaeche laeuft dort bis an
       den Fensterrand. Ein weisser Kasten mit Radius braechte den Rahmen zurueck, der am 15.10.
       ausdruecklich weggenommen wurde. Leiste darueber und Pagination darunter stehen auf der Seite. */
    html[data-up-hell="grau"] .up-root[data-flach="yes"]:not([data-theme="dark"]) .up-box {
      --up-surface: var(--vc-bg);
      background: var(--up-surface);
    }
    /* DAS OFFENE BAND (core "DAS OFFENE BAND", .up-band unter data-flach) ist Inhalt und damit weiss
       wie die flache Tabelle -- dieselbe Form, dieselbe Ueberlegung: bis an die Kanten, oben und
       unten die Linie, die Ueberschriftzeile IM Band (.up-band-head: Ueberschrift, D/W/M) auf Weiss
       wie der Kopf einer Tabelle. Die Werkzeugzeile darueber (Kalender, Filters) bleibt auf der Seite.
       Gemessen auf Citations, Brands, Shopping, Ads und dem Dashboard.
       DAS BAND MIT ZWEI EINHEITEN (.up-band-zwei) bekommt nur das Token, keinen Grund: sein Grund
       ist die Linienfarbe, die durch die 1px-Luecke zwischen den Einheiten scheint, und die
       Einheiten decken mit --up-surface (core) -- jetzt also weiss. Weiss gemalt waere die
       Trennlinie weg.
       Dazu die Gruppenspalte neben der Prompt-Tabelle (.upt-grp-sidepanel): sie steht flach wie im
       Kasten neben dem weissen Tabellenkasten und lag bisher durchsichtig auf der Seite.
       Ein Band ist entweder selbst die flache Wurzel oder steht in ihr (wie in core), daher je zwei
       Formen. (0,5,1) und mehr: schlaegt die Seitenregel (0,3,1) auch an einer Band-Wurzel. */
    html[data-up-hell="grau"] .up-root[data-flach="yes"].up-band:not([data-theme="dark"]),
    html[data-up-hell="grau"] .up-root[data-flach="yes"]:not([data-theme="dark"]) .up-band,
    html[data-up-hell="grau"] .up-root:not([data-theme="dark"]) .upt-grp-sidepanel {
      --up-surface: var(--vc-bg);
    }
    html[data-up-hell="grau"] .up-root[data-flach="yes"].up-band:not(.up-band-zwei, [data-theme="dark"]),
    html[data-up-hell="grau"] .up-root[data-flach="yes"]:not([data-theme="dark"]) .up-band:not(.up-band-zwei),
    html[data-up-hell="grau"] .up-root:not([data-theme="dark"]) .upt-grp-sidepanel {
      background: var(--up-surface);
    }
    /* Die Karten der Opportunities tragen 2 % Schwarz ueber dem Grund (--uo-col-bg) -- auf der grauen
       Seite ein grauer Kasten auf Grau. Im grauen Hell sind sie Inhalt und damit weiss. */
    html[data-up-hell="grau"] .up-root.uo-root:not([data-theme="dark"]) { --uo-col-bg: var(--vc-bg); }
    /* Der gewaehlte Reiter: der Gewaehlt-Ton der Navigation (--up-wahl-bg, 5 % Schwarz) MIT Rand
       (--up-wahl-rand). Die Vorgabe (Kopfzeilengrund #f9f8f9) laege auf #fafafa ohne Unterschied.
       Bis zum 15.10. abends stand hier ein Fleck aus 8 % der Schrift OHNE Rahmen -- zurueckgenommen:
       "weiterhin eine Border". Die Breite (1px) kommt aus der Grundregel von .up-ph-navpille. */
    html[data-up-hell="grau"] .up-root:not([data-theme="dark"]) .up-ph-navpille {
      background: var(--up-wahl-bg);   /* 18.10.: ohne Rand (border: 0 in der Grundregel) */
    }

    /* ================= DAS DUNKEL NACH GEIST: SEITE UND INHALT (15.10.) ===========================
       Das Gegenstueck zum grauen Hell darueber, Regel fuer Regel -- dieselben Flaechen, dieselbe
       Spezifitaet plus eins, nur mit Geists dunkler Skala (Herleitung am dunklen .up-root-Block):
           Seite, Seitenleiste, Seitenkopf, Reiterzeile    Background 2   #000000  (--up-surface)
           Tabellen, Karten, Charts, Drawer, Menues        Background 1   #0a0a0a  (--vc-bg)
       Seit "eine Stufe ruhiger" (15.10. abends, Herleitung am dunklen .up-root-Block) Seite
       #0a0a0a und Inhalt #0f0f0f -- dieselbe Aufteilung, 5 Stufen statt 10 dazwischen.
       Bis heute lag im Dunkeln alles auf EINEM Grund (#101114): Tabelle, Kopf, Chart und
       Seitenleiste in derselben Farbe, getrennt nur durch Raender. Im Licht ist der Inhalt weiss
       auf grauer Seite -- hier ist er jetzt eine Spur heller als die schwarze Seite, wie bei Vercel.
       data-up-dunkel setzt core.js (hellAnwenden), sobald dunkel gemalt wird; nie auf der
       Landingpage. Ohne das Attribut (Landingpage, erstes Bild) gelten nur die Marken am dunklen
       .up-root -- dann liegt wie bisher alles auf dem Seitengrund, nur schon in Geists Toenen. */
    html[data-up-dunkel="geist"] {
      /* Vercel Background 2 war #000; seit "eine Stufe ruhiger" (15.10. abends) Background 1 als
         Seite, wie die Leinwand der Benachrichtigungen. */
      --up-seite-dunkel: #0a0a0a;
      /* DER GEWAEHLTE ZUSTAND DER NAVIGATION IM DUNKELN: Geists Gray Alpha 100 (#ffffff0f, 6 %
         Weiss) -- genau der Ton, den Geists eigene Seitennavigation im Dunkeln fuer den gewaehlten
         Punkt traegt (vercel.com/geist, gemessen rgba(255,255,255,.06)). Er ist das Spiegelbild der
         hellen Wahl: #ededed ist Gray Alpha 100 der hellen Skala auf #fafafa. Gerechnet in L*: hell
         98,3 -> 94,0, dunkel 0 -> 4,1 -- derselbe Schritt.
         DURCHSCHEINEND und nicht deckend wie im Licht, und das ist der Unterschied zwischen den
         Skalen: deckend #0f0f0f laege auf einem Fenster in Background 1 (#0a0a0a, Preferences) nur
         5 Stufen ueber dem Grund; durchscheinend sind es dort #191919, auf der Seite #0f0f0f.
         Der Rand ist der Hauch des Lichts (3 %) in Weiss: ueber dem Grund #171717 statt #0f0f0f.
         Die Seitenleiste traegt keinen (sidebar.css), wie im Licht.
         Eine Stufe ruhiger (15.10. abends) bleibt der Wert, nur der Grund steigt: auf der Seite
         (#0a0a0a) #191919, der Rand darueber #202020; auf dem Inhalt (#0f0f0f) #1d1d1d. In den
         Preferences und in Miras Einstellungen liegt die Wahl seit dem 16.10. auf dem Seitengrund
         (die Spalte traegt --up-seite-dunkel, preferences.css) und ergibt wie in der Seitenleiste
         #191919; bis dahin auf der Kopfflaeche #1f1f1f, dort #2c2c2c.
         SEIT 27.10. DECKEND rgb(31,31,31) (Lukas: "Im Darkmode der selected state soll bitte etwas
         heller sein, also auf Sidebar, in Subpagenav die Backgrounds und so. rgb(31, 31, 31)").
         Deckend wie im Licht (#ededed): ueberall derselbe Ton, auf der Seite (#0a0a0a) wie auf dem
         Inhalt (#0f0f0f) -- vorher #191919 bzw. #1d1d1d. Das Argument oben gegen deckend galt
         #0f0f0f, nur 5 Stufen ueber einem Fenster; #1f1f1f liegt 16 bzw. 21 darueber. Es ist
         dieselbe Stufe wie --vc-heading-bg im Dunkeln (Geist Gray 200), also der Ton, den die Hover
         der Kopfknoepfe und leisen Knoepfe tragen: die lagen bis heute HELLER als das Gewaehlte,
         jetzt gleich. Der Rand bleibt: 3 % Weiss ueber #1f1f1f ergeben #262626, 7 Stufen
         wie vorher (#191919 -> #202020). Einmal hier, alle Gewaehlt-Zustaende lesen das Token. */
      --up-wahl-bg: rgb(31,31,31);
      --up-wahl-rand: rgba(255,255,255,.03);
    }
    /* DIE SCHRIFT IM DUNKELN NACH GEIST (16.10., Nutzer: "Dark: ja, stell mal bitte auf Geist um").
       Neutral statt der leicht blaustichigen Leiter (#e0e0e0 / #c9cbd0 / #a0a0a0 / #6c717a):
         --vc-text    #ededed  Geist Gray 1000, Primaertext
         --vc-muted   #c9c9c9  zwischen 1000 und 900 -- Geist hat dort keine Stufe, die App braucht
                               aber vier; derselbe Wert wie vorher, nur ohne Blaustich
         --vc-third   #a1a1a1  Geist Gray 900, Sekundaertext
         --vc-fourth  #7d7d7d  Geist Gray 800 (vorher #6c717a, auf #0a0a0a 4.0:1, jetzt 4.7:1)
       NUR unter html[data-up-dunkel]: dunkle Elemente im Licht (die Bulk-Leiste) bleiben, wie sie
       sind. Hier fuer jede .up-root (0,3,1 schlaegt den dunklen Block an .up-root, 0,2,0); die
       Flaechen am <body> bekommen dieselben Werte im Geist-Block an :root weiter unten. Menues
       und Tooltips mit EIGENER Leiter (z. B. --vc-muted #a0a0a0 im Tooltip) sind Wert fuer Wert
       getauscht: #e0e0e0 -> #ededed, #c9cbd0 -> #c9c9c9, #a0a0a0 -> #a1a1a1, #6c717a -> #7d7d7d. */
    html[data-up-dunkel="geist"] .up-root[data-theme="dark"] {
      --vc-text: #ededed; --vc-muted: #c9c9c9; --vc-third: #a1a1a1; --vc-fourth: #7d7d7d;
    }
    /* Der Grund hinter allem. Das Kopf-Skript malt ihn im Dunkeln #101114 (kopf.css,
       html[data-up-pagetheme="dark"] body, 0,1,2); hier (0,2,2) wie beim grauen Hell. */
    html[data-up-dunkel="geist"]:not([data-up-hell]),
    html[data-up-dunkel="geist"]:not([data-up-hell]) body { background-color: var(--up-seite-dunkel); }
    /* INNERHALB EINER INHALTSFLAECHE IST DER GRUND DIE INHALTSFLAECHE -- wie im grauen Hell: Drawer,
       Portale und Bubble-Drawer, Baender und Kaesten setzen --up-surface auf --vc-bg zurueck, damit
       ein klebender Kopf, eine Gesamtzeile oder eine festgehaltene Spalte die Farbe DIESER Flaeche
       traegt. (0,4,1) bzw. (0,5,1) gegen die Marke am dunklen .up-root (0,2,0). */
    html[data-up-dunkel="geist"] .up-root[data-theme="dark"]:is(.up-kdr, .up-portal),
    html[data-up-dunkel="geist"] :is(.up-kdr, .up-portal, [id^="drawer-"]) .up-root[data-theme="dark"],
    html[data-up-dunkel="geist"] :is(.up-band, .up-box, .upt-grp-sidepanel) .up-root[data-theme="dark"] {
      --up-surface: var(--vc-bg);
    }
    /* Die flache Tabelle: bis an die Kante, oben und unten die Linie, Grund jetzt Background 1. */
    html[data-up-dunkel="geist"] .up-root[data-flach="yes"][data-theme="dark"] .up-box {
      --up-surface: var(--vc-bg);
      background: var(--up-surface);
    }
    /* Das offene Band (Chart oben) und die Gruppenspalte neben der Prompt-Tabelle: Inhalt. Das Band
       mit zwei Einheiten bekommt nur das Token -- sein Grund ist die Linienfarbe in der Fuge. */
    html[data-up-dunkel="geist"] .up-root[data-flach="yes"][data-theme="dark"].up-band,
    html[data-up-dunkel="geist"] .up-root[data-flach="yes"][data-theme="dark"] .up-band,
    html[data-up-dunkel="geist"] .up-root[data-theme="dark"] .upt-grp-sidepanel {
      --up-surface: var(--vc-bg);
    }
    html[data-up-dunkel="geist"] .up-root[data-flach="yes"][data-theme="dark"].up-band:not(.up-band-zwei),
    html[data-up-dunkel="geist"] .up-root[data-flach="yes"][data-theme="dark"] .up-band:not(.up-band-zwei),
    html[data-up-dunkel="geist"] .up-root[data-theme="dark"] .upt-grp-sidepanel {
      background: var(--up-surface);
    }
    /* Die Karten der Opportunities: Inhalt (opportunities.css trug 5 % Weiss ueber dem Grund). */
    html[data-up-dunkel="geist"] .up-root.uo-root[data-theme="dark"] { --uo-col-bg: var(--vc-bg); }
    /* Der gewaehlte Reiter der Seitennavigation: Gray Alpha 100 mit dem Hauch Rand, wie im Licht.
       Vorher #232326 mit dem vollen Rahmen #34343a -- der kraeftigste Kasten der ganzen Kopfzeile. */
    html[data-up-dunkel="geist"] .up-root[data-theme="dark"] .up-ph-navpille {
      background: var(--up-wahl-bg);   /* 18.10.: ohne Rand */
    }

    /* ---------- Fokus-Ring fuer Eingabefelder ----------
       EIN Fokuszustand fuer die ganze App: blauer Rand plus ein 4px weicher Auslauf dahinter.
       Vorher war der Fokus in jeder Komponente die Hover-Randfarbe, teils zusaetzlich mit einem
       Hintergrundwechsel. Beides ist falsch: der Hover-Rand sagt "der Mauszeiger ist hier", nicht
       "hier landet deine Eingabe" -- man konnte einem Feld nicht ansehen, ob es den Fokus hat,
       solange die Maus darauf stand. Und ein Hintergrundwechsel beim Fokussieren laesst das Feld
       beim Hineinklicken die Farbe wechseln, was sonst nirgends in der App passiert.

       .up-field ist eine ZUSATZKLASSE auf dem Eingabefeld, keine Ersetzung: Rahmen, Radius und
       Groesse bleiben beim Feld selbst, hier steht nur der Fokuszustand. Der Rand faerbt sich um,
       der box-shadow legt den Auslauf dahinter, und outline:0 nimmt den doppelten Ring des
       Browsers weg -- der Ersatz ist sichtbarer als das Original, es geht also nichts verloren.
       Wer eine echte Tastatur-Navigation ohne Maus prueft: der Ring erscheint bei :focus, also
       auch beim Tabben. */
    .up-field:focus, .up-field:focus-visible,
    .up-fieldwrap:focus-within {
      border-color: var(--up-focus);
      box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring);
      outline: 0;
    }
    /* Hover darf den Fokus nicht ueberschreiben: die Hover-Regel eines Feldes steht meistens in
       der Komponentendatei und laedt damit NACH core. Gleiche Spezifitaet haette bedeutet, dass
       die Ladereihenfolge entscheidet -- derselbe Fehler, der hier schon dreimal aufgetreten ist.
       Die doppelte Klasse hebt die Spezifitaet an, statt sich auf die Reihenfolge zu verlassen. */
    .up-field.up-field:focus, .up-field.up-field:focus-visible {
      border-color: var(--up-focus); box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring);
    }

    /* ---------- Hover der Eingabefelder (13.10.) ----------
       EIN Hover fuer jedes Feld mit Rahmen. Vorher drei Sorten: dunkler Rand (--vc-hover-border,
       #6f6f6f -- "sehr dunkel", Add Brand), Rand plus Flaeche (Suchfelder oben in den Dropdowns)
       und gar keiner (Add Prompts, Anmeldung, Dialoge). Jetzt nur der Rand, eine Spur kraeftiger als
       er ohnehin ist, keine Flaeche -- wie Linear. Gemischt am Feld selbst aus seinem Rand und
       seiner Schrift, damit derselbe Wert hell, dunkel und in den Menues (eigene Farbsaetze) passt.
       Spezifitaet 0,2,0: schlaegt die Grundregel jedes Feldes (0,1,0), verliert gegen echte Zustaende
       wie Fehler (.uab-field.is-err .uab-in, 0,3,0) und geoeffnet (.uob-ddwrap.is-open, 0,3,0). Fokus,
       deaktiviert und aria-invalid stehen im :where (ohne Gewicht) -- der Fokusring bleibt.
       Neue Felder: die Klasse hier eintragen ODER .up-field ans Feld setzen, keine eigene Hover-Regel.
       .am-composer (13.10.) ist Miras Eingabekasten -- der Rahmen sitzt am Kasten, nicht am Textfeld
       darin (das hat border: 0), also steht der Kasten hier. :focus-within greift, sobald man tippt. */
    :is(.up-field, .up-fieldwrap, .up-es-input, .up-ddsearch-in, .up-topicmodal-name, .up-ment-search,
        .up-cgm-search-in, .upt-gm-search-in, .uab-in, .uap-input, .uap-rowedit, .uau-input, .ube-feld,
        .uca-input, .uca-bld-input, .ums-in, .uob-input, .uob-select, .usb-dlg-in, .usb-sumin,
        .usb-urlin, .usn-pin-in, .uto-in, .upr-dd-search, .am-ct-input, .am-prev-item-input,
        .am-prev-proj-input, .am-composer):is(:hover):where(:not(:focus, :focus-within, :disabled, [aria-invalid="true"])) {
      border-color: color-mix(in srgb, var(--vc-border) 86%, var(--vc-text));
    }

    /* ---------- Knopf-Zustaende nach Geist (15.10.) ----------
       Vorher zog jeder umrandete Knopf und jeder Menue-Ausloeser beim Ueberfahren seinen Rand auf
       --vc-hover-border (#6f6f6f hell, #707070 dunkel) -- "recht dunkel". Geist macht es anders,
       am 15.10. aus vercel.com/geist/button, /menu und /select ausgelesen (Werte im Hellen):
         Sekundaerknopf    Ruhe Rand Gray 400; Hover NUR die Flaeche (Gray 100), der Rand bleibt.
         Menue-Ausloeser   Hover Flaeche Gray 100 UND der Rand eine Stufe kraeftiger (Gray Alpha
                           400 -> 500, 8 -> 21 Prozent Schwarz) -- dieselbe Stufe wie Select/Input.
         Gedrueckt         nichts Eigenes (data-pressed aendert weder Flaeche noch Rand noch Groesse).
         Tastaturfokus     2px Abstand in der Grundfarbe, dann 2px in der Fokusfarbe.
       Die TOENE bleiben unsere: die Flaeche ist --vc-heading-bg (im grauen Hell ist das ohnehin
       Vercels #f2f2f2), die kraeftigere Kante ist dieselbe Mischung wie beim Hover der Eingabefelder
       darueber (Rand 86, Schrift 14), der Fokus steht in --up-focus. Der Fokus als outline mit
       Abstand und nicht als box-shadow: so ist der Abstand durchsichtig und braucht keine Grundfarbe,
       die auf einer Karte, im Menue und auf der grauen Seite jedesmal eine andere waere.
       An JEDEM Element neu ausgewertet, das eine eigene Palette traegt ([data-theme], Portale am
       <body>): ein Token, der var() enthaelt, wird dort berechnet, wo er steht -- nur an .up-root
       definiert, truege ein Menue mit eigenem Rand den Ton der Wurzel. */
    :root, .up-root, [data-theme] {
      --up-knopf-hover: var(--vc-heading-bg);
      --up-knopf-rand-hover: color-mix(in srgb, var(--vc-border) 86%, var(--vc-text));
      --up-knopf-fokus-abstand: 2px;
    }

    /* ---------- toolbar ---------- */
    .up-head { display: flex; align-items: center; height: 32px; margin-bottom: 16px; gap: 8px; }
    /* .up-heading ist der ABSCHNITTSTITEL, auch in der Kopfzeile einer Tabelle oder eines Charts, mit Zahl ("Event Tracking 1") oder ohne: 16/600 wie
       .up-sec-h und .up-dk-h2 (20.10.; die erste Fassung gab ihm den kleineren Block-Titel, und neben "Merchants" 16 stand "Recent Appearances" 14). */
    .up-heading { display: inline-flex; align-items: center; font-size: var(--up-ueb-abschnitt-gr); font-weight: var(--up-ueb-abschnitt-gew); letter-spacing: var(--up-ueb-abschnitt-spur); color: var(--vc-text); line-height: var(--up-ueb-abschnitt-zeile); white-space: nowrap; }
    /* DER ZAEHLER ALS CHIP (11.10., vom Nutzer so bestellt: "Separator weg, den Count in einen
       Chip mit grauem Background"). Vorher "Ueberschrift . 42" mit einem 4px-Punkt. Der Chip ist
       EIN Baustein fuer jede Zahl neben einer Ueberschrift -- .up-zaehler frei stehend,
       .up-head-count in der Kopfzeile einer Tabelle (dort erst sichtbar mit .has-count).
       Grund und Schrift sind die des leisen Etiketts (.up-marke.is-leise, --up-sel-bg): auf der
       Kartenflaeche deutlich genug, ohne mit dem Inhalt zu konkurrieren. 20px hoch, 12px Ziffern
       in gleicher Breite -- neben einer 14er-Ueberschrift die naechstkleinere Stufe.
       .up-head-sep bleibt als Klasse stehen (im Markup eingebauter Bubble-Elemente), zeigt aber
       nichts mehr. */
    .up-head-sep { display: none; }
    .up-zaehler, .up-head-count {
      display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
      box-sizing: border-box; height: 20px; min-width: 20px; padding: 0 7px;
      border-radius: var(--up-r-s); background: var(--up-sel-bg); color: var(--vc-muted);
      font-size: var(--up-fs-s); font-weight: var(--up-fw-m); line-height: 1;
      font-variant-numeric: tabular-nums; white-space: nowrap;
    }
    /* In der Kopfzeile OHNE eigene Mindestbreite (13.10. angefordert: "die Mindestbreite entfernen" --
       mit den frueheren 36px stand eine einstellige Zahl mit sichtbar mehr Luft links und rechts als
       eine dreistellige). Jeder Chip traegt jetzt dasselbe Polster (7) um seine Ziffern; dass ein
       Schalter rechts daneben beim Wechsel von 102 auf 9 ein Stueck wandert, ist der Preis dafuer. */
    .up-head-count { display: none; margin-left: 8px; }
    .up-heading.has-count .up-head-count { display: inline-flex; }
    .up-zaehler.is-sk, .up-head-count.is-sk { color: transparent; background: var(--vc-sk) var(--up-sk-verlauf); animation: var(--up-sk-bewegung); }
    .up-head-tools { display: flex; align-items: center; gap: 8px; margin-left: auto; flex: 0 0 auto; }

    /* ── Einklappbare Werkzeuggruppe ─────────────────────────────────────────────────────────
       Die Kopfleiste einer grossen Tabelle traegt schnell sechs bis acht Bedienelemente. Diese
       Gruppe schiebt sie zusammen, ohne sie wegzunehmen: eingeklappt bleibt nur der Ausloeser
       stehen, ausgeklappt steht alles genau wie vorher.

       Warum grid-template-columns 0fr -> 1fr und nicht max-width: nur so laeuft die Bewegung
       exakt bis zur ECHTEN Inhaltsbreite. Ein geratenes max-width haelt entweder zu frueh an
       oder laeuft die letzten Pixel ins Leere und wirkt dabei traege. Gleiche Bauart wie der
       Meldungsstreifen im Onboarding, dort mit Zeilen statt Spalten.

       200ms ease, beide Richtungen gleich -- ausdrueckliche Vorgabe.

       Der negative Aussenabstand im eingeklappten Zustand raeumt die Luecke weg, die .up-head-tools
       ueber `gap` auch um ein NULL Pixel breites Kind legt. Ohne ihn stehen zwischen der
       Statusgruppe und dem Ausloeser 16px statt 8px, und die Leiste sieht schief aus. */
    .up-toolgroup { display: grid; grid-template-columns: 0fr; opacity: 0; margin-right: -8px;
      transition: grid-template-columns 200ms ease, opacity 200ms ease, margin-right 200ms ease; }
    /* min-width: 0 ist Pflicht. Ein Grid-Kind bekommt sonst seine Inhaltsbreite als Untergrenze,
       und die Spalte laesst sich gar nicht erst auf 0 ziehen -- die Gruppe bliebe offen stehen. */
    .up-toolgroup-in { display: flex; align-items: center; gap: 8px; min-width: 0; overflow: hidden; }
    .up-root.is-tools-open .up-toolgroup,
    .up-root.is-tools-peek .up-toolgroup { grid-template-columns: 1fr; opacity: 1; margin-right: 0; }
    /* Die Kappung faellt erst NACH der Bewegung -- sonst haengt waehrend des Aufziehens ein Menue
       aus der Gruppe heraus. Und sie MUSS fallen: die Dropdowns darin sind absolut positionierte
       Kinder, und ein overflow:hidden am Vorfahren schneidet sie ab. Gesetzt wird die Klasse von
       einer Uhr mit benanntem Ende, NICHT von transitionend: in einem Bubble-Tab, der gerade
       nicht gemalt wird, laeuft der Uebergang nicht und das Ereignis kommt nie. */
    .up-root.is-tools-shown .up-toolgroup-in { overflow: visible; }

    /* ── Die Halteflaeche ist 16px groesser als die Leiste ────────────────────────────────────
       Solange der Zeiger hier drin ist, klappt die Werkzeuggruppe nicht zu. Ohne den Rand liegt
       die Grenze genau auf der Knopfkante: einmal am Rand vorbeigefahren, und die Leiste faehrt
       zusammen, obwohl der Nutzer sie noch benutzen wollte.

       padding wachsen lassen und die Aussenabstaende gegenrechnen -- so wird die Flaeche groesser,
       ohne dass sich im Layout ein Pixel verschiebt. margin-left bleibt `auto`: das ist die
       Rechtsausrichtung der ganzen Leiste, und ein Wert dort statt auto schoebe sie nach links.
       Nach links reicht die Flaeche trotzdem 16px weiter, weil das padding die Kastenkante
       verschiebt und `auto` nur den Rest verteilt.

       Nach unten sind die 16px genau die margin-bottom von .up-head -- die Flaeche endet also
       exakt an der Tabelle und legt sich nicht ueber ihren Kopf. Waere sie groesser, faenge sie
       Klicks ab, die dem Sortierkopf gehoeren.

       :has(.up-toolgroup) statt einer Regel fuer alle: nur eine Leiste mit einklappbarer Gruppe
       hat ueberhaupt etwas davon. Die anderen Tabellen sollen ihre Kastenmasse behalten. */
    /* :has(> .up-toolgroup) statt eines Klassennamens: die Leiste heisst je Komponente anders
       (.up-head-tools, .ubo-head-tools), aber sie ist immer der ELTERNTEIL der Gruppe. Die
       Ausrichtung (margin-left: auto) bleibt unangetastet -- ein Wert dort statt auto schoebe die
       ganze Leiste nach links. */
    /* .up-toolhost setzt makeToolGroup an tools, dem Elternteil der Gruppe (siehe dort). Vorher
       stand hier ":has(> .up-toolgroup)" -- in den Selector Stats des Nutzers 81ms fuer 78
       Treffer, zweitteuerster Selektor der Seite. Gleiche Spezifitaet (0,2,0), gleiche Wirkung. */
    .up-root .up-toolhost {
      /* Nach RECHTS ausdruecklich nicht. Dort ist die Kante der Komponente -- es gibt nichts,
         wohin man hineinfahren koennte, und die 16px kosten dort etwas: margin-right: -16px zieht
         zwar das Layout zurueck, aber scrollWidth zaehlt die Polsterung mit. Gemessen an allen
         sechs Komponenten und auf jeder Breite: der Kopf brauchte genau 16px mehr, als er hatte.
         Unsichtbar, weil die Polsterung durchsichtig ist -- aber ein Vorfahre mit
         overflow-x: auto haette daraus einen waagerechten Balken gemacht, und den haette
         niemand mit dieser Leiste in Verbindung gebracht.
         Links, oben und unten bleiben die 16px: dort liegt Platz, und dort faehrt der Zeiger
         wirklich vorbei. */
      padding: 16px 0 16px 16px; margin-top: -16px; margin-bottom: -16px;
    }
    /* Die Polsterung reicht 16px nach links in eine Flaeche, die normalerweise leer ist -- aber
       nicht immer: die Ueberschrift waechst mit der Auswahl ("0 selected" samt Loeschkreuz), und
       auf einer schmalen Tabelle kommt sie der Leiste nahe. Ueberlappen sich beide, gewinnt beim
       Klick die Leiste, weil sie im Markup SPAETER kommt -- und das Loeschkreuz waere tot.

       position+z-index statt eines kleineren Randes: dass ein positioniertes Element mit z-index 1
       ueber einem unpositionierten Geschwister liegt, ist eine Zusage der Spezifikation und
       braucht keine Messung. In dieser Testumgebung liesse sich der Klick auch gar nicht pruefen
       (das Fenster ist dort 0x0, elementFromPoint gibt fuer jeden Punkt null). */
    /* .has-toolgroup an der Wurzel, aus demselben Grund. Spezifitaet bleibt (0,3,0). */
    .up-root.has-toolgroup .up-heading { position: relative; z-index: 1; }

    /* ── Ausloeser und Einklapper ────────────────────────────────────────────────────────────
       Beide SIND .up-iconbtn -- 32x32, Radius 8, Farben und Hover kommen von dort. Hier steht
       kein einziger dieser Werte noch einmal, nur WANN sie sichtbar sind.

       Breite statt display, weil display sich nicht animieren laesst und die Vorgabe 200ms in
       beide Richtungen lautet. margin-right raeumt dabei die gap-Luecke weg, die die Leiste auch
       um ein null Pixel breites Kind legt -- ohne sie stuenden 16px statt 8px zwischen den
       Nachbarn. */
    /* ── Warum hier nur Deckkraft und Verschiebung animieren ─────────────────────────────────
       Beide Knoepfe behalten IMMER ihre 32px. Vorher animierten width UND margin-right mit -- zwei
       LAYOUT-Eigenschaften, und die Gruppe daneben ist ein Grid mit 1fr, dessen Spur bei jeder
       geaenderten Kinderbreite neu aufgeloest wird. Das war einmal pro Bild das ganze
       Kopfzeilen-Layout, waehrend darunter eine Tabelle steht. Genau daran hat es geruckelt.
       Deckkraft und transform erledigt der Browser dagegen ohne Layout und ohne Neuzeichnen.

       Der reservierte Platz kostet nichts Sichtbares: die Leiste ist rechtsbuendig
       (margin-left: auto), links von ihr liegt freie Flaeche. Ein 32px-Platz dort schiebt die
       Leiste in einen Bereich, der ohnehin leer war -- es wandert nichts, was der Nutzer ansieht.

       cubic-bezier(.2, 0, .2, 1) statt `ease`: ease bremst am Anfang UND am Ende, und auf sechs
       Pixel Verschiebung liest sich das als Zoegern. Diese Kurve geht sofort los und laeuft sanft
       aus. 200ms bleiben, wie vorgegeben. */
    .up-tbtrig, .up-tbcol { flex: 0 0 auto; overflow: hidden; }

    /* Der AUSLOESER darf seine Breite weiter mitanimieren. Das passiert genau einmal, beim
       Feststellen, gleichzeitig mit dem Aufziehen der Gruppe -- also waehrend das Layout ohnehin
       in Bewegung ist, und der Ruckler wurde auch nie dort gemeldet, sondern beim HOVER.
       Wichtiger noch: seinen Platz zu reservieren kostet echte Pixel. Gemessen bei 640px Breite
       und aufgeklappter Leiste lief prompts-table dadurch um 27px ueber -- die Tabelle mit den
       meisten Werkzeugen hat dort keine 32px zu verschenken. So wie es jetzt steht, gibt der
       Ausloeser seine 32px ab, und der Einklapper nimmt sie: die Leiste ist im festgestellten
       Zustand genau so breit wie vorher. */
    .up-tbtrig {
      width: 32px; opacity: 1;
      transition: width 200ms cubic-bezier(.2, 0, .2, 1), opacity 200ms cubic-bezier(.2, 0, .2, 1);
    }
    .up-root.is-tools-open .up-tbtrig { width: 0; opacity: 0; pointer-events: none; }

    /* Eingeklappt nimmt der Einklapper keinen Platz: dort gibt es nichts zu schliessen, und 32px
       waeren eine Luecke neben dem Ausloeser. Die Breite steht NICHT in der Uebergangsliste, sie
       schaltet also hart um -- und zwar in demselben Augenblick, in dem die Gruppe zu wachsen
       beginnt. In einem Kasten, der gerade aufzieht, sieht das niemand. */
    /* Der EINKLAPPER dagegen bewegt sich nur mit Deckkraft und Verschiebung -- das sind die zwei
       Eigenschaften, die der Browser ohne Layout und ohne Neuzeichnen erledigt. Genau daran hat
       es geruckelt: vorher animierten width UND margin-right mit, und die Gruppe daneben ist ein
       Grid mit 1fr, dessen Spur bei jeder geaenderten Kinderbreite neu aufgeloest wird -- einmal
       pro Bild das ganze Kopfzeilen-Layout, waehrend darunter eine Tabelle steht.
       Seine 32px sind im festgestellten Zustand reserviert; sie kosten nichts, weil der Ausloeser
       im selben Augenblick seine abgibt.

       cubic-bezier(.2, 0, .2, 1) statt `ease`: ease bremst am Anfang UND am Ende, und auf sechs
       Pixel Verschiebung liest sich das als Zoegern. Diese Kurve geht sofort los und laeuft sanft
       aus. 200ms bleiben, wie vorgegeben. */
    .up-tbcol {
      transition: opacity 200ms cubic-bezier(.2, 0, .2, 1),
                  transform 200ms cubic-bezier(.2, 0, .2, 1);
    }
    /* Doppelte Klasse, dieselbe Begruendung wie bei .up-field.up-field weiter oben: .up-iconbtn
       setzt selbst width: 32px, steht in derselben Datei und -- seit die Werkzeugleiste hier
       eingefuegt wurde -- WEITER UNTEN. Bei gleicher Spezifitaet entscheidet die Reihenfolge, und
       die hat gewonnen: der Einklapper war auch im eingeklappten Zustand 32px breit. Gemessen an
       der Kaskade selbst (welche Regel greift), nicht geraten. Sichtbar war es nicht, weil die
       Gruppe dort 0 breit und gekappt ist -- aber eine CSS, die etwas anderes tut als sie sagt,
       fuehrt den naechsten Leser in die Irre. */
    /* Die Ruhelage liegt RECHTS von der Endlage: der Knopf faehrt also von rechts nach links
       herein und beim Ausblenden nach rechts wieder hinaus. Das ist die Richtung, in die auch
       die Leiste selbst zusammenfaehrt -- vorher lief er ihr entgegen. */
    .up-tbcol.up-tbcol { width: 0; opacity: 0; transform: translateX(6px); pointer-events: none; }
    .up-root.is-tools-open .up-tbcol.up-tbcol { width: 32px; }
    /* Hover: NUR Deckkraft und Verschiebung. Kein Layout, keine Gridspur, kein Ruckeln. */
    /* .up-toolhost statt :has(> .up-toolgroup) -- s. den Kommentar oben. In der zweiten Zeile
       bleibt EIN :has() (fuer :focus-visible gibt es keine Klasse), aber es haengt jetzt an einem
       Kandidaten statt an jedem Nachfahren der Wurzel. Spezifitaet unveraendert (0,5,0). */
    .up-root.is-tools-open .up-toolhost:hover .up-tbcol,
    .up-root.is-tools-open .up-toolhost:has(:focus-visible) .up-tbcol {
      opacity: 1; transform: none; pointer-events: auto;
    }
    /* Haelt ein aktiver Filter die Leiste offen, gibt es keinen Ausgang -- ihn trotzdem
       anzubieten hiesse, den Nutzer auf einen Knopf klicken zu lassen, der nichts tut. */
    .up-root.is-tools-locked .up-tbcol { display: none; }

    /* ── Kit aus (unter cfg.minWidth) ────────────────────────────────────────────────────────
       Auf schmalen Komponenten verhaelt sich die Leiste wie vor dem Kit: alles sichtbar, kein
       Ausloeser, kein Einklapper, keine Bewegung. Ueber eine Klasse und nicht durch Umbauen des
       DOM -- bei jedem Ueberschreiten der Grenze Kinder zu verschieben wuerde Fokus, offene
       Menues und Zeigerzustand mitnehmen.
       Auch die Halteflaeche faellt weg: ohne Ausloeser gibt es nichts zu halten, und die 16px
       Polsterung waeren auf einer engen Leiste nur ein Kasten, der ueber seine Nachbarn reicht. */
    .up-root.is-tools-off .up-tbtrig, .up-root.is-tools-off .up-tbcol { display: none; }
    .up-root.is-tools-off .up-toolgroup { transition: none; }
    .up-root.is-tools-off .up-toolhost { padding: 0; margin-top: 0; margin-bottom: 0; }

    /* ---- DER ZEICHENKNOPF, und seit dem 22.09. mit einer LEISEN FASSUNG ------------------
       Fuenf Komponenten hatten diesen Knopf nachgebaut: .ubo-iconbtn, .vot-iconbtn,
       .tcd-iconbtn, .combo-iconbtn, .upt-kpi-eye -- jeweils dieselben acht Angaben, dazu je
       eine eigene Dunkel- und SVG-Regel. Vier davon trugen eine BEGRUENDUNG im Kommentar,
       warum sie das Bauteil nicht benutzen: sein :hover legt eine Flaeche unter das Zeichen,
       und diese Knoepfe wollen nur die Farbe wechseln. Die Begruendung war richtig -- aber
       sie beschreibt eine VARIANTE, keinen Grund fuer fuenf Kopien. Dieselbe Erklaerung stand
       viermal in vier Dateien.
       Jetzt: die Geometrie steht EINMAL hier, und der Unterschied heisst .is-quiet. Die fuenf
       Klassennamen stehen in den Selektorlisten mit, damit kein Markup in fuenf Komponenten
       angefasst werden muss; neuer Code schreibt .up-iconbtn bzw. .up-iconbtn.is-quiet.
       .ubo-iconbtn war die einzige BYTEGLEICHE Kopie -- inklusive Hover mit Flaeche; sie
       braucht die Variante nicht und haengt an der lauten Fassung.
       (Die Farbe #6f6f6f steht hier weiter als Literal. Sie gehoert in ein Token, aber das
       ist die Farbrunde und nicht diese.) */
    .up-iconbtn,
    :is(.ubo-iconbtn, .vot-iconbtn, .tcd-iconbtn, .combo-iconbtn, .upt-kpi-eye,
      .udt-actbtn, .uut-actbtn, .am-prev-mini, .upr-clear-input,
      .udt-sub-search-x, .upt-topicsearch-clear, .upt-grp-sidesearch-clear,
      .usb-ddcustom-clear, .upt-selcount-clear) {
      display: inline-flex; align-items: center; justify-content: center;
      width: 32px; height: 32px; border: 0; border-radius: var(--up-r-m);
      background: transparent; color: var(--vc-third); cursor: pointer; padding: 0; flex: 0 0 auto; position: relative;
    }
    .up-root[data-theme="dark"] :is(.up-iconbtn,
      .ubo-iconbtn, .vot-iconbtn, .tcd-iconbtn, .combo-iconbtn, .upt-kpi-eye,
      .udt-actbtn, .uut-actbtn, .am-prev-mini, .upr-clear-input,
      .udt-sub-search-x, .upt-topicsearch-clear, .upt-grp-sidesearch-clear,
      .usb-ddcustom-clear, .upt-selcount-clear) { color: var(--vc-third); }
    /* the dark base rule above is more specific than .up-iconbtn:hover, so the hover colour
       needs its own dark rule or it never turns primary in dark mode. */
    .up-root[data-theme="dark"] :is(.up-iconbtn,
      .ubo-iconbtn, .vot-iconbtn, .tcd-iconbtn, .combo-iconbtn, .upt-kpi-eye,
      .udt-actbtn, .uut-actbtn, .am-prev-mini, .upr-clear-input,
      .udt-sub-search-x, .upt-topicsearch-clear, .upt-grp-sidesearch-clear,
      .usb-ddcustom-clear, .upt-selcount-clear):hover { color: var(--vc-text); }
    /* LAUT: Farbe und Flaeche. Der Normalfall -- ein Knopf in einer Werkzeugleiste. */
    .up-iconbtn:not(.is-quiet):hover,
    .ubo-iconbtn:hover { color: var(--vc-text); background: var(--vc-heading-bg); }
    /* LEISE: nur die Farbe. Fuer Knoepfe, die in einer Kopfzeile neben einem Titel sitzen --
       dort zieht eine Hover-Flaeche die Zeile optisch auf. */
    .up-iconbtn.is-quiet:hover,
    /* .udt-actbtn und .uut-actbtn stehen hier NICHT: ihr eigener :hover setzt weiterhin eine
       Flaeche (var(--vc-heading-bg)), sie gehoeren also zur lauten Fassung. Beim ersten Umbau
       waren sie faelschlich in dieser Liste -- wirkungslos, weil die oertliche Regel spaeter
       kommt, aber irrefuehrend. */
    :is(.vot-iconbtn, .tcd-iconbtn, .combo-iconbtn, .upt-kpi-eye,
        .am-prev-mini, .upr-clear-input,
        .udt-sub-search-x, .upt-topicsearch-clear, .upt-grp-sidesearch-clear,
        .usb-ddcustom-clear, .upt-selcount-clear):hover {
      color: var(--vc-text); background: transparent; }

    /* ---- DIE GROESSENLEITER (22.09.) --------------------------------------------------------
       Neun weitere Nachbauten waren kleinere Fassungen desselben Knopfes. Gemessen:
           28/7/15  Zeilenaktion in einer Tabelle      .udt-actbtn .uut-actbtn
           24/6/14  kleine Aktion im Fliesstext        .am-prev-mini .upr-clear-input
           20/6/13  das X in einem Suchfeld            vier Stueck
           18/5/12  das X in einer Plakette            .upt-selcount-clear -- EINE Verwendung
       DREI STUFEN UND NICHT VIER: 28 und 24 stehen schon auf der Hoehenleiter der App
       (--up-h-seg, --up-h-chip). 20 ist neu, aber belegt: VIER unabhaengige Komponenten sind
       auf genau diese Zahl gekommen, das ist eine Rolle und keine Streuung. 18 dagegen hatte
       EINE Verwendung, zwei Pixel neben 20 und in derselben Rolle -- eine Verwendung
       rechtfertigt keine Stufe, also geht sie auf 20.
       DER RADIUS WIRD MITGEZOGEN (§1): 32/8 ist 0.25, und 28/7 sowie 24/6 halten das bereits.
       20 stand auf 6 (0.30) und geht auf 5 -- vier Stellen um einen Pixel.
       DAS ZEICHEN WAECHST RELATIV MIT, und das ist kein Versehen: 16/32 = 0.50, aber
       13/20 = 0.65. Ein kleiner Knopf braucht ein verhaeltnismaessig groesseres Zeichen, sonst
       verschwindet es. Die gemessenen Werte bleiben. */
    .up-iconbtn.is-28, .udt-actbtn, .uut-actbtn { width: 28px; height: 28px; border-radius: var(--up-r-m); }
    :is(.up-iconbtn.is-28, .udt-actbtn, .uut-actbtn) svg { width: 15px; height: 15px; }

    .up-iconbtn.is-24, .am-prev-mini, .upr-clear-input { width: 24px; height: 24px; border-radius: var(--up-r-s); }
    :is(.up-iconbtn.is-24, .am-prev-mini, .upr-clear-input) svg { width: 14px; height: 14px; }

    .up-iconbtn.is-20,
    .udt-sub-search-x, .upt-topicsearch-clear, .upt-grp-sidesearch-clear,
    .usb-ddcustom-clear, .upt-selcount-clear { width: 20px; height: 20px; border-radius: var(--up-r-xs); }
    :is(.up-iconbtn.is-20,
        .udt-sub-search-x, .upt-topicsearch-clear, .upt-grp-sidesearch-clear,
        .usb-ddcustom-clear, .upt-selcount-clear) svg { width: 13px; height: 13px; }
    :is(.up-iconbtn,
      .ubo-iconbtn, .vot-iconbtn, .tcd-iconbtn, .combo-iconbtn, .upt-kpi-eye,
      .udt-actbtn, .uut-actbtn, .am-prev-mini, .upr-clear-input,
      .udt-sub-search-x, .upt-topicsearch-clear, .upt-grp-sidesearch-clear,
      .usb-ddcustom-clear, .upt-selcount-clear) svg {
      width: 16px; height: 16px; display: block; }

    /* ---------- der LEISE Trigger: derselbe Knopf wie .up-iconbtn, nur mit Beschriftung --------
       core unterscheidet weiter unten schon zwei Sorten Trigger: einen BESCHRIFTETEN mit Rahmen
       (.up-filter-btn, .up-ment-btn), der im aktiven Zustand eine Flaeche traegt, und einen
       ICON-Knopf ohne Rahmen, bei dem das Badge sagt, dass etwas gesetzt ist. Was fehlte, ist der
       Dritte: beschriftet UND leise -- ein Einstieg in ein Menue, der selbst kein Zustand ist.
       Der "More Filters"-Knopf der Filterleiste ist der erste Verbraucher.

       Alle Werte sind die von .up-iconbtn: 32px hoch, Radius 8, kein Rahmen, keine Flaeche in
       Ruhe, --vc-heading-bg beim Ueberfahren, Zeichen 16. Nur zwei Zahlen kommen dazu, weil ein
       Text da ist: 8px Polster links und rechts und 7px zwischen Zeichen und Wort.
       position: relative, damit .up-badge (absolut) an seiner Ecke sitzt -- genau wie bei
       .up-iconbtn. */
    .up-quietbtn {
      position: relative; flex: 0 0 auto;
      display: inline-flex; align-items: center; gap: 7px;
      height: 32px; padding: 0 8px; border: 0; border-radius: var(--up-r-m); background: transparent;
      color: var(--vc-text); font-family: inherit; font-size: 13px; font-weight: 500; line-height: 1;
      white-space: nowrap; cursor: pointer; outline: 0;
      transition: background-color 120ms ease, color 120ms ease;
    }
    .up-quietbtn:hover { background: var(--vc-heading-bg); }
    /* Offen: dieselbe Flaeche wie jeder andere offene Trigger. Der Wrapper traegt .is-open, das
       setzt UC.makePopover -- also gilt die Regel fuer jedes kuenftige leise Dropdown mit. */
    .is-open > .up-quietbtn { background: var(--vc-heading-bg); }
    .up-quietbtn:focus-visible { box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); }
    .up-quietbtn svg { width: 16px; height: 16px; flex: 0 0 16px; display: block; }
    .up-quietbtn:disabled { opacity: .45; cursor: default; }
    .up-quietbtn:disabled:hover { background: transparent; }

    /* export: a real filled button, identical to the one in the export popup component */
    .up-export {
      display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 8px;
      border: 0; border-radius: var(--up-r-m); background: var(--up-accent); color: var(--up-accent-fg);
      font: inherit; font-size: 12px; font-weight: 560; line-height: 1;
      white-space: nowrap; cursor: pointer; outline: 0; flex: 0 0 auto;
      transition: opacity .14s ease;
    }
    .up-export:hover { opacity: .9; }
    /* Gesperrt (17.10.: Export auf den Reitern Inactive von Prompts und Brands): gedimmt wie
       .up-btn-pri:disabled, der Hover aendert nichts. aria-disabled gleichwertig mit disabled --
       der gesperrte Export braucht sein mouseover fuer den Tooltip mit dem Grund. Mit :hover (0,3,0)
       ueber .up-export:hover (0,2,0), nicht ueber die Reihenfolge. */
    .up-export:is(:disabled, [aria-disabled="true"]),
    .up-export:is(:disabled, [aria-disabled="true"]):hover { opacity: .4; cursor: default; }
    .up-export svg { width: 16px; height: 16px; flex: 0 0 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

    /* ---------- search (slides out from the magnifier) ---------- */
    .up-search { display: inline-flex; align-items: center; flex: 0 0 auto; position: relative; }
    /* Magnifier stays on the left; when opened, the box grows to its right and, because the tools
       are right-aligned, the whole search shifts left. Seit 15.10. ist die offene Lupe das Praefix
       des Geist-Suchfelds (Block "GEIST-SEARCH-INPUT" weiter unten): 14px, 12px vom Rand, in der
       Farbe des Platzhalters, ohne Flaeche. Die Lage gilt ab (0,3,0); Farbe und Flaeche stehen mit
       .up-root davor auf (0,4,0) -- .up-iconbtn:not(.is-quiet):hover wiegt selbst schon (0,3,0), und
       gleich schwer hiesse: die Reihenfolge entscheidet. */
    .up-search.is-open .up-search-btn { justify-content: flex-start; padding-left: 12px; }
    .up-root .up-search.is-open .up-search-btn { color: var(--vc-third); background: transparent; }
    .up-search.is-open .up-search-btn svg { width: 14px; height: 14px; }
    .up-search-box {
      width: 0; opacity: 0; pointer-events: none; overflow: hidden;
      display: flex; align-items: center; position: relative;
      transition: width .22s cubic-bezier(.22,1,.36,1), opacity .16s ease, margin-left .22s cubic-bezier(.22,1,.36,1);
      margin-left: 0;
    }
    .up-search.is-open .up-search-box { width: 200px; opacity: 1; pointer-events: auto; margin-left: 2px; }
    /* No box/border — just free text like Notion's in-table search. */
    .up-search-input {
      flex: 1 1 auto; min-width: 0; height: 32px; padding: 0;
      border: 0; background: transparent; color: var(--vc-text);
      font: inherit; font-size: 13px; outline: 0;
    }
    .up-search-input::placeholder { color: var(--vc-third); }
    /* Offen: Geist-Schrift 14/400 (bis 15.10. 14/500), Text ab 38px vom Feldrand (32 Lupe + 2 + 4). */
    .up-search.is-open .up-search-input { padding-left: 4px; font-size: var(--up-fs-l); font-weight: var(--up-fw-n); }
    /* The clear button's slot is ALWAYS reserved (visibility, not display), so text never shifts
       when the x appears. */
    .up-search-clear {
      flex: 0 0 24px; width: 24px; height: 24px; border: 0; border-radius: var(--up-r-s); background: transparent;
      color: var(--vc-muted); cursor: pointer; padding: 0; margin-left: 4px;
      display: inline-flex; align-items: center; justify-content: center;
      visibility: hidden;
    }
    .up-search-clear svg { width: 16px; height: 16px; display: block; }
    .up-search-clear:hover { color: var(--vc-text); background: var(--vc-heading-bg); }
    .up-search.has-text .up-search-clear { visibility: visible; }
    /* Mobile: an open search takes over the toolbar row. Below MOBILE_SEARCH_MAX the other
       tools hide so the field spans from the heading to the right edge; they return on close. */
    .up-root.is-searchtakeover .up-head-tools { flex: 1 1 auto; min-width: 0; }
    /* :not(.up-toolgroup) seit der einklappbaren Werkzeuggruppe: die Suche liegt dann eine Ebene
       tiefer, und ohne die Ausnahme blendet diese Regel mit der Gruppe die Suche selbst aus --
       auf einer schmalen Tabelle waere die Suchuebernahme damit eine leere Zeile. */
    .up-root.is-searchtakeover .up-head-tools > :not(.up-search):not(.up-toolgroup) { display: none !important; }
    /* Waehrend der Uebernahme ist die Gruppe offen und ungekappt, zeigt aber nur die Suche. */
    .up-root.is-searchtakeover .up-toolgroup { grid-template-columns: 1fr; opacity: 1; margin-right: 0; }
    .up-root.is-searchtakeover .up-toolgroup-in { overflow: visible; }
    .up-root.is-searchtakeover .up-toolgroup-in > :not(.up-search) { display: none; }
    .up-root.is-searchtakeover .up-search { flex: 1 1 auto; min-width: 0; }
    .up-root.is-searchtakeover .up-search-box { width: auto; flex: 1 1 auto; }
    .up-root.is-searchtakeover .up-heading { display: none; }             /* search owns the whole row */
    .up-root.is-searchtakeover .up-search-clear { visibility: visible; }  /* far-right X closes/resets search */

    /* ---- DAS OFFENE SUCHFELD IST DER GEIST-SEARCH-INPUT (15.10.) ----
       Gewuenscht: "den Search-Input uebernehmen, nur halt mit meinem Blau, sonst aber genau das
       Verhalten". vercel.com/geist/search-input und /input, am 15.10. ausgelesen:
         Feld      1px Kante (Gray Alpha 400, als box-shadow), Radius 6, Grund Background 100;
                   klein 32 hoch -- die Werkzeugleiste ist 32 hoch (mittel waere 36).
         Lupe      INNEN links: 14px, Farbe des Platzhalters (Gray 700), 12px vom Rand; der Text
                   beginnt bei 38px. Schrift 14/400.
         Hover     die Kante eine Stufe kraeftiger (Alpha 400 -> 500).
         Fokus     1px Kante plus 4px weicher Ring (Geist: Alpha 600 und 16 Prozent Schwarz).
         Leeren    hat das Feld Text, steht rechts das Tastenschild "Esc" (20 hoch, 12/500, Radius 4,
                   1px Rand) als Knopf, der leert; die Taste Escape leert ebenso (core.js, gleich
                   nach makeSearch). Auf Touch blendet Geist es aus -- hier bleibt dort das X, denn
                   in der Uebernahme der Zeile (schmal) schliesst es die Suche.
       UNSER BLAU: Fokuskante --up-focus, Ring --up-focus-ring -- dieselben wie an jedem Feld der App.
       DER AUFBAU BLEIBT (Lupe .up-search-btn + Kasten, Aufziehen aus der Lupe): offen legt sich
       der Rahmen um BEIDE, die Lupe wird damit zum Praefix des Feldes. Die Breite bleibt
       32 + 2 + 200 -- damit rechnet die Werkzeugleiste ("200px box + 2px margin", urls-table). */
    /* Radius --up-r-m: auf der Geist-Skala (seit dem Merge der Ecken) die Stufe 6 fuer Knopf und Feld
       32-36px -- genau Geists rounded-md am Input. */
    .up-search.is-open {
      border-radius: var(--up-r-m); background: var(--up-feld-bg);
      box-shadow: 0 0 0 1px var(--vc-border);
      transition: box-shadow var(--up-t-1) ease;
    }
    .up-search.is-open:hover:not(:focus-within) { box-shadow: 0 0 0 1px var(--up-knopf-rand-hover); }
    .up-search.is-open:focus-within { box-shadow: 0 0 0 1px var(--up-focus), 0 0 0 4px var(--up-focus-ring); }
    @media (pointer: fine) {
      .up-search .up-search-clear {
        flex: 0 0 auto; width: auto; height: var(--up-h-btn); margin-left: 0; padding: 0 6px;
        border-radius: 0 var(--up-r-m) var(--up-r-m) 0; color: var(--vc-muted);
      }
      .up-search .up-search-clear svg { display: none; }
      .up-search .up-search-clear::before {
        content: "Esc"; display: inline-flex; align-items: center; justify-content: center;
        height: 20px; min-width: 20px; padding: 0 4px; box-sizing: border-box;
        border-radius: var(--up-r-xs); background: var(--vc-bg); box-shadow: 0 0 0 1px var(--vc-border);
        font-size: var(--up-fs-s); font-weight: var(--up-fw-m); line-height: 1;
      }
      .up-search .up-search-clear:hover { background: transparent; color: var(--vc-text); }
    }

    /* ---------- column info icon + explainer tooltip ---------- */
    
    /* ---- Variations-Abschnitt (aus performance-detail nach core, zweiter Verbraucher
       brand-detail). Kopfzeile, Untertitel und Tabellenrahmen stehen hier; die Spaltenbreiten
       bleiben bei der jeweiligen Komponente, weil die Tabelle dort verschieden breit steht. */
    /* Der Abschnitt polstert sich selbst. Vorher hing die Polsterung an einer Klasse der
       aufrufenden Komponente -- beim Umzug nach core fiel sie weg, und der Abschnitt klebte an
       allen vier Kanten. Steht sie hier, kann das keiner Seite mehr passieren. */
    .up-varsec { padding: 16px; }
    .up-sec-head { display: flex; align-items: center; gap: 12px; min-height: 32px; margin-bottom: 14px; }
    .up-sec-titles { display: flex; flex-direction: column; gap: var(--up-ueb-luft); min-width: 0; flex: 1 1 auto; }
    .up-sec-h { flex: 0 0 auto; }
    /* Der Abschnittstitel: .up-heading mit .up-sec-h (shopping, ads, events, performance sekKopf); (0,2,0) schlaegt den Block-Titel (0,1,0). */
    .up-heading.up-sec-h { font-size: var(--up-ueb-abschnitt-gr); font-weight: var(--up-ueb-abschnitt-gew); letter-spacing: var(--up-ueb-abschnitt-spur); line-height: var(--up-ueb-abschnitt-zeile); }
    .up-sec-sub { font-size: var(--up-ueb-unter-gr); font-weight: var(--up-ueb-unter-gew); line-height: var(--up-ueb-unter-zeile); color: var(--vc-muted); }
    /* 16px wie jeder andere Container mit Rahmen in dieser App (.up-box). Vorher 10 -- ein
       kleinerer Innenradius wirkt nur dort richtig, wo ein groesserer Rahmen direkt aussen
       anliegt; hier liegt Polsterung dazwischen, und dann liest sich die Abweichung als Fehler. */
    /* MIT EIGENER FLAECHE (08.10. gemeldet: "im Event Detail fehlt im Darkmode der Hintergrund in
       den Rows -- ich dachte, das sind core-Tabellen"). Die Tabelle war fuer die Variations gebaut,
       und die standen immer IN einer Karte (Brand Detail, frueher das Performance Detail): sie
       borgte deren Flaeche und hatte nie eine eigene. Das Event-Detail und das neue Performance
       Detail stellen sie offen auf die Seite -- gemessen im Dunkeln: Zeilen durchsichtig, also
       Seitengrund #101114 statt der Kastenflaeche #1c1c1f daneben. Jetzt dieselbe Flaeche wie
       .up-box, und im Dunkeln dieselbe Kante (siehe die zwei Listen weiter unten). */
    .up-vartable { border: 1px solid var(--vc-border); border-radius: var(--up-r-2xl); overflow: hidden; background: var(--vc-bg); }
    /* Mit .up-vartable davor, weil .up-row aus core dieselbe Spezifitaet haette und dann die
       Ladereihenfolge entscheiden wuerde -- STYLEGUIDE: nie ueber die Reihenfolge gewinnen. */
    .up-vartable .up-row.up-vrow { height: 55px; flex: 0 0 55px; }
    .up-vartable .up-thead.up-vrow { height: 40px; flex: 0 0 40px; }
    /* .up-row ist in dieser App ein GRID mit var(--up-cols) -- das Spaltenraster der grossen
       Tabellen. Die Variationstabelle hat kein solches Raster, sie hat drei feste Zellen. Ohne
       diese Zeile erbt sie das Grid der zuletzt definierten Tabelle und schiebt ihre Zellen in
       fremde Spalten. Mit .up-vartable davor, damit die Spezifitaet hoeher liegt als die
       Grid-Regel und nicht die Ladereihenfolge entscheidet. */
    .up-vartable .up-row.up-vrow, .up-vartable .up-thead.up-vrow { display: flex; }
    .up-vartable .up-row { cursor: default; }
    /* Sieben Zeilen ganz, die achte angeschnitten: 7.5 x 55px. Daran sieht man, dass es
       weitergeht -- ein sauberer Schnitt sieht aus wie das Ende der Liste. */
    /* scrollbar-gutter: stable UND das gleiche Polster am Kopf -- beides zusammen, sonst stimmt einer
   der beiden Faelle nicht:
   - Ohne stable reserviert der Koerper die Leiste nur, WENN er scrollt. Eine kurze Liste haette
     dann ein Polster am Kopf, dem im Koerper nichts entspricht.
   - Ohne das Polster am Kopf verliert der Koerper unter Windows rund 15px Inhaltsbreite und der
     Kopf nicht. Genau das war die Meldung aus Performance Detail: die Spaltentitel standen versetzt
     zu den Spalten darunter.
   --up-sbw misst core.js einmal beim Start; auf macOS ist es 0, dort aendert sich also nichts. */
.up-vbody { max-height: 412.5px; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; }
.up-vartable .up-thead.up-vrow { padding-right: var(--up-sbw, 0px); }
    /* Die drei Zellbreiten. Der Name nimmt den Rest, die beiden Zahlenspalten stehen fest --
       sonst schiebt ein langer Variantenname die rechte Spalte aus der Tabelle heraus.
       min-width:0 am Namen, sonst weigert sich die Flex-Box zu schrumpfen und die Zeile laeuft
       ueber die rechte Kante hinaus. */
    .up-var-name { flex: 1 1 auto; min-width: 0;
      font-size: 13px; font-weight: 500; color: var(--vc-text); }
    .up-th-vname { flex: 1 1 auto; min-width: 0; }
    .up-var-sov, .up-th-vsov { flex: 0 0 150px; }
    .up-var-cnt, .up-th-vcnt { flex: 0 0 150px; }
    /* Der Name sitzt in einem eigenen Span: der Truncation-Tooltip misst scrollWidth gegen
       clientWidth, und das geht nur an dem Element, das den Text wirklich abschneidet. */
    .up-varname { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    /* Zahl an der rechten Spaltenkante, der Ring links davor (15.10. spaet, wie Zahl und Trend in
       jeder Zelle: core, "RECHTSBUENDIG, DER TREND LINKS DAVOR"). Vorher stand die Zahl links und
       der Ring rechts aussen. Die 16px zum Zellenrand kommen aus der Polsterung von .up-td. */
    .up-td.up-var-sov { justify-content: flex-end; gap: var(--up-spur-abstand); }
    .up-var-sov .up-num { font-variant-numeric: tabular-nums; }
    .up-varring { flex: 0 0 auto; display: inline-flex; line-height: 0; }
    /* 40 Prozent groesser: 16 x 1.4 = 22.4. Die Groesse steht hier und nicht im SVG -- die
       viewBox bleibt 16x16, Radius und Strichstaerke muessen nicht nachgerechnet werden. */
    .up-varring svg { display: block; width: 22.4px; height: 22.4px; }
    .up-varring-track { stroke: var(--vc-heading-bg); }
    /* DIE FARBE DES BOGENS IST DIE CHARTFARBE (23.10.), dieselbe Regel wie UC.chartInk: im Modus
       "Standard + Blau" das ruhige Blau (--up-chart-ink, nur dort gesetzt), sonst die Akzent-Tinte.
       Vorher --up-accent: der Modus setzt kein data-accent, der Ring stand dort in der Schriftfarbe,
       waehrend jede Linie daneben blau war (Lukas: "bei Standard / Blau ... sollen sie blau sein").
       Tinte statt Flaeche: der Ring liegt auf dem Seitengrund wie jede Linie; im Hellen sind beide
       gleich, im Dunkeln traegt Indigo #7C8AF0 statt #5E6AD2, Azur #3D97F0 statt #0075DE. */
    .up-varring-fill { stroke: var(--up-chart-ink, var(--up-accent-ink)); transition: stroke-dasharray 240ms ease; }
    /* Zwei Klassen gegen gap 10 von .up-td, das weiter unten steht -- mit einer kam die 6 nie an
       (gemessen 10, 15.10.); entschieden ueber die Spezifitaet, nicht die Reihenfolge. */
    .up-td.up-var-cnt { gap: 6px; }
    /* Im Skelett sitzen die Balken dort, wo spaeter die Werte stehen: in den Zahlenspalten
       rechts. Ohne das wandert der Balken nach links und das Skelett kuendigt eine andere
       Tabelle an als die, die kommt. */
    .up-tsk .up-var-sov, .up-tsk .up-var-cnt { justify-content: flex-end; }
    /* Der Bezugswert steht kleiner daneben, nicht als eigene Spalte: er erklaert die Zahl links,
       er ist keine zweite Zahl. */
    .up-var-of { font-size: 12px; font-weight: 400; color: var(--vc-fourth); white-space: nowrap; }

    /* ---- Variantentabelle auf schmalen Breiten ----------------------------------------------
       Die zwei Zahlenspalten stehen fest auf je 150px. Auf einem Telefon ist das der ganze
       Bildschirm: bei 360px Traegerbreite bleiben dem Namen 326 - 300 = 26px. Das ist keine
       Spalte mehr, das ist ein Rest, und der Name ist die Spalte, um die es hier geht.
       Nur is-vnarrow, gemessen an der EIGENEN Box (UC.widthTiers) und nicht am Fenster. NICHT
       zusaetzlich is-narrow: das greift je Komponente ab 560 bis 768px, und dort ist Platz --
       eine Verengung waere Schaden ohne Not.

       Der Ring fliegt raus. Er ist die zweite Darstellung derselben Zahl, die unmittelbar links
       daneben steht: wenn Platz fehlt, geht die Wiederholung und nicht der Wert.
       Der Mention Count faellt ganz weg. Ausdruecklich so gewollt -- lieber eine Zahl weniger als
       ein Name, von dem drei Buchstaben und Punkte zu sehen sind.
       Die 130px waren gemessen, nicht gerundet: "Share of Voice" plus die Erklaer-Raute plus
       12/16 Polsterung und 6 Abstand brauchten 129px. Mit 96px war die SPALTENUEBERSCHRIFT
       abgeschnitten -- dann verschiebt man das Problem nur von der Zeile in den Kopf.
       Seit der Kopfschrift 14/500 und dem Info-Zeichen 15 (15.10. spaet) braucht derselbe Kopf
       gemessen 145.1, also 148. Bei 360px Traegerbreite bleiben dem Namen 178. */
    .up-root.is-vnarrow .up-var-sov .up-varring { display: none; }
    .up-root.is-vnarrow .up-var-sov, .up-root.is-vnarrow .up-th-vsov { flex: 0 0 148px; }
    .up-root.is-vnarrow .up-var-cnt, .up-root.is-vnarrow .up-th-vcnt { display: none; }
    .up-root[data-theme="dark"] .up-varring-track { stroke: var(--vc-border); }
    .up-root[data-theme="dark"] .up-varring-fill { stroke: var(--up-chart-ink, var(--up-accent-ink)); }

    .up-th-info, .up-intent-erklaer {
      width: 14px; height: 14px; flex: 0 0 14px; margin-left: auto;
      color: var(--vc-third); opacity: 0; transition: opacity .14s ease;
    }
    .up-th-info svg, .up-intent-erklaer svg { width: 14px; height: 14px; display: block; }
    /* Im Intent-Menue (14.10.) dasselbe Zeichen, dauerhaft sichtbar wie an "Brand Colors": man
       soll sehen, DASS es eine Erklaerung gibt. Eigene Klasse, siehe intentMenuHtml. */
    .up-intent-erklaer { opacity: 1; cursor: pointer; }
    .up-intent-erklaer:hover { color: var(--vc-text); }
    /* DER KOPF DES INTENT-MENUES GLEICH WEIT VON OBEN UND RECHTS (14.10., gemeldet: "das Info-
       Zeichen hat rechts weniger Abstand als oben"). Vorher sass das 14px-Zeichen direkt an der
       Polsterkante (rechts 8px), oben kamen 4px Kopfpolster und die halbe Zeilenhoehe dazu (13px).
       Jetzt ein 22px-Feld mit dem Zeichen in der Mitte, Kopf ohne Polster oben: das Zeichen steht
       oben und rechts je 8 + 4 = 12px von der Kante, und das Feld traegt den Hover der anderen
       Icon-Knoepfe. 22 = 14 + 2 x 4, Radius 6 aus der Skala. */
    /* Mit .up-pop-head verbunden: dessen Polster (4 0 8) steht weiter unten in der Datei und
       gewaenne bei gleicher Spezifitaet. */
    .up-pop-head.up-intent-kopf { display: flex; align-items: center; gap: 8px; min-height: 22px; padding: 0 0 6px; }
    .up-intent-kopf .up-intent-erklaer { width: 22px; height: 22px; flex: 0 0 22px; display: grid; place-items: center;
      border-radius: var(--up-r-s); }
    .up-intent-kopf .up-intent-erklaer:hover { background: var(--vc-heading-bg); }
    .up-th:hover .up-th-info { opacity: 1; }
    .up-th-info:hover { color: var(--vc-text); }
    /* DIE ERKLAERKARTE -- EIN Bauteil fuer die ganze App (28.09. zusammengefuehrt).
       Sie haengt am <body> oder im Portal ihres Konsumenten (UC.makeExplain cfg.mount), also
       ausserhalb von .up-root. Deshalb stand hier jede Farbe literal, und vier Kopien daneben
       taten dasselbe (domains-table, visibility-chart, quick-actions, create-with-ai). Seit die
       umgekehrten Token (--vc-inverse-bg/-ink) und die Tiefen an :root und [data-theme="dark"]
       stehen, erreichen sie die Karte ueberall: sie traegt ihr Thema selbst als data-theme, und
       die dunkle Fassung kommt aus dem Token statt aus einer zweiten Regel je Farbe. */
    .up-explain {
      position: fixed; z-index: 2147483001; width: 248px; padding: 8px;
      /* border: 0 gegen den UA-Rahmen des Top Layers. UC.makeExplain hebt die Karte per
         showPopover() nach oben, und das UA-Stylesheet gibt jedem [popover] "border: solid" --
         gemessen 3px in Schwarz (CanvasText). Die Karten von makeExplain waren dadurch innen 6px
         schmaler als die der Tabellen (226 statt 232) und trugen im Dunkeln einen schwarzen
         Rahmen um den hellen Grund. core.js setzt margin und inset schon zurueck, den Rahmen
         nicht. Aus demselben Grund steht color hier: sonst gilt CanvasText aus derselben Quelle. */
      border: 0; border-radius: var(--up-r-2xl);
      background: var(--vc-inverse-bg); color: var(--vc-inverse-ink);
      box-shadow: var(--up-e-2);
      /* Ausdruecklich links: die Quick Actions haengen die Karte in ihren Schleier, und was
         Bubble dort an Ausrichtung vorgibt, darf den Fliesstext nicht zentrieren. Stand bisher
         nur in ihrer Kopie. */
      text-align: left;
      opacity: 0; pointer-events: none; transition: opacity .14s ease, transform .14s ease;
      transform: translateY(-4px);
      font-family: Geist, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
      box-sizing: border-box;
    }
    .up-explain * { box-sizing: border-box; }
    .up-explain.is-on { opacity: 1; transform: translateY(0); }
    /* caret, positioned per-instance so it always points at the info icon that opened it */
    .up-explain::before {
      content: ""; position: absolute; top: -6px; left: var(--up-caret, 50%);
      transform: translateX(-50%);
      border-left: 12px solid transparent; border-right: 12px solid transparent;
      border-bottom: 11px solid var(--vc-inverse-bg);
    }
    /* flipped above the header when there is no room below */
    .up-explain.is-flipped::before {
      top: auto; bottom: -10px;
      border-bottom: 0; border-top: 11px solid var(--vc-inverse-bg);
    }
    /* the visual sits in its own light panel on top; explanation below it on the dark ground */

    
    /* dark mode inverts the whole card; the inner panel stays a light surface either way */

    

    
    
    /* Die drei Dunkel-Regeln fuer Grund und Spitze sind entfallen: das dreht jetzt das Token. */
    /* Explainer panel content — the light "visual sample" plate + heading/body text on the dark
       ground. prompts-table, urls-table, visibility-chart und domains-table trugen je eine
       wortgleiche Kopie unter eigenem Praefix; seit dem 28.09. zeigen alle vier hierher. */
    /* Column flex, not a plain block: the rows inside are inline-flex, so in a block container
       they flow side by side instead of stacking -- Share showed its up-row and down-row on one
       line and URL Type put all three chips in a row. domains-table's own .udt-explain-vis always
       had this and looked right; the core copy that urls-table migrated onto had dropped it.
       Values kept identical to that rule so both tables render the same panel. */
    /* Die Platte ist die Karte noch einmal umgekehrt: ihr Grund ist die Tinte der Karte, ihre
       Schrift der Grund der Karte. Im Hellen genau die alten Werte (#ffffff / #1f1f1b); im
       Dunkeln #1f1f1b statt #1c1c1f und #eeedef statt #f0f0f0 -- drei und zwei Stufen, und
       create-with-ai trug fuer seine Probe schon genau diese Schrift. color steht an der Platte,
       damit alles darauf die Schrift erbt, statt sie Zeile fuer Zeile zu setzen. */
    .up-explain-vis {
      background: var(--vc-inverse-ink); color: var(--vc-inverse-bg);
      border-radius: var(--up-r-s); padding: 11px 12px;
      display: flex; flex-direction: column; gap: 7px; align-items: flex-start;
      margin-bottom: 9px; min-height: 46px; justify-content: center;
    }
    .up-explain-h { font-size: 14.5px; font-weight: 600; line-height: 1.2; padding: 0 3px; margin-bottom: 4px; color: var(--vc-inverse-ink); }
    /* 68 Prozent der Tinte, in beiden Themen. Im Dunkeln standen 66 -- eine Mischung statt zweier
       Werte, und die zwei Punkte mehr Deckung heben den Kontrast auf dem hellen Grund leicht an. */
    .up-explain-t { font-size: 13.5px; font-weight: 400; line-height: 1.5; padding: 0 3px; color: color-mix(in srgb, var(--vc-inverse-ink) 68%, transparent); }
    .up-explain-row { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 500; color: var(--vc-inverse-bg); }
    .up-explain-row svg { width: 14px; height: 14px; display: block; }
    /* Die Sentiment-Vorschau (UC.sentHtml) in der Zeile: Zahl in Schrift und Farbe der Zeile, wie
       "18.4%" daneben -- die Karte haengt ausserhalb von .up-root, --vc-text loest dort nicht auf. */
    .up-explain-row .up-sent-val { font-size: inherit; color: inherit; }
    /* Die Trendfarben der Probe aus den Marken (15.10., Geist): die Platte steht im Thema der Seite
       (hell Weiss, dunkel #1f1f1b), und die Karte traegt ihr Thema als data-theme -- --vt-up und
       --vt-down kommen damit vom :root- bzw. dunklen Block. Vorher je zwei feste Werte, im Dunkeln
       eigene Tailwind-Toene (#6ee7b7, #fca5a5), die es nirgends sonst gab. */
    .up-explain-up { color: var(--vt-up); }
    .up-explain-down { color: var(--vt-down); }
    /* DIE CODEPROBE (22.10., UC.explainCodeHtml): ein kurzes Beispiel in HTML, Markdown, JavaScript, robots.txt oder
       HTTP auf der Platte, die Auszeichnung eingefaerbt -- die Farbe IST die Aussage (was das Format in den Text
       schreibt, was ein Skript erst spaeter einsetzt). Vorbild und bis heute einziger Ort war Create with AI
       (.uca-sample, .uca-sy-mark, .uca-sy-tag, .uca-sy-attr, .uca-sy-str in create-with-ai.css); Page Audit braucht
       dieselbe, also steht sie hier. Die alten Namen stehen als zweiter Selektor daneben, weil der Vertrag sie fuehrt;
       Create with AI setzt seit dem Umzug nur noch die neuen.
       Breiter als die 248 des Standards: eine Codezeile bricht nicht um (white-space: pre), und 38 Zeichen in Mono 12
       brauchen 274 px -- 320 minus Polster der Karte (2 x 8) und der Platte (2 x 12) lassen 280. Bei 375 Breite
       bleibt die Karte ganz im Bild (makeExplain klemmt sie auf 8 Abstand), erst unter 336 waere sie zu breit. */
    .up-explain:has(.up-explain-code), .up-explain.uca-explain { width: 320px; }
    /* Oben und unten 10 statt 11: die Zeilenhoehe der Probe bringt eigene Luft mit. stretch, damit der Kasten die
       ganze Breite hat und eine zu lange Zeile in ihm rollt statt die Platte aufzuschieben. */
    .up-explain-vis:has(> .up-explain-code) { padding: 10px 12px; align-items: stretch; }
    /* Mono 12 (--up-fs-s): Create with AI trug 11.5, das liegt zwischen zwei Stufen der Skala. Zeilenhoehe 1.6 haelt
       die Zeile bei 19.2 statt 18.98 -- die Platte waechst um weniger als einen Pixel je Zeile. */
    .up-explain-code, .uca-sample {
      font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
      font-size: var(--up-fs-s); line-height: 1.6; font-weight: 400; white-space: pre; overflow-x: auto;
    }
    .up-explain-code::-webkit-scrollbar, .uca-sample::-webkit-scrollbar { height: 0; }
    /* Hell und dunkel aus denselben Marken: sie loesen an der Karte ([data-theme]) auf, die Platte steht im Thema der
       Seite. Ungefaerbter Text erbt die Schrift der Platte -- dass Plain text bei Create with AI gar nichts einfaerbt,
       IST dort die Aussage. */
    .up-sy-mark, .uca-sy-mark, .uca-sy-tag { color: var(--vc-syntax-auszeichnung); }
    .up-sy-attr, .uca-sy-attr { color: var(--vc-syntax-attribut); }
    .up-sy-str, .uca-sy-str { color: var(--vc-syntax-zeichenkette); }
    .up-sy-num { color: var(--vc-syntax-zahl); }
    .up-sy-comment { color: var(--vc-syntax-kommentar); }
    /* Matches the real .up-stack-item/.up-stack-vis chip exactly (28x28, radius --up-r-m, 4px ring) —
       this sample must look like the actual table cell, not an older circular approximation of it. */
    /* Ohne Rahmen, seit der echte Chip keinen mehr hat -- die Zeile darueber verspricht, dass
       diese Probe aussieht wie die Zelle. */
    /* Der 4px-Ring ist die Farbe der Platte (Tinte der Karte), damit sich die Punkte gegenseitig
       ausschneiden wie in der Zelle -- er folgt der Platte jetzt ueber das Token, auch im Dunkeln. */
    .up-explain-dot { width: 28px; height: 28px; border-radius: var(--up-r-m); background: #eeedef;
      border: 0; box-shadow: 0 0 0 4px var(--vc-inverse-ink); display: inline-flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600; color: #6f6f6f; }
    .up-explain-more { background: #f0f0f0; }
    .up-explain[data-theme="dark"] .up-explain-dot { background: #28282c; border-color: #4a4a4a; color: #b8b8b8; }
    .up-explain[data-theme="dark"] .up-explain-more { background: #2c2c2c; }

    

    

    /* ---------- pagination ---------- */
    .up-foot {
      display: flex; align-items: center; justify-content: space-between; gap: 16px;
      margin-top: 16px; min-height: 32px; flex-wrap: wrap;
    }
    .up-pagesize { display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto; }
    .up-pagesize-lbl { font-size: 12px; font-weight: 400; color: var(--vc-muted); white-space: nowrap; }
    .up-pagesize-seg { display: inline-flex; align-items: center; gap: 0; height: 32px; padding: 0;
      background: var(--vc-switch-rail); border-radius: var(--up-r-m); }
    .up-pagesize-btn { height: 32px; min-width: 34px; padding: 0 9px; border: 1px solid transparent; border-radius: var(--up-r-m);
      background: transparent; color: var(--vc-muted); cursor: pointer;
      font: inherit; font-size: 12px; font-weight: 500; line-height: 1;
      display: inline-flex; align-items: center; justify-content: center; }
    .up-pagesize-btn:hover:not(.is-active) { color: var(--vc-text); }
    .up-pagesize-btn.is-active { background: var(--vc-switch-pill); color: var(--vc-text); border-color: var(--vc-switch-border); }
    .up-root[data-theme="dark"] .up-pagesize-btn.is-active { background: var(--vc-switch-pill); border-color: var(--vc-switch-border); }

    .up-pager { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; margin-left: auto; }
    .up-pager-info { font-size: 12px; font-weight: 400; color: var(--vc-muted); white-space: nowrap; margin-right: 8px; }
    .up-page {
      min-width: 32px; height: 32px; padding: 0 8px; border: 1px solid transparent; border-radius: var(--up-r-m);
      background: transparent; color: var(--vc-muted); cursor: pointer;
      font: inherit; font-size: 12px; font-weight: 500; line-height: 1;
      display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
    }
    /* :hover:not(:disabled) is (0,3,0) and outranks .is-active (0,2,0) — without excluding the
       active page here, hovering the page you are already on lightened it back up. */
    .up-page:hover:not(:disabled):not(.is-active) { background: var(--vc-heading-bg); color: var(--vc-text); }
    .up-page.is-active { background: var(--up-accent); color: var(--up-accent-fg); border-color: var(--up-accent); font-weight: 600; }
    .up-page:disabled { opacity: .35; cursor: default; }
    .up-page svg { width: 15px; height: 15px; display: block; }
    .up-page-gap { min-width: 20px; height: 32px; display: inline-flex; align-items: center; justify-content: center;
      color: var(--vc-third); font-size: 12px; flex: 0 0 auto; }
    .up-root.is-vnarrow .up-pager-info, .up-root.is-vnarrow .up-pagesize-lbl { display: none; }
    /* Wrapped onto two lines (not enough room for both groups side by side) — centre each line
       instead of leaving page size hugging the left edge and page nav the right. See
       syncFootWrap() in UC.makePager, shared by every table using this footer. */
    .up-foot.is-wrapped { justify-content: center; }
    /* Im Leer- und Fehlerzustand keine Pagination (22.10., core.js ohneDatenZeilen in makePager). */
    .up-foot.is-leer { display: none; }
    .up-foot.is-wrapped .up-pagesize { flex: 1 1 100%; justify-content: center; }
    .up-foot.is-wrapped .up-pager { flex: 1 1 100%; justify-content: center; margin-left: 0; }

    /* ---------- filter dropdown (citation types) ---------- */
    .up-filter { position: relative; flex: 0 0 auto; }
    /* a labelled control rather than a bare icon: the active selection has to be readable
       without opening the menu */
    .up-filter-btn {
      display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px;
      border: 1px solid transparent; border-radius: var(--up-r-m); background: transparent;
      color: var(--vc-text); font: inherit; font-size: 12px; font-weight: 500; line-height: 1;
      cursor: pointer; white-space: nowrap; max-width: 220px;
    }
    /* Same hover/open treatment as every other dropdown trigger in the app: background goes to
       --vc-heading-bg, the border never changes (there isn't one to change). */
    .up-filter-btn:hover { background: var(--vc-heading-bg); color: var(--vc-text); }
    .up-filter-btn:hover .up-filter-btn-chev { color: var(--vc-text); }
    .up-filter.is-open .up-filter-btn { background: var(--vc-heading-bg); color: var(--vc-text); }
    .up-filter.is-open .up-filter-btn-chev { color: var(--vc-text); }
    /* An applied filter wears the same face as an open dropdown: background in --vc-heading-bg,
       label in --vc-text. "This control is doing something" and "this control is open" are the
       same visual weight on purpose — both mean "look here". */
    .up-filter.is-active .up-filter-btn { background: var(--vc-heading-bg); color: var(--vc-text); }
    .up-filter.is-active .up-filter-btn-chev { color: var(--vc-text); }
    /* Hovering an ACTIVE trigger swaps the chevron for an x: the shortcut out of the filter,
       without opening the menu to reach Reset. Inactive triggers keep their chevron. */
    .up-filter-btn-clear { display: none; width: 14px; height: 14px; flex: 0 0 14px; color: var(--vc-muted); }
    .up-filter-btn-clear:hover { color: var(--vc-text); }
    .up-filter.is-active .up-filter-btn:hover .up-filter-btn-chev { display: none; }
    .up-filter.is-active .up-filter-btn:hover .up-filter-btn-clear { display: block; }
    .up-filter-btn-lbl { overflow: hidden; text-overflow: ellipsis; }
    .up-filter-btn-chev { width: 14px; height: 14px; flex: 0 0 14px; color: var(--vc-muted); }
    .up-filter-menu {
      position: absolute; top: calc(100% + var(--up-dd-gap)); right: 0; z-index: 60;
      min-width: 280px; max-width: 320px; max-height: 400px;
      background: var(--vc-bg); border: 1px solid var(--vc-border); border-radius: var(--up-dd-radius);
      box-shadow: var(--up-dd-shadow); padding: var(--up-dd-pad);
      display: flex; flex-direction: column;
      opacity: 0; transform: translateY(-4px) scale(0.985); pointer-events: none; transition: opacity 140ms ease, transform 140ms ease;
    }
    .up-filter-menu.is-shown { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
    .up-root[data-theme="dark"] .up-filter-menu { background: var(--up-menu-bg); }
    .up-filter-dim { flex: 0 0 auto; margin: 2px 2px 8px; display: inline-flex; padding: 0; gap: 0;
      background: var(--vc-switch-rail); border-radius: var(--up-r-m); border: 0; height: 32px; width: 100%; }
    .up-filter-dim-btn { height: 32px; padding: 0 12px; border: 1px solid transparent; border-radius: var(--up-r-m); background: transparent;
      color: var(--vc-muted); cursor: pointer; font-family: inherit; font-size: 12px; font-weight: 500; line-height: 1;
      display: inline-flex; align-items: center; justify-content: center; flex: 1 1 50%; }
    .up-filter-dim-btn:hover { color: var(--vc-text); }
    .up-filter-dim-btn.is-active { background: var(--vc-switch-pill); color: var(--vc-text); border-color: var(--vc-switch-border); }
    .up-root[data-theme="dark"] .up-filter-dim-btn.is-active { background: var(--vc-switch-pill); border-color: var(--vc-switch-border); }
    /* Fixed height so the row doesn't grow when the Reset button appears — otherwise the whole
       menu jumps a few pixels the moment you tick the first item. */
    /* KEINE waagerechte Polsterung. Sie stand bei 9px, und die Ueberschrift sass damit 9px
       rechts von allem anderen im Menue: Suchfeld, Zeilen und deren Hoverflaeche beginnen an
       der Polsterkante des Menues (8px). Gemessen im Firmen-Dropdown: Titel 18px, Suchfeld
       und Zeilen 9px. Gemeldet am 22.09. mit Bild. */
    .up-filter-head { flex: 0 0 auto; height: 26px; display: flex; align-items: center; justify-content: space-between; padding: 0; margin-bottom: 6px; }
    /* Werte siehe .up-label weiter unten -- hier stand dieselbe Kombination ein zweites Mal. */
    .up-filter-reset { border: 0; background: transparent; color: var(--vc-muted); font-family: inherit; font-size: 11px; font-weight: 500; cursor: pointer; padding: 2px 4px; border-radius: var(--up-r-s); }
    .up-filter-reset:hover { color: var(--vc-text); }
    /* 4px between rows in every dropdown list. Was 2px here and something different in each
       component -- one number, one place. */
    .up-filter-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; }
    .up-filter-item { display: flex; align-items: center; gap: 10px; padding: 6px 9px; border-radius: var(--up-r-s); cursor: pointer; min-width: 0; }
    .up-filter-item:hover { background: var(--vc-heading-bg); }
    .up-filter-check { width: 16px; height: 16px; border-radius: var(--up-r-xs); border: 1.5px solid var(--vc-border); display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; color: var(--up-accent-fg);
      transition: background-color var(--up-cb-t) var(--up-cb-ease), border-color var(--up-cb-t) var(--up-cb-ease); }
    /* display:block, not inline: an inline SVG sits on the text baseline, which pushes the tick a
       little low inside the 16px box -- the "not quite vertically centred" report. block + the
       flex centring on the parent puts it on the true middle. */
    /* Der Haken wird ein- und ausgeblendet statt per display umgeschaltet -- display kennt
       keinen Zwischenschritt, und genau das war das Springen. Er waechst dabei aus 70%: dieselbe
       Bewegung wie beim Oeffnen der Dropdown-Panels, nur kleiner. */
    .up-filter-check svg { width: 11px; height: 11px; display: block; opacity: 0; transform: scale(.7);
      transition: opacity var(--up-cb-t) var(--up-cb-ease), transform var(--up-cb-t) var(--up-cb-ease); }
    .up-filter-item.is-checked .up-filter-check svg { opacity: 1; transform: scale(1); }
    .up-filter-item.is-checked .up-filter-check { background: var(--up-accent); border-color: var(--up-accent); }
    /* DIE LEERE CHECKBOX ZEIGT DEN HOVER IHRER ZEILE MIT (16.10. angefordert). Die Zeile geht
       beim Ueberfahren auf --vc-heading-bg, und das liegt fast im Ton des Rands (--vc-border):
       gemessen hell Rand 235 auf Grund 242-244, dunkel 34 auf 31-36 -- das leere Kaestchen
       verschwand genau in dem Moment, in dem man auf die Zeile zeigt. Darum geht der Rand dann
       auf --vc-third, wie in der Prompt-Tabelle (.up-row:hover .upt-check, prompts-table.css);
       weich ueber den Uebergang, den .up-filter-check schon traegt.
       Eine Regel fuer jedes Dropdown mit dieser Checkbox. Gewaehlte Zeilen behalten ihren
       Akzentrand; gesperrte (.is-disabled, aria-disabled, ein Knopf mit disabled) haben keinen
       Hover und bekommen auch hier keinen. Tastatur: :focus-visible an Zeilen mit tabindex,
       .is-cursor in Topics, Models und Markets -- dort bleibt der Fokus im Suchfeld, die
       Pfeiltasten setzen die Klasse. */
    .up-filter-item:not(.is-checked):not(.is-disabled):not([aria-disabled="true"]):not(:disabled):is(:hover, :focus-visible, .is-cursor) .up-filter-check { border-color: var(--vc-third); }
    /* Gewaehlte Zeile traegt den App-Selected-Ton -- im Types-Dropdown immer, in einer
       Marken-Liste nur auf Ansage.

       Der Unterschied ist nicht kosmetisch, er haengt am Ausgangszustand: wo nichts vorbelegt
       ist (die Filter der Tabellen starten auf "All Brands"), markiert die gefuellte Zeile die
       wenigen Dinge, die der Nutzer selbst gewaehlt hat -- das ist genau die Auskunft, die man
       dort sucht. Wo dagegen ALLE Marken vorbelegt sind (Visibility-Chart, Heatmap: dort ist
       die Liste die Legende und startet vollstaendig angehakt), waere jede Zeile gefuellt und
       die Markierung sagt nichts mehr, sie macht das Panel nur unruhig.

       Darum eine Klasse, die die Komponente selbst setzt, statt einer Regel, die raet. */
    .up-filter-menu .up-filter-item.is-checked,
    .up-filter-list.is-fill-checked .up-filter-item.is-checked { background: var(--up-sel-bg); }
    /* the dropdown shows the SAME chip the table uses — identical to TopCitations, so a type
       looks the same wherever it appears */
    /* Rand 1px (Farbe inline aus UC.typeStyle) innerhalb von border-box, damit Hoehe und Polster bleiben; Schnitt 500 statt 600, seit Variante D (13.10.) traegt die Farbe den Chip, nicht die Staerke. */
    /* Form, Groesse und Schrift seit 15.10. aus dem Geist-Badge (Regel bei .up-tag). */
    .up-filter-tag { display: inline-flex; align-items: center;
      border: 1px solid transparent; box-sizing: border-box;
      white-space: nowrap; max-width: 100%; overflow: hidden; }
    .up-filter-tag-lbl { overflow: hidden; text-overflow: ellipsis; }
    /* Nur noch die Lage -- Aussehen kommt aus .up-btn-pri weiter unten.
       flex: 0 0 auto ist PFLICHT (25.09. gemeldet: "Anwenden im URL-Type-Reiter nur 16px hoch").
       Das Menue ist eine Flex-Spalte mit max-height 400px, und ein Flex-Kind schrumpft von sich
       aus mit, sobald der Inhalt nicht passt -- bis auf seine Texthoehe. Bei den sieben Citation
       Types passte alles, bei den vierzehn URL Types lief die Liste ueber, und der Knopf wurde
       anteilig mitgestaucht. Schrumpfen soll allein die Liste; sie scrollt. */
    .up-filter-submit { width: 100%; margin-top: 6px; flex: 0 0 auto; }
    /* DERSELBE HOVER WIE .up-btn-pri, und das ist der Punkt (19.09. gemeldet: "im Darkmode kommt
       als Hoverstate der klassische Grund aus dem Standard-Setting -- das muessen einfach Core-
       Buttons sein"). Hier standen zwei feste Grautoene, einer je Thema: sie waren die
       aufgehellte bzw. abgedunkelte Fassung der FRUEHEREN Primaerfarbe und kannten die Wahl
       nicht. Eine Deckkraft kennt sie, weil sie nichts ueber die Farbe annimmt -- und sie ist
       das, was jeder andere Primaerknopf der App im Hover tut. */
    .up-filter-submit:not(:disabled):hover { opacity: .88; }

    /* ---------- mentioned brands filter (multi-select) ---------- */
    .up-ment { position: relative; flex: 0 0 auto; }
    .up-ment-btn {
      display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px;
      border: 1px solid transparent; border-radius: var(--up-r-m); background: transparent;
      color: var(--vc-text); font: inherit; font-size: 12px; font-weight: 500; line-height: 1;
      cursor: pointer; white-space: nowrap; max-width: 200px;
    }
    .up-ment-btn:hover { background: var(--vc-heading-bg); color: var(--vc-text); }
    .up-ment-btn:hover .up-ment-chev { color: var(--vc-text); }
    .up-ment.is-open .up-ment-btn { background: var(--vc-heading-bg); color: var(--vc-text); }
    .up-ment.is-open .up-ment-chev { color: var(--vc-text); }
    .up-ment.is-active .up-ment-btn { background: var(--vc-heading-bg); color: var(--vc-text); }
    .up-ment.is-active .up-ment-chev { color: var(--vc-text); }
    .up-ment-clear { display: none; width: 14px; height: 14px; flex: 0 0 14px; color: var(--vc-muted); }
    .up-ment-clear:hover { color: var(--vc-text); }
    .up-ment.is-active .up-ment-btn:hover .up-ment-chev { display: none; }
    .up-ment.is-active .up-ment-btn:hover .up-ment-clear { display: block; }
    .up-ment-lbl { overflow: hidden; text-overflow: ellipsis; }
    /* Ein Zeichen statt eines Logos in der Zeile (UC.makeAuswahlFilter, Typen der Events): so breit
       wie .up-ment-logo, damit die Namen in derselben Flucht stehen. */
    .up-ment-zeichen { width: 18px; height: 18px; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; color: var(--vc-third); }
    .up-ment-zeichen svg { width: 14px; height: 14px; }
    .up-ment-chev { width: 14px; height: 14px; flex: 0 0 14px; color: var(--vc-muted); }
    .up-ment-menu {
      position: absolute; top: calc(100% + var(--up-dd-gap)); right: 0; z-index: 60;
      min-width: 260px; max-width: 320px; max-height: 520px;
      background: var(--vc-bg); border: 1px solid var(--vc-border); border-radius: var(--up-dd-radius);
      box-shadow: var(--up-dd-shadow); padding: var(--up-dd-pad);
      display: flex; flex-direction: column;
      opacity: 0; transform: translateY(-4px) scale(0.985); pointer-events: none; transition: opacity 140ms ease, transform 140ms ease;
    }
    .up-ment-menu.is-shown { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
    /* Linksbuendig, wenn rechtsbuendig links aus dem Bild liefe (makeAuswahlFilter misst das). */
    .up-ment-menu.is-links { right: auto; left: 0; }
    .up-root[data-theme="dark"] .up-ment-menu { background: var(--up-menu-bg); }
    .up-ment-logo { width: 18px; height: 18px; border-radius: var(--up-r-xs); flex: 0 0 auto; background: var(--vt-logo-bg); padding: 1px; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; }
    .up-ment-logo img { width: 16px; height: 16px; border-radius: calc(var(--up-r-xs) - 1px); object-fit: cover; display: block; }
    .up-ment-name { font-size: 13px; font-weight: 500; color: var(--vc-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
    /* Die Anzahl eines Eintrags im Auswahl-Filter (makeAuswahlFilter, item.zahl): rechts aussen,
       leise, Tabellenziffern -- sie ordnet ein, sie ist nicht der Eintrag. */
    .up-ment-zahl { margin-left: auto; padding-left: 8px; flex: 0 0 auto; font-size: var(--up-fs-s); color: var(--vc-third); font-variant-numeric: tabular-nums; }
    .up-ment-empty { padding: 12px; font-size: 12px; color: var(--vc-muted); text-align: center; }
    .up-portal { display: contents; }   /* carries the theme + CSS vars to the portaled menus */
    /* Brand search inside the dropdown, plus a capped list so ~10.5 rows show and the 11th peeks. */
    .up-ment-search {
      width: 100%; height: var(--up-h-btn); margin: 2px 0 6px; padding: 0 12px; box-sizing: border-box; flex: 0 0 auto;
      border: 1px solid var(--vc-border); border-radius: var(--up-r-m); background: var(--up-feld-bg);
      color: var(--vc-text); font: inherit; font-size: var(--up-fs-l); font-weight: var(--up-fw-n); outline: 0;
    }
    .up-ment-search::placeholder { color: var(--vc-third); }
    .up-ment-search:focus { border-color: var(--up-focus); box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); outline: 0; }
    .up-ment-searchwrap { position: relative; margin: 0 0 8px; flex: 0 0 auto; }
    /* 34 statt 30: das X sitzt 6px vom Rand und ist 20px breit, es endet also bei 26. Mit 30 stand
   der letzte Buchstabe direkt an seiner Kante und wirkte, als liefe der Text darunter. */
    .up-ment-searchwrap .up-ment-search { margin: 0; padding-right: 48px; }
    .up-ment-searchclear {
      position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
      border: 0; cursor: pointer; display: none; align-items: center; justify-content: center;
    }
    .up-ment-search:not(:placeholder-shown) + .up-ment-searchclear { display: inline-flex; }
    .up-ment-list { max-height: 336px; }   /* ~10.5 rows -> the 11th peeks and signals scrolling */
    /* Typ-Filter (Citation/URL type): ALLE Zeilen ohne Scrollen (22.10., Nutzer: "die letzte Row wird leicht abgeschnitten
       ... bei normaler Screen Height immer alle Rows"). Bis hier 244 wie die Topics-Liste (.utf-list). Die sieben Citation-
       Typen brauchen 7 x 36 (Typ-Chip 24 + Polster 12) + 6 x 4 = 276; bei 244 drueckte die Flex-Liste jede Zeile auf ihr
       min-height 34, und der letzten fehlten trotzdem 18px (gemessen 1440x900). Die Liste hat eine feste,
       kurze Menge, also nimmt sie ihre eigene Hoehe; gedeckelt wird nur am Fenster: 100dvh minus dem Rand, den makeSubmenu
       oben und unten laesst (klemmen, 2 x 8), minus allem, was in der Schale ueber und unter der Liste steht (Rahmen 2, Polster
       2 x 8, Kopf 26 + 6, Suchfeld 32 + 8): zusammen 106. Erst wenn das Fenster dafuer zu niedrig ist, scrollt die Liste.
       --up-ff-platz: im Hineingehen (vom Chip geoeffnet, oder schmal) klemmt keine Schale, das Panel haengt unter seinem
       Ausloeser; dort setzt filter-bar.js (listePlatz) den gemessenen Platz bis zum Bildrand. Ohne Messung gilt das Fenster.
       Die Zeilen schrumpfen nicht: scrollt die Liste doch, drueckte Flex sie sonst wieder auf 34 unter den 24 hohen Chip. */
    .up-ff-ucittypes .up-ment-list, .up-ff-uurltypes .up-ment-list, .up-ff-dtypes .up-ment-list {
      max-height: min(calc(100dvh - 106px), var(--up-ff-platz, 100dvh)); overflow-y: auto; }
    .up-ff-ucittypes .up-ment-list > .up-ff-opt, .up-ff-uurltypes .up-ment-list > .up-ff-opt, .up-ff-dtypes .up-ment-list > .up-ff-opt { flex-shrink: 0; }
    .up-ment-noresult { padding: 12px; font-size: 12px; color: var(--vc-muted); text-align: center; }
    /* ---- "Not listed? Add your own" unten im Menue (03.10. nach core) --------------------------
       Die Bauart der Branche in Your Brand (settings-brand, dort noch .usb-ddcustom-*), Wert fuer
       Wert: durch eine Linie abgesetzt, ueber und unter ihr derselbe Abstand (12), darunter Feld und
       Knopf nebeneinander. Das X im Feld ist .up-iconbtn.is-20.is-quiet, der Knopf .up-btn-pri --
       hier steht nur die Lage. Das Event-Popup nimmt es fuer den eigenen Typ. */
    .up-ddcustom { --up-ddc-gap: 12px; flex: 0 0 auto; margin-top: var(--up-ddc-gap); padding-top: var(--up-ddc-gap);
      border-top: 1px solid var(--vc-border); display: flex; flex-direction: column; gap: var(--up-ddc-gap); }
    .up-ddcustom-row { display: flex; align-items: center; gap: 8px; }
    .up-ddcustom-field { position: relative; flex: 1 1 auto; min-width: 0; display: block; }
    .up-ddcustom-in.up-ment-search { width: 100%; min-width: 0; margin: 0; padding-right: 34px; }
    /* Zwei Klassen: .up-iconbtn setzt position und display mit einer -- gewonnen wird ueber die
       Spezifitaet, nicht ueber die Reihenfolge (CLAUDE.md 1). */
    .up-ddcustom-field .up-ddcustom-clear { position: absolute; top: 50%; right: 6px; transform: translateY(-50%); display: none; }
    .up-ddcustom-in:not(:placeholder-shown) ~ .up-ddcustom-clear { display: inline-flex; }

    /* ---------- column visibility (gear) ---------- */
    .up-cols { position: relative; flex: 0 0 auto; }
    /* Touch devices: 16px stops iOS from zooming into the search field on focus, and hover
       tooltips make no sense (a tap leaves them stuck on screen). */
    @media (hover: none) {
      /* 16px prevents the iOS focus-zoom and must beat the 14px active rule above (same
         specificity, later in the file), so it targets the open state too. */
      .up-search.is-open .up-search-input { font-size: 16px; }
      /* Ausser dem per Tippen geoeffneten (.is-tipp, 22.10., makeTooltips: tippenZeigen) -- sonst war die
         Angabe hinter einem Tooltip auf dem Telefon nicht zu erreichen. Jeder andere Weg (Ueberfahren,
         Fokus) bleibt dort unsichtbar, aus dem Grund oben. (0,2,0) gegen die Grundregel (0,1,0), ohne
         !important. */
      .up-tip:not(.is-tipp) { display: none; }
    }
    /* THE dropdown shell. .up-sort-menu / .up-cols-menu / .up-ment-menu were three near-identical
       copies of it; anything new that needs a dropdown uses .up-menu and adds only its own width.
       Do NOT borrow one of the named classes for a different dropdown: each component looks its
       own menu up with root.querySelector(".up-cols-menu") and a second element carrying that
       class silently steals the lookup -- that is exactly how Table Settings ended up rendering
       into the grouping dropdown. */
    .up-menu, .up-cols-menu { position: absolute; top: calc(100% + var(--up-dd-gap)); right: 0; z-index: 60; width: max-content; min-width: 230px; max-width: min(420px, calc(100vw - 32px));
      background: var(--vc-bg); border: 1px solid var(--vc-border); border-radius: var(--up-dd-radius); box-shadow: var(--up-dd-shadow); padding: var(--up-dd-pad);
      opacity: 0; transform: translateY(-4px) scale(0.985); pointer-events: none; transition: opacity 140ms ease, transform 140ms ease; }
    .up-menu.is-shown, .up-cols-menu.is-shown { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
    .up-root[data-theme="dark"] .up-menu, .up-root[data-theme="dark"] .up-cols-menu { background: var(--up-menu-bg); }

    /* ══ Untermenue: ein Dropdown NEBEN einem Dropdown ═══════════════════════════════════════
       Die Bauart kommt aus ask-mira ("Move to Project" im Chat-Menue) und steht jetzt hier, weil
       die Filterleiste genau dasselbe braucht -- eine Zeile im Panel, aus der seitlich ein
       zweites Panel herausfaehrt. Mira behaelt seine eigenen Klassen: es faehrt mit einem eigenen
       Token-Satz (--am-*), und die beiden zu EINER Regel zu machen wuerde dort die Farben
       aendern. Die GEOMETRIE ist das Geteilte, und die steht ab hier einmal.

       Die Zahl, auf die es ankommt: der Versatz ist NICHT der Abstand allein. Die 100% beziehen
       sich auf die ZEILE, und zwischen deren Kante und der Kante des Panels liegen noch dessen
       Polster und Rahmen. Mit blossen 6px sass das Untermenue in Mira genau auf dem Menue --
       gemessen 1px Ueberlappung, also keine Luecke, kein Schatten dazwischen, und ein Rahmen, der
       auf der weissen Nachbarkarte nicht zu sehen war. Genau das war dort "dieses Dropdown hat
       keine border". Darum: Polster + Abstand + 1px Rahmen.
       Hier aus den Tokens gerechnet statt als feste 13, damit es mitgeht, wenn jemand
       --up-dd-pad oder --up-dd-gap aendert.

       top: negativ um das Polster -- so steht die erste Zeile des Untermenues auf einer Linie mit
       der Zeile, aus der es kommt, und nicht um das Polster tiefer. */
    .up-subwrap { position: relative; }
    .up-submenu {
      position: absolute; top: calc(-1 * var(--up-dd-pad));
      /* RECHTS ist die Vorgabe: der Winkel in der Zeile zeigt nach rechts, und dorthin gehoert
         dann auch das Panel. Nach links nur, wenn rechts kein Platz ist (.is-flipleft, die Klasse
         setzt UC.makeSubmenu nach einer Messung). */
      /* 100% ist die Polsterbox des Panels; +1 ueberspringt seinen Rahmen. Der sichtbare
         Abstand ist damit genau --up-sub-gap. Vorher stand hier zusaetzlich --up-dd-pad,
         und der sichtbare Abstand war 14px (gemessen: offsetLeft 261, Panel 246 + 1). */
      /* --up-sub-schub: reicht auf keiner Seite der Platz, schiebt UC.makeSubmenu das
         Untermenue um genau den fehlenden Rest ueber das Panel zurueck (16.10., seiteWaehlen in
         core.js). Sonst 0. */
      left: calc(100% + var(--up-sub-gap) + 1px - var(--up-sub-schub, 0px));
      z-index: 2;
      /* FESTE Breite. Vorher stand hier nur min-width: 220px, und eine absolut positionierte
         Schale ohne Breite nimmt die ihres Inhalts -- ein langer Name (die eigenen Gruppierungen
         der Topics) machte den Kasten breiter, und beim Wechsel der Zeile sprang die Breite.
         Ueber ein Token, damit ein Aufrufer sie setzen kann, ohne die Regel zu ueberschreiben.
         --up-sub-platz: der Platz neben dem Panel, wenn er fuer die volle Breite nicht reicht
         (16.10., UC.makeSubmenu misst ihn an der Inhaltsflaeche) -- dann wird das Untermenue
         schmaler statt unter die Seitenleiste oder ueber #main hinaus. Ohne Messung gilt das
         Fenster wie bisher. */
      width: var(--up-sub-w, 300px); max-width: var(--up-sub-platz, calc(100vw - 24px));
      background: var(--vc-bg); border: 1px solid var(--vc-border);
      border-radius: var(--up-dd-radius); box-shadow: var(--up-dd-shadow); padding: var(--up-dd-pad);
      opacity: 0; transform: translateX(-4px) scale(0.985); pointer-events: none;
      /* top und height: EINE Schale fuer alle Zeilen rutscht beim Wechsel an ihren neuen Platz
         statt aus- und wieder einzublenden (UC.makeSubmenu, cfg.shell). 100ms und nicht 200 --
         der Weg ist eine Zeilenhoehe, und bei 200 lief das Rutschen der Hand hinterher.
         Ohne Schalen-Modus stehen die beiden Uebergaenge still: dort aendert sich weder top
         noch height. */
      transition: opacity 110ms ease, transform 110ms ease,
                  top 100ms ease, height 100ms ease;
      /* Der Inhalt wird beim Wechsel getauscht, waehrend die Hoehe noch laeuft. Ohne das
         schaut er waehrend des Zuges unten heraus. */
      overflow: hidden;
    }
    /* ── Schalen-Modus: EIN Kasten, Huellen darin ────────────────────────────────────────────
       Die Schale haengt am Panel und nicht an einer Zeile, also entscheidet ihre eigene Klasse
       ueber Sichtbarkeit und Seite -- nicht die der Zeile. Beides steht hier zusaetzlich; die
       Regeln fuer den Bau mit einem Kasten je Zeile bleiben unberuehrt. */
    .up-submenu.is-shown { opacity: 1; transform: translateX(0) scale(1); pointer-events: auto; }
    .up-submenu.is-flipleft {
      left: auto; right: calc(100% + var(--up-sub-gap) + 1px);
      transform: translateX(4px) scale(0.985);
    }
    .up-submenu.is-flipleft.is-shown { transform: translateX(0) scale(1); }
    .up-submenu.is-flipleft::before {
      left: auto; right: calc(-1 * (var(--up-sub-gap) + var(--up-dd-pad) + 6px));
    }
    /* Die Huellen: genau eine ist sichtbar. display und nicht opacity -- eine unsichtbare
       Huelle im Fluss wuerde die Hoehe der Schale mitbestimmen, und die Hoehe ist hier die
       Aussage. */
    .up-subhost { display: none; }
    .up-subhost.is-on { display: block; }
    .up-root[data-theme="dark"] .up-submenu { background: var(--up-menu-bg); border-color: var(--vc-switch-border); }
    /* Offen wird ueber eine KLASSE, nicht ueber :hover. Der Unterschied ist nicht kosmetisch: die
       Zeile laesst sich auch anklicken und bleibt dann offen, bis eine andere Zeile oder ein Klick
       nach draussen kommt -- mit :hover waere das nicht ausdrueckbar. Den Zeiger verfolgt
       UC.makeSubmenu und setzt die Klasse; :hover bleibt zusaetzlich drin, damit ein Untermenue
       auch ohne das Kit aufgeht (und damit es bei stehendem Zeiger nicht unter der Maus zugeht). */
    /* NUR die Klasse, kein :hover. Ein :hover-Zweig stand hier als Weg ohne JS -- er kann aber
       gar nicht greifen: geschlossen ist das Untermenue pointer-events: none, also nie gehovert.
       Schlimmer, er lud dazu ein, ihn fuer den Grund zu halten, wenn ein geschlossenes Panel
       trotzdem Zeiger faengt (der wirkliche Grund war ein Kind, das pointer-events wieder
       aufhob -- siehe filter-bar.css). Eine Sichtbarkeit, eine Quelle. */
    .up-subwrap.is-subopen > .up-submenu { opacity: 1; transform: translateX(0) scale(1); pointer-events: auto; }
    /* Die Bruecke fuer die Maus muss die Luecke GANZ ueberdecken, sonst schliesst das Untermenue
       auf dem Weg dorthin. Sie waechst mit den Tokens mit, plus 2px Reserve.
       Nur die HOEHE DER ZEILE, nicht die des Untermenues: mit height:100% lag ein 300px hoher,
       unsichtbarer Streifen ueber den Zeilen darunter -- der Zeiger erreichte sie nicht mehr, und
       auf dem Weg ins Untermenue meldete jede Zeile darunter ihren eigenen Hover. Die Bruecke
       muss nur den Weg von DIESER Zeile zum Panel decken. */
    .up-submenu::before {
      content: ""; position: absolute; top: var(--up-dd-pad); height: 34px;
      /* Zurueck bis zur rechten Kante der ZEILE: Abstand + Rahmen des Panels + dessen Polster,
         plus 6px Reserve. Die Zeile endet an der Inhaltskante des Panels, nicht an seinem Rand. */
      left: calc(-1 * (var(--up-sub-gap) + var(--up-dd-pad) + 6px));
      width: calc(var(--up-sub-gap) + var(--up-dd-pad) + 6px);
    }
    /* Nach links, wenn rechts kein Platz ist. Die Klasse setzt UC.makeSubmenu nach einer Messung. */
    .up-subwrap.is-flipleft > .up-submenu {
      left: auto; right: calc(100% + var(--up-sub-gap) + 1px);
      transform: translateX(4px) scale(0.985);
    }
    .up-subwrap.is-flipleft > .up-submenu::before {
      left: auto; right: calc(-1 * (var(--up-sub-gap) + var(--up-dd-pad) + 6px));
    }
    .up-subwrap.is-flipleft.is-subopen > .up-submenu { transform: translateX(0) scale(1); }

    /* ── Schmal: hinein statt heraus ─────────────────────────────────────────────────────────
       Auf einem Telefon gibt es kein "daneben" -- ein Panel, das seitlich herausfaehrt, landet
       ausserhalb des Bildes. Und es gibt keinen Zeiger, der ueber einer Zeile stehen bleiben
       kann. Darum wird aus dem Nebeneinander ein Hineingehen: das Untermenue nimmt die Stelle der
       Liste ein, darueber steht eine Zurueck-Zeile. Das ist das Muster, das jede Filterleiste auf
       dem Telefon benutzt, und es braucht keine zweite Mechanik -- nur andere Regeln fuer
       dieselben Klassen. Die Stufe setzt UC.makeSubmenu als .is-drill am PANEL, gemessen an der
       Seitenbreite und nicht an der Breite dieses Elements: ob seitlich Platz ist, entscheidet
       das Fenster. */
    .is-drill .up-submenu {
      position: static; min-width: 0;
      border: 0; border-radius: 0; box-shadow: none; padding: 0;
      background: transparent;
      transform: none; transition: opacity 110ms ease;
      overflow: visible;
    }
    /* Doppelte Klasse: hier muessen die INLINE-Werte fallen, die der Schalen-Modus fuer den
       Zug setzt (top und height in Pixeln). Ein !important waere der andere Weg und ist
       verboten; eine zweite Klasse reicht, weil Inline nur von !important geschlagen wird --
       darum setzt UC.makeSubmenu die beiden im Drill-Modus selbst zurueck, und diese Regel ist
       das Netz fuer den Fall, dass die Stufe zwischen zwei Messungen wechselt. */
    .is-drill .up-submenu.up-submenu {
      top: auto; right: auto; left: auto; width: 100%; max-width: none; height: auto;
    }
    .is-drill .up-submenu::before { content: none; }
    /* Zu heisst hier WEG, nicht nur durchsichtig: ein Panel im Fluss belegt sonst seine Hoehe. */
    .is-drill .up-subwrap:not(.is-subopen) > .up-submenu { display: none; }
    .is-drill .up-subwrap.is-subopen > .up-submenu { opacity: 1; pointer-events: auto; }
    /* Dasselbe fuer die Schale: zu heisst WEG, nicht bloss durchsichtig -- ein Kasten im Fluss
       belegt sonst seine Hoehe unter den Zeilen. */
    .is-drill .up-submenu:not(.is-shown) { display: none; }
    /* Und die Zeilen der ersten Ebene treten zurueck, solange man drin ist. */
    .is-drill.is-inside .up-subrow { display: none; }
    .is-drill.is-inside .up-subwrap:not(.is-subopen) { display: none; }
    /* Die Zurueck-Zeile gibt es nur drinnen. Sie ist eine .up-optrow wie jede andere Zeile -- und
       genau daran haengt der Grund fuer die doppelte Klasse: .up-optrow setzt display: flex, hat
       dieselbe Spezifitaet und steht in dieser Datei WEITER UNTEN. Bei Gleichstand entscheidet die
       Reihenfolge, und die hat gewonnen: die Zeile stand auch auf einer breiten Seite da, wo es
       kein Drinnen gibt (im Bild als "Back" ueber den drei Filtern zu sehen). Ueber die
       Spezifitaet gewonnen, nicht ueber die Reihenfolge -- STYLEGUIDE. */
    .up-subback.up-subback { display: none; }
    .is-drill.is-inside .up-subback.up-subback { display: flex; }
    .up-subback svg { width: 16px; height: 16px; flex: 0 0 16px; display: block; }

    /* ---------------- shared selected row (sorters and any option list) ----------------
       Put .up-optrow on a row that can be picked and .is-on when it is. Hover and selected are
       the SAME tone on purpose: a row you are pointing at and a row that is chosen are both
       "this one", and two different greys next to each other read as two different meanings.
       Deliberately NOT used for selected brands / types / table settings / anything with a
       switch -- those are multi-select lists where a filled row per entry is too busy. */
    .up-optrow {
      display: flex; align-items: center; gap: 8px; width: 100%;
      border: 0; border-radius: var(--up-r-s); background: transparent; color: var(--vc-text);
      font-family: inherit; text-align: left; cursor: pointer;
    }
    .up-optrow:hover, .up-optrow.is-on { background: var(--up-sel-bg); }
    .up-optrow + .up-optrow { margin-top: 4px; }
    /* 16x16 check, hard right -- the mark the component sorters already use. */
    .up-optrow-check { margin-left: auto; width: 16px; height: 16px; flex: 0 0 16px; display: grid; place-items: center; color: var(--vc-text); opacity: 0; }
    .up-optrow.is-on .up-optrow-check { opacity: 1; }
    .up-optrow-check svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
    /* DAS AKTIONSMENUE (03.10. nach core, Werte 1:1 aus team-orga .uto-menu): Handlungen wie
       "Edit" und "Delete" am Dreipunkt-Knopf einer Zeile oder Karte. Zeilen .up-optrow, 34 hoch,
       Schrift 13/500 -- .up-optrow setzt selbst keine Schrift und erbte sonst die der Seite (im
       Events-Menue gemessen: 16px). Die wegnehmende Zeile steht hinter einem Trennstrich, leise,
       und wird erst am Zeiger rot -- dauerhaft rot liest sich wie ein Fehler. */
    .up-aktionsmenue .up-optrow { min-height: 34px; padding: 0 9px; font-size: var(--up-fs-m); font-weight: var(--up-fw-m); }
    .up-aktionsmenue .up-optrow svg { width: 15px; height: 15px; flex: 0 0 15px; }
    .up-aktionsmenue-lbl { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .up-aktionsmenue-div { height: 1px; background: var(--vc-border); margin: 6px 4px; }
    .up-aktionsmenue .up-optrow.is-gefahr { color: var(--vc-muted); }
    .up-aktionsmenue .up-optrow.is-gefahr:hover { color: var(--vt-down); }
    /* icon buttons have no border, so their open state is the background */
    /* ---------- Icon-Trigger: Hintergrund NUR im offenen Zustand ----------
       Eine Regel fuer die ganze App statt einer pro Komponente. Jeder Popover-Wrapper bekommt
       .is-open von UC.makePopover, jeder Icon-Trigger ist ein .up-iconbtn -- damit greift das
       automatisch fuer jedes kuenftige Icon-Dropdown, ohne dass jemand daran denken muss.

       Und die Unterscheidung, die dabei immer wieder verrutscht: "aktiv" ist NICHT "offen".
       Ein BESCHRIFTETER Trigger (.up-filter-btn, .up-ment-btn: Text plus Chevron) traegt im
       aktiven Zustand einen Hintergrund, weil man ihm sonst nicht ansieht, dass ein Filter
       laeuft. Ein ICON-Button tut das nicht -- dort sagt das Badge, dass etwas gesetzt ist, und
       ein dauerhaft gefuellter Button liest sich als "dieses Menue steht offen". */
    .is-open > .up-iconbtn { color: var(--vc-text); background: var(--vc-heading-bg); }
    .up-cols.is-open .up-cols-btn { color: var(--vc-text); background: var(--vc-heading-bg); }
    /* a column that is the last one left can't be switched off — an empty table helps nobody */
    .up-pop-row.is-locked { opacity: .45; pointer-events: none; }


    /* ---------- Filter-Trigger, der Knopf VOR jedem Dropdown ----------
       Markets, Models, Topics und der Kalender haben diesen Knopf bisher jeder in seiner eigenen
       Datei stehen -- vier byte-identische Kopien. Beim fuenften Verbraucher (Settings) kommt er
       nach core, statt ein fuenftes Mal abgeschrieben zu werden (STYLEGUIDE 25).

       Die Form ist: [Icon] [Beschriftung] [Chevron], zusammengepackt und nur so breit wie sein
       Inhalt. NICHT ein breiter Kasten mit dem Chevron am rechten Rand -- das ist ein
       Formular-Select und sieht in dieser App an keiner Stelle so aus.
       Rahmen sichtbar, Grundfarbe --vc-muted, im Hover und offen die Flaeche und die eine Stufe
       kraeftigere Kante des Geist-Menue-Ausloesers (--up-knopf-*, 15.10.; vorher --vc-hover-border).
       Gewaehlt (has-sel) behaelt seine dunkle Kante -- das ist ein Zustand, kein Hover. Der Chevron
       dreht sich nicht. */
    .up-ddtrigger {
      display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px;
      border: 1px solid var(--vc-border); border-radius: var(--up-r-m); background: var(--vc-bg);
      color: var(--vc-muted); font-family: inherit; font-size: 13px; font-weight: 500;
      line-height: 1; white-space: nowrap; cursor: pointer; outline: 0;
      transition: color 120ms ease, background-color 120ms ease, border-color 120ms ease;
    }
    .up-ddtrigger:hover,
    .up-ddwrap.is-open .up-ddtrigger {
      color: var(--vc-text); background: var(--up-knopf-hover); border-color: var(--up-knopf-rand-hover);
    }
    .up-ddwrap.has-sel .up-ddtrigger {
      color: var(--vc-text); background: var(--vc-heading-bg); border-color: var(--vc-hover-border);
    }
    .up-ddtrigger:focus-visible { outline: var(--up-focus-w) solid var(--up-focus); outline-offset: var(--up-knopf-fokus-abstand); }
    .up-ddtrigger svg { width: 16px; height: 16px; flex: 0 0 16px; display: block; }
    .up-ddtrigger-ic { display: inline-flex; align-items: center; flex: 0 0 auto; }
    .up-ddtrigger-ic img { width: 20px; height: 14px; border-radius: 3px; object-fit: cover; display: block; }
    .up-ddtrigger-lbl { overflow: hidden; text-overflow: ellipsis; max-width: 22ch; }
    /* margin-left:-2px wie in den vier Filtern: der Chevron hat als Glyph links Luft in sich, ohne
       die Korrektur sieht der Abstand zur Beschriftung groesser aus als der zum Icon. */
    .up-ddtrigger-chev { margin-left: -2px; flex: 0 0 16px; }
    .up-ddwrap { position: relative; display: inline-flex; flex: 0 0 auto; }

    /* ---------- search field INSIDE a dropdown ----------
       Ein anderes Tier als .up-search im Tabellen-Header: das dort faehrt aus einem Icon auf und
       lebt in einer Toolbar; dieses steht dauerhaft offen im Panel. Deshalb ein eigener Typ statt
       eines Modifiers -- drei Komponenten hatten je eine eigene Kopie davon.
       Die Lupe sitzt INNEN RECHTS und wird zum Clear-X sobald Text drin ist, statt dass ein
       zweites Bedienelement daneben auftaucht. */
    .up-ddsearch { position: relative; flex: 1 1 auto; min-width: 0; }
    .up-ddsearch-in {
      width: 100%; height: var(--up-h-btn); box-sizing: border-box; padding: 0 48px 0 12px;
      border: 1px solid var(--vc-border); border-radius: var(--up-r-m);
      background: var(--up-feld-bg); color: var(--vc-text);
      font-family: inherit; font-size: var(--up-fs-l); font-weight: var(--up-fw-n); outline: none;
      transition: border-color 150ms ease, background 150ms ease;
    }
    .up-ddsearch-in::placeholder { color: var(--vc-third); }
    /* Hover kommt aus core (Hover der Eingabefelder, 13.10.). */
    .up-ddsearch-in:focus { border-color: var(--up-focus); box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); outline: 0; }
    .up-ddsearch-ic {
      position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
      width: 15px; height: 15px; color: var(--vc-third); pointer-events: none;
    }
    .up-ddsearch-ic svg { width: 15px; height: 15px; display: block; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
    .up-ddsearch-x {
      position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
      display: none; place-items: center; width: 20px; height: 20px; padding: 0;
      border: 0; border-radius: var(--up-r-xs); background: transparent; color: var(--vc-third); cursor: pointer;
    }
    .up-ddsearch-x svg { width: 11px; height: 11px; display: block; fill: none; stroke: currentColor; stroke-width: 3.5; stroke-linecap: round; stroke-linejoin: round; }
    .up-ddsearch-x:hover { background: var(--vc-heading-bg); color: var(--vc-text); }
    .up-ddsearch.has-text .up-ddsearch-ic { display: none; }
    .up-ddsearch.has-text .up-ddsearch-x { display: grid; }

    /* ---- DAS SUCHFELD UND SEIN ESC-SCHILD (20.10.) ----
       Jedes Suchfeld der App (Menues, Popover, Filter, Listen) ist 32 hoch, 14/400, Radius 6, Polster 12 (Geist Small; die Toolbar ist 32, ihre Knoepfe
       auch). Rechts steht, sobald Inhalt drin steht, das Tastenschild "Esc" -- DASSELBE Schild wie in der Toolbar-Suche (.up-search-clear), das X gibt es
       nicht mehr. Es leert; die Taste Escape tut dasselbe (core.js, __upSucheEscLeert). 20 hoch in einem 32 hohen Feld: oben, unten und rechts je 6.
       Das Feld haelt rechts 48 frei (6 + Schild 30 + 12 Luft), damit kein Text darunter laeuft. Nur Suchfelder; Formularfelder tragen das Schild nicht. */
    .up-ment-searchclear, .up-ddsearch-x, .udt-sub-search-x, .upt-topicsearch-clear, .upt-grp-sidesearch-clear, .upt-gm-clear, .up-cgm-clear {
      width: auto; min-width: 20px; height: 20px; padding: 0 4px; box-sizing: border-box;
      border-radius: var(--up-r-xs); background: var(--vc-bg); box-shadow: 0 0 0 1px var(--vc-border);
      color: var(--vc-muted); font-size: var(--up-fs-s); font-weight: var(--up-fw-m); line-height: 1;
    }
    .up-ment-searchclear svg, .up-ddsearch-x svg, .udt-sub-search-x svg, .upt-topicsearch-clear svg, .upt-grp-sidesearch-clear svg, .upt-gm-clear svg, .up-cgm-clear svg { display: none; }
    .up-ment-searchclear::before, .up-ddsearch-x::before, .udt-sub-search-x::before, .upt-topicsearch-clear::before, .upt-grp-sidesearch-clear::before, .upt-gm-clear::before, .up-cgm-clear::before { content: "Esc"; }
    .up-ment-searchclear:hover, .up-ddsearch-x:hover, .udt-sub-search-x:hover, .upt-topicsearch-clear:hover, .upt-grp-sidesearch-clear:hover, .upt-gm-clear:hover, .up-cgm-clear:hover { background: var(--vc-bg); color: var(--vc-text); }

    /* ---------- topic chip ----------
       ONE topic chip for the whole app. It lived only in prompts-table.css, so the first other
       place that needed one (the grouping popup, in the SAME component) got a hand-made
       look-alike with different colours, a different check and a different height. Any surface
       that shows a topic uses this: emoji + label on the topic's own colour, the checkbox sliding
       in from the right when selected. Colour comes in through --ust-tag-color. */
    /* No resting border -- a plain tinted chip, background colour alone carries the topic's
       identity. border stays declared at 1px solid transparent (not dropped entirely) purely so
       .is-on below can still show a real border colour as its selected-state indicator without
       the chip shifting size by 2px the moment it's picked. */
    /* EIN Topic-Chip fuer die ganze App, und ohne Rahmen -- angefordert am 08.09.: "Topics muessen
       ueberall exakt gleich gestylt sein. 28px Hoehe, nur der Hintergrund gefaerbt, keine Border."
       Der Rahmen war 1px solid transparent und trug die is-on-Marke. Genau er war auch die
       Ursache des Windows-Befundes: bei 125-Prozent-Skalierung wird die halbtransparente Fuellung
       an der Grenze zwischen Rahmen- und Polsterkasten zweimal gerundet, und die Naht wird
       sichtbar -- auf einem Mac mit ganzzahliger Skalierung faellt das nie auf. Die Variante
       .is-static hatte ihn deshalb schon nicht mehr; jetzt hat ihn keiner mehr, und damit sind
       Anzeige und Schalter buchstabengleich.
       Die gewaehlte Marke traegt der Haken (.up-topicchip-check), der ohnehin schon einfaehrt --
       eine zweite Marke daneben war nie noetig. */
    /* ---- DER CHIP "DOT" (06.10., Topic-Handoff 2a/2b, Werte final) -------------------------
       Neues Gesicht fuer denselben Chip: Klassen, Aufbau und --ust-tag-color bleiben, damit jeder
       Verbraucher es ohne Aenderung bekommt. Statt der Toenung in Themenfarbe jetzt ein neutraler
       Chip mit Rand, die Farbe traegt ein 6px-Punkt vor dem Namen. 24px statt 28, Radius 6 statt 8,
       Schrift in --vc-text statt in der Themenfarbe. Das Emoji (2b) steht zwischen Punkt und Name,
       wenn eins gesetzt ist.
       Der Rand ist zurueck, den der 08.09. wegen der Naht bei 125-Prozent-Skalierung (Windows)
       entfernt hatte: die Naht kam von einer HALBTRANSPARENTEN Fuellung, die an der Grenze Rahmen-
       zu Polsterkasten zweimal gerundet wurde. Ohne Fuellung (transparent) gibt es nichts, was
       doppelt gerundet werden koennte -- an Windows bei 125 Prozent trotzdem einmal ansehen
       (im Handoff als "nicht gemessen" gefuehrt). */
    .up-topicchip { height: 24px; padding: 0 8px; border-radius: var(--up-r-s); display: inline-flex; align-items: center; gap: 6px;
      cursor: pointer; font-family: inherit; box-sizing: border-box;
      border: 1px solid var(--vc-border); background: transparent;
      color: var(--vc-text); font-size: 12px; font-weight: 500; line-height: 1;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
      transition: background-color var(--up-t-1) ease, border-color var(--up-t-1) ease; }
    /* Der Punkt als ::before -- so kommt er ohne neues Markup in jeden Chip der App. order -1:
       vor Emoji und Name; das Kaestchen (-2) steht noch davor. Im Dunkeln 72 Prozent Themenfarbe
       mit Weiss, sonst verschwinden die dunklen Topic-Farben auf dem dunklen Grund. */
    /* KEIN PUNKT MEHR, EIN STRICH (06.10. angefordert: "so aehnlich wie Sentiment, aber dass man es
       als eigenes erkennt"). Zweite Fassung am selben Abend: 1px breiter, 2px hoeher, 1px runder --
       4x10, Radius 2. Sentiment ist 3x12 und voll gerundet (.up-sent-dot); die Marke der Topic ist
       breiter, kuerzer und eckiger -- dieselbe Familie, auf einen Blick zu unterscheiden. Radius 2
       steht bewusst neben der Skala: es ist die Angabe (1px mehr als die 1 davor). --up-r-xs (4)
       ergaebe auf 4px Breite zwar dasselbe Bild -- der Browser kuerzt jeden Radius auf die halbe
       Seite --, stuende aber fuer eine Rundung, die nicht gemeint ist, und kippte bei jeder
       anderen Breite in eine volle Kappe. */
    .up-topicchip::before, .up-feldchip-av.is-strich::before { content: ""; order: -1; width: 4px; height: 10px; flex: 0 0 4px;
      border-radius: 2px; background: var(--ust-tag-color, var(--vc-fourth)); }
    [data-theme="dark"] .up-topicchip::before, [data-theme="dark"] .up-feldchip-av.is-strich::before { background: color-mix(in srgb, var(--ust-tag-color, var(--vc-fourth)) 72%, white); }
    .up-topicchip-e { font-size: 12px; line-height: 1; }
    /* 2b oder 2a ist eine Produktentscheidung (Handoff): mit UC.topicEmoji(false) traegt kein Chip
       der App ein Emoji, ohne dass ein Verbraucher etwas aendert. Vorgabe: mit (2b). */
    html.up-topic-ohne-emoji .up-topicchip-e { display: none; }
    /* Die Zahl im Chip (Topics Manager): leise, Tabellenziffern, 2px vom Namen abgesetzt. */
    .up-topicchip-zahl { font-size: 11px; font-weight: 600; color: var(--vc-fourth); font-variant-numeric: tabular-nums; padding-left: 2px; }
    /* Die Zustaende, nur an Schaltern: Ueberfahren hebt den Grund auf --vc-heading-bg und den
       Rand eine Stufe an; gewaehlt (is-on) ist der Grund eine Stufe tiefer, gewaehlt + Ueberfahren
       noch eine. Die Farbe bleibt --vc-text -- .up-chiphover faerbt sonst in die Akzent-Tinte.
       Die Zustaende stehen ueber :not(.is-static) und wiegen damit (0,3,0): mehr als
       .up-chiphover:hover (0,2,0), das an denselben Chips haengt. */
    .up-topicchip:not(.is-static):hover { background: var(--vc-heading-bg); border-color: var(--up-chip-rand-hover); color: var(--vc-text); }
    .up-topicchip.is-on { background: var(--up-chip-an); border-color: var(--up-chip-rand-hover); }
    .up-topicchip.is-on:not(.is-static):hover { background: var(--up-chip-an-hover); border-color: var(--up-chip-rand-an-hover); }
    /* Mit Kaestchen links 6 statt 8 -- das Kaestchen steht vor dem Punkt. */
    .up-topicchip:has(.up-topicchip-check) { padding-left: 6px; }
    /* "+2" in einer Tabellenzelle: derselbe Chip ohne Punkt, enger und leiser (Handoff). */
    .up-topicchip.is-mehr { padding: 0 6px; font-size: 11px; font-weight: 600; color: var(--vc-third); }
    .up-topicchip.is-mehr::before { display: none; }
    /* Eine geloeschte Topic (Gruppierungen im Topics Manager, Events): grauer Punkt, leiser Name. */
    .up-topicchip.is-weg { color: var(--vc-muted); }
    .up-topicchip.is-weg::before { background: var(--vc-fourth); }
    /* Ein Skelett-Chip (.up-tsk, Topics Manager) hat noch keine Topic -- also auch keinen Punkt. */
    .up-topicchip.up-tsk::before { display: none; }
    /* Nicht anklickbare Variante: im Prompts-Popover sind die Chips Schalter, im Radar, im
       Detailbereich und in einer Tabellenzelle sind sie reine Anzeige. Die Regel stand bisher nur
       in prompts-table.css -- der Radar gab die Klasse also schon aus und bekam sie nur dann, wenn
       zufaellig auch die Prompts-Table auf der Seite lag. Dritter Verbraucher, also hierher. */
    /* Randlos, und zwar 0 statt transparent: ein transparenter Rand bleibt ein Rand, den
       jede Hover- oder Themenregel wieder faerben kann -- und bei 125-Prozent-Skalierung
       (Windows) wird die halbtransparente Fuellung an der Grenze Rahmenkasten/Polsterkasten
       zweimal gerundet und die Naht sichtbar. Polster um denselben 1px nach, damit der Text
       an derselben Stelle steht und die 28px Hoehe unangetastet bleiben. Nur an der reinen
       Anzeige: die Schalter im Popover brauchen den Rahmen als is-on-Marke. */
    /* Bleibt als eigene Klasse -- sie sagt "nicht anklickbar" und nichts sonst mehr: Rahmen und
       Polster sind jetzt oben schon so. */
    .up-topicchip.is-static { cursor: default; }
    /* Die grosse Fassung (06.10. angefordert): 28 statt 24 im Topics Manager und ueberall, wo man
       Topics AUSWAEHLT (Gruppierungs-Popup, Topic-Popover der Prompts-Tabelle, Edit Topics). Dort
       ist der Chip ein Schalter mit Kaestchen und wird angeklickt, in Tabellenzellen nur gelesen.
       Die Schrift bleibt 12 -- genau so stand der Chip bis zum 06.10. auf 28px (Radius 8, Schrift
       12). Hoehe, Polster und Radius ziehen zusammen um eine Stufe an: Polster je Seite +2 wie die
       Hoehe, Radius von --up-r-s auf --up-r-m. (0,3,0) mit Kaestchen, damit die 8 links die 6 aus
       .up-topicchip:has(...) und die 10 aus .is-gross sicher schlaegt. */
    .up-topicchip.is-gross { height: var(--up-h-seg); padding: 0 10px; border-radius: var(--up-r-m); }
    .up-topicchip.is-gross:has(.up-topicchip-check) { padding-left: 8px; }

    /* KEIN RIESIGES ZEICHEN, BEVOR DIE KOMPONENTEN-CSS DA IST (19.09. gemeldet: "irgendwo in
       Mira flasht beim Laden noch ein altes SVG auf, ganz gross auf der Seite").
       Ein <svg> ohne width/height und ohne eigene Klasse faellt auf die Ersatzgroesse des
       Browsers zurueck -- 300x150 -- solange keine Regel dafuer da ist. core.css laedt zuerst,
       ask-mira.css danach: genau dieses Fenster war zu sehen. Die Regel greift NUR bei
       Zeichen ohne Groessenangabe UND ohne Klasse, laesst also jedes gestylte Bild (Skelette,
       Charts, alle Icons aus UC.icon, die ihre Groesse als Attribut tragen) unberuehrt. */
    #ask-mira svg:not([width]):not([height]):not([class]) { width: 16px; height: 16px; }

    /* ---------- Heat-Rampe ----------
       Fuenf Stufen, in JS interpoliert (UC.heatAt). Sie stehen in CSS statt in einem JS-Blob,
       damit Light und Dark EINE Palette aus der lebenden Kaskade sind statt zweier Listen, die
       auseinanderlaufen. Seit der Detailbereich die Farbe der 65-Prozent-Zelle fuer seine
       Kurve braucht, gibt es zwei Verbraucher -- und zwei Kopien einer Palette sind genau die
       Stelle, an der die eine irgendwann eine andere Blaustufe hat als die andere. Auf .up-root,
       damit jede Komponente sie erbt. */
    /* ---- DIE SCHRIFTSTUFEN GELTEN IM GANZEN DOKUMENT (24.09.) --------------------------------
     Sie standen nur auf .up-root -- und das reicht nicht: die Quick-Actions-Oberflaeche haengt
     am body (sie muss, .mqa-modal wuerde sonst beschnitten), ebenso die koerpermontierten
     Menues. Dort loesten var(--vc-*) ins Leere auf, sobald eine Komponente darauf zeigte statt
     eigene Werte zu fuehren -- gemeldet als "die Lupe fehlt und der Platzhalter ist zu stark":
     ohne Farbe faellt stroke auf keinen Wert und color auf die des Dokuments zurueck.
     Hier stehen sie deshalb noch einmal fuer alles ausserhalb einer Wurzel. .up-root und
     .up-root[data-theme="dark"] sind spezifischer und gewinnen weiterhin, diese Zeilen aendern
     also nichts an dem, was schon funktioniert. */
  /* --up-sel-bg seit dem 03.10. auch hier: die leise Marke (.up-marke.is-leise) steht als Beispiel
     in einer Erklaerkarte, und die haengt am <body> -- ohne den Token hatte sie keinen Grund
     (gemessen: rgba(0,0,0,0)). Dieselben Werte wie an .up-root. */
  :root {
    --vc-text: #171717; --vc-muted: #4d4d4d; --vc-third: #7d7d7d; --vc-fourth: #8f8f8f;
    --vc-text-invert: #e0e0e0; --up-sel-bg: #f4f2f4;
    /* Die Chip-Stufen auch hier: Topic-Chips stehen in Flaechen am body (Topic-Modal, Bulk-Leiste). */
    --vc-border: #e4e2e4; --vc-heading-bg: #f9f8f9;
    --up-chip-rand-hover: #cfcdd0; --up-chip-an: #f4f2f4; --up-chip-an-hover: #eeedef; --up-chip-rand-an-hover: #b9b7bb;
  }
  /* Diese Zeile gilt seit dem 15.10. nur noch fuer DUNKLE ELEMENTE IM LICHT -- die Bulk-Leiste der
     Prompts-Tabelle ist im Licht eine dunkle Leiste (prompts-table.js leistenThema) und darf sich
     beim Umbau des Dunkels um keinen Pixel aendern. Das dunkle THEMA setzt dieselben Marken im
     Block darunter auf Geist; der gewinnt bei gleicher Spezifitaet ueber die Reihenfolge -- er
     steht nur unter html[data-up-dunkel], und das gibt es im Licht nie. */
  [data-theme="dark"] {
    --vc-text: #e0e0e0; --vc-muted: #c9cbd0; --vc-third: #a0a0a0; --vc-fourth: #6c717a;
    --vc-text-invert: #1f1f1b; --up-sel-bg: #28282c;
    --vc-border: #34343a; --vc-heading-bg: #232326;
    --up-chip-rand-hover: #4a4a52; --up-chip-an: #26262a; --up-chip-an-hover: #2c2c30; --up-chip-rand-an-hover: #5a5a62;
  }
  /* DAS DUNKLE THEMA AUSSERHALB EINER WURZEL (15.10., Dunkel nach Geist). Dieselben Werte wie am
     dunklen .up-root -- und dazu die FLAECHEN: Menues, Tooltips und Dialoge am <body> fuehrten bis
     heute je eine EIGENE Kopie der dunklen Palette (brands-overview zweimal, performance-radar,
     settings-brand, das Topic-Modal, das Groessen-Menue), mit Werten aus der Linear-Zeit, die beim
     naechsten Umbau stehen geblieben waeren. Gemessen am 15.10.: genau das waren nach dem
     Umstellen die Flecken in #1c1c1f und #232326 zwischen den Geist-Flaechen. Eine Kopie braucht
     jetzt nur noch, was an ihr ANDERS ist (der kraeftigere Rand einer schwebenden Schicht:
     --vc-border: var(--up-menu-border)).
     :where() haelt die Spezifitaet bei (0,1,0) wie die Zeile darueber -- jede Wurzel
     (.up-root[data-theme="dark"], 0,2,0) und jede eigene Palette gewinnt weiterhin.
     NICHT hier: --up-surface. Der Seitengrund wechselt im Dunkeln zwischen Seite und Inhalt (Block
     "SEITE UND INHALT") und muss von der Wurzel geerbt werden, nicht neu gesetzt.
     Seit "eine Stufe ruhiger" (15.10. abends) dieselben Werte wie am dunklen .up-root, Rolle fuer
     Rolle: Inhalt #0f0f0f, Linie #222222, Kopf/Hover #1f1f1f, Auswahl #242424, aktiv #2e2e2e, Rand
     der schwebenden Schicht #333333, Skelett #1e1e1e; der Schalter aus bleibt #454545. */
  :where(html[data-up-dunkel="geist"]) [data-theme="dark"] {
    --vc-text: #ededed; --vc-muted: #c9c9c9; --vc-third: #a1a1a1; --vc-fourth: #7d7d7d;
    --up-sel-bg: #242424; --vc-border: #222222; --vc-heading-bg: #1f1f1f;
    --up-chip-rand-hover: #333333; --up-chip-an: #242424; --up-chip-an-hover: #2e2e2e; --up-chip-rand-an-hover: rgba(255,255,255,.24);
    --vc-bg: #0f0f0f; --vc-field: #0f0f0f; --vc-sk: #1e1e1e;
    --up-menu-bg: #0f0f0f; --up-menu-border: #333333;
    --vt-row-hover-solid: #1f1f1f; --vc-hover-border: #707070;
    --vc-switch-bg: #1f1f1f; --vc-switch-border: #222222; --up-switch-off: #454545;
    --vc-switch-rail: var(--up-seite-bg); --vc-switch-pill: rgba(255,255,255,.09);
  }
  .up-root {
      --uhm-h0: #f0f3f8; --uhm-h1: #c8d4e5; --uhm-h2: #8aa4c4; --uhm-h3: #4a6e96; --uhm-h4: #1e3a5f;
    }
    .up-root[data-theme="dark"] {
      --uhm-h0: #1e2634; --uhm-h1: #283a58; --uhm-h2: #375582; --uhm-h3: #466ea8; --uhm-h4: #5587c8;
    }
    /* DIE RAMPE FOLGT DER AKZENTFARBE (19.09. angefordert: "wenn eine der 3 anderen Akzentfarben
       gewaehlt ist, die Farbskala des Matrix-Charts auf eine Skala mit der jeweiligen
       Akzentfarbe als Grundfarbe"). Am STANDARD aendert sich nichts -- die zwei Regeln darueber
       bleiben unberuehrt, und ohne data-accent greift keine der sechs hier.
       Gerechnet, nicht gegriffen: uebernommen sind Helligkeit UND Saettigung jeder der fuenf
       Stufen aus der blauen Vorlage, getauscht ist nur der Farbton. Damit hat jede Skala
       dieselbe Abstufung und denselben Kontrast wie die bisherige -- eine frei gewaehlte Rampe
       waere an einem Ende flach geworden. Hell laeuft sie von hell nach dunkel, dunkel
       umgekehrt; auch das ist die Vorlage. */
    html[data-accent="indigo"] .up-root {
      --uhm-h0: #f0f1f8; --uhm-h1: #c8cbe5; --uhm-h2: #8a90c4; --uhm-h3: #4a5296; --uhm-h4: #1e255f;
    }
    html[data-accent="indigo"] .up-root[data-theme="dark"] {
      --uhm-h0: #1e2134; --uhm-h1: #282e58; --uhm-h2: #374082; --uhm-h3: #4652a8; --uhm-h4: #5563c8;
    }
    html[data-accent="azur"] .up-root {
      --uhm-h0: #f0f4f8; --uhm-h1: #c8d7e5; --uhm-h2: #8aa9c4; --uhm-h3: #4a7296; --uhm-h4: #1e405f;
    }
    html[data-accent="azur"] .up-root[data-theme="dark"] {
      --uhm-h0: #1e2934; --uhm-h1: #284058; --uhm-h2: #375d82; --uhm-h3: #4677a8; --uhm-h4: #558fc8;
    }
    html[data-accent="terrakotta"] .up-root {
      --uhm-h0: #f8f2f0; --uhm-h1: #e5cfc8; --uhm-h2: #c4998a; --uhm-h3: #965d4a; --uhm-h4: #5f2e1e;
    }
    html[data-accent="terrakotta"] .up-root[data-theme="dark"] {
      --uhm-h0: #34231e; --uhm-h1: #583428; --uhm-h2: #824937; --uhm-h3: #a85e46; --uhm-h4: #c87155;
    }
    .up-topicchip-lbl { overflow: hidden; text-overflow: ellipsis; }
    /* ---- DER GEWAEHLT-ZUSTAND EINES THEMAS IST EINE CHECKBOX (21.09. angefordert) ----------
       Vorher war es ein Haken, der beim Waehlen EINFUHR: width 0 -> 14, Deckkraft 0 -> 1. Der
       eigentliche Unterschied ist nicht das Aussehen, sondern dass er vorher GAR NICHT DA war.
       Ein Chip ohne Zeichen sah aus wie ein Etikett; dass man ihn anklicken kann, erfuhr man
       erst danach. Die Checkbox steht von Anfang an da, leer, und sagt damit: hier waehlt man.
       Gemeldet als Unterschied zwischen zwei Oberflaechen -- die Themen-Filterliste
       (filters/topics-filter.js, .up-filter-item mit .up-filter-check) hatte die Checkbox
       laengst, die Chips in der Themenauswahl und im Bearbeiten-Popup nicht.

       ES IST DIESELBE CHECKBOX wie in .up-filter-check, nur eine Nummer kleiner: 14 statt 16,
       Radius 4 statt 5, Haken 10 statt 11, Rahmen 1.5 unveraendert -- alle Werte im Verhaeltnis
       14/16 mitgezogen, wie es §1 fuer ein verkleinertes Bauteil verlangt. 16px waeren in einem
       28px hohen Chip zu schwer; die 14 sind ausserdem die Breite, die der alte Haken schon
       belegte.
       Dieselben Uebergangstoken (--up-cb-t / --up-cb-ease) wie die grosse Fassung.

       DER RAHMEN LIEGT AUF currentColor UND NICHT AUF --vc-border: der Chip ist mit der Farbe
       des Themas eingetoent, und ein neutrales Grau verschwindet darauf je nach Thema mal mehr,
       mal weniger. currentColor ist die Schriftfarbe des Chips, also immer im selben Verhaeltnis
       zu seinem Grund -- auf jedem Thema und in beiden Themen gleich gut sichtbar.

       STATISCHE CHIPS SIND NICHT BETROFFEN: .up-topicchip.is-static traegt gar kein
       Haken-Element (geprueft ueber alle Komponenten), sie zeigen also weiterhin nur Emoji und
       Namen. */
    /* Seit dem 06.10. VOR dem Punkt (Handoff: "it sits before the dot"), ohne Abstand nach links. */
    .up-topicchip-check { display: inline-flex; align-items: center; justify-content: center; order: -2;
      width: 14px; height: 14px; flex: 0 0 auto; margin: 0;
      border-radius: var(--up-r-xs); border: 1.5px solid color-mix(in srgb, currentColor 32%, transparent);
      background: transparent; color: inherit;
      transition: background-color var(--up-cb-t) var(--up-cb-ease),
                  border-color var(--up-cb-t) var(--up-cb-ease); }
    .up-topicchip-check.is-on { background: var(--up-accent); border-color: var(--up-accent);
      color: var(--up-accent-fg); }
    .up-topicchip-check svg { width: 10px; height: 10px; display: block; flex: 0 0 auto;
      opacity: 0; transform: scale(.7);
      transition: opacity var(--up-cb-t) var(--up-cb-ease),
                  transform var(--up-cb-t) var(--up-cb-ease); }
    .up-topicchip-check.is-on svg { opacity: 1; transform: scale(1); }
    /* Der Hover der leeren Checkbox wie an .up-filter-check (16.10., Begruendung dort): der Chip
       ist hier die Zeile, sein Rand geht beim Ueberfahren auf --vc-third. Eine volle Themenliste
       (.up-topiclist.is-full) nimmt nichts mehr an, ihre Chips bekommen ihn darum nicht. */
    .up-topicchip:not(.is-static):not(.is-on):not(.up-topiclist.is-full *):is(:hover, :focus-visible) .up-topicchip-check { border-color: var(--vc-third); }
    /* Im Dunkeln (06.10.): die Toenungs-Regeln (22 / 34 Prozent) sind mit dem "Dot" entfallen --
       Grund und Rand kommen aus Token, die das Thema selbst dreht; eigen bleibt nur der Punkt
       (oben). [data-theme="dark"] an JEDEM Vorfahren reicht dafuer, auch an den Flaechen am body
       (Bulk-Leiste, Topic-Modal), die die Chip-Token ueber :root / [data-theme] bekommen. */

    /* ONE chip grid for the whole app -- 8px row+column gap (flex-wrap's own `gap` gives both for
       free). Was missing a base rule entirely: every consumer either duplicated this declaration
       itself (prompts-table's .upt-gm-list) or, like the Edit Topics popup, simply had no gap at
       all and relied on nothing but the chips' own margins. */
    .up-topiclist { display: flex; flex-wrap: wrap; gap: 8px; }
    /* at the cap: chips you cannot add anyway say so instead of silently ignoring the click */
    .up-topiclist.is-full .up-topicchip:not(.is-on) { opacity: .4; cursor: default; pointer-events: none; }

    /* ---------- secondary button ----------
       Extracted from prompts-table's .upt-topicadd, which was the only place it existed. Solid
       border, plain surface, real hover -- the app's "second action next to a primary one". */
    .up-btn-sec { height: 32px; padding: 0 14px; border-radius: var(--up-r-m);
      border: 1px solid var(--vc-border); background: transparent; color: var(--vc-text);
      cursor: pointer; font-family: inherit; font-size: 12.5px; font-weight: 500;
      display: inline-flex; align-items: center; justify-content: center; gap: 7px; }
    .up-btn-sec svg { width: 14px; height: 14px; display: block; }
    /* Geist-Sekundaerknopf (15.10.): beim Ueberfahren nur die Flaeche, der Rand bleibt; Fokus als
       Ring mit Abstand. Werte und Begruendung bei den Knopf-Zustaenden oben (--up-knopf-*). */
    .up-btn-sec:hover { color: var(--vc-text); background: var(--up-knopf-hover); }
    .up-btn-sec:focus-visible { outline: var(--up-focus-w) solid var(--up-focus); outline-offset: var(--up-knopf-fokus-abstand); }
    .up-btn-sec:disabled { opacity: .45; cursor: default; }
    .up-btn-sec:disabled:hover { border-color: var(--vc-border); background: transparent; }

    /* ---------- primary button ----------
       Die Gegenstueck zu .up-btn-sec: dieselbe Hoehe, derselbe Radius, gefuellt statt umrandet.
       Aus settings-brand (.usb-savebtn) hierher gezogen, als brand-editor denselben Knopf
       brauchte -- zwei Kopien eines Hauptknopfes sind zwei Orte, an denen er sich auseinander
       entwickelt. settings-brand behaelt seine Klasse vorerst; sie traegt exakt diese Werte. */
    /* ---- DREI ZWILLINGE SIND HIER EINGEZOGEN (21.09.) --------------------------------------
       .up-filter-submit, .up-topicmodal-save und .upt-topicapply waren handgebaute Kopien dieses
       Knopfes: gefuellt in der Akzentfarbe, aber 34px statt 32, 12.5/13px statt 13, Schnitt 600
       statt 500, Polsterung 20 statt 14 -- und, das war der Fehler, ein EIGENER Deaktiviert-
       Zustand: background --vc-heading-bg, color --vc-third.
       DAS MACHT DEN KNOPF UNSICHTBAR. Die Massenleiste der Prompts-Tabelle fuehrt eine eigene
       Palette, in der --vc-heading-bg #232326 ist -- und ihr eigener Grund (--up-surface) ist
       #232326. Derselbe Wert. Der deaktivierte Apply verschwand also vollstaendig in der Leiste,
       uebrig blieb nur sein Text: gemeldet am 21.09. als "aktuell ist das nur text button".
       Im hellen Thema dasselbe eine Stufe schwaecher (#f9f8f9 auf #ffffff).
       Der Deaktiviert-Zustand des Bauteils macht es richtig: die Fuellung BLEIBT und wird auf
       opacity .4 gedimmt. Ein Knopf, den man nicht druecken kann, soll immer noch wie ein Knopf
       aussehen -- sonst weiss niemand, dass dort etwas freizuschalten ist.
       Die Freischaltlogik war uebrigens schon richtig: der Edit-Topics-Apply vergleicht
       etStaged gegen etOriginal, der Massen-Apply haengt an der Zahl der gewaehlten Themen (er
       FUEGT HINZU, es gibt dort also kein Vorher). Falsch war nur, wie "aus" aussieht. */
    .up-btn-pri,
    .up-filter-submit,
    .up-topicmodal-save,
    .upt-topicapply { height: 32px; padding: 0 14px; border-radius: var(--up-r-m); white-space: nowrap;
      border: 0; background: var(--up-accent); color: var(--up-accent-fg);
      font-family: inherit; font-size: 13px; font-weight: 500; cursor: pointer;
      display: inline-flex; align-items: center; justify-content: center; gap: 7px;
      transition: opacity 120ms ease; }
    .up-btn-pri svg, .up-filter-submit svg { width: 14px; height: 14px; display: block; }
    .up-btn-pri:disabled,
    .up-filter-submit:disabled,
    .up-topicmodal-save:disabled,
    .upt-topicapply:disabled { opacity: .4; cursor: default; }
    .up-btn-pri:not(:disabled):hover,
    .up-filter-submit:not(:disabled):hover,
    .up-topicmodal-save:not(:disabled):hover,
    .upt-topicapply:not(:disabled):hover { opacity: .88; }
    .up-btn-pri:focus-visible,
    .up-filter-submit:focus-visible,
    .up-topicmodal-save:focus-visible,
    .upt-topicapply:focus-visible { outline: 0; box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); }

    /* ---------- Die GROSSE Stufe: .is-lg an .up-btn-pri und .up-btn-sec (25.09.) ----------
       Die ganzseitigen Flaechen -- Anmeldung, Onboarding und jetzt die Einladungsseite -- tragen
       einen groesseren Hauptknopf als die App: 40px hoch, Radius 12, 14px in 600. Bisher stand er
       ZWEIMAL als Kopie (.uau-primary in auth-page.css, .uob-next in onboarding-page.css, dort
       ausdruecklich "Geometrie 1:1 .uau-primary"). Die Einladungsseite waere die dritte gewesen --
       also ist er jetzt hier, als Stufe des Core-Knopfes: Farbe, Zustaende, Hover und Fokus
       bleiben die von .up-btn-pri/.up-btn-sec, nur die Geometrie waechst, und zwar ALLE Werte
       zusammen (Hoehe, Polster, Radius, Abstand, Schrift, Zeichen).
       Die zwei bestehenden Kopien bleiben vorerst, wie sie sind -- sie tragen exakt diese Werte. */
    .up-btn-pri.is-lg, .up-btn-sec.is-lg {
      height: 40px; padding: 0 20px; border-radius: var(--up-r-l); gap: 9px;
      font-size: 14px; font-weight: 600; letter-spacing: -.01em; }
    .up-btn-pri.is-lg svg, .up-btn-sec.is-lg svg { width: 15px; height: 15px; }

    /* ---------- Formularfehler: der Kasten ueber dem Knopf (25.09. nach core gezogen) ----------
       Das "gewohnte Fehlerformat" der ganzseitigen Flaechen. Es stand ZWEIMAL wortgleich da
       (.uau-formerr-in in auth-page.css, .uob-banner-in in onboarding-page.css); die
       Einladungsseite waere die dritte Kopie gewesen.
       Die Huelle faehrt ueber grid-template-rows 0fr -> 1fr auf -- ohne gemessene Pixelhoehe, also
       auch bei zwei Zeilen Text richtig, und ohne dass der Rest darunter springt. .is-on zeigt ihn.
       --vc-neg-border/-bg sind nirgends gesetzt, es gelten die Rueckfallwerte -- dieselben wie in
       beiden Vorlaeufern, also sieht er genau so aus wie dort. */
    .up-formerr {
      display: grid; grid-template-rows: 0fr; opacity: 0; margin-top: 0;
      transition: grid-template-rows var(--up-t-2) var(--up-ease), opacity var(--up-t-2) var(--up-ease),
                  margin-top var(--up-t-2) var(--up-ease);
    }
    .up-formerr > * { overflow: hidden; min-height: 0; }
    .up-formerr.is-on { grid-template-rows: 1fr; opacity: 1; margin-top: 18px; }
    .up-formerr-in {
      padding: 10px 12px; border-radius: var(--up-r-l);
      border: 1px solid var(--vc-neg-border, rgba(200, 60, 60, .28));
      background: var(--vc-neg-bg, rgba(200, 60, 60, .08));
      font-size: 13px; line-height: 1.45; color: var(--vc-text); text-align: left;
    }
    @media (prefers-reduced-motion: reduce){ .up-formerr { transition: none; } }

    /* ---------- Die Fehlerzeile (07.10.): der Fehlerzustand der App als Baustein ----------
       Ein roter Satz in --vt-down, KEIN Kasten, keine Flaeche, kein Rahmen. So stehen Fehler in der
       App seit langem -- team-orga (.uto-err, .uto-formerr), add-brand (.uab-err), settings-brand
       (.usb-fileerr) --, nur gab es dafuer keinen gemeinsamen Baustein, und der einzige Fehler in
       core war der Kasten .up-formerr. Der stand auf den Einzelseiten und wurde am 07.10.
       abgelehnt ("diese daemlichen Container mit der Primaerfont drin").
       Aufklappen ueber grid-template-rows 0fr -> 1fr: keine gemessene Hoehe, und was darunter
       steht, rutscht weich statt zu springen. .is-on zeigt sie. */
    .up-fehlerzeile {
      display: grid; grid-template-rows: 0fr; opacity: 0;
      transition: grid-template-rows var(--up-t-2) var(--up-ease), opacity var(--up-t-2) var(--up-ease);
    }
    .up-fehlerzeile > span {
      overflow: hidden; min-height: 0; display: block; padding-top: 5px;
      font-size: 12px; line-height: 1.4; color: var(--vt-down);
    }
    .up-fehlerzeile.is-on { grid-template-rows: 1fr; opacity: 1; }
    @media (prefers-reduced-motion: reduce){ .up-fehlerzeile { transition: none; } }
    /* Mittig (13.10., Onboarding): der Fehler einer ganzen Seite oder eines Formulars steht als
       derselbe rote Satz zentriert ueber bzw. unter dem Inhalt -- dort, wo der Blick ist. Zwei
       Klassen gegen .up-fehlerzeile > span. */
    .up-fehlerzeile.is-mitte { text-align: center; }
    .up-fehlerzeile.is-mitte > span { padding-top: 0; }

    /* ══ EINZELSEITE (07.10.) ══════════════════════════════════════════════════════════════════
       Der Rahmen der kleinen ganzseitigen Flaechen: check-email, forgot-password, reset-password,
       not-found (UC.makeEinzelseite in core.js baut das Markup). Vier eigene Komponenten, ein
       Rahmen -- vier Kopien davon liefen beim ersten Nachziehen auseinander.
       Das Raster ist das der Anmeldeseite (16px Kartenpolster, dasselbe Spaltenpolster, die
       Wortmarke oben links, der Theme-Knopf oben rechts), der Inhalt steht mittig.
       SO RUHIG WIE DIE APP (07.10. nachgezogen: "subtil professionell, wie der rest der app"):
       KEIN Grafikband, KEINE Kachel um das Zeichen, der Grund ist der Seitengrund der App
       (--up-surface, dasselbe wie html/body im Theme-Snippet), die Ueberschrift steht auf der
       App-Skala (28px, die Groesse der Seitenkoepfe) statt der 44px der Anmeldeseite.
       Zwei Klassen an der Wurzel: .up-root setzt weiter unten in dieser Datei eigene Werte, und
       ein Konflikt wird ueber Spezifitaet geloest, nicht ueber die Reihenfolge. */
    .up-root.up-es {
      min-height: 100vh; min-height: 100dvh;
      display: flex; align-items: stretch; justify-content: center;
      padding: 0; background: var(--up-surface); color: var(--vc-text);
    }
    .up-es-karte {
      width: 100%; min-height: 100vh; min-height: 100dvh;
      display: grid; grid-template-columns: minmax(0, 1fr); padding: 16px;
      background: var(--up-surface);
    }
    /* Das Polster der linken Spalte der Anmeldeseite: senkrecht an vh, waagerecht an vw. */
    .up-es-spalte {
      min-width: 0; display: flex; flex-direction: column;
      padding: clamp(20px, 4vh, 52px) clamp(28px, 4.4vw, 64px);
    }
    .up-es-top { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex: 0 0 auto; }
    .up-es-logo { height: 18px; width: auto; flex: 0 0 auto; display: block; }
    /* .up-iconbtn bringt keinen Fokusring mit (dieselbe Zeile wie in invite-page.css). */
    .up-es-themebtn:focus-visible { outline: 0; box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); }
    /* Etwas ueber der Mitte, nicht exakt darin: ein kurzer Block in der geometrischen Mitte wirkt
       optisch zu tief. Der Abstand unten entspricht der Kopfzeile oben. */
    .up-es-mitte {
      flex: 1 1 auto; min-height: 0; padding-bottom: 48px;
      display: flex; flex-direction: column; justify-content: center; align-items: center;
    }
    /* 360px: ein Feld, ein Knopf -- breiter wirkt es leer. minmax(0, 1fr): sonst waechst die Spalte
       auf ihr laengstes Wort, eine lange Adresse schob die Seite auf 375px hinaus (gemessen). */
    .up-es-block { width: 100%; max-width: 360px; display: grid; grid-template-columns: minmax(0, 1fr); }
    /* Formular und Erfolg im selben Raster uebereinander: die Seite behaelt beim Wechsel ihre Hoehe.
       visibility erst NACH dem Ausblenden: sonst laege der Tabulator in einem unsichtbaren Feld. */
    .up-es-block > .up-es-pane { grid-area: 1 / 1; }
    .up-es-pane {
      transition: opacity var(--up-t-2) var(--up-ease), transform var(--up-t-2) var(--up-ease),
                  visibility 0s linear 0s;
    }
    .up-es-pane.is-off {
      opacity: 0; transform: translateY(6px); pointer-events: none; visibility: hidden;
      transition: opacity var(--up-t-2) var(--up-ease), transform var(--up-t-2) var(--up-ease),
                  visibility 0s linear var(--up-t-2);
    }

    /* ---------- Kopf: Zeichen, Ueberschrift, Satz -- mittig ---------- */
    .up-es-kopf { display: flex; flex-direction: column; align-items: center; text-align: center; }
    /* Das Zeichen allein, ohne Kachel und ohne Flaeche -- in der Schriftfarbe, 28px wie die
       Ueberschrift, Strich 1.5 (dafuer sind die Hugeicons gezeichnet). */
    .up-es-ic { display: block; width: 28px; height: 28px; margin-bottom: 16px; color: var(--vc-text); }
    .up-es-ic svg { width: 28px; height: 28px; display: block; }
    /* Die Kennung ueber der Ueberschrift (die 404): klein, in der Drittfarbe, Ziffern in Spalten. */
    .up-es-oben {
      margin: 0 0 8px; font-size: 13px; font-weight: 500; color: var(--vc-third);
      font-variant-numeric: tabular-nums;
    }
    .up-es-h1 {
      margin: 0; font-size: 28px; line-height: 1.15; font-weight: 600; letter-spacing: -.02em;
      color: var(--vc-text); text-wrap: balance;
    }
    /* Die Unterzeile in der Drittfarbe (07.10. angefordert: "darunter Drittcolor"). */
    .up-es-sub {
      margin: 8px 0 0; font-size: 14px; line-height: 1.45; color: var(--vc-third); text-wrap: balance;
    }

    /* ---------- Felder: die Geometrie von .uau-field/.uau-input ---------- */
    .up-es-form { display: contents; }
    .up-es-felder { margin-top: 28px; display: flex; flex-direction: column; gap: 12px; }
    .up-es-feld { display: flex; flex-direction: column; }
    .up-es-label { font-size: 12px; font-weight: 600; color: var(--vc-muted); }
    .up-es-label + * { margin-top: 7px; }
    .up-es-input {
      height: var(--up-h-gross); width: 100%; padding: 0 12px;
      border: 1px solid var(--vc-border); border-radius: var(--up-r-l); background: var(--up-feld-bg);
      font-family: inherit; font-size: var(--up-fs-l); color: var(--vc-text); outline: none;
      transition: border-color var(--up-t-2) var(--up-ease), box-shadow var(--up-t-2) var(--up-ease);
    }
    .up-es-input::placeholder { color: var(--vc-third); }
    .up-es-input:disabled { opacity: .55; cursor: default; }
    /* Der Rahmen faerbt sich NICHT, solange das Feld den Fokus hat: sonst wechselt die Farbe genau
       in dem Moment, in dem jemand den Fehler beheben will. Die Zeile darunter ist .up-fehlerzeile. */
    .up-es-feld.is-err .up-es-input:not(:focus) { border-color: var(--vt-down); }
    .up-es-feld.is-err .up-fehlerzeile { grid-template-rows: 1fr; opacity: 1; }

    /* ---------- Passwortstaerke (UC.pwStaerkeHtml): die Anzeige der Anmeldeseite ----------
       Oben 8px wie dort, unten keine: dort folgt eine Optionszeile, hier das naechste Feld mit
       seinem eigenen Abstand von 12px -- 8 mehr saehen wie eine Luecke aus. */
    .up-pwst {
      display: grid; grid-template-rows: 0fr; opacity: 0;
      transition: grid-template-rows var(--up-t-2) var(--up-ease), opacity var(--up-t-2) var(--up-ease);
    }
    .up-pwst > span { overflow: hidden; min-height: 0; }
    .up-pwst.is-on { grid-template-rows: 1fr; opacity: 1; }
    .up-pwst-in { padding-top: 8px; display: flex; align-items: center; gap: 10px; }
    .up-pwst-bars { display: flex; gap: 4px; flex: 1 1 auto; }
    .up-pwst-bar {
      height: 4px; flex: 1 1 0; border-radius: var(--up-r-voll); background: var(--vc-sk);
      transition: background var(--up-t-2) var(--up-ease);
    }
    /* Vier Stufen, vier Farben -- dieselben wie auf der Anmeldeseite. */
    .up-pwst[data-level="1"] .up-pwst-bar.is-on { background: var(--vc-neg, #c8412e); }
    .up-pwst[data-level="2"] .up-pwst-bar.is-on { background: #d98324; }
    .up-pwst[data-level="3"] .up-pwst-bar.is-on { background: #b8a02a; }
    .up-pwst[data-level="4"] .up-pwst-bar.is-on { background: var(--vc-pos, #2e9e63); }
    .up-pwst-txt {
      flex: 0 0 auto; min-width: 74px; text-align: right;
      font-size: 12px; font-weight: 500; color: var(--vc-muted);
    }

    /* ---------- Knoepfe, Fehlerkasten, leise Wege ---------- */
    /* 24px ueber dem Hauptknopf: nah genug, dass Feld und Knopf eine Gruppe sind. */
    .up-es-cta { width: 100%; margin-top: 24px; }
    /* Bot-Pruefung (UC.captchaPlatzHtml, 14.10.): ohne Hoehe, solange Turnstile unsichtbar arbeitet --
       abgeschnitten und nicht display:none, damit das Widget darin laeuft. Braucht es einen Haken,
       setzt der Gastgeber .is-sichtbar; dann 24px Abstand wie der Hauptknopf darunter. */
    .up-captcha { height: 0; overflow: hidden; }
    .up-captcha.is-sichtbar { height: auto; overflow: visible; margin-top: 24px; }
    /* Ein Link ist hier ein Knopf: ohne Unterstrich. */
    .up-es a.up-btn-pri { text-decoration: none; }
    /* Beschaeftigt ist nicht gesperrt: .4 hiesse "geht nicht", .55 ist der Wert der Anmeldeseite.
       Drei Klassen gegen .up-btn-pri:disabled. */
    .up-es .up-es-cta.is-busy:disabled { opacity: .55; cursor: progress; }
    /* Der Spinner tritt NEBEN die Beschriftung, er ersetzt sie nicht -- ein Knopf, dessen Text
       verschwindet, wirkt kaputt statt beschaeftigt. Die Drehung ist die von up-ph-spin. */
    .up-btn-spin {
      width: 15px; height: 15px; flex: 0 0 auto; display: none;
      border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
      animation: up-ph-spin 620ms linear infinite;
    }
    .is-busy > .up-btn-spin { display: block; }
    /* Der Fehler des ganzen Formulars: die Fehlerzeile direkt UNTER dem Knopf, mittig -- dort, wo
       der Blick nach dem Klick ist. Zwei Klassen gegen das Polster von .up-fehlerzeile > span. */
    .up-fehlerzeile.up-es-fehler { text-align: center; }
    .up-fehlerzeile.up-es-fehler > span { padding-top: 12px; }
    /* Der Weg zu einem neuen Link steht direkt hinter dem Satz, in derselben Farbe, unterstrichen. */
    .up-es-formerr-link {
      margin-left: 4px; font-weight: 500; color: inherit; white-space: nowrap;
      text-decoration: underline; text-underline-offset: 2px;
    }
    .up-es-formerr-link[hidden] { display: none; }
    /* Der leise Weg: mittig, klein, im Ton des Seitenlinks der Anmeldeseite ("Forgot password?").
       Als Link UND als Knopf (not-found "Go back", forgot-password "Resend link"). */
    .up-es-zurueck {
      display: flex; width: fit-content; margin: 20px auto 0; align-items: center; gap: 6px;
      padding: 0; border: 0; background: none; cursor: pointer; font-family: inherit;
      font-size: 13px; font-weight: 500; color: var(--vc-muted); text-decoration: none;
      transition: color var(--up-t-2) var(--up-ease);
    }
    .up-es-zurueck:hover:not(:disabled) { color: var(--vc-text); }
    .up-es-zurueck:disabled { color: var(--vc-third); cursor: default; }
    .up-es-zurueck svg { width: 14px; height: 14px; display: block; }
    .up-es-zurueck:focus-visible { outline: 0; border-radius: var(--up-r-xs); box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); }

    /* ---------- Einzug ----------
       Wie auf der Anmeldeseite, nur leiser: 8px Weg statt 18 -- auf einer Seite mit drei Zeilen
       soll man das Ankommen spueren, nicht zusehen. 420ms wie --uau-in/--uiv-in/--uob-in,
       backwards, damit die Stufen waehrend ihrer Verzoegerung nicht dastehen. Die Stufe steht am
       Element (data-es-auf), je 120ms Versatz. */
    @keyframes up-es-auf { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
    .up-es.is-entering [data-es-auf] { animation: up-es-auf 420ms var(--up-ease-auf) backwards; }
    .up-es.is-entering [data-es-auf="2"] { animation-delay: var(--up-t-1); }
    .up-es.is-entering [data-es-auf="3"] { animation-delay: calc(var(--up-t-1) * 2); }
    .up-es.is-entering [data-es-auf="4"] { animation-delay: calc(var(--up-t-1) * 3); }

    /* ---------- schmal ---------- */
    .up-es.is-narrow .up-es-spalte { padding: clamp(20px, 6vw, 36px); }
    .up-es.is-narrow .up-es-mitte { padding-bottom: 24px; }

    @media (prefers-reduced-motion: reduce){
      .up-es.is-entering [data-es-auf] { animation: none; }
      .up-btn-spin { animation: none; }
      .up-es-pane, .up-es-pane.is-off { transition: none; }
    }

    /* ---------- Zeilenknopf: die kleine Fassung von .up-btn-sec, die beim Zeilen-Hover kommt ----
       Entstanden als .ubo-editbtn in brands-overview ("Edit" je Marke). Zweiter Verbraucher ist
       "Show Pages" / "Hide Pages" in der Domains-Tabelle, und damit gehoert es hierher: zwei
       Kopien derselben Geometrie laufen beim naechsten Nachziehen auseinander. Die alte Klasse
       steht in jeder Regel mit, damit das Markup von brands-overview unangetastet bleibt --
       derselbe Kniff wie bei .up-seg und .cc-seg.

       Alle Werte von .up-btn-sec im Verhaeltnis 24/32 mitgezogen: Hoehe 32 -> 24, Radius 9 -> 7,
       Innenabstand 7 -> 6, Zeichen 14 -> 13. Das Polster steht bei 8, so vorgegeben. Der Ton ist
       der sekundaere und wird beim Hover der primaere; die Flaeche darunter ist der Hover von
       .up-btn-sec, unveraendert.
       Er kommt OHNE Verweildauer: 200ms Einblenden am Hover der ZEILE, sonst nichts. Der Vorlaeufer
       (.up-rowswap, weiter unten) tauschte erst nach einer Sekunde einen Pfeil gegen den Knopf --
       eine Wartezeit vor der Handlung, um die es in der Zeile geht. */
    .up-rowbtn, .ubo-editbtn {
      margin-left: auto; flex: 0 0 auto;
      height: 24px; padding: 0 8px; border-radius: var(--up-r-s); gap: 6px;
      font-size: 12px; color: var(--vc-muted);
      opacity: 0; pointer-events: none;
      transition: opacity 200ms ease, color 140ms ease, border-color 140ms ease, background 140ms ease;
    }
    .up-rowbtn svg, .ubo-editbtn svg { width: 13px; height: 13px; }
    /* Sichtbar am Hover der ZEILE, nicht des Knopfes -- ein Knopf, der erst erscheint, wenn man
       ihn trifft, ist keiner. Tastatur: auch beim Fokus, sonst ist er nur mit der Maus da. */
    .up-row:hover .up-rowbtn, .up-rowbtn:focus-visible,
    .up-row:hover .ubo-editbtn, .ubo-editbtn:focus-visible { opacity: 1; pointer-events: auto; }
    .up-rowbtn:hover, .ubo-editbtn:hover { color: var(--vc-text); }
    /* Ganz schmal faellt er weg: dort ist die fuehrende Zelle schon auf ihre Mindestbreite
       gestrichen, und ein Knopf darin schiebt den Namen aus dem Bild. Dieselbe Stufe, an der
       auch der Pfeil des Vorlaeufers verschwindet. */
    .up-root.is-vnarrow .up-rowbtn, .up-root.is-vnarrow .ubo-editbtn { display: none; }

    /* ---------- sort dropdown ---------- */
    .up-sort { position: relative; flex: 0 0 auto; }
    .up-sort-menu { position: absolute; top: calc(100% + var(--up-dd-gap)); right: 0; z-index: 60; width: 220px;
      background: var(--vc-bg); border: 1px solid var(--vc-border); border-radius: var(--up-dd-radius); box-shadow: var(--up-dd-shadow); padding: var(--up-dd-pad);
      opacity: 0; transform: translateY(-4px) scale(0.985); pointer-events: none; transition: opacity 140ms ease, transform 140ms ease; }
    .up-sort-menu.is-shown { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
    /* Dark mode: var(--vc-border) (#353535) against these menus' #151515 background reads as
       barely-there — almost the same darkness, so the whole floating card looks edgeless/blurry.
       A dedicated, brighter border-only override for the four dropdown menus (mentioned/cols/
       sort/filter), independent of --vc-border everywhere else, which stays as-is for borders on
       the page's own (lighter) surfaces where the contrast problem doesn't exist. */
    .up-root[data-theme="dark"] .up-ment-menu,
    .up-root[data-theme="dark"] .up-cols-menu,
    .up-root[data-theme="dark"] .up-sort-menu,
    .up-root[data-theme="dark"] .up-filter-menu { border-color: var(--vc-switch-border); }
    .up-root[data-theme="dark"] .up-sort-menu { background: var(--up-menu-bg); }
    /* Hier stand bis zum 22.09. eine zweite Schienenfarbe, nur fuer Umschalter INNERHALB dunkler
       Menues: die durchscheinende Schiene hellte ihren Grund auf und stand im Menue (#232326)
       damit drei Stufen unter der Pille, also praktisch auf ihr. Seit dem 27.09. hellt die
       Schiene im Dunkeln wieder auf -- aber die Pille hellt mit auf, damals stand sie fest. Der
       Abstand zwischen beiden ist damit auf jedem Grund derselbe, und die Ausnahme bleibt
       ueberfluessig -- siehe --vc-switch-rail. */
    .up-sort.is-open .up-sort-btn { color: var(--vc-text); background: var(--vc-heading-bg); }
    .up-pop-head-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 18px; }   /* reserve the Select-all button height up front so nothing resizes */
    .up-pop-action { border: 0; background: transparent; color: var(--vc-muted); font-family: inherit;
      font-size: 11px; font-weight: 500; cursor: pointer; padding: 2px 4px; border-radius: var(--up-r-s); text-transform: none; letter-spacing: 0; }
    .up-pop-action:hover { color: var(--vc-text); }
    .up-pop-action.is-hidden { visibility: hidden; pointer-events: none; }
    /* ---- DIE ETIKETT-BEHANDLUNG (21.09.) --------------------------------------------------
       EIN Aussehen fuer alles, was einen Abschnitt BENENNT statt etwas zu sagen: Gruppenkopf im
       Menue, Feldbeschriftung im Dialog, Abschnittstitel in einem Panel. Gemessen waren es vor
       diesem Umbau 32 Versal-Etiketten in 13 verschiedenen Fassungen und 98 normale
       Ueberschriften in 50 -- fuer hoechstens vier Rollen.

       KEINE VERSALIEN, und das ist eine Ansage und kein Geschmack (21.09.): "Diese Art von
       Headings in Uppercase finde ich ganz schlimm, muss weg." Die Unterscheidung zum Fliesstext
       traegt jetzt Groesse, Schnitt und Farbe -- 12px statt 13, 600 statt 500, gedaempft statt
       Textfarbe. Das reicht, und es liest sich in jeder Sprache, in der Versalien nicht
       funktionieren.
       DIE LAUFWEITE FAELLT MIT WEG. 0.04em stand da, WEIL Versalien Sperrung brauchen; in
       normaler Schreibung sperrt man nicht, das sieht dann nach Fehler aus.
       11 -> 12px: die Grossbuchstaben trugen optisches Gewicht, das jetzt fehlt. Ein Pixel
       mehr gleicht es aus, ohne mit dem Inhalt darunter zu konkurrieren (der steht auf 13).

       NICHT BETROFFEN sind einzelne Buchstaben -- .up-logo-ltr, .up-stack-ltr, .up-model-ltr.
       Das sind Initialen im Favicon-Kaestchen, keine Ueberschriften; ein kleingeschriebenes
       "f" fuer forbes.com waere dort schlicht falsch.

       .up-label ist der Name fuer neuen Code. Die vier bestehenden Klassen stehen daneben,
       damit kein Markup in acht Komponenten angefasst werden muss -- eine Deklaration, vier
       gewachsene Namen. */
    .up-label,
    .up-pop-head,
    .up-pop-sub,
    .up-blockhead,
    .up-filter-title,
    .up-topicmodal-label { font-size: var(--up-ueb-gruppe-gr); font-weight: var(--up-ueb-gruppe-gew); line-height: var(--up-ueb-gruppe-zeile); color: var(--vc-muted); }
    /* Waagerecht 0, aus demselben Grund wie .up-filter-head: die Ueberschrift steht buendig
       mit der Kante der Zeilen darunter (deren Hoverflaeche beginnt an der Polsterkante des
       Menues), nicht eingerueckt gegen sie. */
    .up-pop-head { padding: 4px 0 8px; }
    /* ---------- Blockueberschrift (12.09., aus sidebar.css hierher gezogen) ----------
       Die kleine Versalzeile UEBER einem Inhaltsblock -- in der Hauptseitenleiste die Zeile vor
       jedem Menuebereich (.usn-head), im Power Dashboard vor "Recent chats" und "Overview". Nicht
       dasselbe wie .up-pop-head zwei Zeilen darueber: das ist die Ueberschrift IN einem Dropdown
       (Sekundaerton, eigenes Polster fuer den Menuekasten), das hier ist die Ueberschrift AUF DER
       SEITE selbst -- deshalb die ruhigere Drittfarbe und kein eigenes Polster (das legt jeder
       Aufrufer nach seiner eigenen Anordnung fest).
       Wer mehr braucht als nur die Schrift -- .usn-head ist zugleich ein Knopf mit Chevron und
       Hoverfarbe, siehe sidebar.css --, baut das ALS ZUSATZ zu dieser Klasse, nicht als Ersatz:
       zwei Stellen mit denselben vier Werten (Groesse, Gewicht, Laufweite, Versalien) sind die
       naechste Drift, sobald eine davon geaendert wird und die andere nicht. */
    /* --vc-third UND NICHT --vc-muted (21.09. entschieden, nachdem ich zuerst muted gesetzt
       hatte). Das ist eine bewusste Entscheidung gegen den Kontrast: --vc-third kommt auf Weiss
       auf 2.68:1, auf der Kopfzeile 2.53, im Menue 2.41 -- AA verlangt fuer 12px/600 4.5:1.
       Im dunklen Thema sind es 3.97:1. Die Etiketten sollen zuruecktreten und nur den Abschnitt
       markieren; der Inhalt darunter traegt die Aussage und steht auf --vc-text.
       Wer das drehen will, aendert diese eine Zeile -- alle sechs Etiketten haengen daran. */
    .up-pop-opt { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 10px; border-radius: var(--up-r-s); cursor: pointer; font-size: 13.5px; font-weight: 500; color: var(--vc-text); }
    /* Hover UND selected tragen denselben Ton (--up-sel-bg, 55%/60%): eine Zeile auf die man zeigt
       und eine die gewaehlt ist meinen beide "diese hier", zwei verschiedene Graus nebeneinander
       lesen sich als zwei Bedeutungen. Vorher hatte selected ueberhaupt keinen Hintergrund, nur den
       Haken -- deshalb sah jeder Sorter anders aus als die Filterlisten daneben.
       .up-pop-opt ist der geteilte Optionstyp: diese eine Regel erreicht jeden Sorter der App. */
    .up-pop-opt:hover, .up-pop-opt.is-active { background: var(--up-sel-bg); }
    /* Die Farbe eines Typs vor einer Menuezeile (13.10., "Count as" im Domain-Drawer): dieselbe Form
       wie in den Chart-Legenden (10 x 10, Radius 4), die Farbe inline aus UC.typeColor. Steht mit
       dem Text in .up-pop-opt-l, damit der Haken rechts bleibt. */
    .up-pop-opt-l { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
    .up-pop-form { flex: 0 0 auto; width: 10px; height: 10px; border-radius: var(--up-r-xs); }
    .up-pop-opt .up-check { width: 16px; height: 16px; color: var(--vc-text); opacity: 0; flex: 0 0 auto;
      transform: scale(.7); transition: opacity var(--up-cb-t) var(--up-cb-ease), transform var(--up-cb-t) var(--up-cb-ease); }
    .up-pop-opt .up-check svg { width: 16px; height: 16px; display: block; }
    .up-pop-opt.is-active .up-check { opacity: 1; transform: scale(1); }
    .up-pop-div { height: 1px; background: var(--vc-border); margin: 7px 4px; }
    /* ---- Intent-Label und Branded-Label (13.10., Variante B / B2) ---------------------------
       Zeichen plus Text, OHNE Flaeche und ohne Rand: in einer Zeile neben Topic-Chips (Rand +
       Strich) muss die Absicht leiser sein als die Ordnung des Nutzers. Hoehe die Chip-Stufe (24),
       damit eine Zelle mit Label und eine ohne dieselbe Zeilenhoehe ergeben.
       Das Zeichen in --vc-fourth, der Text in --vc-muted: das Zeichen ist der Wiedererkenner, nicht
       die Aussage. Laden und "nicht zugeordnet" stehen ganz in --vc-fourth -- statisch, ohne
       Bewegung (Ansage 13.10.), dieselbe Form, damit nichts springt, wenn der Wert eintrifft.
       Klickbar (is-klick, ein <button>): 6px Polster und ein Hover-Grund aus --up-sel-bg, wie jede
       Menuezeile. Das Polster gleicht der BEHAELTER aus (-6px links), nicht der Knopf selbst: ein
       negativer Aussenabstand am Knopf machte den Behaelter 6px schmaler, und max-width: 100% zog
       das Label dann um genau diese 6px zusammen -- "Informatio…" in einer 264px breiten Zelle
       (gemessen im Pruefstand pi03: 100 statt 106px). */
    .up-intent, .up-branded { display: inline-flex; align-items: center; gap: 6px; height: var(--up-h-chip);
      min-width: 0; max-width: 100%; box-sizing: border-box; font-family: inherit; font-size: var(--up-fs-s);
      font-weight: var(--up-fw-m); line-height: 1; color: var(--vc-text); white-space: nowrap; }
    /* Schrift in der Hauptfarbe (14.10., Pruefauftrag "zu klein?"): gemessen stehen Topic- und
       Branded-Chip in der Tabelle mit 12px in der Hauptfarbe, das Intent-Label mit 12px in Grau --
       gleich gross, aber eine Stufe schwaecher, und darum las es sich kleiner. Das Zeichen bleibt
       leise (--vc-fourth), Laden und "nicht zugeordnet" bleiben grau. */
    .up-intent > svg, .up-branded > svg { width: 14px; height: 14px; flex: 0 0 14px; color: var(--vc-fourth); }
    .up-intent-lbl, .up-branded-lbl { overflow: hidden; text-overflow: ellipsis; }
    .up-intent.is-pending, .up-intent.is-failed { color: var(--vc-fourth); }
    /* "Classifying" glaenzt (13.10., zweite Runde: "wie im Loader bei Mira"; die erste Fassung war
       ausdruecklich statisch). Dieselbe Mechanik wie .am-run-txt in ask-mira.css: der Text wird
       zur Maske, ein heller Streifen laeuft darueber -- Werte von dort uebernommen (100 Grad,
       30/50/70 Prozent, 1.56s, rueckwaerts). Nur der Text, nicht das Zeichen: die Uhr bleibt
       ruhig. Ohne Bewegungswunsch: still, in der leisen Farbe. */
    /* DER GLANZ ALS BAUSTEIN (22.10. abends, Lukas: "dieser shimmer sollte auch im core sein"). Bis dahin stand
       er dreimal: hier am Intent, in ask-mira.css am laufenden Schritt und in page-audit.css. Jetzt EINE Regel:
       .up-glanz auf den Text, der gerade arbeitet (Mira: der laufende Satz, die Denk- und die Schreibzeile).
       Der Text wird zur Maske, ein heller Streifen (--vc-glanz) laeuft darueber; die Schriftfarbe bleibt die
       des Elements (background-color: currentColor). Werte unveraendert: 100 Grad, 30/50/70 Prozent, ein
       Durchlauf --up-lauf-glanz (1,56 s), rueckwaerts. Ohne Bewegungswunsch still in der eigenen Farbe.
       Der Intent nimmt dieselbe Regel; seinen Versatz gegen das Neuzeichnen setzt core.js (glanzVerzug). */
    .up-glanz, .up-intent.is-pending .up-intent-lbl {
      background-image: linear-gradient(100deg, transparent 30%, var(--vc-glanz) 50%, transparent 70%);
      background-color: currentColor;
      background-size: 200% 100%; background-repeat: no-repeat; background-position: -120% 0;
      -webkit-background-clip: text; background-clip: text;
      -webkit-text-fill-color: transparent;
      animation: upGlanz var(--up-lauf-glanz) linear infinite reverse;
    }
    @keyframes upGlanz { from { background-position: -120% 0; } to { background-position: 220% 0; } }
    /* Der alte Name der Keyframes (Vertrag); dieselbe Bewegung. */
    @keyframes upIntentGlanz { from { background-position: -120% 0; } to { background-position: 220% 0; } }
    @media (prefers-reduced-motion: reduce){
      .up-glanz, .up-intent.is-pending .up-intent-lbl { background: none; animation: none; -webkit-text-fill-color: currentColor; }
    }
    /* Vom Nutzer gesetzt (14.10.: UserCog in einem kleinen Chip mit Grund, statt des Stifts): 12px
       Zeichen, 2px Polster -- ein 16er Kaestchen, Radius --up-r-xs. Der Grund ist eine Mischung
       aus der Schrift und nicht --up-sel-bg: der klickbare Knopf traegt beim Ueberfahren genau
       --up-sel-bg, und der Chip waere darin verschwunden. Abstand zum Text: der gap des Labels. */
    .up-intent-user { display: inline-flex; padding: 2px; border-radius: var(--up-r-xs); color: var(--vc-third);
      background: color-mix(in srgb, var(--vc-text) 7%, transparent); }
    .up-intent-user svg { width: 12px; height: 12px; display: block; }
    :is(.up-intent, .up-branded).is-klick { border: 0; background: transparent; padding: 0 6px;
      border-radius: var(--up-r-s); cursor: pointer; transition: background-color var(--up-t-1) ease; }
    :is(.up-intent, .up-branded).is-klick:hover, :is(.up-intent, .up-branded).is-klick[aria-expanded="true"] { background: var(--up-sel-bg); }
    :is(.up-intent, .up-branded).is-klick:focus-visible { outline: var(--up-focus-w, 2px) solid var(--vc-text); outline-offset: 0; }
    /* BRANDED ALS EIGENER CHIP (14.10., eigene Spalte statt neben dem Intent): hellgrauer Grund
       (--up-sel-bg), 8px Polster, Radius 6 aus der Skala, Schrift in der Hauptfarbe -- es ist eine
       Aussage ueber den Prompt, kein leiser Status. Zwischen Wort und Zeichen ein senkrechter
       Strich, 12px hoch wie die Zeichen daneben. Klickbar wird der Grund beim Ueberfahren eine
       Stufe dunkler, statt wie das Intent-Label erst einen Grund zu bekommen. */
    .up-branded { padding: 0 8px; border-radius: var(--up-r-s); background: var(--up-sel-bg); color: var(--vc-text); }
    :is(.up-branded).is-klick { padding: 0 8px; background: var(--up-sel-bg); }
    :is(.up-branded).is-klick:hover, :is(.up-branded).is-klick[aria-expanded="true"] {
      background: color-mix(in srgb, var(--vc-text) 10%, var(--up-sel-bg)); }
    .up-branded-sep { flex: 0 0 1px; width: 1px; height: 12px; background: color-mix(in srgb, var(--vc-text) 18%, transparent); }
    /* Nicht branded: ohne Chip, nur der leise Ausloeser fuer das Menue ("–" oder "Not branded"). */
    :is(.up-branded).is-leer, :is(.up-branded).is-leer.is-klick { background: transparent; color: var(--vc-fourth); padding: 0 6px; }
    :is(.up-branded).is-leer.is-klick:hover, :is(.up-branded).is-leer.is-klick[aria-expanded="true"] { background: var(--up-sel-bg); }
    /* Branded: das Wort und die Favicons der genannten Marken. 14px mit Radius 4 -- die kleinste
       Logostufe, die als Logo noch lesbar ist; der Anfangsbuchstabe liegt darunter und wird nur
       sichtbar, wenn das Bild ausfaellt (has-img faellt dann weg). */
    .up-branded-favs { display: inline-flex; align-items: center; gap: 2px; }
    .up-branded-fav { position: relative; width: 14px; height: 14px; flex: 0 0 14px; border-radius: var(--up-r-xs);
      overflow: hidden; display: inline-grid; place-items: center; background: var(--vc-heading-bg);
      box-shadow: inset 0 0 0 1px var(--vc-border); }
    .up-branded-fav img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
    .up-branded-ini { font-size: var(--up-fs-xs); font-weight: var(--up-fw-h); color: var(--vc-third); }
    .up-branded-fav.has-img .up-branded-ini { visibility: hidden; }
    .up-branded-mehr { font-size: var(--up-fs-xs); color: var(--vc-fourth); font-variant-numeric: tabular-nums; padding-left: 2px; }
    /* Im Menue: das Zeichen vor jedem Wert in derselben Farbe wie am Label, 16px wie die Haken. */
    .up-intent-opt .up-pop-opt-l > svg { width: 16px; height: 16px; flex: 0 0 16px; color: var(--vc-fourth); }
    /* Die Herkunft unter den Werten (14.10., Entwurf A): eine leise Platte im Ton des
       Tabellenkopfs, ohne Trennlinie darueber -- sie gehoert zur Wahl. 40px hoch wie eine Zeile
       mit Knopf, damit KI- und Hand-Fall gleich hoch stehen und das Menue beim Wechsel nicht
       springt. Radius 10 in der 14er-Huelle mit 8 Innenabstand (konzentrisch gerundet). */
    .up-intent-herkunft { display: flex; align-items: center; gap: 8px; margin-top: 4px; min-height: 40px;
      box-sizing: border-box; padding: 5px 5px 5px 9px; border-radius: var(--up-r-l);
      /* Gestrichelt statt getoent (14.10. gewuenscht: "hebt sich besser ab"). Die Polsterung ist
         um den Rahmen kleiner, damit Inhalt und Hoehe (40) genau stehen bleiben. */
      border: 1px dashed var(--vc-border);
      font-size: var(--up-fs-s); font-weight: var(--up-fw-m); color: var(--vc-third); white-space: nowrap; }
    .up-intent-herkunft-t { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
    /* Mehr Luft zwischen "Von dir gesetzt" und dem Knopf (14.10.): 8 Abstand + 12 = 20. */
    .up-intent-herkunft .up-intent-reset { margin-left: 12px; }
    /* Das KI-Zeichen in der Akzentfarbe (gewuenscht 14.10.: "in der primary accent color"). */
    .up-intent-ki { display: inline-flex; color: var(--up-accent-ink); }
    .up-intent-ki svg { width: 14px; height: 14px; display: block; }
    /* Der Ruecksprung ist ein ECHTER Knopf: die Form von .up-btn-sec auf 28px gezogen (Hoehe aus
       der Skala, --up-h-seg), weiss auf der grauen Platte und mit der Tiefe e-1, die alles auf
       der Flaeche traegt. Radius: Platte 10 minus 6 Innenabstand = 4, auf die Stufe 6 gerundet. */
    .up-intent-reset { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; height: var(--up-h-seg);
      padding: 0 10px 0 8px; border: 1px solid var(--vc-border); border-radius: var(--up-r-s);
      background: var(--up-surface); box-shadow: var(--up-e-1); font: inherit; color: var(--vc-text); cursor: pointer; }
    .up-intent-reset svg { width: 14px; height: 14px; display: block; color: var(--up-accent-ink); }
    /* Hover wie der Geist-Sekundaerknopf (15.10.): nur die Flaeche, der Rand bleibt. */
    .up-intent-reset:hover { background: var(--up-knopf-hover); }
    /* Branded mit Unterzeile: der Grund, warum der Schalter so steht. */
    .up-branded-row .up-pop-label { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
    /* Der Halter des Menues (UC.makeIntentMenu): steht absolut genau ueber dem Ausloeser (Lage aus
       JS), die Huelle haengt links buendig darunter. Der Halter faengt keine Klicks, sonst laege
       ein unsichtbarer Kasten ueber dem Label, solange das Menue zu ist. Das Aufklappen nach oben
       (is-oben, Sammelleiste am unteren Fensterrand) gibt es nur in der Prompt-Tabelle und steht
       in prompts-table.css. */
    .up-intentwrap { position: absolute; z-index: 60; pointer-events: none; }
    /* max-content mit 240 als Untergrenze: auf Deutsch ist die Herkunftszeile ("Von dir gesetzt"
       plus "Der KI ueberlassen") breiter als 240 und brach sonst in zwei Zeilen um. */
    /* Obergrenze 360 (14.10., vorher 320): mit dem Knopf "Automatisch erkennen" und 20px Luft davor
       wurde "Von dir gesetzt" auf Deutsch zu "Von dir ges..." gekuerzt -- gemessen 330px Bedarf. */
    .up-intentwrap .up-intentpop { left: 0; right: auto; width: max-content; min-width: 240px; max-width: min(360px, 92vw); pointer-events: none; }
    .up-intentwrap .up-intentpop.is-shown { pointer-events: auto; }
    /* Die Platte der Intent-Erklaerkarte: die Customer Journey als Weg. Drei Zeilen fuer die Stufen,
       ein senkrechter Strich verbindet ihre Zeichen (die Reise), "Other" abgesetzt ohne Strich.
       Farben der Platte (--vc-inverse-bg auf --vc-inverse-ink), der Nebentext gedaempft wie
       .up-explain-t in der Karte. */
    .up-intent-reise { display: flex; flex-direction: column; gap: 9px; width: 100%; }
    .up-intent-reise-st { position: relative; display: grid; grid-template-columns: 16px auto; column-gap: 9px; row-gap: 1px; align-items: center; }
    .up-intent-reise-ic { grid-row: 1 / span 2; display: inline-flex; align-self: start; padding-top: 2px; }
    .up-intent-reise-ic svg { width: 16px; height: 16px; display: block; }
    .up-intent-reise-name { font-size: var(--up-fs-s); font-weight: var(--up-fw-h); line-height: 1.2; }
    .up-intent-reise-will { font-size: var(--up-fs-xs); line-height: 1.3; color: color-mix(in srgb, var(--vc-inverse-bg) 62%, transparent); }
    /* Der Weg: von der Mitte eines Zeichens bis zum naechsten -- nicht bei der letzten Stufe und
       nicht bei "Other". 9px gap + Zeilenhoehe ueberbrueckt top: 22px bis bottom: -9px. */
    .up-intent-reise-st:not(.is-rest):not(:nth-last-child(2))::before { content: ""; position: absolute; left: 7.5px; top: 21px; bottom: -8px;
      width: 1px; background: color-mix(in srgb, var(--vc-inverse-bg) 30%, transparent); }
    .up-intent-reise-st.is-rest { padding-top: 7px; border-top: 1px solid color-mix(in srgb, var(--vc-inverse-bg) 18%, transparent); }
    /* Karte EINES Intents: seine Stufe voll, die anderen zurueckgenommen -- der Weg bleibt lesbar,
       der gemeinte Schritt steht vorn. */
    .up-intent-reise.has-aktiv .up-intent-reise-st:not(.is-aktiv) { opacity: .4; }
    /* Ausloeser kurz weg (Nachladen): unsichtbar und nicht klickbar, bis er wieder da ist. */
    .up-intentwrap.is-verwaist { visibility: hidden; }
    /* Am rechten Rand haengt das Menue an der rechten Kante des Ausloesers (UC.makeIntentMenu, seiteWaehlen). */
    .up-intentwrap.is-rechts .up-intentpop { left: auto; right: 0; transform-origin: top right; }

    /* Unten 8 statt 14: derselbe Abstand unter der Ueberschrift wie bei .up-pop-head. Es ist
       dieselbe Rolle, und zwei Werte dafuer waren nur gewachsen. Oben bleiben die 8, die
       zusammen mit dem Trenner darueber (7px Aussenabstand) die Zaesur bilden. */
    .up-pop-sub { padding: 8px 10px 8px; }

    /* ---- 4px ZWISCHEN DEN ZEILEN, in JEDEM Dropdown (21.09. angefordert) -----------------
       Gemessen war es vorher NULL: die Zeilen trugen nur ihre eigene Polsterung (9px 10px) und
       klebten aneinander. Mit 9px Radius und einem Hover-Grund, der die ganze Zeile faerbt,
       sahen zwei benachbarte Zeilen im Hover aus wie ein Block.

       Nachbarschaftsregel und KEIN gap am Menue: ein gap am Elternteil faenge auch die
       Ueberschrift und die Trenner mit ein -- die haben ihre eigenen Abstaende (4/10/8 bzw.
       7px Aussenabstand), und die waeren dann zweimal da. So bekommt der Abstand nur, was
       zwischen ZWEI Zeilen liegt: nie vor der ersten, nie nach der letzten, nie um einen
       Trenner herum.

       .up-dense-btn ist ABSICHTLICH nicht dabei: das ist der waagerechte Segmentstreifen
       (siehe .up-dense bei den Segmentleisten), dort waere ein senkrechter Abstand sinnlos. */
    :is(.up-pop-opt, .up-pop-row) + :is(.up-pop-opt, .up-pop-row) { margin-top: 4px; }
    .up-dense { display: inline-flex; width: 100%; padding: 0; gap: 0; background: var(--vc-switch-rail); border-radius: var(--up-r-m); height: 34px; }
    /* 0 8px: die Beschriftung steht nicht mehr auf der Kante ihrer Haelfte. Wirkt dort, wo der
       Schalter sich nach seinem Inhalt richtet (die Zeile im Einstellungsfenster); in einem
       Chart-Menue hat .up-dense width:100%, dort bleibt die Breite wie sie war. */
    .up-dense-btn { flex: 1 1 50%; padding: 0 8px; height: 34px; border: 1px solid transparent; border-radius: var(--up-r-m); background: transparent;
      color: var(--vc-muted); cursor: pointer; font-family: inherit; font-size: 12px; font-weight: 500; line-height: 1;
      display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
    .up-dense-btn svg { width: 15px; height: 15px; }
    .up-dense-btn:hover { color: var(--vc-text); }
    .up-dense-btn.is-active { background: var(--vc-switch-pill); color: var(--vc-text); border-color: var(--vc-switch-border); }
    .up-root[data-theme="dark"] .up-dense-btn.is-active { background: var(--vc-switch-pill); border-color: var(--vc-switch-border); }
    /* icon-only variant (row-height 3-way switch): no label, so it never outgrows the menu */
    .up-dense-btn-icon { flex: 1 1 33.333%; padding: 0; gap: 0; }
    .up-dense-btn-icon svg { width: 16px; height: 16px; }

    /* ---------- chart settings (gear) trigger + its body-mounted dropdown ----------
       Shared by every Chart.js line-chart component (visibility-chart's own colors+line-width
       menu, citations-combo-chart's line-width-only menu) — one hover-reveal/open-state/
       transition-timing definition instead of a copy per component drifting out of sync, which is
       exactly how the pointer-events-fires-before-visible bug above got fixed in one place and
       then silently reintroduced in a duplicate.
       Deliberately keyed on each component's OWN existing button class (.vot-scale-btn,
       .ccl-settings-btn — add the next consumer's class to this same selector list, don't invent
       a shared marker class instead): visibility-chart's button already exists in every already-
       deployed Bubble page. A shared class name would only apply once every one of those pages'
       HTML elements gets manually re-pasted with the new markup — until then the button would
       silently lose ALL of this styling (which is exactly what happened the first time this was
       tried: the button rendered as a bare unstyled sliver). Targeting the class that's already
       there means the sizing/hover/position rules take effect immediately off the CDN pin alone;
       only the gear-vs-pencil SVG *content* genuinely requires a markup paste, because no amount
       of CSS can change what path data an inline <svg> contains.
       The MENU is body-mounted (component boxes are overflow:hidden — see visibility-chart.css's
       original comment on this), positioned in JS from the trigger's getBoundingClientRect(), so —
       living outside any .up-root — it redeclares the --vc- and --vc-switch- tokens here with
       this app's one universal light/dark palette (core.css's own .up-root tokens above) rather
       than per component, so every consumer's menu looks and themes identically by construction. */
    .vot-scale-btn, .ccl-settings-btn, .ubo-scale-btn {
      width: 28px; height: 28px; border-radius: var(--up-r-m); z-index: 5;
      border: 0; background: transparent; color: var(--vc-third);
      cursor: pointer; padding: 0; display: inline-flex; align-items: center; justify-content: center;
      opacity: 0; pointer-events: none;
      transition: opacity 120ms ease, color 120ms ease;   /* fast hide */
    }
    .vot-scale-btn svg, .ccl-settings-btn svg, .ubo-scale-btn svg { width: 14px; height: 14px; display: block; }
    /* Hovering the host panel reveals it, after a delay — the button itself and its own open state
       keep it visible with no delay once already shown, so it never flickers mid-interaction.
       pointer-events is listed with the SAME delay as opacity's delay PLUS DURATION (900+160=
       1060ms) on purpose: pointer-events isn't animatable, so without its own transition entry it
       flips to "auto" the INSTANT the hover starts regardless of what opacity is doing — letting
       hover (and the tooltip) fire on a button that's still transparent mid fade-in. 1060ms is
       where opacity actually finishes landing on 1. Belt-and-suspenders: core.js's tooltip
       delegation ALSO refuses to tip any [data-tip] element still at opacity:0, so even a stale
       CDN pin or a browser that doesn't recompute hover state the instant pointer-events flips
       can't show a tooltip over an invisible button either way. */
    .vot-panel-body:hover .vot-scale-btn, .combo-panel-body:hover .ccl-settings-btn,
    .ubo-panel-body:hover .ubo-scale-btn, .ubo-scale-btn:focus-visible, .ubo-scale-btn.is-open,
    .vot-scale-btn:focus-visible, .ccl-settings-btn:focus-visible,
    .vot-scale-btn.is-open, .ccl-settings-btn.is-open {
      opacity: 1; pointer-events: auto;
      transition: opacity 160ms ease 900ms, pointer-events 0s 1060ms, color 120ms ease;   /* delayed show */
    }
    .vot-scale-btn:hover, .ccl-settings-btn:hover, .vot-scale-btn.is-open, .ccl-settings-btn.is-open,
    .ubo-scale-btn:hover, .ubo-scale-btn.is-open { color: var(--vc-text); }
    /* Skeleton is loading -> the button (and, if it was somehow already open, its menu) hides
       outright, no hover-reveal override. The component toggles .is-line-loading on its OWN root
       (not this button) the same way it already tracks every other loading state, so this is a
       plain descendant rule, not new JS wiring. !important because the alternative is duplicating
       every opacity/pointer-events rule above at higher specificity for one edge case. */
    .vot-root.is-line-loading .vot-scale-btn, .combo-root.is-line-loading .ccl-settings-btn,
    .ubo-root.is-line-loading .ubo-scale-btn {
      opacity: 0 !important; pointer-events: none !important;
    }
    @media (prefers-reduced-motion: reduce){ .vot-scale-btn, .ccl-settings-btn, .ubo-scale-btn { transition-duration: 1ms; } }

    /* Wer eine Palette aufmacht, macht sie VOLLSTAENDIG auf. Dieses Menue haengt am body und
       erbt darum nichts von .up-root. Die vier Geometrie-Tokens fehlten: --up-dd-radius,
       --up-dd-pad und --up-dd-shadow loesten nicht auf, also stand das Panel scharfkantig,
       schattenlos und ohne Innenabstand da, und --up-sel-bg fehlte, also faerbte sich keine
       Zeile beim Ueberfahren. Werte 1:1 aus dem .up-root-Block oben. */
    /* Ein Dropdown-Knopf, dessen Inhalt noch unterwegs ist (makePopover cfg.canOpen).
       Gedimmt statt versteckt: der Knopf soll an seinem Platz bleiben, damit die Werkzeugleiste
       nicht springt, sobald die Daten da sind. */
    .up-iconbtn.is-disabled, .up-filter-btn.is-disabled, .up-ment-btn.is-disabled,
    .up-ddtrigger.is-disabled, .combo-filter-btn.is-disabled, .ccl-settings-btn.is-disabled {
      opacity: .45; pointer-events: none; cursor: default;
    }

    .up-scale-menu {
      --vc-text: #171717; --vc-muted: #4d4d4d; --vc-third: #7d7d7d; --vc-fourth: #8f8f8f; --vc-border: #dcdbdd;
      --vc-heading-bg: #f9f8f9; --vc-hover-border: #6f6f6f; --vc-bg: #ffffff;
      --vc-switch-bg: #f9f8f9; --vc-switch-border: #e4e2e4; --vc-switch-pill: #ffffff;
      --vc-switch-rail: var(--up-seite-bg);
      --up-dd-radius: 12px; --up-dd-pad: 8px; --up-dd-shadow: var(--up-e-2);
      --up-sel-bg: #f4f2f4;
      position: fixed; z-index: 100050; width: 248px;
      background: var(--vc-bg); border: 1px solid var(--vc-border); border-radius: var(--up-dd-radius);
      box-shadow: var(--up-dd-shadow); padding: var(--up-dd-pad);
      color: var(--vc-text); font-family: Geist, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      opacity: 0; transform: translateY(-4px) scale(.985); pointer-events: none;
      transition: opacity 140ms ease, transform 140ms ease;
      display: flex; flex-direction: column; gap: 4px;
    }
    .up-scale-menu.is-shown { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
    /* Im grauen Hell die leise Flaeche der App (17.10.), nach dem Muster der core-Dialoge
       (.up-topicmodal-card): die Grundregel setzt --vc-heading-bg selbst (#f9f8f9) und erreicht die
       Marke --up-leise-grau (#f2f2f2) sonst nicht. Das Menue traegt data-theme selbst, also zieht
       --up-knopf-hover hier mit. --vc-switch-bg bleibt: auch an der Wurzel aendert das graue Hell
       ihn nicht. Nur mit dem Marker am <html>, nur im Hellen; (0,3,1) gegen (0,1,0). */
    html[data-up-hell="grau"] .up-scale-menu:not([data-theme="dark"]) { --vc-heading-bg: var(--up-leise-grau); }
    /* Same brightened dark border as every other dropdown menu in the app — a plain --vc-border
       black-ish tone reads as blurry/edgeless against a dark surface. */
    .up-scale-menu[data-theme="dark"] {
      /* Rahmen einer SCHWEBENDEN Schicht: border-tertiary (#3e3e44), nicht -secondary. Im
         Hellen (#dcdbdd) ist das seit dem 07.09. so, im Dunkeln stand hier noch der Wert fuer
         Rahmen AUF Karten. Eine Schicht, die ueber allem schwebt, braucht die kraeftigere Kante,
         sonst loest sie sich gegen die Seite auf. Dieselbe Korrektur an allen sechs Stellen, die
         eine eigene Palette aufmachen: dieses Menue, die Themen-Karte weiter unten, die Bulkbar
         der Prompts-Tabelle, die zwei Panels von brands-overview, der Tooltip der Matrix und der
         Markendialog. Der letzte trug sogar noch #23252a -- border-primary aus der Zeit vor
         Linears Leiter, auf #1c1c1f also praktisch unsichtbar. */
      /* Seit dem 15.10. Geists dunkle Skala (Dunkel nach Geist, Herleitung am dunklen .up-root):
         Grund Background 1, Rand der schwebenden Schicht (Gray 500), Kopf- und Hoverflaeche Gray
         100, Auswahl Gray 200, Schiene und Pille Gray Alpha 100/200.
         ALLE Werte muessen HIER stehen und nicht nur im dunklen Block an :root: die helle Grundregel
         dieses Menues (.up-scale-menu, 0,1,0) steht weiter unten in der Datei als jener Block
         (ebenfalls 0,1,0) und gewaenne sonst ueber die Reihenfolge -- gemessen am 15.10. am Topic-
         Fenster, das dieselbe Bauart hat: die Seitenspalte stand hell im dunklen Fenster.
         Eine Stufe ruhiger (15.10. abends, Herleitung am dunklen .up-root): Grund #0f0f0f, Rand
         #333333, Kopf/Hover #1f1f1f, Auswahl #242424, Schalterrand #222222; aus bleibt #454545. */
      --vc-text: #ededed; --vc-muted: #c9c9c9; --vc-third: #a1a1a1; --vc-fourth: #7d7d7d; --vc-border: #333333;
      --vc-heading-bg: #1f1f1f; --vc-hover-border: #707070; --vc-bg: #0f0f0f; --up-switch-off: #454545;
      --vc-switch-bg: #1f1f1f; --vc-switch-border: #222222;
      --vc-switch-rail: var(--up-seite-bg); --vc-switch-pill: rgba(255,255,255,.09);
      --up-dd-shadow: var(--up-e-2); --up-sel-bg: #242424;
    }
    /* .up-dense-btn.is-active's own dark override above is scoped to .up-root — this menu lives
       outside it, so the Line Width switch needs its own copy of the same colors. */
    .up-scale-menu[data-theme="dark"] .up-dense-btn.is-active { background: var(--vc-switch-pill); border-color: var(--vc-switch-border); }
    /* Und aus demselben Grund der Knopf des Schalters: seine dunklen Farben haengen an
       .up-root[data-theme="dark"], und dieses Menue haengt am <body>. Ohne die zwei Zeilen bliebe
       der Knopf im Dunklen weiss -- auf der hellen Bahn des eingeschalteten Zustands ist er dann
       nicht mehr zu sehen. Die Bahn selbst zieht von allein mit, sie liest --vc-border und
       --vc-text, und die stehen oben im Menue. */
    .up-scale-menu[data-theme="dark"] .up-switch::after { background: #e0e0e0; }
    .up-scale-menu[data-theme="dark"] .up-switch.is-on::after { background: var(--up-accent-fg); }
    @media (prefers-reduced-motion: reduce){ .up-scale-menu { transition-duration: 1ms; } }
    .up-scale-menu * { box-sizing: border-box; }

    /* Colour-scale option rows. These live here — not in a component file — because UC.makeScaleMenu
       (core.js) is what emits them: JS in core whose class names are styled in ONE component's CSS
       is exactly the split that has already produced a "renders as unstyled text" bug twice. They
       were .vot-scale-* in visibility-chart.css until brands-overview became the second consumer.
       Renaming to .up-* is safe despite already-deployed pages: this menu is built at RUNTIME by
       core.js, so no pasted Bubble markup contains these class names (unlike the TRIGGER button,
       which does — hence .vot-scale-btn keeps its component-specific name in the rule above). */
    /* Granularity switcher (Day/Week/Month). Lives here because visibility-chart AND
       brands-overview both show it; the vc- prefix is kept rather than renamed because it is in
       already-deployed visibility-chart markup (same reasoning as .vot-scale-btn above). */
    /* 26px hoch und nicht 32, Polster 8 statt 12, Flaeche --vc-switch-rail: die Fassung, die in der
       Hero-Sektion der Landingpage entstanden ist und von dort uebernommen wurde. Alle drei Masse
       zusammen (STYLEGUIDE 1) -- eine 32er-Pille mit 8px Polster waere zu weit, eine 26er mit 12px
       zu eng. Die Schrift bleibt bei 12px/500: die 11px im Schaustueck kommen aus dessen eigener
       Verkleinerung ALLER Schriften, nicht aus dem Bauteil.
       align-self: flex-start, damit der Umschalter oben in seinem Kasten sitzt und nicht mittig --
       neben einer Ueberschrift mit Unterzeile sass er sonst auf halber Hoehe. */
/* .cc-gran ist derselbe Umschalter im Combo-Chart und steht hier als ALIAS, genau wie bei
   .up-seg/.cc-seg weiter unten: sein Markup liegt in der von Hand eingefuegten Bubble-Seite, ein
   Umbenennen wuerde sie zerlegen. Vorher stand eine wortgleiche Kopie in citations-combo-chart.css
   -- und jede Aenderung hier (Mindestbreite, Farbe) erreichte ihn deshalb nicht. */
.vc-gran, .cc-gran { display: inline-flex; padding: 0; gap: 0; background: var(--vc-switch-rail); border-radius: var(--up-r-m); border: 0; height: 26px; flex: 0 0 auto; align-self: flex-start; }
/* Im Combo-Chart steht er in einer Zeile mit Knoepfen und deshalb mittig. */
.cc-gran { align-self: center; }
/* min-width 26 = die Hoehe: mit den kurzen Beschriftungen D, W und M (core.js, stampGran) ist
   ein Knopf sonst so breit wie sein Buchstabe, und W waere breiter als D -- der Umschalter
   huepfte beim Wechsel. Quadratisch stehen die drei ruhig nebeneinander. */
    .vc-gran-btn, .cc-gran-btn { height: 26px; min-width: 26px; padding: 0 8px; border: 1px solid transparent; border-radius: var(--up-r-m); background: transparent;
      color: var(--vc-muted); cursor: pointer; font-family: inherit; font-size: 12px; font-weight: 500; line-height: 1;
      display: inline-flex; align-items: center; justify-content: center; }
    .vc-gran-btn:hover, .cc-gran-btn:hover { color: var(--vc-text); }
    .vc-gran-btn.is-active, .cc-gran-btn.is-active { background: var(--vc-switch-pill); color: var(--vc-text); border-color: var(--vc-switch-border); }
    .up-root[data-theme="dark"] .vc-gran-btn.is-active,
    .up-root[data-theme="dark"] .cc-gran-btn.is-active { background: var(--vc-switch-pill); border-color: var(--vc-switch-border); }
    .vc-gran-btn.is-disabled, .cc-gran-btn.is-disabled { opacity: 0.4; pointer-events: none; cursor: default; }

    .up-scale-opt { display: flex; flex-direction: column; gap: 8px; padding: 9px 10px; border-radius: var(--up-r-s); cursor: pointer; }
    .up-scale-opt:hover, .up-scale-opt.is-active { background: var(--up-sel-bg); }
    /* 4px zwischen den Zeilen wie an jedem Dropdown aus core (.up-optrow + .up-optrow) -- ohne
       stiessen zwei getoente Zeilen (Hover und gewaehlt) Kante an Kante aneinander. */
    .up-scale-opt + .up-scale-opt { margin-top: 4px; }
    .up-scale-opt-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
    .up-scale-opt-lbl { font-size: 13.5px; font-weight: 500; color: var(--vc-text); }
    /* Das Info-Zeichen an "Brand Colors". Es IST .up-th-info -- Groesse, Farbe und Hover kommen
       von dort, damit es dasselbe Zeichen ist wie an jedem Spaltenkopf. Hier stehen nur die zwei
       Unterschiede, die eine Menuezeile braucht:
         - dauerhaft sichtbar (am Spaltenkopf erscheint es erst beim Ueberfahren der Zeile) -- man
           soll sehen, DASS es eine Erklaerung gibt, bevor der Zeiger darauf steht,
         - margin-right: auto, damit der Haken an der rechten Kante bleibt wie ohne Zeichen.
       KEIN cursor: help. Der Zeiger wird in dieser App nie zum Fragezeichen -- das gilt hier
       genauso wie an den Spaltenkoepfen, die ebenfalls keinen setzen. */
    .up-scale-opt-info { opacity: 1; margin-left: 6px; margin-right: auto; flex: 0 0 auto; }
    .up-scale-opt .up-check { width: 16px; height: 16px; color: var(--vc-text); opacity: 0; flex: 0 0 auto;
      transform: scale(.7); transition: opacity var(--up-cb-t) var(--up-cb-ease), transform var(--up-cb-t) var(--up-cb-ease); }
    .up-scale-opt .up-check svg { width: 16px; height: 16px; display: block; }
    .up-scale-opt.is-active .up-check { opacity: 1; transform: scale(1); }
    /* "Nebeneinander, etwas übereinandergeschoben, mit sichtbarem Gap dazwischen" — each dot overlaps
       the previous by a third, with a background-colored ring (border) standing in for the gap, so the
       seam between colors stays visible even though the dots physically overlap (the same idiom as an
       avatar stack). */
/* ---------- Chart-Settings am Chart: weg ----------
   Die Wahl steht im Einstellungsfenster (Seite "Charts"). Der Knopf hier stammt aus dem
   handgemachten Bubble-Markup der Chart-Elemente und ist vom Pin aus nicht zu entfernen -- also
   wird er ausgeblendet. NUR das Chart-Zahnrad: .uhm-set-btn (Performance Radar) und
   .uo-settings-btn (Opportunities-Board) sind andere Menues und bleiben.
   display:none und nicht visibility: der Knopf soll auch keinen Platz in der Werkzeugzeile
   beanspruchen. */
.vot-scale-btn, .ubo-scale-btn, .ccl-settings-btn { display: none !important; }

/* ---------- Filter nur als Funktion ----------
   Gesetzt von core (nurFunktionLauf), wenn der Wirt des Filters 1x1 gross ist: dann liegt er nur
   als Funktion auf der Seite, damit die Filterleiste ihn einziehen kann, und sein eigener Trigger
   soll weder zu sehen noch zu treffen sein.
   Nicht display:none -- ein Element ohne Kasten laesst sich nicht messen, und die Leiste prueft
   beim Einziehen die Groesse des Triggers. Also die uebliche Form fuer "da, aber unsichtbar":
   1x1, weggeschnitten, ohne Zeiger. Das MENUE bleibt unberuehrt: es haengt absolut und wird von
   der Leiste geoeffnet, sobald sie den Filter zu sich geholt hat -- und dann ist diese Klasse
   ohnehin wieder weg. */
.up-nurfunktion > .utf-trigger,
.up-nurfunktion > .umf-trigger,
.up-nurfunktion > .umk-trigger,
.up-nurfunktion .utf-trigger,
.up-nurfunktion .umf-trigger,
.up-nurfunktion .umk-trigger {
  position: absolute; width: 1px; height: 1px; min-width: 0; padding: 0; border: 0;
  opacity: 0; pointer-events: none; overflow: hidden; clip-path: inset(50%);
}
/* Die Wurzel selbst nimmt keinen Platz mehr ein: sonst steht in dem 1x1-Kasten ein 32px hoher
   Block, der ueber den Rand hinausragt und darunterliegende Bedienelemente abfaengt. */
/* Die Wurzel KLEMMT jetzt (overflow:hidden). Vorher stand hier visible, damit ein Menue
   herausragen darf -- aber genau dadurch ragte auch alles andere heraus und war sichtbar und
   treffbar. Ein Filter, der nur als Funktion daliegt, oeffnet sein Menue nie an dieser Stelle:
   geoeffnet wird er, nachdem die Filterleiste ihn zu sich geholt hat, und dann ist diese Klasse
   weg. */
.up-nurfunktion { width: 1px; height: 1px; overflow: hidden; pointer-events: none; }
/* Am MENUE nur, wenn es OFFEN ist. Hier stand pointer-events:auto ohne Bedingung -- und das war
   ein Fehler mit Folgen: ein geschlossenes Menue traegt pointer-events:none, damit es nicht im Weg
   ist, und diese Regel hat das aufgehoben. Der Filter liegt als 1x1 auf der Seite, sein Menue
   haengt absolut und ragt darueber hinaus, und ein Klick irgendwo in dem Bereich landete in einer
   unsichtbaren Menuezeile -- gemeldet als "ich klicke da irgendwo und der Models-Filter setzt
   sich". Genau das.
   is-shown setzt makePopover beim Oeffnen; solange es fehlt, faengt das Menue nichts. */
.up-nurfunktion .utf-menu.is-shown,
.up-nurfunktion .umf-menu.is-shown,
.up-nurfunktion .umk-menu.is-shown { pointer-events: auto; }

/* ---------- Flaggenplaettchen ----------
   Aus filters/markets-filter.css hierher gezogen, als die Sprachauswahl im Einstellungsfenster den
   zweiten Abnehmer bekam. Werte unveraendert.
   Der Laendercode liegt UNTEN und die Flagge deckt ihn zu, damit eine Flagge, die 404 liefert,
   "DE" zeigt statt eines kaputten Bildes -- daher beide Ebenen absolut in derselben Box.
   Das Plaettchen ist BREITER als hoch: Flaggen sind Querformat, eine quadratische Box wuerde sie
   entweder zu einem Streifen quetschen oder beschneiden. Die Groesse setzt der Aufrufer. */
.up-flag { position: relative; display: inline-flex; flex: 0 0 auto; overflow: hidden; }
.up-flag-fb {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: var(--vc-heading-bg); color: var(--vc-third);
  font-size: 9px; font-weight: 600; line-height: 1; letter-spacing: 0.02em;
}
/* cover, nicht contain: eine Flagge soll ihr Plaettchen fuellen wie in jeder Flaggenliste, und die
   Seitenverhaeltnisse der Laender gehen weit genug auseinander, dass contain Luecken laesst. */
.up-flag-img { position: relative; width: 100%; height: 100%; object-fit: cover; display: block; }
.up-flag.has-img { background: none; }
.up-flag.has-img .up-flag-fb { display: none; }

    .up-scale-dots { display: flex; }
    .up-scale-dot { width: 18px; height: 18px; border-radius: 50%; flex: 0 0 auto;
      border: 2px solid var(--vc-bg); margin-left: -6px; }
    .up-scale-dot:first-child { margin-left: 0; }

    .up-pop-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 10px; border-radius: var(--up-r-s); }
    .up-pop-row:hover { background: var(--vc-heading-bg); }
    .up-pop-label { font-size: 13.5px; font-weight: 500; color: var(--vc-text); }
    /* Eine gewaehlte Spalte, die die Breite verdraengt (UC.makeColumns, 14.10.): der Name bleibt
       in der Schrift der anderen, darunter in Nebentext der Grund. Kein eigener Zustand am
       Schalter -- der steht auf aus, weil die Spalte nicht zu sehen ist. */
    .up-pop-row.is-platzlos .up-pop-label { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
    .up-pop-hint { font-size: var(--up-fs-s); font-weight: var(--up-fw-n); color: var(--vc-third); white-space: nowrap; }
    /* DIE AUSGESCHALTETE SCHIENE, und warum sie nicht mehr --vc-border bzw. rgba(42,42,42,.6)
       ist: im Dunkeln war dieser Wert BUCHSTABENGLEICH mit --vc-heading-bg -- also mit dem Grund
       jeder Kopfzeile, jeder Seitenspalte und jedes Menuegrunds, auf dem ein Schalter sitzt. Der
       ausgeschaltete Schalter war dort unsichtbar; gemeldet am 06.09. fuer die Spalte des
       Kalenders ("kaum vom Hintergrund der Sidebar zu unterscheiden"), gilt aber ueberall.
       Hier und nicht im Kalender, weil es EIN Bauteil ist: ein Schalter, der an einer Stelle
       anders aussieht als an der naechsten, ist der Anfang des Auseinanderlaufens.
       16 bzw. 18 Prozent der Textfarbe -- in beiden Themen rund 34 Stufen Abstand zum Grund, und
       weit genug von der eingeschalteten Schiene entfernt (die traegt volle Textfarbe). */
    /* FESTE WERTE, KEIN color-mix(var(...)). Der erste Anlauf hier mischte aus --vc-text, und das
       ist in Chrome nicht verlaesslich: ein Element, das VOR dem Setzen des Themas gerendert
       wurde, behaelt den alten Wert -- auch dann noch, wenn data-theme von Hand weg und wieder
       hin gesetzt wird. Gemessen im Prueftand: derselbe Schalter meldete oklab(0.238 / 0.16),
       ein frischer Klon an derselben Stelle color(srgb 0.878 / 0.18). Auf der Seite heisst das:
       jeder Schalter, der beim Themenwechsel schon da ist, bleibt in der alten Farbe stehen.
       Deshalb steht im Bestand ringsum ueberall ein fester Wert je Thema -- das war kein Zufall.
       Die Zahlen sind die gemessenen Mischungen von vorher: #d6d6d4 liegt 41 Stufen unter Weiss
       und 31 unter dem Kopfzeilengrund, #444 liegt 41 ueber dem dunklen Panelgrund. */
    .up-switch { width: 38px; height: 22px; border-radius: var(--up-r-voll); background: var(--up-switch-off, #d6d6d4); position: relative; flex: 0 0 auto; transition: background 160ms ease; cursor: pointer; }
    /* [data-theme="dark"] auf IRGENDEINEM Vorfahren, nicht nur auf .up-root -- genau wie beim
       Topic-Chip weiter oben. Ein Schalter steht auch in einem Portal am Koerper (das
       Einstellungs-Popover von Opportunities zum Beispiel), und das ist keine .up-root. Solange
       hier ein TOKEN stand (--vc-border), fiel das nicht auf: Token erben ueber die
       Attributgrenze. Seit dem 07.09. steht hier ein fester Wert, und fest heisst: er erbt nicht.
       Im Portal stand der Schalter dadurch auf der hellen Schiene -- gemessen als
       rgb(214,214,212) mitten im dunklen Menue. */
    /* Die zwei Regeln bleiben als Sicherung fuer eine Stelle, an der der Token nicht ankommt --
       kosten nichts und schaden nicht. Die Arbeit macht jetzt der Token oben. */
    .up-root[data-theme="dark"] .up-switch,
    [data-theme="dark"] .up-switch { background: var(--up-switch-off, #444444); }
    /* Der Knopf: e-1 ohne Rueckfall (28.09.). In den Menues am <body> galt bisher der Rueckfall
       0 1px 2px .2 -- dreimal so kraeftig wie derselbe Schalter in einer Wurzel. Seit die Tiefen an
       :root stehen, sehen beide gleich aus. */
    .up-switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: var(--up-r-voll); background: #fff; box-shadow: var(--up-e-1); transition: transform 160ms ease; }
    .up-switch.is-on { background: var(--up-accent); }
    /* dark base .up-switch rule out-specifies .up-switch.is-on, so the ON track needs an
       explicit dark rule — otherwise it stays the dark off-colour.
       HIER STAND #e0e0e0 FEST, und das war der gemeldete Fehler (19.09.: "die Flaeche, die da
       aktuell in Fontcolor ist, auch als Primaercolor setzen -- im Lightmode hast du es korrekt
       gemacht, im Darkmode nicht"). Der Token traegt im Dunkeln von sich aus #e0e0e0, am
       Standard aendert sich also nichts; bei einer gewaehlten Akzentfarbe folgt die Bahn ihr
       jetzt auch im dunklen Thema. */
    .up-root[data-theme="dark"] .up-switch.is-on,
    [data-theme="dark"] .up-switch.is-on { background: var(--up-accent); }
    .up-switch.is-on::after { transform: translateX(16px); }
    .up-root[data-theme="dark"] .up-switch::after,
    [data-theme="dark"] .up-switch::after { background: #e0e0e0; }
    /* Der Knopf liegt AUF der eingeschalteten Bahn, traegt also --up-accent-fg wie jede Schrift
       auf einer Akzentflaeche. Im Standard ist das #1b1b1b statt der frueheren #1f1f1b -- vier
       Stufen, nicht sichtbar; bei Indigo oder Azur ist es weiss statt dunkel, und das ist der
       Unterschied zwischen sichtbarem und unsichtbarem Knopf. */
    .up-root[data-theme="dark"] .up-switch.is-on::after,
    [data-theme="dark"] .up-switch.is-on::after { background: var(--up-accent-fg); }

    /* ---------- brand mentioned toggle ---------- */

    

    

    

    

    /* ---------- table ---------- */
    .up-box { border: 1px solid var(--vc-border); background: var(--vc-bg); border-radius: var(--up-r-2xl); overflow: hidden; }
    /* ---------- sticky header (default on >=1000px page width, off via data-sticky="no") ----------
       Seit dem 16.10. auch darunter, aber nur fuer die eine Leiste (data-leiste) und Tabellen mit
       klebendem Fuss (data-kleb-fuss): dort kleben Leiste und Spaltenkopf auf dem Telefon wie breit
       (makeSticky, applySticky); der Fuss bleibt dort im Fluss (klebFuss, klebBreit).
       Both the component head and the column header stick to the scrolling ancestor. overflow:hidden
       on .up-box would trap the column header, so in sticky mode it becomes visible and the visible
       border moves onto the header and body (see below). Backgrounds must be OPAQUE or rows bleed
       through while scrolling underneath. */
    .up-sticky .up-head {
      position: sticky; top: var(--up-sticky-top); z-index: 6;
      /* DIE FLAECHE MUSS DECKEND SEIN, sonst laufen beim Scrollen die Zeilen sichtbar hinter der
         angehefteten Kopfzeile durch. Genau dafuer ist sie da.
         Sie war vom 08.09. bis zum 07.09. auf transparent: nach der Umstellung auf Linears Leiter
         passte --up-surface nicht mehr zur Seitenfarbe, die in Bubble eingestellt ist, und der
         Streifen wurde in sieben Komponenten als "dunkler Hintergrund hinter Heading und Toolbar"
         gemeldet. Die Diagnose war richtig, die Behandlung nicht -- ich habe die Flaeche entfernt,
         statt die beiden Werte anzugleichen. Nachgereicht vom Nutzer: "das war ein Fehler, du
         haettest sie einfach nur angleichen muessen."
         Jetzt steht die Seite in Bubble auf #ffffff (hell) und #101114 (dunkel), also genau auf
         --up-surface, und der Streifen ist unsichtbar UND tut seine Arbeit.
         WER --up-surface AENDERT, MUSS DIE SEITENFARBE IN BUBBLE MITAENDERN. Laufen die beiden
         auseinander, ist der Streifen sofort wieder als Kasten hinter Ueberschrift und
         Werkzeugleiste zu sehen -- das ist kein Fehler dieser Regel, sondern die Anzeige dafuer,
         dass die zwei Werte nicht mehr zusammenpassen. */
      background: var(--up-surface);
      margin-bottom: 0; height: 48px; padding-bottom: 16px;
    }
    /* Rows scroll up past the pinned header; without this they'd show behind a transparent page
       topbar. This opaque strip covers everything from the header up to the top of the viewport. */
    /* Nur solange die Leiste wirklich klebt (.is-geklebt, 13.10., siehe klebPruefenAlle in core.js):
       in Ruhe deckte der Streifen sonst den Inhalt UEBER der Tabelle zu. */
    .up-sticky.is-geklebt .up-head::before {
      content: ""; position: absolute; left: 0; right: 0; bottom: 100%;
      /* Exactly var(--up-sticky-top), not +16px on top of it -- that extra was meant as "a little
         more painted above, just in case", but it does not scale: at the app's usual sticky-top
         (171px) 16 extra is invisible, but at a small one (16px, an embed sitting near the very top
         of its own page) it DOUBLES the painted strip -- confirmed live as an obvious oversized top
         padding that visibly extends past the component even before any scrolling happens (this
         pseudo-element is unconditionally part of .up-head's box, not scroll-triggered). If a sliver
         of a row ever peeks through above a stuck header again, fix it with a couple of px here, not
         a flat +16 that stops matching the configured offset at all. */
      /* --up-sticky-cover statt --up-sticky-top: hat der Scrollport ein Polster, klebt die Leiste
         an dessen Polsterkante, der Streifen muss aber bis zur Rahmenkante reichen. core setzt
         den Wert gemessen (syncDeckstreifen) und NUR dann; ohne Polster gibt es ihn nicht und es
         bleibt beim bisherigen Wert. */
      height: var(--up-sticky-cover, var(--up-sticky-top)); background: var(--up-surface);
    }
    /* DAS DECK UEBER DER KLEBENDEN LEISTE (16.10.): "hier und da Rows hinter der klebenden Topbar,
       in den 16 px zwischen den Knoepfen und der Oberkante des Bildschirms". Der Streifen oben
       haengt an .is-geklebt, und die Klasse setzt JS im Scroll-Takt -- gemessen zweimal zu spaet:
       (1) nach einem Sprung (Ende-Taste, Scrollbalken, schneller Wurf) steht die Leiste schon,
       bevor der naechste Durchlauf die Klasse setzt -- 24 von 27 Messpunkten im Streifen trafen
       Zeilen; scrollt der Compositor selbst, sieht man das ein Bild und laenger; (2) dauerhaft,
       wenn applySticky waehrend des Klebens lief (Nachlauf der Kopfsuche, Groessenwechsel, ein
       neuer Seitenkopf): die gelernte Ruhelage war dann eine geklebte, und nach etwas
       Zurueckscrollen fiel die Klasse weg, obwohl die Leiste weiter klebte (24 Treffer bis oben).
       DAS DECK braucht keinen Zustand. Zwei leere Kaesten direkt VOR der Leiste (core.js
       klebDeckStellen), selbst sticky, mit derselben Flaeche: in Ruhe liegen sie deckungsgleich
       UNTER der Leiste (gleiche Ebene 6, im Baum davor -- die Leiste malt darueber), uebermalen
       also nichts. Klebt die Leiste, scrollen sie noch um ihre eigene Hoehe mit und rasten dann
       direkt ueber ihr ein, bis zur Oberkante des Scrollbereichs. Das macht sticky selbst, auf
       dem Compositor, im selben Bild wie die Leiste -- ohne Klasse, ohne Bildverzug. Eine Zeile
       kann dort erst ankommen, wenn sie Leiste und Spaltenkopf hinter sich hat; das Deck ist
       spaetestens nach seiner Hoehe (hoechstens die der Leiste) oben.
       ZWEI Kaesten, weil jeder hoechstens so hoch sein darf wie die Leiste (sonst schaute er in
       Ruhe unter ihr heraus): auf den Seiten reicht der untere (16 + 1), im Drawer liegen 72 ueber
       der Leiste (56 Polster unter dem Glaskopf + 16), dort nimmt der obere den Rest. Das 1px mehr
       liegt ausserhalb des Scrollbereichs (abgeschnitten) und sorgt dafuer, dass das Deck klebend
       immer hoeher steht als die Leiste -- daran liest klebPruefenAlle, ob sie klebt.
       Am Ende der Wurzel gehen die Kaesten MIT der Leiste: ihr Abstand unten (--up-thead-off, die
       Hoehe der Leiste) zaehlt fuer sticky mit, die Leiste nimmt ihn mit einem Rand oben zurueck --
       in der Summe aendert sich keine Lage. Bis 2 x Leistenhoehe deckt das Deck allein; was
       darueber liegt, deckt weiter der Streifen oben.
       Der Elternteil der Leiste ist in allen Tabellen eine Flex-Spalte (gemessen 16.10., auch die
       Buehne von Prompt Research): dort fallen Raender nicht zusammen. In einem Block-Elternteil
       fielen sie mit einem positiven Rand unter dem Vorgaenger zusammen -- dann hier nachsehen. */
    .up-sticky {
      --up-deck-zone: calc(var(--up-sticky-cover, var(--up-sticky-top)) + 1px);
      --up-deck-u: min(var(--up-deck-zone), var(--up-thead-off));
      --up-deck-o: min(max(calc(var(--up-deck-zone) - var(--up-deck-u)), 0px), var(--up-thead-off));
    }
    .up-kleb-deck { display: none; }
    .up-sticky .up-kleb-deck {
      display: block; flex: none; position: sticky; z-index: 6; pointer-events: auto;
      background: var(--up-surface);
    }
    .up-sticky .up-kleb-deck:not(.is-oben) {
      height: var(--up-deck-u); top: calc(var(--up-sticky-top) - var(--up-deck-u));
      margin-top: calc(-1 * (var(--up-deck-o) + var(--up-deck-u) + var(--up-thead-off)));
      margin-bottom: var(--up-thead-off);
    }
    .up-sticky .up-kleb-deck.is-oben {
      height: var(--up-deck-o); top: calc(var(--up-sticky-top) - var(--up-deck-u) - var(--up-deck-o));
      margin-bottom: calc(var(--up-deck-u) + var(--up-thead-off));
    }
    .up-sticky .up-kleb-deck + .up-head { margin-top: calc(-1 * (var(--up-deck-u) + var(--up-thead-off))); }
    /* In sticky mode the box's visible border is dropped (kept transparent so layout doesn't
       shift) and drawn on the header and body instead. Both span the full width and align exactly,
       so there's no 1px offset between a header sitting inside the box border and the box's own
       border — that offset was what left a doubled corner and a missing straight top edge. */
    .up-sticky .up-box { overflow: visible; border-color: transparent; }
    .up-sticky .up-table { background: transparent; }
    .up-sticky .up-thead {
      position: sticky; top: calc(var(--up-sticky-top) + var(--up-thead-off)); z-index: 5;
      background-image: linear-gradient(var(--vt-head-bg), var(--vt-head-bg));
      background-color: var(--vc-bg);
      border: 1px solid var(--vc-border); border-bottom: none;
      border-top-left-radius: var(--up-r-2xl); border-top-right-radius: var(--up-r-2xl);
    }
    /* The rounded top corners are transparent by nature, so the body's straight side border shows
       through them as it scrolls behind the pinned header (the little vertical stub). These two
       corner pieces fill ONLY the triangle outside the radius with the page background, hiding that
       border while leaving the rounded corner itself (inside the radius) untouched. Absolute takes
       them out of the header's grid flow. */
    .up-sticky .up-thead::before,
    .up-sticky .up-thead::after {
      content: ""; position: absolute; top: -1px; width: var(--up-r-2xl); height: var(--up-r-2xl); pointer-events: none; z-index: 2;
    }
    /* Groesse und Bogen aus --up-r-2xl, dem Radius des Kopfes: bis 15.10. standen hier 16 fest, und
       mit dem Kopf auf Geists 12 liess das Eckstueck zwischen beiden Boegen einen Streifen frei. */
    .up-sticky .up-thead::before { left: -1px;  background: radial-gradient(circle at bottom right, rgba(0,0,0,0) var(--up-r-2xl), var(--up-surface) calc(var(--up-r-2xl) + .5px)); }
    .up-sticky .up-thead::after  { right: -1px; background: radial-gradient(circle at bottom left,  rgba(0,0,0,0) var(--up-r-2xl), var(--up-surface) calc(var(--up-r-2xl) + .5px)); }
    .up-sticky .up-tbody {
      border: 1px solid var(--vc-border); border-top: none;
      border-bottom-left-radius: var(--up-r-2xl); border-bottom-right-radius: var(--up-r-2xl);
      /* clip, not hidden: visually identical (still clips row backgrounds to the rounded corner),
         but per spec `clip` does not establish a scroll container the way `hidden`/`auto`/`scroll`
         do -- and a scroll-container ancestor is exactly what breaks position:sticky for anything
         nested inside it (prompts-table's grouped view sticks its open group's own header here;
         with `hidden` that sticky silently failed, the header just scrolled away with the page).
         `clip` sidesteps the conflict entirely instead of working around it with corner masks. */
      overflow: clip;
    }
    /* ---------- IM DUNKELN DER RAND WIE IM HELLEN (22.10.) ----------
       Lukas: "Im Darkmode fehlen um die freistehenden Tabellen Borders. Im Lightmode sind sie da."
       und "Cards: im Darkmode sollen die bitte auch wie im Lightmode ne Border bekommen." Seitdem
       gilt: JEDE Tabelle und JEDE Karte traegt im Dunkeln denselben Rand wie im Hellen -- dieselbe
       Breite auf denselben Seiten, Farbe --vc-border des Themas (dunkel #222222). Darum steht hier
       KEINE Dunkel-Regel mehr; der Rand kommt aus der Grundregel jeder Flaeche, in beiden Themen.

       Bis zum 22.10. stand hier das Gegenteil, in drei Regeln: "Dark: kein Rand um die aeusseren
       Flaechen" (border-color: transparent an .up-box, .up-vartable, Kopf und Koerper der klebenden
       Tabelle, .vot-box, .tcd-box, .ubo-box, .combo-box, .uto-table, .ube-table, .ubd-card,
       .udd-card, .urd-card, .urd-cite-card, .urt-card, .upt-kpi, .usb-card), dazu an Kasten, Kopf
       und Koerper die Seiten auf Breite 0, und als Ausnahme davon "Im Drawer nach Geist bleibt
       der Rand" (Drawer und Settings). Die Begruendung
       ("die Fuellung traegt die Flaeche") galt seit Geist nur noch halb: Inhalt #0f0f0f auf der
       Seite #0a0a0a sind 5 Stufen, und im Drawer musste der Rand schon am 16.10. zurueck. Jetzt ist
       die Ausnahme die Regel, und die Ausnahme entfaellt mit -- eine Regel statt drei.

       Was dabei wahr bleiben muss, gemessen 22.10. (_h_dunkel_rand.html):
         - Klebend zeichnen Kopf und Koerper den Rand, der Kasten bleibt farblos (Sticky-Block
           oben) -- in BEIDEN Themen gleich, also stehen Kopf und Koerper auf demselben Pixel
           (innen links 26 hell wie dunkel, vorher dunkel 24 gegen hell 26).
         - Keine Doppellinie: wo ein Kasten in einer Karte mit eigenem Rand steht, nimmt das schon
           die helle Regel weg (DAS BAND IM KASTEN, die Variantentabelle in brand-detail.css,
           die flache Tabelle) -- dunkel zeigt dieselben Linien wie hell (0 Doppellinien in
           beiden Themen; die Gegenprobe mit einem Rand mehr findet 20).
         - Der offene Drilldown der Domains-Tabelle: der Streifen links und rechts war ein
           FARBLOSER Rand, durch den der Grund des Kastens schien. Jetzt steht neben dem Panel die
           Kante der Tabelle in --vc-border (klebend der Koerper, frei der Kasten), wie im Hellen.
       Bewusst ohne Rand im Dunkeln bleiben nur Bedienelemente, keine Flaechen: die Zitat-Chips
       (.up-rb-cite, .up-rb-cgroup), Logoplatten (.up-stack-vis, Logos in Onboarding und Einladung),
       der leise Chip (.up-entchip.is-soft) und der gleitende Segmentknopf -- je mit eigener Regel
       und eigenem Grund an ihrer Stelle.
       Fuer neuen Code: eine Flaeche bekommt ihren Rand aus --vc-border in der Grundregel und KEINE
       Dunkel-Regel, die ihn nimmt. */

    /* ---------- Rang-/Indexspalte ("#") ----------
       Die feste, nie ausblendbare Spalte ganz links, die UC.makeColumns ueber cfg.leadWidth als
       eigenen Track anlegt. Stand bis eben nur in brands-overview.css; discover-brands ist der
       zweite Verbraucher, also gehoert es nach §25 hierher -- sonst haette die zweite Tabelle
       wieder ihre eigene Schriftgroesse und Farbe bekommen, und genau so entstehen die
       Unterschiede zwischen Tabellen, die gleich aussehen sollen.
       Die Kopfzelle traegt UC.HASH_ICON, nicht das Zeichen "#". */
    /* Doppelte Klasse als Spezifitaets-Anker. .up-th und .up-td setzen selbst padding
       (0 16px 0 12px) und stehen in dieser Datei WEITER UNTEN -- bei gleicher Spezifitaet
       gewinnt die spaetere Regel, die Zahl sass also 2px links neben der Mitte statt darin.
       In brands-overview.css fiel das nicht auf, weil dort .ubo-root davor stand und damit
       ohnehin die hoehere Spezifitaet hatte; beim Umzug hierher ist die weggefallen.
       Verdoppeln statt Verschieben: eine Regel, die nur durch ihre Position gewinnt, kippt
       beim naechsten Umsortieren wieder. */
    .up-th-idx.up-th-idx, .up-td-idx.up-td-idx { justify-content: center; padding: 0; }
    /* 15 seit der Kopfschrift 14 (15.10. spaet), wie die anderen Zeichen im Kopf. */
    .up-th-idx svg { width: 15px; height: 15px; display: block; }
    .up-td-idx.up-td-idx { font-size: 12px; color: var(--vc-muted); font-variant-numeric: tabular-nums; }

    .up-table { width: 100%; display: flex; flex-direction: column; background: var(--vc-bg); }
    /* Column widths come from --up-cols, which UC.makeColumns' applyCols() writes ONCE on the
       root. Every header and row inherits it, so changing the layout is a single style write
       instead of one per row — with 100 rows and a column drag firing at up to 120 Hz, the
       per-row version was writing ~100 inline styles several times per frame, which is a large
       part of why dragging felt like it ran at a fraction of the frame rate.
       The literal below is only the pre-JS fallback (urls-table's shape, the widest of the
       tables); applyCols() replaces it on first run. The percentages are MINIMUMS; the 1fr
       maxima let the flexible columns absorb the leftover space. */
    .up-thead, .up-row {
      display: grid;
      grid-template-columns: var(--up-cols,
        minmax(30%, 1.6fr)    /* URL              */
        minmax(13%, 150px)    /* Share            */
        minmax(11%, 1fr)      /* Type             */
        minmax(112px, 0.6fr)  /* Mentioned?       */
        minmax(13%, 1fr)      /* Brands mentioned */
        minmax(104px, 0.7fr)  /* Last Seen        */
        minmax(120px, auto)   /* Actions          */);
      align-items: center;
    }
    /* Bounds layout/style invalidation to the row. Without it every one of the measure-then-write
       cycles elsewhere in this file costs a layout of the WHOLE table subtree; with 50-100 rows
       that is the difference between a resize that tracks the cursor and one that stutters. */
    .up-row { contain: layout style; }
    .up-thead { height: 45px; flex: 0 0 45px; background: var(--vt-head-bg); }
    .up-th { font-size: 12px; font-weight: 400; color: var(--vc-muted); padding: 0 16px 0 12px; height: 100%;
      white-space: nowrap; overflow: hidden;
      display: flex; align-items: center; gap: 6px; border-left: 1px solid var(--vc-border); border-bottom: 1px solid var(--vc-border); }
    /* DIE KOPFSCHRIFT JEDER TABELLE: 14/500 (15.10. spaet, "alle Header Rows eine Klasse groesser").
       Vorher zwei Stufen: 13/500 in der flachen Tabelle, 12/400 ueberall sonst (Drawer, Dashboard,
       Details). Jetzt eine Stufe ueber der flachen fuer alle, damit kein Kopf mehr kleiner ist als
       ein anderer -- Top Brands (.vt-th), Top Citations (.tct-th) und die Variantentabelle ziehen
       mit. An .up-root und nicht an .up-th allein: die Vorschauen der Landingpage (.ulh-vis-tab)
       tragen .up-th ausserhalb jeder Wurzel, mit fest gesetzten Spaltenbreiten -- die bleiben, bis
       die Landing-Sitzung sie selbst nachzieht.
       Die Spalten sind darauf neu vermessen (min/minFlach je Tabelle): ein Kopf mit Wort, Zeichen,
       Info und Sortierpfeil braucht bei 14 rund 8 Prozent mehr Platz. */
    .up-root .up-th { font-size: var(--up-fs-l); font-weight: var(--up-fw-m); }
    /* DIE ZEICHEN IM KOPF WACHSEN MIT (15.10. spaet). Gerechnet 14/13 auf die Werte der flachen
       Tabelle, dann gerundet (§1c): Info, Sortierpfeile, Zeichen vor dem Wort und Rautenzeichen
       14 -> 15, Markenlogo 16 -> 17 (Radius 4 bleibt, 4.3 rundet auf die Stufe). Der Abstand
       der Zelle 6 -> 6.5 rundet auf 6 und bleibt. Nur IM Kopf: dasselbe .up-th-info steht an
       Kennzahlen und Abschnittstiteln (Shopping, Ads, Details) und behaelt dort seine 14. */
    .up-th .up-th-info, .vt-th .up-th-info, .tct-th .up-th-info { width: 15px; height: 15px; flex: 0 0 15px; }
    .up-th .up-th-info svg, .vt-th .up-th-info svg, .tct-th .up-th-info svg { width: 15px; height: 15px; }
    /* DAS ZEICHEN VOR DEM WORT (15.10., aus prompts-table nach core gehoben, als zweite und dritte
       Tabelle es brauchten): "Brand Mentions" und "Brand" (squareStack wie Brands in der
       Seitenleiste), "Topics" (tags), "Citations" (globe wie Citations in der Seitenleiste),
       "Type" (dropdownFieldType). Drittfarbe wie Info und Sortierpfeil, schrumpft nie -- verliert
       der Kopf Platz, kuerzt sich das Wort, nicht das Zeichen. Gesetzt mit UC.kopfZeichen. */
    .up-th-ic { display: inline-flex; flex: 0 0 auto; color: var(--vc-third); }
    .up-th-ic svg { width: 15px; height: 15px; display: block; }
    /* Column separators sit on the LEFT edge, not the right, and the exception is :first-child.
       Mirror-imaging this matters: hidden columns are still DOM children, so with border-right +
       ":last-child { border-right: none }" the exception kept landing on a display:none cell while
       the genuinely last VISIBLE column still painted a right border straight against the table's
       own frame — two lines a pixel apart, which read as "the right border gets thicker whenever
       columns drop out". Anchoring on :first-child is immune to that, because the lead column is
       the one column that never hides. Same set of separators either way. */
    .up-th:first-child { border-left: none; }
    :is(.up-th, .vt-th, .tct-th).is-sortable { cursor: pointer; user-select: none; }
    /* Der Kopftext muss SCHRUMPFEN duerfen. Fuer .up-th-txt gab es keine einzige Regel: der Text
       war damit unschrumpfbar, der ganze Inhalt lief bei enger Spalte ueber, und weil overflow
       an der POLSTERKANTE schneidet und nicht an der Inhaltskante, rutschte der Sortierchevron in
       die 16px rechts hinein und klebte am Rahmen. Genau so gemeldet fuer die Visibility-Spalte.
       min-width:0 ist der Kern -- ein Flex-Kind schrumpft ohne das nicht unter seine Inhaltsbreite.
       Der Chevron und das Info-Zeichen bleiben feste Breite, also verliert der TEXT die Pixel und
       nicht das Bedienelement. */
    .up-th-txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    :is(.up-th, .vt-th, .tct-th) > .up-thsort, :is(.up-th, .vt-th, .tct-th) > .up-th-info { flex: 0 0 auto; }
    /* Abstand zwischen Wort, "Trend", Sortierzeichen und Info-Zeichen (18.10.): in Top Brands und Top Citations klebten sie, weil
       .vt-th und .tct-th keinen Spaltenabstand haben. Dieselben 6 wie in jeder .up-th (Domains, URLs, Prompts). */
    :is(.vt-th, .tct-th).is-sortable { gap: 6px; }
    /* ---------- column resizing ---------- */
    .up-th { position: relative; }
    .up-grip {
      position: absolute; top: 0; right: -4px; width: 9px; height: 100%;
      cursor: col-resize; z-index: 6; touch-action: none;
      display: flex; align-items: center; justify-content: center;
    }
    .up-grip::before {
      content: ""; width: 2px; height: 18px; border-radius: 2px;
      background: transparent; transition: background .12s ease;
    }
    .up-grip:hover::before, .up-root.is-resizing .up-grip.is-active::before { background: var(--vc-hover-border); }
    .up-th:last-child .up-grip { display: none; }
    .up-root.is-resizing { cursor: col-resize; user-select: none; }
    .up-root.is-resizing .up-row { cursor: col-resize; }
    :is(.up-th, .vt-th, .tct-th).is-sortable:hover { color: var(--vc-text); }
    /* Tastaturfokus an Sortierkopf und Zeile (17.10.): Hausregel der Knoepfe, 2px Linie in
       --up-focus. Der Abstand liegt INNEN (-2px): Kopf und Zeilen stossen Kante an Kante aneinander
       und an den Rand des Kastens, ein Ring mit 2px Luft ausserhalb wuerde dort abgeschnitten
       bzw. auf die Nachbarzeile gemalt. Ohne Regel zeichnete der Browser sein eigenes Blau (auto 1px). */
    :is(.up-th, .vt-th, .tct-th).is-sortable:focus-visible,
    .up-row:focus-visible { outline: var(--up-focus-w) solid var(--up-focus); outline-offset: calc(var(--up-focus-w) * -1); }
    :is(.up-th, .vt-th, .tct-th).is-sortable:focus-visible { color: var(--vc-text); }

    /* Kopf-Sortierer: zwei Chevrons uebereinander, je 2px zusammengezogen. Die Geometrie ist
       Lucide chevron-up / chevron-down. Der aktive zeigt in die Sortierrichtung. 15 statt 14 seit
       der Kopfschrift 14 (15.10. spaet); das Zusammenziehen 2 x 15/14 = 2.1 bleibt 2. */
    .up-thsort { display: inline-flex; flex-direction: column; align-items: center; flex: 0 0 auto; line-height: 0; }
    .up-thsort svg { width: 15px; height: 15px; display: block; color: var(--vc-third); transition: color 120ms ease; }
    .up-thsort .up-thsort-up { margin-bottom: -2px; }
    .up-thsort .up-thsort-down { margin-top: -2px; }
    .up-thsort.is-asc .up-thsort-up { color: var(--vc-text); }
    .up-thsort.is-desc .up-thsort-down { color: var(--vc-text); }
    /* the sub-label that appears when a column is sorted by its secondary key (Share → Trend).
       Eine Stufe unter der Kopfschrift wie vorher (11 unter 12): seit dem Kopf 14 also 12. */
    .up-th-sub { font-size: var(--up-fs-s); font-weight: 500; color: var(--vc-text); }

    .up-tbody { display: flex; flex-direction: column; }
    /* Soft-reload dim — see UC.makeSoftReload. The rows stay on screen because they're still
       truthful (a sort only re-orders the same result set); dimming says "working" without
       destroying the thing the user is looking at. Search/filters do NOT use this: there the
       result set itself changes, so the old rows would be a lie and the skeleton is the honest
       answer instead. */
    /* .uto-table: die Tabellen der Team-Verwaltung (12.10.) -- waehrend Resend, Revoke, Remove
       oder ein Rollenwechsel laeuft, dimmen sie genauso; vorher gab es bis zur Antwort gar keine
       Rueckmeldung. */
    .up-root.is-reloading :is(.up-tbody, .uto-table, .vt-tbody, .tct-tbody) { opacity: .42; transition: opacity 140ms ease; pointer-events: none; }
    @media (prefers-reduced-motion: reduce){
      .up-root.is-reloading :is(.up-tbody, .uto-table) { transition: none; }
    }
    /* height/flex-basis deliberately don't transition: with 50-100 rows on screen, animating them
       forces a synchronous layout reflow on every row simultaneously, which visibly stalls the
       page for a couple of seconds on Comfortable/Compact toggle before the rows "jump" to their
       final height at once. Per-row hover (background) is instant, no transition — explicit
       instruction: no ease on table-row hover, anywhere. */
    .up-row { height: 72px; flex: 0 0 72px; cursor: pointer; }
    /* Compact row mode: shorter rows, first column shows only the title (URL sub-line hidden). */
    .up-root.is-dense .up-row { height: 55px; flex: 0 0 55px; }
    
    /* Compact mode shrinks the mentioned-brand logos ~20% and scales every gap/shift to match. */

    

    

    

       /* skeleton brand logo matches compact size */
    .up-row:hover { background: var(--vt-row-hover); }
    /* While the page is actively scrolling (see the scroll listener in core.js), hover-matching
       is suspended tbody-wide — content sliding under a stationary cursor still fires real
       mouseenter/mouseleave, and .up-row:hover's background is a paint, not a compositor-only
       change, so a tall table under the cursor during a scroll was a full-width paint per row per
       frame. pointer-events:none takes every hover-driven style with it in one rule (row
       background, chip hover, the goto arrow) instead of having to override each individually. */
    html.up-is-scrolling .up-tbody { pointer-events: none; }
    /* overflow:hidden matches .up-th (see below) -- a cell's own content (a long unbroken chip
       row, e.g.) must not be able to push past the cell's actual grid-track width once that track
       has shrunk. Tooltips/popovers spawned from inside a cell are all position:fixed (mounted on
       body or fixed to the viewport), so this never clips one of those. */
    .up-td { height: 100%; display: flex; align-items: center; gap: 10px; padding: 0 16px 0 12px; min-width: 0;
      overflow: hidden; border-left: 1px solid var(--vc-border); }
    .up-td:first-child { border-left: none; }   /* see .up-th:first-child for why left, not right */
    /* The row separator lives on the ROW now, not on every individual cell. Per-cell borders broke
       in two independent ways: (1) a stale/un-restamped cell (see applyCols()'s rowsStale fix above)
       could sit display:none and silently drop its slice of the line, and (2) prompts-table's
       Dynamic row-height mode gives every .up-td its own height:auto -- if a browser's grid-stretch
       ever rounds one cell's stretched height a subpixel off from its neighbours (fractional row
       heights from wrapped text are common there), each cell's border-bottom lands at a very
       slightly different Y and the line reads as broken. One border on the row's own box, drawn
       once at the row's actual outer edge, is immune to both: it does not care how many cells are
       hidden or how their individual heights were rounded. */
    .up-row { border-bottom: 1px solid var(--vc-border); }
    /* > und nicht bloss ein Nachfahren-Selektor. Ohne den Kind-Kombinator traf die Regel JEDE
       .up-row, die letztes Kind ihres Elternteils ist -- in der Gruppenansicht also die letzte
       Zeile JEDER Gruppe, denn die stehen in .upt-grp-rows-Containern innerhalb von .up-tbody.
       Genau das gemeldete Bild: hier und da fehlt eine Trennlinie, aber nicht ueberall. */
    .up-tbody > .up-row:last-child { border-bottom: none; }

    /* ---------- DIE FLACHE TABELLE (data-flach="yes" an .up-root, 15.10.) ----------
       Zuerst in der Prompt-Tabelle gebaut und dort Stueck fuer Stueck abgenommen, dann hierher
       gehoben, damit jede Tabelle sie mit EINEM Attribut bekommt:
       - kein Kasten: oben und unten eine Linie, an den Seiten keine (0 breit, nicht farblos --
         sonst endet jede Zeilenlinie einen Pixel vor dem Rand), kein Radius, kein Grund;
       - bis an die Kanten: der Kasten laeuft um --up-flach-aus (16, das Polster der Ansicht) links
         und rechts heraus; jede Zeile beginnt links an der Kante (ihr Zellpolster 12 ist der
         Abstand) und haelt rechts --up-flach-aus Polster, damit die letzte Zahl nicht am
         Fensterrand steht. Wo die Tabelle nicht auslaufen darf (Karte, Drawer), setzt die
         Komponente --up-flach-aus: 0px;
       - Kopf und Fuss 48 hoch (--up-flach-h): nach 40 kommt auf der Skala erst 55, die
         Zeilenhoehe; 45 setzte die 28er Knoepfe der Pagination auf halbe Pixel. Die Linie unter
         dem Kopf zieht der Kopf selbst (Schatten nach innen), die Zellen allein liessen sie vor
         dem Auslauf enden;
       - die Beschriftung der Koepfe war hier zuerst 13/500 statt 12/400 ("noch sehr duenn und
         klein"); seit 15.10. spaet steht 14/500 fuer jede Tabelle an .up-root .up-th.
       Andere Zeilenarten einer Tabelle (Gruppenkopf, Unterzeilen eines Drilldowns) braucht die
       Komponente selbst: rechts --up-flach-aus Polster, links nichts.
       .up-root.up-root: gegen die Regeln von core gewinnt die Spezifitaet (bis 22.10. auch gegen
       die dunklen ohne Rand, die es seitdem nicht mehr gibt) und gegen eine Komponente mit
       .xyz-root .up-box -- nie die Ladereihenfolge. makeColumns zieht das Polster der Kopfzeile
       von der Breite ab, mit der es Spalten verteilt (boxWidth). */
    .up-root[data-flach="yes"] { --up-flach-aus: 16px; --up-flach-h: 48px; }
    .up-root.up-root[data-flach="yes"] .up-box {
      border-width: 1px 0; border-color: var(--vc-border); border-radius: 0; background: transparent;
      margin-left: calc(-1 * var(--up-flach-aus)); margin-right: calc(-1 * var(--up-flach-aus));
    }
    .up-root.up-root[data-flach="yes"] .up-table { background: transparent; }
    /* Der Grund des Kopfes ist die SEITENFARBE und nicht durchsichtig: klebt er, laufen die
       Zeilen darunter durch. */
    .up-root.up-root[data-flach="yes"] .up-thead {
      height: var(--up-flach-h); flex: 0 0 var(--up-flach-h);
      background: var(--up-surface); box-shadow: inset 0 -1px 0 var(--vc-border);
      padding-left: 0; padding-right: var(--up-flach-aus);
    }
    .up-root.up-root[data-flach="yes"] .up-box .up-row { padding-left: 0; padding-right: var(--up-flach-aus); }
    /* Klebend: Rahmen und runde Ecken des Kopfes und Koerpers gehoeren zum Kasten, den es hier
       nicht gibt -- 0 breit, sonst stuende links und rechts je ein farbloser Pixel. */
    .up-root.up-root[data-flach="yes"].up-sticky .up-thead {
      background-image: none; background-color: var(--up-surface); border-width: 0; border-radius: 0;
    }
    .up-root.up-root[data-flach="yes"].up-sticky .up-thead::before,
    .up-root.up-root[data-flach="yes"].up-sticky .up-thead::after { display: none; }
    /* DIE LINIE UEBER DEM KLEBENDEN KOPF (16.10.): "wenn die Topbar sticky wird, verschwindet die
       Top-Border ueber der Header-Row". In Ruhe ist diese Linie der obere Rand des Kastens, und der
       Kopf hat keine eigene. Bis 0ed044e scrollte der Kasten in sich (feste Hoehe), sein Rand blieb
       stehen; seitdem scrollt #main, und der Rand laeuft mit dem Kasten unter die Leiste (z 6) --
       gemessen auf Prompts, Responses und Teams in jeder geklebten Lage: nichts ueber dem Kopf.
       Mit Band (Citations, Brands > Tracked) kam sie nur von der Linie der Leiste, also erst mit
       .is-geklebt aus dem Scroll-Takt und eingeblendet.
       Jetzt traegt der Kopf sie selbst, als Schatten 1px UEBER sich: in Ruhe liegt er genau auf dem
       Rand des Kastens (dieselbe deckende Farbe, dieselbe Breite -- der Kopf ist so breit wie der
       Kasten), es aendert sich kein Pixel. Klebend steht der Kopf 1px tiefer als bisher, sodass der
       Schatten nicht in der Leiste liegt (die malt ueber dem Kopf und deckte ihn), sondern im Pixel
       darunter -- dort, wo in Ruhe der Rand steht. Leiste, Linie, Kopf liegen damit klebend genau
       wie in Ruhe, und der Kopf rastet im selben Pixel ein wie die Leiste. Reines sticky, ohne
       Klasse, ohne Bildverzug; am Ende der Tabelle geht die Linie mit dem Kopf unter die Leiste.
       Wer unter dem Kopf klebt (offene Gruppe, Kopf der Gruppenliste in prompts-table.css), rechnet
       das 1px mit. Nicht bei fester Hoehe: dort klebt der Kopf am Kasten, und dessen Rand steht. */
    .up-root.up-root[data-flach="yes"].up-sticky:not(.is-festhoehe) .up-thead {
      top: calc(var(--up-sticky-top) + var(--up-thead-off) + 1px);
      box-shadow: 0 -1px 0 var(--vc-border), inset 0 -1px 0 var(--vc-border);
    }
    .up-root.up-root[data-flach="yes"].up-sticky .up-tbody { border-width: 0; border-radius: 0; }
    /* Die klebende Leiste laeuft mit aus (Bestandsaufnahme 15.10.): ihr deckender Grund und der
       Deckstreifen darueber (.is-geklebt .up-head::before, left/right 0) reichten nur so breit
       wie die Wurzel -- ohne feste Hoehe scrollten die Zeilen des ausgelaufenen Kastens in den
       16px links und rechts sichtbar neben der angehefteten Leiste durch. Das Polster gibt den
       Platz zurueck, der Inhalt der Leiste bleibt, wo er war. */
    .up-root.up-root[data-flach="yes"].up-sticky .up-head {
      margin-left: calc(-1 * var(--up-flach-aus)); margin-right: calc(-1 * var(--up-flach-aus));
      padding-left: var(--up-flach-aus); padding-right: var(--up-flach-aus);
    }
    /* Das Deck darueber (.up-kleb-deck, 16.10.) genau so breit wie die Leiste: schmaler liesse es
       links und rechts je 16 frei, breiter schaute es in Ruhe neben ihr heraus. */
    .up-root.up-root[data-flach="yes"].up-sticky .up-kleb-deck {
      margin-left: calc(-1 * var(--up-flach-aus)); margin-right: calc(-1 * var(--up-flach-aus));
    }
    /* Der Fuss: 48 hoch direkt unter der unteren Linie, links mit auslaufend auf der Kante der
       ersten Zelle (12), die Knoepfe in der 28er Form (--up-h-seg), je 10 darueber und darunter.
       Umgebrochen (core zentriert dann) ohne den Auszug, er schob die Mitte um 8px. */
    .up-root.up-root[data-flach="yes"] .up-foot {
      margin-top: 0; min-height: var(--up-flach-h);
      margin-left: calc(-1 * var(--up-flach-aus)); padding-left: 12px;
    }
    /* Umgebrochen (Telefon) stehen zwei Zeilen im Fuss, hoeher als die 48: ohne Polster klebte die Seitengroesse an der
       Linie der Gesamtzeile. 10 oben und unten, wie bei der einzeiligen Form. */
    .up-root.up-root[data-flach="yes"] .up-foot.is-wrapped { margin-left: 0; padding: 10px 0; }
    .up-root.up-root[data-flach="yes"] .up-foot .up-pagesize-seg { height: var(--up-h-seg); }
    .up-root.up-root[data-flach="yes"] .up-foot .up-pagesize-btn { height: var(--up-h-seg); min-width: 30px; padding: 0 8px; }
    .up-root.up-root[data-flach="yes"] .up-foot .up-page { height: var(--up-h-seg); min-width: var(--up-h-seg); }
    .up-root.up-root[data-flach="yes"] .up-foot .up-page-gap { height: var(--up-h-seg); }
    /* FESTE HOEHE (UC.makeFestHoehe): nur der Kasten scrollt, Leiste oben und Pagination unten
       stehen. --up-flach-box-h ist gemessen; max-height, damit drei Zeilen unter der dritten
       enden und keinen leeren Kasten bis zum Fensterrand ziehen. Der Kopf klebt dann am Kasten
       (top 0), nicht mehr an #main. */
    .up-root.up-root[data-flach="yes"].is-festhoehe .up-box { max-height: var(--up-flach-box-h); overflow: auto; overscroll-behavior: contain; }
    .up-root.up-root[data-flach="yes"].is-festhoehe .up-thead { position: sticky; top: 0; z-index: 5; }

    /* ---------- DIE EINE LEISTE (data-leiste="yes" an .up-root einer Tabelle, 15.10.) ----------
       Zuerst in der Prompt-Tabelle, abends nach core gehoben fuer Responses und weitere Tabellen.
       Der Kopf der Tabelle traegt links, was die DATEN aendert (.up-leiste-scope: Status,
       Kalender, "Filters", Chips -- die Seite haengt ihre Filterzeile hinein), rechts, was nur die
       ANSICHT aendert (.up-head-tools: Suche, Sortierer, Display, Export). Er BRICHT UM statt
       Knoepfe wegzunehmen; die Hoehe kommt aus dem Inhalt (makeSticky misst sie). Die Ueberschrift
       mit Zaehler faellt weg -- die Zahl steht im Seitenkopf bzw. in der Gesamtzeile. */
    .up-root[data-leiste="yes"] .up-head { height: auto; min-height: 32px; flex-wrap: wrap; align-items: flex-start; row-gap: 8px; column-gap: 16px; }
    .up-root[data-leiste="yes"].up-sticky .up-head { height: auto; }
    .up-root[data-leiste="yes"] .up-leiste-scope { flex: 1 1 480px; min-width: 0; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
    .up-root[data-leiste="yes"] .up-head-tools { flex: 0 0 auto; margin-left: auto; min-height: 32px; }
    .up-root[data-leiste="yes"] .up-heading { display: none; }
    /* Telefon: je eine volle Zeile, oben die Daten, darunter rechtsbuendig die Ansicht. */
    .up-root[data-leiste="yes"].is-vnarrow .up-leiste-scope { flex-basis: 100%; }
    .up-root[data-leiste="yes"].is-vnarrow .up-head-tools { flex: 1 1 100%; justify-content: flex-end; }
    /* Die Filterleiste der Seite fliesst in der Leiste MIT: ihre Huellen loesen sich auf, und
       "Filters", jeder Chip und "Clear All" stehen einzeln in der Reihe und brechen dort um. Als
       ein Block rutschte die ganze Leiste samt "Filters" in eine zweite Zeile, sobald zwei Chips
       nicht mehr neben den Kalender passten. "Filters" selbst (.ufb-more) bleibt ein Kasten: an
       ihm haengt das Menue.
       .up-leiste-fluss (15.10.) ist dasselbe fuer eine Filterzeile, die NICHT im Kopf einer Tabelle
       steht, sondern ueber einem Band (Citations): die Klasse an den Flex-Behaelter, die
       Filterleiste als sein direktes Kind. Vorher stand dafuer eine Kopie in citations-page.css. */
    .up-root[data-leiste="yes"] .up-leiste-scope .ufb-root,
    .up-root[data-leiste="yes"] .up-leiste-scope .ufb-bar,
    .up-root[data-leiste="yes"] .up-leiste-scope .ufb-root.has-filters .ufb-chips,
    .up-root[data-leiste="yes"] .up-leiste-scope .ufb-root.has-standard .ufb-chips,
    .up-leiste-fluss > .ufb-root,
    .up-leiste-fluss > .ufb-root > .ufb-bar,
    .up-leiste-fluss > .ufb-root.has-filters .ufb-chips,
    .up-leiste-fluss > .ufb-root.has-standard .ufb-chips { display: contents; }
    .up-root[data-leiste="yes"] .up-leiste-scope .ufb-sep,
    .up-leiste-fluss > .ufb-root .ufb-sep { display: none; }
    .up-root[data-leiste="yes"] .up-leiste-scope .ufb-clear,
    .up-leiste-fluss > .ufb-root .ufb-clear { margin-left: 0; }
    /* Die Filterwurzeln der Leiste (.up-ff, makeLeistenFilter in core.js): ihr Trigger ist nie zu
       sehen -- die Zeile im Menue "Filters" ist der Ausloeser. Die Zeilenhoehe wie die anderen
       Untermenues der Leiste (.uif-opt, .umf-opt: 34). Das Panel stellt filter-bar.css in den
       Fluss. Eigener Inhalt (Regler, .up-ff-panel) bekommt das Polster eines Menues; die
       unsichtbare Liste darunter traegt dann nur den Namen der Wahl fuer den Chip. */
    /* Die Standalone-Wurzeln der drei Filter (Topics, Models, Markets) stehen auf jeder Seite mit
       Filterleiste als Geschwister der Leiste in der Zeile und werden erst von filter-bar.js in ihr
       Untermenue "Filters" geholt (initAll, Timer, Waechter). Bis dahin baute ihr eigenes Skript
       den Knopf in die Zeile: 270 bis 440 ms sichtbar nach einem Seitenwechsel, je belegter der
       Hauptthread, desto laenger. Hier ab dem ersten Bild weg, ohne Platz, solange die Wurzel NEBEN
       der Leiste steht. In .ufb-sub ist sie kein Geschwister mehr, die Regel trifft dort nicht (die
       Selektorform statt einer Reihenfolge; filter-bar.css stylt sie dort weiter). Eine Wurzel
       ohne Leiste daneben (Mira-Bericht) bleibt, wie sie ist. */
    :has(> .ufb-root) > .utf-root,
    :has(> .ufb-root) > .umf-root,
    :has(> .ufb-root) > .umk-root {
      position: absolute; width: 0; height: 0; margin: 0; overflow: hidden;
      visibility: hidden; pointer-events: none;
    }
    .up-ff-heim { display: none; }
    .up-ff-trigger { display: none; }
    .up-ff-opt { min-height: 34px; }
    .up-ff-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--up-fs-m); font-weight: var(--up-fw-m); color: var(--vc-text); }
    /* Ein Typ als Chip (o.chipHtml): der Chip bringt Schrift, Farbe und Hoehe selbst mit (.up-filter-tag). */
    .up-ff-name.up-ff-chip { display: inline-flex; align-items: center; overflow: visible; }
    .up-ff-panel { padding: 12px 10px 14px; min-width: 240px; }
    /* Zu ist das Panel NIE treffbar -- wie die Panels der anderen Filter (.uif-menu usw.), die das
       in ihrer eigenen CSS tun. filter-bar.css gibt ihm auto nur, solange Menue UND Untermenue
       wirklich offen sind. Ohne diese Grundregel blieb ein zugeklapptes Panel unsichtbar, aber
       klickbar (gemessen 15.10.: elementFromPoint ueber der Responses-Tabelle traf den Regler). */
    .up-ff-menu { pointer-events: none; }
    .up-ff-list.is-panel { display: none; }

    /* ---------- DIE LEISTE UEBER DEM BAND (.has-leistenband, UC.bandUnterLeiste, 16.10.) ----------
       Variante A der Leinwand "Citations Seitenaufbau", fuer Citations und Brands > Tracked
       gewaehlt: in der Wurzel der Tabelle steht die eine Leiste oben, darunter das Band (Chart),
       darunter der Kasten. Die Leiste klebt (makeSticky), Kopf, Reiter und Chart scrollen weg, der
       Spaltenkopf klebt direkt unter ihr (--up-thead-off ist ihre Hoehe).
       Abstaende wie ueberall: die Leiste haelt unten ihre 16 (Polster klebend, Rand in Ruhe), das
       Band traegt seine 16 darunter selbst (DIE ZEILE UNTER DEM BAND). Der Stapel der Wurzel faellt
       dafuer auf 0 -- Brands stapelt Chart und Tabelle mit 24, das stuende sonst zusaetzlich
       zwischen Leiste und Band. (0,4,0): schlaegt den Stapel der Komponente (0,3,0). */
    .up-root.up-root[data-flach="yes"].has-leistenband { --up-stapel-luft: 0px; }
    /* Eine Linie unter der Leiste, NUR solange sie klebt (.is-geklebt, klebPruefenAlle): dann
       laeuft der Chart unter ihr weg. In Ruhe steht darunter die Linie des Bandes. Eingeblendet,
       nicht geschaltet. Ueber dem klebenden Spaltenkopf traegt die Linie seit dem 16.10. der Kopf
       selbst (DIE LINIE UEBER DEM KLEBENDEN KOPF), ohne Klasse und ohne Einblenden; er steht dann
       1px unter der Leiste. Darum liegt auch diese Linie 1px UNTER der Leiste (bottom -1px) und
       nicht in ihrem letzten Pixel: genau auf seiner -- in ihrem letzten Pixel stuenden klebend zwei
       Linien uebereinander. */
    .up-root.has-leistenband.up-sticky > .up-head::after {
      content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px; pointer-events: none;
      background: var(--vc-border); opacity: 0; transition: opacity var(--up-t-2) var(--up-ease-expo);
    }
    .up-root.has-leistenband.up-sticky.is-geklebt > .up-head::after { opacity: 1; }

    /* ---------- DER KLEBENDE FUSS (data-kleb-fuss="yes", core.js klebFuss, 16.10.) ----------
       Gesamtzeile und Pagination kleben unten am Scrollbereich, sobald die Tabelle die untere
       Haelfte des Bildes einnimmt (hinaus erst wieder ueber 70 %, core.js FUSS_REIN/FUSS_RAUS,
       16.10. abends), und fahren dabei herein; am Ende der Tabelle stehen sie an ihrem Platz. Nur
       ab 1000 breit -- auf dem Telefon nie. Die Marke steht als Kind der Wurzel vor der
       Pagination. Die Zustaende setzt core.js, hier steht nur, wie sie aussehen:
         .is-fuss-an          sticky (Gesamtzeile ueber der Pagination: --up-fuss-unter)
         .is-fuss-weg         unten hinaus gefahren (--up-fuss-weg, beide als ein Block)
         .is-fuss-still       ohne Uebergang (Startpunkt einer Fahrt, Rueckkehr in den Fluss)
         .is-fuss-klebt       klebt wirklich (Platz unter dem Bild): Schatten nach oben
         .is-fuss-ohne-summe  ohne Gesamtzeile traegt die Pagination den Schatten
       z 4 wie die Gesamtzeile mit fester Hoehe: unter dem klebenden Kopf (5) und der Leiste (6),
       ueber den Zeilen. Der Grund deckt (die Zeilen laufen darunter durch). */
    .up-fuss-marke { display: block; flex: none; height: 0; margin: 0; pointer-events: none; }
    /* Die Pagination laeuft rechts mit aus wie links (DIE FLACHE TABELLE): klebend laufen die
       Zeilen darunter durch und stuenden sonst in den 16 rechts neben ihr im Bild. Das Polster gibt
       den Platz zurueck, die Knoepfe bleiben, wo sie waren -- und immer, nicht erst klebend: beim
       Einrasten aendert sich so kein Mass. */
    .up-root.up-root[data-flach="yes"][data-kleb-fuss="yes"] > .up-foot {
      margin-right: calc(-1 * var(--up-flach-aus)); padding-right: var(--up-flach-aus);
    }
    .up-root[data-kleb-fuss="yes"] > .up-foot { position: relative; }
    .up-root[data-kleb-fuss="yes"] .up-box .up-sumrow,
    .up-root[data-kleb-fuss="yes"] > .up-foot {
      transition: transform var(--up-t-3) var(--up-ease-expo), opacity var(--up-t-2) var(--up-ease-expo);
    }
    .up-root[data-kleb-fuss="yes"].is-fuss-an .up-box .up-sumrow {
      position: sticky; bottom: var(--up-fuss-unter, 0px); z-index: 4;
    }
    /* Die Linie ueber der Pagination fuellt den 1px zwischen beiden (die Linie des Kastens, die
       klebend fehlt); ohne Gesamtzeile ist sie die Kante der Pagination. */
    .up-root[data-kleb-fuss="yes"].is-fuss-an > .up-foot {
      position: sticky; bottom: 0; z-index: 4; background: var(--up-surface);
      box-shadow: 0 -1px 0 var(--vc-border);
    }
    .up-root[data-kleb-fuss="yes"].is-fuss-weg .up-box .up-sumrow,
    .up-root[data-kleb-fuss="yes"].is-fuss-weg > .up-foot {
      transform: translateY(var(--up-fuss-weg, 120px)); opacity: 0;
    }
    .up-root[data-kleb-fuss="yes"].is-fuss-still .up-box .up-sumrow,
    .up-root[data-kleb-fuss="yes"].is-fuss-still > .up-foot { transition: none; }
    /* 18.10.: wieder --up-e-1 (Geists "small"), auf Wunsch "deutlich kleiner, das muss riechen" -- e-2 war zu kraeftig.
       Der Text darunter beschreibt den Stand vom 17.10. (e-2).
       Der Schatten: --up-e-2 (Geists "menu"), nach OBEN -- die Ebene liegt deckungsgleich auf dem Teil
       und ist gespiegelt (scaleY(-1)), so wirft derselbe Wert nach oben statt nach unten, ohne eine
       zweite Zahl. Nur Deckkraft blendet. Verlauf: bis 17.10. --up-e-3 (Geists "modal", bis 32px weit),
       dem Nutzer "zu dolle"; dann --up-e-1 (2px); am 17.10. auf Wunsch "eine kleine Stufe kraeftiger"
       genau eine Stufe hoch auf --up-e-2. Die Linie traegt die Kante, der Schatten bleibt leise. */
    .up-root[data-kleb-fuss="yes"] .up-box .up-sumrow::before,
    .up-root[data-kleb-fuss="yes"] > .up-foot::before {
      content: ""; position: absolute; left: 0; right: 0; top: 0; bottom: 0; pointer-events: none;
      box-shadow: var(--up-e-1); transform: scaleY(-1); opacity: 0;
      transition: opacity var(--up-t-2) var(--up-ease-expo);
    }
    .up-root[data-kleb-fuss="yes"].is-fuss-klebt .up-box .up-sumrow::before,
    .up-root[data-kleb-fuss="yes"].is-fuss-klebt.is-fuss-ohne-summe > .up-foot::before { opacity: 1; }
    @media (prefers-reduced-motion: reduce){
      .up-root[data-kleb-fuss="yes"] .up-box .up-sumrow,
      .up-root[data-kleb-fuss="yes"] > .up-foot,
      .up-root[data-kleb-fuss="yes"] .up-box .up-sumrow::before,
      .up-root[data-kleb-fuss="yes"] > .up-foot::before,
      .up-root.has-leistenband.up-sticky > .up-head::after { transition: none; }
    }

    /* ---------- DAS OFFENE BAND (.up-band unter data-flach="yes", 15.10.) ----------
       Variante A der Vorschau zur Citations-Seite, vom Nutzer gewaehlt und danach auf Brands,
       Shopping und Ads uebertragen -- dort viermal fast gleich lokal gebaut, darum hierher gehoben,
       mit den Werten der vier Vorlagen (citations-combo-chart.css, brands-overview.css,
       shopping.css, ads.css). Die Form der flachen Tabelle oben, uebertragen auf Diagramme:
         .up-band          kein Kasten, laeuft links und rechts um --up-flach-aus bis an die
                           Kanten, oben und unten je eine Linie, an den Seiten keine;
         .up-band-einheit  ein Teil mit eigener Ueberschrift. Sein Polster gibt den Auslauf zurueck:
                           Ueberschrift und Inhalt stehen buendig mit Filterzeile und Tabellen.
                           Ein Band aus einem einzigen Teil traegt beide Klassen (Brands);
         .up-band-head     Ueberschrift samt Werkzeugen als erste Zeile IM Band, 48 hoch wie Kopf
                           und Fuss der flachen Tabelle (--up-flach-h), ohne Linie zum Inhalt;
         .up-band-flaeche  der bisherige Kasten wird zur Flaeche: kein Rahmen, kein Radius, kein
                           Grund, kein Auslauf, auch im Dunkeln nicht. Ein .up-box direkt im Band
                           ist es von selbst;
         .up-band-inhalt   das Polster des Inhalts: 8 oben (die 48er Zeile bringt den Abstand zur
                           Linie mit), 16 unten; seitlich nur, wo er ohne Einheit im Band steht;
         .up-band-zwei     zwei Einheiten in einem Band. Die Trennlinie ist die 1px-Luecke, durch
                           die der Grund des Bandes (die Linienfarbe) scheint -- nebeneinander
                           senkrecht, gestapelt waagerecht; die Einheiten decken mit dem Grund der
                           Seite. So muss die Linie nicht wissen, wie die Einheiten stehen;
         .up-band-raster   dazu das auto-fit-Raster (Shopping, Ads): nebeneinander, solange jede
                           Spalte --up-band-spalte breit sein kann, sonst gestapelt.
       DASSELBE ATTRIBUT WIE DIE TABELLE und kein eigenes: --up-flach-aus und --up-flach-h setzt
       core an .up-root[data-flach="yes"]; ein eigenes Attribut muesste sie ein zweites Mal
       festlegen. Ohne data-flach (die Bubble-Vorlagen) greift keine dieser Regeln.
       Ein Band ist entweder selbst diese Wurzel (Citations, Shopping, Ads) oder steht in ihr
       (Brands) -- darum jede Regel in beiden Formen. Spezifitaet (0,5,0) und mehr: schlaegt die
       flache Tabelle (.up-box, 0,4,0; Shopping und Ads tragen .up-box), die dunklen
       Kartenregeln und die Kastenregeln der Komponenten ohne die Ladereihenfolge. Das !important
       des Zusammenklappens einer Komponente (Bubble) bleibt staerker. */
    .up-root.up-root[data-flach="yes"].up-band,
    .up-root.up-root[data-flach="yes"] .up-band {
      /* width auto: .up-root traegt 100%, damit wuerde der Auslauf das Band nur verschieben. */
      width: auto; margin-left: calc(-1 * var(--up-flach-aus)); margin-right: calc(-1 * var(--up-flach-aus));
      border-top: 1px solid var(--vc-border); border-bottom: 1px solid var(--vc-border);
    }
    .up-root.up-root[data-flach="yes"] .up-band.up-band-einheit,
    :is(.up-root.up-root[data-flach="yes"].up-band, .up-root.up-root[data-flach="yes"] .up-band) .up-band-einheit {
      padding: 0 var(--up-flach-aus);
    }
    /* min-height und nicht height: bricht die Zeile schmal um, waechst sie, statt zu ueberlaufen. */
    :is(.up-root.up-root[data-flach="yes"].up-band, .up-root.up-root[data-flach="yes"] .up-band) .up-band-head {
      height: auto; min-height: var(--up-flach-h); margin-bottom: 0;
    }
    :is(.up-root.up-root[data-flach="yes"].up-band, .up-root.up-root[data-flach="yes"] .up-band):not(.up-band-einheit) > .up-band-head {
      padding-left: var(--up-flach-aus); padding-right: var(--up-flach-aus);
    }
    /* ZWEIZEILIG (Ueberschrift mit Unterzeile, oder schmal umgebrochen): 16 ueber der Schrift wie
       in der 48er Zeile (dort 15.6), 8 unter ihr -- mit den 8 ueber dem Inhalt dieselben 16 wie
       zwischen Linie und Ueberschrift. Ohne das stand die Ueberschrift 6px unter der Linie, weil
       die zwei Zeilen die 48 schon fuellten (Shopping, 15.10. gemeldet: "Top ist hier und da zu
       klein"). Die Regel stand zuerst in brands-overview.css fuer den schmalen Kopf. */
    :is(.up-root.up-root[data-flach="yes"].up-band, .up-root.up-root[data-flach="yes"] .up-band) .up-band-head:is(.is-zweizeilig, :has(.up-sec-sub)) {
      padding-top: 16px; padding-bottom: 8px;
    }
    :is(.up-root.up-root[data-flach="yes"].up-band, .up-root.up-root[data-flach="yes"] .up-band) > .up-box,
    :is(.up-root.up-root[data-flach="yes"].up-band, .up-root.up-root[data-flach="yes"] .up-band) .up-band-flaeche {
      border-width: 0; border-radius: 0; background: transparent; margin-left: 0; margin-right: 0;
    }
    :is(.up-root.up-root[data-flach="yes"].up-band, .up-root.up-root[data-flach="yes"] .up-band) .up-band-inhalt {
      padding: 8px 0 16px;
    }
    :is(.up-root.up-root[data-flach="yes"].up-band, .up-root.up-root[data-flach="yes"] .up-band):not(.up-band-einheit) > .up-band-inhalt {
      padding: 8px var(--up-flach-aus) 16px;
    }
    .up-root.up-root[data-flach="yes"].up-band-zwei,
    .up-root.up-root[data-flach="yes"] .up-band-zwei { gap: 1px; background: var(--vc-border); }
    :is(.up-root.up-root[data-flach="yes"].up-band-zwei, .up-root.up-root[data-flach="yes"] .up-band-zwei) > .up-band-einheit {
      background: var(--up-surface);
    }
    /* :not([hidden]): das display des Rasters schluege sonst das hidden einer Seite. */
    .up-root.up-root[data-flach="yes"].up-band-raster:not([hidden]),
    .up-root.up-root[data-flach="yes"] .up-band-raster:not([hidden]) {
      display: grid; grid-template-columns: repeat(auto-fit, minmax(min(var(--up-band-spalte, 472px), 100%), 1fr));
      align-items: stretch;
    }
    /* AUSGEBLENDET IST DAS BAND GANZ WEG (15.10.: "gehen die ganz weg und lassen sich ueber einen
       dann kommenden Button in der Topbar wieder einblenden"). Keine Restzeile, keine Linie; eine
       Huelle, die nur das Band traegt (Citations, .ucs-chart), faellt mit weg -- sonst stuende
       ihr Abstand im Stapel als Luecke da. Ein- und ausgeblendet wird seit dem 15.10. spaet ueber
       den Umschalter, den UC.bandAus in den Seitenkopf setzt (.up-bandsw, immer da). */
    :is(.up-root.up-root[data-flach="yes"].up-band, .up-root.up-root[data-flach="yes"] .up-band).up-band.is-band-aus { display: none; }
    .up-root :has(> .up-band.is-band-aus:only-child) { display: none; }
    /* Mit dem Umschalter im Kopf weicht das eigene Auge des Bandes ("Nicht ueber so ein Eye-Icon").
       Nur dann: ohne Seitenkopf setzt bandAus kein .is-band-schalter, und das Auge bleibt der Weg. */
    :is(.up-root.up-root[data-flach="yes"].up-band, .up-root.up-root[data-flach="yes"] .up-band).is-band-schalter .up-band-auge { display: none; }
    /* Der Umschalter im Seitenkopf: steht er direkt im Kopf (Citations, Aktualisieren ist dort ein
       eigenes Kind), rueckt er an die Werkzeuge rechts, 8 vor ihnen wie zwischen den Knoepfen
       einer Werkzeuggruppe (.uds-tools, .ubr-tools) -- der Kopf selbst haelt 16 Abstand. In einer
       Werkzeuggruppe steht er ohnehin richtig. .up-band-zeigen ist der Knopf "Show chart" von
       vorher; den setzt core nicht mehr, die Klasse bleibt fuer den Vertrag stehen. */
    .up-ph-top > :is(.up-bandsw, .up-band-zeigen) { margin-left: auto; margin-right: -8px; }
    /* Auf dem Telefon (is-vnarrow, Wurzel unter 500) werden die Kapseln rund, 32 x 32 statt 40 x 32:
       gemessen 15.10. bei 375 auf Brands fehlten der Krumenzeile ("Brands > Tracked", 146) neben
       Umschalter, "Add" und Aktualisieren gut 2px, sie endete in "Bran... > Track...". Die 16 machen
       daraus 14 Luft. Hoehe, Radius und Zeichen bleiben -- nur das Polster neben dem Zeichen faellt
       auf 8, die Form des Themenschalters vor dem 15.10. spaet. */
    .up-root.is-vnarrow .up-ph-top .up-seg.up-bandsw .up-seg-btn { padding: 0 8px; }
    /* Und der Hinzufuegen-Knopf daneben wird zu seinem Zeichen, wie Export in der Werkzeugleiste
       (.is-w0): auf Deutsch ist seine schmale Fassung "Hinzufuegen" 105 breit, gemessen 15.10. bei
       375 blieb von "Brands > Beobachtet" nur "B... > Beob...". Ohne die Worte 32 x 32 (das Polster
       0 8 hat .up-export schon), der Name bleibt als aria-label. Nur in einem Kopf mit Umschalter --
       ohne ihn hatte die Zeile die Luft, und der Knopf behaelt sein Wort. */
    /* .has-bandsw setzt bandAus mit dem Umschalter (25.10.), statt :has(.up-bandsw): ein :has vor einem Elementnamen (span)
       liess jede Aenderung im DOM die Stile breit neu rechnen (gemessen 11 ms je Zeichen). Spezifitaet wie vorher. */
    .up-root.is-vnarrow .up-ph-top.has-bandsw .up-ph-addbtn span { display: none; }

    /* ---------- DIE ZEILE UNTER DEM BAND (15.10.) ----------
       Zwischen der Unterkante eines Bandes und der Zeile darunter (Leiste oder Ueberschrift einer
       Tabelle) steht --up-band-luft (16) -- derselbe Abstand wie in Prompt Insights von der Linie
       der Navigation zur Leiste, und dieselbe Luft wie von der Leiste zum Tabellenkopf (.up-head,
       16 Polster unten). Vorher je Seite anders: 24 (Citations, Brands), 48 (Shopping, Ads).
       Gilt fuer ein Band und fuer eine Gruppe, die mit einem Band endet (Shopping: Kennzahlen und
       Diagramm), sofern danach kein weiteres Band kommt -- zwischen zwei Baendern bleibt der
       Abstand zwischen Bloecken. Der Stapel setzt seinen Abstand als --up-stapel-luft (gap:
       var(--up-stapel-luft)); der Rand gleicht ihn hier auf die 16 aus. Ein Stapel ohne die
       Variable bekommt die 16 obendrauf -- darum setzt jede Seite mit Band sie.
       Kein :has() im :has(): das verwirft der Browser samt der ganzen Regel (gemessen). Darum
       steht "danach beginnt eine Huelle mit einem Band" (Dashboard: .uds-teil) als eigenes
       :not(:has(...)) daneben und nicht im ersten :has(). */
    :is(.up-band, :has(> .up-band:last-child)):has(+ :not(.up-band)):not(:has(+ * > .up-band:first-child)) {
      margin-bottom: calc(var(--up-band-luft) - var(--up-stapel-luft, 0px));
    }
    /* Und darunter dieselbe Luft: eine Abschnittsueberschrift (.up-sec-head, Shopping) direkt ueber
       einer flachen Tabelle haelt bis zu deren Linie 16 statt ihrer 14 -- wie die Leiste und der
       Kopf .up-head (16). Gemessen vorher: Ueberschrift bis Tabelle 14, Leiste bis Tabelle 16. */
    .up-sec-head:has(+ .up-root[data-flach="yes"]) { margin-bottom: var(--up-band-luft); }

    /* ---------- DAS BAND IM KASTEN (Drawer, 15.10. spaet) ----------
       "Elemente wie Visibility Chart oder Top Citations Dashboard als ein Container mit Border, bzw.
       einen durchgehenden Separator mittig". Im Drawer stehen beide als Band (data-flach aus
       .drawer_markup.py) -- dieselben Teile wie auf dem Dashboard: Ueberschriftzeile 48 in der
       Einheit, Flaeche, rechts die flache Tabelle mit der Linie unter der Ueberschriftzeile. Dazu
       kommt nur der Kasten, und der Auslauf faellt weg. Vorher zwei Kaesten mit der Ueberschrift
       darueber und 16 Luft dazwischen.
         - Rand 1px --vc-border, Radius --up-r-2xl (12) wie jede Karte, eigener Grund --vc-bg.
         - Die Trennlinie ist der Rand der zweiten Einheit (nebeneinander links, gestapelt oben),
           nicht die 1px-Luecke des offenen Bandes: die braucht deckende Einheiten auf einem Grund
           in Linienfarbe, und deckende Einheiten mit eckigen Ecken stuenden ueber die runden des
           Kastens hinaus. So bleiben die Einheiten durchsichtig und von Kante zu Kante hoch.
         - overflow bleibt sichtbar: die Menues der Ueberschriftzeile (Sortierung, Filter) haengen
           position:absolute in der Wurzel und waeren sonst abgeschnitten. Was an eine Ecke reicht
           (die Tabelle unten rechts, gestapelt unten), rundet selbst konzentrisch mit 11 -- in der
           Komponente, sie kennt ihre Teile.
       Heute stehen nur Baender, die selbst die Wurzel sind, in einem Drawer (vot, tcd); gestapelt
       heisst dort .is-narrow oder .is-max an der Wurzel.
       Nur im Drawer (.up-kdr aus core, [id^="drawer-"] die Bubble-Gruppen): auf den Seiten bleibt
       das Band offen. Spezifitaet (0,5,0) und mehr: schlaegt das Band (0,4,0) und den Grund der
       Einheiten (0,5,0) ohne die Ladereihenfolge. */
    :is(.up-kdr, [id^="drawer-"]) .up-root.up-root[data-flach="yes"].up-band {
      margin-left: 0; margin-right: 0;
      border: 1px solid var(--vc-border); border-radius: var(--up-r-2xl); background: var(--vc-bg);
    }
    :is(.up-kdr, [id^="drawer-"]) .up-root.up-root[data-flach="yes"].up-band-zwei { gap: 0; }
    :is(.up-kdr, [id^="drawer-"]) .up-root.up-root[data-flach="yes"].up-band-zwei > .up-band-einheit { background: transparent; }
    :is(.up-kdr, [id^="drawer-"]) .up-root.up-root[data-flach="yes"].up-band-zwei > .up-band-einheit + .up-band-einheit {
      border-left: 1px solid var(--vc-border);
    }
    :is(.up-kdr, [id^="drawer-"]) .up-root.up-root[data-flach="yes"].up-band-zwei:is(.is-narrow, .is-max) > .up-band-einheit + .up-band-einheit {
      border-left: 0; border-top: 1px solid var(--vc-border);
    }
    /* Im Dunkeln derselbe Rand wie im Hellen -- seit dem 22.10. fuer jede Flaeche so ("IM DUNKELN
       DER RAND WIE IM HELLEN"); hier galt es schon vorher, weil Drawer und Kasten beide
       Background 1 tragen (#0f0f0f) und ohne Rand vom Kasten nur die Trennlinie zu sehen waere. */
    /* Die Luft unter dem Band (DIE ZEILE UNTER DEM BAND, oben) gilt dem offenen Band vor einer
       Leiste. Im Drawer haelt der Stapel seine Abstaende selbst (64 zwischen den Abschnitten);
       ohne das stuenden unter Top Citations 80 statt 64. Dieselbe Form wie oben, eine Stufe
       spezifischer, und nur hier: keine Huelle im Drawer traegt einen eigenen Rand unten. */
    :is(.up-kdr, [id^="drawer-"]) :is(.up-band, :has(> .up-band:last-child)):has(+ :not(.up-band)):not(:has(+ * > .up-band:first-child)) {
      margin-bottom: 0;
    }

    /* ---------- DIE TABELLENKOEPFE IM DRAWER (15.10. spaet) ----------
       "Bei den Tabellen die Heading Rows in gleicher Height und ohne Background im Lightmode": der
       Kopf jeder Tabelle im Drawer wie der der Seitentabellen -- 48 hoch (--up-flach-h; ohne
       data-flach dieselben 48 wie .up-band-head) und im Hellen ohne eigenen Grund. Vorher 45
       (Responses, URLs, Brand Editor) bzw. 40 (Conversion im URL-Drawer, Variations im
       Brand-Drawer), alle auf --vt-head-bg (#f9f8f9). Die Tabellen BEHALTEN ihren Kasten mit Rand
       und Radius -- nur der Kopf aendert sich. Top Brands und Top Citations bekommen dasselbe als
       Band (visibility-chart.css, topcitations-dashboard.css).
       "Ohne Grund" heisst die Flaeche der Tabelle (--vc-bg) und nicht durchsichtig: der klebende
       Kopf der Responses muss decken, sonst laufen die Zeilen beim Scrollen darunter durch. Der
       Kurzschluss background (statt -color) nimmt auch den Verlauf des klebenden Kopfes mit
       (.up-sticky .up-thead malt --vt-head-bg als background-image).
       Hell nur ueber html[data-up-hell="grau"] (core setzt es allein im Hellen, hellAnwenden) und
       nicht ueber :not([data-theme="dark"]) an .up-root: das traefe auch eine Huelle ohne Thema
       (.uprd-root) ueber einer dunklen Tabelle. Das Dunkel behaelt seinen Kopfgrund, nur die Hoehe
       gilt in beiden Themen.
       (0,4,0) und (0,5,1): schlaegt die Kopfhoehen der Kastentabellen (.ube-table, .uud-ctable,
       .up-vartable, je 0,3,0) und den klebenden Kopf (0,2,0) ohne die Ladereihenfolge.
       Dazu die Settings-Seite (.uss-root, 16.10.): Team und Billing stehen seitdem im Kasten wie
       die Tabellen im Drawer und tragen denselben Kopf -- vorher 45 auf --vt-head-bg aus ihren
       Schalen (.uto-table, .ubl-table, je 0,2,0). Das Team-Protokoll (.up-logtab, Kopf ohne .up-row)
       behaelt damit die 48, die es in der flachen Fassung schon hatte; der Protokollkopf des
       Marken-Editors traegt .up-row und bleibt bei 36 (.up-logtab.up-logtab .up-thead.up-row,
       weiter unten mit gleicher Staerke). */
    :is(.up-kdr, [id^="drawer-"], .uss-root) .up-root.up-root .up-thead {
      height: var(--up-flach-h, 48px); flex: 0 0 var(--up-flach-h, 48px);
    }
    html[data-up-hell="grau"] :is(.up-kdr, [id^="drawer-"], .uss-root) .up-root.up-root .up-thead { background: var(--vc-bg); }



    /* Letter fallback: a broken/missing favicon used to leave an empty square, which reads as a
       loading glitch. The initial keeps every row visually complete. */

    /* go-to arrow, revealed on row hover with a delay — same behaviour as the other tables */

    
    
    /* URL cell: title on top, url underneath — same treatment as the Quick Actions result rows */

    

    /* Mentioned? cell */

    

    /* ---------- brand stack ----------
       Overlapping 32px circles. The 4px visual gap between them is a ring in the ROW's background
       colour, not a real gap: with a negative margin the neighbour would otherwise touch. The ring
       has to follow the row's hover colour too, hence the two overrides below. */
    /* The cell clips: with a negative-margin stack plus the hover spread, a narrow column would
       otherwise let logos spill over the column border and sit on top of the neighbouring cell. */

    
    
    /* only the gap-ring follows the row hover colour; the circle fill stays the (light-in-dark) logo bg */

    
    
    /* plain text, no circle: the overflow count is a label, not another brand */
    
    /* Hovering a logo lifts it, brings it to front, and opens a gap on both sides — created by
       shifting the hovered item and everything AFTER it to the right. Items BEFORE it never move
       (so the first brand can't drift left). Following items shift twice as far so their gaps to
       each other stay at the default: they don't squash together.
       The ::before is a hover-only, enlarged hit area: it covers the item's pre-shift footprint so
       moving right/up can't pull the item out from under the cursor — that's what caused the
       hover/un-hover flicker loop. It only exists while hovered, so it never steals a neighbour's
       hover. */

    

    .up-name { font-size: 14px; font-weight: 500; color: var(--vc-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
    .up-hl { background: rgba(255, 214, 10, 0.38); color: inherit; border-radius: 3px; padding: 0 1px; }
    .up-root[data-theme="dark"] .up-hl { background: rgba(255, 214, 10, 0.26); }
    /* Der aktuelle Treffer, wenn eine Suche Schritte hat (vorheriger/naechster, 22.10. Page Audit "View initial
       HTML"): dieselbe Marke, kraeftiger. Die dunkle Zeile mit, weil die Regel darueber (0,3,0) wiegt. */
    .up-root .up-hl.is-aktiv, .up-root[data-theme="dark"] .up-hl.is-aktiv { background: var(--vc-treffer-aktiv); color: var(--vc-treffer-aktiv-ink); }
    /* Reddit fallback title (UC.redditTitleHtml) — the r/sub prefix and its separator read as a
       muted breadcrumb, same tone as everywhere else this app uses --vc-third for "secondary,
       not the point" text; the parsed slug after it stays the row's own normal text colour. */
    .up-reddit-sub, .up-reddit-sep { color: var(--vc-third); }
    .up-pages { font-size: 12px; font-weight: 400; color: var(--vc-third); white-space: nowrap; flex: 0 0 auto; }
    /* ---------- Die Zahl, die aufklappt (26.10., UC.klappZahlHtml) ----------
       Bis zum 26.10. stand das nur in domains-table.css (.udt-pagesbtn, "12 pages"); fuer die wahrscheinlichen Aliasse
       im Brand-Editor nach core gezogen, Werte 1:1. .udt-pagesbtn/.udt-chev bleiben als zweiter Selektor: die
       Landingpage zeichnet die Domain-Tabelle mit diesen Klassen nach.
       Polster mit gleich grossem Gegenrand: die Trefferflaeche ist rund 28 hoch und reicht ueber den Text hinaus, ohne
       mehr Platz im Fluss zu belegen als der blanke Text. align-self, weil eine Spalten-Flexbox den Knopf sonst auf
       ihre Breite zieht und der Hover als Balken neben dem Text haengt. */
    .up-klappzahl, .udt-pagesbtn { display: inline-flex; align-items: center; gap: 3px; border: 0; align-self: flex-start;
      padding: 5px 6px; margin: -5px -6px; border-radius: var(--up-r-s);
      background: transparent; cursor: pointer; font: inherit; font-size: 12px; font-weight: 400;
      color: var(--vc-third); white-space: nowrap; transition: color var(--up-t-1) ease, background var(--up-t-1) ease; }
    .up-klappzahl:hover, .udt-pagesbtn:hover { color: var(--vc-text); background: var(--vc-heading-bg); }
    /* Offen sagen Farbe und Gewicht, keine Flaeche: eine Flaeche hiesse "du zeigst gerade darauf", und das stimmt
       nicht mehr, sobald der Zeiger weg ist. Schlaegt den Hover auch, solange beides zutrifft. */
    .up-klappzahl.is-open, .up-klappzahl.is-open:hover,
    .udt-pagesbtn.is-open, .udt-pagesbtn.is-open:hover { color: var(--vc-text); background: transparent; font-weight: 500; }
    .up-klappzahl:focus-visible { outline: var(--up-focus-w) solid var(--up-focus); outline-offset: 0; }
    /* Der Winkel steht IMMER da: er allein macht die Zahl als Bedienelement kenntlich. In Ruhe in der vierten
       Farbe, damit eine Tabelle, die man nur ueberfliegt, leise bleibt. Er dreht sich nicht (Vorgabe der App). */
    /* Bis 26.10. stand am Winkel noch "transform 180ms" aus der Zeit, als er sich drehte, und "opacity 140ms" fuer
       ein Einblenden, das es nicht mehr gibt (er steht immer auf 1); beim Umzug auf die Skala gezogen. */
    .up-klappzahl-chev, .udt-chev { width: 13px; height: 13px; flex: 0 0 auto; color: var(--vc-fourth); overflow: hidden;
      transition: opacity var(--up-t-1) ease; }
    :is(.up-klappzahl, .udt-pagesbtn):is(:hover, :focus-visible, .is-open) :is(.up-klappzahl-chev, .udt-chev) { opacity: 1; }
    @media (prefers-reduced-motion: reduce){ .up-klappzahl, .up-klappzahl-chev, .udt-chev { transition: none; } }

    

    

    

    /* 2x28px buttons + 2px gap + the cell's 12/16px padding = 86px of hard content.
       ACTIONS_MIN sits above that so the cell never has to clip. */

    

    /* brief checkmark after copying — confirms the action at the button itself, not only in the toast */
    /* `.uut-actbtn svg { display:block }` is more specific than a bare `.up-ic-done`, which is
       why the checkmark showed permanently. Both states are qualified the same way now. */

    

    .up-empty { padding: 48px 24px; text-align: center; }
    .up-empty-ic { width: 28px; height: 28px; margin: 0 auto 12px; color: var(--vc-third); }
    .up-empty-ic svg { width: 28px; height: 28px; display: block; }
    .up-empty-h { font-size: 14px; font-weight: 500; color: var(--vc-text); margin-bottom: 4px; }
    .up-empty-t { font-size: 12.5px; color: var(--vc-muted); margin-bottom: 14px; }
    .up-empty-btn { height: 32px; padding: 0 12px; border: 1px solid var(--vc-border); border-radius: var(--up-r-m);
      background: transparent; color: var(--vc-text); font: inherit; font-size: 12px; font-weight: 500; cursor: pointer; }
    .up-empty-btn:hover { background: var(--vc-heading-bg); }
    /* ---- DER LEERZUSTAND NACH GEIST (15.10., angefordert: "nutze die Empty States") -------------
       Gelesen an vercel.com/geist/empty-state (berechnete Stile des Beispiels "Blank Slate" und
       "Informational"): eine Spalte, mittig, 24 zwischen Zeichen, Textblock und Aktion; das Zeichen
       32px in einer Kachel mit 10 Polster und 1px Rahmen (54x54, Radius 8, Grund der Karte, Farbe
       wie der Beschreibungstext); Titel 16/24, Schnitt 500, Laufweite -0.32px; Beschreibung 14/20 in
       der Sekundaerfarbe, 8 unter dem Titel; beide hoechstens 340 breit und ausgeglichen umbrochen.
       Die Aktion ist ein Sekundaerknopf (Grund der Karte, 1px Kante, Radius 6, Schrift 14/500).
       Polster 48 oben und unten wie bei Geist; seitlich 24 statt 70, weil der Baustein auch in
       schmalen Drawern und Karten steht -- die 340 Hoechstbreite des Textes macht dort die Arbeit.
       KEIN eigener Rahmen um das Ganze, anders als bei Geist: der Leerzustand steht in dieser App
       fast immer schon in einem (Tabellenkoerper, .up-box), und ein Rahmen im Rahmen waere ein
       Kasten zu viel.
       RADIEN NACH DER GEIST-SKALA (15.10. spaet): Kachel 8 = --up-r-l, Knopf 6 = --up-r-m. Gebaut
       wurde das noch auf der alten Skala (m = 8, s = 6); mit dem Umbau der Ecken waren beide still
       eine Stufe zu klein geworden.
       NUR AN .up-leer, das UC.leerHtml setzt. UC.leseFehlerHtml baut dasselbe .up-empty, und der
       Fehlerzustand bleibt bewusst, wie er ist (Auftrag: Fehlerzustaende nicht anfassen). */
    .up-empty.up-leer { display: flex; flex-direction: column; align-items: center; gap: 24px; }
    .up-leer .up-empty-ic { box-sizing: border-box; width: 54px; height: 54px; margin: 0; padding: 10px;
      display: flex; align-items: center; justify-content: center; flex: 0 0 auto;
      border: 1px solid var(--vc-border); border-radius: var(--up-r-l); background: var(--vc-bg); color: var(--vc-muted); }
    .up-leer .up-empty-ic svg { width: 32px; height: 32px; }
    .up-leer .up-empty-txt { display: flex; flex-direction: column; align-items: center; gap: 8px; }
    .up-leer .up-empty-h { max-width: 340px; margin: 0; font-size: var(--up-fs-xl); line-height: 24px;
      font-weight: var(--up-fw-m); letter-spacing: -0.02em; text-wrap: balance; }
    .up-leer .up-empty-t { max-width: 340px; margin: 0; font-size: var(--up-fs-l); line-height: 20px; text-wrap: balance; }
    .up-leer .up-empty-btn { display: inline-flex; align-items: center; justify-content: center; height: var(--up-h-btn);
      padding: 0 12px; border-radius: var(--up-r-m); background: var(--vc-bg); font-size: var(--up-fs-l); font-weight: var(--up-fw-m); }
    .up-leer .up-empty-btn:hover { background: var(--vc-heading-bg); }
    /* ---- DER LEERZUSTAND IM CHART (15.10., chartLeerHtml in core.js) ----------------------------
       Derselbe Leerzustand wie oben, aber in einer Huelle, die ueber der GANZEN Chartflaeche liegt
       (absolut, inset 0) und ein Groessen-Container ist. Die Flaeche behaelt dadurch die Hoehe des
       Charts -- der Zustand bestimmt sie nicht, die Seite springt nicht -- und der Inhalt sitzt
       senkrecht mittig darin. Die 48 Polster von oben weichen hier der Mitte; 16 bleiben, damit
       nichts an die Kante stoesst.
       Was nicht passt, faellt weg, gestaffelt nach der gemessenen Flaeche statt nach dem Aufrufer:
         voll       Kachel 54, 24, Titel 24, 8, Satz 20-60   = 130-170, mit Polster bis 202
         unter 220  ohne Satz, Abstand 16: Kachel und Titel  = 94, mit Polster 126
         unter 130  nur der Titel                            = 24, mit Polster 56
       Unter 280 Breite faellt der Satz ebenfalls weg: dort braucht er vier Zeilen und waere hoeher
       als die volle Form oben rechnet.
       Der Wirt (makeTypeChart setzt .up-chart-leer-wirt, solange der Zustand in ihm steht) braucht
       nur die Positionierung; seine Hoehe hat er aus seiner Karte. Die Linie braucht nichts, ihr
       .up-line-wrap ist schon positioniert.
       is-fluss: fuer Flaechen ohne eigene Hoehe (Balkenliste). Kein Container, kein absolut -- der
       Zustand steht im Fluss und bringt seine Hoehe selbst mit, immer in der kompakten Form
       (Kachel und Titel, 24 Polster: 142). Balkenlisten stehen in schmalen Kaesten und Spalten;
       mit Satz waeren es bis zu 220, also mehr als die Liste mit ihren Zeilen meist braucht. */
    .up-chart-leer-wirt { position: relative; }
    .up-chart-leer { position: absolute; inset: 0; z-index: 1; container: up-chart-leer / size; }
    .up-chart-leer > .up-empty.up-leer { box-sizing: border-box; height: 100%; padding: 16px 24px; justify-content: center; }
    @container up-chart-leer (max-height: 219px) {
      .up-chart-leer > .up-empty.up-leer { gap: 16px; }
      .up-chart-leer .up-empty-t { display: none; }
    }
    @container up-chart-leer (max-width: 279px) {
      .up-chart-leer .up-empty-t { display: none; }
    }
    @container up-chart-leer (max-height: 129px) {
      .up-chart-leer .up-empty-ic { display: none; }
    }
    .up-chart-leer.is-fluss { position: static; container-type: normal; }
    .up-chart-leer.is-fluss > .up-empty.up-leer { height: auto; padding: 24px; gap: 16px; }
    .up-chart-leer.is-fluss .up-empty-t { display: none; }
    /* ---- DER LEERZUSTAND IN DER TABELLE NEBEN DEM CHART (15.10., tabelleLeerHtml in core.js) ----
       Die Huelle gibt dem absoluten .up-chart-leer seine Flaeche: so hoch wie das Skelett der
       Tabelle (Zeilen x Zeilenhoehe, die Zahl setzt der Aufrufer), damit zwischen Laden und Leer
       nichts springt. flex 1: in einer Tabelle mit fester Hoehe (Top Brands, Top Citations: Band
       aus Kopf und acht Zeilen) waechst sie bis an die Unterkante, der Zustand steht mittig in der
       ganzen Flaeche. Die Stufen nach Groesse kommen unveraendert aus den Container-Queries oben.
       DIE ZEILENHOEHE folgt der Zeile, die das Skelett zeichnet: .up-row ist 72, dicht 55
       (--up-h-row) -- dieselben zwei Regeln wie bei .up-row weiter oben, also zieht ein Wechsel
       der Dichte die Flaeche mit. Tabellen mit eigener Zeile setzen --up-tab-leer-zeile in ihrer
       CSS (Top Brands und Top Citations 55, Brands Overview 56, Agentic-Listen 36), die
       Variantentabelle hier (.up-vrow, 55). */
    .up-tab-leer { --up-tab-leer-zeile: 72px; position: relative; flex: 1 1 auto; min-width: 0;
      min-height: calc(var(--up-tab-leer-zeilen, 4) * var(--up-tab-leer-zeile)); }
    .up-root.is-dense .up-tab-leer, .up-vartable .up-tab-leer { --up-tab-leer-zeile: var(--up-h-row); }
    /* ---- DER LEERZUSTAND IM BAND MIT ZWEI EINHEITEN (15.10. spaet: "ja natuerlich alignen") ----
       Zwei Leerzustaende nebeneinander sassen verschieden hoch und in verschiedener Form, weil jeder
       ueber SEINER Teilflaeche lag: der Chart ueber der Zeichenflaeche, der Doughnut unter seiner
       Kopfzeile, die Tabelle unter ihrem Tabellenkopf, die Balkenliste im Fluss oben und immer
       kompakt. Gemessen bei 1440 (Versatz der Mitten): Ads 108 und kompakt gegen voll, Shopping
       kompakt gegen voll (Oberkanten 24), Dashboard Visibility 34 und Top Citations 28, Citations 21.
       Im Band mit zwei Einheiten liegt die Ebene darum ueber dem GANZEN KASTEN der Einheit, dem Kind
       unter der Ueberschriftzeile: der reicht in beiden Einheiten von derselben Linie bis an die
       Unterkante des Bandes (das Raster bzw. die Zeile dehnt beide). Gleiche Flaeche heisst gleiche
       Mitte, und die Stufe (voll, kompakt, nur Titel) kommt aus denselben Container-Queries oben mit
       derselben Groesse -- egal ob Chart, Doughnut, Balkenliste oder Tabelle. Was zwischen Kasten
       und Ebene positioniert war (Zeichenflaeche, Wirt des Doughnuts, Huelle der Tabelle, Panels der
       Komponenten), steht dafuer statisch, aber nur solange die Ebene darin steht: gefuellt greift
       keine dieser Regeln.
       DER TABELLENKOPF bleibt unter der Ebene bedienbar (pointer-events none; die Ebene traegt
       keinen Knopf). Damit der volle Zustand ihn nie beruehrt, ist die Huelle der Tabelle hier
       mindestens 220 hoch: mit dem Kopf (49) wird der Kasten 269, der volle Zustand (bis 170 mit
       dreizeiligem Satz) laesst oben 49 frei. Die Tabellen der Baender liegen ohnehin darueber
       (Top Brands und Top Citations 440, Topics with Ads 275, Product Movement 288).
       DIE BALKENLISTE wird hier eine Ebene wie die anderen. is-fluss setzt Satz und Abstand fest
       (kompakt); beides wird nur ueber dieselben Grenzen zurueckgenommen, ab denen die Ebene sie
       sonst zeigt (220 hoch, 280 breit). Ihre Huelle .up-bars-leer (makeBarList) haelt die Hoehe
       ihres Skeletts -- Zeilen x 26 (.up-bar-sk-row: Spur 20, Polster 2 x 3) --, damit sie
       gestapelt, ohne Nachbarn, nicht auf ihr Polster zusammenfaellt und zwischen Laden und Leer
       nichts springt.
       Nur .up-band-zwei: ein Band aus einer Einheit (Brands, die Diagramme ueber Shopping und Ads)
       hat keinen Nachbarn und bleibt, wie es ist; ohne data-flach (Bubble) greift ohnehin nichts.
       Spezifitaet (0,6,0) und mehr: schlaegt die Positionen der Bausteine (.up-line-wrap,
       .up-chart-leer-wirt, .up-tab-leer) und der Komponenten ohne die Ladereihenfolge. */
    :is(.up-root.up-root[data-flach="yes"].up-band-zwei, .up-root.up-root[data-flach="yes"] .up-band-zwei) > .up-band-einheit > :has(.up-chart-leer) {
      position: relative;
    }
    /* :not(.up-chart-leer) statt eines zweiten :has(.up-chart-leer) (25.10.): gleichwertig -- wer selbst die Ebene enthaelt,
       dessen Vorfahr unter der Einheit enthaelt sie auch -- und gleich schwer (eine Klasse). Das vordere :has machte JEDES
       Element zum moeglichen Anker: jede Aenderung im DOM rechnete danach die Stile der halben Seite neu (gemessen auf der
       Landingpage 31 ms je getipptem Zeichen allein durch diese Regel, mit allen drei Funden 37 statt 6.5). */
    :is(.up-root.up-root[data-flach="yes"].up-band-zwei, .up-root.up-root[data-flach="yes"] .up-band-zwei) > .up-band-einheit > :not(.up-chart-leer) :has(.up-chart-leer) {
      position: static;
    }
    :is(.up-root.up-root[data-flach="yes"].up-band-zwei, .up-root.up-root[data-flach="yes"] .up-band-zwei) > .up-band-einheit > :has(.up-chart-leer) .up-chart-leer {
      position: absolute; inset: 0; container: up-chart-leer / size; pointer-events: none;
    }
    :is(.up-root.up-root[data-flach="yes"].up-band-zwei, .up-root.up-root[data-flach="yes"] .up-band-zwei) > .up-band-einheit > :has(.up-chart-leer) .up-chart-leer > .up-empty.up-leer {
      height: 100%; padding: 16px 24px;
    }
    @container up-chart-leer (min-height: 220px) {
      :is(.up-root.up-root[data-flach="yes"].up-band-zwei, .up-root.up-root[data-flach="yes"] .up-band-zwei) .up-chart-leer.is-fluss > .up-empty.up-leer { gap: 24px; }
    }
    @container up-chart-leer (min-height: 220px) and (min-width: 280px) {
      :is(.up-root.up-root[data-flach="yes"].up-band-zwei, .up-root.up-root[data-flach="yes"] .up-band-zwei) .up-chart-leer.is-fluss .up-empty-t { display: block; }
    }
    :is(.up-root.up-root[data-flach="yes"].up-band-zwei, .up-root.up-root[data-flach="yes"] .up-band-zwei) .up-tab-leer {
      min-height: max(220px, calc(var(--up-tab-leer-zeilen, 4) * var(--up-tab-leer-zeile)));
    }
    :is(.up-root.up-root[data-flach="yes"].up-band-zwei, .up-root.up-root[data-flach="yes"] .up-band-zwei) .up-bars-leer {
      min-height: calc(var(--up-bars-leer-zeilen, 5) * 26px);
    }
    .up-row.up-tsk { cursor: default; }
    .up-row.up-tsk:hover { background: transparent; }
    .up-tsk-bar { display: inline-block; height: 12px; border-radius: var(--up-sk-r); background: var(--vc-sk) var(--up-sk-verlauf); animation: var(--up-sk-bewegung); }
    .up-tsk-logo { display: inline-block; width: 24px; height: 24px; border-radius: var(--up-r-m); background: var(--vc-sk) var(--up-sk-verlauf); animation: var(--up-sk-bewegung); flex: 0 0 auto; }
    /* Die eine Skelettklasse (Geist, siehe --up-sk-bewegung oben): Grund, Glanz, Radius, Bewegung.
       Groesse und Lage bringt das Element selbst mit. */
    .up-skelett { background: var(--vc-sk) var(--up-sk-verlauf); border-radius: var(--up-sk-r); animation: var(--up-sk-bewegung); }
    /* Die Kachel ist 150 % breit; von 300 % nach 0 faehrt sie um genau eine Kachelbreite nach rechts
       (bei background-position p gilt: Versatz = p mal (Breite minus Kachel) = p mal -50 %). Danach
       steht wieder dasselbe Bild -- ein nahtloser Lauf wie Geists translate(-50%) in reverse. */
    @keyframes up-sk-geist { from { background-position: 300% 0; } to { background-position: 0 0; } }
    /* uutpulse traegt seit dem 15.10. kein Skelett mehr (alle auf up-sk-geist). Die Keyframes
       bleiben stehen: sie stehen im Vertrag, und eine Komponente auf einem aelteren Pin nennt sie. */
    @keyframes uutpulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }
    /* SKELETTE, DIE NIEMAND SIEHT, STEHEN STILL (13.10.): beim Start baut die App alle Ansichten und
       Drawer mit Skeletten -- gemessen 1280 Endlos-Animationen, rund 1090 davon in geparkten
       Ansichten oder geschlossenen Drawern. Stil und Malen in jedem Bild: der Hauptthread war in Ruhe
       zu 100 Prozent belegt (5.1 s Arbeit in 5 s, 4x CPU), mit dieser Regel 0.5 s. Pausieren statt
       abschalten: kein Layoutwechsel, und beim Einblenden laeuft es an derselben Stelle weiter.
       html davor fuer die Spezifitaet gegen .x .y::after-Regeln der Skelette. */
    html [id^="view-"]:not(.view-on) *, html [id^="view-"]:not(.view-on) *::before, html [id^="view-"]:not(.view-on) *::after,
    html .up-kdr:not(.open) *, html .up-kdr:not(.open) *::before, html .up-kdr:not(.open) *::after { animation-play-state: paused; }

    /* Narrow layouts drop columns too, but that is handled together with the gear menu in
       applyCols() — an inline grid-template from JS would beat a class rule here, so both
       have to come from the same place. */
    /* ---------- progressive disclosure by width ----------
       Four breakpoints, dropping the least important control first. Order of sacrifice:
       brand toggle (a shortcut, reachable via the filter) → citation type label collapses to an
       icon-width control → sort (the header sorters still work) → columns gear. Export and search
       survive longest: export is the reason people come to the table, search is the only way to
       find something in a long list. */
    /* Labelled dropdowns are never squeezed into icon-sized buttons: a control that loses its
       label stops saying what it filters, which is the whole reason it is labelled. They drop out
       one at a time instead, least important first.
         w3  brand toggle      (a shortcut; the same filter exists in the dropdowns)
         w2  mentioned brands  (the broader of the two brand filters)
         w1  sort              (the header sorters still cover Share and Last Seen)
         w0  columns gear      + Export collapses to its icon
       Search and the type filter survive longest: search is the only way through a long list,
       and the type filter is the one people actually reach for. */
    
    .up-root.is-w2 .up-ment { display: none; }
    .up-root.is-w1 .up-sort { display: none; }
    .up-root.is-w0 .up-cols { display: none; }
    .up-root.is-w0 .up-export span { display: none; }        /* export shrinks to its icon */
    .up-root.is-w0 .up-export { padding: 0 8px; }
    .up-root.is-w0 .up-search.is-open .up-search-box { width: 120px; }
    .up-root.is-vnarrow .up-search.is-open .up-search-box { width: 140px; }

    /* table: same idea, dropping cell content before dropping whole columns
       (a domain name you can't read is worse than a missing trend chip) */
    
    .up-root.is-t1 .up-pages { display: none; }

    /* ---------- tooltip (same as the other components) ---------- */
    /* DER TOOLTIP LIEGT UEBER ALLEM (21.09. gemeldet: "die Tooltips in der eingeklappten Leiste
       sollen trotzdem ueber dem Dropdown liegen -- bei offenem Pinned-Menue liegt das Tooltip
       von Recent darunter"). Gemessen lagen beide als Kinder des <body> nebeneinander, es
       entschied also allein die Zahl: Tooltip 2147483000 gegen Miras Menue 2147483600. Und das
       Menue ist nicht der einzige: quick-actions steht auf 2147483601, die Benachrichtigung auf
       2147483500, der Markendialog auf 2147483200.
       Ein Tooltip ist der oberste Gegenstand der Oberflaeche -- er beschriftet, was darunter
       liegt, und kann nichts verdecken, weil er pointer-events: none traegt und beim ersten
       Klick verschwindet. Also eine Zahl ueber ALLEN anderen, mit etwas Luft unter dem
       Hoechstwert von int32 (2147483647), damit darueber noch etwas Platz bleibt. */
    .up-tip {
      position: fixed; z-index: 2147483640; box-sizing: border-box;
      height: 24px; display: inline-flex; align-items: center; padding: 0 9px;
      border-radius: var(--up-r-m); background: #1f1f1b; color: #ffffff;
      font-family: Geist, system-ui, sans-serif; font-size: 12px; font-weight: 500; line-height: 1;
      white-space: nowrap; pointer-events: none;
      /* e-2, die Tiefe von allem, was an einem Ausloeser haengt -- dieselbe wie die Erklaerkarte,
         die ein grosser Tooltip ist. Vorher ein eigener Wert (0 4px 14px .18). Das Token
         erreicht den Tooltip am <body>, seit die Tiefen an :root stehen. */
      box-shadow: var(--up-e-2);
      opacity: 0; transform: translateY(4px);
      transition: opacity .14s ease, transform .14s ease;
    }
    /* wider, wrapping variant for the full URL title (which can be long) */
    /* Mehrzeilig 8 statt 6 (Geist, 15.10.): die Rollenregel der Ecken -- einzeilig 6, mehrzeilig 8 --
       galt bis dahin nur fuer die Chart-Tooltips. */
    .up-tip.is-wide { height: auto; white-space: normal; max-width: 340px; padding: 6px 9px; line-height: 1.35; align-items: flex-start; border-radius: var(--up-r-l); }
    .up-tip.is-on { opacity: 1; transform: translateY(0); }
    /* Rechts angesetzt bekommt der Chip KEINE Verschiebung: er sitzt auf der Mitte des
       Ausloesers, und die Grundregel oben wuerde ihn um 4px nach unten schieben. Den Wert setzt
       placeTip() direkt am Element -- eine Regel hier verlor gegen die Grundregel, gemessen blieb
       transform: translateY(4px) aktiv. Eingeblendet wird allein ueber die Deckkraft. */
    .up-tip[data-theme="dark"] { background: #f0f0f0; color: var(--vc-text-invert); }

    /* ---------- Der Toast (10.10., UC.toast in core.js) ----------
       Unten mittig im INHALTSbereich: links die Breite der Seitenleiste, die sidebar.js als
       --up-sidebar-w ans <html> schreibt (dieselbe Mitte wie die Massenleiste der Prompts-Tabelle).
       --up-toast-unten setzt core, wenn unten schon etwas liegt. Die Huelle laesst Klicks durch,
       nur die Toasts selbst nehmen sie an.
       z-index ueber Drawern, Menues und Dialogen, unter dem Tooltip (2147483640 -- der
       beschriftet und verdeckt nie). */
    .up-toasts {
      position: fixed; left: var(--up-sidebar-w, 0px); right: 0; bottom: var(--up-toast-unten, 24px);
      z-index: 2147483630; display: flex; flex-direction: column; align-items: center; gap: 8px;
      padding: 0 16px; pointer-events: none;
      font-family: Geist, system-ui, sans-serif; -webkit-font-smoothing: antialiased;
      transition: bottom var(--up-t-2) var(--up-ease);
    }
    /* Im Top Layer (popover, siehe core.js toastHost) bringt der Browser eigene Vorgaben mit:
       inset 0, margin auto, Rahmen, Hintergrund. Weggeraeumt wie bei .uca-portal[popover]. */
    .up-toasts[popover] {
      top: auto; width: auto; height: auto; max-width: none; max-height: none;
      margin: 0; border: 0; background: transparent; color: inherit; overflow: visible;
    }
    .up-toasts[popover]:not(:popover-open) { display: none; }
    .up-toast {
      box-sizing: border-box; pointer-events: auto;
      display: flex; align-items: flex-start; gap: 10px;
      width: fit-content; max-width: min(420px, calc(100vw - 32px)); min-height: 44px;
      /* Radius der Kartenstufe (12, Geist Toast; bis 15.10. --up-r-xl = 12, das jetzt 8 ist). */
      padding: 12px 12px 12px 14px; border-radius: var(--up-r-2xl);
      background: var(--vc-toast-bg); color: var(--vc-toast-ink); box-shadow: var(--up-e-4);
      opacity: 0; transform: translateY(24px) scale(.96);
      transition: opacity var(--up-t-2) var(--up-ease-auf), transform var(--up-t-2) var(--up-ease-auf);
    }
    /* DEUTLICHER HEREIN (12.10. gemeldet: "sieht oft aus, als wuerden die einfach aufploppen"):
       24px Weg statt 10, die Dauer des Seitenauftritts (600ms) mit easeOutExpo wie mobbin -- der
       groesste Teil der Strecke liegt in den ersten 200ms, der Rest setzt sanft auf. Dazu 120ms
       Abstand zur Handlung: der Toast antwortet auf den Klick, statt mit ihm zusammenzufallen. Die
       Deckkraft ist schneller als der Weg, sonst steht ein halb durchsichtiger Kasten lange da. */
    .up-toast.is-on { opacity: 1; transform: none;
      transition: opacity var(--up-t-3) ease var(--up-t-1), transform var(--up-t-auftritt) var(--up-ease-expo) var(--up-t-1); }
    @media (prefers-reduced-motion: reduce) {
      .up-toast, .up-toast.is-on { transform: none; transition: opacity var(--up-t-1) ease; }
    }
    /* Abgang: der Handoff nennt 160 ms -- auf die Skala gezogen (--up-t-1, 120 ms); der Unterschied
       ist nicht zu sehen, eine Dauer neben der Skala waere ein dritter Wert fuer dasselbe. */
    .up-toast.is-weg { opacity: 0; transform: scale(.97); transition: opacity var(--up-t-1) ease, transform var(--up-t-1) ease; }
    .up-toast-ic { flex: 0 0 16px; width: 16px; height: 16px; margin-top: 2px; display: inline-flex; color: var(--vc-toast-neutral); }
    .up-toast-ic svg { width: 16px; height: 16px; display: block; }
    .up-toast.is-success .up-toast-ic { color: var(--vc-toast-ok); }
    .up-toast.is-error .up-toast-ic { color: var(--vc-toast-fehler); }
    .up-toast-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; padding-right: 4px; }
    /* break-word, nicht anywhere: bei "anywhere" zaehlt jeder Buchstabe als moegliche Bruchstelle,
       und im schmalen Inhaltsbereich brach der Text Zeichen fuer Zeichen um (10.10. gemessen bei
       180px neben der Leiste). So bleibt das laengste Wort die kleinste Breite. */
    .up-toast-t { font-size: var(--up-fs-m); font-weight: var(--up-fw-m); line-height: 20px; overflow-wrap: break-word; }
    .up-toast-hv { color: var(--vc-toast-akzent); overflow-wrap: anywhere; }
    .up-toast-d { font-size: var(--up-fs-m); font-weight: var(--up-fw-n); line-height: 18px; color: var(--vc-toast-sub); overflow-wrap: break-word; }
    .up-toast-act {
      flex: 0 0 auto; height: 24px; margin-top: -2px; padding: 0 8px; border: 0; border-radius: var(--up-r-s);
      background: var(--vc-toast-flaeche); color: var(--vc-toast-ink);
      font-family: inherit; font-size: var(--up-fs-m); font-weight: var(--up-fw-m); cursor: pointer;
      transition: background var(--up-t-1) var(--up-ease);
    }
    .up-toast-act:hover { background: var(--vc-toast-flaeche-hover); }
    .up-toast-x {
      flex: 0 0 20px; width: 20px; height: 20px; padding: 0; border: 0; border-radius: var(--up-r-s);
      display: inline-flex; align-items: center; justify-content: center;
      background: transparent; color: var(--vc-toast-zu); cursor: pointer;
      transition: background var(--up-t-1) var(--up-ease), color var(--up-t-1) var(--up-ease);
    }
    .up-toast-x svg { width: 13px; height: 13px; display: block; }
    .up-toast-x:hover { color: var(--vc-toast-ink); background: var(--vc-toast-flaeche); }
    .up-toast-act:focus-visible, .up-toast-x:focus-visible { outline: var(--up-focus-w) solid var(--up-focus); outline-offset: 1px; }
    /* DIE MELDUNG MIT INHALT (17.10., Meldungen je Event): eine feste Breite statt fit-content --
       drei stehende Toasts mit Adressen verschiedener Laenge sprangen sonst als Treppe. 380 liegt
       unter dem Deckel von 420 und laesst an Host und Pfad genug stehen. */
    .up-toast.is-reich { width: min(380px, calc(100vw - 32px)); }
    /* AM TELEFON NIE UEBER EINEM OFFENEN DIALOG (Audit EV-03, Rest): die stehenden Meldungen liegen im Top
       Layer ueber jedem Dialog und deckten bei 375 Breite Felder des "Create event"-Dialogs (Datum, Typ).
       Solange ein Dialog offen ist, sind sie unsichtbar, aber nicht geschlossen: danach stehen sie wieder
       da, und gelesen ist nichts. Ab 500 Breite ist daneben Platz, dort bleibt alles wie es war. */
    @media (max-width: 499px) {
      html:has(.up-modal-backdrop.is-shown) .up-toast.is-bleibt { visibility: hidden; }
    }
    /* DIE KARTE (20.10., Meldung "X is cited" bei Events; Vorbild: Mitteilungskarte des Nutzers): links eine Kachel 44 mit dem Bild der ersten
       zitierten Seite, rechts der Name des Events und eine Zeile Text. Die Farben sind die des Toasts (umgekehrt: dunkel im hellen Thema, hell im
       dunklen), dazu eine Haarlinie aus der Flaeche des Toasts. Radius 16 (Kartenstufe gross), die Kachel 12. */
    .up-toast.is-karte { align-items: center; gap: 14px; padding: 14px; border-radius: var(--up-r-3xl); border: 1px solid var(--vc-toast-flaeche); }
    .up-toast-bild {
      flex: 0 0 44px; width: 44px; height: 44px; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
      border-radius: var(--up-r-2xl); border: 1px solid var(--vc-toast-flaeche-hover); background: var(--vc-toast-flaeche);
      color: var(--up-karte-zeichen, var(--vc-toast-ink));
    }
    .up-toast-bild img { width: 24px; height: 24px; border-radius: var(--up-r-m); object-fit: contain; display: block; }
    .up-toast-bild svg { width: 20px; height: 20px; display: block; }
    .up-toast.is-karte .up-toast-txt { gap: 2px; }
    .up-toast.is-karte .up-toast-t { font-size: var(--up-fs-l); line-height: 20px; }
    .up-toast.is-karte .up-toast-x { align-self: flex-start; margin: -6px -6px 0 0; }
    .up-toast.is-klick { cursor: pointer; }
    .up-toast.is-klick:focus-visible { outline: var(--up-focus-w) solid var(--up-focus); outline-offset: var(--up-knopf-fokus-abstand); }
    /* Die Adressen: eine Zeile je Seite, Favicon 14 vor dem gekuerzten Text, leiser als der Satz
       darueber (12 in der Nebenschrift). "+1 more" steht buendig unter dem Text der Zeilen
       (14 Bild + 8 Abstand). */
    .up-toast-liste { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; min-width: 0; }
    .up-toast-zeile { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: var(--up-fs-s); line-height: 16px; color: var(--vc-toast-sub); }
    .up-toast-fav { flex: 0 0 14px; width: 14px; height: 14px; border-radius: calc(var(--up-r-xs) - 1px); }
    .up-toast-url { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--vc-toast-ink); }
    .up-toast-urlwort { color: var(--vc-toast-ink); }
    .up-toast-mehr { margin-top: 4px; padding-left: 22px; font-size: var(--up-fs-s); line-height: 16px; color: var(--vc-toast-sub); }
    .up-toast-d.up-toast-fertig { display: flex; align-items: center; gap: 6px; margin-top: 6px; color: var(--vc-toast-ink); }
    .up-toast-fertig svg { flex: 0 0 14px; width: 14px; height: 14px; }
    @media (prefers-reduced-motion: reduce){
      .up-toast, .up-toast.is-on, .up-toast.is-weg { transform: none; }
      .up-toasts { transition: none; }
    }
    /* Tooltips keep the same dark card in both themes: they float above the page, not inside
       the surface, so inverting them made them read as a different component. */

/* ==========================================================================================
   CHART KITS — styling for UpstreemCore.makeLine / makeTypeChart
   ==========================================================================================
   These used to live once per component (.vc-line-* + .cc-line-*, .tcl-* + .ccd-*), which is how
   the same chart drifted apart between components. Everything here is now the single source; a
   component only supplies the wrap/canvas/legend/body elements and its own box sizing. */

/* ---------- line chart ---------- */
.up-line-wrap { position: relative; flex: 1 1 auto; min-height: 0; width: 100%; }

/* ---- Wasserzeichen im Liniendiagramm --------------------------------------------------------
   Das Div setzt core.js (injectWatermark) leer ein, alles Sichtbare steht hier -- so wechselt die
   Datei beim Themenwechsel von selbst mit, sobald setUpstreemTheme data-theme umstellt.

   Zur Groesse, weil sie nicht mehr die alte ist und das erklaert werden muss: das frueher inline
   gezeichnete Zeichen hatte ein Seitenverhaeltnis von 3.84 (707 zu 184), die Lockup-Datei hat
   7.04 (760.5 zu 108) -- sie ist deutlich flacher. "Gleiche Groesse" geht damit nicht auf beiden
   Achsen. Gemessen wurde die TINTE, also der wirklich bedeckte Bereich, per Pixelabtastung:
     alt, Kasten 150 x 39.04   ->  Tinte 147 x 28
     neu, Kasten 150 x 21.31   ->  Tinte 141 x 22   (gleiche Breite, 21% flacher)
     neu, Kasten 275 x 39.04   ->  Tinte 258 x 40   (gleiche Hoehe, 75% breiter)
   Genommen ist der Kasten mit der GLEICHEN FLAECHE: 173 x 24.57 ergibt Tinte etwa 163 x 25, also
   beide Achsen innerhalb von zehn Prozent des alten Zeichens. Deckkraft (0.05) ist unveraendert.

   ZUR LAGE: top 47% und dazu 4px nach unten (08.09., in zwei Schritten -- erst "4px, oder eher
   6", dann "nochmal 2px nach oben"; das ergibt die 4 vom Anfang). Die 47% sind der alte Wert --
   das Zeichen sitzt absichtlich etwas ueber der Mitte, weil unter dem Chart die Achsenbeschriftung
   steht und die Flaeche darueber optisch groesser ist. Die 6px kommen als Verschiebung im
   transform dazu und nicht als geaenderte Prozentzahl: Prozent haengen an der HOEHE des Kastens,
   und in einem hohen Chart waere derselbe Prozentwert ein anderer Weg in Pixeln. Angefordert
   waren Pixel, also stehen dort Pixel.

   background-size: contain und nicht 100% 100%: die Datei behaelt ihr Verhaeltnis, auch wenn
   jemand den Kasten spaeter anfasst. */
.upstreem-watermark {
  position: absolute; top: 47%; left: 50%; transform: translate(-50%, calc(-50% + 4px));
  width: 173px; height: 24.57px;
  opacity: 0.05; pointer-events: none; z-index: 2;
  background-repeat: no-repeat; background-position: center; background-size: contain;
  background-image: url("https://tgdossbsevnonssyuewp.supabase.co/storage/v1/object/public/BRANDSTYLES/upstreem-lockup-1f1f1f.svg");
}
.up-root[data-theme="dark"] .upstreem-watermark {
  background-image: url("https://tgdossbsevnonssyuewp.supabase.co/storage/v1/object/public/BRANDSTYLES/upstreem-lockup-e0e0e0.svg");
}
.up-line-canvas { display: block; width: 100% !important; height: 100% !important; }
/* Schrift der Leerzeilen nach Geist (15.10.): copy-14, also 14/20 in der Sekundaerfarbe -- so steht
   der knappe Leerzustand auch im Command Menu von Geist ("No results found"). */
.up-line-empty { position: absolute; left: 0; right: 0; top: 58%; transform: translateY(-50%); display: flex; align-items: center; justify-content: center; font-size: var(--up-fs-l); line-height: 20px; color: var(--vc-muted); }

/* line skeleton: grid, axis labels and a fake series path. Seit dem 15.10. mit dem Schimmer nach
   Geist (--up-sk-verlauf, siehe oben) statt des weissen Lichtlaufs: die Raster- und Achsenbalken
   tragen ihn als Hintergrund, die Kurve ueber einen maskierten Streifen (.up-sk-wisch, wie der
   Ring). */
.up-line-sk { position: absolute; inset: 0; pointer-events: none; }
.up-line-sk .sk-linechart { width: 100%; height: 100%; box-sizing: border-box; display: flex; flex-direction: column; gap: 6px; padding: 32px; }
.up-line-sk .sk-lc-grid { flex: 1; position: relative; display: flex; flex-direction: column; justify-content: space-between; }
.up-line-sk .sk-lc-hline { height: 1px; background: var(--vc-sk) var(--up-sk-verlauf); border-radius: 1px; position: relative; overflow: hidden; animation: var(--up-sk-bewegung); }
.up-line-sk .sk-lc-svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.up-line-sk .sk-lc-astop0 { stop-color: var(--vc-sk); stop-opacity: .5; }
.up-line-sk .sk-lc-astop1 { stop-color: var(--vc-sk); stop-opacity: 0; }
.up-line-sk .sk-lc-stroke { fill: none; stroke: var(--vc-sk); stroke-width: 1.875; stroke-linecap: round; }
.up-line-sk .sk-lc-xaxis { display: flex; justify-content: space-between; flex-shrink: 0; padding: 0 1px; }
.up-line-sk .sk-lc-xlabel { height: 8px; width: 24px; background: var(--vc-sk) var(--up-sk-verlauf); border-radius: var(--up-sk-r); position: relative; overflow: hidden; animation: var(--up-sk-bewegung); }
/* upskt und upskt-path tragen seit dem 15.10. nichts mehr (Lichtlauf und Strich-Schimmer sind dem
   Geist-Skelett gewichen); sie bleiben fuer den Vertrag stehen. */
@keyframes upskt { 100% { transform: translateX(100%); } }
@keyframes upskt-path { 0% { stroke-dashoffset: 1000; } 100% { stroke-dashoffset: -1000; } }

/* ---------- line legend (balanced rows, hover-dims the other series) ---------- */
.up-legend { width: 100%; height: auto; box-sizing: border-box; overflow: visible; --up-column-gap: 16px; margin-top: 12px; flex: 0 0 auto; }
.up-legend.is-hidden { display: none; }
.up-legend .up-company-rows { width: 100%; height: auto; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; row-gap: 8px; box-sizing: border-box; }
.up-legend .up-company-row { width: 100%; min-height: 24px; display: flex; align-items: center; justify-content: center; column-gap: var(--up-column-gap, 16px); box-sizing: border-box; overflow: hidden; min-width: 0; }
.up-legend .up-company-item { height: 24px; padding: 2px 8px; border: 0; background: transparent; display: inline-flex; align-items: center; justify-content: flex-start; flex: 0 1 auto; min-width: 0; box-sizing: border-box; white-space: nowrap; transition: opacity 200ms ease; }
.up-legend .up-company-color { width: 10px; height: 10px; min-width: 10px; min-height: 10px; border-radius: var(--up-r-xs); flex: 0 0 auto; }
/* Vergleichsreihe (Event-Detail): das Feld gestrichelt umrandet statt gefuellt -- wie ihre Linie.
   Die Farbe kommt inline als border-color. */
.up-legend .up-company-color.is-dash { background: transparent; border: 2px dashed; box-sizing: border-box; }

/* ---- Event-Pins in den Liniendiagrammen (Design-Handoff "Event-Pins im Analytics-Chart", 02.10.) --
   Die Masse sind die der Vorgabe, Wert fuer Wert: Pin 20 x 20, Radius 6, Rahmen 1px, Zeichen 11px
   mit Deckkraft .72; Tooltip 20px rechts der Pin-Mitte, Polster 7/10, Radius 8; Stapel mit 14px
   Versatz und 1,5px Ring; Karte min. 190 breit, Polster 6. Die Hilfslinie zeichnet makeLine auf
   der Leinwand. Der Anker (.up-evm-ort) hat keine Groesse und sitzt auf der Pin-Mitte.
   Zwei Abweichungen: die Schrift ist Geist wie in der ganzen App (Vorgabe: System-UI), und der
   Zeitraum im Kartenkopf steht auf 11 statt 10,5px -- halbe Pixel runden je Browser anders, die
   Skala der App hat sie darum nicht (CLAUDE.md 1c). */
/* Der Kasten eines Charts ist sein eigener Stapelkontext (13.10.): z-index 9999/10000 von Tooltip
   und Pins gelten nur darin. Ohne das stachen sie durch die ganze Seite -- ueber Drawer (9905-9930),
   Menues und Popover. Jetzt liegen sie auf der Ebene der Seite, in der das Chart steht. */
.up-chart-kasten { isolation: isolate; }
.up-evm-layer { position: absolute; inset: 0; pointer-events: none;
  /* UEBER dem Tooltip von Chart.js (.up-line-tt, z-index 9999 im selben Kasten). Mit 2 lag die
     Karte eines Stapels hinter ihm (03.10. gemeldet); die Ebene selbst nimmt keinen Zeiger, nur
     Pins und eine offene Karte tun es. */
  z-index: 10000; }
.up-evm-ort { position: absolute; width: 0; height: 0; }
/* Die anderen Events leiser (cfg.markerDimmen, Event-Detail): nur die Deckkraft, keine neue Farbe.
   Der Wert gilt auch fuer Hilfslinien und Baender auf der Leinwand (makeLine liest ihn). Hover,
   Tastaturfokus und eine per Tipp geoeffnete Karte (.is-offen) zeigen den Pin wieder voll; das
   Fokus-Event bleibt immer voll. */
.up-chart-kasten { --up-evm-dim: .45; }
.up-evm-layer.is-gedimmt .up-evm-ort:not(.is-fokus) { opacity: var(--up-evm-dim); transition: opacity var(--up-t-1) var(--up-ease); }
.up-evm-layer.is-gedimmt .up-evm-ort:not(.is-fokus):hover,
.up-evm-layer.is-gedimmt .up-evm-ort:not(.is-fokus):focus-within,
.up-evm-layer.is-gedimmt .up-evm-ort:not(.is-fokus).is-offen { opacity: 1; }
@media (prefers-reduced-motion: reduce){ .up-evm-layer.is-gedimmt .up-evm-ort { transition: none; } }
/* Der Knopf traegt seine Pins: einer beim einzelnen Event, bis zu drei im Stapel, je 14px versetzt
   (Vorgabe), der vorderste oben. --n und --i setzt makeLine. */
.up-evm-knopf { position: absolute; left: -10px; top: -10px; height: 20px; width: calc(20px + (var(--n, 1) - 1) * 16px);
  padding: 0; margin: 0; border: 0; background: none; cursor: pointer; pointer-events: auto; font: inherit; }
.up-evm-knopf:focus-visible { outline: 2px solid var(--up-focus); outline-offset: 2px; border-radius: var(--up-r-m); }
.up-evm { position: absolute; left: calc(var(--i, 0) * 16px); top: 0; z-index: calc(3 - var(--i, 0)); width: 20px; height: 20px; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--up-evm-rand); border-radius: var(--up-r-s); background: var(--vc-bg);
  color: var(--vc-muted); box-shadow: var(--up-e-1); }
.up-evm svg { width: 11px; height: 11px; opacity: .72; flex: 0 0 auto; }
/* Das Fokus-Event im Event-Detail: dasselbe Zeichen, voll gedeckt -- der Rest ist Vorgabe. */
.up-evm.is-fokus { border-color: var(--vc-third); }
.up-evm.is-fokus svg { opacity: 1; }
/* Im Stapel ein Ring in der Flaeche des Diagramms -- er ist die sichtbare Luft zwischen den
   ueberlappenden Pins. Vorgabe 1,5px; am 03.10. zwei Pixel mehr verlangt, und zwar an DIESER Luft,
   nicht am Versatz (der kurz auf 16 stand und die Pins nur auseinanderschob). */
.up-evm-stapel .up-evm { box-shadow: 0 0 0 3.5px var(--vc-bg); }

/* DIE KARTE -- eine fuer einen Pin und fuer einen Stapel. Rechts oben am Marker: 20px rechts der
   Mitte des letzten Pins, 3px ueber seiner Oberkante; an der Kante spiegelt makeLine sie nach links
   (.is-links). Erscheint bei Hover, Fokus oder nach einem Tipp (.is-offen), 120ms Einblenden.
   Schatten e-2 wie jede Karte am Ausloeser (15.10.): die Vorgabe (0 4px 14px .08) lag zwischen
   den alten e-1 und e-2, die beide nicht passten -- das heutige e-2 (Geist menu, drei schmale Lagen
   bis 6 Prozent) ist genau diese leise Stufe, und das Dunkel trug es schon. */
.up-evm-karte { position: absolute; z-index: 5; left: calc(20px + (var(--n, 1) - 1) * 16px); top: -13px; white-space: nowrap;
  min-width: 190px; padding: 6px; display: flex; flex-direction: column; gap: 2px;
  background: var(--vc-bg); border: 1px solid var(--vc-border); border-radius: var(--up-r-m);
  box-shadow: var(--up-e-2);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity var(--up-t-1) ease, visibility 0s linear var(--up-t-1); }
.up-root[data-theme="dark"] .up-evm-karte { background: var(--up-menu-bg); border-color: var(--up-menu-border); box-shadow: var(--up-e-2); }
.up-evm-ort.is-links .up-evm-karte { left: auto; right: 20px; }
.up-evm-ort:hover .up-evm-karte,
.up-evm-ort:focus-within .up-evm-karte,
.up-evm-ort.is-offen .up-evm-karte { opacity: 1; visibility: visible; pointer-events: auto; transition-delay: 0s; }
/* Die Zeilen sind klickbar; die 10px Luft zwischen Pin und Karte ueberbrueckt ein unsichtbarer
   Streifen, sonst ginge der Hover auf dem Weg dorthin verloren. */
.up-evm-karte::before { content: ""; position: absolute; top: 0; left: -12px; width: 12px; height: 34px; }
.up-evm-ort.is-links .up-evm-karte::before { left: auto; right: -12px; }
.up-evm-ic { display: inline-flex; flex: 0 0 auto; color: var(--vc-muted); }
.up-evm-ic svg { width: 13px; height: 13px; opacity: .7; }
.up-evm-karte-kopf { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 2px 2px 4px; }
/* Datum links in der vierten Schriftfarbe, die Zahl rechts eine Stufe kraeftiger in der dritten
   (03.10.) -- sie ist die Auskunft, das Datum die Einordnung. */
.up-evm-zeitraum { padding-left: 4px; font-size: var(--up-fs-xs); color: var(--vc-fourth); font-variant-numeric: tabular-nums; }
.up-evm-zahl { font-size: var(--up-fs-xs); color: var(--vc-third); background: var(--up-sel-bg); border-radius: var(--up-r-xs); padding: 2px 6px; }
.up-evm-zeile { display: flex; align-items: center; gap: 8px; width: 100%; padding: 5px 6px; margin: 0;
  border: 0; border-radius: var(--up-r-xs); background: none; font: inherit; text-align: left; cursor: pointer; }
.up-evm-zeile:hover, .up-evm-zeile:focus-visible { background: var(--up-sel-bg); outline: none; }
.up-evm-zeile-name { flex: 1 1 auto; margin-right: 12px; font-size: var(--up-fs-s); font-weight: 500; color: var(--vc-text); }
/* Die Daten der Zeilen wie das Datum im Kopf: vierte Schriftfarbe statt der Farbe der Hilfslinie
   (die lag neben der Skala der Schriftfarben). */
.up-evm-zeile-datum { flex: 0 0 auto; font-size: var(--up-fs-xs); color: var(--vc-fourth); font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce){ .up-evm-karte { transition: none; } }
.up-legend .up-company-favicon { width: 16px; height: 16px; min-width: 16px; min-height: 16px; border-radius: var(--up-r-xs); object-fit: cover; display: block; flex: 0 0 auto; }
.up-legend .up-company-name { font-family: Geist, Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 12px; font-weight: 500; line-height: 16px; color: var(--vc-text); white-space: nowrap; max-width: 150px; overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; min-width: 0; }
.up-root.is-narrow .up-legend .up-company-name { max-width: 90px; }
.up-legend .up-company-inner-gap { width: 8px; height: 1px; flex: 0 0 8px; }
/* off-screen probe row: the balanced-rows algorithm measures intrinsic item widths from this */
.up-legend .up-company-measure { position: absolute; left: -99999px; top: -99999px; visibility: hidden; pointer-events: none; display: flex; column-gap: var(--up-column-gap, 16px); width: auto; height: auto; overflow: visible; }

/* ---------- doughnut ---------- */
.up-chart-empty { font-size: var(--up-fs-l); line-height: 20px; color: var(--vc-muted); text-align: center; padding: 40px 0; }
.up-donut-layout { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 28px; width: 100%; }
.up-donut-wrap { position: relative; flex: 0 0 auto; width: min(52%, 200px); aspect-ratio: 1 / 1; }
.up-donut-wrap canvas { width: 100% !important; height: 100% !important; }
/* once the legend drops BELOW the donut it no longer shares width with it — let the ring grow */
.up-donut-layout.is-collapsed .up-donut-wrap { width: min(75%, 220px); }
.up-donut-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; pointer-events: none; }
.up-donut-center .n { font-size: 22px; font-weight: 500; color: var(--vc-text); line-height: 1; }
.up-donut-center .lbl { font-size: 14px; font-weight: 400; color: var(--vc-third); margin-top: 8px; line-height: 1; }
.up-donut-legend { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.up-donut-legend-row { display: flex; align-items: center; gap: 10px; }
.up-donut-legend-row.is-clickable { cursor: pointer; margin: -4px -6px; padding: 4px 6px; border-radius: var(--up-r-s); transition: background 120ms ease; }
.up-donut-legend-row.is-clickable:hover { background: var(--vc-heading-bg); }
.up-donut-legend-chip { width: 12px; height: 12px; border-radius: var(--up-r-xs); flex: 0 0 auto; }
/* Das Logo in der Legende: zwischen Farbfleck und Name, mit derselben Luecke wie der Fleck sie
   zum Namen hat. 14px, damit es neben dem 10px-Fleck und dem 13px-Text nicht dominiert. */
.up-donut-legend-logo { width: 14px; height: 14px; border-radius: var(--up-r-xs); flex: 0 0 auto;
  object-fit: cover; display: block; }
.up-donut-legend-name { font-size: 12px; font-weight: 500; color: var(--vc-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; flex: 1 1 auto; min-width: 0; }
.up-donut-legend-pct { font-size: 12px; font-weight: 500; color: var(--vc-muted); white-space: nowrap; flex: 0 0 auto; }
.up-donut-layout.is-collapsed { flex-direction: column; gap: 18px; }
.up-donut-layout.is-collapsed .up-donut-legend { flex-direction: row; flex-wrap: wrap; justify-content: center; gap: 10px 16px; }

/* ---------- bars ---------- */
.up-bars { display: flex; flex-direction: column; gap: 0; width: 100%; }
/* Beschriftungsspalte links (UC.makeBarList mit labelCol). Nur dort, wo genug Platz ist -- die
   Entscheidung faellt an der EIGENEN Box der Komponente, nicht am Fenster. Die Spalte ist fest
   breit, damit alle Balken an derselben Kante beginnen; ein zu langer Name endet mit Punkten. */
.up-bars.has-labelcol .up-bar-label {
  flex: 0 0 148px; width: 148px; display: inline-flex; align-items: center; gap: 8px;
  min-width: 0; padding-right: 12px; box-sizing: border-box;
}
.up-bar-logo { width: 16px; height: 16px; flex: 0 0 16px; border-radius: var(--up-r-xs); object-fit: contain; display: block; }
.up-bar-labeltxt {
  font-size: 12px; font-weight: 500; color: var(--vc-text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
/* Steht der Name links in der Spalte, ist der Prozentwert das EINZIGE Kind im Balken -- und
   space-between schiebt ein einzelnes Kind nach links, also an den Anfang des Balkens. Dort
   gehoert er nicht hin: er ist der Endwert und steht am Ende. */
.up-bars.has-labelcol .up-bar-fill { justify-content: flex-end; }
.up-bar-row { display: flex; align-items: center; padding: 5px 0; }
.up-bar-row.is-clickable { cursor: pointer; }
/* Hover reads on the FILL and the grey TRACK's own background only — never a border, never the
   row as a whole (a full-row treatment reads as "the row is a button", which it isn't outside
   is-clickable, and even there the row's own hit area is enough without a visible outline).
   filter: brightness() works directly against each bar's inline background color, so it doesn't
   need its own per-item colour math the way the doughnut's hoverBackgroundColor does (core.js) —
   same visual language (colours read "stronger" on hover), different mechanism because one is a
   canvas fill and the other a DOM element's inline style. The track darkens via background, not
   filter — filter on the track would also repaint .up-bar-fill sitting inside it, compounding
   with the fill's own brightness() instead of leaving it alone. */
.up-bar-row:hover .up-bar-fill { filter: brightness(1.12); }
/* A dimmed (filtered-out) bar's fill is already a light neutral grey — brightening THAT further
   on hover read as "turns nearly white," not "is being hovered." Darken instead, same amount the
   grey track itself darkens by, for one consistent "muted things get a bit darker on hover" rule
   rather than two different directions depending on which grey element you're looking at. */
.up-bar-row.is-dimmed:hover .up-bar-fill { filter: brightness(0.94); }
.up-bar-row:hover .up-bar-track { background: color-mix(in srgb, var(--vc-heading-bg) 100%, black 6%); }
.up-bar-track { flex: 1; height: 32px; background: var(--vc-heading-bg); border-radius: var(--up-r-m); overflow: visible; position: relative; min-width: 0; transition: background 120ms ease; }
/* Einfahrt wie Linie und Ring (11.10.): dieselbe Dauer, dieselbe Kurve. Vorher .5s mit
   (.4,0,.2,1) -- ein langsam anfahrender Verlauf, der neben der Linie wie ein Ruck aussah. */
.up-bar-fill { height: 100%; border-radius: var(--up-r-m); transition: width var(--up-t-auftritt) var(--up-ease-quart), filter 120ms ease; display: flex; align-items: center; justify-content: space-between; padding: 0 10px; min-width: 0; gap: 8px; overflow: hidden; }
.up-bar-name { font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: opacity .18s ease; }
.up-bar-pct { font-size: 11px; font-weight: 600; white-space: nowrap; flex: 0 0 auto; transition: opacity .18s ease; }
/* shown instead of the inside labels when the bar is too short to hold them */
.up-bar-outside { position: absolute; top: 50%; transform: translateY(-50%); left: 0; pointer-events: none; display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; transition: opacity .18s ease; }
/* Das Logo im Balken sitzt vor dem Namen, in beiden Namensfeldern (innen und ausserhalb). Es
   faedt mit dem Text ein, weil es in demselben span liegt -- eine eigene Animation waere ein
   zweiter Takt in derselben Bewegung. */
.up-bar-name, .up-bar-name-out { display: inline-flex; align-items: center; gap: 6px; }
/* makeBarList zeichnet dieselben Balken noch einmal: ein Bild lang ohne Uebergang (06.10.). */
.up-bars.is-sofort :is(.up-bar-fill, .up-bar-name, .up-bar-pct, .up-bar-outside) { transition: none; }
/* DAS LOGO IM BALKEN IN EINEM KAESTCHEN (06.10. fuer alle Balkenlisten, angefordert an den
   Ads-Balken: "genau so wie ueberall, wie bei Domain Detail"). Bis hier stand das Kaestchen nur
   lokal in domain-detail.css (01.10., Modell-Logos): Rahmen, heller Grund, 2px Luft -- ueberall
   sonst lag das Bild randlos auf einer 15px-Platte, und ein Favicon, das seine Flaeche ganz
   fuellt, sah aus wie ein loses Bild. Jetzt ist das Kaestchen der Standard, fuer Logos UND
   Zeichen-Platten (Haendler in Shopping, Werbetreibende ohne Logo in Ads).
   20px aussen: 16 Bild + 2x1 Polster + 2x1 Rahmen, Radius s (6). Die Spur ist 32px hoch, es passt.
   1px Polster statt der 2 aus domain-detail (06.10. abends: "das Polster wirkt an ein paar Stellen
   zu gross"): Favicons bringen ihren eigenen Rand mit, mit 2px stand das Bild darin bei 14px und
   wirkte verloren. 1px ist das Polster jeder anderen Logo-Kachel in core (.up-logo-box, .up-ment-logo).
   Am Bild selbst und ohne Huelle: Rahmen und Polster gehen an einem img, object-fit contain
   beschneidet nichts. Im Dunkeln verschmilzt der Rahmen mit dem hellen Grund. */
.up-bar-logo { box-sizing: border-box; width: 20px; height: 20px; flex: 0 0 20px; padding: 1px;
  border: 1px solid var(--vc-border); border-radius: var(--up-r-s);
  background: var(--vt-logo-bg); object-fit: contain; display: block; }
.up-root[data-theme="dark"] .up-bar-logo { background: var(--vc-text); border-color: var(--vc-text); }
/* MODELL-LOGOS OHNE HELLE PLATTE (23.10., Lukas: "LLM-Logos brauchen nirgendwo in der App im Darkmode
   einen hellen Background"). Die Platte oben ist fuer Favicons und Markenlogos, die oft dunkel auf
   transparent sind; ein Modell-Logo ist im Dunkeln selbst hell (OpenAI als weisse Datei ueber
   modelLogoUrl). Das Kaestchen bleibt mit Rahmen und Polster, nur der Grund ist dunkel -- die Flaeche
   der Karte und nicht transparent: im Balken steht es auf der Fuellung, und die ist im Dunkeln in
   den ersten Stufen hell (BALKEN_GRAU_DUNKEL beginnt bei #e0e0e0), ein weisses Logo darauf waere weg.
   Das Spiegelbild des Hellen, wo es Weiss mit Rahmen auf jeder Fuellung steht. */
.up-root[data-theme="dark"] .up-bar-logo.is-modell { background: var(--vc-bg); border-color: var(--vc-border); }
.up-root[data-theme="dark"] .up-bar-logo.is-modell.is-zeichen { color: var(--vc-third); }
/* Die Zeichen-Platte (makeBarList, it.zeichen, 04.10.): dasselbe Kaestchen, darauf das Zeichen in
   der Tinte der Logo-Platten (--vt-logo-ink, wie der Buchstabe in .up-logo-box). */
.up-bar-logo.is-zeichen { display: inline-flex; align-items: center; justify-content: center; color: var(--vt-logo-ink, var(--vc-muted)); }
.up-bar-logo.is-zeichen svg { width: 12px; height: 12px; display: block; }
.up-bar-name-out { font-size: 11px; font-weight: 600; }
.up-bar-pct-out { font-size: 11px; font-weight: 600; }
/* Die eigene Wertspalte (cfg.pctCol). Rechts ausgerichtet und mit tabellarischen Ziffern, damit
   die Zahlen untereinander stehen -- eine Zahlenspalte, die wandert, liest sich als Fehler. */
.up-bar-pctcol { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
  color: var(--vc-text); font-size: 13px; font-weight: 600; }

/* ---------- doughnut skeleton ----------
   Sized to match .up-donut-wrap/.up-donut-layout exactly (200px cap, 220px once collapsed) —
   this used to be its own slightly-smaller 180px guess, which is why the skeleton circle visibly
   grew the instant the real chart replaced it. */
/* max-height ueberall (23.09. gemeldet: der Ring clippt unten in der Maerkte-Karte). Die Breite
   war gedeckelt, die HOEHE nicht: aspect-ratio 1 machte aus min(52%, 200px) eine ebenso hohe
   Box -- gemessen 192px in einem 173px-Koerper, also 19px unter der Kante abgeschnitten.
   Der Ring haengt an einem svg mit viewBox, er skaliert also von selbst mit, sobald die Box
   niedriger wird, und bleibt dabei rund. Dieselbe Ueberlegung wie beim Balken-Skelett, nur
   greift sie hier ueber die Hoehe statt ueber die Zeile. */
.up-donut-sk { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 28px; width: 100%; max-height: 100%; pointer-events: none; }
.up-donut-sk.is-collapsed { flex-direction: column; gap: 18px; }
.up-sk-chart { flex: 0 0 auto; width: min(52%, 200px); aspect-ratio: 1; max-height: 100%; }
.up-donut-sk.is-collapsed .up-sk-chart { width: min(75%, 220px); }
/* object-fit: contain haelt den Ring rund, wenn max-height die Box flacher macht als breit --
   ohne das zoege das svg in die Breite. */
.up-sk-chart svg { width: 100%; height: 100%; display: block; object-fit: contain; }
/* Der Schimmer im Ringskelett per CSS statt SMIL (13.10.): ein <animateTransform> tickt im
   Hauptthread in jedem Bild, auch geparkt und in einer 0x0-Huelle, und animation-play-state haelt
   es nicht an. Sechs Ringe kosteten in Ruhe 1.4 s von 5 s (1x CPU).
   SEIT DEM 15.10. NACH GEIST, und dabei repariert: der Streifen trug die Maske selbst, und eine
   Maske faehrt mit der Verschiebung ihres Elements mit -- er zeigte also immer dasselbe Stueck
   Ring, und das lag links neben dem Ring (x -60 bis -10): man sah nichts. Jetzt sitzt die Maske an
   einer Gruppe, und nur der Streifen darin faehrt. Der Streifen ist zwei Geist-Kacheln breit
   (Glanz bei 25 und 75 Prozent, sonst durchsichtig) und faehrt um die Haelfte seiner Breite,
   also um genau eine Kachel: dasselbe Bild wie --up-sk-verlauf an einem Balken, im selben Takt.
   transform-box: fill-box macht die 50 % zur Haelfte des Streifens, egal wie breit er ist --
   der Ring hat 300 Einheiten, die Linie 1380. */
.up-sk-wisch { transform-box: fill-box; animation: upskwisch var(--up-sk-dauer) var(--up-sk-kurve) infinite; }
@keyframes upskwisch { from { transform: translateX(0); } to { transform: translateX(50%); } }
@media (prefers-reduced-motion: reduce){ .up-sk-wisch { animation: none; } }
.up-sk-ring { stroke: var(--vc-sk); }
/* Die Stopps tragen den Glanz aus core (--up-sk-glanz) und nur seine Deckkraft wechselt: so
   laeuft der Verlauf ohne grauen Saum aus, in beiden Themen. */
.up-sk-g0 { stop-color: var(--up-sk-glanz); stop-opacity: 0; }
.up-sk-g1 { stop-color: var(--up-sk-glanz); stop-opacity: 1; }
.up-sk-legend { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.up-sk-row { display: flex; align-items: center; gap: 10px; }
.up-sk-dot { width: 12px; height: 12px; border-radius: var(--up-r-xs); background: var(--vc-sk) var(--up-sk-verlauf); flex: 0 0 auto; position: relative; overflow: hidden; animation: var(--up-sk-bewegung); }
.up-sk-lbl { height: 10px; background: var(--vc-sk) var(--up-sk-verlauf); border-radius: var(--up-sk-r); position: relative; overflow: hidden; animation: var(--up-sk-bewegung); }
.up-sk-pct { height: 10px; background: var(--vc-sk) var(--up-sk-verlauf); border-radius: var(--up-sk-r); flex: 0 0 auto; position: relative; overflow: hidden; animation: var(--up-sk-bewegung); }

/* ---------- bar-mode skeleton (same shimmer, shaped like .up-bar-track rows) ---------- */
.up-bars-sk { display: flex; flex-direction: column; gap: 0; width: 100%; pointer-events: none; }
/* DIE HOEHE DER ECHTEN ZEILE, NICHT EINE EIGENE (23.09. gemeldet: "der skeleton des bar charts
   clippt unten ueber die card grenze"). Gemessen in der Topics-Karte der Prompts-Tabelle: eine
   echte Balkenzeile ist 26px hoch, fuenf davon 128px in einem 172px-Koerper. Das Skelett stand
   auf 42 je Zeile -- fuenf davon 210px, also 38 ueber den Koerper und 4 sichtbar ueber die
   Kartenkante hinaus (der Koerper zentriert mit justify-content: safe center, der Ueberlauf
   verteilt sich also nach oben UND unten).
   20 Spur + 2x3 Polster = 26, dieselbe Zeile wie daneben. Damit springt beim Wechsel vom
   Skelett zur Liste auch nichts mehr. */
.up-bar-sk-row { display: flex; align-items: center; padding: 3px 0; }
/* Radius 6 wie die echte Spur (.up-bar-track), nicht Geists 5: das Skelett haelt die Form dessen,
   was gleich dort steht. */
.up-bar-sk-track { display: block; height: 20px; border-radius: var(--up-r-m); background: var(--vc-sk) var(--up-sk-verlauf); position: relative; overflow: hidden; animation: var(--up-sk-bewegung); }

/* ==========================================================================================
   TABLE PRIMITIVES — shared cell furniture
   ==========================================================================================
   Each of these existed 3-4 times under different prefixes (.uut-/.udt-/.tct-/.vt-) with
   byte-identical rules. One copy now; components keep only their own column widths and the
   cells that are genuinely theirs. */

/* delta chip: arrow + value, green when the change is good, red when it isn't */
.up-trend { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 600; flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.up-trend svg { width: 16px; height: 16px; display: block; }
.up-trend.pos { color: var(--vt-up); }
.up-trend.neg { color: var(--vt-down); }

/* favicon holder with a letter fallback — a broken favicon used to leave an empty square, which
   reads as a loading glitch; the initial keeps every row visually complete */
/* ---------- Zaehl-Badge am Icon-Button ----------
   Der kleine Kreis oben rechts an einem Trigger ("N Filter aktiv"). Lag zweimal in
   Komponenten-CSS (brands-overview, citations-combo-chart) und ist beim dritten Verbraucher
   prompt zu gross und zu schwer geraten, weil ich ihn frei nachgebaut habe statt ihn zu nehmen.
   Die beiden alten Klassennamen haengen als Aliase mit dran -- ihr Markup steht in vom Nutzer
   eingefuegten Bubble-Seiten, ein Rename wuerde die zerlegen. */
/* --vc-text-invert statt zweier Literale: der Grund traegt schon var(--vc-text), die Schrift
   darauf war von Hand gebaut -- #e0e0e0 hell und eine eigene Dunkel-Regel mit #1f1f1b. Genau die
   zwei Werte fuehrt --vc-text-invert. Die Dunkel-Regel faellt damit weg. (22.09.) */
.up-badge, .ubo-filter-badge, .ubo-cols-badge, .udb-cols-badge, .combo-filter-badge {
  position: absolute; top: -3px; right: -3px; z-index: 2;
  min-width: 16px; height: 16px; padding: 0 4px; border-radius: var(--up-r-voll);
  background: var(--vc-text); color: var(--vc-text-invert);
  font-family: inherit; font-size: 10px; font-weight: 600; line-height: 1;
  display: none; align-items: center; justify-content: center;
  font-variant-numeric: tabular-nums; pointer-events: none;
}
.up-badge.is-visible, .ubo-filter-badge.is-visible,
.ubo-cols-badge.is-visible, .udb-cols-badge.is-visible, .combo-filter-badge.is-visible { display: inline-flex; }
/* Reine Markierung ohne Zahl. Fuer Trigger, bei denen eine Zahl nichts beantwortet -- der
   Radar-Picker hat zwei Achsen, "13" waere dort die Summe aus Aepfeln und Birnen.

   Masse und Position sind NICHT neu gewaehlt, sondern die des Grouping-Buttons der Prompts-
   Tabelle (.upt-group-dot): 6px, top/right 5px, --vc-text, per opacity eingeblendet. Ich hatte
   hier 8px auf 0/0 frei nachgebaut, obwohl die Vorgabe im Repo lag -- .upt-group-dot haengt
   deshalb jetzt als Alias mit dran, damit es EINE Sache ist und nicht wieder auseinanderlaeuft.
   Sichtbar wird der Punkt ueber .is-visible (Badge) bzw. .is-on am Wrapper (Grouping-Button). */
.up-badge.is-dot, .upt-group-dot {
  min-width: 6px; width: 6px; height: 6px; padding: 0; top: 5px; right: 5px;
  border-radius: 50%; background: var(--vc-text);
  opacity: 0; transition: opacity 120ms ease;
}
.up-badge.is-dot.is-visible { display: block; opacity: 1; }

.up-logo-box { width: 24px; height: 24px; border-radius: var(--up-r-m); background: var(--vt-logo-bg); padding: 1px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center; position: relative; }
.up-logo-box img { width: 22px; height: 22px; border-radius: calc(var(--up-r-m) - 1px); object-fit: cover; display: block; }
.up-logo-ltr { font-size: 11px; font-weight: 600; color: var(--vt-logo-ink, var(--vc-muted)); line-height: 1; text-transform: uppercase; }
.up-logo-box.has-img .up-logo-ltr { display: none; }
/* Ein Modell- oder Plattform-Logo in der Kachel (.is-modell, 23.10.: Page Audit, MCP Server): im Dunkeln
   ohne die helle Platte, wie der Modell-Chip (.up-model-chip .up-ment-logo) -- die Logos sind dort selbst
   hell. Durchsichtig statt einer Flaeche: die Kachel steht in Tabellenzeilen, und deren Hover faerbt den
   Grund. Der Buchstabe steht dann auf der Zeile, also in ihrer Schrift statt in der Tinte der Platte.
   (Die Erklaerkarte am <body> braucht keine eigene Zeile: sie ist keine .up-root, --vt-logo-bg ist dort
   gar nicht gesetzt, die Kachel also schon durchsichtig -- gemessen 23.10.) */
.up-root[data-theme="dark"] .up-logo-box.is-modell { background: transparent; }
.up-root[data-theme="dark"] .up-logo-box.is-modell .up-logo-ltr { color: var(--vc-muted); }
/* EIN ZEICHEN STATT DES ANFANGSBUCHSTABENS (06.10., Ads: "Werbetreibende ohne Logo zeigen das
   Werbetreibende-Zeichen"). Dieselbe Stelle und Tinte wie der Buchstabe, in beiden Kacheln: der
   Logo-Kachel der Tabellen (24px, Zeichen 14) und dem 18px-Logo der Chips (Zeichen 11). Kommt
   das Bild doch, verschwindet es wie der Buchstabe. */
.up-logo-zeichen { display: inline-flex; color: var(--vt-logo-ink, var(--vc-muted)); }
.up-logo-zeichen svg { width: 14px; height: 14px; display: block; }
.up-ment-logo .up-logo-zeichen svg { width: 11px; height: 11px; }
.up-logo-box.has-img .up-logo-zeichen, .up-ment-logo.has-img .up-logo-zeichen { display: none; }

/* ---------- DER LOGO-FAECHER (UC.makeFaecher, 29.09.) ----------
   Fuenf Karten auf einem Platz, gedreht um EINEN Punkt weit unter ihnen: transform-origin 50%
   440% liegt 141px unter der Karte, so stehen sie von selbst auf einem Bogen, die aeusseren etwas
   tiefer. Die Mitte liegt vorne (z-index aus dem JS), jede Lage faehrt 40ms nach der inneren auf.
   DIE KARTE IST EINE LOGO-PLATTE: --vt-logo-bg wie .up-logo-box; Buchstabe und Zeichen darauf in
   --vt-logo-ink. Im Dunkeln seit dem 26.10. nicht mehr die ganze Karte hell, sondern wie der
   Logo-Stapel nur das Bild (Regel unten). Der Ring in der Farbe der Flaeche dahinter trennt die
   ueberlappenden Karten -- ein Einbau auf anderem Grund setzt --up-fan-ring (Quick Actions).
   Das Logo steht mit 20px mittig und wird NICHT beschnitten (contain): cover schnitt breite Logos
   ab, gemessen an der Wolke von Salesforce.
   Der Bogen (aus Mira uebernommen): bei 12 Grad liegt die Mitte der Nachbarkarte 26px daneben,
   bei 24 Grad 51 -- die Karten ueberlappen um gut 6px, und jedes Logo bleibt ganz sichtbar (bei
   350% waren es 15px Deckung). Die aeusseren sitzen 3 und 11px tiefer; 160 x 60 umschliesst den
   ganzen Bogen. */
.up-root.up-fan-block { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.up-fan { position: relative; width: 160px; height: 60px; pointer-events: none; }
.up-fan-c {
  position: absolute; left: calc(50% - 16px); top: 4px; width: 32px; height: 32px; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  border-radius: var(--up-r-m); background: var(--vt-logo-bg); color: var(--vt-logo-ink, var(--vc-muted));
  /* Der Rand der Laderkachel, aus der die Karte kommt (.am-tload-logo: 1px --am-border, und das
     ist --vc-border). Beim Umzug nach core fehlte er -- 29.09. spaet: "waren nicht vorher Borders
     um die Cards? bitte wieder hin". box-sizing border-box: die Karte bleibt 32. */
  border: 1px solid var(--vc-border);
  font-size: var(--up-fs-l); font-weight: var(--up-fw-h); line-height: 1;
  box-shadow: 0 0 0 2px var(--up-fan-ring, var(--up-menu-bg)), var(--up-e-chip);
  transform-origin: 50% 440%; transform: rotate(0deg);
  transition: transform var(--up-t-3) var(--up-ease-auf);
  transition-delay: calc(var(--n, 0) * 40ms);
}
.up-fan.is-auf .up-fan-c { transform: rotate(var(--w, 0deg)); }
/* Das Zusammenlegen vor dem Auffaechern laeuft ohne Uebergang -- Begruendung an makeFaecher. */
.up-fan.is-still .up-fan-c { transition: none; }
.up-fan-c img { width: 20px; height: 20px; object-fit: contain; border-radius: var(--up-r-xs); display: block; }
.up-fan-c.is-zeichen svg { width: 16px; height: 16px; display: block; }
/* IM DUNKELN WIE DIE UEBRIGEN LOGO-CHIPS (26.10., Lukas: "im darkmode sind da brutal breite weisse
   background ... dass es so wirkt wie alle anderen chips im darkmode"). Die 32er-Karte war ganz
   --vt-logo-bg (#e0e0e0), das 20er Logo stand also in 5px heller Flaeche auf #0f0f0f -- im Hellen
   faellt dieselbe Flaeche nicht auf, weil Weiss auf Weiss liegt. Jetzt wie .up-stack im Dunkeln:
   die helle Platte nur unter dem Bild (dessen eigene Flaeche, contain laesst bei breiten Logos
   sonst den Grund durch) und einen Pixel darum (Schatten des Bildes, konzentrisch). Die Karte
   selbst traegt die Farbe ihres Rings, ist also deckend -- sie liegt 6px ueber der Nachbarin, und
   eine durchsichtige Karte liesse deren Rand durchscheinen (dieselbe Falle wie der Folgechip im
   Stapel). Der Rand bleibt: er ist hier die Kante der Karte, wie im Hellen, und ohne ihn standen
   die Zeichen-Karten (kein Logo) als lose Zeichen auf dem Grund. Buchstabe und Zeichen stehen
   dann auf dem Grund, also in dessen Schrift statt in der Tinte der Platte (wie .is-modell).
   Masse unberuehrt: Karte 32, Bild 20, Radius 6 und 4, Ring 2, Drehpunkt. */
.up-root[data-theme="dark"] .up-fan-c { background: var(--up-fan-ring, var(--up-menu-bg)); color: var(--vc-muted); }
.up-root[data-theme="dark"] .up-fan-c img { background: var(--vt-logo-bg); box-shadow: 0 0 0 1px var(--vt-logo-bg); }
/* "In der Drittfarbe und recht duenn" (29.09. spaet) -- 400 ist der duennste Schnitt der Achse. */
.up-fan-cap { font-size: var(--up-fs-m); font-weight: var(--up-fw-n); line-height: 1.4; color: var(--vc-third); white-space: nowrap; }
@media (prefers-reduced-motion: reduce){ .up-fan-c { transition: none; } }

/* ---------- Das Ladebild fuer lange Laeufe (UC.makeLadebild, dritte Fassung 16.10.) ----------
   VARIANTE A, ENTSCHLACKT (16.10., Nutzer: "Loader: bitte Variante A, entschlackt"). Die Form vom
   01.10. bleibt -- Zeichen der Seite in einem Kreis, auf dem ein Bogen umlaeuft, darunter Titel und
   Statuszeile --, nur ohne das, was sie schwer machte:
     - KEINE KACHEL MEHR um das Zeichen (vorher 44 mit Verlauf, Rand und Schatten e-1). Das Zeichen
       steht frei wie auf den ganzseitigen Flaechen, in --vc-muted. .up-lb-tile bleibt als Huelle
       des Zeichens stehen (Vertrag), traegt aber keine Flaeche mehr.
     - KREIS 48 statt 72, im Dialog 40 (cfg.kompakt). Die Groessen laufen ueber drei Variablen an
       .up-lb, damit die kompakte Form nur sie umstellt und nicht jede Regel zweimal steht.
     - Bogen in der TEXTFARBE (--vc-text), Spur in der LINIENFARBE (--vc-border). Vorher trug der
       Bogen --up-accent, das ist eine Rolle (Knopf, Auswahl) und keine Schrift.
     - Die Statuszeile BLENDET nur noch ueber (200 aus, 200 ein) statt nach oben zu schieben: eine
       Bewegung neben dem Spinner statt zwei.
   Der Bogen ist ein Fuenftel des Umfangs und laeuft in einer Sekunde einmal um -- die uebliche
   Drehzahl eines Spinners: langsamer liest sich als Haenger, schneller als Hektik.
   Strich: 1.5px in beiden Groessen. Das SVG rechnet in 48er Einheiten, im 40er Kreis sind 1.8
   Einheiten also wieder 1.5px. Radius 23.25 = 24 minus halber Strich, der Bogen beruehrt den Rand.
   Titel 14/500 auf 20, Statuszeile 13 auf 19 in --vc-third. Die Hoehe der Statuszeile ist fest:
   der Text wechselt, und ohne feste Hoehe huepfte alles darunter bei jedem Wechsel. */
.up-lb { --up-lb-ring: 48px; --up-lb-zeichen: 20px; --up-lb-strich: 1.5;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  width: min(100%, 420px); margin: 0 auto; animation: upLbAuf var(--up-t-3) var(--up-ease-expo) both; }
/* Im Dialog (Sitemap im Anlegen-Dialog der Events): ein Teilschritt, kein ganzer Bildschirm. */
.up-lb.is-kompakt { --up-lb-ring: 40px; --up-lb-zeichen: 16px; --up-lb-strich: 1.8; }
@keyframes upLbAuf { from { opacity: 0; } to { opacity: 1; } }
.up-lb-mark { position: relative; width: var(--up-lb-ring); height: var(--up-lb-ring); display: grid; place-items: center;
  flex: 0 0 auto; color: var(--vc-muted); }
.up-lb-spin { position: absolute; inset: 0; width: 100%; height: 100%; display: block; overflow: visible;
  animation: upLbDreh 1s linear infinite; }
@keyframes upLbDreh { to { transform: rotate(360deg); } }
.up-lb-spur, .up-lb-bogen { fill: none; stroke-width: var(--up-lb-strich); }
.up-lb-spur { stroke: var(--vc-border); }
/* Das Schliessen zum vollen Kreis bei stop() laeuft ueber dieselbe Angabe, darum die transition. */
.up-lb-bogen { stroke: var(--vc-text); stroke-linecap: round; stroke-dasharray: 20 100;
  transition: stroke-dasharray var(--up-t-3) var(--up-ease); }
.up-lb.is-fertig .up-lb-spin { animation-play-state: paused; }
.up-lb.is-fertig .up-lb-bogen { stroke-dasharray: 100 0; }
.up-lb-tile { display: grid; place-items: center; }
/* Der Ring als kleines Zeichen (UC.ringHtml, 22.10.): Spur, Bogen und Drehung wie .up-lb-spin oben, Strich 1.5px in
   jeder Groesse (die viewBox rechnet in Pixeln). Reduziert langsamer, nicht still -- wie der grosse Ring. */
.up-ring { display: block; flex: 0 0 auto; overflow: visible; animation: upLbDreh 1s linear infinite; }
.up-ring-spur, .up-ring-bogen { fill: none; stroke-width: 1.5; }
.up-ring-spur { stroke: var(--vc-border); }
.up-ring-bogen { stroke: var(--vc-text); stroke-linecap: round; stroke-dasharray: 20 100; }
@media (prefers-reduced-motion: reduce){ .up-ring { animation-duration: 2.4s; } }

/* ---------- Kopierkasten (UC.kopierBoxHtml, 22.10., Geist Snippet) ----------
   Rand und Flaeche eines Feldes (--up-feld-bg, Rand --vc-border), 32 hoch wie ein Knopf, darum Radius 6. Die Zeile in
   Mono 13, das Zeichen 16 rechts in der dritten Textfarbe, beim Ueberfahren in der Textfarbe; nur die Flaeche
   aendert sich beim Ueberfahren (Knopf-Hover der App). Kopie und Haken liegen uebereinander und tauschen in 120 ms
   (Deckkraft und Skalierung), der Haken in der Farbe "gut". Die Live-Region ist unsichtbar, aber vorgelesen. */
.up-kopierbox { display: inline-flex; align-items: center; gap: 12px; height: var(--up-h-btn); max-width: 100%; padding: 0 8px 0 12px;
  border: 1px solid var(--vc-border); border-radius: var(--up-r-m); background: var(--up-feld-bg); color: var(--vc-text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; font-size: var(--up-fs-m); line-height: 20px;
  position: relative; cursor: pointer; flex: 0 0 auto; transition: background-color var(--up-t-1) var(--up-ease); }
.up-kopierbox:hover { background: var(--up-knopf-hover); }
.up-kopierbox:focus-visible { outline: var(--up-focus-w) solid var(--up-focus); outline-offset: var(--up-knopf-fokus-abstand); }
.up-kopierbox-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-kopierbox-ic { position: relative; display: grid; width: 16px; height: 16px; flex: 0 0 auto; color: var(--vc-third); }
.up-kopierbox:hover .up-kopierbox-ic { color: var(--vc-text); }
.up-kopierbox-kopie, .up-kopierbox-haken { grid-area: 1 / 1; display: grid; place-items: center;
  transition: opacity var(--up-t-1) var(--up-ease), transform var(--up-t-1) var(--up-ease); }
.up-kopierbox-ic svg { width: 16px; height: 16px; display: block; }
.up-kopierbox-haken { opacity: 0; transform: scale(.6); color: var(--vt-up); }
.up-kopierbox.is-kopiert .up-kopierbox-kopie { opacity: 0; transform: scale(.6); }
.up-kopierbox.is-kopiert .up-kopierbox-haken { opacity: 1; transform: none; }
/* .up-ansage (28.10.): die Live-Region, die einen Seitenwechsel ansagt (fireViewChange), unsichtbar wie diese. */
.up-kopierbox-live, .up-ansage { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* Eine Ansicht, die den Fokus bekommt, weil er in der alten lag (fireViewChange): kein Ring um die ganze Seite. */
[id^="view-"][tabindex="-1"]:focus { outline: none; }
@media (prefers-reduced-motion: reduce){ .up-kopierbox-kopie, .up-kopierbox-haken { transition: none; } }
.up-lb-tile svg { width: var(--up-lb-zeichen); height: var(--up-lb-zeichen); display: block; }
.up-lb-name { margin-top: 16px; max-width: 100%; font-size: var(--up-fs-l); font-weight: var(--up-fw-m); line-height: 20px;
  color: var(--vc-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-lb.is-kompakt .up-lb-name { margin-top: 12px; }
.up-lb-sub { margin-top: 2px; max-width: 100%; font-size: var(--up-fs-m); color: var(--vc-third);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* TITEL UND UNTERZEILE MACHEN DEN TRAEGER NICHT BREITER (16.10. bei 375 gemessen). Ohne Umbruch ist
   ihre Mindestbreite die ganze Zeile, und ein Traeger, der sich nach seinem Inhalt richtet (Prompt
   Research: ein Grid um .upr-loading-inner), wuchs mit: drei lange Schlagworte schoben #main um
   138px ueber den Rand, statt mit "..." zu enden. Breite 0 mit Mindestbreite 100%: zur Mindestbreite
   tragen sie nichts bei, im Bild fuellen sie das Ladebild und kuerzen dort. */
.up-lb-name, .up-lb-sub { width: 0; min-width: 100%; }
.up-lb-loop { position: relative; height: 19px; overflow: hidden; width: 100%; margin-top: 4px; }
/* Nur die Deckkraft wechselt. Der Tausch des Texts wartet in core.js 240ms -- etwas laenger als das
   Ausblenden, weil Uhr und Uebergang nicht an derselben Zeit haengen; ein Tausch vor dem Ende liesse
   den alten Satz das letzte Stueck sichtbar wegspringen. */
.up-lb-text {
  position: absolute; left: 0; right: 0; top: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: var(--up-fs-m); line-height: 19px; color: var(--vc-third); opacity: 1;
  transition: opacity var(--up-t-2) var(--up-ease-auf);
}
.up-lb-text.is-out, .up-lb-text.is-in { opacity: 0; }
/* Reduziert heisst langsamer, nicht still: ein Spinner, der steht, sagt "haengt". Die Statuszeile
   wechselt dann hart. */
@media (prefers-reduced-motion: reduce){
  .up-lb { animation: none; }
  .up-lb-spin { animation-duration: 2.4s; }
  .up-lb-bogen, .up-lb-text { transition: none; }
}

    /* ---------- Globus statt Buchstabe, wo das Bild ein FAVICON war ----------
       Ein Favicon, das nicht laedt, hinterliess den Anfangsbuchstaben der Domain -- ein "R" fuer
       reddit.com sagt nichts, und bei einer Subdomain ist es schlicht falsch. Fuer Seiten steht
       deshalb ein Globus da, genau wie im Onboarding.
       Bei MARKEN bleibt der Buchstabe: dort ist er die Marke, und "K" fuer Kestrel ist eine
       Aussage. Deshalb ein eigener Aufhaenger (.up-fav) an den Kaesten, die eine Seite zeigen,
       und keine Aenderung an .up-logo-box selbst.
       Maske und keine Bilddatei: so traegt der Globus die Textfarbe seiner Umgebung und stimmt in
       beiden Themen. */
    .up-fav:not(.has-img) .up-logo-ltr,
    .up-fav:not(.has-img) .up-stack-ltr,
    .up-fav:not(.has-img) .udt-logo-ltr,
    .up-fav:not(.has-img) .udt-sub-ltr,
    .up-fav:not(.has-img) .uut-logo-ltr { display: none; }
    /* Der Globus haengt normalerweise am KASTEN. Im Stapel-Chip nicht: dort ist der Kasten
       (.up-stack-item) mit Absicht unbeweglich -- er traegt die Trefferflaeche, und nur die
       Sichtebene .up-stack-vis darin hebt und schiebt sich beim Hover. Ein Globus am Kasten blieb
       damit stehen, waehrend Platte, Rahmen und Ring um ihn herum wegfuhren: gemeldet als
       "das Globe-Icon verschiebt sich nicht mit". Ein Buchstabe hat das Problem nie gehabt, der
       liegt schon IN der Sichtebene.
       Also zeichnet ihn im Stapel die Sichtebene, und der Kasten laesst es. Dieselben
       Deklarationen, nur ein zweiter Selektor -- kein zweiter Globusblock, der auseinanderlaufen
       koennte. Die Zahlen (62 Prozent, mittig) beziehen sich auf die Sichtebene, und die fuellt
       den Kasten zu 100 Prozent: derselbe Kreis an derselben Stelle wie vorher. */
    .up-fav:not(.has-img)::after,
    .up-stack-item.up-fav:not(.has-img) .up-stack-vis::after {
      content: ""; position: absolute; left: 50%; top: 50%;
      width: 62%; height: 62%; transform: translate(-50%, -50%);
      background: var(--vc-third);
      -webkit-mask: var(--up-globus) center / contain no-repeat;
      mask: var(--up-globus) center / contain no-repeat;
      pointer-events: none;
    }
    .up-stack-item.up-fav:not(.has-img)::after { content: none; }

    /* ---------- Globus, wo ein Favicon gescheitert ist ----------
       Zwei Faelle, beide bedient vom Zuhoerer in core.js (faviconRueckfall):
       .up-globus-an   an einem Kasten, der das Bild getragen hat und sonst nichts anzeigen wuerde
                       -- der Kasten behaelt seine Groesse, der Globus sitzt darin.
       .up-globus-frei anstelle eines Bildes, das ohne Kasten in einer Zeile stand (Tooltip,
                       Legende, Filterzeile). Die Groesse setzt der Zuhoerer aus dem Bild, das
                       dort stand -- sonst rutscht die Zeile beim Fehlschlag.
       Farbe aus --vc-third mit festem Rueckfall: diese Marken koennen ausserhalb von .up-root
       stehen. */
    .up-globus-an { position: relative; }
    .up-globus-an::after {
      content: ""; position: absolute; left: 50%; top: 50%;
      width: 62%; height: 62%; transform: translate(-50%, -50%);
      background: var(--vc-third, #9e9e9e);
      -webkit-mask: var(--up-globus) center / contain no-repeat;
      mask: var(--up-globus) center / contain no-repeat;
      pointer-events: none;
    }
    .up-globus-frei {
      display: inline-block; flex: 0 0 auto; vertical-align: middle;
      background: var(--vc-third, #9e9e9e);
      -webkit-mask: var(--up-globus) center / contain no-repeat;
      mask: var(--up-globus) center / contain no-repeat;
      pointer-events: none;
    }

/* ---------- Zeilen-Affordanz: Pfeil, der nach einer Verweildauer zur Aktion wird ----------
   STILLGELEGT. Der Block hat keinen Verbraucher mehr: "Edit" in brands-overview und "Show Pages"
   in domains-table sind inzwischen .up-rowbtn (weiter oben) -- der Knopf kommt beim Hover der
   Zeile sofort, in der Zelle, ohne Pfeil davor und ohne Wartezeit. Seit dem 16.10. ist auch der
   Pfeil hier aus (kein hereingleitendes Zeichen in Tabellen, ausdruecklicher Wunsch). Die Regeln
   bleiben, weil der Vertrag die Klassen fuehrt. Wer eine Zeilenaktion braucht, nimmt .up-rowbtn:
   ohne den Pfeil waere hier eine Sekunde Leere vor der Aktion, und die Aktion laege ueber dem
   Zellinhalt statt an seiner Stelle.

   Was der Block war:
   Die Bauart kommt aus domains-table ("Show Pages") und steht jetzt hier, weil brands-overview
   mit seinem Edit-Knopf genau dasselbe braucht -- Buchstabe fuer Buchstabe dieselben Zeiten,
   Groessen und Positionen. Die .udt-*-Klassen stehen in jeder Regel mit, damit das Markup der
   Domains-Tabelle unangetastet bleibt (derselbe Kniff wie bei .up-seg/.cc-seg).

   Der Anker ist EIN nullbreites Flex-Kind mit margin-left:auto, gegen das beide Kinder absolut
   liegen. Vorher waren Pfeil und Aktion zwei Geschwister mit je eigenem auto-Rand: Flexbox teilt
   den freien Platz gleichmaessig auf ALLE auto-Raender einer Zeile auf, der Pfeil landete also
   irgendwo in der Mitte statt an der Kante.

   Die Uebergabe ist gestaffelt: beim Hover kommt der Pfeil nach 220ms; nach 1s Verweilen (die
   Klasse setzt UC.rowDwell) geht der Pfeil OHNE Verzoegerung in 120ms weg und die Aktion kommt
   140ms spaeter -- eins geht, dann kommt das naechste, statt dass sich beide um denselben Platz
   streiten. */
.up-rowswap, .udt-row-affordance {
  display: inline-block; margin-left: auto; position: relative; flex: 0 0 auto; width: 0; height: 32px;
}
/* Der Pfeil: seit 16.10. aus (siehe Kopf des Blocks und .up-row-goto darunter). */
.up-rowswap-goto, .udt-row-goto { display: none; }
/* Die Aktion sitzt in demselben Platz, den der Pfeil hatte: kein Rahmen, keine Pille -- eine
   Beschriftung mit Zeichen, nicht ein Knopf, der aus dem Nichts auftaucht. */
.up-rowswap-act, .udt-row-showpages {
  position: absolute; top: 50%; right: 0;
  display: inline-flex; align-items: center; gap: 6px; transform: translateY(-50%) translateX(6px);
  height: 32px; padding: 0; border: 0; background: transparent; cursor: pointer; font: inherit;
  font-size: 12px; font-weight: 500; color: var(--vc-third); white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity 160ms ease, transform 160ms ease, color 120ms ease; transition-delay: 0ms;
}
.up-rowswap-act svg, .udt-row-showpages svg { width: 14px; height: 14px; flex: 0 0 14px; display: block; }
.up-rowswap-act:hover, .up-rowswap-act:focus-visible,
.udt-row-showpages:hover, .udt-row-showpages:focus-visible { color: var(--vc-text); }
.up-row.is-rowswap .up-rowswap-act, .up-row.is-showpages-hover .udt-row-showpages {
  opacity: 1; transform: translateY(-50%) translateX(0); pointer-events: auto; transition-delay: 140ms;
}
.up-root.is-vnarrow .up-rowswap, .up-root.is-vnarrow .udt-row-affordance { display: none; }

/* Zeilenpfeil (arrowUpRight am Ende der ersten Zelle): STILLGELEGT seit 16.10., ausdruecklicher
   Wunsch -- in keiner Tabelle mehr ein Zeichen, das beim Hover hereingleitet. Dass die Zeile
   klickbar ist, sagen ihre Hover-Flaeche und der Zeiger schon. Der Pfeil kostete dafuer Platz:
   als Flex-Kind mit margin-left:auto belegte er 16px plus den Abstand der Zelle IMMER, auch
   unsichtbar. In Top Brands und Top Domains kam er nie (dort ist die Zeile keine .up-row), kuerzte
   aber die Namen (gemessen 1440 breit: "Mercedes Benz" 92.9 statt 101.3px).
   Kein Markup erzeugt ihn mehr (prompts-table, visibility-chart, topcitations-dashboard). Die
   Regel bleibt, weil der Vertrag die Klasse fuehrt und Markup ausserhalb dieser Dateien (die
   Landing-Vorschau, alte Pruefstaende) sonst einen festen Pfeil zeigte; display:none nimmt ihm
   auch den Platz. */
.up-row-goto { display: none; }

/* type/citation chip. Rand 1px (Farbe inline aus UC.typeStyle) innerhalb von border-box, damit Hoehe und Polster bleiben; Schnitt 500 statt 600, seit Variante D (13.10.) traegt die Farbe den Chip, nicht die Staerke.
   Form, Groesse, Schrift und Polster seit 15.10. aus dem Geist-Badge direkt darunter. */
.up-tag { display: inline-flex; align-items: center;
  border: 1px solid transparent; box-sizing: border-box;
  white-space: nowrap; max-width: 100%; overflow: hidden; }
.up-tag-dot { width: 6px; height: 6px; border-radius: var(--up-r-voll); flex: 0 0 auto; }
.up-tag-lbl { overflow: hidden; text-overflow: ellipsis; }

/* ---------- DER TYP-CHIP IST EIN GEIST-BADGE (15.10.) ----------
   Gewuenscht: "die Badges nutzen fuer Citation-Type-Chips und URL-Type-Chips, aber unsere Farben
   und Opacitys beibehalten". vercel.com/geist/badge, am 15.10. ausgelesen: voll gerundet, Schnitt
   500, Tabellenziffern, keine eigene Kante. Drei Groessen:
       sm  20 hoch, Polster 8, Schrift 11, Sperrung 0.2px, Abstand 4
       md  24 hoch, Polster 12, Schrift 12, Abstand 5     <- die Zeile einer Tabelle (wie bisher 24)
       lg  32 hoch, Polster 12, Schrift 14, Abstand 6     (in der App nicht gebraucht)
   EINE Regel fuer jeden Citation- und URL-Type-Chip, statt sieben gleichlautender Kopien in fuenf
   Dateien (Tabellen, Drilldown, Erklaerkarten, Filtermenue, Drawer, Dashboard). Die Komponenten
   behalten nur, was nicht Form ist (Lage, Kuerzung). FARBEN BLEIBEN WIE SIE SIND: Schrift, Grund,
   Rand und Punkt kommen weiter inline aus UC.typeStyle -- der Rand in der Grundfarbe ist
   unsichtbar und haelt die Groesse, genau wie bisher.
   Der Punkt des URL-Typs steht an der Stelle von Geists Zeichen (das dort 14px misst und 4 nach
   links ins Polster rueckt) -- ein 6px-Punkt braucht das nicht, er sitzt einfach im Polster. */
:root {
  --up-badge-h: var(--up-h-chip); --up-badge-px: 12px; --up-badge-gap: 5px; --up-badge-fs: var(--up-fs-s);
  --up-badge-h-s: 20px; --up-badge-px-s: 8px; --up-badge-gap-s: 4px; --up-badge-fs-s: var(--up-fs-xs); --up-badge-ls-s: 0.2px;
}
:is(.up-tag, .up-filter-tag, .udt-tag, .uut-tag, .tct-tag) {
  height: var(--up-badge-h); padding: 0 var(--up-badge-px); gap: var(--up-badge-gap);
  border-radius: var(--up-r-voll); font-size: var(--up-badge-fs); font-weight: var(--up-fw-m);
  line-height: 1; font-variant-numeric: tabular-nums;
}
:is(.udt-sub-tag, .udt-explain-chip, .uut-explain-chip) {
  height: var(--up-badge-h-s); padding: 0 var(--up-badge-px-s); gap: var(--up-badge-gap-s);
  border-radius: var(--up-r-voll); font-size: var(--up-badge-fs-s); font-weight: var(--up-fw-m);
  letter-spacing: var(--up-badge-ls-s); line-height: 1; font-variant-numeric: tabular-nums;
}

/* Visibility / Rank / Sentiment metric cells — shared by visibility-chart's Top Brands table
   and prompts-table's Sentiment/Rank/Visibility columns. */
/* tabular-nums: digits keep the same advance width, so a column of numbers lines up across rows
   instead of jittering. urls-table/domains-table always had this on their own .uut-num/.udt-num;
   visibility-chart's .vt-num never did, and extracting THAT one into .up-num quietly spread the
   omission to prompts-table. Fixing it here fixes both at once. */
.up-zeilenname { font-size: var(--up-fs-l); font-weight: var(--up-fw-m); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* DER NAME IN DER ERSTEN ZELLE EINER TABELLE (20.10.): ein Kern-Stil fuer Marke, Haendler, Werbetreibender -- 14/500 wie .up-num daneben, vorher erbte jede Tabelle ihre eigene
   Groesse (Shopping und Ads: .up-varname ohne Schrift, also 16/400; Brands Overview: 13.5/500). Das Logo davor ist .up-logo-box (24, Polster 1). */
.up-num { font-size: 14px; font-weight: 500; color: var(--vc-text); white-space: nowrap; flex: 0 0 auto; font-variant-numeric: tabular-nums; }
/* In einer Tabellenzelle darf die Zahl schrumpfen und kuerzt dann mit Auslassungspunkten (06.10.).
   Die Zelle schneidet erst an ihrem POLSTERRAND ab (overflow hidden), nicht am Inhalt -- ein
   langer Wert ("€19.62–€21.62" in 100px) lief deshalb bis zu 4,5px in die 16px rechts hinein, und
   das Polster ist Pflicht (gemeldet an der Preisspalte der Shopping-Produkte). */
.up-td > .up-num { flex-shrink: 1; min-width: var(--up-spur, 0); text-align: right; overflow: hidden; text-overflow: ellipsis; }
/* DIE FESTE SPUR (15.10., Token --up-spur-* an .up-root): setzt eine Zelle --up-spur (meist ueber
   ihre Spaltenklasse in der Komponente, z. B. .upt-td-visibility { --up-spur: var(--up-spur-pct0) }),
   steht die Zahl darin rechts. Ohne --up-spur bleibt die Mindestbreite 0 und rechts wirkungslos --
   jede Zelle ohne Zuordnung sieht aus wie bisher. Gilt mit und ohne Trend dahinter ("egal ob mit
   Trend oder ohne"). min-width und nicht width: ein Ausreisser jenseits der Skala sprengt die
   Spur seiner Zeile, statt abgeschnitten zu werden. Die Zahl direkt in der Zelle bekommt die Spur
   in der Regel darueber (dort stand min-width 0; eine zweite Regel gleicher Spezifitaet haette
   ueber die Reihenfolge entschieden).
   NUR DIE DIREKTE ZAHL (15.10. spaet, vorher auch jede verschachtelte): in einer Huelle -- Rang
   hinter dem #-Zeichen, Sentiment hinter seinem Balken -- schob die Spur die Zahl von ihrem Zeichen
   weg (gemessen "# 1": 24 statt 4, Balken bis "83": 15 statt 6; "das bitte wieder so wie vorher").
   Seit die Zahl an der rechten Zellkante steht, braucht es dort keine Spur mehr: die Kante ist fuer
   alle Zeilen dieselbe, und Zeichen und Zahl bleiben ein Stueck. */
/* RECHTSBUENDIG, DER TREND DIREKT VOR DEM WERT (17.10.; davor 15.10. spaet "Trend links an der
   Zellkante", jetzt ausdruecklich geaendert: "Der Trend steht nicht mehr ganz links in der Zelle,
   sondern direkt vor dem Wert. Trend und Wert sitzen zusammen rechtsbuendig, der Wert ganz rechts").
   Eine Zahlzelle -- direktes Kind ist eine Zahl (.up-num), ein Rang (.up-rank-group) oder
   Sentiment (.up-sent) -- stellt ihren Wert an die rechte Kante, der Trend steht per order DAVOR,
   durch --up-spur-abstand (12) vom Wert getrennt. Die rechte Kante ist dieselbe wie die der
   Gesamtzeile darunter (.up-sumzelle, rechtsbuendig, Polster 16) und die des Kopftextes darueber
   (.up-th-zahl, siehe unten).
   DER FESTE PLATZ DES TRENDS (--up-spur-trend, gemessen am breitesten Chip "100.0%"): damit die
   Trends in jeder Zeile gleich weit vom Wert stehen und rechts untereinander enden, sitzt der
   Chip rechtsbuendig in einem Platz fester Breite. Eine Zeile OHNE Trend hat diesen Platz einfach
   leer: der Wert steht am rechten Rand und damit auf demselben Pixel, ob der Platz gefuellt ist
   oder nicht -- ein unsichtbarer Platzhalter wuerde daran nichts aendern und nur das Kuerzen
   ungleich machen (eine Zeile ohne Trend liesse ihrer Zahl dann mehr Raum als die Nachbarin).
   Der Platz ist ein Basiswert (flex: 0 1), kein Zwang: wird die Spalte enger als Platz + Abstand
   + Wert, gibt er bis auf die Breite des Chips nach, der Chip selbst wird nie gekuerzt.
   DIE SPUR DES WERTES haelt die linke Kante des Platzes fest: eine Zahl direkt in der Zelle hat
   ihre Spur (--up-spur, oben), Rang und Sentiment sind Huellen und bekommen sie als Breite der
   ganzen Huelle (Zeichen 12 + 4 bzw. Balken 3 + 6 plus die Spur der Zahl), ihr Inhalt steht
   darin rechts. Ohne --up-spur gilt das Format der Huelle (Rang "10.0", Sentiment "100"): eine
   Rang- oder Sentiment-Spalte steht damit auch dann in einer Flucht, wenn die Komponente ihr
   keine Spur zugewiesen hat. Zahl in einer Huelle ("102 / 150") rueckt die Komponente selbst nach rechts.
   Am Inhalt erkannt (:has) und nicht an der Spaltenklasse: die Spur ordnet jede Komponente selbst
   zu, die Ausrichtung soll ueberall dieselbe sein. Ohne .up-pille: Plan- und Status-Pillen bauen
   auf .up-sent auf (Teams, gemessen), sind aber Text. :where haelt die Regel auf der Spezifitaet
   von .up-td: jede Ausnahme -- die zentrierte Indexspalte (.up-td-idx), eine Datumsspalte, die
   ihre Ziffern als .up-num setzt (Shopping, Ads) -- gewinnt mit einer Klasse mehr, ohne Reihenfolge.
   Dieselben Regeln gelten fuer die Zellen von Top Brands (.vt-td) und Top Citations (.tct-td) und
   die Wertspalte der Listen der Power-Uebersicht (.upw-li-val): sie sind keine .up-td, zeigen aber
   dieselben Zahlen. */
:is(.up-td, .vt-td, .tct-td):has(> :where(.up-num, .up-rank-group, .up-sent:not(.up-pille), .tct-num, .tct-used)) { justify-content: flex-end; }
/* Der Abstand 12 nur zwischen Zeiger und Zahl -- Trend oder der Ring der Variationen
   (.up-varring) --, nicht zwischen einer Zahl und ihrem Zusatz ("of 120", .up-var-cnt: 6). */
:is(.up-td, .vt-td, .tct-td):is(.up-td, .vt-td, .tct-td):has(> :where(.up-trend, .up-varring)) { column-gap: var(--up-spur-abstand); }
:is(.up-td, .vt-td, .tct-td, .upw-li-val) > :where(.up-trend, .up-varring) { order: -1; }
:is(.up-td, .vt-td, .tct-td):has(> :where(.up-num, .up-rank-group, .up-sent:not(.up-pille), .tct-num, .tct-used)) > .up-trend,
.upw-li-val > .up-trend { flex: 0 1 var(--up-spur-trend); justify-content: flex-end; }
:is(.up-td, .vt-td) > .up-rank-group { min-width: calc(var(--up-spur, var(--up-spur-rang)) + 16px); justify-content: flex-end; }
:is(.up-td, .vt-td) > .up-sent:not(.up-pille) { min-width: calc(var(--up-spur, var(--up-spur-note)) + 9px); justify-content: flex-end; }
/* DER KOPF EINER ZAHLENSPALTE (17.10.): "Bei Zahlenspalten steht auch die Ueberschrift rechtsbuendig,
   das Sortier-Icon links neben dem Text. Textspalten wie Brand bleiben links." Der Text endet
   auf demselben Pixel wie die Werte darunter (beide Polster rechts 16, siehe .up-th und .up-td).
   Das Sortierzeichen steht LINKS neben dem Text, das Info-Zeichen noch weiter links: es erscheint
   nur beim Ueberfahren (opacity, belegt seinen Platz also auch unsichtbar) und stuende es rechts
   vom Text, endete der Text nicht mehr an der Kante der Werte. Der Platz des Sortierzeichens
   ist fest (zwei Chevrons, aktiv nur eingefaerbt), die Lage des Textes springt beim Wechsel der
   Sortierung also nicht. Das Zeichen vor dem Wort (.up-th-ic) bleibt vor dem Wort.
   WER ZAHLENSPALTE IST, entscheidet core.js nach dem Inhalt (zahlSpalten): jede Spalte, in der
   eine Zeile eine Zahl, einen Rang oder Sentiment als direktes Kind der Zelle hat. Keine Liste
   von Spaltennamen je Seite. Zwei Klassen gegen .up-th (und die Kopfzellen von Top Brands und Top
   Citations): entschieden ueber die Spezifitaet, nicht die Ladereihenfolge. */
:is(.up-th, .vt-th, .tct-th).up-th-zahl { justify-content: flex-end; }
:is(.up-th, .vt-th, .tct-th).up-th-zahl > .up-th-info { order: -3; margin-left: 0; }
:is(.up-th, .vt-th, .tct-th).up-th-zahl > .up-thsort { order: -2; }
/* Ohne Sortierzeichen (Top Brands, Top Citations, nicht sortierbare Spalten) klebte das Info-Zeichen am Wort:
   .vt-th und .tct-th haben keinen Spaltenabstand, .up-th nur 6. Gemeldet 17.10. ("zu nah am Text"): hier 8,
   nur wo kein Sortierzeichen dazwischen steht (dort stimmt der Abstand schon). */
:is(.up-th, .vt-th, .tct-th).up-th-zahl:not(:has(> .up-thsort)) { column-gap: 8px; }
/* DAS SKELETT EINER ZAHLENSPALTE (17.10.): der Balken steht rechts, wo die Zahl spaeter steht -- vorher links
   unter einem linken Kopf ("alle Skeleton Loader sind noch falsch"). Gesetzt von skeletonRows (zahl: true);
   der Kopf folgt ueber zahlSpaltenKopf. */
:is(.up-td, .vt-td, .tct-td).up-tsk-zahl { justify-content: flex-end; }
.up-rank-group { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.up-hash { width: 12px; height: 12px; color: var(--vc-third); flex: 0 0 auto; display: block; }
/* ---------- SENTIMENT OHNE KASTEN (29.09. angefordert) ----------
   "Aktuell ist das ja in so Containern, das moechte ich nicht mehr. Die Zahl soll in gleicher
   Fontsize sein wie Visibility oder Rank, der Container darum -- Border und Paddings -- weg. Und
   der Shape davor bitte 4px breit und 16px hoch." Nachgeschaerft am selben Abend: 3px breit, 12px
   hoch.
   Also kein Rahmen, kein Polster, keine feste Hoehe: ein Balken 3x12 in der Farbe des Werts
   (UC.sentColor, inline), daneben die Zahl in genau der Schrift von .up-num (14/500) -- dieselbe
   wie Visibility und Rank in derselben Zeile. Rund an den Enden: bei 4px Breite ist der volle
   Radius die halbe Breite.
   Das Markup kommt NUR aus UC.sentHtml(wert). Vorher baute jede Komponente ihren eigenen String
   (neun Stellen, eine davon mit eigenem Kasten). Wo Visibility und Rank groesser stehen
   (Kennzahlkarten), zieht der Kontext NUR .up-sent-val mit; der Balken bleibt 3x12. */
.up-sent { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.up-sent-dot { width: 3px; height: 12px; border-radius: var(--up-r-voll); flex: 0 0 auto; }
.up-sent-val { font-size: var(--up-fs-l); font-weight: 500; color: var(--vc-text); white-space: nowrap;
  font-variant-numeric: tabular-nums; }
/* DIE PILLE MIT RAHMEN bleibt -- fuer das, was kein Messwert ist: den Tarif (UC.planPilleHtml) und
   den Status "Active" in Teams. Beide nutzen dasselbe Markup wie Sentiment, sind aber Plaketten.
   Hier stehen die Werte, die .up-sent bis zum 29.09. selbst trug: 24px, Polster 6, Rahmen, Radius
   6, Punkt 6x6 mit Radius 2 (ein abgerundetes Quadrat, kein Kreis), Schrift 13. */
.up-sent.up-pille { height: var(--up-h-chip); padding: 0 6px; border: 1px solid var(--vc-border); border-radius: var(--up-r-s); }
.up-pille .up-sent-dot { width: 6px; height: 6px; border-radius: 2px; }
.up-pille .up-sent-val { font-size: var(--up-fs-m); }
/* DIE MARKE HINTER EINEM NAMEN (03.10. aus team-orga.css nach core): "You" in der Mitgliederliste
   und in der Marktbewegung der Events, "upstreem Team" in der Mitgliederliste. EINE Geometrie,
   die Farbe je Marke -- vorher lebte die Form nur in team-orga, und die Events bauten sich eine
   zweite. Masse unveraendert von dort: 20 hoch, Polster 7, Radius 7, Schrift 11/600. "You" in
   der Akzent-Tinte mit 10 Prozent Flaeche; am Standard ein leises Grau mit fast schwarzer Schrift. */
.up-marke { flex: 0 0 auto; height: 20px; padding: 0 7px; border-radius: var(--up-r-s); max-width: 100%;
  font-size: var(--up-fs-xs); font-weight: var(--up-fw-h); letter-spacing: .02em; line-height: 20px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.up-you { background: color-mix(in srgb, var(--up-accent-ink) 10%, transparent); color: var(--up-accent-ink); }
/* Die leise Marke: eine Einordnung ohne Betonung -- der Typ eines Events auf seiner Karte (03.10.:
   "den Typ ohne Icon in einem kleinen Chip"). Die Flaeche ist die des Zaehler-Chips in der Karte
   der Event-Pins (--up-sel-bg, "3 Events"): dieselbe Art Angabe, derselbe Grund. --vc-heading-bg
   war zu nah am Kartenweiss (#f9f8f9 auf #fff), der Chip las sich als blosser Text. */
.up-marke.is-leise { background: var(--up-sel-bg); color: var(--vc-muted); }
/* Die leise Marke in der Akzentfarbe (05.10., "Beta"): dieselbe Staerke wie .is-leise, nur aus
   der Primaerfarbe statt aus der Schriftfarbe -- Text in der Akzent-Tinte, die Flaeche ein Hauch
   davon. Angefordert: "nicht Deckkraft o. Ae. aendern, nur das, was vorher Schriftfarbe war, in
   Primaerfarbe". Die Anteile sind so gewaehlt, dass am Standard-Akzent (= Schriftfarbe) der alte
   Grund herauskommt: 5 % auf Weiss ergibt #f4f4f4 (war --up-sel-bg #f4f2f4), 6 % im Dunkeln auf
   #1c1c1f ergibt #28282b (war #28282c). Eine volle Akzentflaeche war die erste Fassung -- zu laut. */
.up-marke.is-akzent { background: color-mix(in srgb, var(--up-accent-ink) 5%, var(--vc-bg)); color: var(--up-accent-ink); }
.up-root[data-theme="dark"] .up-marke.is-akzent { background: color-mix(in srgb, var(--up-accent-ink) 6%, var(--vc-bg)); }

/* DIE TABELLE, DIE WAAGERECHT SCROLLT (05.10. aus shopping.css nach core, seit Ads sie auch
   braucht): UC.makeColumns setzt im Scrollmodus (cfg.scrollen) --up-cols-min, die Summe der
   Mindestbreiten. Darunter scrollt der Kasten waagerecht, statt dass Spalten wegfallen ("ich will
   beides", ziehbare erste Spalte UND die ganze Tabelle). Die erste Spalte bleibt dabei stehen; der
   Trenner als Schatten, weil die Linie der naechsten Zelle mit wegscrollt, und der Grund wie die
   Zeile darunter, sonst scheint der Inhalt durch.
   Markup: .up-colscroll > .up-colscroll-innen > .up-thead + .up-tbody */
.up-colscroll { overflow-x: auto; overscroll-behavior-x: contain; }
.up-colscroll-innen { min-width: var(--up-cols-min, 0px); }
.up-colscroll .up-thead > .up-th:first-child, .up-colscroll .up-row > .up-td:first-child {
  position: sticky; left: 0; z-index: 1; box-shadow: 1px 0 0 var(--vc-border); }
.up-colscroll .up-thead > .up-th:first-child { background: var(--vt-head-bg); }
/* Flache Tabelle: ihr Kopf traegt die Seitenfarbe (data-flach oben), also auch die stehende erste Kopfzelle --
   sonst stand sie als grauer Fleck im weissen Kopf (Impact-Liste, gemeldet 23.10.: "die oberen linken
   heading zellen grau eingefaerbt"). */
.up-root.up-root[data-flach="yes"] .up-colscroll .up-thead > .up-th:first-child { background: var(--up-surface); }
.up-colscroll .up-row > .up-td:first-child { background: var(--vc-bg); }
.up-colscroll .up-row:hover > .up-td:first-child { background: var(--vt-row-hover); }
/* KOPF UND GESAMTZEILE DES SCROLLKASTENS KLEBEN AN DER SEITE (20.10., UC.colscrollTeilen): sie stehen in eigenen Haltern vor und hinter dem
   Kasten im selben .up-box, damit sie an der Seite kleben koennen (ein Scrollkasten ist in beiden Achsen ein Scroller, siehe core.js).
   overflow clip statt hidden: kein Scroller, und der Schatten der Gesamtzeile nach oben bleibt sichtbar; waagerecht schneidet clip an der
   Kante des Halters. Das Innere fahrt um --up-cs-x mit dem Kasten, die stehende erste Spalte nimmt das wieder zurueck. */
.up-colscroll.up-cs-kopf, .up-colscroll.up-cs-fuss { overflow-x: clip; overflow-y: visible; }
.up-cs-kopf > .up-colscroll-innen, .up-cs-fuss > .up-colscroll-innen { transform: translateX(calc(-1 * var(--up-cs-x, 0px))); }
.up-cs-kopf .up-thead > .up-th:first-child, .up-cs-fuss .up-sumrow > .up-sumzelle:first-child { transform: translateX(var(--up-cs-x, 0px)); }
/* Der Kopf klebt als Ganzes unter der Leiste, mit denselben Massen wie der Kopf einer Tabelle ohne Scrollkasten (oben: .up-sticky .up-thead und
   die flache Tabelle, 1px tiefer wegen der Linie darueber); der Kopf im Halter selbst klebt nicht mehr. */
.up-sticky .up-cs-kopf { position: sticky; top: calc(var(--up-sticky-top) + var(--up-thead-off)); z-index: 5; }
.up-sticky .up-cs-kopf .up-thead { position: static; }
.up-root.up-root[data-flach="yes"].up-sticky:not(.is-festhoehe) .up-cs-kopf { top: calc(var(--up-sticky-top) + var(--up-thead-off) + 1px); box-shadow: 0 -1px 0 var(--vc-border); }
.up-root.up-root[data-flach="yes"].up-sticky:not(.is-festhoehe) .up-cs-kopf .up-thead { box-shadow: inset 0 -1px 0 var(--vc-border); }
/* Die Gesamtzeile im Halter: Kleben, Fahrt und Schatten des klebenden Fusses (unten, "DER KLEBENDE FUSS") gelten fuer den Halter, nicht fuer die
   Zeile darin -- dort waere sie ein Kind eines Kastens ohne Hoehe, an dem nichts klebt, und die Fahrt schnitte sie am Halter ab. */
.up-root[data-kleb-fuss="yes"] .up-box .up-cs-fuss { position: relative; transition: transform var(--up-t-3) var(--up-ease-expo), opacity var(--up-t-2) var(--up-ease-expo); }
.up-root[data-kleb-fuss="yes"].is-fuss-an .up-box .up-cs-fuss { position: sticky; bottom: var(--up-fuss-unter, 0px); z-index: 4; }
.up-root[data-kleb-fuss="yes"].is-fuss-weg .up-box .up-cs-fuss { transform: translateY(var(--up-fuss-weg, 120px)); opacity: 0; }
.up-root[data-kleb-fuss="yes"].is-fuss-still .up-box .up-cs-fuss { transition: none; }
.up-root[data-kleb-fuss="yes"] .up-box .up-cs-fuss .up-sumrow,
.up-root[data-kleb-fuss="yes"].is-fuss-weg .up-box .up-cs-fuss .up-sumrow { transform: none; opacity: 1; transition: none; }
.up-root[data-kleb-fuss="yes"] .up-box .up-cs-fuss::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; bottom: 0; pointer-events: none;
  box-shadow: var(--up-e-1); transform: scaleY(-1); opacity: 0; transition: opacity var(--up-t-2) var(--up-ease-expo);
}
.up-root[data-kleb-fuss="yes"].is-fuss-klebt .up-box .up-cs-fuss::before { opacity: 1; }
.up-root[data-kleb-fuss="yes"] .up-box .up-cs-fuss .up-sumrow::before { content: none; }

/* DIE KLICKBARE KARTE (13.10.): EIN Zustand fuer Hover, Druck und Fokus jeder Karte, die man
   anklicken kann ("alle Cards sollen die gleichen Hover/Klick-States kriegen wie die im
   Response-Table Card Mode"). Vorher hatte jede Karte ihren eigenen: die Antwortkarte Rand plus
   --up-e-1, die Quellenkachel eine gefaerbte Flaeche, Produkt- und Ad Card ein Anheben um 2px mit
   --up-e-chip, die Event-Karte --vc-hover-border, die Opportunity-Karte Rand plus Flaeche.
   Vom 13. bis 15.10. waren die Werte die der Antwortkarte (.urt-card, Kartenentwurf 13.10.): der
   Rand eine Spur kraeftiger plus --up-e-1, beim Druck 1 Prozent kleiner.
   SEIT DEM 15.10. DER HOVER VON GEIST, am 15.10. auf vercel.com gemessen. Eine ruhende Karte ist
   dort material-base ("base for resting cards", vercel.com/geist/materials): Ring 1px, KEIN
   Schatten -- deshalb hat auch hier keine Karte einen, und das ist Absicht, kein Verlust. Beim
   Ueberfahren bekommt keine klickbare Karte einen Schatten, und der Rand bleibt: die Kachel im
   Marketplace toent ihre Flaeche (#fff -> #f9f9f9, die halbe Gray 100), die Karte der Docs geht
   auf Background 200, die Vorlagen-Karte zeigt nur einen Pfeil. Dieselbe Regel wie beim Knopf
   (Knopf-Zustaende nach Geist, oben): nur die Flaeche aendert sich.
   Die Toenung liegt als EBENE ueber dem Grund der Karte (background-image), nicht an seiner
   Stelle: die Opportunity-Karte traegt im Dunkeln einen durchscheinenden Grund (--uo-col-bg,
   5 Prozent Weiss), den eine feste Farbe ersetzen wuerde. Die halb durchsichtige Hover-Flaeche
   ergibt auf Weiss genau Geists #f9f9f9 und auf jedem anderen Grund den halben Schritt dorthin.
   Ein Verlauf laesst sich nicht ueberblenden -- Geist blendet in 80 ms, das ist von sofort nicht
   zu unterscheiden. Druck und Fokus bleiben, wie sie waren.
   Geometrie (Radius, Polster, Rand, Flaeche) bleibt bei der Karte -- hier steht NUR der Zustand.
   Im Dunkeln nimmt die Randliste oben einigen Karten den Rand ((0,3,0) gegen (0,2,0) hier): die
   zeigen im Hover ebenfalls nur die Toenung.
   is-hl ist derselbe Zustand, von aussen gesetzt (Produktkarte und ihr Wort im Antworttext); die
   statische Produktkarte ohne diese Klasse holt sich --up-karte-hover in response-detail.css.
   Markup: die Klasse an jede klickbare Karte, NICHT an Skelett, Vorschau oder statische Fassung --
   die reagieren nicht und brauchen so auch keine Ruecknahme. Traeger am 13.10.: .urt-card,
   .urd-cite-card, .urd-pc (mit Produkt-Id), .up-adcard (adCardHtml), .uad-auf-ad, .uev-karte,
   .uo-card. */
:root, .up-root, [data-theme] { --up-karte-hover: color-mix(in srgb, var(--up-knopf-hover) 50%, transparent); }
/* DAZU HEBUNG UND HAUCH SCHATTEN (15.10. spaet, Nutzer: "nur Flaeche ist super. Ganz leichter
   Slide-up-Effekt wie wir ihn schon hatten und minimal mehr Schatten ist aber auch gut"): 2px nach
   oben wie die Ad Card bis zum 13.10., Schatten e-1 (Geist small, die leiseste Stufe). Der Rand
   bleibt, wie bei Geist. Beim Druck faellt die Hebung weg und die Karte wird 1 % kleiner. */
.up-karte-klick { cursor: pointer;
  transition: transform var(--up-t-2) var(--up-ease), box-shadow var(--up-t-2) var(--up-ease); }
.up-karte-klick:hover, .up-karte-klick.is-hl { background-image: linear-gradient(var(--up-karte-hover), var(--up-karte-hover));
  transform: translateY(-2px); box-shadow: var(--up-e-1); }
.up-karte-klick:active { transform: scale(.99); }
@media (prefers-reduced-motion: reduce) { .up-karte-klick:hover, .up-karte-klick.is-hl { transform: none; } }
.up-karte-klick:focus-visible { outline: 2px solid var(--vc-text); outline-offset: 2px; }

/* DIE AD CARD (05.10., Ads V1, core.js adCardHtml): die Produktkarte des Response Detail (.urd-pc)
   als Baustein fuer Anzeigen. Polster 8, Radius 16, innen 8 am Bild (konzentrisch; seit 15.10.
   Geist-Skala: Karte 12, Bild 12 - 8 = 4, in .urd-pc ebenso); Text 14/6/6,
   Fuss unten buendig (margin-top auto). Hover, Druck und Fokus kommen seit dem 13.10. aus
   .up-karte-klick (oben), die adCardHtml mitgibt -- vorher hob sich die Karte um 2px mit
   --up-e-chip. Das Raster (.up-adgrid) fuellt, was da ist, ab 240px je Karte. */
.up-adgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
.up-adcard { display: flex; flex-direction: column; min-width: 0; padding: 8px; border-radius: var(--up-r-2xl);
  border: 1px solid var(--vc-border); background: var(--vc-bg); box-sizing: border-box; text-align: left;
  font: inherit; color: inherit; }
/* Das Werbemittel: 16:10 auf der Kopfflaeche, contain -- ein Banner wird nie beschnitten. Ohne
   Polster und ohne multiply (anders als ein Produktfoto steht ein Banner nicht auf Weiss frei). */
.up-adcard-media { position: relative; aspect-ratio: 16 / 10; border-radius: var(--up-r-s); background: var(--vc-heading-bg);
  overflow: hidden; display: flex; align-items: center; justify-content: center; }
.up-adcard-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; }
.up-adcard-ph { display: inline-flex; color: var(--vc-fourth); }
.up-adcard-ph svg { width: 22px; height: 22px; }
.up-adcard-media.has-img .up-adcard-ph { display: none; }
/* Das Format oben links: 22 hoch, Radius 6 wie die Position auf der Produktkarte (.urd-pc-pos). */
.up-adcard-fmt { position: absolute; top: 10px; left: 10px; height: 22px; padding: 0 7px; border-radius: var(--up-r-s);
  background: var(--vc-bg); color: var(--vc-text); font-size: var(--up-fs-xs); font-weight: 600; line-height: 22px;
  white-space: nowrap; box-shadow: var(--up-e-1); }
.up-adcard-body { display: flex; flex-direction: column; gap: 8px; padding: 14px 6px 6px; flex: 1 1 auto; min-width: 0; }
/* Der Advertiser als Marken-Chip (UC.markenChip), der Name eine Stufe kleiner und gedaempft wie
   der Absender auf der Produktkarte; die Marke (You / Competitor) dahinter. */
.up-adcard-top { display: flex; align-items: center; min-width: 0; min-height: 20px; }
.up-adcard-top .up-marken-chip { min-width: 0; max-width: 100%; }
.up-adcard-top .up-ment-name { font-size: var(--up-fs-s); font-weight: 500; color: var(--vc-muted); }
.up-adcard-title { font-size: var(--up-fs-l); font-weight: 500; line-height: 1.4; letter-spacing: -0.01em; color: var(--vc-text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-height: calc(2 * 1.4em);
  overflow-wrap: anywhere; }
.up-adcard-title .is-empty { color: var(--vc-third); }
.up-adcard-desc { font-size: var(--up-fs-m); line-height: 1.45; color: var(--vc-muted); overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.up-adcard-foot { margin-top: auto; padding-top: 6px; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.up-adcard-price { font-size: var(--up-fs-xl); font-weight: 600; letter-spacing: -0.01em; color: var(--vc-text);
  font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.up-adcard-domain { display: flex; align-items: center; gap: 5px; min-width: 0; font-size: var(--up-fs-s); color: var(--vc-third); }
.up-adcard-domain svg { width: 12px; height: 12px; flex: 0 0 12px; display: block; }
.up-adcard-domain span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.up-adcard-meta { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: var(--up-fs-s); color: var(--vc-muted); white-space: nowrap; }
/* Modell und Markt als die Chips der Tabellen (05.10. abends: "das LLM-Logo und den Market wie in
   der Prompts-Tabelle, mit der Flagge"). Der Name des Modells in 12 wie der Werbetreibende oben in
   der Karte (.up-adcard-top), die Kachel bleibt bei 18 -- dieselbe Paarung, schon einmal hier. */
.up-adcard-model { min-width: 0; }
.up-adcard-model .up-ment-name { font-size: var(--up-fs-s); color: var(--vc-muted); }
.up-adcard-meta .up-market { flex: 0 0 auto; }
.up-adcard-date { margin-left: auto; flex: 0 0 auto; color: var(--vc-third); font-variant-numeric: tabular-nums; }

/* DER SEITEN-DRAWER (05.10., core.js makeSeitenDrawer). Der Kasten am <body> erzeugt keine Box
   (display: contents), traegt aber die Farbmarken. Zwei Klassen gegen die eine von .up-root.
   DIE DAUERN SIND DIE DER APP, NICHT DER SKALA: 180ms hinaus, 220ms herein, Schleier 160/200ms --
   die Drawer des Kopf-Skripts (project-host-app-layout) und der Opportunities-Drawer fahren genau
   so. Ein Drawer, der anders faehrt als jeder andere der App, faellt auf; darum die benannte
   Ausnahme wie --up-dd-radius. */
.up-root.up-sdrawer-host { display: contents; }
/* Der Schleier ist der aller Drawer (--vc-drawer-schleier, 16.10.): Schwarz zu 20 % in beiden
   Themen. Vorher 18 % der Schrift und im Dunkeln 45 % von --vc-text-invert -- das hellte den
   dunklen Grund auf (siehe am Token). */
.up-sdrawer-scrim { position: fixed; inset: 0; z-index: 9903; display: none; opacity: 0;
  background: var(--vc-drawer-schleier);
  -webkit-backdrop-filter: blur(1.5px); backdrop-filter: blur(1.5px);
  transition: opacity 160ms ease; }
.up-sdrawer { position: fixed; top: 0; right: 0; bottom: 0; z-index: 9904; width: min(560px, 100vw); display: none;
  flex-direction: column; box-sizing: border-box; background: var(--vc-bg); border-left: 1px solid var(--vc-border);
  box-shadow: var(--up-e-drawer); color: var(--vc-text);
  font-family: Geist, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; -webkit-font-smoothing: antialiased;
  transform: translateX(110%); transition: transform 180ms ease-in; will-change: transform; backface-visibility: hidden; }
.up-sdrawer-host.is-open .up-sdrawer-scrim { display: block; }
.up-sdrawer-host.is-open .up-sdrawer { display: flex; }
.up-sdrawer-host.is-in .up-sdrawer-scrim { opacity: 1; transition: opacity 200ms ease; }
.up-sdrawer-host.is-in .up-sdrawer { transform: translateX(0); transition: transform 220ms cubic-bezier(0.32, 0.72, 0, 1); }
@media (prefers-reduced-motion: reduce) { .up-sdrawer, .up-sdrawer-scrim { transition: none; } }
/* Die Kopfzeile in den Massen von drawer-topbar.css: 32 hoch, 7px Polster, 12px darueber, 8
   darunter; Krume 12/600 mit Zeilenhoehe 1, Typ und Trenner in der dritten Stufe, Logo 18/r6. */
.up-sdrawer-top { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 7px; margin: 12px 0 8px; min-width: 0; }
.up-sdrawer-top .up-iconbtn { flex: 0 0 auto; }
.up-sdrawer-crumb { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px;
  font-size: var(--up-fs-s); font-weight: 600; line-height: 1; }
.up-sdrawer-typ, .up-sdrawer-sep { flex: 0 0 auto; color: var(--vc-third); white-space: nowrap; }
/* Das Logo in der Krume ist das 18px-Logo der Marken-Chips (.up-ment-logo) -- dieselbe Groesse
   wie .utb-logo, ohne eigene Regel. */
.up-sdrawer-name { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.up-sdrawer-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-gutter: stable; padding: 4px 24px 40px;
  display: flex; flex-direction: column; gap: 28px; }

/* DIE DRAWER AUS CORE (11.10., core.js KERN_DRAWER; Response Detail als erster).
   Die Huelle traegt die Ids des Kopf-Skripts (#drawer-<art>, #backdrop-<art>), und das Kopf-
   Skript setzt Bewegung und Ebene mit !important (translateX 110 %, 220/180ms, dz/bz-Klassen).
   Was hier zu Bewegung und Ebene steht, ist darum der RUECKFALL fuer eine Seite ohne Kopf-
   Skript (Pruefstand, spaeter die Web-App) -- mit den Werten der Recherche vom 11.10.: herein
   260ms, hinaus 200ms, beide mit der Kurve von vaul, und nie ease-in.
   Form: BUENDIG (13.10., vorher schwebend 8px vom Rand mit Radius 12): volle Hoehe, rechts am
   Fensterrand, ohne Rundung -- wie die Seitenpanels von Linear, Notion, Stripe und Vercel. Bei
   88 % Breite las sich der Rand eher als Deko; die Ebene tragen jetzt Schatten (--up-e-drawer)
   und die eine Kante links. Unter 768px wird er zur ganzen Seite wie in der Mobilvorlage -- ein
   Blatt von rechts, kein Blatt von unten: die Vorlage zeigt eine volle Seite mit Kopf, und
   Wischen vom Rand kollidiert auf iOS mit Zurueck. */
.up-root.up-kdr { position: fixed; top: 0; right: 0; bottom: 0; z-index: 9920;
  width: min(var(--up-kdr-breite, 840px), 100vw); box-sizing: border-box;
  display: flex; flex-direction: column; overflow: hidden;
  /* Der Grund der Seite (--up-surface), nicht der Karten (--vc-bg): im Dunkeln stand der Drawer
     sonst heller als die Seite darunter (12.10. gemeldet). Im Hellen sind beide weiss. */
  background: var(--up-surface, var(--vc-bg)); color: var(--vc-text);
  border: 0; border-left: 1px solid var(--vc-border); border-radius: 0; box-shadow: var(--up-e-drawer);
  font-family: Geist, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif; -webkit-font-smoothing: antialiased;
  transform: translateX(110%); transition: transform var(--up-t-2) cubic-bezier(.32, .72, 0, 1);
  will-change: transform; backface-visibility: hidden; outline: none; }
.up-root.up-kdr.open { transform: none; transition-duration: var(--up-t-3); }
/* Der Schleier aller Drawer (--vc-drawer-schleier, 16.10.): Schwarz zu 20 %, hell und dunkel
   gleich. Bis 16.10. stand hier im Dunkeln 45 % von --vc-text-invert (#1f1f1b), und das hellte
   den Grund #0a0a0a auf, statt ihn zurueckzunehmen. Die Unschaerfe von 1.5px setzt das
   Kopf-Skript an [id^="backdrop-"].open; hier steht sie noch einmal fuer eine Seite ohne
   Kopf-Skript (Pruefstand), erst offen, damit der geschlossene Schleier nichts rechnet. */
.up-root.up-kdr-scrim { position: fixed; inset: 0; z-index: 9915; opacity: 0; pointer-events: none;
  background: var(--vc-drawer-schleier);
  transition: opacity var(--up-t-2) ease; }
.up-root.up-kdr-scrim.open { opacity: 1; pointer-events: auto;
  -webkit-backdrop-filter: blur(1.5px); backdrop-filter: blur(1.5px); }
@media (prefers-reduced-motion: reduce) { .up-root.up-kdr, .up-root.up-kdr-scrim { transition: opacity var(--up-t-1) ease; } }

/* Der Kopf: 56 hoch (32er Knoepfe plus 12 darueber und darunter), steht, waehrend der Inhalt
   darunter laeuft. Die Linie kommt erst mit dem Rollen (.is-gerollt) -- oben ist der Kopf Teil
   der Flaeche. Die Knoepfe sind .up-iconbtn aus core, unveraendert. */
/* GLAS (12.10. angefordert, "wie Apple"): der Kopf liegt UEBER dem Rollbereich, der Inhalt laeuft
   unter ihm hindurch und scheint durch -- 72 % Deckkraft des Seitengrunds, dahinter 24px
   Unschaerfe mit etwas mehr Saettigung (so mischt Apple seine Materialien). Erst 92 %: "sieht
   man kaum" (12.10.). Der Rollbereich
   beginnt mit dem Abstand der Kopfhoehe (--up-kdr-kopf-h), oben steht also nichts verdeckt.
   Ohne backdrop-filter (alter Browser) bleibt es ein fast deckender Kopf -- lesbar wie vorher. */
.up-root.up-kdr { --up-kdr-kopf-h: 56px; }
.up-kdr-kopf { position: absolute; top: 0; left: 0; right: 0; z-index: 3;
  display: flex; align-items: center; gap: 8px; height: var(--up-kdr-kopf-h);
  padding: 0 12px; box-sizing: border-box; min-width: 0;
  background: color-mix(in srgb, var(--up-surface, var(--vc-bg)) 72%, transparent);
  -webkit-backdrop-filter: saturate(1.8) blur(24px); backdrop-filter: saturate(1.8) blur(24px);
  border-bottom: 1px solid transparent; transition: border-color var(--up-t-1) ease; }
.up-kdr.is-gerollt .up-kdr-kopf { border-bottom-color: var(--vc-border); }
.up-root .up-kdr-knopf[hidden] { display: none; }
/* Ohne Zurueck-Knopf beginnt die Krume auf der Kante der Inhaltsspalte nicht, aber auf der des
   Kreuzes gegenueber: 8px Luft wie die Polsterung des Knopfes, damit links und rechts gleich
   weit vom Rand stehen. */
.up-kdr-krume { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px;
  padding-left: 8px; font-size: var(--up-fs-m); font-weight: 500; line-height: 1; }
.up-kdr.has-zurueck .up-kdr-krume { padding-left: 0; }
.up-kdr-art, .up-kdr-sep { flex: 0 0 auto; color: var(--vc-third); white-space: nowrap; }
.up-kdr-name { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--vc-text); }
.up-kdr-werkzeuge, .up-kdr-eigen { flex: 0 0 auto; display: flex; align-items: center; gap: 4px; }
.up-kdr-eigen:empty { display: none; }
.up-kdr-sk { display: inline-block; vertical-align: middle; width: 132px; height: 12px;
  border-radius: var(--up-sk-r); background: var(--vc-sk) var(--up-sk-verlauf); animation: var(--up-sk-bewegung); }

/* Der Koerper rollt, die Seite darunter nicht (overscroll-behavior: am Ende des Inhalts lief
   das Rollen sonst auf #main weiter, trotz der Sperre des Kopf-Skripts auf iOS).
   DIE SPALTE NIMMT DIE GANZE BREITE (12.10.): mit 720 in der Mitte stand der Inhalt in einem
   80-%-Drawer mit ueber 200px Luft je Seite ("padding left right zu gross"). Die Lesebreite
   begrenzt jetzt der Text selbst (.urd-body .up-rb, 720). 20px Rand: dieselbe Kante wie die
   Krume im Kopf (12 Polster + 8). */
/* overflow-x hidden: ein GESCHLOSSENES Menue (der Kalender oben rechts) steht linksbuendig an
   seinem Ausloeser und reichte 150px ueber die Kante -- gemessen auf 390px, die Flaeche liess
   sich seitlich schieben. Geoeffnet richtet es sich selbst aus (is-right) und bleibt innen. */
/* position + z-index 0: der Koerper ist eine eigene Stapelebene UNTER dem Glaskopf (z 3). Ohne sie
   legte sich ein klebender Tabellenkopf (.up-sticky .up-head, z 6, samt Deckstreifen bis oben) ueber
   den Glaskopf -- gemessen 13.10., als die Tabellen in den Drawern zu kleben begannen. */
.up-kdr-koerper { position: relative; z-index: 0; flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain;
  scrollbar-gutter: stable; padding-top: var(--up-kdr-kopf-h); }
.up-kdr-spalte { box-sizing: border-box;
  padding: 16px 20px calc(96px + env(safe-area-inset-bottom)); }
/* Response Detail: doppelter Rand (12.10. angefordert, "padding left right verdoppeln") -- dort
   ist der Inhalt ein Lesetext, und 20px an der Kante liessen ihn an den Rand gedrueckt wirken.
   Auf dem Telefon gilt weiter der schmale Rand (unten). */
.up-kdr-response .up-kdr-spalte { padding-left: 40px; padding-right: 40px; }
.up-kdr-warte { display: flex; flex-direction: column; gap: 14px; padding-top: 8px; }
.up-kdr-warte .up-kdr-sk { display: block; height: 16px; }
.up-kdr-warte .up-kdr-sk.is-l { width: 88%; height: 28px; }
.up-kdr-warte .up-kdr-sk.is-m { width: 56%; height: 28px; }
.up-kdr-warte .up-kdr-sk.is-s { width: 32%; margin-top: 8px; }

/* DER DETAIL-KOPF (12.10.): was Response Detail und URL Detail gleich haben, und spaeter jeder
   weitere Drawer -- die Meta-Zeile mit feinen Trennstrichen, der Titel, die Leisten auf der
   Kopfflaeche (Zahl gross links, Inhalt rechts) und die Abschnittskoepfe. Stand zuerst lokal in
   response-detail.css; mit dem zweiten Nutzer gehoert es hierher (CLAUDE.md 1b, Punkt 2).
   Die Werte und ihre Begruendung: Titel 28 = --up-fs-3xl, dieselbe Stufe wie der Seitenkopf
   (die Designvorlage setzt ihn als groesste Zeile); Strich 1x12 in der Rahmenfarbe, so hoch wie
   die Versalhoehe der 13er Schrift, kein Mittelpunkt (CLAUDE.md 5); Leiste Radius 16 wie die
   Karten der App, links 20 statt 12 Polster, weil dort Text und kein Knopf an der Kante steht. */
.up-dk { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.up-dk-meta { display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap; min-height: 24px;
  font-size: var(--up-fs-m); color: var(--vc-third); }
.up-dk-meta:empty { display: none; }
.up-dk-sep { flex: 0 0 1px; width: 1px; height: 12px; background: var(--vc-border); }
.up-dk-titel { margin: 0; font-size: var(--up-fs-3xl); font-weight: 600; line-height: 1.2; letter-spacing: -0.02em;
  max-width: 820px; text-wrap: pretty; overflow-wrap: anywhere; color: var(--vc-text); }
.up-dk-leiste { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: 12px 24px; min-width: 0; margin-top: 16px; padding: 12px 12px 12px 20px;
  border-radius: var(--up-r-2xl); background: var(--vc-heading-bg); }
.up-root .up-dk-leiste[hidden] { display: none; }
.up-dk-zahl { flex: 0 0 auto; display: inline-flex; align-items: baseline; gap: 8px;
  font-size: var(--up-fs-m); font-weight: 500; color: var(--vc-third); }
.up-dk-zahl-n { font-size: var(--up-fs-2xl); font-weight: 700; line-height: 1; letter-spacing: -0.02em;
  color: var(--vc-text); font-variant-numeric: tabular-nums; }
.up-dk-zahl-n:empty { display: none; }
/* Abschnittskopf: Ueberschrift ueber der Unterzeile, 16/600 und 13. */
.up-dk-h2 { font-size: var(--up-ueb-abschnitt-gr); font-weight: var(--up-ueb-abschnitt-gew); letter-spacing: var(--up-ueb-abschnitt-spur); line-height: var(--up-ueb-abschnitt-zeile); color: var(--vc-text); }
/* Die Unterzeile in der DRITTEN Textstufe (12.10. angefordert): sie erklaert die Ueberschrift
   und tritt hinter den Inhalt des Abschnitts zurueck. */
.up-dk-unter { font-size: var(--up-ueb-unter-gr); font-weight: var(--up-ueb-unter-gew); line-height: var(--up-ueb-unter-zeile); color: var(--vc-muted); }
/* DER KOPF MIT BILD (13.10. aus brand-drawer.css hierher, seit der Domain-Drawer ihn auch traegt):
   Logo oder Favicon links, daneben der Titel (.up-dk-titel) und darunter eine leise Zeile. Die
   Kachel ist .up-logo-box aus core in Kopfgroesse, genau die des Kopfes von performance-detail und
   Ads (.upd-held-logobox): 44, Radius 12 aussen, innen 11 -- das konzentrische Kind (CLAUDE.md 1c);
   56 war dem Nutzer zu gross. */
.up-dk-held { display: flex; align-items: center; gap: 14px; min-width: 0; }
.up-dk-held-bild { display: inline-flex; flex: 0 0 auto; }
.up-dk-logo.up-logo-box { width: 44px; height: 44px; flex: 0 0 44px; border-radius: var(--up-r-xl); }
.up-dk-logo.up-logo-box img { width: 42px; height: 42px; border-radius: calc(var(--up-r-xl) - 1px); }
.up-dk-logo .up-logo-ltr { font-size: var(--up-fs-xl); }
.up-dk-held-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1 1 auto; }
/* Die leise Zeile unter dem Titel: 13 in der zweiten Textfarbe; ist sie ein Knopf, wird sie beim
   Hover dunkler und unterstrichen (wie die Adresse unter dem Titel im URL-Drawer, .uud-link). */
.up-dk-held-sub { display: flex; min-height: 20px; min-width: 0; }
.up-dk-held-link { display: inline-flex; align-items: center; max-width: 100%; min-width: 0; padding: 0; border: 0;
  background: none; font: inherit; font-size: var(--up-fs-m); line-height: 20px; color: var(--vc-muted);
  cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.up-dk-held-link:hover { color: var(--vc-text); text-decoration: underline; text-underline-offset: 3px; }
/* KOPF UND FILTER IN EINER ZEILE (13.10.): links der Kopf (Bild, Name, Unterzeile), rechts Kalender
   und Filterknopf, auf der MITTE des Kopfes ("oben rechts zentriert", zweite Runde -- erst an der
   Unterkante). Heute im Domain-Drawer; Brand, Prompt und URL tragen ihre Filter in der Meta-Zeile
   darueber (.up-dk-metazeile). Reicht die Breite nicht, bricht die Zeile und die Filter stehen
   linksbuendig darunter (space-between laesst ein umgebrochenes Kind links). Der Kopf gibt bis
   280px nach -- sonst wuerde ein langer Name zerlegt, bevor die Filter weichen. */
.up-dk-heldzeile { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: 16px 24px; min-width: 0; }
.up-dk-heldzeile > :first-child { flex: 1 1 auto; min-width: min(100%, 280px); }
/* Mit den Chips neben den Knoepfen (27.10., filter-bar.js chipLage) haelt der Kopf nur, was sein Name braucht:
   ein kurzer Name ("Nike", 130) liess sonst 150 leer, und schon der zweite Chip musste bei 840 Drawerbreite unter
   den Kopf. Zerlegt wird der Name dadurch nicht -- die Zeile bricht, sobald Name und Filterreihe in ganzer Breite
   nicht nebeneinander passen, und dann stehen die Chips wieder in ihrer eigenen Zeile. */
.up-dk-heldzeile.has-chips-neben > :first-child { min-width: 0; }
/* META-ZEILE UND FILTER (13.10. zweite Runde: "Oben rechts zentriert", im URL-Drawer "auf Hoehe des
   zuletzt-gesehen-Datums"): die Filter stehen in der OBERSTEN Zeile des Kopfes, rechts und mittig
   zu ihr, nicht neben Name oder Titel darunter. Die Meta-Zeile darf schrumpfen und umbrechen; ist
   sie leer (:empty in core), stehen die Filter allein oben rechts. Zu schmal: darunter, links. */
.up-dk-metazeile { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: 16px 24px; min-width: 0; }
.up-dk-metazeile > .up-dk-meta { flex: 1 1 auto; min-width: 0; }
/* Leere Meta-Zeile (Laden, keine Angaben): ohne sie stuende die Filterreihe als einziges Kind links. */
.up-dk-metazeile > .up-dk-meta:empty + .up-dk-filterzeile { margin-left: auto; }
/* Die Filterreihe: 8px wie jede Werkzeugreihe. Mit aktiven Filtern stehen deren Chips im
   Filterknopf-Baustein daneben (ufb bricht selbst um) -- darum darf er schrumpfen, der Kalender
   nicht. flex-start: allein unter dem Kopf UND selbst umgebrochen (sehr schmal) stuenden ihre Teile
   mit flex-end rechts gestaffelt statt linksbuendig (gemessen bei 480). */
.up-dk-filterzeile { display: flex; align-items: center; justify-content: flex-start; flex-wrap: wrap; gap: 8px;
  flex: 0 1 auto; min-width: 0; max-width: 100%; }
.up-dk-filterzeile > .udr-root { flex: 0 0 auto; }
.up-dk-filterzeile > .ufb-root { flex: 0 1 auto; width: auto; min-width: 0; }
/* DIE CHIPS DER FILTER IN EIGENER ZEILE (22.10.): unter Kopf und Filtern, linksbuendig, damit Kalender und
   Filters oben rechts stehen bleiben (Vorbild Polaris IndexFilters, Cloudscape). Die Leiste zieht ihre Chips
   per data-chips-ziel hierher (filter-bar.js), Anzeige und Verbergen regelt .ufb-chipzeile. Sie steht als Kind
   im Kopf (.up-dk, Spalte mit 16 Abstand), einen eigenen Rand braucht sie darum nicht. Leer (vor dem Aufbau
   der Leiste): weg, sonst stuende eine Luecke von 16 im Kopf.
   Ihre Id darf NICHT mit "drawer-" beginnen: das Kopf-Skript behandelt jedes [id^="drawer-"] als Drawer und
   schiebt es zu 110 % aus dem Bild (gemessen 22.10.: translateX 1222px). Darum "chips-drawer-<art>".
   SEIT 27.10. NUR NOCH, WENN ES NEBEN DEN KNOEPFEN NICHT PASST: wo Platz ist, setzt filter-bar.js (chipLage) die
   Zeile als erstes Kind in die Filterreihe (.is-neben): Chips, Trenner, Kalender, Filters in einer Zeile, die
   Reihe rechtsbuendig, die Knoepfe bleiben stehen. Gemessen, keine Breite je Drawer. */
.up-dk-chipzeile:empty { display: none; }
/* DIE ABSCHNITTSFORM (13.10., Vorbild Domain Detail; "die Borders um die grossen Elemente weg, das
   sieht cleaner aus"). Ein Baustein im Drawer steht ohne Kartenrahmen, Radius und eigene Flaeche
   auf dem Grund des Drawers -- die Luft zwischen den Abschnitten trennt. Gesetzt von der
   einbettenden Stelle ueber data-form="abschnitte" an der Wurzel des Bausteins (Domain- und
   Brand-Drawer, je das grosse Chart oben); Dashboard und Landingpage setzen es nicht und behalten ihre Karten. Erst in drei
   Bausteinen einzeln gebaut, darum hier. Tabellen behalten ihren Kasten wie ueberall in der App --
   darum neben ihnen auch kein Trenner (er laege als zweite Linie neben ihrem Rand).
   NUR die grossen Charts oben (Brand Detail, Domain Detail): Visibility Chart und Top Citations
   stehen im Drawer seit 15.10. spaet als EIN Kasten mit Trennlinie (DAS BAND IM KASTEN, oben bei
   den Baendern) -- vorher je zwei Karten (13.10. zweite Runde, "einfach core").
   (0,4,0) gegen die Kartenregeln der Bausteine und deren dunkle Randregel (0,3,0). */
.up-root[data-form="abschnitte"] :is(.ubd-card, .udd-chartcard, .udd-funnelcard) {
  border: 0; border-radius: 0; background: transparent; padding: 0; }
.up-dk-held-link:focus-visible { outline: var(--up-focus-w, 2px) solid var(--vc-text); outline-offset: 2px; border-radius: var(--up-r-xs); }
/* Skelett des Kopfes, in den Massen dessen, was gleich dasteht. */
.up-dk-sk { display: inline-block; height: 14px; border-radius: var(--up-sk-r); background: var(--vc-sk) var(--up-sk-verlauf);
  animation: var(--up-sk-bewegung); }
.up-dk-sk.is-logo { width: 44px; height: 44px; border-radius: var(--up-r-xl); }
.up-dk-sk.is-titel { display: block; width: 240px; max-width: 100%; height: 28px; }
.up-dk-sk.is-s { width: 120px; }

/* Tablet: die volle Breite -- 80 % von 900px liessen links einen Streifen Seite stehen, der nur
   noch nach Platzverlust aussah (so wie die Bubble-Gruppe, die dort auf 100 % ging). */
@media (max-width: 1023px) {
  .up-root.up-kdr { width: 100vw; }
}
@media (max-width: 767px) {
  .up-root.up-kdr { top: 0; right: 0; bottom: 0; width: 100vw; border: 0; border-radius: 0; }
  .up-root.up-kdr { --up-kdr-kopf-h: calc(56px + env(safe-area-inset-top)); }
  .up-kdr-kopf { padding-top: env(safe-area-inset-top); }
  /* Wie in der Mobilvorlage: der Kopf nennt nur, WAS offen ist ("Response"), mittig. Modell und
     Datum stehen darunter im Inhalt -- auf 375px haette die Krume ohnehin nur Punkte gezeigt. */
  .up-kdr-kopf { display: grid; grid-template-columns: 1fr auto 1fr; }
  .up-kdr-kopf > [data-kdr-zurueck] { grid-area: 1 / 1; justify-self: start; }
  .up-kdr-krume { grid-area: 1 / 2; justify-content: center; padding-left: 0; }
  .up-kdr-werkzeuge { grid-area: 1 / 3; justify-self: end; }
  .up-kdr-sep, .up-kdr-name { display: none; }
  .up-kdr-art { color: var(--vc-text); font-weight: 600; }
  .up-kdr-spalte, .up-kdr-response .up-kdr-spalte { padding-left: 16px; padding-right: 16px; }
}
/* Der Tarif als Pille (UC.planPilleHtml, 28.09. aus teams.css hierher, seit settings-billing sie
   auch zeigt). Der Grundton des Punktes steht HIER und nicht im JS: .up-sent-dot bringt keine
   Farbe mit (die Sentiment-Zelle setzt sie immer inline), und ein Tarif ohne Farbeintrag hatte
   in teams einen unsichtbaren Punkt (gemessen rgba(0,0,0,0) bei "Legacy Free"). Ein Tarif mit
   Eintrag ueberschreibt ihn inline; ohne laufenden Tarif bleibt es bei diesem Ton, der Name wird
   gedaempft. */
.up-planpill .up-sent-dot { background: var(--vc-third); }
.up-planpill.is-off .up-sent-val { color: var(--vc-muted); }
/* Mobil kuerzt die Pille, statt abgeschnitten zu werden (29.09., Teams: "der Tarif ist
   abgeschnitten, truncate den einfach im mobile"). .up-sent steht sonst auf flex 0 0 auto; hier
   darf sie schrumpfen, der Name endet mit Auslassungspunkten.
   NUR auf schmalen Wurzeln: am Desktop ist die Tarifzelle der Teams-Tabelle innen 137px breit und
   ">_ Developer Access" 147px -- die Pille laeuft dort unsichtbar ins Polster der Zelle, und eine
   Kuerzung waere an genau der Stelle, an der Platz ist, ein neuer Fehler (so gemessen). */
.up-root.is-narrow .up-planpill { max-width: 100%; min-width: 0; flex-shrink: 1; }
.up-root.is-narrow .up-planpill .up-sent-val { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Der dauerhafte Entwicklerzugang ("Legacy Free", UC.planIstDev): kein Stufenpunkt, sondern ein
   Prompt >_ und der Name in Monospace, der Rahmen gestrichelt -- er ist kein Tarif der Leiter
   Essential/Professional/Enterprise und soll auch nicht so aussehen. Dieselbe Hoehe und dasselbe
   Polster wie jede Pille, damit die Spalte ruhig bleibt; 12 statt 13, weil Monospace je Zeichen
   breiter laeuft und die Tarifspalte der Teams-Tabelle bei 140 anfaengt. Eine Schrift wird nicht
   geladen: ui-monospace ist auf jedem System da, auf dem Mac ist es SF Mono. */
/* .up-sent davor: die Pille bekommt ihren Rahmen seit dem 29.09. aus .up-sent.up-pille (zwei Klassen),
   und die Abweichung gewinnt ueber die Spezifitaet, nicht ueber die Reihenfolge. */
.up-sent.up-planpill.is-dev { border-style: dashed; border-color: var(--vc-fourth); }
.up-planpill.is-dev .up-sent-val,
.up-planpill-prompt { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; }
.up-planpill.is-dev .up-sent-val { font-size: var(--up-fs-s); letter-spacing: -0.01em; }
.up-planpill-prompt { font-size: var(--up-fs-xs); font-weight: var(--up-fw-b); color: var(--vc-third); line-height: 1; flex: 0 0 auto; }
/* ---------- "nothing here" convention ----------
   ONE muted dash for every "no value" cell in the app — sentiment/rank/share percentages,
   brand-mention/citation stacks with zero items. Before this there were three different
   answers to "nothing here" (a bare unstyled "-" on .up-stack-empty, a plain "–" inline in
   .up-num/.up-sent-val with no color override so it rendered in full text-strength color, and
   responses-table's own SVG minus icon) — one per component, none of them muted. This is the
   single source every one of them now points at. */
/* Der leere Stapel traegt jetzt ein Minus-Icon statt eines Schriftzeichens: feste Groesse, und
   die Drittfarbe nimmt es aus dem Lesefluss -- es sagt "hier ist nichts", und das soll nicht so
   laut sein wie ein Wert. */
.up-stack-empty { display: inline-flex; align-items: center; justify-content: center;
  color: var(--vc-third); }
.up-stack-empty svg { width: 14px; height: 14px; display: block; }

.up-num.is-empty, .up-sent-val.is-empty, .up-stack-empty,
.tct-num.is-empty, .udt-sub-share.is-empty { color: var(--vc-fourth); font-weight: 500; }

/* ---------- icon segmented control ----------
   Lifted verbatim from topcitations-dashboard's doughnut/bar switch (.tcl-seg), which is the
   look every icon-pair switch in the app already had. It lives here so a new one cannot be
   "almost" it: components add .up-seg/.up-seg-btn, they do not restate the rules.
   NOTE this is a different control from .up-dense — that one is the full-width label switcher
   inside the Table Settings popover. */
/* Dieselbe Fassung wie beim Day/Week/Month-Umschalter darueber: 26px hoch, Polster 8, leisere
   Flaeche, oben ausgerichtet. Die Zeichen ziehen mit auf 14px -- 16px in einer 26er-Pille waeren
   dieselbe Enge, gegen die STYLEGUIDE 1 warnt. */
/* align-self: center -- die kleinen Umschalter stehen MITTIG in ihrer Zeile. Bei 26px Hoehe neben
   einer 32px-Zeile sah oben buendig aus wie verrutscht.
   Zwei bleiben oben buendig, und beide aus demselben Grund: sie stehen nicht in einer Zeile mit
   Knoepfen, sondern in der oberen Ecke einer Kachel, deren Kopf mehrzeilig ist -- .vc-gran im
   Visibility-Chart und .tcl-seg im Zitatteil. Ihre Ausnahme steht unten. */
/* VIER WEITERE ALIASE (27.09.), aus demselben Grund wie .cc-seg/.ubo-seg/.tcl-seg: es waren
   Nachbauten dieses Bauteils in den Komponentendateien, Wert fuer Wert abgeschrieben, und jeder
   lief auf seine Art auseinander -- .upt-status malte seine Pille aus --vc-bg/--up-sel-bg statt aus
   der Pillenmarke und stand damit nach dem Anheben der Schiene im Dunkeln UNTER ihr (gemessen:
   im Menue -6.9 Stufen). Gefunden beim Aufraeumen nach der Meldung "Switcher im Darkmode kaum zu
   erkennen", mit der Frage, ob noch Individualbauten da sind.
     .ubo-yaxis        Y-Achse der Landscape-Matrix (brands-overview)   Markup in der Bubble-Seite
     .tcd-mode         Domains/URLs in Top Citations, grosse Form unten  Markup in der Bubble-Seite
     .upt-status       Active/Inactive der Prompts-Tabelle              Kasten in der Bubble-Seite
     .udt-sub-dispseg  Title/URL im Drilldown der Domains-Tabelle       eigene 24px, siehe dort
   Alias und nicht Umbenennung: die ersten drei stehen im eingefuegten Bubble-Markup, das kein
   CDN-Pin erreicht. Der vierte darf nach STYLEGUIDE (Drilldown-Bedienelemente) die Klasse von
   core gar nicht tragen -- ein ungezieltes querySelector eines Kits wuerde ihn sonst fuer das
   eigene Element halten. In domains-table.css bleibt nur seine Groesse. */
.up-seg, .cc-seg, .ubo-seg, .tcl-seg,
.ubo-yaxis, .tcd-mode, .upt-status, .udt-sub-dispseg { display: inline-flex; padding: 0; gap: 0; background: var(--vc-switch-rail); border-radius: var(--up-r-m); border: 0; height: 26px; flex: 0 0 auto; align-self: center; }
.tcl-seg { align-self: flex-start; }

/* ---- Der gleitende Umschalter --------------------------------------------------------------
   Die aktive Stufe wird nicht umgefaerbt, sie FAEHRT: ein Streifen liegt unter den Knoepfen und
   zieht in 200ms an die neue Stelle. Dasselbe Bild, das der Umschalter im Onboarding schon hatte.

   Der Streifen ist ein ::before und KEIN eingehaengtes Element -- und das ist der Kern dieser
   Fassung. Die erste hat einen span in den Umschalter gehaengt, und genau daran ist am 29.08. eine
   ganze Bubble-App stillgestanden: die Umschalter stehen dort im Markup der HTML-Elemente, Bubble
   setzt dieses Markup beim Zeichnen ueber jQuery.html() immer wieder neu, und ein fremder Knoten
   darin fuehrt zu einer Schleife, aus der die Seite nicht mehr herauskommt. In der Konsole war
   kein Fehler zu sehen -- nur eine Seite, die nie fertig wurde, und dieselbe Ladewarnung dreimal
   hintereinander aus drei verschiedenen Skripten, also dasselbe Element dreimal neu gezeichnet.
   Ein Pseudo-Element gibt es im DOM nicht. Es kann von niemandem gesehen, geerbt oder
   wegdiffed werden. core setzt nur noch vier Zahlen als Eigenschaften an den Kasten. */
.up-seg, .cc-seg, .ubo-seg, .tcl-seg, .vc-gran, .cc-gran, .up-pagesize-seg, .up-filter-dim,
.up-dense, .tcd-mode, .uca-format-seg, .ubo-yaxis, .upt-status, .udt-sub-dispseg,
.uap-tabs { position: relative; }
.up-seg::before, .cc-seg::before, .ubo-seg::before, .tcl-seg::before, .vc-gran::before,
.cc-gran::before, .up-pagesize-seg::before, .up-filter-dim::before, .up-dense::before,
.tcd-mode::before, .uca-format-seg::before, .ubo-yaxis::before, .upt-status::before,
.udt-sub-dispseg::before, .uap-tabs::before {
  content: ""; position: absolute; left: 0; top: 0; z-index: 0; box-sizing: border-box;
  width: var(--up-seg-w, 0); height: var(--up-seg-h, 100%);
  transform: translate(var(--up-seg-x, 0), var(--up-seg-y, 0));
  border-radius: var(--up-r-m); background: var(--vc-switch-pill); border: 1px solid var(--vc-switch-border);
  opacity: 0; pointer-events: none;
}
/* Sichtbar und beweglich erst, wenn core gemessen hat. Die Deckkraft faehrt NICHT mit: sonst
   blendet der Streifen beim ersten Bild aus der Breite 0 heran, und das behauptet einen Wechsel,
   den es nicht gab. */
.is-gleitend::before { opacity: 1; }
.is-gleitend::before { transition: transform var(--up-seg-dauer, 200ms) var(--up-seg-kurve, ease),
  width var(--up-seg-dauer, 200ms) var(--up-seg-kurve, ease), height var(--up-seg-dauer, 200ms) var(--up-seg-kurve, ease); }
/* --up-seg-dauer und --up-seg-kurve setzt core NUR, wenn eine neu gebaute Box eine laufende Fahrt
   uebernimmt (segSchreiben, 29.09.): Restzeit statt 200ms, ease-out statt ease. Sonst fehlen sie,
   und es gilt der Rueckfall -- dieselben 200ms ease wie immer. */
/* Ein Schreibvorgang OHNE Fahrt. Gesetzt von segMessen fuer alles, was kein echter Wechsel des
   aktiven Knopfes ist: die erste Messung nach einem Neuaufbau, eine engere Werkzeugleiste, ein
   Menue, das aufgeht. Zwei Klassen gegen eine, also ueber die Spezifitaet und nicht ueber die
   Reihenfolge gewonnen. */
.is-gleitend.is-sofort::before { transition: none; }
[data-theme="dark"] .up-seg::before, [data-theme="dark"] .cc-seg::before,
[data-theme="dark"] .ubo-seg::before, [data-theme="dark"] .tcl-seg::before,
[data-theme="dark"] .vc-gran::before, [data-theme="dark"] .cc-gran::before,
[data-theme="dark"] .up-pagesize-seg::before, [data-theme="dark"] .up-filter-dim::before,
[data-theme="dark"] .up-dense::before, [data-theme="dark"] .tcd-mode::before,
[data-theme="dark"] .uca-format-seg::before, [data-theme="dark"] .ubo-yaxis::before,
[data-theme="dark"] .upt-status::before, [data-theme="dark"] .udt-sub-dispseg::before,
/* DIE GLEITENDE PILLE UND JEDER AKTIVE KNOPF EINES UMSCHALTERS liegen HOEHER als ihre Schiene,
   nicht tiefer. Nach dem Farbtausch stand hier ueberall der Grund der Seite, also die dunkelste
   Flaeche der App, mitten auf einem helleren Umschalter. Der Wert kam nicht aus einer
   Entscheidung: die alte Pille trug #121212, und der Tausch machte daraus stumpf #08090a.
   Gemeldet als "die Switcher in Dropdowns sind jetzt kaputt", und das waren sie.
   Danach bg-quaternary (#28282c) auf border-secondary, seit dem 27.09. --vc-switch-pill als
   Anhebung der Schiene: auf jedem Grund 14 bis 15 Stufen ueber ihr (siehe dort). Sieben Stellen,
   alle mit demselben Muster. */
[data-theme="dark"] .uap-tabs::before { background: var(--vc-switch-pill); border-color: var(--vc-switch-border); }
/* ---- UND DIE SCHIENE DARUNTER --------------------------------------------------------------
   Gemeldet am 07.09. zuerst fuer das Dunkle ("die grossen Switcher, die 32px hoch sind, muessen
   denselben Grund bekommen wie die kleinen, weil der Selected-State sich sonst zu wenig davon
   unterscheidet"), danach fuer alle Dropdowns ("kaum erkennbar, der Kontrast ist zu niedrig").
   Ursache war, dass die GROESSE den Grund bestimmte: die grossen trugen den vollen Ton, der
   dicht an der Pille liegt (dunkel 5.0 Stufen, hell 6.7).
   Hier stand danach eine Regel mit vierzehn Selektoren, die alle grossen Umschalter auf den Ton
   der kleinen zwang. Sie ist am 22.09. weggefallen: die Schiene haengt jetzt an EINER Marke
   (--vc-switch-rail), die Groesse aendert nur noch die Hoehe, und damit gibt es nichts mehr
   nachzuziehen. Die Regel hatte ausserdem einen stillen Preis -- sie ueberschrieb per .up-root
   die Komponentendateien, und deren eigene Grundangaben waren dadurch wirkungslos, ohne dass man
   es ihnen ansah: acht tote Zeilen, gefunden beim Messen. Jetzt nennt jede Schiene ihren Grund
   selbst, und es gibt nur einen. */
/* Die Knoepfe stehen ueber dem Streifen. */
.is-gleitend > button { position: relative; z-index: 1; }
/* Und die aktive Stufe gibt ihre eigene Flaeche ab, SOBALD der Streifen misst. Drei Stufen der
   Spezifitaet, weil die Aktivregeln der Bauteile in drei Stufen stehen: .up-seg-btn.is-active
   (0,2,0), [data-theme="dark"] ... (0,3,0) und .up-root[data-theme="dark"] .vc-gran-btn.is-active
   (0,4,0). Ueber die Spezifitaet gewonnen, nicht ueber die Ladereihenfolge. */
.is-gleitend > button.is-active,
[data-theme="dark"] .is-gleitend > button.is-active,
.up-root[data-theme="dark"] .is-gleitend > button.is-active {
  background: transparent; border-color: transparent;
}
/* KEINE Ausnahme fuer prefers-reduced-motion. Sie stand hier und war der wahrscheinlichste Grund
   dafuer, dass der Umschalter im Onboarding glitt und kein anderer: dessen eigene Marke hatte nie
   eine solche Regel. Wer die Bewegung auf seinem Rechner abgestellt hat, saehe damit genau EINEN
   Umschalter fahren und alle anderen springen -- der Unterschied waere auffaelliger als die
   Bewegung selbst. 200ms auf einer Flaeche von wenigen Pixeln ist ausserdem keine Bewegung, die
   jemanden stoert; die Regel schuetzte vor nichts. */
/* ---- Die zweite GROESSE des Umschalters ----------------------------------------------------
   is-lg ist der Umschalter, der nicht als Werkzeug in einer Kopfzeile steht, sondern allein ueber
   dem Inhalt und die Ansicht der ganzen Seite waehlt (brand-detail, domain-detail).
   Die Groesse bestimmt den GRUND nicht mehr: seit dem 22.09. tragen kleine wie grosse
   Umschalter --vc-switch-rail, und der steht in der Grundregel von .up-seg weiter oben. Hier
   bleibt nur, was die Groesse wirklich aendert -- die Hoehe. */
.up-seg.is-lg, .cc-seg.is-lg, .ubo-seg.is-lg, .tcl-seg.is-lg,
/* Der Umschalter im Topics-Dropdown (Match-Modus), der keine eigene Klasse hat und deshalb an
   seiner Beschriftung gefasst wird -- aria-label steht so in seinem Markup. Er steht in einem
   Element ausserhalb dieser Bibliothek, traegt aber ihre Klassen: ohne is-lg bekam er die
   kleine Form, obwohl er in einem Dropdown genauso 32 sein soll wie ueberall sonst. Die
   Umschalter von Models und Markets standen hier mit; die gibt es seit dem 28.09. nicht mehr. */
.up-seg[aria-label="Match mode"],
/* Domains/URLs in Top Citations ersetzt dort die Ueberschrift und war schon als Nachbau 32 hoch,
   nur mit 12px Polster statt 16 -- ohne Begruendung, also Drift. Kein is-lg im Markup (Bubble),
   deshalb an seiner Wurzel gefasst: zwei Klassen schlagen die eine der Grundregel. */
.tcd-root .tcd-mode {
  height: 32px;
}
.up-seg.is-lg .up-seg-btn, .cc-seg.is-lg .cc-seg-btn,
.ubo-seg.is-lg .ubo-seg-btn, .tcl-seg.is-lg .tcl-seg-btn,
.up-seg[aria-label="Match mode"] .up-seg-btn,
.tcd-root .tcd-mode-btn { height: 32px; padding: 0 16px; }

/* gap + type spec: this control was lifted from an ICON-ONLY switcher, so a button carrying an icon
   AND a label had its two children touching, and its text fell back to whatever the page inherited.
   Both are set here rather than per component -- gap is a no-op on the single-child icon variants
   that already use this, and the type matches .vc-gran-btn, the app's other labelled switcher
   (STYLEGUIDE §2: switcher label 12px/500). */
/* Die Knoepfe der vier Aliase vom 27.09. (siehe die Grundregel oben) stehen in jeder dieser Regeln
   mit: .ubo-yaxis-btn, .tcd-mode-btn, .upt-status-btn, .udt-sub-disp-btn. */
.up-seg-btn, .cc-seg-btn, .ubo-seg-btn, .tcl-seg-btn,
.ubo-yaxis-btn, .tcd-mode-btn, .upt-status-btn, .udt-sub-disp-btn { height: 26px; padding: 0 8px; border: 1px solid transparent; border-radius: var(--up-r-m); background: transparent;
  color: var(--vc-muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  gap: 7px; font-family: inherit; font-size: 12px; font-weight: 500; line-height: 1; }
.up-seg-btn svg, .cc-seg-btn svg, .ubo-seg-btn svg, .tcl-seg-btn svg,
.ubo-yaxis-btn svg, .tcd-mode-btn svg, .upt-status-btn svg, .udt-sub-disp-btn svg { width: 14px; height: 14px; color: var(--vc-muted); display: block; }
/* HIER STAND EINE DREHUNG UM 90 GRAD, und sie ist am 23.09. entfallen. Sie kam am 22.09. auf
   Wunsch dazu, weil das Zeichen damals senkrechte Saeulen zeigte, der Umschalter aber eine
   liegende Rangliste meint. Seit das Zeichen ChartBarBig ist, LIEGT es schon -- die Drehung
   stellte es also wieder auf und spiegelte es dabei sichtbar.
   Das hat vier Runden gekostet, in denen ich das ZEICHEN gewechselt habe, waehrend die Lage
   aus der CSS kam. Wer die Lage des Balken-Zeichens aendern will, aendert ab jetzt das Zeichen
   (ChartBarBig liegend, ChartColumnBig stehend) und nicht die Darstellung. */
.up-seg-btn:hover, .up-seg-btn:hover svg,
.cc-seg-btn:hover, .cc-seg-btn:hover svg,
.ubo-seg-btn:hover, .ubo-seg-btn:hover svg,
.tcl-seg-btn:hover, .tcl-seg-btn:hover svg,
.ubo-yaxis-btn:hover, .tcd-mode-btn:hover, .upt-status-btn:hover, .udt-sub-disp-btn:hover { color: var(--vc-text); }
/* Die drei Aliase sind KEIN Stil-Duplikat, sondern der Verzicht auf eine Umbenennung: das
   Markup dieser Switcher steht in der vom Nutzer eingefuegten Bubble-Seite, nicht in einer Datei
   die ein CDN-Pin erreicht. Ein Rename hier haette deren Seiten zerlegt -- citations-combo-chart
   bricht sogar seine ganze Initialisierung ab, wenn es seine Buttons nicht findet. */
.up-seg-btn.is-active, .cc-seg-btn.is-active, .ubo-seg-btn.is-active, .tcl-seg-btn.is-active,
.ubo-yaxis-btn.is-active, .tcd-mode-btn.is-active, .upt-status-btn.is-active, .udt-sub-disp-btn.is-active { background: var(--vc-switch-pill); color: var(--vc-text); border-color: var(--vc-switch-border); }
.up-seg-btn.is-active svg, .cc-seg-btn.is-active svg, .ubo-seg-btn.is-active svg, .tcl-seg-btn.is-active svg { color: var(--vc-text); }
[data-theme="dark"] .up-seg-btn.is-active,
[data-theme="dark"] .cc-seg-btn.is-active,
[data-theme="dark"] .ubo-seg-btn.is-active,
[data-theme="dark"] .tcl-seg-btn.is-active,
[data-theme="dark"] .ubo-yaxis-btn.is-active, [data-theme="dark"] .tcd-mode-btn.is-active,
[data-theme="dark"] .upt-status-btn.is-active, [data-theme="dark"] .udt-sub-disp-btn.is-active { background: var(--vc-switch-pill); border-color: var(--vc-switch-border); }

/* ---------- DER THEMENSCHALTER NACH GEIST (15.10., UC.themeSchalterHtml) ----------
   "Bitte den Theme Switcher uebernehmen" -- Geists Theme Switcher (vercel.com/geist/theme-switcher),
   Mass fuer Mass aus seinem Markup gelesen: ein Streifen, voll gerundet, drei runde Knoepfe System /
   Light / Dark nur mit Zeichen (16px), aussen ein 1px-Ring statt einer Schiene; der gewaehlte Knopf
   eine helle Flaeche mit eigenem 1px-Ring (Gray 400) und einem Hauch Schatten (0 1px 2px). Zwei
   Groessen wie dort: 32 (Einstellungsseite, "use the default size on a settings page") und 24
   ("small for dense chrome ... dropdowns") -- hier --up-h-btn und --up-h-chip.
   EIN .up-seg UND KEIN NACHBAU: der Streifen ist der gleitende Umschalter aus core (segLauf misst
   .is-active, die Flaeche ist das ::before). Hier steht nur, was Geist anders macht als die
   Grundform: rund statt Radius 8, keine Schiene, die Ringe aussen. Alles ueber .up-root davor --
   die Grundregeln stehen eine Spezifitaetsstufe tiefer, entschieden wird nicht ueber die Reihenfolge.
   DIE RINGE LIEGEN AUSSEN (box-shadow), wie bei Geist: so fallen Ring des Streifens und Ring der
   Flaeche oben und unten auf dieselbe Linie, statt nebeneinander zwei Pixel zu zeichnen.
   Farben aus den Marken der Umschalter: beide Ringe --vc-switch-border, die Flaeche --vc-switch-pill --
   im grauen Hell #ebebeb und #ffffff, also genau Geists Gray 400 und Background 100. Nicht
   --vc-border fuer den aeusseren Ring: das Einstellungsfenster setzt es auf seine dunklere
   Fensterlinie (#dcdbdd), und dann stuenden zwei verschiedene Ringe uebereinander. */
/* DERSELBE SCHALTER IM SEITENKOPF (15.10. spaet, UC.bandSchalterHtml): der Umschalter des Bandes
   mit zwei Feldern, Chart und Tabelle / nur Tabelle ("Switcher nur mit 2 Icons, aber entspanntem
   Padding"). Er steht in JEDER Regel hier mit drin und hat keine eigene -- zwei Schalter dieser
   Form, die auseinanderlaufen koennen, waeren ein Nachbau (CLAUDE.md 1). :is() mit zwei Klassen
   haelt die Spezifitaet bei einer Klasse, also genau dort, wo sie vorher stand. 32 hoch wie die
   Knoepfe des Seitenkopfs (.up-ph-iconbtn, .up-ph-addbtn). */
.up-root .up-seg:is(.up-themesw, .up-bandsw) { height: var(--up-h-btn); padding: 0; gap: 0; border-radius: var(--up-sw-r, var(--up-r-m));
  background: transparent; box-shadow: 0 0 0 1px var(--vc-switch-border); }
.up-root .up-seg:is(.up-themesw, .up-bandsw).is-klein { height: var(--up-h-chip); }
.up-root .up-seg:is(.up-themesw, .up-bandsw)::before { border: 0; border-radius: var(--up-sw-r, var(--up-r-m));
  box-shadow: 0 0 0 1px var(--vc-switch-border), var(--up-e-1); }
/* MEHR LUFT IN DEN DREI FELDERN (15.10. spaet: "den Theme Switcher besser stylen, mehr Padding in den
   3 Bereichen"): die Knoepfe sind nicht mehr rund (Breite = Hoehe), sondern Kapseln mit Polster
   12 bzw. 8 neben dem 16er-Zeichen -- 40 x 32 und 32 x 24. Die gleitende Flaeche misst den Knopf
   (segLauf) und wird damit von selbst zur Kapsel. */
.up-root .up-seg:is(.up-themesw, .up-bandsw) .up-seg-btn { width: auto; height: var(--up-h-btn); padding: 0 12px;
  border: 0; border-radius: var(--up-sw-r, var(--up-r-m)); color: var(--vc-third); }
.up-root .up-seg:is(.up-themesw, .up-bandsw).is-klein .up-seg-btn { width: auto; height: var(--up-h-chip); padding: 0 8px; }
.up-root .up-seg:is(.up-themesw, .up-bandsw) .up-seg-btn svg { width: 16px; height: 16px; color: inherit; }
.up-root .up-seg:is(.up-themesw, .up-bandsw) .up-seg-btn:hover,
.up-root .up-seg:is(.up-themesw, .up-bandsw) .up-seg-btn.is-active { color: var(--vc-text); }
/* Bevor core gemessen hat (oder wo der Streifen abgeschaltet ist), traegt der gewaehlte Knopf die
   Flaeche selbst -- dieselbe wie das ::before. Danach gibt er sie ab (.is-gleitend > button). */
.up-root .up-seg:is(.up-themesw, .up-bandsw):not(.is-gleitend) .up-seg-btn.is-active {
  background: var(--vc-switch-pill); box-shadow: 0 0 0 1px var(--vc-switch-border), var(--up-e-1); }
.up-root .up-seg:is(.up-themesw, .up-bandsw) .up-seg-btn:focus-visible { outline: 0;
  box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); }

/* ---------- "X mentioned?" yes/no cell ----------
   Shared by urls-table and responses-table (both had their own byte-identical copy under a local
   prefix, which is how they drifted apart in the first place). A FILLED 14px badge carries the
   colour, the glyph inside is knocked out of it, and the label stays in the primary text colour —
   a coloured label next to a coloured icon read as two competing signals. */
/* ---- Marken-Schalter ("Brand X mentioned?") ---------------------------------------------------
   Dreistufig: aus, ja, nein. Lag als Kopie in urls-table.css; zweiter Verbraucher (response-detail),
   also hierher -- die alten .uut-*-Klassen bleiben als Alias, damit dort kein Markup anzufassen ist.
   Werte unveraendert: 32px hoch wie jeder Kopfzeilen-Knopf, Radius 8, Kaestchen 16px mit Radius 5. */
.up-brandtoggle, .uut-brand-toggle { display: none; align-items: center; justify-content: space-between;
  gap: 10px; height: 32px; padding: 0 8px; border-radius: var(--up-r-m); background: transparent; border: 0;
  cursor: pointer; flex: 0 0 auto; font-family: inherit; }
.up-brandtoggle.is-visible, .uut-brand-toggle.is-visible { display: inline-flex; }
.up-brandtoggle:hover, .uut-brand-toggle:hover { background: var(--vc-heading-bg); color: var(--vc-text); }
.up-brandtoggle.is-yes, .up-brandtoggle.is-no,
.uut-brand-toggle.is-yes, .uut-brand-toggle.is-no { background: var(--vc-heading-bg); }
.up-brandtoggle-lbl, .uut-brand-toggle-lbl { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.up-brandcheck, .uut-brand-check { width: 16px; height: 16px; border-radius: var(--up-r-xs);
  border: 1.5px solid var(--vc-border); display: inline-flex; align-items: center;
  justify-content: center; flex: 0 0 auto; }
.up-brandcheck svg, .uut-brand-check svg { width: 11px; height: 11px; display: none; }
/* DAS ZEICHEN AUF DER AKZENTFLAECHE IST --up-accent-fg, und nur das (18.09.). Sichtbar ist
   ohnehin nur eines der beiden svg, und nur in is-yes/is-no -- dort liegt es auf --up-accent,
   und welche Farbe darauf lesbar ist, entscheidet die Akzentwahl, nicht das Thema. Hier stand
   --up-surface: das passte, solange der Akzent die Schriftfarbe des Themas war, und wird mit
   Indigo oder Azur im Dunkeln zu dunkel auf dunkel. */
.up-brandcheck, .uut-brand-check { color: var(--up-accent-fg); }
.up-brandtoggle.is-yes .up-brandcheck, .up-brandtoggle.is-no .up-brandcheck,
.uut-brand-toggle.is-yes .uut-brand-check, .uut-brand-toggle.is-no .uut-brand-check {
  background: var(--up-accent); border-color: var(--up-accent); }
/* Die dunkle Sonderregel darunter faerbt NICHT mehr das Zeichen -- das macht --up-accent-fg
   oben, in beiden Themen. Sie bleibt fuer alles andere, was hier im Dunkeln abweicht. */
.up-root[data-theme="dark"] .up-brandtoggle.is-yes .up-brandcheck,
.up-root[data-theme="dark"] .up-brandtoggle.is-no .up-brandcheck,
.up-root[data-theme="dark"] .uut-brand-toggle.is-yes .uut-brand-check,
.up-root[data-theme="dark"] .uut-brand-toggle.is-no .uut-brand-check {
  color: var(--up-surface, #1c1c1f); }
.up-brandtoggle.is-yes .up-brandcheck .up-brandcheck-yes,
.uut-brand-toggle.is-yes .uut-brand-check .uut-brand-check-yes { display: block; }
.up-brandtoggle.is-no .up-brandcheck .up-brandcheck-no,
.uut-brand-toggle.is-no .uut-brand-check .uut-brand-check-no { display: block; }
.up-brandlogo, .uut-brand-logo { width: 16px; height: 16px; border-radius: var(--up-r-xs); flex: 0 0 auto;
  display: block; object-fit: cover; }
.up-brandlabel, .uut-brand-label { font-size: 12px; font-weight: 500; color: var(--vc-text);
  white-space: nowrap; }

/* ---- Entitaeten-Chip -------------------------------------------------------------------------
   Ein anklickbarer Chip mit Logo und Name. Es gab in core das Logo-Name-Paar (.up-ment-logo /
   .up-ment-name) und den Themen-Chip (.up-topicchip) -- aber keinen NEUTRALEN Chip: der Themen-Chip
   traegt die Farbe seines Themas als Hintergrund, was fuer eine Marke falsch waere.
   Die Geometrie ist die des Themen-Chips (28px, Radius 8, gap 6..8), damit ein Chip mit Logo und
   einer mit Emoji in derselben Zeile gleich hoch stehen. Links weniger Polsterung als rechts, weil
   das Logo dort schon Rand mitbringt. Hover kommt aus .up-chiphover -- dieselbe Klasse, die die
   Themen-Chips tragen. */
.up-entchip { height: 28px; padding: 0 10px 0 5px; border-radius: var(--up-r-m); display: inline-flex;
  align-items: center; gap: 8px; cursor: pointer; font-family: inherit;
  border: 1px solid var(--vc-border); background: var(--vc-bg);
  white-space: nowrap; overflow: hidden; min-width: 0; max-width: 100%; box-sizing: border-box; }
.up-entchip.is-static { cursor: default; }
/* is-soft: derselbe Chip mit leichtem Grund statt weissem. Der Rahmen BLEIBT im hellen Thema --
   dort ist der Grund so blass, dass ohne Rahmen kein Chip mehr zu sehen ist, nur ein Schatten von
   einem. Im Dunkeln faellt er weg: da ist der Grund heller als seine Umgebung und traegt die Form
   allein. Hover verdichtet den Grund. */
.up-entchip.is-soft { background: var(--vc-heading-bg); }
.up-root[data-theme="dark"] .up-entchip.is-soft { border-color: transparent; }
/* Nur dort, wo der Chip NICHT den geteilten Hover traegt: .up-entchip.is-soft:hover ist
   spezifischer als .up-chiphover:hover und hat ihn geschlagen -- ein Chip mit beiden Klassen zeigte
   den Hover des Chips und nicht den der App. */
.up-entchip.is-soft:not(.up-chiphover):hover { background: var(--vt-row-hover-solid, var(--vc-switch-bg)); }
.up-entchip:focus-visible { outline: 2px solid var(--vc-text); outline-offset: 1px; }
/* SPALTEN ALS CHIPS (15.10., UC.makeColumns cfg.form "chips", Display-Menue der Prompt-Tabelle nach
   Entwurf B): derselbe Chip, nur ohne Zeichen -- darum gleiches Polster links und rechts -- und als
   Schalter. An = is-soft (leichter Grund, wie oben), aus = gestrichelter Rand ohne Grund in der
   vierten Schriftfarbe: "gibt es, ist aber nicht zu sehen". Hoehe, Radius und Rand bleiben die des
   Chips. */
/* 10 seitlich wie jede Zeile eines Dropdowns (.up-pop-row) -- die Chips stehen auf der Textkante. */
.up-spaltchips { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 10px 4px; }
.up-entchip.up-spaltchip { padding: 0 10px; font-size: var(--up-fs-s); font-weight: var(--up-fw-m); color: var(--vc-text); }
.up-entchip.up-spaltchip.is-aus { border-style: dashed; background: transparent; color: var(--vc-fourth); }
.up-entchip.up-spaltchip.is-aus:hover { color: var(--vc-text); border-color: var(--vc-hover-border); }
.up-entchip.up-spaltchip.is-locked { cursor: default; }
/* Eine Zeile "Name ........ Bedienelement" in einem Dropdown (Row height, Grouping im Display-Menue). */
.up-pop-feld { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; min-height: 40px; padding: 0 10px; }
/* Der Kopf ueber Feldern und Chips steht auf derselben Kante wie sie (sonst 0 seitlich). */
.up-pop-head.up-pop-head-feld { padding-left: 10px; padding-right: 10px; }
/* Der Segmentstreifen darin steht RECHTS neben einem Namen, also so breit wie sein Inhalt statt
   der ganzen Zeile, und 28 hoch (--up-h-seg) statt 34 -- Schiene UND Knoepfe zusammen, sonst
   stoesst die Pille an den Rand. Radius 8 bleibt. */
.up-pop-feld .up-dense { width: auto; flex: 0 0 auto; height: var(--up-h-seg); }
.up-pop-feld .up-dense-btn { flex: 0 0 auto; height: var(--up-h-seg); padding: 0 10px; }

/* ---- KEIN HINEINZOOMEN BEIM FOKUS AUF DEM TELEFON ----------------------------------------
   iOS-Safari zoomt die Seite, sobald ein Feld mit einer Schrift UNTER 16px den Fokus bekommt.
   Das ist kein Fehler, sondern eine Lesehilfe -- nur passt sie zu unseren Komponenten nicht:
   eine Tabelle oder ein Chart, in das hineingezoomt wird, ist danach unbedienbar, und der
   Nutzer muss von Hand zurueckzoomen. Die einzige Abhilfe, die OHNE Eingriff in das
   viewport-Meta auskommt, ist die Schriftgroesse selbst: ab 16px zoomt iOS nicht.

   DER PREIS, und er gehoert hierhin: auf dem Telefon steht in JEDEM Eingabefeld 16px statt
   der 12 bis 14, die am Schreibtisch stehen. Die Felder werden dort also etwas hoeher. Der
   Weg ueber user-scalable=no im Meta waere unsichtbar, nimmt aber der ganzen Seite das
   Zoomen -- auch dem, der es zum Lesen braucht. Deshalb dieser.

   Die Spezifitaet ist Absicht: eine nackte input-Regel (0,0,1) verliert gegen jede
   Komponentenregel wie .up-search-input (0,1,0). :not([type]) hebt sie auf 0,2,1 und schlaegt
   damit auch die zweiklassigen Regeln. Kein !important -- CLAUDE.md 1.
   Gemessen wird das, nicht angenommen: der Prueftand prueft JEDES Feld im Baum. */
@media (hover: none) and (pointer: coarse) {
  input:not([type="checkbox"]):not([type="radio"]),
  textarea:not([readonly]),
  select:not([multiple]) { font-size: 16px; }
}

/* ---- is-lifted: der Chip, wie ihn die Palette traegt -------------------------------------
   Die Werte sind die von .mqa-chip in quick-actions.css, Wert fuer Wert: Radius 7.6, gap 6.5,
   Polsterung 0 6.5 0 9.75, halbdurchsichtiger Grund und ZWEI Schattenlagen. Die zwei Lagen
   sind Absicht und keine Verzierung -- eine Lage kann nur eines von beiden: mit 3px Streuung
   ist die Kante weich, mit 1px fehlt das Abheben. Zusammen 2px Gesamtausdehnung.
   Im Dunkeln beide kraeftiger (24 und 16 statt 13 und 7 Prozent), weil ein Schatten auf
   dunklem Grund weniger hergibt. Alle vier Deckkraefte am 09.09. um 40 Prozent zurueck
   (22/12 und 40/26 mal 0.6) -- der Chip soll sich abheben und nicht schweben.
   WARUM HIER UND NICHT NUR DORT: der Chip ist derselbe Gedanke an zwei Orten -- die Palette
   und der Picker in Ask Mira zeigen beide Brand, Prompt, Domain, URL. Die Palette BEHAELT
   ihre eigene Geometrie, weil ihr Bubble-Loader nur quick-actions.css zieht und core dort nicht
   im Baum ist; dieselbe Begruendung wie bei ihren Farbpaletten. Diese Datei ist die Quelle.
   DER SCHATTEN IST SEIT DEM 28.09. EIN TOKEN (--up-e-chip, oben bei den Tiefen): die vier Zahlen
   stehen nur noch dort, die dunkle Fassung kommt mit dem Thema. quick-actions.css liest dasselbe
   Token und traegt die alten Werte nur noch als Rueckfall fuer Seiten ohne core. */
.up-entchip.is-lifted {
  height: 26px; gap: 6.5px; padding: 0 6.5px 0 9.75px; border-radius: var(--up-r-m);
  font-size: 12.5px; font-weight: 500;
  background: color-mix(in srgb, var(--vc-heading-bg) 50%, transparent);
  box-shadow: var(--up-e-chip);
}
/* ---- Feld-Chip: ein gewaehlter Bezug IM Eingabefeld (14.10.) ------------------------------
   Der Chip aus Ask Mira (.am-pick-tag), als Baustein fuer jedes Feld, das Chips aufnimmt --
   zuerst Prompt Research ("genau so wie die Item-Chips, die wir bei Mira haben"). Grund, Rahmen,
   Radius und Schatten kommen aus .up-entchip.is-lifted, die Masse sind die von Mira, Wert fuer
   Wert: 28px hoch, 7 Abstand, links 5 und rechts 4 Polster, eine 18er-Kachel links, ein 19er-
   Kreuz rechts. Drei Klassen im Selektor, weil .up-entchip.is-lifted zwei hat -- Konflikt ueber
   Spezifitaet, nicht ueber die Ladereihenfolge.
   Die Kachel traegt, was der Bezug IST: bei einer Topic ihr Emoji oder ihren Farbstrich (dieselbe
   Marke wie .up-topicchip, 4x10, Radius 2), beim Intent sein Zeichen. Radius 5 = 6 minus 1
   (konzentrisch in der 7.6er-Ecke des Chips mit 5 Polster, gerundet). */
.up-feldchip.up-entchip.is-lifted { height: 28px; gap: 7px; padding: 0 4px 0 5px; color: var(--vc-text); vertical-align: middle; }
/* Der Chip IM Feld (20.10.): weiss wie das Feld (--up-feld-bg, im Dunkeln dessen dunkler Ton) und der kleinste Schatten der Skala (--up-e-1) statt des gehobenen Chip-Schattens
   --up-e-chip: er liegt auf einem Feld, nicht ueber der Seite. Gilt fuer Prompt Research und Ask Mira (.am-pick-tag). */
.up-feldchip.up-entchip.is-lifted { background: var(--up-feld-bg); box-shadow: var(--up-e-1); }
.up-feldchip-av { width: 18px; height: 18px; flex: 0 0 auto; border-radius: var(--up-r-xs); overflow: hidden;
  background: var(--vc-heading-bg); display: flex; align-items: center; justify-content: center; color: var(--vc-third); }
.up-feldchip-av svg { width: 12px; height: 12px; display: block; }
.up-feldchip-av .up-topicchip-e { font-size: 12px; line-height: 1; }
/* Der Farbstrich der Topic steht in der Regel von .up-topicchip::before mit (dieselbe Marke,
   dieselbe Begruendung fuer Radius 2 dort). */
.up-feldchip-lbl { min-width: 40px; max-width: 320px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.up-feldchip-x { width: 19px; height: 19px; flex: 0 0 auto; border: 0; border-radius: var(--up-r-s); padding: 0;
  background: transparent; color: var(--vc-muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: background var(--up-t-2) ease, color var(--up-t-2) ease; }
.up-feldchip-x svg { width: 12px; height: 12px; display: block; }
.up-feldchip-x:hover { color: var(--vc-text); background: color-mix(in srgb, var(--vc-text) 6%, transparent); }

/* Die kleine Fassung (.mqa-mini): 22px statt 26, Radius 6.4. Der Schatten ist derselbe wie beim
   grossen Chip -- die Begruendung steht bei --up-e-chip. */
.up-entchip.is-lifted.is-sm {
  height: 22px; gap: 5.5px; padding: 0 5.5px 0 8.25px; border-radius: var(--up-r-s); font-size: 11px;
}

/* ---- Mention-Liste ---------------------------------------------------------------------------
   Die Aufzaehlung der Marken, die irgendwo vorkommen. Lag als lokaler Block in response-detail;
   url-detail braucht denselben Chip, also gehoert er hierher -- zwei Kopien sind genau der Weg,
   auf dem sie auseinanderlaufen.

   ALLE Masse haengen an --up-mc, der Hoehe. Die Verhaeltnisse sind die des Grundchips
   .up-entchip (28px): Polsterung 10/5, Radius 8, Innenabstand 8, Logo 18, Bild 16, Buchstabe 10.
   Bei --up-mc: 32px ergibt das Zeichen fuer Zeichen die Zahlen, die in response-detail standen
   (11.43 / 5.71 / 9.14 / 20.57 / 18.29 / 11.43) -- der Block dort ist damit nicht geaendert,
   sondern nur umgezogen.
   Der NAME faellt bewusst aus dem Verhaeltnis: 0.45 der Hoehe statt 13.5/28 = 0.48. Er stand
   sonst zu gross neben seinem Logo, und eine Schrift fast so hoch wie das Bild daneben laesst
   den Chip nach Ueberschrift aussehen statt nach Aufzaehlung.
   is-lg ist dieselbe Liste eine Stufe groesser -- eine Zahl aendert sich, alles andere zieht mit. */
.up-mentlist { --up-mc: 32px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.up-mentlist.is-lg { --up-mc: 40px; }
/* Drei Selektorteile, weil .up-entchip.is-soft oben zwei hat und sonst gewinnt. */
.up-mentlist .up-entchip.is-soft {
  height: var(--up-mc);
  padding: 0 calc(var(--up-mc) * 0.3571) 0 calc(var(--up-mc) * 0.1786);
  border-radius: calc(var(--up-mc) * 0.2857);
  gap: calc(var(--up-mc) * 0.2857);
  max-width: 100%;
}
/* Grund und Rahmen NUR in Ruhe. Standen sie in der Regel darueber, schlugen sie mit ihren drei
   Klassen den geteilten Hover aus core (.up-chiphover:hover, zwei) -- der Chip haette dann gar
   keinen Hover. Mit :not(:hover) gilt hier die Ruhe und oben der Hover.
   Hell: KEIN Grund, nur der Rahmen, den .up-entchip ohnehin mitbringt. */
.up-mentlist .up-entchip.is-soft:not(:hover) { background: transparent; }
/* Dunkel: dort ist der Grund heller als seine Umgebung und traegt die Form allein -- ein Rahmen
   je Marke waere zu viel, ein rahmenloser Chip ohne Grund waere nichts mehr. */
.up-root[data-theme="dark"] .up-mentlist .up-entchip.is-soft:not(:hover) { background: var(--vc-field); }
.up-mentlist .up-ment-logo {
  width: calc(var(--up-mc) * 0.6429); height: calc(var(--up-mc) * 0.6429);
  border-radius: calc(var(--up-mc) * 0.1786);
}
.up-mentlist .up-ment-logo img {
  width: calc(var(--up-mc) * 0.5714); height: calc(var(--up-mc) * 0.5714);
  border-radius: calc(var(--up-mc) * 0.1429);
}
.up-mentlist .up-model-ltr { font-size: calc(var(--up-mc) * 0.3571); }
.up-mentlist .up-ment-name { font-size: calc(var(--up-mc) * 0.45); }

/* ---- Modell-Chip / Market-Chip ----------------------------------------------------------------
   Beide lagen als Kopie in responses-table bzw. prompts-table. Die alten Klassen bleiben als
   Alias erhalten, damit dort kein Markup angefasst werden muss. Die Werte sind unveraendert die
   der Vorbilder: 18px Logokachel aus .up-ment-logo, Flagge 18x14 mit Radius 3. */
.up-model-chip, .urt-model-chip { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.up-model-ltr, .urt-model-ltr { font-size: 10px; font-weight: 600; color: var(--vt-logo-ink, var(--vc-muted)); line-height: 1;
  text-transform: uppercase; }
.up-ment-logo.has-img .up-model-ltr, .up-ment-logo.has-img .urt-model-ltr { display: none; }
/* Im Dunkeln traegt die Kachel keinen Ton: die Modell-Logos sind dort selbst hell. */
.up-root[data-theme="dark"] .up-model-chip .up-ment-logo { background: transparent; padding: 0; }
/* Ohne Platte steht der Buchstabe auf der Karte -- dort die Schrift der Karte und nicht die Tinte
   der hellen Platte (--vt-logo-ink), die gilt nur, wo die Platte liegt: in den Markenchips von
   response-detail und url-detail (28.09., dort war der Buchstabe im Dunkeln unsichtbar). */
.up-root[data-theme="dark"] .up-model-chip .up-model-ltr { color: var(--vc-muted); }

.up-market, .upt-market { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.up-flag, .upt-flag { width: 18px; height: 14px; border-radius: 3px; overflow: hidden; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center; line-height: 0;
  background: var(--vc-heading-bg); }
.up-flag img, .upt-flag img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Der Code ist eine Beschriftung der Flagge, kein eigener Wert -- also eine Stufe zurueck. */
.up-market-code, .upt-market-code { font-size: 12px; font-weight: 600; color: var(--vc-muted);
  letter-spacing: 0.02em; white-space: nowrap; }

.up-ment-cell { display: inline-flex; align-items: center; gap: 8px; font-size: 13.5px; font-weight: 500; color: var(--vc-muted); }
.up-ment-badge { width: 14px; height: 14px; border-radius: var(--up-r-xs); flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center; }
/* The glyph is the SURFACE colour, not a hardcoded white: on dark the badge is a light tint and
   a white glyph would vanish into it. 8px, not 9: a 14px badge minus a 9px glyph splits into a
   fractional 2.5px pad on each side, which different rows can round to 2px-left/3px-right or the
   reverse depending on the badge's own sub-pixel screen position — reads as the glyph drifting
   left/right row to row even though the flexbox centering math is exact. 8px divides the 14px
   badge into a flat, unambiguous 3px on both sides every time. */
.up-ment-badge svg { width: 8px; height: 8px; display: block; color: var(--up-surface, #fff); }
.up-ment-cell.is-yes .up-ment-badge { background: var(--vt-up); }
.up-ment-cell.is-no  .up-ment-badge { background: var(--vt-down); }
.up-root[data-theme="dark"] .up-ment-badge svg { color: var(--vc-text-invert); }

/* ---------- "<brand logo> mentioned?" column header ----------
   urls-table established this; it lives here so every table with the column gets the same thing. */
/* 17 statt 16 seit der Kopfschrift 14 (15.10. spaet, 16 x 14/13 = 17.2); der Radius 4.3 rundet auf 4.
   Ohne eigenen Aussenabstand: bis 15.10. trug das Logo 5px UND den Abstand der Zelle (6), also 11
   bis zum Wort, wo vor "Visibility" in der Prompt-Tabelle 6 stehen. Die flache Tabelle hatte das
   schon abgestellt ("die 5px fehlten der breiteren Kopfschrift"); seit jeder Kopf dieselbe Schrift
   traegt, gilt es fuer jeden -- sonst stuende das Logo im Drawer anders als auf der Seite. */
.up-th-brandlogo { width: 17px; height: 17px; border-radius: var(--up-r-xs); flex: 0 0 auto; object-fit: cover; }

/* ---------- brand-mention chip stack ----------
   Overlapping 28px logo plates (rounded squares, see below). Shared by urls-table's Brands mentioned column and prompts-table's
   Brand Mentions column. The 4px visual gap between them is a ring in the ROW's background
   colour, not a real gap: with a negative margin the neighbour would otherwise touch. The ring
   has to follow the row's hover colour too, hence the override below.
   No size/margin transition: with up to 100 rows × 4 logos on screen, animating those on the
   dense-mode toggle forces simultaneous layout reflow across hundreds of elements and visibly
   stalls the page for a couple of seconds (see .up-row for the same reasoning). The hover
   lift/spread stays on .up-stack-vis's transform, which is cheap and only ever touches one
   row's stack at a time. */
/* ---- DER STAPEL IST WIEDER EINE REIHE VON LOGOPLATTEN (16.10.) ----
   Am 15.10. war der Stapel eine Geist-AvatarGroup geworden: erst Ueberlappung 8 und Ring 1 mit dem
   "+N" als Kreis (b6f2d5b), dann jeder Chip ein Kreis (4c5e41c), zuletzt 32er Kreise mit Ring 3 und
   Ueberlappung 5 (0d4c322). Am 16.10. zurueckgenommen (Nutzer: "Lass uns das bitte wieder
   rueckgaengig machen, also einfach altes Design. Die eckigen."). Es gilt wieder der Stand davor:
     Chip          28 (die 28er Stufe der Hoehen-Skala, --up-h-seg), ein abgerundetes Quadrat --
                   ein Logo ist eine Logoplatte und kein Portraet, und Platten tragen in core
                   --up-r-m (.up-logo-box, die Zeile in Quick Actions).
     Ueberlappung  4, der Ring 4 in der Zeilenfarbe -- der Ring ist der ABSTAND zum Nachbarn.
     Reihenfolge   der SPAETERE Chip liegt oben (Reihenfolge im Dokument, kein z-index je Chip).
     "+N"          reiner Text 12/500 mit 8 Abstand, kein Kreis: die Zahl ist ein Etikett, keine
                   weitere Marke.
   AUF DER SKALA STATT DER ALTEN ZAHLEN (§1c, Radius nach Rolle seit 15.10.): Chip 6 statt 8 (28 hoch
   -> 6), das Bild konzentrisch 6 - 3 = 3 statt 5; dicht ebenfalls 6 und 3 statt 7.04 und 4.28 --
   0.88 x 6 = 5.3 liegt auf der Stufe 6, und ein Bruchteil im Radius rundet je Browser anders. Der
   Buchstabe 11 (--up-fs-xs) statt 10, dicht ebenfalls 11 statt 8.8 -- die Skala beginnt bei 11, und
   .up-logo-ltr traegt in der 24er Platte dieselbe 11.
   Von der Geist-Fassung bleiben die drei Marken unten (EIN Ort fuer Groesse, Ueberlappung und Ring,
   der dichte Modus rechnet sie mit 0.88 um) und isolation am Stapel: der angefasste Chip (z-index 3)
   steigt damit nicht ueber eine klebende erste Spalte. Der Ring in der Hoverfarbe einer
   klickbaren Karte (.up-karte-klick) bleibt ebenfalls -- die Karte toent sich seit dem 15.10. */
/* --up-stack-gr: die Chipgroesse als EINE Marke, weil Chip und das dichte Mass sie teilen. Aus der
   Hoehen-Skala, die ebenfalls an :root steht (Kopf dieser Datei) -- so loest sie auch fuer einen
   Stapel ausserhalb einer .up-root auf. --up-stack-ring-w liest auch der Einzug der Karte
   (.urt-card-citations in responses-table.css). */
:root { --up-stack-gr: var(--up-h-seg); --up-stack-ueber: 4px; --up-stack-ring-w: 4px; }
.up-stack { display: inline-flex; align-items: center; min-width: 0; max-width: 100%; isolation: isolate; }
.up-stack-item {
  width: var(--up-stack-gr); height: var(--up-stack-gr); flex: 0 0 var(--up-stack-gr);
  display: inline-flex; align-items: center; justify-content: center;
  position: relative; cursor: default;
}
.up-stack-item + .up-stack-item { margin-left: calc(var(--up-stack-ueber) * -1); }
/* Hit-testing lives on .up-stack-item, which never moves — only its inner .up-stack-vis animates
   on hover. With the outer box empty and static, neighbouring items' hover boundaries never move,
   so crossing from one logo to the next always fires a clean mouseout/mouseover pair — the
   border/background/ring live on .up-stack-vis instead, so the whole circle (not just the image
   inside it) lifts and shifts together on hover. */
/* padding, not flex-centering a smaller fixed-px child, is what actually guarantees the logo
   sits dead-center: 28px outer minus a 5px (odd) or 21px (also odd relative to 28) child left a
   fractional remainder that flexbox splits unevenly depending on the item's own sub-pixel screen
   position — the same class of drift already fixed once on the Mentioned-badge icon. Padding
   removes the split entirely: the image is sized 100%/100% of the PADDED content box (border-box
   is already the sitewide default, see .up-root *), so it always lands exactly 2px from every
   edge with nothing left over to round differently row to row. */
/* Der helle Rand ist EIN Pixel breit, und das ist dasselbe Mass wie an .up-logo-box in der
   Tabelle des Visibility Charts. Vorher waren es 2px Polsterung PLUS 1px Rahmen: im Dunkeln
   liegt die Platte (--vt-logo-bg, #e0e0e0) auf #1b1b1b, da ist jeder dieser drei Pixel zu sehen,
   und die Chips trugen einen weissen Ring, den dieselben Logos zwei Komponenten weiter nicht
   haben. Groesse (28), Ueberlappung (-4) und der 4px-Ring in Zeilenfarbe bleiben unberuehrt --
   der Ring ist der ABSTAND zum Nachbarn, nicht der Rand des Chips.
   Bildradius entsprechend 8 - 1 = 7 statt 8 - 2 = 6. */
/* Rahmen UND Polsterung: 1px Rahmen, dann 2px Platte, dann das Logo. Die Platte traegt die Farbe
   (--vt-logo-bg), der Rahmen die Kante -- im Hellen in --vc-border, im Dunkeln in der Plattenfarbe,
   wo er mit ihr verschmilzt und der Chip damit aussieht wie vorher, nur einen Pixel groesser
   umrandet. Eine Geometrie fuer beide Themen.
   Rechnung: 28px Chip - 2x1 Rahmen - 2x2 Polsterung = 22px Bild, Bildradius 8 - 3 = 5
   (seit 15.10. auf der Geist-Skala: Chip --up-r-m = 6, Bild 6 - 3 = 3).
   Der 4px-Ring aus box-shadow bleibt unberuehrt: das ist der ABSTAND zum Nachbarn, nicht die
   Kante des Chips. */
.up-stack-vis {
  display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 100%;
  border-radius: var(--up-r-m); padding: 2px; background: var(--vt-logo-bg); box-shadow: 0 0 0 var(--up-stack-ring-w) var(--vc-bg);
  border: 1px solid var(--vc-border);
  transition: transform .16s ease;
  /* Bezug fuer den Globus des Favicon-Rueckfalls (siehe .up-fav weiter oben). In Ruhe traegt die
     Sichtebene keine transform, ohne position waere der Bezug also der Kasten -- und der bewegt
     sich nicht. Eine Zeile, damit die Rechnung in beiden Zustaenden dieselbe ist. */
  position: relative;
}
/* Im Dunkeln kein Rahmen, und die helle Platte ist nur EINEN Pixel breiter als das Logo (27.09.
   gemeldet: "die Brand-Chips haben im Darkmode eine zu dicke helle Border -- wie das Favicon in
   der URL-Tabelle"). Dort sind es 22px Bild in einer 24px-Platte, also 1px Rand, und genau das
   steht auch als Regel in performance-detail.css und quick-actions.css ("der helle Rand um ein
   Logo ist ueberall in der App genau einen Pixel breit"). Hier waren es 2px -- die ganze
   Polsterung.
   Geloest ueber die MALFLAECHE, nicht ueber die Masse: die Platte endet an der Inhaltskante
   (background-clip: content-box, also genau unter dem Bild), und den einen Pixel Rand malt der
   Schatten des Bildes darunter in derselben Farbe. Rahmen, Polsterung und Bildgroesse bleiben
   unberuehrt -- in beiden Themen dieselbe Geometrie, und die Kacheln mit eigenem Mass
   (performance-detail ohne Rahmen, die kompakte Zeile) bekommen denselben 1px-Rand, ohne dass
   ihr Bild im Dunkeln schrumpft. Der Ring folgt dem Radius des Bildes, er ist also konzentrisch. */
.up-root[data-theme="dark"] .up-stack-vis { border-color: transparent; background-clip: content-box; }
.up-root[data-theme="dark"] .up-stack-item img { box-shadow: 0 0 0 1px var(--vt-logo-bg); }
/* DER VORGAENGER SCHIEN DURCH (28.09. gemeldet: "links vor jedem Chip ist noch ein schmaler
   Bereich des Chips davor sichtbar", nur im Dunkeln). Die Regel darueber macht Rahmen und
   Polsterung durchsichtig -- 3px je Seite --, und ein Chip ueberlappt seinen Vorgaenger um 4px.
   Der 4px-Ring (box-shadow) sollte die Ueberlappung zudecken, aber ein aeusserer Schatten malt nie
   IN seinen eigenen Kasten: in diesen 3px stand also der rechte Rand des Vorgaengers, seine Platte
   und sein heller 1px-Ring. Im Hellen ist die Platte deckend bis zur Kante, dort gibt es das nicht.
   Jetzt malt ein Folgechip diese 3px in der Farbe des Rings: die Platte bleibt eine eigene Schicht
   nur unter dem Logo (content-box), die Ringfarbe liegt darunter bis zur Rahmenkante. Der Chip
   sieht damit aus wie vorher -- 1px heller Rand, dann Abstand --, nur dass hinter dem Abstand
   nichts mehr durchscheint.
   Die Ringfarbe ist eine Marke (--up-stack-ring), weil sie den Ring der Zeile begleiten muss:
   beim Hover der Zeile, im dichten Modus und in der Leiste (sidebar.css) setzen die Regeln des
   Rings sie mit. Nur Folgechips: der erste liegt ueber nichts, und die einzelnen Kacheln in
   performance-detail (ohne Ring) bleiben durchsichtig wie am 27.09. bestellt.
   Vom 15. zum 16.10. lag mit der Geist-Gruppe der Vorgaenger oben, die Regel stand darum an
   :not(:last-child); seit der Rueckkehr liegt wieder der Nachfolger oben und malt die Flaeche. */
.up-root[data-theme="dark"] .up-stack-item + .up-stack-item .up-stack-vis {
  background: linear-gradient(var(--vt-logo-bg), var(--vt-logo-bg)) content-box,
              var(--up-stack-ring, var(--vc-bg));
}
/* only the gap-ring follows the row hover colour; the circle fill stays the (light-in-dark) logo bg.
   Die Karte (.urt-card) gehoert seit dem Kartenentwurf vom 13.10. NICHT hierher: mit dieser Regel
   bekam jeder Chip einer gehoverten Karte einen Ring in der Hover-Farbe der TABELLENZEILE --
   gemeldet als "Border um die Brand- und Citation-Chips". */
.up-row:hover .up-stack-vis { box-shadow: 0 0 0 var(--up-stack-ring-w) var(--vt-row-hover-solid, var(--vc-bg));
  --up-stack-ring: var(--vt-row-hover-solid, var(--vc-bg)); }
/* Seit dem 15.10. toent sich die klickbare Karte beim Hover doch wieder (Geists Hover, siehe
   .up-karte-klick) -- dann muss der Ring genau den Ton der Karte tragen, sonst stuende jetzt ein
   weisser Ring auf dem getoenten Grund, dasselbe Bild wie im Bericht oben, nur umgekehrt. Deckend
   gerechnet (die halbe Hover-Flaeche auf --vc-bg, dem Grund der Antwortkarte), weil der Folgechip
   im Dunkeln mit --up-stack-ring den Vorgaenger ZUDECKEN muss; ein durchscheinender Ton liesse ihn
   durch. Das "+N" ist seit dem 16.10. wieder Text ohne Ring. */
.up-karte-klick:is(:hover, .is-hl) .up-stack-vis {
  --up-stack-ring: color-mix(in srgb, var(--vc-bg) 50%, var(--up-knopf-hover));
  box-shadow: 0 0 0 var(--up-stack-ring-w) var(--up-stack-ring); }
/* Das Bild sitzt 3px innen (1 Rahmen + 2 Polster), also konzentrisch 6 - 3 = 3 (vor dem 15.10.
   8 - 3 = 5). Der Buchstabe 11 (--up-fs-xs): bis 15.10. stand hier 10, unter dem Anfang der Skala;
   .up-logo-ltr traegt in der 24er Platte ebenfalls 11. */
.up-stack-item img { width: 100%; height: 100%; border-radius: calc(var(--up-r-m) - 3px); object-fit: contain; display: block; }
.up-stack-ltr { font-size: var(--up-fs-xs); font-weight: 600; color: var(--vt-logo-ink, var(--vc-muted)); line-height: 1; text-transform: uppercase; }
.up-stack-item.has-img .up-stack-ltr { display: none; }
/* plain text, no circle: the overflow count is a label, not another brand.
   Vom 15. zum 16.10. ein Geist-Kreis in Chipgroesse; wieder zurueck (Kopf des Blocks). */
.up-stack-more {
  margin-left: 8px; flex: 0 0 auto;
  font-size: var(--up-fs-s); font-weight: var(--up-fw-m); color: var(--vc-muted); white-space: nowrap;
  transition: transform .16s ease;
}
/* Der Anhang hinter dem Stapel (brandStack opts.tailHtml). Dieselbe Uebergangszeit wie das "+N",
   damit beide zusammen gehen und nicht zwei Bewegungen zu sehen sind. Der Abstand kommt aus dem
   Fuss, der ihn vorher als eigenes Kind hatte -- 8px, wie die Luecke in .urt-card-footleft. */
.up-stack-tail {
  display: inline-flex; align-items: center; margin-left: 8px; flex: 0 0 auto;
  transition: transform .16s ease;
}
/* Hovering a logo lifts it, brings it to front, and opens a gap on both sides — created by
   shifting the hovered item's visual layer and everything AFTER it to the right. Items BEFORE
   it never move (so the first brand can't drift left). Following items shift twice as far so
   their gaps to each other stay at the default: they don't squash together.
   z-index 3 wie vor dem 15.10.; isolation am Stapel haelt ihn dort drin. */
.up-stack-item:hover { z-index: 3; }
.up-stack-item:hover .up-stack-vis { transform: translateX(8px) translateY(-3px); }
.up-stack-item:first-child:hover .up-stack-vis { transform: translateY(-3px); }
.up-stack-item:hover ~ .up-stack-item .up-stack-vis,
.up-stack-item:hover ~ .up-stack-more,
.up-stack-item:hover ~ .up-stack-tail { transform: translateX(16px); }
/* Ein ruhiger Stapel (brandStack opts.still, 19.10., Citation Tracking auf der Karte): kein Anheben, keine Luecke beim Hover. */
.up-stack.is-still .up-stack-item:hover { z-index: auto; }
.up-stack.is-still .up-stack-item .up-stack-vis,
.up-stack.is-still .up-stack-item:hover .up-stack-vis,
.up-stack.is-still .up-stack-item:first-child:hover .up-stack-vis,
.up-stack.is-still .up-stack-item:hover ~ .up-stack-item .up-stack-vis { transform: none; }
.up-stack-item:first-child:hover ~ .up-stack-item .up-stack-vis,
.up-stack-item:first-child:hover ~ .up-stack-more,
.up-stack-item:first-child:hover ~ .up-stack-tail { transform: translateX(8px); }

/* Mirror image of the four rules above, for a stack pinned to the RIGHT edge of its container
   (Citations in the responses table, and the same stack in a card footer): spreading rightwards
   there pushes chips out of the cell/card instead of into free space. Same distances, same lift,
   same easing — only the axis is flipped. brandStack(..., {spread:"left"}).
   `:has(~ …)` is the previous-sibling selector CSS otherwise lacks: "an item that is followed by
   the hovered one" is the mirror of the plain `~` used above. The last CHIP carries .is-last from
   brandStack — :last-child would miss it when a "+N" badge follows, and expressing it inline would
   need :has() inside :has(), which CSS forbids (the rule is dropped as invalid). */
/* FIRST cancel the right-hand spread. The four rules above still match inside a left-spreading
   stack — they are not scoped to it — so without this the chips AFTER the hovered one kept
   sliding right while the ones before it slid left. Nothing in a left-spread stack may move
   right, so the trailing items (and the "+N" badge, which sits at the right edge) stay put. */
.up-stack.is-spread-left .up-stack-item:hover ~ .up-stack-item .up-stack-vis,
.up-stack.is-spread-left .up-stack-item:hover ~ .up-stack-more,
.up-stack.is-spread-left .up-stack-item:hover ~ .up-stack-tail,
.up-stack.is-spread-left .up-stack-item:first-child:hover ~ .up-stack-item .up-stack-vis,
.up-stack.is-spread-left .up-stack-item:first-child:hover ~ .up-stack-more,
.up-stack.is-spread-left .up-stack-item:first-child:hover ~ .up-stack-tail { transform: none; }

.up-stack.is-spread-left .up-stack-item:hover .up-stack-vis { transform: translateX(-8px) translateY(-3px); }
.up-stack.is-spread-left .up-stack-item.is-last:hover .up-stack-vis { transform: translateY(-3px); }
.up-stack.is-spread-left .up-stack-item:has(~ .up-stack-item:hover) .up-stack-vis { transform: translateX(-16px); }
.up-stack.is-spread-left .up-stack-item:has(~ .up-stack-item.is-last:hover) .up-stack-vis { transform: translateX(-8px); }
.up-root.is-dense .up-stack.is-spread-left .up-stack-item:hover .up-stack-vis { transform: translateX(-7.04px) translateY(-3px); }
.up-root.is-dense .up-stack.is-spread-left .up-stack-item.is-last:hover .up-stack-vis { transform: translateY(-3px); }
.up-root.is-dense .up-stack.is-spread-left .up-stack-item:has(~ .up-stack-item:hover) .up-stack-vis { transform: translateX(-14.08px); }
.up-root.is-dense .up-stack.is-spread-left .up-stack-item:has(~ .up-stack-item.is-last:hover) .up-stack-vis { transform: translateX(-7.04px); }
/* Compact mode shrinks the mentioned-brand logos ~20% and scales every gap/shift to match. */
/* Dichte Zeilen: Faktor 0.88 auf alles, ueber die drei Marken des Stapels statt je Regel --
   Groesse 28 -> 24.64, Ueberlappung und Ring 4 -> 3.52, wie vor dem 15.10. --, damit Chip, Ring,
   Zeilen-Hover und der Einzug der Karte (.urt-card-citations) EINEN Wert lesen. Polster 2 -> 1.76,
   das Bild also 24.64 - 2 - 3.52 = 19.12.
   NICHT mitgerechnet: Radius und Buchstabe. 0.88 x 6 = 5.3 liegt auf der Stufe 6, das Bild 6 - 2.76
   = 3.2 auf 3 -- beides wie im normalen Chip, die Regeln oben gelten also unveraendert (vor dem
   15.10. standen hier 7.04 und 4.28, Bruchteile, die je Browser anders runden). Der Buchstabe
   bliebe mit 0.88 unter dem Anfang der Skala (9.7), er steht wie in der 24er .up-logo-box auf 11.
   Das "+N" bleibt 12 (die Schrift der Zelle), nur sein Abstand zieht mit. */
.up-root.is-dense { --up-stack-gr: calc(var(--up-h-seg) * 0.88); --up-stack-ueber: calc(4px * 0.88); --up-stack-ring-w: calc(4px * 0.88); }
.up-root.is-dense .up-stack-vis { padding: calc(2px * 0.88); }
.up-root.is-dense .up-stack-more { margin-left: calc(8px * 0.88); }
.up-root.is-dense .up-stack-item:hover .up-stack-vis { transform: translateX(7.04px) translateY(-3px); }
.up-root.is-dense .up-stack-item:first-child:hover .up-stack-vis { transform: translateY(-3px); }
.up-root.is-dense .up-stack-item:hover ~ .up-stack-item .up-stack-vis,
.up-root.is-dense .up-stack-item:hover ~ .up-stack-more { transform: translateX(14.08px); }
.up-root.is-dense .up-stack-item:first-child:hover ~ .up-stack-item .up-stack-vis,
.up-root.is-dense .up-stack-item:first-child:hover ~ .up-stack-more { transform: translateX(7.04px); }

/* ---------- interactive chip hover ----------
   For any coloured chip/tag that is CLICKABLE (topic tags in a table cell, topic tags in the
   bulk editor, and anything similar that follows). The chip keeps its own tint at rest; on
   hover it drops to the neutral surface with the shared hover border, so "this is a control"
   reads the same everywhere regardless of which colour the tag happens to carry.
   Applied by adding .up-chiphover next to the chip's own class. */
.up-chiphover { transition: background 120ms ease, border-color 120ms ease, color 120ms ease; }
/* Zwei Marken statt vier Literalen: #f9f8f9/#232326 ist --vt-row-hover-solid, #1f1f1b/#e0e0e0
   ist --up-accent-ink. Die eigene Dunkel-Regel faellt damit weg -- sie war nur da, weil die
   hellen Werte fest verdrahtet waren.
   Der Chip kann auf der Komponentenwurzel ODER auf einer Flaeche am body liegen (der Bulk-Leiste
   der Prompts-Tabelle). Deshalb fuehrt diese Leiste --vt-row-hover-solid seit dem 22.09. selbst
   mit; --up-accent-ink setzt ihr das JS ohnehin schon (akzentSyncen). (22.09.) */
.up-chiphover:hover { background: var(--vt-row-hover-solid); color: var(--up-accent-ink); box-shadow: none; }

/* compact "No data" line for tables that live in a fixed-height box and have no room for the
   full illustrated empty state. Schrift seit dem 15.10. nach Geist (copy-14, 14/20), wie
   .up-chart-empty; das Polster bleibt -- die Kaesten, in denen sie steht, haben ihre Hoehe. */
.up-empty-mini { padding: 24px; text-align: center; color: var(--vc-muted); font-size: var(--up-fs-l); line-height: 20px; }

/* ============================================================
   Topic create/edit modal (UC.makeTopicModal) — STYLEGUIDE §27's reference implementation, moved
   here from topics-manager.css once prompts-table's "Add Topic" button became a second consumer
   of the exact same modal. Body-mounted (outside any .up-root), so — like .upt-bulkbar and every
   other document.body-mounted surface in this library — it redeclares its own --vc- and
   --up-surface tokens locally instead of relying on the cascade from a .up-root ancestor it
   doesn't have.
   ============================================================ */
.up-topicmodal-backdrop {
  position: fixed; inset: 0; z-index: 100000;   /* above the bulk-bar's 99999 */
  background: var(--vc-dialog-schleier, rgba(0,0,0,.30));
  backdrop-filter: blur(var(--up-schleier-blur, 1.5px)); -webkit-backdrop-filter: blur(var(--up-schleier-blur, 1.5px));
  display: flex; align-items: center; justify-content: center; padding: 24px;
  opacity: 0; pointer-events: none;
  transition: opacity 160ms ease;
}
.up-topicmodal-backdrop.is-shown { opacity: 1; pointer-events: auto; }
/* Darker backdrop in dark mode: the same 30% black reads as noticeably lighter/washed-out over a
   dark page than over a light one, so dark mode gets its own, slightly heavier value. */
.up-topicmodal-backdrop[data-theme="dark"] { background: rgba(0,0,0,.35); }

.up-topicmodal-card {
  --vc-text: #171717; --vc-muted: #4d4d4d; --vc-third: #7d7d7d; --vc-fourth: #8f8f8f; --vc-border: #dcdbdd;
  --vc-heading-bg: #f9f8f9; --vc-hover-border: #6f6f6f; --up-surface: var(--up-popup-bg, #ffffff); --vc-sk: #eeedef;
  /* Die Fokus-Tokens MUESSEN hier mit rein. Diese Karte haengt am body, nicht unter .up-root, wo
     die Tokens definiert sind -- ohne sie ist var(--up-focus) leer, und eine Deklaration mit einer
     leeren Variablen ist ungueltig. Die :focus-Regel des Namensfeldes weiter unten stand damit
     zwar in der Datei, hat aber nichts getan, und das Feld behielt seinen alten Fokuszustand.
     Wer hier eine Palette neu aufmacht, muss sie VOLLSTAENDIG aufmachen. */
  --up-focus: #4A9EFF; --up-focus-ring: rgba(74,158,255,0.20); --up-focus-w: 2px;
  width: 100%; max-width: 420px; max-height: calc(100vh - 48px); overflow-y: auto;
  background: var(--up-popup-bg, var(--up-surface)); border: 1px solid var(--vc-border);
  border-radius: var(--up-r-2xl);
  /* Ohne Rueckfall (28.09.): die Tiefen stehen jetzt an :root, das Token loest auch hier am body
     auf. Bis dahin galt hier der Rueckfall (0 20px 48px .22) statt e-4, dunkel wie hell. */
  box-shadow: var(--up-e-4);
  color: var(--vc-text); font-family: Geist, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  opacity: 0; transform: translateY(-4px) scale(.985); pointer-events: none;
  transition: opacity 140ms ease, transform 140ms ease;
  /* One flex column, one gap value (32px) between every top-level block (header, each field,
     footer) — padding carries the OUTER 16px on all four sides, gap carries everything between. */
  position: relative;
  padding: 16px;
  display: flex; flex-direction: column; gap: 32px;
}
.up-topicmodal-backdrop.is-shown .up-topicmodal-card { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.up-topicmodal-backdrop[data-theme="dark"] .up-topicmodal-card {
  /* Seit dem 15.10. Geists dunkle Skala: der Grund eines Dialogs ist Background 1, der Rand der
     einer schwebenden Schicht (Gray 500), die leise Flaeche (Seitenspalte der Preferences) und das
     Skelett Gray 100. Alle vier stehen HIER, weil die helle Grundregel dieser Karte sie selbst
     setzt -- eine geerbte Marke verliert gegen jede eigene Deklaration. Gemessen am 15.10., als sie
     hier fehlten: die Seitenspalte der Preferences stand in #f9f8f9 im dunklen Fenster.
     Eine Stufe ruhiger (15.10. abends): Grund #0f0f0f, Rand #333333, leise Flaeche #1f1f1f,
     Skelett #1e1e1e; der Schalter aus bleibt #454545. Die Seitenspalte der Preferences (und von
     Miras Einstellungen) traegt seit dem 16.10. nicht mehr die leise Flaeche, sondern den
     Seitengrund wie die Seitenleiste der App (preferences.css, .ums-aside). */
  --vc-text: #ededed; --vc-muted: #c9c9c9; --vc-third: #a1a1a1; --vc-fourth: #7d7d7d; --vc-border: #333333;
  --vc-heading-bg: #1f1f1f; --vc-hover-border: #707070; --up-surface: var(--up-popup-bg, #0a0a0a); --vc-sk: #1e1e1e; --up-switch-off: #454545;
  --up-focus: #6DB2FF; --up-focus-ring: rgba(109,178,255,0.28);
}
/* IM GRAUEN HELL DIE LEISE FLAECHE DER APP (16.10.). Die helle Grundregel oben steht noch auf dem
   Wert vor dem grauen Hell (#f9f8f9), und weil die Karte ihn selbst setzt, erreicht sie das Grau
   der Wurzel (#f2f2f2, core "DAS GRAUE HELL") nicht -- die Wurzel ist der Schleier, die Karte sein
   Kind. Gemessen vorher: der Hover des Schliessen-Knopfs #f9f8f9 auf der weissen Karte (6 Stufen),
   der Schleier darueber #f2f2f2. Betrifft jede Flaeche in einem Dialog, die --vc-heading-bg traegt:
   Schliessen-Hover, die Ablage im Prompt-Import, gesperrte Knoepfe, Flaggen ohne Bild.
   Nur mit dem Marker am <html> (ohne ihn -- Landingpage, erstes Bild -- bleibt der alte Wert) und
   nur im Hellen; (0,4,1) gegen die Grundregel (0,1,0), die dunkle Fassung (0,3,0) schliesst das
   :not() aus. */
html[data-up-hell="grau"] .up-topicmodal-backdrop:not([data-theme="dark"]) .up-topicmodal-card {
  --vc-heading-bg: var(--up-leise-grau);
}
@media (prefers-reduced-motion: reduce){ .up-topicmodal-backdrop, .up-topicmodal-card { transition-duration: 1ms; } }
.up-topicmodal-card * { box-sizing: border-box; }

.up-topicmodal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.up-topicmodal-heading { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.up-topicmodal-title { font-size: 22px; font-weight: 500; margin: 0; line-height: 1.25; }
/* Optional second line under the title — no current consumer uses one, but the token exists so a
   future one doesn't have to invent its own size/color/weight. */
.up-topicmodal-sub { font-size: 16px; font-weight: 400; color: var(--vc-third); margin: 0; line-height: 1.3; }
/* 32x32 hit target, pulled -8px top/right via margin so it visually sits close to the card's true
   corner despite the card's own 16px padding — a plain 16px inset read as floating, not anchored. */
/* .up-popup-close ist der allgemeine Name: JEDES Popup/Modal nimmt diesen Knopf, statt sich einen
   eigenen zu bauen. Wichtig daran ist, was NICHT drinsteht -- kein Rahmen, auch nicht im Hover.
   Ein Rahmen, der erst beim Ueberfahren erscheint, laesst den Knopf springen und ist im ganzen
   Rest der App nirgends so; der Hover ist ueberall nur Flaeche + Primaerfarbe (siehe .up-iconbtn).
   .up-topicmodal-close bleibt als Zweitname bestehen, damit bestehendes Markup weiterlaeuft. */
.up-popup-close, .up-topicmodal-close { flex: 0 0 auto; width: 32px; height: 32px; margin: -8px -8px 0 0; border: 0; border-radius: var(--up-r-m);
  background: transparent; color: var(--vc-muted); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .14s ease, color .14s ease; }
.up-popup-close svg, .up-topicmodal-close svg { width: 16px; height: 16px; display: block; }
.up-popup-close:hover, .up-topicmodal-close:hover { background: var(--vc-heading-bg); color: var(--vc-text); }

.up-topicmodal-body { display: flex; flex-direction: column; gap: 32px; }
.up-topicmodal-field { display: flex; flex-direction: column; gap: 8px; }
/* Die Fehlerzeile sitzt im Feld und darf im Ruhezustand keinen Platz belegen: der gap von 8px
   kaeme sonst immer dazu, und das padding-top 5px des Satzes bleibt auch in der zugeklappten Zeile
   stehen (gemessen: Feld 65 statt 60 hoch). Also den gap aufheben und die 5px erst geben, wenn
   die Zeile sichtbar ist -- dann steht der Satz 5px unter dem Feld wie ueberall in der App. */
.up-topicmodal-field > .up-topicmodal-err { margin-top: -8px; }
.up-topicmodal-field > .up-topicmodal-err > span { padding-top: 0; }
.up-topicmodal-field > .up-topicmodal-err.is-on > span { padding-top: 5px; }
/* Each field's own heading: primary color + weight, not muted — these are content labels a user
   reads every time the modal opens, not secondary chrome. */
/* .up-topicmodal-label steht jetzt OBEN in der Sammelregel bei .up-label, nicht mehr hier.
   Der Grund ist eine gemessene Panne: als die Etikettfarbe von --vc-muted auf --vc-third ging,
   hat diese eine Regel es nicht mitbekommen -- fuenf Etiketten standen auf rgb(158,158,158),
   die sechste auf rgb(107,111,120). Eine zweite Deklaration derselben Rolle ist genau die
   Sorte Abweichung, gegen die der ganze Umbau laeuft.
   Sechs Verwender in drei Dialogen: Name, Appearance, Topics, Group name, Prompt, Topics. */
.up-topicmodal-name { height: var(--up-h-gross); padding: 0 12px; border: 1px solid var(--vc-border); border-radius: var(--up-r-l);
  background: var(--up-feld-bg); color: var(--vc-text); font: inherit; font-size: 14px; outline: 0; width: 100%; }
.up-topicmodal-name:focus { border-color: var(--up-focus); box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); }

/* Appearance row: emoji + color trigger buttons. */
.up-topicmodal-approw { display: flex; align-items: center; gap: 8px; }
.up-topicmodal-pickbtn { flex: 0 0 auto; width: 36px; height: 36px; padding: 0; border-radius: var(--up-r-m);
  border: 1px solid var(--vc-border); background: transparent; cursor: pointer; font-size: 17px; line-height: 1;
  color: var(--vc-muted); display: inline-flex; align-items: center; justify-content: center; }
.up-topicmodal-pickbtn svg { width: 17px; height: 17px; display: block; }
/* Menue-Ausloeser nach Geist (15.10.): Flaeche plus eine Stufe kraeftigere Kante statt --vc-hover-border. */
.up-topicmodal-pickbtn:hover, .up-topicmodal-pickbtn.is-open { border-color: var(--up-knopf-rand-hover); background: var(--up-knopf-hover); }
.up-topicmodal-pickswatch { width: 17px; height: 17px; border-radius: 50%; display: block; border: 1px solid rgba(0,0,0,.14); }

.up-topicmodal-pickpanel { margin-top: 10px; }
.up-topicmodal-colorgrid { display: grid; grid-template-columns: repeat(10, 1fr); gap: 6px; }
.up-topicmodal-colorcell { width: 100%; aspect-ratio: 1 / 1; padding: 0; border: 1px solid transparent; border-radius: var(--up-r-m);
  background: transparent; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms ease, border-color 120ms ease; }
.up-topicmodal-colorcell:hover { background: var(--vc-heading-bg); border-color: var(--vc-hover-border); }
.up-topicmodal-colorblob { width: 22px; height: 22px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; color: #fff; }
.up-topicmodal-colorblob svg { width: 12px; height: 12px; display: block; stroke: currentColor; stroke-width: 4; }
/* Every color-swatch checkmark in the app (this modal, and prompts-table's two local pickers)
   uses one fixed ink instead of a per-swatch contrast-solved color: white in light mode, the
   theme's own primary text color in dark -- simpler, and one less thing that can drift out of
   sync between three separate render sites. */
.up-topicmodal-backdrop[data-theme="dark"] .up-topicmodal-colorblob { color: var(--vc-text); }
.up-topicmodal-emojipicker {
  width: 100%; height: 268px;
  --num-columns: 8; --emoji-size: 1.25rem; --emoji-padding: 0.35rem;
  --border-radius: var(--up-r-l); --border-color: var(--vc-border); --background: transparent;
  --input-border-radius: var(--up-r-m); --input-border-color: var(--vc-border); --input-font-size: 12.5px;
  --indicator-color: var(--vc-text); --outline-color: var(--vc-hover-border);
}

.up-topicmodal-foot { display: flex; align-items: center; gap: 10px; }
.up-topicmodal-delete { height: 32px; padding: 0 14px; border-radius: var(--up-r-m); border: 1px solid var(--vc-border);
  background: transparent; color: var(--vc-muted); font: inherit; font-size: 12.5px; font-weight: 500; cursor: pointer; }
/* --vt-down MIT Rueckfall, und der Rueckfall ist kein Schoenheitsfehler: der Schleier dieses
   Dialogs wird an vier Stellen gebaut und nur an zwei davon mit .up-root (preferences.js und
   ask-mira.js ja, prompts-table.js und core.js nein). Ohne .up-root ist die Marke unaufloesbar,
   und eine color-Deklaration mit unaufloesbarem var() ist als Ganzes ungueltig -- der Knopf
   verlaere dann seine Warnfarbe ganz. So bekommt er im Dunkeln das helle Rot #d25d5d, wo die
   Marke ankommt, und sonst weiter das alte #b0200c. */
.up-topicmodal-delete:hover { border-color: var(--vt-down, #b0200c); color: var(--vt-down, #b0200c); }
.up-topicmodal-delete.is-armed { border-color: var(--vt-down, #b0200c); background: var(--vt-down, #b0200c); color: #fff; }
.up-topicmodal-delete.is-armed:hover { opacity: .9; }
/* Nur noch die Lage -- Aussehen kommt aus .up-btn-pri. */
.up-topicmodal-save { margin-left: auto; }

/* ---- Das allgemeine Popup (UC.makeModal, 02.10.) -------------------------------------------
   Nur, was die Huelle oben nicht schon hat. is-breit: der Anlegen-Dialog der Events hat drei
   Schritte mit Listen -- 420 waere dort zu eng; 560 ist die Breite des Plan-Dialogs. Der Fuss
   stellt die Knoepfe rechts; ein Gefahr-Knopf (Loeschen) traegt die Warnfarbe wie
   .up-topicmodal-delete. Der Kreisel erscheint nur im Knopf, der gerade arbeitet. */
.up-modal.is-breit { max-width: 560px; }
.up-modal-foot { justify-content: flex-end; }
.up-modal-foot > button { display: inline-flex; align-items: center; gap: 6px; }
/* Ein Knopf, den ein Schritt nicht braucht (Zurueck auf Schritt 1): das hidden-Attribut allein
   verliert gegen display: inline-flex darueber. */
.up-modal-foot > button[hidden] { display: none; }
/* ============================================================
   DAS POPUP NACH GEISTS MODAL (19.10., Nutzer: "bitte nutzt kuenftig vercel.com/geist/modal"). Gilt fuer jedes Popup aus UC.makeModal
   (.up-modal); gemessen an Geists Default, Sticky und Eingabefeld-Modal:
     Karte        540 breit, Radius 12, Schatten e-4, hoechstens min(800px, 80vh); sie ist der Rollbereich
     Kopf         Titel 20/26, 600, -0.4px; darunter Untertitel 14/20 in der zweiten Textfarbe, 8px Abstand; 20 Polster rundum
     Inhalt       20 links und rechts, 20 unten
     Fuss         12 Polster, links Cancel, rechts die Hauptaktion (space-between), Knoepfe 32 hoch; eigene Flaeche und eine
                  Haarlinie oben; klebt unten
     Rollt es     klebt auch der Kopf oben (mit Haarlinie, sobald etwas darunter wegrollt: .is-gerollt), der Fuss wirft seinen Strich
     Telefon      unter 540 wird die Karte ein Blatt am unteren Rand
   Fokus: das erste Eingabefeld; ohne Feld die Karte selbst (nie ein Knopf, nie das X); in einem Popup mit Gefahraktion Cancel. */
.up-modal, .up-geist {
  --up-modal-pad: 20px;
  padding: 0; gap: 0; max-width: 540px; max-height: min(800px, 80vh); overflow-y: auto; overflow-x: hidden; outline: 0;
  font-size: var(--up-fs-l);
}
.up-modal.is-breit, .up-geist.is-breit { max-width: 560px; }
:is(.up-modal, .up-geist) > .up-topicmodal-head {
  position: sticky; top: 0; z-index: 3; flex: 0 0 auto; padding: var(--up-modal-pad) var(--up-modal-pad) 16px; gap: 12px;
  background: var(--up-popup-bg, var(--up-surface)); transition: box-shadow var(--up-t-1) var(--up-ease);
}
:is(.up-modal, .up-geist).is-gerollt > .up-topicmodal-head { box-shadow: 0 1px 0 0 var(--up-popup-linie); }
:is(.up-modal, .up-geist) .up-topicmodal-heading { gap: 8px; }
:is(.up-modal, .up-geist) .up-topicmodal-title { font-size: var(--up-fs-modaltitel); line-height: 26px; font-weight: 600; letter-spacing: -0.4px; color: var(--vc-text); }
:is(.up-modal, .up-geist) .up-topicmodal-sub { font-size: var(--up-fs-l); line-height: 20px; color: var(--vc-muted); }
:is(.up-modal, .up-geist) .up-popup-close { margin: -6px -6px 0 0; }
/* 4 oben: der klebende Kopf (opak) deckt sonst den Rahmenring (1px) und den Fokusring (2px) des ersten Elements ab -- gemessen am Umschalter in Add Prompts. Der Kopf gibt dieselben 4 unten ab. */
:is(.up-modal, .up-geist) > .up-topicmodal-body { padding: 4px var(--up-modal-pad) var(--up-modal-pad); flex: 1 0 auto; gap: 24px; }
:is(.up-modal, .up-geist) > .up-modal-err.is-on { margin: 0 var(--up-modal-pad) var(--up-modal-pad); }
:is(.up-modal, .up-geist) > .up-topicmodal-foot {
  position: sticky; bottom: 0; z-index: 3; flex: 0 0 auto; margin-top: auto; padding: 12px; gap: 12px; justify-content: space-between;
  background: var(--up-popup-fuss); box-shadow: 0 -1px 0 0 var(--up-popup-linie);
}
/* Der Schatten des Fusses (Geists Sticky-Modal): liegt unter dem Fuss noch Inhalt (.has-mehr, core modalVerhalten), blendet er 200ms
   ein, sonst aus. */
:is(.up-modal, .up-geist) > .up-topicmodal-foot::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 8px; pointer-events: none;
  background: linear-gradient(to top, var(--up-popup-schatten), transparent); opacity: 0;
  transition: opacity var(--up-t-2) var(--up-ease);
}
:is(.up-modal, .up-geist).has-mehr > .up-topicmodal-foot::before { opacity: 1; }
/* Dasselbe oben: ist Inhalt nach oben weggerollt (.has-oben), wirft der klebende Kopf einen Schatten nach unten -- man sieht, dass darueber etwas ist. */
:is(.up-modal, .up-geist) > .up-topicmodal-head::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 8px; pointer-events: none;
  background: linear-gradient(to bottom, var(--up-popup-schatten), transparent); opacity: 0;
  transition: opacity var(--up-t-2) var(--up-ease);
}
:is(.up-modal, .up-geist).has-oben > .up-topicmodal-head::after { opacity: 1; }
:is(.up-modal, .up-geist) > .up-topicmodal-foot > button { height: var(--up-h-btn); font-size: var(--up-fs-l); }
/* Eine einzelne Aktion steht rechts (Geists "Single button"), nicht links: space-between setzt sie sonst an den Anfang. */
:is(.up-modal, .up-geist) > .up-topicmodal-foot > button:last-child { margin-left: auto; }
/* DIE RICHTUNG STEHT HIER SELBST (22.10., Prompt Research / Klickrouten): jede Schale traegt auch .up-root, und .up-root ist
   flex-direction: column. Dann ist align-items die WAAGERECHTE Achse: "flex-end" schob die Karte nach rechts (bei voller Breite
   also nirgendwohin), senkrecht blieb sie mittig -- gemessen bei 375: Blatt 293 ueber dem unteren Rand statt 0. In Zeilenrichtung
   meint align-items die Senkrechte, und die Karte steht unten. Breite 100 %, damit auch eine Karte ohne eigene Breite das Blatt fuellt. */
@media (max-width: 540px) {
  .up-topicmodal-backdrop:has(> .up-geist, > .up-modal) { flex-direction: row; align-items: flex-end; padding: 10px 0 0; }
  .up-topicmodal-backdrop > :is(.up-modal, .up-geist) { width: 100%; max-width: 100%; max-height: 90vh; max-height: 90dvh; border-radius: var(--up-r-2xl) var(--up-r-2xl) 0 0; border-bottom: 0; }
}
/* Der Gefahr-Knopf ist gefuellt rot mit weisser Schrift (10.10. angefordert, vorher umrandet und
   erst beim Ueberfahren rot). Mit .up-modal-foot davor: er muss .up-btn-sec samt :hover schlagen,
   und das entscheidet die Spezifitaet, nicht die Reihenfolge. Ueberfahren dunkelt ein wenig ab. */
.up-modal-foot > .up-modal-gefahr { background: var(--vc-danger); border-color: var(--vc-danger); color: var(--vc-danger-fg); }
.up-modal-foot > .up-modal-gefahr:hover { background: color-mix(in srgb, var(--vc-danger) 88%, #000); border-color: color-mix(in srgb, var(--vc-danger) 88%, #000); color: var(--vc-danger-fg); }
.up-modal-spin { display: none; width: 12px; height: 12px; border-radius: var(--up-r-voll);
  border: 2px solid currentColor; border-right-color: transparent; animation: upModalDreh 700ms linear infinite; }
.up-modal-foot > button.is-busy .up-modal-spin { display: inline-block; }
@keyframes upModalDreh { to { transform: rotate(360deg); } }
.up-modal-err:not(.is-on) { display: none; }
/* Felder im Popup, in der Form des Namensfeldes (.up-topicmodal-name): derselbe Rahmen, Radius 9,
   Polster 12, Schrift 14 und derselbe Fokusring -- die Klasse bleibt am Element, hier steht nur,
   was ein mehrzeiliges Feld und ein Auswahlknopf anders brauchen. */
.up-topicmodal-name.up-modal-text { display: block; height: auto; min-height: 76px; padding: 9px 12px; line-height: 1.45; resize: vertical; }
.up-topicmodal-name.up-modal-wahl { display: flex; align-items: center; gap: 8px; text-align: left; cursor: pointer; }
/* Ein Auswahlfeld: Hover wie Geist-Select (15.10.), nur die Kante eine Stufe kraeftiger. */
.up-modal-wahl:hover { border-color: var(--up-knopf-rand-hover); }
.up-modal-wahl-ic, .up-modal-wahl-chev { display: inline-flex; flex: 0 0 auto; color: var(--vc-third); }
.up-modal-wahl-ic svg { width: 16px; height: 16px; }
.up-modal-wahl-chev svg { width: 14px; height: 14px; }
.up-modal-wahl-txt { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-modal-wahl-txt.is-leer { color: var(--vc-fourth); }
/* Ein erklaerender Satz unter einem Etikett oder am Fuss eines Schritts: leiser als der Inhalt,
   lauter als nichts. */
.up-modal-hinweis { margin: 0; font-size: var(--up-fs-m); line-height: 1.45; color: var(--vc-third); }
/* Saetze, die zusammengehoeren (Loeschdialoge von Prompts und Brands, 11.10. nach core): 8px
   zwischen ihnen statt der 32px, die die Dialogflaeche zwischen ihre Teile legt. */
.up-modal-saetze { display: flex; flex-direction: column; gap: 8px; }

/* ---- Die Trefferzeile der Entitaets-Suche (UC.entityRow mit dem Praefix "up-es", 02.10.) -----
   entityRow traegt "up-es" seit jeher als Vorgabe, aber Regeln dazu gab es keine: Ask Mira malt
   mit .am-pick-*, die Palette mit .mqa-* (beide mit eigenen Marken, beide bleiben). Hier dieselben
   Werte wie .am-pick-row ff. in ask-mira.css, Wert fuer Wert, nur aus den Marken der App statt
   aus --am-*: --am-text = --vc-text, --am-muted = --vc-muted, --am-muted-2 = --vc-third,
   --am-border = --vc-border, --am-soft = --vc-heading-bg. Wer eine URL sucht, soll dieselbe
   Zeile sehen wie in Mira (Spezifikation der Events, 57). */
.up-es-row { display: flex; align-items: center; gap: 11px; width: 100%; padding: 8px 10px;
  border: 0; border-radius: var(--up-r-l); background: transparent; cursor: pointer;
  font-family: inherit; text-align: left; transition: background var(--up-t-2) ease; }
.up-es-row:hover, .up-es-row.is-active { background: var(--up-sel-bg, var(--vc-heading-bg)); }
.up-es-row.is-taken { opacity: 0.45; pointer-events: none; }
.up-es-av { width: 30px; height: 30px; flex: 0 0 auto; border-radius: var(--up-r-m); overflow: hidden;
  border: 1px solid var(--vc-border); background: var(--vc-heading-bg); position: relative;
  display: flex; align-items: center; justify-content: center; }
.up-es-av img { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }
.up-es-av.is-flag { width: 24px; height: 18px; border-radius: 3px; border: 0; }
.up-es-av-fb { display: none; align-items: center; justify-content: center; width: 100%; height: 100%; color: var(--vc-muted); }
.up-es-av.is-fb .up-es-av-fb { display: flex; }
/* > svg dazu: ohne Bild legt entityAvatar den Rueckfall DIREKT in den Kasten, nicht in -av-fb
   (Treffer ohne Favicon, das "+" fuer ein neues Ziel) -- sonst stand dort ein 24er-Zeichen. */
.up-es-av-fb svg, .up-es-av > svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.up-es-av.is-fb { color: var(--vc-muted); }
.up-es-av-t { font-size: var(--up-fs-xs); font-weight: 600; color: var(--vc-muted); }
.up-es-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.up-es-primary { font-size: var(--up-fs-l); font-weight: 500; color: var(--vc-text); letter-spacing: -0.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.up-es-secondary { font-size: var(--up-fs-s); color: var(--vc-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.up-es-type { flex: 0 0 auto; font-size: var(--up-fs-xs); font-weight: 500; color: var(--vc-third); letter-spacing: 0.01em; }
.up-es-hl { background: transparent; color: inherit; font-weight: 700; }

/* ---- Die Kennzahl-Kachel (UC.kpiKarte, 02.10.) ------------------------------------------------
   Masse von .upw-kpi (power-dashboard), die Zahl auf der Skalenstufe 22 statt 21. */
.up-kpi { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.up-kpi-label { font-size: var(--up-fs-m); font-weight: var(--up-fw-m); color: var(--vc-muted); }
/* DAS INFO-ZEICHEN DER KACHEL (22.10. abends, UC.kpiKarte({ explain })): .up-th-info aus core, rechts in der Zeile des
   Etiketts -- also oben rechts in der Kachel (Lukas: "Icons, die on hover oben rechts in Cards sichtbar werden"). Es steht
   mit margin-left: auto aus der Grundregel von .up-th-info; sichtbar beim Ueberfahren der Kachel wie im Tabellenkopf beim
   Ueberfahren der Kopfzelle. Shopping und Ads seit dem 22.10. abends ebenso (kpiKarte explain); die Details setzen ihr Zeichen
   selbst hinter das Wort und bleiben, wie sie sind (ohne .has-info). */
.up-kpi-label.has-info { display: flex; align-items: center; gap: 6px; min-width: 0; }
.up-kpi:hover .up-kpi-label.has-info > .up-th-info { opacity: 1; }
.up-kpi-row { display: flex; align-items: baseline; flex-wrap: wrap; column-gap: 8px; row-gap: 2px; min-height: var(--up-h-seg); }
.up-kpi-val { font-size: var(--up-fs-2xl); font-weight: var(--up-fw-m); line-height: 1.15; letter-spacing: -0.4px;
  font-variant-numeric: tabular-nums; color: var(--vc-text); }
.up-kpi-vorher { font-size: var(--up-fs-xl); font-weight: var(--up-fw-m); color: var(--vc-third); font-variant-numeric: tabular-nums; }
.up-kpi-pfeil { display: inline-flex; color: var(--vc-fourth); align-self: center; }
.up-kpi-pfeil svg { width: 14px; height: 14px; }
.up-kpi-trend { display: inline-flex; align-items: center; align-self: center; }
.up-kpi-trend .up-trend { font-size: var(--up-fs-l); }
/* Fusszeile wie in der Overview des Agentic Dashboards (.upw-kpi-foot): 11px in der dritten
   Farbe, 10px unter der Zahl (5 gap + 5 hier). */
.up-kpi-foot { font-size: var(--up-fs-xs); color: var(--vc-third); margin-top: 5px; }
/* DAS KENNZAHLEN-BAND (04.10.): mehrere UC.kpiKarte in EINEM Kasten (.up-box am Behaelter),
   getrennt durch senkrechte Linien ueber die volle Hoehe -- die Bauart der Kacheln in
   performance-detail (.upd-kpi + .upd-kpi): border-left an jeder ausser der ersten, so bleibt die
   Aussenkante die des Kastens. Kein gap-Trick: der verschwindet bei Bruchteilbreiten.
   Zuerst im Event-Detail (.uev-kpis), dann viermal in Shopping -- darum hier. Spalten ueber
   --up-kpiband-n (Vorgabe 3). is-4: im schmalen Fenster zwei mal zwei, die Linien wandern mit;
   ganz schmal alle untereinander, der Trenner liegt dann oben. */
.up-kpiband { display: grid; grid-template-columns: repeat(var(--up-kpiband-n, 3), minmax(0, 1fr)); }
.up-kpiband.is-4 { --up-kpiband-n: 4; }
.up-kpiband > .up-kpi { padding: 16px; }
.up-kpiband > .up-kpi + .up-kpi { border-left: 1px solid var(--vc-border); }
.up-root.is-narrow .up-kpiband.is-4 { --up-kpiband-n: 2; }
.up-root.is-narrow .up-kpiband.is-4 > .up-kpi:nth-child(odd) { border-left: 0; }
.up-root.is-narrow .up-kpiband.is-4 > .up-kpi:nth-child(n+3) { border-top: 1px solid var(--vc-border); }
.up-root.is-vnarrow .up-kpiband, .up-root.is-vnarrow .up-kpiband.is-4 { --up-kpiband-n: 1; }
.up-root.is-vnarrow .up-kpiband > .up-kpi + .up-kpi { border-left: 0; border-top: 1px solid var(--vc-border); }
/* DIE OFFENE FASSUNG (07.10. angefordert fuer Shopping und Ads: "die Aussenborder weg, nur die
   vertikalen Separatoren"): das Band ohne .up-box -- kein Kasten, kein Grund, die Kennzahlen stehen
   auf der Seite und sind nur durch die senkrechten Linien getrennt. Ohne Kasten gibt es keine Kante,
   von der die erste Zahl Abstand halten muesste: die erste Kachel einer Zeile hat links kein
   Polster, die letzte rechts keins -- so steht die Beschriftung buendig mit den Ueberschriften
   darunter. Oben und unten 4px statt 16, damit die Linien etwas ueber die Schrift hinausreichen,
   ohne dass ein leerer Streifen ueber der Leiste steht. Die Linien selbst kommen aus den Regeln
   darueber, auch im schmalen 2 x 2 und untereinander. */
.up-kpiband.is-offen > .up-kpi { padding: 4px 16px; }
.up-kpiband.is-offen > .up-kpi:first-child { padding-left: 0; }
.up-kpiband.is-offen > .up-kpi:last-child { padding-right: 0; }
.up-root.is-narrow .up-kpiband.is-offen.is-4 > .up-kpi:nth-child(odd) { padding-left: 0; }
.up-root.is-narrow .up-kpiband.is-offen.is-4 > .up-kpi:nth-child(even) { padding-right: 0; }
/* Zwei Zeilen: die zweite haelt Abstand zur Linie zwischen ihnen. */
.up-root.is-narrow .up-kpiband.is-offen.is-4 > .up-kpi:nth-child(n+3) { padding-top: 12px; margin-top: 8px; }
/* DIE SENKRECHTE STOESST AN DIE WAAGERECHTE (22.10.): im 2 x 2 stand der Abstand UEBER der Linie als
   margin-top der zweiten Zeile -- die senkrechte Linie der ersten Zeile (border-left ihrer zweiten
   Kachel) endete damit 8px ueber der waagerechten, nur die der zweiten Zeile stiess an. Gemeldet in
   Shopping ("nur unten angesetzt"), gemessen in Ads bei 900: oben 160-218, Linie bei 226, unten
   226-317. Jetzt steht derselbe Abstand als Polster in der ersten Zeile (4 + 8 = 12, die Schrift
   bleibt, wo sie war), und die zweite Zeile beginnt an der Linie. Nur im 2 x 2: untereinander
   (.is-vnarrow) gibt es keine senkrechten Linien, dort bleibt der Rand. */
.up-root.is-narrow:not(.is-vnarrow) .up-kpiband.is-offen.is-4 > .up-kpi:nth-child(-n+2) { padding-bottom: 12px; }
.up-root.is-narrow:not(.is-vnarrow) .up-kpiband.is-offen.is-4 > .up-kpi:nth-child(n+3) { margin-top: 0; }
.up-root.is-vnarrow .up-kpiband.is-offen > .up-kpi { padding-left: 0; padding-right: 0; }
.up-root.is-vnarrow .up-kpiband.is-offen > .up-kpi + .up-kpi { padding-top: 12px; margin-top: 8px; }
/* ---------- Beispielplatten der Variations-Erklaerkarten (UC.variationsPlatte) ----------
   13.10. aus performance-detail.css hierher: brand-detail braucht dieselben. Jede Regel traegt
   auch den alten Namen (.upd-explain-vis/-sov), den performance-detail weiter mitgibt -- so bleibt
   der Vertrag ohne Entfernung, und die Deklarationen stehen trotzdem nur einmal da.
   Die Karte wird von UC.makeExplain an den body gehaengt, also ausserhalb jeder .up-root. Dort
   loest KEIN --vc-Token auf: der Ring waere ohne Strich, das "of N" ohne Farbe. Beides bekommt
   hier feste Werte, und zwar zweimal -- die Karte ist invertiert, im hellen Theme ist die Platte
   dunkel und umgekehrt.

   Die Platte soll aussehen wie die Zeile darunter, nur eine Spur groesser: 13px statt der 12px,
   die die geteilte Regel setzt. Nur an .up-varvis, damit die Erklaerkarten der anderen Komponenten
   unveraendert bleiben. */
/* gap: 6px: derselbe Abstand zwischen Zahl und Nebentext wie in der Zelle, damit die Probe die
   Zelle trifft. */
.up-varvis .up-explain-row, .upd-explain-vis .up-explain-row { font-size: 13px; gap: 6px; }
/* Zahl und Nebentext exakt wie in der Zelle darunter: 14px / 12px. Nur die Farben sind gedreht,
   weil die Platte invertiert ist -- die Groessen sollen sich nicht unterscheiden, sonst ist es kein
   Beispiel mehr. */
.up-varvis .up-num, .upd-explain-vis .up-num { font-size: 14px; font-weight: 500; color: inherit; }
.up-varvis .up-var-of, .upd-explain-vis .up-var-of { font-size: 12px; color: rgba(31,31,27,0.55); }
/* Die Share-of-Voice-Zeile steht weiter auseinander als die Mention-Count-Zeile: dort sind Zahl
   und "of N" ein zusammenhaengender Wert, hier sind es zwei Darstellungen derselben Groesse und
   duerfen als zwei Dinge lesbar sein. */
.up-varvis .up-varvis-sov, .upd-explain-vis .upd-explain-sov { gap: 32px; }
/* Der Ring behaelt seine echte Groesse. Die geteilte Regel .up-explain-row svg setzt 14px und ist
   genauso stark wie .up-varring svg -- ohne diese Zeile entschiede die Ladereihenfolge. */
.up-varvis .up-varring svg, .upd-explain-vis .up-varring svg { width: 22.4px; height: 22.4px; }
.up-varvis .up-varring-track, .upd-explain-vis .up-varring-track { stroke: #e4e2e4; }
.up-varvis .up-varring-fill, .upd-explain-vis .up-varring-fill { stroke: #1f1f1b; }
/* Dark: die Platte ist dunkel, also drehen sich Ring und Nebentext mit. */
.up-explain[data-theme="dark"] .up-varvis .up-var-of, .up-explain[data-theme="dark"] .upd-explain-vis .up-var-of { color: rgba(240,240,240,0.55); }
.up-explain[data-theme="dark"] .up-varvis .up-varring-track, .up-explain[data-theme="dark"] .upd-explain-vis .up-varring-track { stroke: #28282c; }
.up-explain[data-theme="dark"] .up-varvis .up-varring-fill, .up-explain[data-theme="dark"] .upd-explain-vis .up-varring-fill { stroke: #f0f0f0; }

/* DIE PROTOKOLL-TABELLE (13.10. aus team-orga.css hierher; Team-Log und Marken-Log): eine
   .up-table mit eigenen --up-cols je Protokoll. Kleiner, dichter, sachlich. 34px Zeile gegen 52 in den Tabellen darueber, 12px Schrift gegen
   13.5, und ueber 14 Zeilen ein eigener Ausschnitt statt einer Seite, die ins Endlose waechst. */
/* Doppelte Klasse: die Tabelle einer Komponente setzt ihre eigenen Zeilen (.uto-table .up-row:not(...),
   .uto-table .up-td) mit derselben Staerke -- ohne die zweite Klasse entschiede die Ladereihenfolge. */
.up-logtab.up-logtab .up-thead.up-row { height: 36px; flex: 0 0 36px; }
.up-logtab.up-logtab .up-row:not(.up-thead) { height: 34px; flex: 0 0 34px; }
/* Nur die Zellen: der Kopf traegt seit 15.10. spaet die Schrift jedes Tabellenkopfs (14/500) --
   "alle Header Rows" schliesst das Protokoll ein, der Inhalt bleibt dicht. */
.up-logtab.up-logtab .up-td { font-size: var(--up-fs-s); }
/* Ab 14 Zeilen ein eigener Ausschnitt statt einer Seite, die ins Endlose waechst. 476 = 36 Kopf
   + 13 x 34 -- also genau vierzehn Zeilen, danach scrollt es.
   Hier darf overflow stehen, im Gegensatz zum Kasten oben: im Protokoll gibt es kein Zeilenmenue,
   das herausfahren muesste. overflow-x bleibt hidden -- waagerecht wird nichts geschoben, dafuer
   fallen auf schmalen Seiten zwei Spalten weg (unten). */
.up-logtab.up-logtab { max-height: 476px; overflow-x: hidden; overflow-y: auto; }
/* Zeit und Ereignisname in Festbreitenschrift: in einem Protokoll liest man Spalten, nicht Saetze.
   Die Familie ist die des Systems -- diese App liefert keine eigene Festbreitenschrift aus, und
   eine nachgeladene waere ein Netzabruf fuer fuenf Zellen. */
/* 11 (--up-fs-xs) statt der 11.5 aus team-orga: auf die Skala gezogen, als der Baustein nach core kam. */
.up-log-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--up-fs-xs); letter-spacing: -0.01em; color: var(--vc-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-log-ev { color: var(--vc-text); }
/* Die drei Ereignisarten bekommen einen Punkt davor, damit man das Protokoll ueberfliegen kann:
   eingeladen, angenommen, zurueckgezogen. Die Toene sind die vorhandenen der App: --vt-up fuer
   angenommen, --vt-brand fuer eingeladen, die dritte Farbe fuer zurueckgezogen. Kein Rot: eine
   zurueckgezogene Einladung ist kein Fehler, sondern eine Entscheidung. */
.up-log-evwrap { display: flex; align-items: center; gap: 7px; min-width: 0; }
.up-log-dot { flex: 0 0 6px; width: 6px; height: 6px; border-radius: var(--up-r-voll); background: var(--vc-third); }
.up-log-dot.is-ok { background: var(--vt-up); }
.up-log-dot.is-off { background: var(--vc-third); }
.up-log-dot.is-new { background: var(--vt-brand, var(--vc-muted)); }
.up-log-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--vc-muted); }

/* DIE SPARKLINE (UC.sparkHtml, 04.10.): 72 x 24 neben dem Wert einer Kachel, Linie 1.5 in der
   Chartfarbe, der letzte Punkt als Marke. Keine Achse, keine Flaeche -- sie sagt nur die
   Richtung, die Zahl daneben sagt den Wert.
   Chartfarbe seit dem 23.10. wie UC.chartInk: --up-chart-ink im Modus "Standard + Blau", sonst die
   Akzent-Tinte. Vorher nur die Tinte -- im Modus Blau also die Schriftfarbe. */
.up-spark { display: block; width: 72px; height: 24px; overflow: visible; flex: 0 0 auto; }
.up-spark-linie { fill: none; stroke: var(--up-chart-ink, var(--up-accent-ink)); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.up-spark-punkt { fill: var(--up-chart-ink, var(--up-accent-ink)); stroke: none; }
.up-kpi-foot strong { font-weight: var(--up-fw-m); color: var(--vc-muted); }
/* DIE WERTE IN DER KACHEL SIND DIE DER TABELLENZELLEN (03.10.): .up-num, UC.sentHtml (Balken +
   Zahl) und .up-rank-group mit Raute -- nur groesser gestellt, wie in power-dashboard (.upw-kpi-val)
   und brand-detail (.ubd-kpi-val). Ohne diese Zeilen stand die Zahl in ihrer Zellgroesse (14px) in
   einer 22px-Kachel, und Rang und Sentiment sahen hier anders aus als ueberall sonst.
   Sentiment-Balken 4x16 wie in power-dashboard. Die Raute bleibt klein und in der dritten Farbe
   (03.10.: mit 20px "zu gross und zu dunkel" -- im Visibility-Chart steht sie 12px neben 14px
   Zahlen): 14px neben der 22px-Zahl, wie performance-detail sie neben 18px haelt (12). Der
   Vorher-Wert davor eine Stufe kleiner und zurueckgenommen: Raute in Zellgroesse 12. */
.up-kpi-val .up-num, .up-kpi-val .up-sent-val { font-size: inherit; font-weight: inherit; letter-spacing: inherit; }
.up-kpi-val .up-sent { gap: 8px; }
.up-kpi-val .up-sent-dot { width: 4px; height: 16px; }
.up-kpi-val .up-hash { width: 14px; height: 14px; }
.up-kpi-vorher .up-num, .up-kpi-vorher .up-sent-val { font-size: inherit; font-weight: inherit; color: inherit; }
.up-kpi-vorher .up-hash { width: 12px; height: 12px; }
/* Die Fusszeile traegt seit dem 03.10. einen Trend-Chip aus core ("vs. comparison" + Chip) statt
   fett gesetzten Texts -- dieselbe Einheit wie der Trend neben der Zahl. Chip in Fussgroesse:
   Schrift wie der Fuss, Pfeil 14 statt 16 (Faktor 11/13, gerundet). */
.up-kpi-foot { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 6px; }
.up-kpi-foot .up-trend { font-size: inherit; }
.up-kpi-foot .up-trend svg { width: 14px; height: 14px; }
.up-kpi.is-sk:not(.is-faecher) { gap: 10px; }
/* Seit dem 15.10. mit dem Geist-Schimmer: vorher stand die Kachel als einziges Skelett der Seite
   still neben pulsierenden Tabellenzeilen. */
.up-kpi-sk { display: block; height: 12px; border-radius: var(--up-sk-r); background: var(--vc-sk) var(--up-sk-verlauf); animation: var(--up-sk-bewegung); }
.up-kpi-sk-l { width: 40%; } .up-kpi-sk-v { width: 70%; height: 24px; } .up-kpi-sk-f { width: 55%; }
/* Das Skelett in den Faechern der Kachel (UC.kpiKarteSkelett mit opts.fuss, 22.10.): Abstand, Mindesthoehe
   der Zahlenzeile und Luecke im Fuss kommen aus den Regeln der Kachel oben. In Beschriftung und Fuss steht
   der Balken IN einer Textzeile (inline-block), damit die Zeile so hoch wird wie mit Text: ein Balken als
   Block brachte 12 statt 17 bzw. 14. Oben an der Zeile (top), nicht mittig: mittig ragte der 12er-Balken
   in der 11er-Fusszeile 0.08 unter die Schrift und machte die Zeile 14.08 hoch. Jede Fusszeile ist eine
   eigene Zeile wie die Fallzahl (.uev-konf). */
.up-kpi.is-faecher .up-kpi-label > .up-kpi-sk, .up-kpi.is-faecher .up-kpi-sk-zeile > .up-kpi-sk { display: inline-block; vertical-align: top; }
.up-kpi.is-faecher .up-kpi-sk-zeile { flex: 1 0 100%; }

/* ======================================================================
   Page Header Kit — shared by every page-headers/* component (first user:
   prompts-page-header.js). One dedicated component per page (see that
   file's own header comment for why), but they all share this exact
   meta/heading/description block plus the sliding-tab subpage nav below
   it, so both live here instead of being copy-pasted into each page's own
   CSS/JS. A future page header only needs its own PAGES array, heading/
   description text and event names -- everything visual is this kit.
   Markup shape a page header is expected to provide:
     <div class="up-root up-ph-root">
       <div class="up-ph-top">
         <div class="up-ph-left">
           <div class="up-ph-meta"><img class="up-ph-metalogo"/><span class="up-ph-metatxt">...</span></div>
           <h1 class="up-ph-heading">...</h1>
           <p class="up-ph-desc">...</p>
         </div>
         <button class="up-ph-addbtn up-export">...</button>
       </div>
       <div class="up-ph-nav" role="tablist"></div>  <!-- built by UC.makePageNav, see core.js -->
     </div>
   ====================================================================== */
/* Top padding is 16px, not 32: the meta row sits close to the page's own top edge. Everything that
   sits top-RIGHT (Add button, refresh/docs icons, KPI strips) used to carry a -16px margin to pull
   itself back up against the old 32px padding; with 16px there is nothing left to pull back, so
   those margins are 0 now and only the is-vnarrow variants below still offset. */
.up-ph-root { padding: 16px 0 16px; background: transparent; }
/* Die Bubble-Gruppe um den Seitenkopf (core.js kopfPolsterDurchlassen, 29.09.): ihre eigene leere
   Flaeche -- das untere Polster von 16px und die Luecken -- faengt keine Klicks mehr, sie gehen an
   das, was darunter liegt (die Toolbar der Topics-Verwaltung). Die direkten Kinder holen sich die
   Klicks zurueck, alles darin bleibt bedienbar. Nur pointer-events, keine Stapelung. */
.up-ph-gruppe { pointer-events: none; }
.up-ph-gruppe > * { pointer-events: auto; }

.up-ph-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.up-ph-left { display: flex; flex-direction: column; min-width: 0; }

/* margin-bottom 8px (not the 12px heading->desc gets) -- the meta line reads as a label sitting
   directly above the heading, not a peer paragraph, so it stays visually closer to it than the
   heading is to the description below. */
.up-ph-meta { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
/* ── Keine Meta-Zeile mehr in den SEITENKOEPFEN ──────────────────────────────────────────────
   Sie sagt nichts, was die Ueberschrift nicht schon sagt: in welchem Arbeitsbereich man steht,
   nennt die Seitenleiste. Erst am Dashboard entfernt, jetzt fuer alle sieben.
   display: none und nicht ausbauen -- der Einwand von damals ("ein LEERER Kasten hinterlaesst
   seine 8px") gilt fuer einen Kasten, der noch da ist. Ohne Kasten gibt es auch keinen
   Aussenabstand.
   Hier stand .up-ph-root als Haken, mit der Begruendung, die Meta-Zeile von teams.js sei
   ausdruecklich gewuenscht. Das war ein Missverstaendnis: gewuenscht war die Zeile UEBERALL weg.
   Und der Haken hat genau das verhindert -- prompt-research und teams bauen ihren Kopf selbst und
   tragen .up-ph-root nicht, also stand die Zeile dort weiter. Gemeldet am 01.09. fuer genau diese
   zwei Seiten. Jetzt ohne Haken. */
.up-ph-meta { display: none; }
/* grayscale(1): a brand's own logo colour would otherwise compete with this being a quiet,
   secondary line above the real heading. */
/* Tuned so pure BLACK lands on --vc-third (#808080, L=128) -- the colour of the label beside it.
   grayscale(1) alone cannot dim black at all: black is already fully desaturated, so it came
   through at full strength next to nicely muted colourful ones. contrast() below 1 pulls every
   luminance toward mid-grey, which is what lifts the black end; brightness() places the result.

   opacity() would hit the same number and leave colourful logos alone -- but it blends toward the
   BACKGROUND. Measured on #121212, a black logo drops back to L=9 with it: invisible in dark
   mode, which is the second half of the same bug. contrast/brightness works on the pixels, so the
   same value comes out on either surface. Measured through the filter engine itself
   (canvas ctx.filter), against white AND #121212:
     input        grayscale(1)   this
     black             0         127   <- the target
     #222             34         141
     brand blue      105         171
     brand red        83         163
     white           255         235

   The cost, named rather than hidden: no filter can tell a black logo from a blue one, so lifting
   the black end lifts everything with it -- colourful logos come out paler than they were. Black
   at the label's colour was the requirement; this is what it costs. */
.up-ph-metalogo {
  width: 15px; height: 15px; display: block; flex: 0 0 auto; object-fit: cover;
  /* 4px, damit das Logo nicht als scharfkantiges Quadrat neben dem runden Rest der Zeile sitzt.
     Deckt auch den eingebauten Seitenkopf in prompt-research ab -- der nutzt dieselbe Klasse. */
  border-radius: var(--up-r-xs);
  filter: grayscale(1) contrast(0.30) brightness(1.43);
}
.up-ph-metatxt { font-size: 12px; font-weight: 500; color: var(--vc-third); line-height: 1; }

.up-ph-heading { margin: 0 0 12px; font-size: 28px; font-weight: 500; color: var(--vc-text);
  letter-spacing: -0.5px; line-height: 1.2; }
/* 15px puts the description at ~54% of the 28px heading -- inside the ~50-57% ratio typical for a
   subtitle under an H1; 17px (61%) read a shade too close to the heading's own weight. */
/* 16/450 statt 15/400 -- erst am Dashboard erprobt, jetzt fuer JEDEN Seitenkopf. Die Zeile ist
   die einzige Erklaerung, die eine Seite ueber sich abgibt; bei 15/400 stand sie als Kleingedrucktes
   unter der Ueberschrift. Der Wert stand vorher als Sonderfall in dashboard-page-header.css und ist
   dort raus: ein Bauteil, zwei Groessen waere die naechste Meldung. */
.up-ph-desc { margin: 0; font-size: 16px; font-weight: 450; color: var(--vc-muted); line-height: 1.4; }

.up-ph-addbtn { flex: 0 0 auto; margin-top: 0; }
/* Belegt, solange der Lauf dauert, den der Knopf angestossen hat (30.09., zuerst "Look for new
   Opportunities"): gedimmt und ohne Zeiger, damit ein zweiter Klick keinen zweiten Lauf startet.
   Was gerade passiert, zeigt das Ladebild auf der Seite (UC.makeLadebild) -- der Knopf sagt nur,
   dass er gerade nichts annimmt. */
.up-ph-addbtn.is-busy { opacity: .55; pointer-events: none; cursor: default; }

/* 32x32, icon-only, primary color at rest (var(--vc-text)) with a hover background -- deliberately
   NOT .up-iconbtn (that one is muted-by-default, primary-on-hover) or .up-export (a filled button):
   this is its own third state a page header can reuse wherever it needs an icon-only utility
   action next to its Add/tool buttons. First used by dashboard-page-header.js's Refresh button,
   promoted here once prompts-page-header.js needed the identical one. */
/* Der Zeichenknopf oben rechts in JEDEM Seitenkopf (Refresh, Docs, Suche) -- und ab jetzt
   derselbe Knopf wie die Ausgaenge oben rechts im Onboarding-Kopf (.uob-link). Die Geometrie war
   dort schon woertlich diese: 32x32, Radius 8, kein Rahmen, durchsichtig, Polster 0. Verschieden
   waren nur drei Dinge, und sie sind der Grund fuer diese Aenderung:

     Farbe        --vc-text        ->  --vc-muted, und erst beim Ueberfahren --vc-text. Ein
                                      Ausgang oder ein Nachladen ist nicht die Aussage der Seite;
                                      die Ueberschrift daneben ist es.
     Zeichen      16               ->  15, dasselbe Mass wie dort
     Fokusring    fehlte           ->  wie ueberall sonst

   Die Hoverflaeche bleibt --vc-heading-bg (das Onboarding hat dafuer ein eigenes #f7f8fa): das
   ist der Ton, den jeder ruhige Zeichenknopf der App beim Ueberfahren zeigt -- .up-iconbtn und
   .up-quietbtn eingeschlossen. Zwei Hovertoene im selben Kopf waeren der schlechtere Tausch. */
/* .dph-docsbtn STEHT HIER MIT DRIN, und das ist Absicht (15.09.). Ein eingebautes Bubble-Element
   bekommt Aenderungen an bubble/*.html nicht mit -- die Vorlage ist ein Bauplan, kein Update. Der
   Docs-Knopf trug frueher ein Buch-SVG PLUS das Wort "Docs" und nicht die Klasse .up-ph-iconbtn;
   dashboard-page-header.js raeumt das beim Init auf, aber bis dahin steht dort ein nackter
   HTML-Button mit Rahmen und Beschriftung. Genau so gemeldet und im Bild belegt.
   Warum in core.css und nicht in dashboard-page-header.css: core.css laedt in der ERSTEN Welle
   des Vorlade-Schnipsels, die Seitenkopf-CSS in der zweiten. Eine Regel in der zweiten Welle
   kaeme zu spaet fuer genau die Millisekunden, um die es geht.
   Gruppiert und nicht kopiert: dieselben Werte, ein Selektor mehr -- sonst haetten wir zwei
   Zeichenknoepfe, die auseinanderlaufen koennen (CLAUDE.md 1).
   font-size: 0 gehoert nur hier hin: es loescht eine Beschriftung, die im neuen Markup gar nicht
   mehr vorkommt. Bei .up-ph-iconbtn waere es eine Regel gegen ein Problem, das es dort nicht gibt. */
.up-ph-iconbtn, .dph-docsbtn { display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border: 0; border-radius: var(--up-r-m); background: transparent;
  color: var(--vc-muted); cursor: pointer; padding: 0; flex: 0 0 auto;
  transition: color 120ms ease, background-color 120ms ease; }
.dph-docsbtn { font-size: 0; line-height: 0; }
.up-ph-iconbtn:hover, .dph-docsbtn:hover { background: var(--vc-heading-bg); color: var(--vc-text); }
.up-ph-iconbtn:focus-visible, .dph-docsbtn:focus-visible { outline: 0; box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); }
.up-ph-iconbtn svg, .dph-docsbtn svg { width: 15px; height: 15px; display: block; }
/* One full turn, ease-in-out, on click -- triggered via UC.spinOnce(btn) (core.js), which
   toggles this class off/on with a forced reflow in between so a second click mid-spin restarts
   the animation instead of doing nothing (the class name alone wouldn't change, so a plain
   classList.add on an already-spinning button is a no-op without that reset). */
@keyframes up-ph-spin { to { transform: rotate(360deg); } }
/* Gedreht wird die Huelle (UC.spinOnce legt sie beim ersten Mal um das Zeichen): ein HTML-Element,
   dessen Transformation der Compositor uebernimmt -- am svg selbst nicht, dort ruckelte es, sobald
   die Seite beim Aktualisieren rechnete. > svg bleibt fuer Knoepfe ohne Huelle. */
.up-spinhuelle { display: block; line-height: 0; }
.up-ph-iconbtn.is-spinning > svg,
.up-ph-iconbtn.is-spinning > .up-spinhuelle { animation: up-ph-spin 1s ease-in-out; will-change: transform; }

/* ---------- subpage nav: N tabs + a sliding active indicator (built by UC.makePageNav) ---------- */
.up-ph-nav { display: flex; position: relative; margin-top: 32px; gap: 4px; }
/* The continuous 1px baseline every item's own indicator sits on top of -- ONE shared separator
   spanning the full nav width, not a copy under each item. Both this and .up-ph-navunderline
   anchor to the same bottom:0, so neither ever causes a layout shift switching between the 1px
   (unselected) and 2px (selected) look. */
.up-ph-nav::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--vc-border); }
/* Rechts 3px mehr als links (29.09. angefordert: erst 2 -- "dann sieht es symmetrischer aus mit
   den Icons" --, am Abend noch 1 dazu: "so dass der aktive Strich 1px rechts weiter geht"). Das
   Zeichen vorne hat in seinem 16er-Kasten eigene Luft, die Schrift hinten keine -- bei gleichem
   Polster stand der Strich darunter links sichtbar weiter ueber als rechts. */
.up-ph-navitem { display: flex; align-items: center; justify-content: center; gap: 6px;
  height: 32px; padding: 0 15px 0 12px; cursor: pointer; user-select: none; }
/* Ein Reiter mit Adresse ist seit 28.10. ein Link (makePageNav cfg.href): ohne Unterstrich, Farben tragen Zeichen und Wort. */
a.up-ph-navitem { text-decoration: none; color: inherit; }
/* Die NICHT gewaehlten Reiter in der Drittfarbe, Zeichen und Schrift (29.09. angefordert, vorher
   --vc-muted). Damit steht die Reiterzeile im selben Ton wie die Brotkrumen darueber, deren
   Vorstufen auch --vc-third tragen; Hover hebt weiter auf --vc-text, gewaehlt bleibt Akzent. */
.up-ph-navicon { display: inline-flex; flex: 0 0 auto; color: var(--vc-third); transition: color 120ms ease; }
.up-ph-navicon svg { width: 16px; height: 16px; display: block; }
/* DAS MARKENLOGO ALS REITERZEICHEN (Settings: "Your Brand"). Seit dem 11.10. hier statt in
   settings-page-header.css: der alte Kopf UND die Seite Settings (settings-page.js) zeigen es, und
   zwei Kopien liefen auseinander. Groesse: .up-ph-navicon misst nur SVG, ein <img> ohne Mass kaeme
   in seiner natuerlichen Aufloesung. Der Selektor ohne Elternteil, wie er vorher in
   settings-page-header.css stand. Ohne grayscale -- das Logo ist das Erkennungszeichen des
   Reiters; nicht gewaehlt wird es ueber die Deckkraft leiser, weil color ein Bild nicht faerbt. */
:is(.sph-navlogo, .uss-navlogo) {
  width: 16px; height: 16px; display: block; flex: 0 0 auto; object-fit: cover; border-radius: var(--up-r-xs); }
.up-ph-navitem:not(.is-selected) :is(.sph-navlogo, .uss-navlogo) { opacity: .55; transition: opacity var(--up-t-1) ease; }
.up-ph-navitem:not(.is-selected):hover :is(.sph-navlogo, .uss-navlogo) { opacity: 1; }
.up-ph-navlabel { font-size: 14px; font-weight: 500; color: var(--vc-third); white-space: nowrap; transition: color 120ms ease; }
/* DER GEWAEHLTE REITER GANZ IN DER AKZENTFARBE (18.09. angefordert): Zeichen, Schrift und der
   Streifen darunter. Nur hier -- die Seitenleiste bleibt in ihrer Schriftfarbe, dort ist die
   Auswahl eine Flaeche und kein Strich.
   --up-accent-ink und nicht --up-accent: hier liegt die Farbe als SCHRIFT auf dem Seitengrund,
   nicht als Flaeche unter einer Schrift. Der Unterschied ist gemessen, siehe oben beim Token. */
.up-ph-navitem.is-selected .up-ph-navicon,
.up-ph-navitem.is-selected .up-ph-navlabel { color: var(--up-accent-ink); }
.up-ph-navitem:not(.is-selected):hover .up-ph-navicon,
.up-ph-navitem:not(.is-selected):hover .up-ph-navlabel { color: var(--vc-text); }
/* The active indicator: slides left/width to the newly selected tab on click (200ms ease, set in
   JS via inline style so it can animate to an arbitrary measured position/width -- a CSS-only
   nth-child approach can't do that for a JS-built, reorderable list). Position is set with
   transition:none on first paint (see UC.makePageNav) so it doesn't grow in from 0 on load.
   z-index:1 -- without it this paints BEHIND .up-ph-nav::after: a ::after's generated box is
   always painted after all of its element's real children, regardless of DOM/append order, so the
   grey 1px separator would silently cover the bottom half of this 2px indicator. */
.up-ph-navunderline { position: absolute; bottom: 0; left: 0; width: 0; height: 1px; background: var(--up-accent-ink);   /* 1 statt 2 (18.10.) */
  z-index: 1; transition: left 200ms ease, width 200ms ease; }
/* DER GEWAEHLTE REITER IN EINEM BEHAELTER (13.10. angefordert: "leichter Field-Background und
   Border, eher kleines Padding"; Rundung zweite Runde 8 statt 12). Er fuellt den Reiter in der
   Breite GENAU -- die Luft um Zeichen und Schrift ist das Polster des Reiters selbst. So ist der
   Akzentstrich darunter genau so lang wie der Behaelter, und der erste Behaelter beginnt an der
   Kante des Inhalts statt im 16px-Rand (beides 13.10. angefordert).
   Der Behaelter ist .up-ph-navpille (makePageNav): unter den Reitern, von links nach rechts so breit
   wie der gewaehlte Reiter -- dieselbe Fahrt wie der Strich (200ms ease) und wie die Pille der
   Umschalter (.is-gleitend, 200ms ease). Die Reiter liegen darueber (z-index 1). */
.up-ph-navitem { position: relative; z-index: 1; }
.up-ph-navpille { position: absolute; top: 0; bottom: 0; left: 0; width: 0; z-index: 0; pointer-events: none;
  background: var(--vc-heading-bg); border: 0; border-radius: var(--up-r-l);   /* 18.10.: ohne Rahmen, Radius eine Stufe hoeher (6 -> 8) */
  transition: left 200ms ease, width 200ms ease; }

/* ---------- DER KOMPAKTE SEITENKOPF MIT BROTKRUMEN (29.09. angefordert) ----------
   "Aktuell nehmen die mir zu viel Platz ein und sind groesstenteils nicht aligned mit dem Layout
   der Sidebar." Also: keine Beschreibung mehr, die Ueberschrift wird zu kleinen Brotkrumen
   (UC.makePageCrumbs), und die Kopfzeile bekommt eine Trennlinie auf GENAU der Hoehe der Linie in
   der Seitenleiste.
   Die Zahlen sind abgelesen, nicht gewaehlt: .usn-bar hat 8px Polster oben, .usn-brand ist 32px
   hoch mit 8px darunter, ihre Linie liegt also bei y 47 bis 48. Hier dasselbe: 8 oben, eine
   32px-Zeile (die Hoehe der Knoepfe rechts, --up-h-btn), 8 darunter, die Linie im letzten Pixel.
   Derselbe Abgleich stand schon am Power-Kopf des Dashboards und hat dort gepasst.
   ZUERST fuer vier Seiten (Dashboard, Prompt Insights, Citations, Brands), am 29.09. nachmittags
   die drei uebrigen eigenen Koepfe dazu (Performance, Opportunities, Settings). Die Liste im
   :is() bleibt trotzdem: .up-ph-root allein traefe auch die Vorfuehrkoepfe der Landingpage
   (landing-hero.js), die ihre eigenen Masse tragen. Teams und Prompt Research bauen ihren Kopf
   IN der Komponente -- fuer sie steht der Block weiter unten (.uts-pagehead, .upr-pagehead).
   Ueber die Wurzelklassen und nicht ueber .has-crumbs: die Geometrie soll stehen, BEVOR das JS
   laeuft, sonst springt der Kopf beim Laden von 164px auf 48. */
/* SEIT DEM 29.09. ABENDS RECHTS 8 STATT 16: "das Padding runter auf das, was bei den 32er-Knoepfen
   an Luft nach oben und unten zur Linie ist" -- 8, gemeint rechts (eine erste Fassung setzte
   links, zurueckgenommen). Links bleibt 16. Die Zahlen setzt UC.makePageCrumbs
   (KOPF_EINZUG_L / KOPF_EINZUG_R), die Rechnung darunter gilt unveraendert.
   DER INHALT DES SEITENKOPFS STEHT IMMER 16px VOM RAND -- nie mehr, nie weniger (29.09.).
   Drei Meldungen zu genau dieser Zahl: erst 32 (16 aus Bubble plus 16 hier, "16px zu viel"), dann
   0 (hier raus, Bubble-Polster auch weg: der Strich des ersten Reiters lag auf den 16px, "die
   eigentlich der duenne Separator sein sollten"). Also rechnet UC.makePageCrumbs aus, wie viel
   Polster die Bubble-Gruppe schon gibt, und legt hier nur den Rest auf 16 drauf
   (--up-ph-innen-l/-r). Die Linien laufen davon unabhaengig bis an die Kante der Gruppe. */
.up-ph-root:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) {
  padding-top: 8px; padding-left: var(--up-ph-innen-l, 0px); padding-right: var(--up-ph-innen-r, 0px);
  /* Gibt Bubble MEHR als 16 (Gruppenpolster plus Versatz des HTML-Elements), zieht sich das Element
     um den Ueberschuss nach aussen und wird um genau so viel breiter. */
  margin-left: calc(-1 * var(--up-ph-zug-l, 0px)); margin-right: calc(-1 * var(--up-ph-zug-r, 0px));
  width: calc(100% + var(--up-ph-zug-l, 0px) + var(--up-ph-zug-r, 0px)); }
.up-ph-root:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-desc,
.up-ph-top:is(.uts-pagehead, .upr-pagehead, .uev-pagehead, .ush-pagehead, .uad-pagehead, .ucs-pagehead, .uds-pagehead, .upf-pagehead, .uop-pagehead, .upi-pagehead, .ubr-pagehead, .uss-pagehead, .uvb-pagehead, .upa-pagehead, .umc-pagehead) .up-ph-desc { display: none; }
.up-ph-root:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-top {
  align-items: center; min-height: var(--up-h-btn); padding-bottom: 8px; position: relative; }
/* Die Linie laeuft bis an die Kante der Bubble-Gruppe um den Kopf, also so weit UEBER das Element
   hinaus, wie die Gruppe Polster hat -- 16 oder 0, je nachdem, ob es in Bubble schon raus ist.
   UC.makePageCrumbs misst das (--up-ph-rand-l/-r); ohne Messung liegt sie unter dem Element. */
.up-ph-root:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-top::after,
.up-ph-top:is(.uts-pagehead, .upr-pagehead, .uev-pagehead, .ush-pagehead, .uad-pagehead, .ucs-pagehead, .uds-pagehead, .upf-pagehead, .uop-pagehead, .upi-pagehead, .ubr-pagehead, .uss-pagehead, .uvb-pagehead, .upa-pagehead, .umc-pagehead)::after {
  content: ""; position: absolute; bottom: 0; height: 1px; pointer-events: none;
  left: calc(-1 * var(--up-ph-rand-l, 0px)); right: calc(-1 * var(--up-ph-rand-r, 0px));
  background: var(--vc-border); }
/* Die Ueberschrift als Krumenzeile: 14px, auf der Mitte der 32px-Zeile. Vor dem JS steht hier der
   Seitenname aus der Vorlage -- in derselben Groesse, also ohne Sprung. */
.up-ph-root:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-heading,
.up-ph-top:is(.uts-pagehead, .upr-pagehead, .uev-pagehead, .ush-pagehead, .uad-pagehead, .ucs-pagehead, .uds-pagehead, .upf-pagehead, .uop-pagehead, .upi-pagehead, .ubr-pagehead, .uss-pagehead, .uvb-pagehead, .upa-pagehead, .umc-pagehead) .up-ph-heading {
  display: flex; align-items: center; gap: 6px; min-width: 0; margin: 0;
  font-size: var(--up-fs-l); font-weight: 500; line-height: var(--up-h-btn); letter-spacing: 0;
  color: var(--vc-third); white-space: nowrap; }
/* DIE REITERLEISTE IST SO HOCH WIE DIE KOPFZEILE (29.09. spaet angefordert: "dass die Topbar und
   die Bar darunter mit der Subpage gleich gross sind"). Kopfzeile: 8 + 32 + 8 = 48 bis zur ersten
   Linie. Reiterleiste: von dieser Linie bis zu ihrer eigenen Grundlinie ebenfalls 48 -- ohne
   Abstand darueber, die Reiter selbst 48 hoch, ihr Text also auf der Mitte der Leiste statt am
   Boden. Der Strich des gewaehlten Reiters sitzt weiter auf der Grundlinie.
   Auch ihre Grundlinie laeuft bis an die Kante der Bubble-Gruppe, wie die erste. */
/* DAS ZEICHEN DES ERSTEN REITERS STEHT GENAU UNTER DEM ZEICHEN DER KRUMENZEILE (29.09.
   angefordert: "wenn in der Topbar ein Icon waere, dann waere dieses Icon mittig exakt ueber dem
   Icon des Subpage-Nav-Items darunter"). Beide Zeichen sind 16 breit, also muessen ihre linken
   Kanten gleich liegen: auf dem Inhaltsabstand der Kopfzeile. Mit den 12px Seitenpolster im
   Reiter lag das Zeichen darunter 12px weiter rechts. Hier hat der Reiter keins: sein Kasten IST
   Zeichen plus Beschriftung, und der Strich des gewaehlten Reiters -- UC.makePageNav misst den
   Kasten -- beginnt am Zeichen statt 12px davor. Damit bleibt er auch aus dem Randstreifen, auf
   dem nur die duenne Linie liegen soll.
   Der Abstand von einer Beschriftung zum naechsten Zeichen bleibt, was er war: 12 + 4 + 12 = 28.
   Die Klickflaeche auch -- ::before reicht je die halbe Luecke nach aussen, die zwei Haelften
   treffen sich in der Mitte.
   RECHTS 3px Polster (29.09.: nachmittags 2, "symmetrischer mit den Icons"; abends 1 mehr, "so
   dass der aktive Strich 1px rechts weiter geht"): der Strich laeuft 3px ueber das Textende
   hinaus. Die Luecke wird dafuer 25 -- vom Textende bis zum naechsten Zeichen bleiben es
   3 + 25 = 28, kein Reiter rueckt. SEIT DEM 13.10. (Behaelter um den gewaehlten Reiter): 8
   Polster links und rechts am Reiter, Luecke 12 -- Schrift zu Schrift weiter 8 + 12 + 8 = 28.
   ::before links und rechts je 6: zusammen genau die Luecke, ohne dass sich Reiter ueberlappen. */
.up-root.uvb-root .up-ph-nav,
.up-root.ucs-root .up-ph-nav,
.up-root.uad-root .up-ph-nav,
.up-root.upi-root .up-ph-nav,
.up-root.ubr-root .up-ph-nav,
.up-root.uss-root .up-ph-nav,
.up-root.ush-root .up-ph-nav,
/* Page Audit seit dem 22.10. abends auch hier (Lukas: "behandle die Navigation, als waere sie eine Subpage-Nav"): vorher stand
   sie mit den 32 des allgemeinen .up-ph-nav unter der Kopfzeile, gemessen 32 statt 0 wie in Brands und Citations. */
.up-root.upa-root .up-ph-nav,
/* Der Brand-Editor seit dem 27.10. (Reiter Overview und History im Drawer, Lukas: "korrektes styling"): dieselben
   Reiter wie die Seiten, in allen Listen hier. Den Abstand zum Kopf und die Grundlinie bis an die Kanten des Drawers
   setzt brand-editor.css; schmal wird er erst unter 500 (narrowAt in brand-editor-drawer.js). */
.up-root.ube-root .up-ph-nav,
.up-ph-root:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-nav { margin-top: 0; gap: 12px; }
.up-root.uvb-root .up-ph-navitem,
.up-root.ucs-root .up-ph-navitem,
.up-root.uad-root .up-ph-navitem,
.up-root.upi-root .up-ph-navitem,
.up-root.ubr-root .up-ph-navitem,
.up-root.uss-root .up-ph-navitem,
.up-root.ush-root .up-ph-navitem,
.up-root.upa-root .up-ph-navitem,
.up-root.ube-root .up-ph-navitem,
.up-ph-root:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-navitem {
  height: calc(var(--up-h-btn) + 16px); padding: 0 8px; position: relative; }
.up-root.ucs-root .up-ph-navitem::before,
.up-root.uad-root .up-ph-navitem::before,
.up-root.upi-root .up-ph-navitem::before,
.up-root.ubr-root .up-ph-navitem::before,
.up-root.uss-root .up-ph-navitem::before,
.up-root.ush-root .up-ph-navitem::before,
.up-root.upa-root .up-ph-navitem::before,
.up-root.ube-root .up-ph-navitem::before,
.up-ph-root:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-navitem::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: -6px; right: -6px; }
/* Der ERSTE Reiter reicht nach links hoechstens bis an die Kante der Gruppe -- bei einem Einzug
   unter 13 laege sonst ein Teil der Klickflaeche ueber dem Rand der Seitenleiste. */
.up-root.ucs-root .up-ph-navitem:first-child::before,
.up-root.uad-root .up-ph-navitem:first-child::before,
.up-root.upi-root .up-ph-navitem:first-child::before,
.up-root.ubr-root .up-ph-navitem:first-child::before,
.up-root.uss-root .up-ph-navitem:first-child::before,
.up-root.ush-root .up-ph-navitem:first-child::before,
.up-root.upa-root .up-ph-navitem:first-child::before,
.up-root.ube-root .up-ph-navitem:first-child::before,
.up-ph-root:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-navitem:first-child::before { left: calc(-1 * min(6px, var(--up-ph-rand-l, 0px))); }
/* ZWEITE RUNDE (13.10.: "Padding ganz minimal kleiner, Akzentstrich auf ganzer Laenge, nicht ueber
   die linke Grenze"): der Behaelter ist 28 hoch (--up-h-seg, die naechste Stufe unter 32) und
   genau so breit wie der Reiter, der dafuer 8 Polster links und rechts traegt (vorher 10 Luft, der
   Behaelter ragte um 10 nach links in den Rand). Der Abstand zwischen den Reitern sinkt um diese
   2 x 8 von 25 auf 12 -- von Schrift zu Schrift bleiben es 28 wie vorher.
   Folge: das erste Reiterzeichen steht 8 weiter rechts als das Krumenzeichen darueber. */
.up-root:is(.ucs-root, .uad-root, .upi-root, .ubr-root, .uss-root, .ush-root, .uvb-root, .upa-root, .ube-root) .up-ph-navpille,
.up-ph-root:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-navpille {
  top: 10px; bottom: 10px; }
/* Im SCHMALEN Kopf (is-narrow, unter 768) teilen sich die Reiter die Breite (flex 1 1 0, Inhalt
   mittig) -- dort gibt es keine linke Kante, die unter dem Krumenzeichen stehen koennte. Also die
   alte Geometrie: 4px Luecke und 12px Seitenpolster, sonst laegen die gestreckten Reiter 28px
   auseinander. Und keine verlaengerte Klickflaeche: bei 4px Luecke griffe sie 10px in den
   Nachbarn, und der spaetere Nachbar liegt oben -- ein Klick rechts in Reiter 1 waehlte Reiter 2.
   Der Platzhalter (.uvb-root) steht mit in den Listen, seit er die echten Reiter zeichnet (14.10.):
   sonst lagen seine Reiter im schmalen Fenster 12 statt 4 auseinander und sprangen beim Tausch. */
.up-root.ucs-root.is-narrow .up-ph-nav,
.up-root.uad-root.is-narrow .up-ph-nav,
.up-root.upi-root.is-narrow .up-ph-nav,
.up-root.ubr-root.is-narrow .up-ph-nav,
.up-root.uss-root.is-narrow .up-ph-nav,
.up-root.ush-root.is-narrow .up-ph-nav,
.up-root.upa-root.is-narrow .up-ph-nav,
.up-root.uvb-root.is-narrow .up-ph-nav,
.up-root.ube-root.is-narrow .up-ph-nav,
.up-ph-root.is-narrow:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-nav { gap: 4px; }
.up-root.ucs-root.is-narrow .up-ph-navitem,
.up-root.uad-root.is-narrow .up-ph-navitem,
.up-root.upi-root.is-narrow .up-ph-navitem,
.up-root.ubr-root.is-narrow .up-ph-navitem,
.up-root.uss-root.is-narrow .up-ph-navitem,
.up-root.ush-root.is-narrow .up-ph-navitem,
.up-root.upa-root.is-narrow .up-ph-navitem,
.up-root.uvb-root.is-narrow .up-ph-navitem,
.up-root.ube-root.is-narrow .up-ph-navitem,
.up-ph-root.is-narrow:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-navitem { padding: 0 15px 0 12px; }
/* Schmal: die Pille folgt dem gestreckten Reiter von selbst (gemessen wird sein Rechteck). */
.up-root.ucs-root.is-narrow .up-ph-navitem::before,
.up-root.uad-root.is-narrow .up-ph-navitem::before,
.up-root.upi-root.is-narrow .up-ph-navitem::before,
.up-root.ubr-root.is-narrow .up-ph-navitem::before,
.up-root.uss-root.is-narrow .up-ph-navitem::before,
.up-root.ush-root.is-narrow .up-ph-navitem::before,
.up-root.upa-root.is-narrow .up-ph-navitem::before,
.up-root.ube-root.is-narrow .up-ph-navitem::before,
.up-ph-root.is-narrow:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-navitem::before { content: none; }
.up-ph-root:is(.dph-root, .pph-root, .cph-root, .bph-root, .pfph-root, .oph-root, .sph-root) .up-ph-nav::after {
  left: calc(-1 * var(--up-ph-rand-l, 0px)); right: calc(-1 * var(--up-ph-rand-r, 0px)); }
/* Die Seiten mit eigener Wurzel (cfg.komponente): dort zieht sich nur die KOPFZEILE rechts um die
   Differenz nach aussen (--up-ph-kopfzug-r, 8), die Reiterleiste steht weiter in der Wurzel. Ihre
   Grundlinie muss also um genau diesen Zug weiter laufen -- gemessen am 13.10. in der
   Next.js-Huelle: Kopflinie bis 1024, Reiterlinie bis 1016, also 8 vor dem Rand. In Bubble
   dieselbe Rechnung. */
.up-root:is(.ucs-root, .uad-root, .upi-root, .ubr-root, .uss-root, .ush-root, .uvb-root, .upa-root) .up-ph-nav::after {
  left: calc(-1 * (var(--up-ph-rand-l, 0px) + var(--up-ph-kopfzug-l, 0px)));
  right: calc(-1 * (var(--up-ph-rand-r, 0px) + var(--up-ph-kopfzug-r, 0px))); }
.up-ph-crumb { display: inline-flex; align-items: center; gap: 6px; min-width: 0; color: var(--vc-third); }
.up-ph-crumb.is-akt { color: var(--vc-text); }
/* Das Zeichen der Seite vor ihrem Namen, in dessen Farbe (erbt von .up-ph-crumb). 16 breit wie
   das Reiterzeichen darunter -- die Gleichheit ist die Ausrichtung, siehe oben. */
.up-ph-crumbic { display: inline-flex; flex: 0 0 auto; }
.up-ph-crumbic svg { width: 16px; height: 16px; display: block; }
.up-ph-crumbsep { display: inline-flex; flex: 0 0 auto; color: var(--vc-third); }
.up-ph-crumbsep svg { width: 14px; height: 14px; display: block; }
.up-ph-crumbname { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Eine fruehere Stufe als Knopf (Events: "Events > Overview > Name"). Sieht aus wie die Krume,
   bekommt beim Ueberfahren die Farbe der aktuellen Stufe -- kein Unterstrich, keine Flaeche. */
/* Seit 28.10. auch als Link (a, mit Adresse): dieselbe Krume, ohne Unterstrich. */
:is(button, a).up-ph-crumb { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; text-decoration: none;
  transition: color var(--up-t-1) var(--up-ease); }
:is(button, a).up-ph-crumb:hover, :is(button, a).up-ph-crumb:focus-visible { color: var(--vc-text); outline: none; }

/* Ein Kopf OHNE Reiterleiste endet an seiner Linie (29.09. nachmittags, Performance und
   Opportunities): wie der Dashboard-Kopf im Analysemodus. Den Abstand zum naechsten Block gibt
   darunter die Bubble-Gruppe (16), sonst stuenden 32 unter einer einzelnen Zeile. */
.up-ph-root:is(.pfph-root, .oph-root) { padding-bottom: 0; }

/* ---------- DER KOMPAKTE KOPF IN EINER KOMPONENTE (29.09. nachmittags) ----------
   Teams und Prompt Research bauen ihren Seitenkopf SELBST, als erstes Kind ihrer Wurzel
   (.uts-pagehead, .upr-pagehead) -- ein eigenes Kopf-Element mit eigener Bubble-Gruppe gibt es
   dort nicht. Also misst UC.makePageCrumbs die Komponente: sie steht wie ein Kopf in ihrer
   Gruppe, und dieselbe Rechnung legt ihren GANZEN Inhalt -- Kopf, Tabelle, Startfeld -- auf 16px
   vom Rand (--up-ph-innen-l/-r) oder zieht sie um Bubbles Ueberschuss hinaus (--up-ph-zug-l/-r).
   Damit ist es gleich, ob in Bubble noch 16 Polster an der Gruppe stehen oder 0: der Inhalt
   steht bei 16, die Linie unter dem Kopf laeuft bis an die Kante der Gruppe (--up-ph-rand-l/-r).
   Ueber .has-crumbs: die setzt makePageCrumbs im selben Zug, in dem die Komponente ihren Kopf
   baut -- vorher steht noch nichts auf der Seite, das springen koennte.
   Beschreibung, Linie und Ueberschrift teilen sich die Regeln des kompakten Kopfs oben. Hoehe
   und Luft der Zeile stehen in der Komponente (teams.css, prompt-research.css), weil dort ihr
   Regler data-head-top sitzt; hier nur, was keinen Regler braucht. */
.up-root:is(.uts-root, .upr-root, .uev-root, .ush-root, .uad-root, .ucs-root, .uds-root, .upf-root, .uop-root, .upi-root, .ubr-root, .uss-root, .uvb-root, .upa-root).has-crumbs {
  padding-left: var(--up-ph-innen-l, 0px); padding-right: var(--up-ph-innen-r, 0px);
  margin-left: calc(-1 * var(--up-ph-zug-l, 0px)); margin-right: calc(-1 * var(--up-ph-zug-r, 0px));
  width: calc(100% + var(--up-ph-zug-l, 0px) + var(--up-ph-zug-r, 0px)); }
/* Die Kopfzeile steht rechts bei 8 wie in jedem Seitenkopf, der Inhalt der Komponente darunter
   bei 16: also zieht sie sich um die Differenz nach aussen (--up-ph-kopfzug-l/-r aus
   UC.makePageCrumbs, ohne JS 0; links ist die Differenz heute 0). */
.up-ph-top:is(.uts-pagehead, .upr-pagehead, .uev-pagehead, .ush-pagehead, .uad-pagehead, .ucs-pagehead, .uds-pagehead, .upf-pagehead, .uop-pagehead, .upi-pagehead, .ubr-pagehead, .uss-pagehead, .uvb-pagehead, .upa-pagehead, .umc-pagehead) { align-items: center; min-height: var(--up-h-btn); position: relative;
  margin-left: calc(-1 * var(--up-ph-kopfzug-l, 0px)); margin-right: calc(-1 * var(--up-ph-kopfzug-r, 0px)); }

/* ---------- DER UMSCHALTER DER SEITENLEISTE IN DER KOPFZEILE (16.10.) ----------
   "Sidebar-Button im Mobile: in die richtige Topbar, ganz links, nicht floaten" -- an der Stelle
   des schwebenden .usn-fab (sidebar.css). core.js (seitenleistenKnopf) setzt ihn in JEDEN
   Seitenkopf als erstes Kind der Kopfzeile, Mira (mira-page.js) in ihren eigenen Kopf. Zu sehen
   ist er nur, wenn die Seitenleiste ganz ausgeblendet ist: sidebar.js schreibt ihren Zustand an
   <html> (data-usn-modus, heute "hint" unter 500px Fensterbreite). Ohne Seitenleiste auf der
   Seite steht das Attribut nicht da, und der Knopf bleibt weg.
   Zwei Klassen gegen das display von .up-ph-iconbtn: ueber die Spezifitaet, nicht die Stelle. */
.up-ph-iconbtn.up-ph-seitenleiste { display: none; }
html[data-usn-modus="hint"] .up-ph-iconbtn.up-ph-seitenleiste { display: inline-flex; }
/* LAGE IN DER KOPFZEILE. Der Knopf steht links so, wie die Knoepfe rechts stehen: 8 vom Rand der
   Gruppe (--up-ph-rand-r), in der 48er-Zeile also 8 ueber, unter und neben sich. Sein Zeichen
   (16 in 32) steht damit bei 16 -- auf der Kante, an der ohne ihn das Krumenzeichen stand und
   an der die Bloecke darunter beginnen. Die Kopfzeile beginnt bei --up-ph-rand-l (16), also
   zieht er sich um die Differenz nach aussen; ohne Messung gelten dieselben Vorgaben.
   Rechts nimmt er die halbe Luecke der Zeile (16) zurueck: zur Krume stehen 8 wie zwischen den
   Knoepfen rechts (.up-head-tools), von Zeichen zu Zeichen damit 16.
   Die Krume WAECHST in den freien Platz: .up-ph-top verteilt mit space-between, und mit drei
   Kindern stuende sie sonst in der Mitte zwischen Knopf und Werkzeugen. Nur, solange der Knopf
   zu sehen ist -- auf dem Desktop bleibt die Zeile, wie sie war. */
html[data-usn-modus="hint"] .up-ph-top > .up-ph-iconbtn.up-ph-seitenleiste {
  margin-left: calc(var(--up-ph-rand-r, 8px) - var(--up-ph-rand-l, 16px)); margin-right: -8px; }
html[data-usn-modus="hint"] .up-ph-top > .up-ph-seitenleiste + * { flex: 1 1 auto; min-width: 0; }
/* DIE UEBRIGEN MACHEN PLATZ ("natuerlich machen die Elemente darin dann Platz fuer den Button").
   Gemessen bei 375 mit dem Knopf: "Opportunities" endete in "Opportuniti..." neben "Look for new
   Opportunities", "Prompt Insights > All Prompts" in "Prompt ... > All Pr...". Zwei Stufen:
   1. Ein Hinzufuegen-Knopf zeigt seine kurze Fassung (.up-ph-addbtn-kurz, knopfEtikett), wie
      sonst im schmalen Kopf (is-narrow). Nur, wo die Wurzel KEINEN schmalen Kopf hat: is-narrow
      setzt nur makePageNav bzw. widthTiers, und Seiten ohne Reiter (Opportunities, Performance)
      hatten ihn auf dem Telefon nie. Wo er gesetzt ist, entscheiden weiter dessen Regeln -- auch
      die, die den Knopf ganz auf sein Zeichen bringen (is-vnarrow, Brands und Prompt Insights);
      eine Kurzfassung von hier wuerde sie wieder aufmachen.
   2. Reicht das nicht, faellt der Name der ERSTEN Krumenstufe weg und ihr Zeichen bleibt
      (.is-knapp, gemessen von krumeEinpassen in core.js): "Zeichen > All Prompts". Die Stufe, auf
      der man steht, bleibt immer ausgeschrieben. */
html[data-usn-modus="hint"] .up-root:not(.is-narrow) > .up-ph-top:has(> .up-ph-seitenleiste) .up-ph-addbtn .up-ph-addbtn-full { display: none; }
html[data-usn-modus="hint"] .up-root:not(.is-narrow) > .up-ph-top:has(> .up-ph-seitenleiste) .up-ph-addbtn .up-ph-addbtn-kurz { display: inline; }
html[data-usn-modus="hint"] .up-ph-heading.is-knapp > .up-ph-crumb:first-child:not(.is-akt) :is(.up-ph-crumbname, .up-ph-crumbmarke) { display: none; }

/* ---------- DIE LUFT UNTER DEM SEITENKOPF (15.10. spaet) ----------
   Von der Linie des Kopfes bzw. der Reiterleiste bis zur ersten Zeile darunter stehen 16
   (--up-band-luft), auf jeder Seite und in jedem Reiter: dieselbe Luft wie ueber und unter der
   Leiste der Prompt-Tabelle, dem Vorbild ("dass die Paddings top/bottom einheitlich sind").
   Steht eine Filterzeile dazwischen, traegt sie ihre 16 ueber sich selbst, und der Inhalt folgt
   16 unter ihr. Bis hierher stand in jeder der neun Seiten eine eigene Zeile mit 24 und in sechs
   davon eine zweite, die das unter der Filterzeile auf 16 zuruecksetzte -- ohne Filterzeile
   blieben 24 (Topics, Opportunities, Performance, Settings), mit ihr und in den Seiten mit
   eigenem Kopf (Teams, Events: 16 unter dem Kopf) 16. Gemessen 15.10.: der Reiter Topics stand
   8px tiefer als All Prompts und Responses daneben, und unter dem Platzhalter (.uvb-main, 16)
   sprang der Inhalt beim Tausch um 8. Unten 64 wie bisher; eine Seite mit fester Hoehe nimmt
   sie selbst zurueck (prompts-page.css). */
:is(.ucs-main, .uds-main, .upi-main, .ubr-main, .ush-main, .uad-main, .uop-main, .upf-main, .uss-main) {
  padding: var(--up-band-luft) 0 64px; }

/* ---------- DER PLATZHALTER EINER ANSICHT (.uvb-*, core.js seitenVorab, 13.10. abends) ----------
   Steht, solange die Dateien einer Ansicht beim ersten Besuch laden (Next.js-App), statt einer
   leeren Flaeche. Der Kopf steht oben in den Listen der Seitenkoepfe -- dieselbe Geometrie wie
   jede Seite, 8 ueber und unter der 32er-Zeile wie .ucs-pagehead und Geschwister. Darunter ein
   ruhiges Skelett in den Massen, mit denen jede Seite beginnt: Filterzeile in Knopfhoehe, Karten
   im Rahmen und Radius der Diagrammkarten (16, combo-box-left).
   Einblenden wie der Auftritt der Seiten (makeAuftritt: Deckkraft und 10px Weg, easeOutExpo,
   50ms Staffel), damit die Seite die Bewegung an derselben Stelle uebernehmen kann. */
.uvb-root { display: block; }
.uvb-root .uvb-pagehead { padding: 8px 0; animation: uvb-auf var(--up-t-auftritt) var(--up-ease-expo) both; }
/* Die Reiterleiste der Seiten mit Reitern (Prompts, Citations, Brands, Shopping, Ads, Settings):
   Hoehe und Grundlinie aus den Listen oben (.up-root.uvb-root .up-ph-nav/-navitem), die Reiter
   seit dem 14.10. die echten (core.js VORAB_REITER) statt grauer Balken. Ohne Klick: bis die Seite
   steht, hat ein Reiter nichts zu oeffnen. */
.uvb-root .uvb-nav { animation: uvb-auf var(--up-t-auftritt) var(--up-ease-expo) 50ms both; pointer-events: none; }
.uvb-main { display: flex; flex-direction: column; gap: 16px; padding-top: 16px;
  animation: uvb-auf var(--up-t-auftritt) var(--up-ease-expo) 100ms both; }
.uvb-zeile { display: flex; gap: 8px; }
.uvb-sk { display: block; background: var(--vc-sk) var(--up-sk-verlauf); border-radius: var(--up-r-m); animation: var(--up-sk-bewegung); }
.uvb-sk.is-knopf { width: 112px; height: var(--up-h-btn); }
.uvb-sk.is-knopf.is-kurz { width: 88px; }
.uvb-sk.is-titel { width: 132px; height: 12px; border-radius: var(--up-sk-r); }
.uvb-karte { box-sizing: border-box; height: 240px; padding: 20px; border: 1px solid var(--vc-border); border-radius: var(--up-r-2xl); }
.uvb-karte.is-gross { height: 360px; }
@keyframes uvb-auf { from { opacity: 0; transform: translateY(10px); } }
@media (prefers-reduced-motion: reduce) {
  .uvb-root .uvb-pagehead, .uvb-root .uvb-nav, .uvb-main { animation-name: uvb-auf-weich; }
  .uvb-sk { animation: none; }
}
@keyframes uvb-auf-weich { from { opacity: 0; } }

/* ---------- DIE KOMPONENTEN TRAGEN IHR SEITENPOLSTER SELBST (29.09. angefordert) ----------
   "Gib den ganzen Komponenten einfach 16px padding left und right, nur nicht den Topbars."
   Bisher gab die Bubble-Gruppe der Ansicht allen Bloecken das Polster -- auch dem Seitenkopf, und
   dessen Linien liefen deshalb nicht bis an den Rand. Jetzt tragen es die Bloecke selbst, und in
   Bubble kommt es raus.
   NUR in den vier Ansichten mit dem neuen Seitenkopf (die #view-Behaelter des Kopfskripts) --
   dieselben Komponenten in einem Drawer behalten ihr Mass. NUR die grossen Bloecke: Kalender,
   Filterknoepfe und Umschalter stehen nebeneinander in einer Zeile; 16px an jedem waeren Luecken
   zwischen ihnen. Keiner dieser Wurzeln hatte bisher ein eigenes Seitenpolster.
   Die Filterleiste "More Filters" (.ufb-root) stand anfangs mit in der Liste und ist am selben
   Tag wieder raus (gemeldet): sie steht in der Zeile neben dem Kalender, ist also genau so ein
   Knopf und kein Block.
   Ebenso .upw-root (das Agentic Dashboard, am selben Tag gefunden): seine Spalte traegt ihr
   Polster selbst (--upw-pad in power-dashboard.css), im schmalen Fenster ausdruecklich 16
   ("ueberall 16px im mobile Dashboard", 15.09.). Die 16 hier kamen DAZU -- 32 statt 16, genau
   die Verdopplung, vor der der Kommentar dort warnt. */
:is(#view-dashboard, #view-prompts, #view-citations, #view-brands)
  .up-root:is(.vot-root, .tcd-root, .combo-root, .upt-root, .urt-root, .utm-root,
              .uut-root, .udt-root, .ubo-root, .udb-root) { padding-left: 16px; padding-right: 16px; }
/* IN DER CITATIONS-SEITE (citations-page.js, 08.10.) stehen Chart und Tabellen im Inhalt der
   Komponente, und den legt UC.makePageCrumbs schon auf 16 vom Rand (--up-ph-innen-l/-r). Die 16
   oben kaemen dazu -- 32 statt 16. Mit der Kennung, sonst verliert die Regel gegen die obere. */
:is(#view-dashboard, #view-prompts, #view-citations, #view-brands) .ucs-root
  .up-root:is(.combo-root, .uut-root, .udt-root) { padding-left: 0; padding-right: 0; }
/* Ebenso in der Dashboard-Seite (dashboard-page.js, 08.10.): Visibility-Chart, Top Citations und
   Responses stehen im Inhalt der Komponente, der schon 16 vom Rand steht. */
:is(#view-dashboard, #view-prompts, #view-citations, #view-brands) .uds-root
  .up-root:is(.vot-root, .tcd-root, .urt-root) { padding-left: 0; padding-right: 0; }

/* ---------- DIESELBEN 16px IN DEN FUENF UEBRIGEN ANSICHTEN (29.09. nachmittags) ----------
   Mit den Koepfen von Performance, Opportunities, Prompt Research, Teams und Settings muss auch
   dort das Seitenpolster aus Bubble raus -- sonst enden die Linien der Koepfe wieder 16px vor dem
   Rand. Die Bloecke tragen es dann selbst, wie in den ersten vier Ansichten.
   Ueber [id^="view-"] und nicht ueber die Namen: bestaetigt sind nur #view-opportunities und
   #view-settings, die uebrigen setzt das Kopf-Skript der App (view-<name>), und ein falsch
   geratener Name liesse die Regel still ins Leere laufen. Drawer liegen in keiner Ansicht.
   Nicht dabei: .uo-root bringt seine 16px seit jeher selbst mit; .uts-root und .upr-root tragen
   ihren Kopf in sich und rechnen ihren Randabstand mit ihm (Block darueber). .upd-root war eine
   Karte mit Rahmen und trug darum Aussenabstand; seit dem 07.10. steht der Bereich offen wie die
   Detailbereiche von Events, Shopping und Ads und polstert sich wie der Radar darueber. */
[id^="view-"] .up-root:is(.uhm-root, .usb-root, .uto-root, .ubl-root, .upd-root) { padding-left: 16px; padding-right: 16px; }
/* IN DER PERFORMANCE-SEITE (performance-page.js, 09.10.) stehen Radar, Detailbereich und
   URL-Tabelle im Inhalt der Komponente, und den legt UC.makePageCrumbs schon auf 16 vom Rand. Die
   16 oben kaemen dazu -- 32 statt 16, wie bei Citations und Dashboard. Ueber die Seitenkennung, sonst
   verliert die Regel gegen die obere. */
[id^="view-"] .upf-root .up-root:is(.uhm-root, .upd-root, .uut-root) { padding-left: 0; padding-right: 0; }
/* IN DER SEITE PROMPT INSIGHTS (prompts-page.js, 10.10.): Prompt-Tabelle, Response-Tabelle und
   Topic-Verwaltung stehen im Inhalt der Komponente, der schon 16 vom Rand steht -- wie bei
   Citations und Dashboard. */
:is(#view-dashboard, #view-prompts, #view-citations, #view-brands) .upi-root
  .up-root:is(.upt-root, .urt-root, .utm-root) { padding-left: 0; padding-right: 0; }
/* IN DER SEITE BRANDS (brands-page.js, 11.10.): Brands Overview und Explore Brands stehen im
   Inhalt der Komponente, der schon 16 vom Rand steht -- wie bei Prompt Insights. */
:is(#view-dashboard, #view-prompts, #view-citations, #view-brands) .ubr-root
  .up-root:is(.ubo-root, .udb-root) { padding-left: 0; padding-right: 0; }
/* IN DER SEITE SETTINGS (settings-page.js, 11.10.): Your Brand, Team Organisation und Billing
   stehen im Inhalt der Komponente, der schon 16 vom Rand steht -- die 16 aus dem Block "DIESELBEN
   16px" kaemen dazu. Ueber die Seitenkennung, sonst verliert die Regel gegen die dortige. */
[id^="view-"] .uss-root .up-root:is(.usb-root, .uto-root, .ubl-root) { padding-left: 0; padding-right: 0; }

/* ---------- responsive (classes set by UC.makePageNav via UC.onResize) ---------- */
/* is-narrow (<768px default): tabs go from auto-width/left-packed to even thirds, still centered
   internally (.up-ph-navitem already centers its own icon+label). A page header's own "Add X"
   button can drop its trailing word here by wrapping it in a child with class up-ph-addbtn-full. */
.up-root.is-narrow .up-ph-navitem { flex: 1 1 0; }
/* NIE UEBER DEN RAND (06.10. abends angefordert, fuer jeden Seitenkopf). makePageNav misst, ob die
   Reiterzeile ueberlaeuft, und setzt dann zwei Stufen nacheinander:
   1. is-ohne-zeichen: die Zeichen fallen weg, Polster 8 (die Fassung, die Shopping und Ads bis
      hier je fuer sich unter 500px hatten -- gemessen bei 400: 108px Ueberlauf mit Zeichen);
   2. is-kuerzen, nur wenn es auch ohne Zeichen nicht passt: Reiter, die breiter sind als ihr
      Anteil, werden auf die Kappe --up-nav-kappe begrenzt (makePageNav rechnet sie aus, kurze
      Namen bleiben ganz) und der Name endet mit "…".
   Gemessen und nicht an einer Breite festgemacht: wie viele Reiter mit welchen Namen in welcher
   Sprache passen, sagt keine feste Stufe. (0,5,0), damit es die schmalen Reiter der kompakten
   Koepfe weiter unten ((0,4,0), Polster 0 15 0 12) schlaegt. */
.up-root .up-ph-nav.is-ohne-zeichen .up-ph-navicon { display: none; }
.up-root .up-ph-nav.is-ohne-zeichen .up-ph-navitem.up-ph-navitem { padding: 0 8px; }
.up-root .up-ph-nav.is-kuerzen .up-ph-navitem.up-ph-navitem { flex: 1 1 auto; min-width: 0; max-width: var(--up-nav-kappe, none); }
.up-root .up-ph-nav.is-kuerzen .up-ph-navlabel { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.up-root.is-narrow .up-ph-addbtn-full { display: none; }
/* Die schmale Fassung des Etiketts. Sie ist ein eigener Satz aus dem Katalog und nicht der Rest
   eines abgeschnittenen -- im Deutschen steht das Verb hinten, ein abgeschnittener Satz waere
   dort das falsche Wort. knopfEtikett() in core.js baut beide Spans. */
.up-ph-addbtn-kurz { display: none; }
.up-root.is-narrow .up-ph-addbtn-kurz { display: inline; }
/* Hier stand bis 18.08. eine Zusatzpolsterung: unter 500px bekamen Seitenkoepfe (.up-ph-root) und
   Komponenten mit eigener Kopfzeile (.up-sidebar-clear -- ask-mira, prompt-research) 32px Luft
   nach oben, damit Bubbles mobiler Sidebar-Schalter oben links nicht auf der Ueberschrift lag.
   Diesen Schalter gibt es nicht mehr: die Leiste ist unsere eigene (usn-bar) und bringt ihren
   Knopf selbst mit. Die Polsterung war damit nur noch ein Sprung in der Hoehe, sobald das Fenster
   schmal wurde -- ersatzlos raus, auf Ansage.

   Mit ihr gehen VIER Gegenrechnungen: .up-ph-addbtn hier und die -16px an .bph-topright,
   .pph-topright und .cph-refreshbtn in den Seitenkopf-Dateien. Sie existierten ausschliesslich,
   um diese Polsterung wieder abzuziehen; allein stehengelassen haetten sie die Knoepfe im
   schmalen Fenster 16px nach oben gezogen.

   .up-sidebar-clear wird von beiden Komponenten weiter gesetzt und traegt jetzt keine Regel mehr.
   Absichtlich: die Klasse steht im Vertrag, und der naechste Kopf, der oben links etwas zeichnet,
   haengt sich wieder daran. */

/* ======================================================================
   Dropdown-Panel-Kante — EIN Ort, wie die Panel-Farbe daneben.

   Gemessen auf der Live-Seite: das Panel ist #151515, der Drawer darunter #121212.
   Drei Stufen von 255 -- die Flaeche verschwindet darin, und das liest sich als
   "das Dropdown ist durchsichtig". Auf der Seite sind es sechs Stufen gegen
   #1b1b1b, gerade genug, dass es dort nicht auffiel.

   Die Panel-FARBE bleibt, wo sie ist: sie ist eine bewusste, app-weite
   Entscheidung, und sie wegen eines Kontexts zu verschieben waere der falsche
   Hebel. Was fehlte, ist die Kante. core hatte diese Aufhellung schon -- aber
   nur fuer .up-filter-menu, und die Filter-Dropdowns, date-range und
   add-prompts bringen eigene Menue-Klassen mit und wurden nie mitgenommen.
   Deshalb hier alle an einer Stelle, statt die Regel ein siebtes Mal zu kopieren.
   ====================================================================== */
.up-root .up-filter-menu, .up-root .up-menu, .up-root .up-cols-menu,
.up-root .up-sort-menu, .up-root .up-ment-menu, .up-root .up-pop,
/* ---- Hover IN einem Menue: der Auswahlton, nicht der Kopfzeilenton -----------------------------
   Gemeldet am 07.09. fuer die Type-Dropdowns in den Tabellenkoepfen: "entweder haben die Hover
   Effekte exakt die gleiche Farbe wie der BG, oder sie sind schlicht nicht sichtbar."

   Genau so war es, und zwar rechnerisch: die Zeilen holen ihren Hover aus --vc-heading-bg
   (#232326 im Dunkeln), und der Grund JEDES dieser Menues ist --up-menu-bg -- ebenfalls #232326.
   Gemessen in _h_dd_hover.html: Abstand 0.0. Auf einer Karte oder in einer Werkzeugleiste ist
   derselbe Ton richtig und gut sichtbar (dort liegt er auf --vc-bg, #1c1c1f); nur AUF einem
   Menue faellt er mit dem Grund zusammen. Die Dropdowns der Topics-, Markets- und Models-Filter
   traf es genauso -- dort fiel es nur nicht auf, weil ihre Zeilen meist angehakt sind und dann
   den Auswahlton tragen.

   Darum wird der Ton nicht in zwanzig :hover-Regeln getauscht, sondern EINMAL an der Quelle: in
   einem Menue IST der leise Ton der Auswahlton. Damit gilt hier die Hausregel, die .up-optrow
   schon beschreibt -- "eine Zeile, auf die man zeigt, und eine Zeile, die gewaehlt ist, sind
   beide 'diese hier', und zwei verschiedene Graus nebeneinander lesen sich als zwei
   Bedeutungen". Alles, was in einem Menue liegt, zieht mit: Zeilen, Schalterzeilen, die
   Icon-Knoepfe der Kopfzeile, das X im Suchfeld.

   Die Schiene eines Umschalters folgte diesem Muster bis zum 22.09. ebenfalls; sie braucht es
   seit ihrem deckenden Ton nicht mehr. Die Trigger sind NICHT betroffen: sie sind Geschwister des Menues, keine
   Kinder, und ihr geoeffneter Zustand bleibt der Kopfzeilenton auf der Seitenflaeche. */
.up-root .up-filter-menu, .up-root .up-menu, .up-root .up-cols-menu,
.up-root .up-sort-menu, .up-root .up-ment-menu, .up-root .up-pop,
.up-root .up-submenu,
.up-root .utf-menu, .up-root .umf-menu, .up-root .umk-menu,
.up-root .udr-menu, .up-root .uap-menu {
  --vc-heading-bg: var(--up-sel-bg);
}

.up-root .utf-menu, .up-root .umf-menu, .up-root .umk-menu,
.up-root .udr-menu, .up-root .uap-menu {
  border-color: var(--up-menu-border);
  /* Ganz nach oben, direkt unter die Bulk-Bar (99999) und den Modal-Shell (100000), die beide
     weiter gewinnen muessen. 9940 reichte nicht: Teile des Drawers bauen hoeher als das
     dokumentierte 9905-9930-Band, und eine Zahl, die man gegen einen fremden Host schaetzt, ist
     immer die naechste Runde. Ueber --up-menu-z pro Placement nachjustierbar. */
  z-index: var(--up-menu-z, 99998);
}

/* ---- .up-scroll ------------------------------------------------------------------------------
   Die schmale, dezente Scrollbar des Hauses. Stand in fuenf Dateien fast gleich -- die
   Unterschiede waren zufaellig, die FARBEN aber nicht: drei Komponenten ziehen sie aus der
   Haus-Palette (--vc-*), ask-mira und quick-actions aus ihrer eigenen (--am-*). Eine Klasse mit
   festen Farben haette die beiden anders aussehen lassen.

   Darum: Geometrie fest, Farbe ueber zwei Variablen mit Rueckfall. Wer nichts setzt, bekommt die
   Haus-Toene; wer eine eigene Palette fuehrt, setzt --up-scroll-thumb und --up-scroll-track auf
   dem eigenen Wurzelelement und behaelt sein Aussehen.

   Firefox kennt ::-webkit-scrollbar nicht und nimmt scrollbar-width/scrollbar-color -- beide
   Zeilen gehoeren zusammen, sonst ist es in einem der beiden Browser unveraendert breit. */
.up-scroll { scrollbar-width: thin;
  scrollbar-color: var(--up-scroll-thumb, var(--vc-border)) var(--up-scroll-track, transparent); }
.up-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
.up-scroll::-webkit-scrollbar-track { background: var(--up-scroll-track, transparent); }
.up-scroll::-webkit-scrollbar-thumb { background: var(--up-scroll-thumb, var(--vc-border));
  border-radius: var(--up-r-voll); }
.up-scroll::-webkit-scrollbar-thumb:hover { background: var(--up-scroll-thumb-hover, var(--vc-hover-border)); }
.up-scroll::-webkit-scrollbar-corner { background: transparent; }

/* ---- Variantentabelle (UC.variationRows) -----------------------------------------------------
   Die Klassen, die das Kit ausgibt. Standen als .upd-* in performance-detail und gelten jetzt fuer
   beide Verbraucher -- der Ring hat keine Farbe im SVG, er holt sie von hier; ohne diese Regeln
   waere er unsichtbar.

   Kein Farbton fuer den Bogen: Share of Voice ist keine Wertung, sondern ein Anteil. Ein gruener
   oder roter Ring wuerde eine Aussage treffen, die die Zahl nicht hergibt. */
.up-varname { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-varring { display: inline-flex; align-items: center; flex: 0 0 auto; line-height: 0; }
/* 40 Prozent groesser: 16 x 1.4 = 22.4. Die Groesse steht hier und nicht im SVG -- die viewBox
   bleibt 16x16, alles darin skaliert proportional mit, und Radius und Strichstaerke muessen nicht
   nachgerechnet werden. */
.up-varring svg { display: block; width: 22.4px; height: 22.4px; }
.up-varring-track { stroke: var(--vc-heading-bg); }
/* Chartfarbe wie oben (23.10.): --up-chart-ink im Modus "Standard + Blau", sonst die Akzent-Tinte. */
.up-varring-fill { stroke: var(--up-chart-ink, var(--up-accent-ink)); transition: stroke-dasharray 240ms ease; }
.up-var-of { font-size: 12px; font-weight: 400; color: var(--vc-fourth); white-space: nowrap; }

/* ══ DAS KONTINGENT IM SEITENKOPF (25.10., UC.kontingentHtml und UC.kontingentZeigen) ═══════════
   Stand bis dahin als .upi-quota in prompts-page.css, Werte unveraendert; zweiter Nutzer ist der Kopf
   von Brands. Ring aus core (.up-varring) und die Zahl in der Schrift von .up-num, das Wort leise wie
   in den Leisten. 8px mehr Abstand zu den Knoepfen: es ist eine Angabe, kein Knopf der Reihe. Steht
   davor noch ein Bedienelement (Brands: der Umschalter des Bandes), dieselbe Luft auf der linken Seite.
   Die alten Namen der Prompts-Seite stehen als zweiter Selektor (Vertrag). */
.up-kontingent, .upi-quota { display: inline-flex; align-items: center; gap: 8px; margin-right: 8px; white-space: nowrap; }
.up-kontingent:not(:first-child) { margin-left: 8px; }
/* Die Huelle des Rings als Flex, nicht als Zeilenkasten (14.10. abends, gemeldet: "nicht mittig"):
   als Block trug sie Zeilenhoehe und Unterlaenge mit, 26.9 hoch fuer einen 22.4er Ring -- der Ring
   sass 2.3px ueber der Mitte von Text und Knoepfen. */
.up-kontingent-ring, .upi-quota-ring { display: flex; align-items: center; }
.up-kontingent.is-leer, .upi-quota.is-leer { display: none; }
.up-kontingent-text, .upi-quota-text { display: inline-flex; align-items: baseline; gap: 4px; font-size: var(--up-fs-m); }
.up-kontingent-zahl, .upi-quota-zahl { color: var(--vc-text); font-weight: var(--up-fw-m); }
.up-kontingent-wort, .upi-quota-wort { color: var(--vc-third); }
.up-kontingent.is-fehler .up-kontingent-zahl { color: var(--vt-down); }
/* Gruen bis zum Limit, ab dem Limit seit 28.10. orange (--vt-voll-flaeche: eine Grenze, kein Fehler), nach Geist (15.10. abends: "das Gruen vom Prompt Quota ...
   Geist"): Geists Gauge zeichnet den Bogen in Green 700 und die Fehlerstufe in Red 800 -- die
   Flaechenstufen, nicht die Schriftstufe 900 von --vt-up/--vt-down. Spezifitaet ueber die
   Dunkel-Regel des Rings (.up-root[data-theme="dark"] .up-varring-fill, 0,3,0), sonst gewaenne im
   Dunkeln die Chartfarbe. */
.up-root .up-kontingent .up-kontingent-ring .up-varring-fill { stroke: var(--vt-up-flaeche); }
.up-root .up-kontingent.is-voll .up-kontingent-ring .up-varring-fill { stroke: var(--vt-voll-flaeche); }
/* Schmal: Ring und Zahl, das Wort steht im Tooltip (wie die Knoepfe ihr zweites Wort ablegen). Ohne
   Grenze gibt es keinen Ring, dort bleibt das Wort. */
.up-root.is-narrow .up-kontingent:not(.is-ohne-grenze) .up-kontingent-wort { display: none; }
/* Telefon: die Luft der Reihe (8) statt 16. Gemessen bei 375 mit dem Umschalter der Seitenleiste:
   auf Brands stehen dort Bandschalter, Kontingent, Add und Refresh, und der Krume fehlten 11.1px --
   aus "Zeichen > Tracked" wurde "Zeichen > Trac...". */
.up-root.is-vnarrow .up-kontingent { margin-left: 0; margin-right: 0; }

/* ══ Custom Groupings ═══════════════════════════════════════════════════════════════════════════
   Diese Regeln standen in prompts-table.css und sind hierher GEWANDERT, Werte unveraendert. Grund:
   die Verwaltung der Gruppierungen gibt es an zwei Orten (Dropdown der prompts-table, Abschnitt im
   topics-manager), und "1:1 dasselbe" ist nur zu halten, wenn es EINE Regel ist.

   Jede Regel traegt beide Selektoren: den alten .upt-*-Namen und den Kit-Namen .up-cgm-*. Das
   Markup aus UC.makeGroupingModal setzt beide Klassen. Damit bleibt die Tabelle pixelgleich, der
   Manager bekommt dasselbe Aussehen ohne zweite Kopie, und kein Klassenname verschwindet aus dem
   Vertrag -- .contract_diff zaehlt entfernte Klassen als Alarm, und zu Recht: an einem
   .upt-gm-*-Namen kann fremdes CSS in der Bubble-App haengen.
   Das Farbraster (.upt-colorgrid und Geschwister) ist mitgewandert, weil der Themen-Ersteller der
   prompts-table es ebenfalls benutzt: es war schon vor dieser Runde an zwei Stellen im Einsatz und
   stand trotzdem in einer Komponentendatei. */
/* ---------- "New grouping" popup: shell + chips are core's, only the layout here is local ---------- */
.upt-gm-backdrop .up-topicmodal-card, .up-cgm-backdrop .up-topicmodal-card { max-width: 560px; }
.upt-gm-labelrow, .up-cgm-labelrow { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.upt-gm-count, .up-cgm-count { font-size: 12px; font-weight: 500; color: var(--vc-muted); font-variant-numeric: tabular-nums; }
/* Hidden until the magnifier is clicked — with a handful of topics a permanent search field is
   just a field nobody needs. */
.upt-gm-search, .up-cgm-search { position: relative; display: none; align-items: center; }
.upt-gm-search.is-open, .up-cgm-search.is-open { display: flex; }
.upt-gm-right, .up-cgm-right { display: inline-flex; align-items: center; gap: 8px; }
.upt-gm-searchbtn, .up-cgm-searchbtn { width: 26px; height: 26px; border: 0; border-radius: var(--up-r-m); padding: 0;
  background: transparent; color: var(--vc-muted); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; }
.upt-gm-searchbtn svg, .up-cgm-searchbtn svg { width: 14px; height: 14px; display: block; }
.upt-gm-searchbtn:hover, .up-cgm-searchbtn:hover, .upt-gm-searchbtn.is-open, .up-cgm-searchbtn.is-open { color: var(--vc-text); background: var(--vc-heading-bg); }
/* Expanded list scrolls instead of growing without limit — 100 topics must not push the footer
   off screen. */
.upt-gm-list.is-scroll, .up-cgm-list.is-scroll { max-height: 240px; overflow-y: auto; }
.upt-gm-more, .up-cgm-more { width: 100%; height: 30px; border: 0; border-radius: var(--up-r-m); background: transparent;
  color: var(--vc-muted); font-family: inherit; font-size: 12.5px; font-weight: 500; cursor: pointer; }
.upt-gm-more:hover, .up-cgm-more:hover { color: var(--vc-text); background: var(--vc-heading-bg); }
.upt-gm-search-in, .up-cgm-search-in { flex: 1 1 auto; height: var(--up-h-btn); padding: 0 48px 0 12px; border: 1px solid var(--vc-border);
  border-radius: var(--up-r-m); background: var(--up-feld-bg); color: var(--vc-text); font: inherit; font-size: var(--up-fs-l); font-weight: var(--up-fw-n); outline: 0; }
.upt-gm-search-in:focus, .up-cgm-search-in:focus { border-color: var(--up-focus); box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); outline: 0; }
.upt-gm-clear, .up-cgm-clear { position: absolute; right: 6px; border: 0; cursor: pointer;
  display: none; align-items: center; justify-content: center; }
.upt-gm-clear.is-on, .up-cgm-clear.is-on { display: inline-flex; }
.upt-gm-list, .up-cgm-list { display: flex; flex-wrap: wrap; gap: 8px; }

.upt-gm-namerow, .up-cgm-namerow { display: flex; align-items: center; gap: 8px; }
.upt-gm-colorwrap, .up-cgm-colorwrap { flex: 0 0 auto; }
.upt-gm-dotbtn, .up-cgm-dotbtn { width: 36px; height: 36px; padding: 0; border-radius: var(--up-r-m); border: 1px solid var(--vc-border);
  background: transparent; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
/* Menue-Ausloeser nach Geist (15.10.): Flaeche plus eine Stufe kraeftigere Kante. */
.upt-gm-dotbtn:hover, .up-cgm-dotbtn:hover, .upt-gm-colorwrap.is-open .upt-gm-dotbtn, .up-cgm-colorwrap.is-open .up-cgm-dotbtn { border-color: var(--up-knopf-rand-hover); background: var(--up-knopf-hover); }
.upt-gm-dot, .up-cgm-dot { width: 16px; height: 16px; border-radius: 50%; display: block; border: 1px solid rgba(0,0,0,.14); }
/* Centered (same reasoning as .upt-topicpickpanel above: a left-aligned grid under a full-width
   row looks like it fell over) and smooth: max-height/opacity animate together on open, driven by
   .is-open added a frame after mount (see openGroupModal's data-gm-colorbtn handler) so there is
   an actual "before" state to transition FROM. Because the panel node itself is never recreated
   (only its innerHTML), the rest of the popup -- everything below it in the modal -- reflows in
   step with the same 200ms ease instead of snapping to the new height. */
.upt-gm-colorpanel, .up-cgm-colorpanel { display: flex; justify-content: center;
  max-height: 0; opacity: 0; overflow: hidden; margin-top: 0;
  transition: max-height 200ms ease, opacity 200ms ease, margin-top 200ms ease; }
.upt-gm-colorpanel.is-open, .up-cgm-colorpanel.is-open { max-height: 160px; opacity: 1; margin-top: 10px; }
/* Rows are tone scales (vibrant / muted / deep), columns are hues — read down for a mood,
   across for a color. Centred, because a left-aligned 8-wide grid under a full-width row above
   it looked like it had fallen over. */
.upt-colorgrid, .up-cgm-colorgrid { display: grid; grid-template-columns: repeat(10, 34px); gap: 6px; }
/* The 34x34 cell is the hit target and carries the hover, the blob inside is just the colour —
   splitting them is what lets the swatch stay a fixed size while the cell gets a proper
   button-sized surface, instead of a small dot you have to aim at.
   Transparent border reserved at rest (not added on hover) so the hover state doesn't shift
   the swatch by the border's own width. */
.upt-colorcell, .up-cgm-colorcell { width: 34px; height: 34px; padding: 0; border: 1px solid transparent; border-radius: var(--up-r-m);
  background: transparent; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 120ms ease, border-color 120ms ease; }
.upt-colorcell:hover, .up-cgm-colorcell:hover { background: var(--vc-heading-bg); border-color: var(--vc-hover-border); }
/* No inset ring and no fractional-px spread: an inset shadow on a 20px circle plus a 3.5px
   outer ring is what made these look soft/smeared rather than crisp. The blob is now a plain
   filled circle at whole-pixel size, and selection is shown INSIDE it. */
.upt-colorblob, .up-cgm-colorblob { width: 22px; height: 22px; border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center; flex: 0 0 auto; color: #fff; }
.upt-colorblob svg, .up-cgm-colorblob svg { width: 12px; height: 12px; display: block; stroke: currentColor; stroke-width: 4; }
/* Fixed ink, not per-swatch contrast-solved: white in light mode, primary text in dark. Same rule
   as core's canonical picker (up-topicmodal-colorblob) -- two selectors because this cell shows up
   both inside .up-root (the inline topic creator) and inside the body-mounted group-modal
   backdrop, which carries its own data-theme rather than inheriting .up-root's. */
.up-root[data-theme="dark"] .upt-colorblob,
.up-root[data-theme="dark"] .up-cgm-colorblob,
.upt-gm-backdrop[data-theme="dark"] .upt-colorblob, .up-cgm-backdrop[data-theme="dark"] .up-cgm-colorblob { color: var(--vc-text); }
.upt-topicmenu-empty, .up-cgm-empty { padding: 14px 8px; text-align: center; font-size: 12.5px; color: var(--vc-muted); }

/* ---------- Zeile einer Gruppierung: Punkt, Auge, Dreipunktmenue, Zieh-Platzhalter ------------
   Auch das gab es nur in prompts-table.css und wird jetzt an zwei Orten gebraucht. Gewandert mit
   den Werten, die dort erprobt sind, und mit beiden Klassennamen je Regel.
   Neu ist EINE Klasse: .up-cg-row. Sie markiert "das ist eine Gruppierungs-Zeile" und traegt die
   Regeln, die vom Zustand der ZEILE abhaengen (Hover deckt Auge und Menue auf, das Ziehen macht die
   Zeile zum Platzhalter). Vorher hingen die an .upt-group-item, also am Zeilentyp des Popovers --
   im topics-manager ist die Zeile eine andere, die Regeln sind dieselben. Beide Orte setzen die
   Klasse zusaetzlich zu ihrer eigenen. */
.upt-grp-cdot, .up-cg-dot { width: 8px; height: 8px; border-radius: var(--up-r-voll); flex: 0 0 auto; display: block; }

/* Auge und Kebab: 24x24, gedaempft, erscheinen erst beim Hover der Zeile -- eine Aktionszeile, in
   der bei Ruhe alle Symbole stehen, liest sich unruhiger als der Rest. Ein verstecktes Grouping
   bleibt trotzdem als versteckt erkennbar (gedimmter Name und Punkt weiter unten). */
.upt-group-eye, .up-cg-eye,
.upt-group-more, .up-cg-more { width: 24px; height: 24px; flex: 0 0 auto; border: 0;
  border-radius: var(--up-r-s); padding: 0; background: transparent; color: var(--vc-muted); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity 120ms ease, color 120ms ease, background 120ms ease; }
.upt-group-eye svg, .up-cg-eye svg,
.upt-group-more svg, .up-cg-more svg { width: 14px; height: 14px; display: block; }
.up-cg-row:hover .up-cg-eye, .up-cg-row.is-menuopen .up-cg-eye,
.up-cg-row:hover .up-cg-more, .up-cg-row.is-menuopen .up-cg-more { opacity: 1; }
.up-cg-eye:hover, .up-cg-more:hover,
.up-cg-row.is-menuopen .up-cg-more { color: var(--vc-text); background: var(--vc-heading-bg); }
.upt-group-eye.is-off, .up-cg-eye.is-off { color: var(--vc-fourth); }
/* Ein verstecktes Grouping liest sich auch in der Liste als versteckt. */
.up-cg-row:has(.up-cg-eye.is-off) .up-pop-label,
.up-cg-row:has(.up-cg-eye.is-off) .up-cg-dot { opacity: .45; }

/* core's .up-menu ist die Huelle -- gleicher Rahmen, Schatten, Radius und Uebergang wie jedes
   andere Dropdown. Nur die Verankerung ist hier. Oeffnet nach LINKS, weil die Zeile am rechten
   Rand ihres Behaelters sitzt. */
.upt-group-rowmenu, .up-cg-rowmenu { top: calc(100% - 2px); right: 4px; left: auto; width: 128px; padding: 6px;
  display: flex; flex-direction: column; }
/* KEIN eigener gap mehr (war 2px). Die Zeilen darin sind .up-pop-opt, und deren Abstand kommt
   seit dem 21.09. aus der einen Regel weiter oben -- 4px, wie in jedem Dropdown. Ein gap hier
   haette sich mit ihr ADDIERT: gemessen waeren es 6px geworden, also ausgerechnet in dem Menue,
   das am kompaktesten sein soll, der groesste Abstand der App. */
/* 32px, flach, nicht aus Polsterung gerechnet -- jede Menuezeile dieser App ist 32px hoch. */
.upt-group-rowmenu .up-pop-opt, .up-cg-rowmenu .up-pop-opt { justify-content: flex-start; height: 32px; padding: 0 10px; font-size: 13px; }
/* Die Zeile sitzt konzentrisch im Menue (15.10., Geist-Skala): Menue 12 minus Polster. Die Menues
   mit --up-dd-pad (8) tragen sie deshalb mit 4 (.up-pop-opt oben), diese beiden und das Aktions-
   menue der Brands-Uebersicht haben 6 Polster und damit 6 -- genau Geists Menue (12, 6, 6). */
.upt-group-rowmenu .up-pop-opt, .up-cg-rowmenu .up-pop-opt, .ubo-actmenu .up-pop-opt { border-radius: var(--up-r-m); }
/* Gefahrenrot ueber den Token, wie in prompts-table.css. Dort steht die Kopie dieses Menues
   ("Byte-for-byte the Grouping popover's own row menu"), und die ist am 21.09. umgestellt
   worden -- die beiden waeren im dunklen Thema sonst auseinandergelaufen: hier #b0200c, dort
   das hellere #d25d5d von --vt-down. */
.upt-group-rowmenu .up-pop-opt.is-danger, .up-cg-rowmenu .up-pop-opt.is-danger, .ubo-actmenu .up-pop-opt.is-danger { color: var(--vt-down); }
.upt-group-rowmenu .up-pop-opt.is-danger:hover, .up-cg-rowmenu .up-pop-opt.is-danger:hover, .ubo-actmenu .up-pop-opt.is-danger:hover { background: color-mix(in srgb, var(--vt-down) 8%, transparent); }

/* Beim Ziehen wird die Zeile ihr eigener Platzhalter, statt nur zu dimmen: der echte Inhalt
   verschwindet, zwei graue Bloecke treten an seine Stelle, ein Rahmen markiert den Platz. Reiner
   Klassenwechsel, kein innerHTML-Tausch -- ein abgebrochenes Ziehen (ausserhalb losgelassen,
   Escape) kann die Zeile also nicht halb umgebaut hinterlassen. NICHT animiert: ein pulsendes
   Skelett liest sich als "laedt", und das ist es nicht. */
.up-cg-row.is-draggable { cursor: grab; }
.up-cg-row.is-dragging { border: 1px solid var(--vc-border); border-radius: var(--up-r-m); cursor: grabbing; }
.up-cg-row.is-dragging > .up-cg-dot,
.up-cg-row.is-dragging > .up-pop-label,
.up-cg-row.is-dragging > .up-cg-eye,
.up-cg-row.is-dragging > .up-cg-more { display: none; }
.upt-group-sk-dot, .up-cg-sk-dot,
.upt-group-sk-text, .up-cg-sk-text { display: none; background: var(--vc-border); border-radius: var(--up-r-xs); }
.up-cg-row.is-dragging > .up-cg-sk-dot { display: block; width: 8px; height: 8px; border-radius: var(--up-r-voll); flex: 0 0 auto; }
.up-cg-row.is-dragging > .up-cg-sk-text { display: block; width: 45%; height: 12px; }
/* Greifhand und keine Textauswahl, solange irgendwo gezogen wird: ohne draggable-Attribut faellt
   auch die Auswahlsperre des Browsers weg, und der Zeiger markiert die Namen. */
.up-cg-list:has(.is-dragging) .up-cg-row { cursor: grabbing; }
/* Nur an der Liste (25.10.): user-select: auto der Kinder folgt einem none des Elternteils, das "*" dahinter war doppelt und
   teuer -- ein :has vor einem Allesselektor laesst jede Aenderung im DOM die Stile breit neu rechnen (gemessen 31 ms). */
.up-cg-list:has(.is-dragging) { user-select: none; -webkit-user-select: none; }

/* ---- Response Body ---------------------------------------------------------------------------
   Der Text einer Modellantwort. Ersetzt die drei zenith-Elemente, deren Werte aus einer Zeit
   stammen, in der es die Tokens noch nicht gab: 16px Grundschrift bei 1.8 Zeilenhoehe, 18px fuer
   JEDES <strong> (also auch mitten im Satz), und ueber 40 !important-Regeln gegen Bubbles
   Vorfahren. Hier gilt das Mass der App: 14px Text wie in jeder Karte, 1.65 Zeilenhoehe fuer
   Lauftext (Tabellen und Listen sind dichter), Betonung ueber das Gewicht statt ueber die Groesse.

   Die Tabelle ist die aus ask-mira, Wert fuer Wert: Rahmen mit Radius 10, 13.5px, Kopf in
   --vc-heading-bg, Zellen 9px/12px, jede zweite Zeile minimal getoent. Sie ist das Vorbild, weil
   sie die einzige Tabelle der App ist, die in einem Textfluss steht und dort funktioniert. */
/* 15px (Vorgabe: 1px groesser) bei Zeilenhoehe 1.75. Die 1.75 sind kein Geschmack, sondern eine
   Rechnung: 15 * 1.75 = 26.25px, und genau so hoch ist der Marken-Chip (26px). Damit passt er in
   die Zeilenbox, ohne sie zu dehnen -- vorher war die Zeile 23px und der Chip 26px, also wuchs
   JEDE Zeile mit einer Marke um 3px plus den Versatz, was wie ein halber Absatz aussah. */
.up-rb { font-size: 15px; line-height: 1.75; color: var(--vc-text); overflow-wrap: break-word; }
.up-rb-p { margin: 0 0 12px; }
.up-rb-p:last-child { margin-bottom: 0; }
/* Betonung ueber das Gewicht, nicht ueber die Groesse: <strong> steht mitten in Saetzen, und
   16px inmitten von 14px liess ganze Absaetze springen. */
.up-rb strong { font-weight: 600; }
.up-rb em { font-style: italic; }
/* Zwischentitel: eine Stufe ueber dem Text, mit Luft davor. 24px oben statt 16, weil der Titel
   einen neuen Abschnitt beginnt und nicht zum Absatz darueber gehoert. */
.up-rb-h { font-size: 15px; font-weight: 600; line-height: 1.4; letter-spacing: -0.01em;
  color: var(--vc-text); margin: 24px 0 8px; }
.up-rb-h:first-child { margin-top: 0; }
.up-rb-hr { border: 0; border-top: 1px solid var(--vc-border); margin: 20px 0; }
.up-rb-list { margin: 0 0 12px; padding-left: 22px; }
.up-rb-list li { margin: 0 0 6px; }
.up-rb-list li:last-child { margin-bottom: 0; }
.up-rb-list li::marker { color: var(--vc-third); }

/* ---- Tabellen (Werte 1:1 aus ask-mira) ---- */
.up-rb-tablewrap { display: block; width: 100%; max-width: 100%; overflow-x: auto; margin: 20px 0;
  border: 1px solid var(--vc-border); border-radius: var(--up-r-l);
  -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
.up-rb-table { border-collapse: collapse; width: 100%; font-size: 13.5px; table-layout: auto; }
.up-rb-table thead th { text-align: left; font-weight: 600; color: var(--vc-muted);
  background: var(--vc-heading-bg); padding: 9px 12px; white-space: normal;
  overflow-wrap: break-word; border-bottom: 1px solid var(--vc-border); }
.up-rb-table tbody td { padding: 10px 12px; border-bottom: 1px solid var(--vc-border);
  vertical-align: top; white-space: normal; overflow-wrap: break-word; line-height: 1.6; }
.up-rb-table tbody tr:last-child td { border-bottom: 0; }
/* Die getoenten Zeilen tragen DIESELBE Farbe wie die Kopfzeile, nur mit 50% Deckkraft. Vorher war
   es --vt-row-hover, und das ist in beiden Themen praktisch derselbe Ton wie --vc-heading-bg: die
   Kopfzeile war damit von jeder zweiten Datenzeile nicht zu unterscheiden und las sich nicht mehr
   als Kopf. Ueber dieselbe Farbe gebaut und nicht ueber eine zweite, damit der Zusammenhang bleibt
   -- die Kopfzeile ist der Vollton, die Zebra-Zeilen sind sein Abklang. */
.up-rb-table tbody tr:nth-child(even) td {
  background: color-mix(in srgb, var(--vc-heading-bg) 50%, transparent); }
.up-rb-table th:first-child, .up-rb-table td:first-child { padding-left: 14px; }
.up-rb-table th:last-child,  .up-rb-table td:last-child  { padding-right: 14px; }

/* ---- Zitat-Chip ----
   Das alte Element gab ihm 24px Hoehe bei 11px Text und 12px Abstand zwischen Favicon und Text --
   ein Loch mitten im Chip. Hier: 22px Hoehe, 12px Text, 6px Abstand, und ein Rahmen statt einer
   Hintergrundfarbe, damit er im Lauftext nicht als Block liegt. */
/* EIN Mittel je Thema, nicht zwei: im Hellen der Rahmen ohne Grund, im Dunkeln der Grund ohne
   Rahmen. Der Grund liegt hell so nah am Kartenton, dass er nichts hinzufuegt, und der Rahmen liegt
   dunkel so nah am Kartenrand, dass er als zweite Kante liest. Also je Thema das, was traegt.
   Der Grund steht in der Dunkel-Regel weiter unten, hier ist er durchsichtig. */
.up-rb-cite { display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 7px;
  margin: 0 1px; border: 1px solid var(--vc-border); border-radius: var(--up-r-s); background: transparent;
  vertical-align: middle; cursor: pointer; max-width: 100%; box-sizing: border-box; line-height: 1;
  transition: background 120ms ease, border-color 120ms ease; }
.up-rb-cite:hover { background: var(--vc-heading-bg); border-color: var(--vc-hover-border); }
/* Im Dunkeln umgekehrt: Grund in der Feldfarbe, kein Rahmen. Der Hover verdichtet den Grund. */
.up-root[data-theme="dark"] .up-rb-cite { background: var(--vc-field); border-color: transparent; }
.up-root[data-theme="dark"] .up-rb-cite:hover { background: var(--vc-heading-bg); }
.up-rb-cite:focus-visible { outline: 2px solid var(--vc-text); outline-offset: 1px; }
/* 14 statt 13, und der Radius zieht im selben Verhaeltnis mit (3 * 14/13 = 3.23). Der Chip ist
   24px hoch, das Bild passt also weiter hinein, ohne an den Rahmen zu stossen. */
.up-rb-cite-fav { width: 14px; height: 14px; border-radius: 3px; flex: 0 0 auto;
  object-fit: cover; display: block; }
/* Die Nummer der Quelle (respBody cfg.nummern, 05.10.): dieselbe Zahl wie an der Kachel im
   Abschnitt "Citations". 16px im 24px-Chip, Radius 4 -- konzentrisch zu den 7 des Chips bei 3px
   Luft. Grund wie die Seite, damit sie sich vom Chip abhebt. */
.up-rb-cite-nr { flex: 0 0 auto; min-width: 16px; height: 16px; padding: 0 4px; box-sizing: border-box;
  border-radius: var(--up-r-xs); background: var(--vc-bg); color: var(--vc-muted);
  font-size: var(--up-fs-xs); font-weight: 600; line-height: 16px; text-align: center;
  font-variant-numeric: tabular-nums; }
.up-rb-cite-txt { font-size: 12px; font-weight: 500; color: var(--vc-muted); line-height: 1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.up-rb-cite:hover .up-rb-cite-txt { color: var(--vc-text); }

/* ---- Zitat-Gruppe (mehrere Quellen hintereinander) -------------------------------------------
   Die Favicons ineinandergeschoben wie ein Stapel, dahinter "N Sources" -- dasselbe Bild, das die
   KI-Produkte von Google benutzen, und dasselbe Prinzip wie .up-stack in den Tabellen: der Stapel
   sagt "mehrere", die Zahl sagt "wie viele". Masse wie der einzelne Chip, damit beide in einer
   Zeile gleich hoch stehen. */
/* Grund und Rahmen wie beim einzelnen Zitations-Chip -- beide gehoeren zur selben Familie. */
.up-rb-cgroup { display: inline-flex; align-items: center; gap: 7px; height: 24px; padding: 0 8px 0 6px;
  margin: 0 1px; border: 1px solid var(--vc-border); border-radius: var(--up-r-s); background: transparent;
  vertical-align: middle; cursor: pointer; box-sizing: border-box; line-height: 1; white-space: nowrap;
  transition: background 120ms ease, border-color 120ms ease; }
.up-rb-cgroup:hover { background: var(--vc-heading-bg); border-color: var(--vc-hover-border); }
/* Dieselbe Entscheidung wie beim einzelnen Chip: dunkel der Grund, hell der Rahmen. */
.up-root[data-theme="dark"] .up-rb-cgroup { background: var(--vc-field); border-color: transparent; }
.up-root[data-theme="dark"] .up-rb-cgroup:hover { background: var(--vc-heading-bg); }
.up-rb-cgroup:focus-visible { outline: 2px solid var(--vc-text); outline-offset: 1px; }
.up-rb-cg-favs { display: inline-flex; align-items: center; flex: 0 0 auto; }
/* 4px Ueberlappung, genau wie .up-stack-item -- ein Stapel sieht in der ganzen App gleich aus.
   Der Rahmen in Hintergrundfarbe trennt die Bilder, sonst verschmelzen zwei aehnliche Favicons. */
/* 15 statt 14, Radius mit (4 * 15/14 = 4.29). Der Rahmen in Hintergrundfarbe bleibt bei 1.5px --
   er trennt die gestapelten Bilder und haengt nicht an ihrer Groesse. */
.up-rb-cg-fav { width: 15px; height: 15px; border-radius: var(--up-r-xs); object-fit: cover; display: block;
  box-shadow: 0 0 0 1.5px var(--vc-bg); background: var(--vt-logo-bg); }
.up-rb-cg-fav + .up-rb-cg-fav { margin-left: -4px; }
.up-rb-cgroup:hover .up-rb-cg-fav { box-shadow: 0 0 0 1.5px var(--vc-heading-bg); }
.up-rb-cg-txt { font-size: 12px; font-weight: 500; color: var(--vc-muted); }
.up-rb-cgroup:hover .up-rb-cg-txt { color: var(--vc-text); }

/* Der Kasten, der die Quellen einer Gruppe auflistet. Kein Tooltip aus core: der ist einzeiliger
   Text, hier stehen Favicon und Titel je Zeile und man muss sie anklicken koennen. */
.up-rb-glist { position: absolute; z-index: 40; min-width: 240px; max-width: 380px;
  background: var(--up-surface, var(--vc-bg)); border: 1px solid var(--vc-border);
  border-radius: var(--up-dd-radius); box-shadow: var(--up-e-2); padding: 5px;
  display: none; flex-direction: column; gap: 1px; }
.up-rb-glist.is-open { display: flex; }
.up-rb-glist button { display: flex; align-items: center; gap: 9px; width: 100%; border: 0;
  background: transparent; font: inherit; font-size: 13px; text-align: left; padding: 7px 8px;
  border-radius: var(--up-r-m); cursor: pointer; color: var(--vc-text); min-width: 0; }
.up-rb-glist button:hover { background: var(--vc-heading-bg); }
.up-rb-glist img { width: 16px; height: 16px; border-radius: var(--up-r-xs); flex: 0 0 16px; object-fit: cover;
  display: block; background: var(--vt-logo-bg); }
.up-rb-glist span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* ---- Markenauszeichnung ----
   Das alte Element zeichnete einen gestrichelten Rahmen als eingebettetes SVG, dessen Groesse ein
   ResizeObserver und ein MutationObserver nachfuehrten -- weil ein CSS-Rahmen am umbrechenden
   Inline-Element unten abgeschnitten wurde. Der Chip hier bricht nicht um (white-space: nowrap),
   damit ist der Rahmen wieder CSS und die zwei Beobachter entfallen. */
/* 26px statt 22 (Vorgabe: 4px hoeher) und ein 2px gestrichelter Rahmen -- die Strichelung ist die
   Idee der alten Elemente, dort noch als eingebettetes SVG mit zwei Beobachtern, weil ein
   CSS-Rahmen am umbrechenden Inline-Element abgeschnitten wurde. Der Chip hier bricht nicht um
   (white-space: nowrap), damit genuegt border-style: dashed.
   vertical-align: -6px statt -4: der Chip ist 4px hoeher geworden und saesse sonst zu hoch. */
/* vertical-align: middle statt eines Versatzes in Pixeln: der Chip wird an der Mitte des
   Fliesstextes ausgerichtet, also sitzt seine Schrift auf derselben Linie wie der Text drumherum.
   Der Pixelversatz vorher war fuer EINE Schriftgroesse gerechnet und verrutschte bei jeder anderen.
   line-height: 1 im Chip, damit seine eigene Schrift die Chiphoehe nicht noch einmal aufblaeht. */
.up-rb-brand { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 8px;
  margin: 0 1px; border: 2px dashed var(--vt-brand-border); border-radius: var(--up-r-m);
  background: var(--vt-brand-bg); vertical-align: middle; cursor: pointer; white-space: nowrap;
  line-height: 1; box-sizing: border-box;
  transition: background 120ms ease, border-color 120ms ease; }
.up-rb-brand:hover { background: var(--vt-brand-bg-hover); border-color: var(--vt-brand-border-hover); }
.up-rb-brand:focus-visible { outline: 2px solid var(--vc-text); outline-offset: 1px; }
.up-rb-brand-ic { width: 14px; height: 14px; border-radius: var(--up-r-xs); flex: 0 0 auto; object-fit: cover;
  display: block; }
.up-rb-brand-txt { font-size: 13.5px; font-weight: 600; color: var(--vc-text); line-height: 1; }

/* ---- Schmal ----
   Gemessen an der eigenen Box. Die alten Elemente hatten min-width: 680px an der Tabelle: auf dem
   Telefon schob das die ganze Seite zur Seite, weil der Rahmen um die Tabelle mitwuchs. Hier
   scrollt nur der Rahmen, und die Zellen duerfen enger werden statt die Seite zu sprengen. */
.up-root.is-narrow .up-rb { font-size: 13.5px; }
.up-root.is-narrow .up-rb-table { font-size: 13px; }
.up-root.is-narrow .up-rb-table thead th { white-space: nowrap; padding: 8px 10px; }
.up-root.is-narrow .up-rb-table tbody td { padding: 8px 10px; min-width: 84px; }
.up-root.is-narrow .up-rb-tablewrap { margin: 16px 0; }
.up-root.is-narrow .up-rb-h { margin-top: 20px; font-size: 14.5px; }
/* Der Chip darf schmal werden, aber nicht verschwinden: 132px zeigen Favicon plus Domain. */
.up-root.is-narrow .up-rb-cite { max-width: 132px; }
.up-root.is-vnarrow .up-rb-cite { max-width: 108px; }

/* ======================================================================
   Hintergrundbild: Raster, Bogen, vier Kreuzmarken, vier Lichter
   ======================================================================
   Vier Ebenen und nicht eine. Die Vorlage (onboarding-bg.svg) traegt vier Gruppen mit ganz
   verschiedenem Gewicht, und das Gewicht muss je THEMA anders sein: das Raster ist auf hellem
   Grund fast unsichtbar (gezeichnet mit 2% Schwarz auf Weiss), die Marken sind auf dunklem Grund
   zu hell. Mit EINER Ebene haengen alle am selben Wert, und wer die eine hebt, hebt die anderen
   mit. Beides so gemeldet am 26.08.

   Es kommt als MASKE und nicht als Bild: die Vorlage ist fuer DUNKEL gezeichnet -- weisse Striche
   bei 2% Deckkraft, ein weisser Bogen. Auf hellem Grund waere davon nichts zu sehen. Als Maske
   bleibt die Struktur im Alphakanal erhalten, und die FARBE kommt von hier, also je Thema die
   richtige.

   Die Lichter sind KEINE Maske, sondern vier Verlaeufe. Leise heisst auf dunklem Grund eine Spanne
   von vier Graustufen fuer einen Verlauf ueber achthundert Pixel; ein Bitmap zerfaellt dabei in
   drei harte Hoehenlinien, und weil Chrome grosse Weichzeichner auf einem verkleinerten Puffer
   rechnet, sind diese Linien eckig -- gemeldet als "vage viele Vierecke". Verlaeufe dithert Skia,
   dieselbe Spanne bleibt glatt. Feldvergleich: _h_uob_band.html.

   HIER stehen nur Bild und Gewicht. Lage im Stapel, Ein- und Ausblenden und ob es auf dem Telefon
   ueberhaupt erscheint, entscheidet die Komponente an ihrem eigenen Huellelement -- die zwei
   Nutzer wollen davon Verschiedenes (Onboarding blendet beim Laden ein, Mira blendet aus, sobald
   ein Chat offen ist).

   Die vier Werte sind ERZEUGT. Quelle ist onboarding-bg.svg, Werkzeug .bg_split.py; wer das Bild
   aendert, aendert die Datei und laesst das Skript laufen. Von Hand tippt hier niemand.
   Als Adresse im Stylesheet und nicht als eigene Datei, weil sonst eine weitere Anfrage in der
   Kette haengt: Lader-JS, dann CSS, dann Maske entdecken, dann SVG holen -- auf der echten Seite
   waren das 1-2 Sekunden, in denen das Bild nachtraeglich aufpoppte. */
/* An .up-root und NICHT an :root. Das war ein Versuch und ein Fehler: die Werte sehen themenfrei
   aus, aber --up-bgart-licht enthaelt color-mix(... var(--vc-muted) ...), und ein var() INNERHALB
   einer eigenen Eigenschaft wird dort aufgeloest, wo sie DEKLARIERT ist. Auf :root ist --vc-muted
   nicht definiert, also wurde die ganze Eigenschaft ungueltig und verworfen -- gemessen:
   getComputedStyle(html).getPropertyValue("--up-bgart-licht") kam leer zurueck, waehrend bogen,
   raster und marken (reine Adressen, kein var) sauber ansprachen. Die Lichter waeren damit auch im
   Onboarding und hinter Miras Startschirm verschwunden.
   Wer die Grafik ausserhalb einer Komponente benutzen will, gibt seiner Huelle die Klasse
   .up-root -- so macht es die Landingpage. */
.up-root {
  --up-bgart-licht:
    radial-gradient(ellipse 61.53% 73.26% at 94.44% 0.56%, color-mix(in srgb, var(--vc-muted) 6.160%, transparent), color-mix(in srgb, var(--vc-muted) 0%, transparent)),
    radial-gradient(ellipse 61.53% 73.26% at 20.49% 85.39%, color-mix(in srgb, var(--vc-muted) 6.160%, transparent), color-mix(in srgb, var(--vc-muted) 0%, transparent)),
    radial-gradient(ellipse 56.04% 75.84% at -3.12% 19.44%, color-mix(in srgb, var(--vc-muted) 10.368%, transparent), color-mix(in srgb, var(--vc-muted) 0%, transparent)),
    radial-gradient(ellipse 56.04% 75.84% at 94.44% 74.16%, color-mix(in srgb, var(--vc-muted) 10.368%, transparent), color-mix(in srgb, var(--vc-muted) 0%, transparent));
  --up-bgart-bogen: url("data:image/svg+xml,%3Csvg viewBox='0 0 1440 890' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M719.5 760.5C1018.92 760.5 1289.98 814.385 1486.15 901.485C1584.24 945.036 1663.59 996.88 1718.41 1054.42C1773.23 1111.97 1803.5 1175.18 1803.5 1241.5C1803.5 1307.82 1773.23 1371.03 1718.41 1428.58C1663.59 1486.12 1584.24 1537.96 1486.15 1581.51C1289.98 1668.61 1018.92 1722.5 719.5 1722.5C420.077 1722.5 149.024 1668.61 -47.154 1581.51C-145.244 1537.96 -224.593 1486.12 -279.412 1428.58C-334.229 1371.03 -364.5 1307.82 -364.5 1241.5C-364.5 1175.18 -334.229 1111.97 -279.412 1054.42C-224.593 996.88 -145.244 945.036 -47.154 901.485C149.024 814.385 420.077 760.5 719.5 760.5Z' stroke='white' stroke-opacity='0.11'/%3E%3C/svg%3E");
  --up-bgart-raster: url("data:image/svg+xml,%3Csvg viewBox='0 0 1440 890' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='0.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='0.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='0.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='0.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='0.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='0.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='0.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='0.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='0.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='80.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='80.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='80.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='80.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='80.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='80.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='80.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='80.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='80.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='80.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='160.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='160.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='160.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='160.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='160.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='160.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='160.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='160.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='160.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='160.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='240.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='240.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='240.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='240.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='240.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='240.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='240.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='240.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='240.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='240.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='320.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='320.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='320.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='320.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='320.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='320.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='320.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='320.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='320.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='320.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='400.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='400.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='400.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='400.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='400.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='400.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='400.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='400.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='400.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='400.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='480.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='480.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='480.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='480.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='480.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='480.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='480.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='480.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='480.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='480.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='560.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='560.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='560.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='560.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='560.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='560.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='560.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='560.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='560.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='560.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='640.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='640.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='640.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='640.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='640.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='640.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='640.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='640.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='640.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='640.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='720.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='720.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='720.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='720.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='720.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='720.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='720.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='720.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='720.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='720.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='800.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='800.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='800.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='800.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='800.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='800.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='800.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='800.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='800.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='800.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='880.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='880.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='880.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='880.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='880.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='880.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='880.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='880.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='880.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='880.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='960.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='960.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='960.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='960.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='960.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='960.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='960.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='960.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='960.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='960.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1040.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1040.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1040.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1040.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1040.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1040.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1040.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1040.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1040.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1040.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1120.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1120.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1120.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1120.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1120.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1120.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1120.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1120.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1120.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1120.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1200.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1200.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1200.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1200.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1200.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1200.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1200.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1200.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1200.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1200.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1280.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1280.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1280.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1280.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1280.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1280.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1280.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1280.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1280.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1280.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1360.25' y='0.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1360.25' y='80.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1360.25' y='160.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1360.25' y='240.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1360.25' y='320.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1360.25' y='400.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1360.25' y='480.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1360.25' y='560.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1360.25' y='640.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3Crect x='1360.25' y='720.25' width='79.5' height='79.5' stroke='white' stroke-opacity='0.06' stroke-width='0.5'/%3E%3C/svg%3E");
  --up-bgart-marken: url("data:image/svg+xml,%3Csvg viewBox='0 0 1440 890' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg opacity='0.31'%3E%3Cpath d='M160 237L160 170' stroke='url(%23paint0_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M160 241L160 308' stroke='url(%23paint1_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M158 240L91 240' stroke='url(%23paint2_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M163 240L230 240' stroke='url(%23paint3_linear_0_9475)' stroke-linecap='round'/%3E%3C/g%3E%3Ccircle cx='160' cy='240' r='3' fill='%23B9B9B9'/%3E%3Cg opacity='0.31'%3E%3Cpath d='M400 398L400 331' stroke='url(%23paint4_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M400 402L400 469' stroke='url(%23paint5_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M398 401L331 401' stroke='url(%23paint6_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M403 401L470 401' stroke='url(%23paint7_linear_0_9475)' stroke-linecap='round'/%3E%3C/g%3E%3Ccircle cx='400' cy='401' r='3' fill='%23B9B9B9'/%3E%3Cg opacity='0.31'%3E%3Cpath d='M1119 157L1119 90' stroke='url(%23paint8_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M1119 161L1119 228' stroke='url(%23paint9_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M1117 160L1050 160' stroke='url(%23paint10_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M1122 160L1189 160' stroke='url(%23paint11_linear_0_9475)' stroke-linecap='round'/%3E%3C/g%3E%3Ccircle cx='1119' cy='160' r='3' fill='%23B9B9B9'/%3E%3Cg opacity='0.31'%3E%3Cpath d='M1279 478L1279 411' stroke='url(%23paint12_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M1279 482L1279 549' stroke='url(%23paint13_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M1277 481L1210 481' stroke='url(%23paint14_linear_0_9475)' stroke-linecap='round'/%3E%3Cpath d='M1282 481L1349 481' stroke='url(%23paint15_linear_0_9475)' stroke-linecap='round'/%3E%3C/g%3E%3Ccircle cx='1279' cy='481' r='3' fill='%23B9B9B9'/%3E%3Cdefs%3E%3ClinearGradient id='paint0_linear_0_9475' x1='160.498' y1='170' x2='160.498' y2='237' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint1_linear_0_9475' x1='160.498' y1='308' x2='160.498' y2='241' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint2_linear_0_9475' x1='91' y1='240.498' x2='158' y2='240.498' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint3_linear_0_9475' x1='230' y1='240.498' x2='163' y2='240.498' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint4_linear_0_9475' x1='400.498' y1='331' x2='400.498' y2='398' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint5_linear_0_9475' x1='400.498' y1='469' x2='400.498' y2='402' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint6_linear_0_9475' x1='331' y1='401.498' x2='398' y2='401.498' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint7_linear_0_9475' x1='470' y1='401.498' x2='403' y2='401.498' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint8_linear_0_9475' x1='1119.5' y1='90' x2='1119.5' y2='157' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint9_linear_0_9475' x1='1119.5' y1='228' x2='1119.5' y2='161' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint10_linear_0_9475' x1='1050' y1='160.498' x2='1117' y2='160.498' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint11_linear_0_9475' x1='1189' y1='160.498' x2='1122' y2='160.498' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint12_linear_0_9475' x1='1279.5' y1='411' x2='1279.5' y2='478' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint13_linear_0_9475' x1='1279.5' y1='549' x2='1279.5' y2='482' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint14_linear_0_9475' x1='1210' y1='481.498' x2='1277' y2='481.498' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint15_linear_0_9475' x1='1349' y1='481.498' x2='1282' y2='481.498' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='white' stop-opacity='0'/%3E%3Cstop offset='0.377' stop-color='white' stop-opacity='0.255'/%3E%3Cstop offset='0.675' stop-color='white' stop-opacity='0.511'/%3E%3Cstop offset='0.925' stop-color='white' stop-opacity='0.705'/%3E%3Cstop offset='1' stop-color='white'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
}
.up-bgart-licht, .up-bgart-bogen, .up-bgart-raster, .up-bgart-marken,
.up-bgart-flimmer { position: absolute; inset: 0; }
/* Nur die drei Masken. Die Lichter haben keine, und background-color wuerde ihre Flaeche voll
   ausmalen, weil nichts sie beschneidet. */
.up-bgart-bogen, .up-bgart-raster, .up-bgart-marken {
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  /* Dieselbe Groesse und Lage fuer alle drei: nur so liegen sie deckungsgleich. */
  -webkit-mask-size: cover; mask-size: cover;
  /* --vc-text dreht sich mit dem Thema: dunkel ist es hell, hell ist es dunkel. Genau das braucht
     eine Vorlage, die fuer weiss auf dunkel gezeichnet wurde. */
  background-color: var(--vc-text);
}
.up-bgart-bogen  { -webkit-mask-image: var(--up-bgart-bogen);  mask-image: var(--up-bgart-bogen); }
.up-bgart-raster { -webkit-mask-image: var(--up-bgart-raster); mask-image: var(--up-bgart-raster); }
.up-bgart-marken { -webkit-mask-image: var(--up-bgart-marken); mask-image: var(--up-bgart-marken); }
/* Die Lichter in --vc-muted und leise. Sie sind in der Vorlage mit mittlerem Grau gemalt; mit
   nahezu Weiss gefuellt hoben sie den ganzen Grund an, und weil sie fast die ganze Flaeche decken,
   sah man beim Wechsel auf einen Schritt ohne Bild einen Sprung. Gerechnet in L* auf dunklem
   Grund, mit dem staerksten Licht der Vorlage:
     --vc-text, Ebene 1.0     +16.6   viel zu hell
     --vc-muted, Ebene 1.0    +11.5   immer noch ein Sprung
     --vc-muted, Ebene 0.2     +1.3   so leise wie das Raster (dL* 1.9)
   Auf #121212 statt #1b1b1b gerechnet ergibt dasselbe: Raster 1.83, Lichter 1.29 -- der Grund von
   Mira braucht also keine eigenen Werte. */
.up-bgart-licht { background-image: var(--up-bgart-licht); opacity: .2; }
.up-root[data-theme="dark"] .up-bgart-licht { opacity: .2; }
/* Das Raster soll im Hellen GENAUSO wirken wie im Dunkeln -- nicht denselben Alphawert haben,
   sondern denselben wahrgenommenen Abstand zum Grund. Gerechnet in L*, weil der Augenabstand dort
   linear ist:
     dunkel  Strich #e0e0e0 auf #1b1b1b, wirksames Alpha 0.06 x 0.33 = 0.0198  ->  dL* 1.94
     hell    Strich #1f1f1b auf #ffffff, dafuer noetiges Alpha 0.025           ->  Ebene 0.42
   Mit voller Ebene waere es dL* 4.67, also zweieinhalbmal so kraeftig -- genau das war gemeldet. */
.up-bgart-bogen  { opacity: .45; }
.up-bgart-raster { opacity: .42; }
/* Die vier Sternmarken sind NICHT MEHR EINGESETZT -- an ihrer Stelle liegt das Flimmerraster
   darunter (07.09. angefordert). Bild, Klasse und Gewicht bleiben trotzdem stehen: der Name ist
   Teil des Vertrags nach aussen (an einem .up-bgart-*-Namen kann CSS in der Bubble-App haengen),
   und wer die Marken zurueckwill, haengt das Element wieder ein, ohne dass hier etwas
   nachgezogen werden muss. */
.up-bgart-marken { opacity: .45; }

/* ---------- Flimmerraster ----------
   Das Bild malt UC.makeFlickerGrid in ein Canvas (siehe dort: warum ein Canvas, was gegen die
   Vorlage von magicui geaendert ist, und was fuer die Leistung getan ist). HIER stehen nur die
   zwei Dinge, die zur Kaskade gehoeren: die Farbe und die Maske.

   DIE FARBE ueber color und nicht als Angabe im Aufruf: das Canvas liest sie von seinem
   Elternteil und noch einmal bei jedem Themenwechsel. Der angeforderte Aufruf traegt
   color="rgb(0, 0, 0)" -- das ist die richtige Farbe fuer HELL und im Dunkeln unsichtbar. Ueber
   --vc-text ist es im Hellen genau dieses Schwarz (#1f1f1b) und im Dunkeln die Textfarbe des
   dunklen Themas, und es gibt keinen zweiten Ort, an dem jemand eine Farbe pflegen muesste.

   KEINE DAEMPFUNG MEHR UEBER DIE EBENE. Hier stand opacity .388 / .32, damit die Deckkraft des
   Kits (damals 0.25) in beiden Themen auf denselben wahrgenommenen Abstand kommt. Angefordert
   ist jetzt maxOpacity 0.4, und zwar als die Zahl, die magicui auch benutzt -- dort gibt es
   keine Ebene darueber. Also ist die Deckkraft des Kits die ganze Wahrheit: 0.4, wie bestellt.
   Die Punkte sind dafuer viel kleiner und weiter auseinander (3px auf 23px Abstand statt 5 auf
   18), sie bedecken also 1.7 Prozent der Flaeche statt 7.7 -- deutlich weniger Farbe insgesamt,
   bei kraeftigeren einzelnen Punkten. Wird es im Dunkeln zu laut, ist es EINE Zeile hier.

   DIE MASKE HAELT DIE MITTE FREI. Dort steht der Inhalt der Seite, und ein Raster hinter einer
   Ueberschrift ist kein Hintergrund mehr, sondern Unruhe. Aussen voll, innen nichts, mit einem
   langen Verlauf dazwischen -- eine harte Kante waere als Kreis zu sehen.

   ZWEIMAL GEAENDERT, und die Zahlen sagen warum. Zuerst stand hier
       ellipse 58% 54%, transparent 0%, #000 100%
   also ein Verlauf, der SOFORT beim Mittelpunkt anfaengt. Auf 1440x890 heisst das: am Rand des
   Textblocks (240px von der Mitte) liegt die Maske schon bei 29 Prozent, es stehen also Punkte
   mit knapp einem Drittel Staerke direkt hinter der Ueberschrift. Im Dunkeln fallen die kaum
   auf, im Hellen sind es dunkle Punkte auf Weiss -- gemeldet als "wirkt gerade im Lightmode
   noch etwas unruhig mittig im Bild".
   Jetzt
       ellipse 74% 70%, transparent 0%, transparent 30%, #000 100%
   Der erste Teil ist groesser (Radien 1066 x 623 statt 835 x 481), und der zweite ist neu: die
   ersten 30 Prozent sind VOLLSTAENDIG frei. Auf 1440x890 sind das 320px zu den Seiten und 187px
   nach oben und unten -- der Textblock (480px breit) liegt damit ganz in der freien Zone, und
   der Verlauf beginnt erst dahinter. Am Rand der Karte (480px von der Mitte) steht die Maske
   bei 21 Prozent statt bei 57.

   EINE Geometrie fuer beide Themen und nicht zwei. Gemeldet war das Helle -- das ist der
   strengere Fall, weil dunkle Punkte auf Weiss mehr auffallen als helle auf Dunkel. Wer nach
   dem strengeren Fall bemisst, hat den leichteren mit erledigt; zwei Masken waeren zwei
   Wahrheiten fuer dieselbe Frage. */
.up-bgart-flimmer {
  color: var(--vc-text);
  -webkit-mask-image: radial-gradient(ellipse 74% 70% at 50% 46%,
                        transparent 0%, transparent 30%, #000 100%);
          mask-image: radial-gradient(ellipse 74% 70% at 50% 46%,
                        transparent 0%, transparent 30%, #000 100%);
}
/* Dunkel: das Raster auf ein Drittel -- die Vorlage zeichnet es mit 6%, gemeint waren 2%, und
   genau die kommen so heraus. Die Marken auf die Haelfte, sie waren zu hell. Der Bogen voll, er
   ist die einzige Linie, die tragen soll. */
.up-root[data-theme="dark"] .up-bgart-bogen  { opacity: 1; }
.up-root[data-theme="dark"] .up-bgart-raster { opacity: .33; }
.up-root[data-theme="dark"] .up-bgart-marken { opacity: .5; }

/* ══ Tarifkarten (UC.makePlans, 28.09.) ═════════════════════════════════════════════════════════
   Die Karte der Landingpage (landing-hero.css, die ulh-preis-Regeln), in die App geholt. Gleich
   geblieben sind der Aufbau, die Abstaende (Polster 28/26/30, Luecke 20, Name 6 ueber der
   Unterzeile, Betrag 18 darunter, Knopf 20, Liste 22 mit 9 zwischen den Zeilen), das Etikett auf
   der mittleren Karte, der Ring und die aufklappende Ersparnis.

   AUF DIE SKALA GEZOGEN hat sich, was die Landingpage mit eigener Typoskala fuehren darf und die
   App nicht (CLAUDE.md 1c):
       Radius 18        -> 16  (--up-r-2xl)
       Name 17          -> 16  (--up-fs-xl)
       Betrag 38        -> var(--up-fs-display), also weiter 38: das ist die Stufe fuer genau
                           diese Rolle, "die EINE grosse Zahl einer Karte"
       Merkmale 13.5    -> 13  (--up-fs-m)
       Etikett 11.5     -> 11  (--up-fs-xs)
       Uebergang 220ms  -> --up-t-2 (200ms)
       Knopf            -> .up-btn-pri.is-lg aus core (40 hoch, Radius 12, 14/600) statt des
                           Landing-Knopfs (40, Radius 10, 14/500) -- ein zweiter Hauptknopf
                           neben dem der App waere genau der Zwilling, den core verhindern soll
   Die Onboarding-Fassung (15/26, dichter gesetzt) ist KEIN Vorbild hier: dort muss der ganze
   Ablauf auf 760px Hoehe passen.

   FARBEN nur aus Marken: die Karte steht in der App auf .up-root, und in einem Fenster am body
   (settings-billing) haengt die Marke an dessen .up-root-Klasse. Die Ersparnis traegt dasselbe
   Gruen wie Landingpage und Onboarding -- mit demselben Rueckfall wie dort, weil --vc-pos in
   core nie gesetzt wurde. */
.up-plans { display: flex; flex-direction: column; min-width: 0; }
.up-plans-switch { display: flex; justify-content: center; }
.up-plans-row {
  margin-top: 26px;
  display: grid; grid-template-columns: repeat(var(--up-plan-cols, 3), minmax(0, 1fr)); gap: 20px;
  align-items: start;
}
/* Untereinander, sobald die REIHE schmal ist (widthTiers am .up-plans, siehe makePlans). Die
   Unterzeile braucht dann keine Mindesthoehe mehr -- die haelt nur drei Koepfe nebeneinander auf
   einer Linie. */
.up-plans.is-narrow .up-plans-row { grid-template-columns: minmax(0, 1fr); gap: 22px; }
.up-plans.is-narrow .up-plan-desc { min-height: 0; }
.up-plan {
  position: relative; display: flex; flex-direction: column; text-align: left; min-width: 0;
  padding: 28px 26px 30px; border: 1px solid var(--vc-border); border-radius: var(--up-r-2xl);
  background: var(--vc-bg); color: var(--vc-text); box-shadow: var(--up-e-1);
}
/* Zweiter Ring statt 2px Rahmen, sonst spraenge der Inhalt um einen Pixel gegen die Nachbarn
   (derselbe Kniff wie auf der Landingpage und im Onboarding). Welche Karte ihn traegt, entscheidet
   makePlans: der Tarif des Nutzers, sonst die mittlere. */
.up-plan.is-ring { border-color: var(--up-accent); box-shadow: 0 0 0 1px var(--up-accent), var(--up-e-1); }
.up-plan-tag {
  position: absolute; top: -11px; left: 26px; height: 22px; padding: 0 10px; border-radius: var(--up-r-voll);
  background: var(--up-accent); color: var(--up-accent-fg);
  font-size: 11px; font-weight: 600; letter-spacing: .02em; white-space: nowrap;
  display: inline-flex; align-items: center;
}
.up-plan-name { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; color: var(--vc-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Die Unterzeile sagt, fuer WEN der Tarif ist -- sie gehoert vor die Zahl. Mindesthoehe fuer zwei
   Zeilen, damit die drei Betraege nebeneinander auf einer Hoehe stehen. */
.up-plan-desc { margin: 6px 0 0; font-size: 14px; line-height: 1.45; color: var(--vc-third);
  text-wrap: pretty; min-height: 40px; }
.up-plan-price { margin-top: 18px; display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.up-plan-amt { font-size: var(--up-fs-display); font-weight: 600; letter-spacing: -.035em;
  line-height: 1.05; font-variant-numeric: tabular-nums; color: var(--vc-text); white-space: nowrap; }
.up-plan-per { font-size: 14px; color: var(--vc-third); white-space: nowrap; }
/* Klappt auf und zu (0fr -> 1fr) statt zu erscheinen: so schiebt sie die Kartenhoehe weich mit. */
.up-plan-note { display: grid; grid-template-rows: 0fr; opacity: 0;
  transition: grid-template-rows var(--up-t-2) var(--up-ease), opacity var(--up-t-2) var(--up-ease); }
.up-plan-note > span { overflow: hidden; min-height: 0; display: block; padding-top: 6px;
  font-size: 13px; font-weight: 500; color: var(--vc-pos, #2e9e63); }
.up-plan-note.is-on { grid-template-rows: 1fr; opacity: 1; }
.up-plan-cta { margin-top: 20px; display: flex; }
.up-plan-cta > .up-btn-pri { width: 100%; }
/* "Current plan" an der Stelle des Knopfes: dieselbe Geometrie wie .up-btn-pri.is-lg (40 hoch,
   Radius 12, Polster 20, Abstand 9, 14/600), damit die Karte nicht kuerzer wird -- aber als
   ruhige Flaeche und ohne Zeiger, weil es hier nichts zu kaufen gibt. Der Haken in der Tinte der
   Akzentfarbe, wie die You-Marke in team-orga. */
.up-plan-mine {
  width: 100%; height: 40px; padding: 0 20px; border-radius: var(--up-r-l); gap: 9px;
  border: 1px solid var(--vc-border); background: var(--vc-heading-bg); color: var(--vc-text);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 600; letter-spacing: -.01em; white-space: nowrap;
  cursor: default; user-select: none;
}
.up-plan-mine svg { width: 15px; height: 15px; flex: 0 0 auto; display: block; color: var(--up-accent-ink); }
.up-plan-feats { margin: 22px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 9px; }
.up-plan-feat { display: flex; align-items: flex-start; gap: 9px; font-size: 13px; line-height: 1.4; color: var(--vc-third); }
.up-plan-feat b { font-weight: 600; color: var(--vc-text); }
.up-plan-ic { display: inline-flex; flex: 0 0 auto; margin-top: 2px; color: var(--vc-text); }
.up-plan-ic svg { width: 15px; height: 15px; display: block; }
.up-plans-foot { margin: 30px 0 0; font-size: 13px; line-height: 1.45; color: var(--vc-third); }
/* Das Skelett: Polster, Rahmen und Radius der echten Karte, damit das Raster beim Eintreffen der
   Tarife stehen bleibt. Kein Schatten -- eine Huelle soll nicht wie eine fertige Karte wirken. */
.up-plan.is-skel { gap: 10px; box-shadow: none; }
/* Hoehen und Abstaende nach den Zeilen der Karte: Name 16, Unterzeile 2 x 12, Betrag 38 (18
   darueber), Knopf 40 (20 darueber), Liste mit 22 darueber. Die Balken sind Platzhalter, keine
   Nachbildung -- es geht darum, dass die Kartenhoehe beim Eintreffen stehen bleibt. */
.up-plan-sk-name { width: 44%; height: 16px; }
.up-plan-sk-desc { width: 86%; height: 12px; }
.up-plan-sk-desc.is-kurz { width: 58%; }
.up-plan-sk-amt  { width: 52%; height: 38px; border-radius: var(--up-r-xl); margin-top: 16px; }
.up-plan-sk-note { width: 46%; height: 12px; }
.up-plan-sk-btn  { width: 100%; height: 40px; border-radius: var(--up-r-l); margin-top: 14px; }
.up-plan-sk-z    { width: 88%; height: 12px; }
.up-plan-sk-btn + .up-plan-sk-z { margin-top: 16px; }
.up-plan-sk-z.is-kurz { width: 64%; }

/* ---------- Das Tarif-Fenster (UC.makePlanDialog, 28.09.) ----------
   Stand bis zum 28.09. als .ubl-* in settings-billing.css, dieselben Werte. Die Schale ist core's
   Modal; hier steht nur, was dieses Fenster anders macht.
   z-index 100002: ueber dem Access Gate (100001), das es als seine Tarifliste oeffnet, und ueber
   core's anderen Fenstern (100000). Zwei Klassen gegen die eine von .up-topicmodal-backdrop. */
.up-plandlg.up-topicmodal-backdrop { z-index: 100002; }
/* 1160 (28.09. angefordert: "etwas mehr max width"; vorher 1000): drei Karten zu gut 350 plus
   zwei Luecken zu 20 plus das Polster der Karte. Bei 1000 standen sie knapp 300 breit, die Karten
   der Landingpage bei 1280 Fensterbreite knapp 390 -- darunter wird ihre Merkmalsliste eng.
   Zwei Klassen, weil .up-topicmodal-card eine eigene Breite (420) setzt. */
.up-plandlg .up-plandlg-card { width: 100%; max-width: 1160px; }
.up-plandlg .up-plandlg-card.up-geist { max-height: min(900px, 90vh); }
.up-plandlg-card:focus { outline: 0; }
.up-plandlg-body { min-width: 0; }
/* Der Fehlersatz unter den Karten steht nur da, wenn ein Klick keinen Empfaenger fand; zu ist er
   eine Zeile der Hoehe 0 und naehme trotzdem die Luecke der Karte (32) mit. */
.up-plandlg-card > .up-plandlg-err:not(.is-on) { display: none; }
.up-plandlg-card > .up-plandlg-err.is-on { margin: 0 var(--up-modal-pad) var(--up-modal-pad); }
@media (prefers-reduced-motion: reduce){ .up-plan-note { transition: none; } }

/* ---------- DIE GESAMTZEILE EINER TABELLE (.up-sumrow, UC.makeSumZeile, 15.10.) ----------
   Eine feste Zeile unter den Zeilen und ueber der Pagination (Empfehlung vom 15.10., vom Nutzer
   bestellt). Links NUR die Anzahl, in den Spalten eine Berechnung, die der Nutzer waehlt -- von
   Haus aus keine: dort steht "+ Calculation" (15.10. spaet: "per default nix an calculations"; erst "+ Add", umbenannt, "sonst denkt man, man kann da was hinzufuegen"). Jede
   Angabe ist ein Paar: der Wert in der Primaerfarbe und eine Stufe groesser als die Zellenschrift
   (16 statt der 14 von .up-num), dann Abstand, dann die Bezeichnung in der VIERTFARBE, ebenso
   "+ Calculation" und das Zeichen am Knopf ("alles was in dritt color ist -> in viert color").
   Primaerfarbe heisst hier --up-accent-ink: die Akzentfarbe als SCHRIFT auf dem Seitengrund,
   nicht --up-accent (die Flaeche, siehe die Akzente oben).
   Vorbild der Geometrie ist der Gruppenkopf der Prompt-Tabelle (prompts-table.css, .upt-grp-head):
   dasselbe Raster wie .up-row (--up-cols) und dasselbe Zellpolster wie .up-td (0 16 0 12). Anders
   als die Zeilen traegt sie SENKRECHTE LINIEN an den Zellgrenzen und steht in den Spalten
   RECHTSBUENDIG (beides bestellt, 15.10. spaet): eine Rechenzeile unter der Tabelle wie bei
   Attio. Auch die Anzahl steht rechts in IHRER Zelle, der ersten ("das x gesamt auch rechts
   buendig") -- links heisst: in der linken Zelle, nicht an ihrer linken Kante.
   Hoehe wie Kopf und Fuss der flachen Tabelle (--up-flach-h, 48), ohne data-flach dieselben 48.
   Oben eine Linie; die letzte Zeile darueber hat keine (.up-tbody > .up-row:last-child), also
   steht nie eine doppelte. Die Linie ist ein Schatten nach innen wie unter dem Kopf der flachen
   Tabelle und kein Rahmen: mit 1px Rahmen blieben 47 fuer den Inhalt, und der 28er Knopf stand
   auf halben Pixeln (gemessen 813.5). Grund ist die Seitenfarbe und deckend: mit fester Hoehe
   klebt die Zeile unten im scrollenden Kasten, und die Zeilen laufen darunter durch.
   position: relative, damit das Menue der Zeile (.up-summenu) an ihr haengt; mit fester Hoehe
   sticky, und sticky ist ebenso ein Bezugsrahmen. */
.up-sumrow {
  display: grid; grid-template-columns: var(--up-cols); align-items: center;
  position: relative; height: var(--up-flach-h, 48px); flex: 0 0 var(--up-flach-h, 48px);
  box-shadow: inset 0 1px 0 var(--vc-border); background: var(--up-surface);
}
.up-sumrow.is-aus { display: none; }
/* Im Leer- und Fehlerzustand keine Gesamtzeile (22.10., core.js ohneDatenZeilen; Lukas zu Page Audit Frage 10). Getrennt von
   is-aus: das ist die Wahl des Nutzers im Display-Menue, die bleibt, wenn die Zeilen zurueckkommen. */
.up-sumrow.is-leer { display: none; }
/* Rechts das Auslauf-Polster wie jede Zeile der flachen Tabelle, links nichts (core.css "DIE
   FLACHE TABELLE": das Zellpolster 12 ist der Abstand zur Kante). */
.up-root.up-root[data-flach="yes"] .up-sumrow { padding-left: 0; padding-right: var(--up-flach-aus); }
/* Feste Hoehe (UC.makeFestHoehe): unten am Kasten kleben, damit die Summe ueber der Pagination
   stehen bleibt, waehrend die Zeilen scrollen. z 4: unter dem klebenden Kopf (5), ueber den
   Zeilen. Der Bezugsrahmen ist .up-table, das alle Zeilen umfasst -- die Zeile klebt also ueber
   die ganze Scrollstrecke. */
.up-root.up-root[data-flach="yes"].is-festhoehe .up-sumrow { position: sticky; bottom: 0; z-index: 4; }
.up-sumzelle {
  display: flex; align-items: center; justify-content: flex-end; gap: 16px; min-width: 0; height: 100%;
  overflow: hidden; padding: 0 16px 0 12px; border-left: 1px solid var(--vc-border);
}
.up-sumzelle:first-child { border-left: none; }
/* Die Aktionsspur hat in der Gesamtzeile keine Zelle: die Linie an ihrer linken Kante trennt sie
   ab wie die Linien zwischen den Zellen. Breite min(1px, Spur): ohne Spur (0px) keine Linie. */
.up-sumrow::after { content: ""; position: absolute; top: 0; bottom: 0; right: var(--up-akt-w, 0px);
  width: min(1px, var(--up-akt-w, 0px)); background: var(--vc-border); pointer-events: none; }
.up-root.up-root[data-flach="yes"] .up-sumrow::after { right: calc(var(--up-akt-w, 0px) + var(--up-flach-aus)); }
.up-root.is-vnarrow .up-sumrow::after { display: none; }
/* Die erste Zelle traegt heute nur die Anzahl ("102 count"); kann aber mehrere Paare tragen, und
   dann nur, solange sie ganz passen: die Reihe ist eine Zeile hoch, und was nicht mehr daneben passt, bricht in
   eine zweite um, die abgeschnitten ist. Kein Messen, und nie ein halbes Paar. 24 hoch, weil ein
   Paar aus 16er und 13er Schrift gut 21 braucht (seit 15.10. mittig, nicht auf der Grundlinie,
   siehe .up-sumpaar). Der Zeilenabstand
   von 24 schiebt die zweite Zeile ganz unter die Kante: ohne ihn begann sie bei 21 in einem 24
   hohen Kasten, also 3px im Bild (gemessen). Die Anzahl steht vorn und faellt damit als letzte. */
.up-sumreihe {
  display: flex; flex-wrap: wrap; align-items: center; column-gap: 16px; row-gap: 24px;
  height: 24px; min-width: 0; overflow: hidden;
}
.up-sumpaar {
  display: inline-flex; align-items: center; gap: 6px; min-width: 0; flex: 0 1 auto;
  white-space: nowrap; line-height: 20px;
}
/* MITTIG statt auf der Grundlinie (15.10. spaet: "den Count selber mittig zentrieren"): auf der
   gemeinsamen Grundlinie stand die 16er Zahl gemessen 2.2px ueber der Zeilenmitte, das 13er Wort
   und "+ Calculation" daneben auf ihr. */
.up-sumwert {
  flex: 0 0 auto; font-size: var(--up-fs-xl); font-weight: var(--up-fw-h);
  color: var(--up-accent-ink); font-variant-numeric: tabular-nums;
}
/* Ein fehlender Wert ist "–" in der Farbe jeder leeren Zelle (.up-num.is-empty) und nie 0: eine
   Null in der Primaerfarbe waere eine Aussage, die niemand gemacht hat. */
.up-sumwert.is-leer { color: var(--vc-fourth); }
/* Eine Berechnung, die nicht geholt werden konnte (sumFehlerHtml, 17.10.): ein eigener Zustand und
   nicht der Strich fuer "kein Wert". 13 statt 16, damit der Satz in eine schmale Zelle passt und
   nicht wie eine Zahl aussieht; Fehlerfarbe wie jeder Fehlersatz der App (.up-fehlerzeile). */
.up-sumwert.is-fehler { font-size: var(--up-fs-m); font-weight: var(--up-fw-m); color: var(--vt-down); }
.up-sumlabel {
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
  font-size: var(--up-fs-m); font-weight: var(--up-fw-m); color: var(--vc-fourth);
}
/* Eine Zelle mit Auswahl (Gesamtwert, Durchschnitt, Zaehlwert): das Paar ist der Knopf. Polster und
   Gegenrand gleich gross, damit der Wert auf der Kante der Spalte bleibt und der Knopf keinen
   Platz im Raster nimmt -- die Flaeche beim Ueberfahren laeuft 6 in das Zellpolster. Das Zeichen
   dahinter dreht sich nie (Hausregel), es sagt nur, dass hier eine Auswahl ist.
   DAS ZEICHEN WEICHT ZUERST, nicht die Bezeichnung: der Knopf bricht um wie die Reihe der ersten
   Zelle, und ein Zeichen, das nicht mehr daneben passt, faellt in die abgeschnittene zweite
   Zeile. Gemessen an Rank auf seiner Mindestbreite (108, erste Spalte breit gezogen): mit festem
   Zeichen stand dort "3.0 med…" (44.6 statt 33.4px fuer "median"). Die erste Zeile muss dabei 28
   hoch bleiben, sonst rutscht das Paar ohne Zeichen an die Oberkante (gemessen dy 0 statt 3.5):
   dafuer die Stuetze ::before, 0 breit und 28 hoch, ihr Abstand zum Paar mit -4 aufgehoben. */
.up-sumknopf {
  display: inline-flex; flex-wrap: wrap; align-items: center; column-gap: 4px; row-gap: var(--up-h-seg);
  min-width: 0; max-width: calc(100% + 12px); overflow: hidden;
  height: var(--up-h-seg); padding: 0 6px; margin: 0 -6px; border: 0; border-radius: var(--up-r-m);
  background: transparent; color: inherit; font: inherit; cursor: pointer;
  transition: background var(--up-t-1) ease;
}
.up-sumknopf::before { content: ""; flex: 0 0 0px; width: 0; height: var(--up-h-seg); margin-right: -4px; }
/* DAS PAAR PASST SICH DEM KNOPF AN (15.10. spaet): ein Paar, das breiter ist als der Knopf, brach
   als Ganzes in die abgeschnittene zweite Zeile -- 66px tief in der 48er Zelle, NICHTS war zu
   sehen, nicht einmal der Wert (gemessen in Teams: "Developer Access most common" in 172px, in
   Brands "Rising / falling" bei 143px). Begrenzt auf die Knopfbreite bleibt es in der ersten Zeile:
   der Wert steht ganz (flex 0 0 auto), die Bezeichnung kuerzt mit Auslassungspunkten. */
.up-sumknopf > .up-sumpaar { max-width: 100%; }
/* LANGE NAMEN KUERZEN MIT (15.10. spaet): eine Zahl steht immer ganz, ein Name ("UGC / Community"
   in 150px, "08. Oct – 14. Oct" in 120px) darf kuerzen -- sonst schnitt der Knopf ihn hart ab.
   Zuerst weicht die Bezeichnung dahinter (flex-shrink 1000 gegen 1), dann der Name mit "…". */
.up-sumwert.is-text { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.up-sumpaar > .up-sumlabel { flex: 0 1000 auto; }
/* Das Durchschnittszeichen statt "avg" (sumPaarHtml): 13 wie die Schrift der Bezeichnung, in ihrer
   Farbe, weicht nie (ein halbes Zeichen liest sich nicht). */
.up-sumpaar > .up-sumlabel.up-sumavg { flex: 0 0 auto; display: inline-flex; align-items: center; }
.up-sumavg svg { width: 13px; height: 13px; display: block; }
.up-sumknopf:hover, .up-sumrow.is-open .up-sumknopf.is-offen { background: var(--vc-heading-bg); }
.up-sumknopf:focus-visible { outline: 0; box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); }
.up-sumchev { display: inline-flex; align-items: center; flex: 0 0 12px; width: 12px; height: var(--up-h-seg); color: var(--vc-fourth); }
.up-sumchev svg { width: 12px; height: 12px; display: block; }
/* "+ Calculation": eine Spalte ohne gewaehlte Berechnung. Als Knopf (.up-sumknopf.up-sumadd), wo die
   Tabelle Berechnungen anbietet -- er oeffnet dasselbe Menue wie ein gewaehlter Wert. Wo sie keine
   hat, steht seit 20.10. ein Minus (.up-sumkeine) statt des Texts: kein Ueberfahren, kein Zeiger, weil kein Klick etwas taete. Zeichen 12 wie
   das Zeichen am Knopf, Schrift wie die Bezeichnung eines Paars. */
.up-sumadd {
  display: inline-flex; align-items: center; gap: 4px; min-width: 0; white-space: nowrap;
  font-size: var(--up-fs-m); font-weight: var(--up-fw-m); color: var(--vc-fourth);
}
span.up-sumadd { height: var(--up-h-seg); cursor: default; }
.up-sumadd svg { flex: 0 0 12px; width: 12px; height: 12px; display: block; }
/* Als Knopf gegen font/color: inherit von .up-sumknopf -- ueber zwei Klassen, nicht ueber die
   Reihenfolge. Kein Umbruch und keine Stuetze: "+ Calculation" ist ein Stueck, es gibt kein Zeichen, das
   zuerst weichen koennte. */
.up-sumknopf.up-sumadd {
  flex-wrap: nowrap; gap: 4px;
  font-size: var(--up-fs-m); font-weight: var(--up-fw-m); color: var(--vc-fourth);
}
.up-sumknopf.up-sumadd::before { content: none; }
/* "+ Calculation" kuerzt mit "…", wenn die Zelle zu schmal ist (20.10.: vorher schnitt der Knopf rechts hart ab). Das Zeichen bleibt ganz, der Text
   weicht; bei noch weniger Platz steht nur das "+". Die Mindestbreiten der Spalten bleiben, wie sie sind. */
.up-sumadd > .up-sumadd-t { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.up-sumknopf.up-sumadd { max-width: calc(100% + 12px); }
/* Eine Spalte ohne Berechnung: ein Minus in der Farbe jeder leeren Zelle, kein Knopf, kein Zeiger. */
.up-sumkeine { display: inline-flex; align-items: center; justify-content: center; height: var(--up-h-seg); color: var(--vc-fourth); cursor: default; }
.up-sumkeine svg { width: 14px; height: 14px; display: block; }
/* Das Menue klappt nach OBEN auf: die Zeile steht unten, darunter kommt nur noch die Pagination.
   Es haengt an der Zeile (eine Zeile, ein Menue), die Lage links setzt makeSumZeile an den Knopf.
   Zwei und drei Klassen gegen .up-menu / .up-menu.is-shown -- nie die Ladereihenfolge. Liegt es
   doch einmal verdeckt (ein sehr niedriger Kasten), hebt menuEscape es in den Top Layer. */
.up-menu.up-summenu {
  top: auto; right: auto; bottom: calc(100% + var(--up-dd-gap)); left: 0;
  width: 220px; transform: translateY(4px) scale(0.985);
}
.up-menu.up-summenu.is-shown { transform: translateY(0) scale(1); }
/* Telefon (is-vnarrow): nur die Anzahl, ueber die ganze Breite. Unter 620 stehen ohnehin nur
   noch Prompt und Visibility; drei Kennzahlen mit Menue in zwei Spalten waeren ein Bedienfeld,
   das der Daumen nicht trifft, und die Anzahl ist die eine Zahl, die dort zaehlt. */
.up-root.is-vnarrow .up-sumzelle:not(:first-child) { display: none; }
.up-root.is-vnarrow .up-sumzelle:first-child { grid-column: 1 / -1; }
.up-root.is-vnarrow .up-sumreihe .up-sumpaar + .up-sumpaar { display: none; }

/* ══ DIE ANLEITUNG (15.10., UC.makeAnleitung) ═══════════════════════════════════════════════════
   Die Erklaerkarte der App (.up-explain: Grund, Tinte, Radius, Polster, Tiefe e-2, die helle Platte,
   Titel 14.5/600, Satz 13.5) mit dem, was eine Fuehrung braucht. Nur was ANDERS ist, steht hier:
   breiter (zwei Knoepfe und ein Satz brauchen mehr als 248), klickbar (die Erklaerkarte laesst
   Klicks durch), ohne den Versatz beim Einblenden (sie springt von Schritt zu Schritt an eine
   neue Stelle -- ein Gleiten von 4px dort waere Unruhe), und die Spitze auch links und rechts.
   Knoepfe in der Sprache des Toasts, der zweiten umgekehrten Karte: Flaeche --vc-toast-flaeche,
   Hoehe 28 (--up-h-seg), Radius nach Rolle (Geist, 15.10.: Knopf 25-36 hoch -> 6, --up-r-m);
   Fokus wie jeder Knopf mit --up-knopf-fokus-abstand. Der Hauptknopf
   ist die Platte noch einmal: Grund in der Tinte der Karte, Schrift in ihrem Grund. */
.up-explain.up-anleitung { width: 296px; max-width: calc(100vw - 16px); transform: none; }
/* Klickbar nur, solange sie zu sehen ist -- beim Ausblenden (140ms) faengt sie nichts mehr ab. */
.up-explain.up-anleitung.is-on { pointer-events: auto; }
.up-anleitung:focus { outline: 0; }
.up-anleitung .up-anl-zahl {
  padding: 0 3px; margin-bottom: 4px; font-size: var(--up-fs-xs); font-weight: var(--up-fw-m); line-height: 1.3;
  color: color-mix(in srgb, var(--vc-inverse-ink) 55%, transparent); font-variant-numeric: tabular-nums;
}
.up-anl-fuss { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
.up-anl-knoepfe { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; flex: 0 0 auto; }
.up-anl-btn {
  height: var(--up-h-seg); padding: 0 12px; border: 0; border-radius: var(--up-r-m);
  background: var(--vc-toast-flaeche); color: var(--vc-inverse-ink);
  font-family: inherit; font-size: var(--up-fs-m); font-weight: var(--up-fw-m); line-height: 1; white-space: nowrap; cursor: pointer;
  transition: background var(--up-t-1) var(--up-ease);
}
.up-anl-btn:hover { background: var(--vc-toast-flaeche-hover); }
.up-anl-btn.is-pri { background: var(--vc-inverse-ink); color: var(--vc-inverse-bg); }
.up-anl-btn.is-pri:hover { background: color-mix(in srgb, var(--vc-inverse-ink) 86%, var(--vc-inverse-bg)); }
.up-anl-btn:disabled, .up-anl-btn:disabled:hover { opacity: .45; cursor: default; background: var(--vc-toast-flaeche); }
.up-anl-btn.is-pri:disabled, .up-anl-btn.is-pri:disabled:hover { background: var(--vc-inverse-ink); }
/* "Skip guide" leise wie der Satz darueber (68 Prozent der Tinte) -- die Nebenhandlung. Das
   Polster links holt die Schrift auf die Kante des Satzes (der steht mit 3px Polster). */
.up-anl-skip {
  height: var(--up-h-seg); padding: 0 6px; margin-left: -3px; border: 0; border-radius: var(--up-r-m); background: transparent;
  color: color-mix(in srgb, var(--vc-inverse-ink) 68%, transparent);
  font-family: inherit; font-size: var(--up-fs-s); font-weight: var(--up-fw-m); line-height: 1; white-space: nowrap; cursor: pointer;
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
  transition: background var(--up-t-1) var(--up-ease), color var(--up-t-1) var(--up-ease);
}
.up-anl-skip:hover { color: var(--vc-inverse-ink); background: var(--vc-toast-flaeche); }
.up-anl-btn:focus-visible, .up-anl-skip:focus-visible { outline: var(--up-focus-w) solid var(--up-focus); outline-offset: var(--up-knopf-fokus-abstand); }
/* Die Spitze: unten (Karte unter dem Element) ist die von .up-explain, oben .is-flipped von dort.
   Links und rechts neu, in denselben Massen gedreht (12 breit, 11 tief, 6 ueber der Kante). */
.up-explain.up-anleitung[data-seite="rechts"]::before {
  top: var(--up-caret-y, 50%); left: -6px; transform: translateY(-50%);
  border-top: 12px solid transparent; border-bottom: 12px solid transparent;
  border-right: 11px solid var(--vc-inverse-bg); border-left: 0;
}
.up-explain.up-anleitung[data-seite="links"]::before {
  top: var(--up-caret-y, 50%); left: auto; right: -6px; transform: translateY(-50%);
  border-top: 12px solid transparent; border-bottom: 12px solid transparent;
  border-left: 11px solid var(--vc-inverse-bg); border-right: 0;
}
/* Mittig (kein Element) und unten angedockt (kein Platz daneben): ohne Spitze -- sie zeigte ins
   Leere. Angedockt ohne Platte: die Karte soll dort so wenig wie moeglich zudecken. */
.up-explain.up-anleitung[data-seite="mitte"]::before, .up-explain.up-anleitung[data-seite="dock"]::before { display: none; }
.up-anleitung[data-seite="dock"] .up-anl-vis { display: none; }
.up-explain.up-anleitung[data-seite="dock"] { box-shadow: var(--up-e-3); }
/* Zuruecktreten im Dock, solange ein Menue offen steht (makeAnleitung, zuruecktreten): nur die
   Deckkraft, die Karte bleibt an ihrer Stelle. visibility folgt erst nach der Ausblendung, damit
   sie dann auch fuer Tab und Vorleser weg ist; beim Wiederkommen gilt sofort die Regel darueber
   (ohne Verzoegerung), die Karte blendet von dort aus ein. */
.up-explain.up-anleitung.is-on.is-zurueck {
  opacity: 0; pointer-events: none; visibility: hidden;
  transition: opacity var(--up-t-1) var(--up-ease), visibility 0s linear var(--up-t-1);
}
@media (prefers-reduced-motion: reduce) { .up-anl-btn, .up-anl-skip { transition: none; } }

/* EIN ZEICHENKNOPF IM SEITENKOPF, DER EINEN ZUSTAND HAT (15.10., "Guide" der Events): gedrueckt
   steht er auf der Flaeche einer gewaehlten Zeile (--up-sel-bg) in der Schriftfarbe -- der Knopf
   zeigt den Zustand, das Zeichen bleibt Strich (keine gefuellten Zeichen). */
/* Beide Zeichenknoepfe des Kopfs: .up-ph-iconbtn (die Seiten) und .up-iconbtn (Events, Info).
   Zwei Klassen gegen deren :hover -- gedrueckt bleibt gedrueckt, auch unter dem Zeiger. */
:is(.up-ph-iconbtn, .up-iconbtn)[aria-pressed="true"],
:is(.up-ph-iconbtn, .up-iconbtn)[aria-pressed="true"]:hover { background: var(--up-sel-bg); color: var(--vc-text); }
/* Im Dunkeln setzt core die Zeichenfarbe von .up-iconbtn ueber .up-root[data-theme="dark"] (drei
   Klassen) -- die Farbe des gedrueckten Zustands braucht dort eine Stufe mehr. */
.up-root[data-theme="dark"] :is(.up-ph-iconbtn, .up-iconbtn)[aria-pressed="true"] { color: var(--vc-text); }
/* Im grauen Hell der Gewaehlt-Zustand der Navigation (--up-wahl-bg MIT Rand, 15.10.), als Ring innen
   wie in Seitenleiste und Preferences -- der Knopf hat keinen Rand, ein echter verschoebe das Zeichen. */
html[data-up-hell="grau"] .up-root:not([data-theme="dark"]) :is(.up-ph-iconbtn, .up-iconbtn)[aria-pressed="true"],
html[data-up-hell="grau"] .up-root:not([data-theme="dark"]) :is(.up-ph-iconbtn, .up-iconbtn)[aria-pressed="true"]:hover {
  background: var(--up-wahl-bg); box-shadow: inset 0 0 0 1px var(--up-wahl-rand); }
/* Im Dunkeln nach Geist dasselbe (15.10.): Gray Alpha 100 mit dem Hauch Rand, Herleitung bei
   --up-wahl-bg im Block "DAS DUNKEL NACH GEIST: SEITE UND INHALT". */
html[data-up-dunkel="geist"] .up-root[data-theme="dark"] :is(.up-ph-iconbtn, .up-iconbtn)[aria-pressed="true"],
html[data-up-dunkel="geist"] .up-root[data-theme="dark"] :is(.up-ph-iconbtn, .up-iconbtn)[aria-pressed="true"]:hover {
  background: var(--up-wahl-bg); box-shadow: inset 0 0 0 1px var(--up-wahl-rand); }

/* ================= DIE UMGEKEHRTE KARTE: NEBENSCHRIFT UND GLANZ (16.10., Benachrichtigung) =====
   Eigener Block fuer die schwebende Benachrichtigung (notification-card.css, Variante C "Invers"
   der Leinwand "Notification Varianten"). Zwei Werte, die die umgekehrte Karte bisher nicht hatte;
   an denselben Selektoren wie --vc-inverse-bg/-ink oben, damit sie auch am <body> aufloesen.
   --vc-inverse-sub  die Nebenschrift (Bezeichnung, Text, Zweitknopf, X). Im Hellen derselbe Wert
                     wie --vc-toast-sub. Im Dunkeln NICHT --vc-toast-sub (#6b6f78): auf #eeedef sind
                     das 4.32:1 und damit unter den 4.5, die 13px-Text braucht; #5f636b aus der
                     Vorlage hat 5.17:1. Gerechnet mit den WCAG-Leuchtdichten.
   --vc-inverse-glanz  der Lichtstreifen, der einmal ueber die Karte wandert. Licht ist in beiden
                     Themen weiss -- kein vorhandenes Token traegt Weiss in beiden (--vc-inverse-ink
                     ist im Dunkeln #1f1f1b). Auf der dunklen Karte ein Hauch (9%), auf der hellen
                     kraeftig (75%), sonst ist er auf #eeedef nicht zu sehen. Werte der Vorlage. */
:root, .up-root, [data-theme="light"] {
  --vc-inverse-sub: #a0a0a0;
  --vc-inverse-glanz: rgba(255,255,255,.09);
  /* Die umgekehrte Auswahlkarte (events.js, "Track an Event", 19.10.): Seitengrund, Feld und Schrift des ANDEREN Themas. Im Hellen also die des Dunkels. */
  --up-wk-invers-bg: #0a0a0a; --up-wk-invers-feld: #1f1f1f; --up-wk-invers-ink: #ededed; --up-wk-invers-rand: #2e2e2e;
  /* Der Schatten der gekippten Tafel in den Auswahlkarten (19.10.): rundum, auch nach oben -- die Tafel ist rechts und unten abgeschnitten, ein nach unten fallender
     Schatten (--up-e-*) kaeme gar nicht ins Bild. */
  --up-wk-schatten: 0 -3px 14px rgba(0,0,0,.10), 0 4px 14px rgba(0,0,0,.08);
  /* eine Stufe leiser, fuer die helle Tafel rechts ("Track Citations") */
  --up-wk-schatten-leise: 0 -2px 10px rgba(0,0,0,.06), 0 3px 10px rgba(0,0,0,.05);
  /* DER GRUND EINES POPUPS (19.10., Nutzer): der Seitengrund, nicht die Flaeche einer Karte. Im Hellen Weiss, im grauen Hell der Seitengrund (unten, an
     .up-root im grauen Hell), im Dunkeln #0a0a0a (--up-seite-dunkel). Dialogkarte, Palette und die anderen Fenster lesen nur dieses Token. */
  --up-popup-bg: #ffffff;
  --up-seite-bg: #ffffff;
  /* Fuss eines Popups (Geist Background 2) und die Haarlinie unter dem Kopf und ueber dem Fuss (Gray Alpha 400). */
  --up-popup-fuss: #fafafa; --up-popup-linie: rgba(0,0,0,.08);
  --up-popup-schatten: rgba(0,0,0,.05);
  --up-fs-modaltitel: 20px;   /* Titel eines Popups: Geists text-heading-20, zwischen --up-fs-xl und --up-fs-2xl */
  /* DAS EINGABEFELD (19.10., Nutzer): im Hellen Weiss, auch im grauen Hell und in jedem Popup. Vorher lasen die Felder je Datei etwas anderes (--vc-bg, --up-surface,
     --vc-field, transparent) und fielen in Fenstern mit grauem Grund auseinander. Alle Felder lesen nur dieses Token. */
  --up-feld-bg: #ffffff;
}
[data-theme="dark"], .up-root[data-theme="dark"] {
  --vc-inverse-sub: #5f636b;
  --vc-inverse-glanz: rgba(255,255,255,.75);
  --up-wk-invers-bg: #f2f2f2; --up-wk-invers-feld: #ffffff; --up-wk-invers-ink: #171717; --up-wk-invers-rand: #dcdbdd;
  --up-wk-schatten: 0 -3px 14px rgba(0,0,0,.55), 0 4px 14px rgba(0,0,0,.45);
  --up-wk-schatten-leise: 0 -2px 10px rgba(0,0,0,.35), 0 3px 10px rgba(0,0,0,.3);
  --up-popup-bg: #0a0a0a;
  --up-seite-bg: #0a0a0a;
  --up-popup-fuss: #000000; --up-popup-linie: rgba(255,255,255,.14);
  --up-popup-schatten: rgba(255,255,255,.07);
  --up-feld-bg: #0f0f0f;   /* wie --vc-field im Dunkeln */
}

/* DIE KLEINEN UMSCHALTER AM TELEFON 32 HOCH (18.10., Nutzer): unter 500 (is-vnarrow) sind sie mit 24 bis 26 zu klein fuer
   den Finger. Eine Regel fuer alle, statt je Komponente (domain-detail, domains-table, power-dashboard trugen eigene 24).
   Ausgenommen: der Themen- und Bandschalter im Kopf (eigene Form), die Seitengroesse der Pagination (steht neben
   den 28er Seitenknoepfen). Radius 6 wie jede 32er Form (Rolle nach Hoehe). */
.up-root.up-root.is-vnarrow :is(.up-seg, .cc-seg, .ubo-seg, .tcl-seg, .ubo-yaxis, .tcd-mode, .upt-status, .udt-sub-dispseg, .vc-gran, .cc-gran):not(.up-themesw):not(.up-bandsw) {
  height: 32px; border-radius: var(--up-r-m);
}
.up-root.up-root.is-vnarrow :is(.up-seg, .cc-seg, .ubo-seg, .tcl-seg, .ubo-yaxis, .tcd-mode, .upt-status, .udt-sub-dispseg, .vc-gran, .cc-gran):not(.up-themesw):not(.up-bandsw) :is(.up-seg-btn, .cc-seg-btn, .ubo-seg-btn, .tcl-seg-btn, .ubo-yaxis-btn, .tcd-mode-btn, .upt-status-btn, .udt-sub-disp-btn, .vc-gran-btn, .cc-gran-btn) {
  height: 32px; border-radius: var(--up-r-m);
}

/* ============ EINE FORM FUER ALLE UMSCHALTER (18.10., Nutzer: "kuenftig sind alle Switcher so wie die neuen aus den Topbars") ============
   Die Form der Kopf-Umschalter (.up-themesw, .up-bandsw): keine graue Schiene, ein 1px-Ring aussen (--vc-switch-border), der gleitende
   Streifen in --vc-switch-pill mit Ring und --up-e-1, Text tertiaer und beim Ueberfahren und aktiv primaer. EINE Regel statt je
   Komponente: Hoehe, Polster und Schrift bleiben bei jedem Umschalter, wie sie sind (24/26/28/32) -- hier steht nur das Aussehen.
   Der Radius folgt der Rolle nach Hoehe wie bei jedem Knopf: 6 fuer 25 bis 36, 4 fuer 24. Wer 24 hoch ist, setzt --up-sw-r: var(--up-r-s)
   an seinem Umschalter (domain-detail, domains-table, power-dashboard, die kleine Form der Kopf-Umschalter).
   .up-root doppelt, damit die Regel die Grundformen der Bauteile (0,2,0) und die dunklen (0,3,1) auch ohne die Ladereihenfolge schlaegt. */
.up-root.up-root :is(.up-seg, .cc-seg, .ubo-seg, .tcl-seg, .vc-gran, .cc-gran, .up-pagesize-seg, .up-filter-dim, .up-dense, .tcd-mode, .uca-format-seg, .ubo-yaxis, .upt-status, .udt-sub-dispseg, .uap-tabs) {
  background: var(--vc-switch-rail); box-shadow: 0 0 0 1px var(--vc-switch-border); border-radius: var(--up-sw-r, var(--up-r-m)); }
.up-root.up-root :is(.up-seg, .cc-seg, .ubo-seg, .tcl-seg, .vc-gran, .cc-gran, .up-pagesize-seg, .up-filter-dim, .up-dense, .tcd-mode, .uca-format-seg, .ubo-yaxis, .upt-status, .udt-sub-dispseg, .uap-tabs)::before {
  border: 0; border-radius: var(--up-sw-r, var(--up-r-m)); background: var(--vc-switch-pill);
  box-shadow: 0 0 0 1px var(--vc-switch-border), var(--up-e-1); }
.up-root.up-root :is(.up-seg, .cc-seg, .ubo-seg, .tcl-seg, .vc-gran, .cc-gran, .up-pagesize-seg, .up-filter-dim, .up-dense, .tcd-mode, .uca-format-seg, .ubo-yaxis, .upt-status, .udt-sub-dispseg, .uap-tabs) > :is(.up-seg-btn, .cc-seg-btn, .ubo-seg-btn, .tcl-seg-btn, .vc-gran-btn, .cc-gran-btn, .up-pagesize-btn, .up-filter-dim-btn, .up-dense-btn, .tcd-mode-btn, .ubo-yaxis-btn, .upt-status-btn, .udt-sub-disp-btn, .uca-format-btn, .uap-tab) {
  color: var(--vc-third); border-radius: var(--up-sw-r, var(--up-r-m)); }
.up-root.up-root :is(.up-seg, .cc-seg, .ubo-seg, .tcl-seg, .vc-gran, .cc-gran, .up-pagesize-seg, .up-filter-dim, .up-dense, .tcd-mode, .uca-format-seg, .ubo-yaxis, .upt-status, .udt-sub-dispseg, .uap-tabs) > :is(.up-seg-btn, .cc-seg-btn, .ubo-seg-btn, .tcl-seg-btn, .vc-gran-btn, .cc-gran-btn, .up-pagesize-btn, .up-filter-dim-btn, .up-dense-btn, .tcd-mode-btn, .ubo-yaxis-btn, .upt-status-btn, .udt-sub-disp-btn, .uca-format-btn, .uap-tab):hover,
.up-root.up-root :is(.up-seg, .cc-seg, .ubo-seg, .tcl-seg, .vc-gran, .cc-gran, .up-pagesize-seg, .up-filter-dim, .up-dense, .tcd-mode, .uca-format-seg, .ubo-yaxis, .upt-status, .udt-sub-dispseg, .uap-tabs) > :is(.up-seg-btn, .cc-seg-btn, .ubo-seg-btn, .tcl-seg-btn, .vc-gran-btn, .cc-gran-btn, .up-pagesize-btn, .up-filter-dim-btn, .up-dense-btn, .tcd-mode-btn, .ubo-yaxis-btn, .upt-status-btn, .udt-sub-disp-btn, .uca-format-btn, .uap-tab).is-active { color: var(--vc-text); }
.up-root.up-root :is(.up-seg, .cc-seg, .ubo-seg, .tcl-seg, .vc-gran, .cc-gran, .up-pagesize-seg, .up-filter-dim, .up-dense, .tcd-mode, .uca-format-seg, .ubo-yaxis, .upt-status, .udt-sub-dispseg, .uap-tabs) > :is(.up-seg-btn, .cc-seg-btn, .ubo-seg-btn, .tcl-seg-btn, .vc-gran-btn, .cc-gran-btn, .up-pagesize-btn, .up-filter-dim-btn, .up-dense-btn, .tcd-mode-btn, .ubo-yaxis-btn, .upt-status-btn, .udt-sub-disp-btn, .uca-format-btn, .uap-tab) svg { color: inherit; }
/* Bevor core gemessen hat (oder wo der Streifen abgeschaltet ist), traegt der gewaehlte Knopf die Flaeche selbst. */
.up-root.up-root :is(.up-seg, .cc-seg, .ubo-seg, .tcl-seg, .vc-gran, .cc-gran, .up-pagesize-seg, .up-filter-dim, .up-dense, .tcd-mode, .uca-format-seg, .ubo-yaxis, .upt-status, .udt-sub-dispseg, .uap-tabs):not(.is-gleitend) > :is(.up-seg-btn, .cc-seg-btn, .ubo-seg-btn, .tcl-seg-btn, .vc-gran-btn, .cc-gran-btn, .up-pagesize-btn, .up-filter-dim-btn, .up-dense-btn, .tcd-mode-btn, .ubo-yaxis-btn, .upt-status-btn, .udt-sub-disp-btn, .uca-format-btn, .uap-tab).is-active {
  background: var(--vc-switch-pill); box-shadow: 0 0 0 1px var(--vc-switch-border), var(--up-e-1); }
.up-root .up-seg:is(.up-themesw, .up-bandsw).is-klein { --up-sw-r: var(--up-r-s); }

/* ============ DIE AUSWAHLKARTE (.up-wk, 21.10., Nutzer: "Die Cards gehoeren nach Core") ============
   Die Karte aus dem Dialog "Create" von Impact (events.css .uev-wk, 19.10., Vorlage: Vercels Template-Karten), jetzt als geteilter
   Baustein: Text oben (Titel 16 Medium mit Pfeil, der beim Ueberfahren erscheint, Satz 14 in der dritten Textfarbe), darunter eine
   grosse Tafel ab 15 % von links, rechts und unten abgeschnitten, gegen den Uhrzeigersinn gekippt, darin eine 208 x 80 grosse Karte mit
   dem Zeichen. Ein Klick waehlt und geht weiter -- kein "Continue". is-invers: die Tafel in den Farben des ANDEREN Themas
   (--up-wk-invers-* oben). Markup nur ueber UC.wkKarteHtml. Masse 1:1 aus events.css; Impact zieht auf diesen Baustein um (eigene Sitzung),
   bis dahin stehen die .uev-wk-Regeln dort weiter.
   is-kompakt (21.10., Page Audit, Nutzer: "bisschen kleiner in height"): 160 statt 201.5 hoch, Tafel und Zeichen ruecken im selben Mass
   hoch -- vom Zeichen bleibt die Haelfte zu sehen (40 von 80, in der grossen Form 46 von 80). */
.up-wk-raster { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.up-wk { --up-wk-tafel: var(--vc-heading-bg); --up-wk-feld: var(--up-feld-bg); --up-wk-ink: var(--vc-text); --up-wk-rand: color-mix(in srgb, var(--vc-text) 10%, transparent);
  --up-wk-feld-rand: var(--vc-border); --up-wk-tafelschatten: var(--up-wk-schatten-leise);
  position: relative; display: flex; flex-direction: column; justify-content: flex-start; align-items: stretch; height: 201.5px; min-width: 0; box-sizing: border-box;
  padding: 20px 24px 0; overflow: hidden; text-align: left; cursor: pointer; text-decoration: none;
  border: 1px solid var(--vc-border); border-radius: var(--up-r-l); background: var(--vc-bg); color: var(--vc-text); font: inherit;
  transition: background var(--up-t-2) var(--up-ease); }
.up-wk.is-invers { --up-wk-tafel: var(--up-wk-invers-bg); --up-wk-feld: var(--up-wk-invers-feld); --up-wk-ink: var(--up-wk-invers-ink);
  --up-wk-rand: color-mix(in srgb, var(--up-wk-invers-ink) 7%, transparent); --up-wk-feld-rand: var(--up-wk-invers-rand); --up-wk-tafelschatten: var(--up-wk-schatten); }
.up-wk:focus-visible { outline: 0; box-shadow: 0 0 0 var(--up-focus-w) var(--up-focus-ring); }
.up-wk:disabled, .up-wk[aria-disabled="true"] { cursor: default; opacity: .55; }
.up-wk-kopf { position: relative; z-index: 1; display: flex; align-items: center; gap: 8px; }
.up-wk-t { font-size: var(--up-fs-xl); font-weight: var(--up-fw-m); line-height: 1; }
.up-wk-pfeil { display: inline-flex; opacity: 0; transform: translate(-4px, 4px); transition: opacity var(--up-t-2) var(--up-ease), transform var(--up-t-2) var(--up-ease); }
.up-wk-pfeil svg { width: 14px; height: 14px; }
.up-wk:not(:disabled):hover .up-wk-pfeil, .up-wk:focus-visible .up-wk-pfeil { opacity: 1; transform: none; }
.up-wk-s { position: relative; z-index: 1; display: block; max-width: 90%; margin-top: 10px; font-size: var(--up-fs-l); line-height: 1.3; color: var(--vc-third); }
.up-wk-tafel { position: absolute; left: 15%; top: 116px; width: 100%; height: 100%; box-sizing: border-box; display: flex; align-items: flex-start; justify-content: center; padding-top: 40px;
  border: 1px solid var(--up-wk-rand); border-radius: var(--up-r-m); background: var(--up-wk-tafel); box-shadow: var(--up-wk-tafelschatten);
  transform: rotate(-5.5deg); transform-origin: 50% 80px; transition: transform var(--up-t-3) var(--up-ease); }
.up-wk:not(:disabled):hover .up-wk-tafel, .up-wk:focus-visible .up-wk-tafel { transform: rotate(-4.7deg); }
.up-wk-feld { display: flex; align-items: center; justify-content: center; flex: 0 0 auto; width: min(208px, 62%); height: 80px; box-sizing: border-box;
  border: 1px solid var(--up-wk-feld-rand); border-radius: var(--up-r-3xl); background: var(--up-wk-feld); color: var(--up-wk-ink); }
.up-wk-feld svg { width: 32px; height: 32px; }
.up-wk.is-kompakt { height: 160px; }
.up-wk.is-kompakt .up-wk-tafel { top: 88px; padding-top: 32px; }
/* Die Kopierkarte (23.10., wkKarteHtml mit kopieren): statt des Pfeils das Zeichen Kopieren, nach dem Kopieren der gruene
   Haken wie im Kopierkasten (.up-kopierbox-haken, gleiche Werte) -- und so lange bleibt das Zeichen auch ohne Hover stehen. */
.up-wk-pfeil.is-kopie { display: inline-grid; }
.up-wk-kopie, .up-wk-haken { grid-area: 1 / 1; display: grid; place-items: center;
  transition: opacity var(--up-t-1) var(--up-ease), transform var(--up-t-1) var(--up-ease); }
.up-wk-haken { opacity: 0; transform: scale(.6); color: var(--vt-up); }
.up-wk.is-kopiert .up-wk-kopie { opacity: 0; transform: scale(.6); }
.up-wk.is-kopiert .up-wk-haken { opacity: 1; transform: none; }
.up-wk.is-kopiert .up-wk-pfeil { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce){ .up-wk-kopie, .up-wk-haken { transition: none; } }
@media (max-width: 640px) { .up-wk-raster { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) { .up-wk, .up-wk-pfeil, .up-wk-tafel { transition: none; } }
