/* ═══════════════════════════════════════════════════
   AKTUELLES & TERMINE — Modal-Styles
   Scope: nur fuer-eltern.php via _aktuelles-termine.php
   ═══════════════════════════════════════════════════ */

/* ── KALENDER MODAL ── */
#kalModal { position: fixed; inset: 0; z-index: 500; pointer-events: none; overflow: hidden; }
#kalBackdrop {
    position: absolute;
    inset: 0;
    /* Blur & Tint statisch — backdrop-filter zu transitionieren ist eine
       der teuersten GPU-Operationen überhaupt (Vollbildfläche, bei jedem
       Öffnen/Schließen). Der Fade läuft allein über opacity; visuell
       identisch, Compositor-Last massiv geringer. visibility:hidden
       verhindert, dass die Blur-Ebene im Ruhezustand gemalt wird. */
    background: rgba(244, 247, 251, 0.68);
    backdrop-filter: blur(18px) saturate(145%);
    -webkit-backdrop-filter: blur(18px) saturate(145%);
    opacity: 0;
    visibility: hidden;
    transition:
        opacity 0.4s cubic-bezier(0.22,1,0.36,1),
        visibility 0.4s;
}

#kalModal.is-open #kalBackdrop {
    opacity: 1;
    visibility: visible;
    pointer-events: all;
}

#kalSheet {
    position: absolute;
    left: -1px;
    right: -1px;
    bottom: 0;
    height: 100dvh;
    background:
        radial-gradient(ellipse 82% 30% at 50% 0%, rgba(72,160,214,0.045) 0%, transparent 72%),
        radial-gradient(ellipse 64% 20% at 82% 100%, rgba(213,90,146,0.03) 0%, transparent 72%),
        linear-gradient(180deg, rgba(252,253,255,0.985) 0%, rgba(249,251,254,0.982) 38%, rgba(247,249,252,0.98) 100%);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border: 1px solid rgba(255,255,255,0.94);
    border-bottom: none;
    border-radius: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transform: translateY(105%);
    transition: transform 0.48s cubic-bezier(0.16,1,0.3,1);
    box-shadow:
        0 -36px 90px -24px rgba(15,23,42,0.16),
        0 0 0 1px rgba(255,255,255,0.72) inset;
    pointer-events: all;
}

#kalModal.is-open #kalSheet {
    transform: translateY(0);
}

@media (min-width: 540px) {
    #kalSheet {
        position: absolute;
        bottom: auto;
        left: 50%;
        right: auto;
        top: 50%;
        transform: translate(-50%, -44%) scale(0.95);
        width: min(420px, 92vw);
        border-radius: 24px;
        border-bottom: 1px solid rgba(255,255,255,0.94);
        height: min(600px, 82dvh);
        max-height: 82dvh;
        opacity: 0;
        transition:
            transform 0.44s cubic-bezier(0.16,1,0.3,1),
            opacity 0.3s ease;
    }


    #kalModal.is-open #kalSheet {
        transform: translate(-50%, -50%) scale(1);
        opacity: 1;
    }
}
.kal-handle { width: 36px; height: 4px; background: rgba(0,0,0,0.1); border-radius: 9999px; margin: 12px auto 0; flex-shrink: 0; }
@media (min-width: 540px) { .kal-handle { display: none; } }
.kal-sh-header { padding: calc((var(--kal-sh-h) - var(--kal-sh-leaf)) / 2) 20px; padding-inline-start: calc(26px - var(--kal-sh-leaf) * 1.2 / 34); display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid rgba(255,255,255,0.7); flex-shrink: 0; background: linear-gradient(135deg, rgba(72,160,214,0.07) 0%, rgba(213,90,146,0.05) 50%, rgba(255,255,255,0.2) 100%); transition: background 0.32s ease; }
#kalModal.search-open .kal-sh-header { background: linear-gradient(135deg, rgba(72,160,214,0.11) 0%, rgba(213,90,146,0.07) 60%, rgba(127,177,78,0.04) 100%); border-bottom-color: rgba(72,160,214,0.12); }

/* ── MARKENZONE: Kalenderblatt + Titelblock ──
   Zwei Token, eine Zwangsbedingung: --kal-sh-h ist die Gesamthöhe des
   Headers (gemessen 74px), --kal-sh-leaf die Höhe des Blatts. Das Padding
   ist daraus abgeleitet — (Gesamt − Blatt) / 2 — statt fest notiert. Die
   Kopfzeile kann beim Vergrößern des Blatts deshalb gar nicht wachsen: sie
   ist mathematisch an 74px gebunden, egal welchen Wert leaf trägt.
   BREITENBILANZ, der eigentliche Grund für das clamp: Der Header teilt sich
   in vier feste Posten — Padding 2×20, Blatt, Gap, Aktionsblock 34+8+34=76.
   Was übrig bleibt, muss die Titelspalte tragen, und deren breitestes
   Element ist nicht der Titel, sondern die Subline mit gemessenen 142.67px.
   Bei festen 52px Blatt blieben auf 320px Viewport nur 4.45px Reserve. Das
   ist zu wenig: bricht die Subline um, wächst der Header nicht mit, weil die
   Titelspalte eine feste Höhe trägt — der Text liefe stattdessen über die
   Kanten. Das Blatt schrumpft deshalb auf schmalen Geräten, und das Padding
   wächst im Gegenzug. Die Höhe bleibt in jeder Zone exakt 74px, die kleinste
   Reserve über 320 bis 1440px beträgt 33.21px.
   Die clamp-Grenzen tragen bewusst nicht die üblichen Geometriefaktoren:
   nach oben ist bei 55.64 Schluss, weil sonst das Padding unter 9px fiele,
   nach unten bei 40, weil darunter die Zahl gegen den Titel zu klein wird.
   Beide Enden sind hart begründet, nicht skaliert. Klemmpunkte 309.1 und
   430.0px — die obere Grenze greift damit exakt auf der Referenzbreite.
   Der Gap ist nicht mehr aus der Blatthöhe abgeleitet, sondern aus dem
   Kartenraster: 14px ist der Abstand zwischen Bubble und Titel in
   .kal-item, abzüglich des rechten Korpusversatzes von 1.96px.
   KANTENFLUCHT, beide Seiten des Blatts: Links sitzt die sichtbare Kante
   des Korpus auf 26px, dort steht auch die Kante der Kalenderblätter in den
   Karten (margin 10 + Rahmen 1 + Innenabstand 15). Rechts ist der Korpus
   exakt 54px breit — die Breite von .kal-bubble — und der Titel beginnt
   dadurch auf 94px, genau wie die Artikelüberschriften darunter. Der Korpus
   beginnt in der viewBox nicht bei 0, sondern bei x = 1.2 von 34 Einheiten
   Höhe; dieser Versatz steckt in beiden calc, damit die Flucht auf der
   Grafikkante liegt und nicht auf dem Elementrand.
   Die Höhe ist deshalb fest statt fluid: Breite und Höhe des Blatts hängen
   über die viewBox zusammen, eine mitwachsende Höhe würde die Breite
   mitziehen und die 54px-Flucht auf allen Geräten außer 430 brechen.
   Getrennt steuern ließen sie sich nur mit preserveAspectRatio="none", was
   Rundungen und Schrift verzerren würde. Die Breitensicherung für schmale
   Geräte übernimmt stattdessen der Subline-Breakpoint weiter unten.
   Rechts bleiben 20 — der Aktionsblock ist Bedienung, kein Inhalt, und
   gehört an den Sheet-Rand. */
.kal-sh-header { --kal-sh-h: 74px; --kal-sh-leaf: 55.64px; }
.kal-sh-brand { display: flex; align-items: center; gap: calc(14px - var(--kal-sh-leaf) * 1.2 / 34); min-width: 0; }
.kal-sh-cal {
    height: var(--kal-sh-leaf);
    width: calc(var(--kal-sh-leaf) * 35.3978 / 34);
    flex-shrink: 0; display: block;
    filter: drop-shadow(0 1px 1.5px rgba(30,41,59,0.10));
    transition: transform 0.34s cubic-bezier(0.34,1.56,0.64,1);
}
.kal-sh-cal svg { display: block; width: 100%; height: 100%; }
@media (hover: hover) and (pointer: fine) {
    .kal-sh-brand:hover .kal-sh-cal { transform: translateY(-1.5px) rotate(-2.6deg); }
}
@media (prefers-reduced-motion: reduce) { .kal-sh-cal { transition: none; } }
/* Zweite Sicherung gegen die feste Spaltenhöhe: selbst wenn die Rechnung
   oben auf einem unbekannt schmalen Gerät nicht aufgeht, darf keine der
   beiden Zeilen umbrechen — sie würde sonst aus der Spalte herauslaufen,
   statt den Header wachsen zu lassen. */
.kal-sh-title-wrap { display: flex; flex-direction: column; justify-content: center; gap: 2px; min-width: 0; height: var(--kal-sh-leaf); }
.kal-sh-title-wrap > * { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.kal-sh-title { font-size: 1.08rem; font-weight: 900; color: #1E293B; display: flex; align-items: center; gap: 10px; letter-spacing: -0.02em; }
/* Kein Grün mehr im Verlauf: der dritte Stop lag auf 100 % und damit exakt
   auf dem letzten Buchstaben von AMINA — seit "· Wien" entfallen ist, lief
   der Markenname in einen Olivton aus. Jetzt Blau zu Pink, und das Pink ist
   bei 88 % voll erreicht, damit das letzte Wort geschlossen in einer Farbe
   steht statt im Übergang zu enden.
   align-self ist hier keine Kosmetik, sondern Voraussetzung: background-clip
   spannt den Verlauf über die BOX, nicht über den Text. Als Block wäre die
   Box so breit wie die Spalte — sobald "Kindergruppen" unter 340px entfällt,
   säße das kurze "AMINA" allein im linken, rein blauen Drittel und das Pink
   käme nie an. Auf Inhaltsbreite geschrumpft läuft der Verlauf in jeder
   Fassung über den vollen Text. */
.kal-sh-subtitle { align-self: flex-start; max-width: 100%; font-size: 0.55rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.22em; background: linear-gradient(90deg, #48A0D6 0%, #D55A92 88%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; opacity: 0.85; }
/* Unter 360px trägt nur noch der Markenname. Die Grenze liegt höher als
   früher, weil das Blatt jetzt fest 57.93px breit ist statt auf schmalen
   Geräten mitzuschrumpfen — der Preis für die 54px-Flucht zu den Karten.
   Gerechnet: Bei 340px blieben der Titelspalte 150px bei einem Bedarf von
   142.67 für die lange Fassung, also 7.33px Reserve. Zu wenig. Mit der
   kurzen Fassung ab 360 abwärts liegt die kleinste Reserve im gesamten
   Bereich bei 27.33px (bei genau 360 mit langer Fassung). Die Ellipsis
   greift rechnerisch erst unterhalb von 292px — unter jedem realen Gerät.
   Der Puffer ist bewusst, weil das Auslassungszeichen hier unsichtbar
   bliebe: -webkit-text-fill-color ist transparent, die Kürzung sähe nach
   hartem Abschnitt aus statt nach Kürzung. */
@media (max-width: 359.98px) { .kal-sh-sub-lang { display: none; } }



.kal-close-btn { width: 34px; height: 34px; border-radius: 50%; border: none; background: rgba(213,90,146,0.08); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s, transform 0.3s cubic-bezier(0.34,1.56,0.64,1); -webkit-tap-highlight-color: transparent; color: #D55A92; }
.kal-close-btn:hover { background: rgba(213,90,146,0.15); transform: rotate(90deg) scale(1.1); }
.kal-close-btn:focus { outline: none; }
.kal-close-btn:focus-visible { outline: 2px solid rgba(213,90,146,0.4); outline-offset: 2px; }

.kal-layers { flex: 1; overflow: hidden; position: relative; min-height: 0; }
#kalLayer1 { position: absolute; inset: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: none; touch-action: pan-y; -webkit-overflow-scrolling: touch; padding-bottom: calc(env(safe-area-inset-bottom, 16px) + 16px); }
body.kal-modal-open { overflow: hidden; }
html.kal-modal-open { overflow: hidden; }
#kalLayer2 {
    position: fixed;
    top: 0; left: 0; width: 100%; height: 100%;
    z-index: 600;
    background: #fff;
    transform: translateX(100%);
    transition: transform 0.42s cubic-bezier(0.16,1,0.3,1), visibility 0s linear 0.42s;
    visibility: hidden;
    display: flex; flex-direction: column;
    /* overscroll-behavior gehört an den Scroller, nicht hierher: dieses
       Element scrollt selbst nicht, die Angabe blieb wirkungslos. Sie
       sitzt jetzt auf .kal-l2-body. */
    -webkit-overflow-scrolling: touch;
}
#kalLayer2.is-open {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.42s cubic-bezier(0.16,1,0.3,1), visibility 0s linear 0s;
}
/* will-change nur solange sich die Ebene tatsächlich bewegt — als Teil
   von .is-open hielt es einen Compositor-Layer über die gesamte Lesedauer
   offen, obwohl er nur für die 420 ms der Ein- und Ausfahrt gebraucht
   wird. Gesetzt beim Zustandswechsel, gelöst per transitionend. */
/* Der Container ist reiner Landepunkt für den Fokus (tabindex="-1"),
   per Tastatur nicht erreichbar und braucht deshalb keinen Ring. Die
   sichtbaren Fokus-Zustände der bedienbaren Elemente darin bleiben
   unberührt. */
#kalLayer2:focus { outline: none; }
#kalLayer2.is-moving { will-change: transform; }
#kalLayer2.is-swiping { transition: none; will-change: transform; visibility: visible; }
/* ── Navigationsleiste Layer 2 (Large-Title-Prinzip) ──────────────────
   Die Leiste liegt als Overlay ÜBER dem Scroll-Body statt als Flex-Item
   davor: dadurch schiebt sich das Hero-Bild beim Scrollen sichtbar unter
   die Leiste, statt an einer harten Kante abgeschnitten zu werden.
   Fläche, Hairline und der kleine Kopftitel sind im Ruhezustand
   unsichtbar und erscheinen erst nach echtem Scroll — gesteuert über
   .is-scrolled / .is-titled, gesetzt von zwei IntersectionObservern
   (kalNavInit), niemals über Scroll-Listener.
   Sämtliche Maße hängen an einer einzigen Einheit (--kal-l2-u), damit
   Buttongröße, Innenabstand und die daraus berechnete Body-Reserve
   (--kal-l2-hh) über jede Viewportbreite proportional gekoppelt bleiben.
   Das seitliche 16px-Padding bleibt bewusst fix: es ist strukturell an
   das Body-Padding gekoppelt, jede Abweichung bräche die Fluchtlinie
   zwischen Zurück-Button und Artikeltext.

   Genau dieses Maß steht deshalb als --kal-l2-pad an einer einzigen
   Stelle. Es ist keine Dekoration, sondern die Fluchtlinie des ganzen
   Moduls: das Body-Padding, der seitliche Ausbruch von Hero, Wellenfuß
   und Fußbild und der linke Anschlag des Artikelmotivs sind dasselbe
   Maß und dürfen sich nie auseinanderentwickeln. Bewusst ohne clamp():
   ein mitwachsender Wert würde die Fluchtlinie zwischen Zurück-Button
   und Artikeltext viewportabhängig verschieben. */
#kalLayer2 {
    --kal-l2-pad: 16px;
    --kal-l2-u: clamp(8.6px, 2.79vw, 15px);
    --kal-l2-btn: calc(var(--kal-l2-u) * 3);
    --kal-l2-hh: calc(var(--kal-l2-btn) + var(--kal-l2-u) * 2 + env(safe-area-inset-top, 0px));
}
.kal-l2-header {
    position: absolute;
    top: 0; left: 0; right: 0;
    z-index: 12;
    display: flex;
    align-items: center;
    gap: var(--kal-l2-u);
    padding: var(--kal-l2-u) 16px;
    padding-top: calc(var(--kal-l2-u) + env(safe-area-inset-top, 0px));
    pointer-events: none;
}
.kal-l2-header > * { pointer-events: auto; }
/* Milchglasfläche samt Hairline als eine Einheit. Einzige animierte
   Eigenschaft ist opacity (Compositor-only); im Ruhezustand opacity 0,
   wodurch Safari das Backdrop gar nicht erst berechnet. */
.kal-l2-header::before {
    content: "";
    position: absolute;
    inset: 0;
    background: rgba(255,255,255,0.82);
    backdrop-filter: blur(18px) saturate(1.7);
    -webkit-backdrop-filter: blur(18px) saturate(1.7);
    box-shadow: inset 0 -1px 0 rgba(15,23,42,0.07);
    opacity: 0;
    transition: opacity 0.32s ease;
    pointer-events: none;
}
.kal-l2-header.is-scrolled::before { opacity: 1; }
/* ── KEIN AUSBLENDSAUM UNTER DER LEISTE ───────────────────────────────────
   Hier stand ein weisser Verlauf unter der Kopfleiste, der den Artikeltext
   ueber zwei Zeilenhoehen ins Weiss auslaufen liess. Er ist auf
   ausdruecklichen Wunsch entfernt worden: Auf einem Artikel mit dunklem
   randlosen Heldenbild legte er sich als heller Schleier ueber das Bild und
   nahm ihm die Farbe.
   Bewusst ersatzlos und nicht etwa auf Artikel ohne Heldenbild eingegrenzt —
   ein Saum, der mal da ist und mal nicht, waere schwerer nachzuvollziehen
   als gar keiner. Die Folge ist bekannt und akzeptiert: Der Text verschwindet
   beim Scrollen an der Unterkante der Milchglasflaeche mit einer harten
   Kante, statt sich zu verlieren. Die Milchglasflaeche selbst
   (.kal-l2-header::before) bleibt unangetastet, ebenso der untere
   Ausblendsaum ueber dem Textende (.kal-l2-body::after). */
/* overflow-x: hidden ist hier kein Schoenheitsstrich, sondern Pflicht.
   Ohne Angabe rechnet der Browser overflow-x wegen overflow-y: auto
   ebenfalls auf auto — und sobald ein Kind seitlich hinausragt, entsteht auf
   iOS das horizontale Gummiband. Genau das tut das Artikelmotiv: Es ragt
   links bewusst bis zur Modalkante und bei einer Drehung schwenkt seine
   Huelle zusaetzlich zur Seite. hidden schneidet beides sauber an der
   Modalkante ab, ohne die vertikale Scrollflaeche anzutasten. Die
   randlosen Hero- und Fussbilder reichen mit ihrem negativen Rand exakt an
   diese Kante und bleiben deshalb unangetastet. */
.kal-l2-body { flex: 1; position: relative; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; padding: calc(var(--kal-l2-hh) + 4px) var(--kal-l2-pad) calc(24px + env(safe-area-inset-bottom, 0px)); -webkit-overflow-scrolling: touch; }
/* Scroll-Messpunkt der Navigationsleiste: bewusst absolut positioniert
   und damit vollständig aus dem Fluss. Als Fluss-Element würde sein
   Margin mit dem negativen Margin eines randlosen Heros kollabieren —
   Ergebnis wäre exakt jene Haarlinie zwischen Leiste und Bild, die wir
   an Wellen-Nähten bereits kennen. Absolut positioniert scrollt er
   trotzdem korrekt mit, weil er am Padding-Ursprung des Scrollers hängt. */
#kalL2Top { position: absolute; top: calc(var(--kal-l2-hh) + 4px); left: 0; width: 100%; height: 2px; pointer-events: none; }
/* ── ARTIKELWECHSEL PER WISCH ──
   Beim Wischen nach links wechselt der Inhalt, nicht die Ebene. Deshalb
   bewegt sich hier ausschließlich der Artikelbereich — Header, Titel,
   Buttons und Fortschrittsbalken stehen still. Der Rechts-Wisch verlässt
   dagegen die ganze Ebene und schiebt sie weiterhin komplett weg.

   Der Wechsel selbst ist ein kurzer Fort-und-Herein-Übergang statt eines
   harten Schnitts: Der alte Artikel zieht sich nach links zurück und
   verblasst, der neue kommt von rechts. 150ms hin, 200ms zurück — lang
   genug, um als Bewegung gelesen zu werden, kurz genug, um beim schnellen
   Durchblättern nicht zu bremsen.

   .is-ziehend schaltet die Transition ab, solange der Finger liegt: Sonst
   liefe jede Fingerbewegung gegen eine nachlaufende Animation, was genau
   die Unruhe erzeugt, die dieser Umbau beseitigt. */
.kal-l2-body { transition: transform 0.2s cubic-bezier(0.22,1,0.36,1), opacity 0.2s ease; }
.kal-l2-body.is-ziehend { transition: none; }
.kal-l2-body.is-raus { transition: transform 0.15s ease-in, opacity 0.15s ease-in; transform: translateX(-22px); opacity: 0; }
.kal-l2-body.is-rein { transition: none; transform: translateX(26px); opacity: 0; }
@media (prefers-reduced-motion: reduce) {
    .kal-l2-body, .kal-l2-body.is-raus, .kal-l2-body.is-rein { transition: none; transform: none; opacity: 1; }
}

/* ── LESEFORTSCHRITT ──────────────────────────────────────────────────────
   Ohne JavaScript: eine CSS-Scroll-Timeline bindet den Balken an die
   Scrollposition von .kal-l2-body. Kein Scroll-Listener, kein rAF, keine
   Arbeit auf dem Main Thread — die Skalierung passiert im Compositor.

   Der Balken sitzt IM Header und nicht im Scroll-Container. Das ist der
   entscheidende Unterschied zu einer sticky-Lösung: Er ist ein absolut
   positioniertes Kind des Headers und liegt mit bottom:0 an dessen
   Unterkante — es gibt also keinen Offset, der zur Safe-Area, zur
   Scrollport-Oberkante oder zu einem transformierten Vorfahren passen
   müsste. Die Position ergibt sich aus der Struktur statt aus einer
   Rechnung.

   Möglich wird das durch timeline-scope: .kal-l2-body benennt seine
   Scroll-Timeline, #kalLayer2 macht den Namen für den gesamten Teilbaum
   sichtbar, und der Balken im Header greift ihn ab. Ohne diesen Umweg
   wäre eine Timeline nur für Nachfahren des Scrollers nutzbar.

   Kein will-change: Scroll-Timelines laufen ohnehin im Compositor.
   Ohne Unterstützung bleibt der Balken per display:none unsichtbar.

   prefers-reduced-motion bleibt unangetastet: der Balken bewegt sich
   ausschließlich während des Scrollens und ist damit Statusanzeige statt
   Animation. */
#kalLayer2 { timeline-scope: --kal-lesefortschritt; }
.kal-l2-body { scroll-timeline-name: --kal-lesefortschritt; scroll-timeline-axis: block; }
.kal-l2-header > .kal-l2-lesen {
    display: none;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
    height: 3px;
    /* Hellblau über Amina-Blau nach Hellgrün. Der Stop bei 50% ist hier
       nicht als Stütze nötig — Blau und Grün liegen im Farbkreis über
       Türkis benachbart, der Verlauf bleibt von selbst bunt (Mitte ≈ #69B496).
       Er sitzt trotzdem drin, weil das Amina-Blau als Ankerfarbe der Marke
       in der Mitte stehen soll statt sich wegzumischen.
       Die Endtöne sind bewusst nicht die hellsten möglichen: Auf dem weißen
       Artikelgrund fiele ein #8ED3F0 mit 1,65:1 praktisch aus. Die gewählten
       Werte liegen bei rund 2,1:1 und wirken hell, ohne zu verschwinden.
       3px statt 2px: Auf Retina ist eine 2px-Linie unter einem Milchglas-
       Header optisch fast nicht vorhanden — 3px liest sich als bewusstes
       Element, ohne schwer zu wirken. */
    background: linear-gradient(90deg, #63BEE6 0%, #48A0D6 50%, #8AC757 100%);
    border-radius: 0 3px 3px 0;
    transform: scaleX(0);
    transform-origin: 0 50%;
    pointer-events: none;
}
@keyframes kalLesenLauf { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@supports (timeline-scope: --x) {
    .kal-l2-header > .kal-l2-lesen {
        display: block;
        animation: kalLesenLauf linear both;
        animation-timeline: --kal-lesefortschritt;
    }
}
/* Scroll-Affordance — 1:1 vom Info-Modul (.oz-info-slide::after): weicher
   Fade am unteren Rand, solange Inhalt folgt; verschwindet am Textende
   (Toggle via IntersectionObserver auf den kalL2Ende-Marker). Sticky-Offset
   neutralisiert das untere Padding, damit der Schleier an der Sichtkante
   klebt; Layer 2 ist reinweiß — der Verlauf schließt nahtlos. */
.kal-l2-body::after {
    content: "";
    position: sticky;
    bottom: calc((24px + env(safe-area-inset-bottom, 0px)) * -1);
    display: block;
    height: 56px;
    margin-top: -56px;
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.96) 78%);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.4s ease;
}
.kal-l2-body.kal-fade::after { opacity: 1; }
/* ── Footer-Bild: dekorativer, randbündiger Abschluss des Artikels — Design
   1:1 vom Info-Modul (.oz-info-footer-img). Bricht seitlich exakt das
   Body-Padding aus (16px, kein Überstand) und neutralisiert das untere
   Padding samt Safe-Area, damit das Bild bündig mit der Unterkante des
   Modals abschließt. Rein optisch, nie anklickbar (pointer-events: none);
   steht im DOM nach dem kalL2Ende-Marker — siehe Render-Kommentar. */
.kal-det-footer {
    margin: clamp(1.6rem, 5.5vw, 2.6rem) calc(var(--kal-l2-pad) * -1) 0;
    line-height: 0;
    pointer-events: none;
    user-select: none;
}
/* Bündiger Abschluss OHNE Reststreifen: Margin-Kompensation funktioniert im
   Scroll-Container nicht (das untere Body-Padding bleibt als Scrollfläche
   bestehen — exakt der im Info-Modul dokumentierte Fall). Stattdessen wie
   dort: enthält der Body ein Footer-Bild, entfällt das untere Padding samt
   Safe-Area komplett; der Sticky-Fade wird auf die neue Kante genullt. */
.kal-l2-body:has(.kal-det-footer) { padding-bottom: 0; }
.kal-l2-body:has(.kal-det-footer)::after { bottom: 0; }
.kal-det-footer img {
    display: block;
    width: 100%;
    height: auto;
    user-select: none;
    -webkit-user-drag: none;
}
/* ── Weiche Welle am Bildkopf (Admin-Schalter) ───────────────────────
   Gegenstück zur Hero-Welle und deren exakte Spiegelung an der
   Horizontalen: dieselbe Kurve, dieselbe Höhe, dieselben clamp-Faktoren.
   Sie steht als erstes Kind im bestehenden Footer-Wrapper und zieht sich
   per negativem Margin über die Bildoberkante — anders als beim Hero
   braucht es dafür keinen neuen Container.
   Zwei Pixel Überlappung nach oben, aus demselben Grund wie unten am
   Hero: eine bündige Kante rutscht früher oder später auf eine halbe
   Gerätepixel-Grenze und lässt dort eine Haarlinie des Bildes stehen.
   Oben trifft Weiß auf Weiß, die Überlappung ist unsichtbar.
   z-index ist nötig, weil die Welle im DOM VOR dem Bild steht und ohne
   Stapelkontext von ihm überdeckt würde — beim Hero folgt sie dem Bild
   und liegt schon dadurch darüber. */
.kal-det-footer.has-wave {
    /* Halber Kopfabstand: die oberen rund 31% der Welle sind immer
       deckend weiß und wirken bereits als Abstand. Zusammen ergibt das
       wieder genau den Weg, den das Bild ohne Welle zum Text hätte. */
    margin-top: clamp(0.8rem, 2.75vw, 1.3rem);
}
.kal-det-footer-wave {
    display: block;
    position: relative;
    z-index: 1;
    width: 100%;
    height: clamp(28.8px, 9.3vw, 50px);
    margin-bottom: calc(clamp(28.8px, 9.3vw, 50px) * -1 + 2px);
    fill: #fff;
    pointer-events: none;
}
/* ── ANTWORT DER LISTE AUF EINEN TIPP IN DER ZEITACHSE ────────────────────
   Drei Ebenen mit eigener Zeit, die zusammen einen Moment ergeben statt
   eines Blinkens:

     AURA   — ein weicher farbiger Schein, der hinter der Karte aufblüht
              und wieder vergeht. Er trägt die Emotion.
     KONTUR — eine feine Linie, die von leicht außerhalb einrastet und
              stehen bleibt, während die Aura schon vergeht. Sie trägt die
              Information: DIESE Karte.
     LICHT  — ein schmaler Schimmer, der einmal quer über die Karte läuft.
              Er trägt die Wertigkeit.

   Bewegt werden ausschließlich transform und opacity — alle drei Ebenen
   liegen im Compositor, der Hauptthread bleibt frei. Die Aura nutzt einen
   konstanten Schatten; animiert wird nur ihre Deckkraft und ihr Maßstab,
   nie der Radius selbst.

   Warum ein eingefügtes Element statt Pseudo-Elementen: ::before und
   ::after sind auf .kal-item bereits für Pin-Glow, Entwurf- und
   Geplant-Marken vergeben. Ein eigenes Element kollidiert mit nichts,
   trägt drei Ebenen statt zwei und verschwindet nach dem Abspielen
   rückstandsfrei aus dem DOM — es bleibt keine Klasse zurück, die später
   mit etwas anderem in Konflikt geraten könnte.

   Die Kurve cubic-bezier(0.16,1,0.3,1) legt fast die gesamte Bewegung an
   den Anfang und lässt sie lange auslaufen. Das ist der Unterschied
   zwischen "es blinkt" und "es kommt zur Ruhe". */
.kal-tr-fx {
    position: absolute; inset: 0;
    border-radius: inherit;
    pointer-events: none;
    z-index: 5;
}
.kal-tr-aura {
    position: absolute; inset: 0;
    border-radius: inherit;
    box-shadow: 0 5px 20px 1px rgba(var(--kal-tr-rgb, 72,160,214), 0.5),
                0 0 9px 0 rgba(var(--kal-tr-rgb, 72,160,214), 0.3);
    opacity: 0;
    transform: scale(0.985);
    animation: kalTrAura 1.6s cubic-bezier(0.16,1,0.3,1) forwards;
}
@keyframes kalTrAura {
    0%   { opacity: 0;    transform: scale(0.985); }
    20%  { opacity: 0.9;  transform: scale(1.008); }
    55%  { opacity: 0.45; transform: scale(1.004); }
    100% { opacity: 0;    transform: scale(1.018); }
}
.kal-tr-line {
    position: absolute; inset: 0;
    border-radius: inherit;
    border: 1.5px solid rgba(var(--kal-tr-rgb, 72,160,214), 0.85);
    opacity: 0;
    transform: scale(1.035);
    animation: kalTrLine 1.6s cubic-bezier(0.16,1,0.3,1) forwards;
}
@keyframes kalTrLine {
    0%   { opacity: 0; transform: scale(1.035); }
    15%  { opacity: 1; transform: scale(1);     }
    64%  { opacity: 1; transform: scale(1);     }
    100% { opacity: 0; transform: scale(1);     }
}
/* Der Clip-Rahmen erbt die Rundung der Karte, damit das Licht exakt an
   ihren Kanten endet und nicht über die Ecken hinausläuft. */
.kal-tr-clip {
    position: absolute; inset: 0;
    border-radius: inherit;
    overflow: hidden;
}
.kal-tr-sheen {
    position: absolute; top: -40%; left: 0;
    width: 40%; height: 180%;
    background: linear-gradient(90deg,
        rgba(255,255,255,0) 0%,
        rgba(var(--kal-tr-rgb, 72,160,214), 0.14) 42%,
        rgba(255,255,255,0.52) 52%,
        rgba(var(--kal-tr-rgb, 72,160,214), 0.14) 62%,
        rgba(255,255,255,0) 100%);
    transform: translateX(-170%) skewX(-14deg);
    opacity: 0;
    animation: kalTrSheen 1.1s cubic-bezier(0.36,0.66,0.28,1) 0.1s forwards;
}
@keyframes kalTrSheen {
    0%   { opacity: 0; transform: translateX(-170%) skewX(-14deg); }
    16%  { opacity: 1; transform: translateX(-50%)  skewX(-14deg); }
    84%  { opacity: 1; transform: translateX(210%)  skewX(-14deg); }
    100% { opacity: 0; transform: translateX(300%)  skewX(-14deg); }
}
/* Ohne Bewegung bleibt die Aussage, aber ohne Wanderung und ohne
   Maßstabsänderung: die Kontur blendet auf und wieder ab. Aura und Licht
   entfallen — sie sind reine Bewegung. */
@media (prefers-reduced-motion: reduce) {
    .kal-tr-aura, .kal-tr-clip { display: none; }
    .kal-tr-line {
        transform: none;
        animation: kalTrLineRuhig 1.4s ease forwards;
    }
    @keyframes kalTrLineRuhig {
        0% { opacity: 0; } 12% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 0; }
    }
}

/* ── ZEITLEISTE ───────────────────────────────────────────────────────────
   Die Achse beginnt heute und endet beim letzten Termin. Jeder Punkt trägt
   sein Datum — Marken ohne Beschriftung sind Dekoration, keine Navigation.

   VERTIKALE AUSRICHTUNG — der Kern dieser Fassung:
   Alle Elemente der Bahn sitzen in einer Schiene fester Höhe (--hb-schiene)
   und werden darin per Flexbox zentriert. Kein einziges negatives Margin.
   Die frühere Fassung rechnete die Mitte über negative Margins nach, und
   eine davon war fehlerhaft: clamp(-4px, -1.165vw, -6.25px) hat MIN größer
   als MAX — CSS liefert dann immer MIN, unabhängig vom Viewport. Die Punkte
   schwebten dadurch zehn Pixel über der Linie. Mit einer zentrierenden
   Schiene kann dieser Fehler gar nicht mehr entstehen: Die Mitte ergibt
   sich aus dem Layout, nicht aus einer Rechnung.

   EINHEITLICHE GEOMETRIE:
   Alle Ereignispunkte haben dieselbe Größe und denselben Aufbau. Der
   Fokustermin unterscheidet sich ausschließlich durch einen Hof — nicht
   durch eine andere Bauform. Zuvor war er zusätzlich 25% größer, wodurch
   der grüne Punkt wie ein anderes System wirkte. Farbe zeigt die Kategorie,
   der Hof zeigt den Fokus; beides bleibt sauber getrennt.

   Alle Positionen kommen als fertige Prozentwerte aus PHP. */
.kal-hb {
    --hb-schiene: clamp(19px, 5.58vw, 28px);
    --hb-punkt:   clamp(8px, 2.33vw, 11.7px);
    --hb-einzug:  clamp(16px, 4.651vw, 23.4px);
    position: relative;
    padding: clamp(10.39px, 3.02vw, 15.19px) clamp(15.2px, 4.42vw, 22.24px) 0;
}

/* ── Kopfzeile ── */
.kal-hb-kopf {
    display: flex; align-items: center; gap: clamp(5.61px, 1.63vw, 8.2px);
    margin-bottom: clamp(11.21px, 3.26vw, 16.4px);
    width: 100%; padding: 0; border: none; background: none;
    font-family: inherit; text-align: left;
    opacity: 0;
}
#kalModal.is-open .kal-hb-kopf { animation: kalHbAuf 0.55s cubic-bezier(0.22,1,0.36,1) 0.1s both; }
@keyframes kalHbAuf { from { opacity: 0; transform: translateY(clamp(3.2px, 0.93vw, 4.68px)); } to { opacity: 1; transform: translateY(0); } }
.kal-hb-kopf.is-klick { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.kal-hb-kopf.is-klick:active { opacity: 0.6; }
/* Bauartgleich mit dem Fokuspunkt auf der Achse — gleiche Größe, gleicher
   Hof. Dadurch gehören beide sichtbar zusammen, ohne Verbindungslinie. */
.kal-hb-kopf-dot {
    position: relative; flex-shrink: 0;
    width: var(--hb-punkt); height: var(--hb-punkt);
    border-radius: 50%;
}
.kal-hb-kopf-dot::after {
    content: ''; position: absolute; inset: calc(var(--hb-punkt) * -0.30);
    border-radius: 50%; background: currentColor; opacity: 0.17;
}
.kal-hb-kopf-dot::before {
    content: ''; position: absolute; inset: calc(var(--hb-punkt) * -0.56);
    border-radius: 50%; background: currentColor; opacity: 0.07;
}
.kal-hb-kopf-titel {
    font-size: clamp(0.735rem, 3.419vw, 1.075rem); font-weight: 800;
    color: #1E293B; letter-spacing: -0.01em;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
/* Die Zeitangabe trägt die Kategoriefarbe des Termins, nicht fest Blau.
   Zuvor stand neben einem grünen Ausflug oder rosa Schließtagen eine blaue
   Pille — Punkt und Pille sprachen von verschiedenen Dingen. Jetzt bilden
   Punkt, Pille und die Marke auf der Achse eine Farbfamilie; das ist die
   Verbindung, die eine Verbindungslinie überflüssig macht.
   Die Schrift wird gegen Dunkelblau abgemischt, damit sie auf dem hellen
   Grund derselben Farbe lesbar bleibt. Rückfall ist Amina-Blau. */
.kal-hb-kopf-wann {
    flex-shrink: 0; white-space: nowrap;
    padding: clamp(1.62px, 0.47vw, 2.36px) clamp(6.4px, 1.86vw, 9.36px);
    border-radius: 99px;
    background: rgba(var(--hb-fokus-rgb, 72,160,214), 0.1);
    font-size: clamp(0.545rem, 2.535vw, 0.797rem); font-weight: 800;
    letter-spacing: 0.02em;
    color: rgb(var(--hb-fokus-rgb, 72,160,214));
    filter: saturate(1.1) brightness(0.78);
}
/* Gruppe in der Kopfzeile, nur bei mehreren Terminen. Box Wert für Wert wie
   die Zeitangabe daneben — Polsterung, Schrift, Radius —, damit beide Pillen
   exakt gleich hoch stehen und ihre clamp()-Grenzen bei denselben Breiten
   greifen. Farbe und Kontur wie die Gruppen-Pille der Karte: Kontur statt
   Füllung unterscheidet "für wen" von "wann". Der Abstand zwischen Wort und
   Ziffern in em koppelt sich an die Schrift und braucht keine eigene Kurve.
   Die Laufweite hinter dem letzten Buchstaben wird exakt zurückgenommen. */
.kal-hb-kopf-grp {
    flex-shrink: 0; display: inline-flex; align-items: baseline; gap: 0.3em;
    white-space: nowrap;
    padding: clamp(1.62px, 0.47vw, 2.36px) clamp(6.4px, 1.86vw, 9.36px);
    border-radius: 99px;
    font-size: clamp(0.545rem, 2.535vw, 0.797rem); font-weight: 800;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
    color: #475569;
    box-shadow: inset 0 0 0 1px rgba(100,116,139,0.22);
}
.kal-hb-kopf-grp-wort { text-transform: uppercase; letter-spacing: 0.1em; margin-inline-end: -0.1em; color: #64748b; }

/* ── Bahn ──
   Der seitliche Einzug ist der feste Innenabstand der Achse: Er gibt der
   Beschriftung des ersten und letzten Punktes Raum, ohne dass sie über den
   Rand läuft. */
.kal-hb-bahn {
    position: relative;
    height: calc(var(--hb-schiene) + clamp(13.6px, 3.953vw, 19.9px));
    margin: 0 var(--hb-einzug);
}
/* ── Symmetrie der Achsenenden ──
   Links endet die Achse im Heute-Punkt: Er sitzt mittig auf 0% und ragt
   über margin-left: calc(punkt / -2) um genau eine halbe Punktbreite über
   den Einzug hinaus. Rechts endet sie in der Pfeilspitze — und die lag
   nicht spiegelbildlich, sondern deutlich weiter innen.

   Ursache ist Leerraum in der viewBox des Pfeils: Sie ist 12 Einheiten
   breit, der Pfad reicht aber nur bis 7,6 und die runde Kappe bis 8,9.
   Rechts der sichtbaren Spitze stehen also 3,1/12 der Kastenbreite leer,
   bei 0,92 × Punkt Kastenbreite sind das 0,238 × Punkt. Statt einer
   halben Punktbreite Überstand hatte die Spitze 0,238 Punktbreiten
   Rückstand — der Unterschied zur linken Seite betrug damit 0,738 ×
   Punkt, auf der 430er-Basis rund 7,4px. Genau das war sichtbar.

   Der Einzug rückt rechts um exakt diesen Betrag ein. Der Faktor steht
   bewusst NICHT als gerundete Dezimalzahl (0,738) im calc(), sondern als
   vollständiger Ausdruck aus seinen Bestandteilen: die halbe Punktbreite
   des Heute-Punkts plus der leere viewBox-Anteil (3,1 von 12 Einheiten)
   der 0,92 × Punkt breiten Pfeilbox. Damit ist die Symmetrie mathematisch
   exakt und nicht auf drei Nachkommastellen genau — der Browser rechnet
   den Bruch selbst, ohne Rundungsrest.

   Damit ragt die Spitze wie der Heute-Punkt eine halbe Punktbreite über
   den Einzug hinaus; beide Enden stehen spiegelgleich, gerechnet an der
   linken Seite. Die viewBox selbst bleibt unangetastet — sie an den Pfad
   zuzuschneiden hätte die Skalierung und damit die auf 0,2 × Punkt
   gerechnete Strichstärke verschoben.

   Bewusst nur bei .hat-mehr: Ohne Pfeil endet die Achse im letzten
   Terminpunkt, der bereits eine halbe Punktbreite überragt — dort ist
   die Symmetrie schon gegeben, und der volle Einzug bleibt als Raum für
   die Beschriftung des letzten Punktes erhalten. Bei .hat-mehr liegt der
   letzte Punkt konstruktionsbedingt weit vor dem Achsenende, sein Label
   braucht diesen Raum also nicht.

   Der Rest bleibt bei var(--hb-einzug): 20,0 − 7,4 = 12,6px auf der
   430er-Basis, 16 − 5,9 = 10,1px bei 320px, 23,4 − 8,6 = 14,8px am
   oberen Anschlag — an keiner Breite negativ, an keiner Breite kippt die
   Bahn über den Rand von .kal-hb hinaus. */
.kal-hb.hat-mehr .kal-hb-bahn {
    margin-inline-end: calc(
        var(--hb-einzug)
        - var(--hb-punkt) / 2
        - var(--hb-punkt) * 0.92 * 3.1 / 12
    );
}
.kal-hb-linie {
    position: absolute; left: 0; top: 0; height: var(--hb-schiene);
    display: flex; align-items: center;
    transform: scaleX(0); transform-origin: 0 50%;
}
.kal-hb-linie::before {
    /* Die Linienstärke hängt an der Punktgröße statt fest bei 2px zu
       stehen. Als fester Wert wirkte sie auf einem 320px-Gerät ein Viertel
       so dick wie ein Punkt, auf einem großen nur ein Sechstel — die Achse
       sah damit auf kleinen Geräten grober aus. Bei 20% des Punkts bleibt
       das Verhältnis über jede Breite gleich. */
    content: ''; display: block; width: 100%;
    height: calc(var(--hb-punkt) * 0.2);
    border-radius: calc(var(--hb-punkt) * 0.2 / 2); background: rgba(148,163,184,0.5);
}
/* Früher blendete die Linie hier ins Nichts aus. Das las sich als
   verblassender Zierstrich, nicht als Fortsetzung. Jetzt läuft sie ohne
   Ausblendung durch bis in den Pfeil hinein.

   Der frühere Faktor 0,78 stammte aus einer Rechnung, die nur waagrecht
   dachte: Der Strich des Chevrons setzt bei 2,4/12 der viewBox an, die
   runde Kappe ragt um 1,3/12 nach links, macht 0,84 × Punkt bis zur
   rechten Kante — Überlappung ab 0,78. Nur sitzen diese Kappen an den
   Schenkelenden bei y=2,6 und y=11,4, also weit ober- und unterhalb der
   Linie. Auf der Höhe der Linie selbst (Mittelachse y=7, Dicke 2,61
   viewBox-Einheiten, also y 5,7 bis 8,3) beginnt der Chevron erst dort,
   wo die Unterkante des oberen Schenkels die Oberkante der Linie
   schneidet. Bei einer Steigung von 4,4/5,2 und einem senkrechten
   Halbabstand von 1,3/cos(40,2°) = 1,70 liegt dieser Punkt bei x = 4,05
   — umgerechnet 0,61 × Punkt vom rechten Rand. Die alte Linie endete bei
   0,78 und ließ dadurch rund 0,17 × Punkt Luft: die sichtbare Fuge
   zwischen Achse und Pfeil.

   0,58 setzt das Linienende 0,03 × Punkt hinter den Schnittpunkt — die
   Schenkel docken an den Ecken der Linie an wie die Widerhaken eines
   echten Pfeils an seinem Schaft, mit genug Überlappung, dass auf keinem
   Pixelraster eine Fuge aufblitzt, und zu wenig, als dass die doppelte
   Halbdeckung sichtbar würde (0,3px an einer ohnehin schräg
   kantengeglätteten Stelle).

   Die Rundung fällt an dieser Kante weg: Sie ist die Naht zum Pfeil,
   kein Linienende. Rund hätte sie zwei Kerben dort hinterlassen, wo die
   Schenkel andocken. Links bleibt sie erhalten. */
.kal-hb-linie.laeuft-aus::before {
    width: calc(100% - var(--hb-punkt) * 0.58);
    border-start-end-radius: 0;
    border-end-end-radius: 0;
}

/* ── Fortsetzung: Pfeil + Anzahl am rechten Achsenende ──
   Bauartgleich zu "Heute": gleiche Höhe, gleiche Grundlinie, unbunt.
   Die Angabe ist rechtsbündig statt mittig — mittig unter dem Pfeil wäre
   sie zur Hälfte über den Innenabstand der Bahn hinausgelaufen. */
.kal-hb-mehr {
    position: absolute; right: 0; top: 0;
    height: var(--hb-schiene);
    display: flex; align-items: center; justify-content: flex-end;
    /* Der Pfeil ist die Spitze der Linie, nicht ein Symbol daneben. Damit er
       so gelesen wird, muss er dieselbe optische Dichte haben: Die
       Strichstärke von 2,6 in der 12er-viewBox ergibt bei einer Pfeilbreite
       von 0,92 × Punkt exakt 0,2 × Punkt — also haargenau die Linienstärke
       (.kal-hb-linie::before). Das war schon richtig und bleibt.
       Der TON folgt jetzt nicht mehr der Linie, sondern der Startmarke:
       rgba(100,116,139,0.62) ist exakt der Wert von .kal-hb-heute::before.
       Damit trägt die Achse ein sauberes System — Anfangsmarke und
       Endmarke im selben Ton, die Verbindung dazwischen eine Stufe leichter.
       Eine frühere Fassung hatte den Pfeil bewusst auf den Linienwert
       gesetzt, weil er als eigenes dunkleres Bauteil las. Der Unterschied
       jetzt: Damals stand er allein dunkler da, heute korrespondiert er mit
       dem Heute-Punkt am anderen Ende. Dieselbe Farbe, andere Begründung.
       Der Grundton ist derselbe wie in der Termin-Pille (#64748B), nur mit
       Deckung 0,62 — Pille, Startmarke und Pfeil bleiben eine Familie. */
    color: rgba(100,116,139,0.62);
    pointer-events: none;
    opacity: 0;
}
.kal-hb-mehr svg {
    display: block;
    width: calc(var(--hb-punkt) * 0.92);
    height: calc(var(--hb-punkt) * 1.08);
}
.kal-hb-mehr i {
    /* Über der Achse statt darunter: Auf der unteren Grundlinie stand die
       Angabe direkt neben dem letzten Datum und las sich als dessen
       Fortsetzung ("10. Sep +1 Termin"). Oben gehört sie sichtbar zur
       Achse als Ganzes und nicht zu einem einzelnen Punkt. Den Platz dafür
       schafft .hat-mehr über die Kopfzeile — nichts überlagert etwas. */
    position: absolute; bottom: 100%; right: 0;
    /* Der Abstand zur Achse wandert von padding auf margin: Das padding
       gehört jetzt in die Pille hinein, der Abstand nach außen. Bei
       bottom:100% verschiebt margin-bottom den Rahmenkasten um genau
       diesen Betrag nach oben — ein negativer Wert setzt ihn also tiefer.
       Die Pille rückt um 1px (430er-Basis) in die Schiene hinein: Sie hat
       durch ihren Rahmen mehr optisches Gewicht als der frühere freie Text
       und stand dadurch zu hoch, fast schon losgelöst von der Achse. Bis
       zur Pfeilspitze bleiben rechnerisch 7,6px Luft — die Pille berührt
       nichts. Negative Werte drehen in clamp() die Reihenfolge: der
       kleinste Wert ist der betragsmäßig größte. */
    margin-bottom: clamp(-1.17px, -0.233vw, -0.8px);
    /* Haarlinien-Pille: innen fast weiß, Rahmen in der Schriftfarbe, aber
       auf 42% heruntergemischt. Bei voller Deckung wog der geschlossene
       Ring schwerer als die Schrift, die er umschließt — eine Umrandung
       belegt mehr Fläche als ein paar Glyphen und liest sich dadurch
       dunkler, obwohl derselbe Ton anliegt. 42% stellt die Dichte gleich.
       color-mix statt fester Hex-Angabe, damit Rahmen und Schrift nie
       auseinanderlaufen; die rgba-Zeile davor ist der Rückfall für
       Browser ohne color-mix.
       Der Radius bleibt bewusst unter der vollen Kapsel: 8px auf der
       430er-Basis bei knapp 20px Pillenhöhe. Die geschlossene Kapsel las
       sich als Statusabzeichen wie die Kategorie-Pillen in der Liste
       darunter — dies hier ist aber eine Randnotiz zur Achse. Das weichere
       Rechteck nimmt der Form diese Anmutung.
       Die Innenabstände folgen der Projektformel von der 430px-Basis
       (3,4px bzw. 7,2px) mit denselben Faktoren 0,80x und 1,17x wie
       Schriftgröße, Punkt und Schiene — die Pille wächst dadurch exakt
       proportional zu ihrem Inhalt. Die 1px Rahmenstärke bleibt bewusst
       fest: Eine Haarlinie ist eine Gerätekonstante, keine Maßangabe;
       skaliert man sie mit, wird sie auf kleinen Geräten unter ein Pixel
       gerundet und verschwindet stellenweise. */
    display: inline-flex; align-items: center;
    padding: clamp(2.72px, 0.791vw, 3.98px) clamp(5.76px, 1.674vw, 8.42px);
    border: 1px solid rgba(100,116,139,0.42);
    border-color: color-mix(in srgb, currentColor 42%, transparent);
    border-radius: clamp(6.4px, 1.86vw, 9.36px);
    background: rgba(255,255,255,0.72);
    font-style: normal; white-space: nowrap; line-height: 1.15;
    font-size: clamp(0.48rem, 2.233vw, 0.702rem); font-weight: 700;
    /* Derselbe Ton wie die Datumsangaben: Die Datei hält bewusst genau zwei
       Ebenen — Fokustermin dunkel, alles andere gleich ruhig. Ein dritter
       Grauton hätte sich als Unschärfe gelesen, nicht als Hierarchie. */
    letter-spacing: 0.03em; color: #64748B;
}
#kalModal.is-open .kal-hb-mehr { animation: kalHbAuf 0.5s cubic-bezier(0.22,1,0.36,1) 0.72s both; }
/* Trägt die Achse eine Fortsetzung, wächst der Abstand zur Kopfzeile um
   genau eine Beschriftungszeile — gleiche clamp-Faktoren wie die Angabe
   selbst, damit der zusätzliche Raum über jede Breite exakt so groß ist
   wie das, was hineingesetzt wird.
   Seit die Angabe eine Pille ist, wird der Bedarf komponentenweise
   gerechnet statt geschätzt. Von den ursprünglichen 6,05vw geht der alte
   Abstand zur Achse ab (0,698vw), dafür kommen der neue, negative
   Abstand (−0,233vw), der doppelte senkrechte Innenabstand (2 × 0,791vw
   = 1,582vw) und die doppelte Haarlinie (2 × 1px) hinzu:
   6,05 − 0,698 − 0,233 + 1,582 = 6,701vw, dazu 2px als Konstante.
   Die Grenzen tragen dieselben Faktoren 0,80x und 1,17x auf den
   vw-Anteil: 0,80 × 28,81 + 2 = 25,05 und 1,17 × 28,81 + 2 = 35,71.
   Nachgerechnet bei 320/375/430/537/768px: Die Umschaltpunkte liegen
   unverändert bei 344px und 503px — die Achse verhält sich an jeder
   Breite exakt wie zuvor, nur der Betrag stimmt jetzt zur Pille. */
.kal-hb.hat-mehr .kal-hb-kopf { margin-bottom: clamp(25.05px, calc(6.701vw + 2px), 35.71px); }
#kalModal.is-open .kal-hb-linie { animation: kalHbLinie 0.8s cubic-bezier(0.22,1,0.36,1) 0.18s both; }
@keyframes kalHbLinie { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* ── Heute: bauartgleich, nur unbunt ──
   Bewusst gefüllt, nicht offen: Ein leerer Ring heißt in jeder anderen
   Zeitleiste "noch nicht erreicht" oder "inaktiv" — ausgerechnet für den
   Punkt, der jetzt gilt, war das die falsche Aussage. Gefüllt = erreicht,
   grau = kein Ereignis. Die Unterscheidung trägt jetzt die Farbe allein,
   und die ist eindeutiger als eine Bauform. */
.kal-hb-heute {
    position: absolute; left: 0; top: 0;
    width: var(--hb-punkt); height: var(--hb-schiene);
    margin-left: calc(var(--hb-punkt) / -2);
    display: flex; align-items: center; justify-content: center;
    opacity: 0;
}
.kal-hb-heute::before {
    content: ''; display: block;
    width: var(--hb-punkt); height: var(--hb-punkt);
    border-radius: 50%;
    background: rgba(100,116,139,0.62);
    /* Derselbe weiße Hof wie bei den Ereignispunkten: Er hält die Marke
       von der Linie frei, wenn ein Termin nahe an heute liegt. */
    box-shadow: 0 0 0 calc(var(--hb-punkt) * 0.25) #fff;
    box-sizing: border-box;
}
#kalModal.is-open .kal-hb-heute { animation: kalHbAuf 0.5s cubic-bezier(0.34,1.35,0.5,1) 0.5s both; }

/* ── Ereignismarken ── */
.kal-hb-mark {
    position: absolute; top: 0; height: var(--hb-schiene);
    padding: 0; border: none; background: none;
    display: flex; align-items: center; justify-content: center;
    cursor: default; -webkit-tap-highlight-color: transparent;
    opacity: 0;
}
.kal-hb-mark.is-punkt { width: var(--hb-punkt); margin-left: calc(var(--hb-punkt) / -2); }
.kal-hb-mark.is-klick { cursor: pointer; }
#kalModal.is-open .kal-hb-mark { animation: kalHbMark 0.46s cubic-bezier(0.34,1.35,0.5,1) both; }
@keyframes kalHbMark { from { opacity: 0; transform: translateY(clamp(2.4px, 0.698vw, 3.51px)); } to { opacity: 1; transform: translateY(0); } }

.kal-hb-punkt {
    display: block; width: 100%; height: var(--hb-punkt);
    border-radius: calc(var(--hb-punkt) / 2); background: currentColor;
    box-shadow: 0 0 0 calc(var(--hb-punkt) * 0.25) #fff;
    transition: transform 0.22s cubic-bezier(0.34,1.4,0.5,1);
}
/* Zeiträume: schlanker als Punkte — sie tragen die Strecke, der Punkt den
   Moment. Gleiche Farbe, gleiche Rundung, nur flacher. */
.kal-hb-mark.is-spanne .kal-hb-punkt { height: calc(var(--hb-punkt) * 0.62); }

/* Fokus ausschließlich über den Hof, nicht über die Größe */
.kal-hb-mark.is-naechster .kal-hb-punkt {
    /* Zwei Ringe statt einem: innen etwas kräftiger, außen fast nur ein
       Hauch. Ein einzelner Ring endet mit einer harten Kante und wirkt
       dadurch wie ein Bedienelement; zwei gestufte lassen die Farbe
       auslaufen. Das ist der Unterschied zwischen markiert und veredelt. */
    box-shadow: 0 0 0 calc(var(--hb-punkt) * 0.25) #fff,
                0 0 0 calc(var(--hb-punkt) * 0.44) color-mix(in srgb, currentColor 17%, transparent),
                0 0 0 calc(var(--hb-punkt) * 0.70) color-mix(in srgb, currentColor 7%, transparent);
}
@supports not (color: color-mix(in srgb, red 50%, blue)) {
    .kal-hb-mark.is-naechster .kal-hb-punkt {
        box-shadow: 0 0 0 calc(var(--hb-punkt) * 0.25) #fff,
                    0 0 0 calc(var(--hb-punkt) * 0.44) rgba(100,116,139,0.15),
                    0 0 0 calc(var(--hb-punkt) * 0.70) rgba(100,116,139,0.06);
    }
}

/* ── Beschriftungen: gemeinsame Grundlinie unter der Schiene ── */
.kal-hb-label, .kal-hb-heute i {
    position: absolute; top: var(--hb-schiene);
    padding-top: clamp(3.2px, 0.93vw, 4.68px);
    left: 50%; transform: translateX(-50%);
    font-style: normal; white-space: nowrap; pointer-events: none;
    /* Steigung nach der Projektformel von der 430px-Basis (9,6px):
       vw = 9.6/430*100, Grenzen 0,80x und 1,17x — dieselben Faktoren wie
       Punkt und Schiene. Zuvor lag die Untergrenze exakt auf dem 430er-Wert,
       wodurch die Beschriftung von 320 bis 480px konstant blieb, während
       alles andere um 40% wuchs. */
    font-size: clamp(0.48rem, 2.233vw, 0.702rem); font-weight: 700;
    letter-spacing: 0.03em; color: #64748B;
}
/* "Heute" trägt dieselbe Farbe und dasselbe Gewicht wie die Datumsangaben.
   Zuvor war es einen Ton schwächer — drei leicht verschiedene Grautöne lesen
   sich als Unschärfe, nicht als Hierarchie. Jetzt gibt es genau zwei Ebenen:
   der Fokustermin dunkel und fett, alles andere gleich ruhig. Dass "Heute"
   kein Ereignis ist, sagt bereits seine unbunte Farbe. */
/* Das Gewicht trägt die Betonung, nicht die Größe. */
.kal-hb-mark.is-naechster .kal-hb-label { color: #1E293B; font-weight: 800; }
.kal-hb-mark.is-spanne .kal-hb-label { left: 0; transform: none; }

.kal-hb-mark.is-offen-r .kal-hb-punkt {
    border-top-right-radius: 0; border-bottom-right-radius: 0;
    -webkit-mask-image: linear-gradient(270deg, transparent 0%, #000 30%);
            mask-image: linear-gradient(270deg, transparent 0%, #000 30%);
}
.kal-hb-mark.is-klick:active .kal-hb-punkt { transform: scale(1.25); }
.kal-hb-mark.is-ziel .kal-hb-punkt { animation: kalHbPuls 0.9s ease-out; }
@keyframes kalHbPuls {
    0%   { box-shadow: 0 0 0 2.5px #fff, 0 0 0 0 currentColor; }
    100% { box-shadow: 0 0 0 2.5px #fff, 0 0 0 11px rgba(255,255,255,0); }
}

@media (prefers-reduced-motion: reduce) {
    #kalModal.is-open .kal-hb-linie,
    #kalModal.is-open .kal-hb-heute,
    #kalModal.is-open .kal-hb-mark,
    #kalModal.is-open .kal-hb-mehr,
    #kalModal.is-open .kal-hb-kopf { animation: none; opacity: 1; transform: none; }
    .kal-hb-linie { transform: scaleX(1); }
    .kal-hb-kopf, .kal-hb-heute, .kal-hb-mehr { opacity: 1; }
    .kal-hb-mark.is-ziel .kal-hb-punkt { animation: none; }
    .kal-hb-punkt { transition: none; }
}

/* Oben 24 statt 16: Die Achse endet mit ihren Datumsbeschriftungen, die in
   Größe und Gewicht derselben Klasse angehören wie dieses Label — bei 16px
   las sich "BEVORSTEHEND" fast als fünfte Datumsangabe der Zeitleiste statt
   als Beginn eines neuen Abschnitts. Unten bleiben 8, das Verhältnis steht
   damit bei 3:1. Eine Gruppenüberschrift gehört zu dem, was ihr folgt, und
   braucht vor sich mehr Abstand als hinter sich.
   Als padding und nicht als margin, weil das Label sticky auf top:0 klebt:
   ein margin würde beim Anhaften einen Spalt zwischen Containerkante und
   Blur-Fläche lassen, durch den die Karten sichtbar hindurchliefen. */
.kal-slabel { position: sticky; top: 0; z-index: 5; padding: 24px 20px 8px; background: rgba(248,250,255,0.85); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); font-size: 0.58rem; font-weight: 900; text-transform: uppercase; letter-spacing: 0.2em; color: rgba(30,41,59,0.65); border-bottom: none; display: flex; align-items: center; gap: 10px; }
.kal-slabel::before { content: ''; display: inline-block; height: 1px; width: 20px; background: linear-gradient(90deg, currentColor, transparent); opacity: 0.6; flex-shrink: 0; }
.kal-slabel::after { content: ''; display: inline-block; height: 1px; flex: 1; background: linear-gradient(90deg, rgba(0,0,0,0.07), transparent); }
.kal-slabel + .kal-item { margin-top: 6px; }

/* ── AKTIV-Label: Live-Punkt statt Icon. Grün signalisiert "gilt jetzt"
   und grenzt die Gruppe vom reinen Zukunfts-Label ab. Der Ring pulst
   bewusst nur drei Durchläufe — identische Bewegungs-Hierarchie wie
   der Heute-Sonar der Bubble, damit im Modal nie zwei Dauerloops
   gleichzeitig die Compositor-Layer halten. */
.kal-slabel.is-live { color: #5f9a4a; }
.kal-slabel-dot { position: relative; display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #7FB14E; flex-shrink: 0; margin-right: -3px; }
.kal-slabel-dot::after {
    content: ''; position: absolute; inset: 0; border-radius: 50%;
    border: 1.5px solid #7FB14E; opacity: 0; transform: scale(1);
    will-change: auto;
}
#kalModal.is-open .kal-slabel-dot::after { animation: kal-live-puls 2.4s ease-out 3; will-change: transform, opacity; }
@keyframes kal-live-puls {
    0%   { opacity: 0.65; transform: scale(1); }
    70%  { opacity: 0;    transform: scale(2.9); }
    100% { opacity: 0;    transform: scale(2.9); }
}
@media (prefers-reduced-motion: reduce) {
    #kalModal.is-open .kal-slabel-dot::after { animation: none; will-change: auto; }
}



/* ── LEITGRÖSSE DER KARTENTYPOGRAFIE ──
   Sämtliche Schriftgrößen der Textspalte — Titel, Kategorie-Pille, Teaser
   und alle Chips der Meta-Zeile — leiten sich aus dieser einen Größe ab.
   Vorher standen dort sieben voneinander unabhängige rem-Zahlen; jede
   Änderung an einer davon verschob still das Verhältnis zu den übrigen.
   Die Faktoren sind die bisherigen Werte bezogen auf 16px und stehen als
   vollständige Brüche im Code, nicht als gerundete Dezimalzahl.
   Die Kurve: 1rem auf 430px Gerätebreite, nach unten auf das 0,72-fache
   und nach oben auf das 1,25-fache begrenzt. Der mittlere Term ist als
   100vw × 16 / 430 notiert statt als gerundetes 3,72vw, damit die Basis
   exakt getroffen wird.
   Nicht enthalten ist das Kalenderblatt links. Dessen Anbauteile —
   Ungelesen-Punkt, Erinnerungs-Plakette, Binder-Stifte — sind ausdrücklich
   auf feste Pixel und auf das Geräteraster kalibriert (siehe die Begründung
   bei .kal-item.is-offen .kal-bubble::before und .kal-item-erin). Eine
   mitwandernde Kachel würde genau diese Kalibrierung zerstören.
   Bezugsgröße ist bewusst die Karte und NICHT der Viewport. #kalSheet ist
   nur bis 540px vollbreit; darüber wird es zum zentrierten Dialog von
   min(420px, 92vw). Eine vw-Kurve ließe die Schrift dort weiterwachsen,
   während der verfügbare Platz schrumpft — Schrift und Container liefen
   gegeneinander. 100cqi ist die Inline-Breite der Karte selbst, die Basis
   378px ist deren Inhaltsbreite auf 430px Gerätebreite. Deklariert wird
   die Größe auf .kal-item-body, weil cqi sich gegen den Container des
   Elements auflöst, in dem die Einheit steht — auf der Karte selbst wäre
   das ein Vorfahr und nicht sie. Dasselbe Muster wie bei
   .kal-det-text.kal-deko. */
.kal-item {
    container-type: inline-size;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 13px 15px;
    margin: 0 10px 8px;
    border-radius: 18px;
    border: 1px solid rgba(45,62,80,0.10);
    background: rgba(255,255,255,0.84);
    box-shadow:
        0 3px 12px rgba(15,23,42,0.04),
        0 1px 3px rgba(15,23,42,0.025),
        0 1px 0 rgba(255,255,255,0.84) inset;
    transition:
        background 0.16s ease,
        transform 0.2s cubic-bezier(0.34,1.56,0.64,1),
        box-shadow 0.16s ease,
        border-color 0.16s ease;
    -webkit-tap-highlight-color: transparent;
    cursor: default;
    min-height: 76px;
    opacity: 0;
    transform: translateY(18px);
}
.kal-item:last-child { margin-bottom: 4px; }

@keyframes kal-stagger-in {
    0%   { opacity: 0; transform: translateY(18px); }
    100% { opacity: 1; transform: translateY(0); }
}

#kalLayer1.kal-animating > * {
    animation: kal-stagger-in 0.42s cubic-bezier(0.16, 1, 0.3, 1) both;
}
#kalLayer1.kal-animating > *:nth-child(1)  { animation-delay: 0.20s; }
#kalLayer1.kal-animating > *:nth-child(2)  { animation-delay: 0.28s; }
#kalLayer1.kal-animating > *:nth-child(3)  { animation-delay: 0.36s; }
#kalLayer1.kal-animating > *:nth-child(4)  { animation-delay: 0.44s; }
#kalLayer1.kal-animating > *:nth-child(5)  { animation-delay: 0.52s; }
#kalLayer1.kal-animating > *:nth-child(6)  { animation-delay: 0.60s; }
#kalLayer1.kal-animating > *:nth-child(7)  { animation-delay: 0.68s; }
#kalLayer1.kal-animating > *:nth-child(8)  { animation-delay: 0.74s; }
#kalLayer1.kal-animating > *:nth-child(n+9) { animation-delay: 0.78s; }

@media (prefers-reduced-motion: reduce) {
    .kal-item { opacity: 1; transform: none; }
    #kalLayer1.kal-animating > * { animation: none; opacity: 1; transform: none; }
}

.kal-item.is-clickable { cursor: pointer; }
@media (hover: hover) and (pointer: fine) {
    .kal-item.is-clickable:hover { background: rgba(255,255,255,0.95); box-shadow: 0 4px 18px rgba(72,160,214,0.10), 0 1px 4px rgba(15,23,42,0.05); transform: translateY(-1px); }
}
.kal-item.is-clickable:active { transform: scale(0.98); box-shadow: 0 1px 4px rgba(15,23,42,0.06); }
/* Verblassen ohne Kontrastverlust: die pauschalen 40 % drückten Titel und
   Teaser deutlich unter WCAG 4.5:1. Stattdessen verblasst das Chrome
   (Kalenderblatt, Chip, Meta) einzeln — Titel (#475569 ≈ 7:1) und Teaser
   (#64748B ≈ 4.8:1 auf Weiß) bleiben je eine Stufe gedimmt, aber lesbar. */
.kal-item.is-past .kal-bubble { opacity: 0.55; }
.kal-item.is-past .kal-item-title { color: #475569; }
.kal-item.is-past .kal-item-teaser { color: #64748B; }
.kal-item.is-past .kal-item-meta { opacity: 0.75; }
.kal-item.is-past .kal-item-kat { opacity: 0.65; }
.kal-item.kat-fest        { background: rgba(72,160,214,0.04); }
.kal-item.kat-ausflug     { background: rgba(127,177,78,0.04); }
.kal-item.kat-information { background: rgba(245,158,11,0.04); }
.kal-item.kat-schliesstage { background: rgba(213,90,146,0.04); }
.kal-item.kat-rueckblick  { background: rgba(139,92,246,0.04); }
.kal-item.kat-wichtig     { background: rgba(239,68,68,0.04); }
.kal-item.kat-neuigkeit   { background: rgba(127,177,78,0.04); }


/* ── Datums-Badge als echtes Kalenderblatt — Design 1:1 vom Info-Modul
   (.oz-label-date): weiße Blattfläche, Farbband oben mit zwei Binder-
   Ringen, große Tageszahl in Tinte, Monat in der Statusfarbe darunter.
   Bewusste Abweichungen zur Referenz: kein backdrop-filter (die Liste
   rendert viele Badges gleichzeitig — GPU-Audit) und keine Sweep/Pop-
   Animationen (Aufmerksamkeits-Effekte des einzelnen Lead-Chips wären
   in einer Liste übertrieben). Band selbst gerundet statt overflow:hidden
   am Blatt, damit der Heute-Sonar (::after, inset -4px) ungeclippt bleibt. */
/* ── ANGLEICHUNG an das Header-Blatt ──
   Ziel ist eine Familie, keine Kopie: gleiche Grundform, gleiche
   Rundungslogik, gleiche Befestigung oben, gleicher Kontaktschatten — aber
   die Kategoriefarbe bleibt der Träger und das Blatt hier bleibt eine Stufe
   ruhiger als der Header. Der Header behält sein festes Blau, sein Textband
   mit HEUTE und den kräftigeren Ring.
   Eckradius 14 -> 8: Der Header hat bei identischen 54px Breite einen
   realen Radius von 6.87px, relativ also 0.127 der Breite gegen bisher
   0.259 hier. 8px liegt knapp darüber und hält damit die Kachel eine Spur
   weicher als das Blatt oben — das ist der bewusste Abstand im Detailgrad.
   Kontaktschatten ergänzt: Der farbige Streuschatten bleibt als Identität,
   darunter kommt dieselbe enge, neutrale Lage wie beim Header-Blatt
   (0 1px 1.5px rgba(30,41,59,0.10)). Sie erzeugt die Bodenhaftung, die dem
   Header seine Plastizität gibt — ein farbiger Streuschatten allein liest
   sich als Schein, nicht als Auflage. */
.kal-bubble { flex-shrink: 0; width: 54px; min-height: 56px; padding: 13px 4px 6px; border-radius: 8px; text-align: center; position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(180deg, #ffffff 0%, #ffffff 55%, color-mix(in srgb, var(--kb-col, #48A0D6) 7%, #ffffff) 100%); border: 1px solid color-mix(in srgb, var(--kb-col, #48A0D6) 22%, transparent); box-shadow: inset 0 1px 0 rgba(255,255,255,0.95), 0 1px 1.5px rgba(30,41,59,0.10), 0 3px 10px var(--kb-shadow, rgba(72,160,214,0.12)); }
/* Band 9 -> 10px und Radius dem neuen Eckradius nachgezogen (8 minus 1px
   Rahmen = 7). Beim Header nimmt das Band 27.3 % der Korpushöhe ein, hier
   sind es jetzt 17.9 % statt 16.1 % — bewusst deutlich weniger, weil dieses
   Band keinen Text trägt und bei voller Header-Höhe leer wirken würde. */
.kal-bubble-band { position: absolute; top: 0; left: 0; right: 0; height: 10px; border-radius: 7px 7px 0 0; background: linear-gradient(180deg, color-mix(in srgb, var(--kb-col, #48A0D6) 78%, #ffffff) 0%, var(--kb-col, #48A0D6) 100%); }
/* Binder-Ringe des Kalenders — Position wie Referenz (26% von links/rechts) */
/* BEFESTIGUNG statt Ösen: Bisher saßen hier zwei weiße Punkte IM Band —
   Löcher also. Das Header-Blatt trägt stattdessen zwei Stifte, die oben aus
   dem Korpus herausragen. Das ist das auffälligste Erkennungsmerkmal der
   Familie und wird deshalb übernommen.
   Sichtbar ist beim Header nur der Teil oberhalb der Korpuskante: 3.27px
   hoch bei 3.27px Breite, oben durch die runde Strichkappe abgeschlossen.
   Genau das wird hier nachgebaut — 3.2px im Quadrat, oben mit halbem
   Radius gerundet, unten bündig auf der Bandoberkante. Ein durchgehender
   Stift wie im SVG ginge nicht: dort liegt er hinter dem Korpus, hier
   müsste ein Pseudo-Element des Bandes über ihm liegen und würde als
   grauer Strich auf der Farbfläche sichtbar.
   Position 34.8 % der Breite — derselbe relative Ort wie im Header (12.3188
   und 23.079 von 35.3978 viewBox-Einheiten). Die 1.6px sind die halbe
   Stiftbreite, damit die Prozentangabe die Mitte meint und nicht die Kante.
   Deckkraft 0.44 gegen 0.45 im Header und 3.3px gegen 3.27px Breite: knapp
   unterhalb des Originals, damit die Halterung definiert genug ist, ohne
   dem Header-Blatt seinen Vorrang streitig zu machen. */
.kal-bubble-band::before, .kal-bubble-band::after { content: ''; position: absolute; top: -3.3px; width: 3.3px; height: 3.3px; border-radius: 1.65px 1.65px 0 0; background: rgba(30,41,59,0.44); }
.kal-bubble-band::before { left: calc(34.8% - 1.6px); }
.kal-bubble-band::after  { right: calc(34.8% - 1.6px); }
/* Ohne Datum (gepinnte Wichtig-Karte): glatte Farbkante ohne Ösen —
   kein Kalenderblatt ohne Datum (Referenz-Regel) */
.kal-bubble-band.kb-no-rings::before, .kal-bubble-band.kb-no-rings::after { display: none; }
/* ── ABGERISSEN: vergangene Termine ────────────────────────────────────
   Ein vergangener Tag ist im Abreißkalender ein abgerissenes Blatt. Die
   Binder-Stifte entfallen deshalb, und die Oberkante wird zum Riss.

   UNREGELMÄSSIG statt Sägezahn: Gleichmäßige Zacken lesen sich als
   Zackenschere oder Briefmarke, nicht als Abriss. 15 Stützpunkte mit
   ungleichem Abstand, alternierend Kuppe und Kerbe. Die x-Lagen stehen in
   54steln der Kachelbreite und treffen damit exakt ganze Pixel der fest
   dokumentierten 54px. Die Tiefe steht in Zehnteln von --kr-tief (3px):
   Das 10px-Band behält an der tiefsten Kerbe noch 7px Farbe.

   DREI RISSE statt einem Stempel: Nur die Tiefenwerte --kr-0 bis --kr-14
   wechseln über :nth-child(3n+…). Beide Polygone lesen dieselben Werte,
   Riss und Faser können deshalb nie auseinanderlaufen.

   FORM UNVERÄNDERT: Eckradius, Rahmen und Bandradius bleiben exakt wie bei
   jeder anderen Kachel. Der Riss liegt unterhalb der Bindung, die Schultern
   des Blatts bleiben also rund — sonst wirkt die Kachel breiter und
   kantiger als ihre Nachbarn, obwohl sie gleich groß ist. Damit der obere
   Rahmen trotzdem nirgends als getönte Linie über dem Riss stehen bleibt,
   beginnt der Riss um --kr-rahmen tiefer: y_Kachel = Rahmen + Tiefe. Er
   schneidet die 1px-Rahmenkante damit an jeder Stelle vollständig weg.

   KACHEL: clip-path reicht seitlich und unten um --kr-aus über die Kachel
   hinaus, damit der Schatten erhalten bleibt. --kr-aus = Versatz + Unschärfe
   des weitesten Schattens (0 3px 10px), also exakt 13px.

   FASER: Farbiges Papier zeigt beim Reißen einen hauchdünnen weißen Saum.
   Das Band trägt denselben Riss, um --kr-faser (2/3px = 2 Gerätepixel auf
   DPR 3) nach unten versetzt. In der Lücke erscheint der weiße Blattkörper —
   ohne zusätzliches Element. Umrechnung in Bandkoordinaten: Das Band sitzt
   innerhalb des 1px-Rahmens, ist also 2px schmaler und 1px tiefer als die
   Kachel. x_Band = (Bandbreite + 2 × Rahmen) × n / 54 − Rahmen ergibt exakt
   n − 1 px. y_Band = y_Kachel − Rahmen + Faser = Tiefe + Faser, der Rahmen
   hebt sich exakt auf.

   Statisch — kein @keyframes, kein transform, kein will-change. Nichts
   gehört in perfObs, keine prefers-reduced-motion-Regel nötig. Der Stapel
   entfällt bei vergangenen Terminen serverseitig ($_ctx !== 'past'),
   Ungelesen-Punkt und Erinnerungs-Plakette ebenfalls per JS.

   ARTIKEL: Vergangene Terminzeilen (.kal-termin.is-vorbei) tragen exakt
   denselben Riss. Ihre Kachel ist dieselbe .kal-bubble mit denselben 54px
   und demselben weitesten Schatten (0 3px 10px), alle Konstanten gelten
   also unverändert. Die drei Risse wechseln dort über die Zeilen des
   Terminblocks, wie in der Liste über die Karten. Die Aufklapp-Seiten
   (.kal-seite-vorne) entstehen nur an kommenden Terminen und kreuzen den
   Riss nie. */
.kal-item.is-past, .kal-termin.is-vorbei { --kr-tief: 3px; --kr-rahmen: 1px; --kr-faser: calc(2px / 3); --kr-aus: calc(3px + 10px); --kr-0: 2; --kr-1: 8; --kr-2: 1; --kr-3: 9; --kr-4: 3; --kr-5: 7; --kr-6: 0; --kr-7: 10; --kr-8: 2; --kr-9: 8; --kr-10: 1; --kr-11: 6; --kr-12: 3; --kr-13: 9; --kr-14: 2; }
.kal-item.is-past:nth-child(3n+2), .kal-termin.is-vorbei:nth-child(3n+2) { --kr-0: 4; --kr-1: 9; --kr-2: 2; --kr-3: 6; --kr-4: 0; --kr-5: 8; --kr-6: 3; --kr-7: 10; --kr-8: 1; --kr-9: 7; --kr-10: 4; --kr-11: 9; --kr-12: 0; --kr-13: 7; --kr-14: 3; }
.kal-item.is-past:nth-child(3n), .kal-termin.is-vorbei:nth-child(3n) { --kr-0: 1; --kr-1: 7; --kr-2: 4; --kr-3: 10; --kr-4: 2; --kr-5: 9; --kr-6: 1; --kr-7: 6; --kr-8: 3; --kr-9: 10; --kr-10: 0; --kr-11: 8; --kr-12: 2; --kr-13: 7; --kr-14: 4; }
.kal-item.is-past .kal-bubble, .kal-termin.is-vorbei .kal-bubble {
    clip-path: polygon(
        calc(0% - var(--kr-aus)) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-0) / 10),
        0% calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-0) / 10),
        calc(100% * 4 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-1) / 10),
        calc(100% * 7 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-2) / 10),
        calc(100% * 12 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-3) / 10),
        calc(100% * 15 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-4) / 10),
        calc(100% * 19 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-5) / 10),
        calc(100% * 24 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-6) / 10),
        calc(100% * 27 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-7) / 10),
        calc(100% * 32 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-8) / 10),
        calc(100% * 35 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-9) / 10),
        calc(100% * 40 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-10) / 10),
        calc(100% * 44 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-11) / 10),
        calc(100% * 47 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-12) / 10),
        calc(100% * 51 / 54) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-13) / 10),
        100% calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-14) / 10),
        calc(100% + var(--kr-aus)) calc(var(--kr-rahmen) + var(--kr-tief) * var(--kr-14) / 10),
        calc(100% + var(--kr-aus)) calc(100% + var(--kr-aus)),
        calc(0% - var(--kr-aus)) calc(100% + var(--kr-aus))
    );
}
.kal-item.is-past .kal-bubble-band, .kal-termin.is-vorbei .kal-bubble-band {
    clip-path: polygon(
        calc((100% + 2 * var(--kr-rahmen)) * 0 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-0) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 4 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-1) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 7 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-2) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 12 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-3) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 15 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-4) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 19 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-5) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 24 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-6) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 27 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-7) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 32 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-8) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 35 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-9) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 40 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-10) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 44 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-11) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 47 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-12) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 51 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-13) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 54 / 54 - var(--kr-rahmen)) calc(var(--kr-tief) * var(--kr-14) / 10 + var(--kr-faser)),
        calc((100% + 2 * var(--kr-rahmen)) * 54 / 54 - var(--kr-rahmen)) 100%,
        calc(0% - var(--kr-rahmen)) 100%
    );
}
.kal-item.is-past .kal-bubble-band::before, .kal-item.is-past .kal-bubble-band::after,
.kal-termin.is-vorbei .kal-bubble-band::before, .kal-termin.is-vorbei .kal-bubble-band::after { display: none; }
/* Gewicht 900 -> 800 und Farbe auf den Projekt-Token #1E293B: beides exakt
   wie die Tageszahl im Header-Blatt. Das bisherige #1e1b4b war ein eigener
   Indigo-Ton, der sonst nirgends im Modul vorkommt. */
.kal-bubble-day { display: block; font-size: 1.3rem; font-weight: 800; line-height: 1.05; color: #1E293B; font-variant-numeric: tabular-nums; letter-spacing: 0.01em; padding-inline-start: 0.01em; margin-top: 4px; }
.kal-bubble-mon { display: block; font-size: 0.58rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.09em; padding-inline-start: 0.09em; color: var(--kb-col, #48A0D6); margin-top: 1px; }
/* ── ABREISSBLOCK: mehrere Termine ─────────────────────────────────────
   Hinter dem Kalenderblatt liegen die Termine, die nach dem angezeigten
   noch kommen, als weitere Blätter — höchstens zwei. Sie lugen UNTEN heraus
   wie bei einem echten Abreißblock, der oben gebunden ist. Oben sitzen die
   Binder-Stifte, oben rechts der Ungelesen-Punkt, unten rechts die
   Erinnerungs-Plakette; ein seitlich versetzter Stapel kollidierte mit
   zwei davon. Unten liegt nur der Rand, und die Plakette sitzt mit z-index 3
   sichtbar auf dem Block.

   Feste Pixel wie die Kachel selbst: Sie ist dokumentiert auf 54px und das
   Geräteraster kalibriert. Ein mit dem Viewport wandernder Versatz an einer
   unbeweglichen Kachel würde das Verhältnis je Breite verschieben — genau
   das verbietet das Kopplungsgesetz. Überstand und Einzug stehen in
   --kst-auf und --kst-ein und sind auf DPR 3 ganze Gerätepixel.

   Jedes Blatt ist pro Stufe um einen Schritt schmaler und ragt um einen
   Schritt weiter heraus. Der Eckradius schrumpft im selben Verhältnis wie die
   Breite, damit alle Blätter dieselbe Form haben: 8px × (54 − 2 × Einzug) / 54.

   Die Blätter liegen mit z-index −1 HINTER der Kachel. Das funktioniert, weil
   die Kachel selbst keinen Stapelkontext bildet, die Karte (transform) aber
   schon — die Blätter bleiben damit über dem Kartengrund. Die einzige Regel,
   die der Kachel einen Stapelkontext gibt (opacity im Archiv), betrifft
   keinen Block: Im Archiv wird keiner ausgegeben.

   margin-bottom um die sichtbare Überstandshöhe: Die Karte zentriert ihre
   Kinder, und so steht Kachel plus Block als Gruppe optisch mittig statt der
   Kachel allein mit einem Überhang darunter.

   Statisch — kein @keyframes, kein transform, kein will-change. Nichts
   gehört in perfObs, keine prefers-reduced-motion-Regel nötig. */
.kal-bubble.hat-stapel { --kst-auf: 5; --kst-ein: 3; }
/* Ausdrücklich statt stillschweigend: Die Karte bildet schon über
   container-type und transform einen Stapelkontext. isolation macht die
   Voraussetzung für negative z-index unabhängig davon, falls eine der beiden
   Regeln einmal wegfällt (etwa transform: none bei reduzierter Bewegung). */
.kal-item:has(.kal-bubble.hat-stapel) { isolation: isolate; }
/* Der farbige Streuschatten der Kachel lag über den Blättern und verwusch
   sie. Beim Block trägt ihn das Blatt direkt darunter. */
.kal-bubble.hat-stapel { box-shadow: inset 0 1px 0 rgba(255,255,255,0.95), 0 1px 1.5px rgba(30,41,59,0.10); }
.kal-bubble.hat-stapel-1 { margin-bottom: calc(var(--kst-auf) * 1px); }
.kal-bubble.hat-stapel-2 { margin-bottom: calc(var(--kst-auf) * 2px); }
/* GESTALT: gestaffelter Stapel mit Tiefe, wie gestapelte Mitteilungen.
   Die beiden vorigen Fassungen legten die Blattkanten auf 3px Abstand
   aneinander. Auf dem Gerät verschmolzen sie zu einer doppelten Kontur —
   man sah einen dicken Rand, keine Blätter. Ein Stapel wird erst lesbar,
   wenn jede Stufe eine eigene Fläche zeigt und nach hinten zurücktritt:
   Jedes Blatt ragt 5px heraus, ist je Seite 3px schmaler, und das hintere
   Blatt ist mit 60 % Deckkraft sichtbar weiter entfernt als das vordere.
   Auf DPR 3 sind 5px und 3px ganze Gerätepixel (15 und 9).
   Papier kaum getönt (5 % Kategoriefarbe), Kontur 30 % — die Fläche trägt
   die Form, nicht die Linie. Der Eckradius schrumpft exakt mit der Breite:
   8px × (54 − 2 × Einzug) / 54.
   Reihenfolge über z-index −1 und −2: Bei gleichem Wert malte der Browser
   das später im Markup stehende hintere Blatt über das vordere. */
.kal-stapel-blatt {
    position: absolute;
    top: 0;
    pointer-events: none;
    background: color-mix(in srgb, var(--kb-col, #48A0D6) 5%, #ffffff);
    border: 1px solid color-mix(in srgb, var(--kb-col, #48A0D6) 30%, transparent);
    box-shadow: 0 1px 2px rgba(30,41,59,0.06);
}
.kal-stapel-blatt--1 {
    z-index: -1;
    left: calc(var(--kst-ein) * 1px);
    right: calc(var(--kst-ein) * 1px);
    bottom: calc(var(--kst-auf) * -1px);
    border-radius: calc(8px * (54 - 2 * var(--kst-ein)) / 54);
    box-shadow: 0 1px 1.5px rgba(30,41,59,0.10), 0 3px 10px var(--kb-shadow, rgba(72,160,214,0.12));
}
.kal-stapel-blatt--2 {
    z-index: -2;
    left: calc(var(--kst-ein) * 2px);
    right: calc(var(--kst-ein) * 2px);
    bottom: calc(var(--kst-auf) * -2px);
    border-radius: calc(8px * (54 - 4 * var(--kst-ein)) / 54);
    opacity: 0.6;
}
/* ── UMBLÄTTERN ─────────────────────────────────────────────────────────
   Das vordere Kalenderblatt hebt sich an der Bindung und gibt für einen
   Moment das Blatt darunter frei — mit dem Datum des nächsten Termins, das
   der Server aus der Projektion setzt (Eintrag index + 1). Dann fällt es
   zurück und federt einmal nach. Die Bewegung trägt damit eine Information:
   nicht nur "da liegt mehr", sondern "als Nächstes kommt der 30.".

   AUFBAU: Die Kachel selbst bleibt unbewegt und ist das Blatt darunter — ihr
   Hintergrund, ihre Kontur, ihr Band mit den Stiften und das nächste Datum im
   normalen Fluss. Darüber liegt .kal-seite-vorne mit dem Datum der Karte. Sie
   erbt Hintergrund, Kontur, Radius und Polsterung wörtlich von der Kachel
   (inherit) und liegt mit inset: -1px exakt auf deren Randkasten — beide
   Blätter sind dadurch pixelgleich, Band und Ziffern stehen deckungsgleich.
   Die Stifte zeichnet nur das hintere Band; das vordere trägt kb-no-rings,
   sonst stünden sie beim Anheben doppelt.

   Warum nicht die Kachel drehen: Ein transform an der Kachel bildete einen
   Stapelkontext, und die Blätter mit negativem z-index lägen dann über ihrem
   Hintergrund. Die Perspektive steht deshalb IN der transform-Funktion der
   Seite und nicht als perspective-Eigenschaft an der Kachel — auch die würde
   einen Stapelkontext bilden.

   Ungelesen-Punkt und Erinnerungs-Plakette haben z-index 3 und bleiben über
   der Seite (z-index 2).

   BEWEGUNG, Takt 6,4s (alle Prozentwerte sind exakte Sekundenmarken):
   0,4s Ruhe · 0,5s Anheben auf 78° (weich auslaufend) · 0,9s Halten, damit
   das Datum darunter gelesen werden kann · 0,5s Fallen (beschleunigend, wie
   unter Schwerkraft) · 0,1s Nachfedern auf 5° · 0,1s Setzen · 3,9s Ruhe.

   WINKEL, gerechnet statt geschätzt: Die Seite ist 58px hoch (Kachel plus
   Kontur). Angehoben verdeckt sie oben H × cos(α) × 600 / (600 − H × sin(α)).
   Bei den ersten 58° waren das 33,5px — genau dort stehen die Ziffern des
   nächsten Datums, sichtbar blieb nur der Monat. Bei 78° sind es 13,3px:
   Das Blatt steht als schmaler Streifen an der Bindung, Tag und Monat
   darunter liegen vollständig frei. Die Unterkante wird dabei durch die
   Perspektive 59,6px breit, rund 3px je Seite über die Kachel — sichtbar
   als Blatt, das sich zum Betrachter hebt, ohne in die Textspalte zu ragen.
   Drehpunkt ist die Bindung oben mittig. Beim Anheben dunkelt die Seite um
   höchstens 10 % ab — sie dreht sich vom Licht weg.

   Nur transform und opacity. Gate wie beim Rufzeichen-Herzschlag: Modal
   offen, kein Artikel darüber, Blatt zu mindestens 60 % sichtbar und Tab
   sichtbar (is-lebt). will-change nur in genau diesem Zustand. Erste Runde
   nach 0,9s, wenn die Karten eingelaufen sind. */
.kal-bubble.hat-umblatt .kal-seite-vorne {
    position: absolute;
    inset: -1px;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: inherit;
    background: inherit;
    border: inherit;
    border-radius: inherit;
    transform-origin: 50% 0;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
}
/* Abdunkelung als eigene Ebene, damit sie nur über opacity animiert wird.
   Radius: Kachelradius minus Kontur, weil die Ebene innerhalb der Kontur liegt. */
.kal-bubble.hat-umblatt .kal-seite-vorne::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: calc(8px - 1px);
    background: #1E293B;
    opacity: 0;
    pointer-events: none;
}
@keyframes kalUmblatt {
    0%, 6.25%  { transform: perspective(600px) rotateX(0deg);  animation-timing-function: cubic-bezier(0.22,1,0.36,1); }
    14.0625%   { transform: perspective(600px) rotateX(78deg); }
    28.125%    { transform: perspective(600px) rotateX(78deg); animation-timing-function: cubic-bezier(0.55,0,0.85,0.35); }
    35.9375%   { transform: perspective(600px) rotateX(0deg);  animation-timing-function: cubic-bezier(0.22,1,0.36,1); }
    37.5%      { transform: perspective(600px) rotateX(5deg);  animation-timing-function: cubic-bezier(0.55,0,0.85,0.35); }
    39.0625%, 100% { transform: perspective(600px) rotateX(0deg); }
}
@keyframes kalUmblattSchatten {
    0%, 6.25%  { opacity: 0;   animation-timing-function: cubic-bezier(0.22,1,0.36,1); }
    14.0625%   { opacity: 0.1; }
    28.125%    { opacity: 0.1; animation-timing-function: cubic-bezier(0.55,0,0.85,0.35); }
    35.9375%, 100% { opacity: 0; }
}
#kalModal.is-open:not(.kal-l2an) #kalLayer1 .kal-bubble.hat-umblatt.is-lebt .kal-seite-vorne {
    will-change: transform;
    animation: kalUmblatt 6.4s linear 0.9s infinite both;
}
#kalModal.is-open:not(.kal-l2an) #kalLayer1 .kal-bubble.hat-umblatt.is-lebt .kal-seite-vorne::after {
    will-change: opacity;
    animation: kalUmblattSchatten 6.4s linear 0.9s infinite both;
}
@media (prefers-reduced-motion: reduce) {
    #kalModal.is-open:not(.kal-l2an) #kalLayer1 .kal-bubble.hat-umblatt.is-lebt .kal-seite-vorne,
    #kalModal.is-open:not(.kal-l2an) #kalLayer1 .kal-bubble.hat-umblatt.is-lebt .kal-seite-vorne::after { animation: none; will-change: auto; }
}
/* Zeitraum in der Meta-Zeile: Miniatur der Detail-Timeline — gefüllter
   Start-Knoten, Linie, offener End-Knoten — gefolgt von "bis …". Das Blatt
   bleibt pur (nur Von); die Spanne lebt außerhalb, in der Statusfarbe. */
.kal-item-bis { display: inline-flex; align-items: center; gap: calc(var(--kal-item-fs, 1rem) * 5 / 16); font-size: calc(var(--kal-item-fs, 1rem) * 0.74); font-weight: 700; color: var(--kb-col, #48A0D6); white-space: nowrap; }
.kal-item-bis svg { flex-shrink: 0; display: block; opacity: 0.9; }
/* Zeitloser Eintrag (reine Info): eigene Serifen-i-Glyphe (Kundenwunsch,
   1:1 übernommen, fill via currentColor). Glyphe füllt ~99% der viewBox-
   Höhe; per Kurven-Sampling vermessen exakt mittig (Mitte 127.9/128) —
   kein Zentrier-Shift nötig. Feine currentColor-Kontur (sw 3.5) gibt der
   Serifen-Form etwas mehr Gewicht. Bewusst OHNE Puls — der Herzschlag
   bleibt exklusiv dem Wichtig-Signal vorbehalten. */
.kal-bubble-info { display: block; width: 21px; height: auto; color: var(--kb-col, #48A0D6); margin-top: 2px; }
/* Wichtig: handgezeichnetes Rufzeichen — Glyphe 1:1 vom Info-Modul (.oz-info-ruf)
   inkl. der charakteristischen 5-Grad-Drehung; Groesse im Blatt-Verhaeltnis der Referenz */
.kal-bubble-ruf { display: block; width: 25px; height: auto; color: var(--kb-col, #EF4444); margin-top: 2px; transform: rotate(5deg); }
/* Herzschlag (lub-dub): läuft NUR wenn (1) das Modal offen ist, (2) Layer 2
   die Liste nicht verdeckt (kal-l2an) und (3) das Blatt laut Observer im
   sichtbaren Bereich liegt (is-puls) — nie im Hintergrund. Die 5°-Drehung
   der Referenz bleibt in jedem Frame erhalten. */
@keyframes kalRufHerz {
    0%, 30%, 100% { transform: rotate(5deg) scale(1); }
    8%            { transform: rotate(5deg) scale(1.22); }
    16%           { transform: rotate(5deg) scale(1); }
    22%           { transform: rotate(5deg) scale(1.12); }
}
#kalModal.is-open:not(.kal-l2an) .kal-bubble-ruf.is-puls { animation: kalRufHerz 2.6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
    #kalModal.is-open:not(.kal-l2an) .kal-bubble-ruf.is-puls { animation: none; }
}
.kal-item.is-today  { --kb-bg: rgba(72,160,214,0.08);  --kb-col: #48A0D6; --kb-border: rgba(72,160,214,0.3);  --kb-shadow: rgba(72,160,214,0.14); }
.kal-item.is-future { --kb-bg: rgba(72,160,214,0.08);  --kb-col: #48A0D6; --kb-border: rgba(72,160,214,0.25); --kb-shadow: rgba(72,160,214,0.12); }
.kal-item.is-past   { --kb-bg: rgba(0,0,0,0.04);       --kb-col: #94a3b8; --kb-border: rgba(0,0,0,0.10);      --kb-shadow: rgba(0,0,0,0.04); }
/* Vergangen: Blatt bleibt weiß — Band und Monat greifen die graue
   Statusfarbe (--kb-col) auf, das Verblassen erledigt die Item-Opacity */
.kal-item.kat-fest        { --kb-bg: rgba(72,160,214,0.08);  --kb-col: #48A0D6; --kb-border: rgba(72,160,214,0.3);  --kb-shadow: rgba(72,160,214,0.14); }
.kal-item.kat-ausflug     { --kb-bg: rgba(127,177,78,0.08);  --kb-col: #7FB14E; --kb-border: rgba(127,177,78,0.3);  --kb-shadow: rgba(127,177,78,0.14); }
.kal-item.kat-information { --kb-bg: rgba(245,158,11,0.08);  --kb-col: #F59E0B; --kb-border: rgba(245,158,11,0.3);  --kb-shadow: rgba(245,158,11,0.14); }
.kal-item.kat-schliesstage { --kb-bg: rgba(213,90,146,0.08); --kb-col: #D55A92; --kb-border: rgba(213,90,146,0.3);  --kb-shadow: rgba(213,90,146,0.14); }
.kal-item.kat-rueckblick  { --kb-bg: rgba(139,92,246,0.08);  --kb-col: #8B5CF6; --kb-border: rgba(139,92,246,0.3);  --kb-shadow: rgba(139,92,246,0.14); }
.kal-item.kat-neuigkeit   { --kb-bg: rgba(127,177,78,0.08);  --kb-col: #7FB14E; --kb-border: rgba(127,177,78,0.3);  --kb-shadow: rgba(127,177,78,0.14); }
.kal-item.kat-wichtig     { --kb-bg: rgba(239,68,68,0.08);   --kb-col: #EF4444; --kb-border: rgba(239,68,68,0.3);   --kb-shadow: rgba(239,68,68,0.14); }
.kal-item.is-today .kal-bubble { position: relative; }
/* Sonar GPU-konform: transform:scale statt inset-Keyframes (inset = Layout-
   Reflow pro Frame). Endlich statt infinite — drei Pulse pro Modal-Öffnung
   (die Animation startet erst mit is-open und damit bei jedem Öffnen neu),
   danach Ruhe: der Herzschlag des Rufzeichens bleibt das einzige
   Dauersignal der Liste (Bewegungs-Hierarchie). */
.kal-item.is-today .kal-bubble::after { content: ''; position: absolute; inset: -4px; border-radius: 12px; border: 2px solid var(--kb-col); opacity: 0; pointer-events: none; }
#kalModal.is-open .kal-item.is-today .kal-bubble::after { animation: kal-sonar 2.2s ease-out 3; }
@keyframes kal-sonar { 0% { transform: scale(0.94); opacity: 0.7; } 100% { transform: scale(1.18); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { #kalModal.is-open .kal-item.is-today .kal-bubble::after { animation: none; } }
.kal-item.is-pinned { position: relative; border-radius: 14px; border: 1.5px solid rgba(239,68,68,0.35) !important; background: rgba(255,255,255,0.55) !important; margin: 4px 8px; padding: 14px 12px; opacity: 1 !important; transform: none !important; animation: none !important; }
/* Glow auf eigener Overlay-Ebene: animiert wird NUR opacity — die alten
   box-shadow/border-color-Keyframes erzwangen einen Repaint pro Frame.
   ::before, weil ::after dem ENTWURF-Badge gehört. Drei Pulse pro Modal-
   Öffnung, danach trägt der statische rote Rahmen das Signal allein
   (Bewegungs-Hierarchie: nur der Ruf-Herzschlag pulsiert dauerhaft).
   animation:none auf der Karte selbst unterdrückt weiterhin den
   Einflug-Stagger — das übernahm zuvor die Glow-Animation mit. */
.kal-item.is-pinned::before { content: ''; position: absolute; inset: 0; border-radius: 14px; border: 1.5px solid rgba(239,68,68,0.6); box-shadow: 0 0 14px 4px rgba(239,68,68,0.2); opacity: 0; pointer-events: none; }
#kalModal.is-open .kal-item.is-pinned::before { animation: kal-pin-glow 2.4s ease-in-out 3; }
@keyframes kal-pin-glow { 0%, 100% { opacity: 0; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .kal-item.is-pinned::before { animation: none !important; opacity: 0.45; } }
.kal-item-body { --kal-item-fs: 1rem; flex: 1; min-width: 0; }
/* Die Kurve steht bewusst hinter einer Abfrage auf die Einheit selbst.
   Custom Properties werden erst an der Verwendungsstelle ausgewertet: Kennt
   ein Browser cqi nicht, ist nicht etwa die Variable ungesetzt und der
   var()-Ersatzwert griffe — die nutzende Eigenschaft wird ungültig und
   fällt bei font-size auf den geerbten Wert zurück. Titel, Teaser und
   Chips liefen dann in der Größe des Elternelements. Mit dieser Hülle ist
   der Rückfall stattdessen exakt der bisherige Stand: feste 1rem.
   Die gepinnte Karte hat schmaleren Außenabstand und breiteres Innenmaß und
   ist dadurch 388px statt 378px im Inhalt. Mit derselben Basis liefe ihre
   Schrift 2,6 % größer als die der Karten darunter — bei gleicher Rolle im
   selben Bild ein sichtbarer Bruch. Sie bekommt deshalb ihre eigene
   Inhaltsbreite als Basis, gemessen auf 430px Gerätebreite. */
@supports (width: 1cqi) {
    .kal-item-body { --kal-item-fs: clamp(calc(1rem * 0.72), calc(100cqi * 16 / 378), calc(1rem * 1.25)); }
    .kal-item.is-pinned .kal-item-body { --kal-item-fs: clamp(calc(1rem * 0.72), calc(100cqi * 16 / 388), calc(1rem * 1.25)); }
}
.kal-item-title { --kal-title-fs: var(--kal-item-fs, 1rem); font-size: var(--kal-title-fs); font-weight: 800; color: #1E293B; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.kal-item-meta { font-size: calc(var(--kal-item-fs, 1rem) * 0.8); font-weight: 600; color: #94a3b8; margin-top: calc(var(--kal-item-fs, 1rem) * 3 / 16); display: flex; align-items: center; flex-wrap: wrap; gap: calc(var(--kal-item-fs, 1rem) * 3 / 16) calc(var(--kal-item-fs, 1rem) * 5 / 16); }
.kal-today-chip { display: inline-block; font-size: calc(var(--kal-item-fs, 1rem) * 0.65); font-weight: 900; text-transform: uppercase; letter-spacing: 0.1em; color: #D55A92; background: rgba(213,90,146,0.1); border-radius: 9999px; padding: calc(var(--kal-item-fs, 1rem) * 2 / 16) calc(var(--kal-item-fs, 1rem) * 8 / 16); }
/* Countdown ("Morgen", "in 3 Tagen"): gleiche Chip-Familie wie Heute,
   Farbe folgt der Status-/Kategoriefarbe des Eintrags */
.kal-count-chip { display: inline-block; font-size: calc(var(--kal-item-fs, 1rem) * 0.65); font-weight: 900; text-transform: uppercase; letter-spacing: 0.1em; color: var(--kb-col, #48A0D6); background: color-mix(in srgb, var(--kb-col, #48A0D6) 11%, transparent); border-radius: 9999px; padding: calc(var(--kal-item-fs, 1rem) * 2 / 16) calc(var(--kal-item-fs, 1rem) * 8 / 16); }
/* Serien-Rhythmus ("Jeden Montag"): dieselbe Chip-Geometrie wie Heute und
   Countdown — identische Schriftgröße, identisches Padding, identischer
   Radius. Drei Pillen in einer Zeile dürfen sich nicht um Bruchteile
   unterscheiden; die Kopplung wiegt hier schwerer als jede eigene Idee.
   Zurückgenommen in Slate statt in einer Markenfarbe: Der Chip beschreibt
   eine Daueraussage und darf nicht mit "Heute" oder "Morgen" um
   Aufmerksamkeit konkurrieren. */
.kal-serie-chip { display: inline-flex; align-items: center; gap: calc(var(--kal-item-fs, 1rem) * 4 / 16); max-width: 100%; min-width: 0; font-size: calc(var(--kal-item-fs, 1rem) * 0.65); font-weight: 900; text-transform: uppercase; letter-spacing: 0.1em; color: #64748b; background: rgba(100,116,139,0.1); border-radius: 9999px; padding: calc(var(--kal-item-fs, 1rem) * 2 / 16) calc(var(--kal-item-fs, 1rem) * 8 / 16); }
/* Ein langer Rhythmus wie "Alle 2 Wochen am Montag, Mittwoch und Freitag"
   darf umbrechen statt die Zeile zu verbreitern — die Meta-Zeile ist ein
   Flex-Container, und ein nicht schrumpfender Chip schöbe die Listenkarte
   sonst über ihren Rand hinaus. Der Text steht direkt im Chip, die Regel
   gehört deshalb auf den Chip selbst. */
.kal-serie-chip { overflow-wrap: anywhere; }
.kal-serie-chip svg { flex-shrink: 0; display: block; opacity: 0.85; }

/* ══ GRUPPEN-PILLE ═══════════════════════════════════════════════════════
   Eine Komponente, zwei Größen: klein in der Meta-Zeile der Listenkarte,
   groß und rechtsbündig neben dem Artikeltitel.

   Bewusst OHNE Markenfarbe. Blau, Grün, Gelb und Pink sind im Kalender
   bereits vollständig an die Kategorien vergeben — eine grüne Gruppen-
   Pille neben einem grünen "Ausflug" würde eine Zusammengehörigkeit
   behaupten, die es nicht gibt. Farbe trägt hier schon eine Bedeutung
   und bekommt keine zweite. Erkannt wird die Pille an Form und Stellung,
   und das ist der zuverlässigere der beiden Kanäle.

   Slate ist dabei kein Ausweichton, sondern das im Modul bereits
   etablierte Register für strukturelle Angaben: .kal-serie-chip steht in
   exakt derselben Farbfamilie. Die Pille tritt damit einer vorhandenen
   Ordnung bei, statt eine sechste Chip-Gattung zu erfinden.

   Zweifarbig statt einfarbig: Das Wort tritt zurück, die Ziffern führen.
   Bei mehreren gewählten Gruppen ist die Ziffernfolge die eigentliche
   Information — "Amina" steht in dieser Einrichtung ohnehin vor jedem
   Namen. Der Unterschied entsteht über zwei echte Farbwerte und NICHT
   über opacity: eine abgeblendete Schrift fiele unter die Kontrast-
   schwelle. #64748b erreicht auf Weiß 4,76:1, #334155 sogar 10,36:1 —
   beide über den 4,5:1, die AA für diese Schriftgrößen verlangt.

   Keine Animation, kein transform, kein will-change: Es gibt nichts, was
   perfObs pausieren müsste, und die Pille erzeugt keine Dauerlast. Aus
   demselben Grund braucht sie keine prefers-reduced-motion-Regel. ── */
.kal-grp {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
    font-weight: 800;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    color: #334155;
}
.kal-grp-wort { text-transform: uppercase; color: #64748b; }
/* Laufweite legt hinter den LETZTEN Buchstaben denselben Zwischenraum wie
   zwischen die Buchstaben. Ohne die negative Gegenrechnung säße das Wort
   sichtbar zu weit vom Trenner entfernt, und die Pille wäre rechts vom
   Wort optisch aufgerissen. Der Betrag ist exakt die Laufweite, nicht ein
   gerundeter Näherungswert. */
.kal-grp-teiler { flex-shrink: 0; width: 1px; background: rgba(100,116,139,0.32); border-radius: 1px; }
.kal-grp-zahlen { display: inline-flex; align-items: baseline; }
.kal-grp-sep { color: #64748b; padding-inline: 0.16em; }

/* ── Kleine Fassung: Meta-Zeile der Listenkarte ──
   Schriftgröße, Polsterung und Laufweite sind Zeichen für Zeichen die der
   Geschwister-Chips (.kal-today-chip, .kal-count-chip, .kal-serie-chip).
   Das ist eine bewusste Entscheidung gegen eine eigene clamp()-Kurve: Die
   Meta-Zeile IST die Komponente, ihre Chips sind aneinander gekoppelt,
   und ein einzelner fluid skalierender Chip zwischen vier festen würde
   das Zeilenbild bei jeder Auflösung anders brechen.
   Der Radius ist trotzdem gerechnet statt als 9999px-Platzhalter gesetzt:
   Die Höhe ist hier ableitbar (Zeilenhöhe plus zweimal Polsterung), also
   ist die exakte halbe Höhe der richtige Wert.
   Kontur statt Füllung — als einziger Chip der Zeile. Das unterscheidet
   die Gattung "für wen" von der Gattung "wann", ohne eine weitere Farbe
   in die Zeile zu bringen. */
.kal-grp--liste {
    font-weight: 900;
    font-size: calc(var(--kal-item-fs, 1rem) * 0.65);
    line-height: 1.5;
    padding: calc(var(--kal-item-fs, 1rem) * 2 / 16) calc(var(--kal-item-fs, 1rem) * 8 / 16);
    gap: calc(var(--kal-item-fs, 1rem) * 5 / 16);
    border-radius: calc(var(--kal-item-fs, 1rem) * 0.65 * 1.5 / 2 + var(--kal-item-fs, 1rem) * 2 / 16);
    /* Zurückgenommen gegenüber der Fassung im Beitrag: In der Meta-Zeile
       steht die Pille an letzter Stelle, war dort aber das dunkelste
       Element — die Ziffern kräftiger als die Uhrzeit davor. Stellung sagte
       "am unwichtigsten", Farbe sagte "am wichtigsten". Nachgegeben hat das
       Gewicht und nicht die Stellung: Die starke Fassung bleibt dem Beitrag
       vorbehalten, wo sie Platz hat und allein steht. #475569 hält auf der
       Karte 7,5:1 und bleibt damit weit über der Schwelle. */
    color: #475569;
    background: rgba(255,255,255,0.55);
    box-shadow: inset 0 0 0 1px rgba(100,116,139,0.20);
}
.kal-grp--liste .kal-grp-wort { letter-spacing: 0.1em; margin-inline-end: -0.1em; }
.kal-grp--liste .kal-grp-teiler { display: none; }

/* ── Titelzeile im Beitrag ──
   Der Titel behält seine Schriftgröße unverändert; die Pille leitet ihre
   sämtlichen Maße aus derselben Größe ab. Dadurch greifen alle clamp-
   Grenzen bei exakt denselben Viewports wie die des Titels — nicht
   ungefähr, sondern konstruktionsbedingt, weil es nur EINE clamp-Kurve
   gibt und jedes Maß ein Bruchteil davon ist. Alle Brüche stehen als
   Verhältnis (11/30, 14/30, ...) und nicht als gerundete Dezimalzahl.
   Bezugsgröße ist der Titelgrad bei 430px: 30px.

   Der Außenabstand nach unten wandert vom Titel auf die Zeile und ist
   dort zeichengleich derselbe Wert. Damit steht der Fließtext bei einem
   Beitrag mit Gruppen-Pille auf dem exakt gleichen Pixel wie bei einem
   ohne — und beim Umbruch in die zweite Zeile klebt die Pille nicht
   unter dem Titelabstand.

   align-items: baseline statt eines gerechneten Versatzes: Die Pille
   sitzt damit auf der Schriftlinie der ersten Titelzeile, auch wenn der
   Titel zwei- oder dreizeilig wird. Das ist die typografisch richtige
   Ausrichtung und braucht keine einzige Magic Number.

   min-width als Umbruchschwelle: Unterschreitet der Platz für den Titel
   das Achtfache seiner eigenen Schriftgröße, rutscht die Pille in eine
   zweite Zeile — rechtsbündig, weil margin-inline-start:auto dort
   weiterwirkt. Nachgerechnet greift das erst deutlich unter 320px; die
   Schwelle ist ein Netz, kein Regelfall.

   Die Pille wird in der HÖHE DES ZEILENKASTENS zentriert, nicht auf der
   Schriftlinie ausgerichtet. Grundlinien-Ausrichtung ist typografisch
   sauber gedacht, sitzt hier aber sichtbar falsch: Sie legt die
   Schriftlinie der Pille auf die des Titels, und weil der Pillenkasten
   darunter noch Unterlänge und Polsterung trägt, hängt er nach unten aus.
   Nachgerechnet bei 430px: Zeilenkasten 34,52px, Pillenkasten 20,01px,
   Pillenmitte bei 24,48px gegen eine Kappenmitte bei 17,72px — der Kasten
   stand 6,76px zu tief, und genau das war im Screenshot zu sehen.

   Der Ausgleich braucht KEINE Schriftmetrik, nur die beiden Höhen:
   (Zeilenhöhe − Pillenhöhe) / 2. Als Bruch aus den Bestandteilen —
   23/20 für die Zeilenhöhe 1.15, 2/3 für die Pillenhöhe 20/30, Differenz
   29/60, halbiert 29/120. Keine gerundete Dezimalzahl, und weil der
   Ausdruck an derselben clamp-Kurve hängt, stimmt er bei jeder Auflösung.
   Kastenmitte und optische Kappenmitte liegen bei dieser Schrift rund
   ein Hundertstel Geviert auseinander, also unter einem halben Pixel. */
.kal-det-titelzeile {
    --kal-tsize: clamp(1.35rem, 6.98vw, 2.34rem);
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: calc(var(--kal-tsize) * 10 / 30);
    margin-bottom: clamp(10.8px, 3.49vw, 18.75px);
}
.kal-det-titelzeile .kal-det-title {
    margin-bottom: 0;
    flex: 0 1 auto;
    min-width: min(100%, calc(var(--kal-tsize) * 8));
}
.kal-grp--det {
    margin-inline-start: auto;
    /* Siehe Herleitung oben: (23/20 − 2/3) / 2 = 29/120 der Titelgröße. */
    margin-block-start: calc(var(--kal-tsize) * 29 / 120);
    font-size: calc(var(--kal-tsize) * 11 / 30);
    line-height: calc(var(--kal-tsize) * 14 / 30);
    padding: calc(var(--kal-tsize) * 3 / 30) calc(var(--kal-tsize) * 11 / 30);
    gap: calc(var(--kal-tsize) * 7 / 30);
    border-radius: calc(var(--kal-tsize) * 10 / 30);
    /* Formensprache der Faktenkarte, nicht die eines aufgeklebten
       Abzeichens: derselbe winzige Verlauf von oben nach unten, dieselbe
       violett getönte Kante statt einer grauen, und die erste Schatten-
       lage der Karte. Die vorige Fassung war flach grau mit klar
       sichtbarer Haarlinie — sie stand im Beitrag, gehörte ihm aber nicht
       an. Die Kante trägt hier 0.08 statt der 0.055 der Karte: Auf zwanzig
       Pixel Höhe muss dieselbe Linie kräftiger sein, um dieselbe Wirkung
       zu haben wie auf einer handbreiten Karte.
       Nur die erste der beiden Schattenlagen der Karte — die zweite hebt
       eine Fläche an, und eine Pille soll nicht schweben. */
    background: linear-gradient(180deg, #fdfdfe 0%, #f3f5f9 100%);
    box-shadow: inset 0 0 0 1px rgba(30, 27, 75, 0.08),
                0 1px 1px rgba(30, 27, 75, 0.028);
}
.kal-grp--det .kal-grp-wort { letter-spacing: 0.15em; margin-inline-end: -0.15em; }
.kal-grp--det .kal-grp-zahlen { letter-spacing: 0.06em; margin-inline-end: -0.06em; }
.kal-grp--det .kal-grp-teiler { height: calc(var(--kal-tsize) * 8 / 30); }
.kal-item-arrow { flex-shrink: 0; color: var(--kb-col, #48A0D6); opacity: 0.45; width: 24px; height: 24px; border-radius: 50%; background: transparent; border: 1px solid currentColor; display: flex; align-items: center; justify-content: center; transition: transform 0.25s cubic-bezier(0.34,1.56,0.64,1), opacity 0.2s ease; }
@media (hover: hover) and (pointer: fine) {
    .kal-item.is-clickable:hover .kal-item-arrow { transform: translateX(2px) scale(1.06); opacity: 0.9; }
}
.kal-item.is-past .kal-item-arrow { color: #94a3b8; opacity: 0.5; }



.kal-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 3rem 2rem; text-align: center; gap: 10px; }
.kal-empty svg { opacity: 0.28; }
.kal-empty p { font-size: 0.78rem; color: #94a3b8; font-weight: 600; line-height: 1.5; }
.kal-list-footer { padding: 16px 18px 10px; display: flex; align-items: center; justify-content: center; gap: 6px; }
.kal-list-footer-dot { width: 4px; height: 4px; border-radius: 50%; background: rgba(0,0,0,0.12); }
#kalLayer2, #kalLayer2 * { font-family: 'Plus Jakarta Sans', system-ui, sans-serif; }
lottie-player, dotlottie-player { display: block; }
/* Zurück und Schließen: eigenständige Glas-Pillen mit eigenem Blur —
   nötig, weil die Leiste im Ruhezustand transparent über dem Hero-Bild
   liegt und ein flacher Grauton dort unlesbar würde. Kein Rahmen, kein
   Glow (Projektstandard für Buttons). */
.kal-back-btn, .kal-l2-close {
    position: relative;
    width: var(--kal-l2-btn); height: var(--kal-l2-btn);
    border-radius: 50%; border: none;
    background: rgba(255,255,255,0.62);
    backdrop-filter: blur(14px) saturate(1.6);
    -webkit-backdrop-filter: blur(14px) saturate(1.6);
    box-shadow: 0 2px 10px rgba(15,23,42,0.10);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background 0.22s ease, transform 0.22s cubic-bezier(0.16,1,0.3,1);
    -webkit-tap-highlight-color: transparent;
    flex-shrink: 0;
    color: #334155;
}
/* Trefferfläche auf 44px angehoben, ohne den Kreis optisch aufzublähen */
.kal-back-btn::after, .kal-l2-close::after {
    content: "";
    position: absolute;
    top: 50%; left: 50%;
    width: 44px; height: 44px;
    transform: translate(-50%, -50%);
}
.kal-back-btn svg, .kal-l2-close svg {
    width: 42%; height: 42%;
    transition: transform 0.26s cubic-bezier(0.16,1,0.3,1);
}
.kal-back-btn:hover, .kal-l2-close:hover { background: rgba(255,255,255,0.88); }
.kal-back-btn:hover svg { transform: translateX(-2px); }
.kal-l2-close:hover svg { transform: rotate(90deg); }
.kal-back-btn:active, .kal-l2-close:active { transform: scale(0.92); }
.kal-back-btn:focus-visible, .kal-l2-close:focus-visible { outline: 2px solid rgba(72,160,214,0.55); outline-offset: 2px; }
/* Kopftitel: erscheint erst, wenn der große Artikeltitel unter die
   Leiste gescrollt ist — Übergang ausschließlich über opacity und
   transform, kein font-size-Morphing (das wäre ein Layout-Trigger und
   bräuchte einen Scroll-Listener). */
.kal-l2-htitle {
    flex: 1;
    min-width: 0;
    text-align: center;
    font-size: clamp(0.8rem, 3.3vw, 1.05rem);
    font-weight: 800;
    color: #1E293B;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    opacity: 0;
    transform: translateY(6px);
    transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.16,1,0.3,1);
    pointer-events: none;
}
.kal-l2-header.is-titled .kal-l2-htitle { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
    .kal-l2-header::before, .kal-l2-htitle, .kal-back-btn, .kal-l2-close, .kal-back-btn svg, .kal-l2-close svg { transition: none; }
    .kal-l2-htitle, .kal-l2-header.is-titled .kal-l2-htitle { transform: none; }
    .kal-back-btn:active, .kal-l2-close:active, .kal-back-btn:hover svg, .kal-l2-close:hover svg { transform: none; }
}
/* ═══════════════════════════════════════════════════
   ERINNERE MICH — Glocke am rechten Ende der Terminzeile
   Der Termin steht links, die Glocke rechts auf gleicher Höhe. Ein Tipp
   öffnet die passenden Vorlaufzeiten; jede erzeugt eine echte Kalender-
   Erinnerung über eine Einzel-ICS mit VALARM. Keine Push-Infrastruktur,
   keine Registrierung, keine Berechtigung — das Betriebssystem übernimmt.

   Bewegung gibt es ausschließlich als Antwort auf eine Berührung —
   kein Dauerlauf, kein will-change, kein Beobachter nötig. Der
   Bestätigungsring läuft genau einmal und ausschließlich über transform
   und opacity; sein Rand steht als statischer box-shadow und wird nie
   neu gezeichnet. */
/* margin-left:auto statt space-between: So sitzt die Glocke auch dann
   rechts, wenn links nichts steht. */
/* Ein einziges Maß für Glocke UND Zeilenhöhe im Auswahlfeld: Damit sind
   Auslöser und Auswahl exakt gleich groß, und der Eckradius der Zeilen ist
   als halbe Höhe ableitbar statt geraten. */
.kal-erin {
    position: relative; flex: 0 0 auto; margin-left: auto; z-index: 8;
    --kal-erin-mass: clamp(52px, 12.093vw, 67.6px);
    /* Zwei Masse, nicht eines: --kal-erin-mass gilt fuer das Bauteil als
       Ganzes und traegt auch die Zeilenhoehe im Auswahlfeld;
       --kal-erin-knopf gilt nur fuer den Knopf. Getrennt sind sie, weil der
       Knopf in der Faktenzeile schrumpfen muss, das Auswahlfeld aber NICHT
       mitschrumpfen darf — es legt sich ueber den Artikel und braucht dort
       unveraendert grosse, sicher treffbare Zeilen. Solange nichts
       ueberschrieben wird, sind beide identisch. */
    --kal-erin-knopf: var(--kal-erin-mass);
}
.kal-erin-btn {
    position: relative;
    display: flex; align-items: center; justify-content: center;
    /* 52px auf dem iPhone Pro Max, Untergrenze klar über dem
       44px-Touch-Floor. */
    width:  var(--kal-erin-knopf);
    height: var(--kal-erin-knopf);
    padding: 0;
    border: none;
    /* Breite und Höhe sind identisch — der exakte Kreis ist ableitbar,
       ein 9999px-Sentinel wäre hier nur geraten. */
    border-radius: 50%;
    cursor: pointer;
    background: rgba(255, 255, 255, 0.58);
    /* Identische Glas-Rezeptur wie das Auswahlfeld — zwei Flächen
       desselben Bauteils dürfen nicht unterschiedlich streuen. */
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    color: var(--kal-acc, #48A0D6);
    /* Zweites inset ist das Innenlicht: eine hauchdünne helle Kante
       INNEN, die dem Glas Tiefe gibt, ohne einen Rahmen zu setzen. */
    /* Der Kontaktschatten steht in einer eigenen Größe, weil er vom
       Untergrund abhängt: Auf dem hellgrauen Seitengrund trägt die Fläche
       selbst zur Abgrenzung bei, auf einer weißen Karte nicht. Die
       Glas-Rezeptur darüber und der farbige Abdruck darunter bleiben in
       jedem Fall unverändert. */
    --kal-erin-kontakt: 0 2px 6px rgba(30, 27, 75, 0.05);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.98),
                inset 0 0 0 1px rgba(255, 255, 255, 0.6),
                var(--kal-erin-kontakt),
                0 16px 34px -18px color-mix(in srgb, var(--kal-acc, #48A0D6) 62%, transparent);
    transition: background-color 0.18s ease, transform 0.22s cubic-bezier(0.34,1.56,0.64,1), box-shadow 0.18s ease;
    -webkit-tap-highlight-color: transparent;
}
.kal-erin-btn:hover {
    background: rgba(255, 255, 255, 0.86);
    transform: translateY(clamp(-2.6px, -0.465vw, -1.2px));
    box-shadow: inset 0 1px 0 #ffffff,
                inset 0 0 0 1px rgba(255, 255, 255, 0.7),
                0 2px 6px rgba(30, 27, 75, 0.05),
                0 22px 42px -20px color-mix(in srgb, var(--kal-acc, #48A0D6) 62%, transparent);
}
.kal-erin-btn:active { transform: scale(0.95); }
.kal-erin-btn:focus-visible { outline: 2px solid color-mix(in srgb, var(--kal-acc, #48A0D6) 40%, transparent); outline-offset: 2px; }
.kal-erin-btn[aria-expanded="true"] { background: color-mix(in srgb, var(--kal-acc, #48A0D6) 12%, #ffffff); }
.kal-erin-ic {
    position: absolute;
    top: 50%; left: 50%;
    width: clamp(18px, 5.116vw, 28.6px);
    height: auto;
    transform: translate(-50%, -50%);
    /* Drehpunkt an der Aufhängung, nicht in der Mitte — eine Glocke
       schwingt um ihre Krone. Gilt für Läuten wie Hover, damit beide
       dieselbe Bewegung beschreiben. */
    transform-origin: 50% 16%;
    transition: transform 0.28s cubic-bezier(0.34,1.56,0.64,1);
}
.kal-erin-ic path { transition: fill 0.26s ease; }

/* ── Läuten: alle 3 Sekunden ein kurzer Schwung ───────────────────────
   Warum überhaupt Bewegung: Die Glocke ist die einzige Funktion im
   Artikel, nach der niemand sucht, weil niemand sie erwartet. Ein
   gedämpfter Schwung holt den Blick — die übrigen gut zwei Sekunden
   steht sie vollkommen still.
   Kosten: eine einzige Rotation auf einem 20px-Element. Kein Layout,
   kein Paint, reine Compositor-Arbeit; die Amplitude klingt wie bei
   einer echten Glocke ab, statt gleichförmig zu wackeln.
   will-change steht bewusst IN dieser Regel und nicht am Element:
   So entsteht der eigene Layer erst mit der Sichtbarkeit und fällt mit
   ihr wieder weg, statt über die gesamte Lesedauer offen zu bleiben.
   Läuft nur, wenn der Beobachter die Glocke im Sichtfeld meldet, noch
   keine Vorlaufzeit gewählt wurde und das Auswahlfeld zu ist — wer
   bereits erinnert wird, braucht keinen Anstoß mehr. */
.kal-erin-btn.is-sicht:not(.is-gesetzt):not([aria-expanded="true"]) .kal-erin-ic {
    will-change: transform;
    animation: kalErinLaeuten 3s linear infinite;
    /* Versatz bei mehreren Glocken, vom Skript je Knopf gesetzt (0,35s pro
       Glocke). Ohne Variable null — der einzelne Termin läutet wie bisher.
       Die Variable steht am Knopf und wird hier am Symbol gelesen, weil sie
       erbt. */
    animation-delay: var(--kal-erin-versatz, 0s);
}
/* Der Anschlag vergrößert die Glocke und lässt sie mit der Amplitude
   wieder abklingen. Die Maßstäbe sind nicht geschätzt, sondern aus der
   Drehung abgeleitet: Zuwachs = 0,26 × (Winkel / 13°), also derselbe
   Dämpfungsverlauf für beide Größen. 11/13 ergibt 0,22 · 7,5/13 ergibt
   0,15 · 5/13 ergibt 0,10 · 2,8/13 ergibt 0,056 · 1,2/13 ergibt 0,024.
   Ohne diese Kopplung liefen zwei Bewegungen nebeneinander her statt
   einer.
   Skaliert wird um denselben Punkt wie gedreht (die Aufhängung): Die
   Glocke wächst nach unten aus ihrer Krone heraus, statt in alle
   Richtungen aufzuquellen. Bei 20px Grundgröße erreicht sie im Anschlag
   rund 25px und bleibt damit klar innerhalb des 52px-Kreises.
   scale ist einheitenlos und von der clamp-Pflicht nicht berührt. */
@keyframes kalErinLaeuten {
    0%        { transform: translate(-50%, -50%) rotate(0deg)     scale(1); }
    3%        { transform: translate(-50%, -50%) rotate(-13deg)   scale(1.26); }
    8%        { transform: translate(-50%, -50%) rotate(11deg)    scale(1.22); }
    13%       { transform: translate(-50%, -50%) rotate(-7.5deg)  scale(1.15); }
    18%       { transform: translate(-50%, -50%) rotate(5deg)     scale(1.1); }
    23%       { transform: translate(-50%, -50%) rotate(-2.8deg)  scale(1.056); }
    28%       { transform: translate(-50%, -50%) rotate(1.2deg)   scale(1.024); }
    32%, 100% { transform: translate(-50%, -50%) rotate(0deg)     scale(1); }
}
/* Der erste Selektor trägt dieselbe Spezifität wie die Läut-Regel und
   steht danach — ohne ihn bliebe der Hover-Anstoß auf dem Desktop
   wirkungslos, weil die Animation gewönne. */
.kal-erin-btn.is-sicht:hover:not(.is-gesetzt) .kal-erin-ic,
.kal-erin-btn:hover .kal-erin-ic { animation: none; transform: translate(-50%, -50%) rotate(-9deg) scale(1.06); }
/* Gewählter Zustand: Die Glocke füllt sich und bekommt einen Punkt —
   sichtbar, ohne dass man das Feld öffnen muss. */
.kal-erin-btn.is-gesetzt .kal-erin-ic path:first-of-type { fill: color-mix(in srgb, var(--kal-acc, #48A0D6) 20%, transparent); }
/* Plakette mit Haken statt schlichtem Punkt: Ein Punkt heißt in fast
   jeder Oberfläche "neu" oder "ungelesen". Hier soll er "erledigt"
   heißen — das kann nur ein Haken. Er sitzt bewusst ein Stück über der
   Kreiskante, damit er auch neben der gefüllten Glocke ablesbar bleibt. */
.kal-erin-haken {
    position: absolute;
    top: -6%; right: -6%;
    display: flex; align-items: center; justify-content: center;
    width:  clamp(9.6px, 3.721vw, 20.8px);
    height: clamp(9.6px, 3.721vw, 20.8px);
    border-radius: 50%;
    background: var(--kal-acc, #48A0D6);
    color: #ffffff;
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.96), 0 2px 5px rgba(30, 27, 75, 0.14);
    opacity: 0;
    transform: scale(0.4);
    transition: opacity 0.2s ease, transform 0.3s cubic-bezier(0.34,1.56,0.64,1);
}
.kal-erin-haken svg { display: block; width: 64%; height: auto; }
.kal-erin-btn.is-gesetzt .kal-erin-haken { opacity: 1; transform: scale(1); }
/* Bestätigungsring: läuft genau einmal, rein auf dem Compositor. Der
   Ring selbst ist ein statischer box-shadow, animiert werden nur Größe
   (transform) und Deckkraft. */
.kal-erin-ring {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    pointer-events: none;
    opacity: 0;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--kal-acc, #48A0D6) 55%, transparent);
}
.kal-erin-btn.is-puls .kal-erin-ring { animation: kalErinRing 0.72s cubic-bezier(0.22,1,0.36,1) 1 both; }
@keyframes kalErinRing {
    from { transform: scale(0.98); opacity: 0.85; }
    to   { transform: scale(1.42); opacity: 0; }
}

.kal-erin-pop {
    position: absolute;
    top: calc(100% + clamp(4.8px, 1.86vw, 10.4px));
    right: 0;
    z-index: 20;
    /* Bewusst schmal: Das Feld legt sich über den Artikeltext, jede
       zusätzliche Breite verdeckt mehr davon. Der Hinweis im Fußbereich
       ist stattdessen so kurz gefasst, dass er in diese Breite passt. */
    min-width: clamp(184px, 51.163vw, 286px);
    display: flex;
    flex-direction: column;
    /* Eine einzige Luft-Einheit für den gesamten Innenrhythmus: Abstand
       Überschrift → erste Zeile, Zeile → Zeile und Zeile → Fußbereich sind
       damit per Definition identisch, nicht nur ungefähr gleich. */
    --kal-erin-luft: clamp(3px, 1.163vw, 6.5px);
    gap: var(--kal-erin-luft);
    padding: clamp(7.2px, 2.791vw, 15.6px) clamp(4.8px, 1.86vw, 10.4px) clamp(4.8px, 1.86vw, 10.4px);
    border-radius: clamp(12px, 4.651vw, 26px);
    background: rgba(255, 255, 255, 0.9);
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.95),
                0 2px 6px rgba(30, 27, 75, 0.06),
                0 18px 40px -16px color-mix(in srgb, var(--kal-acc, #48A0D6) 60%, transparent);
    /* Ursprung oben rechts: Das Feld wächst aus der Glocke heraus,
       nicht aus seiner eigenen Mitte. */
    transform-origin: top right;
    opacity: 0;
    transform: translateY(clamp(-7.8px, -1.395vw, -3.6px)) scale(0.96);
    transition: opacity 0.18s ease, transform 0.18s cubic-bezier(0.22,1,0.36,1);
}
.kal-erin-pop.is-offen { opacity: 1; transform: translateY(0) scale(1); }
.kal-erin-pop[hidden] { display: none !important; } /* flex würde hidden sonst überschreiben */
.kal-erin-kopf {
    font-size: clamp(0.54rem, 2.158vw, 0.66rem); font-weight: 900; text-transform: uppercase; letter-spacing: 0.18em;
    color: color-mix(in srgb, var(--kal-acc, #48A0D6) 72%, #64748b);
    /* Kein eigener Fußabstand — den setzt der gap des Feldes, sonst
       stünde die Überschrift weiter von der ersten Zeile entfernt als die
       Zeilen untereinander. */
    padding: 0 clamp(9.6px, 3.721vw, 20.8px);
    padding-block-end: 0;
}
.kal-erin-opt {
    display: flex; align-items: center; justify-content: space-between;
    gap: clamp(7.2px, 2.791vw, 15.6px);
    width: 100%;
    /* Feste Höhe statt Mindesthöhe plus Innenabstand: So ist jede Zeile
       exakt gleich hoch — die gefüllte aktive Zeile kann nicht mehr höher
       wirken als die übrigen. Der Inhalt zentriert sich darin. */
    height: var(--kal-erin-mass);
    padding: 0 clamp(9.6px, 3.721vw, 20.8px);
    border: none;
    /* Die Höhe ist bekannt, also ist der Radius exakt die halbe Höhe —
       kein 9999px-Sentinel, keine Rundung. */
    border-radius: calc(var(--kal-erin-mass) / 2);
    background: transparent;
    cursor: pointer;
    text-align: left;
    transition: background-color 0.16s ease;
    -webkit-tap-highlight-color: transparent;
}
.kal-erin-opt:hover { background: color-mix(in srgb, var(--kal-acc, #48A0D6) 9%, transparent); }
.kal-erin-opt:active { background: color-mix(in srgb, var(--kal-acc, #48A0D6) 16%, transparent); }
.kal-erin-opt:focus-visible { outline: 2px solid color-mix(in srgb, var(--kal-acc, #48A0D6) 40%, transparent); outline-offset: -2px; }
/* Zwei Zeilen statt zwei Spalten: Vorlaufzeit und Zeitpunkt haben damit
   eine klare Rangfolge statt um dieselbe Breite zu konkurrieren — und
   ein langer Text wie "1 Woche vorher" kann nicht mehr umbrechen. */
.kal-erin-opt-txt { display: flex; flex-direction: column; gap: clamp(0.6px, 0.233vw, 1.3px); min-width: 0; }
.kal-erin-opt-label {
    font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
    font-size: clamp(0.78rem, 3.256vw, 0.92rem);
    font-weight: 800; color: #1e1b4b; line-height: 1.15; white-space: nowrap;
    transition: color 0.16s ease;
}
.kal-erin-opt-wann {
    font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
    font-size: clamp(0.68rem, 2.605vw, 0.8rem);
    font-weight: 600;
    /* Zurückhaltend, aber nicht anstrengend: #64748b liegt auf der hellen
       Fläche bei rund 5,5:1 und damit klar über der AA-Schwelle. */
    color: #64748b;
    line-height: 1.25; white-space: nowrap;
    transition: color 0.16s ease;
}
.kal-erin-opt-ok {
    flex: 0 0 auto; display: block;
    width: clamp(10.8px, 4.186vw, 23.4px); height: auto;
    color: var(--kal-acc, #48A0D6);
    opacity: 0; transform: scale(0.5);
    transition: opacity 0.18s ease, transform 0.3s cubic-bezier(0.34,1.56,0.64,1);
}
.kal-erin-opt.is-aktiv { background: color-mix(in srgb, var(--kal-acc, #48A0D6) 13%, transparent); }
/* Der aktive Zustand färbt bewusst NICHT die Schrift ein: Die
   Akzentfarbe folgt der Kategorie, und eine Beschriftung in Gelb oder
   Grün auf der eigenen hellen Tönung fällt unter jeden Kontrastwert
   (nachgerechnet: 2,55:1 bei Information). Getönte Fläche und Haken
   tragen den Zustand, die Schrift bleibt lesbar — nur der Zeitpunkt
   rückt etwas näher heran. Geprüft über alle sieben Kategoriefarben:
   Beschriftung 13,3:1 bis 14,3:1, Zeitpunkt 6,3:1 bis 6,8:1. */
.kal-erin-opt.is-aktiv .kal-erin-opt-wann  { color: #475569; }
.kal-erin-opt.is-aktiv .kal-erin-opt-ok    { opacity: 1; transform: scale(1); }
/* Eigener Informationsbereich statt loser Fußzeile — erscheint nur dort,
   wo er auch stimmt: bei den Tages-Vorlaufzeiten. Die kurzfristigen
   Optionen tragen ihre Uhrzeit selbst in der Zeile. */
/* Getönte Fläche statt durchgezogener Linie: Eine Linie von Kante zu
   Kante zerschneidet das Feld und wirkt technisch. Der leicht getönte
   Block liegt im selben Raster wie die Zeilen darüber und gehört
   sichtbar dazu. */
.kal-erin-fuss {
    padding: clamp(6px, 2.326vw, 13px) clamp(9.6px, 3.721vw, 20.8px);
    border-radius: clamp(9px, 3.488vw, 19.5px);
    background: color-mix(in srgb, var(--kal-acc, #48A0D6) 7%, transparent);
    font-size: clamp(0.6rem, 2.326vw, 0.72rem); font-weight: 600;
    color: #64748b; line-height: 1.35;
}
.kal-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

@media (prefers-reduced-motion: reduce) {
    .kal-erin-btn, .kal-erin-ic, .kal-erin-ic path, .kal-erin-haken,
    .kal-erin-pop, .kal-erin-opt, .kal-erin-opt-label, .kal-erin-opt-wann, .kal-erin-opt-ok { transition: none; }
    .kal-erin-btn:hover { transform: none; }
    .kal-erin-btn:hover .kal-erin-ic { transform: translate(-50%, -50%); }
    /* Das Läuten entfällt ersatzlos — die Glocke steht dann einfach da. */
    .kal-erin-btn.is-sicht:not(.is-gesetzt):not([aria-expanded="true"]) .kal-erin-ic {
        animation: none; will-change: auto;
    }
    .kal-erin-pop { transform: none; }
    /* Der Bestätigungsring entfällt ersatzlos: Die gefüllte Glocke und der
       Punkt sagen dasselbe, ganz ohne Bewegung. */
    .kal-erin-btn.is-puls .kal-erin-ring { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════
   META-SYSTEM: gemeinsame Maße für Ortskarte, Hinweis und Route-Button
   ──────────────────────────────────────────────────────────────────────
   Drei Bausteine, die zu verschiedenen Zeitpunkten entstanden sind, sehen
   nur dann nach EINER Sache aus, wenn Radius, Innenabstand, Symbolgröße,
   Schatten und Zwischenraum aus derselben Quelle kommen. Genau dafür sind
   diese Variablen da: Sie stehen einmal hier und werden unten ausschließlich
   referenziert — kein Baustein trägt eigene Maße, damit keiner davonlaufen
   kann.

   Verankert am Detail-Body, nicht an :root: Die Werte gehören zu diesem
   Modul und sollen nirgendwo sonst auf der Seite Nebenwirkungen haben.

   Abgeleitete Größen stehen als vollständiger calc()-Ausdruck aus ihren
   Bestandteilen — der Symbol-Radius ist exakt 5/16 der Symbolbreite und
   nicht ein gerundeter Dezimalwert, damit die Rundung auf jeder Breite
   mitwächst statt zu driften.

   Basis wie im gesamten Projekt iPhone Pro Max (430px):
   Radius 17,2 · Innen 7,4/12,0 · Symbol 31,8 · Zwischenraum 9,0.
   Der senkrechte Innenabstand ist gegenüber der ersten Fassung um 2,7px
   je Seite zurückgenommen — das nimmt jeder Zeile 5,4px und der zweizeiligen
   Karte knapp 11px, ohne dass Symbol oder Schrift kleiner werden mussten. */
#kalL2Body {
    --kmeta-r:      clamp(12.38px, 4vw, 21.5px);
    --kmeta-py:     clamp(4.43px, 1.72vw, 9.61px);
    --kmeta-px:     clamp(7.22px, 2.8vw, 15.65px);
    --kmeta-gap:    clamp(6.71px, 2.6vw, 14.53px);
    /* Symbol- und Aktionsmass: 44px auf dem Referenzgeraet — und das ist
       keine Design-Laune, sondern der Beruehrungs-Mindestwert. Zuvor waren
       Glocke und Route-Pfeil 31,8px gross und mussten ihre Trefferflaeche
       ueber ein unsichtbares Pseudo-Element auf 44px aufblasen. Jetzt IST
       die sichtbare Flaeche die Trefferflaeche: ein Konstrukt weniger, und
       beide Schaltflaechen endlich so gross, wie ein Daumen sie erwartet.
       Die Zeile waechst dadurch von 46,6 auf 58,8px — bewusst bezahlt. */
    --kmeta-ic:     clamp(26.4px, 10.233vw, 57.2px);
    --kmeta-ic-r:   calc(var(--kmeta-ic) * 5 / 16);
    --kmeta-space:  clamp(5.42px, 2.1vw, 11.74px);
    /* Mass fuer Aktionen am rechten Zeilenrand — Route-Pfeil wie Glocke.
       Exakt so gross wie die Symbolkachel links: Die Zeile hat damit an
       beiden Enden dasselbe Mass, und weil die Kachel die Zeilenhoehe
       ohnehin vorgibt, kostet die Vergroesserung keinen einzigen Pixel
       Hoehe. Als angenehme Folge wird die Trennlinie spiegelsymmetrisch —
       sie ist links wie rechts um Kachel plus Spaltenabstand eingerueckt.
       Gemeinsames Mass, weil beide in untereinanderliegenden Zeilen sitzen
       und dieselbe Senkrechte teilen muessen — zwei Werte waeren zwei
       Achsen. */
    --kmeta-akt:    var(--kmeta-ic);
    /* Der Schatten ist auf ein Minimum zurückgenommen. Er hatte zuletzt
       die Aufgabe, die Karte allein zu definieren, und zeichnete dafür
       unten eine sichtbare Kante — der klassische App-Karten-Look.
       Die Definition kommt jetzt aus der Fläche selbst: ein kaum messbarer
       Verlauf von reinem Weiß nach oben zu einem Hauch Kühle unten, dazu
       eine Haarlinie bei knapp sechs Prozent. Beides zusammen liest das
       Auge als Materialkante, nicht als Schlagschatten — und genau das
       trennt Papier von Benutzeroberfläche.
       Der Schatten bleibt trotzdem stehen, nur eben als Andeutung: Ohne
       ihn läge die Karte flach in der Seite statt eine Spur darüber. */
    --kmeta-shadow: 0 1px 1px rgba(30, 27, 75, 0.028),
                    0 2px 5px -4px rgba(30, 27, 75, 0.12);
    /* Die Haarlinie als eigener Wert: Faktenkarte und Hinweis müssen
       dasselbe Kastenmodell haben, sonst verschieben sich ihre Textkanten
       um genau diesen einen Pixel gegeneinander. */
    --kmeta-kante:  rgba(30, 27, 75, 0.055);
}

/* ── FAKTENBLOCK: Datum, Uhrzeit und Ort in EINER Figur ───────────────────
   Die frühere Lösung stapelte zwei Systeme: kleine eingefärbte Pille oben,
   große weiße Karte darunter. Zwei Bauformen für zwei gleichrangige
   Angaben — das liest sich wie nachträglich ergänzt, egal wie sauber jede
   für sich gebaut ist.

   Hier trägt EIN ruhiger weißer Block alle Eckdaten. Alle Zeilen teilen
   Höhe, Radius, Schatten, Symbolfläche, Typografie und Innenabstand, weil
   sie sämtlich aus dem Meta-System oben kommen. Geteilt wird nur durch
   eine Haarlinie: Sie kostet keinen vertikalen Raum, während zwei
   getrennte Karten zweimal Rand, zweimal Schatten und den Zwischenraum
   bezahlen müssten.

   Farbe tragen ausschließlich die Symbole — getönte Fläche in der
   Kategoriefarbe, Glyphe in derselben Farbe. Eine vollflächig gefüllte
   Kachel war bei einer einzelnen Zeile noch ein Akzent; bei zwei oder drei
   Zeilen untereinander wäre sie eine blaue Spalte, und der Block würde
   wieder lauter als die Überschrift.

   Kein overflow:hidden: Es war noetig, solange die Trennlinie ein
   border-top ueber die volle Breite war und ueber die Rundung hinauslief.
   Seit sie als beidseitig eingerueckte Haarlinie gezeichnet wird, erreicht
   sie die Ecken gar nicht mehr — und ein Beschnitt haette jetzt einen
   handfesten Schaden: Er wuerde das Auswahlfeld der Glocke an der
   Kartenkante abschneiden. */
.kal-fakten {
    /* Doppelter Grundabstand nach unten — derselbe Wert wie beim Hinweis.
       Der einfache Abstand war die Zäsur INNERHALB der Kopfgruppe; nach
       unten hin endet die Gruppe aber, und der Fließtext beginnt. Beide
       Bausteine tragen deshalb denselben Fuß, egal welcher der letzte ist.
       Der frühere weiße Rahmen ist einer echten Haarlinie gewichen: Weiß
       auf Weiß war unsichtbar und kostete trotzdem zwei Pixel — diese hier
       trägt die Kontur, die dem Schatten abgenommen wurde. */
    margin: 0 0 calc(var(--kmeta-space) * 2);
    border-radius: var(--kmeta-r);
    /* Der Verlauf ist bewusst winzig (rund anderthalb Prozent Helligkeits-
       unterschied): Er soll nicht als Farbe gesehen, sondern als leichte
       Wölbung des Papiers empfunden werden. */
    background: linear-gradient(180deg, #ffffff 0%, #fbfbfd 100%);
    border: 1px solid var(--kmeta-kante);
    box-shadow: var(--kmeta-shadow);
    text-align: left;
}
.kal-fakt {
    display: flex;
    align-items: center;
    gap: var(--kmeta-gap);
    padding: var(--kmeta-py) var(--kmeta-px);
}
/* Trennlinie als Pseudo-Element statt als border-top: Eine Rahmenlinie
   kann nur die volle Breite. Diese hier setzt links erst hinter der
   Symbolspalte an und endet rechts vor dem Rand — dadurch liest sie sich
   als konstruierte Gliederung entlang der Textkante und nicht als
   generischer Tabellenstrich. Der linke Wert ist kein gewaehlter Abstand,
   sondern die exakte Summe aus Innenabstand, Symbolbreite und Spalten-
   abstand: Die Linie beginnt damit auf denselben Pixel wie der Text
   darueber und darunter. */
.kal-fakt + .kal-fakt { position: relative; }
.kal-fakt + .kal-fakt::before {
    content: '';
    position: absolute;
    top: 0;
    left: calc(var(--kmeta-px) + var(--kmeta-ic) + var(--kmeta-gap));
    right: calc(var(--kmeta-px) * 2);
    height: 1px;
    background: rgba(30, 27, 75, 0.09);
    pointer-events: none;
}
/* Trägt die Zeile rechts eine Aktion, endet die Linie exakt vor deren
   Spalte — spiegelbildlich zum linken Ende, das hinter der Symbolspalte
   beginnt. Ohne diese Regel liefe sie unter dem Pfeil hindurch und
   schnitte ihn optisch an; mit ihr rahmen Symbol- und Aktionsspalte die
   Linie beidseitig sauber ein. Wieder keine gewählte Zahl, sondern die
   Summe der Bestandteile. */
.kal-fakt + .kal-fakt:has(.kal-fakt-go)::before,
.kal-fakt + .kal-fakt:has(.kal-fakt-grad)::before {
    right: calc(var(--kmeta-px) + var(--kmeta-akt) + var(--kmeta-gap));
}
.kal-fakt-ic {
    flex: 0 0 auto;
    width: var(--kmeta-ic);
    height: var(--kmeta-ic);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--kmeta-ic-r);
    /* Kraeftiger als zuvor: Bei 12 Prozent verschwamm die Flaeche auf dem
       weissen Karton und das Symbol sah aus, als fehle ihm der Hintergrund.
       17 Prozent traegt es sichtbar, bleibt aber weit unter der gefuellten
       Kachel, die bei mehreren Zeilen eine farbige Spalte ergaebe. */
    background: color-mix(in srgb, var(--kal-acc, #48A0D6) 17%, #ffffff);
    color: var(--kal-acc, #48A0D6);
}
.kal-fakt-ic svg { width: 54%; height: 54%; }
/* Die Symbolsammlung selbst wird nie gezeigt — sie liefert nur die
   Vorlagen fuer <use>. Kein display:none, weil Safari referenzierte
   Symbole aus einem vollstaendig ausgeblendeten Teilbaum in einzelnen
   Faellen nicht mehr zeichnet; stattdessen aus dem Fluss genommen und
   auf null Groesse gesetzt. */
.kal-svg-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
/* Direktweg zur Karte, dort wo die Adresse steht: Der grosse Button unten
   bleibt die abschliessende Hauptaktion, aber wer die Adresse liest, will
   sie oft genau in diesem Moment oeffnen. Das Symbol bleibt bewusst leise —
   die Flaeche ist unsichtbar, aber mit 44px so gross, wie ein Daumen sie
   braucht; sichtbar ist nur der Pfeil. */
.kal-fakt-go {
    flex: 0 0 auto;
    position: relative;
    margin-left: auto;
    /* Sichtbare Groesse = gemeinsames Aktionsmass = 44px = Beruehrungsziel.
       Frueher stand hier ein kleinerer Kreis mit einer unsichtbar
       vergroesserten Trefferflaeche darunter — eine Kruecke, die nur noetig
       war, solange die Zeile nicht wachsen durfte. */
    width: var(--kmeta-akt);
    height: var(--kmeta-akt);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    /* Halbtransparent war die falsche Zurueckhaltung: Ein Pfeil auf halber
       Deckkraft sieht nicht diskret aus, sondern deaktiviert — und stand
       ohne eigene Flaeche verloren am rechten Rand.
       Die Flaeche ist jetzt Zeichen fuer Zeichen dieselbe wie an der
       Glocke: Glas mit Innenlicht, darunter ein farbiger Abdruck in der
       Kategoriefarbe. Das ist keine Kopie um der Einheit willen, sondern
       eine Aussage — beide sind Schaltflaechen, beide oeffnen etwas, und
       sie stehen in derselben Karte untereinander. Die getoente Fuellung
       von zuvor liess den Pfeil wie eine zweite Symbolkachel aussehen,
       also wie etwas zum Ansehen statt zum Antippen. Die gefuellte Kachel
       links und der Glaskreis rechts trennen jetzt genau das:
       Information gegen Handlung. */
    background: rgba(255, 255, 255, 0.58);
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    color: var(--kal-acc, #48A0D6);
    /* Zweites inset ist das Innenlicht: eine hauchduenne helle Kante INNEN,
       die dem Glas Tiefe gibt, ohne einen Rahmen zu setzen. */
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.98),
                inset 0 0 0 1px rgba(255, 255, 255, 0.6),
                0 2px 6px rgba(30, 27, 75, 0.05),
                0 16px 34px -18px color-mix(in srgb, var(--kal-acc, #48A0D6) 62%, transparent);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
    transition: background-color 0.18s ease, box-shadow 0.18s ease, transform 0.2s cubic-bezier(0.34,1.56,0.64,1);
}
.kal-fakt-go svg { width: 46%; height: auto; }
/* ── GRADZAHL IN DER WETTERZEILE ──────────────────────────────────────
   Derselbe Kreis wie Glocke und Pfeil — gleiches Mass, gleiche Senkrechte,
   gleiche Farbe der Ziffern — aber flach: kein Glas, kein Innenlicht,
   kein farbiger Abdruck, keine Erhebung. In dieser Karte bedeutet ein
   erhabener Kreis "hier kann man tippen"; die Gradzahl ist Information,
   nichts weiter. Gleiche Form, andere Tiefe. Damit der flache Kreis auf
   dem weissen Karton ueberhaupt als Flaeche lesbar bleibt, traegt er die
   Haarlinie der Karte selbst (--kmeta-kante) — dieselbe Kontur, die auch
   die Karte vom Papier trennt: eine Scheibe, die AUF der Karte liegt,
   nicht ein Knopf, der aus ihr heraussteht.

   Schriftgroesse aus dem Kreismass, nicht aus der Wertzeile: Der Kreis
   waechst mit dem Geometrie-Faktor (0,6/1,3), die Wertzeile mit dem
   Typografie-Faktor (0,72/1,25) — an einer festen Zahl gebunden liefe die
   Ziffer bei 320px aus dem Kreis. 14,08 ist die Groesse der Wertzeile auf
   dem Referenzgeraet, 44 das Kreismass dort: Auf 430px ist die Gradzahl
   also exakt so gross wie der Text daneben, und sie bleibt auf jeder
   Breite im selben Verhaeltnis zum Kreis. Die breiteste Angabe "−15°"
   misst in Plus Jakarta Sans 700 2,213em, also 31,2 von 44px — je 6,4px
   Luft zur Kreiskante.

   Keine Animation, kein transform, kein will-change, keine Transition:
   nichts fuer perfObs, nichts fuer prefers-reduced-motion. */
.kal-fakt-grad {
    flex: 0 0 auto;
    margin-left: auto;
    width: var(--kmeta-akt);
    height: var(--kmeta-akt);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: #ffffff;
    border: 1px solid var(--kmeta-kante);
    color: var(--kal-acc, #48A0D6);
    font-size: calc(var(--kmeta-akt) * 14.08 / 44);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    -webkit-user-select: none;
    user-select: none;
}
/* ── GLOCKE IN DER DATUMSZEILE ────────────────────────────────────────────
   Sobald die Faktenkarte gebaut wird, hat die Glocke kein Zuhause mehr:
   Frei unter der Karte hing sie im Nichts und riss ein Loch zwischen
   Eckdaten und Text. Sie gehoert an das Datum — man erinnert sich an einen
   Zeitpunkt, nicht an eine Adresse — und damit an das rechte Ende genau
   jener Zeile.

   Alle Masse kommen aus derselben Quelle wie beim Route-Pfeil eine Zeile
   tiefer: gleiche Groesse, gleicher Randabstand, also dieselbe Senkrechte.
   Ueberschrieben wird ausschliesslich --kal-erin-knopf. Ginge stattdessen
   --kal-erin-mass, schrumpften die Zeilen des Auswahlfeldes gleich mit —
   ihre Hoehe und ihr Radius haengen daran. Das Feld legt sich ueber den
   Artikel und braucht dort volle, sicher treffbare Zeilen; es hat mit der
   Enge der Faktenzeile nichts zu tun.

   Glas, Kategoriefarbe, farbiger Abdruck und das Laeuten alle drei
   Sekunden bleiben unveraendert — der Knopf ist dieselbe Schaltflaeche,
   nur in Zeilenmass. Bei 44px deckt er das Beruehrungsziel aus eigener
   Kraft; die unsichtbare Hilfsflaeche darunter ist ersatzlos entfallen. */
.kal-erin.is-inline { --kal-erin-knopf: var(--kmeta-akt); z-index: 12; }
/* Die halbe Knopfbreite ergibt auf dem Referenzgeraet exakt 22px — auf den
   Pixel dieselbe Glocke wie in der frei stehenden Bauform. Kleiner wurde
   also nur die Glasscheibe, das Zeichen selbst nicht. */
.kal-erin.is-inline .kal-erin-ic { width: calc(var(--kal-erin-knopf) / 2); }
/* Die Bestaetigungs-Plakette ist am Knopf bemessen, nicht absolut: Am
   grossen Knopf misst sie 16 von 52px, also genau vier Dreizehntel. Bliebe
   sie bei 16px, waere sie am eingereihten Knopf mehr als halb so gross wie
   dieser und saehe aus wie ein Fehler. Als Bruchteil geschrieben haelt sie
   ihr Verhaeltnis in jeder Bauform — und der Bruch steht ungekuerzt da,
   damit die Herkunft der Zahl lesbar bleibt. */
.kal-erin.is-inline .kal-erin-haken {
    width:  calc(var(--kal-erin-knopf) * 4 / 13);
    height: calc(var(--kal-erin-knopf) * 4 / 13);
}
.kal-fakt-go:hover {
    background: rgba(255, 255, 255, 0.86);
    box-shadow: inset 0 1px 0 #ffffff,
                inset 0 0 0 1px rgba(255, 255, 255, 0.7),
                0 2px 6px rgba(30, 27, 75, 0.05),
                0 22px 42px -20px color-mix(in srgb, var(--kal-acc, #48A0D6) 62%, transparent);
    transform: translate(1px, -1px);
}
.kal-fakt-go:focus-visible { outline: 2px solid color-mix(in srgb, var(--kal-acc, #48A0D6) 45%, transparent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .kal-fakt-go { transition: none; } .kal-fakt-go:hover { transform: none; } }
.kal-fakt-txt { display: flex; flex-direction: column; min-width: 0; }
/* Laufweite von 0,09em auf 0,06em: Weit gesperrte Versalien wirken wie
   Beschriftung an einem Geraet. Enger gesetzt lesen sie sich als Rubrik —
   erwachsener, und die kurzen Woerter zerfallen nicht in Einzelbuchstaben.
   Groesse von 8,88 auf 10,10px: Mit der auf 44px gewachsenen Symbolspalte
   war die Rubrik zur Fussnote geschrumpft — sie benennt aber, WAS in der
   Zeile steht, und muss ohne Anstrengung lesbar sein. Der Kontrast steigt
   dabei mit: Ab dieser Groesse traegt #6b7280 die 4,5:1-Schwelle nicht nur
   knapp, sondern mit Reserve. */
.kal-fakt-label { font-size: clamp(0.379rem, 2.35vw, 0.821rem); font-weight: 800; text-transform: uppercase; letter-spacing: 0.06em; line-height: 1.45; color: #6b7280; }
.kal-fakt-val { font-size: clamp(0.634rem, 3.274vw, 1.1rem); font-weight: 700; color: #1e1b4b; line-height: 1.25; overflow-wrap: anywhere; }

/* ── TERMINBLOCK: mehrere Termine ────────────────────────────────────────
   Baut auf dem Eckdaten-Block auf (.kal-fakten, .kal-fakt, Rubrik und
   Wert) und bringt drei eigene Dinge mit:

   1. DAS KALENDERBLATT DER LISTE. Keine zweite, kleinere Zeichnung, sondern
      exakt .kal-bubble — dieselbe Kachel mit Stiften, Band, Tag und Monat,
      die Eltern von der Karte kennen. Dieselbe Form an beiden Orten ist das
      stärkste Zeichen, dass Karte und Artikel dasselbe meinen. Die Kachel ist
      auf 54px kalibriert; eine verkleinerte Fassung hätte den Monat unter die
      Lesbarkeitsgrenze gedrückt. Die Kategoriefarbe kommt aus --kal-acc,
      vergangene Termine nehmen dasselbe Grau wie das Archiv der Liste.

   2. EINE GEMEINSAME SPALTE. Die Kachelspalte ist 54px breit. Die Ortszeile
      im Block hat ein kleineres Symbol; es steht exakt mittig in derselben
      Spalte, damit alle Texte auf einer Kante beginnen. Die Haarlinien
      beginnen und enden an genau diesen Kanten.

   3. RUHE STATT MARKIERUNG. Der nächste Termin trägt eine hauchzarte Fläche
      und einen Zustands-Chip; vergangene Termine treten zurück. Kein Rahmen,
      keine Unterstreichung, kein Glow.

   Alle fluiden Maße kommen aus den Block-Variablen oder aus dem Wert-clamp
   mit identischen Grenzen. Die festen 54px und 3,3px gehören zur Kachel
   und sind dort dokumentiert kalibriert. */
.kal-fakten--termine {
    --kal-tb-spalte: 54px;
    --kb-col: var(--kal-acc, #48A0D6);
    --kb-shadow: color-mix(in srgb, var(--kal-acc, #48A0D6) 16%, transparent);
}
/* Die Stifte ragen 3,3px über die Kachel hinaus. Genau so viel Polster kommt
   oben und unten dazu, damit sie nie an der Haarlinie der Zeile darüber
   anstehen und die Kachel optisch mittig in ihrer Zeile bleibt. */
/* STAPELUNG: Jede Zeile ist ein eigener Stapelkontext (isolation), damit die
   Sprung-Markierung mit z-index −1 unter dem Inhalt und über der Zeilenfläche
   liegt. Die Kehrseite: Ein Auswahlfeld der Glocke kann dann nicht über die
   FOLGENDEN Zeilen hinausragen — die stehen später im Markup und würden samt
   Text darübergemalt. Deshalb hebt sich eine Zeile, solange ihr Auswahlfeld
   im Layout steht, als Ganzes über ihre Geschwister. Bewusst am hidden-
   Attribut und nicht an aria-expanded: Beim Schliessen blendet das Feld noch
   260ms aus, und so lange muss die Zeile oben bleiben. */
.kal-termin:has(.kal-erin-pop:not([hidden])) { z-index: 30; }
.kal-termin {
    position: relative;
    isolation: isolate;
    align-items: center;
    padding-top: calc(var(--kmeta-py) + 3.3px);
    padding-bottom: calc(var(--kmeta-py) + 3.3px);
}
.kal-termin-kachel { flex: 0 0 var(--kal-tb-spalte); display: flex; justify-content: center; }
.kal-termin-kachel .kal-bubble { box-shadow: inset 0 1px 0 rgba(255,255,255,0.95), 0 1px 1.5px rgba(30,41,59,0.10), 0 3px 10px var(--kb-shadow); }
/* Zeitraum: Das vordere Blatt trägt einen Abreißblock (.hat-stapel). Wie in
   der Liste gibt es seinen farbigen Streuschatten an das Blatt darunter ab —
   sonst läge er über den Blättern und verwüsche sie. Die Regel darüber ist
   gleich spezifisch und steht später; ohne diese Zeile gewönne sie. */
.kal-termin-kachel .kal-bubble.hat-stapel { box-shadow: inset 0 1px 0 rgba(255,255,255,0.95), 0 1px 1.5px rgba(30,41,59,0.10); }
/* ── DURCHBLÄTTERN (mehrtägige Schließtage) ──────────────────────────────
   Die Seite, die kalBlattFlip() nur für die Dauer des Blätterns einsetzt.
   Gebaut wie .kal-seite-vorne: Hintergrund, Kontur, Radius und Polsterung
   wörtlich von der Kachel geerbt, mit inset −1px exakt auf deren
   Randkasten — Band, Tag und Monat stehen pixelgleich über denen der
   Kachel. Die Stifte zeichnet nur das Band der Kachel (kb-no-rings).
   Drehpunkt ist die Bindung oben mittig; die Perspektive steht in der
   transform-Funktion (JS), nicht als Eigenschaft an der Kachel — die bildete
   sonst einen Stapelkontext, und der Blattstapel mit negativem z-index
   läge über ihrem Hintergrund. Bewegt wird ausschließlich transform;
   will-change setzt das Skript inline und entfernt es mit der Seite. */
.kal-flip-seite {
    position: absolute;
    inset: -1px;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: inherit;
    background: inherit;
    border: inherit;
    border-radius: inherit;
    transform-origin: 50% 0;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    pointer-events: none;
}
/* Abdunkelung der Seite, 1:1 wie .kal-seite-vorne::after beim Umblättern:
   dieselbe Farbe, derselbe Radius (Kachelradius minus Kontur, weil die
   Ebene innerhalb der Kontur liegt). Als eigenes Element, damit die Web
   Animations API es erreicht; animiert wird nur opacity. Im Markup zuletzt,
   liegt es damit über Band, Tag und Monat. */
.kal-flip-schatten {
    position: absolute;
    inset: 0;
    border-radius: calc(8px - 1px);
    background: #1E293B;
    opacity: 0;
    pointer-events: none;
}
/* Das Skript startet bei reduzierter Bewegung gar nicht erst; diese Zeile
   sichert nur ab, falls die Einstellung während eines Laufs wechselt. */
@media (prefers-reduced-motion: reduce) {
    .kal-flip-seite { display: none; }
}
.kal-fakten--termine .kal-fakt:not(.kal-termin) .kal-fakt-ic {
    margin-inline: calc((var(--kal-tb-spalte) - var(--kmeta-ic)) / 2);
}
/* Haarlinie: beginnt an der Textkante (Polster + Spalte + Abstand) und endet
   vor der Aktionsspalte, wenn die Zeile eine Glocke oder einen Pfeil trägt. */
.kal-fakten--termine .kal-fakt + .kal-fakt::before {
    left: calc(var(--kmeta-px) + var(--kal-tb-spalte) + var(--kmeta-gap));
}
.kal-fakten--termine .kal-fakt + .kal-fakt:has(.kal-erin)::before {
    right: calc(var(--kmeta-px) + var(--kmeta-akt) + var(--kmeta-gap));
}

/* Nächster Termin: eine Fläche, die von links nach rechts ausläuft — sie
   gehört zur Kachel und zum Text, nicht zur Glocke. Die Ecken folgen dem
   Block, innen um die Kontur verringert. */
.kal-termin.is-naechster {
    background: linear-gradient(90deg, color-mix(in srgb, var(--kal-acc, #48A0D6) 9%, #ffffff) 0%, color-mix(in srgb, var(--kal-acc, #48A0D6) 3%, #ffffff) 100%);
}
.kal-termin.is-naechster:first-child { border-top-left-radius: calc(var(--kmeta-r) - 1px); border-top-right-radius: calc(var(--kmeta-r) - 1px); }
.kal-termin.is-naechster:last-child { border-bottom-left-radius: calc(var(--kmeta-r) - 1px); border-bottom-right-radius: calc(var(--kmeta-r) - 1px); }

/* Gruppe und Chip als Flex-Zeile: Der Chip steht auf der Mitte der
   Textzeile, statt über eine geschätzte vertical-align-Verschiebung
   hingeschoben zu werden. Bricht der Platz, wandert er sauber in eine
   eigene Zeile darunter. */
.kal-termin .kal-fakt-val { display: flex; flex-wrap: wrap; align-items: center; column-gap: 0.55em; row-gap: calc(var(--kmeta-py) / 2); }
/* Zustands-Chip hinter der Gruppe. Schrift, Gewicht und Laufweite der
   Rubrik — derselbe clamp() wie .kal-fakt-label, also exakt gekoppelt;
   Polster und Radius in em, sie wachsen mit. Die Textfarbe ist die
   Kategoriefarbe zu 55 % mit #1E293B gemischt: Rein wäre Gelb auf Weiß rund
   2:1, die Mischung hält bei allen Kategoriefarben 4,5:1. */
.kal-termin-chip {
    display: inline-block;
    padding: 0.34em 0.7em;
    /* Exakt die halbe Höhe: Zeilenhöhe 1em plus zweimal 0,34em Polster. */
    border-radius: calc((1em + 0.34em * 2) / 2);
    font-size: clamp(0.379rem, 2.35vw, 0.821rem);
    font-weight: 800;
    letter-spacing: 0.06em;
    line-height: 1;
    text-transform: uppercase;
    white-space: nowrap;
    color: color-mix(in srgb, var(--kal-acc, #48A0D6) 55%, #1E293B);
    background: color-mix(in srgb, var(--kal-acc, #48A0D6) 15%, #ffffff);
}
.kal-termin.is-vorbei .kal-termin-chip { color: #64748b; background: #f1f5f9; }

/* Vergangen: lesbar, aber zurückgenommen — dieselbe Graustufe wie das
   Archiv der Liste, damit "vorbei" überall gleich aussieht. */
.kal-termin.is-vorbei { --kb-col: #94a3b8; --kb-shadow: rgba(0,0,0,0.04); }
.kal-termin.is-vorbei .kal-bubble { opacity: 0.7; }
.kal-termin.is-vorbei .kal-fakt-val { color: #6b7280; }
.kal-termin.is-vorbei .kal-fakt-label { color: #94a3b8; }

/* Nebenzeilen: eigener Ort und Wetter dieses Termins. Schrift 13/15 des
   Werts mit identischen clamp()-Grenzen. Keine Unterstreichung: Der Ort ist
   eine ruhige Zeile mit Ortsmarke vorn und einem kleinen Pfeil hinten, der
   sagt, dass sie zur Karte führt. Die Trefferfläche wächst unsichtbar auf
   44px, ohne die Zeile höher zu machen. */
.kal-termin-zeile {
    display: flex;
    align-items: center;
    gap: 0.4em;
    margin-top: calc(var(--kmeta-py) / 2);
    font-size: calc(clamp(0.634rem, 3.274vw, 1.1rem) * 13 / 15);
    font-weight: 600;
    line-height: 1.35;
    color: #475569;
}
.kal-termin-zeile-txt { min-width: 0; overflow-wrap: anywhere; }
.kal-termin-zeile svg { flex: 0 0 auto; width: 1.05em; height: 1.05em; color: var(--kal-acc, #48A0D6); }
.kal-termin.is-vorbei .kal-termin-zeile { color: #94a3b8; }
.kal-termin.is-vorbei .kal-termin-zeile svg { color: #94a3b8; }
a.kal-termin-ort { position: relative; align-self: flex-start; color: #475569; text-decoration: none; }
.kal-termin-go { display: inline-flex; flex: 0 0 auto; opacity: 0.55; }
.kal-termin-go svg { width: 0.8em; height: 0.8em; color: currentColor; }
a.kal-termin-ort::after { content: ''; position: absolute; left: 0; right: 0; top: 50%; height: 44px; transform: translateY(-50%); }
a.kal-termin-ort:focus-visible { outline: 2px solid color-mix(in srgb, var(--kal-acc, #48A0D6) 45%, transparent); outline-offset: 3px; border-radius: 6px; }
@media (hover: hover) { a.kal-termin-ort:hover .kal-termin-zeile-txt { color: #1E293B; } a.kal-termin-ort:hover .kal-termin-go { opacity: 1; } }

/* ── SPRUNG AUS DER ZEITLEISTE ──
   Kein Rahmen um die Zeile, sondern eine eigene weiche Karte, die in der
   Zeile aufgeht: um ein halbes Polster eingerückt, Eckradius exakt
   konzentrisch zum Block (Blockradius minus Einzug), Fläche und feine Kontur
   in der Kategoriefarbe, dazu ein weicher Schatten. Sie liegt UNTER dem Inhalt
   (z-index −1 im eigenen Stapelkontext der Zeile) — Text, Kachel und Glocke
   bleiben unverändert scharf.
   Ablauf: 0,35s warten, bis die Ebene weitgehend hereingefahren ist; weich
   aufgehen und dabei von 96 % auf volle Größe; 1s stehen; weich vergehen.
   Nur transform und opacity, einmalig auf eine Handlung hin. Das Skript
   entfernt die Klasse nach 2,4s. Bei reduzierter Bewegung steht die Karte
   ohne Bewegung und verschwindet mit der Klasse. */
.kal-termin::after {
    content: '';
    position: absolute;
    z-index: -1;
    inset: calc(var(--kmeta-py) / 2) calc(var(--kmeta-px) / 2);
    border-radius: calc(var(--kmeta-r) - var(--kmeta-px) / 2);
    background: color-mix(in srgb, var(--kal-acc, #48A0D6) 11%, #ffffff);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--kal-acc, #48A0D6) 22%, transparent), 0 6px 18px color-mix(in srgb, var(--kal-acc, #48A0D6) 16%, transparent);
    opacity: 0;
    transform: scale(0.96);
    pointer-events: none;
}
@keyframes kalTerminZiel {
    0%   { opacity: 0; transform: scale(0.96); animation-timing-function: cubic-bezier(0.22,1,0.36,1); }
    22%  { opacity: 1; transform: scale(1); }
    70%  { opacity: 1; transform: scale(1); animation-timing-function: cubic-bezier(0.4,0,0.6,1); }
    100% { opacity: 0; transform: scale(1); }
}
.kal-termin.is-ziel::after { animation: kalTerminZiel 1.9s linear 0.35s both; }
@media (prefers-reduced-motion: reduce) {
    .kal-termin.is-ziel::after { animation: none; opacity: 1; transform: none; }
}

/* ── AUSTEILEN ───────────────────────────────────────────────────────────
   Zwei Zustände, beide nur, solange das Skript sie setzt:
   .kal-austeil  Ausgangslage: Die Kacheln liegen gestapelt hinter der
                 ersten, die Zeileninhalte sind noch nicht zu sehen.
   .is-an        Bewegung: Kacheln gleiten in ihre Zeilen, Inhalte folgen.
   Danach entfernt das Skript beide Klassen — der Block steht wieder
   vollständig ohne Sonderregel, ohne will-change und ohne Stapelkontexte
   der Bewegung.

   STAPEL, exakt wie der Abreißblock der Karte: Stufe k (der erste Termin
   ist Stufe 0) liegt um den gemessenen Weg --kal-aus-dy nach oben versetzt,
   also deckungsgleich mit der ersten Kachel. Von dort ragt sie um 5px je
   Stufe heraus und ist je Seite um 3px schmaler. Skaliert wird um die
   Oberkante; die Verkleinerung hebt die Unterkante um 56px × (1 − s), das
   wird im Versatz ausgeglichen. Mit s = 1 − 6k/54 = 1 − k/9 ergibt das
   genau 56px × k/9 zusätzlich. Deckkraft 1,8 − 0,6k, begrenzt auf 0…1:
   Stufe 1 voll, Stufe 2 bei 60 %, ab Stufe 3 unsichtbar — wie auf der
   Karte, wo der Block auf zwei Blätter begrenzt ist.

   DECKUNG: Damit die erste Kachel oben liegt, obwohl ihre Zeile im Markup
   zuerst steht, geben die Zeilen während des Austeilens ihren eigenen
   Stapelkontext ab (isolation: auto), und jede Kachel bekommt einen
   z-index, der mit der Stufe fällt. Die Zeilenflächen liegen darunter.
   Die Sprung-Markierung, die den Stapelkontext braucht, beginnt ohnehin
   erst nach dem Austeilen.

   DIE ERSTE ZEILE bleibt vollständig sichtbar: Ihre Kachel ist das oberste
   Blatt und bewegt sich nicht. Ausgeblendet werden nur die Inhalte der
   Zeilen, deren Kachel noch unterwegs ist — so steht nie eine leere Karte da.

   ZEITPLAN: Alle Werte kommen als Variablen aus dem Skript (eine Quelle).
   Kachel   Basis + k × Stufe, Dauer Kachel
   Inhalt   Basis + k × Stufe + Text, Dauer Textdauer
   Basis ist die Restzeit des Hereinfahrens der Ebene plus eine kurze Pause.
   Die Werte hinter dem Komma der var() sind nur Rückfall.
   --kal-aus-t zieht bei einer Fortsetzung nach dem Nachladen alle
   Verzögerungen um die bereits verstrichene Zeit vor.
   Weicher Auslauf ohne Nachfedern. Nur transform und opacity. */
.kal-fakten--termine.kal-austeil .kal-termin { isolation: auto; }
/* ── AUFKLAPPEN DER ERSTEN KACHEL ────────────────────────────────────────
   Während ausgeteilt wird, klappt die erste Kachel einmal auf und zeigt den
   nächsten Termin. Zwei Seiten liegen dafür deckungsgleich auf ihr: oben ihr
   eigenes Datum, darunter das nächste. Nur die obere bewegt sich.

   Bewegung und Werte wie beim Umblättern auf der Karte in der Liste:
   .kal-seite-vorne, Bindung oben, 78° (dort gerechnet: Die Seite verdeckt
   dann nur noch 13,3px oben, Tag und Monat darunter liegen frei),
   perspective(600px), leichte Abdunkelung.

   Zwei Animationen an derselben Seite: Das Aufklappen hält seinen Endzustand
   (both), das Zufallen wirkt erst ab seiner Verzögerung (forwards) und
   gewinnt dann, weil es später steht. Mit backwards stünde die Kachel von
   Anfang an offen.

   Ausserhalb des Austeilens sind beide Seiten ausgeblendet — die Zeile zeigt
   dann schlicht ihr eigenes Datum, und es steht nirgends zweimal dasselbe.

   SPEZIFITÄT: Das Ausblenden muss .kal-bubble.hat-umblatt .kal-seite-vorne aus
   der Liste schlagen (drei Klassen), sonst blieben die Seiten nach dem
   Austeilen stehen — und ohne die Klasse .kal-austeil fielen beide auf
   denselben z-index zurück, sodass die im Markup spätere oben läge und die
   Zeile das Datum des nächsten Termins zeigte. Deshalb tragen alle Regeln hier
   den vollen Pfad inklusive .kal-bubble.hat-umblatt. */
.kal-termin .kal-bubble.hat-umblatt .kal-seite-vorne { display: none; }
.kal-austeil .kal-termin:first-child .kal-bubble.hat-umblatt .kal-seite-vorne { display: flex; z-index: 9; }
.kal-austeil .kal-termin:first-child .kal-bubble.hat-umblatt .kal-seite-vorne.ist-naechste { z-index: 8; }
@keyframes kalKlappAuf {
    from { transform: perspective(600px) rotateX(0deg); }
    to   { transform: perspective(600px) rotateX(78deg); }
}
@keyframes kalKlappZu {
    from { transform: perspective(600px) rotateX(78deg); }
    to   { transform: perspective(600px) rotateX(0deg); }
}
@keyframes kalKlappSchattenAuf { from { opacity: 0; } to { opacity: 0.1; } }
@keyframes kalKlappSchattenZu  { from { opacity: 0.1; } to { opacity: 0; } }
.kal-austeil.is-an .kal-termin:first-child .kal-seite-vorne:not(.ist-naechste) {
    will-change: transform;
    animation:
        kalKlappAuf var(--kal-klapp-auf, 0.24s) cubic-bezier(0.22,1,0.36,1) calc(var(--kal-aus-t, 0s) + var(--kal-klapp-basis, 0s)) both,
        kalKlappZu  var(--kal-klapp-zu, 0.26s) cubic-bezier(0.5,0,0.5,1)   calc(var(--kal-aus-t, 0s) + var(--kal-klapp-zu-start, 0s)) forwards;
}
.kal-austeil.is-an .kal-termin:first-child .kal-seite-vorne:not(.ist-naechste)::after {
    animation:
        kalKlappSchattenAuf var(--kal-klapp-auf, 0.24s) cubic-bezier(0.22,1,0.36,1) calc(var(--kal-aus-t, 0s) + var(--kal-klapp-basis, 0s)) both,
        kalKlappSchattenZu  var(--kal-klapp-zu, 0.26s) cubic-bezier(0.5,0,0.5,1)   calc(var(--kal-aus-t, 0s) + var(--kal-klapp-zu-start, 0s)) forwards;
}
@media (prefers-reduced-motion: reduce) {
    .kal-austeil .kal-termin:first-child .kal-bubble.hat-umblatt .kal-seite-vorne { display: none; }
}
.kal-austeil .kal-termin-kachel {
    --kal-aus-trans: translateY(calc(var(--kal-aus-dy, 0px) + 5px * var(--kal-aus-k, 0) + 56px * var(--kal-aus-k, 0) / 9)) scale(calc(1 - var(--kal-aus-k, 0) / 9));
    --kal-aus-op: clamp(0, 1.8 - 0.6 * var(--kal-aus-k, 0), 1);
    position: relative;
    z-index: var(--kal-aus-z, 1);
    transform-origin: 50% 0;
    transform: var(--kal-aus-trans);
    opacity: var(--kal-aus-op);
}
.kal-austeil .kal-termin:not(:first-child) > .kal-fakt-txt,
.kal-austeil .kal-termin:not(:first-child) > .kal-erin,
.kal-austeil .kal-fakt:not(.kal-termin) > * { opacity: 0; }
.kal-austeil .kal-fakt + .kal-fakt::before { opacity: 0; }
/* Glocken läuten erst, wenn ihre Kachel angekommen ist: Während des
   Austeilens ruht das Läuten; mit dem Entfernen der Klasse setzt die Welle
   von oben nach unten ein. */
.kal-austeil .kal-erin-btn.is-sicht:not(.is-gesetzt):not([aria-expanded="true"]) .kal-erin-ic { animation: none; will-change: auto; }

@keyframes kalAusKachel {
    from { transform: var(--kal-aus-trans); opacity: var(--kal-aus-op); }
    to   { transform: translateY(0) scale(1); opacity: 1; }
}
@keyframes kalAusInhalt {
    /* Der kleine Hub in em statt px: Er wächst mit der Schrift der Zeile. */
    from { opacity: 0; transform: translateY(0.25em); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes kalAusLinie {
    from { opacity: 0; }
    to   { opacity: 1; }
}
.kal-austeil.is-an .kal-termin:not(:first-child) .kal-termin-kachel {
    will-change: transform, opacity;
    animation: kalAusKachel var(--kal-aus-kachel, 0.55s) cubic-bezier(0.22,1,0.36,1) both;
    animation-delay: calc(var(--kal-aus-t, 0s) + var(--kal-aus-basis, 0.08s) + var(--kal-aus-k, 0) * var(--kal-aus-stufe, 0.13s));
}
.kal-austeil.is-an .kal-termin:not(:first-child) > .kal-fakt-txt,
.kal-austeil.is-an .kal-termin:not(:first-child) > .kal-erin,
.kal-austeil.is-an .kal-fakt:not(.kal-termin) > * {
    will-change: transform, opacity;
    animation: kalAusInhalt var(--kal-aus-textdauer, 0.4s) cubic-bezier(0.22,1,0.36,1) both;
    animation-delay: calc(var(--kal-aus-t, 0s) + var(--kal-aus-basis, 0.08s) + var(--kal-aus-k, 0) * var(--kal-aus-stufe, 0.13s) + var(--kal-aus-text, 0.18s));
}
.kal-austeil.is-an .kal-fakt + .kal-fakt::before {
    animation: kalAusLinie var(--kal-aus-textdauer, 0.4s) cubic-bezier(0.22,1,0.36,1) both;
    animation-delay: calc(var(--kal-aus-t, 0s) + var(--kal-aus-basis, 0.08s) + var(--kal-aus-k, 0) * var(--kal-aus-stufe, 0.13s) + var(--kal-aus-text, 0.18s));
}
@media (prefers-reduced-motion: reduce) {
    .kal-austeil .kal-termin-kachel { transform: none; opacity: 1; }
    .kal-austeil .kal-termin:not(:first-child) > .kal-fakt-txt,
    .kal-austeil .kal-termin:not(:first-child) > .kal-erin,
    .kal-austeil .kal-fakt:not(.kal-termin) > *,
    .kal-austeil .kal-fakt + .kal-fakt::before { opacity: 1; }
    .kal-austeil.is-an .kal-termin:not(:first-child) .kal-termin-kachel,
    .kal-austeil.is-an .kal-termin:not(:first-child) > .kal-fakt-txt,
    .kal-austeil.is-an .kal-termin:not(:first-child) > .kal-erin,
    .kal-austeil.is-an .kal-fakt:not(.kal-termin) > *,
    .kal-austeil.is-an .kal-fakt + .kal-fakt::before { animation: none; will-change: auto; }
}

/* Breite Ansichten: Genau ZWEI Zeilen (Datum und Ort) dürfen nebeneinander
   stehen — die häufigste und die schmalste Variante. Bei drei Zeilen bliebe
   eine allein in der zweiten Reihe stehen und ihre Trennlinie zeigte in die
   falsche Richtung; deshalb entscheidet data-n und nicht auto-fit. */
@media (min-width: 720px) {
    .kal-fakten[data-n="2"] { display: grid; grid-template-columns: 1fr 1fr; }
    .kal-fakten[data-n="2"] .kal-fakt + .kal-fakt::before {
        /* Waagrecht wird senkrecht: Dieselbe Linie, um 90 Grad gedreht,
           mit demselben Abstand oben und unten wie im Hochformat seitlich. */
        top: var(--kmeta-py);
        bottom: var(--kmeta-py);
        left: 0;
        right: auto;
        height: auto;
        width: 1px;
    }
}

/* Untertitel: bisher als Inline-Stil am Element, jetzt als Klasse — der
   Abstand nach unten muss wissen, ob der Faktenblock folgt, und das kann
   ein Inline-Stil nicht. Folgt er, rückt er näher heran: Untertitel und
   Eckdaten sind eine Lesegruppe. */
/* Doppelte Klasse im Selektor, nicht aus Vorliebe: Das Element traegt
   auch .kal-det-text, und dessen Regeln stehen weiter unten im Stylesheet.
   Bei gleicher Spezifitaet gaebe die spaetere Regel den Ausschlag und der
   Untertitel saehe aus wie Fliesstext. Zwei Klassen entscheiden die Frage
   unabhaengig von der Reihenfolge. */
/* Der Untertitel wird NICHT getrennt. Er ist eine Bildunterschrift zum
   Titel und wird als Ganzes gelesen, nicht Zeile für Zeile — ein
   Trennstrich darin liest sich als Fehler, nicht als Typografie.
   hyphens:none unterdrückt auch die server-seitigen Soft-Hyphens, das lange
   Wort rutscht dadurch vollständig in die nächste Zeile. overflow-wrap
   bleibt als Notausgang: Passt ein Wort allein nicht in die Zeile, bricht
   es hart um, statt aus der Spalte zu laufen. */
.kal-det-text.kal-det-untertitel {
    font-size: 1rem;
    font-weight: 600;
    color: #1E293B;
    text-align: left;
    hyphens: none;
    -webkit-hyphens: none;
    overflow-wrap: break-word;
    margin-bottom: clamp(8.4px, 3.256vw, 18.2px);
}
.kal-det-text.kal-det-untertitel:has(+ .kal-fakten) { margin-bottom: var(--kmeta-space); }

/* ── HINWEIS ──────────────────────────────────────────────────────────────
   Kein Alarm, sondern eine organisatorische Nachricht: Bei Regen drinnen,
   Treffpunkt verschoben, bitte Gummistiefel mitgeben. Deshalb Rosé statt
   Signalrot, eine Haarlinie statt eines Rahmens und ein Symbol ohne eigene
   Fläche — ein gekachelter Kreis daneben sähe aus wie ein zweiter Button
   und würde zum Antippen einladen, wo es nichts anzutippen gibt.
   Die Tinte ist dunkel genug für sicheren Kontrast auf der hellen Fläche
   und trotzdem weit weg vom Fehlermeldungs-Rot.
   Kein Blinken, kein Puls: Ein Hinweis, der sich bewegt, wird auf Dauer
   weggeklickt statt gelesen. */
.kal-warn {
    --kwarn-ink: #9F1239;
    display: flex;
    /* Mittig zur GESAMTEN Textgruppe, nicht zur Rubrikzeile: Das Symbol
       gehoert zur Aussage, nicht zur Ueberschrift. Bei flex-start sass es
       auf Hoehe des Eyebrows und damit sichtbar zu hoch. */
    align-items: center;
    /* Exakt derselbe Spaltenabstand wie in der Faktenkarte. Zusammen mit
       der gleich breiten Symbolspalte unten beginnt der Text hier auf
       demselben Pixel wie "8. Juni 2026" und "Waisenhauspark" darüber —
       eine durchgehende senkrechte Kante über beide Bausteine hinweg. */
    gap: var(--kmeta-gap);
    /* Rechts mehr Luft als links: Der Satz soll in ruhigen Zeilen an
       Wortgrenzen umbrechen. Zusammen mit der abgeschalteten Silben-
       trennung unten verhindert das den Bruch mitten im Wort, der die
       Zeile technisch aussehen liess. */
    padding: calc(var(--kmeta-py) * 9 / 10) calc(var(--kmeta-px) * 1.7) calc(var(--kmeta-py) * 9 / 10) var(--kmeta-px);
    margin: 0 0 calc(var(--kmeta-space) * 2);
    border-radius: var(--kmeta-r);
    background: rgba(244, 63, 94, 0.05);
    /* Unsichtbarer Rahmen mit exakt derselben Stärke wie an der Faktenkarte:
       Er zeichnet nichts, hält aber das Kastenmodell identisch. Ohne ihn
       begänne der Text hier einen Pixel weiter links als darüber — und
       genau die durchgehende Senkrechte wäre wieder dahin.
       Sichtbar bleibt der Hinweis ohne Rahmen und ohne Schatten. Beides
       zusammen machte aus einer
       Randnotiz ein Bauteil: Der Rahmen zog eine Grenze, der Schatten hob
       sie von der Seite ab — und schon stand dort eine zweite Karte neben
       der Faktenkarte, obwohl der Hinweis gar keine ist. Uebrig bleibt
       eine getoente Flaeche, die den Text traegt statt ihn einzurahmen.
       Der Kontrast der Tinte auf dieser Flaeche traegt die Aufmerksamkeit
       vollstaendig allein; die Kante war dafuer nie noetig. */
    border: 1px solid transparent;
    text-align: left;
}
.kal-warn-ic {
    flex: 0 0 auto;
    /* Die Spalte ist so breit wie die Symbolkachel der Faktenkarte, obwohl
       das Zeichen selbst kleiner bleibt: Nur so liegt seine Mitte auf
       derselben Senkrechten wie Kalender- und Ortssymbol darüber, und der
       Text startet auf derselben Kante. Sichtbar ist weiterhin nur das
       Zeichen — die Spalte trägt keine Fläche. */
    width: var(--kmeta-ic);
    height: var(--kmeta-ic);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--kwarn-ink);
}
/* 58 statt der 54 Prozent der Faktensymbole: Jene sitzen auf einer
   getönten Kachel, die ihnen Präsenz gibt. Dieses Zeichen steht frei und
   bräuchte bei gleicher Größe optisch zu wenig Raum — die vier Prozent
   gleichen die fehlende Fläche aus, ohne die Achse zu verschieben. */
/* 62 Prozent der Spaltenbreite = 27,28px. Das Warnzeichen darf als
   einziges aus der Zeichenfamilie herausragen: Kalender, Ort und Glocke
   benennen, dieses hier unterbricht. Ein Hinweis, der eine Abweichung vom
   Angekuendigten meldet, muss vor dem Lesen wirken.
   Die Strichstaerke ist dabei mitgerechnet und nicht mitgewachsen — dazu
   die Begruendung am Zeichen selbst. Die Zeilenhoehe aendert sich weiterhin
   nicht: Sie wird vom zwei- bis dreizeiligen Text bestimmt, der ueber 50px
   misst. */
.kal-warn-ic svg { width: 62%; height: 62%; overflow: visible; }

/* ── EIN ANSCHLAG, VERZOEGERT ─────────────────────────────────────────────
   Der Hinweis steht mitten im Lesefluss und wird beim Scrollen leicht
   ueberfahren. Ein Anschlag holt den Blick — danach nie wieder. Ein Zeichen,
   das mehrfach oder dauerhaft pulst, wird nach kurzer Zeit weggesehen. Die
   Glocke laeutet wiederholt, weil sie zu etwas einlaedt; dieses Zeichen
   meldet einen Zustand und hat nach einem Anschlag alles gesagt.

   Die Verzoegerung von 0,4 Sekunden ist der eigentliche Kniff: Ohne sie
   faellt der Anschlag genau in die Bewegung des Scrollens und geht darin
   unter — das Auge folgt noch dem Text, nicht dem Zeichen. Mit der kurzen
   Pause kommt der Schlag, wenn die Seite bereits steht. Er wird dann nicht
   nur gesehen, sondern als Ereignis gelesen.
   fill-mode both haelt waehrend der Wartezeit den Nullzustand: kein
   Aufblitzen, kein Sprung vor dem Schlag.

   Der Ruecklauf unter 1 und der kleine Nachschlag sind kein Zierrat: Ein
   Sprung, der einfach zurueckfaellt, liest sich als Ruckeln — erst das
   Ausschwingen liest sich als Anschlag.

   Animiert wird ausschliesslich transform — kein Layout, kein Repaint der
   Umgebung, reine Compositor-Arbeit auf einem 27px-Element. will-change
   steht IN der Regel und nicht am Element, damit der eigene Layer mit der
   Klasse entsteht und mit ihr wieder verschwindet.
   transform-origin: center — die Vergroesserung geht nach allen Seiten
   gleich weit, das Zeichen bleibt auf seiner Achse stehen. overflow:visible
   am SVG, weil die Spitze beim Hoechststand sonst am eigenen Zeichenrahmen
   abgeschnitten wuerde. */
@keyframes kalWarnAnschlag {
      0% { transform: scale(1); }
     39% { transform: scale(1.875); }
     63% { transform: scale(0.94); }
     82% { transform: scale(1.06); }
    100% { transform: scale(1); }
}
.kal-warn-ic svg { transform-origin: 50% 50%; }
.kal-warn-ic.is-sicht svg {
    will-change: transform;
    animation: kalWarnAnschlag 0.71s cubic-bezier(0.34, 1.4, 0.64, 1) 0.4s both;
}
@media (prefers-reduced-motion: reduce) {
    .kal-warn-ic.is-sicht svg { animation: none; will-change: auto; }
}
/* ── VERGANGEN: der Hinweis tritt zurück ──────────────────────────────────
   Exakt die Grautöne der vergangenen Terminzeile: Tinte #6b7280 wie deren
   Wert, Fläche als Ableitung desselben Schiefertons wie ihr Chip. Kopf und
   Symbol folgen über --kwarn-ink von selbst. #6b7280 hält auf der hellen
   Fläche 4,6:1. Der Anschlag entfällt — ein Zeichen, das bei einer
   erledigten Sache aufspringt, meldet eine Störung, die es nicht mehr gibt. */
.kal-warn.is-vorbei { --kwarn-ink: #6b7280; background: rgba(100, 116, 139, 0.06); }
.kal-warn.is-vorbei .kal-warn-ic.is-sicht svg { animation: none; will-change: auto; }
.kal-warn-txt { display: flex; flex-direction: column; min-width: 0; }
/* Exakt dieselbe Groesse wie die Rubriken der Faktenkarte — sie stehen
   direkt untereinander und sind derselbe Zeilentyp. Zwei Werte waeren hier
   nicht Nuance, sondern Unaufmerksamkeit. */
.kal-warn-kopf { font-size: clamp(0.379rem, 2.35vw, 0.821rem); font-weight: 800; text-transform: uppercase; letter-spacing: 0.06em; line-height: 1.45; color: color-mix(in srgb, var(--kwarn-ink) 78%, #ffffff); overflow-wrap: anywhere; }
/* hyphens:none und Umbruch nur an Wortgrenzen: Ein Hinweis ist ein kurzer
   Satz, kein Fliesstext — eine Trennung mitten im Wort wirkt dort wie ein
   Umbruchfehler. break-word bleibt als Notausgang fuer den Einzelfall, in
   dem ein Wort breiter waere als die Zeile. */
.kal-warn-body { font-size: clamp(0.61rem, 3.15vw, 1.058rem); font-weight: 600; line-height: 1.4; color: var(--kwarn-ink); hyphens: none; -webkit-hyphens: none; overflow-wrap: break-word; }

/* Artikeltitel: war auf die frühere gerahmte Ansicht ausgelegt und stand
   dort neben einem Bild mit Rand. Unter einem randlosen Hero in voller
   Breite braucht er echtes Display-Format, sonst nimmt sich die Leiste
   oben zwar zurück, aber niemand tritt an ihre Stelle.
   Maße nach der Projektformel (Basis 30px bei 430px, Faktoren 0,72/1,25),
   Zeilenhöhe und Laufweite ziehen mit: größere Grade vertragen engeren
   Satz, sonst zerfällt der Titel in Einzelwörter.
   text-wrap: balance verteilt mehrzeilige Titel auf gleich lange Zeilen
   und verhindert das einzelne Wort in der Schlusszeile; Browser ohne
   Unterstützung brechen wie bisher. */
.kal-det-title {
    font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
    font-size: clamp(1.35rem, 6.98vw, 2.34rem);
    font-weight: 800;
    color: #1E293B;
    margin-bottom: clamp(10.8px, 3.49vw, 18.75px);
    line-height: 1.15;
    letter-spacing: -0.02em;
    text-wrap: balance;
}
/* Der Titel bringt über seinen Versalien einen unsichtbaren Vorlauf mit:
   halber Zeilendurchschuss plus der Teil der Oberlänge, der über die
   Versalhöhe hinausgeht. Bei 30,7px Titel sind das 7,3px. Der gesetzte
   Abstand zur Zeitraum-Karte darüber und der sichtbare Abstand liefen
   dadurch auseinander — geometrisch saßen oben und unten exakt dieselben
   14,31px, zu sehen waren oben 14,31 und unten 21,62. Die Karte klebte an
   der Kopfzeile und hing vom Titel ab.
   text-box-trim schneidet die erste Zeile oben auf die Versalhöhe zurück;
   danach ist der gesetzte Abstand der, den man sieht. Nur oben getrimmt,
   damit der Abstand zum Untertitel darunter unverändert bleibt.
   Ein Ausgleich über einen größeren Abstand wäre der schlechtere Weg: Der
   Vorlauf hängt an Zeilenhöhe und Schriftmetrik, und die legen die Engines
   unterschiedlich aus.
   Die Gruppen-Pille in der Titelzeile war auf die ungetrimmte Zeilenbox
   zentriert — (23/20 − 2/3)/2 der Titelgröße. Nach dem Trimmen ist die
   Bezugskante die Versalhöhe, und der Wert lautet Versalhöhe/2 minus halbe
   Pillenhöhe: 745/2000 − 10/30. Ohne die Anpassung säße sie 6,2px zu tief.
   Browser ohne text-box-trim behalten beide Werte unverändert. */
@supports (text-box-trim: trim-start) and (text-box-edge: cap alphabetic) {
    .kal-det-title {
        text-box-trim: trim-start;
        text-box-edge: cap alphabetic;
    }
    .kal-grp--det {
        margin-block-start: calc(var(--kal-tsize) * 745 / 2000 - var(--kal-tsize) * 10 / 30);
    }
}
.kal-det-text {
    /* Fluide Lesegröße. Bewusst NICHT nach der 0,72/1,25-Projektformel:
       die gilt für positionierte und dekorative Elemente, Fließtext darf
       nie unter die Lesbarkeitsschwelle fallen. Der Korridor ist deshalb
       eng gewählt und so gelegt, dass er bis 432px Viewport exakt auf dem
       bisherigen Wert liegt — auf dem iPhone ändert sich kein einziger
       Zeilenumbruch. Erst darüber wächst er auf angenehme 17,9px.
       Alle Größen darunter hängen als em an diesem einen Wert: eine Zahl
       fluide zu machen genügt, der Rest folgt proportional. */
    font-size: clamp(0.98rem, 0.71rem + 1vw, 1.12rem);
    font-weight: 400;
    color: #475569;
    /* 1,7 statt 1,8: Bei dieser Schriftgroesse zerfaellt der Absatz bei 1,8
       in einzelne Zeilen, statt als Block gelesen zu werden — der Zeilen-
       abstand wird dann groesser als der Wortabstand mal Faktor und das
       Auge verliert die Rueckfuehrung zum Zeilenanfang. 1,7 haelt den
       Absatz zusammen, ohne eng zu wirken. */
    line-height: 1.7;
    margin-bottom: 1.15em;
    /* Flattersatz statt Blocksatz. Die Begründung für den Blocksatz war,
       dass die server-seitige Silbentrennung genug Umbruchpunkte liefert,
       um Wortabstands-Flüsse zu verhindern. Das trägt, solange die Spalte
       breit genug ist — neben einem Float-Bild ist sie es nicht: Dort
       bleiben rund 58% der Breite und damit etwa zwanzig Zeichen je Zeile,
       und keine Trennung der Welt kann eine so kurze Zeile bündig ziehen,
       ohne die Wortabstände zu dehnen.
       Gemessen bei 440px Gerätebreite: Der Wortabstand dieser Schrift misst
       2,84px. Im Blocksatz wurde er neben dem Bild auf bis zu 28,69px
       gestreckt, im Mittel auf 9,62px — das Zehnfache im Extrem. Im
       Flattersatz bleibt er konstant bei 2,84px.
       Getrennt wird gar nicht mehr. Im Blocksatz war die Trennung nötig,
       um die Wortabstände zu halten; im Flattersatz hat sie nur noch den
       Flatterrand geglättet, und dafür ist der Preis zu hoch — neben dem
       schmalen Float fällt in fast jeder Zeile ein Trennstrich an, und
       Wörter wie einschließlich oder gemeinsame zerfallen mitten im Lesen.
       hyphens:none unterdrückt beides: die Auto-Trennung des Browsers UND
       die server-seitig gesetzten Soft-Hyphens. Die Grenzwerte für die
       Trennung sind mit entfallen, sie wären ab hier toter Code.
       overflow-wrap bleibt als Notausgang: Ein Wort, das allein nicht in
       die Zeile passt, bricht hart um, statt aus der Spalte zu laufen. */
    text-align: left;
    text-wrap: pretty;
    hyphens: none;
    -webkit-hyphens: none;
    overflow-wrap: break-word;
}
/* Überschriften: linksbündig im Flattersatz — Blocksatz erzeugt in kurzen,
   großen Zeilen unruhige Wortabstände. hyphens:manual erlaubt weiterhin
   die server-seitigen Soft-Hyphens als Notfall-Umbruch bei sehr langen
   Komposita, unterdrückt aber aggressive Auto-Trennung im Titelbild. */
.kal-det-text h2, .kal-det-text h3 { text-align: left; hyphens: manual; -webkit-hyphens: manual; }
/* Zitat-Design: siehe Editorial-Pull-Quote-Block weiter unten (1:1 vom Info-Modul) */
/* Tabellenzellen erben den Blocksatz nicht — justify in schmalen Zellen
   erzeugt extreme Wortabstände. th behält sein UA-Verhalten (zentriert). */
.kal-det-text td { text-align: left; }
.kal-det-text td, .kal-det-text th { hyphens: manual; -webkit-hyphens: manual; }
.kal-det-text p { margin-bottom: .55em; }
.kal-det-text figure.kal-float-right { float: right; margin: 0 0 12px 14px; width: 38%; }
/* Alle drei Varianten gemeinsam — ein Float-Bild kann als <img>, als
   dotlottie-player (.lottie) oder als lottie-player (.json) im Artikel
   landen, je nach hochgeladener Datei. Die frühere Regel nannte nur
   dotlottie-player; bei einer .json-Datei griff sie deshalb gar nicht.

   margin: 0 ist der eigentliche Punkt: Für Lotties im Fließtext gilt weiter
   unten margin: 10px auto — sinnvoll für ein zentriertes Element zwischen
   zwei Absätzen, falsch für ein umflossenes am Textanfang. Ohne diese
   Rücknahme startet das Bild zehn Pixel unterhalb der ersten Zeile, während
   der Text bereits ab Zeile eins umfließt. Genau diese Lücke oben rechts
   war sichtbar. */
.kal-det-text figure.kal-float-right img,
.kal-det-text figure.kal-float-right dotlottie-player,
.kal-det-text figure.kal-float-right lottie-player { width: 100%; height: auto; border-radius: 10px; display: block; margin: 0; }
.kal-det-text::after { content: ''; display: table; clear: both; }
/* ── HINTERGRUNDMOTIV DES ARTIKELS ────────────────────────────────────
   Liegt ausschließlich hinter dem Fließtext, linksbündig, und bricht
   seitlich exakt das Body-Padding aus — an dessen Token, nicht an einer
   zweiten Sechzehn. Weiter als bis zur Modalkante darf es nicht:
   .kal-l2-body scrollt, und ein Element jenseits der Kante erzeugt auf
   iOS ein horizontales Gummiband. Der Überstand, der links jenseits der
   Kante liegen soll, wird deshalb NICHT über die Elementbreite gelöst,
   sondern über einen negativen background-position-x. Das Bild wird
   angeschnitten, die Box bleibt vollständig im Scrollport.

   z-index:-1 auf dem Pseudoelement plus isolation:isolate auf dem
   Träger ist dasselbe Muster, das im selben Stylesheet beim
   Pull-Quote-Panel bereits auf iOS läuft: Es hält das Motiv hinter dem
   Text und trotzdem im eigenen Stapelkontext, statt es hinter den
   weißen Modalgrund fallen zu lassen. Als CSS-Background kann eine
   hochgeladene SVG außerdem kein Skript ausführen — derselbe Grund,
   aus dem hier kein <img> und kein <object> steht.

   Die Klasse .kal-deko ist das Tor: .kal-det-text trägt auch der
   Untertitel unter dem Titel, und der bekommt nie ein Motiv.

   --kal-deko-w (Motivbreite) und --kal-deko-top (Startversatz unter dem
   Umflussbild) setzt JS als echte px-Länge. Beides MUSS eine Länge
   sein: Prozentwerte in background-position beziehen sich auf
   (Fläche − Bildgröße) und nicht auf die Fläche — die Rechnung wäre
   sonst nicht nur ungenau, sondern falsch. Die Breite skaliert über
   .kal-det-text.offsetWidth und ist damit projektkonform fluide, ohne
   dass hier ein clamp() stünde, das sie erst recht entkoppeln würde.

   ANSCHNITT — --kal-deko-crop kommt aus dem Admin und bestimmt, welcher
   Anteil des Motivs links jenseits der Modalkante liegt und damit
   verborgen wird. Als Anteil DER MOTIVBREITE formuliert und nicht als
   Länge: Dadurch bleibt derselbe Bildausschnitt sichtbar, gleichgültig
   wie groß das Motiv gestellt ist und auf welchem Display es steht.
   Umgesetzt über background-position-x und nicht über eine breitere Box:
   Ein Element, das links über die Modalkante hinausragt, erzeugt in
   .kal-l2-body horizontales Gummiband auf iOS. Das Bild wird
   angeschnitten, die Box bleibt im Scrollport.
   Der Vorgabewert 0.18 steht als Fallback im var() und entspricht dem
   Wert, der vor Einführung des Felds fest hier im Stylesheet stand.

   DREHUNG — --kal-deko-rot dreht das Motiv um seine eigene Mitte. Damit
   das überhaupt geht, ist das Pseudoelement seit dieser Fassung exakt so
   groß wie das Motiv: Breite aus der eingestellten Größe, Höhe über das
   Seitenverhältnis, das der Endpoint aus der Datei liest. Vorher spannte
   die Box über die ganze Textspalte bis zum Artikelende — eine Drehung
   hätte diesen riesigen Rahmen mitgeschwenkt und das Motiv irgendwohin
   geworfen. Der Anschnitt sitzt jetzt in der Position der Box statt in
   der Hintergrundposition, damit er auch nach einer Drehung an der
   Modalkante schneidet und nicht schräg mitdreht.
   background-size bleibt bewusst 100% auto und nicht 100% 100%: Sollte
   das gelesene Seitenverhältnis einmal danebenliegen, sitzt der Drehpunkt
   leicht versetzt — das Motiv selbst wird aber nie verzerrt, weil seine
   Höhe aus der Datei kommt.

   VERSATZ — --kal-deko-off kommt aus dem Admin und schiebt das Motiv
   nach unten, gemessen in Anteilen der Textspaltenbreite. Deshalb
   100cqw und kein clamp() mit festen Grenzen: Ein clamp() hätte einen
   Mindest- und einen Höchstwert in Pixeln, und genau an diesen beiden
   Grenzen bräche die Proportion — auf schmalen und auf breiten
   Displays säße das Motiv dann eben NICHT mehr dort, wo es eingestellt
   wurde. Ein reiner Container-Anteil hat keine Grenzen, an denen er
   umknicken könnte, und hält die eingestellte Stelle auf jeder
   Auflösung exakt. Der Versatz greift an der Hintergrundposition und
   nicht am top des Pseudoelements: Ein verschobenes top würde die Box
   von oben verkleinern und bei großem Versatz auf null Höhe bringen —
   das Motiv verschwände auf einen Schlag, statt nach unten zu wandern
   und am Textende sauber auszulaufen.
   Vorgabe 0 bedeutet: exakt die Position, die das Motiv vor Einführung
   dieses Felds hatte.

   DECKKRAFT — bewusst ungefiltert: Der Wert aus dem Admin gilt über die
   gesamte Motivfläche, von der Modalkante bis zum rechten Ende, ohne
   Verlauf und ohne Obergrenze. Eine frühere Fassung deckelte die
   Deckkraft unter der Textspalte über eine Maske und ließ sie nur im
   textfreien linken Streifen voll stehen. Das war zweimal falsch: Es
   machte das Motiv links dunkler als rechts, obwohl ein gleichmäßiges
   Motiv gefordert war, und es nahm dem Regler oberhalb des Deckels jede
   Wirkung. Ein Regler, der nur die Hälfte seines Bereichs tut, ist
   kaputt.

   Zur Einordnung, damit die Zahl bei der Wahl des Werts bekannt ist:
   Der Fließtext ist #475569 auf Weiß, das sind 7,58:1. Ein Motiv liegt
   im Blocksatz genau unter dem Zeilenanfang. In Amina-Blau bleiben bei
   17 % noch 6,44:1, in --amina-dark 5,44:1; ein sehr dunkles Motiv
   erreicht die AA-Schwelle von 4,5:1 bei etwa 22 %. Die Motivfarbe ist
   im Frontend nicht steuerbar — ein per background-image geladenes SVG
   ist ein eigenes Dokument und kennt weder currentColor noch die Tokens
   dieser Seite. Die Wahl eines lesbaren Werts liegt damit vollständig
   im Admin, und genau dort gehört sie hin.

   Keine Animation, kein will-change, keine transition: Es gibt nichts
   für den perfObs zu pausieren. */
.kal-det-text.kal-deko {
    /* container-type macht die Textspalte zur Abfrage-Einheit für ihr
       eigenes Pseudoelement. Erst dadurch lässt sich die Motivbreite als
       echte LÄNGE ausdrücken (100cqw = Breite dieser Spalte), ohne dass
       JavaScript sie ausmessen und einspeisen muss.
       Das ist der entscheidende Punkt: Vorher hing die Breite an einer
       Custom Property aus dem Skript. Kam sie aus irgendeinem Grund nicht
       an, war die Bildbreite null und das Motiv nicht etwa zu klein,
       sondern vollständig unsichtbar — ohne jeden Hinweis darauf, woran
       es lag. Jetzt zeichnet CSS das Motiv aus eigener Kraft; JavaScript
       liefert nur noch den Startversatz, und fällt der aus, beginnt das
       Motiv eben oben statt unter dem Umflussbild.
       Ein Browser ohne Container-Queries kennt cqw nicht, verwirft die
       background-size-Angabe und zeichnet das Motiv in seiner eigenen
       Größe — sichtbar, nur nicht skaliert. Auch das ist der harmlose
       Ausgang.
       Nebenwirkung, bewusst in Kauf genommen und geprüft: container-type
       erzeugt Layout-Containment und damit einen eigenen Blockkontext.
       Der Float im Artikel wird dadurch ohnehin eingeschlossen; die
       vorhandene Clearfix-Regel bleibt unangetastet und schadet nicht. */
    container-type: inline-size;
    position: relative;
    isolation: isolate;
}
.kal-det-text.kal-deko::before {
    /* Bewusst HIER deklariert und nicht auf dem Träger: cqw löst sich
       gegen den Container des Elements auf, in dem die Einheit benutzt
       wird. Auf dem Träger selbst wäre das ein Vorfahr, hier ist es die
       Textspalte — und nur das ist gemeint. */
    --kal-deko-w: calc(100cqw * var(--kal-deko-size, 0.45));
    --kal-deko-h: calc(var(--kal-deko-w) / var(--kal-deko-ar, 1));
    content: '';
    position: absolute;
    z-index: -1;
    top: calc(var(--kal-deko-top, 0px) + 100cqw * var(--kal-deko-off, 0));
    inset-inline-start: calc((var(--kal-l2-pad) + var(--kal-deko-w) * var(--kal-deko-crop, 0.18)) * -1);
    width: var(--kal-deko-w);
    height: var(--kal-deko-h);
    background-image: var(--kal-deko-img, none);
    background-repeat: no-repeat;
    background-size: 100% auto;
    background-position: 0 0;
    opacity: var(--kal-deko-op, 0.17);
    pointer-events: none;
}
/* Die Drehung steht bewusst in einer eigenen Klasse und nicht oben in der
   Grundregel. Ein transform erzeugt auch bei rotate(0deg) einen eigenen
   Stapelkontext und auf mobilen Browsern haeufig eine eigene Compositing-
   Ebene — und das waere dann bei JEDEM dekorierten Artikel der Fall,
   einschliesslich aller, die gar nicht gedreht sind. Das widerspricht der
   Regel, dass GPU-Arbeit nur dort entsteht, wo sie gebraucht wird. Die
   Klasse setzt das Skript nur, wenn die Drehung ueber null liegt. */
.kal-det-text.kal-deko.kal-deko-dreh::before {
    transform: rotate(var(--kal-deko-rot, 0deg));
}
/* SPIEGELN — waagrecht, um die eigene Mitte. Aus demselben Grund wie die
   Drehung eine eigene Klasse und keine Eigenschaft in der Grundregel.
   Die dritte Regel ist kein Luxus, sondern zwingend: transform ist EINE
   Eigenschaft. Stuenden Drehung und Spiegelung in getrennten Regeln, wuerde
   die spaeter kommende die frueher kommende vollstaendig ersetzen und eines
   von beidem waere wirkungslos. Deshalb der kombinierte Fall ausgeschrieben.
   Reihenfolge rotate() vor scaleX(): Transformationen wirken von rechts nach
   links, also wird zuerst gespiegelt und das Ergebnis dann gedreht. So dreht
   ein eingestellter Winkel auch am gespiegelten Motiv in dieselbe Richtung
   wie am ungespiegelten — andersherum liefe die Drehung gegenlaeufig, sobald
   jemand den Spiegeln-Schalter umlegt. */
.kal-det-text.kal-deko.kal-deko-spiegel::before {
    transform: scaleX(-1);
}
.kal-det-text.kal-deko.kal-deko-dreh.kal-deko-spiegel::before {
    transform: rotate(var(--kal-deko-rot, 0deg)) scaleX(-1);
}
/* EINFÄRBEN — nur wenn im Admin eine Kategoriefarbe gewählt wurde.
   Ein per background-image geladenes SVG ist ein eigenes Dokument: Es kennt
   weder currentColor noch die Tokens dieser Seite, seine Farben sind von
   außen nicht erreichbar. Der einzige Weg, es trotzdem umzufärben, ist der
   Wechsel des Verfahrens — das Motiv wird zur Maske, gefüllt wird eine
   einfarbige Fläche. Deshalb steht das hier als eigene Klasse und nicht als
   zusätzliche Eigenschaft in der Regel darüber: Ohne Farbwahl bleibt es beim
   Hintergrundbild und das Motiv behält seine eigenen Farben.
   mask-size und mask-position spiegeln background-size und
   background-position eins zu eins — sonst säße die Maske anders als das
   Bild und schnitte das Motiv an einer willkürlichen Kante ab. */
.kal-det-text.kal-deko.kal-deko-tint::before {
    background-image: none;
    background-color: var(--kal-deko-tint, currentColor);
    -webkit-mask-image: var(--kal-deko-img, none);
    -webkit-mask-repeat: no-repeat;
    -webkit-mask-size: 100% auto;
    -webkit-mask-position: 0 0;
    mask-image: var(--kal-deko-img, none);
    mask-repeat: no-repeat;
    mask-size: 100% auto;
    mask-position: 0 0;
}
/* Erhöhter Kontrastbedarf, erzwungene Farben und Druck: Das Motiv ist
   reine Dekoration und trägt keine Information — es fällt in allen drei
   Fällen ersatzlos weg. Bei forced-colors zusätzlich deshalb, weil das
   System Hintergrundbilder ohnehin nicht garantiert und ein halb
   durchgereichtes Motiv der schlechtere Ausgang wäre. */
@media (prefers-contrast: more) {
    .kal-det-text.kal-deko::before { display: none; }
}
@media (forced-colors: active) {
    .kal-det-text.kal-deko::before { display: none; }
}
@media print {
    .kal-det-text.kal-deko::before { display: none; }
}
.kal-det-text dotlottie-player, .kal-det-text lottie-player { display: block; border-radius: 8px; max-width: 100%; margin: 10px auto; }
.kal-det-text strong { font-weight: 700; color: #1E293B; }
/* Inline-Links im Artikel (auch Mail- und Anmeldeformular-Links aus dem
   Editor): folgen dem etablierten Inline-Link-Stil der Site — Akzentfarbe
   des Artikels, abgesetzte Unterstreichung, kein Browser-Blau. */
.kal-det-text a { color: var(--kal-acc, #48A0D6); font-weight: 700; text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--kal-acc, #48A0D6) 40%, transparent); text-underline-offset: 0.19em; transition: text-decoration-color 0.18s ease; }
.kal-det-text a:hover { text-decoration-color: var(--kal-acc, #48A0D6); }
/* Anmeldeformular über dem Kalender-Modal: #anfrageModal (footer.php) liegt
   normalerweise unter dem Kalender-Overlay (z-index 2147483600). Wird das
   Formular aus einem Artikel heraus geöffnet, hebt diese Regel es über beide
   Ebenen — nur solange das Kalender-Modal offen ist (html.kal-modal-open). */
html.kal-modal-open #anfrageModal { z-index: 2147483620; }
.kal-det-text em { font-style: italic; }
.kal-det-text blockquote {
    /* ── Editorial Pull-Quote — 1:1 vom Info-Modul (.oz-info-text blockquote) ──
       Eckige Aussparung oben links: knapp um die SICHTBARE Glyphe.
       Die SVG-viewBox trägt rechts ~16% Leerraum (Pfad endet bei x=13.4/16) —
       die Breite ist darauf kalibriert, damit die Luft rechts exakt
       der Luft unter der Glyphe entspricht (pixelsymmetrische Einfassung).
       PROPORTIONSSYSTEM: Alle gekoppelten Maße (Glyphe + Aussparung +
       Wasserzeichen) nutzen einheitlich Min = Basis×0.72, Max = Basis×1.25 —
       dadurch klemmen alle clamp()-Werte bei denselben Viewports (310/537px)
       und die Einfassung bleibt auf JEDER Auflösung exakt symmetrisch.
       Akzentfarbe --kal-acc wird pro Event in JS auf die Kategoriefarbe
       gesetzt (Fallback: Amina Blau). */
    --kal-bq-notch-w: clamp(1.366rem, 7.06vw, 2.372rem);
    --kal-bq-notch-h: clamp(1.006rem, 5.2vw, 1.747rem);
    position: relative;
    isolation: isolate;
    margin: clamp(1.15rem, 3.8vw, 1.5rem) 0 1.15em;
    padding: clamp(1.7rem, 5.6vw, 2.05rem) clamp(1.15rem, 4vw, 1.6rem) clamp(1.2rem, 4.2vw, 1.65rem);
    color: #334155;
    font-style: italic;
    font-weight: 600;
    /* Zitat bewusst im Flattersatz und ungetrennt: hyphens:none unterdrückt
       auch die serverseitigen Soft-Hyphens — der persönliche Charakter bleibt */
    text-align: left;
    hyphens: none;
    -webkit-hyphens: none;
}
.kal-det-text blockquote::before {
    /* Editorial Pull-Quote: Farbwash aus der Kategoriefarbe auf eigener
       Ebene (z-index -1), damit die Mask nur das Panel schneidet und
       niemals Glyphe oder Text. Oben links ist ein scharfkantiges
       Rechteck ausgespart, in dem die Glyphe knapp eingefasst sitzt.
       Die Mask besteht aus zwei Deckflächen: rechts neben der Aussparung
       (volle Höhe) und unterhalb der Aussparung (Restbreite). Ohne Mask-
       Support bleibt das Panel voll gerundet (graceful degradation). */
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: clamp(16px, 5vw, 22px);
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--kal-acc, #48A0D6) 9%, #ffffff) 0%,
        color-mix(in srgb, var(--kal-acc, #48A0D6) 3%, #ffffff) 100%);
    -webkit-mask-image: linear-gradient(#000, #000), linear-gradient(#000, #000);
    -webkit-mask-size: calc(100% - var(--kal-bq-notch-w)) 100%, var(--kal-bq-notch-w) calc(100% - var(--kal-bq-notch-h));
    -webkit-mask-position: 100% 0, 0 100%;
    -webkit-mask-repeat: no-repeat;
    mask-image: linear-gradient(#000, #000), linear-gradient(#000, #000);
    mask-size: calc(100% - var(--kal-bq-notch-w)) 100%, var(--kal-bq-notch-w) calc(100% - var(--kal-bq-notch-h));
    mask-position: 100% 0, 0 100%;
    mask-repeat: no-repeat;
    pointer-events: none;
}
.kal-bq-ic {
    /* Glyphe sitzt knapp eingefasst in der eckigen Aussparung —
       Werte im Proportionssystem ×0.72/×1.25 (siehe Aussparung) */
    position: absolute;
    inset-inline-start: clamp(0.155rem, 0.8vw, 0.269rem);
    top: clamp(-0.168rem, -0.5vw, -0.097rem);
    width: clamp(1.238rem, 6.4vw, 2.15rem);
    color: var(--kal-acc, #48A0D6);
    pointer-events: none;
}
.kal-bq-ic svg { display: block; width: 100%; height: auto; }
.kal-bq-wm {
    /* Wasserzeichen-Glyphe: zweite Tiefenebene, fast unsichtbar,
       um 180° gedreht als schließendes Anführungszeichen —
       Werte im Proportionssystem ×0.72/×1.25 (siehe Aussparung) */
    position: absolute;
    inset-inline-end: clamp(0.387rem, 2vw, 0.672rem);
    bottom: clamp(0.31rem, 1.6vw, 0.538rem);
    width: clamp(2.032rem, 10.5vw, 3.527rem);
    color: var(--kal-acc, #48A0D6);
    opacity: 0.07;
    transform: rotate(180deg);
    pointer-events: none;
}
.kal-bq-wm svg { display: block; width: 100%; height: auto; }
.kal-det-text img { max-width: 100%; height: auto; border-radius: 10px; display: block; margin: 0.51em auto; }
.kal-det-text img.img-right { float: right; display: inline; margin: 4px 0 12px 16px; max-width: 42%; }
.kal-det-text img.img-left { float: left; display: inline; margin: 4px 16px 12px 0; max-width: 42%; }
.kal-det-text .ql-size-small { font-size: 0.84em; }
.kal-det-text .ql-size-large { font-size: 1.17em; font-weight: 600; }
.kal-det-text .ql-size-huge  { font-size: 1.38em; font-weight: 700; }
.kal-det-text ul { list-style-type: disc; padding-left: 1.5em; margin: .4em 0 .65em; }
.kal-det-text ol { list-style-type: decimal; padding-left: 1.5em; margin: .4em 0 .65em; }
.kal-det-text li { margin-bottom: .25em; }
.kal-det-text h2 { font-size: 1.17em; font-weight: 800; color: #1E293B; margin: .8em 0 .3em; line-height: 1.2; }
.kal-det-text h3 { font-size: 1.02em; font-weight: 700; color: #1E293B; margin: .6em 0 .25em; line-height: 1.2; }
.kal-det-text table { width: 100%; border-collapse: collapse; margin: 0.64em 0; font-size: 0.92em; }
.kal-det-text td, .kal-det-text th { border: 1px solid #e2e8f0; padding: 0.38em 0.64em; }
.kal-det-text th { background: #f1f5f9; font-weight: 700; color: #1E293B; }
/* Textmarker: Der Editor schreibt farbige Marker inline (Amina Blau,
   Pink, Grün, Gelb bei 35% Deckung) — diese Inline-Farbe gewinnt hier
   und bleibt unangetastet. Marker ohne Farbe, also Altbestand aus der
   Zeit vor der Farbauswahl, fielen bisher auf das Browser-Neongelb
   zurück und waren damit die einzige Fläche im Artikel außerhalb der
   Palette; sie erben jetzt Amina Gelb.
   box-decoration-break: clone rundet einen über zwei Zeilen laufenden
   Marker an allen vier Enden statt nur außen — vorher brach die Form
   beim Umbruch auf.
   Das horizontale Padding wird per negativem Margin exakt ausgeglichen:
   im Blocksatz würde es sonst die Wortabstände der ganzen Zeile
   auseinanderziehen, weil der Marker zusätzliche Breite beansprucht. */
.kal-det-text mark {
    background-color: rgba(245,158,11,0.32);
    color: inherit;
    border-radius: 4px;
    padding: 1px 4px;
    margin: 0 -4px;
    box-decoration-break: clone;
    -webkit-box-decoration-break: clone;
}
.kal-det-text p[style*="text-align: center"],.kal-det-text h2[style*="text-align: center"],.kal-det-text h3[style*="text-align: center"] { text-align: center; }
.kal-det-text p[style*="text-align: right"],.kal-det-text h2[style*="text-align: right"],.kal-det-text h3[style*="text-align: right"] { text-align: right; }
.kal-det-text p[style*="text-align: justify"],.kal-det-text h2[style*="text-align: justify"] { text-align: justify; }
.kal-det-text .hl-blue { display:block; background:rgba(72,160,214,.08); border-left:3px solid #48A0D6; padding:0.64em 0.89em; margin:0.64em 0; border-radius:0 10px 10px 0; }
.kal-det-text .hl-pink { display:block; background:rgba(213,90,146,.08); border-left:3px solid #D55A92; padding:0.64em 0.89em; margin:0.64em 0; border-radius:0 10px 10px 0; }
.kal-det-text .hl-green { display:block; background:rgba(127,177,78,.08); border-left:3px solid #7FB14E; padding:0.64em 0.89em; margin:0.64em 0; border-radius:0 10px 10px 0; }
.kal-det-text .hl-yellow { display:block; background:rgba(245,158,11,.08); border-left:3px solid #F59E0B; padding:0.64em 0.89em; margin:0.64em 0; border-radius:0 10px 10px 0; }
.kal-det-text dotlottie-player, .kal-det-text lottie-player { display: block !important; width: 220px; max-width: 100%; margin: 12px auto !important; border-radius: 8px; }
/* ENTWURF-Badge: erscheint nur im Vorschau-Modus (Drafts sind sonst nie
   im Markup) — Amber-Signal mit gestrichelter Kante, oben rechts am Item */
.kal-item.is-entwurf { position: relative; }
.kal-item.is-entwurf::after { content: 'ENTWURF'; position: absolute; top: clamp(-9px, -1.63vw, -5px); right: clamp(8.4px, 3.26vw, 18.2px); z-index: 2; font-size: 0.55rem; font-weight: 900; letter-spacing: 0.14em; color: #b45309; background: #fef3c7; border: 1px dashed rgba(180,83,9,0.4); border-radius: 9999px; padding: 2px 9px; pointer-events: none; }
/* Geplant: nur in der Admin-Vorschau sichtbar. Geometrie exakt wie beim
   Entwurf-Badge (gekoppelte clamp-Grenzen, damit beide bei jeder Breite
   auf derselben Höhe sitzen), aber in Amina-Blau und mit durchgezogenem
   Rand — der Beitrag ist fertig, ihm fehlt nur die Zeit. */
.kal-item.is-geplant { position: relative; }
.kal-item.is-geplant::after { content: 'GEPLANT'; position: absolute; top: clamp(-9px, -1.63vw, -5px); right: clamp(8.4px, 3.26vw, 18.2px); z-index: 2; font-size: 0.55rem; font-weight: 900; letter-spacing: 0.14em; color: #1d6fa5; background: #e3f2fb; border: 1px solid rgba(72,160,214,0.45); border-radius: 9999px; padding: 2px 9px; pointer-events: none; }

/* Zurückgehaltene Vergangen-Einträge: [hidden] muss die display:flex-Regel
   der Items explizit schlagen (bekannte Falle: display-Regeln überschreiben
   das native hidden-Attribut) */
.kal-item[hidden] { display: none !important; }
/* "Mehr anzeigen": ruhiger Geist-Schalter im Modul-Duktus — Beschriftung
   plus Restzähler, verschwindet nach dem letzten Schritt */
.kal-mehr-btn {
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    width: 100%; margin: 4px 0 2px; padding: 12px 16px;
    border: 1.5px dashed rgba(100,116,139,0.25); border-radius: 16px;
    background: rgba(255,255,255,0.55); cursor: pointer;
    transition: background 0.25s ease, border-color 0.25s ease;
    -webkit-tap-highlight-color: transparent;
}
.kal-mehr-btn:hover { background: rgba(255,255,255,0.85); border-color: rgba(72,160,214,0.35); }
.kal-mehr-btn:focus-visible { outline: 2px solid rgba(72,160,214,0.45); outline-offset: 2px; }
.kal-mehr-txt { font-size: 0.82rem; font-weight: 800; color: #475569; }
.kal-mehr-zahl { font-size: 0.62rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.14em; color: #94a3b8; }
/* "Termine abonnieren": ruhige Glas-Pille in Amina-Blau am Listen-Ende —
   webcal:// öffnet direkt die Abo-Ansicht der Kalender-App */
.kal-abo-btn { display: flex; flex-direction: column; align-items: center; gap: 3px; width: fit-content; max-width: 100%; margin: 16px auto 0; padding: 12px 22px; border-radius: 16px; background: rgba(72,160,214,0.10); text-decoration: none; transition: background 0.25s ease, transform 0.25s ease; -webkit-tap-highlight-color: transparent; }
.kal-abo-btn:hover { background: rgba(72,160,214,0.16); }
.kal-abo-btn:active { transform: scale(0.97); }
.kal-abo-btn:focus-visible { outline: 2px solid rgba(72,160,214,0.45); outline-offset: 2px; }
.kal-abo-txt { display: inline-flex; align-items: center; gap: 7px; font-size: 0.84rem; font-weight: 800; color: #3187bd; }
.kal-abo-txt svg { flex-shrink: 0; }
.kal-abo-sub { font-size: 0.58rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: #94a3b8; text-align: center; }
@media (prefers-reduced-motion: reduce) { .kal-abo-btn { transition: none; } .kal-abo-btn:active { transform: none; } }

/* webcal-Fallback: erscheint nur, wenn der Klick auf "Termine abonnieren"
   die Seite NICHT verlassen hat (Desktop ohne Kalender-Handler) */
.kal-abo-fallback { display: flex; flex-direction: column; align-items: center; gap: 7px; margin: 10px auto 0; text-align: center; }
.kal-abo-fallback[hidden] { display: none !important; } /* flex würde hidden sonst überschreiben */
.kal-abo-fallback span { font-size: 0.6rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: #aab4be; }
.kal-abo-fallback button { border: none; cursor: pointer; font-family: inherit; font-size: 0.72rem; font-weight: 800; color: #48A0D6; padding: 9px 18px; min-height: 34px; border-radius: 99px; background: rgba(255,255,255,0.55); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 1px 4px rgba(15,23,42,0.06); transition: background 0.2s ease, transform 0.2s ease; -webkit-tap-highlight-color: transparent; }
.kal-abo-fallback button:hover { background: rgba(255,255,255,0.8); }
.kal-abo-fallback button:active { transform: scale(0.96); }
.kal-abo-fallback button:focus-visible { outline: 2px solid rgba(72,160,214,0.45); outline-offset: 2px; }

/* Kategorie-Feed-Auswahl: dezente Linkzeile, Touch-Ziele ≥44px */
.kal-abo-kats { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 0 8px; margin: 4px auto 0; padding: 0 16px; }
.kal-abo-kats-lbl { font-size: 0.58rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: #94a3b8; }
.kal-abo-kats a { font-size: 0.68rem; font-weight: 800; color: #48A0D6; text-decoration: none; padding: 0 8px; min-height: 44px; display: inline-flex; align-items: center; border-radius: 10px; transition: background 0.16s ease; -webkit-tap-highlight-color: transparent; }
.kal-abo-kats a:hover { background: rgba(72,160,214,0.08); }
.kal-abo-kats a:focus-visible { outline: 2px solid rgba(72,160,214,0.45); outline-offset: 1px; }

/* "Neu"-Punkt: seit dem letzten Besuch angelegte Einträge (iOS-Konvention:
   blauer Ungelesen-Punkt) — rein dekorativ, Rot bleibt Wichtig vorbehalten */
/* Der frühere blaue NEU-Punkt am Titel ist entfallen: Er sagte praktisch
   dasselbe wie der rote Punkt am Icon, und zwei Marker für eine Aussage
   sind einer zu viel. */
/* ── UNGELESEN-PUNKT ──
   Markiert wird, was offen ist — nicht, was erledigt ist. Eine Dämpfung
   gelesener Beiträge schied aus: Vergangene Termine sind bereits gedämpft,
   beide Zustände sähen identisch aus und die Liste behauptete zweierlei
   mit einer Gestaltung.

   Der Punkt sitzt am Icon und nicht am Titel, weil dort die Blickachse
   beim Überfliegen verläuft: Man scannt die linke Spalte hinunter und
   sieht sofort, wo etwas offen ist — unabhängig von der Titellänge.

   ::before und nicht ::after: Letzteres trägt bei heutigen Terminen den
   Sonar-Ring. Der z-index hält den Punkt auch bei laufendem Sonar oben.

   Der weiße Ring trennt ihn vom Icon darunter — ohne ihn verschwimmt Rot
   auf dem roten Kalenderkopf eines Wichtig-Beitrags.

   Um 15% zurückgenommen und einen Ton weniger grell: Sind alle Beiträge
   ungelesen, summieren sich sechs Punkte auf die sechsfache Fläche des
   Fokuspunkts in der Zeitleiste — das Signal auf den Karten übertönte
   dann die Aussage darüber. Kleiner und etwas wärmer bleibt es als
   Hinweis erkennbar, ohne die Aufmerksamkeit zu beanspruchen, die der
   nächste Termin verdient.

   Alle Maße über clamp() an der 430px-Basis: 7,8px Punkt, 1,57px Ring,
   Versatz -2,76px. Punkt, Ring und Versatz teilen denselben Skalenfaktor,
   damit das Verhältnis Ring zu Punkt über jede Viewportbreite bei rund 20%
   bleibt — würde nur der Punkt schrumpfen, wirkte der Ring zunehmend
   klobig und der Punkt verrutschte relativ zur Icon-Ecke. */
/* ANDOCKEN statt schweben, und feste Werte statt clamp.
   Zwei Fehler steckten hier: Erstens lag die Punktmitte 1.10px ausserhalb
   des Eckbogens — sie war auf den alten Eckradius von 14px gerechnet, nach
   der Angleichung auf 8px stimmte der Bezug nicht mehr. Jetzt sitzt sie
   exakt auf dem Scheitel des Bogens: Bogenmittelpunkt (46|8), Scheitel auf
   45 Grad bei (51.6569|2.3431), halber Punkt davon abgezogen. Damit liegt
   genau die Haelfte des Punktes auf dem Blatt und die andere daneben — er
   dockt an der Ecke an, statt daneben zu liegen.
   Zweitens war die Position fluid, die Bezugsgeometrie aber nicht: Die
   Bubble ist fest 54px breit mit festem 8px-Radius. Ein vw-Wert verschiebt
   den Punkt gegen eine unbewegliche Ecke — auf 320px sass er 0.6px anders
   als auf 430. Feste px sind hier keine Nachlaessigkeit, sondern die
   einzige richtige Notation.
   top und right sind zwangslaeufig identisch, weil der Scheitel auf 45 Grad
   liegt. Als calc aus seinen Bestandteilen notiert, nicht als -1.5569. */
.kal-item.is-offen .kal-bubble::before {
    content: '';
    position: absolute;
    top: calc(8px - 8px / 1.4142136 - 7.8px / 2);
    right: calc(8px - 8px / 1.4142136 - 7.8px / 2);
    z-index: 3;
    width: 7.8px;
    height: 7.8px;
    border-radius: 50%;
    background: #E8564F;
    box-shadow: 0 0 0 1.4px #fff, 0 1px 2px rgba(232,86,79,0.28);
    pointer-events: none;
}

/* ── Erinnerungs-Marker in der Liste ──────────────────────────────────
   Gegenstück zur Plakette im Artikel: Wer eine Vorlaufzeit gewählt hat,
   sieht das schon in der Übersicht. Bewusst die UNTERE rechte Ecke — die
   obere gehört dem Ungelesen-Punkt, zwei Zeichen an derselben Stelle
   würden sich überlagern.
   Maße nach demselben Skalenfaktor wie der Punkt darüber, damit beide
   Zeichen über jede Viewportbreite im selben Verhältnis zur Kachel
   stehen. Getragen wird die Kategoriefarbe der Kachel (--kb-col), nicht
   eine eigene — der Marker gehört zum Eintrag, nicht neben ihn. */
.kal-item-erin {
    position: absolute;
    /* Feste Werte wie beim Ungelesen-Punkt: Die Kachel ist 54px breit mit
       festem 8px-Radius, eine mit dem Viewport wandernde Plakette kann an
       einer unbeweglichen Ecke nur falsch sitzen. Der bisherige vw-Wert
       ergab ausserdem krumme Groessen — 3.953vw sind auf 430px 16.998px,
       und ein border-radius von 50 % auf einer Box mit .998 Kantenlaenge
       rundet in Safari oben und seitlich unterschiedlich. Genau daher kam
       der leicht ovale Eindruck; gemessen waren es 164 zu 168 Bildpixel.
       Glatte 17px plus aspect-ratio als Absicherung loesen das. */
    bottom: -2.35px;
    right:  -2.35px;
    z-index: 3;
    aspect-ratio: 1;
    display: flex; align-items: center; justify-content: center;
    /* 17px auf dem Pro Max statt der ersten 12: Ein Glockenzeichen ist
       kein Punkt — es braucht Fläche, sonst verschmelzen Körper und
       Klöppel zu einem Fleck. Bei 54px Kachelbreite entspricht das rund
       einem Drittel und damit der üblichen Proportion einer Plakette. */
    width:  17px;
    height: 17px;
    border-radius: 50%;
    background: var(--kb-col, #48A0D6);
    color: #ffffff;
    box-shadow: 0 0 0 1.4px #fff, 0 1px 3px rgba(15,23,42,0.28);
    pointer-events: none;
}
/* 74% statt 66%: Der Ring sitzt außerhalb, der Innenraum darf deshalb
   fast vollständig vom Zeichen genutzt werden. */
/* 13px fest statt 74 %: Prozent ergab 12.580px in einer 17px-Plakette, also
   einen Rand von 2.2100px auf jeder Seite. Das ist kein Vielfaches des
   Geraeterasters — bei DPR 3 liegen die Schritte bei 0.3333px, und Safari
   rundet die Position der Flexbox darauf. Genau daher kam der seitliche
   Versatz, der in zwei unabhaengigen Screenshots reproduzierbar bei +0.43
   und +0.47px lag: nicht die Glyphe war verschoben, sondern ihre Box.
   13px lassen exakt 2.0px Rand, teilbar durch 1/3 — die Zentrierung faellt
   damit auf jedem Geraeteraster auf denselben Punkt. 11px waeren die andere
   glatte Option, liegen aber weiter vom bisherigen Mass entfernt. */
.kal-item-erin svg { display: block; width: 13px; height: 13px; transition: transform 0.25s cubic-bezier(0.34,1.56,0.64,1); }
@media (hover: hover) {
    .kal-item.is-clickable:hover .kal-item-erin svg { transform: rotate(-12deg); }
}
@media (prefers-reduced-motion: reduce) {
    .kal-item-erin svg { transition: none; }
    .kal-item.is-clickable:hover .kal-item-erin svg { transform: none; }
}

/* Jahres-Trenner im Vergangen-Archiv */
.kal-jahr-sep { display: flex; align-items: center; gap: 10px; margin: 14px 10px 6px; }
.kal-jahr-sep[hidden] { display: none !important; } /* flex würde hidden überschreiben */
.kal-jahr-sep::before, .kal-jahr-sep::after { content: ''; flex: 1; height: 1px; }
.kal-jahr-sep::before { background: linear-gradient(90deg, transparent, rgba(100,116,139,0.18)); }
.kal-jahr-sep::after  { background: linear-gradient(90deg, rgba(100,116,139,0.18), transparent); }
.kal-jahr-sep span { font-size: 0.6rem; font-weight: 800; letter-spacing: 0.2em; color: #94a3b8; }

/* ── JSON-Diät: Lade-Schimmer, solange der Detail-Fetch läuft ── */
.kal-det-lade { margin-bottom: 18px; }
.kal-det-lade span { display: block; height: 12px; border-radius: 6px; background: #eef2f7; margin-bottom: 10px; }
.kal-det-lade span:nth-child(2) { width: 92%; }
.kal-det-lade span:nth-child(3) { width: 64%; }
.kal-det-lade-hero { width: 100%; aspect-ratio: 16 / 9; border-radius: 14px; background: #eef2f7; margin-bottom: 14px; }
@keyframes kalLadePuls { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
.kal-det-lade, .kal-det-lade-hero { animation: kalLadePuls 1.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .kal-det-lade, .kal-det-lade-hero { animation: none; } }

/* ── Bilder-Galerie — 1:1 vom Info-Modul (.oz-info-gal) ── */
.kal-det-gal { margin: clamp(0.4rem, 2vw, 0.9rem) 0 clamp(1rem, 3.5vw, 1.6rem); }
.kal-gal-track {
    display: flex;
    gap: clamp(0.5rem, 2vw, 0.85rem);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 2px;
}
.kal-gal-track::-webkit-scrollbar { display: none; }
.kal-gal-item {
    flex: 0 0 auto;
    scroll-snap-align: center;
    border: none;
    padding: 0;
    margin: 0;
    background: #f1f4f8;
    border-radius: clamp(14px, 4vw, 20px);
    overflow: hidden;
    cursor: zoom-in;
    box-shadow: 0 10px 26px -14px rgba(30, 27, 75, 0.4);
    transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
    -webkit-tap-highlight-color: transparent;
}
.kal-gal-item img { display: block; width: 100%; height: 100%; object-fit: cover; }
.kal-gal-item:active { transform: scale(0.97); }
.kal-gal-item:focus-visible { outline: 3px solid var(--kal-acc, #48A0D6); outline-offset: 3px; }

/* Einzelbild: volle Breite, natürliches Verhältnis */
.kal-gal-single .kal-gal-track { overflow: visible; }
.kal-gal-single .kal-gal-item { width: 100%; aspect-ratio: 16 / 10; }

/* Mehrere Bilder: Strip mit großen, teils sichtbaren Nachbarn */
.kal-gal-multi .kal-gal-item { width: clamp(220px, 78%, 360px); aspect-ratio: 4 / 3; }

/* Dots */
.kal-gal-dots { display: flex; justify-content: center; gap: 7px; margin-top: clamp(0.55rem, 2.2vw, 0.8rem); }
.kal-gal-dot {
    width: 7px; height: 7px; border-radius: 9999px;
    background: color-mix(in srgb, var(--kal-acc, #48A0D6) 28%, #d9dde5);
    transition: width 0.35s cubic-bezier(0.22, 1, 0.36, 1), background 0.35s ease;
}
.kal-gal-dot.is-on { width: 22px; background: var(--kal-acc, #48A0D6); }

/* Während Berührung: natives Scrollen ohne smooth-Verhalten */
.kal-gal-multi.kal-gal-paused .kal-gal-track { scroll-behavior: auto; }

@media (prefers-reduced-motion: reduce) {
    .kal-gal-item { transition: none; }
    .kal-gal-dot { transition: none; }
}

/* ── Lightbox-Modal für Galerie — 1:1 vom Info-Modul (.oz-lightbox) ── */
.kal-lightbox {
    --kal-lb-c: #48A0D6;
    position: fixed; inset: 0; z-index: 2147483600;
    display: flex; align-items: center; justify-content: center;
    background: #f8fbfd;
    -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
    opacity: 0; visibility: hidden;
    transition: opacity 0.35s ease, visibility 0s linear 0.35s;
    overflow: hidden;
}
.kal-lightbox[hidden] { display: none !important; }
.kal-lightbox.is-open { opacity: 1; visibility: visible; transition: opacity 0.35s ease; }
/* Deko-Wolke links oben — in Event-Akzentfarbe */
.kal-lb-cloud {
    position: absolute;
    top: clamp(-14px, -2vw, -6px);
    left: clamp(-30px, -6vw, -18px);
    width: clamp(120px, 38vw, 210px);
    height: auto;
    opacity: 0.09;
    color: var(--kal-lb-c);
    pointer-events: none;
    z-index: 0;
}
.kal-lb-track {
    position: relative; z-index: 1;
    display: flex; width: 100%; height: 100%;
    overflow-x: auto; scroll-snap-type: x mandatory;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.kal-lb-track::-webkit-scrollbar { display: none; }
.kal-lb-slide {
    flex: 0 0 100%; scroll-snap-align: center;
    display: flex; align-items: center; justify-content: center;
    padding: clamp(1.4rem, 6vw, 3.4rem);
    box-sizing: border-box;
}
.kal-lb-slide img {
    max-width: 100%; max-height: 100%;
    object-fit: contain; border-radius: clamp(12px, 3vw, 16px);
    box-shadow: 0 30px 70px -22px rgba(30, 27, 75, 0.32);
}
/* Schließ-Sonne: identisch zum Info-Modul — dreht bei Hover */
.kal-lb-close {
    position: absolute;
    top: max(clamp(0.8rem, 3vw, 1.2rem), env(safe-area-inset-top));
    right: clamp(0.8rem, 3vw, 1.2rem);
    z-index: 9;
    display: flex; align-items: center; justify-content: center;
    width: 46px; height: 46px;
    border: none; border-radius: 9999px;
    background: transparent; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.kal-lb-close svg {
    width: clamp(40px, 10vw, 46px); height: clamp(40px, 10vw, 46px);
    filter: drop-shadow(0 2px 7px rgba(251, 191, 36, 0.34));
    transition: transform 0.5s cubic-bezier(0.34, 1.45, 0.5, 1);
}
.kal-lb-close:hover svg { transform: rotate(90deg) scale(1.06); }
.kal-lb-close:active svg { transform: rotate(90deg) scale(0.92); transition-duration: 0.18s; }
.kal-lb-close:focus-visible { outline: none; box-shadow: 0 0 0 2.5px var(--kal-lb-c); }
.kal-lb-dots { position: absolute; bottom: max(1.3rem, env(safe-area-inset-bottom)); left: 0; right: 0; z-index: 2; display: flex; justify-content: center; gap: 8px; }
.kal-lb-dot { width: 8px; height: 8px; border-radius: 9999px; background: color-mix(in srgb, var(--kal-lb-c) 26%, #d9dce3); transition: width 0.35s ease, background 0.35s ease; }
.kal-lb-dot.is-on { width: 24px; background: var(--kal-lb-c); }
.kal-lb-counter { position: absolute; top: max(1.1rem, env(safe-area-inset-top)); left: clamp(1rem, 4vw, 1.4rem); z-index: 2; color: color-mix(in srgb, var(--kal-lb-c) 75%, #1e1b4b); font-weight: 800; font-size: 0.9rem; letter-spacing: 0.02em; }
@media (prefers-reduced-motion: reduce) {
    .kal-lightbox { transition: opacity 0.01s; }
    .kal-lb-close svg { transition: none; }
    .kal-lb-close:hover svg, .kal-lb-close:active svg { transform: none; }
}
.kal-det-hero { width: 100%; height: auto; max-height: min(62vh, 480px); object-fit: contain; border-radius: 14px; display: block; margin: 0 auto 14px; }
dotlottie-player.kal-det-hero { aspect-ratio: unset !important; width: 60%; max-width: 240px; height: auto !important; border-radius: 0; margin: 0 auto 14px; }
/* ── Hero randlos (Vollbleed) ────────────────────────────────────────
   Aktiv nur, wenn der Schalter „Randlos" im Admin gesetzt ist: das Bild
   hebt dann das obere Body-Padding vollständig auf, läuft unter die
   Navigationsleiste und bricht seitlich das 16px-Padding aus. Ist der
   Schalter aus, bleibt der gerahmte Zustand mit Radius unverändert —
   der richtige Fall für freigestellte Motive, die randlos ins Nichts
   auslaufen würden. Lottie-Animationen tragen die Klasse grundsätzlich
   nicht; sie bleiben zentriert bei 60% Breite.
   object-fit wechselt auf cover, weil contain bei randlosem Bild weiße
   Seitenbalken erzeugen würde. object-position hebt den Ausschnitt leicht
   an, weil Motive in Gruppenaufnahmen typischerweise im oberen Drittel
   sitzen — der Beschnitt nimmt so eher Boden als Gesichter. */
img.kal-det-hero.is-bleed,
#kalL2Top + .kal-det-lade-hero {
    width: calc(100% + var(--kal-l2-pad) * 2);
    max-width: none;
    margin: calc((var(--kal-l2-hh) + 4px) * -1) calc(var(--kal-l2-pad) * -1) clamp(14.4px, 4.65vw, 25px);
    border-radius: 0;
}
img.kal-det-hero.is-bleed {
    max-height: min(58vh, 460px);
    object-fit: cover;
    object-position: center 38%;
}
/* ── Weiche Welle am Bildfuß (Admin-Schalter) ────────────────────────
   Trägt das Bild eine Welle, entfällt sein unterer Abstand — den führt
   die Welle, damit zwischen beiden kein Spalt entstehen kann.
   Die Welle wird um zwei Pixel höher gezogen, als ihr negativer Margin
   sie anhebt: sie überlappt die Bildunterkante also messbar, statt sie
   nur zu berühren. Das ist dasselbe Prinzip wie an den Wellen-Nähten
   der Öffnungszeiten — eine bündige Kante rutscht früher oder später
   auf eine halbe Gerätepixel-Grenze und lässt dort eine Haarlinie des
   Bildes durchscheinen. Unten trifft Weiß auf Weiß, die Überlappung ist
   damit unsichtbar und eine Lücke geometrisch ausgeschlossen. */
img.kal-det-hero.is-bleed.has-wave { margin-bottom: 0; }
.kal-det-hero-wave {
    display: block;
    position: relative;
    width: calc(100% + var(--kal-l2-pad) * 2);
    height: clamp(28.8px, 9.3vw, 50px);
    margin-top: calc(clamp(28.8px, 9.3vw, 50px) * -1 + 2px);
    margin-left: calc(var(--kal-l2-pad) * -1);
    margin-right: calc(var(--kal-l2-pad) * -1);
    /* Halber Abstand: die unteren rund 31% der Welle sind konstruktions-
       bedingt immer deckend weiß und wirken bereits als Fußraum. Mit dem
       vollen Abstand addierte sich beides, und der Weg vom Bildinhalt zur
       Kategorie-Pille wurde spürbar länger als ohne Welle. Faktoren und
       Kurve bleiben identisch, nur die Basis ist halbiert. */
    margin-bottom: clamp(7.2px, 2.33vw, 12.5px);
    fill: #fff;
    pointer-events: none;
}
/* Der Ladeplatzhalter nimmt den randlosen Zustand vorweg: er erscheint,
   bevor der Schalter bekannt ist (er kommt erst mit den schweren Feldern),
   und randlos ist der Regelfall. Sein 16/9-Verhältnis skaliert mit der
   neuen Breite proportional mit und bleibt deshalb unangetastet. */
/* Kein flächiger Schleier über dem Bild: Ein Verlauf, der die ganze
   Bildbreite abdunkelt, um zwei Buttons lesbar zu halten, kostet auf
   jedem hellen Motiv sichtbar Qualität — auf einer Schneelandschaft legt
   er sich wie Schmutz über den Himmel. Den Kontrast tragen deshalb die
   Pillen selbst: eine nahezu deckende Fläche plus Schlagschatten löst
   sich lokal von jedem Untergrund ab, hell wie dunkel, und lässt das
   Bild unangetastet. Nach dem Scrollen greift wieder der zurückhaltende
   Ruhezustand über der Glasfläche. */
#kalLayer2:has(img.kal-det-hero.is-bleed) .kal-l2-header:not(.is-scrolled) .kal-back-btn,
#kalLayer2:has(img.kal-det-hero.is-bleed) .kal-l2-header:not(.is-scrolled) .kal-l2-close {
    background: rgba(255,255,255,0.94);
    box-shadow: 0 3px 14px rgba(15,23,42,0.24);
}
.kal-kat-chip { display: inline-flex; align-items: center; padding: clamp(1.8px, 0.698vw, 3.9px) clamp(6px, 2.326vw, 13px); border-radius: 9999px; font-size: 0.65rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: clamp(4.8px, 1.86vw, 10.4px); }
.kal-kat-chip.kat-fest        { background: rgba(72,160,214,0.1);  color: #48A0D6; }
.kal-kat-chip.kat-ausflug     { background: rgba(127,177,78,0.1);  color: #7FB14E; }
.kal-kat-chip.kat-information { background: rgba(245,158,11,0.1);  color: #F59E0B; }
.kal-kat-chip.kat-schliesstage { background: rgba(213,90,146,0.1); color: #D55A92; }
.kal-kat-chip.kat-rueckblick  { background: rgba(139,92,246,0.1);  color: #8B5CF6; }
.kal-kat-chip.kat-neuigkeit   { background: rgba(127,177,78,0.1);  color: #7FB14E; }
.kal-kat-chip.kat-wichtig     { background: rgba(239,68,68,0.1);   color: #EF4444; }
.kal-kat-chip.kat-default     { background: rgba(100,116,139,0.1); color: #64748b; }
.kal-item-teaser { font-size: calc(var(--kal-item-fs, 1rem) * 0.76); font-weight: 500; color: #475569; margin-top: calc(var(--kal-item-fs, 1rem) * 3 / 16); line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Der Satz bei mehreren Terminen steht an der Stelle des Teasers, ist aber
   keine Beschreibung, sondern eine Tatsache — deshalb eine Stufe kräftiger.
   #334155 hält auf der Karte 10,36:1. Im Archiv gewinnt die bestehende
   Regel .kal-item.is-past .kal-item-teaser mit höherer Spezifität. */
.kal-item-teaser.kal-item-termine { font-weight: 600; color: #334155; }
/* Der Satz einer Serie mit Tagesplan ("Montags für Amina 4, mittwochs
   für Amina 2") ist länger als der Satz bei mehreren Terminen und trägt
   in jedem Teil eine Aussage — gekürzt auf "… mittwochs für Am…" fehlte
   bei 375px genau die zweite Gruppe. Er darf deshalb auf zwei Zeilen
   laufen, danach kürzt er wie jeder Teaser. Die Höhe steht mit dem
   Server-Markup fest, bevor die Seite gezeichnet wird: kein CLS. */
.kal-item-teaser.kal-item-serie-satz { white-space: normal; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
/* Kategorie-Pille, vertikal exakt auf die Mitte der Versalhöhe des Titels.
   vertical-align:middle hing an der x-Höhe und setzte die Pille dadurch um
   (Versalhöhe − x-Höhe)/2 zu tief — bei 16px Titel gemessene 1,585px, auf
   dem Gerät deutlich sichtbar.
   overflow:hidden ist hier keine Kosmetik, sondern Voraussetzung: erst damit
   ist die Baseline eines inline-block laut CSS 2.1 die untere Margin-Kante
   statt der Textbaseline. Die Ausrichtung hängt danach allein an der Boxhöhe
   und nicht mehr an Ascender/Descender, die Chromium und WebKit
   unterschiedlich auslegen. Beschnitten wird nichts: höchste vorkommende
   Glyphe ist das Ü in RÜCKBLICK, es bleibt mit Reserve in der Padding-Box.
   line-height als glatter px-Wert statt geerbtem Faktor 1.3, weil WebKit
   Zeilenhöhen auf ganze Pixel rundet und Chromium nicht — die Pille war
   dadurch je nach Engine 17px oder 17,65px hoch. Jetzt überall 17px, auf
   iOS also unverändert.
   --kal-kat-h ist die Sollhöhe der Pille und die Leitgröße der Komponente:
   das vertikale Padding, der Radius und die Ausrichtung leiten sich daraus
   ab, nicht umgekehrt.
   Sämtliche Maße hängen an --kal-title-fs, der Schriftgröße des Titels, den
   die Pille beschriftet. Eine eigene Schriftgröße in rem wäre nicht fluid
   und würde bei jeder späteren Skalierung des Titels aus dem Verhältnis
   laufen. Die Faktoren sind die bisherigen Werte bezogen auf 16px Titel und
   stehen als vollständige Brüche im Code, nicht als gerundete Dezimalzahl.
   Wird der Titel später fluid gesetzt, skaliert die Pille ohne weitere
   Änderung proportional mit.
   --kal-cap ist die Versalhöhe von Plus Jakarta Sans, 745 von 1000 Einheiten
   pro Em, direkt aus der Schriftdatei gelesen. */
.kal-item-kat {
    --kal-kat-fs: calc(var(--kal-title-fs, 1rem) * 0.56);
    --kal-kat-lh: calc(var(--kal-title-fs, 1rem) * 11 / 16);
    --kal-kat-h: calc(var(--kal-title-fs, 1rem) * 17 / 16);
    --kal-kat-bw: 1px;
    --kal-kat-pad-y: calc((var(--kal-kat-h) - var(--kal-kat-bw) * 2 - var(--kal-kat-lh)) / 2);
    --kal-kat-pad-x: calc(var(--kal-title-fs, 1rem) * 7 / 16);
    --kal-cap: calc(745 / 1000);
    display: inline-block;
    font-size: var(--kal-kat-fs);
    line-height: var(--kal-kat-lh);
    font-weight: 900;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    padding: var(--kal-kat-pad-y) var(--kal-kat-pad-x);
    border-radius: calc(var(--kal-kat-h) / 2);
    margin-left: var(--kal-kat-pad-x);
    border: var(--kal-kat-bw) solid currentColor;
    opacity: 0.85;
    overflow: hidden;
    vertical-align: calc(var(--kal-title-fs, 1rem) * var(--kal-cap) / 2 - var(--kal-kat-h) / 2);
}
/* Textlage IN der Pille. Ohne Trimmung bestimmen Ascender und Descender, wo
   die Versalien in der Zeilenbox sitzen — und genau da weichen die Engines
   voneinander ab: auf dem Desktop stand der Text 0,3px zu tief, auf iOS
   dagegen 1,7px zu hoch, also unten sichtbar mehr Luft als oben.
   text-box-trim schneidet die Zeilenbox oben auf die Versalhöhe und unten
   auf die Grundlinie zurück. Damit ist die Inhaltsbox exakt der sichtbare
   Buchstabenblock, und symmetrisches Padding zentriert ihn zwangsläufig.
   Das Padding wird hier neu berechnet, weil die Inhaltshöhe nicht mehr die
   Zeilenhöhe ist, sondern die Versalhöhe — die Pille bleibt dadurch 17px.
   Browser ohne diese Eigenschaft behalten die Regel oben unverändert. */
@supports (text-box-trim: trim-both) and (text-box-edge: cap alphabetic) {
    .kal-item-kat {
        text-box-trim: trim-both;
        text-box-edge: cap alphabetic;
        --kal-kat-pad-y: calc((var(--kal-kat-h) - var(--kal-kat-bw) * 2 - var(--kal-kat-fs) * var(--kal-cap)) / 2);
    }
}

/* ── SEARCH PANEL ── */
.kal-sh-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.kal-search-btn { width: 34px; height: 34px; border-radius: 50%; border: none; background: rgba(72,160,214,0.08); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s ease, transform 0.2s cubic-bezier(0.34,1.56,0.64,1); -webkit-tap-highlight-color: transparent; color: #48A0D6; flex-shrink: 0; }
.kal-search-btn:hover { background: rgba(72,160,214,0.15); transform: scale(1.08); }
.kal-search-btn.is-active { background: rgba(72,160,214,0.18); }
.kal-search-btn:focus { outline: none; }
.kal-search-btn:focus-visible { outline: 2px solid rgba(72,160,214,0.4); outline-offset: 2px; }

/* Lupe Micro-Press */
@keyframes kal-btn-press {
    0%   { transform: scale(1); }
    40%  { transform: scale(0.92); }
    100% { transform: scale(1); }
}
.kal-search-btn.is-pressing { animation: kal-btn-press 0.14s cubic-bezier(0.25,0.46,0.45,0.94) both; }

/* Panel Container — nur Hintergrund, kein eigenes Slide */
#kalSearchPanel {
    position: absolute; inset: 0; z-index: 10;
    display: flex; flex-direction: column;
    background: linear-gradient(180deg, rgba(238,246,255,0.99) 0%, rgba(250,252,255,0.99) 35%, rgba(248,246,255,0.99) 100%);
    backdrop-filter: blur(28px); -webkit-backdrop-filter: blur(28px);
    opacity: 0; pointer-events: none;
    transition: opacity 0.08s ease;
}
#kalSearchPanel.is-open { opacity: 1; pointer-events: all; }
#kalSearchPanel::before {
    content: ''; position: absolute; top: 0; left: 10%; right: 10%; height: 1px;
    background: linear-gradient(90deg, transparent, rgba(72,160,214,0.25), rgba(213,90,146,0.18), rgba(127,177,78,0.15), transparent);
    pointer-events: none; opacity: 0; transform: scaleX(0.4);
    transition: opacity 0.38s ease 0.22s, transform 0.38s cubic-bezier(0.22,1,0.36,1) 0.22s;
    transform-origin: center;
}
#kalSearchPanel.is-open::before { opacity: 1; transform: scaleX(1); }

/* Phase 1 — Suchfeld: wächst aus Header heraus */
@keyframes kal-bar-in {
    0%   { opacity: 0; transform: translateY(-6px) scaleX(0.93); }
    100% { opacity: 1; transform: translateY(0) scaleX(1); }
}
.kal-search-bar {
    padding: 10px 14px; display: flex; align-items: center; gap: 0;
    border-bottom: 1px solid rgba(0,0,0,0.04); flex-shrink: 0;
    opacity: 0; transform: translateY(-6px) scaleX(0.93); transform-origin: top center;
}
#kalSearchPanel.is-open .kal-search-bar {
    animation: kal-bar-in 0.30s cubic-bezier(0.22,1,0.36,1) 0.04s both;
}

/* Phase 2 — Layer1 Dimmen entfernt */


/* Phase 3 — Pills: Stagger */
@keyframes kal-pill-in {
    0%   { opacity: 0; transform: translateY(6px); }
    100% { opacity: 1; transform: translateY(0); }
}
.kal-search-pills {
    display: flex; gap: 5px; overflow-x: auto; padding: 9px 14px 11px;
    scrollbar-width: none; -ms-overflow-style: none; flex-shrink: 0;
    border-bottom: 1px solid rgba(0,0,0,0.04); touch-action: pan-x; -webkit-overflow-scrolling: touch;
}
.kal-search-pills::-webkit-scrollbar { display: none; }
#kalSearchPanel.is-open .kal-filter-pill {
    animation: kal-pill-in 0.26s cubic-bezier(0.22,1,0.36,1) both;
}
#kalSearchPanel.is-open .kal-filter-pill:nth-child(1) { animation-delay: 0.18s; }
#kalSearchPanel.is-open .kal-filter-pill:nth-child(2) { animation-delay: 0.21s; }
#kalSearchPanel.is-open .kal-filter-pill:nth-child(3) { animation-delay: 0.24s; }
#kalSearchPanel.is-open .kal-filter-pill:nth-child(4) { animation-delay: 0.27s; }
#kalSearchPanel.is-open .kal-filter-pill:nth-child(5) { animation-delay: 0.30s; }
#kalSearchPanel.is-open .kal-filter-pill:nth-child(6) { animation-delay: 0.33s; }
#kalSearchPanel.is-open .kal-filter-pill:nth-child(7) { animation-delay: 0.36s; }
#kalSearchPanel.is-open .kal-filter-pill:nth-child(8) { animation-delay: 0.39s; }
#kalSearchPanel.is-open .kal-filter-pill:nth-child(9) { animation-delay: 0.42s; }
#kalSearchPanel.is-open .kal-filter-pill:nth-child(10) { animation-delay: 0.45s; }
#kalSearchPanel.is-open .kal-filter-pill:nth-child(11) { animation-delay: 0.48s; }
#kalSearchPanel.is-open .kal-filter-pill:nth-child(12) { animation-delay: 0.51s; }

/* Phase 4 — Count: sanft mit Wipe */
@keyframes kal-count-in {
    0%   { opacity: 0; }
    100% { opacity: 1; }
}
.kal-search-count {
    font-size: 0.58rem; font-weight: 800; letter-spacing: 0.16em; text-transform: uppercase;
    color: #c4cdd6; padding: 12px 20px 4px; display: flex; align-items: center;
    gap: 8px; flex-shrink: 0; opacity: 0;
}
.kal-search-count.is-visible {
    animation: kal-count-in 0.22s cubic-bezier(0.22,1,0.36,1) both;
}
.kal-search-count::after { content: ''; flex: 1; height: 1px; background: linear-gradient(90deg, rgba(0,0,0,0.05), transparent); }

/* Phase 5 — Karten: feines Reflow-Gefühl */
@keyframes kal-result-in {
    0%   { opacity: 0; transform: translateY(10px); }
    100% { opacity: 1; transform: translateY(0); }
}
#kalSearchResults .kal-item {
    opacity: 0; transform: translateY(10px);
    animation: kal-result-in 0.26s cubic-bezier(0.22,1,0.36,1) both;
}
#kalSearchResults .kal-item:nth-child(1) { animation-delay: 0.38s; }
#kalSearchResults .kal-item:nth-child(2) { animation-delay: 0.42s; }
#kalSearchResults .kal-item:nth-child(3) { animation-delay: 0.46s; }
#kalSearchResults .kal-item:nth-child(4) { animation-delay: 0.50s; }
#kalSearchResults .kal-item:nth-child(n+5) { animation-delay: 0.54s; }

@media (prefers-reduced-motion: reduce) {
    .kal-search-bar, .kal-filter-pill, .kal-search-count, #kalSearchResults .kal-item { animation: none !important; opacity: 1 !important; transform: none !important; }
}


/* kal-search-bar ist oben in den Animationsregeln definiert */

.kal-search-input-wrap { flex: 1; display: flex; align-items: center; gap: 7px; background: rgba(255,255,255,0.6); border: 1px solid rgba(0,0,0,0.07); border-radius: 10px; padding: 0 8px 0 10px; transition: border-color 0.2s ease, box-shadow 0.2s ease; box-shadow: inset 0 1px 3px rgba(0,0,0,0.03); min-width: 0; }
.kal-search-input-wrap:focus-within { border-color: rgba(72,160,214,0.3); box-shadow: 0 0 0 3px rgba(72,160,214,0.07), inset 0 1px 3px rgba(0,0,0,0.02); }

.kal-search-input { flex: 1; height: 38px; border: none; background: transparent; font-family: 'Plus Jakarta Sans', system-ui, sans-serif; font-size: 1rem; font-weight: 500; color: #1E293B; outline: none; min-width: 0; -webkit-appearance: none; }


.kal-search-input::placeholder { color: #b0bec9; }
.kal-search-input::-webkit-search-cancel-button { display: none; }
.kal-search-clear { width: 28px; height: 28px; min-width: 28px; border-radius: 50%; border: none; background: rgba(0,0,0,0.07); display: flex; align-items: center; justify-content: center; cursor: pointer; -webkit-tap-highlight-color: transparent; color: #64748b; opacity: 0; pointer-events: none; transition: opacity 0.15s ease; flex-shrink: 0; }
.kal-search-clear.is-visible { opacity: 1; pointer-events: all; }
.kal-search-cancel { font-size: 0.7rem; font-weight: 600; color: #b0bec9; background: none; border: none; cursor: pointer; white-space: nowrap; min-height: 44px; padding: 0 0 0 12px; -webkit-tap-highlight-color: transparent; flex-shrink: 0; letter-spacing: 0.02em; transition: color 0.18s ease; }
.kal-search-cancel:hover { color: #64748b; }

.kal-filter-pill { display: inline-flex; align-items: center; padding: 5px 13px; border-radius: 99px; white-space: nowrap; font-size: 0.6rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; border: 1px solid rgba(255,255,255,0.85); background: rgba(255,255,255,0.72); color: #475569; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background 0.16s ease, color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease; min-height: 30px; flex-shrink: 0; box-shadow: 0 1px 3px rgba(15,23,42,0.08), inset 0 1px 1px rgba(255,255,255,0.7); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
/* Pin-SVG in Pill und Slabel: fixer Mini-Abstand statt Flex-Gap */
.kal-filter-pill svg, .kal-slabel svg { flex-shrink: 0; margin-right: 5px; }
.kal-slabel svg { margin-right: -4px; }
.kal-filter-pill:hover { color: #334155; background: rgba(255,255,255,0.9); box-shadow: 0 2px 8px rgba(15,23,42,0.08), inset 0 1px 2px rgba(255,255,255,0.9); }
.kal-filter-pill:active { transform: scale(0.95); }
.kal-filter-pill.is-active { color: #fff; border-color: transparent; box-shadow: 0 3px 10px rgba(0,0,0,0.14), inset 0 1px 2px rgba(255,255,255,0.25); }

.kal-filter-pill[data-filter="alle"].is-active       { background: #48A0D6; }
.kal-filter-pill[data-filter="fixiert"].is-active    { background: #EF4444; }
.kal-filter-pill[data-filter="heute"].is-active      { background: #D55A92; }
.kal-filter-pill[data-filter="fest"].is-active       { background: #48A0D6; }
.kal-filter-pill[data-filter="ausflug"].is-active    { background: #7FB14E; }
.kal-filter-pill[data-filter="information"].is-active { background: #F59E0B; }
.kal-filter-pill[data-filter="schliesstage"].is-active { background: #D55A92; }
.kal-filter-pill[data-filter="rueckblick"].is-active  { background: #8B5CF6; }
.kal-filter-pill[data-filter="neuigkeit"].is-active   { background: #7FB14E; }
.kal-filter-pill[data-filter="wichtig"].is-active     { background: #EF4444; }
.kal-filter-pill[data-filter="vergangen"].is-active  { background: #94a3b8; }


.kal-search-results { flex: 1; overflow-y: auto; overflow-x: hidden; overscroll-behavior: none; touch-action: pan-y; -webkit-overflow-scrolling: touch; padding-bottom: calc(env(safe-area-inset-bottom, 16px) + 16px); }
#kalSearchResults .kal-item { opacity: 1 !important; transform: none !important; animation: none !important; }

.kal-search-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 3rem 2rem; text-align: center; gap: 12px; }
.kal-search-empty-icon { width: 52px; height: 52px; border-radius: 50%; background: rgba(72,160,214,0.08); display: flex; align-items: center; justify-content: center; }
.kal-search-empty strong { display: block; font-size: 0.95rem; font-weight: 800; color: #475569; margin-bottom: 4px; }
.kal-search-empty p { font-size: 0.8rem; color: #94a3b8; font-weight: 500; line-height: 1.6; }
.kal-search-highlight { color: #F59E0B; font-weight: 900; background: rgba(245,158,11,0.12); border-radius: 3px; padding: 0 2px; }

/* ── FILTER PILL: DIESE WOCHE ── */
.kal-filter-pill[data-filter="diese-woche"].is-active { background: linear-gradient(135deg, #48A0D6 0%, #7FB14E 100%); }

/* ── DETAIL ACTION BAR (Kalender + Teilen) — Teil 1 des Artikel-Abspanns:
   zwei zentrierte, bordlose Glas-Chips statt konkurrierender Rahmen-Boxen.
   Glas = rgba-Weiß 0,72 wie die Timeline-Karte (bewusst OHNE backdrop-filter —
   verschachtelte Filter im Layer-2-Modal stören iOS); die Chip-Schatten sind
   je Aktion dezent eingefärbt, der Text bleibt Tinte, das Icon trägt die
   Semantikfarbe. Liquid-Hover über die etablierte Spring-Kurve. */
.kal-det-actions { display: flex; justify-content: center; flex-wrap: wrap; gap: clamp(4.8px, 1.86vw, 10.4px); margin-top: clamp(15.6px, 6.047vw, 33.8px); }
.kal-det-action-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: clamp(4.2px, 1.628vw, 9.1px);
    padding: clamp(7.8px, 3.023vw, 16.9px) clamp(12px, 4.651vw, 26px);
    border-radius: 9999px;
    border: none;
    background: rgba(255, 255, 255, 0.72);
    cursor: pointer; font-size: 0.74rem; font-weight: 800;
    color: #1e1b4b;
    letter-spacing: 0.02em; line-height: 1;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.95),
                0 1px 3px rgba(30, 27, 75, 0.06),
                0 8px 20px -12px var(--kab-shadow, rgba(72,160,214,0.55));
    transition: background-color 0.18s ease, transform 0.22s cubic-bezier(0.34,1.56,0.64,1), box-shadow 0.18s ease;
    -webkit-tap-highlight-color: transparent;
    /* Touch-Ziel: Formel-Min läge unter 44px — der Accessibility-Floor gewinnt */
    min-height: clamp(44px, 10.698vw, 59.8px);
    font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
}
#kalDetCalBtn   { --kab-shadow: rgba(72,160,214,0.55); }
#kalDetShareBtn { --kab-shadow: rgba(127,177,78,0.55); }
/* ── ROUTE: die Hauptaktion der Leiste ────────────────────────────────────
   Eigene Zeile über den beiden anderen. flex-basis 100% erzwingt den
   Umbruch im bestehenden flex-wrap-Container — dafür braucht es keinen
   zusätzlichen Wrapper, der die Zentrierung der Zeile darunter neu
   aufrollen würde.

   Dreispaltiges Raster statt inline-flex: Die Mittelspalte trägt Symbol
   und Beschriftung und sitzt dadurch exakt in der Buttonmitte, während der
   Pfeil rechts am Rand steht. Mit einer reinen Flex-Zeile ginge beides
   nicht gleichzeitig — entweder wäre der Text durch den Pfeil nach links
   verschoben, oder der Pfeil klebte am Text. Die leere erste Spalte ist
   das Gegengewicht des Pfeils und hält die Mitte ehrlich.

   Der Pfeil ist kein Zierrat: Er ist das Versprechen, dass hier die
   Karten-App aufgeht und nicht innerhalb der Seite navigiert wird.

   Flacher als zuvor (rund 56px statt 60), Schatten enger gezogen — die
   Wirkung als Hauptaktion kommt aus Breite, getöntem Symbolkreis und
   Schriftgewicht, nicht aus Höhe und Weichzeichner. */
.kal-det-action-wide {
    flex: 1 0 100%;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 0;
    text-decoration: none;
    color: #1e1b4b;
    font-size: 0.8rem;
    /* 48 statt 56px: Die Beschriftung steht zweizeilig und misst mit ihren
       Zeilenhoehen rund 26px — bei 56px blieben oben und unten je 15px
       Leere, und der Button wirkte innen hohl. 48px legen einen ruhigen,
       gleichmaessigen Rand um den Inhalt, ohne das Beruehrungsziel zu
       gefaehrden. */
    min-height: clamp(28.8px, 11.163vw, 62.4px);
    padding: 0 clamp(7.2px, 2.791vw, 15.6px);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.95),
                0 2px 4px -1px rgba(30, 27, 75, 0.06),
                0 8px 16px -12px var(--kab-shadow);
}
.kal-det-action-wide:hover {
    box-shadow: inset 0 1px 0 #ffffff,
                0 2px 4px -1px rgba(30, 27, 75, 0.06),
                0 12px 22px -14px var(--kab-shadow);
}
.kal-route-mid { grid-column: 2; display: inline-flex; align-items: center; gap: clamp(6px, 2.326vw, 13px); }
/* Zweizeilig statt einer langen Zeile: "Route öffnen" ist die Handlung,
   "in Google Maps" die Auskunft, wohin sie führt. Untereinander gesetzt
   liest man zuerst das eine und bei Bedarf das andere — eine durchgehende
   Zeile zwang beides auf dieselbe Stufe und klang nach Funktionsbeschreibung.
   Die Zusatzzeile ist links buendig zur Hauptzeile, nicht zentriert: zwei
   unterschiedlich lange, mittig gesetzte Zeilen ergaeben eine Wimpelform. */
.kal-route-txt { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.12; }
.kal-route-haupt { font-size: 0.82rem; font-weight: 800; letter-spacing: 0.01em; color: #1e1b4b; }
/* Gemischte Schreibung statt gesperrter Versalien: "IN GOOGLE MAPS" las
   sich wie eine Systemmeldung. Klein, gerade gesetzt und ohne Laufweite
   ist es eine Herkunftsangabe — der Ton, in dem eine Zeitung ihre Quelle
   nennt, nicht der einer Statusleiste. Der Eigenname behaelt dabei seine
   normale Schreibung, was ihn ueberhaupt erst als Marke erkennbar macht.
   Farbe und Groesse sind nachgezogen: Bei #8a92a3 lag der Kontrast bei
   rund 3,2:1 und die Zeile las sich wie ausgegraut — also wie etwas, das
   gerade nicht geht. #6b7280 erreicht 4,8:1, besteht damit die AA-Schwelle
   fuer Fliesstext und ist zugleich exakt der Ton der Rubrikzeilen in der
   Faktenkarte. Der Abstand zur Hauptzeile ist ganz entfallen: Die beiden
   Zeilen sind ein Satz, den nur die Gewichtung teilt, kein Zweizeiler. */
.kal-route-zusatz { font-size: 0.72rem; font-weight: 600; letter-spacing: 0; color: #6b7280; margin-top: 0; }
/* Getönter Kreis: hebt das Symbol aus der weißen Fläche, ohne einen
   zweiten Button zu behaupten — deshalb ohne Rand und ohne Schatten. */
.kal-route-ic {
    flex: 0 0 auto;
    width: clamp(15.6px, 6.047vw, 33.8px);
    height: clamp(15.6px, 6.047vw, 33.8px);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    /* Kategoriefarbe statt festem Rosa, in derselben Tönung wie die
       Symbolkachel der Faktenkarte: Der Weg zur Karte beginnt oben in der
       Ortszeile und endet hier — beide Enden derselben Handlung müssen
       dieselbe Farbe tragen. Ein fest verdrahtetes Rosa hätte bei einem
       blauen oder grünen Artikel wie ein Fremdkörper gewirkt. */
    background: color-mix(in srgb, var(--kal-acc, #48A0D6) 17%, #ffffff);
    color: var(--kal-acc, #48A0D6);
}
.kal-route-ic svg { width: 56%; height: 56%; }
.kal-route-pfeil { grid-column: 3; justify-self: end; width: clamp(7.8px, 3.023vw, 16.9px); height: auto; color: var(--kal-acc, #48A0D6); opacity: 0.7; transition: transform 0.22s cubic-bezier(0.34,1.56,0.64,1); }
.kal-det-action-wide:hover .kal-route-pfeil { transform: translateX(clamp(1.2px, 0.465vw, 2.6px)); }
@media (prefers-reduced-motion: reduce) { .kal-route-pfeil { transition: none; } .kal-det-action-wide:hover .kal-route-pfeil { transform: none; } }
/* Auch der Abdruck unter dem Button folgt der Kategorie. Er war das
   auffälligste Rosa im ganzen Artikel — ein farbiger Schatten trägt weit
   und wird eher wahrgenommen als das Symbol, das ihn erklären soll.
   60 Prozent Deckkraft im Farbmix: Der Schatten hat in seiner eigenen
   Deklaration bereits einen negativen Streuwert und läuft dadurch weich
   aus; eine volle Farbe wäre dort zu präsent. */
#kalDetMapsBtn  { --kab-shadow: color-mix(in srgb, var(--kal-acc, #48A0D6) 60%, transparent); }
.kal-det-action-btn:hover { background: rgba(255, 255, 255, 0.94); transform: translateY(-2px); box-shadow: inset 0 1px 0 #ffffff, 0 1px 3px rgba(30, 27, 75, 0.06), 0 14px 28px -12px var(--kab-shadow, rgba(72,160,214,0.55)); }
.kal-det-action-btn:active { transform: scale(0.95); }
.kal-det-action-btn svg { flex-shrink: 0; }
.kal-det-action-btn:focus-visible { outline: 2px solid color-mix(in srgb, var(--kal-acc, #48A0D6) 40%, transparent); outline-offset: 2px; border-radius: 9999px; }
@media (prefers-reduced-motion: reduce) { .kal-det-action-btn { transition: none; } .kal-det-action-btn:hover { transform: none; } }

/* ── DETAIL NAV (Voriger / Nächster) — Teil 2 des Abspanns: rahmenlose,
   typografische Lese-Navigation im Editorial-Stil. Der Trenner darüber ist
   eine akzentfarbene Haarlinie mit mittigem Knoten-Punkt (dieselbe Punkt-
   Sprache wie die Timeline-Rest-Zeile); die Pfeile sitzen in weichen
   Akzent-Kreisen. Kein Rahmen, kein Kasten — der emotionale Schlusspunkt
   gehört dem Footer-Bild darunter. */
.kal-det-nav { position: relative; display: flex; align-items: stretch; gap: clamp(4.8px, 1.86vw, 10.4px); margin-top: clamp(13.2px, 5.116vw, 28.6px); padding-top: clamp(10.8px, 4.186vw, 23.4px); }
.kal-det-nav::before { content: ''; position: absolute; top: 0; left: 6%; right: 6%; height: 1px; background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--kal-acc, #48A0D6) 26%, transparent) 24%, color-mix(in srgb, var(--kal-acc, #48A0D6) 26%, transparent) 76%, transparent); }
.kal-det-nav::after { content: ''; position: absolute; top: 0; left: 50%; width: clamp(3px, 1.163vw, 6.5px); height: clamp(3px, 1.163vw, 6.5px); border-radius: 9999px; background: color-mix(in srgb, var(--kal-acc, #48A0D6) 60%, #ffffff); transform: translate(-50%, -50%); }
/* Navigation folgt dem Kategorie-Akzent des geöffneten Artikels
   (--kal-acc, gesetzt in openDetail) — wie Zitat-Panel, Galerie-Dots
   und Lightbox. Fallback Amina Blau. */
.kal-det-nav-btn {
    flex: 1; min-width: 0; display: flex; align-items: center; gap: clamp(6px, 2.326vw, 13px);
    padding: clamp(5.4px, 2.093vw, 11.7px) clamp(6px, 2.326vw, 13px);
    border-radius: clamp(10.8px, 4.186vw, 23.4px);
    border: none;
    background: transparent;
    cursor: pointer; text-align: left;
    -webkit-tap-highlight-color: transparent;
    transition: background-color 0.18s ease, transform 0.2s cubic-bezier(0.34,1.56,0.64,1);
    min-height: clamp(44px, 11.163vw, 62.4px); font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.kal-det-nav-btn:hover { background: color-mix(in srgb, var(--kal-acc, #48A0D6) 8%, transparent); transform: translateY(-1px); }
.kal-det-nav-btn:active { transform: scale(0.97); }
.kal-det-nav-btn:focus-visible { outline: 2px solid color-mix(in srgb, var(--kal-acc, #48A0D6) 40%, transparent); outline-offset: 2px; }
.kal-det-nav-btn.is-next { flex-direction: row; text-align: right; justify-content: flex-end; }
/* Pfeil im weichen Akzent-Kreis: Chip-Größe und Innenabstand gekoppelt
   (28px / 7px Basis, identische min/max-Faktoren) */
.kal-det-nav-btn svg { flex-shrink: 0; box-sizing: border-box; width: clamp(16.8px, 6.512vw, 36.4px); height: clamp(16.8px, 6.512vw, 36.4px); padding: clamp(4.2px, 1.628vw, 9.1px); border-radius: 9999px; background: color-mix(in srgb, var(--kal-acc, #48A0D6) 10%, transparent); color: var(--kal-acc, #48A0D6); transition: background-color 0.18s ease, transform 0.2s cubic-bezier(0.34,1.56,0.64,1); }
.kal-det-nav-btn:hover svg { background: color-mix(in srgb, var(--kal-acc, #48A0D6) 18%, transparent); }
.kal-det-nav-btn.is-prev:hover svg { transform: translateX(-2px); }
.kal-det-nav-btn.is-next:hover svg { transform: translateX(2px); }
.kal-det-nav-info { display: flex; flex-direction: column; gap: clamp(1.8px, 0.698vw, 3.9px); min-width: 0; flex: 1; }
.kal-det-nav-btn.is-next .kal-det-nav-info { align-items: flex-end; }
.kal-det-nav-label { font-size: 0.52rem; font-weight: 900; text-transform: uppercase; letter-spacing: 0.2em; padding-inline-start: 0.2em; color: color-mix(in srgb, var(--kal-acc, #48A0D6) 62%, transparent); }
.kal-det-nav-btn.is-next .kal-det-nav-label { padding-inline-start: 0; padding-inline-end: 0.2em; }
.kal-det-nav-title { font-size: 0.78rem; font-weight: 700; color: #1E293B; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
/* Vertikale Haarlinie zwischen Voriger und Nächster (nur wenn beide da sind) */
.kal-det-nav-divider { display: block; flex: 0 0 auto; width: 1px; align-self: stretch; margin: clamp(3px, 1.163vw, 6.5px) 0; background: linear-gradient(180deg, transparent, color-mix(in srgb, var(--kal-acc, #48A0D6) 22%, transparent), transparent); }
@media (prefers-reduced-motion: reduce) { .kal-det-nav-btn { transition: none; } .kal-det-nav-btn:hover { transform: none; } .kal-det-nav-btn svg { transition: none; } .kal-det-nav-btn.is-prev:hover svg, .kal-det-nav-btn.is-next:hover svg { transform: none; } }


