/* ==========================================================================
   JFcompanion Widget — Basis-Look

   Wird AUTOMATISCH ausgeliefert — kein manueller Copy-Paste-Schritt mehr
   nötig. widget.js injiziert bei jeder Einbindungsart (Blade UND die vier
   Nicht-Blade-Embeds: reines HTML/React/Vue/Angular, siehe
   CompanionSettings::getWidgetEmbedTabs()) selbst einen
   <link id="companion-default-css" rel="stylesheet">, der diese Datei per
   ServeWidgetCssController roh aus dem Package ausliefert (siehe
   injectDefaultCss() in widget.js, gleiches "kein Build-Schritt, Package-
   Datei ist die einzige Quelle"-Prinzip wie bei widget.js selbst). Diese
   Datei hier IST also das ausgelieferte Stylesheet, keine separate
   Kopiervorlage mehr.

   SHADOW DOM (siehe init() in widget.js): der <link> landet nicht mehr in
   <head>, sondern in einem Shadow Root — #companion-widget im Document
   (Blade/Embed-Snippet-Markup) ist seitdem der Shadow-HOST
   (attachShadow({mode:'open'})), das komplette Widget-Markup (Orb, Pills,
   Panel, Backdrop, Resize-Handle) lebt darunter in einem ZWEITEN, GLEICH
   benannten #companion-widget INNERHALB des Shadow Roots (Shadow Roots
   sind ein eigener ID-Scope, keine Kollision mit dem Host). Grund: volle
   CSS-Isolation in beide Richtungen — eine echte Kollision auf einer
   Testseite (fremdes .companion-chat-CSS zerschoss das Panel) hat gezeigt,
   dass reines "alles mit #companion-widget präfixieren" nicht reicht,
   sobald der Host selbst kollidierende Selektoren mitbringt. Alle
   Selektoren HIER in dieser Datei bleiben deshalb absichtlich fast
   unverändert (#companion-widget, #companion-backdrop, ...) — sie landen
   jetzt einfach in einem <style> INNERHALB des Shadow Roots und matchen
   dort exakt dieselben (jetzt shadow-internen) Elemente wie vorher im
   Light DOM. Einzige echte Anpassung: :root -> :host weiter unten (siehe
   dort). mode:'open' (nicht 'closed'), damit E2E-Tests/DevTools weiter
   hineinsehen können.

   Früher (bis zu einem Live-Test-Fund) war das anders: diese Datei war
   eine reine Referenz zum manuellen Einfügen in Filament → Companion →
   Style → "Custom CSS". Das erreichte aber NUR den Blade-Embed-Pfad
   (rendert innerhalb dieser App) — die vier Nicht-Blade-Embeds auf einer
   FREMDEN Kundenseite haben nie irgendein CSS bekommen, weder Default noch
   Custom, weil dort niemand je diesen <style>-Tag zu sehen bekam. Backdrop,
   Scroll-Sperre und Orb-Ausblenden hängen alle an CSS-Regeln hier — ohne
   sie war der Chat auf einer echten Kundenseite unbrauchbar (Scrollen ging
   weiter, Orb blieb neben dem offenen Panel sichtbar, kein Backdrop).

   Das Custom-CSS-Feld in Filament (companion_settings.custom_css, siehe
   widget.blade.php) existiert weiterhin — es bleibt ein reiner OVERRIDE.
   widget.js klont dessen <style>-Inhalt jetzt zusätzlich in den Shadow
   Root (siehe cloneCustomCssIntoShadow() dort), NACH dem Default-CSS-<link>
   (Kaskaden-Reihenfolge: Default vor Override) — ohne diesen Klon würde
   ein gespeichertes Custom-CSS seit der Shadow-Kapselung gar nicht mehr
   ankommen (ein <style> im Light DOM erreicht Shadow-Tree-Inhalte nicht).
   Dank des identisch benannten inneren #companion-widget matchen bereits
   gespeicherte "#companion-widget ..."-Selektoren im Custom-CSS weiterhin
   unverändert. Erreicht nach wie vor nur den Blade-Pfad, das ist
   unverändert ein bekannter, nicht in diesem Umbau behobener Rahmen.

   Bewusst self-contained: auf einer fremden Kundenseite existieren weder
   die --color-brand-* Tokens noch die POC-Styles aus jfconcept-locals
   app.css. Jede var() hat deshalb einen Fallback.

   DOM (aus widget.js):
     #companion-widget (Document)  Shadow-HOST, von Blade/Embed-Snippet
                                    gerendert, trägt data-companion-*-
                                    Attribute. Bleibt LEER (kein Light-DOM-
                                    Kind) — der Shadow Root darunter trägt
                                    das gesamte Markup:
       #shadow-root (open)
         link#companion-default-css   siehe injectDefaultCss()
         style#companion-widget-style Klon des Custom-CSS, siehe oben
         div.companion-backdrop       Geschwister des inneren
                                       #companion-widget, NICHT sein Kind —
                                       ein Vollflächen-Overlay als Kind
                                       hätte dieselbe Größen-
                                       Beschränkung wie der Balken und
                                       würde vom overflow:hidden des
                                       Containers beschnitten. Dimmt/blurrt
                                       die Seite während der Chat offen
                                       ist. position:fixed funktioniert im
                                       Shadow Root normal (kein eigener
                                       Containing Block ohne transform/
                                       filter auf einem Vorfahren).
         #companion-widget (Shadow)   EIN Container trägt Balken- UND
                                       Panel-Optik — GLEICHE id wie der
                                       Host oben, aber eigener Shadow-Tree-
                                       ID-Scope, keine Kollision.
           button.companion-orb          absolut positioniert, 💬↔X-Crossfade,
                                          wandert von der Balken- in die
                                          Header-Ecke (siehe Morph-Animation unten)
             svg.companion-orb__icon--chat
             svg.companion-orb__icon--close
           div.companion-pills           display none|flex (inline von JS)
             button.companion-pill
           div.companion-chat            display none|flex (inline von JS),
                                          position:relative (Containing Block
                                          für die beiden absoluten Kinder
                                          companion-booking-dim/-view unten)
             div.companion-chat-body       EIN Wrapper um Kopf/Verlauf/
                                            Vorschläge/Formular/KI-Hinweis
                                            (siehe enterBookingView() in
                                            widget.js) -- bekommt bei offenem
                                            Buchungs-Embed die Klasse
                                            .companion-chat-body--dimmed
                                            (blur+reduzierte Deckkraft+inert)
                                            statt einzeln display:none
               div.companion-header
                 span.companion-header__name
                 div.companion-header__actions
               div.companion-panel__scroll
                 div.companion-typing        Tippindikator während der Ask-Request läuft
                 div.companion-msg           Kontakt-Icon-Klick hängt ein Nachrichtenpaar
                                             an (Echo + Antwort mit tel:/mailto:-Link,
                                             siehe appendContactChannelReply() in widget.js)
                 button/a.companion-cta-scroll Scroll-CTA (nur wenn ctaZoneKey gesetzt; <a>
                                              statt <button>, wenn die Zielzone laut
                                              page_pattern auf einer anderen Seite liegt,
                                              siehe ctaZoneTarget() in widget.js)
               div.companion-panel__suggestions
                 button.companion-pill       Followups
               form.companion-freetext-form
                 input.companion-freetext-input
                 button.companion-freetext-submit
               p.companion-ai-hint          dauerhafter KI-Hinweis unter der Eingabe
             div.companion-booking-dim     Dimm-Layer über companion-chat-body,
                                            siehe .companion-backdrop-Pendant
                                            weiter unten -- .is-visible via JS
             div.companion-booking-view    Termin-Embed als echtes Overlay
                                            (position:absolute) ÜBER dem
                                            geblurrten companion-chat-body,
                                            siehe enterBookingView()/
                                            exitBookingView() in widget.js

   Morph-Animation Balken -> Chatfenster (siehe openChat/closeChat in widget.js):
   #companion-widget (im Shadow Root) selbst ist der EINE Container, der
   zwischen Balken- und Panel-Optik wechselt — kein zweites schwebendes
   Element mehr. Ablauf:
     1. Pills faden aus, Orb-Icon crossfadet zu X, Backdrop faded ein
                                                            (--companion-dur-pill
                                                             bzw. --companion-dur-backdrop)
     2. Klasse .companion-morphing an, Breite wächst      (--companion-dur-width)
     3. Höhe wächst (startet schon bei 65% der Breiten-Dauer, NICHT erst
        nach deren Ende — Review-Fix gegen einen sichtbaren "breit+flach"-
        Zwischenzustand, siehe MORPH_OVERLAP_RATIO in widget.js), Orb
        wandert in die Header-Ecke                        (--companion-dur-height)
     4. .companion-morphing raus, .is-open an, Inhalt fadet ein
                                                            (--companion-dur-content)
   Schließen läuft exakt rückwärts (Backdrop fadet mit Phase 1 rückwärts
   wieder aus, die Ueberlappung aus Phase 2/3 gilt spiegelbildlich für deren
   rückwärtigen Pendants). Breite/Höhe werden während der Animation über
   JS-gesetzte Custom Properties --companion-w/--companion-h angesteuert —
   beide Achsen laufen überlappend statt strikt seriell, bleiben aber
   jeweils EIGENSTAENDIGE Transitions mit eigener Dauer (kein gemeinsamer,
   gleichzeitig gestarteter Start) — ein FLIP-artiger Kniff: die Zielgröße
   wird kurz unsichtbar über die echte .is-open-Klasse gemessen
   (measureOpenRect in widget.js), dann als Pixelwert gesetzt, bevor der
   nächste Frame ihn animiert. Wichtig für diesen Rundweg (messen -> als
   Pixelwert zurückschreiben): box-sizing: border-box weiter unten — ohne
   das beschreibt width/height nur die Content-Box, das gemessene (und damit
   bereits Padding-inklusive) getBoundingClientRect()-Ergebnis würde beim
   Zurückschreiben ein zweites Mal um das Padding aufgebläht.
   prefers-reduced-motion: reduce überspringt den Morph komplett (direkter
   Umschalt ohne Zwischenschritte, siehe openChat/closeChat) UND kappt
   zusätzlich alle übrigen Transitions/Animationen hier (inkl. Backdrop)
   per globalem Media-Query weiter unten.
   Nach dem Schließen kehren die Pills gestaffelt zurück: eigener
   data-state="returning" (companion-pill-return, fährt von LINKS ein) statt
   des vertikalen Zonenwechsel-Zustands "entering" — siehe closeChat in
   widget.js.
   ========================================================================== */

/* =====================================================================
   Abstandsskala dieser Datei
   ---------------------------------------------------------------------
   Die Skala ist nicht gesetzt, sondern aus dem Bestand abgelesen (Zählung
   vom 26.08.2026 über 92 Zahlenwerte in Abständen und festen Größen).
   Der Befund: Ab 12px lagen bereits über 85 Prozent der Werte auf einem
   Vierer-Raster. Darunter galt es nie — und zwar aus einem Grund, der beim
   Hinsehen offensichtlich ist: Unter 12px bietet ein Vierer-Raster nur drei
   Stufen (0, 4, 8), und die dichten Bedienelemente in einem 461px breiten
   Fenster brauchen mehr. Ein durchgängiges Vierer-Raster hätte 16
   Fundstellen gebrochen statt zwei, ausgerechnet an den engsten Stellen.

   Drei Spuren, weil nicht jede Zahl im Stylesheet ein Abstand ist:

   Spur A — Layout (gap, padding, margin)
       Zweier-Schritte unter 12px, Vierer-Schritte ab 12px:
       2 4 6 8 10 12 16 20 24 28 32 36 40 44
       Neue Abstände gehören auf diese Stufen.

   Spur B — Objektmaße (kein Raster)
       Icon-Kantenlängen, Punktdurchmesser, Bauteil-Maxima — und Maße, die
       aus einem anderen Element ausgerechnet sind. Ein 14px-Icon folgt dem
       Glyphen, nicht dem Rhythmus; es auf 16 zu ziehen, damit die Zahl durch
       vier teilbar ist, ändert das Icon ohne Gewinn. Für ausgerechnete
       Maße gilt die Auflage aus Spur C: Die Rechnung gehört daneben-
       geschrieben, besser noch als calc() in den Code, damit sie die
       nächste Änderung am Bezugselement überlebt.

   Spur C — Optische Korrektur
       2px, ausschließlich zum Ausgleich eines Rahmens oder einer Schrift-
       grundlinie, und nur mit Kommentar. Ohne diese Auflage wird aus der
       Korrektur nach zwei Jahren ein weiterer unerklärter Wert.

   NICHT von dieser Skala erfasst: die Custom Properties, die diese Datei nur
   benutzt und nicht definiert — --radius-button, --radius-bubble,
   --radius-element, --gap-s, --gap-m. Sie werden im Stylesheet der
   KUNDENSEITE definiert, schlagen absichtlich durch den Shadow Root durch
   und sind fließend: --radius-button ergab gemessen 13,4838px bei 1440px
   Fensterbreite und 10,0965px bei 390px. Sie gehören nicht uns. Einen
   dieser Werte auf eine feste Zahl zu ziehen wäre eine Produkt-
   entscheidung und keine Aufräumarbeit — es zerstört die Anpassung des
   Widgets an die Wirtsseite.
   ===================================================================== */

/* Morph-Timings, zentral hier justierbar. Müssen zu den MORPH_*_MS
   Konstanten in widget.js passen (dort sequenziert JS die Phasen über
   setTimeout statt transitionend — siehe Kommentar dort).

   :host statt :root (Shadow-DOM-Umbau): :root traf schon VOR dem Umbau
   nicht das innere #companion-widget selbst (siehe alter Kommentar hier —
   .companion-backdrop war ein Geschwister davon, Custom Properties
   vererben sich nur den DOM-Baum hinunter). Seit diese ganze Datei in
   einem Shadow Root landet, würde :root außerdem gar nichts mehr
   Sinnvolles treffen: :root bezeichnet immer das Root-Element des
   DOKUMENTS (<html>), unabhängig davon, in welchem Shadow Root das
   Stylesheet selbst liegt — diese Werte kämen im Shadow Root also gar
   nicht mehr zuverlässig an. :host dagegen setzt sie direkt auf den
   Shadow-HOST (das #companion-widget-Element im Document), von dem aus sie
   ganz normal in den gesamten Shadow-Tree hinein vererben — erreicht damit
   sowohl das innere #companion-widget ALS AUCH #companion-backdrop (beide
   Kinder/Nachfahren des Hosts im flachen Baum), ohne dass beide noch
   getrennt behandelt werden müssten. */
:host {
    --companion-dur-pill: 0.15s;
    --companion-dur-width: 0.25s;
    --companion-dur-height: 0.3s;
    --companion-dur-content: 0.15s;
    --companion-dur-morph: 0.55s; /* dur-width + dur-height, für Radius/Padding/Hintergrund */
    --companion-dur-backdrop: 0.32s;
    --companion-ease: cubic-bezier(0.2, 0.7, 0.3, 1);

    /* Glass-Look (Nutzerwunsch): der Container selbst ist durchscheinend,
       seine Kinder NICHT. Deshalb ausschliesslich ueber background-alpha +
       backdrop-filter geloest und bewusst NICHT ueber opacity auf
       #companion-widget -- opacity erzeugt eine Gruppen-Deckkraft, die jedes
       Kind (Text, Icons, Pills, Eingabefeld) mit ausblasst; genau das war
       frueher der Grund fuer den entfernten @media(hover:hover)-Block mit
       opacity:0.8. background-alpha faerbt dagegen nur die Flaeche des
       Containers, Kinder malen darueber mit voller Deckkraft.
       EINE Quelle fuer Balken, Morph und offenes Panel -- die drei
       Zustaende sollen sich in der Glas-Anmutung nicht unterscheiden,
       sonst blitzt beim Morph ein Deckkraft-/Blur-Sprung auf. */
    --companion-glass-bg: rgba(30, 30, 30, 0.55);
    --companion-glass-blur: blur(22px) saturate(160%);
    /* Kanten-Highlight als INSET-Schatten, nicht als border: eine echte
       1px-Border haette den Container um 2px in beide Achsen wachsen
       lassen und damit die FLIP-Messung (measureOpenRect/measureBarRect in
       widget.js) verschoben. inset box-shadow ist layout-neutral. */
    --companion-glass-edge:
        inset 0 1px 0 rgba(255, 255, 255, 0.16),
        inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}

/* Fallback ohne backdrop-filter (aeltere Browser, deaktivierte GPU-
   Kompositierung): ohne Weichzeichner ist eine 0.55er Flaeche kein Glas
   mehr, sondern nur noch ein schlecht lesbarer Schleier ueber fremdem
   Seiteninhalt. Ein einzelner Token-Override reicht -- Balken, Morph und
   Panel lesen alle dieselbe Variable und werden damit gemeinsam wieder
   nahezu deckend. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    :host {
        --companion-glass-bg: rgba(30, 30, 30, 0.94);
    }
}

/* Systemeinstellung "Transparenz reduzieren" (macOS/iOS/Windows): gleicher
   Token-Override, hier aus Barrierefreiheitsgruenden statt mangels
   Browser-Support. Der Blur bleibt gesetzt, traegt bei 0.94 aber praktisch
   nichts mehr bei. */
@media (prefers-reduced-transparency: reduce) {
    :host {
        --companion-glass-bg: rgba(30, 30, 30, 0.94);
    }
}

/* Self-contained-Anspruch (siehe Dateikopf) gilt auch für box-sizing: eine
   fremde Kundenseite hat nicht zwingend einen globalen border-box-Reset.
   Ohne diesen hier würde width/height auf #companion-widget nur die
   Content-Box beschreiben und padding käme ON TOP oben drauf — bei
   .is-open/.companion-morphing wären das +2*1.875rem in jede Richtung,
   und die FLIP-Messung (measureOpenRect/measureBarRect, siehe widget.js)
   würde diesen bereits aufgeblähten Wert beim nächsten Zurückschreiben
   als --companion-w/-h nochmal aufblähen — der Balken/das Panel wäre
   spürbar größer als berechnet und ließe sich nicht mehr sauber in der
   Ecke verankern. */
#companion-widget,
#companion-widget *,
#companion-widget *::before,
#companion-widget *::after,
#companion-backdrop {
    box-sizing: border-box;
}

#companion-widget {
    position: fixed;
    right: var(--gap-m, 1.875rem);
    bottom: var(--gap-m, 1.875rem);
    /* Über dem Seiten-Header, unter Cookie-Bannern. */
    z-index: 120;

    display: flex;
    /* Orb steht im DOM VOR den Pills, soll aber rechts sitzen. */
    flex-direction: row-reverse;
    align-items: center;
    gap: var(--gap-s, 1.25rem);

    width: max-content;
    max-width: min(60rem, calc(100vw - 2 * var(--gap-m, 1.875rem)));

    /* BUGFIX (Live-Debug, Messbefund 68x24px): ohne min-height bestimmt
       ALLEIN das Padding (0.75rem oben+unten = 24px) die Balkenhöhe — der
       Orb (.companion-orb, 2.25rem/36px) trägt trotz seiner sichtbaren
       Größe NICHTS zur Container-Höhe bei, weil er für die Morph-
       Positionsanimation position:absolute ist (siehe .companion-orb unten)
       und damit aus dem Flex-Layout-Fluss fällt; leere/kollabierte Pills
       tragen ebenfalls nichts bei. Resultat war ein 24px-Balken, aus dem der
       Orb sichtbar unten herausragte. min-height 60px (~PoC-Referenz 64px)
       ist der Boden, align-items:center oben zentriert die (im Fluss
       stehenden) Pills automatisch mit; der Orb bleibt bei top:0.75rem
       (siehe unten) — bei jetzt garantierten 60px Höhe zentriert das
       exakt: 12px Luft + 36px Orb + 12px Luft = 60px. measureBarRect() in
       widget.js misst diesen CSS-Wert live, keine JS-Anpassung nötig.
       border-radius bleibt var(--radius-bubble, 999px): der Wert clamped
       CSS-seitig ohnehin auf die halbe (kürzere) Kantenlänge, ergibt bei
       60px Höhe automatisch die volle Pill-Form.

       FOLGEFEHLER (Live-Debug): dieselbe Ursache griff auch in der BREITE —
       ohne Pills (Orb-only-Zustand) trägt der absolut positionierte Orb
       ebenfalls nichts zu width:max-content bei, übrig blieb nur das
       Padding (1rem links+rechts = 32px), der Container wurde schmaler als
       der Orb (36px) und dieser ragte links heraus. min-width analog zur
       min-height, mit demselben 12px-Versatz gerechnet: 12px Luft + 36px
       Orb + 12px Luft = 60px.

       BUGFIX 2 (Live-Debug, Screenshot-Vergleich: Balken wirkte als Pille
       statt Kreis, Messbefund 68x60px): min-width war ehemals 4.25rem
       (68px), berechnet aus .companion-orb's damaligem right:1rem (16px)
       Versatz — während min-height weiterhin aus dessen top:0.75rem (12px)
       Versatz folgte. Diese zwei UNTERSCHIEDLICHEN Orb-Versätze (12px
       vertikal vs. 16px horizontal) waren der eigentliche Grund für die
       Pillenform: 68x60px statt eines Kreises. Fix: .companion-orb bekommt
       im Basiszustand (siehe .companion-orb unten) jetzt right:0.75rem
       statt right:1rem — symmetrisch zu top:0.75rem — und min-width folgt
       hier derselben 60px-Rechnung wie min-height. Betrifft NUR den
       Basiszustand; .is-open/.is-width-target/.is-height-target setzen
       top/right ohnehin explizit auf 1.875rem und sind von dieser Aenderung
       unberührt. Der Pills-sichtbar-Fall hängt nicht an min-width/-height
       (dort treibt width:max-content über die tatsächliche Pill-Breite
       den Container längst über diesen Boden hinaus). */
    min-height: 3.75rem;
    min-width: 3.75rem;

    padding: 0.75rem 1rem;
    border-radius: var(--radius-bubble, 999px);
    /* Glas statt deckend (aktueller Nutzerwunsch, loest die frueheren
       0.96/"der Balken soll nicht transparent sein"-Werte ab): die
       Container-FLAECHE ist durchscheinend, der Seitenhintergrund
       dahinter wird weichgezeichnet und leicht gesaettigt. Werte liegen
       in --companion-glass-* auf :host (siehe oben), damit Balken, Morph
       und offenes Panel garantiert identisch aussehen.
       Kein opacity auf diesem Container -- das wuerde jedes Kind
       (Text, Icons, Pills, Eingabefeld) mit ausblassen; genau darum
       geht die Transluzenz ausschliesslich ueber background-alpha. */
    background: var(--companion-glass-bg, rgba(30, 30, 30, 0.55));
    -webkit-backdrop-filter: var(--companion-glass-blur, blur(22px) saturate(160%));
    backdrop-filter: var(--companion-glass-blur, blur(22px) saturate(160%));
    /* Zwischenzeitlich war hier gar kein backdrop-filter mehr gesetzt: bei
       der damaligen 0.96-Deckkraft war er wirkungslos und wurde aus
       Performance-Gruenden gestrichen (er ist laut Kommentar bei
       .companion-morphing weiter unten der teuerste Rendering-Anteil).
       Mit der Glas-Deckkraft ist er wieder DER Effekt und steht deshalb
       bewusst schon in dieser Basisregel -- der Ruhezustand ist der am
       laengsten sichtbare, und ein Balken ohne Blur haette sonst eine
       andere Anmutung als das Panel. Der Preis ist bekannt und
       akzeptiert. */
    box-shadow:
        var(--companion-glass-edge,
            inset 0 1px 0 rgba(255, 255, 255, 0.16),
            inset 0 0 0 1px rgba(255, 255, 255, 0.08)),
        0 1px 2px rgba(0, 0, 0, 0.2),
        0 18px 48px rgba(0, 0, 0, 0.28);

    font-family: Poppins, system-ui, sans-serif;
}

/* --- Breiten-Morph bei Pillenwechsel ---------------------------------------
   Der Bar-Zustand oben hat width:max-content OHNE eigene width-Transition —
   ändert sich die Pillenzahl/-länge (Zonenwechsel, Resize-Neubewertung,
   siehe renderPills()/morphPillsWidthTo() in widget.js), sprang die
   Balkenbreite bisher hart auf den neuen Wert. Eigene, schmale Klasse statt
   Erweiterung der Basisregel oben: width transitioniert NUR, während
   morphPillsWidthTo() sie aktiv hält (FLIP: alte Pixelbreite -> neue
   Pixelbreite -> zurück auf max-content) — die Basisregel bleibt für jeden
   sonstigen Reflow unangetastet (kein Transition-Overhead z.B. bei einem
   Resize ohne tatsächlichen Pillenwechsel).

   Eigenständig von .companion-morphing/--companion-w weiter unten (eigene
   Klasse, eigene Custom Property --companion-pill-w) und praktisch nie mit
   ihr gleichzeitig aktiv: renderPills()/morphPillsWidthTo() laufen nur,
   während der Chat GESCHLOSSEN ist (siehe applyActiveZone() in widget.js),
   openChat() räumt zusätzlich defensiv per stopPillsWidthMorph() auf, falls
   doch mitten in den ~250ms geklickt wird — sollten beide Klassen dennoch
   einmal zusammentreffen, gewinnt .companion-morphing (gleiche Spezifität,
   aber später in dieser Datei definiert).

   NICHT im Mobil-Breakpoint aktiv (siehe @media max-width:640px weiter
   unten, width:auto dort): widget.js prüft das selbst per
   pillsWidthMorphEligible() und setzt die Klasse dort gar nicht erst — sonst
   würde die höhere Selektor-Spezifität hier (ID+Klasse) das dortige
   width:auto überstimmen. */
#companion-widget.companion-pills-width-morphing {
    width: var(--companion-pill-w, max-content);
    transition: width var(--companion-dur-width) var(--companion-ease);
}

/* --- Morph: Balken <-> Chatfenster ----------------------------------------
   .companion-morphing ist NUR während der Animation aktiv (siehe widget.js).
   Breite/Höhe kommen aus JS-gesetzten Pixelwerten (--companion-w/-h), damit
   die beiden Phasen (erst Breite, dann Höhe, jetzt mit Ueberlappung — siehe
   MORPH_OVERLAP_RATIO in widget.js) weitgehend parallel statt komplett
   sequenziell ablaufen. Radius/Padding/Hintergrund dürfen dagegen über die
   volle Morph-Dauer gemeinsam überblenden — sie haben keine Sequenz-
   Anforderung.

   FIX (Review Finding 1: width/height-Animation statt transform, teuer
   zusammen mit backdrop-filter): echtes transform-basiertes FLIP (scale
   statt width/height) wurde geprüft und verworfen — mehrere Kinder hängen
   direkt an diesem Container (Orb absolut positioniert mit eigener
   top/right-Transition, Pills-Leiste, Chat-Panel mit Formular/Buttons/Text),
   ohne einen einzelnen Inhalts-Wrapper, der gegen skaliert werden könnte;
   ein Scale würde Text/Icons/Formularfelder verzerren. Zusätzlich wäre
   border-radius unter dem hier stark NICHT-uniformen Balken->Panel-
   Seitenverhältnis (sx != sy) zur Ellipse verzerrt worden. width/height
   bleiben deshalb bestehen — stattdessen zwei gezielte, risikoarme
   Entlastungen:
     1. will-change unten kündigt dem Browser die width/height-Aenderung
        vorab an (eigene Compositing-Layer-Vorbereitung statt Neuberechnung
        pro Frame).
     2. Historisch wurde zusätzlich der teuerste Anteil, der
        backdrop-filter, während der Morph-Phase auf none gesetzt und erst
        im .is-open-Ruhezustand wieder aktiviert. Das ist mit der
        Glas-Umstellung (siehe --companion-glass-* auf :host) ENTFALLEN:
        bei 0.55 Deckkraft ist der Weichzeichner nicht mehr nur Zierde,
        sondern der Effekt selbst — ihn mitten im Morph abzuschalten
        liesse den Seiteninhalt hart durchschlagen und am Ende wieder
        zuschnappen. Alle drei Zustände (Balken, Morph, offen) tragen
        deshalb jetzt denselben Blur. Bleibt: will-change aus Punkt 1.
        Der separate, seitenweite Dimm-Blur auf #companion-backdrop
        (eigenes, nicht animierendes Element) ist davon unberührt. */
#companion-widget.companion-morphing {
    flex-direction: column;
    align-items: stretch;
    width: var(--companion-w, max-content);
    height: var(--companion-h, auto);
    max-width: none;
    padding: var(--gap-m, 1.875rem);
    border-radius: var(--radius-element, 24px);
    background: var(--companion-glass-bg, rgba(30, 30, 30, 0.55));
    /* Frueher wurde der Blur waehrend des Morphs auf none gesetzt (Perf,
       siehe Kommentar oben). Seit der Glas-Umstellung geht das nicht mehr:
       bei 0.55 Deckkraft ist der Weichzeichner der eigentliche Effekt --
       ihn fuer die Morph-Dauer abzuschalten liesse den Seiteninhalt
       mittendrin hart durchschlagen und beim .is-open-Ruhezustand wieder
       zuschnappen. Er bleibt deshalb ueber alle drei Zustaende identisch. */
    -webkit-backdrop-filter: var(--companion-glass-blur, blur(22px) saturate(160%));
    backdrop-filter: var(--companion-glass-blur, blur(22px) saturate(160%));
    overflow: hidden;
    will-change: width, height;
    transition:
        width var(--companion-dur-width) var(--companion-ease),
        height var(--companion-dur-height) var(--companion-ease),
        border-radius var(--companion-dur-morph) var(--companion-ease),
        padding var(--companion-dur-morph) var(--companion-ease),
        background-color var(--companion-dur-morph) var(--companion-ease);
}

/* Ruhezustand offen: keine Transition (sonst würde z.B. ein Resize-Event
   animieren, siehe pillResizeHandler-Pendant für den Chat in widget.js) —
   das gilt jetzt auch für den Griff-Strich (siehe .companion-resize-handle
   weiter unten): height ändert sich hier IMMER hart, ob durch die
   min(82vh,780px)-Formel oder per Drag über --companion-user-h, es gibt
   nichts zu deaktivieren/reaktivieren wie bei einer Transition. */
#companion-widget.is-open {
    flex-direction: column;
    align-items: stretch;
    /* Vorher 480x(min(70vh,620px)): nach einer Frage blieb kaum Platz für
       Frage+Antwort, ständiges Scrollen nötig (siehe Review). Moderat
       größer statt maximal — 520px/82vh bleiben auf einem 1280px-
       Laptop-Fenster noch klar als schwebendes Panel erkennbar, nicht
       bildschirmfüllend. */
    width: min(520px, calc(100vw - 2 * var(--gap-m, 1.875rem)));
    /* --companion-user-h: vom Griff-Strich per Drag/Pfeiltasten gesetzt
       (widget.js, applyUserHeight) und ab dann für die Session (bis zum
       nächsten teardown()) beibehalten — leer/nicht gesetzt fällt auf
       die ursprüngliche Formel zurück. Kein Sonderfall für den Morph
       nötig: measureOpenRect()/measureBarRect() lesen ohnehin die
       tatsächlich gerenderte Höhe dieser Regel aus, eine laufende
       Drag-Aenderung landet also automatisch im nächsten Oeffnen/
       Schließen-Zyklus. */
    height: var(--companion-user-h, min(82vh, 780px));
    max-width: none;
    padding: var(--gap-m, 1.875rem);
    border-radius: var(--radius-element, 24px);
    background: var(--companion-glass-bg, rgba(30, 30, 30, 0.55));
    /* Identisch zur Basisregel und zu .companion-morphing -- gleiche
       Tokens, damit ueber den kompletten Oeffnen/Schliessen-Weg kein
       Deckkraft- oder Blur-Sprung sichtbar wird. Explizit wiederholt
       statt weggelassen: .companion-morphing setzt beide Eigenschaften
       ebenfalls, ein Auslassen hier haette am Ende des Morphs einen
       Zustandswechsel bedeutet. */
    -webkit-backdrop-filter: var(--companion-glass-blur, blur(22px) saturate(160%));
    backdrop-filter: var(--companion-glass-blur, blur(22px) saturate(160%));
    overflow: hidden;
    /* Der Container-Gap (gap: var(--gap-s, 1.25rem) aus der Basisregel
       oben) ist im offenen Zustand funktionslos: .companion-chat ist hier
       das einzige sichtbare Flow-Kind (Orb und Griff-Strich sind position:
       absolute, .companion-pills ist -- nach dem Bugfix an
       .companion-pills.is-morph-hidden oben zuverlässig -- display:none)
       und ein Gap wirkt nur ZWISCHEN Flex-Items. Trotzdem explizit auf 0
       gesetzt statt implizit bei 1.25rem zu belassen: sollte
       .companion-pills durch einen künftigen, heute noch unbekannten Bug
       wieder sichtbar werden, schiebt ein von vornherein neutralisierter
       Gap .companion-chat dann NICHT zusätzlich um 20px nach unten --
       genau der Fehlerabstand, der live gemessen wurde (siehe Kommentar an
       .companion-pills.is-morph-hidden oben für die tatsächliche Ursache).
       Kein !important, keine JS-Änderung nötig: #companion-widget.is-open
       hat als ID+Klasse höhere Spezifität als die reine ID-Basisregel und
       gewinnt automatisch.
       Kein Sprung beim Öffnen/Schließen: .companion-morphing (während der
       gesamten Morph-Animation aktiv, siehe dort) überschreibt gap NICHT
       und erbt weiterhin die Basisregel (1.25rem) -- unschädlich, weil
       .companion-pills schon ab Phase 1 (weit vor .companion-morphing/
       .is-open) auf display:none gesetzt wird (siehe openChat() in
       widget.js) und der Gap damit während der GESAMTEN Morph-Sequenz nie
       etwas zu tun hat -- es gibt nie mehr als einen sichtbaren Flex-Item.
       Für Öffnen wie Schließen ist deshalb kein `transition: gap` nötig. */
    gap: 0;
}

/* --- Größen-Griff (Höhe) ------------------------------------------------
   Kleiner horizontaler Balken oben mittig, mit dem sich die Panel-Höhe per
   Pointer (Maus + Touch, siehe pointerdown/move/up + setPointerCapture in
   widget.js) oder Pfeiltasten anpassen lässt — Breite bleibt IMMER fix,
   nur --companion-user-h ändert sich. Absolut positioniert (wie der Orb),
   kein Teil des Flex-Flusses von .companion-chat — schwebt über dem
   Header, ohne dessen Layout zu beeinflussen. Nur sichtbar/bedienbar im
   Ruhezustand offen (.is-open) — während des Morphs wäre die Höhe
   ohnehin JS-kontrolliert (--companion-h, siehe .companion-morphing oben)
   und ein Drag würde dagegenlaufen. */
#companion-widget .companion-resize-handle {
    position: absolute;
    top: 0.5rem;
    left: 50%;
    transform: translateX(-50%);
    width: 2.5rem;
    height: 0.3125rem;
    border-radius: var(--radius-bubble, 999px);
    background: rgba(255, 255, 255, 0.28);
    border: none;
    padding: 0;
    cursor: ns-resize;
    /* Verhindert, dass ein Touch-Drag auf dem schmalen Griff als
       Seiten-Scroll/-Geste interpretiert wird, bevor das eigene
       pointermove übernimmt. */
    touch-action: none;
    z-index: 3;
    opacity: 0;
    pointer-events: none;
    transition:
        opacity var(--companion-dur-content, 0.15s) var(--companion-ease, cubic-bezier(0.2, 0.7, 0.3, 1)),
        background-color 0.2s var(--companion-ease, cubic-bezier(0.2, 0.7, 0.3, 1));
}

#companion-widget.is-open .companion-resize-handle {
    opacity: 1;
    pointer-events: auto;
}

#companion-widget .companion-resize-handle:hover,
#companion-widget .companion-resize-handle:focus-visible,
#companion-widget .companion-resize-handle.is-dragging {
    background: rgba(255, 255, 255, 0.55);
}

#companion-widget .companion-resize-handle:focus-visible {
    outline: 2px solid var(--color-brand-orange, #fd7a12);
    outline-offset: 2px;
}

/* --- Backdrop --------------------------------------------------------------
   Geschwister-Element von #companion-widget (siehe DOM-Legende, von
   widget.js direkt vor den Container gehängt) — dimmt/blurrt die Seite
   während der Chat offen ist. Trägt jetzt wieder einen Klick-Handler
   (siehe widget.js, backdrop.addEventListener('click', ...)): ein Klick
   außerhalb des Chat-Panels schließt den Chat, außer während der
   Booking-View. z-index knapp UNTER dem Widget (120), aber über der
   restlichen Seite — genau diese Reihenfolge sorgt dafür, dass ein Klick
   auf Orb/Pills/Chat-Panel den Backdrop-Handler gar nicht erst erreicht.
   Werte (Blur, Farbe, Dauer/Easing der Opacity) an den cal.com-PoC
   angeglichen. */
#companion-backdrop {
    position: fixed;
    inset: 0;
    z-index: 110;
    background: rgba(30, 30, 30, 0.6);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--companion-dur-backdrop, 0.32s) cubic-bezier(0, 0, 0.2, 1);
}

/* @supports-Fallback: Browser ohne backdrop-filter zeigen einfach nur die
   dunkle Fläche ohne Weichzeichnung — degradiert sauber statt zu brechen. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
    #companion-backdrop {
        -webkit-backdrop-filter: blur(8px);
        backdrop-filter: blur(8px);
    }
}

#companion-backdrop.is-visible {
    opacity: 1;
    pointer-events: auto;
}

/* --- Seiten-Scroll-Sperre ---------------------------------------------------
   Bewusst KEIN CSS mehr hier: die Sperre (html/body overflow:hidden,
   Scrollbar-Kompensation per body-padding + Widget-transform) läuft seit
   dem Live-Test-Fund komplett über Inline-Styles in widget.js
   (lockPageScroll()/unlockPageScroll()) — eine rein CSS-getragene Sperre
   griff nicht, wenn das CSS selbst fehlte (siehe Dateikopf: Default-CSS war
   früher keine garantierte Lieferung). Inline-Styles kommen direkt vom
   Skript, das auf jeder Einbindungsart läuft, und sind unabhängig davon,
   ob dieses Stylesheet überhaupt geladen wurde. */

/* --- Orb ----------------------------------------------------------------- */

/* Immer absolut positioniert (in BEIDEN Zuständen) statt Flex-Kind: so
   steuert allein top/right, wo der Button sitzt, und er kann unabhängig
   von Balken- <-> Panel-Flex-Richtung von der Balken-Ecke in die
   Header-Ecke wandern. right folgt der Breiten-Phase, top der
   Höhen-Phase — dieselbe Kopplung wie beim Größen-Morph des
   Containers, siehe openChat/closeChat in widget.js.

   GEPRÜFT UND BEWUSST UNVERÄNDERT GELASSEN (Kollegen-Screenshot: X wirkte
   wie in einer eigenen Zeile über Name+Aktionen, PoC-Referenz schlug vor,
   das X analog zu .companion-panel__close dort in den Header-Flex-Fluss
   zu holen): Anders als im PoC (companion-legacy-prototyp.css.disabled im
   jfconcept-local-Repo) ist der Schließen-Button hier NICHT ein separates,
   nur im offenen Panel existierendes Element — es ist DASSELBE Orb-
   Element, das im Balken-Zustand der Chat-Auslöser ist (kein eigener
   Header-DOM zu diesem Zeitpunkt, chatPanel ist dann display:none). Der
   ganze Öffnen/Schließen-Morph hängt an genau dieser Konstanz: is-width-
   target/is-height-target (siehe unten) transitionieren top/right vom
   Balken-Offset zum Panel-Eck-Offset MIT PHASENVERSATZ zur Breiten-/
   Höhen-Animation des Containers (siehe MORPH_WIDTH_OVERLAP_MS/
   setMorphSize()-Choreographie in widget.js). Aus dem absoluten Fluss
   herausgenommen und stattdessen als Flex-Kind von .companion-header
   platziert, würde dieser eigene top/right-Animationspfad komplett
   entfallen — der Orb müsste dann per Layout (nicht mehr per animierten
   Offset) von der Balken- in die Panel-Ecke springen, ein harter Schnitt
   statt der choreografierten Wanderung. Das wäre kein CSS-Detail, sondern
   der Kern des Morphs — deshalb hier NICHT umgebaut. Die gemeldete
   "zweizeilige" Optik wird stattdessen über .companion-header selbst
   adressiert (min-width:0 am Namen, explizites flex-wrap:nowrap,
   kompakteres padding-bottom, siehe dort) — der Orb bleibt exakt an
   seiner bestehenden is-open-Zielposition (top/right:1.875rem), die
   rechnerisch bereits mit der oberen Kante des Headers übereinstimmt
   (beide beziehen sich auf dieselbe Container-Padding-Kante). */
#companion-widget .companion-orb {
    position: absolute;
    top: 0.75rem;
    /* War right:1rem (16px) — asymmetrisch zu top:0.75rem (12px) und damit
       Ursache der Pillenform im geschlossenen Orb-only-Zustand (siehe
       BUGFIX 2 am min-width/-height oben). Jetzt symmetrisch: 12px auf
       allen vier Seiten, Container wird bei 60x60px zum echten Kreis. */
    right: 0.75rem;
    z-index: 2;
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border-radius: 50%;
    /* Grundfläche dunkel wie die Leiste — der Gradient liegt als eigene
       Ebene darüber (::before) und kann dadurch unabhängig atmen. */
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.14);
    color: #fff;
    line-height: 1;
    overflow: hidden;
    cursor: pointer;
    transition:
        transform 0.3s cubic-bezier(0.2, 0.7, 0.3, 1),
        border-color 0.3s cubic-bezier(0.2, 0.7, 0.3, 1),
        box-shadow 0.3s cubic-bezier(0.2, 0.7, 0.3, 1),
        top var(--companion-dur-height, 0.3s) var(--companion-ease, cubic-bezier(0.2, 0.7, 0.3, 1)),
        right var(--companion-dur-width, 0.25s) var(--companion-ease, cubic-bezier(0.2, 0.7, 0.3, 1));
}

/* Zielposition: obere rechte Ecke des Panels, an derselben Stelle, an der
   vorher der eigenständige Schließen-Button im Header saß.

   BUGFIX (Review-Screenshot: Orb ragt während des Morphs unten aus dem
   Container heraus, Höhen-Samples oszillieren sichtbar): top UND right
   NICHT mehr pauschal an .companion-morphing gekoppelt — dieses Klasse
   wird schon bei Phase-2-START (Breite beginnt zu wachsen) gesetzt, lange
   BEVOR die Höhe überhaupt zu wachsen anfängt (Phase 3). Da beide vorher
   in EINER Regel standen, sprang top() sofort beim Klassenwechsel auf
   1.875rem und animierte los, während der Container noch auf Balkenhöhe
   war — der Orb "führte" dem wachsenden Container voraus und ragte
   sichtbar unten heraus.

   Jetzt zwei getrennte Klassen, die widget.js exakt im selben Frame wie
   die jeweilige setMorphSize()-Phase setzt: is-width-target (Start Phase 2,
   right beginnt) und is-height-target (Start Phase 3, top beginnt) — right
   und top starten dadurch garantiert erst, wenn der Container selbst in
   genau dieser Achse zu wachsen beginnt. .is-open (Ruhezustand) setzt
   weiterhin beide zusammen, dort gibt es keinen Uebergang mehr zu
   sequenzieren. */
#companion-widget.is-open .companion-orb {
    top: 1.875rem;
    right: 1.875rem;
}

#companion-widget .companion-orb.is-width-target {
    right: 1.875rem;
}

#companion-widget .companion-orb.is-height-target {
    top: 1.875rem;
}

/* Flächenfarbe des Orb-Icons. War früher ein animierter (companion-
   orb-drift, 9s Loop) radial-gradient über einer separaten ::before-Ebene
   ("wandernder" Verlauf gelb->orange), dann auf Nutzerwunsch ersetzt durch
   eine flache Farbe OHNE Verlauf: #FD9D46. Bewusst NICHT über die geteilte
   --color-brand-orange-Variable (die färbt an vielen anderen Stellen im
   Chat-Panel Buttons/Links/Hover-Zustände, siehe z.B. .companion-pill:hover
   oder .companion-cta-scroll weiter unten — hier ging es dem Nutzer explizit
   NUR um den Orb-Button, eine Variablenänderung hätte all das mitgefärbt).
   ::before-Ebene bleibt bestehen (statt direkt auf .companion-orb selbst zu
   färben), weil sie weiterhin per opacity zwischen Chat-Icon-Zustand (hier,
   voll deckend) und X/Header-Zustand (.is-x::before, opacity:0, siehe
   unten) umschaltet — die Grundfläche darunter (.companion-orb selbst,
   rgba(255,255,255,0.06)) bleibt der neutrale Header-Look.
   NEUE UMKEHRUNG (Kollegen-Feedback, POC-Referenz companion-legacy-
   prototyp.css.disabled im jfconcept-local-Repo): der wandernde Gradient
   kommt als ZUSÄTZLICHE Ebene zurück (siehe .companion-orb::after weiter
   unten) — diese Regel hier bleibt UNVERÄNDERT bestehen (flache Farbe +
   is-x-Crossfade), die Aurora liegt als eigene ::after-Ebene DARÜBER,
   rein additiv (position:absolute, aus dem Fluss, beeinflusst keine
   Orb-Geometrie). Ersetzt die Flächenfarbe also NICHT, ergänzt sie nur um
   den Sheen obendrauf — exakt wie vom Kollegen gefordert ("als
   zusätzliche Ebene, ohne die bestehende is-x-Crossfade-Mechanik zu
   zerstören"). */
#companion-widget .companion-orb::before {
    content: '';
    position: absolute;
    inset: 0;
    background: #fd9d46;
    opacity: 1;
    transition:
        opacity var(--companion-dur-pill, 0.15s) var(--companion-ease, cubic-bezier(0.2, 0.7, 0.3, 1)),
        background-color var(--companion-dur-pill, 0.15s) var(--companion-ease, cubic-bezier(0.2, 0.7, 0.3, 1));
}

/* Header-Zustand (X, is-x von widget.js gesetzt): Flächenfarbe komplett
   ausblenden statt nur zu dämpfen — bleibt der schlichte graue Kreis.
   Zweite Selektorzeile deckt explizit den Hover-Fall ab (is-x macht die
   Spezifität höher als ein generisches :hover::before und damit
   ordnungsunabhängig): die Farbe darf beim Hover über dem offenen
   Header-X NICHT wieder auftauchen. */
#companion-widget .companion-orb.is-x::before,
#companion-widget .companion-orb.is-x:hover::before {
    opacity: 0;
}

/* --- Aurora (wandernder Gradient) -----------------------------------------
   Aus dem cal.com-PoC-Prototyp übernommen (companion-legacy-prototyp.css.
   disabled im jfconcept-local-Repo, dortige .companion-orb__aurora-Regel,
   dort ein eigenes DOM-Element einer React-Komponente) — hier als ::after-
   Pseudo-Element auf .companion-orb selbst portiert statt als neues
   DOM-Element: kein Eingriff in orb.innerHTML (widget.js baut dort nur die
   beiden Icon-Spans auf, siehe .companion-orb__icon), keine zusätzliche
   Wartung eines dritten Kindelements bei jedem start()/teardown()-Zyklus.
   Rein additiv/dekorativ: position:absolute + inset (relativ zu .companion-
   orb, dessen eigene position:absolute/width/height/border-radius/
   overflow:hidden davon UNBERÜHRT bleiben) — beeinflusst keine Breite,
   Höhe oder Position und damit auch NICHT die vom Öffnen/Schließen-Morph
   gemessene/gesetzte Geometrie (setMorphSize()/is-width-target/
   is-height-target in widget.js/weiter oben lesen ausschließlich
   container-/panel-Maße, nie irgendetwas vom Orb selbst — siehe dortige
   Kommentare). Liegt in der Stapelreihenfolge ÜBER ::before (spätere
   Pseudo-Element-Regel im Quelltext) und UNTER den Icon-Kindelementen
   (die sich per position:relative + Grid-Placement explizit darüber
   heben, siehe .companion-orb__icon-Kommentar unten).
   --color-brand-yellow gibt es in dieser Datei nicht (siehe Datei-
   weite Variablen-Situation, nur --color-brand-orange hat einen
   Fallback) — deshalb hier der literale Wert #fdbd19 statt eines
   fallback-losen var(--color-brand-yellow), analog zum Umgang mit
   --color-brand-orange sonst überall in dieser Datei. */
/* KORREKTUR (Kollege): der 1:1 aus dem PoC übernommene
   `transform: scale(...)` im Hover unten wurde von der laufenden
   companion-orb-drift-Keyframe-Animation ständig überschrieben, weil
   BEIDE dieselbe Kaskaden-Eigenschaft (transform) ansteuern -- eine
   Animation gewinnt gegen jede statische Deklaration/Transition auf
   derselben Eigenschaft, jeden einzelnen Frame neu. Der PoC hatte diesen
   Bug bereits (dort nie aufgefallen, weil dort niemand genau hingesehen
   hat) -- ihn mitzukopieren, macht ihn nicht richtig. Fix: die
   individuellen Transform-Properties (scale/rotate/translate, seit
   Baseline 2021/2022 breit unterstützt -- Chrome 104+, Firefox 72+,
   Safari 14.1+) laufen auf einer EIGENEN Kaskaden-Schiene, unabhängig von
   `transform`. Die Keyframe-Animation animiert weiterhin NUR
   transform:translate() (siehe @keyframes unten, jetzt ohne die scale()-
   Anteile), `scale` bleibt dadurch komplett frei für Hover/Transition.
   Der frühere "Atem"-Effekt aus den Keyframes (1 -> 1.12 -> 0.96, Teil
   der Wander-Bewegung) entfällt dabei bewusst, statt ihn in eine zweite,
   parallele scale-Keyframe-Animation zu verschieben -- die hätte exakt
   denselben Konflikt nur auf die scale-Eigenschaft verlagert (dieselbe
   Regel: eine laufende Animation auf einer Eigenschaft blockt jede
   statische/transitionierte Deklaration auf derselben Eigenschaft). Die
   Wander-Bewegung (translate) bleibt vollständig erhalten, nur das
   ständige Mitpulsieren der Größe während des Driftens fällt weg --
   exakt der "Lazy-Fix", den der Kollege vorgeschlagen hat.
   NACHTRAG (Kontrast-Fix, siehe Kommentar an der :hover::after-Regel
   unten): der urspruenglich hier geplante Hover-Scale-Boost (1 -> 1.15)
   ist mittlerweile wieder entfernt -- er vergroesserte die helle
   Gradient-Flaeche der Aurora genau ueber dem Icon und war Mitursache
   eines gemeldeten Kontrastverlusts. Die scale-Property-Architektur
   bleibt trotzdem wie hier beschrieben bestehen (transition:scale unten
   ist damit aktuell ein No-Op, kostet nichts und haelt die Option offen,
   falls ein spaeterer, kontrastsicherer Hover-Scale gewuenscht wird) --
   der eigentliche Grund fuer die Trennung von `transform` (Kommentar
   oben) bleibt unveraendert gueltig. */
#companion-widget .companion-orb::after {
    content: '';
    position: absolute;
    inset: -40%;
    background: radial-gradient(
        circle at 35% 35%,
        #fdbd19 0%,
        var(--color-brand-orange, #fd7a12) 45%,
        transparent 70%
    );
    opacity: 0.5;
    scale: 1;
    animation: companion-orb-drift 9s ease-in-out infinite;
    transition:
        opacity 0.3s cubic-bezier(0.2, 0.7, 0.3, 1),
        scale 0.3s cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* KORREKTUR (Nutzer-Feedback: Sprechblasen-Icon im Hover schlecht zu
   erkennen; Ruhezustand ist laut Nutzer-Screenshot ausdrücklich RICHTIG
   und bleibt unangetastet -- siehe .companion-orb::before/::after
   Basisregeln oben, unveraendert).
   Verifiziert (nicht geraten): das Icon selbst faerbt sich per
   stroke="currentColor" (iconMarkup() in widget.js) aus color:#fff auf
   .companion-orb -- bereits volles, deckendes Weiss, weder im
   Ruhezustand noch im Hover per rgba/Alpha abgeschwaecht (keine
   :hover-Regel aendert color/stroke). Der Kontrastverlust kommt
   nachweislich NICHT vom Icon, sondern vom Untergrund:
   #companion-widget .companion-orb:hover::before (siehe oben) DUNKELT
   sogar ab (#fd9d46 -> #e48d3f) -- das verbessert den Kontrast gegen
   Weiss sogar, ist also nicht die Ursache. Die Aurora-Ebene (::after,
   diese Regel) dagegen wurde im Hover SOWOHL heller (opacity 0.5 -> 0.85)
   ALS AUCH groesser (scale 1 -> 1.15) -- beides zusammen schiebt genau
   die hellste Stelle des radial-gradient (#fdbd19, gelb, heller als
   sowohl die Ruhezustands- als auch die Hover-Flaechenfarbe) ueber eine
   groessere Flaeche des 36px-Orbs, direkt unter das Icon. Nachgerechnet
   (WCAG-relative-Luminanz, Blend von ::before + ::after mit jeweiliger
   Opacity an der hellsten Gradient-Stelle): Ruhezustand blendet auf ca.
   Kontrast 1.87:1 gegen Weiss, der bisherige Hover-Boost (0.85/1.15) auf
   ca. 1.79:1 -- SCHLECHTER als der vom Nutzer als richtig bestaetigte
   Ruhezustand, und genau das war die gemeldete Schwaeche. Die groessere
   Flaeche (scale 1.15) verschaerft das zusaetzlich raeumlich: der helle
   Kern deckt im Hover mehr von der Icon-Flaeche ab als vorher.
   Fix an der tatsaechlich gefundenen Ursache (nicht am Icon): Hover-
   Verstaerkung der Aurora zurueckgenommen statt das Icon aufzuhellen --
   opacity nur noch leicht ueber den Ruhewert (0.5 -> 0.6, behaelt ein
   spuerbares, aber gedaempftes Hover-Feedback) und KEIN scale-Wachstum
   mehr (bleibt bei 1, verhindert die raeumliche Ausdehnung der hellen
   Zone ueber das Icon). Nachgerechnet mit denselben Werten: Kontrast
   steigt auf ca. 1.99:1 -- leicht BESSER als der Ruhezustand (1.87:1),
   erfuellt damit "kraeftiger weiss als jetzt, nicht schlechter als im
   Ruhezustand". Das Drift-Keyframe (companion-orb-drift, siehe unten)
   laeuft unveraendert weiter -- die Aurora bleibt spuerbar "lebendig" im
   Hover, nur ohne die kontrastschaedliche Groessen-/Helligkeits-
   Verdopplung. */
#companion-widget .companion-orb:hover::after {
    opacity: 0.6;
}

/* Präzisierung Kollegen-Feedback: die Aurora gilt NUR für den
   Balken-Zustand (Orb = primärer Chat-Auslöser). Im Schließen-Zustand
   (is-x) soll der Orb "ruhig und zurückhaltend" bleiben, kein wandernder
   Orange-Gradient unter dem X — gleiche Opacity-0-Behandlung wie die
   Flächenfarbe (::before) direkt oberhalb, aus demselben Grund (Zweite
   Selektorzeile deckt den Hover-Fall explizit ab, Spezifität schlägt ein
   generisches :hover::after). */
#companion-widget .companion-orb.is-x::after,
#companion-widget .companion-orb.is-x:hover::after {
    opacity: 0;
}

/* Enger als beim großen Modal-Aurora-Vorbild (dort 40% Amplitude) — bei
   36px Orb-Durchmesser würde dieselbe Amplitude den Gradienten zeitweise
   ganz aus der sichtbaren Fläche schieben. Amplitude/Timing 1:1 aus dem
   PoC übernommen (dort exakt dieselbe Einschränkung, dieselben Werte) --
   NUR die scale()-Anteile pro Step sind raus (siehe Kommentar an
   .companion-orb::after oben, Grund: Kaskaden-Konflikt mit dem
   Hover-scale). Reine Wander-Bewegung per transform:translate(), keine
   Größenänderung mehr während des Driftens. */
@keyframes companion-orb-drift {
    0%,
    100% {
        transform: translate(0%, 0%);
    }
    33% {
        transform: translate(14%, 8%);
    }
    66% {
        transform: translate(-8%, 12%);
    }
}

/* Hover-/Fokus-Feedback der Flächenfarbe: flache, ca. 10% dunklere Variante
   von #fd9d46 (kein filter:brightness() — das würde auch das ::icon in
   derselben Grid-Zelle mit abdunkeln, siehe .companion-orb__icon oben).
   Bewusst NICHT über --color-brand-orange (siehe Kommentar am
   ::before-Basisregel oben). Gilt nur, solange die Fläche sichtbar ist
   (opacity:1) — im is-x-Zustand überschreibt die höher-spezifische
   .is-x:hover::before-Regel weiter oben mit opacity:0. focus-visible
   deckt Tastaturbedienung ab, gleiche Farbe wie Hover für Konsistenz. */
#companion-widget .companion-orb:hover::before,
#companion-widget .companion-orb:focus-visible::before {
    background-color: #e48d3f;
}

/* Zwei Lucide-Icons (message-circle + x) aus widget.js liegen übereinander
   in derselben Grid-Zelle (Orb ist display:grid;place-items:center) und
   crossfaden per Opacity — die X-Variante blendet ein, sobald der Orb die
   Klasse is-x trägt (widget.js setzt sie synchron mit dem Pills-Fade beim
   Oeffnen). `position: relative` + explizite Grid-Platzierung heben beide
   über die Aurora-Ebene UND übereinander, statt dass grid-auto-flow sie
   in zwei Zeilen auseinanderzieht. */
#companion-widget .companion-orb__icon {
    position: relative;
    grid-area: 1 / 1;
    display: block;
    width: 1rem;
    height: 1rem;
    opacity: 0;
    transition: opacity var(--companion-dur-pill, 0.15s) var(--companion-ease, cubic-bezier(0.2, 0.7, 0.3, 1));
}

#companion-widget .companion-orb__icon--chat {
    opacity: 1;
}

#companion-widget .companion-orb.is-x .companion-orb__icon--chat {
    opacity: 0;
}

#companion-widget .companion-orb.is-x .companion-orb__icon--close {
    opacity: 1;
}

#companion-widget .companion-orb:hover {
    transform: translateY(-1px) scale(1.06);
}

/* Glow (Border-Tint + weicher box-shadow-Schein) nur im Balken-Zustand,
   solange der Orb der primäre Chat-Auslöser ist. Kollegen-Feedback/
   Präzisierung: sobald der Orb zum Schließen-X geworden ist (Klasse is-x,
   siehe setOrbOpenState() in widget.js -- synchron mit dem Öffnen/
   Schließen-Morph gesetzt, deckt sich 1:1 mit "Panel offen"), ist das ein
   Low-Priority-Button und soll KEINEN Glow mehr zeigen. :not(.is-x)
   grenzt diese Regel auf den echten Auslöser-Zustand ein, die eigene
   .is-x:hover-Regel direkt darunter greift mit höherer Selektor-
   Spezifität (zwei Klassen) für den Schließen-Fall. */
#companion-widget .companion-orb:hover:not(.is-x) {
    border-color: rgba(253, 189, 25, 0.5);
    box-shadow: 0 0 18px rgba(253, 122, 18, 0.45);
}

/* Schließen-Zustand (X, is-x): kein Glow, stattdessen ein heller
   Hintergrund -- Kollegen-Feedback "Low-Priority-Button, kein Glow,
   weißer Hintergrund". rgba(255,255,255,0.22) statt reinem #fff: deutlich
   heller als sowohl die neutrale Grundfläche (0.06) als auch der reguläre
   Header-Action-Hover (0.14, siehe .companion-header__action:hover) --
   der Unterschied bleibt klar sichtbar, ohne den harten Bruch eines
   vollflächigen weißen Kreises auf dem dunklen Glas-Panel. Trifft die
   Fläche von .companion-orb selbst (nicht ::before/::after): beide
   Pseudo-Ebenen (Flächenfarbe UND Aurora, siehe dort) sind im
   is-x-Zustand ohnehin schon auf opacity:0 geschaltet. */
#companion-widget .companion-orb.is-x:hover {
    border-color: rgba(255, 255, 255, 0.32);
    background: rgba(255, 255, 255, 0.22);
    box-shadow: none;
}

/* --- Pills --------------------------------------------------------------- */

#companion-widget .companion-pills {
    align-items: center;
    justify-content: flex-end;
    gap: var(--gap-s, 1.25rem);
    min-width: 0;
    max-width: 60rem;
    /* Der Orb ist jetzt absolut positioniert (siehe oben) statt Flex-Nachbar
       — ohne diese Reserve würde die Leiste bis unter den Button laufen,
       der früher per Flex-Gap Platz bekam. */
    margin-right: calc(2.25rem + var(--gap-s, 1.25rem));
    opacity: 1;
    transform: translateX(0);
    transition:
        opacity 0.45s cubic-bezier(0.2, 0.7, 0.3, 1),
        transform 0.45s cubic-bezier(0.2, 0.7, 0.3, 1),
        max-width 0.45s cubic-bezier(0.2, 0.7, 0.3, 1),
        margin-left 0.45s cubic-bezier(0.2, 0.7, 0.3, 1),
        margin-right 0.45s cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* Eigene, schnellere Ausblend-Transition für Phase 1 des Oeffnen/Schließen-
   Morphs (openChat/closeChat in widget.js) — unabhängig vom 0.45s
   Zonenwechsel-Kollaps oben (data-pills-hidden). Die Selektor-Spezifität
   (zwei Klassen statt einer) gewinnt automatisch gegen die Basis-Regel,
   kein !important nötig. */
#companion-widget .companion-pills.is-morph-hidden {
    transition: opacity var(--companion-dur-pill, 0.15s) var(--companion-ease, cubic-bezier(0.2, 0.7, 0.3, 1));
    opacity: 0;
    pointer-events: none;
}

/* KORRIGIERT (Live-Messung im Browser, siehe Kollegen-Report): eine vorher
   an dieser Stelle stehende Regel ("ABSICHERUNG ... Sicherheitsnetz
   sinnvoll", #companion-widget.is-open .companion-pills { display: none })
   war NACHWEISLICH wirkungslos und ist deshalb ENTFERNT, nicht nur
   umformuliert -- eine Klassenregel kann ein per JS gesetztes pillsPanel.
   style.display NIE überstimmen (Inline-Style gewinnt in der Kaskade immer
   gegen jeden Klassenselektor, unabhängig von dessen Spezifität), "greift
   nur dort, wo sonst nichts vorgegeben wäre" beschrieb also gar keinen
   echten Sicherheitsnetz-Fall. Die live gemessene Ursache der
   "zweizeiligen" Kopfzeile war eine ANDERE als die vorher rekonstruierte:
   nicht die Reihenfolge von .is-open vs. pillsPanel.style.display in
   openChat()/closeChat() selbst (die war bereits korrekt), sondern ein
   UNGUARDED Initial-Reveal-Timer in start() (widget.js, "pillsDelayTimer =
   window.setTimeout(...) { pillsPanel.style.display = 'flex'; }, 1500)")
   -- er feuerte unabhängig vom Chat-Zustand und überschrieb ein bereits
   korrekt auf 'none' gesetztes pillsPanel zurück auf 'flex', wenn der Chat
   innerhalb der ersten 1,5s nach Seitenaufbau geöffnet wurde (z.B. beim
   Testen: Seite laden, sofort auf den Orb klicken). Jetzt in widget.js mit
   demselben !state.open-Guard wie der direkt benachbarte
   pillResizeHandler behoben -- die eigentliche, ursächliche Reparatur,
   kein CSS-Workaround. Der verbleibende, tatsächlich wirksame Rest der
   Absicherung ist der gap:0 in der #companion-widget.is-open-Regel weiter
   oben (padding/width/height/...), siehe dortigen Kommentar. */

/* Keine Zone unter der Viewport-Mitte: Die Fragen ziehen sich nach rechts
   unter den Orb zurück — dorthin, wo der Einstieg bleibt. widget.js setzt
   das Attribut, die Dauer muss zu PILLS_COLLAPSE_MS dort passen. */
#companion-widget[data-pills-hidden='true'] .companion-pills {
    max-width: 0;
    /* Der Abstand zum Orb muss mit weg, sonst bliebe eine Lücke stehen.
       margin-LEFT, nicht -right wie im POC: die Leiste läuft row-reverse,
       der Orb steht dadurch rechts vom Panel. */
    margin-left: calc(-1 * var(--gap-s, 1.25rem));
    /* BUGFIX (Live-Debug, Messbefund: geschlossener Balken 68x60px statt
       60x60px, TROTZ max-width:0 und opacity:0): die Basisregel oben setzt
       margin-right auf die Orb-Reserve (2.25rem+gap-s, ~56px) — diese Regel
       hier hat bislang NUR margin-left zurückgesetzt. Ein Flex-Item trägt
       seine Margins auch bei width:0 weiter zur intrinsischen Breite des
       Elternelements (width:max-content) bei, selbst wenn es unsichtbar
       ist (opacity:0) — exakt dieselbe Bug-Klasse wie zuvor beim Backdrop
       (unsichtbar per opacity, aber weiterhin layoutwirksam). Ohne dieses
       margin-right:0 blieb der geschlossene Orb-Balken auch nach der
       min-width/orb-Symmetrie-Korrektur oben (siehe .companion-orb,
       min-width) 8px zu breit für einen echten Kreis. */
    margin-right: 0;
    opacity: 0;
    transform: translateX(1.5rem);
    pointer-events: none;
}

#companion-widget .companion-pill {
    /* Schrumpfen erlaubt (0 1 auto statt 0 0 auto) und min-width: 0, sonst
       greift text-overflow nie: Ein nicht-schrumpfbares Flex-Item behält
       seine Inhaltsbreite und läuft stattdessen links aus der Leiste
       heraus — bei row-reverse fällt dabei sogar das Padding weg, die
       erste Pill wird hart abgeschnitten. Der volle Text bleibt über das
       title-Attribut aus widget.js erreichbar. */
    flex: 0 1 auto;
    min-width: 0;
    border: 1px solid rgba(255, 255, 255, 0.28);
    border-radius: var(--radius-bubble, 999px);
    background: transparent;
    /* Vertikal von 0.625rem auf 0.5rem gekürzt (Review: Balken wirkte höher
       als sein Inhalt braucht). Rechnung: Pill-Innenhöhe = 2*Padding (0.5rem
       = 8px je Seite) + 1px Border je Seite + line-height 1.2*0.9375rem
       (18px) = 36px; Balken-Padding (#companion-widget, 0.75rem = 12px je
       Seite) kommt oben drauf: 36px + 24px = 60px — deckt sich EXAKT mit
       dessen min-height (3.75rem, siehe dort, an die Orb-Größe gebunden).
       Die Balkenhöhe wird dadurch nicht mehr von der Pill-Innenpolsterung
       über den Orb-bedingten Boden hinaus getrieben (vorher effektiv 64px
       bei unveränderter min-height) — kein zusätzliches max-height/
       fit-content nötig, da die Basisregel oben ohnehin height:auto lässt
       und nur diesen (jetzt engeren) Boden über min-height erzwingt. Kein
       Layout-Sprung beim Ein-/Ausblenden: die Pills togglen über
       display:none/flex bzw. max-width (data-pills-hidden), nie über eine
       Höhenanimation. */
    padding: 0.5rem 1rem;
    font: inherit;
    font-size: 0.9375rem;
    font-weight: 500;
    line-height: 1.2;
    color: rgba(255, 255, 255, 0.92);
    white-space: nowrap;
    /* Lange Fragen kürzen statt die Leiste sprengen. */
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
    transition:
        background-color 0.25s cubic-bezier(0.2, 0.7, 0.3, 1),
        border-color 0.25s cubic-bezier(0.2, 0.7, 0.3, 1),
        transform 0.25s cubic-bezier(0.2, 0.7, 0.3, 1);
}

#companion-widget .companion-pill:hover {
    background: var(--color-brand-orange, #fd7a12);
    border-color: var(--color-brand-orange, #fd7a12);
    transform: translateY(-2px);
}

/* Zonenwechsel: alte Pills nach unten weg, neue von oben herein. Die
   animation-delay der einzelnen Pill setzt widget.js inline (Stagger).
   Bewusst großzügiger als die Mikro-Hover der Seite — der Wechsel IST
   das Feature, er darf Zeit brauchen.

   Dauern müssen zu PILL_LEAVE_MS in widget.js passen: läuft die
   CSS-Animation länger als der Timer dort, springt der Nachbau in eine
   noch laufende Abgangs-Animation. */
#companion-widget .companion-pill[data-state='entering'] {
    animation: companion-pill-in 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}

#companion-widget .companion-pill[data-state='leaving'] {
    animation: companion-pill-out 0.34s cubic-bezier(0.4, 0, 1, 1) forwards;
    pointer-events: none;
}

@keyframes companion-pill-in {
    from {
        opacity: 0;
        transform: translateY(-14px) scale(0.94);
    }
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

@keyframes companion-pill-out {
    from {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
    to {
        opacity: 0;
        transform: translateY(12px) scale(0.96);
    }
}

/* Richtungs-Slide beim Zonenwechsel (siehe zoneTransitionDirection() +
   renderPills() in widget.js) — löst companion-pill-in/-out oben NICHT ab,
   sondern ergänzt sie: 'entering'/'leaving' bleiben der Rückfall für den
   ersten Aufbau und den Resize-Nachbau (keine bekannte Richtung, siehe
   dort), diese vier Zustände greifen NUR bei einem echten Wechsel zwischen
   zwei Zonen mit bekannter Reihenfolge. 'forward' (Ziel-Zone liegt im
   Bundle SPÄTER): neue Pills fahren von RECHTS ein, alte nach LINKS raus —
   spiegelt die Bundle-Reihenfolge, in der man "weiter nach unten/vorne"
   scrollt. 'backward' (frühere Zone) exakt umgekehrt. Gleiche Dauern wie
   companion-pill-in/-out oben (müssen zu PILL_LEAVE_MS in widget.js
   passen, siehe dortiger Kommentar) — nur Achse und Vorzeichen des
   translateX wechseln, keine neue Zeitkonstante. */
#companion-widget .companion-pill[data-state='entering-forward'] {
    animation: companion-pill-in-forward 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}

#companion-widget .companion-pill[data-state='leaving-forward'] {
    animation: companion-pill-out-forward 0.34s cubic-bezier(0.4, 0, 1, 1) forwards;
    pointer-events: none;
}

#companion-widget .companion-pill[data-state='entering-backward'] {
    animation: companion-pill-in-backward 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}

#companion-widget .companion-pill[data-state='leaving-backward'] {
    animation: companion-pill-out-backward 0.34s cubic-bezier(0.4, 0, 1, 1) forwards;
    pointer-events: none;
}

@keyframes companion-pill-in-forward {
    from {
        opacity: 0;
        transform: translateX(18px) scale(0.94);
    }
    to {
        opacity: 1;
        transform: translateX(0) scale(1);
    }
}

@keyframes companion-pill-out-forward {
    from {
        opacity: 1;
        transform: translateX(0) scale(1);
    }
    to {
        opacity: 0;
        transform: translateX(-18px) scale(0.96);
    }
}

@keyframes companion-pill-in-backward {
    from {
        opacity: 0;
        transform: translateX(-18px) scale(0.94);
    }
    to {
        opacity: 1;
        transform: translateX(0) scale(1);
    }
}

@keyframes companion-pill-out-backward {
    from {
        opacity: 1;
        transform: translateX(0) scale(1);
    }
    to {
        opacity: 0;
        transform: translateX(18px) scale(0.96);
    }
}

/* Pills-Rückkehr nach dem Schließen-Morph (siehe closeChat in widget.js,
   ruft paintPills('returning') auf). Eigener Keyframe statt Wiederverwendung
   von companion-pill-in: der Zonenwechsel fährt vertikal von oben ein,
   die Rückkehr aus dem geschlossenen Chat soll gestaffelt von LINKS
   kommen — zwei unterschiedliche Bewegungsrichtungen für zwei
   unterschiedliche Auslöser. animation-delay setzt widget.js inline
   (gleicher PILL_STAGGER_MS wie beim Zonenwechsel). */
#companion-widget .companion-pill[data-state='returning'] {
    animation: companion-pill-return 0.4s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}

@keyframes companion-pill-return {
    from {
        opacity: 0;
        transform: translateX(-18px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

/* --- Chat-Panel ----------------------------------------------------------
   Kein eigenständiges schwebendes Panel mehr: #companion-widget selbst
   trägt jetzt Größe, Radius, Hintergrund und Blur (siehe .is-open /
   .companion-morphing weiter oben) — der Chat ist nur noch deren Inhalt
   und füllt den verbleibenden Platz. widget.js setzt display:flex als
   Inline-Style beim Oeffnen, display:none beim Schließen; die Opacity-
   Transition hier ist Phase 4 (Content-Fade) des Morphs. */

#companion-widget .companion-chat {
    /* Containing Block für companion-booking-dim/companion-booking-view
       (beide position:absolute, siehe deren Regeln weiter unten) -- ohne
       dieses position:relative würden sie sich gegen den nächsten
       positionierten Vorfahren ausrichten (#companion-widget selbst),
       dessen Padding mit einrechnen und dadurch zu weit nach außen
       ragen. */
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    /* Spalte bleibt hier explizit gesetzt (nicht nur auf companion-chat-body
       darunter): companion-chat-body ist das einzige normal-fliessende Kind
       (companion-booking-dim/companion-booking-view sind position:absolute,
       nehmen also gar nicht am Flex-Layout teil) -- ohne flex-direction
       würde es implizit auf den row-Default zurückfallen, was sich zwar bei
       nur einem Flow-Kind meist unauffällig verhält, aber unnötig fragil
       wäre. */
    flex-direction: column;
    text-align: left;
    opacity: 0;
    transition: opacity var(--companion-dur-content, 0.15s) var(--companion-ease, cubic-bezier(0.2, 0.7, 0.3, 1));
}

/* companion-chat-body buendelt Kopf/Verlauf/Vorschläge/Formular/KI-Hinweis
   zu EINEM Block (siehe chatBody in widget.js) -- traegt jetzt die Spalten-
   Anordnung, die vorher direkt auf .companion-chat lag (deren Kinder sind
   seit dem Buchungs-Embed-Overlay-Umbau companion-chat-body + companion-
   booking-dim + companion-booking-view statt Kopf/Verlauf/... direkt). */
#companion-widget .companion-chat-body {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    /* Spalte: Kopf und Eingabe stehen fest, nur der Verlauf dazwischen
       scrollt. */
    flex-direction: column;
    gap: var(--gap-s, 1.25rem);
    /* Bugfix (Nutzer-Report): waehrend das Cal-Termin-Embed offen ist, soll
       der Chat dahinter sichtbar-aber-zurückgetreten wirken statt komplett
       zu verschwinden -- filter:blur() auf GENAU diesem einen Container
       (nicht auf einzelne Kind-Elemente, siehe Performance-Hinweis) + eine
       reduzierte Deckkraft. pointer-events:none macht ihn zusätzlich inert,
       damit ein Klick durch den blur hindurch nicht versehentlich eine
       Chat-Aktion darunter auslöst. Moderater Blur-Radius (6px) statt der
       teureren 20px vom Backdrop (dort ein einzelnes, nicht animierendes
       Element; hier potenziell mit großem, textreichem Verlauf dahinter). */
    transition:
        filter var(--companion-dur-backdrop, 0.32s) cubic-bezier(0, 0, 0.2, 1),
        opacity var(--companion-dur-backdrop, 0.32s) cubic-bezier(0, 0, 0.2, 1);
}

#companion-widget .companion-chat-body--dimmed {
    filter: blur(6px);
    opacity: 0.4;
    pointer-events: none;
}

/* Dimm-Layer über companion-chat-body, wenn das Buchungs-Embed offen ist --
   gleicher Farbton wie #companion-backdrop (siehe dort) für ein
   konsistentes Modal-Gefühl, aber OHNE eigenen backdrop-filter: der blur
   liegt schon auf companion-chat-body selbst (siehe oben), ein zweiter
   backdrop-filter hier wäre nur zusätzliche, redundante Kompositierungs-
   Last für denselben visuellen Effekt. */
#companion-widget .companion-booking-dim {
    position: absolute;
    inset: 0;
    z-index: 2;
    border-radius: var(--radius-element, 24px);
    background: rgba(30, 30, 30, 0.6);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--companion-dur-backdrop, 0.32s) cubic-bezier(0, 0, 0.2, 1);
}

#companion-widget .companion-booking-dim.is-visible {
    opacity: 1;
}

/* --- Kopfzeile ------------------------------------------------------------
   Name + Kontakt-Aktionen. Das Schließen übernimmt jetzt der Orb-Button
   (siehe oben) — er sitzt absolut in genau dieser Ecke, sobald der Chat
   offen ist, ein eigener Schließen-Button im Header entfällt dadurch.
   padding-right reserviert seinen Platz, damit Name/Icons nicht darunter
   laufen. */

#companion-widget .companion-header {
    display: flex;
    flex: 0 0 auto;
    /* Explizit statt nur auf den flex-Default verlassen (Kollegen-
       Feedback/Screenshot: X-Icon wirkte wie in einer eigenen Zeile über
       Name+Aktionen -- siehe ausführlicher Kommentar am Orb weiter unten,
       .companion-orb ~Zeile 504, WARUM das X trotzdem NICHT hierher in
       den Flex-Fluss geholt wurde). Self-contained-Anspruch dieser Datei
       (siehe Dateikopf): eine fremde Host-Seite mit eigenem globalen
       flex-wrap-Reset soll diese Zeile nicht umbrechen können. */
    flex-wrap: nowrap;
    align-items: center;
    gap: 0.75rem;
    /* NACHGEMESSEN (Kollegen-Rückfrage): der Orb-Offset im geschlossenen
       Balken (.companion-orb Basisregel, top:0.75rem) ist NICHT der Wert,
       der greift, während der Header sichtbar ist -- #companion-widget.
       is-open .companion-orb (top/right:1.875rem, 1 ID + 2 Klassen) hat
       höhere Spezifität als die Basisregel (1 ID + 1 Klasse) und gewinnt
       jedes Mal, wenn .is-open gesetzt ist. Und .is-open ist IMMER gesetzt,
       sobald chatPanel/Header sichtbar sind: beide Öffnen-Pfade in
       openChat() setzen container.classList.add('is-open') und
       chatPanel.style.opacity='1' im selben Codeblock (reduced-motion-Pfad
       sowie Phase-4-Ende des animierten Pfads) -- es gibt keinen sichtbaren
       Frame mit Header, aber ohne .is-open.
       Nachgerechnet mit den tatsächlichen Werten: top/right eines absolut
       positionierten Kindes sind laut Spec relativ zur PADDING-Box des
       Containing Block (hier: #companion-widget) verankert. Der Orb-Offset
       (1.875rem) ist damit exakt so groß wie das eigene Padding von
       #companion-widget.is-open (padding: var(--gap-m, 1.875rem), siehe
       dort) -- die Orb-Oberkante fällt folglich GENAU auf die Content-Box-
       Oberkante des Containers. .companion-chat / .companion-chat-body
       haben BEIDE kein eigenes Padding (nur gap), .companion-header ist
       deren erstes Flex-Kind -- die Header-Oberkante liegt damit ebenfalls
       exakt auf dieser Content-Box-Oberkante.
       KORREKTUR/ERGÄNZUNG (zweite Kollegen-Rückfrage, DANACH per Live-
       Messung im Browser bestätigt): diese Rechnung stimmt nur, wenn
       .companion-chat selbst OHNE vorausgehendes Flow-Geschwister direkt
       an der Container-Oberkante beginnt. DOM-Reihenfolge in widget.js ist
       aber orb (absolut, zählt nicht) -> pillsPanel -> chatPanel --
       pillsPanel ist ein FLEX-GESCHWISTER von chatPanel auf
       Container-Ebene (Spaltenlayout in .is-open), nicht dessen Kind. Die
       Live-Messung zeigte genau diesen Fall tatsächlich eingetreten:
       companion-chat lag bei top:50 statt top:30, exakt Pillenhöhe(0) +
       gap(20px) darüber -- pillsPanel stand mit display:flex im DOM,
       obwohl openChat() sie eigentlich auf 'none' setzt. Ursache war ein
       unguarded 1500ms-Initial-Reveal-Timer in start() (siehe Kommentar an
       .companion-pills.is-morph-hidden weiter oben), NICHT eine falsche
       Reihenfolge in openChat()/closeChat() selbst -- die war die ganze
       Zeit korrekt. Jetzt doppelt behoben: der Timer ist in widget.js mit
       demselben !state.open-Guard wie pillResizeHandler versehen (die
       eigentliche, ursächliche Reparatur), UND #companion-widget.is-open
       setzt zusätzlich gap:0 (siehe dortiger Kommentar) -- selbst wenn
       .companion-pills durch einen künftigen Bug nochmal sichtbar würde,
       hätte der Gap dann nichts mehr zu verschieben. Die Rechnung oben
       (Orb-Oberkante = Header-Oberkante) gilt damit zuverlässig.
       Rechnerisches Ergebnis: Orb-Oberkante und Header-Oberkante fallen
       zusammen (0px Differenz, beide bei Offset 30 relativ zur
       Container-Padding-Box -- exakt der von der Live-Messung gemeldete
       orbTopCss-Wert 30px, jetzt auch für companion-chat statt der zuvor
       gemessenen 50px).
       Einzige verbleibende Differenz: die vertikale MITTE. Höchstes
       Header-Kind ist .companion-header__action mit height:2rem (32px),
       align-items:center zentriert die Zeile also auf 32px Höhe -> Mitte
       bei 16px relativ zum Header-Offset. Der Orb ist 2.25rem (36px) hoch
       -> Mitte bei 18px relativ zu SEINEM (identischen) Offset. Delta:
       2px, Header-Mitte liegt 2px höher als Orb-Mitte -- rechnerisch KEIN
       Zweizeilen-Bug, sondern eine kaum wahrnehmbare 2px-Differenz. Um
       auch diese letzten 2px zu schliessen (statt es bei "praktisch
       unsichtbar" zu belassen): padding-top schiebt die Header-Zeile genau
       um das Delta nach unten, Mitte dann exakt bei 18px = Orb-Mitte.
       Gegengerechnet mit den absoluten Live-Messwerten: Header-Offset 30
       (Container-Padding-Box-Kante, siehe oben) + padding-top 2px + halbe
       Icon-Zeile 16px = 48px absolute Icon-Mittellinie. Orb-Mitte absolut:
       orbTopCss 30px + halbe Orb-Höhe 18px = 48px. Beide treffen sich
       exakt bei 48px. */
    padding-top: 0.125rem;
    /* War var(--gap-s, 1.25rem) -- Kollegen-Feedback: Kopfzeile insgesamt
       zu hoch. resizeClampMin() in widget.js liest die tatsächliche
       Header-Höhe LIVE per getBoundingClientRect() (siehe dort) und die
       Öffnen/Schließen-Morph-Zielhöhe kommt ebenfalls aus einer LIVE
       getBoundingClientRect()-Messung (measureOpenRect() in widget.js) --
       eine kleinere Kopfzeile wird von beiden automatisch mitgenommen,
       keine hartkodierte Höhe hängt an diesem Wert. */
    padding-bottom: 0.75rem;
    padding-right: 2.75rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}

#companion-widget .companion-header__name {
    /* Ohne min-width:0 schrumpft ein Flex-Item nie unter seine
       Inhaltsbreite (Flexbox-Default min-width:auto) -- bei einem langen
       "Name — Rolle" (siehe setHeaderContact() in widget.js) hätte das
       den Namen über die eigentlich vorgesehene Ellipse (siehe
       text-overflow unten) hinaus breitgedrückt, auf Kosten von
       .companion-header__actions daneben. Aus dem PoC übernommen
       (companion-legacy-prototyp.css.disabled im jfconcept-local-Repo,
       dortige .companion-header__name-Regel, exakt derselbe Grund). */
    min-width: 0;
    font-size: 1rem;
    font-weight: 600;
    color: #fff;
    /* "Name — Rolle" kann länger als der Standardname sein — nicht in eine
       zweite Zeile umbrechen (hätte keinen Platz), stattdessen kürzen. */
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* Foto/Initialen eines Ansprechpartners im Header (siehe setHeaderContact()
   in widget.js) — display:none im Ruhezustand, wird per JS eingeblendet,
   sobald eine Antwort einen Kontakt setzt. flex-shrink:0, damit ein langer
   Name (bzw. dessen Ellipse oben) den Kreis nicht staucht. */
#companion-widget .companion-header__avatar {
    flex: 0 0 auto;
}

#companion-widget .companion-header__avatar-img {
    width: 1.75rem;
    height: 1.75rem;
    font-size: 0.6875rem;
}

#companion-widget .companion-header__actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    /* Schiebt die Kontakt-Icons an den rechten Rand (vor der Orb-Reserve). */
    margin-right: auto;
}

#companion-widget .companion-header__action {
    display: grid;
    place-items: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.06);
    color: rgba(255, 255, 255, 0.92);
    cursor: pointer;
    transition:
        background-color 0.25s cubic-bezier(0.2, 0.7, 0.3, 1),
        border-color 0.25s cubic-bezier(0.2, 0.7, 0.3, 1);
}

#companion-widget .companion-header__action svg {
    width: 1rem;
    height: 1rem;
}

#companion-widget .companion-header__action:hover {
    background: var(--color-brand-orange, #fd7a12);
    border-color: var(--color-brand-orange, #fd7a12);
}

/* --- Verlauf --------------------------------------------------------------
   Der einzige scrollende Teil. Kopf und Eingabe bleiben stehen. */

#companion-widget .companion-panel__scroll {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    overflow-y: auto;
    /* Verhindert Scroll-Chaining an die (während der Chat offen ist
       ohnehin per Inline-Style gesperrte) Seite dahinter, siehe gleiche
       Absicherung bei .companion-panel__suggestions weiter unten. */
    overscroll-behavior: contain;
    padding-right: 0.5rem;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.28) transparent;
    /* Scroll-Fade oben/unten (Kollegen-Feedback, übernommen aus dem
       cal.com-PoC-Prototyp — companion-legacy-prototyp.css.disabled im
       jfconcept-local-Repo, dortige .companion-panel__scroll-Regel —
       gleicher BEM-Klassenname, hier eins zu eins portiert, nur ohne die
       dortigen undefinierten Custom Properties --gap-s/--radius-*, die es
       in DIESEM Stylesheet nicht gibt). --fade-top/--fade-bottom starten
       bei 0 (kein Fade) und werden von widget.js per data-Attribut
       gesetzt, sobald tatsächlich noch Inhalt in die jeweilige Richtung
       folgt (siehe updateScrollFade() dort) — reine CSS-Lösung wäre hier
       nicht möglich, da CSS für sich genommen nicht weiß, ob ein
       Scroll-Container gerade ganz oben/unten steht oder ob er überhaupt
       scrollbar ist. mask-image blendet den Rand weich aus, statt hart
       abzuschneiden — der zweite Hinweis (neben dem sichtbaren
       Scrollbalken direkt darüber) darauf, dass dort noch etwas liegt. */
    --fade-top: 0px;
    --fade-bottom: 0px;
    mask-image: linear-gradient(
        to bottom,
        transparent 0,
        #000 var(--fade-top),
        #000 calc(100% - var(--fade-bottom)),
        transparent 100%
    );
    -webkit-mask-image: linear-gradient(
        to bottom,
        transparent 0,
        #000 var(--fade-top),
        #000 calc(100% - var(--fade-bottom)),
        transparent 100%
    );
}

#companion-widget .companion-panel__scroll[data-fade-top='true'] {
    --fade-top: 1.5rem;
}

#companion-widget .companion-panel__scroll[data-fade-bottom='true'] {
    --fade-bottom: 1.5rem;
}

#companion-widget .companion-msg {
    max-width: 85%;
    /* NICHT der große Element-Radius: Bei zwei- bis dreizeiligen Bubbles
       rundet der die Ecken fast bis zur Mitte aus. */
    border-radius: var(--radius-button, 12px);
    padding: 0.75rem 1rem;
    font-size: 0.9375rem;
    line-height: 1.65;
    white-space: pre-line;
}

/* Eigene Nachricht: rechts, gefüllt. */
#companion-widget .companion-msg--user {
    align-self: flex-end;
    background: var(--color-brand-orange, #fd7a12);
    color: #fff;
}

/* Companion: links, gedeckt. */
#companion-widget .companion-msg--companion {
    align-self: flex-start;
    background: rgba(255, 255, 255, 0.1);
    color: rgba(255, 255, 255, 0.92);
}

/* Companion-Antworten werden seit renderRichTextToHtml() (widget.js) aus
   einfachem Markdown zu <p>/<ul>/<ol>/<li>/<strong>/<em> gerendert statt als
   reiner Text — kompakte eigene Absatz-/Listen-Abstände statt der
   Browser-Defaults, erstes/letztes Element ohne zusätzlichen Rand, damit es
   sich nahtlos ins .companion-msg-Padding einfügt. */
#companion-widget .companion-msg--companion p,
#companion-widget .companion-msg--companion ul,
#companion-widget .companion-msg--companion ol {
    margin: 0.5em 0;
}

#companion-widget .companion-msg--companion p:first-child,
#companion-widget .companion-msg--companion ul:first-child,
#companion-widget .companion-msg--companion ol:first-child {
    margin-top: 0;
}

#companion-widget .companion-msg--companion p:last-child,
#companion-widget .companion-msg--companion ul:last-child,
#companion-widget .companion-msg--companion ol:last-child {
    margin-bottom: 0;
}

#companion-widget .companion-msg--companion ul,
#companion-widget .companion-msg--companion ol {
    padding-left: 1.2em;
}

#companion-widget .companion-msg--companion li {
    margin: 0.2em 0;
}

#companion-widget .companion-msg--companion strong {
    font-weight: 600;
}

#companion-widget .companion-msg__link {
    color: #fff;
    text-decoration: underline;
}

/* Nur die Rufnummer, nicht jeder Link: eine über zwei Zeilen gebrochene
   Telefonnummer liest sich wie zwei Nummern (am laufenden Widget beobachtet,
   der Umbruch fiel zwischen Vorwahl und Anschluss). Für Mail-Adressen wäre
   dasselbe schädlich -- die können breiter als die Bubble sein und würden
   dann herausragen statt umzubrechen. Gesetzt wird die Modifikator-Klasse in
   appendContactChannelReply() (widget.js) aus action.type. */
#companion-widget .companion-msg__link--phone {
    white-space: nowrap;
}

/* --- Bewertungsleiste -------------------------------------------------------
   Daumen hoch/runter unter einer Antwort (appendFeedbackBar() in widget.js).
   Wird dort NUR aus finish()/fallback() angehaengt, also unmittelbar nach
   der Antwort-Bubble und VOR Kontakt-Karte/CTA/Vorschlags-Pillen -- kein
   eigener eingerahmter Block, sondern eine schmale, zurueckhaltende Zeile
   direkt im Verlauf, analog zu einem stillen Reaktions-Icon statt einer
   eigenstaendigen UI-Komponente. justify-content:flex-end statt des
   frueheren Default-Linksbuendig: die Leiste haengt an einer
   Companion-Antwort (die rechts im Verlauf liegt), links ausgerichtete
   Daumen wirkten wie ein eigenes, an der Bubble vorbeilaufendes Element. */
#companion-widget .companion-feedback {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.375rem;
    flex-wrap: wrap;
}

/* opacity statt color/background fuer den Ruhezustand -- bleibt trotzdem
   sichtbar genug, um als Steuerelement erkennbar zu sein, ohne neben der
   Antwort-Bubble laut zu wirken. Die Icons selbst sind seit dem Umbau auf
   Outline-SVG (buildThumbIcon() in widget.js statt Emoji-Glyphen)
   currentColor-faerbbar -- display:grid zentriert das SVG exakt in der
   quadratischen Trefferflaeche. */
#companion-widget .companion-feedback__btn {
    display: grid;
    place-items: center;
    background: none;
    border: 0;
    cursor: pointer;
    padding: 0.25rem;
    line-height: 1;
    color: rgba(255, 255, 255, 0.92);
    opacity: 0.45;
    transition: opacity 0.2s ease;
}

#companion-widget .companion-feedback__btn:hover {
    opacity: 0.8;
}

/* aria-pressed spiegelt die getroffene Wahl (siehe updatePressedState() in
   widget.js) -- Attribut-Selektor statt einer zusaetzlichen CSS-Klasse,
   damit Zustand und Barrierefreiheits-Semantik dieselbe Quelle haben. Die
   Wahl liest sich ueber Farbgewicht (volle Deckkraft + leichte Fill-Flaeche
   im Icon), NICHT ueber einen Glyphen-Wechsel -- es gibt nur ein SVG pro
   Daumen, kein "gefuelltes" Zweit-Icon zum Umschalten. */
#companion-widget .companion-feedback__btn[aria-pressed='true'] {
    opacity: 1;
}

#companion-widget .companion-feedback__btn[aria-pressed='true'] .companion-feedback__icon {
    fill: currentColor;
    fill-opacity: 0.18;
}

#companion-widget .companion-feedback__icon {
    display: block;
    flex: 0 0 auto;
}

/* Transiente Bestaetigung nach einem Klick (showConfirmation() in
   widget.js) -- EIN Element, das Text/Sichtbarkeit wechselt, kein neuer
   Knoten pro Klick. Bleibt technisch immer im DOM (nie display:none/
   visibility:hidden), damit aria-live='polite' Textaenderungen zuverlaessig
   ansagt -- max-width:0 + overflow:hidden zieht es optisch auf Null
   zusammen, ohne es aus dem Accessibility-Tree zu nehmen (gleiches Prinzip
   wie eine visuell-versteckte, aber vorlesbare Utility-Klasse). */
#companion-widget .companion-feedback__note {
    flex: 0 1 auto;
    max-width: 0;
    overflow: hidden;
    opacity: 0;
    white-space: nowrap;
    font-size: 0.75rem;
    color: rgba(255, 255, 255, 0.65);
    transition:
        opacity 0.2s ease,
        max-width 0.2s ease;
}

#companion-widget .companion-feedback__note.is-visible {
    max-width: 12rem;
    opacity: 1;
}

/* Kommentarfeld nach Daumen runter -- eigene Zeile (width: 100%ig im
   flex-wrap-Container oben), damit Eingabe und Buttons auf schmalen
   Chat-Panels nicht mit den Daumen-Buttons um denselben Platz konkurrieren.
   margin-top bewusst groesser als der gap zwischen Daumen-Buttons oben
   (0.375rem) -- seit Senden/Ueberspringen ebenfalls Outline-Icons sind
   (buildFeedbackIcon() in widget.js statt Text), besteht sonst die Gefahr,
   dass vier aehnliche Outline-Glyphen (2 Daumen oben, 2 hier drunter) als
   EINE Reihe aus vier Buttons statt als zwei getrennte Ebenen (Bewertung /
   Kommentar-Aktion) wahrgenommen werden. Der groessere Abstand hier ist
   die erste von zwei Massnahmen dagegen -- die zweite ist die gefuellte
   Pillenform von Senden/Ueberspringen weiter unten, die sich bewusst vom
   randlosen, transparenten Daumen-Button oben absetzt.

   flex-direction: column (Gestaltungsentwurf 2026-08-26, Umbau
   Eingabefeld -> Textarea): das Feld wurde vom einzeiligen <input> zur
   mitwachsenden <textarea>, Senden/Ueberspringen stehen seither in einer
   eigenen Zeile DARUNTER statt daneben (siehe .companion-feedback__comment-actions
   unten und appendCommentField() in widget.js) -- ein mehrzeiliges Feld
   neben zwei Buttons haette deren vertikale Ausrichtung beliebig gemacht. */
#companion-widget .companion-feedback__comment {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    width: 100%;
    margin-top: 0.5rem;
}

/* Aktionszeile unter dem Kommentarfeld (siehe appendCommentField() in
   widget.js) -- rechtsbuendig wie die Daumen-Zeile in .companion-feedback
   oben, damit beide Ebenen derselben Ausrichtung folgen. */
#companion-widget .companion-feedback__comment-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.375rem;
}

/* Rahmen/Radius/Hintergrund kommen ueber die geteilte Klasse
   .companion-freetext-input (widget.js setzt sie als ZWEITE Klasse auf
   dieselbe <textarea>, siehe appendCommentField()) -- bewusst keine eigene
   Kopie dieser drei Werte hier, sonst laufen Kommentarfeld und das
   Freitext-Feld direkt darunter beim naechsten Anfassen wieder
   auseinander. Diese Regel traegt nur die echten Abweichungen: kleinere
   Schrift/Innenabstand fuer die schmalere Zeile. Der Fokus-Zustand ist
   bewusst KEINE Abweichung mehr (siehe :focus-visible unten) -- beide
   Felder zeigen dieselbe dezent aufgehellte Border, damit sie optisch als
   ein System wirken. Kombinierter Selektor (beide Klassen) statt nur
   .companion-feedback__input: die spaeter im File stehende
   .companion-freetext-input-Basisregel (Punkt/Radius/BG, siehe dort) hat
   bei gleicher Spezifitaet sonst die letzte Deklaration und wuerde
   padding/font-size wieder auf die Freitext-Werte zurueckziehen.

   resize/max-height/overflow-y sind neu seit dem Umbau von <input> auf
   <textarea> (Gestaltungsentwurf 2026-08-26): resize:none, weil die Hoehe
   bereits per Auto-Grow ueber den 'input'-Listener in appendCommentField()
   gesteuert wird -- ein zusaetzlicher nativer Resize-Griff waere ein
   zweiter, widerspruechlicher Weg zum selben Ziel. max-height ist die
   visuelle Obergrenze fuer dieses Wachstum (ca. 6-7 Zeilen bei der
   Schriftgroesse hier); darueber hinaus wird intern gescrollt statt das
   Feld weiter aufzublasen. */
#companion-widget .companion-freetext-input.companion-feedback__input {
    padding: 0.375rem 0.5rem;
    font-size: 0.8125rem;
    resize: none;
    max-height: 8rem;
    overflow-y: auto;
}

/* Ruhiger Fokus-Zustand statt Doppelring: die fruehere zweischichtige
   box-shadow-Loesung (innerer Akzentring + aeusserer heller Ring) wirkte
   auf dem Bildschirm wie ein schwerer Doppel-Rahmen und wurde deshalb
   verworfen. Das Feld haengt in der Feedback-Leiste, die wiederum im
   eigenen Scroll-Container des Panels haengt -- es liegt also IMMER auf
   dem selbst kontrollierten Panel-Hintergrund, nie direkt auf einer
   fremden Kundenseite. Ein zusaetzlicher Aussenring "fuer unbekannte
   Seiten-Hintergruende" ist damit unnoetig, dieses Argument galt nie fuer
   dieses Feld. Stattdessen wechselt nur die ohnehin vorhandene
   Border-Farbe auf denselben Wert wie beim Freitext-Feld
   (.companion-freetext-input:focus-visible weiter unten im File) --
   gleiche Technik, gleicher Wert, damit Kommentarfeld und Freitext-Feld
   als ein System wirken. Reine Farbaenderung, keine Breitenaenderung an
   der Border, sonst wuerde das Feld beim Fokussieren um einen Pixel
   springen. outline:none braucht deshalb den Border-Farbwechsel direkt in
   derselben Regel als Ersatz. Kombinierter Selektor
   (.companion-freetext-input.companion-feedback__input) statt eines
   einzelnen Klassen-Selektors, damit diese Regel unabhaengig von der
   Reihenfolge im Stylesheet IMMER gegen die spaeter im File stehende
   .companion-freetext-input:focus-visible-Regel gewinnt -- beide matchen
   sonst mit derselben Spezifitaet. */
#companion-widget .companion-freetext-input.companion-feedback__input:focus-visible {
    outline: none;
    border-color: rgba(255, 255, 255, 0.55);
}

/* Icon-only seit dem Umbau von Text ("Senden"/"Ueberspringen") auf
   Outline-SVG (buildFeedbackIcon() in widget.js) -- display:grid +
   place-items:center zentriert das Icon in der quadratisch wirkenden
   Pillenflaeche, analog zu .companion-feedback__btn oben, aber MIT
   sichtbarer Hintergrundflaeche (siehe die zwei Regeln direkt darunter).
   Genau diese gefuellte Pillenform ist die zweite Massnahme gegen das
   Vier-Glyphen-Problem (erste: groesserer margin-top am Elternblock oben):
   die randlosen, halbtransparenten Daumen-Icons in der Zeile darueber und
   diese gefuellten Icon-Chips hier sind dadurch auf den ersten Blick zwei
   unterschiedliche Bedienelement-Typen, nicht vier gleich aussehende
   Glyphen in einer Reihe. aria-label/title tragen die Beschriftung (siehe
   widget.js) -- kein Text mehr im Markup. */
/* Senden-Knopf: unverändert ein Icon-Button. Stand bis 2026-08-26 in einer
   gemeinsamen Regel mit .companion-feedback__skip -- seit dieser ein
   Textknopf ohne Fläche ist (siehe Regel darunter), haben die beiden keine
   gemeinsame Geometrie mehr. */
#companion-widget .companion-feedback__submit {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    border: 0;
    cursor: pointer;
    padding: 0.375rem;
    border-radius: var(--radius-button, 12px);
}

/* Textknopf statt Icon (siehe skip.textContent in widget.js): tertiär,
   also OHNE Fläche und ohne Rahmen -- Fläche gegen keine Fläche trennt
   Haupt- von Nebenhandlung deutlicher als zwei gleich aussehende Icons
   nebeneinander.

   min-height hielt Senden/Ueberspringen bis zum Umbau auf
   .companion-feedback__comment-actions (Gestaltungsentwurf 2026-08-26,
   Textarea-Umbau) nur als BODEN auf gleicher Hoehe -- die tatsaechliche
   Hoehe kam damals von der Streckung durch das seinerzeit gleichzeilige
   Eingabefeld. Seit Senden/Ueberspringen in einer eigenen Zeile UNTER dem
   Kommentarfeld stehen (siehe .companion-feedback__comment-actions,
   align-items:center, kein Streckungs-Nachbar mehr), ist dieser Wert die
   tatsaechlich wirksame Hoehe, kein Boden mehr.

   Als Rechnung stehen geblieben statt als fertige 26px: so bleibt er
   richtig, wenn sich Icon-Groesse oder Innenabstand des Senden-Knopfs
   daneben (14px Icon, siehe Regel darunter, plus dessen 2 x 0.375rem
   Innenabstand) je aendern -- beide Buttons sollen weiterhin dieselbe Zeilenhoehe
   ergeben. */
#companion-widget .companion-feedback__skip {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    border: 0;
    cursor: pointer;
    padding: 0 0.25rem;
    min-height: calc(14px + 2 * 0.375rem);
    background: none;
    font: inherit;
    font-size: 0.8125rem;
    line-height: 1;
}

/* Icon bewusst kleiner (14px) als die Daumen-Icons oben (18px, siehe
   .companion-feedback__icon) -- dritte Massnahme gegen das
   Vier-Glyphen-Problem: unterschiedliche Groesse macht die zwei Ebenen
   (Bewertung oben / Kommentar-Aktion hier) auch bei fluechtigem Hinsehen
   sofort unterscheidbar, ohne dass sich am DOM-Aufbau des Icons selbst
   etwas aendert (buildFeedbackIcon() bleibt fuer alle vier Icons
   identisch, das ist reine Darstellungsgroesse per CSS). */
#companion-widget .companion-feedback__submit .companion-feedback__icon {
    width: 14px;
    height: 14px;
}

/* Senden gefuellt, Ueberspringen zurueckhaltend -- aber bewusst KEIN
   starker Kontrast zwischen beiden: der Kommentar ist ausdruecklich
   optional (Placeholder "Was hat gefehlt? (optional)"), Ueberspringen ist
   der haeufigere Weg. Ein auffaellig primaeres Senden neben einem stark
   verblassten Ueberspringen wuerde Ueberspringen wie ein Abbrechen wirken
   lassen und den Besucher Richtung Kommentar draengen -- nicht gewollt.
   Seit Überspringen ein Textknopf ohne Fläche ist (siehe Regel oben),
   trägt die Unterscheidung die Fläche selbst und nicht mehr die Farbe --
   Überspringen bleibt deshalb im neutralen Weißton, kein Rot/Grau-Warnton. */
#companion-widget .companion-feedback__submit {
    background: var(--color-brand-orange, #fd7a12);
    color: #fff;
}

#companion-widget .companion-feedback__submit:hover {
    background: #e48d3f;
}

#companion-widget .companion-feedback__skip {
    color: rgba(255, 255, 255, 0.75);
}

#companion-widget .companion-feedback__skip:hover {
    color: rgba(255, 255, 255, 0.92);
}

/* --- Kontakt-Karte ----------------------------------------------------------
   Ansprechpartner unter einer Antwort mit entry.contactId (siehe
   renderEntryContact() in widget.js) — Foto/Initialen, Name, Rolle,
   Mail-/Telefon-Links. buildAvatar() erzeugt dasselbe .companion-avatar auch
   für den Header (siehe companion-header__avatar-img oben), dort nur kleiner
   skaliert per zusätzlicher Klasse. */

#companion-widget .companion-avatar {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    border-radius: 50%;
    object-fit: cover;
    background: rgba(255, 255, 255, 0.1);
}

#companion-widget .companion-avatar--initials {
    font-weight: 600;
    color: rgba(255, 255, 255, 0.92);
    background: var(--color-brand-orange, #fd7a12);
}

#companion-widget .companion-contact-card {
    display: flex;
    align-self: flex-start;
    /* flex-start statt center: die Karte trägt seit dem Pill-Umbau der
       Mail-/Telefon-/Termin-Aktionen (siehe .companion-contact-card__action/
       __cta unten) unterschiedlich viel Inhalt — der Avatar soll dabei
       immer oben an Name/Rolle ausgerichtet bleiben, nicht mittig zur
       gesamten (wachsenden) Höhe. */
    align-items: flex-start;
    gap: 0.75rem;
    max-width: 85%;
    padding: 0.75rem 1rem;
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: var(--radius-button, 12px);
    background: rgba(255, 255, 255, 0.05);
}

#companion-widget .companion-contact-card__avatar {
    width: 2.75rem;
    height: 2.75rem;
    font-size: 1rem;
}

#companion-widget .companion-contact-card__info {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
    min-width: 0;
}

#companion-widget .companion-contact-card__name {
    margin: 0;
    font-size: 0.9375rem;
    font-weight: 600;
    color: #fff;
}

#companion-widget .companion-contact-card__role {
    margin: 0;
    font-size: 0.8125rem;
    color: rgba(255, 255, 255, 0.65);
}

/* Kompakte Icon-Reihe statt gestapelter Text-Chips (Nutzer-Report: volle
   Mail-/Telefon-Chips untereinander brauchten zu viel vertikalen Platz,
   siehe buildContactCard() in widget.js für die Klapp-Logik). Ersetzt die
   frühere "Pille mit Text daneben"-Optik unten — der frühere Kollegen-
   Feedback-Wert 0.75rem (gegen zu dicht stehende TEXT-Pillen) passt nicht
   mehr zu reinen Icon-Buttons, deshalb hier auf denselben Wert wie die
   Header-Actions (.companion-header__actions) gesetzt statt neu erfunden. */
#companion-widget .companion-contact-card__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.375rem;
}

/* Mail-/Telefon-Icons der Ansprechpartner-Kontaktkarte (buildContactCard()
   in widget.js). Dieselbe Kreis-Button-Geometrie wie
   .companion-header__action (2rem/32px Trefferfläche trotz kompakter
   1rem-Icon-Fläche -- "kompakter" heißt kleinere LAYOUT-Fläche, nicht
   kleinere Tippfläche) statt der früheren Text-Pillen-Optik. Weiß mit
   Rahmen statt vollflächig orange gefüllt (Nutzer-Report: eine
   Orange-Füllung wirkte neben dem ebenfalls orangenen Termin-Icon daneben
   wie zwei Primär-Aktionen).

   Kein [aria-expanded='true']-Zustand mehr: seit dem Umbau auf Sprechblasen
   (2026-08-26) klappen diese Icons nichts mehr auf, sie hängen eine Antwort
   in den Verlauf. Ein "aktiver" Kanal existiert damit nicht mehr -- im
   Verlauf gibt es nur das zuletzt Gesagte. */
#companion-widget .companion-contact-card__action {
    display: grid;
    place-items: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, 0.35);
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.06);
    color: rgba(255, 255, 255, 0.92);
    cursor: pointer;
    transition:
        background-color 0.25s cubic-bezier(0.2, 0.7, 0.3, 1),
        border-color 0.25s cubic-bezier(0.2, 0.7, 0.3, 1);
}

#companion-widget .companion-contact-card__action:hover {
    background: rgba(255, 255, 255, 0.14);
    border-color: rgba(255, 255, 255, 0.55);
}

#companion-widget .companion-contact-card__action-icon {
    width: 1rem;
    height: 1rem;
    flex: 0 0 auto;
}

/* Termin-Icon-Button der Ansprechpartner-Kontaktkarte -- seit dem Icon-
   Umbau kein Text-Pill mit "Termin"-Label mehr, sondern derselbe runde
   Kreis-Button wie Mail/Telefon (.companion-contact-card__action oben,
   siehe buildContactCard() in widget.js: die Klasse heißt weiterhin
   .companion-contact-card__cta, nur die Geometrie wurde auf Kreisform
   umgestellt, damit an dieser Stelle nichts sonst anfassen muss). Steht
   jetzt in DERSELBEN Reihe wie Mail/Telefon (.companion-contact-card__actions,
   siehe buildContactCard()) statt separat darunter -- eigene Klasse bleibt
   trotzdem bestehen (statt Wiederverwendung von __action), weil der
   Termin-Button als klare Primär-Aktion bewusst Marken-Orange behält,
   während Mail/Telefon weiß bleiben; NICHT vollflächig gefüllt (kein
   background), sondern nur per Rahmenfarbe abgesetzt -- dieselbe
   Weiß-mit-Rahmen-Regel wie bei Mail/Telefon gilt also auch hier, nur mit
   Orange statt Weiß als Akzentfarbe. */
#companion-widget .companion-contact-card__cta {
    display: grid;
    place-items: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 1px solid var(--color-brand-orange, #fd7a12);
    border-radius: 50%;
    background: rgba(253, 122, 18, 0.08);
    color: var(--color-brand-orange, #fd7a12);
    text-decoration: none;
    cursor: pointer;
    transition:
        background-color 0.25s cubic-bezier(0.2, 0.7, 0.3, 1),
        transform 0.25s cubic-bezier(0.2, 0.7, 0.3, 1);
}

#companion-widget .companion-contact-card__cta:hover {
    background: rgba(253, 122, 18, 0.16);
    transform: translateY(-1px);
}

#companion-widget .companion-contact-card__cta-icon {
    width: 1rem;
    height: 1rem;
    flex: 0 0 auto;
}

/* CTA-Scroll-Button unter einer Antwort (siehe ctaZoneKey im Bundle) —
   optisch an den Freitext-Absenden-Button angelehnt, aber outlined statt
   gefüllt, damit er sich neben den Chat-Bubbles nicht wie eine weitere
   Nachricht liest. */
#companion-widget .companion-cta-scroll {
    align-self: flex-start;
    padding: 0.5rem 1rem;
    border: 1px solid var(--color-brand-orange, #fd7a12);
    border-radius: var(--radius-bubble, 999px);
    background: transparent;
    font: inherit;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--color-brand-orange, #fd7a12);
    cursor: pointer;
    transition: transform 0.25s cubic-bezier(0.2, 0.7, 0.3, 1);
}

#companion-widget .companion-cta-scroll:hover {
    transform: translateY(-1px);
}

/* .companion-cta-scroll wird auch für den "Termin buchen"-Link der
   Buchungskarte wiederverwendet (siehe appendBookingCard() in widget.js) —
   dort als <a>, nicht <button>, daher hier explizit die Standard-
   Unterstreichung ausschalten. */
#companion-widget a.companion-cta-scroll {
    display: inline-block;
    text-decoration: none;
}

/* --- Buchungs-Themen (Kalender-Aktion) ------------------------------------
   Themen-Navigation (renderTopicLevel in widget.js) nutzt bewusst dieselbe
   Optik wie die Vorschlags-Pills (.companion-panel__suggestions .companion-pill
   oben) — hier nur die "Zurück"-Pill und das Thema-Badge der Buchungskarte
   ergänzt. */
#companion-widget .companion-pill--back {
    border-style: dashed;
    color: rgba(255, 255, 255, 0.65);
}

/* Thema-Badge in der Buchungskarte (appendBookingCard()): reiner Text ohne
   page_url, sonst Link auf die Themen-Seite (target=_blank), siehe
   topic.pageUrl in BundleBuilder::build(). */
#companion-widget .companion-topic-badge {
    display: inline-flex;
    align-self: flex-start;
    margin-top: 0.25rem;
    padding: 0.25rem 0.625rem;
    border: 1px solid var(--color-brand-orange, #fd7a12);
    border-radius: var(--radius-bubble, 999px);
    background: transparent;
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--color-brand-orange, #fd7a12);
    text-decoration: none;
}

#companion-widget a.companion-topic-badge:hover {
    text-decoration: underline;
}

/* --- Buchungs-Embed (cal.com) ----------------------------------------------
   Echtes Overlay bei Blatt-Themen mit targetType 'cal' (siehe
   enterBookingView()/exitBookingView() in widget.js) — schwebt als
   eigenständige Karte (position:absolute, eigener Hintergrund/Radius/
   Schatten) ÜBER companion-chat-body/companion-booking-dim, statt die
   Chat-Ansicht zu ersetzen. Bugfix (Nutzer-Report): vorher wurden Kopf/
   Verlauf/Vorschläge/Formular per display:none komplett entfernt — dabei
   blieben aber Elemente AUSSERHALB des Panels (Pill-Leiste im geschlossenen
   Zustand, Orb) unverändert sichtbar und konkurrierten optisch mit dem
   Embed; ausserdem wirkte der Wechsel hart statt wie ein Modal. inset
   (statt 0) lässt ringsum etwas vom geblurrten/gedimmten Chat durchscheinen
   — genau das gewünschte "Modal über Inhalt"-Bild. */
#companion-widget .companion-booking-view {
    position: absolute;
    inset: 0.75rem;
    z-index: 3;
    display: flex;
    flex-direction: column;
    gap: var(--gap-s, 1.25rem);
    text-align: left;
    background: rgba(24, 24, 26, 0.98);
    border-radius: var(--radius-button, 12px);
    padding: var(--gap-s, 1.25rem);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
}

#companion-widget .companion-booking-view__header {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    gap: 0.5rem;
    padding-bottom: var(--gap-s, 1.25rem);
    padding-right: 2.75rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}

#companion-widget .companion-booking-view__back {
    align-self: flex-start;
    padding: 0;
    border: 0;
    background: transparent;
    font: inherit;
    font-size: 0.8125rem;
    color: rgba(255, 255, 255, 0.65);
    cursor: pointer;
}

#companion-widget .companion-booking-view__back:hover {
    color: #fff;
    text-decoration: underline;
}

#companion-widget .companion-booking-view__toprow {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

#companion-widget .companion-booking-view__avatar {
    width: 2.25rem;
    height: 2.25rem;
    font-size: 0.8125rem;
}

#companion-widget .companion-booking-view__title {
    margin: 0;
    font-size: 1.0625rem;
    font-weight: 600;
    color: #fff;
}

#companion-widget .companion-booking-view__subrow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

#companion-widget .companion-booking-view__with {
    font-size: 0.8125rem;
    color: rgba(255, 255, 255, 0.65);
}

#companion-widget .companion-booking-view__frame-wrap {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    overflow: hidden;
    border-radius: var(--radius-button, 12px);
    background: rgba(255, 255, 255, 0.04);
}

#companion-widget .companion-booking-view__iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}

#companion-widget .companion-booking-view__fallback {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 0.75rem;
    padding: 1rem;
    font-size: 0.875rem;
    color: rgba(255, 255, 255, 0.75);
}

/* --- Vorschläge ----------------------------------------------------------
   Folgefragen bzw. die übrigen Fragen der Zone, direkt über der Eingabe. */

/* max-height + eigener Scroll statt "weniger Vorschläge anzeigen" (siehe
   Review-Präzisierung): bei vielen Followups/Meintest-du-Kandidaten frisst
   sich der Bereich sonst durch den Nachrichtenverlauf. Richtwert ~1/3 der
   Panel-Höhe — über vh statt Prozent, weil Prozent-Höhen auf einem
   Flex-Item innerhalb einer Flex-Spalte (.companion-chat) browserabhängig
   fragil sind (definite-size-Propagation je nach Engine unterschiedlich),
   während die Panel-Höhe selbst schon vh-basiert ist (.is-open oben).
   Scrollbar-Optik identisch zur Nachrichtenliste (.companion-panel__scroll)
   für ein einheitliches Bild. overscroll-behavior:contain verhindert
   Scroll-Chaining an die (während des offenen Chats ohnehin gesperrte)
   Seite dahinter — dieselbe Absicherung wie bei der Nachrichtenliste. Gilt
   automatisch auch für den "Meintest du"-Block (appendMeintestDu in
   widget.js baut ihn mit derselben Klasse), NICHT für die Balken-Pills
   (eigene Klasse .companion-pills). */
#companion-widget .companion-panel__suggestions {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
    padding-top: var(--gap-s, 1.25rem);
    border-top: 1px solid rgba(255, 255, 255, 0.14);
    max-height: min(30vh, 240px);
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.28) transparent;
}

/* Punkt 4 (2026-08-24): Der frühere Fallback, bei dem renderSuggestions()
   die Kontakt-Icons in diesen Container renderte, wenn keine
   Vorschlags-Pills mehr übrig waren, entfällt (Duplikat des Chat-Headers,
   siehe widget.js). Die zugehörige :has(.companion-header__action)-Regel
   trifft dadurch nirgends mehr und wurde entfernt — der leere Container
   verschwindet jetzt ohnehin über die :empty-Regel direkt darunter. */
#companion-widget .companion-panel__suggestions:empty {
    display: none;
}

/* Buchungsthemen (.companion-topic-pills, siehe renderTopicLevel in
   widget.js — gilt für Haupt- UND Unterthemen-Ebene, beide nutzen dieselbe
   Klasse) sollen als fließendes Raster laufen statt als Liste gestapelt zu
   sein. Nur diese Zusatzklasse überschreiben, NICHT die Basisregel oben —
   die Folgefragen-Pills und der "Meintest du"-Block (beide ohne diese
   Klasse) bleiben bewusst einspaltig. */
#companion-widget .companion-panel__suggestions.companion-topic-pills {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
}

/* Punkt 6a (2026-08-24): Themen-Pills erbten bisher unveraendert dieselbe
   Groesse wie die normalen Folgefragen-Pills (.companion-panel__suggestions
   .companion-pill weiter unten, 0.875rem / 0.5rem 0.875rem) — bei vielen
   Themen im Grid wirkte das ueberladen. Eigene, spezifischere Regel nur fuer
   Themen-Pills (Selektor-Spezifitaet ueber die Zusatzklasse), Folgefragen
   und die Vorschlags-Leiste bleiben unberuehrt.
   min-height: 2rem (32px) ist bewusst eine Tap-Flaechen-Untergrenze, keine
   feste Hoehe: dank box-sizing: border-box (siehe Zeile 255, gilt fuer den
   gesamten #companion-widget-Baum) schliesst min-height Padding und Rahmen
   ein, die Pille faellt also nie unter 32px, selbst wenn Schrift + Padding
   rechnerisch weniger ergaeben. 32px ist keine Neuerfindung, sondern die
   bereits im Widget etablierte Tap-Flaeche der Kontakt-Icon-Buttons
   (.companion-contact-card__action/__cta, siehe Punkt 3) — komfortabel
   ueber der WCAG-2.5.8-Mindestgroesse (AA, 24x24px), aber bewusst unter den
   von Apple/Google empfohlenen 44px fuer isolierte Steuerelemente, die fuer
   mehrere nebeneinander im Grid liegende, kompakt gewuenschte Themen-Pills
   zu breit waeren und dem Ziel (weniger wuchtig) entgegenlaufen wuerden. */
#companion-widget .companion-panel__suggestions.companion-topic-pills .companion-pill {
    font-size: 0.8125rem; /* 13px */
    padding: 0.25rem 0.625rem; /* 4px / 10px, knapper als die geerbten 8px/16px */
    min-height: 2rem; /* 32px Tap-Flaechen-Boden */
}

/* Vorschlags-Pills umbrechen statt zu kürzen — anders als in der Leiste
   ist hier Platz, und eine Ellipse wäre hier reiner Informationsverlust. */
#companion-widget .companion-panel__suggestions .companion-pill {
    max-width: 100%;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    text-align: left;
    font-size: 0.875rem;
    padding: 0.5rem 1rem;
}

/* --- Freitext-Eingabe ---------------------------------------------------- */

#companion-widget .companion-freetext-form {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 0.5rem;
    padding-top: var(--gap-s, 1.25rem);
    border-top: 1px solid rgba(255, 255, 255, 0.14);
}

/* border/border-radius/background hier werden auch vom Kommentarfeld der
   Bewertungsleiste mitgenutzt (companion-feedback__input traegt diese
   Klasse zusaetzlich, siehe appendCommentField() in widget.js und
   .companion-feedback__input weiter oben in dieser Datei) -- beim Aendern
   dieser drei Werte an das Kommentarfeld denken, es soll optisch immer
   zu diesem Feld passen, nicht eigenstaendig driften. */
#companion-widget .companion-freetext-input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.625rem 1rem;
    border: 1px solid rgba(255, 255, 255, 0.28);
    border-radius: var(--radius-bubble, 999px);
    background: transparent;
    font: inherit;
    font-size: 0.9375rem;
    color: rgba(255, 255, 255, 0.92);
}

#companion-widget .companion-freetext-input::placeholder {
    color: rgba(255, 255, 255, 0.45);
}

#companion-widget .companion-freetext-input:focus {
    /* outline:none, da der Fokus beim Hineinklicken mit der Maus keine
       Hervorhebung zeigen soll (Ruhezustand-Border aus
       .companion-freetext-input bleibt einfach bestehen). Ersatzlos
       entfallen darf der Fokus-Indikator aber nicht: für Tastaturnutzer
       greift unten die :focus-visible-Regel mit einer dezent
       aufgehellten Border statt des früheren auffälligen orangen
       Rings. */
    outline: none;
}

#companion-widget .companion-freetext-input:focus-visible {
    border-color: rgba(255, 255, 255, 0.55);
}

#companion-widget .companion-freetext-submit {
    flex: 0 0 auto;
    padding: 0.625rem 1.25rem;
    border: 0;
    border-radius: var(--radius-bubble, 999px);
    background: var(--color-brand-orange, #fd7a12);
    font: inherit;
    font-size: 0.9375rem;
    font-weight: 500;
    color: #fff;
    cursor: pointer;
    transition: transform 0.25s cubic-bezier(0.2, 0.7, 0.3, 1);
    /* Der Button-Text wechselt waehrend des Cooldowns zwischen "Senden"
       und der Sekunden-Countdown-Zahl bzw. "…" (siehe startAskCooldown()/
       writeCooldownLabel() in widget.js) -- min-width an "Senden" (dem
       breitesten der drei Inhalte) verhindert, dass der Button bei jedem
       Wechsel schmaler wird und dadurch springt. tabular-nums haelt
       zusaetzlich die Ziffern selbst gleich breit (10 -> 9 -> ... -> 1). */
    min-width: 5.75rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

#companion-widget .companion-freetext-submit:hover {
    transform: translateY(-1px);
}

/* --- Freitext-Senden-Cooldown ---------------------------------------------
   Der Senden-Button wird nach jedem Submit für ASK_COOLDOWN_MS (10s)
   gesperrt (siehe startAskCooldown() in widget.js) — deckt sich mit dem
   serverseitigen 10s-Rate-Limit auf /api/ask (AppServiceProvider::boot()).
   Alle drei Werte MÜSSEN synchron bleiben, ändert sich der Server-Wert, dort
   mitziehen. Der ablaufende Ring ist ein conic-gradient auf einem
   ::after-Pseudo-Element, dessen Winkel (--companion-cooldown-angle) NICHT
   per CSS-Keyframe läuft, sondern von startAskCooldown() selbst per
   requestAnimationFrame gesetzt wird — siehe dortigen Kommentar: das Widget
   lädt dieses Stylesheet als <link> in den Shadow Root (injectDefaultCss()),
   und eine @property-Typregistrierung aus einem Shadow-Root-Stylesheet
   greift dort nicht zuverlässig, wodurch eine reine CSS-Keyframe-Lösung nur
   hart zwischen 0° und 360° sprang statt den Ring zu füllen (Live-Befund).
   Der var()-Fallback unten (0deg) deckt außerdem den Rand-Moment VOR dem
   ersten JS-Write ab. */
#companion-widget .companion-freetext-submit--cooldown {
    position: relative;
    opacity: 0.55;
    cursor: not-allowed;
    pointer-events: none;
}

#companion-widget .companion-freetext-submit--cooldown::after {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: inherit;
    padding: 2px;
    background: conic-gradient(#fff var(--companion-cooldown-angle, 0deg), rgba(255, 255, 255, 0.18) var(--companion-cooldown-angle, 0deg));
    -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
    mask-composite: exclude;
}

/* --- KI-Tippindikator -----------------------------------------------------
   Drei pulsierende Punkte, während askAi() (widget.js) auf die Antwort des
   Ask-Endpoints wartet. Nutzt companion-msg/companion-msg--companion für
   Bubble-Optik (Hintergrund, Radius, Ausrichtung) und ergänzt nur das
   Punkte-Layout — wird vor der echten Antwort wieder aus dem DOM entfernt. */
#companion-widget .companion-typing {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.75rem 1rem;
}

#companion-widget .companion-typing span {
    width: 0.4rem;
    height: 0.4rem;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.6);
    animation: companion-typing-bounce 1.1s ease-in-out infinite;
}

#companion-widget .companion-typing span:nth-child(2) {
    animation-delay: 0.15s;
}

#companion-widget .companion-typing span:nth-child(3) {
    animation-delay: 0.3s;
}

@keyframes companion-typing-bounce {
    0%,
    60%,
    100% {
        transform: translateY(0);
        opacity: 0.5;
    }
    30% {
        transform: translateY(-4px);
        opacity: 1;
    }
}

/* --- KI-Hinweis -------------------------------------------------------------
   Dauerhaft sichtbar unter der Eingabe (widget.js hängt es direkt nach dem
   freeTextForm an) — kein Tooltip, kein Dismiss, bewusst immer da.
   Kollegen-Feedback: wirkte zu groß/zu weit von der Eingabe abgesetzt.
   font-size runter (0.75rem -> 0.6875rem), padding-top runter (0.375rem ->
   0.25rem, der GRÖSSERE Anteil des oberen Abstands kommt ohnehin nicht von
   hier, sondern vom gap:var(--gap-s,1.25rem) des umschließenden Flex-Containers
   .companion-chat-body zwischen ALLEN dessen Kindern (Kopf/Verlauf/
   Vorschläge/Formular/Hinweis) — das gilt gleichermaßen an jeder Stelle
   und wurde hier bewusst nicht angetastet, um die übrigen Abstände nicht
   mit zu verändern).
   Der Abstand NACH UNTEN kam nicht von diesem Element selbst (margin:0
   blieb immer 0), sondern vom oberen/unteren Container-Padding auf
   #companion-widget.is-open (padding: var(--gap-m), gilt einheitlich auf
   allen vier Seiten) — der Hinweis ist das letzte Flex-Kind, direkt davor
   liegt also diese volle Container-Bottom-Padding. Ein pauschales Kürzen
   DIESES Container-Paddings hätte auch den oberen/seitlichen Abstand
   überall sonst im Panel verkleinert. Stattdessen gezielt NUR hier: ein
   negativer margin-bottom zieht die untere Panel-Kante bis knapp an den
   Hinweistext heran, overflow:hidden auf dem Container (siehe .is-open)
   schneidet den Rest sauber ab, ohne dass irgendein anderer Abstand im
   Panel sich mitverändert. */
#companion-widget .companion-ai-hint {
    flex: 0 0 auto;
    margin: 0 0 -0.75rem;
    padding-top: 0.25rem;
    font-size: 0.6875rem;
    line-height: 1.4;
    color: rgba(255, 255, 255, 0.45);
    text-align: center;
}

/* --- Reduzierte Bewegung ---------------------------------------------------
   Die MORPH_*_MS-Sequenz in widget.js prüft prefers-reduced-motion bereits
   selbst und schaltet dort direkt um (kein setTimeout/rAF-Ablauf). Das
   deckt aber nur den Oeffnen/Schließen-Morph ab — alle UEBRIGEN CSS-
   Animationen/Transitions hier (Zonenwechsel-Pills, Pill-Rückkehr, der
   is-morph-hidden-Fade, Hover-Transforms) liefen ohne diesen Block
   weiterhin normal, obwohl der Nutzer reduzierte Bewegung eingestellt hat.
   !important ist hier bewusst: Diese Regel soll jede andere Transition-/
   Animation-Dauer im Widget übersteuern, ohne dass an jeder einzelnen
   Stelle oben ein zusätzlicher Media-Query-Zweig nötig wäre.
   animation-iteration-count:1 hat jetzt wieder einen konkreten Treffer:
   die Aurora-Ebene (.companion-orb::after, companion-orb-drift-Keyframe,
   siehe dort) bringt den früher einzigen endlos laufenden Loop dieser
   Art zurück (Kollegen-Feedback, POC-Referenz). #companion-widget *
   allein reicht dafür NICHT — der Universalselektor * trifft keine
   Pseudo-Elemente —, daher hier zusätzlich *::before/*::after explizit
   aufgenommen (deckt auch die Flächenfarbe auf .companion-orb::before
   mit ab, deren eigene Transition ohnehin schon kurz ist). */
@media (prefers-reduced-motion: reduce) {
    #companion-widget,
    #companion-widget *,
    #companion-widget *::before,
    #companion-widget *::after,
    #companion-backdrop {
        transition-duration: 0.01ms !important;
        transition-delay: 0s !important;
        animation-duration: 0.01ms !important;
        animation-delay: 0s !important;
        animation-iteration-count: 1 !important;
    }
}

/* Verteidigungslinie gegen eine veraltete gecachte widget.js-Version: der
   Orb ist seit dem Ein-Container-Morph selbst der Schließen-Button, ein
   separater .companion-panel__close im Header wird von der AKTUELLEN
   widget.js nicht mehr erzeugt. Sollte er durch eine veraltete Version
   trotzdem im DOM landen, wäre er ein zweites X neben dem Orb — hier hart
   ausgeblendet statt eine zweite Bedienung zuzulassen.

   Seit dem Shadow-DOM-Umbau NICHT mehr "eine kollidierende Custom-CSS-
   Regel von einer fremden Host-Seite" als möglicher Auslöser relevant
   (der ursprüngliche Grund für diese Zeile mit) — Shadow-DOM-Kapselung
   verhindert genau das strukturell, Host-CSS erreicht diesen Shadow Root
   gar nicht mehr. Die einzige verbleibende, unwahrscheinliche Ursache ist
   eine veraltete widget.js-Version, die intern noch den alten Button
   erzeugt — deshalb bleibt die Regel als reine JS-Versions-Absicherung
   stehen, nicht mehr als CSS-Isolations-Notbehelf. */
#companion-widget .companion-panel__close {
    display: none !important;
}

/* --- Mobil --------------------------------------------------------------- */

@media (max-width: 640px) {
    #companion-widget {
        right: 0.75rem;
        left: 0.75rem;
        /* Nutzer-Feedback (schmaler ~480px-Screenshot): die Leiste klebte zu
           dicht am unteren Viewport-Rand. bottom ist die EINZIGE Quelle für
           Balken, Morph UND offenes Panel in diesem Media Query (keine der
           beiden setzt bottom selbst, siehe .companion-morphing/.is-open
           weiter unten) — eine Änderung hier zieht sich also automatisch
           konsistent durch den gesamten Öffnen/Schließen-Morph, ohne dass
           Start- und Endzustand auseinanderlaufen. Zusätzlich per max()
           gegen die Home-Indicator-Safe-Area moderner iPhones abgesichert:
           die Datei nutzte env() bisher nirgends, ein reiner rem-Wert würde
           dort trotzdem unter dem Indicator liegen. Zwei Deklarationen
           (Fallback zuerst) nach demselben Muster wie die dvh/vh-Höhe bei
           .is-open weiter unten, falls ein Browser env() nicht kennt. */
        bottom: 1.25rem;
        bottom: max(env(safe-area-inset-bottom, 0px), 1.25rem);
        width: auto;
        max-width: none;
        justify-content: flex-end;
        /* Deckt den Zusammenfall auf Orb-Größe ab (siehe
           #companion-widget[data-pills-hidden='true'] weiter unten): links/
           Breite sollen sichtbar mit demselben 0.45s-Rhythmus wie der
           Pillen-Kollaps selbst (.companion-pills-Basisregel, PILLS_COLLAPSE_MS
           in widget.js) ein-/ausfahren statt hart zu springen. Wirkt nur in
           genau diesem Fall: right/bottom/justify-content ändern sich sonst
           nie, und .companion-morphing/.is-open setzen weiter unten ihre
           eigene, höher spezifische transition (id+class) bzw. lassen left
           unverändert bei 0.75rem — kein Zielkonflikt mit dem Öffnen/
           Schließen-Morph. */
        transition:
            left 0.45s var(--companion-ease, cubic-bezier(0.2, 0.7, 0.3, 1)),
            width 0.45s var(--companion-ease, cubic-bezier(0.2, 0.7, 0.3, 1));
    }

    /* Keine Fragen für die aktuell aktive Zone: setupZoneSwitching() in
       widget.js filtert Zonen ohne Einträge bereits aus
       switchableZoneElements heraus (Kommentar dort: "an eligible zone with
       0 entries would otherwise ... render an empty pills panel") — so eine
       Zone verhält sich für den Observer wie "keine Zone unter der
       Viewport-Mitte" und setPillsHidden(true) setzt bereits zuverlässig
       data-pills-hidden='true', wodurch .companion-pills selbst (Regel
       weiter oben, #companion-widget[data-pills-hidden='true']
       .companion-pills) auf max-width:0/opacity:0 kollabiert.
       OHNE diese Regel hier bliebe der BALKEN selbst trotzdem bei der
       Vollbreite/festem left aus der Basisregel oben stehen (anders als
       auf dem Desktop, wo width:max-content dem kollabierenden Kind
       automatisch folgt) — ein leerer dunkler Balken neben dem Orb, obwohl
       gar keine Pills mehr da sind. left:auto + width:max-content fallen
       auf dieselbe Content-Breiten-Logik wie der Desktop-Basiszustand
       zurück (siehe #companion-widget oben, außerhalb dieses Media
       Queries) — der Container schrumpft auf Orb-Größe zusammen, right/
       bottom bleiben unverändert aus der Basisregel. :not(.is-open):
       not(.companion-morphing) verhindert einen Widerspruch mit den
       Vollbreiten-Regeln für den offenen Chat weiter unten, falls
       data-pills-hidden ausnahmsweise während des Öffnens bestehen bleibt
       (gleiche Selektor-Spezifität wie .is-open/.companion-morphing dort,
       :not() schließt eine Kollision von vornherein aus, statt sich auf
       Regel-Reihenfolge zu verlassen). */
    #companion-widget[data-pills-hidden='true']:not(.is-open):not(.companion-morphing) {
        left: auto;
        width: max-content;
    }

    /* Pills scrollen horizontal statt die Leiste über den Rand zu drücken. */
    #companion-widget .companion-pills {
        overflow-x: auto;
        scrollbar-width: none;
        /* Etwas Luft rechts, damit die letzte Pill nicht am Orb klebt. */
        padding-right: 0.25rem;
        /* `flex-end` vom Desktop AUFHEBEN: Beim Überlaufen schiebt es den
           Inhalt nach LINKS aus dem Container — die erste Pill säße
           außerhalb und wäre nicht erreichbar. Mobil wird gewischt, also
           von links auffüllen. */
        justify-content: flex-start;
        /* Weiches statt hartes Kanten-Ende: der Text bleibt vollständig
           erreichbar (siehe Kommentar an .companion-pill direkt unten,
           NICHT verändert), aber am Rand blendet ein mask-image-Fade die
           letzte(n) Pill(s) sanft aus, statt sie hart am Container-Rand
           abzuschneiden (Nutzer-Report: abgeschnittener Text UND wirkte
           wie eine angeschnittene, nicht sauber geschlossene Umrandung).
           Gleiches Muster wie #companion-widget .companion-panel__scroll
           oben (--fade-top/-bottom dort) — hier horizontal, JS pflegt
           die beiden Custom Properties über data-fade-left/-right
           (siehe updatePillsScrollFade() in widget.js). 0px Default: ohne
           tatsächlichen Overflow (z. B. eine kurze Frage, die schon ganz
           reinpasst) darf am Rand nichts wegblenden. */
        --fade-left: 0px;
        --fade-right: 0px;
        mask-image: linear-gradient(
            to right,
            transparent 0,
            #000 var(--fade-left),
            #000 calc(100% - var(--fade-right)),
            transparent 100%
        );
        -webkit-mask-image: linear-gradient(
            to right,
            transparent 0,
            #000 var(--fade-left),
            #000 calc(100% - var(--fade-right)),
            transparent 100%
        );
    }

    #companion-widget .companion-pills[data-fade-left='true'] {
        --fade-left: 1.5rem;
    }

    #companion-widget .companion-pills[data-fade-right='true'] {
        --fade-right: 1.5rem;
    }

    #companion-widget .companion-pills::-webkit-scrollbar {
        display: none;
    }

    /* Mobil wird gewischt statt gekürzt — eine Ellipse auf einer scrollbaren
       Reihe würde Text verstecken, den man erreichen könnte. Der weiche
       Rand-Fade oben (mask-image auf .companion-pills) ersetzt hier
       NICHTS davon — er blendet nur die Optik am Rand aus, versteckt aber
       keinen erreichbaren Text (Scroll bleibt unverändert möglich). */
    #companion-widget .companion-pills .companion-pill {
        flex: 0 0 auto;
        overflow: visible;
        text-overflow: clip;
        padding: 0.5rem 1rem;
        font-size: 0.8125rem;
    }

    /* Kontakt-Karte im Chat (buildContactCard() in widget.js, Avatar + Name/
       Rolle + Mail-/Telefon-Icon-Reihe + Termin-Button): die Desktop-
       Deckelung max-width:85% (Basisregel, siehe #companion-widget
       .companion-contact-card oben) macht die Karte auf kleineren Handys
       unnötig schmal — hier bewusst NUR max-width/width überschrieben,
       align-self/Innenaufbau bleiben unangetastet (kein Redesign).
       Card-Padding (Abstand zu den Kanten) zugleich grosszügiger: dieselbe
       var(--gap-s) wie an vergleichbarer Stelle bereits die Buchungs-
       Embed-Karte (siehe .companion-booking-view oben, deren gap/padding)
       — statt eines neu erfundenen Werts. */
    #companion-widget .companion-contact-card {
        max-width: 100%;
        width: 100%;
        padding: var(--gap-s, 1.25rem);
    }

    /* KEIN mobiler Override mehr für .companion-contact-card__actions'
       gap hier: die Icon-Umstellung (siehe Basisregel weiter oben, gap
       jetzt 0.5rem statt der früheren Text-Chip-Reihe) braucht auf Mobil
       keinen zusätzlichen Abstand mehr — zwei/drei schmale Kreis-Buttons
       drängen sich bei 0.5rem nicht, anders als die früheren breiten
       Text-Chips, für die dieser Override ursprünglich eingeführt wurde.
       Auch für den Termin-Button (.companion-contact-card__cta) gilt seit
       dem Icon-Umbau KEIN eigener mobiler Override mehr -- er sitzt jetzt
       selbst IN dieser Reihe (buildContactCard() in widget.js) statt
       separat darunter, der frühere margin-top-Override hier wäre also
       nur noch totes CSS auf einem Element, das seine Position gar nicht
       mehr über margin-top bezieht. */

    /* Frage-Pills im offenen Chat (.companion-panel__suggestions, "mehrere
       untereinander stehende" Followups/"Meintest du") nutzen mobil nicht
       die volle Breite: die Basisregel oben setzt align-items:flex-start,
       die Pills sind Buttons ohne eigene width-Vorgabe und schrumpfen daher
       auf ihre Textbreite (Cross-Achse einer flex-direction:column-Reihe).
       stretch lässt sie stattdessen die volle Container-Breite ausfüllen —
       .companion-pill setzt bereits max-width:100%/text-align:left (Regel
       weiter oben, unverändert), das passt unmittelbar dazu. Betrifft NUR
       diesen Basisfall: die spezifischeren :has(...)- und
       .companion-topic-pills-Varianten (beide oben, höhere Spezifität durch
       zusätzlichen Selektor-Teil) setzen ihr eigenes align-items und werden
       davon nicht überschrieben. Von der Breiten-Morph-Logik der Balken-
       Pills (--companion-pill-w/.companion-pills-width-morphing/
       pillsWidthMorphEligible() in widget.js) komplett unabhängig — andere
       Klasse, anderer Container, dort geht es um die BALKEN-Breite beim
       Zonenwechsel, nicht um diese gestapelte Liste im offenen Panel. */
    #companion-widget .companion-panel__suggestions {
        align-items: stretch;
    }

    /* Ruhezustand offen: volle Breite abzüglich der 0.75rem-Ränder von
       oben statt der Desktop-Formel (min(520px, 100vw - 2*gap-m)) — sonst
       bliebe mehr Rand stehen als beim Balken. Höhe entsprechend fast
       bildschirmfüllend (0.75rem oben UND unten, symmetrisch zu den
       Seitenrändern) statt der Desktop-Formel min(82vh,780px) — auf einem
       Handy-Viewport ist ohnehin kein Platz für "schwebend", der Chat darf
       hier die ganze nutzbare Fläche haben. dvh mit vh-Fallback für
       Browser ohne dynamische Viewport-Einheiten (iOS-Adressleiste zieht
       sich sonst mit 100vh in eine falsche, zu große Höhe ein).
       .companion-morphing braucht hier KEIN Pendant: seine Breite/Höhe
       kommt als Pixelwert von JS (measureOpenRect misst live gegen dieses
       Media Query), passt sich also automatisch an. */
    #companion-widget.is-open {
        left: 0.75rem;
        right: 0.75rem;
        width: auto;
        /* --companion-user-h (Griff-Strich, siehe oben) gilt auch hier —
           der Handle bleibt auf Mobil aktiv, der Fallback ist nur die
           fast-Vollhöhen-Formel, kein zweites, eigenständiges Limit. */
        height: var(--companion-user-h, calc(100vh - 1.5rem));
        height: var(--companion-user-h, calc(100dvh - 1.5rem));
    }
}
