/* ── KPI STRIP ── */
/* Flat: no card background/divider-line trick (previously a colored gap
   simulating seams between tiles) — tiles sit directly on the page
   background, separated purely by column-gap. Horizontal scroll-snap stays
   for narrow viewports, where the tiles still need to scroll individually. */
.kpi-strip {
	display: flex;
	flex-wrap: nowrap;
	overflow-x: auto;
	overflow-y: hidden;
	scroll-snap-type: x mandatory;
	/* Ohne dies korrigiert der Browser eine Loslass-Position irgendwo
	   zwischen zwei Kacheln (z.B. nach einem Maus-Drag ohne/mit zu wenig
	   Schwung für js/kpi-strip-drag.js' Momentum) mit einem harten Sprung
	   zum nächsten Snap-Punkt statt animiert dahin zu gleiten (Nutzerwunsch
	   08.09.2026). Betrifft nur DIESE automatische Snap-Korrektur - direktes
	   Setzen von scrollLeft (Momentum-Loop, s.o.) bleibt davon unberührt und
	   springt weiterhin pro Frame ohne eigene Verzögerung, wie es für eine
	   selbstgebaute Animation sein muss. */
	scroll-behavior: smooth;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: none;
	/* Ohne dies kann Chrome/WebKit scrollLeft nach einem spaeteren Reflow
	   (z.B. Font-Swap von Inter-Variable.woff2, font-display:swap in
	   tokens.css) eigenmaechtig verschieben, um den sichtbaren Anker stabil
	   zu halten - siehe gotchas-frontend-js.md Cold-Boot-Scroll-Reset. */
	overflow-anchor: none;
	/* max() statt reinem --col-gap (Nutzerfund 08.09.2026, live in Chrome UND
	   Safari reproduziert): wenn der Bleed unten (--kpi-strip-bleed-left/-
	   -right) bis zum echten Rand reicht, landet der Scroll-Container-eigene
	   Clip-Rand exakt bei --page-pad-safe-left minus sich selbst = 0. Ist
	   --col-gap (Standard ~22px) kleiner als dieser Bleed-Wert (auf Desktop
	   z.B. ~35px), ragt der rechte Rand der VORHERIGEN Kachel (Kachel[i].right
	   = Kachel[i+1].left - gap) noch INS sichtbare Fenster hinein statt vom
	   Clip-Rand verschluckt zu werden - sichtbar als Rest der weggescrollten
	   Kachel am linken Rand. gap muss also mindestens so breit sein wie der
	   Bleed, sonst reicht der räumliche Puffer zwischen zwei Kacheln nicht,
	   um die vorherige Kachel rechtzeitig ausserhalb des Clip-Rands zu
	   schieben. */
	gap: max(var(--col-gap), var(--kpi-strip-bleed-left), var(--kpi-strip-bleed-right));
	/* Puls-Glow (.price-flash-up/-down, filter:drop-shadow) auf der ersten
	   Kachel (Live-Kurs) blutet ~10px nach links über deren Box hinaus - ohne
	   diesen Puffer schneidet overflow-x:auto ihn direkt an der Strip-Kante
	   ab. padding-left gibt dem Glow innerhalb des Scroll-Containers Raum,
	   margin-left hebt die dadurch entstehende Rechtsverschiebung des
	   sichtbaren Inhalts wieder auf. Unter dem projektweiten
	   box-sizing:border-box (siehe layout.css Reset) gleicht width:100% die
	   margin-left-Verschiebung NICHT automatisch aus (anders als bei
	   content-box) - width muss explizit um denselben Betrag wachsen, sonst
	   wird der Scrollport rechts entsprechend schmaler.
	   --kpi-strip-bleed-left/-right (css/tokens.css:
	   max(20px, var(--page-pad-safe-left/-right))) - 20px ist eine
	   UNTERGRENZE (grosszügiger als die reine 10px-Maximalblur, für
	   Antialiasing-Spielraum), kein Deckel: auf schmalen Phones ohne Notch
	   bleibt der Bleed mindestens bei den alten 20px, auf Desktop/breiten
	   Screens und echten Notch-Geräten wächst er mit --page-pad-safe-left/-
	   -right darüber hinaus und reicht wie das Schwester-Bleed-Muster
	   .intraday-chart--bleed (weiter unten in dieser Datei) bis zum echten
	   Rand. Siehe css/tokens.css für die volle Zwischenstände-Geschichte
	   (zwei verworfene Versuche: reine --page-pad-Kopplung ohne Untergrenze,
	   dann ein reiner fester Deckel ohne Wachstum auf Desktop). */
	width: calc(100% + var(--kpi-strip-bleed-left) + var(--kpi-strip-bleed-right));
	padding-left: var(--kpi-strip-bleed-left);
	margin-left: calc(-1 * var(--kpi-strip-bleed-left));
	/* Ohne dies rastet Chromes scroll-snap beim ersten Layout/Overflow auf
	   scrollLeft:<Puffer> statt 0 ein (snapt an den padding-left-Puffer, nicht
	   an dessen Ende) - der sichtbare Inhalt sässe dann links vom
	   --kpi-strip-bleed-left des restlichen Contents verschoben (R296,
	   gefunden beim Verschmälern des Browsers). scroll-padding-left
	   verschiebt den Snap-Referenzpunkt um genau den Puffer, sodass die
	   natürliche Ruheposition wieder scrollLeft:0 ist und der erste Tile
	   bündig mit --page-pad bleibt. */
	scroll-padding-left: var(--kpi-strip-bleed-left);
	/* Rechtsseitiges Pendant (Nutzerwunsch 03.09.2026) - kein Glow auf der
	   letzten Kachel, rein symmetrischer Luftraum: die letzte Kachel soll am
	   Scroll-Ende genauso "eingerückt" wirken wie die erste am Anfang statt
	   direkt am Browserrand anzustossen. Anders als links KEIN margin-right
	   nötig - die zusätzliche width schiebt den rechten Rand automatisch um
	   --kpi-strip-bleed-right nach aussen (Box-Mathematik: margin-left plus
	   vergrösserte width verschiebt beide Kanten symmetrisch), es gibt hier
	   keine Kachel-Position, die separat kompensiert werden müsste. */
	padding-right: var(--kpi-strip-bleed-right);
	scroll-padding-right: var(--kpi-strip-bleed-right);
}
.kpi-strip::-webkit-scrollbar {
	display: none;
}
/* Grab-Cursor nur, wenn tatsächlich gescrollt werden kann - bei 6 sichtbaren
   Kacheln (Standard, keine dieser Klassen) gibt es nichts zu ziehen
   (Nutzerwunsch 08.09.2026). js/kpi-strip-drag.js prüft zusätzlich zur
   Laufzeit selbst (scrollWidth > clientWidth), bevor ein Drag überhaupt
   startet - diese Regel ist rein visuell/deckungsgleich dazu. */
.kpi-strip:is(.kpi-strip--visible-5, .kpi-strip--visible-4, .kpi-strip--visible-3, .kpi-strip--visible-2, .kpi-strip--paired) {
	cursor: grab;
}
.kpi-strip--dragging {
	cursor: grabbing;
	/* Verhindert Textauswahl/Bild-Ghosting beim Ziehen mit gedrückter Maustaste. */
	user-select: none;
	-webkit-user-select: none;
	scroll-snap-type: none;
}
/* Sichtbar-Stufen statt fix 6 Kacheln: bleibt IMMER ein horizontaler
   Scroll-Snap-Slider (Nutzerwunsch 08.09.2026 - ersetzt ein zuvor gebautes
   und wieder verworfenes Grid-Stufen-Layout, das bei Overflow komplett
   gestapelt statt geslidet hätte). js/kpi-strip-overflow.js entscheidet per
   ResizeObserver, wie viele Kacheln gleichzeitig sichtbar sein sollen -
   bewusst NICHT anhand von scrollWidth/clientWidth, siehe Kommentar dort,
   sonst Rückkopplungsschleife zwischen den Stufen. Jede Stufe setzt nur die
   feste Kachelbreite (flex-basis) für N sichtbare Kacheln - der Rest bleibt
   ganz normal im selben Scroll-Container erreichbar, Snap weiterhin pro
   einzelner Kachel (.kpi's scroll-snap-align unten, unverändert für alle
   Stufen ausser --paired). */
.kpi-strip--visible-5 .kpi {
	flex: 0 0 calc((100% - 4 * var(--col-gap)) / 5);
}
.kpi-strip--visible-4 .kpi {
	flex: 0 0 calc((100% - 3 * var(--col-gap)) / 4);
}
.kpi-strip--visible-3 .kpi {
	flex: 0 0 calc((100% - 2 * var(--col-gap)) / 3);
}
.kpi-strip--visible-2 .kpi {
	flex: 0 0 calc((100% - 1 * var(--col-gap)) / 2);
}
/* Paare (engste Stufe): je 2 Kacheln stecken in einem .kpi-slide-Wrapper
   (js/views/overview.js), normalerweise display:contents (s.u.) - hier wird
   der Wrapper zur echten, vertikal gestapelten Box und selbst zur
   Scroll-Snap-Einheit, die 2 Kacheln darin verlieren ihre eigene
   Breiten-/Snap-Logik. overflow-y muss von der Basisregel (oben, hidden)
   auf visible zurückgesetzt werden, sonst wird die untere Kachel jedes
   Paars abgeschnitten. */
.kpi-strip--paired {
	overflow-y: visible;
	/* Einspaltiger Zustand: eine Kachel nimmt die volle Breite ein, deshalb
	   nutzt nur die eigentliche Zahl (--fs-kpi-value/-value-lg) den gewonnenen
	   Platz - bewusst NICHT Label/Sub/Meta/Badges, sonst wirkt die Kachel mit
	   zu vielen unterschiedlichen Schriftgrössen überladen (Nutzerentscheid
	   11.09.2026, nach Live-Ansicht des ursprünglich breiter angelegten
	   Faktors). --kpi-fs-scale wird deshalb NUR an den zwei Werte-Stellen
	   konsumiert, nicht mehr an allen --fs-kpi-*. */
	--kpi-fs-scale: 1.4;
}
.kpi-strip--paired .kpi-slide {
	display: flex;
	flex-direction: column;
	flex: 0 0 100%;
	scroll-snap-align: start;
	gap: var(--gap-stack);
}
.kpi-strip--paired .kpi-slide > .kpi {
	flex: none;
	width: 100%;
	scroll-snap-align: none;
}
/* .kpi-slide ist in JEDER anderen Stufe (6/5/4/3/2 sichtbar) unsichtbar fürs
   Layout - die 2 enthaltenen .kpi-Kacheln bleiben direkte Flex-Kinder von
   .kpi-strip, exakt wie ohne den Wrapper. Safari 16+ (Projekt-Baseline). */
.kpi-slide {
	display: contents;
}
.kpi {
	padding-block: 0;
	padding-inline: 0;
	/* Inhaltsbreite als Untergrenze, Wachstum bei Restplatz erlaubt
	   (Nutzerwunsch 08.09.2026, zweiter Schritt nach der Umstellung von
	   flex:1 1 auto weg von der Gleichverteilung). shrink:0 verhindert
	   weiterhin, dass Kacheln unter ihre Inhaltsbreite gequetscht werden
	   (.kpi-strip bleibt horizontal scrollbar, statt zu quetschen) -
	   grow:1 lässt sie aber gleichmässig in vorhandenen Restplatz wachsen,
	   sobald der Container mehr Breite hat als alle Kacheln + Gaps
	   zusammen brauchen, statt den Platz einfach leer zu lassen. Gilt nur
	   für die 6-sichtbar-Standardstufe (keine Klasse) - alle anderen Stufen
	   überschreiben flex oben mit einer festen Breite. */
	flex: 1 0 auto;
	min-width: 200px;
	scroll-snap-align: start;
	user-select: none;
	-webkit-user-select: none;
}
/* kpi-jump: klickbare KPI-Kachel, scrollt smooth zu einer Detail-Karte weiter
   unten (data-scroll-target, siehe js/app.js). Bewusst kein Hover-/Active-
   Hintergrund - keine KPI-Kachel im Projekt hat einen solchen Effekt,
   Nutzerentscheid 27.08.2026: Styleguide-Konsistenz vor neuem Muster. */
.kpi-jump {
	cursor: pointer;
}
.kpi-l {
	font-size: var(--fs-kpi-label);
	font-weight: 400;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--t3);
	/* Gleiche Line-Box-Metrik wie .kpi-fetched-at/.kpi-countdown (unten,
	   line-height:1.3) - ohne das hatte diese Basisregel keinen expliziten
	   line-height (Browser-Default "normal"), wodurch die Live-Kurs-Kachel
	   (Flex-Zeile aus Punkt/Uhrzeit/Countdown, siehe .kpi-price .kpi-l) auf
	   einer anderen Grundlinie sass als die reinen Text-Labels der
	   Nachbarkacheln (Nutzerfund 08.09.2026). */
	line-height: 1.3;
	margin-bottom: 4px;
}
.kpi-v {
	font-family: var(--fm);
	font-variant-numeric: tabular-nums;
	font-size: calc(var(--fs-kpi-value) * var(--kpi-fs-scale));
	font-weight: 300;
	color: var(--t1);
	line-height: 1;
	/* Nutzerbeobachtung 28.08.2026: bei bestimmten Browserbreiten brach eine
	   grosse KPI-Zahl mitten in der Zeile um. Fmt.money()'s eigenes
	   Trennzeichen zwischen Symbol und Zahl ist bereits ein NNBSP (U+202F,
	   nicht umbrechbar) - die eigentliche Bruchstelle sind Aufrufer, die ein
	   Währungssymbol MIT eingebautem echten Leerzeichen übergeben (z.B.
	   Fmt.money(v, 'CHF ')) oder mehrere Werte mit " · " kombinieren (siehe
	   .kpi-s unten) - an genau diesen Stellen brach die Zeile um, sobald die
	   Kachel schmal genug wurde. */
	white-space: nowrap;
}
.kpi-v.p {
	color: var(--pos);
}
.kpi-v.n {
	color: var(--neg);
}
.kpi-s {
	font-family: var(--fm);
	font-variant-numeric: tabular-nums;
	font-size: var(--fs-kpi-sub);
	color: var(--t3);
	margin-top: 3px;
	/* Enthält oft ein €/CHF-Duo mit echten Leerzeichen (" · ", "CHF "-Präfix
	   mit eingebautem Space statt NNBSP, siehe Kommentar bei .kpi-v oben) -
	   ohne nowrap brach genau diese Zeile um. */
	white-space: nowrap;
}
.kpi-price .kpi-s {
	margin-top: 4px;
}
.kpi-row,
.kpi-row-lg,
.kpi-row-sm,
.kpi-row-sm5 {
	display: flex;
	align-items: baseline;
}
.kpi-row {
	gap: 6px;
	margin-top: 4px;
}
.kpi-row-lg {
	gap: 8px;
	margin-top: 4px;
}
.kpi-row-sm {
	gap: 6px;
	margin-top: 3px;
}
.kpi-row-sm5 {
	gap: 5px;
	margin-top: 3px;
}
.kpi-v-lg {
	font-size: calc(var(--fs-kpi-value-lg) * var(--kpi-fs-scale));
}
/* Secondary/dimmed values (regulärer Regular-Session-Echo, Live-Gewinn-%-
   Nebenzeile, Ø-Einstand-Nebenwerte) bleiben bei normalem Gewicht — nur der
   PRIMÄRE .kpi-v-Wert ist fett (600), genau wie im Mockup .kpi-value vs.
   .kpi-reg-value/.kpi-sub. */
.kpi-v-sub {
	font-size: var(--fs-kpi-value-sub);
	font-weight: 300;
}
.kpi-v-sub-muted {
	font-size: var(--fs-kpi-value-sub);
	font-weight: 300;
	color: var(--t2);
}

/* Regulär-Block (Pre-/Aftermarket): wiederholt unter dem Live-/Extended-
   Block dieselben Kennzahlen auf Basis des regulären Schlusskurses,
   komplett gedimmt statt einer einzelnen Zweitzeile (KpiPrice.moneyCard()/
   render(), js/kpi-price.js) - Nutzerwunsch 08.08.2026: alles, was zur
   regulären Handelszeit angezeigt würde (inkl. €/CHF), soll im Pre-/
   Aftermarket ebenfalls sichtbar bleiben, nur optisch zurückgenommen. Nur
   Abstand + Deckkraft, keine Trennlinie/Farbe - kommt mit der geplanten
   flacheren Design-Überarbeitung ohnehin noch dazu. margin-top von 8px auf
   16px erhöht (Nutzerwunsch 19.08.2026, nach dem Rückbau des zwischen-
   zeitlichen Subgrid-Alignments: etwas mehr Abstand zwischen dem Block, der
   die regulären Werte zeigt, und dem gedimmten Pre-/After-Market-Block) -
   16px statt eines beliebigen Zwischenwerts, weil es die bestehende 8px-
   Verdopplung dieser Kachel-internen Mikro-Abstände fortsetzt (dieselbe
   8/16px-Logik wie die admin.css-Innenabstände, siehe design-system.md,
   Abschnitt "Spacing & Grid" - es gibt dafür bewusst KEIN eigenes
   --gap-Token, die bestehenden --col-gap/--gap-group sind für grössere
   Layout-Abstände reserviert, nicht für Kachel-interne Mikro-Abstände). */
.kpi-reg-block {
	display: grid;
	grid-template-rows: 1fr;
	opacity: 0.5;
	margin-top: 16px;
	transition: grid-template-rows var(--dur-slow) var(--ease-out-expo), opacity var(--dur-slow) var(--ease-out-expo), margin-top var(--dur-slow) var(--ease-out-expo);
}
/* Inner wrapper needed for the grid-template-rows collapse trick: a grid
   track can only be animated to 0 if ITS content is clipped by an
   overflow:hidden child - the row itself doesn't clip (see
   Nutzerwunsch-Kommentar bei moneyCard(), js/kpi-price.js, für den
   Kontext dieses Ein-/Ausblendens statt eines harten Sprungs). */
.kpi-reg-block-inner {
	overflow: hidden;
	min-height: 0;
}
/* Pre-/Aftermarket → Regular Hours (und umgekehrt): sanft aus-/einblenden
   statt hart zeigen/verstecken - der `regVisible`-Parameter in
   KpiPrice.moneyCard() setzt/entfernt diese Klasse verzögert per rAF (siehe
   overview.js updateLiveValues()), damit die Transition tatsächlich abläuft
   statt bei einem outerHTML-Ersatz sofort im Endzustand zu landen. */
.kpi-reg-block-hidden {
	grid-template-rows: 0fr;
	opacity: 0;
	margin-top: 0;
	pointer-events: none;
}
.kpi-price .kpi-l {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 6px;
	/* Optische Korrektur: rückt Punkt/Uhrzeit/Countdown-Zeile 3px ein, damit
	   sie sauberer mit der Kante darunter/darüber fluchtet. Nur hier, nicht
	   an der generischen .kpi-l-Basisregel — die ist die geteilte Label-Zeile
	   aller anderen KPI-Kacheln ohne diesen Versatz. */
	padding-left: 3px;
}
.kpi-price-left {
	display: flex;
	align-items: center;
	gap: 5px;
}
.kpi-status-dot {
	width: 8px;
	height: 8px;
	border-radius: 50%;
	display: inline-block;
	flex-shrink: 0;
	background: var(--dot-bg);
	animation: var(--dot-anim, none);
}
.kpi-fetched-at,
.kpi-countdown {
	/* Flex-Items von .kpi-price-left/.kpi-price .kpi-l - ohne min-height
	   kollabiert die leere Zelle auf 0 Höhe im Moment zwischen outerHTML-
	   Neuaufbau (updateLiveValues(), js/views/overview.js) und dem nächsten
	   Countdown-Tick (bis zu 1s später, KpiPrice.startCountdown()), der den
	   Text erst wieder setzt - das liess die .kpi-l-Zeile kurz schrumpfen und
	   die grosse Kurszahl darunter sichtbar nach oben rutschen (Nutzer-
	   Feedback 14.08.2026). min-height allein reicht nicht: der Browser-
	   Default `line-height: normal` ist NICHT deterministisch (hängt von
	   Font-Metriken/Plattform ab, z.B. Safari vs. Chrome unterschiedlich -
	   Safari ist die Compat-Baseline dieses Projekts) - ein empirisch in
	   einem Browser gemessener min-height-Wert kann in einem anderen leicht
	   danebenliegen und einen Rest-Sprung übriglassen (genau das war der
	   Fall, Nutzer-Nachbesserung 14.08.2026). Fix: line-height EXPLIZIT auf
	   denselben Wert wie min-height setzen - dann ist die Line-Box-Höhe bei
	   vorhandenem Text durch dieselbe Zahl bestimmt wie die reservierte
	   Höhe im leeren Zustand, byte-identisch in jedem Browser, kein Raten
	   gegen "normal" mehr nötig. */
	line-height: 1.3;
	min-height: 1.3em;
}
/* font-weight bewusst NICHT gesetzt (Nutzerwunsch 25.08.2026) - beide erben
   damit das font-weight:600 der umschliessenden .kpi-l (siehe kpi-price.js),
   genau wie die Label-Zeile an derselben Position in den anderen KPI-Kacheln
   (".kpi-l" ohne Kind-Override bleibt dort automatisch bei 600). Vorher
   hatten beide ein eigenes font-weight:400, wodurch diese Zeile in der
   Live-Kurs-Kachel dünner wirkte als die Label-Zeile in den übrigen Kacheln. */
.kpi-fetched-at {
	font-size: var(--fs-kpi-meta);
	color: var(--t3);
	font-variant-numeric: tabular-nums;
}
.kpi-countdown {
	font-size: var(--fs-kpi-meta);
	color: var(--t3);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}
.kpi-countdown-icon {
	/* Ersetzt das frühere Unicode-Glyph ↻ (U+21BB), das im Tesla-Browser
	   mangels Font-Support unsichtbar blieb (Nutzer-Feedback 19.08.2026) -
	   inline SVG mit currentColor ist fontunabhängig und übernimmt
	   automatisch die --t3-Farbe von .kpi-countdown. */
	width: 1em;
	height: 1em;
	vertical-align: -0.15em;
	margin-left: 2px;
}

/* ── PRICE FLASH ── */
@keyframes flash-up {
	0% {
		color: var(--pos);
		filter: drop-shadow(0 0 10px var(--pos));
	}
	30% {
		color: inherit;
		filter: none;
	}
	60% {
		color: var(--pos);
		filter: drop-shadow(0 0 8px var(--pos));
	}
	100% {
		color: inherit;
		filter: none;
	}
}
@keyframes flash-down {
	0% {
		color: var(--neg);
		filter: drop-shadow(0 0 10px var(--neg));
	}
	30% {
		color: inherit;
		filter: none;
	}
	60% {
		color: var(--neg);
		filter: drop-shadow(0 0 8px var(--neg));
	}
	100% {
		color: inherit;
		filter: none;
	}
}
.price-flash-up {
	animation: flash-up 1s ease-in-out forwards;
}
.price-flash-down {
	animation: flash-down 1s ease-in-out forwards;
}

/* ── HOLIDAY BANNER ── */
.holiday-banner {
	background: var(--amber-bg);
	color: var(--amber);
	font-size: var(--fs-hint);
	font-weight: 400;
	text-align: center;
	padding-block: 6px;
	padding-inline: 16px;
	border-bottom: 0.5px solid var(--amber);
	letter-spacing: 0.02em;
}

/* Debug-date banner, when clickable (admin only) – click resets to live mode */
.debug-banner-clickable {
	cursor: pointer;
	transition: background var(--dur-sm);
}
@media (hover: hover) and (pointer: fine) {
	.debug-banner-clickable:hover {
		background: var(--amber);
		color: var(--bg);
	}
}

/* ── KPI PRICE CARD ── */
.kpi-price {
	grid-column: span 1;
	position: relative;
	/* Eigener Stacking-Context, damit .kpi-emoji-bg (z-index:-1) NUR hinter
	   dem Inhalt DIESER Karte landet - ohne isolation würde ein
	   position:relative ohne eigenen z-index keinen Stacking-Context
	   aufmachen, das negative z-index-Kind würde dann bis zur nächsten
	   Stacking-Context-Ebene (z.B. .kpi-strip) durchgereicht und dort HINTER
	   dem opaken .kpi-Kartenhintergrund landen - unsichtbar. */
	isolation: isolate;
	/* Hebt den Text vom Emoji-Hintergrund ab (Nutzer-Feedback 08.08.2026):
	   Schatten-Farbe = Kartenhintergrund selbst, dadurch unsichtbar über der
	   normalen flachen Kartenfläche, aber wirkt wie ein weicher Rand/Halo,
	   sobald darunter das gemusterte .kpi-emoji-bg liegt. text-shadow ist ein
	   vererbtes Property, gilt daher automatisch für alle Text-Kinder dieser
	   Karte (kpi-l/kpi-v/kpi-s/kpi-price-chg/...), keine weitere Selektoren
	   nötig. */
	text-shadow: 0 0 3px var(--bg), 0 0 3px var(--bg);
}

/* Emoji-Hintergrund bei starken Tagesbewegungen (Nutzerwunsch 27.07.2026,
   Schwellen ±3%/±6%/±9% + Emoji-Set mit Nutzer geklärt 08.08.2026). Negativer
   z-index platziert das Tile HINTER den (nicht positionierten) Inhalt von
   .kpi-price - ein positionierter Nachfahre mit z-index 0/auto würde sonst
   trotzdem vor normalem Flow-Inhalt gemalt werden. Animation läuft rein über
   `transform` (compositor-only, kein Repaint) - wichtig für Dauerbetrieb auf
   Tesla-/iPad-Kiosk-Screens. */
.kpi-emoji-bg {
	position: absolute;
	inset: 0;
	overflow: hidden;
	z-index: var(--z-behind);
	pointer-events: none;
	/* Weicher Rand statt hartem Rechteck-Schnitt (Nutzer-Feedback 08.08.2026).
	   Rechteckige Vignette statt Ellipse: eine runde/elliptische Maske passt
	   nicht zur rechteckigen Kartenform (rundet die Ecken sichtbar stärker ab
	   als die Kanten-Mitten) - hier stattdessen zwei lineare Verläufe (einer
	   je Achse, jeweils an BEIDEN Enden auf transparent auslaufend) per
	   mask-composite überlagert: nur wo beide Verläufe deckend sind, bleibt
	   das Tile sichtbar - jede der vier Kanten (nicht nur die Ecken) läuft
	   dadurch gleichmässig auf 100% transparent aus. Safari (Browser-
	   Baseline) braucht dafür das ältere `-webkit-mask-composite: source-in`
	   (nicht den Standard-Wert `intersect`, den Safari nicht kennt). */
	-webkit-mask-image:
		linear-gradient(to right, transparent, black 18%, black 82%, transparent),
		linear-gradient(to bottom, transparent, black 18%, black 82%, transparent);
	-webkit-mask-composite: source-in;
	mask-image:
		linear-gradient(to right, transparent, black 18%, black 82%, transparent),
		linear-gradient(to bottom, transparent, black 18%, black 82%, transparent);
	mask-composite: intersect;
}
.kpi-emoji-bg-tile {
	position: absolute;
	top: -50%;
	left: -50%;
	width: 200%;
	height: 200%;
	background-repeat: repeat;
	/* Kachel-Raster 56px - zurückgesetzt von 76px (Nutzer-Feedback 08.08.2026:
	   die Zwischengrösse von vor der letzten Vergrösserung war besser). */
	background-size: 56px 56px;
	/* 5% dunkler als zuvor (0.16 → 0.152), Nutzer-Feedback 08.08.2026. */
	opacity: 0.152;
	animation-duration: 6s;
	animation-timing-function: linear;
	animation-iteration-count: infinite;
	/* Hält die Animation phasenstabil über Re-Renders hinweg (Klick-zum-
	   Refresh auf die KPI-Kachel, Teslanaire-Tab-Wechsel, Kursgrenze-Tap etc.
	   bauen das Tile als frisches DOM-Element neu auf, das sonst wieder bei
	   Frame 0 einsteigen würde - sichtbares "Ruckeln"). js/kpi-price.js setzt
	   --emoji-anim-offset auf einen aus der Wanduhr abgeleiteten negativen
	   Versatz (Date.now() % Loop-Dauer), sodass ein neu erzeugtes Element
	   exakt an der Stelle weiterläuft, an der ein durchgehend laufendes
	   Element gerade wäre - keine JS-seitige Animationsstatus-Verwaltung
	   nötig. */
	animation-delay: var(--emoji-anim-offset, 0ms);
}
/* Diagonale Anordnung à la Louis-Vuitton-Monogramm: NICHT über CSS-Rotation
   des Tiles (das kippt auch die Emoji-Glyphen selbst, unerwünscht laut
   Nutzer-Feedback 08.08.2026 - "die Icons sollen nicht rotiert werden"),
   sondern direkt im SVG-Pattern über zwei Emoji pro Kachel, um eine halbe
   Kachelbreite/-höhe versetzt (siehe background-image je Klasse unten) -
   ergibt beim Kacheln ein diagonales Versatz-Raster aus aufrechten Icons.
   Die Scroll-BEWEGUNG selbst ist bewusst rein vertikal (translateY, kein
   x-Anteil) - Nutzer-Klarstellung 08.08.2026: nur das Muster soll diagonal
   angeordnet sein, nicht die Animationsrichtung. Loop-Distanz = exakte
   Tile-Höhe (56px), damit die Kachelung nahtlos wraps. */
@keyframes kpi-emoji-scroll-up {
	from { transform: translateY(0); }
	to   { transform: translateY(-56px); }
}
@keyframes kpi-emoji-scroll-down {
	from { transform: translateY(0); }
	to   { transform: translateY(56px); }
}
.kpi-emoji-gain-mild .kpi-emoji-bg-tile,
.kpi-emoji-gain-strong .kpi-emoji-bg-tile,
.kpi-emoji-gain-extreme .kpi-emoji-bg-tile {
	animation-name: kpi-emoji-scroll-up;
}
.kpi-emoji-loss-mild .kpi-emoji-bg-tile,
.kpi-emoji-loss-strong .kpi-emoji-bg-tile,
.kpi-emoji-loss-extreme .kpi-emoji-bg-tile {
	animation-name: kpi-emoji-scroll-down;
}
.kpi-emoji-gain-mild .kpi-emoji-bg-tile {
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='56'%3E%3Ctext x='15' y='21' font-size='16' text-anchor='middle'%3E%F0%9F%9A%80%3C/text%3E%3Ctext x='43' y='49' font-size='16' text-anchor='middle'%3E%F0%9F%9A%80%3C/text%3E%3C/svg%3E");
}
.kpi-emoji-gain-strong .kpi-emoji-bg-tile {
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='56'%3E%3Ctext x='15' y='21' font-size='16' text-anchor='middle'%3E%F0%9F%8C%88%3C/text%3E%3Ctext x='43' y='49' font-size='16' text-anchor='middle'%3E%F0%9F%8C%88%3C/text%3E%3C/svg%3E");
}
.kpi-emoji-gain-extreme .kpi-emoji-bg-tile {
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='56'%3E%3Ctext x='15' y='21' font-size='16' text-anchor='middle'%3E%F0%9F%A6%84%3C/text%3E%3Ctext x='43' y='49' font-size='16' text-anchor='middle'%3E%F0%9F%A6%84%3C/text%3E%3C/svg%3E");
}
.kpi-emoji-loss-mild .kpi-emoji-bg-tile {
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='56'%3E%3Ctext x='15' y='21' font-size='16' text-anchor='middle'%3E%F0%9F%90%BB%3C/text%3E%3Ctext x='43' y='49' font-size='16' text-anchor='middle'%3E%F0%9F%90%BB%3C/text%3E%3C/svg%3E");
}
.kpi-emoji-loss-strong .kpi-emoji-bg-tile {
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='56'%3E%3Ctext x='15' y='21' font-size='16' text-anchor='middle'%3E%F0%9F%92%A9%3C/text%3E%3Ctext x='43' y='49' font-size='16' text-anchor='middle'%3E%F0%9F%92%A9%3C/text%3E%3C/svg%3E");
}
.kpi-emoji-loss-extreme .kpi-emoji-bg-tile {
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='56'%3E%3Ctext x='15' y='21' font-size='16' text-anchor='middle'%3E%F0%9F%92%80%3C/text%3E%3Ctext x='43' y='49' font-size='16' text-anchor='middle'%3E%F0%9F%92%80%3C/text%3E%3C/svg%3E");
}
@media (prefers-reduced-motion: reduce) {
	.kpi-emoji-bg-tile {
		animation: none;
	}
}

/* Tageskurs-Chart-Modul: eigenständiger Block (nicht mehr Teil einer
   KPI-Kachel, Nutzerwunsch 10.08.2026), randabfallend bis zum echten
   Viewport-Rand. Von allen vier Views (Übersicht, Tesla, iPad, iPhone)
   genutzt - Basisklasse ist überall gleich, der Bleed-Modifier
   (.intraday-chart--bleed) gleicht nur dort, wo der umgebende Container
   selbst horizontales Padding trägt (Desktop/Tesla über .page-full, iPhone
   über .iphone-shell), per negativem Margin aus - .tesla-shell trägt seit
   der Tesla-Content-Übernahme zusätzlich die .page-full-Klasse, genau
   damit diese Regel dort ebenfalls greift. iPad (weiterhin padding-frei
   über .ipad-shell) braucht den Modifier bislang nicht. Kein
   Achsen/Legenden-Chrome (siehe Charts.intraday opts.sparkline in
   js/charts.js). Höhe kommt aus --intraday-chart-h (js/intraday-chart-resize.js
   setzt das auf :root, per Drag-Handle unten einstellbar und in localStorage
   persistiert) - der Fallback-Wert greift nur, falls das Skript ausnahmsweise
   noch nicht gelaufen ist. Kein pointer-events:none mehr (war nötig, solange
   der Chart als Overlay IN der KPI-Kachel lag, damit Klicks zum Klick-zum-
   Refresh darunter durchgereicht wurden) - das Modul liegt jetzt im normalen
   Fluss, nichts mehr "darunter", und der Hover-Tooltip (Nutzerwunsch
   10.08.2026, siehe .intraday-tooltip unten) braucht Pointer-Events auf dem
   Canvas. */
.intraday-chart {
	position: relative;
	height: var(--intraday-chart-h, 20svh);
	user-select: none;
	-webkit-user-select: none;
	/* Zwei-Finger-Pinch zum Höhe-Verstellen (js/intraday-chart-resize.js,
	   Nutzerwunsch 14.08.2026) braucht verlässliches preventDefault() im
	   touchmove-Handler, sonst kann Safari die Geste trotzdem noch als
	   natives Pinch-Zoom interpretieren. pan-y (nicht none!) lässt dabei
	   bewusst Ein-Finger-Vertikal-Scroll zu - der JS-Handler greift ohnehin
	   nur bei genau 2 Touches ein, ein "none" hätte hier zusätzlich das
	   normale Scrollen der Seite blockiert, sobald ein Scroll-Swipe
	   zufällig innerhalb des Charts beginnt. */
	touch-action: pan-y;
}
.intraday-chart canvas {
	display: block;
	width: 100% !important;
	height: 100% !important;
}

/* Diagonal-stripe placeholder für einen Tag ohne jeden Verlauf - gesetzt als
   .is-empty von renderIntradaySparkline() (js/charts.js).

   War ursprünglich an html.debug-date-active gebunden (Nutzerwunsch
   28.08.2026, js/app.js updateDebugDateBanner()): der Intraday-Dateiname wird
   bewusst über trading_day_key() (echte ET-Uhr) statt app_today() gebildet,
   der X-Debug-Date-Header lief also an der Route vorbei und sie hätte den
   echten heutigen Tag geliefert, als gehörte er zum simulierten Datum - der
   Chart wurde deshalb im Zeitreise-Modus pauschal ausgeblendet. Seit die
   Route ein ?date= entgegennimmt (R309, Tages-Navigation), löst sich das
   richtig auf, und das Kriterium ist nicht mehr "Zeitreise aktiv", sondern
   "für diesen Tag liegt nichts vor" - ein simuliertes Datum innerhalb der
   ~31-Tage-Retention zeigt jetzt seinen echten Verlauf. ::before overlays the
   module's own box (position:relative from .intraday-chart above) rather than
   a separate element, so it automatically takes exactly the chart's current
   size/position in all four views (overview/tesla/ipad/iphone) with no
   per-view markup change. Canvas is hidden (not removed) underneath so a
   still-running Chart.js instance keeps working invisibly and needs no extra
   guard when debug mode is turned back off. */
.intraday-chart.is-empty canvas {
	visibility: hidden;
}
/* Solid var(--t3) fill + a repeated SVG alpha mask (not repeating-linear-
   gradient(135deg, ...)) - a diagonal hard-stop gradient computes its
   infinite repeat analytically per pixel, and at 135deg (not aligned to the
   pixel grid) that per-pixel evaluation rounds slightly differently every
   few repeats, producing a visible banding "beat" every 6-7 lines on HiDPI
   screens (Nutzerbeobachtung 28.08.2026). A 12x12 SVG tile (Nutzerwunsch
   28.08.2026: erst "etwas mehr Abstand" auf 16x16, dann konkret "12x12
   Abstand" - war ursprünglich 8x8) is rasterized ONCE and then
   simply repeated as a bitmap via mask-repeat, so every stripe is a
   pixel-identical copy - no per-repeat rounding drift possible. Color
   stays a plain var(--t3) background (theme-aware, no duplicated hex in
   the SVG) - the mask only supplies the stripe shape as white-on-
   transparent alpha. */
.intraday-chart.is-empty::before {
	content: '';
	position: absolute;
	inset: 0;
	background: var(--t3);
	opacity: 0.5;
	pointer-events: none;
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Cpath d='M-2,2 L2,-2 M0,12 L12,0 M10,14 L14,10' stroke='white' stroke-width='1.5'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Cpath d='M-2,2 L2,-2 M0,12 L12,0 M10,14 L14,10' stroke='white' stroke-width='1.5'/%3E%3C/svg%3E");
	-webkit-mask-size: 12px 12px;
	mask-size: 12px 12px;
	-webkit-mask-repeat: repeat;
	mask-repeat: repeat;
}
.page-full .intraday-chart--bleed {
	/* margin-inline (symmetrisches Shorthand) durch getrennte
	   margin-left/-right ersetzt, da die Safe-Area-Insets links/rechts
	   unterschiedlich gross sein können (Landscape, Notch nur auf einer
	   Seite) - width muss die Summe beider Seiten gegenrechnen (08.09.2026,
	   analog zum bestehenden Kommentar bei .kpi-strip weiter oben). */
	margin-left: calc(-1 * var(--page-pad-safe-left));
	margin-right: calc(-1 * var(--page-pad-safe-right));
	width: calc(100% + var(--page-pad-safe-left) + var(--page-pad-safe-right));
}
.iphone-shell .intraday-chart--bleed {
	margin-inline: calc(-1 * clamp(16px, 5vw, 28px));
	width: calc(100% + 2 * clamp(16px, 5vw, 28px));
}

/* Drag-Handle am unteren Rand des Chart-Moduls (Nutzerwunsch 10.08.2026: Höhe
   soll sich direkt im Browser einstellen lassen, siehe
   js/intraday-chart-resize.js). Trefferzone (12px) bleibt INNERHALB der
   eigenen Box (bottom:0, nicht negativ) - manche Views (z.B. .iphone-shell)
   haben keinen Abstand zum nächsten Block, ein Hinausragen würde dort ins
   nächste Element überlappen. Die sichtbare Linie selbst ist nur 1px hoch
   und sitzt am UNTEREN Rand der Trefferzone (align-items:flex-end statt
   center) - "ganz unten am Canvas" (Nutzerwunsch), die grössere Trefferzone
   bleibt nur zum Greifen, nicht sichtbar. */
.intraday-chart-handle {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 12px;
	cursor: ns-resize;
	pointer-events: auto;
	display: flex;
	align-items: flex-end;
	justify-content: center;
}
/* Standardmässig unsichtbar (Nutzerwunsch 10.08.2026) - erst bei :hover ODER
   während eines aktiven Drags (.active, von js/intraday-chart-resize.js
   gesetzt, damit die Linie nicht verschwindet, wenn der Cursor beim
   schnellen Ziehen die 12px-Trefferzone kurz verlässt) sichtbar. */
.intraday-chart-handle::after {
	content: '';
	width: 32px;
	height: 1px;
	background: var(--t3);
	opacity: 0;
	transition: opacity var(--dur-fast);
}
.intraday-chart-handle.active::after {
	opacity: 1;
}
@media (hover: hover) and (pointer: fine) {
	.intraday-chart-handle:hover::after {
		opacity: 1;
	}
}

/* Hover-Tooltip (Nutzerwunsch 10.08.2026, js/charts.js sparklineTooltip()) -
   eigenes HTML statt Chart.js' Canvas-Tooltip, damit Uhrzeit/Pre-Label
   unabhängig links/rechts auf einer Zeile stehen können und exakt dieselbe
   KPI-Markup/-Klassen (.kpi-s/.kpi-ext-label/.kpi-row-lg/.kpi-v/
   .kpi-price-chg) wiederverwendet werden - ausdrücklich OHNE eigenen
   Hintergrund/Rahmen (Nutzerwunsch). Position kommt aus --tooltip-x/-y
   (dynamisch pro Hover-Frame von sparklineTooltip() gesetzt, dort bereits
   gegen die Containerbreite geclampt, damit der Tooltip nie über den
   Viewport-Rand hinausragt - Frontend-Konvention: Custom Property statt
   voller Inline-style). left/top transitionen, damit die Position beim
   Wechsel zwischen Punkten sanft gleitet statt zu springen (Nutzerwunsch).
   text-shadow wie .kpi-price (Halo in --bg-Farbe für Lesbarkeit über der
   Chart-Linie/-Füllung) - vererbt an alle Kind-Texte; .kpi-ext-label
   überschreibt das bereits selbst mit none (eigene Pillen-Hintergrundfarbe,
   siehe dort), automatisch korrekt. */
.intraday-tooltip {
	position: absolute;
	left: var(--tooltip-x, 0px);
	top: var(--tooltip-y, 0px);
	/* Stand ursprünglich (Nutzerwunsch 14.08.2026) IMMER unterhalb des
	   gehoverten Punkts - ersetzte eine frühere Oben-mit-Unten-Fallback-
	   Logik, die je nach Punkthöhe im kurzen Sparkline-Modul unvorhersehbar
	   zwischen den Seiten wechselte ("fliegt weit runter"). Auf erneuten
	   Nutzerwunsch 14.08.2026 auf IMMER OBERHALB gedreht - auf Touch-
	   Geräten verdeckt der Finger sonst den Punkt samt Tooltip darunter.
	   translate(-100%) statt einer festen px-Höhe, da die Tooltip-Höhe je
	   nach Inhalt (Pre/After-Label-Zeile vorhanden oder nicht) variiert.
	   Abstand 30px (Nutzerwunsch 17.08.2026 - davor Zwischenstopps bei
	   3px/8px/10px/20px). */
	transform: translate(-50%, calc(-100% - 30px));
	pointer-events: none;
	opacity: 0;
	transition: opacity var(--dur-xs), left var(--dur-instant) ease-out, top var(--dur-instant) ease-out;
	white-space: nowrap;
	z-index: var(--z-base);
	/* Nutzerwunsch 17.08.2026: minimal stärker als das sonstige 3px-Halo
	   (z.B. .kpi-price oben) - 4px statt 3px Blur, sonst unverändert.
	   Nutzerwunsch 21.09.2026: nochmals stärker, 8px + dritte Lage. */
	text-shadow: 0 0 8px var(--bg), 0 0 8px var(--bg), 0 0 8px var(--bg);
	/* Nutzerwunsch 14.08.2026: Luft zum Browserrand, wenn der Punkt nahe am
	   Rand des (per .intraday-chart--bleed bis zum echten Viewport-Rand
	   reichenden) Sparkline-Moduls gehovert wird. padding statt margin, da
	   das horizontale Rand-Clamping in sparklineTooltip() (js/charts.js,
	   `halfW = el.offsetWidth / 2`) automatisch mitwächst - offsetWidth
	   schliesst Padding ein, Margin nicht, würde also ohne JS-Änderung
	   wirkungslos bleiben. An den bestehenden Grid-Gap-Token gebunden statt
	   einer eigenen Magic Number (Nutzerwunsch), halbiert - der volle
	   --col-gap (clamp 20-36px, für Abstände zwischen Grid-Spalten gedacht)
	   wäre für dieses kleine Tooltip unproportional gross. */
	padding-inline: calc(var(--col-gap) / 2);
}
.intraday-tooltip.visible {
	opacity: 1;
}
/* Zeitstempel im Tooltip (.kpi-s erbt sonst --t3/grau) auf Nutzerwunsch
   21.09.2026 wie der Kurs selbst in --t1, da der Halo-Text-Shadow für
   Lesbarkeit sorgt und Grau hier zu blass wirkte. */
.intraday-tooltip .kpi-s {
	color: var(--t1);
}
.intraday-tooltip-row {
	display: flex;
	align-items: center;
	gap: 6px;
}
/* $/%-Änderung in eigener Zeile UNTER dem Kurs statt daneben (Nutzerwunsch
   10.08.2026: "dann wird das kompakter") - schmalerer Tooltip. Zeilenabstand
   auf Nutzerwunsch 14.08.2026 leicht verringert (vorher 2px). */
.intraday-tooltip .js-chg {
	margin-top: 1px;
}
/* Tagesgewinn/-verlust + Gesamtgewinn/-verlust (Nutzerwunsch 14.08.2026,
   Labels bewusst weggelassen auf Nutzerwunsch) - gleiche Zeilenhöhe/Abstand
   wie .js-chg oben. */
.intraday-tooltip .js-daychg,
.intraday-tooltip .js-totalchg {
	margin-top: 1px;
}

/* Kurs-Rolleffekt im Tooltip (Nutzerwunsch 10.08.2026: "wie eine Flughafen-
   Klappanzeige"/vertikales Ziffern-Scrollen) - siehe renderRollingPrice()
   in js/charts.js. Jede Ziffer eine eigene 1em-hohe, überlaufversteckte
   Zelle; die Ziffernstreifen-Zeile 0-9 wird per transform:translateY um
   die Zielziffer (--roll-digit, von JS gesetzt) verschoben - die CSS-
   Transition übernimmt die Animation automatisch bei jeder Änderung.
   Nicht-Ziffern (Währungssymbol, Trenner, Vorzeichen) werden separat als
   normaler Text gerendert, kein Rollen. */
.roll-digit {
	display: inline-block;
	height: 1em;
	overflow: hidden;
	vertical-align: bottom;
}
.roll-digit-strip {
	display: flex;
	flex-direction: column;
	transform: translateY(calc(var(--roll-digit, 0) * -1em));
	transition: transform var(--dur-md) var(--ease-out-back);
}
.roll-digit-strip span {
	height: 1em;
	line-height: 1;
}

.kpi-price-chg {
	font-family: var(--fm);
	font-variant-numeric: tabular-nums;
	font-size: calc(var(--fs-kpi-price-chg) * var(--kpi-fs-scale));
	white-space: nowrap;
}
.kpi-price-chg.p {
	color: var(--pos);
}
.kpi-price-chg.n {
	color: var(--neg);
}
.kpi-price-chg-sm {
	font-size: var(--fs-kpi-price-chg-sm);
}

.kpi-ext-label {
	font-family: var(--fm);
	font-size: var(--fs-kpi-ext-label);
	color: var(--amber);
	font-weight: 500;
	letter-spacing: 0.05em;
	/* Bisher nur zufällig versal, wenn ein Ancestor (.kpi-l, .iphone-symbol)
	   text-transform:uppercase mitbrachte - im Sparkline-Tooltip fehlte so
	   ein Ancestor, daher blieb "Pre"/"After"/"Overnight" dort klein/gross
	   gemischt (28.08.2026). Jetzt direkt auf der Klasse, unabhängig von
	   Ancestors und von der Schreibweise der JS-Strings (js/state.js,
	   js/charts.js). */
	text-transform: uppercase;
	background: var(--amber-bg);
	padding-block: 1px;
	padding-inline: 5px;
	border-radius: var(--radius);
	/* Sitzt auf der eigenen --amber-bg-Pille, nicht auf --bg - .kpi-price's
	   text-shadow (Farbe = --bg) würde hier sichtbar nicht verschmelzen,
	   sondern einen unpassenden Halo um den Amber-Text erzeugen (Opus-5-
	   Review 08.08.2026). */
	text-shadow: none;
}
.kpi-ext-label-sm {
	font-size: var(--fs-kpi-ext-label-sm);
}
/* Sparkline-Klick-"History"-Fixierung (State.pin(), js/state.js,
   Nutzerwunsch 19.08.2026) - dieselbe Pillen-Form wie .kpi-ext-label, aber
   in Blau statt Amber, damit sie sich klar vom Pre/After-Label unterscheidet
   (--blue/--blue-bg, css/tokens.css, bereits an anderer Stelle als Label-
   Farbe genutzt, siehe css/tables.css). */
.kpi-ext-label-history {
	color: var(--blue);
	background: var(--blue-bg);
}
/* Overnight-Session (Blue Ocean ATS via Alpaca, api/lib/market.php) - selbe
   Blau-Pille wie .kpi-ext-label-history, damit sie sich klar vom Amber
   Pre/After-Label unterscheidet. */
.kpi-ext-label-overnight {
	color: var(--blue);
	background: var(--blue-bg);
}

/* Zeigt an, wenn fetch_live_quote() (api/lib/market.php) während der
   regulären Handelszeit auf CNBC zurückgefallen ist, weil Finnhubs eigene
   Quote veraltet war - siehe finnhub_stale_seconds. Bis das Chart-Modul
   (Nutzerwunsch 10.08.2026) aus der KPI-Kachel gelöst wurde, sass dieses
   Badge absolut positioniert auf der jetzt entfernten Sparkline-Fläche -
   jetzt normales Flow-Element unter der Range-Zeile, kein eigener
   Positionierungskontext mehr nötig. */
.kpi-fallback-badge {
	display: inline-block;
	margin-top: 6px;
	font-family: var(--fm);
	font-size: var(--fs-kpi-fallback-badge);
	color: var(--amber);
	font-weight: 600;
	letter-spacing: 0.05em;
	background: var(--amber-bg);
	padding-block: 1px;
	padding-inline: 5px;
	border-radius: var(--radius);
	/* Gleicher Grund wie .kpi-ext-label oben: eigene --amber-bg-Pille, nicht
	   --bg - geerbter text-shadow würde hier fehlfarbig sein. */
	text-shadow: none;
}

/* ── TAGES-NAVIGATION UNTER DEM INTRADAY-CHART ────────────────────────────────
   js/intraday-day-nav.js. Bewusst dieselbe Optik wie der Range-Switcher im
   Analyse-Tab (.tf, css/views.css) statt einer eigenen Sprache - es ist
   dieselbe Sorte Bedienelement (Zeitraum wechseln), nur tageweise.

   Im Live-Modus steht hier NUR das linke Chevron (Nutzerentscheid 03.09.2026):
   kein Datum, kein Prozentwert - der aktuelle Tag braucht keine Beschriftung,
   und die Tagesperformance steht ohnehin in der Kurs-KPI darüber. Erst beim
   Zurückblättern kommen Datum, rechtes Chevron und "Heute" dazu; die
   Prozentzahl des gewählten Tages erscheint dann ebenfalls oben in der KPI
   (blaue "History"-Pille, .kpi-ext-label-history) statt hier. */
/* Klammert Chart + Leiste zu EINEM Flex-Kind von .page-below-kpi zusammen -
   dessen gap ist der grosse Sektionsabstand (~55px) und würde die Leiste
   sonst vom Chart lösen, zu dem sie gehört. Das Bleed des Charts
   (.intraday-chart--bleed, negative margin-inline) funktioniert darin
   unverändert: der Wrapper ist selbst volle Breite. */
.intraday-module {
	display: flex;
	flex-direction: column;
}
/* ── CHART-STEUERUNG (js/intraday-controls.js, Nutzerwunsch 10.09.2026) ──
   Icon-Trigger rechts über dem Chart, öffnet ein Anchor-positioniertes
   Dropdown (gleiches Muster wie Position/Holidays/View-Switch/Hamburger,
   css/views.css) mit Auto-Zoom-/NASDAQ-Schaltern sowie den unverändert
   eigenen Containern von Marktphasen-Schalter und Tages-Navigation. */
.intraday-controls {
	display: flex;
	justify-content: flex-end;
	/* Sitzt seit 10.09.2026 unter dem Chart statt darüber (Nutzerwunsch) -
	   margin-top statt margin-bottom für denselben kleinen Abstand. */
	margin-top: 4px;
}
.intraday-controls-dropdown {
	position: fixed;
	display: flex;
	flex-direction: column;
	gap: 16px;
	background: var(--bg-hover);
	border-radius: var(--radius);
	padding: 14px;
	min-width: 220px;
	max-width: calc(100vw - 32px);
	z-index: var(--z-dropdown);
	overflow: auto;
}
/* Ein Abschnitt = eine Überschrift + ihre Schalter (Nutzerwunsch 10.09.2026:
   "saubere Überschriften" statt einer einzigen ungegliederten Liste). Der
   grössere Gap des Panels oben trennt Abschnitte voneinander, dieser hier
   hält Überschrift + Inhalt eines Abschnitts eng zusammen. */
.ic-section {
	display: flex;
	flex-direction: column;
	gap: 10px;
}
.ic-section-title {
	font-size: var(--fs-label-sm);
	font-weight: 500;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--t3);
}
/* Switch VOR dem Label (Nutzerwunsch 10.09.2026) - reine Reihenfolge im
   Markup, kein CSS-`order` nötig. */
.ic-switch-row {
	display: flex;
	align-items: center;
	gap: 10px;
	font-size: var(--fs-label-sm);
	color: var(--t2);
	cursor: pointer;
}
/* Eigene, minimale Switch-Optik statt einer nackten Checkbox - kein
   generisches .switch/.toggle existiert bereits im Projekt (Admin-Push-
   Einstellungs-Switches wurden mit dem Feature selbst entfernt, s. AGENTS.md
   "Push notifications are single-subscriber-hardcoded"). Die Checkbox selbst
   bleibt im DOM (a11y, Klick-Ziel) und wird nur visuell versteckt. */
.ic-switch {
	position: relative;
	display: inline-block;
	width: 34px;
	height: 20px;
	flex: none;
}
.ic-switch input {
	position: absolute;
	inset: 0;
	margin: 0;
	opacity: 0;
	cursor: pointer;
}
.ic-switch-track {
	position: absolute;
	inset: 0;
	background: var(--bg3);
	border-radius: 999px;
	transition: background var(--dur-fast);
}
.ic-switch-track::before {
	content: '';
	position: absolute;
	top: 2px;
	left: 2px;
	width: 16px;
	height: 16px;
	border-radius: 50%;
	background: var(--bg);
	transition: transform var(--dur-fast);
}
.ic-switch input:checked + .ic-switch-track {
	background: var(--t1);
}
.ic-switch input:checked + .ic-switch-track::before {
	transform: translateX(14px);
}
.ic-switch input:focus-visible + .ic-switch-track {
	outline: 2px solid var(--t1);
	outline-offset: 2px;
}
/* ── MARKTPHASEN-SCHALTER (js/intraday-phase-toggle.js, Nutzerwunsch
   08.09.2026, seit 10.09.2026 im Dropdown, seit demselben Tag vertikale
   Switch-Zeilen statt horizontaler Text-Buttons) ── jede Phase ein eigener
   <button> mit demselben .ic-switch/.ic-switch-track-Look wie Auto-Zoom/
   NASDAQ oben, aber ohne <input> (s. Docblock an ensureSkeleton(),
   js/intraday-phase-toggle.js: ein Klick muss erst die Kaskaden-Invariante
   auswerten, bevor der neue Zustand feststeht). Echte <button>s statt
   Radio+Label, weil ein Toggle einen echten Redraw (Achse + Punkte,
   js/charts.js) auslösen muss, keinen reinen CSS-Zustand. */
.intraday-phase-toggle {
	display: flex;
	flex-direction: column;
	gap: 10px;
}
.intraday-phase-toggle:empty {
	display: none;
}
.ipt-btn {
	appearance: none;
	background: none;
	border: none;
	padding: 0;
	font: inherit;
	display: flex;
	align-items: center;
	gap: 10px;
	cursor: pointer;
}
.ipt-label {
	font-size: var(--fs-label-sm);
	color: var(--t2);
}
.ipt-btn .ic-switch-track {
	background: var(--bg3);
}
.ipt-btn.active .ic-switch-track {
	background: var(--t1);
}
.ipt-btn.active .ic-switch-track::before {
	transform: translateX(14px);
}
/* Gesperrt ist eine Phasen-Zeile nur bei aktivem Auto-Zoom/Aktuelle Phase
   (s. js/intraday-phase-toggle.js) - optisch weiterhin im jeweiligen
   an/aus-Zustand, aber nicht klickbar. Bewusst KEIN :hover-Effekt auf diesen
   Buttons (anders als sonst auf Desktop-Affordanzen üblich, s.
   .claude/rules/frontend.md): der Cursor bleibt nach einem Klick meist auf
   dem Button stehen (Nutzerfund 09.09.2026, ursprünglich für die horizontale
   Fassung notiert, gilt strukturell weiter). Reduzierte Opacity NUR auf dem
   Switch, nicht auf dem Label (Nutzerwunsch 10.09.2026: "klar als nicht
   schaltbar aussehen", zweiter Anlauf: "Schrift normal lassen, Button etwas
   mehr durchsichtig") - der erste Anlauf hatte opacity auf den ganzen Button
   gelegt, das machte auch den Text unnötig blass. */
.ipt-btn.locked {
	cursor: default;
}
.ipt-btn.locked .ic-switch {
	opacity: 0.3;
}
/* Ein gesperrter INAKTIVER Switch (jede der vier Phasen kann das sein,
   sobald Auto-Zoom/Aktuelle Phase sie gerade ausblendet) würde sonst die
   ohnehin blasse --bg3-Spur zusätzlich auf 30% Opacity abdunkeln und
   praktisch unsichtbar werden (Nutzerfund 10.09.2026: "im moment sieht man
   den nicht gut") - eigene, kräftigere Basisfarbe NUR für diesen Fall, damit
   sie nach der 0.3-Opacity oben noch erkennbar bleibt. Aktiv-gesperrt bleibt
   unverändert bei --t1 aus der .active-Regel oben. */
.ipt-btn.locked:not(.active) .ic-switch-track {
	background: var(--t2);
}
/* ── TAGES-NAVIGATION (js/intraday-day-nav.js) ── seit 10.09.2026 im Dropdown
   unter eigener Überschrift, Chevrons permanent sichtbar statt nur bei Hover
   (Nutzerwunsch: "nicht mehr nur on hover sichtbar", "chevrons sauber in
   buttons"). Löst das frühere, für die volle Chart-Breite gedachte Design ab
   (grosse unsichtbare Trefferfläche + Hover-Reveal, Bleed-Trick bis an
   --page-pad) - im schmalen, gepolsterten Dropdown-Panel ergab das nur noch
   unmotiviert wirkenden Leerraum. Datum ist jetzt IMMER sichtbar (auch live),
   nicht mehr nur im History-Modus. */
.intraday-day-nav {
	display: flex;
	align-items: center;
	gap: 8px;
}
.idn-date {
	flex: 1;
	font-family: var(--fm);
	/* Tabellenziffern (Nutzerwunsch 10.09.2026: "monospaced wochentag") -
	   dieses Projekt hat keine separate Monospace-Type, --fm ist bereits
	   Inter + tabular-nums als Ersatz dafür (s. css/tokens.css-Docblock).
	   Wirkt aber nur auf Ziffern, nicht auf den Wochentag - der Rest der
	   Breitenfixierung steckt in .idn-wd unten. */
	font-variant-numeric: tabular-nums;
	font-size: var(--fs-label-sm);
	color: var(--t2);
}
/* Eigene, breitenfeste Zelle für den Wochentag (Nutzerfund 10.09.2026).
   ECHTES `width`, nicht `min-width` - der eigentliche Grund, warum jeder
   bisherige Versuch (min-width: 1.7em, dann 2ch) weiterhin sprang: eine
   min-width ist nur eine UNTERGRENZE, sie verhindert nie, dass die Box
   WEITER wird, wenn der Inhalt von sich aus breiter ist. Genau das passiert
   hier praktisch immer - `ch` bemisst sich an der Breite der Ziffer "0", die
   in Inter schmaler ist als die Grossbuchstaben in "Mo"/"Mi"/"Do" - die
   Box wuchs also bei jedem Kürzel unterschiedlich über ihre eigene
   min-width hinaus, exakt das Springen, das damit behoben werden sollte.
   Mit fester `width` ist die Zellbreite jetzt IMMER gleich, unabhängig vom
   Inhalt - 3ch mit Reserve für die deutschen Kurzformen (Mo/Di/Mi/Do/Fr,
   Handelstage, kein Sa/So nötig).

   KEIN `overflow: hidden` (Nutzerfund 10.09.2026, direkt im Anschluss: Text
   sass nicht mehr auf derselben Baseline wie der Rest des Datums) - ein
   inline-block ohne sichtbaren Overflow hat per Spec keine normale
   Baseline mehr (die aus der letzten Zeile seines Inhalts), der Browser
   fällt dann auf die untere Rand-Kante der Box zurück. Das verschob genau
   den Wochentag sichtbar gegenüber "10.09.2026" daneben. Ohne overflow:hidden
   bleibt die normale Text-Baseline erhalten; ein Overflow ist bei den fünf
   bekannten, kurzen Handelstag-Kürzeln in 3ch ohnehin nie zu erwarten. */
.idn-wd {
	display: inline-block;
	width: 3ch;
}
/* Echte Buttons mit eigener Fläche statt der früheren unsichtbaren
   Trefferfläche - `disabled` (nicht mehr `.idn-off`/visibility, s.
   js/intraday-day-nav.js) macht "kein weiterer Tag in diese Richtung"
   sichtbar statt unsichtbar. Flach wie `.icon-btn` (css/layout.css) - kein
   Rahmen/Hintergrund am Button selbst, aktiv/inaktiv/hover ausschliesslich
   über Textfarbe (design-system.md "Keine Hintergrundflächen irgendwo") -
   die ursprüngliche Fassung hatte hier einen eigenen gefüllten --bg3-Kasten
   (Nutzerfund 10.09.2026: "chevron buttons halten sich noch nicht an den
   button stil"). Grösse/Trefferfläche bleiben unverändert, nur die
   Hintergrundfläche fällt weg. */
.idn-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	flex: none;
	border: none;
	background: transparent;
	color: var(--t3);
	cursor: pointer;
	line-height: 0;
}
.idn-btn:disabled {
	opacity: 0.35;
	cursor: default;
	pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
	.idn-btn:hover {
		color: var(--t1);
	}
}
/* Grösse und Strichstärke bleiben entkoppelt (wie zuvor) - der viewBox ist 24
   Einheiten breit, eine stroke-width von 24/N ergibt bei einer
   Darstellungsgrösse von N Pixeln genau 1px auf dem Schirm. Kleiner als die
   vorherigen 34px, da das Icon jetzt in einer eigenen 28px-Fläche mit
   sichtbarem Hintergrund sitzt statt eine grosse unsichtbare Trefferfläche
   allein zu füllen. */
.idn-icon {
	--idn-icon-px: 16;
	width: calc(var(--idn-icon-px) * 1px);
	height: calc(var(--idn-icon-px) * 1px);
	stroke-width: calc(24 / var(--idn-icon-px));
	display: block;
}
.idn-today:disabled {
	opacity: 0.35;
	cursor: default;
	pointer-events: none;
}
