/* ═══════════════════════════════════════════════════════════════════════════
   BAUFORM DER MODULSEITEN
   ═══════════════════════════════════════════════════════════════════════════

   Angelegt am 25.08.2026. Abgeleitet aus dem Quelltext des Vorbilds —
   src/components/nest/ModulePanel.tsx und primitives.tsx im Lovable-Projekt
   Asset_Nest, Route /entwurf-depot. Jede Zahl hier steht dort; die
   Tailwind-Klasse ist danebengeschrieben.

   WAS HIER STEHT
   Die drei Bausteine, aus denen jede Modulseite besteht:

     .master-frame.seitenkopf  Kopfblock — Leitzahl links, Kennzahlen rechts
     .breakdown       Der Bereich darunter für die Einzelteile
     .daten-tabelle   Tabellenform mit Kopfton und Summenzeile

   WAS HIER NICHT STEHT
   Grundwerte (Farben, Radien, Abstände) — die kommen aus shared/tokens.css.
   Seiten-Eigenheiten — die bleiben im jeweiligen css/styles.css.

   OFFENER PUNKT (25.08.2026)
   Der Hub (css/styles.css) trägt eigene, gewachsene Fassungen von
   .panel-raster, .panel-seite, .seite-kachel und .hero-value — teilweise
   doppelt definiert. Sie sind bewusst NOCH NICHT ersetzt: das automatische
   Herausschneiden von 39 verstreuten Regeln hätte die Kaskade kippen können,
   und der Hub steht gerade. Aufräumen folgt, sobald Depot und InOut auf
   dieser Datei laufen — dann ist der Vergleich messbar statt geraten.
   Bis dahin gilt: der Hub lädt css/styles.css NACH dieser Datei und
   gewinnt damit. Zwei Fassungen, eine davon befristet.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ─── Kopfblock einer Modulseite = ZONE, benannte Variante ──────────────────
   03.09.2026: .modul-panel war eine VIERTE Zone-Bauform.

   Nachgemessen im Browser, nicht aus dem CSS gelesen:

       Eigenschaft      .master-frame      .modul-panel
       Flaeche          #f4ece4            #f4ece4       gleich
       Rahmen           1px #dac9ad        1px #dac9ad   gleich
       Radius           20px               20px          gleich
       Polster          24px               24px          gleich
       Abstand unten    16px               12px          ABWEICHUNG

   Vier von fuenf Eigenschaften identisch. Das ist keine eigene Bauform,
   sondern eine Kopie mit einer Abweichung, die niemand entschieden hat --
   dieselbe Sorte wie die fuenf .master-frame-Definitionen vom 02.09.2026,
   nur ist sie damals durchgerutscht, weil sie ANDERS HEISST. check-drift
   sucht denselben Selektor in mehreren Dateien; zwei verschiedene Namen mit
   gleicher Wirkung findet es nicht.

   DIE ABWEICHUNG BLEIBT, ABER SIE BEKOMMT EINEN NAMEN. Sie stillschweigend
   auf 16px anzugleichen waere eine Gestaltungsentscheidung, die hier nebenbei
   getroffen wuerde. Als benannte Variante steht sie sichtbar da und laesst
   sich in einem Schritt aendern -- oder eben nicht.

   .modul-panel-kopf und -sub bleiben, wie sie heissen: sie sind der INHALT
   dieser Zone (Pille, h1, Untertitel), keine Bauform.

   Vorbild: <section className="rounded-[14px] p-4 sm:p-5"
            style={{ background:"var(--v6-head)",
                     border: "1px solid var(--v6-head-line)" }}>          */
.master-frame.seitenkopf { margin-bottom: var(--frame-gap); }

/* Kopfzeile: Pille, Titel, Untertitel.
   Der SEITENKOPF (dashboard-header mit h1 und Beschreibung) entfällt auf
   Modulseiten — er wiederholte, was hier ohnehin steht. Deshalb trägt dieser
   Block ein h1 und nicht ein h2: er IST die Überschrift der Seite. */
/* ═══ DRITTE KOPFBAUFORM, VON MIR UEBERSEHEN  (17.09.2026) ═════════════════
   MWM: "bei depot/ greift die Ueberschriftregel nicht. Depot & Cash · Bestand
   ist noch immer auf 600, und die Ueberschriften der blauen Frames sind
   groesser gesetzt als Depot & Cash · Bestand."

   Er hat recht, und der Fehler ist meiner. Ich habe die Regel gemessen und
   gemeldet "46 Ueberschriften, alle 22 px" -- gescannt hatte ich aber nur
   <h2>. Dieser Block traegt bewusst ein <h1> (siehe Begruendung darueber:
   er IST die Ueberschrift der Seite). Er stand damit ausserhalb meiner
   Messung UND ausserhalb der Regel, obwohl er genau der Fall ist, den sie
   meint: der Kopf des beigen Traegerrahmens.

   Gemessen war er --fs-kpi / --fw-halbfett = 19,5 px / 600. Die blauen
   Bloecke darunter standen auf 22 px -- die Seitenueberschrift war kleiner
   als ihre eigenen Abschnitte. Umgekehrte Hierarchie.

   --fs-kpi ist ausserdem die Rolle "Kennzahl, grosse Summe". Derselbe
   geborgte Zahlengrad, den Depot am 09.09. schon einmal fuer eine
   Ueberschrift benutzt hatte (#147). Zweites Vorkommen, gleiche Ursache.

   Betroffen: depot/index.html und estate/analyse.html.
   tools/miss-ueberschriften.mjs misst jetzt h1, h2 und h3 in Kopfbauformen,
   damit eine Ueberschrift nicht wieder durch die Tagwahl aus der Regel
   faellt. */
.modul-panel-kopf { margin-bottom: var(--sp-3); }
.modul-panel-kopf h1 {
  font-size: var(--fs-abschnittstitel);
  font-weight: var(--fw-fett);
  line-height: var(--lh-zahl);                     /* leading-none */
  letter-spacing: var(--ls-titel);
  margin: var(--sp-2) 0 0;
}
.modul-panel-kopf .modul-panel-sub {
  font-size: var(--fs-xs);            /* text-[11.5px] */
  color: var(--muted);
  margin: var(--sp-2) 0 0;
}

/* Leitzahl links, Kennzahlen rechts.
   300 px statt der 260 des Hubs — so steht es im Vorbild für Modulseiten
   (lg:grid-cols-[minmax(0,1fr)_300px]). Der Unterschied ist gewollt: hier
   stehen Beträge, im Hub Wegweiser. */
.modul-raster {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--sp-2);
  align-items: stretch;
}
@media (max-width: 900px) { .modul-raster { grid-template-columns: 1fr; } }

/* Card p-4 */
.modul-leitkarte {
  background:var(--innenkarte-flaeche);
  border:1px solid var(--karten-linie);
  /* --r-gross, nicht --r-bedien (17.09.2026): die Leitkarte ist der
     BEHAELTER der Unterkacheln, und sie trug 8px, waehrend die Kacheln in
     ihr 10px trugen. Innen runder als aussen -- die inneren Kanten schwangen
     aus der aeusseren heraus. Gefunden von check-kantenschachtelung, das an
     diesem Tag entstand; der Fehler stammt aus meinem Kartenumbau vom
     selben Tag. Jetzt: Leitkarte 10 (sie ist eine Karte), Unterkacheln 8
     (sie sind eine Ebene tiefer). */
  border-radius: var(--r-gross);
  padding: var(--sp-4);
}
.modul-leitkarte > .kachel-label {
  display: block; color: var(--muted); font-size: var(--fs-kicker);
  text-transform: uppercase; letter-spacing: var(--ls-versal); font-weight: var(--fw-halbfett);
}
/* Leitzahl und Veraenderung nebeneinander, auf der Schriftlinie ausgerichtet.
   align-items: baseline und nicht center: bei 26 px neben 11,5 px wuerde
   "mittig" die kleine Zahl optisch nach oben ziehen -- die gemeinsame
   Grundlinie ist das, was das Auge als "auf einer Hoehe" liest. */
.leitzahl-zeile {
  display: flex; align-items: baseline; gap: var(--sp-2);
  flex-wrap: wrap;
}
/* Dieselben zwei Farben wie ueberall sonst auf der Seite. */
.leitzahl-delta {
  font-size: var(--fs-xs); font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.leitzahl-delta.positive { color: var(--zahl-positiv); }
.leitzahl-delta.negative { color: var(--zahl-negativ); }
/* Bezugspunkt unter der Leitzahl (26.08.2026, MWM). Kleinste Stufe und
   gedaempft: die Angabe qualifiziert die Prozentzahl darueber, sie
   konkurriert nicht mit ihr. Eigene Zeile statt Klammer hinter der Zahl --
   die Leitzahl-Zeile traegt schon Betrag und Delta, ein drittes Element
   haette sie bei schmalem Fenster umbrechen lassen. */
.leitzahl-seit {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: var(--fs-fuss);
  line-height: 1.3;
}
.modul-leitzahl {
  display: block;
  font-size: var(--fs-leitzahl);      /* text-[26px] */
  font-weight: var(--fw-halbfett); line-height: var(--lh-zahl); letter-spacing: var(--ls-titel);
  font-variant-numeric: tabular-nums;
  margin-top: var(--sp-2);
}

/* Anteilsbalken unter der Leitzahl — dieselbe Bauform wie im Hub:
   6 px hoch, 4 px Lücke, Segmente einzeln rund, keine graue Schiene.
   Der Balken sagt, woraus die Leitzahl besteht; die Kennzahlen rechts sagen,
   wie viel das jeweils ist. Zwei Antworten auf dieselbe Frage, eine als
   Verhältnis, eine als Betrag. */
.anteil-balken {
  /* Die Luecke steht als Variable, weil js/dashboard.js sie mitrechnen muss:
     der zusammenfassende Balken darunter ist EIN Segment und muss so breit
     werden wie zwei Segmente PLUS ihrer Luecke. Zwei Zahlen fuer denselben
     Abstand waeren der naechste Fall, der auseinanderlaeuft. */
  --balken-luecke: 4px;
  display: flex; height: var(--balken-hoehe); gap: var(--balken-luecke);
  margin-top: var(--sp-3);
}
.anteil-balken > div { border-radius: var(--r-pill); transition: width .3s; }

/* Kennzahlen rechts: Card px-3 py-2.5 */
/* ═══ DAS KOPFRASTER EINER MODULSEITE (26.08.2026) ══════════════════════════
   Zwei Spalten: links die Grafik, rechts die Kennzahlen. Darueber optional
   eine Karte ueber die volle Breite.

     .modul-kopf          das Raster
     .modul-kopf-breit    Kind, das beide Spalten nimmt
     .modul-kennzahlen    die rechte Spalte (weiter unten in dieser Datei)

   Stand bis heute als .io-kopf / .io-balkenkarte NUR in inout/css/styles.css.
   Als Estate denselben Aufbau bekam, waere die Regel ein zweites Mal
   entstanden -- mit denselben Werten, an einer anderen Stelle, und ab dem
   naechsten Umbau mit anderen. Genau der Weg, auf dem die Suite schon
   dreimal auseinandergelaufen ist.

   align-items: start und NICHT stretch. Der Unterschied hat am 26.08.2026
   einen 660 px hohen Ring erzeugt: stretch zieht die Grafikkarte an der
   Kachelspalte hoch, und eine Grafik mit height:100% darin wuchs mit. Der
   INHALT bestimmt hier die Hoehe, nicht der verfuegbare Platz -- sonst
   fuellt eine Karte ihn mit nichts. */
/* ═══ DIE SPALTEN SCHLIESSEN UNTEN BUENDIG AB  (17.09.2026) ════════════════
   MWM zu /estate/: "passe die Frames so an, dass sie wie im Main immer einen
   buendigen Block ergeben."

   GEMESSEN, warum es bei Depot schon stimmte und bei Estate und InOut nicht:
   die drei Modulseiten benutzen ZWEI VERSCHIEDENE Container fuer dieselbe
   Sache -- den zweispaltigen Kopf.

     .modul-raster   Depot           1fr / 300px    align-items: stretch
     .modul-kopf     Estate, InOut   2fr / 1fr      align-items: START   ←

   `start` heisst: die rechte Spalte wird nur so hoch wie ihr Inhalt. Damit
   gibt es unten keinen Rest -- und die Regel von gestern, die den Analyse-
   Knopf in den Rest wachsen laesst (.modul-kennzahlen > .teaser:last-child),
   hatte auf diesen zwei Seiten schlicht nichts zu verteilen. Der Fix war
   richtig, er lief nur ins Leere.

   Mit `stretch` ist die Spalte so hoch wie die Leitkarte links, der Knopf
   waechst in den Rest, und der Block schliesst unten buendig ab -- genau wie
   auf dem Hub und bei Depot.

   NICHT ZUSAMMENGEFUEHRT: dass es zwei Container fuer einen Zweck gibt, ist
   die eigentliche Unordnung -- sie unterscheiden sich auch in den
   Spaltenbreiten (2fr/1fr gegen 1fr/300px). Das Vereinheitlichen verschiebt
   die Breiten auf drei Seiten und ist eine eigene Aufgabe. Hier faellt nur
   die eine Eigenschaft, die den Unterschied macht. */
.modul-kopf {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--sp-2);
  align-items: stretch;
}
.modul-kopf-breit { grid-column: 1 / -1; }

@media (max-width: 900px) {
  .modul-kopf { grid-template-columns: minmax(0, 1fr); }
}

/* Zwei Kennzahlen NEBENEINANDER innerhalb der breiten Karte -- fuer
   Groessen, die den Balken darueber erklaeren (Estate: Restschuld und
   Cashflow unter dem Beleihungsbalken). Eigene Klasse statt Inline-Raster,
   damit die zweite Modulseite, die das braucht, sie findet. */
.modul-unterkacheln {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}
/* WEISS wie jede andere Karte (26.08.2026, MWM: "mach dir mal trotz allem
   einfach n weissen Untergrund"). Ich hatte --frame-innen genommen, weil es
   ein Rahmen im Rahmen ist -- aber diese zwei Kacheln sitzen auf --paper,
   nicht auf einem Zonenboden. Ein abgesetzter Ton auf Weiss macht sie zu
   einem Kasten IM Kasten, obwohl sie einfach zwei Kacheln sind.

   --frame-innen bleibt richtig fuer Bloecke auf getoentem Grund (die
   Positionsliste in InOut, die Sonderbloecke). Hier nicht. */
.modul-unterkachel {
  background: var(--innenkarte-flaeche);
  border: 1px solid var(--karten-linie);
  /* Eine Stufe unter der Leitkarte, die sie enthaelt — siehe dort. */
  border-radius: var(--r-bedien);
  padding: var(--sp-2) var(--sp-3);
  display: flex;
  flex-direction: column;
}
/* Zahl links, Kurve rechts -- die Kurve erklaert die Zahl, sie steht nicht
   fuer sich. Feste Hoehe, damit beide Kacheln gleich hoch bleiben, auch
   wenn eine keine Kurve hat.

   OHNE ELTERNTEIL im Selektor seit dem 31.08.2026 (MWM: "mach die
   Kaltmiete-Karte kleiner und setz die Kurve rechts ins Frame").

   Die beiden Regeln standen als `.modul-unterkachel .kachel-*` und trafen
   damit nur die zwei Kacheln im Leitblock. Die Kaltmiete-Kachel ist eine
   .modul-kennzahl -- dort griff weder die Flex-Zeile noch die feste
   Kurvengroesse. Folge: das SVG lief mit preserveAspectRatio="none" ueber
   die volle Kachelbreite und schob sich unter die Zahl, die Kachel wurde
   dreimal so hoch wie ihre Nachbarn.

   Die Klassennamen beschreiben, was sie tun -- "eine Zeile aus Zahl und
   Kurve" gilt in jeder Kachel gleich. Ein Elternteil davor macht aus einer
   Bauform eine Ortsangabe. */
.kachel-zeile {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-2);
}
.kachel-kurve {
  flex-shrink: 0;
  height: 26px;
  width: 72px;
  display: block;
}
/* Dieselben Grade wie .modul-kennzahl darunter -- Kicker fuer die
   Beschriftung, --fs-h2 fuer die Zahl. Ich hatte hier zuerst --fs-label und
   --fs-kennzahl geschrieben; beide Tokens gibt es nicht. Ein var() auf einen
   Namen, den niemand definiert hat, faellt still auf den geerbten Wert
   zurueck -- kein Fehler, keine Meldung, nur eine Schrift, die zufaellig
   irgendeine Groesse hat. */
.modul-unterkachel > span {
  display: block;
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-versal);
  text-transform: uppercase;
  color: var(--muted);
  font-weight: var(--fw-halbfett);
}
.modul-unterkachel strong {
  font-size: var(--fs-kpi);
  font-weight: var(--fw-halbfett);
  font-variant-numeric: tabular-nums;
  display: block;
  margin-top: 2px;
}
.modul-unterkachel small {
  display: block;
  font-size: var(--fs-fuss);
  color: var(--muted);
  margin-top: 2px;
}

/* ═══ DIE SPALTE SCHLIESST UNTEN BUENDIG AB  (16.09.2026) ══════════════════
   MWM: "mach den Analyse-Button etwas höher, so dass der Frame unten bündig
   ist mit dem Nettovermögen-Frame links."

   GEMESSEN, woran es lag: .modul-raster stellt beide Spalten auf
   `align-items: stretch` -- die Kachelspalte IST also bereits so hoch wie die
   Leitkarte links. Nur ihr Inhalt sass oben: `align-content: start` packt die
   Zeilen an den Anfang und laesst den Rest der Spalte leer. Die Luft unten
   war kein fehlender Platz, sondern ungenutzter.

   WARUM FLEX STATT GRID. Im Raster braeuchte es fuer "die letzte Zeile nimmt
   den Rest" eine feste Zeilenliste mit 1fr am Ende -- und die Spalte hat je
   nach Modul drei oder vier Zeilen (InOut hat eine Kennzahl mehr). Eine
   Zeilenliste, die man pro Seite pflegen muss, waere die naechste Stelle, die
   beim Hinzufuegen einer Kachel stillschweigend falsch wird.

   Als Flex-Spalte steht die Regel dagegen an EINER Stelle und gilt unabhaengig
   von der Anzahl: das letzte Element waechst in den Rest. Der Teaser ist auf
   allen drei Modulseiten das letzte Kind -- nachgemessen, nicht angenommen.

   Sonst aendert sich nichts: die Kinder lagen schon einspaltig untereinander,
   die Luecke kommt weiter aus `gap`, und die volle Breite ist in beiden
   Verfahren der Standard.

   `1 0 auto` und nicht `1`: die Kurzform setzt flex-basis auf 0 und laesst
   den Knopf schrumpfen, sobald die Spalte einmal ZU KURZ ist -- unter 900 px
   faellt das Raster auf eine Spalte, dann gibt es keinen Rest zu verteilen.
   Mit `0 auto` waechst er nur nach oben, nie unter seinen Inhalt. */
.modul-kennzahlen { display: flex; flex-direction: column; gap: var(--sp-2); }
.modul-kennzahlen > .teaser:last-child { flex: 1 0 auto; }
/* Zwei Kennzahlen in einer Zeile innerhalb der Kachelspalte. Fuer Groessen,
   die einander bedingen -- Marktwert und Beliehen, wo die eine der Bezug der
   anderen ist. minmax(0,1fr) und nicht 1fr: sonst sprengt eine lange Zahl
   die Spalte, weil 1fr eine min-width von auto hat. */
.kennzahl-paar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-2);
}
/* .seite-kachel ist die Hub-Fassung derselben Kachel (26.08.2026
   eingesammelt, war seit dem 25.08. als OFFENER PUNKT im Kopf dieser Datei
   vermerkt). Sie stand als Kopie in css/styles.css und wich in vier Werten
   ab -- Leitzahl var(--fs-kpi) statt var(--fs-kpi), fehlendes margin-top,
   fehlendes Abschneiden langer Beschriftungen. Keine der vier war eine
   Entscheidung. */
/* --r-gross wie jede andere Kachel der Suite (31.08.2026, Radius-Angleich
   ueber alle vier Module). Diese beiden standen als einzige Kacheln auf
   --r-bedien, dem Wert fuer Felder und Bloecke -- neben
   .modul-unterkachel und .portfolio-strip > div fielen sie dadurch als
   eckiger auf, ohne dass es dafuer einen Grund gab. */
.modul-kennzahl,
.seite-kachel {
  position: relative;
  background:var(--innenkarte-flaeche);
  border:1px solid var(--karten-linie);
  border-radius: var(--r-gross);
  padding: var(--sp-zelle) var(--sp-3);
}
.modul-kennzahl > span,
.seite-kachel > span {
  display: block; color: var(--muted); font-size: var(--fs-kicker);
  text-transform: uppercase; letter-spacing: var(--ls-versal); font-weight: var(--fw-halbfett);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.modul-kennzahl strong,
.seite-kachel strong {
  display: block; font-size: var(--fs-kpi);   /* text-[17px] */
  font-weight: var(--fw-halbfett); line-height: var(--lh-zahl); letter-spacing: var(--ls-titel);
  font-variant-numeric: tabular-nums; margin-top: var(--sp-1);
}
.modul-kennzahl small,
.seite-kachel small {
  display: block; color: var(--muted); font-size: var(--fs-kicker); margin-top: var(--sp-1);
}
/* Die Prozentzeile unter einer Kennzahl traegt dieselben zwei Farben wie die
   Spalte "Veraenderung" in den Tabellen -- eine Zahl mit Vorzeichen bedeutet
   auf der ganzen Seite dasselbe, egal wo sie steht. */
.modul-kennzahl small.kennzahl-delta { font-variant-numeric: tabular-nums; }
.modul-kennzahl small.kennzahl-delta.positive { color: var(--zahl-positiv); }
.modul-kennzahl small.kennzahl-delta.negative { color: var(--zahl-negativ); }
/* Bezugspunkt unter einer Kennzahl — dieselbe Rolle wie .leitzahl-seit unter
   der grossen Zahl, nur eine Ebene kleiner. */
.modul-kennzahl small.kennzahl-seit {
  color: var(--muted);
  font-size: var(--fs-fuss);
  margin-top: 1px;
  line-height: 1.3;
}

/* ─── Wegweiser: eine Kachel, die woandershin fuehrt ─────────────────────
   Aus css/styles.css hierher gezogen (26.08.2026). Sie fuehrt vom Hub zu
   einer Modulseite und von einer Modulseite zu ihrer Analyse -- dieselbe
   Geste, dieselbe Form. Die Antwort auf den Zeiger (Heben, Schatten) steht
   in shared/global.css, wie bei jeder anderen Kachel auch. */
.teaser {
  position: relative;
  display: flex; align-items: center;
  min-height: 62px;
  padding: var(--sp-4) var(--sp-5);
  text-decoration: none; color: inherit; cursor: pointer;
  background:var(--innenkarte-flaeche);
  border:1px solid var(--karten-linie);
  border-radius: var(--r-bedien, 10px);
  box-shadow: var(--shadow-soft);
  /* KEINE eigene transition mehr (09.09.2026, #131). Sie stand hier mit
     rohen .18s und ueberschrieb — weil diese Datei nach shared/global.css
     laedt — die geteilte Zeigergeste, die dort fuer .teaser ausdruecklich
     mitgesetzt wird. Die vier Kartenarten hatten damit wieder je eine
     eigene Staerke, genau das, was der Token --hover-dauer beenden
     sollte: "vorher standen .18s ease an neun Stellen und .2s ease an
     einer -- ein Unterschied, den niemand gewollt, aber auch niemand
     bemerkt hat." Neun wurden eingesammelt, diese vier blieben liegen. */
}
.teaser h3, .teaser > span:first-child {
  font-size: var(--fs-sm); font-weight: var(--fw-halbfett); line-height: 1.25;
  letter-spacing: var(--ls-titel); margin: 0; padding-right: 26px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.teaser small, .teaser-zusatz {
  color: var(--muted); font-weight: var(--fw-normal); font-size: var(--fs-xs);
}
/* In der Kennzahlen-Spalte einer Modulseite: flacher, damit er sich in die
   Kachelreihe fuegt statt sie zu sprengen. */
/* Flacher als im Hub, aber SONST identisch (Korrektur 26.08.2026): hier
   stand flex-direction:column, wodurch der Pfeil unter den Text rutschte
   statt rechts oben zu sitzen wie ueberall sonst. Der Pfeil ist absolut
   positioniert -- eine Richtungsaenderung am Elternteil haette ihn gar nicht
   betreffen duerfen, und genau deshalb faellt so etwas erst im Bild auf. */
.modul-kennzahlen .teaser {
  min-height: 0; padding: var(--sp-zelle) var(--sp-3); box-shadow: none;
}
.modul-kennzahlen .teaser h3 { font-weight: var(--fw-mittel); padding-right: var(--sp-6); }

/* ═══ DER PFEIL BLEIBT AUF HALBER HOEHE  (16.09.2026, Folge der Zeile drueber)
   .oeffnen-pfeil steht in shared/global.css absolut auf `top: 14px`. Im
   flachen Teaser der Kachelspalte war das zufaellig die Mitte -- der Kasten
   war rund 40 px hoch. Seit er in den Rest der Spalte waechst, ist er es
   nicht mehr: der Text zentriert sich, der Pfeil bleibt oben kleben, und die
   beiden driften um genau das auseinander, was der Kasten dazugewonnen hat.

   NICHT ueber `top: 50%; transform: translateY(-50%)` geloest, obwohl das der
   naechstliegende Weg waere: die Zeigergeste in global.css setzt beim Hover
   ein eigenes `transform` auf denselben Pfeil. Das eine wuerde das andere
   ueberschreiben, und der Pfeil spraenge beim Ueberfahren nach oben.

   Stattdessen von oben nach unten aufgespannt und der Inhalt darin
   zentriert. Das `transform` bleibt frei fuer die Geste, die es schon
   benutzt. */
.modul-kennzahlen .teaser .oeffnen-pfeil {
  top: 0; bottom: 0; display: flex; align-items: center;
}

/* ═══ IN DER KACHELSPALTE FAERBT SICH DIE LINIE NICHT  (16.09.2026) ════════
   MWM: "der Button bekommt beim Hover eine Haarlinie, soll er nicht. Miss
   nach, wie das bei den anderen Buttons reagiert."

   NACHGEMESSEN, alle anklickbaren Flaechen der Suite:

     .module-card    Rahmen transparent          → Modulfarbe beim Hover
     .project-card   Rahmen ink-Ton              → Modulfarbe beim Hover
     .steuer-teaser  Rahmen accent-Ton           → Modulfarbe beim Hover
     .teaser         Rahmen --karten-linie       → Modulfarbe beim Hover
     .modul-kennzahl Rahmen --karten-linie       → BLEIBT
     .secondary-btn  Rahmen --karten-linie       → --accent
     .primary-btn    Rahmen --karten-linie       → bleibt

   Auf dem HUB steht der Teaser zwischen .module-card-Kacheln, die dieselbe
   Antwort geben -- dort ist die farbige Linie die gemeinsame Sprache.

   In der KACHELSPALTE einer Modulseite stehen daneben drei .modul-kennzahl,
   und die reagieren ueberhaupt nicht. Der Teaser war damit der einzige
   Baustein der Spalte, der seine Kante einfaerbt: keine Auszeichnung,
   sondern ein Bruch in einer Reihe, die sonst ruhig ist.

   WAS BLEIBT: Hebung und der wandernde Pfeil. Er IST anklickbar, und das
   soll man sehen -- nur eben in derselben Lautstaerke wie der Rest der
   Spalte. Die Ausnahme steht bewusst hier neben `box-shadow: none`: beide
   haben denselben Grund, naemlich "fuegt sich in die Kachelreihe, statt sie
   zu sprengen". Wer die eine aufhebt, sollte die andere mitlesen. */
.modul-kennzahlen .teaser:hover,
.modul-kennzahlen .teaser:focus-visible { border-color: var(--karten-linie); }

/* ─── Der Bereich unter dem Panel ────────────────────────────────────────────
   Im Vorbild ein kühl unterlegtes Feld (--v6-module-field), das die
   Einzelteile zusammenfasst. Bei uns ist das --zone-boden-b, derselbe
   Blaugrau-Ton, den der Hub für seine Modul-Zone benutzt. */
.breakdown {
  background:var(--zone-boden-b);
  /* Hier stand "1px solid transparent" -- und 300 Zeilen weiter unten machte
     eine Sammelregel dieselbe Kante wieder sichtbar. Zwei Zeilen, zwei
     Aussagen, entschieden hat die Reihenfolge statt eine Absicht. Beide sind
     weg; es gilt "KEINE KANTE AUF EINER FARBIGEN FLAECHE". */
  border-radius: var(--r-gross);
  padding: var(--zone-polster);
  margin-bottom: var(--frame-gap);
}
/* Kopfzeile: Label links, Haarlinie dazwischen, Anmerkung rechts.
   Die Linie ist kein Schmuck — sie bindet Label und Anmerkung zu einer Zeile
   zusammen, statt zwei Textinseln nebeneinanderzustellen. */
.breakdown-kopf {
  display: flex; align-items: center; gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.breakdown-kopf .kachel-label { color: var(--muted); }
.breakdown-kopf .breakdown-linie {
  flex: 1; height: 1px; background:var(--karten-linie);
}
.breakdown-kopf .breakdown-notiz { color: var(--muted); font-size: var(--fs-kicker); }

/* ─── Tabellen ───────────────────────────────────────────────────────────────
   DataTable() aus primitives.tsx:
     Card overflow-hidden, radius 10
     table  text-[12.5px]
     thead  tr background --v6-block · th label-xs px-3 py-2
     tbody  tr border-t --v6-line-soft · td px-3 py-2 · rechts num font-medium
     tfoot  tr border-t --v6-line · td font-semibold, eigener Grund

   Unsere Zellen trugen bis dahin 12/18 px Polster bei 11,7 px Schrift — mehr
   Platz UND schlechter lesbar, die unangenehme Kombination. */
.daten-tabelle {
  background:var(--innenkarte-flaeche);
  border:1px solid var(--karten-linie);
  border-radius: var(--r-bedien);
  overflow: hidden;
}
.daten-tabelle table {
  width: 100%; border-collapse: collapse;
  font-size: var(--fs-sm);            /* 12,5 px */
}
.daten-tabelle thead tr { background:var(--tabellen-kopf); }
.daten-tabelle th {
  padding: var(--sp-2) var(--sp-3);
  color: var(--muted); font-size: var(--fs-kicker);
  text-transform: uppercase; letter-spacing: var(--ls-versal); font-weight: var(--fw-halbfett);
  text-align: left; white-space: nowrap;
}
.daten-tabelle td { padding: var(--sp-2) var(--sp-3); }
.daten-tabelle tbody tr { border-top: 1px solid var(--line-soft); }
.daten-tabelle th.rechts, .daten-tabelle td.rechts {
  text-align: right; font-variant-numeric: tabular-nums; font-weight: var(--fw-mittel);
}
.daten-tabelle tfoot tr { border-top: 1px solid var(--karten-linie); }
.daten-tabelle tfoot td {
  background:var(--tabellen-kopf); font-weight: var(--fw-halbfett);
}


/* ─── Zwei Kennzahlen nebeneinander: nur noch der schmale Umbruch ───────────
   Die Grunddefinition steht weiter oben in dieser Datei, bei
   .modul-kennzahlen. Hier stand sie ein ZWEITES Mal — und die zweite
   Fassung machte die erste kaputt (aufgeraeumt 09.09.2026, #130).

   Oben steht `minmax(0, 1fr) minmax(0, 1fr)` mit Begruendung: "sonst
   sprengt eine lange Zahl die Spalte, weil 1fr eine min-width von auto
   hat." Hier stand `1fr 1fr` — genau der Zustand, den der Kommentar oben
   als Fehler beschreibt, 160 Zeilen spaeter stillschweigend
   wiederhergestellt. Der Browser nimmt die spaetere Regel; die Reparatur
   war also seit ihrer Entstehung wirkungslos.

   Das ist der Unterschied zwischen "tote Angabe" und "zurueckgedrehte
   Reparatur": check-drift meldet beide gleich, aber hier ueberlebte der
   FALSCHE Wert. Sichtbar wird es erst bei einem Betrag, der breiter ist
   als seine halbe Spalte — also selten und dann heftig.

   Was hier bleibt, ist der Grund, warum es das Paar ueberhaupt gibt
   (25.08.2026, MWM: "die Zahlen sind riesengross und daneben superviel
   Platz"): Depot und Cash sind gleichrangig und kurz, nebeneinander nutzen
   sie die Spaltenbreite. Die Sparrate bleibt einzeilig darunter — sie ist
   eine andere Art von Zahl (Fluss statt Bestand). */
@media (max-width: 520px) { .kennzahl-paar { grid-template-columns: 1fr; } }

/* Diagramm INNERHALB der Leitkarte, unter der Zahl.
   Es ersetzt den Anteilsbalken: derselbe Platz, aber statt eines Zustands
   der ganze Verlauf. Der Zustand von heute ist der rechte Rand. */
.modul-leitkarte .chart-mid { margin-top: var(--sp-3); height: 190px; }
.modul-leitkarte .chart-footnote {
  margin: var(--sp-2) 0 0; font-size: var(--fs-fuss); color: var(--muted);
  line-height: 1.4;
}

/* ═══════════════════════════════════════════════════════════════════════════
   ZAHLEN LINKS · BILD RECHTS · DETAILS DARUNTER
   ═══════════════════════════════════════════════════════════════════════════
   25.08.2026, Entscheidung MWM. Dieselbe Form wie der Kopfblock oben, nur
   eine Ebene tiefer — dadurch trägt die ganze Seite eine Grammatik statt
   fünf Sonderfälle, und dieselbe Form passt danach auf InOut und Estate.

   VORHER standen in jedem Block drei Dinge untereinander: Werte quer über
   die volle Breite, darunter eine Tabelle, darunter eine Grafik über die
   volle Breite. Wer die Zahl mit der Kurve vergleichen wollte, musste lesen,
   scrollen und zurückdenken. Nebeneinander steht die Zahl neben dem Verlauf,
   aus dem sie stammt.

   Die Tabelle bleibt DARUNTER und nicht daneben: sie ist die Herkunft der
   Zahl, nicht ihre Aussage. Wer die Aussage sucht, liest oben; wer die
   Herkunft prüfen will, geht tiefer. */
.werte-und-bild {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  gap: var(--sp-3);
  /* stretch statt start (25.08.2026, MWM: "die drei Felder genauso hoch wie
     die Grafik nebenan"). Die Bildspalte gibt die Hoehe vor -- ein Diagramm
     hat eine feste Hoehe, drei Kacheln nicht. Also strecken sich die
     Kacheln, nicht das Bild. */
  align-items: stretch;
  margin-bottom: var(--sp-3);
}
@media (max-width: 900px) { .werte-und-bild { grid-template-columns: 1fr; } }

/* Die Wertespalte: eine Zeile je Zahl, gestapelt.
   Kein Raster mit gleich breiten Kacheln — die Zahlen sind verschieden lang
   und stehen untereinander, damit man sie an derselben Kante vergleicht. */
/* Gleich hohe Zeilen, die zusammen die Hoehe der Bildspalte fuellen.
   minmax(min-content, 1fr): sie teilen den Platz gleichmaessig, unterschreiten
   aber nie ihren Inhalt -- sonst wuerde bei kurzem Diagramm Text abgeschnitten.
   Dieselbe Loesung wie im Hub-Panel; dort steht die Begruendung ausfuehrlich. */
.wertespalte {
  display: grid;
  grid-auto-rows: minmax(min-content, 1fr);
  gap: var(--sp-2);
  align-content: stretch;
}
/* position: relative als Bezug fuer den Quell-Verweis, der oben rechts in der
   Karte sitzt (shared/ui-icons.css, .quell-verweis). */
.wertezeile {
  position: relative;
  background:var(--innenkarte-flaeche);
  border:1px solid var(--karten-linie);
  border-radius: var(--r-bedien);
  padding: var(--sp-zelle) var(--sp-3);
}
.wertezeile > span {
  display: block; color: var(--muted); font-size: var(--fs-kicker);
  text-transform: uppercase; letter-spacing: var(--ls-versal); font-weight: var(--fw-halbfett);
}
.wertezeile strong {
  display: block; font-size: var(--fs-kpi);
  font-weight: var(--fw-halbfett); line-height: var(--lh-zahl); letter-spacing: var(--ls-titel);
  font-variant-numeric: tabular-nums; margin-top: var(--sp-1);
}
.wertezeile small {
  display: block; color: var(--muted); font-size: var(--fs-fuss);
  margin-top: var(--sp-1); line-height: var(--lh-eng);
}

/* NACHRANGIG — eine Stufe leichter, gleiche Groesse (26.08.2026, MWM).
   In beiden Wertespalten traegt genau EINE Karte die Aussage: links "pro
   Monat 250 EUR" (was Martin tatsaechlich tut), unten "heute 111.895 EUR"
   (was tatsaechlich da ist). Die anderen sind Hochrechnungen daraus --
   pro Jahr, in 20 Jahren, Bandbreite. Sie standen im selben Fettgrad und
   haben deshalb genauso laut mitgeredet.

   Bewusst NICHT ueber :not(:first-child) geloest: das waere eine Regel ueber
   die Position statt ueber die Bedeutung, und sie kippt still, sobald jemand
   eine Karte umsortiert. Die Klasse steht im Markup, wo die Entscheidung
   hingehoert.

   Nur der Fettgrad sinkt (600 -> 500), nicht die Groesse: es geht um weniger
   Praesenz, nicht um weniger Rang. Eine kleinere Zahl waere eine andere
   Aussage -- "unwichtig" statt "abgeleitet". */
.wertezeile.nachrangig strong { font-weight: var(--fw-mittel); }

/* Die Bildspalte trägt die Grafik in einer eigenen Karte — sonst schwebt sie
   auf dem Zonenboden, während links Karten stehen. */
.bildspalte {
  background:var(--innenkarte-flaeche);
  border:1px solid var(--karten-linie);
  border-radius: var(--r-bedien);
  padding: var(--sp-4);
}
.bildspalte > div { min-width: 0; }
.bildspalte .chart-sub { margin: 0 0 var(--sp-2); }
.bildspalte .chart-mid, .bildspalte .chart-tall { height: 210px; }
.bildspalte .chart-footnote {
  margin: var(--sp-2) 0 0; font-size: var(--fs-fuss);
  color: var(--muted); line-height: 1.4;
}

/* ─── Abstand zwischen Themen-Pille und Überschrift ──────────────────────────
   25.08.2026, Wunsch MWM ("bei Depot ist der gequetscht, sollte sein wie auf
   der Anfangsseite bei Assets").

   Der Abstand stand bisher an jeder Überschrift einzeln — im Hub 8 px, in
   Estate 7, in InOut wieder anders, und in Depot hatte ich ihn beim
   Flachmachen der Kopfzeile versehentlich auf 0 gesetzt. Vier Seiten, vier
   Werte, keiner davon irgendwo als Regel festgehalten.

   Jetzt EINE Regel für alle: --sp-2, das `mt-2` des Vorbilds. Sie greift über
   den Geschwister-Selektor, also überall dort, wo eine Überschrift direkt auf
   eine Pille folgt — unabhängig davon, ob der Block .modul-panel,
   .master-frame, .canvas-frame oder .breakdown heißt.

   Warum nicht als Abstand UNTER der Pille: die Pille steht auch allein
   (Breakdown-Kopfzeile, Modulkarten). Ein margin-bottom dort würde in diesen
   Fällen Luft erzeugen, die niemand bestellt hat. Der Abstand gehört zu dem
   Paar, nicht zu einem der beiden. */
/* ═══ DIE PILLE, EINMAL FUER DIE SUITE (26.08.2026, MWM) ═══════════════════
   "Die Pille hat hier wieder eine abweichende Schrift und Hoehe im Vergleich
    zu InOut und Depot."

   Sie stand als eigene Regel in VIER Stylesheets -- css/, depot/, estate/,
   inout/ -- mit identischen Werten bis auf einen: Depot trug font-weight 600,
   die anderen drei 700. Vier Kopien, eine Abweichung, und die war sichtbar.

   Niemand hat die 700 entschieden; sie stammen aus der Zeit vor der
   13-px-Basis, als die Suite ueberall fett war. Depot wurde beim Umbau
   nachgezogen, die anderen drei nicht -- kein Beschluss, nur drei Dateien,
   an die niemand gedacht hat.

   Jetzt 600 fuer alle, wie jede andere Beschriftung der Suite. */
.brand-kicker {
  display: inline-block;
  font-size: var(--fs-pille);
  letter-spacing: var(--ls-versal);
  font-weight: var(--fw-halbfett);
  padding: var(--sp-1) 11px;
  border-radius: var(--r-pill);
  background: rgba(var(--accent-rgb), var(--wash-leicht));
  color: var(--accent);
  /* Aus demselben Grund wie bei .thema: ohne festen Wert erbt die Pille die
     Zeilenhoehe ihres Umfelds und ist je Seite verschieden hoch. */
  line-height: var(--lh-eng);
  vertical-align: middle;
}

.brand-kicker + h1,
.brand-kicker + h2,
.brand-kicker + h3 { margin-top: var(--sp-2); }

/* ═══════════════════════════════════════════════════════════════════════════
   ZONEN: AUSSENLINIE UND THEMEN-PILLE — für die ganze Suite
   ═══════════════════════════════════════════════════════════════════════════
   25.08.2026, Wunsch MWM. Gilt auf allen 13 Seiten, weil diese Datei überall
   geladen wird.

   1 · DIE AUSSENLINIE
   Alle vier Blockarten trugen `border:1px solid transparent` — Platz für
   eine Linie, aber keine Linie. Sichtbar war nur die Fläche, und auf hellem
   Grund verschwamm ihre Kante. Die Seitenleiste hatte ihre Haarlinie, die
   Inhaltsblöcke nicht.

   Der Ton folgt der Fläche: warmer Rahmen um den warmen Boden, kühler um den
   kühlen. Ein Rahmen in der falschen Temperatur sieht aus, als gehöre er
   nicht dazu.

   2 · DIE THEMEN-PILLE
   Sie stand in der Akzentfarbe auf blassem Akzent-Grund — zweimal dieselbe
   Farbe, und lauter als die Überschrift darunter. Jetzt: helle Fläche,
   gedämpfte Schrift, wie im Vorbild. Die Pille sagt, WORUM es geht; die
   Überschrift sagt WAS. Nur eine von beiden darf rufen.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Warme Zone: Kopfblock einer Seite. Seit 03.09.2026 nur noch EIN Selektor
   -- .modul-panel ist in .master-frame.seitenkopf aufgegangen. */
/* ═══ DIE HAARLINIE UM DIE HAUPTFRAMES (17.09.2026) ═══════════════════════
   MWM: "die Haarlinien aussen um die Hauptframes koennen wir entfernen bzw.
   die gleiche Farbe geben wie dem Frame."

   Die gleiche Farbe, nicht border:none. Der Unterschied ist ein Pixel
   Aussenmass: nimmt man den Rahmen weg, schrumpft jeder Hauptframe um 2px
   in Breite und Hoehe, und alles darin rueckt nach. Mit gleicher Farbe
   bleibt die Geometrie, nur die Linie verschwindet.

   UND SIE ZIEHT MIT: border-color zeigt auf DIESELBE Variable wie
   background. Waehlt jemand in den Design-Einstellungen eine andere
   Flaechen-Ebene, wechselt der Rand automatisch mit -- eine zweite Angabe
   waere sonst genau die Sorte Doppelung, die hier seit Wochen aufgeraeumt
   wird. Gemessen stand die Linie bei 1,37:1 gegen ihre Flaeche; sichtbar,
   aber ohne Aussage.

   --zone-linie-a/-b bleiben: sie haben fuenf bzw. sieben weitere Nutzer,
   darunter die Skizzen auf farben.html, wo eine sichtbare Kante die Bauform
   ZEIGT und deshalb bleiben muss. */
/* Hier stand heute frueh ".master-frame { border:1px solid var(--zone-boden-a) }"
   -- Linie in Flaechenfarbe, die Zwischenstufe auf dem Weg zu der Regel, die
   jetzt weiter unten steht ("KEINE KANTE AUF EINER FARBIGEN FLAECHE").
   Weg statt getarnt. */

/* Kühle Zone: die Aufschlüsselung darunter */
/* --zone-linie-b statt --karten-linie (25.08.2026, MWM: "ein winziges
   bisschen dunkler"). Der Wert stammt aus dem Vorbild
   (--v6-module-field-line) und liegt rund vier Punkte unter der warmen
   Kartenlinie -- auf blaugrauem Grund braucht eine Kante etwas mehr
   Gegensatz als auf Cream, sonst verschwindet sie darin.
   Am 26.08.2026 kurzzeitig entsaettigt und noch am selben Tag zurueck-
   genommen: gemeint waren die getoenten Felder IN den Tabellen, nicht der
   Grund der Karten (siehe shared/tokens.css). */
/* #moduleSection SELBST kam am 27.08.2026 dazu (MWM: "auf der Seite ist um
   das blaue Frame nicht die Haarlinie gezogen").
   Hier stand `#moduleSection > section` -- die KINDER des Blocks, nicht der
   Block. Das war kein Tippfehler, sondern ein ueberholter Stand: bis zum
   01.08.2026 lagen dort mehrere Unterabschnitte mit eigener Flaeche. Seit
   der Block selbst den Zonenboden traegt, zeigte die Regel auf Elemente,
   die es nicht mehr gibt -- und der einzige Block, um den es geht, blieb
   ohne Kante.
   Der Kinder-Selektor bleibt stehen: er kostet nichts und faengt den Fall,
   dass dort wieder Unterabschnitte entstehen. */
/* ═══ KEINE KANTE AUF EINER FARBIGEN FLAECHE (17.09.2026) ═══════════════════
   MWM: "entferne nur bei den beiden Frames, also Frames mit der Farbe, die
   Haarlinie".

   Die Regel dahinter, allgemein gefasst:

     Eine Flaeche, die Zonenfarbe traegt, traegt KEINE Kante.
     Eine weisse Flaeche traegt eine.

   Warum das kein Geschmack ist: eine Kante hat genau eine Aufgabe, naemlich
   zu sagen, wo eine Flaeche aufhoert. Eine farbige Flaeche sagt das schon
   selbst -- der Farbwechsel IST die Kante. Die Linie zeichnet dann eine
   Grenze nach, die ohnehin sichtbar ist, und macht aus einer ruhigen Flaeche
   einen umrandeten Kasten. Eine weisse Karte auf weissem Grund dagegen hat
   keinen Farbwechsel, den sie benutzen koennte; dort traegt die Linie die
   ganze Abgrenzung und bleibt.

   Diese Sammelregel stand hier und setzte VIER Selektoren auf dieselbe
   Kante. Drei davon sind farbige Flaechen und verlieren sie jetzt; der
   vierte -- .canvas-frame -- bekommt seine Kante ohnehin schon weiter unten
   in seiner eigenen Bauform-Definition (Zeile ~1145). Die Regel war also
   schon vorher zur Haelfte Doppelung: dieselbe Entscheidung zweimal in
   derselben Datei, 320 Zeilen auseinander. Bei .breakdown war es sogar ein
   WIDERSPRUCH -- die Bauform oben setzte "1px solid transparent", diese
   Regel machte sie wieder sichtbar, und welche galt, entschied die
   Reihenfolge statt eine Absicht.

   Zur Geometrie: die Kante faellt ersatzlos weg, nicht auf "transparent".
   Eine reservierte unsichtbare Kante ist genau die Geisterregel, die
   spaeter niemand mehr erklaeren kann. Mit box-sizing: border-box bleibt die
   Aussenkante an ihrem Platz; der Inhalt bekommt 1px mehr Luft je Seite. */
.breakdown,
#moduleSection,
#moduleSection > section,
.master-frame,
.canvas-frame.zone { border: 0; }

/* Die STRUKTUR-Pille — eine Regel für alle Blockarten.
   MAIN DASHBOARD, ZUFLUSS, AUSBLICK, GELDFLUSS, PROJEKTE: sie sagen, welche
   ART von Block folgt. Grau, versal, gesperrt.

   :not(.thema) ist am 26.08.2026 dazugekommen und ist kein Feinschliff,
   sondern ein Fix. Die Themen-Pille (weiter unten, .brand-kicker.thema) hat
   Spezifitaet 0,2,0 -- diese Regel hier 0,3,0. Sie hat die Modulfarbe still
   ueberschrieben, und auf den Modulseiten stand die Pille deshalb grau da,
   waehrend sie im Hub farbig war. Zwei Regeln, die um dasselbe Element
   streiten, sind ein Fehler; :not() macht sie disjunkt: hier Struktur, dort
   Thema, kein Wettlauf mehr. */
.master-frame > .block-kopf .brand-kicker:not(.thema),
.master-frame > .section-heading .brand-kicker:not(.thema),
.master-frame.seitenkopf .brand-kicker:not(.thema),
.canvas-frame > .section-heading .brand-kicker:not(.thema),
.breakdown .brand-kicker:not(.thema),
/* Der Hub hat eine zweite Bauform: #moduleSection traegt seine Kopfzeile
   OHNE umgebenden Frame. Sie fehlte in der ersten Fassung dieser Regel --
   deshalb stand "MODULE" dort weiter in der alten Akzent-Optik, waehrend
   alles andere schon umgestellt war. */
#moduleSection > .section-heading .brand-kicker:not(.thema) {
  background:var(--pille-flaeche-a);
  color: var(--muted);
  font-weight: var(--fw-halbfett);
  /* font-size steht nicht mehr hier: die Grundform .brand-kicker sagt
     var(--fs-pille), und das ist seit dem 17.09.2026 dasselbe Mass. Eine
     Ueberschreibung mit demselben Wert ist eine zweite Definition, die beim
     naechsten Wechsel stehenbleibt -- genau so entstanden die zwei
     Pillengroessen, die es bis heute gab. */
  /* VERSALIEN (25.08.2026, MWM: "alles grossschreiben, einheitlich").
     Die Pille traegt eine Kapitelmarke, keinen Satz. Grossbuchstaben mit
     Sperrung lesen sich als Marke; gemischte Schreibung liest sich als Text
     und konkurriert dann mit der Ueberschrift darunter.
     text-transform statt Grossschreibung im Text: der Inhalt bleibt normal
     geschrieben und damit uebersetzbar -- "ETFs" wuerde sonst als "ETFS" in
     der Sprachdatei stehen und waere dort falsch. */
  text-transform: uppercase;
  letter-spacing: var(--ls-versal);
  padding: 3px 9px;
  border-radius: var(--r-pill);
}
/* Beide Strukturpillen werden aus ihrem Zonenboden und der Kartenfläche
   gemischt. Beim Wechsel der Flächen-Presets ziehen sie dadurch mit. */
.canvas-frame > .section-heading .brand-kicker:not(.thema),
#moduleSection > .section-heading .brand-kicker:not(.thema),
.breakdown .brand-kicker:not(.thema) { background:var(--pille-flaeche-b); border:1px solid var(--pille-linie); }


/* ─── Modulfarbe am Kopfblock ────────────────────────────────────────────────
   25.08.2026, Wunsch MWM: die Pille auf der Depot-Seite soll aussehen wie die
   auf der Modulkarte im Hub -- farbige Flaeche, schwarze Schrift.

   Die Zuordnung data-farbe → --modulfarbe stand bisher nur an .module-card
   (css/styles.css). Sie gilt jetzt fuer JEDES Element mit dem Attribut:
   die Farbe gehoert zur Rolle "Depot", nicht zur Bauform "Karte".

   Der Gewinn ist nicht Schmuck, sondern Zuordnung: wer aus dem Hub auf die
   Depot-Seite klickt, sieht dieselbe Farbe wieder, mit der die Karte dort
   markiert war. Die Seite sagt damit selbst, woher man kommt. */
[data-farbe="estate"] { --modulfarbe: var(--serie-immobilien); }
[data-farbe="depot"]  { --modulfarbe: var(--serie-depot); }
[data-farbe="inout"]  { --modulfarbe: var(--serie-plan); }

/* Diese Regel steht NACH der allgemeinen Pillen-Regel weiter oben und
   gewinnt deshalb -- sie ist zusaetzlich spezifischer (Attribut + Klasse).
   Schrift in --ink, nicht in der Modulfarbe: farbiger Text auf farbigem
   Grund ist zweimal dieselbe Aussage und schlechter lesbar als schwarz. */
/* THEMEN-PILLE — eine Regel, alle vier Vorkommen (26.08.2026, MWM).
   Traegt die Modulfarbe und sagt damit "das hier gehoert zu jenem Modul":
   im Hub auf den drei Karten, auf jeder Modulseite ueber dem Titel. Wer aus
   dem Hub auf eine Modulseite klickt, sieht dieselbe Farbe wieder.

   NICHT ueber [data-farbe] .brand-kicker geloest, sondern ueber eine eigene
   Klasse: auf einer Modulseite tragen auch die STRUKTUR-Kicker den Namen
   .brand-kicker -- MAIN DASHBOARD, ZUFLUSS, AUSBLICK, GELDFLUSS. Die sagen,
   welche Art von Block folgt, nicht zu welchem Modul er gehoert, und bleiben
   deshalb neutral. Eine Regel ueber den Ort haette sie mitgefaerbt.

   Der Modulfarbton bleibt erhalten, wird aber auf einen leicht getönten
   Träger gemischt: im weißen Hub-Modulkärtchen kühl, im warmen Kopf-Frame
   warm. Die Farbe bezeichnet das Modul, nicht eine zusätzliche Zone. */
.brand-kicker.thema {
  --pille-thema-tragflaeche: color-mix(in srgb, var(--zone-boden-b) 15%, var(--paper));
  background:color-mix(in srgb, var(--modulfarbe) 15%, var(--pille-thema-tragflaeche));
  /* Die leise Kante nutzt dieselben beiden Mischpartner wie die Fläche. */
  border:1px solid color-mix(in srgb, var(--modulfarbe) 32%, var(--pille-thema-tragflaeche));
  /* Schrift in --ink, nicht in der Modulfarbe: farbiger Text auf farbigem
     Grund ist zweimal dieselbe Aussage und schlechter lesbar als schwarz. */
  color: var(--ink);
  /* Maße stehen VOLLSTAENDIG hier (26.08.2026). Vorher erbte die Themen-Pille
     Groesse, Polster und Radius von der Struktur-Regel darueber und setzte nur
     Farbe und Schrift selbst. Als diese Regel per :not(.thema) getrennt wurde,
     waere die Themen-Pille ohne diese vier Zeilen auf die nackte
     .brand-kicker-Basis zurueckgefallen -- die in jedem Modul anders aussieht
     (estate: 700er Schrift auf Akzentfarbe, depot: 600er). Eine Pille, die
     "ueberall gleich" heissen soll, darf nicht davon abhaengen, welche
     Moduldatei zufaellig zuletzt geladen wurde. */
  font-weight: var(--fw-mittel);
  /* font-size: siehe Struktur-Pille oben -- die Grundform traegt das Mass. */
  letter-spacing: var(--ls-versal-eng);
  text-transform: none;
  padding: 3px 9px;
  border-radius: var(--r-pill);
  /* ZEILENHOEHE FEST (26.08.2026, MWM: "die Pille ist noch immer hoeher").
     Sie stand nicht hier -- also erbte die Pille sie vom Umfeld. In Estate
     traegt .section-heading eine andere Flex-Ausrichtung als in InOut und
     Depot, und damit eine andere geerbte line-height. Gleiche Schrift,
     gleiches Polster, andere Hoehe.
     Mit einem festen Wert ist die Hoehe schlicht Schriftgrad plus zweimal
     Polster -- unabhaengig davon, in welchem Kasten sie steht. Eine Pille,
     die "ueberall gleich" heissen soll, darf nicht davon abhaengen, was ihr
     Elternteil gerade fuer eine Zeile vorgibt.
     Ebenso vertical-align: die Grundlinie eines inline-block richtet sich
     sonst am Text daneben aus und schiebt die Pille nach oben. */
  line-height: var(--lh-eng);
  vertical-align: middle;
}

/* Themen-Pille auf dem warmen Kopf-Frame: gleicher Modulfarbton, aber
   der Träger kommt aus der warmen Zone statt aus dem kühlen Kartenumfeld. */
.master-frame > .section-heading .brand-kicker.thema,
.master-frame > .block-kopf .brand-kicker.thema {
  --pille-thema-tragflaeche: color-mix(in srgb, var(--zone-boden-a) 45%, var(--paper));
}


/* ─── Zweiter Balken: was bleibt (26.08.2026, MWM) ─────────────────────────
   Liegt unter dem Anteilsbalken, gleiche Staerke, rechtsbuendig -- er fasst
   die zwei rechten Segmente zusammen.

   justify-content: flex-end schiebt das eine Segment ans rechte Ende, ohne
   dass eine Position gerechnet werden muss. Die BREITE setzt das Skript,
   aus denselben Zahlen wie die Segmente darueber.

   Erste Fassung war eine geschweifte Klammer mit Zahl daneben. Sie hat drei
   Betraege in einem Blickfeld gestapelt (Leitzahl, Segmente, Klammer) und
   damit genau die Ruhe gekostet, die der Balken haben soll. Der Betrag steht
   jetzt im Tooltip. */
.balken-bleibt {
  margin-top: var(--sp-1);
  /* RECHTSBUENDIG (26.08.2026, MWM: "der gruene Balken ganz nach rechts
     buendig, jetzt ist er linksbuendig, das macht keinen Sinn").
     .anteil-balken ist ein Flex-Container ohne eigenes justify-content --
     also flex-start, also links. Das eine Segment darin muss dorthin, wo
     die Segmente liegen, die es zusammenfasst: ans rechte Ende. */
  justify-content: flex-end;
}

/* ═══════════════════════════════════════════════════════════════════════════
   ABSCHNITTSKOPF — EINE DEFINITION FUER ALLE SEITEN      (02.09.2026)
   ═══════════════════════════════════════════════════════════════════════════

   Vorher: vier Grunddefinitionen (Hub, Estate, Depot, InOut) plus drei
   identische Media-Query-Kopien plus zwei Ueberschreibungen in
   ui-icons.css. Zwoelf Regeln fuer eine Kopfzeile.

   Gemessen am 02.09.2026 auf der laufenden Demo -- so sah es AUS:

       Hub     flex-end · gap 18 · unten 20
       Estate  end      · gap 18 · unten 20      (end == flex-end)
       InOut   flex-end · gap 18 · unten 20
       Depot   center   · gap 12 · unten 12

   Drei Seiten also gleich, eine anders. Und "18px" und "20px" waren rohe
   Zahlen: es gibt keinen Token dazu, check-tokens prueft Abstaende nicht,
   also stand die Zahl da und niemand sah es.

   NEU: 16 statt 18 (--sp-4), 20 bleibt (--sp-5). Der Sprung von 18 auf 16
   ist zwei Pixel und liegt unter dem, was man sieht -- dafuer sitzt der
   Wert jetzt auf der Leiter. Depots engere Fassung bleibt, aber als
   BENANNTE Variante .section-heading.eng statt als vierte Kopie.

   Die Chevron-Variante steht weiter in shared/ui-icons.css: dort haengt sie
   an :has(> .frame-collapse-btn), also an der Frage "hat dieser Kopf einen
   Klapp-Knopf" -- und die gehoert zum Chevron, nicht zur Kopfzeile. */
.section-heading {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-4);
  /* 17.09.2026: war --sp-5 (20px), jetzt ein Verhaeltnis zur Schriftgroesse.
     Begruendung und Faktoren stehen bei --titel-abstand in shared/tokens.css. */
  margin-bottom: var(--titel-abstand);
}
/* ZWEI FAELLE, wie MWM sie benannt hat ("wenn nichts drunter steht und wenn
   was drunter steht"):
     ohne Unterzeile   voller Abstand, die Ueberschrift trennt allein
     mit Unterzeile    halber Abstand -- der Speicherstand steht schon
                       dazwischen und trennt mit; voll gesetzt waere die
                       Trennung doppelt und der Kopf lauter als sein Inhalt.
   .eng ist derselbe Fall unter anderem Namen: Depot benutzt die Variante
   dort, wo der Kopf ueber einer Tabelle sitzt und nicht ueber einem Diagramm. */
.section-heading.eng,
.section-heading:has(.tabellen-stand) { margin-bottom: var(--titel-abstand-eng); }
/* Die Ueberschrift zu ihrer EIGENEN Unterzeile: die beiden gehoeren zusammen. */
.section-heading > div > .tabellen-stand { margin-top: var(--titel-abstand-unterzeile); }
/* Der Titel in der Kopfzeile. Stand viermal einzeln: Hub, Estate und InOut
   Zeichen fuer Zeichen gleich, Depot mit zwei eigenen Werten (09.09.2026).
   Die drei gleichen werden hier zur einen Fassung; Depots Abweichung bleibt
   als benannter Ueberschreiber in depot/css/styles.css stehen -- sie zu
   vereinheitlichen waere ein Sichtwechsel, kein Aufraeumen. */
/* 17.09.2026: Groesse auf --fs-abschnittstitel (22 px). Die Begruendung und
   die Regel stehen bei der Tokendefinition in shared/tokens.css; hier steht
   nur, dass diese eine Stelle sie fuer ALLE Modul-, Analyse- und Steuerseiten
   traegt -- es gibt seit 09.09. keine zweite Fassung mehr.
   Der Rohwert `margin: 7px` bleibt vorerst stehen: er ist ein eigener,
   aelterer Befund (Abstand neben der Skala) und haette hier nichts gesucht. */
.section-heading h2 {
  font-family: var(--font-body);
  font-size: var(--fs-abschnittstitel);
  margin: 7px 0 0;
  font-weight: var(--fw-fett);
  letter-spacing: var(--ls-titel);
}

/* ═══ ZWEI EBENEN, EIN GRAD, ZWEI GEWICHTE — DIE REGEL  (17.09.2026) ═══════
   MWM: "die Headlines in den beigen 22 in fett und unten drunter in Blau in
   regulaerer Schrift. Versucht daraus eine Regel zu stellen, dass das klar
   ist und dass das bei allen Seiten auch so angelegt wird."

   DIE REGEL, an der BAUFORM festgemacht, nicht an der Seite:

     beiger Traeger   .master-frame > .section-heading h2   22 px  fett   700
                      .block-kopf h2                        22 px  fett   700
     blauer Block     .canvas-frame > .section-heading h2   22 px  halbfett 600

   Der Traeger ist der Rahmen, der die Seite haelt; die Bloecke darunter sind
   seine Abschnitte. Beide Ueberschriften sind gleich GROSS, weil beide
   Ueberschriften sind. Verschieden SCHWER, weil die eine ueber der anderen
   steht. Zwei Unterschiede fuer eine Rangfolge waeren einer zu viel -- dann
   liest man die untere als andere Gattung statt als Unterpunkt.

   GEMESSEN vor dem Umbau, 37 Ueberschriften auf 11 Seiten: 7 im Traeger,
   29 im Block, 1 im Modulraster des Hub (eigene Zeile in css/styles.css).
   Das Gewicht war vorher UEBERALL fett -- die Rangfolge existierte nicht,
   ausser auf dem Hub, wo sie verkehrt herum stand.

   WARUM `>` UND NICHT NUR NACHFAHRE: auf den Analyseseiten liegen die
   Bloecke im Traeger. Mit einem Nachfahrenselektor wuerde die Traegerregel
   auch die Blockueberschriften greifen; welche der beiden gewinnt, haengt
   dann an der Reihenfolge im Stylesheet statt an einer Entscheidung.
   Gemessen: .section-heading ist in 36 von 37 Faellen direktes Kind ihres
   Rahmens; der 37. ist das Hub-Modulraster.

   Durchgesetzt von tools/check-rollen.mjs, Pruefung 4. */
.master-frame > .section-heading h2 { font-weight: var(--fw-fett); }
.canvas-frame > .section-heading h2 { font-weight: var(--fw-halbfett); }
/* ═══ DIAGRAMMTITEL — EINE FASSUNG (09.09.2026, #150) ═══════════════════════
   Die Zeile ueber einem Diagramm, die sagt, was es zeigt: "Schulden vs. Wert
   — Ist", "Gesamtvermoegen Depot + Cash, fortgeschrieben", "Nur die Sparrate,
   ohne heutigen Bestand".

   Stand in zwei Fassungen, die verschieden aussahen:
     depot/css/styles.css      --ink,   --fw-fett,  margin 0 0 --sp-3
     estate/css/calculator.css --muted, kein Gewicht, margin-bottom --sp-5

   ZUERST FALSCH EINGEORDNET. Beim Vergleich der beiden CSS-Bloecke sah es
   nach ZWEI Rollen aus -- dunkle Unterueberschrift gegen graue
   Bildunterschrift -- und der Fall wurde als begruendete Ausnahme
   eingetragen. Das Nachsehen im Markup ergab das Gegenteil: alle fuenf
   Verwendungen stehen UEBER einem Diagramm und benennen es. Eine Rolle,
   zwei Anstriche. Ein CSS-Block sagt eben nicht, wofuer er da ist.

   --ink und Gewicht gewinnen, weil es ein TITEL ist: bei --fs-kicker (11 px)
   verschwindet eine graue Zeile ueber einer Grafik, und was man suchen muss,
   findet man nicht. Der engere Abstand (--sp-3) gewinnt aus demselben Grund
   -- der Titel gehoert zu seinem Diagramm, nicht in die Mitte dazwischen.

   SICHTWECHSEL: die drei Estate-Titel werden dunkler, fetter und ruecken
   naeher an ihr Diagramm. */
.chart-sub {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-kicker);
  font-weight: var(--fw-fett);
  color: var(--ink);
}
.chart-sub.left { text-align: left; }

/* Dichtere Fassung: Kopfzeilen ueber Tabellen, wo die Zeile darunter eng
   folgt. Bisher nur in Depot, dort ohne Namen. */
.section-heading.eng {
  align-items: center;
  gap: var(--sp-3);
  /* margin-bottom stand hier als --sp-3 (12px) und weiter oben, seit
     17.09.2026, als --titel-abstand-eng (≈11px). ZWEI Werte fuer denselben
     Abstand, und die spaetere Regel gewann -- die Verhaeltnisregel lief ins
     Leere, ohne dass irgendwo etwas rot wurde. Genau die Sorte Fund, die
     dieses Stylesheet seit Wochen einsammelt.
     Der Abstand steht jetzt nur noch oben bei .section-heading.eng. */
  flex-wrap: wrap;
}
/* Unter 800 px kippt der Kopf in eine Spalte -- stand dreimal identisch in
   estate, depot und inout. Der Hub hatte die Regel nie, obwohl er dieselbe
   Kopfzeile benutzt: eine Auslassung, die niemandem auffiel, weil man den
   Hub selten schmal ansieht. Jetzt gilt sie ueberall. */
@media (max-width: 800px) {
  .section-heading { flex-direction: column; align-items: stretch; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   RAHMEN — EINE DEFINITION, ZWEI BENANNTE VARIANTEN      (02.09.2026)
   ═══════════════════════════════════════════════════════════════════════════

   Vorher sieben Regeln in fuenf Dateien: zwei Grundformen (Hub und Estate,
   wortgleich) und fuenf Ueberschreibungen, drei davon in Estate ueber
   Seiten-IDs und -Klassen.

   Gemessen am 02.09.2026 auf der laufenden Demo -- so sah es AUS:

       weiss + Schatten   Hub-Nebenseiten, estate/analyse        ~37×
       kuehl, kein Sch.   depot, inout, estate/index + project   ~13×
       warm,  kein Sch.   estate/portfolio                         1×

   Es sind also DREI Erscheinungen, nicht sieben Regeln. Zwei davon standen
   nirgends als Variante, sondern entstanden dadurch, dass ein Modul die
   Grundform pauschal ueberschrieb -- was auch die Analyse-Seiten desselben
   Moduls miterwischte, ohne dass das jemand entschieden haette.

   Jetzt: Grundform ist die Karte. Wer eine Zonenflaeche will, sagt es. */
.canvas-frame {
  background: var(--innenkarte-flaeche);
  box-shadow: var(--shadow);
  border: 1px solid var(--zone-linie-b);
  border-radius: var(--r-gross);
  padding: var(--sp-4) var(--sp-5) var(--sp-5);
  margin-bottom: var(--frame-gap);
}
/* Kuehle Zonenflaeche: der Rahmen ist hier kein Objekt auf dem Tisch,
   sondern der Tisch selbst. Deshalb kein Schatten -- ein Schatten unter
   einer Flaeche, die nichts traegt, behauptet eine Ebene, die es nicht gibt. */
/* Nur die ZONE: ein .canvas-frame ohne .zone ist ein weisser Block, und
   dort traegt die Linie die Abgrenzung gegen den Zonenboden -- sie bleibt.
   Die Kante selbst faellt oben in "KEINE KANTE AUF EINER FARBIGEN FLAECHE".
   Hier stand bis zum 17.09.2026 "border-color: var(--zone-boden-b)" --
   Linie in Flaechenfarbe, also eine unsichtbare Linie mit Platzbedarf. Das
   war die Zwischenstufe auf dem Weg zu dieser Regel und ist jetzt durch sie
   ersetzt: weg statt getarnt. */
.canvas-frame.zone       { background: var(--zone-boden-b); box-shadow: none; }

/* .zone-warm ENTFAELLT (03.09.2026). Sie hatte genau ein Vorkommen --
   estate/portfolio.html -- und war dort die falsche Bauform: die Huelle
   jener Seite ist funktional eine ZONE, nicht ein Rahmen mit Zonenfarbe.
   Der Kommentar in estate/css/styles.css sagte das seit dem 01.08.2026
   wortwoertlich ("dieselbe Rolle wie das master-frame auf index.html"),
   nur stand es nicht im Markup. Seit heute traegt die Seite .master-frame,
   und die Variante hat keinen Nutzer mehr.

   GELOESCHT STATT VORGEHALTEN: eine Variante ohne Vorkommen ist eine
   Einladung, sie beim naechsten Mal wieder zu benutzen -- und dann steht
   dieselbe Frage wieder da. Wer eine warme Zone braucht, nimmt die Zone. */

/* ═══════════════════════════════════════════════════════════════════════════
   ZONE (master-frame) — EINE DEFINITION                  (02.09.2026)
   ═══════════════════════════════════════════════════════════════════════════

   Vorher vier Definitionen in vier Dateien plus die Randregel hier oben.
   Sie unterschieden sich in zwei Dingen und stimmten in drei ueberein:

       gleich       Flaeche --zone-boden-a, Radius --r-gross, --zone-polster
       verschieden  margin-bottom (frame-gap-lg im Hub und Estate,
                    frame-gap in Depot und InOut)
       verschieden  Estate ist ein <a> und braucht Linkverhalten

   Und die Modulfarbe hiess zweimal anders fuer denselben Wert:
   --accent-dark im Hub, --gold-dark in Depot und InOut -- beides #7a5949.
   Estate setzte sie gar nicht. Der Name --gold-dark faellt weg.

   Der groessere Abstand unten (frame-gap-lg) gilt jetzt ueberall: die Zone
   ist der schwerste Block der Seite, und der Unterschied von 12 zu 16 px
   war keine Entscheidung, sondern eine Kopie, die zweimal anders ausfiel. */
.master-frame {
  --accent: var(--accent-dark);
  --accent-rgb: var(--accent-dark-rgb);
  background: var(--zone-boden-a);
  /* Keine Kante -- siehe "KEINE KANTE AUF EINER FARBIGEN FLAECHE" weiter
     oben. Hier stand --zone-linie-a; heute frueh wurde sie auf
     --zone-boden-a gesetzt (Linie in Flaechenfarbe), jetzt faellt sie ganz. */
  border-radius: var(--r-gross);
  padding: var(--zone-polster);
  margin-bottom: var(--frame-gap-lg);
}

/* ─── Variante: gestapelte Zone (03.09.2026) ────────────────────────────────
   MWM: "da sind zum Beispiel Kacheln mit den Werten heute direkt unter dem
   Nettovermoegenswert" -- auf entwicklung.html klebte .hero-value am
   .portfolio-strip darunter.

   DIE URSACHE STAND SCHON IM CODE, ALS BEGRUENDUNG. Bei .hero-value in
   css/styles.css:

       "KEIN margin-bottom mehr (25.08.2026). [...] Ein Abstand, der an einem
        Element klebt statt an seiner Umgebung, ueberlebt jeden Umbau und
        niemand sucht ihn dort."

   Das ist richtig -- und deshalb muss die Umgebung ihn liefern. Auf dem Hub
   tut sie das: dort sitzt .hero-value im .panel-raster, und das Raster hat
   eine Luecke. Auf entwicklung.html sitzt derselbe Baustein DIREKT in der
   Zone, wo es kein Raster gibt. Ein Baustein ausserhalb des Kontexts, fuer
   den er gebaut wurde.

   Statt .hero-value den Abstand zurueckzugeben (und damit den Hub wieder
   kaputtzumachen) bekommt die ZONE eine benannte Variante: ihre direkten
   Kinder stehen gestapelt und halten Abstand. Das ist dieselbe Grammatik wie
   .canvas-frame.zone -- eine Bauform, benannte Abweichungen.

   `> * + *` und nicht `> *`: der Abstand gehoert ZWISCHEN zwei Dinge, nicht
   an jedes. Das erste Kind bekommt keinen, das letzte auch nicht -- sonst
   summiert er sich mit dem Polster der Zone, und genau daraus entstanden die
   54 px vom 25.08.2026. */
.master-frame.gestapelt > * + * { margin-top: var(--sp-5); }
/* Und das letzte Kind gibt seinen eigenen Abstand ab. Sonst addiert er sich
   zum Polster der Zone -- .portfolio-strip traegt 24 px, die Zone 24, macht
   48 px Luft unter dem Inhalt, die niemand gesetzt hat. Genau die Rechnung,
   aus der am 25.08.2026 die 54 px unter .hero-value entstanden sind. */
.master-frame.gestapelt > :last-child { margin-bottom: 0; }

/* Estate macht die Zone anklickbar -- sie fuehrt auf die Portfolio-Seite.
   Vorher standen color/cursor/position/text-decoration dafuer ohne
   Kennzeichnung in estate/css/styles.css, als waeren es Eigenschaften der
   Zone. Sie sind Eigenschaften des LINKS. */
a.master-frame {
  display: block;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  position: relative;
}

/* ═══ SEITENHUELLE UND KOPFZEILEN-AKTIONEN (09.09.2026, DRY-Audit) ═══════════
   .dashboard-shell stand in vier Modul-Stylesheets, dreimal identisch, im
   Hub mit position:relative dazu. .canvas-actions stand in dreien -- Depot
   mit align-items:center und flex-wrap, Estate und InOut ohne. Uebernommen
   ist jeweils die vollstaendigste Fassung: position:relative schadet
   niemandem, und Aktionen in einer Kopfzeile sollen mittig sitzen und bei
   Platzmangel umbrechen statt ueberzulaufen.

   Der schmale Umbruch (520 px) hatte zwei Werte: Hub var(--sp-4), die
   Module 30px. Der Token gewinnt -- 30 kennt die Abstandsleiter nicht. */
.dashboard-shell {
  max-width: 1380px; margin: auto;
  padding: var(--sp-4) var(--sp-6) 80px; position: relative;
}
@media (max-width: 520px) { .dashboard-shell { padding: var(--sp-4) var(--sp-4) 60px; } }
.canvas-actions { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }

/* ═══ DIE KPI-LEISTE — EINE DEFINITION (09.09.2026, DRY-Audit #4) ═══════════
   .portfolio-strip stand in VIER Modul-Stylesheets (css/, estate/, depot/,
   inout/), und in dreien davon ZWEIMAL: eine Grunddefinition plus ein
   spaeterer Nachtrag "align-items: start" -- drei byteidentische Bloecke.
   Der Nachtrag trug in allen dreien denselben Kommentar:

     "Portiert aus dem Hub-Stylesheet (Korrektur 26.07.2026, dort schon
      gefixt, hier nachgezogen -- der Fehler war also nie behoben, nur an
      einer von vier Stellen)."

   Das Problem war erkannt und dann durch Kopieren geloest. Die naechste
   Korrektur (04.09.: Label-zu-Zahl-Abstand, align-items flex-end statt
   center, Reserve ueber --label-zeile statt 34px) ist wieder nur im Hub
   passiert -- die drei Module standen bis heute auf dem Stand vom 26.07.
   Deshalb stand auch --fs-kpi-klein viermal roh im CSS.

   Jetzt EINE Definition, hier, im Stylesheet, das jede Modulseite laedt.
   Uebernommen ist die Hub-Fassung mit ihrer Messung vom 04.09.2026:

     Polster oben bis Labeltext   24 px
     Labeltext bis Zahl           11 px   (vorher 17 -- der Ausreisser)
     Zahl bis Unterzeile           5 px

   min-height = 2 Labelzeilen bleibt: sie haelt die Zahlen ALLER Kacheln
   einer Leiste auf einer Linie, auch wenn ein Label umbricht. Gemessen
   bei 820 px mit "Nettovermoegen in 20 Jahren": Versatz 0 statt 19 px.
   align-items flex-end schiebt den Totraum UEBER das Label -- das Label
   gehoert zu seiner Zahl, nicht zum Kachelrand.

   Modulabweichungen (.strip-drei fuer drei Kacheln) stehen darunter.
   Was ein Modul sonst braucht, schreibt es als EIGENE Regel -- nicht als
   Kopie dieser. */
.portfolio-strip {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-3); margin-bottom: var(--sp-6); align-items: start;
}
.portfolio-strip.strip-drei { grid-template-columns: repeat(3, 1fr); }
.portfolio-strip > div {
  background: var(--innenkarte-flaeche); border: 1px solid var(--karten-linie);
  border-radius: var(--r-gross); padding: var(--sp-3) var(--sp-4); box-shadow: var(--shadow);
  display: flex; flex-direction: column;
}
.portfolio-strip > div > span {
  display: flex; align-items: flex-end; gap: 5px; flex-wrap: nowrap;
  min-height: calc(2 * var(--label-zeile));   /* Reserve fuer den Umbruch */
  line-height: var(--label-zeile);
  color: var(--muted); font-size: var(--fs-fuss); text-transform: uppercase;
  letter-spacing: var(--ls-versal); font-weight: var(--fw-fett); margin-bottom: var(--sp-2);
}
.portfolio-strip > div > span .info-hint { display: inline-flex; flex-shrink: 0; }
.portfolio-strip strong {
  font-size: var(--fs-kpi-klein); font-weight: var(--fw-fett); letter-spacing: var(--ls-titel);
  font-variant-numeric: tabular-nums; line-height: 1.2;
}
.portfolio-strip small {
  display: block; color: var(--muted); font-size: var(--fs-pille);
  margin-top: var(--sp-1); font-weight: var(--fw-halbfett);
}
/* Umbruch der Leiste: vier Kacheln, zwei, eine. Stand ebenfalls in vier
   Dateien mit denselben Schwellen -- check-drift hat es nach dem ersten
   Umzug gemeldet. In inout/css/styles.css fehlte dabei die oeffnende
   @media-Klammer (Zeile 566): die Zweispaltigkeit galt dort bei jeder
   Breite. Das ist am 09.09.2026 NICHT still korrigiert worden, weil es
   inzwischen wie Absicht aussehen kann -- offen, MWM entscheidet. */
@media (max-width: 800px) { .portfolio-strip { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .portfolio-strip { grid-template-columns: 1fr; } }

/* ═══ DIE ERGEBNIS-KACHEL (04.09.2026) ═════════════════════════════════════
   In einer KPI-Leiste steht meist eine Zahl, die aus den anderen FOLGT --
   die Summe, die Differenz, das Ergebnis. Sie ist dunkel abgesetzt, damit
   das Auge sie zuerst findet.

   VORHER hing das an `.portfolio-strip > div:last-child` in css/styles.css.
   Zwei Fehler in einer Zeile: die Farbe stand fuer eine POSITION statt fuer
   eine Sache -- auf steuer.html traf sie den Grenzsteuersatz, eine Annahme,
   waehrend das Ergebnis "Das spart" weiss danebenstand. Und sie stand in
   einer Datei, die nur die Wurzelseiten laden, also blieben die vier
   Modulseiten grau, ohne dass das jemand entschieden haette.

   REGEL: hoechstens EINE Ergebnis-Kachel je Leiste. Zwei betonte Zahlen
   betonen nichts. Steht das Ergebnis schon als .hero-value ueber der Leiste
   -- wie auf entwicklung.html --, bekommt die Leiste gar keine: sie ist dann
   die Aufschluesselung, nicht die Antwort.

   Die Textfarben muessen mit umgestellt werden, sonst verschwindet dunkler
   Text auf dunklem Grund. Deshalb sind es vier Regeln und nicht eine.

   Geprueft von tools/check-tokens.mjs Punkt 11. */
.portfolio-strip > .kachel-ergebnis {
  background: var(--kpi-flaeche);
  border-color: transparent;
}
.portfolio-strip > .kachel-ergebnis > span   { color: var(--flaeche-invers-text-muted); }
.portfolio-strip > .kachel-ergebnis strong   { color: var(--flaeche-invers-text); }
.portfolio-strip > .kachel-ergebnis small    { color: var(--flaeche-invers-text-muted); }

/* ═══ DIE KARTEN AUF estate/portfolio.html SIND METRICS-RAHMEN (04.09.2026) ══
   MWM: "die seite sieht noch immer anders aus … da werden auch andere Farben
   verwendet. mess das auch."

   Gemessen am Demo-Stand, dieselbe Ebene auf zwei Metrics-Seiten:

                   entwicklung.html          estate/portfolio.html
     Bauform       .canvas-frame             .card (<details>)
     Flaeche       #ffffff  --paper          #fbfbfa  --frame-innen
     Rand          #c2d1de  --zone-linie-b   #e2dbcf  --karten-linie
     Ecke          18px     --r-gross        14px     --r-gross

   Drei Unterschiede, keiner davon entschieden: .card ist die allgemeine
   Karten-Bauform aus estate/css/calculator.css. Sie war nie als Rahmen einer
   Metrics-Seite gemeint — die Seite hat sie nur benutzt, weil sie vor den
   Bauformen gebaut wurde.

   ENG GEFASST auf `.results.card-grid > [data-card-id]`: nur die ziehbaren
   Karten dieser einen Leiste. `.card` traegt anderswo in Estate weiter ihr
   eigenes Aussehen — sie ist dort auch keine Aussage, sondern eine Karte.

   Radius, Flaeche und Rand kommen aus denselben Rollen wie beim
   .canvas-frame. Steht der eine Wert eines Tages anders, steht der andere
   mit. Geprueft von tools/check-seitenklassen.mjs. */
.results.card-grid > [data-card-id] {
  background: var(--innenkarte-flaeche);
  border-color: var(--zone-linie-b);
  border-radius: var(--r-gross);
}


/* ═══════════════════════════════════════════════════════════════════════════
   BAUFORMEN DER METRICS-SEITEN (hierher verschoben am 16.09.2026)
   ═══════════════════════════════════════════════════════════════════════════
   Sie standen bis heute in css/styles.css, weil entwicklung.html die erste
   Seite war, die sie brauchte. Mit inout/analyse.html, depot/analyse.html
   und estate/portfolio.html sind es vier -- und drei davon laden css/
   styles.css nicht. Eine Bauform, die auf vier Seiten gilt, gehoert in die
   geteilte Datei; alles andere endet als vierfach gepflegte Kopie.
   ═══════════════════════════════════════════════════════════════════════════ */

.split-zeile { display: flex; align-items: stretch; gap: 0; margin-top: var(--sp-6); }
.split-bar {
  /* h-1.5 gap-1 = 6px hoch, 4px zwischen den Segmenten (entwurf-6.tsx).
     Keine graue Schiene mehr darunter: im Vorbild tragen die Segmente
     selbst die Form, der Rest ist Papier. Eine Schiene wuerde behaupten,
     es gaebe einen Sollwert, den die Segmente noch nicht ausfuellen. */
  display: flex; height: var(--balken-hoehe); gap: var(--sp-1); overflow: visible;
  flex: 1; min-width: 0;
}

.zufluss-streifen {
  /* Steht in derselben Zeile wie .split-bar und muss dieselbe Hoehe haben --
     sonst sieht die Luecke zwischen beiden aus wie ein Fehler statt wie
     eine Aussage. Vorher 6px getippt, also die dritte Stelle mit
     derselben Zahl ohne Zusammenhang. */
  height: var(--balken-hoehe); flex-shrink: 0; margin-left: var(--sp-1);
  border-radius: var(--r-pill);
  cursor: help;
  transition: filter .15s;
}
.zufluss-streifen:hover, .zufluss-streifen:focus-visible {
  filter: brightness(1.15);
  outline: none;
}
.split-bar div { transition: width .3s; border-radius: var(--r-pill); }
/* ─── Legende als Tabelle statt Fließzeile (25.08.2026) ──────────────────
   Vorher standen die drei Anteile nebeneinander in einer Zeile:
   "Immobilien 780.611 € · 87,5 %  Depot 58.758 € · 6,6 %  Cash …"

   Drei Beträge, die man vergleichen soll, aber nicht untereinander stehen.
   Man muss sie im Text erst suchen, bevor man sie gegeneinander halten
   kann. Dieselbe Regel wie in allen Tabellen des Projekts: Bezeichnung
   links, Zahl rechtsbündig, Zahlen in einer Spalte.

   grid statt flex, drei Spalten: Name (wächst), Betrag, Anteil. */
.split-legend {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: var(--sp-innen) var(--sp-4);
  /* --sp-2, nicht --sp-4 (09.09.2026, #130): 800 Zeilen weiter unten stand
     ein zweites `.split-legend { margin-top: var(--sp-2) }`. Der kleinere
     Abstand war also immer der sichtbare. Zusammengefuehrt, damit hier
     steht, was auch gilt. */
  margin-top: var(--sp-2);
  font-size: var(--fs-fuss);
  color: var(--muted);
}
.split-legend > span {
  display: contents;               /* die drei Kinder rutschen ins Raster */
}
/* Haarlinie unter jeder Zeile ausser der letzten (25.08.2026).
   Drei Angaben ohne Trennung lesen sich als ein Block; mit Linie sind es
   drei Zeilen, die man einzeln erfassen kann. Sie liegt an den Zellen und
   nicht am umschliessenden <span> — das traegt display:contents und hat
   deshalb selbst keine Kante, an der eine Linie haengen koennte. */
.split-legend > span > * { padding-bottom: 7px; border-bottom: 1px solid var(--line-soft); }
.split-legend > span:last-child > * { border-bottom: 0; padding-bottom: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   METRICS-UMBAU 16.09.2026 — sechs Bloecke statt acht Sektionen
   ═══════════════════════════════════════════════════════════════════════════
   Alle Klassen unten sind am selben Tag in design/contract.json eingetragen.
   Das war zweimal der Fehler (.row-delete-btn, .module-card h3): CSS
   geaendert, Contract nicht nachgezogen, Gate meldet es Tage spaeter.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   DIE WEISSE KARTE — GRUNDDEFINITION  (16.09.2026, Fund beim Estate-Umbau)
   ═══════════════════════════════════════════════════════════════════════════
   .chart-block-full ist die Karte, auf der die gesamte Zonenboden-Regel
   steht: "Zonenboden nur mit weisser Zwischenebene" (design/contract.json,
   seitenklassen.metrics). Die Regel wurde am 16.09. genau deshalb
   praezisiert statt gestrichen.

   GEMESSEN AM SELBEN TAG: die Klasse hatte ueberhaupt keine Grunddefinition.
   Sie stand zweimal MODUL-LOKAL -- in depot/css/styles.css und in
   inout/css/styles.css, mit leicht verschiedenen Werten (die eine mit
   Rahmen und margin-bottom, die andere ohne Rahmen und mit margin-top).

   Die Folge: entwicklung.html benutzt die Klasse sechsmal und laedt KEINE
   der beiden Dateien. Dort war die "weisse Zwischenebene" also nur ein
   Klassenname im Markup -- der Inhalt lag direkt auf dem blauen Boden,
   genau der Zustand, den MWM am 03.09. bemaengelt hatte ("da geht das ein
   bisschen unter"). check-seitenklassen konnte das nicht sehen: das Gate
   liest Markup, nicht berechneten Stil.

   Eine Regel, die ihr eigenes Gate besteht und trotzdem nicht gilt, ist
   schlimmer als eine Regel, die fehlt.

   WARUM DIE ZWEI KOPIEN TROTZDEM STEHENBLEIBEN: sie laden NACH dieser
   Datei und gewinnen damit auf ihren eigenen Seiten weiter. Diese Regel
   hier aendert deshalb nichts an Depot und InOut -- sie ergaenzt nur die
   Seiten, die bisher leer ausgingen. Das Zusammenfuehren der drei Fassungen
   verschiebt Pixel auf vier Seiten und ist eine eigene Aufgabe.

   Die Werte sind die der Depot-Fassung: mit Rahmen. Eine Karte auf einem
   farbigen Boden braucht ihre Kante, sonst franst sie aus. */
.chart-block-full {
  background: var(--innenkarte-flaeche);
  border: 1px solid var(--karten-linie);
  border-radius: var(--r-gross);
  padding: var(--sp-3) var(--sp-4);
  box-shadow: var(--shadow);
  margin-bottom: var(--sp-5);
}

/* ─── Zwei gleichrangige Rahmen nebeneinander ──────────────────────────────
   Die Dichte, die am Cockpit-Entwurf gefiel, entsteht durch PAARE — nicht
   durch kleinere Schrift. .panel-raster (minmax(0,1fr) 260px) taugt dafuer
   nicht: es ist absichtlich asymmetrisch, Hauptspalte plus Randspalte.
   Hier sind beide Seiten gleich wichtig.

   Der Umbruchpunkt ist 980px und nicht 800px wie sonst: zwei Rahmen mit je
   einer Grafik brauchen mehr Platz als zwei Textspalten. Darunter
   untereinander — dann ist es eine lange Seite, aber eine lesbare. */
.frame-paar {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--frame-gap);
}
.frame-paar > .canvas-frame { margin-bottom: 0; }
.frame-paar + .frame-paar,
.frame-paar { margin-bottom: var(--frame-gap); }
@media (max-width: 980px) {
  .frame-paar { grid-template-columns: 1fr; }
}

/* ─── Leitzeile: drei Zahlen statt einer grossen ───────────────────────────
   Eine Zeitachse hat ZWEI Enden. Die Leitzahl nur an einem zu zeigen
   unterschlaegt die Aussage der Grafik — "890.069 €" allein beantwortet
   nicht, wohin es laeuft. Deshalb hier heute → in N Jahren → Rendite,
   in absteigendem Gewicht.

   Bewusst KEIN .hero-value: dessen strong ist eine einzelne Leitzahl mit
   Balken darunter. Drei Werte in dieser Form waeren drei Leitzahlen. */
/* ═══ WARUM GRID UND NICHT FLEX MIT BASELINE (16.09.2026) ═══════════════
   MWM: "die 1,5 Millionen stehen in der falschen Zeile … das ist
   gestalterisch gruselig."

   Hier stand `display:flex; align-items:baseline`. Das klingt richtig und
   ist es nicht: Die Grundlinie eines BLOCKS ist die seiner ERSTEN
   Textzeile — und das war hier die kleine graue Marke, nicht die Zahl
   darunter. Flex hat also sauber ausgerichtet, nur die falschen Zeilen:
   die MARKEN standen auf einer Linie, und weil die Zahlen darunter
   verschieden gross sind (3 rem gegen 1,5 rem), rutschten sie
   auseinander.

   Mit `align-items:flex-end` waere es nicht besser geworden: dann fluchten
   die Unterkanten der Kaesten, und der Abstand Unterkante-zu-Grundlinie
   haengt wieder an der Schriftgroesse.

   Zwei GRID-REIHEN loesen es an der Wurzel: Reihe 1 nimmt alle Marken,
   Reihe 2 alle Zahlen. Beide fluchten, unabhaengig davon, wie gross die
   Zahl ist oder ob eine Marke umbricht. `display:contents` auf den Paaren
   laesst ihre Kinder direkt ins Raster durch.

   Die dritte Spalte ist 1fr, damit die rechte Gruppe an den Rand rueckt —
   ohne margin-left:auto, das im Grid nur das Element und nicht die Spalte
   bewegt. */
.leitzeile {
  display: grid;
  grid-template-columns: max-content max-content 1fr;
  grid-template-rows: auto auto;
  grid-auto-flow: column;
  align-items: end;
  column-gap: var(--sp-6);
  row-gap: var(--sp-1);
  margin-bottom: var(--sp-4);
}
.leitzeile > .leit-paar { display: contents; }

/* ═══ DIE ZAHLEN STEHEN AUF EINER GRUNDLINIE, NICHT AUF EINER UNTERKANTE ═══
   (16.09.2026, MWM: "1.595.020 € steht nicht unten bündig mit 890.069 €")

   Der Rahmen traegt `align-items: end` -- das stellt die KAESTEN unten
   buendig, nicht die Schrift. Und ein Kasten ist nicht die Zeile:

     890.069 €   39 px Schrift, line-height 1 → Grundlinie ~8 px ueber der Unterkante
     1.595.020 € 19,5 px Schrift, line-height 1 → Grundlinie ~3,9 px darueber

   Unterkanten buendig heisst also: die kleinere Zahl sitzt gut vier Pixel zu
   TIEF. Genau der Versatz, den man auf dem Bild sieht.

   `align-self: baseline` nur auf den ZAHLEN: im Raster bilden Elemente einer
   Zeile mit Grundlinien-Ausrichtung eine gemeinsame Gruppe. Die Marken
   darueber behalten `end` vom Rahmen -- sie sind alle gleich gross, dort ist
   Unterkante und Grundlinie dasselbe, und ein zweites Verhalten fuer nichts
   waere eine Regel ohne Anlass. */
.leitzeile > .leit-paar > .leit-jetzt,
.leitzeile > .leit-paar > .leit-dann { align-self: baseline; }
.leitzeile > .leit-rechts > * { justify-self: end; text-align: right; }
@media (max-width: 700px) {
  /* Untereinander: drei Paare, jedes zweizeilig. grid-auto-flow zurueck auf
     row, damit Marke und Zahl beieinander bleiben. */
  .leitzeile { grid-template-columns: 1fr; grid-template-rows: none; grid-auto-flow: row; }
  .leitzeile > .leit-paar { display: block; }
  .leitzeile > .leit-rechts > * { justify-self: start; text-align: left; }
}
.leitzeile .leit-marke {
  display: block;
  font-size: var(--fs-fuss);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: var(--ls-versal);
  font-weight: var(--fw-fett);
  margin-bottom: var(--sp-1);
}
/* ═══ DIESELBE TYPOGRAFIE WIE DIE LEITZAHL AUF DEM HUB  (16.09.2026) ═══════
   MWM: "die 890.069 € ist zu groß gesetzt oder zumindest in fett geschrieben
   — übernimm die Typo und Größe vom Main."

   NACHGEMESSEN, beide Stellen nebeneinander:

     Groesse   Hub --fs-leitzahl-hub   Metrics --fs-leitzahl-hub   gleich
     Gewicht   Hub --fw-halbfett 600   Metrics --fw-fett 700       ABWEICHUNG
     Laufweite Hub --ls-titel          Metrics --ls-titel          gleich

   Die Groesse war also nie das Problem -- 700 gegen 600 laesst dieselbe
   Zahl groesser WIRKEN. Und in css/styles.css steht bei der Hub-Regel seit
   jeher der Zusatz "font-semibold, NICHT 700". Die Absicht war da, sie stand
   nur nicht in der Metrics-Fassung; ich hatte sie beim Anlegen der Bauform
   am 16.09. auf --fw-fett gesetzt, weil "Leitzahl" nach fett klang.

   Eine Zahl derselben Rolle darf nicht auf zwei Seiten zwei Gewichte haben. */
.leitzeile .leit-jetzt {
  display: block;
  font-size: var(--fs-leitzahl-hub);
  line-height: var(--lh-zahl);
  font-weight: var(--fw-halbfett);
  letter-spacing: var(--ls-titel);
  font-variant-numeric: tabular-nums;
}
.leitzeile .leit-dann {
  display: block;
  font-size: var(--fs-kpi);
  line-height: var(--lh-zahl);
  font-weight: var(--fw-halbfett);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* .leit-pfeil ist am 16.09.2026 entfallen — ein viertes Element in einer
   Zeile aus Paaren. Die Richtung sagen die Marken selbst.
   .leit-rechts steht jetzt oben bei der Grid-Definition. */

/* ─── Stellschrauben in der Kopfzone ───────────────────────────────────────
   Sie stehen jetzt unter der Kurve, deren rechte Haelfte sie bewegen.
   Die Linie darueber trennt Ergebnis von Eingabe, ohne einen zweiten
   Rahmen zu oeffnen: ein .canvas-frame IM .chart-block-full waere ein
   Rahmen im Rahmen. */
.stellschrauben {
  border-top: 1px solid var(--line-soft);
  padding-top: var(--sp-4);
  margin-top: var(--sp-4);
}
.stellschrauben-kopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}

/* ─── Zielband unter einer Kennzahl ────────────────────────────────────────
   Fuer Kennzahlen, die ein anerkanntes SOLL haben — bisher genau eine: die
   Notreserve (Standard 3–6 Monatsausgaben, 6–12 bei unsicherem Einkommen).

   Ohne das Band ist "14,6 Monate" eine Zahl ohne Massstab, und der Leser
   muss raten, ob das gut ist. Die gruene Zone ist das Zielband, der
   schwarze Strich der Ist-Wert. Liegt er rechts davon, ist das ein Haken
   und kein Alarm — genau die Lesart, die der alte Name "Reichweite ohne
   Einnahmen" verhindert hat. */
.zielband {
  position: relative;
  height: var(--balken-hoehe);
  border-radius: var(--r-pill);
  background: var(--line-soft);
  margin: var(--sp-2) 0 var(--sp-1);
}
.zielband > i {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: var(--r-pill);
  background: var(--good);
  opacity: .3;
}
.zielband > b {
  position: absolute;
  top: -3px;
  width: 3px;
  height: 12px;
  border-radius: var(--r-bild);
  background: var(--ink);
}

/* ─── Ertragsliste: Wasserfall des Jahresertrags ───────────────────────────
   Ein Balken je Posten, Laenge proportional zum Betrag. Die Trennlinie
   zwischen "fliesst" und "waechst" ist die Aussage der Ansicht, nicht
   Dekoration: oben steht Geld, ueber das man verfuegen kann, unten
   Buchwert. Chart.js kann das nicht ohne Verrenkungen — ein horizontaler
   Balken mit Beschriftung links und rechts ist im DOM einfacher und
   barrierefrei lesbar. */
.ertrag-liste { display: grid; gap: var(--sp-2); }
/* ═══ DIE NAMENSSPALTE MUSS DEN LAENGSTEN NAMEN FASSEN (16.09.2026) ═════
   MWM: "bei Immobilien-Wertsteigerung ist der Tooltip in die Zeile darunter
   verrutscht."

   Gemessen: "Immobilien-Wertsteigerung" plus Info-Symbol braucht 186 px,
   die Spalte war 13,5 em = 168 px breit. Der Name passte, das Symbol nicht
   — und ein umbrechendes Symbol macht die Zeile hoeher als ihre Nachbarn,
   wodurch die ganze Liste aus dem Takt geraet.

   Zwei Aenderungen, beide noetig:
     16em statt 13,5em   — 200 px, mit Reserve fuer den laengsten Namen
     .ez-name als Flex   — Text und Symbol sind Geschwister in EINER Zeile;
                           nowrap auf dem Text haelt sie zusammen.

   16em ist eine MESSUNG und kein Gefuehl: bei --fs-sm (12,5 px) und rund
   0,52 em mittlerer Zeichenbreite fasst sie ~30 Zeichen plus Symbol. Der
   laengste Name hat 25. */
.ertrag-zeile {
  display: grid;
  grid-template-columns: minmax(0, 16em) minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp-3);
}
.ertrag-zeile > .ez-name {
  font-size: var(--fs-sm);
  display: flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}
.ertrag-zeile > .ez-name > .info-hint { flex-shrink: 0; }
.ertrag-zeile > .ez-bahn { min-width: 0; }
.ertrag-zeile > .ez-wert {
  font-size: var(--fs-sm);
  font-weight: var(--fw-fett);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}
.ez-balken {
  height: 22px;
  border-radius: var(--r-bild);
  display: flex;
  align-items: center;
  padding: 0 var(--sp-2);
  min-width: 2px;
}
/* KEINE Farbe hier. Die Balkenfarben kommen aus shared/palette.js ueber
   farbe("positiv") / farbe("immobilien") / farbe("negativ") -- genau wie
   .split-dot sie bekommt. Eine Diagrammfarbe als CSS-Variable zu fuehren
   waere eine zweite Quelle: die Palette kennt einen Farbenblind-Satz, den
   das Stylesheet nicht kennt. */
.ez-trenner {
  border-top: 1px solid var(--karten-linie);
  margin: var(--sp-3) 0 var(--sp-2);
  padding-top: var(--sp-2);
  display: flex;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.ez-trenner > span {
  font-size: var(--fs-kicker);
  font-weight: var(--fw-fett);
  letter-spacing: var(--ls-versal);
  text-transform: uppercase;
}
/* Farben der beiden Marken ebenfalls aus der Palette, siehe oben. */
@media (max-width: 700px) {
  .ertrag-zeile { grid-template-columns: 1fr auto; }
  .ertrag-zeile > .ez-bahn { grid-column: 1 / -1; }
  /* Auf schmalen Fenstern darf der Name umbrechen — dort steht er allein
     in seiner Zeile und reisst nichts mit. */
  .ertrag-zeile > .ez-name { white-space: normal; }
}

/* ─── Herkunftsmarke ───────────────────────────────────────────────────────
   GEMESSEN oder ANNAHME, direkt im Balken. Ohne sie behauptet die Summe
   eine Genauigkeit, die nur die Haelfte der Posten hergibt: drei der sechs
   Ertragsposten kommen aus den Stellschrauben, nicht aus den Daten. */
.quelle {
  font-size: var(--fs-mini);
  text-transform: uppercase;
  letter-spacing: var(--ls-versal);
  font-weight: var(--fw-fett);
  color: var(--flaeche-invers-text);
  white-space: nowrap;
}

/* ─── Objektzeile: Balken statt achtspaltiger Tabelle ──────────────────────
   Die Tabelle stand bis 16.09.2026 hier (MWM: "das müsste man auch
   vernünftig darstellen"). Acht Zahlen nebeneinander liest man, statt sie
   zu sehen — das Verhaeltnis Eigenkapital zu Restschuld war nur durch
   Kopfrechnen zu haben. Jetzt ein Balken: links Eigenkapital, rechts
   Restschuld, Breite proportional zum groessten Objekt. Die Farben kommen
   aus der Palette -- farbe("immobilien") und farbe("restschuld"), dieselben
   Rollen wie in jedem Diagramm der Suite. Die Zahlen stehen
   vollstaendig darunter, es geht keine verloren. */
.objekt-zeile + .objekt-zeile { margin-top: var(--sp-4); }
.objekt-kopf {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-3);
  margin-bottom: var(--sp-innen);
}
.objekt-kopf > .ok-name { font-size: var(--fs-sm); font-weight: var(--fw-mittel); }
.objekt-kopf > .ok-wert { font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.objekt-balken {
  display: flex;
  height: 22px;
  border-radius: var(--r-bild);
  overflow: hidden;
  background: var(--line-soft);
}
/* Auch hier keine Farbe: linker Teil farbe("immobilien"), rechter Teil
   farbe("restschuld") -- beide aus shared/palette.js. */
.objekt-zahlen {
  display: flex;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin-top: var(--sp-innen);
  font-size: var(--fs-fuss);
  color: var(--muted);
}
.objekt-zahlen b { font-variant-numeric: tabular-nums; color: var(--ink); }

/* ─── Zweispaltige Kennzahlenreihe ─────────────────────────────────────────
   Variante von .portfolio-strip fuer die schmale Spalte eines .frame-paar.
   Vier Kacheln nebeneinander waeren dort je ~100px breit — schmaler als
   ihre eigene Beschriftung. */
.portfolio-strip.zweispaltig { grid-template-columns: 1fr 1fr; }

/* ─── Positiver Wert in einer Leitzahl ─────────────────────────────────────
   Dieselbe Rolle wie .leitzahl-delta.positive in shared/modulseite.css und
   .entity-list td.delta.positive in shared/tabelle.css: derselbe
   Modifier-Name, dasselbe Token, eine Bedeutung. */
.hero-value > strong.positive { color: var(--zahl-positiv); }

/* ═══════════════════════════════════════════════════════════════════════════
   NACHMESSUNG 16.09.2026 — Kachel-Innenraum, Abstaende, Zonenboden
   ═══════════════════════════════════════════════════════════════════════════ */

/* ─── KPI-Leiste ohne Zweizeilen-Reserve ───────────────────────────────────
   MWM: "da stehen die Werte sehr weit unten, innerhalb der Karte ist oben
   sehr viel Space."

   Stimmt, und der Grund ist eine RICHTIGE Regel am falschen Ort. Seit dem
   04.09. reserviert jede KPI-Kachel zwei Labelzeilen (siehe
   shared/modulseite.css). Der Grund dort: bricht EIN Label um, rutscht seine
   Zahl unter die der Nachbarn — die Reserve haelt alle vier Zahlen auf einer
   Linie. `align-items: flex-end` schiebt den ungenutzten Platz UEBER das
   Label, weil das Label zu seiner Zahl gehoert, nicht zum Kachelrand.

   Genau dieser Platz ist sichtbar, wenn KEIN Label umbricht: dann sind es
   19 px Luft in jeder Kachel der Leiste.

   NACHGERECHNET am 16.09. fuer alle drei Leisten dieser Seite, bei
   --fs-fuss (10,5 px), --ls-versal und 227 px Textplatz je Kachel:

     Kopfzone      laengstes Label "Ertragsjahr · fliesst"       171 px
     Ertragsjahr   laengstes Label "Mietrendite auf Eigenkapital" 230 px  ← umgebrochen
     Endbild       laengstes Label "Effektiv p. a. nominal"      186 px

   Ein einziger Ueberlaeufer, und der ist gekuerzt ("auf EK", ausgeschrieben
   in der Unterzeile). Damit bricht auf dieser Seite keins mehr um, und die
   Reserve ist ueberall Totraum.

   .einzeilig sagt: diese Leiste kommt mit einer Labelzeile aus. Das ist eine
   MESSUNG, keine Meinung -- und sie kippt, sobald ein Label laenger wird.
   Deshalb rechnet tools/check-kachelzeilen.mjs sie bei jedem Lauf nach.
   Von Hand gesetzt UND geprueft ist etwas anderes als von Hand gesetzt und
   geglaubt. */
.portfolio-strip.einzeilig > div > span { min-height: var(--label-zeile); }

/* ─── Kachelfuss: Zielband und Unterzeile gehoeren zusammen ────────────────
   Die Notreserve-Kachel hat ein Element mehr als ihre Nachbarn (das
   Zielband). Ohne Buendelung waere sie die einzige mit vier Kindern -- und
   jede Regel, die ueber Kinder zaehlt, haette dort einen Sonderfall. */
.kachel-fuss > .zielband { margin-top: var(--sp-2); }
.kachel-fuss > small     { margin-top: var(--sp-1); }
/* Die Menge, ueber die eine Kachel rechnet ("nur vermietet · 1"). Sie steht
   unter der Aussage, nicht daneben: die Aussage gilt, die Menge sagt wofuer.
   Leiser als der Fusstext darueber -- dieselbe Rolle wie die Pille im
   Rahmenkopf, nur auf Kachelebene. (17.09.2026, Deckungsgrad/DSCR) */
.kachel-fuss > .kachel-menge {
  color: var(--muted);
  font-size: var(--fs-fuss);
  font-variant-numeric: tabular-nums;
}

/* ─── Abstand zwischen weisser Karte und KPI-Leiste ────────────────────────
   MWM: "da zusammengerechnet die 1,45 Millionen, sind die Kacheln sehr dicht
   dran an den Informationen."

   Gemessen: .chart-block-full hat margin-bottom 0, .portfolio-strip hat
   margin-bottom var(--sp-6) -- also Luft UNTER der Leiste, aber keine
   darueber. Der Abstand gehoert an die Beziehung, nicht an ein Element:
   eine Karte, auf die eine Kennzahlenleiste folgt, braucht Platz dazwischen.
   Dieselbe Bauart wie `.hero-value > strong + .split-bar` weiter oben. */
.canvas-frame > .chart-block-full + .portfolio-strip,
.canvas-frame > .frame-paar + .chart-block-full { margin-top: var(--sp-5); }

/* ─── Zonenboden: der Inhalt liegt in weissen Karten ───────────────────────
   Die Metrics-Regel vom 04.09. verbot den Zonenboden mit zwei Gruenden. Der
   erste ("bei einem Diagramm liegt nichts darauf -- das Diagramm IST der
   Inhalt") traf die alte Seite: dort stand der Inhalt direkt im Rahmen. Der
   zweite ("der blaugraue Rand steht in derselben Farbfamilie wie die
   Datenreihen") gilt nur, wo das Blau die Daten BERUEHRT.

   Seit dem 16.09. liegt in jedem Rahmen dieser Seite eine weisse Karte --
   genau wie auf den Modulseiten. Damit trifft der erste Grund nicht mehr zu
   (es liegt wirklich etwas darauf), und der zweite auch nicht (kein Balken
   und keine Kurve beruehrt das Blau).

   Die Regel ist deshalb nicht gestrichen, sondern PRAEZISIERT: Zonenboden
   nur mit weisser Zwischenebene. check-seitenklassen prueft das. */
.canvas-frame.zone > .chart-block-full:last-child,
.canvas-frame.zone > .portfolio-strip:last-child { margin-bottom: 0; }
