    /* ── Sponti-Begleiter: Figur sitzt fest in der Ecke, keine Ein-/Ausfahr-Animation ── */
    .sponti-companion {
      position: fixed;
      bottom: -1.6rem;
      left: -1.4rem; /* 0.8rem weiter rechts als ursprünglich (-2.2rem) — Bubble-Konstanten unten entsprechend nachgerechnet */
      z-index: 120;
      pointer-events: none;
    }
    .sponti-companion.is-away { display: none; }

    .sponti-figure-wrap {
      position: relative;
      width: var(--companion-reserve);
      height: var(--companion-reserve);
      pointer-events: auto;
    }
    /* Default: alle Mood-Gesichter ausgeblendet bis auf mood-1 (neutral) als
       Platzhalter, bevor JS per setSpontiFace() das szenenpassende Gesicht
       setzt — verhindert ein FOUC mit allen Gesichtern übereinander. */
    #sponti-figure-svg [id^="mood-"] { display: none; }
    #sponti-figure-svg #mood-1 { display: block; }
    .sponti-figure {
      position: relative;
      width: 100%;
      height: 100%;
      object-fit: contain;
      object-position: bottom left;
    }

    /* Kopf-Breite aus den SVG-Pfaddaten gemessen (Pfade 6/7 = Kopfsilhouette,
       x:[95,478] im viewBox 0 0 536 971) → rechte Kante bei 478/971*100≈49.2%
       der Figur-Box (object-fit:contain+bottom-left, Höhe limitiert, daher
       x-Offset=0 → dieselbe /971-Formel wie für die Y-Achse). Die Blase
       beginnt klar dahinter, damit sie den Kopf nie überlagert. Mundhöhe
       (Pfade 12+13, Zentrum y=352) liegt bei 352/971*100≈36.3%, Pfeil zeigt
       von dort auf den Mund. */
    .sponti-bubble {
      position: absolute;
      bottom: 60%; /* fix nahe Mundhöhe verankert, Blase wächst nach oben statt nach unten */
      left: 58%;
      /* rechte Kante exakt auf der Linie des Profil-Buttons im Header
         (.app-header padding-right: 1.25rem=20px). Companion sitzt fixed bei
         left:-1.4rem(-22.4px), Figur-Box 264px breit → Konstante
         -22.4+264+20=261.6, als calc(konstante - 100vw) viewport-relativ
         trotz Verschachtelung in der Figur-Box. width:auto füllt den Raum
         zwischen left/right. Pro Breakpoint angepasste Konstante nötig,
         da Figurgrösse/companion-Offset dort abweichen (s. Media Queries). */
      right: calc(261.6px - 100vw);
      width: auto;
      min-width: 170px;
      max-width: var(--bubble-max-width);
      /* etwas heller als --panel (rgba(10,24,32,.78)), sonst kaum vom fast
         gleichfarbigen App-Hintergrund (#111f26) zu unterscheiden; Kontrast
         primär über box-shadow (Tiefe) statt grelle Farbe lösen */
      background: rgba(26,46,56,.94);
      backdrop-filter: blur(20px);
      -webkit-backdrop-filter: blur(20px);
      box-shadow: 0 4px 16px rgba(0,0,0,.35);
      color: var(--text);
      border: 1.5px dashed rgba(205,169,104,.62);
      border-radius: 16px;
      /* unten etwas mehr Padding als oben (.85rem statt .6rem) - rückt die
         letzte Textzeile näher an die Spickel-Spitze (s. .sponti-bubble-tail
         bottom:17px), die selbst nicht weiter runter darf, ohne in die
         border-radius-Rundung der Box zu rutschen (s. Kommentar dort). */
      padding: .6rem .9rem .85rem;
      font-size: 1rem;
      font-weight: 400; /* normal statt fett (600) — deutlich weniger dominant */
      line-height: 1.4;
      transition: opacity .2s ease, transform .2s ease;
      /* Wächst standardmässig nach oben (s. bottom:60% oben). Bei langen
         Texten würde sie dabei über den oberen Rand von .app-content
         hinauswachsen - _spontiKeepBubbleInBounds() (JS) misst das und
         setzt --bubble-shift, um die Blase per transform soweit nötig
         nach unten zu schieben, ohne sie abzuschneiden. */
      transform: translateY(var(--bubble-shift, 0px));
    }
    /* Zusatzsatz fuer ETA-Hinweise (s. _spontiSaySubtext(), app.js) - fliesst
       direkt im selben Absatz/Stil wie der Haupttext weiter (inline, gleiche
       Groesse/Farbe/Gewicht) statt als optisch abgesetzter zweiter Block, da
       inhaltlich eine Fortsetzung desselben Gedankens ist, kein Nebenhinweis
       (Konsistenzfix 2026-07-07). Der fuehrende Leerraum kommt aus dem in
       _spontiSaySubtext() vorangestellten Leerzeichen. */
    .sponti-bubble-subtext {
      display: inline;
    }
    /* Wechsel-Animation beim Sprüche-Tausch (Ladescreen, s. _spontiSaySwap) —
       kurzer Fade+Versatz statt reinem Opacity-Blink */
    .sponti-bubble.swap-out {
      opacity: 0;
      transform: translateY(calc(4px + var(--bubble-shift, 0px)));
    }
    /* Sprechpfeil als Inline-SVG (statt CSS-Border-Dreieck): nur so lässt
       sich auch auf den schrägen Kanten ein gestrichelter Rand abbilden,
       analog stroke-dasharray bei .route-arrow weiter unten. Polygon deckt
       die Lücke zur Box in Blasenfarbe, der Pfad zeichnet nur die beiden
       sichtbaren/linken Kanten gestrichelt nach. bottom:17px statt z.B. 12px,
       damit der Pfeil auf dem geraden Teil der Kante ansetzt statt in der
       border-radius:16px-Rundung zu landen (dort entsteht sonst eine Lücke
       zwischen Rand und Pfeil) - 17px ist der kleinstmögliche Wert knapp
       über dem 16px-Radius, der die Spitze möglichst nah an die Mitte der
       letzten Textzeile bringt (s. padding-bottom oben). */
    .sponti-bubble-tail {
      position: absolute;
      left: -12px;
      bottom: 17px;
      overflow: visible;
      /* Gegenverschiebung zu --bubble-shift: die Blase selbst darf nach unten
         wandern, wenn sie sonst über den Container hinausragen würde - der
         Spickel soll dabei aber an Ort und Stelle (Mundhöhe) bleiben statt
         mitzuwandern. */
      transform: translateY(calc(-1 * var(--bubble-shift, 0px)));
    }

    .start-settings-area {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 1rem;
      width: calc(100% - 3rem);
      max-width: 320px;
    }

    /* .btn-accept (Zustimmungsbutton Ergebnisseite) teilt bewusst exakt diese
       Basisregel statt einer eigenen Kopie — zwei gleich-spezifische Regeln
       hätten sich bei border-radius/border-right gegen .btn-start-arrow
       widersprochen (Kaskaden-Reihenfolge), je nachdem welche zuletzt im
       Quellcode stand. Eine gemeinsame Regel macht beide Buttons strukturell
       identisch statt sie synchron halten zu müssen. */
    .btn-start-big, .btn-accept {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: .6rem;
      width: 100%;
      max-width: 320px;
      background: var(--accent-green);
      color: #07110d; /* statt #163028: erst damit volle AAA-Normaltext-Schwelle (7:1) auf Grün erreicht */
      border: 3px solid #163028;
      border-radius: 19px;
      min-height: 58px;
      padding: 1rem 1.25rem;
      font-size: 1.1rem;
      font-weight: 750;
      cursor: pointer;
      transition: background .15s, opacity .15s, transform .1s;
      letter-spacing: .01em;
      font-family: inherit;
      white-space: nowrap;
    }

    /* Statt opacity (wirkt verwaschen) Hintergrund Richtung --bg abdunkeln —
       bleibt barrierefreundlich: Kontrast gegen den dunklen Button-Text fällt
       von ~7:1 (Ruhezustand, AAA) auf ~5.5:1 (Hover) bzw. ~4.6:1 (Active),
       beide klar über der AA-Normaltext-Schwelle von 4.5:1. */
    .btn-start-big:hover,  .btn-accept:hover  { background: color-mix(in srgb, var(--accent-green) 85%, var(--bg) 15%); }
    .btn-start-big:active, .btn-accept:active { background: color-mix(in srgb, var(--accent-green) 75%, var(--bg) 25%); transform: scale(.97); }
    .btn-start-big:disabled { opacity: .5; cursor: not-allowed; }

    /* Rechte Ecke wird zur Pfeilspitze statt Icon im Button: zwei gestapelte
       Formen (dunkler Rand aussen, goldene Spitze innen, gleichmässig per
       scale() verkleinert statt eigener Masse) sitzen als eigenständiges
       Element mit kleinem Abstand neben der Pillenform (statt nahtlos
       angeschlossen — sonst wirkt Grün/Gold wie ein einziger, unklar
       getrennter Block). Konvention für alle vorwärtsführenden Haupt-Buttons
       ("nächster Schritt") — nicht für Rückwärts-Aktionen wie "Zurück zum
       Start". */
    .btn-start-arrow {
      border-radius: 19px 0 0 19px;
      position: relative;
      /* Pfeilspitze + Abstand (6px) zählen zur Gesamtbreite des Buttons statt
         diese zu überschreiten (war früher width:100% + margin-right:34px -
         dadurch wurde der Button+Spitze zusammen breiter als alle Nachbar-
         Buttons). Einzige Quelle der Wahrheit statt der früheren Pro-Screen-
         Overrides (.btn-accept/.login-actions/.weather-block-actions), die
         teils gar nicht griffen, weil der Button nicht im erwarteten Wrapper
         sass. */
      width: calc(100% - 50px);
    }
    .btn-start-arrow::before,
    .btn-start-arrow::after {
      content: '';
      position: absolute;
      left: calc(100% + 6px);
      /* rechteckiger Ansatz links + spitzes Dreieck rechts, Spitze leicht
         angeschliffen statt scharfkantig */
      clip-path: polygon(0% 0%, 45% 0%, 88% 38%, 100% 50%, 88% 62%, 45% 100%, 0% 100%);
    }
    .btn-start-arrow::before {
      /* top/bottom statt top:50%+fixer Höhe+translateY(-50%): so deckt sich
         die Spitze exakt mit der Border-Box des Buttons (-3px = Randbreite),
         egal wie hoch der Button durch Textumbruch tatsächlich wird, statt
         bei abweichender Höhe sichtbar zu verrutschen. */
      top: -3px;
      bottom: -3px;
      width: 44px;
      background: #163028;
    }
    .btn-start-arrow::after {
      /* Exakt um die Randbreite (3px, s. .btn-start-big border) eingerückt
         statt der früheren transform:scale(.88) - die hat Breite und Höhe
         gleichermassen verkleinert, wodurch der sichtbare Rand oben/unten
         dicker war als an der Spitze. top:0/bottom:0 (statt -3px) + width
         44px-3px ergeben einen gleichmässig 3px breiten Rand auf allen
         Seiten der Spitze. */
      top: 0;
      bottom: 0;
      width: 41px;
      background: #b58936;
    }

    .start-center-group {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 1.5rem;
      /* Explizite Breite statt auto/fit-content: ohne sie zentriert
         align-items:center den Button anhand seiner eigenen Layout-Box, OHNE
         die per Pfeilspitzen-Pseudoelement überlaufende Spitze (die liegt
         ausserhalb des Flex-Layouts) - der Button+Spitze-Verbund landete
         dadurch sichtbar ausserhalb der Mitte. Mit fester Breite löst sich
         .btn-start-arrow width:calc(100% - 50px) (s. dort) gegen DIESE
         Breite auf und alles zentriert sich konsistent zum Day-Picker. */
      width: 100%;
      max-width: 320px;
    }
    /* Selbst mit fester Breite zentriert align-items:center den Button
       weiterhin anhand seiner eigenen (durch width:calc(100% - 50px)
       schmaleren) Layout-Box, OHNE die überlaufende Pfeilspitze - sichtbar
       als asymmetrischer Versatz (Lücke links UND rechts, Spitze ragt dann
       zusätzlich rechts raus). align-self:flex-start verhindert das gezielt
       in den Containern, wo der Button direktes Flex-Kind eines
       align-items:center-Containers ist (betrifft NICHT .login-main/
       .weather-block-actions, die anders aufgebaut sind und bereits korrekt
       linksbündig sitzen). */
    .start-center-group .btn-start-arrow,
    #screen-error .btn-start-arrow {
      align-self: flex-start;
    }
    /* Spezifität .error-register-btn.btn-next statt nur .error-register-btn,
       sonst gewinnt .btn-next{display:block} (kommt später im Stylesheet,
       gleiche Spezifität) die Kaskade und der Button bleibt IMMER sichtbar,
       auch für eingeloggte User (Bug behoben 2026-07-07). */
    .error-register-btn.btn-next { display: none; }
    #screen-error.error-cta-register .error-register-btn.btn-next,
    #screen-no-routes.error-cta-register .error-register-btn.btn-next { display: block; }
    /* Push-Opt-in bei No-Match (Feature "Auto-Pipeline"): default versteckt,
       Sichtbarkeit wird rein per JS (_updateNotifyContext(), app.js) gesteuert,
       da sie vom Vorhandensein eines Demand-Signal-Profils abhaengt, nicht
       nur vom Login-Status wie bei .error-cta-register. */
    .btn-notify-request { display: none; }
    #screen-start .btn-start-big:active  { transform: scale(.97); }

    /* .prefs-modal-* Basisklassen sind nach public/css/app/modal.css
       ausgelagert (gemeinsam mit allen anderen Modals genutzt). */

    .setting-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: .9rem;
      gap: .8rem;
    }

    .setting-row:last-child { margin-bottom: 0; }

    .setting-label {
      font-size: .85rem;
      color: var(--muted-aaa);
      flex-shrink: 0;
      font-weight: 600;
    }

    .toggle-group { display: flex; gap: .3rem; flex-wrap: wrap; justify-content: flex-end; }

    .toggle-btn {
      padding: .3rem .65rem;
      border-radius: 20px;
      border: 1.5px solid rgba(200,225,235,.32);
      background: none;
      color: var(--muted-aaa);
      font-size: .8rem;
      font-weight: 600;
      cursor: pointer;
      transition: all .15s;
      font-family: inherit;
      -webkit-tap-highlight-color: transparent;
    }

    .toggle-btn.active { background: var(--accent-green); border-color: var(--accent-green); color: #07110d; } /* Gold käme bei dieser Textgrösse nie über ~6.6:1 (math. Decke), Grün schafft AAA */
    .toggle-btn:not(.active):hover { border-color: var(--accent); color: var(--accent); }

    .donation-preset.active { background: var(--accent-green); border-color: var(--accent-green); color: #07110d; }

    .donation-intro { margin-bottom: .6rem; }
    .donation-intro p { margin: 0 0 .5rem; font-size: .85rem; color: var(--muted-aaa); line-height: 1.45; }
    .donation-ask { margin: 0 0 .8rem; font-size: .85rem; color: var(--muted-aaa); line-height: 1.45; }
    .donation-disclaimer { margin: 0 0 .8rem; font-size: .75rem; color: var(--muted-aaa); line-height: 1.4; }
    .donation-presets { display: flex; gap: .6rem; margin: 0 0 .8rem; }
    .donation-custom { display: flex; align-items: center; gap: .6rem; margin: 0 0 1rem; font-size: .85rem; color: var(--muted-aaa); white-space: nowrap; }
    .donation-stepper { display: flex; align-items: stretch; border: 1px solid var(--border-color, #ccc); border-radius: 8px; overflow: hidden; }
    .donation-stepper input { width: 4.2rem; padding: .4rem .3rem; border: none; background: transparent; color: inherit; text-align: center; font-size: .9rem; }
    .donation-stepper input::-webkit-outer-spin-button,
    .donation-stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
    .donation-stepper input[type=number] { -moz-appearance: textfield; }
    .donation-step { width: 2rem; border: none; background: transparent; color: var(--muted-aaa); font-size: 1.1rem; line-height: 1; cursor: pointer; }
    .donation-step:hover { background: rgba(255,255,255,.08); }
    .donation-step:first-child { border-right: 1px solid var(--border-color, #ccc); }
    .donation-step:last-child { border-left: 1px solid var(--border-color, #ccc); }
    .donation-actions { display: flex; gap: .6rem; }

    .divider { height: 1px; background: var(--line); margin: .9rem 0; }

    .sponti-confetti { position: fixed; inset: 0; pointer-events: none; z-index: 9999; overflow: hidden; }
    .sponti-confetti-piece {
      position: absolute;
      top: -10px;
      width: 8px;
      height: 8px;
      border-radius: 1px;
      opacity: .9;
      animation-name: sponti-confetti-fall;
      animation-timing-function: ease-in;
      animation-fill-mode: forwards;
    }
    @keyframes sponti-confetti-fall {
      0%   { transform: translate(0, 0) rotate(0deg); opacity: .9; }
      100% { transform: translate(calc(var(--drift) * 120px), 105vh) rotate(540deg); opacity: 0; }
    }

