/* ERZEUGT — nicht bearbeiten. Handaenderungen werden beim naechsten Lauf
   ueberschrieben und vorher als Drift gemeldet.

   Quelle (die einzige, D-015 Auflage 1 i.d.F. von D-016):
     frontend/src/design/hub/css/hub.css und die Dateien seiner
     @import-Kette, in deren Reihenfolge. Die Kette wird geparst, nicht
     nachgepflegt (Befund N-29).

   Erzeuger:  frontend/src/design/hub/preview/build-preview.mjs
   Verteiler: node frontend/scripts/sync-hub-layer.mjs        (D-016)
   Pruefung:  frontend/scripts/check-hub-bundle.mjs (haengt im prebuild)

   EIN Text, drei Orte — byte-gleich, sonst wird die Pruefung rot:
     frontend/src/design/hub/preview/hub.bundle.css   Vorschau/Pruefstaende
     marktentwicklung/frontend/css/hub-layer.css      Auslieferung
     bank-level/web/frontend/css/hub-layer.css        Auslieferung

   Warum eine Datei statt @import-Kette: ueber file:// zickt @import, und
   zehn Requests je Sub-App-Seite waeren zehn Driftflaechen mehr. */

/* ══ tokens-common.css ══ */
/* ============================================================================
   HUB-TOKENS · GEMEINSAME EBENE (relaunch26)
   PAKET M1.1 Schritt 2 · Grundlage: D-001, D-009 (Variante A "Blaues Band")

   Herkunft jedes Werts: theme.json, assets/css/redesign.css:50-93 und
   assets/css/brand.css des Themes BKS24. Keine Umfärbung, keine erfundenen
   Zwischentöne.

   ── WARUM `.bks-hub` UND NICHT `:root` ────────────────────────────────────
   `frontend/src/design/tokens.css` ist global über `main.js` geladen und
   definiert `--surface`, `--shadow-1` und `--shadow-2` mit ANDEREN Werten
   (warme Papierwelt). Läge diese Datei auf `:root`, würde die Ladereihenfolge
   entscheiden, welche Werte gewinnen — und über 20 bestehende Ansichten
   umlackieren. D-009 verlangt ausdrücklich: nachrechnen, bevor etwas
   ausgerollt wird.

   Deshalb ist die neue Ebene ein OPT-IN pro Teilbaum: alles, was innerhalb
   eines Elements mit der Klasse `bks-hub` liegt, sieht die neuen Werte;
   alles außerhalb bleibt unberührt. CSS-Variablen erben, das kostet nichts.

   Der Migrationspfad steht in `../README.md`. Kurzfassung: aus `.bks-hub`
   wird eines Tages `:root, .bks-hub`, dann fällt die alte Datei weg. Das
   ist ein eigenes Paket mit eigenem Bild-Gate, nicht dieses hier.
   ============================================================================ */

.bks-hub {
  /* ── Marke ── */
  --bks-blue: #006699;
  --bks-blue-700: #00557f;
  --bks-blue-800: #004566;
  --bks-blue-900: #0d2137;
  --bks-blue-050: #eaf2f7;
  --bks-blue-100: #d6e6ef;

  /* ── Text und Trennung ── */
  --ink: #231f20;
  --divider: #2a2a2a;
  --grey-700: #3b4650;
  --grey-600: #51606b;
  --grey-500: #6c7a85;

  /* ── Akzent (Chrome, nie Datenfarbe — siehe tokens-data.css) ── */
  --orange: #e86a2a;
  --accent-text: #b8470f;
  --accent-tint: #fbeadf;

  /* ── Flächen ── */
  --white: #ffffff;
  --page: #ffffff;
  --stage: #eef1f3;
  --surface: #f6f8f9;

  /* ── Linien. Eine Hairline ist 1px, nie 2px, nie doppelt. ── */
  --hairline: #e3e7ea;
  --hairline-2: #d4dade;

  /* ── Radien. Über 8px geht nichts, in Datenflächen über 4px nichts. ── */
  --r-sm: 2px;
  --r-md: 4px;
  --r-lg: 8px;

  /* ── Schatten. --shadow-1 ist die Obergrenze für Datenflächen. ── */
  --shadow-1: 0 1px 2px rgba(14, 32, 48, 0.05), 0 1px 1px rgba(14, 32, 48, 0.04);
  --shadow-2: 0 6px 22px rgba(14, 32, 48, 0.09), 0 2px 6px rgba(14, 32, 48, 0.05);

  /* ── Raster ── */
  --wrap: 1200px;
  --gutter: 40px;

  /* ── Abstände. Vielfache von 4. ── */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;
  --s-10: 40px;
  --s-12: 48px;

  /* ── Schrift ── */
  --sans: "TeX Gyre Heros", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  /* ── Statusfarben ──
     Aus der alten tokens.css unverändert übernommen; das Theme hat kein
     Statusvokabular. Bekannte offene Kante (README.md des Mockups,
     Abweichung 4): die Tints sind wärmer als die neue Grauwelt. Der
     Fehlerzustand nutzt deshalb hier nur die Linie, nicht die Fläche.  */
  --ok: #2c6d3e;
  --ok-tint: #e4efe6;
  --warn: #b8762b;
  --warn-tint: #f6ebd7;
  --err: #8c2c2c;
  --err-tint: #f0dbdb;
}

@media (max-width: 860px) {
  .bks-hub {
    --gutter: 20px;
  }
}


/* ══ tokens-data.css ══ */
/* ============================================================================
   HUB-TOKENS · ZUSATZEBENE FÜR DATENFLÄCHEN
   PAKET M1.1 Schritt 2 · Grundlage: D-001 ("ein Token-Set, zwei Dichten")

   Das ist die Ebene, für die ein Marketing-Theme kein Vokabular hat:
   Mono-Ziffern, Standzeile, Hairline-Regeln, Chart-Palette, Datendichte.
   Sie kommt aus `frontend/src/design/tokens.css` und dem Designkonzept und
   liegt im selben Scope `.bks-hub` wie die gemeinsame Ebene.
   ============================================================================ */

.bks-hub {
  /* ── Schrift der Daten ──
     Ziffern immer mono mit tabular-nums, damit Spalten stehen. Gilt für
     jede Zahl, auch für einzelne im Fließtext (siehe `.hub-num`).          */
  --mono: "JetBrains Mono", "SF Mono", ui-monospace, monospace;

  /* ── Dichte. Zwei Werte, ein Set (D-001). ──
     14px ist die Datendichte. 16px setzt `.bks-hub--marketing`.
     Alle Schriftgrößen der Datenflächen sind an --dens-* gebunden,
     nicht an feste px-Werte, damit der Dichtewechsel eine Zeile bleibt.   */
  --dens-base: 14px;
  --dens-lede: 16px;      /* Vorlauftext auf Datenseiten */
  --dens-table: 13px;     /* Tabellenzelle */
  --dens-meta: 13px;      /* Untertitel, Einheitenzeile */
  --dens-micro: 11px;     /* Spaltenkopf, Zustandsschlüssel */
  --dens-stand: 10.5px;   /* Standzeile */
  --dens-h2: 20px;        /* Kartenüberschrift */
  --dens-stat: 34px;      /* Kennwert im Befundblock */

  /* ── Hairline-Regeln als Token, nicht als wiederholter Wert ── */
  --rule: 1px solid var(--hairline);
  --rule-strong: 1px solid var(--hairline-2);

  /* ── Chart-Chrome ── */
  --chart-grid: var(--hairline);
  --chart-axis: var(--hairline-2);
  --chart-tick: var(--grey-500);
  --chart-ref: var(--grey-500);
  --chart-gap: 2px;       /* Pflichtfuge zwischen zwei Marken */

  /* ══ KATEGORIALE CHART-PALETTE ═══════════════════════════════════════════
     Werte unverändert aus dem Designkonzept, Abschnitt 4.

     NACHGERECHNET am 25.07.2026 gegen #FFFFFF UND #F6F8F9 (Auflage A aus
     D-009). Verfahren, Tabellen und Urteil:
       .autopilot/evidence/M1.1/cvd/cvd-nachrechnung.md
     Rechenskript: .autopilot/evidence/M1.1/cvd/cvd_check.py

     Ergebnis in zwei Sätzen:
     · Der Hintergrundwechsel kostet nichts. Alle sechs Farben halten gegen
       beide neuen Hintergründe die 3:1-Schwelle (knappster Wert: --chart-3
       mit 3,22:1 gegen #F6F8F9). Paarabstände sind bei deckenden Marken
       hintergrundunabhängig.
     · Die Palette ist aber KEINE Menge frei kombinierbarer Farben. Vier von
       15 Paaren fallen durch — auch auf dem alten Papier #FAFAF7.

     ────────────────────────────────────────────────────────────────────────
     VERBINDLICHE REGEL, nicht verhandelbar ohne neue Nachrechnung:

     (1) Eine Abbildung zeigt HÖCHSTENS DREI Serien gleichzeitig.
     (2) Zulässige Tripel (jedes Paar hält beide Gates):
             chart-1 + chart-2 + chart-4   ← kanonisch
             chart-2 + chart-3 + chart-4
             chart-2 + chart-4 + chart-5
     (3) Verboten in derselben Abbildung:
             1+5  (Protanopie ΔEok 2,5 — praktisch identisch)
             2+6  (Deuteranopie ΔEok 4,6 — praktisch identisch)
             1+3  (normales Sehen ΔEok 14,4 — unter dem Boden 15)
             3+5  (normales Sehen ΔEok 14,8)
         4+6 nur mit Zweitkodierung (Protanopie ΔEok 7,1).
     (4) Die vierte Serie ist keine Farbe, sondern eine Entscheidung:
         Facettierung (Small Multiples) oder "Sonstige". Ein Vierer-Set
         existiert in dieser Palette nicht — gerechnet, nicht geschätzt.
     (5) Farbe trägt nie allein die Identität: Legende plus
         Direktbeschriftung am Markenende sind Pflicht, nicht Kür.
     (6) Serienfarbe färbt nie Text. Werte, Achsen und Legendentext tragen
         Text-Token (--ink / --grey-600 / --chart-tick).
     ────────────────────────────────────────────────────────────────────────

     Frage 3 aus D-009 ist damit gerechnet: Burgund BLEIBT --chart-2. Die
     Stress-Serie auf #E86A2A umzustellen kollidiert nicht mit dem Akzent,
     sondern mit --chart-4 Ocker (Protanopie ΔEok 0,8) und senkt die größte
     saubere Serienmenge von drei auf zwei.
     ═══════════════════════════════════════════════════════════════════════ */
  --chart-1: #006699;   /* BKS-Blau — Primärserie (Bestand, NPL-Quote) */
  --chart-2: #9d3448;   /* Burgund — Stress / Ausfall */
  --chart-3: #3e93bf;   /* helles Blau — Vergleich (nie mit chart-1 zusammen) */
  --chart-4: #c07a2b;   /* Ocker — zweite Serie (Watchlist / Stage 2) */
  --chart-5: #7a5ea8;   /* Violett — Reserve (nie mit chart-1 oder chart-3) */
  --chart-6: #1e8158;   /* Grün — Reserve (nie mit chart-2) */
}

/* ── Marketingdichte: dieselben Token, ein anderer Grundwert (D-001). ──
   Nur die Größen wandern, keine Farbe und keine Linie.                   */
.bks-hub--marketing {
  --dens-base: 16px;
  --dens-lede: 18px;
  --dens-meta: 15px;
  --dens-micro: 12px;
  --dens-h2: 24px;
}


/* ══ base.css ══ */
/* ============================================================================
   HUB-BASIS · Typografie und Grundverhalten innerhalb von `.bks-hub`
   Kein Reset auf html/body — die Ebene ist ein Teilbaum, nicht die Seite.
   ============================================================================ */

.bks-hub {
  font-family: var(--sans);
  font-size: var(--dens-base);
  line-height: 1.5;
  color: var(--ink);
  background: var(--page);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;

  /* Vererbte Werte, die von aussen kommen, hier abfangen — gemessen in M1.5.
     Beide Sub-Apps setzen in ihrer eigenen `styles.css` `body { font-weight:
     300 }` (bank-level:60, marktentwicklung:31), bank-level zusaetzlich
     `font-variant-numeric: tabular-nums`. Beides erbt in den Teilbaum und traf
     genau die Stellen, die KEIN eigenes Gewicht setzen: `.hub-band__en` und
     die Standzeile. Titel (600), Kicker (700) und `b` (700) waren nie
     betroffen — die Spezifitaetsannahme aus D-015 stimmt fuer Selektoren, nur
     Vererbung geht an ihr vorbei.

     Der Fix gehoert hierher und nirgendwo sonst: `.bks-hub` IST der Opt-in-
     Geltungsbereich der Ebene (siehe Kopf von tokens-common.css), Light DOM
     erbt per Entwurf (D-015), und ein Eingriff im Element oder in der
     Sub-App-CSS waere ein Bruch von Auflage 1. Im Hub selbst aendert die
     Zeile nichts: dort waren beide Werte ohnehin schon `400` bzw. `normal`
     (Bildnachweis: hub-preview-*.png vor/nach byte-gleich). */
  font-weight: 400;
  font-variant-numeric: normal;
}

.bks-hub *,
.bks-hub *::before,
.bks-hub *::after {
  box-sizing: border-box;
}

/* Flex-/Grid-Kinder dürfen unter ihre Inhaltsbreite schrumpfen. Ohne das
   scrollt die ganze Seite waagerecht statt nur die Tabelle. */
.bks-hub .hub-shrink > *,
.bks-hub .hub-grid > *,
.bks-hub .hub-strip > * {
  min-width: 0;
}

.bks-hub img,
.bks-hub svg {
  max-width: 100%;
}

.bks-hub a {
  color: var(--bks-blue);
}

/* Ein Fokusring, überall derselbe: weißer Absatz, dann Markenblau. */
.bks-hub :where(a, button, select, input, [tabindex]):focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--white), 0 0 0 4px var(--bks-blue);
  border-radius: var(--r-sm);
}

/* ── Satzbreite ── */
.hub-wrap {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

/* ── Zahlen. Jede Zahl in einer Datenfläche trägt das. ── */
.hub-num,
.hub-mono {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 400;
}

/* ── Leerstelle. "–" heißt: kein veröffentlichter Wert. Nie 0. ──
   Der Screenreader-Text steckt in HubValue.vue, nicht in der Farbe.      */
.hub-dash {
  /* Nicht --hairline-2 wie im Mockup: dort liegt der Strich bei 1,6:1 gegen
     Weiß und ist am Bild praktisch unsichtbar — eine leere Zelle liest sich
     dann als 0, also genau die falsche Aussage, die die Regel verhindern
     soll. --grey-500 liegt bei 4,1:1 und bleibt trotzdem zurückhaltender
     als jede Zahl daneben. */
  color: var(--grey-500);
}

/* ── Standzeile: Stand, Quelle, Panelgröße. ── */
.hub-standzeile {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--dens-stand);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--grey-500);
  font-variant-numeric: tabular-nums;
}

.hub-standzeile b {
  color: var(--grey-700);
  font-weight: 500;
}

/* Kleine Versalzeile über einem Abschnitt. */
.hub-sechead {
  margin: 0 0 var(--s-3);
  font-size: var(--dens-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--grey-500);
  font-weight: 700;
}

/* Nur für Screenreader — trägt die Bedeutung, die Farbe nie allein trägt. */
.hub-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Schaltflächen ── */
.hub-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font: inherit;
  font-size: var(--dens-table);
  font-weight: 700;
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--bks-blue);
  border-radius: var(--r-md);
  background: var(--bks-blue);
  color: var(--white);
  cursor: pointer;
  text-decoration: none;
}

.hub-btn--out {
  background: transparent;
  color: var(--bks-blue);
}

.hub-btn--ghost {
  background: transparent;
  color: var(--grey-700);
  border-color: var(--hairline-2);
}

.hub-btn--ghost:hover {
  border-color: var(--grey-500);
  color: var(--ink);
}

.hub-btn--quiet {
  background: transparent;
  border-color: transparent;
  color: var(--bks-blue);
  font-weight: 400;
  padding: var(--s-1) 0;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ── Testbetrieb-Stempel ──
   Marken-Akzent auf hellem Tint, nicht Burgund: Burgund ist Datenfarbe
   (--chart-2), und ein Chrome-Element darf keine Serienfarbe tragen.   */
.hub-stamp {
  display: inline-block;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border: 1px solid var(--accent-text);
  color: var(--accent-text);
  background: var(--accent-tint);
  padding: 3px 7px;
  border-radius: var(--r-sm);
  white-space: nowrap;
}


/* ══ shell.css ══ */
/* ============================================================================
   HUB-SCHALE · Plattformleiste und Modulband
   Freigegeben am Bild: Variante A "Blaues Band" (D-009). Das Band ist das
   `page-hero`-Band der Website, unverändert — inklusive des zurückhaltenden
   120°-Verlaufs auf #004566. Das ist der EINZIGE Verlauf im System.
   ============================================================================ */

/* ── Plattformleiste ── */
.hub-platbar {
  background: var(--white);
  border-bottom: var(--rule);
}

.hub-platbar__inner {
  display: flex;
  align-items: center;
  gap: 28px;
  height: 60px;
}

.hub-lockup {
  display: inline-flex;
  align-items: center;
  line-height: 1;
  text-decoration: none;
  color: inherit;
  flex: 0 0 auto;
}

/* Die Wortmarke ist die Datei assets/img/bks-wordmark.svg aus dem Theme,
   inline eingesetzt. Nichts nachgezeichnet. */
.hub-lockup__mark {
  color: var(--bks-blue);
  display: flex;
  align-items: center;
}

.hub-lockup__mark svg {
  height: 26px;
  width: auto;
  display: block;
}

.hub-lockup__div {
  width: 2px;
  background: var(--divider);
  border-radius: 1px;
  height: 34px;
  margin: 0 14px;
  flex: 0 0 auto;
}

.hub-lockup__sub {
  display: flex;
  flex-direction: column;
  font-size: 11px;
  line-height: 1.2;
  color: var(--ink);
  white-space: nowrap;
}

.hub-modnav {
  display: flex;
  gap: 2px;
  margin-left: auto;
  flex-wrap: wrap;
}

.hub-modnav a {
  font-size: var(--dens-table);
  text-decoration: none;
  color: var(--grey-600);
  padding: 6px 10px;
  border-bottom: 2px solid transparent;
}

.hub-modnav a:hover {
  color: var(--ink);
}

.hub-modnav a[aria-current] {
  color: var(--bks-blue);
  font-weight: 700;
  border-bottom-color: var(--bks-blue);
}

/* ── Modulband ── */
.hub-band {
  background: var(--bks-blue);
  color: var(--white);
  position: relative;
  overflow: hidden;
}

.hub-band::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg, var(--bks-blue) 0%, var(--bks-blue-800) 100%);
}

.hub-band__inner {
  position: relative;
  padding-top: 44px;
  padding-bottom: 38px;
}

.hub-band__kicker {
  margin: 0 0 14px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.72);
  display: flex;
  align-items: center;
  gap: var(--s-3);
}

.hub-band__kicker::before {
  content: "";
  width: 2px;
  height: 14px;
  background: currentColor;
  flex: none;
}

.hub-band h1 {
  margin: 0;
  font-size: clamp(30px, 3.8vw, 44px);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.015em;
  color: var(--white);
}

.hub-band__en {
  margin: var(--s-2) 0 0;
  font-size: 15px;
  color: rgba(255, 255, 255, 0.62);
  letter-spacing: 0.01em;
}

.hub-band .hub-standzeile {
  margin-top: 22px;
  color: var(--bks-blue-100);
}

.hub-band .hub-standzeile b {
  color: var(--white);
}

/* Hinweis im Band, wenn die Blöcke der Seite unterschiedlich datiert sind.
   Der Stand im Band ist dann der jüngste, nicht der einzige — verbindlich
   ist die Standzeile am jeweiligen Block. */
.hub-band__split {
  color: var(--white);
  border-bottom: 1px dotted rgba(255, 255, 255, 0.5);
  cursor: help;
}

/* ── Bühne unter dem Band ── */
.hub-stage {
  background: var(--stage);
  padding: var(--s-8) 0 0;
}

.hub-foot {
  border-top: var(--rule);
  margin-top: 56px;
  padding: 22px 0 60px;
}

@media (max-width: 860px) {
  .hub-platbar__inner {
    height: auto;
    padding-top: var(--s-3);
    padding-bottom: var(--s-3);
    flex-wrap: wrap;
    gap: var(--s-3);
  }

  .hub-modnav {
    margin-left: 0;
    width: 100%;
    overflow-x: auto;
    flex-wrap: nowrap;
  }

  .hub-modnav a {
    white-space: nowrap;
  }

  .hub-band__inner {
    padding-top: var(--s-8);
    padding-bottom: var(--s-6);
  }
}


/* ══ card.css ══ */
/* ============================================================================
   HUB-KARTE · die Blockanatomie der Datenfläche
   Mit D-009 ist die Rahmenfrage zugunsten gerahmter Karten entschieden:
   weiße Fläche, 1px Hairline, 4px Radius, --shadow-1 als Obergrenze.

   Warum die Karte der Ort der Zustände ist: laut CLAUDE.md ("Block-basiertes
   Dashboard") meldet nicht jede Bank in jedem Quartal jedes EBA-Template.
   Jeder Block zeigt deshalb unabhängig seine neuesten Daten, mit eigenem
   Stichtag und eigener Quelle. Also gehören "lädt", "leer" und "Fehler" an
   die Karte, nicht an die Seite — eine Seite mit einem Ladezustand wäre
   architekturfremd.
   ============================================================================ */

.hub-card {
  background: var(--white);
  border: var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  margin-bottom: var(--s-6);
}

.hub-card__head {
  padding: 22px var(--s-6) var(--s-4);
  border-bottom: var(--rule);
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
}

.hub-card__headtext {
  min-width: 0;
}

.hub-card__head h2 {
  margin: 0;
  font-size: var(--dens-h2);
  font-weight: 600;
  color: var(--bks-blue);
  letter-spacing: -0.01em;
}

/* Einheitenzeile. Steht immer da, wo die Zahl gelesen wird — nicht in der
   Fußnote. "in Prozent des Bruttobuchwerts" ist Teil der Zahl. */
.hub-card__unit {
  margin: 6px 0 0;
  font-size: var(--dens-meta);
  color: var(--grey-600);
}

.hub-card__headaside {
  margin-left: auto;
  flex: 0 0 auto;
}

.hub-card__body {
  padding: var(--s-6);
}

.hub-card__foot {
  padding: var(--s-3) var(--s-6) var(--s-4);
  border-top: var(--rule);
  display: flex;
  justify-content: space-between;
  gap: var(--s-4);
  align-items: baseline;
  flex-wrap: wrap;
}

/* Karte im Testbetrieb: der Stempel sitzt im Kartenkopf, nicht über der
   Zahl. Er qualifiziert die Herkunft der Daten, nicht ihren Wert. */
.hub-card--testbetrieb {
  border-color: var(--accent-text);
}

.hub-card--testbetrieb .hub-card__head {
  background: var(--accent-tint);
}

/* ── Befundblock ──
   Kennwerte ÜBER den Datenbestand (Median, Aggregat, Meldequote), keine
   Erfolgszahlen. Deshalb Fließtext zuerst, Zahlen darunter in einem
   hairline-geteilten Streifen — keine Reihe fettgedruckter Kacheln.       */
/* Waagerechtes Polster: EIN Wert für alle Kartenflächen (--s-6, 24px).
   Damit stehen Kartentitel, Vorlauftext, erste Tabellenspalte und Standzeile
   auf derselben linken Kante — im Mockup liefen sie mit 26px / 32px / 14px
   auseinander, was in einer Kartenkolonne sofort auffällt. Das ist die eine
   bewusste Abweichung vom freigegebenen Bild. */
.hub-finding {
  background: var(--white);
  border: var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  padding: 28px var(--s-6);
  margin-bottom: var(--s-6);
}

.hub-finding p {
  margin: 0 0 22px;
  font-size: var(--dens-lede);
  line-height: 1.6;
  color: var(--grey-700);
  max-width: 68ch;
}

.hub-finding p b {
  color: var(--ink);
}

.hub-strip {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border-top: var(--rule);
  padding-top: var(--s-5);
}

.hub-strip > div {
  padding: 0 26px;
  border-left: var(--rule);
}

.hub-strip > div:first-child {
  border-left: 0;
  padding-left: 0;
}

.hub-strip__n {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--dens-stat);
  font-weight: 500;
  color: var(--bks-blue);
  line-height: 1;
  letter-spacing: -0.01em;
}

.hub-strip__n small {
  font-size: 0.5em;
  margin-left: 3px;
  color: var(--grey-500);
}

.hub-strip__l {
  margin-top: var(--s-2);
  font-size: var(--dens-meta);
  color: var(--grey-600);
  line-height: 1.4;
}

@media (max-width: 900px) {
  .hub-strip {
    grid-template-columns: 1fr;
    gap: var(--s-4);
  }

  .hub-strip > div {
    border-left: 0;
    padding-left: 0;
    border-top: var(--rule);
    padding-top: var(--s-3);
  }

  .hub-strip > div:first-child {
    border-top: 0;
    padding-top: 0;
  }

  .hub-finding {
    padding: var(--s-5) var(--s-5);
  }

  .hub-card__head,
  .hub-card__body {
    padding-left: var(--s-5);
    padding-right: var(--s-5);
  }

  .hub-card__foot {
    padding-left: var(--s-5);
    padding-right: var(--s-5);
  }
}


/* ══ filters.css ══ */
/* ============================================================================
   HUB-FILTERLEISTE · mit echtem Aktivzustand
   Im Mockup zeigte die Leiste nur die Dimensionen ("noch grob"). Hier trägt
   sie Zustand: die Auswahl ist sichtbar, die Abweichung vom Standard ist
   benannt, und es gibt einen Weg zurück.

   Bauteilwahl nach Länge der Optionsliste, nicht nach Geschmack:
   · zwei bis vier Optionen  → Segmentgruppe (`.hub-chip`), Zustand direkt sichtbar
   · lange oder wachsende Liste (27 Länder, alle Quartale) → `<select>`
   Ein `<select>` kann keinen Aktivzustand zeigen, deshalb markiert
   `[data-changed]` die Abweichung vom Standardwert.
   ============================================================================ */

.hub-filters {
  background: var(--white);
  border: var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  margin-bottom: var(--s-6);
}

.hub-filters__row {
  /* 24px waagerecht wie jede andere Kartenfläche — mit den 18px des Mockups
     standen die Filterlabels 6px links neben allen Kartentiteln. */
  padding: var(--s-3) var(--s-6);
  display: flex;
  gap: 22px;
  align-items: flex-end;
  flex-wrap: wrap;
}

.hub-filter__label {
  font-size: var(--dens-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--grey-500);
  display: block;
  margin-bottom: var(--s-1);
  font-weight: 700;
}

.hub-filter select {
  font: inherit;
  font-size: var(--dens-table);
  padding: 6px 10px;
  border: var(--rule-strong);
  border-radius: var(--r-sm);
  background: var(--white);
  color: var(--ink);
  min-width: 190px;
}

/* Textfeld — die dritte Bauteilwahl neben Segmentgruppe und Select.
   Nicht nach Laenge der Optionsliste, sondern nach ihrem Fehlen: was sich
   nicht aufzaehlen laesst (ein Name, ein Stichwort), wird getippt.
   Alles ausser der Grundbreite ist deckungsgleich mit `.hub-filter select` —
   ein Filterfeld darf nicht danach aussehen, aus welchem Bauteil es kommt.
   220px statt 190px, weil ein Suchbegriff mehr Platz braucht als die laengste
   Option einer Liste, die daneben schon gekuerzt angezeigt wird. */
.hub-filter input {
  font: inherit;
  font-size: var(--dens-table);
  padding: 6px 10px;
  border: var(--rule-strong);
  border-radius: var(--r-sm);
  background: var(--white);
  color: var(--ink);
  min-width: 220px;
}

.hub-filter input::placeholder {
  color: var(--grey-500);
}

/* Abweichung vom Standardwert — der Aktivzustand eines Selects und eines
   Textfelds. Ein `<select>` kann keinen Aktivzustand zeigen, ein leeres
   Textfeld auch nicht; `[data-changed]` markiert beide gleich. */
.hub-filter select[data-changed],
.hub-filter input[data-changed] {
  border-color: var(--bks-blue);
  background: var(--bks-blue-050);
  color: var(--bks-blue);
  font-weight: 700;
}

/* ── Segmentgruppe ── */
.hub-chips {
  display: flex;
  gap: var(--s-1);
  flex-wrap: wrap;
}

.hub-chip {
  font: inherit;
  font-size: var(--dens-table);
  padding: 6px var(--s-3);
  border: var(--rule-strong);
  border-radius: var(--r-sm);
  background: var(--white);
  color: var(--grey-700);
  cursor: pointer;
  white-space: nowrap;
}

.hub-chip:hover {
  border-color: var(--grey-500);
  color: var(--ink);
}

/* Der Aktivzustand. Drei Signale gleichzeitig, damit er nicht an Farbe
   allein hängt: Rahmen, Fläche und Schriftschnitt. */
.hub-chip[aria-pressed="true"] {
  border-color: var(--bks-blue);
  background: var(--bks-blue-050);
  color: var(--bks-blue);
  font-weight: 700;
}

.hub-chip[aria-pressed="true"]:hover {
  border-color: var(--bks-blue-700);
}

.hub-chip .hub-chip__n {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--grey-500);
  margin-left: 6px;
  font-weight: 400;
}

.hub-chip[aria-pressed="true"] .hub-chip__n {
  color: var(--bks-blue-700);
}

.hub-filters__spacer {
  margin-left: auto;
}

/* ── Zustandszeile der Leiste ──
   Steht nur da, wenn die Auswahl vom Standard abweicht. Sie ist der Grund,
   warum niemand eine gefilterte Ansicht für die Gesamtlage hält.          */
.hub-filters__state {
  border-top: var(--rule);
  padding: var(--s-2) var(--s-6);
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  flex-wrap: wrap;
  background: var(--surface);
  border-radius: 0 0 var(--r-md) var(--r-md);
}

.hub-filters__state .hub-standzeile b {
  color: var(--bks-blue);
}

@media (max-width: 900px) {
  /* Mit dem Kartenpolster mitziehen (--s-5), sonst stehen die Filterlabels
     4px rechts neben allen Kartentiteln. */
  .hub-filters__row,
  .hub-filters__state {
    padding-left: var(--s-5);
    padding-right: var(--s-5);
  }

  .hub-filter {
    width: 100%;
  }

  /* Beide Felder gehen auf die volle Breite. Das Textfeld war bis M2.0d eine
     lokale Nachbildung, die nur die Schreibtischhaelfte von `.hub-filter
     select` gespiegelt hatte — auf schmalen Geraeten stand es deshalb 220px
     breit neben fuenf randfuellenden Selects (sichtbar in
     .autopilot/evidence/M2.0c-verify/screenshots/netzwerk-liste-
     authenticated-500.png). */
  .hub-filter select,
  .hub-filter input {
    min-width: 0;
    width: 100%;
  }

  .hub-filters__spacer {
    margin-left: 0;
    width: 100%;
  }
}


/* ══ table.css ══ */
/* ============================================================================
   HUB-HAIRLINE-TABELLE
   Regeln, die nicht verhandelbar sind, wenn das Muster später skaliert wird:
   · Zahlen rechts, mono, tabular-nums. Text links. Nie gemischt.
   · Eine Hairline unter jeder Zeile, keine Vertikallinien, keine Zebrastreifen.
   · Der Spaltenkopf trägt den Stichtag der SPALTE, nicht der Seite.
   · Eine Leerstelle ist "–", niemals 0 und niemals leer.
   · Unter 900px scrollt die Tabelle waagerecht und die Institutsspalte bleibt
     stehen — sonst verliert man beim Scrollen die Zeilenidentität.
   ============================================================================ */

.hub-tablewrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* `contain: paint` ist hier kein Zierrat, sondern die Reparatur eines
     gemessenen Fehlers: bei 500px schob die Eigenbreite der Tabelle (770px,
     getrieben von den nicht umbrechenden Spaltenköpfen) die GANZE Seite
     waagerecht — `scrollWidth` 767 bei 500px Viewport, `scrollX` bis 267.
     `overflow-x: auto` allein hält das nicht, und `overflow: hidden` auf
     Karte oder Bühne hält es auch nicht (beides gemessen). Erst die
     Paint-Containment schneidet die Eigenbreite an dieser Kante ab.
     Die Tabelle selbst scrollt weiterhin (Innenbreite 770 bei 458 sichtbar). */
  contain: paint;
}

.hub-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
}

.hub-table caption {
  text-align: left;
  padding: 0 var(--s-6) var(--s-3);
  font-size: var(--dens-meta);
  color: var(--grey-600);
}

.hub-table thead th {
  font-size: var(--dens-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--grey-600);
  font-weight: 700;
  text-align: right;
  padding: var(--s-2) var(--s-4);
  background: var(--surface);
  border-bottom: var(--rule-strong);
  white-space: nowrap;
  vertical-align: bottom;
}

.hub-table thead th:first-child {
  text-align: left;
}


/* Spaltenweise Datierung. Der Stichtag steht unter dem Spaltennamen, in
   Mono, damit er als Datum und nicht als Untertitel gelesen wird. */
.hub-th__sub {
  display: block;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  color: var(--grey-500);
  font-weight: 400;
  margin-top: 3px;
  text-transform: none;
}

/* Spalte mit abweichendem Stichtag: dunkler und halbfett, ohne Farbsignal
   und ohne Symbol. Die Erklärung steht in der Fußzeile der Karte. */
.hub-th__sub[data-deviating] {
  color: var(--grey-700);
  font-weight: 500;
}

.hub-table tbody th {
  text-align: left;
  font-weight: 500;
  color: var(--ink);
  padding: 9px var(--s-4);
  border-bottom: var(--rule);
  background: var(--white);
}

.hub-table tbody td {
  text-align: right;
  padding: 9px var(--s-4);
  border-bottom: var(--rule);
  color: var(--grey-700);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--dens-table);
}

/* Randspalten auf die Kartenkante, Innenspalten enger.
   MUSS nach den Zellenregeln stehen: dort setzt die `padding`-Kurzschrift
   padding-left/right neu, und bei gleicher Spezifität gewinnt die spätere
   Regel. Stand die Regel vorher, lag die erste Spalte 8px links neben allen
   anderen Kartenflächen — von der Bildprüfung in shoot.mjs gefunden. */
.hub-table thead th:first-child,
.hub-table tbody th {
  padding-left: var(--s-6);
}

.hub-table thead th:last-child,
.hub-table tbody td:last-child {
  padding-right: var(--s-6);
}

/* Die Leitspalte der Tabelle: der Wert, um den es geht. */
.hub-table tbody td.hub-td--lead {
  color: var(--ink);
  font-weight: 500;
}

/* ── TEXTSPALTEN ───────────────────────────────────────────────────────────
   PAKET M2.0d. Bis hierher war `tbody td` auf Zahlen ausgelegt (rechts, mono,
   tabular) — das ist richtig fuer eine Kennzahlentabelle und falsch fuer ein
   Verzeichnis. /netzwerk und /barometer hatten dafuer je eine eigene lokale
   Fassung (`.nz-td-text`, `.bar-txttable`); beide stehen jetzt hier.

   Zwei Bauteile, weil es zwei Faelle gibt und nicht einen:
   · `.hub-td--text` / `.hub-th--text`  EINE Textspalte in einer Zahlentabelle
   · `.hub-table--text`                 eine Tabelle OHNE Zahlenspalte
   Die gemeinsame Grundlage steht in einem Block, damit eine Textzelle nicht
   davon abhaengt, ueber welchen der beiden Wege sie gesetzt wurde.
   Die Regeln stehen NACH `.hub-table tbody td`, sonst gewaenne bei gleicher
   Spezifitaet die Zahlenregel — dieselbe Falle wie beim Kartenpolster oben. */
.hub-table tbody td.hub-td--text,
.hub-table--text tbody td {
  text-align: left;
  font-family: var(--sans);
  font-variant-numeric: normal;
  color: var(--grey-700);
}

.hub-table thead th.hub-th--text {
  text-align: left;
}

/* Eine einzelne Textspalte steht neben Zahlen und darf die Zeilenhoehe nicht
   verdoppeln — sie bleibt in einer Zeile und laesst die Tabelle scrollen. */
.hub-table tbody td.hub-td--text {
  white-space: nowrap;
}

/* Eine reine Textmatrix hat keine Zahlenzeile, an der sich die Hoehe messen
   liesse: dort bricht jede Zelle um und alle haengen oben. */
.hub-table--text thead th {
  text-align: left;
  white-space: normal;
}

.hub-table--text tbody td {
  white-space: normal;
  vertical-align: top;
  font-size: var(--dens-table);
}

.hub-table--text tbody th {
  vertical-align: top;
  max-width: 30ch;
}

.hub-table tbody tr:hover th,
.hub-table tbody tr:hover td {
  background: var(--bks-blue-050);
}

/* ── Ladeplatzhalter in der Tabelle ──
   Gleiche Zeilenhöhe wie eine echte Zeile, damit die Karte beim Eintreffen
   der Daten nicht springt. */
.hub-skel {
  display: block;
  height: 10px;
  border-radius: var(--r-sm);
  background: var(--hairline);
}

.hub-skel--num {
  margin-left: auto;
  width: 46px;
}

.hub-skel--name {
  width: 62%;
  max-width: 210px;
}

@media (prefers-reduced-motion: no-preference) {
  .hub-skel {
    animation: hub-skel-pulse 1.6s var(--ease) infinite;
  }

  @keyframes hub-skel-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.45; }
  }
}

@media (max-width: 900px) {
  /* Auf schmalen Breiten ist der waagerechte Weg schon durch die Zahlen
     belegt; eine Textspalte bricht dort lieber um, als ihn zu verlaengern. */
  .hub-table tbody td.hub-td--text {
    white-space: normal;
  }

  .hub-table tbody th,
  .hub-table thead th:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    /* Hairline als Schatten, weil ein border-right beim Sticky mitscrollt. */
    box-shadow: 1px 0 0 var(--hairline);
  }

  .hub-table tbody th {
    white-space: normal;
    min-width: 116px;
  }

  .hub-table caption {
    padding-left: var(--s-5);
    padding-right: var(--s-5);
  }

  /* Unter 900px schrumpft das Kartenpolster auf --s-5; die Tabellenkanten
     ziehen mit, sonst bricht die gemeinsame Textkante genau dort. */
  .hub-table thead th:first-child,
  .hub-table tbody th {
    padding-left: var(--s-5);
  }

  .hub-table thead th:last-child,
  .hub-table tbody td:last-child {
    padding-right: var(--s-5);
  }
}


/* ══ prose.css ══ */
/* ============================================================================
   HUB-FLIESSTEXT · `.hub-prose`
   PAKET M2.0d — zusammengefuehrt aus drei lokalen Fassungen (M2.0 /netzwerk,
   M2.0b /barometer, M2.0c /wiki/:id), die dieselben Regeln dreimal geschrieben
   hatten.

   ── DIE EINE REGEL, DIE ALLES ANDERE ERKLAERT ─────────────────────────────
   `.hub-prose` gestaltet NACKTE Elemente. Ein Element mit eigener Klasse ist
   ein Bauteil und bleibt unangetastet.

   Das ist keine Stilfrage, sondern die Betriebsbedingung dieser Flaeche:
   · Der Artikelkoerper des Glossars kommt als fertiges Markup aus dem Bestand
     und traegt keine Klassen — nur eine Regel am Element kann ihn erreichen.
   · Dieselbe Flaeche enthaelt aber Bauteile der Ebene: die Hairline-Tabelle
     (`.hub-table`), Verweislisten, Schaltflaechen. Griffe eine Regel wie
     `.hub-prose td` blank durch, schluege sie `.hub-table tbody td` bei
     gleicher Spezifitaet und spaeterer Ladereihenfolge — die Zahlenspalte
     waere linksbuendig und die Hairline-Optik zerlegt.
   Deshalb steht `:not([class])` an jeder Blockregel. Inline-Auszeichnungen
   (`a`, `strong`, `b`, `em`) tragen es nicht: dort ist die Klasse die Regel,
   nicht die Ausnahme (`.glossary-link`, `.wiki-inline-link`), und die
   Auszeichnung soll auch dann greifen.

   ── DAS TAILWIND-PREFLIGHT ────────────────────────────────────────────────
   `frontend/src/index.css` nimmt JEDER Liste Marke und Einzug. Ohne die
   `list-style`-Zeilen unten stehen Aufzaehlungen aus dem Bestand als
   eingerueckte Absatzfolge da — am Bild gesehen, nicht vermutet (M2.0c,
   55 Aufzaehlungen im Glossar). Die Wiederherstellung gehoert deshalb zum
   Bauteil und nicht in jede Ansicht.

   ── ZWEI ZUSAMMENGEFUEHRTE ABWEICHUNGEN (M2.0d, dokumentiert) ─────────────
   1. Abstand ueber `h3`: /wiki fuehrte --s-8, /barometer --s-10. Kanonisch
      ist --s-8. Begruendung: `h2` traegt hier 72px Vorlauf (--s-10 Rand plus
      --s-8 Polster ueber der Linie). Mit --s-10 ueber `h3` stuenden die
      beiden Stufen 8px auseinander und die Gliederung waere am Bild nicht
      mehr ablesbar. Mit --s-8 bleibt ein sichtbarer Schritt.
   2. Letztes Kind ohne Aussenabstand: /barometer fuehrte das (`.bar-p:last-
      child`), /wiki nicht. Kanonisch ist: der Prosablock traegt seinen
      Aussenabstand selbst, seine Kinder tragen nur den Abstand zueinander.
      Sonst addiert sich unter jedem Block ein Rest, den keine Ansicht
      angefordert hat.
   ============================================================================ */

.hub-prose {
  font-size: var(--dens-lede);
  line-height: 1.7;
  color: var(--grey-700);
}

/* Der Block traegt seinen Aussenabstand selbst — siehe Kopf, Abweichung 2. */
.hub-prose > :last-child {
  margin-bottom: 0;
}

/* ── Gliederung ──
   `h2` ist der Abschnitt. Die Haarlinie darueber traegt --hairline-2 und
   nicht --hairline: auf der Buehne (--stage #eef1f3) liegt die feine
   Haarlinie bei rund 1,1:1 und ist am Bild nicht vorhanden. */
.hub-prose h2:not([class]) {
  margin: var(--s-10) 0 var(--s-4);
  padding-top: var(--s-8);
  border-top: 1px solid var(--hairline-2);
  font-size: clamp(22px, 2vw, 26px);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--ink);
}

/* Beginnt der Block mit einem Abschnitt, trennt schon die Kante darueber. */
.hub-prose h2:not([class]):first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* Zwischentitel. Markenblau wie der Kartentitel (card.css) — damit liest sich
   eine Seite als eine Folge aus Text und Beleg, nicht als zwei Systeme
   nebeneinander. */
.hub-prose h3:not([class]) {
  margin: var(--s-8) 0 var(--s-3);
  font-size: var(--dens-h2);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--bks-blue);
}

.hub-prose h3:not([class]):first-child {
  margin-top: 0;
}

.hub-prose p:not([class]) {
  margin: 0 0 var(--s-5);
}

/* ── Aufzaehlungen ──
   Nur nackte Listen. Eine Liste MIT Klasse ist ein Bauteil (Verweisliste,
   Quellenliste, Registerliste) und bringt Marke, Einzug und Abstand selbst
   mit; griffe die Regel dort durch, bekaeme jedes Kachel- und Linklisten-
   Element einen Aufzaehlungspunkt und einen Zeilenabstand, den es nicht
   angefordert hat. */
.hub-prose ul:not([class]) {
  list-style: disc;
  margin: 0 0 var(--s-5);
  padding-left: var(--s-5);
}

.hub-prose ol:not([class]) {
  list-style: decimal;
  margin: 0 0 var(--s-5);
  padding-left: var(--s-5);
}

.hub-prose :is(ul, ol):not([class]) li {
  margin: 0 0 var(--s-2);
}

.hub-prose :is(ul, ol):not([class]) li::marker {
  color: var(--grey-500);
}

/* ── Auszeichnung im Satz ── */
.hub-prose strong,
.hub-prose b {
  color: var(--ink);
  font-weight: 600;
}

.hub-prose em {
  font-style: italic;
}

/* Querverweis im Text. Unterstrichen, weil Farbe allein die Bedeutung nicht
   traegt; der ruhige Unterstrich zieht beim Ueberfahren an. Die Regel steht
   bewusst OHNE `:not([class])`: die Verweise im Bestand tragen Klassen
   (`.wiki-inline-link`, `.wiki-autolink`), und fuer den Leser ist der
   Unterschied zwischen redaktionell gesetztem und automatischem Verweis
   bedeutungslos. */
.hub-prose a {
  color: var(--bks-blue);
  text-decoration: underline;
  text-decoration-color: var(--bks-blue-100);
  text-underline-offset: 3px;
}

.hub-prose a:hover {
  text-decoration-color: var(--bks-blue);
}

/* Ein Verweis in einer Ueberschrift bleibt Ueberschrift. Die Auto-Verlinkung
   des Glossars greift auf den ganzen Koerper zu, also auch auf
   Abschnittstitel: aus "Die Anatomie der CRE-Krise" wurde sonst ein Titel mit
   einem blauen Wort darin (M2.0c, erste Bildrunde). */
.hub-prose :is(h2, h3) a {
  color: inherit;
  text-decoration-color: var(--hairline-2);
}

.hub-prose :is(h2, h3) a:hover {
  color: var(--bks-blue);
  text-decoration-color: var(--bks-blue);
}

/* Eine Schaltflaeche im Fliesstext bleibt Schaltflaeche. `.hub-btn` (base.css)
   setzt `text-decoration: none` mit (0,1,0) und verloere gegen `.hub-prose a`
   (0,1,1) — ohne diese Zeilen bekaeme jeder Knopf in einem Prosablock einen
   Unterstrich.

   DIE FARBE GEHOERT ZUR SELBEN LUECKE, und sie war die teurere: `.hub-prose a`
   ueberschreibt auch `color`, und die Vollvariante des Knopfes traegt weissen
   Text auf `--bks-blue`. Im Fliesstext wurde daraus blauer Text auf blauem
   Grund — gemessen Vordergrund und Hintergrund beide rgb(0,102,153), also
   Kontrast 1:1 und die Beschriftung schlicht unsichtbar. Betroffen war jeder
   `.hub-btn` in einem Prosablock; aufgefallen ist es an den beiden Verweisen
   zwischen Barometer und In-Depth.

   Die Varianten stehen NACH der Grundregel, weil sie dieselbe Spezifitaet
   (0,2,0) haben und sonst von ihr geschlagen wuerden — `--out`, `--ghost` und
   `--quiet` liegen auf transparentem Grund und wollen ihre eigene Textfarbe
   behalten. */
.hub-prose .hub-btn {
  text-decoration: none;
  color: var(--white);
}

.hub-prose .hub-btn--out,
.hub-prose .hub-btn--quiet {
  color: var(--bks-blue);
}

.hub-prose .hub-btn--ghost {
  color: var(--grey-700);
}

.hub-prose .hub-btn--ghost:hover {
  color: var(--ink);
}

.hub-prose .hub-btn--quiet {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ── Code ── */
.hub-prose code:not([class]) {
  font-family: var(--mono);
  font-size: 0.88em;
  background: var(--surface);
  border: var(--rule);
  border-radius: var(--r-sm);
  padding: 1px 5px;
}

.hub-prose pre:not([class]) {
  margin: 0 0 var(--s-5);
  padding: var(--s-4);
  background: var(--surface);
  border: var(--rule);
  border-radius: var(--r-md);
  overflow-x: auto;
}

.hub-prose pre:not([class]) code {
  border: 0;
  padding: 0;
  background: none;
}

/* ── Tabelle im Fliesstext ──
   Nicht `.hub-table`: dieses Markup kommt aus dem Bestand und traegt keine
   Klasse, also kann die Hairline-Tabelle der Ebene es nicht erreichen. Die
   Regeln bilden ihre Optik am nackten Element nach — ohne Kartenpolster, weil
   die Tabelle hier IM Satz steht und nicht in einer Karte. `display: block`
   macht sie bei 500px waagerecht scrollbar; sonst schoebe sie die ganze Seite
   auf (gemessen in M2.0c). */
.hub-prose table:not([class]) {
  display: block;
  overflow-x: auto;
  margin: 0 0 var(--s-5);
  border-collapse: collapse;
  font-size: var(--dens-table);
  line-height: 1.45;
}

.hub-prose table:not([class]) th {
  text-align: left;
  vertical-align: bottom;
  padding: var(--s-2) var(--s-4) var(--s-2) 0;
  border-bottom: 1px solid var(--hairline-2);
  font-size: var(--dens-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--grey-600);
  font-weight: 700;
  white-space: nowrap;
}

.hub-prose table:not([class]) td {
  text-align: left;
  vertical-align: top;
  padding: var(--s-2) var(--s-4) var(--s-2) 0;
  border-bottom: var(--rule);
  color: var(--grey-700);
}

@media (max-width: 900px) {
  /* Karten und Befundblock ziehen ihr Polster auf --s-5 zusammen (card.css).
     Der Fliesstext folgt, damit die eine Kante erhalten bleibt. */
  .hub-prose h2:not([class]) {
    padding-top: var(--s-6);
    margin-top: var(--s-8);
  }
}


/* ══ chart.css ══ */
/* ============================================================================
   HUB-CHART-RAHMEN
   Der Chart selbst ist SVG mit aus den Werten gerechneter Geometrie
   (HubGroupedBars.vue) — kein Balken "nach Gefühl", keine Chart-Bibliothek.

   Die Kompaktform ist der einzige Punkt, an dem das Mockup nachweislich
   falsch lag: bei 500px schrumpfte die Abbildung auf Briefmarkengröße, weil
   die linke Beschriftungsspalte 168 von 460 Pixeln nahm. Gelöst wird das
   nicht durch Skalieren, sondern durch einen zweiten Satz: Institutsname
   über dem Balkenpaar, Plotfläche über die volle Breite.

   Beide Sätze stehen im Markup und werden per Media Query umgeschaltet.
   Der Grund: keine Client-Messung (kein ResizeObserver), damit die Fläche
   serverseitig gerendert, statisch ausgeliefert und deterministisch
   fotografiert werden kann. Kosten: acht Zeilen SVG doppelt. Vertretbar.
   ============================================================================ */

.hub-chart {
  position: relative;
}

.hub-chart svg {
  display: block;
  width: 100%;
  height: auto;
}

.hub-chart__wide {
  display: block;
}

.hub-chart__compact {
  display: none;
}

@media (max-width: 700px) {
  .hub-chart__wide {
    display: none;
  }

  .hub-chart__compact {
    display: block;
  }
}

/* ── Legende ──
   Pflicht ab zwei Serien (Farbe darf Identität nie allein tragen). Die
   Marke ist ein 3px-Riegel in der Serienfarbe, der Text trägt Text-Token. */
.hub-legend {
  display: flex;
  gap: var(--s-5);
  font-size: 12px;
  color: var(--grey-600);
  margin-top: var(--s-4);
  flex-wrap: wrap;
}

.hub-legend i {
  display: inline-block;
  width: 14px;
  height: 3px;
  margin-right: 7px;
  vertical-align: middle;
}

/* Referenzlinie (Median, EU-Aggregat): gestrichelt, grau, immer beschriftet.
   Nie eine Serienfarbe — eine Bezugsgröße ist keine Serie. */
.hub-chart__reflabel {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  fill: var(--chart-ref);
}


/* ══ states.css ══ */
/* ============================================================================
   HUB-ZUSTÄNDE · lädt, leer, Fehler, Testbetrieb
   Ein Zustand ist kein Platzhalter. Er sagt, WAS fehlt, SEIT WANN, und was
   der Nutzer stattdessen tun kann. Der Leerzustand des Banken-Dashboards ist
   deshalb kein "keine Daten", sondern der dokumentierte Realfall: Deutsche
   Bank meldete zu Q3/2025 CODIS KM1, aber keinen FINDIS-Report.
   ============================================================================ */

.hub-state {
  padding: 26px var(--s-6);
  border: var(--rule);
  border-radius: var(--r-md);
  background: var(--white);
}

/* Als Zustand INNERHALB einer Karte: kein zweiter Rahmen, keine zweite
   Rundung. Nur das Innere wird ersetzt. */
.hub-card__body > .hub-state,
.hub-state--inline {
  border: 0;
  border-radius: 0;
  padding: var(--s-6) 0;
  background: transparent;
}

/* Der Inline-Reset muss auch die Signalkante der Modifikatoren löschen —
   sonst gewinnt er dank `.hub-card__body > .hub-state` (Spezifität 0,2,1)
   gegen `.hub-state--err` (0,1,0) nur zufällig. Explizit ist besser. */
.hub-state--inline.hub-state--err,
.hub-state--inline.hub-state--empty {
  border-left: 0;
}

.hub-state__key {
  font-family: var(--mono);
  font-size: var(--dens-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--grey-500);
  margin-bottom: var(--s-2);
}

.hub-state h4 {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
}

.hub-state p {
  margin: 0;
  font-size: var(--dens-table);
  color: var(--grey-600);
  max-width: 56ch;
}

.hub-state p + p {
  margin-top: var(--s-3);
}

/* Fehler: die Linke Kante trägt das Signal, nicht die Fläche. Der
   Fehler-Tint aus der alten tokens.css ist wärmer als die neue Grauwelt
   (siehe tokens-common.css) — als Vollfläche würde er auffallen wie ein
   Fremdkörper. */
/* Freistehender Zustand (Galerie, Doku): die linke Kante trägt das Signal. */
.hub-state--err {
  border-left: 3px solid var(--err);
}

.hub-state--empty {
  border-left: 3px solid var(--hairline-2);
}

.hub-state--err .hub-state__key {
  color: var(--err);
}

/* IN einer Karte trägt die KARTE das Signal, nicht der Zustand.
   Zwei Gründe, beide am Bild aufgefallen:
   · Ein 3px-Riegel am linken Rand des Kartenkörpers klebt am Text und
     verschiebt ihn aus der gemeinsamen Textkante aller Kartenflächen.
   · Semantisch ist nicht der Zustand fehlerhaft, sondern der Block.
   `inset`-Schatten statt `border-left`, weil ein Rahmen den Inhalt um 3px
   verschieben und die Kante genau dort brechen würde, wo sie zählt. */
.hub-card:has(> .hub-card__body > .hub-state--err) {
  box-shadow: var(--shadow-1), inset 3px 0 0 var(--err);
}

/* Für den Leerzustand KEIN Riegel an der Karte: eine fehlende Meldung ist
   kein Defekt, sondern eine Aussage über die Datenlage. Ein Signalstreifen
   dafür wäre gegen --hairline-2 ohnehin fast unsichtbar und würde die
   Bedeutung des Streifens verwässern — er heißt "hier ist etwas kaputt". */

/* ── Unbestimmter Ladebalken ── */
.hub-bar-indet {
  height: 2px;
  background: var(--hairline);
  overflow: hidden;
  margin-top: var(--s-4);
  max-width: 220px;
}

.hub-bar-indet i {
  display: block;
  height: 2px;
  width: 40%;
  background: var(--bks-blue);
  animation: hub-slide 1.4s var(--ease) infinite;
}

@keyframes hub-slide {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}

@media (prefers-reduced-motion: reduce) {
  .hub-bar-indet i {
    animation: none;
    width: 100%;
    opacity: 0.4;
  }
}

/* ── Zustände nebeneinander (nur für die Referenzfläche/Doku) ── */
.hub-states {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-5);
  margin-bottom: var(--s-6);
}

.hub-states > .hub-state {
  box-shadow: var(--shadow-1);
}

@media (max-width: 900px) {
  .hub-states {
    grid-template-columns: 1fr;
  }
}
