/*
 * landing.css — Landingpage-specific styles.
 * Loaded after chrome.css. Class names (.signup, .brand-mark) differ from
 * countdown's .cd-* prefixed names so the two pages can't collide.
 */


    * { margin: 0; padding: 0; box-sizing: border-box; }

    /* Navigation rules live in /chrome.css. */

    /* === Sections ===
       z-index scale (top-level overlays must NOT live inside a stacking
       context that ceilings them — keep them at body level):
         100  nav.top-nav (sticky header)
         200  cart-overlay (scrim)
         201  cart-drawer (off-canvas; in-chat adds use openDrawer:false +
              the FAB hides while the drawer is open — chat surfaces sit
              higher and would otherwise occlude checkout)
         300  checkout-overlay (loading state)
         1000 consent-banner (outranks page content; every chat entry point
              is consent-gated — open() nudges instead — so the overlay/FAB
              never paint over it)
         1090 fab-wrap (chat keep-in-view dock)
         1100 chat-scrim / 1101 chat-overlay (immersive chat modal)
         1150 survey-card (operator-placed; floats over the chat overlay)
         9000 logo-customizer-overlay (admin/dev tool)
       Do NOT put z-index on .page — it would trap nested overlays
       (banner, survey, cart-drawer, checkout) below the chat overlay + FAB
       which live outside .page. */
    .page {
      position: relative;
    }

    section {
      padding: var(--section-pad);
    }

    /* === Hero === */
    .hero {
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      text-align: center;
      padding-top: 5rem;
      background: var(--off-white);
    }

    /* Hero brand mark (design handoff v0.14.4): 104px desktop / 76px mobile
       (was 120), a gentle 6s float, and honest trigger affordances — it IS
       the customizer's triple-click/Enter target, so it gets cursor:pointer,
       a focus ring and rounded hit shape. */
    .brand-mark {
      width: 104px;
      height: 104px;
      margin-bottom: 1.1rem;
      display: flex;
      align-items: center;
      justify-content: center;
      -webkit-user-select: none;
      user-select: none;
      cursor: pointer;
      border-radius: 14px;
      /* Halo anchor: the customized glow paints as a ::before underlay
         (CSS filters mis-render on WebKit — see logo-customizer.js).
         isolation guarantees the z-index:-1 halo stays INSIDE this box's
         stacking context (above the hero background) even under
         prefers-reduced-motion, where the float animation that would
         otherwise create the context is off. */
      position: relative;
      isolation: isolate;
      animation: mark-float 6s ease-in-out infinite;
    }
    .brand-mark::before {
      content: '';
      position: absolute;
      z-index: -1;
      inset: calc(-1 * var(--lc-glow-size, 0px));
      border-radius: 50%;
      background: radial-gradient(closest-side, var(--lc-glow-color, transparent), transparent 72%);
      pointer-events: none;
    }
    .brand-mark:focus-visible {
      outline: 2px solid var(--blue);
      outline-offset: 4px;
    }
    @keyframes mark-float {
      0%, 100% { transform: translateY(0); }
      50% { transform: translateY(-8px); }
    }
    @media (max-width: 640px) {
      .brand-mark { width: 76px; height: 76px; margin-bottom: 0.7rem; }
    }
    @media (prefers-reduced-motion: reduce) {
      .brand-mark { animation: none; }
    }

    .brand-mark svg {
      width: 100%;
      height: 100%;
      /* Custom glow lands on the inner path (WebKit bbox-artifact fix) and
         would clip at the svg viewport without this. */
      overflow: visible;
      filter: drop-shadow(0 0 18px rgba(70, 127, 247, 0.25));
    }

    /* ── Logo Customizer Easter Egg ──────────────────────────────
     *  State: CLOSED (default) ──triple-click──▶ OPEN (overlay)
     *         OPEN ──Escape / click-outside / Apply / Reset──▶ CLOSED
     *  Persistence: localStorage('olympianer-logo-config')
     *  On load: read config → apply to #flow gradient or use default
     * ──────────────────────────────────────────────────────────── */
    /* Light DS-token panel (design handoff v0.14.4 — was a dark navy card):
       soft navy scrim, centered white card, gradient preset swatches with
       the name overlaid, direction GLYPH buttons (was a <select>), Tempo +
       Glow sliders with live values, "So bleibt's" / "Zurücksetzen". */
    .logo-customizer-overlay {
      position: fixed;
      inset: 0;
      background: rgba(28, 36, 75, 0.28);
      backdrop-filter: blur(2px);
      -webkit-backdrop-filter: blur(2px);
      z-index: 9000;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 1rem;
    }

    .logo-customizer-panel {
      width: min(370px, 100%);
      max-height: calc(100vh - 2rem);
      overflow-y: auto;
      background: var(--white);
      border-radius: var(--radius-panel);
      box-shadow: var(--shadow-modal);
      padding: 1.1rem 1.2rem 1.2rem;
      display: flex;
      flex-direction: column;
      gap: 0.85rem;
      color: var(--navy);
    }

    .lc-head {
      display: flex;
      align-items: center;
      gap: 0.6rem;
    }
    .lc-title {
      font-size: 0.95rem;
      font-weight: 600;
      color: var(--navy);
      margin: 0;
    }
    .lc-close {
      margin-left: auto;
      width: 36px;
      height: 36px;
      border-radius: 50%;
      border: none;
      background: var(--off-white);
      color: var(--gray);
      font-size: 1.25rem;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.15s;
      flex-shrink: 0;
    }
    .lc-close:hover {
      background: var(--blue-light);
      color: var(--navy);
    }

    .lc-row {
      display: flex;
      flex-direction: column;
      gap: 0.45rem;
    }
    .logo-customizer-panel label {
      display: block;
      font-size: 0.64rem;
      font-weight: 600;
      letter-spacing: 0.14em;
      text-transform: uppercase;
      color: var(--gray);
    }

    .lc-preset-grid {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      gap: 0.45rem;
    }
    /* Gradient swatch — the preset's own gradient is the button face,
       its name overlaid (inline :style carries the gradient). */
    .lc-preset-btn {
      position: relative;
      height: 40px;
      border-radius: 10px;
      border: 2px solid transparent;
      padding: 0;
      cursor: pointer;
      overflow: hidden;
      transition: transform 0.15s, border-color 0.15s, box-shadow 0.15s;
    }
    .lc-preset-btn:hover { transform: var(--lift); }
    .lc-preset-btn:focus-visible {
      outline: 2px solid var(--blue);
      outline-offset: 2px;
    }
    .lc-preset-btn.active {
      border-color: var(--navy);
      box-shadow: 0 0 0 2px var(--white) inset;
    }
    .lc-preset-btn span {
      position: absolute;
      inset: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: var(--font-sans);
      font-size: 0.7rem;
      font-weight: 600;
      color: #fff;
      text-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
      letter-spacing: 0.03em;
    }

    .lc-dirs {
      display: flex;
      gap: 0.4rem;
    }
    .lc-dirs button {
      width: 40px;
      height: 40px;
      border-radius: 10px;
      border: 1px solid var(--border);
      background: var(--white);
      color: var(--navy);
      font-size: 1.05rem;
      cursor: pointer;
      transition: all 0.15s;
    }
    .lc-dirs button:hover {
      border-color: var(--blue);
      color: var(--blue);
    }
    .lc-dirs button:focus-visible {
      outline: 2px solid var(--blue);
      outline-offset: 2px;
    }
    .lc-dirs button[aria-pressed="true"] {
      background: var(--blue);
      border-color: var(--blue);
      color: var(--white);
    }

    .lc-row input[type="range"] {
      width: 100%;
      accent-color: var(--blue);
      cursor: pointer;
    }
    /* Slider endpoint words (Langsam→Schnell / Aus→Max) — friendlier than
       the old numeric s/px readouts (manual-review finding #6, v0.14.5). */
    .lc-scale {
      display: flex;
      justify-content: space-between;
      font-size: 0.66rem;
      font-weight: 500;
      color: var(--gray);
      margin-top: -0.15rem;
    }

    .lc-actions {
      display: flex;
      gap: 0.5rem;
      margin-top: 0.1rem;
    }
    .lc-btn-apply {
      flex: 1;
      min-height: 44px;
      border: none;
      border-radius: var(--radius-pill);
      background: var(--blue);
      color: var(--white);
      font-family: var(--font-sans);
      font-weight: 600;
      font-size: 0.85rem;
      cursor: pointer;
      transition: background 0.3s;
    }
    .lc-btn-apply:hover { background: var(--blue-hover); }
    .lc-btn-reset {
      min-height: 44px;
      padding: 0 1.1rem;
      border: 1px solid var(--border);
      border-radius: var(--radius-pill);
      background: var(--white);
      color: var(--gray);
      font-family: var(--font-sans);
      font-weight: 600;
      font-size: 0.82rem;
      cursor: pointer;
      transition: all 0.15s;
    }
    .lc-btn-reset:hover {
      border-color: var(--blue);
      color: var(--blue-text);
    }

    .no1 {
      font-size: 0.8rem;
      font-weight: 500;
      letter-spacing: 0.3em;
      text-transform: uppercase;
      color: var(--blue);
      margin-bottom: 0.75rem;
    }

    h1 {
      font-size: clamp(2.8rem, 10vw, 5rem);
      font-weight: 700;
      letter-spacing: 0.05em;
      text-transform: uppercase;
      line-height: 1;
      margin-bottom: 0.5rem;
      color: var(--navy);
    }

    .persona {
      font-size: clamp(0.8rem, 2vw, 1rem);
      font-weight: 400;
      letter-spacing: 0.15em;
      color: var(--gray);
      margin-bottom: 0.75rem;
    }

    .tagline {
      font-size: clamp(0.8rem, 2vw, 1rem);
      font-weight: 400;
      letter-spacing: 0.15em;
      color: var(--gray);   /* BUG-10: --gray-light (~2.5:1) fails WCAG AA on off-white; --gray ~4.8:1 */
      margin-bottom: 1.25rem;   /* 1.1: tighter S0 stack */
    }

    .divider {
      width: 50px;
      height: 3px;
      background: var(--blue);
      margin: 0 auto 1.25rem;   /* 1.1: tighter S0 stack */
      border-radius: 2px;
    }

    .hero-message {
      font-size: clamp(1rem, 2.5vw, 1.2rem);
      font-weight: 300;
      color: var(--gray);
      max-width: 520px;
      line-height: 1.8;
      margin-bottom: 2.5rem;
      font-style: italic;
      min-height: 3.8em;
      transition: opacity 0.5s ease;
    }

    .hero-message strong {
      color: var(--navy);
      font-weight: 500;
    }

    .hero-cta {
      display: inline-block;
      padding: 0.95rem 2.8rem;
      background: var(--blue);
      color: var(--white);
      font-size: 0.95rem;
      font-weight: 600;
      letter-spacing: 0.05em;
      text-decoration: none;
      border-radius: 50px;
      transition: all 0.3s;
      border: 2px solid var(--blue);
    }

    .hero-cta:hover {
      background: var(--blue-hover);
      border-color: var(--blue-hover);
    }

    /* ─── First-view launch announcement card ───
       Lives inside section.hero, outside the teaser state divs, so a fresh AND
       a returning fan sees it. Replaced the old announcement pill + 6-tile
       mini rail: one compact card with a single packshot, no prices, so
       nothing in the hero needs a PAngV note. The hero is flex-centred with
       min-height:100dvh, so this stays deliberately small — it must never push
       the chat composer off-screen. */
    /* v0.26.2: the card HUGS its content instead of being a fixed 340px box.
       The hero is a centred column flex container, so `width: fit-content`
       sizes the card to max-content (the headline never wraps on desktop) and
       `max-width: 100%` keeps it inside the hero's padding on narrow phones.
       Nothing here is a magic pixel number, so a copy change re-fits itself. */
    .hero-launch-card {
      display: flex;
      align-items: center;
      /* v0.26.3: 18px gap / 24px trailing padding. The card shrink-wraps its
         content (width: fit-content), so these two values ARE the horizontal
         breathing room — there is no spare width to absorb a tight setting.
         Tuned between two failures: a fixed 340px width left dead space after
         the headline, and pure fit-content with 14/15px felt cramped. */
      gap: 1.15rem;
      width: fit-content;
      max-width: 100%;
      /* v0.26.6: 1.1rem -> 1.75rem. The card and the greeting below it are two
         separate invitations (shop / chat) and were reading as one stack. The
         two tightened overrides further down (short viewports, consent-pending)
         stay at 0.6rem on purpose — they exist to keep the composer above the
         fold on SE-class devices, and fold pressure outranks breathing room. */
      margin: 0 auto 1.75rem;
      padding: 0.55rem 1.5rem 0.55rem 0.6rem;
      background: var(--white);
      border: 1px solid var(--border);
      border-radius: var(--radius-card);
      box-shadow: var(--shadow-card);
      text-align: left;
      text-decoration: none;
      transition: transform var(--transition-fast) var(--ease),
                  border-color var(--transition-fast) var(--ease);
    }
    /* v0.26.3: the thumbnail slot is a positioned WRAPPER holding two stacked
       image layers, so heroLaunchLoop() can cross-fade instead of fading a
       single img out through the card background (the white flash). The
       wrapper owns every box property the single <img> used to own — same
       56px width, same 4/5 ratio, same radius and background — so the card
       (width: fit-content) keeps its exact layout. */
    .hlc-shot {
      position: relative;
      display: block;
      flex: 0 0 auto;
      width: 56px;
      aspect-ratio: 4 / 5;
      border-radius: calc(var(--radius-card) - 4px);
      background: var(--off-white);
      overflow: hidden;
    }
    /* object-fit: contain — packshots must stay fully visible (no cover crops
       on product imagery anywhere in this repo). */
    .hero-launch-card img {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      object-fit: contain;
      /* Decorative layers inside an <a>: they must never be the click target
         nor affect the focus ring. */
      pointer-events: none;
      /* ONLY opacity transitions — nothing here can move the card's layout. */
      transition: opacity 700ms var(--ease);
    }
    .hlc-text {
      display: flex;
      flex-direction: column;
      gap: 0.12rem;
      min-width: 0;
    }
    .hlc-line {
      font-size: 0.85rem;
      font-weight: 600;
      line-height: 1.25;
      color: var(--navy);
    }
    /* --blue-text is the AA-safe variant for blue TEXT on a light surface
       (--blue is a FILL color and fails contrast here). */
    .hlc-cta {
      font-size: 0.75rem;
      font-weight: 600;
      line-height: 1.2;
      color: var(--blue-text);
    }
    .hero-launch-card:hover {
      transform: translateY(-2px);
      border-color: var(--blue);
    }

    /* Short phones (SE-class, 390x667). v0.27.0 measured the whole hero here
       instead of trimming whatever looked biggest, and the result was the
       opposite of the standing assumption.
       The note in TODOS said the fix meant shrinking `.resume-card` or the brand
       block. Measured at 390x667 with a returning fan, the actual budget was:

         hero padding-top                80px      <- pure whitespace
         brand-mark + margin             87px
         no1/h1/persona/tagline/divider 168px
         launch card + margin            80px
         welcome + gap                   69px
         .resume-card                   231px
         hero padding-bottom             48px      <- pure whitespace

       128px of whitespace in the two paddings alone, against a resume card that
       overhung the fold by ~103px. So no content had to be cut: the target is
       not "the hero box fits" (it never will at 667px) but "the fan can SEE the
       resume CTA without scrolling", and reclaiming padding gets there.

       That reframing is the point worth keeping. `min-height: 100dvh` + flex
       centring makes the hero LOOK like a fixed budget being overrun, which
       invites cutting content; the overrun was mostly decorative space.

       Everything below is proportional tightening, not removal — every element
       that renders on a tall phone still renders here. The launch card keeps
       its place too: it is the one thing that must survive every hero state. */
    @media (max-height: 700px) and (max-width: 640px) {
      /* The two paddings, which cost more than the brand block and the tagline
         combined. The hero already begins below the nav (nav is in flow), so
         padding-top here is decoration, not clearance. */
      .hero { padding-top: 1.25rem; padding-bottom: 1.25rem; }

      /* Continues the 120 -> 104 -> 76 progression the design handoff started,
         one step further for the shortest screens only. */
      .hero .brand-mark { width: 60px; height: 60px; margin-bottom: 0.5rem; }

      /* Rhythm between the four brand lines, halved. The lines themselves are
         untouched — nothing is hidden, so the hero reads the same, tighter. */
      .hero .no1 { margin-bottom: 0.375rem; }
      .hero h1 { margin-bottom: 0.375rem; }
      .hero .persona { margin-bottom: 0.375rem; }
      .hero .tagline { margin-bottom: 0.625rem; }
      .hero .divider { margin-bottom: 0.75rem; }

      /* The returning-fan greeting wraps to two lines at 390px; a hair smaller
         keeps it at two rather than pushing to three as the card tightens. */
      .hero .teaser-welcome { font-size: 0.95rem; }

      .hero-launch-card {
        margin-bottom: 0.6rem;
        padding: 0.4rem 0.7rem 0.4rem 0.45rem;
        gap: 0.55rem;
      }
      .hlc-shot { width: 44px; }
      .hlc-line { font-size: 0.8rem; }
      .hlc-cta { font-size: 0.7rem; }
    }

    @media (prefers-reduced-motion: reduce) {
      .hero-launch-card,
      .hero-launch-card:hover { transform: none; transition: none; }
      /* The product loop is also disabled in JS (heroLaunchLoop() bails before
         it starts a timer or fetches), so this only kills the fade property. */
      .hero-launch-card img { transition: none; }
    }

    /* === Section Headers === */
    .section-label {
      font-size: 0.8rem;
      font-weight: 600;
      letter-spacing: 0.2em;
      text-transform: uppercase;
      color: var(--blue);
      margin-bottom: 0.5rem;
    }

    h2 {
      font-size: clamp(1.8rem, 5vw, 2.8rem);
      font-weight: 700;
      color: var(--navy);
      margin-bottom: 1rem;
    }

    .section-desc {
      font-size: 1rem;
      font-weight: 300;
      color: var(--gray);
      max-width: 620px;
      line-height: 1.8;
      /* v0.17.0: every section's content column is centered (was only
         .social/.newsletter via their section-level rules; two pages used
         inline auto-margin styles as a workaround — removed). */
      margin-left: auto;
      margin-right: auto;
      margin-bottom: 2.5rem;
    }

    /* === Products === */
    .products {
      background: var(--white);
    }

    /* v0.17.0: center the section header block like .social/.newsletter do —
       auto-margining only the grids left labels + headings hugging the left
       padding edge (~190px asymmetry at 1440px). Card-internal text keeps
       its own alignment. */
    .products > .section-label, .products > h2,
    .movement > .section-label, .movement > h2 {
      text-align: center;
    }
    .products > .section-desc, .movement > .section-desc {
      text-align: center;
    }

    .product-grid {
      display: grid;
      /* v0.20.1: self-regulating column count, no extra breakpoint —
         the minmax minimum + the cap ARE the breakpoints:
           1 column  below ~645px viewport,
           2 columns from ~645px  (2×280 + 32 = 592 container),
           3 columns from ~985px  (3×280 + 64 = 904 container).
         280px keeps 2-up cards ≤436px (the 960-cap 2-up cards had grown
         too tall at 4:5), and 6 cards fill 3-up as two clean rows.
         min(100%, 280px) guards ~340px phones against overflow. */
      grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
      gap: 2rem;
      max-width: 1200px;
      margin-left: auto;
      margin-right: auto;
    }

    .product-card {
      /* Flex column so cards stretch to tallest sibling + .product-info can
         use margin-top: auto to bottom-anchor the CTA group. */
      display: flex;
      flex-direction: column;
      border: 1px solid var(--border);
      border-radius: 12px;
      overflow: hidden;
      transition: box-shadow 0.3s, transform 0.3s;
      background: var(--white);
    }

    .product-card:hover {
      box-shadow: 0 8px 30px rgba(70, 127, 247, 0.12);
      transform: translateY(-4px);
    }

    .product-img {
      width: 100%;
      /* v0.20.0: 4:5 — the masters' native ratio. Square cover-cropped the
         packshots (and every shooting frame); portrait shows the full frame
         on the grid, the chat tiles and the PDP related tiles alike. */
      aspect-ratio: 4 / 5;
      background: var(--off-white);
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .product-img img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    .product-info {
      display: flex;
      flex-direction: column;
      flex: 1;
      padding: 1.25rem;
    }
    /* Equal-height card pattern: the first matching action element absorbs
       excess vertical space via margin-top: auto, pushing it + everything
       after to the bottom of .product-info. */
    .product-info > .variant-pills,
    .product-info > .product-actions,
    .product-info > .product-btn {
      margin-top: auto;
    }

    .product-info .name {
      font-size: 1rem;
      font-weight: 600;
      color: var(--navy);
      margin-bottom: 0.4rem;
      line-height: 1.4;
    }

    .product-info .price {
      font-size: 1.25rem;
      font-weight: 700;
      color: var(--blue);
      margin-bottom: 0.4rem;
    }

    .product-info .detail {
      font-size: 0.85rem;
      font-weight: 300;
      color: var(--gray);
      line-height: 1.6;
      margin-bottom: 1rem;
      /* v0.17.0: cards render the uniform catalog teaser; the 2-line clamp
         is a safety net so a drifting teaser can't unbalance card heights. */
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow: hidden;
    }

    .product-btn {
      display: block;
      width: 100%;
      padding: 0.6rem 1.4rem;
      background: var(--blue);
      border: 1px solid var(--blue);
      border-radius: 50px;
      color: var(--white);
      font-size: 0.8rem;
      font-weight: 600;
      letter-spacing: 0.05em;
      text-align: center;
      text-decoration: none;
      cursor: pointer;
      transition: all 0.3s;
    }
    .product-btn:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }

    .product-btn:hover {
      background: var(--blue-hover);
      border-color: var(--blue-hover);
    }

    /* Per-product detail link below .product-btn on grid + chat cards. */
    .product-detail-link {
      display: block;
      margin-top: 0.6rem;
      text-align: center;
      font-size: 0.78rem;
      font-weight: 500;
      color: var(--gray);
      text-decoration: none;
      letter-spacing: 0.02em;
      transition: color 0.2s;
    }
    .product-detail-link:hover {
      color: var(--blue);
      text-decoration: underline;
      text-underline-offset: 3px;
    }

    /* === Movement / About === */
    .movement {
      background: var(--off-white);
    }

    .movement-grid {
      display: grid;
      /* v0.22.1: 280px minimum caps the columns at 3 under the 1000px cap
         (4×280 + 3×32 = 1216 > 1000) — the six cards always land as 3×2,
         then 2×3 below ~660px viewport, then single column. The old 220px
         minimum let a 4+2 orphan row form at full width. */
      grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
      gap: 2rem;
      /* v0.20.1: 1000px — subordinate to the 1200px product grid above it
         but no longer pinched next to it (was 800). */
      max-width: 1000px;
      margin-left: auto;
      margin-right: auto;
    }

    .movement-item {
      padding: 1.5rem;
      border-left: 3px solid var(--blue);
      background: var(--white);
      border-radius: 0 8px 8px 0;
    }

    .movement-item h3 {
      font-size: 1.1rem;
      font-weight: 600;
      color: var(--navy);
      margin-bottom: 0.5rem;
    }

    .movement-item p {
      font-size: 0.9rem;
      font-weight: 300;
      color: var(--gray);
      line-height: 1.8;
    }

    /* Social section styles live in chrome.css (sitewide canonical). */

    /* === Newsletter === */
    .newsletter {
      background: var(--navy);
      text-align: center;
      display: flex;
      flex-direction: column;
      align-items: center;
    }

    .newsletter h2,
    .newsletter .section-label { color: var(--white); }
    .newsletter .section-label { opacity: 0.7; }
    .newsletter .section-desc { color: rgba(255,255,255,0.6); }

    .signup-form {
      max-width: 460px;
      width: 100%;
    }

    .signup {
      display: flex;
      gap: 0;
    }

    .signup input {
      flex: 1;
      padding: 0.9rem 1.2rem;
      background: rgba(255,255,255,0.1);
      border: 1px solid rgba(255,255,255,0.2);
      border-right: none;
      border-radius: 50px 0 0 50px;
      color: var(--white);
      font-family: 'Poppins', sans-serif;
      font-size: 0.9rem;
      font-weight: 300;
      outline: none;
      transition: border-color 0.3s;
    }

    .signup input::placeholder { color: rgba(255,255,255,0.4); }
    .signup input:focus { border-color: var(--blue); }

    .signup button {
      padding: 0.9rem 1.8rem;
      background: var(--blue);
      border: 1px solid var(--blue);
      border-radius: 0 50px 50px 0;
      color: var(--white);
      font-size: 0.95rem;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.3s;
      white-space: nowrap;
    }

    .signup button:hover {
      background: var(--blue-hover);
      border-color: var(--blue-hover);
    }

    .signup-success {
      color: var(--blue);
      font-size: 1rem;
      font-weight: 400;
      padding: 0.9rem;
    }

    /*
     * === Chat Stage (v0.14.2 — Claude Design handoff) ===
     *
     * The hero rests in a TEASER: welcome -> composer -> chips for fresh
     * fans, or a resume card for returning ones. The composer is LOCKED
     * (visible, tappable -> nudges the cookie banner) until consent settles.
     * Sending opens the immersive .chat-overlay (fixed scrim + near-
     * fullscreen modal); scrolling past the hero reveals the floating
     * .fab-wrap dock. Those blocks live at the END of this file
     * ("CHAT-STAGE INTERACTION STATES"). The shared message/bubble/rail
     * styles below render INSIDE the overlay body.
     */

    .chat-new {
      margin-left: auto;
      background: none;
      border: 1px solid var(--border);
      border-radius: 6px;
      color: var(--gray);
      cursor: pointer;
      font-family: 'Poppins', sans-serif;
      font-size: 0.65rem;
      font-weight: 500;
      padding: 0.25rem 0.5rem;
      transition: all 0.2s;
    }

    .chat-new:hover { border-color: var(--blue); color: var(--blue); }

    .msg a {
      color: var(--blue);
      text-decoration: underline;
      text-underline-offset: 2px;
    }

    .msg.user a { color: var(--white); }

    .msg-row {
      display: flex;
      flex-direction: column;
      max-width: 85%;
      min-width: 0;   /* flex min-size floor — a wide card rail must shrink, not blow out the transcript */
    }
    .msg-row.bot { align-self: flex-start; align-items: flex-start; }
    .msg-row.user { align-self: flex-end; align-items: flex-end; }

    .msg {
      padding: 0.75rem 1rem;
      border-radius: 12px;
      font-size: 0.85rem;
      font-weight: 400;
      line-height: 1.6;
    }

    .msg.bot {
      background: var(--blue-light);
      border: 1px solid rgba(70, 127, 247, 0.15);
      color: var(--navy);
    }

    .msg.user {
      /* --blue-text, not --blue: white text on #467ff7 is 3.72:1 (fails AA
         normal); on #2e5cd0 it is ~5.7:1 (tokens.css contrast mandate). */
      background: var(--blue-text);
      color: var(--white);
    }

    /* === Chat multi-card rail (v0.18.0: full-bleed slider) ===
       ONE idiom for every count > 1: a scroll-snap slider that bleeds
       edge-to-edge across the chat window. The wrap swallows the transcript
       padding with negative margins; the rail re-adds it as its own padding,
       so cards START aligned with the bubble's left edge, the scroll window
       extends to the chat's right edge, and — because overflow clips at the
       padding box, not the content box — cards scrolled leftward stay
       visible until they reach the window's left edge.
         .rail-1                      single card, bubble-aligned, no scroll
         .rail-2/3/4/grid (2+ cards)  the full-bleed slider */
    .chat-card-rail {
      display: block;
      margin-top: 0.5rem;
    }
    .chat-card-rail .product-card { margin-top: 0; }

    /* Single card — page-tile width (not full-bubble), aligned left.
       The padding mirrors the wrap's negative margin so alignment holds. */
    .chat-card-rail.rail-1 { padding-inline: var(--chat-pad-x, 0px); }
    .chat-card-rail.rail-1 .product-card,
    .chat-card-rail.rail-1 .sc-card {
      width: 100%;
      max-width: 240px;
    }
    /* v0.30.13 (operator): product teasers in the chat are 180px wide, 4:5
       kept — ~290px tall instead of ~390px, so a reply plus its rail fits a
       phone with the keyboard down. Social cards stay 240px (their own ruling). */
    .chat-card-rail.rail-1 .product-card { max-width: 180px; }

    /* 2+ cards — full-bleed scroll-snap slider */
    .chat-card-rail.rail-2,
    .chat-card-rail.rail-3,
    .chat-card-rail.rail-4,
    .chat-card-rail.rail-grid {
      display: flex;
      flex-direction: row;
      gap: 0.5rem;
      overflow-x: auto;
      scroll-snap-type: x mandatory;
      scroll-behavior: smooth;
      scrollbar-width: thin;
      padding: 0 var(--chat-pad-x, 0.25rem) 0.25rem;
      scroll-padding-left: var(--chat-pad-x, 0.25rem);
    }
    .chat-card-rail.rail-2 .product-card,
    .chat-card-rail.rail-3 .product-card,
    .chat-card-rail.rail-4 .product-card,
    .chat-card-rail.rail-grid .product-card,
    .chat-card-rail.rail-2 .sc-card,
    .chat-card-rail.rail-3 .sc-card,
    .chat-card-rail.rail-4 .sc-card,
    .chat-card-rail.rail-grid .sc-card {
      flex: 0 0 240px;
      scroll-snap-align: start;
      min-width: 0;
    }
    .chat-card-rail.rail-2 .product-card,
    .chat-card-rail.rail-3 .product-card,
    .chat-card-rail.rail-4 .product-card,
    .chat-card-rail.rail-grid .product-card { flex-basis: 180px; }

    /* === Rail navigation arrows ===
       Overlaid on either side of the rail, vertically centered (carousel
       idiom — manual-review finding #2, v0.14.5; they used to sit in a row
       BELOW the rail). display:contents lets the buttons position against
       the relative wrap. Buttons are 44×44 (WCAG 2.1 AA touch target). */
    /* v0.18.0: the wrap bleeds across the transcript padding — full chat
       width. Rail rows always use the full transcript width (was ≤640px
       only) so the bleed has room at every breakpoint. */
    /* Explicit width: the msg-row flex column aligns items flex-start, so a
       block wrap would shrink-wrap to the rail's max-content instead of
       filling the row + bleed. */
    .chat-card-rail-wrap {
      position: relative;
      width: calc(100% + 2 * var(--chat-pad-x, 0px));
      margin-inline: calc(-1 * var(--chat-pad-x, 0px));
    }
    .chat-overlay .msg-row:has(.chat-card-rail-wrap) { max-width: 100%; width: 100%; }
    .chat-card-rail-nav { display: none; }
    .chat-card-rail-wrap.has-arrows .chat-card-rail-nav { display: contents; }
    /* v0.14.7: 2-card rails get arrows at EVERY width (desktop included) —
       when the pair fits, both arrows simply sit disabled; the affordance
       stays consistent across breakpoints. */
    .chat-card-rail-wrap[data-count="2"] .chat-card-rail-nav { display: contents; }
    .chat-card-rail-arrow {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      z-index: 2;
      width: 44px;
      height: 44px;
      border-radius: 50%;
      border: 1px solid var(--border);
      background: rgba(255, 255, 255, 0.94);
      box-shadow: 0 6px 18px rgba(28, 36, 75, 0.18);
      color: var(--navy);
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, opacity 0.2s;
    }
    /* v0.18.0: the wrap spans the full chat width, so the arrows sit just
       inside the window edges (the transcript's overflow-x clip would cut
       off anything hanging outside). */
    .chat-card-rail-arrow:first-of-type { left: 6px; }
    .chat-card-rail-arrow:last-of-type { right: 6px; }
    .chat-card-rail-arrow:hover:not(:disabled) {
      background: var(--off-white);
      border-color: var(--blue);
      color: var(--blue);
    }
    .chat-card-rail-arrow:disabled {
      opacity: 0.35;
      cursor: not-allowed;
    }
    .chat-card-rail-arrow svg {
      width: 18px;
      height: 18px;
      fill: currentColor;
      pointer-events: none;
    }
    @media (prefers-reduced-motion: reduce) {
      .chat-card-rail-arrow { transition: none; }
    }

    /* === Chat product teaser tiles (v0.18.0) — the PDP "Mehr vom Drop"
       idiom: image + name + price, the whole tile is a link to the product
       page. Inherits .product-card chrome; sizing comes from the rail. === */
    .chat-product-tile { text-decoration: none; color: inherit; }
    .chat-product-tile .product-img { position: relative; }
    .chat-product-tile .product-info { padding: 0.6rem 0.7rem; }
    .chat-product-tile .name { font-size: 0.85rem; }
    .chat-product-tile:hover { box-shadow: 0 8px 24px rgba(28, 36, 75, 0.10); }

    /* === Social link cards in CHAT (v0.14.13) — OG-hydrated, rail idiom ===
       .sc-card, NOT .social-card: that class is taken by the page's Social
       SECTION tiles. An <a> card: thumbnail (16:9, platform-icon fallback on
       a blue-light field when no/expired OG image), platform row, title. */
    .sc-card {
      display: flex;
      flex-direction: column;
      background: var(--white);
      border: 1px solid var(--border);
      border-radius: 12px;
      overflow: hidden;
      text-decoration: none;
      color: var(--navy);
      transition: border-color 0.15s ease, box-shadow 0.15s ease;
    }
    .sc-card:hover { border-color: var(--blue); box-shadow: 0 8px 24px rgba(28, 36, 75, 0.10); }
    .sc-img { position: relative; display: block; aspect-ratio: 16 / 9; background: var(--blue-light); overflow: hidden; }
    /* absolute + inset, NOT height:100% — a percentage height inside an
       aspect-ratio box resolves against the image's own intrinsic ratio and
       balloons the card (TikTok thumbs are 9:16 portrait). Same fix as the
       v0.17.0 PDP tiles. */
    .sc-img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
    .sc-fallback { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--blue); }
    .sc-fallback svg { width: 34px; height: 34px; fill: currentColor; }
    .sc-body { display: flex; flex-direction: column; gap: 0.25rem; padding: 0.6rem 0.7rem 0.75rem; }
    .sc-platform { display: flex; align-items: center; gap: 0.3rem; font-size: 0.62rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--blue-text); }
    .sc-platform svg { width: 12px; height: 12px; fill: currentColor; flex-shrink: 0; }
    /* Publish date (from the corpus index) — right end of the platform row. */
    .sc-date { margin-left: auto; font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--gray); }
    .sc-title { font-size: 0.8rem; font-weight: 600; line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
    .sc-desc { font-size: 0.72rem; color: var(--gray); line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

    /* === A11y: focus-visible ring === */
    .chat-teaser-wrap button:focus-visible,
    .chat-overlay button:focus-visible,
    .chat-overlay a:focus-visible,
    .fab-wrap button:focus-visible,
    .chat-card-rail-arrow:focus-visible {
      outline: 2px solid var(--blue);
      outline-offset: 2px;
      border-radius: inherit;
    }
    /* Composer text inputs get their ring via .composer:focus-within; the
       locked composer carries its own box-shadow ring (chat-stage block). */

    /* BUG-5: visually-hidden aria-live container (screen-reader only). */
    .sr-only {
      position: absolute;
      width: 1px; height: 1px;
      padding: 0; margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }

    /* BUG-7 / 2.1: retry button on error/timeout bot bubbles. */
    .msg-retry {
      margin-top: 0.4rem;
      padding: 0.4rem 0.85rem;
      min-height: 36px;
      border: 1px solid var(--blue);
      border-radius: 999px;
      background: var(--white);
      color: var(--blue);
      font-size: 0.8rem;
      font-weight: 600;
      cursor: pointer;
      transition: background 0.15s, color 0.15s;
    }
    .msg-retry:hover { background: var(--blue); color: var(--white); }

    /* Error/timeout bot bubble — visually distinct from a normal reply (Theme C / 2.1). */
    .msg.bot.error {
      background: #fff4f4;
      border: 1px solid #f3c2c2;
    }

    /* === prefers-reduced-motion === */
    @media (prefers-reduced-motion: reduce) {
      .chat-card-rail.rail-2,
      .chat-card-rail.rail-3,
      .chat-card-rail.rail-4,
      .chat-card-rail.rail-grid {
        scroll-behavior: auto;
      }
    }

    /* R5b: thumbs feedback widget — sits under the bubble, outside it */
    .msg-feedback {
      margin-top: 0.25rem;
      padding-left: 0.2rem;
      font-size: 0.7rem;
      color: var(--gray);
      opacity: 0.7;
      transition: opacity 120ms ease;
    }
    .msg-row.bot:hover .msg-feedback { opacity: 1; }
    .feedback-btn {
      background: transparent;
      border: none;
      padding: 0.15rem 0.3rem;
      margin-right: 0.1rem;
      cursor: pointer;
      font-size: 0.95rem;
      line-height: 1;
      border-radius: 6px;
      min-width: 44px;          /* WCAG 2.5.5: hit area >=44px (glyph stays small, centered) */
      min-height: 44px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      transition: background 120ms ease, transform 120ms ease;
    }
    .feedback-btn:hover {
      background: rgba(70, 127, 247, 0.1);
      transform: scale(1.15);
    }
    .feedback-btn:focus-visible {
      outline: 2px solid var(--blue);
      outline-offset: 1px;
    }
    .feedback-thanks {
      font-size: 0.7rem;
      color: var(--blue);
      font-weight: 500;
    }

    .msg .sender {
      font-size: 0.7rem;
      font-weight: 600;
      letter-spacing: 0.05em;
      text-transform: uppercase;
      color: var(--blue-text);   /* AA on the blue-light bubble (was --blue, 3.4:1) */
      margin-bottom: 0.3rem;
      display: block;
    }

    .typing-text {
      font-size: 0.75rem;
      font-weight: 400;
      color: var(--blue-text);   /* AA on the blue-light bubble */
      font-style: italic;
    }

    .typing-dots {
      display: flex;
      gap: 4px;
      padding: 0.5rem 0;
    }

    .typing-dots span {
      width: 6px;
      height: 6px;
      background: var(--blue);
      border-radius: 50%;
      animation: typingBounce 1.4s ease-in-out infinite;
    }

    .typing-dots span:nth-child(2) { animation-delay: 0.2s; }
    .typing-dots span:nth-child(3) { animation-delay: 0.4s; }

    @keyframes typingBounce {
      0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
      30% { transform: translateY(-6px); opacity: 1; }
    }

    /* Footer rules moved to /chrome.css (sitewide canonical .legal-footer). */

    /* === Mobile === */
    @media (max-width: 640px) {
      /* Product buttons: increase touch target to 44px+ (WCAG min-height). */
      .product-btn {
        padding: 0.75rem 1.6rem;
        font-size: 0.85rem;
        min-height: 44px;
        display: flex;
        width: 100%;
        align-items: center;
        justify-content: center;
      }

      .signup {
        flex-direction: column;
        gap: 0.5rem;
      }

      .signup input {
        border-right: 1px solid rgba(255,255,255,0.2);
        border-radius: 50px;
        min-height: 48px;
        font-size: 16px; /* Prevent iOS zoom on focus */
      }

      .signup button {
        border-radius: 50px;
        min-height: 48px;
      }

      /* Footer mobile rules moved to /chrome.css (sitewide canonical). */

      /* Movement items: slightly reduce padding for tighter layout */
      .movement-item {
        padding: 1.25rem;
      }

      /* Reduce hero h1 slightly on very small screens */
      h1 {
        font-size: clamp(2.4rem, 10vw, 5rem);
      }

      /* (The mobile cart-drawer override lives with the drawer rules below —
         a media query carries no extra specificity, so a copy up here lost to
         the plain `.cart-drawer` further down the file.) */
    }

    /* Extra small screens (iPhone SE / 375px and below) */
    @media (max-width: 380px) {
      body {
        font-size: 16px;
      }

      .hero-message {
        font-size: 0.95rem;
        padding: 0 0.5rem;
      }

      .section-desc {
        font-size: 0.92rem;
      }

      .product-info .detail {
        font-size: 0.82rem;
      }

      /* Mobile: variant pills slightly larger touch targets */
      .variant-pill {
        height: 38px;
        padding: 0 14px;
        min-width: 44px;
      }

      /* Cart line items: tighter on mobile */
      .cart-line-img {
        width: 50px;
        height: 50px;
      }
    }

    /* === Size Variant Pills === */
    /* v0.15 (Drop 2): one .variant-group per option axis (Farbe, Größe) */
    .variant-group {
      margin-bottom: 0.5rem;
    }
    .variant-group-label {
      display: block;
      font-size: 0.7rem;
      font-weight: 600;
      letter-spacing: 0.06em;
      text-transform: uppercase;
      color: var(--gray);
      margin-bottom: 0.3rem;
      text-align: center;
    }
    .variant-pills {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
      justify-content: center;
      margin-bottom: 0.75rem;
    }
    .variant-group .variant-pills { margin-bottom: 0; }
    /* v0.29.5/6: Patch Farbe dots on a grid card sit two per row — the patch's
       swatch axis AND the bag's patch picker. Columns are max-content and items
       centre inside them, so every button stays exactly as wide as its text. */
    .product-card .variant-pills--grid2,
    .product-card .variant-group--patch .variant-pills {
      display: grid;
      grid-template-columns: repeat(2, max-content);
      justify-content: center;
      justify-items: center;
    }
    .variant-pill {
      height: 32px;
      padding: 0 12px;
      border-radius: 50px;
      border: 1px solid var(--border);
      background: var(--white);
      color: var(--navy);
      font-size: 0.8rem;
      font-weight: 500;
      cursor: pointer;
      transition: all 0.2s;
      font-family: 'Poppins', sans-serif;
    }
    .variant-pill:hover { border-color: var(--blue); }
    .variant-pill.selected {
      background: var(--blue);
      color: var(--white);
      border-color: var(--blue);
    }
    .variant-pill.unavailable {
      color: var(--gray-dim);
      text-decoration: line-through;
      opacity: 0.5;
      cursor: not-allowed;
    }
    /* v0.28.2 patch picker (grid + PDP share these; produkt.css loads after
       and only adds PDP-specific bits like the hint line). Colour-dot pills:
       the dot carries the embroidery colour, the pill stays a text pill. */
    .variant-pill--dot {
      display: inline-flex;
      align-items: center;
      gap: 7px;
    }
    .patch-dot {
      flex: none;
      width: 13px;
      height: 13px;
      border-radius: 50%;
      background: var(--patch-dot, #999);
      box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
    }
    .variant-pill--dot.selected .patch-dot {
      box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15),
                  0 0 0 2px rgba(255, 255, 255, 0.9);
    }
    /* Smart-CTA nudge: add-to-cart without a required choice pulses the missing picker row. */
    .variant-group.nudge .variant-pills {
      animation: oly-patch-pulse 0.85s ease-out 2;
      border-radius: 10px;
    }
    @keyframes oly-patch-pulse {
      0%   { box-shadow: 0 0 0 0 rgba(28, 36, 75, 0.45); }
      100% { box-shadow: 0 0 0 9px rgba(28, 36, 75, 0); }
    }
    @media (prefers-reduced-motion: reduce) {
      .variant-group.nudge .variant-pills { animation: none; }
    }
    /* v0.17.0: the card photo is a link to the PDP — needs a visible focus
       ring (no grid link had one; the chat-scoped rules don't apply here).
       Absolutely filled instead of height:100% — WebKit resolves percentage
       heights against the aspect-ratio parent as 0, collapsing the image
       (caught by T2 on the webkit project). .product-img is position:relative. */
    .product-img-link {
      display: block;
      position: absolute;
      inset: 0;
    }
    .product-img-link:focus-visible {
      outline: 2px solid var(--blue);
      outline-offset: -2px;
    }

    /* === Cart Drawer === */
    .cart-overlay {
      position: fixed;
      inset: 0;
      background: rgba(0, 0, 0, 0.3);
      z-index: 200;
      opacity: 0;
      transition: opacity 0.3s;
    }
    .cart-overlay.open { opacity: 1; }

    .cart-drawer {
      position: fixed;
      top: 0;
      right: 0;
      /* The visible viewport. No `bottom: 0` alongside it — that pair
         over-constrains the box and the engine drops one of them anyway. */
      height: 100dvh;
      width: 380px;
      /* `100%`, not `100vw`: `100vw` counts a classic scrollbar's width and
         pushes the drawer off-screen by that much. */
      max-width: 100%;
      background: var(--white);
      z-index: 201;
      display: flex;
      flex-direction: column;
      transform: translateX(100%);
      transition: transform 0.3s ease;
      box-shadow: -4px 0 20px rgba(0, 0, 0, 0.1);
    }
    .cart-drawer.open { transform: translateX(0); }

    /* Full-width on phones. MUST come after the base rule: a media query adds
       no specificity, so an earlier copy loses to the plain `.cart-drawer`
       below it — which is how this silently did nothing for two releases. */
    @media (max-width: 640px) {
      .cart-drawer { width: 100%; }
    }

    .cart-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 0.75rem;
      flex-shrink: 0;
      height: var(--nav-height);
      padding: 0 1.25rem;
      margin-top: env(safe-area-inset-top, 0px);
      border-bottom: 1px solid var(--border);
    }
    @media (max-width: 640px) {
      /* nav.top-nav uses `padding: 0 1rem` below 641px — match it. */
      .cart-header { padding: 0 1rem; }
    }
    .cart-header h3 {
      font-size: 1rem;
      font-weight: 600;
      color: var(--navy);
    }
    /* Geometry and colour come from the shared .icon-x (chrome.css). Nothing
       left to declare: the 44px target's right edge lands on the header gutter,
       which is exactly where nav.top-nav puts .nav-cart — the control the fan
       tapped to open this drawer. */

    .cart-items {
      flex: 1;
      overflow-y: auto;
      /* Don't chain the rubber-band to the frozen page behind (v0.22.4). */
      overscroll-behavior: contain;
      -webkit-overflow-scrolling: touch;
      padding: 1rem 1.25rem;
    }

    .cart-line {
      display: flex;
      gap: 0.75rem;
      padding: 0.75rem 0;
      border-bottom: 1px solid var(--border);
    }
    .cart-line:last-child { border-bottom: none; }

    .cart-line-img {
      width: 60px;
      height: 60px;
      border-radius: 8px;
      object-fit: cover;
      background: var(--off-white);
    }

    /* min-width: 0 lets a long product title WRAP instead of pushing the
       actions column (whose "Entfernen" label must never wrap) out of view. */
    .cart-line-info { flex: 1; min-width: 0; }
    /* One line, ellipsis on overflow (v0.22.5) — a wrapping title pushed the
       price and the stepper apart and made lines different heights. Relies on
       `min-width: 0` on .cart-line-info: without it the flex item refuses to
       shrink below its content and there is nothing to ellipsise. */
    .cart-line-title {
      font-size: 0.85rem;
      font-weight: 600;
      color: var(--navy);
      margin-bottom: 2px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .cart-line-variant {
      font-size: 0.75rem;
      color: var(--gray);
      margin-bottom: 4px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .cart-line-price {
      font-size: 0.85rem;
      font-weight: 600;
      color: var(--blue);
    }

    /* Stepper on top, "Entfernen" underneath, both flush right (v0.22.4).
       flex-shrink: 0 keeps the column intact — the title column shrinks. */
    .cart-line-actions {
      display: flex;
      flex-direction: column;
      align-items: flex-end;
      gap: 0.4rem;
      flex-shrink: 0;
    }
    .cart-qty-row {
      display: flex;
      align-items: center;
      gap: 0.5rem;
    }
    .cart-qty-btn {
      width: 28px;
      height: 28px;
      border: 1px solid var(--border);
      border-radius: 50%;
      background: var(--white);
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 0.9rem;
      color: var(--navy);
      transition: all 0.2s;
      font-family: 'Poppins', sans-serif;
    }
    .cart-qty-btn:hover { border-color: var(--blue); color: var(--blue); }
    /* 28px circles, 44x40 tap area (v0.22.4): the pseudo-element hit-tests for
       its originating button, so the touch target grows without the design
       changing. 40px tall, not 44 — at 44 the areas would reach into the
       "Entfernen" row 0.4rem below and steal its taps. */
    .cart-qty-btn { position: relative; }
    .cart-qty-btn::after {
      content: '';
      position: absolute;
      top: 50%;
      left: 50%;
      width: 44px;
      height: 40px;
      transform: translate(-50%, -50%);
    }

    .cart-qty {
      font-size: 0.85rem;
      font-weight: 600;
      min-width: 20px;
      text-align: center;
    }

    /* v0.22.4: a "Entfernen" text link, not a hairline × — the glyph was too
       subtle to find and far too small to hit on a phone. */
    .cart-remove-btn {
      display: inline-flex;
      align-items: center;
      min-height: 44px;
      padding: 0 0 0 0.5rem;
      background: none;
      border: none;
      cursor: pointer;
      color: var(--gray);
      font-family: inherit;
      font-size: 0.8rem;
      font-weight: 500;
      text-decoration: underline;
      text-underline-offset: 2px;
      white-space: nowrap;
      transition: color 0.2s;
    }
    .cart-remove-btn:hover,
    .cart-remove-btn:focus-visible { color: #ef4444; }

    /* v0.22.4: the message keeps its place near the top while "Weiter shoppen"
       is pushed to the bottom edge — the same line the "Zur Kasse" button
       occupies when the cart has items, so both states share one rhythm.
       min-height fills the scroll area; .cart-items has a definite height. */
    .cart-empty {
      display: flex;
      flex-direction: column;
      align-items: center;
      min-height: 100%;
      text-align: center;
      padding: 2rem 0 0;
      color: var(--gray);
    }
    .cart-empty p {
      margin-bottom: 1rem;
      font-size: 0.9rem;
    }
    .cart-empty .product-btn { margin-top: auto; }

    .cart-footer {
      padding: 1rem 1.25rem;
      /* No safe-area inset: 100dvh already ends above the browser's bottom bar,
         which itself covers the home indicator. */
      border-top: 1px solid var(--border);
    }
    .cart-subtotal {
      display: flex;
      justify-content: space-between;
      margin-bottom: 0.75rem;
      font-size: 0.95rem;
      font-weight: 600;
      color: var(--navy);
    }
    .cart-subtotal-amount { color: var(--blue); }
    /* v0.26.4 order breakdown. Zwischensumme / Versand / Gesamt reuse
       .cart-subtotal for the label-left / amount-right row; only the deltas
       live here. The two upper rows sit tighter than the old single-row
       footer so the block reads as one table. */
    .cart-subtotal.cart-shipping-line,
    .cart-subtotal.cart-grand-total { margin-bottom: 0; }
    .cart-subtotal.cart-shipping-line { margin-top: -0.35rem; }
    /* Hairline above the total, drawn on the row itself so no extra element
       is needed; the padding keeps it off the digits. */
    .cart-subtotal.cart-grand-total {
      margin-top: 0.5rem;
      padding-top: 0.5rem;
      border-top: 1px solid var(--border);
      font-size: 1.05rem;
      font-weight: 700;
    }
    .cart-shipping-note {
      font-size: 0.75rem;
      color: var(--gray);
      text-align: center;
      margin-top: 0.5rem;
    }
    /* v0.26.5: the free-shipping nudge now sits ABOVE the arithmetic and uses
       .ship-perk — the same treatment as the PDP incentive, so "free shipping"
       reads as one idea across the site instead of two lookalike notes. Only
       the cart-specific spacing lives here.
       tokens.css has no success colour and this is not the place to invent one,
       so both states use --blue-text; the ✓ and the heavier weight carry the
       "achieved" reading. */
    /* Scoped with .cart-footer on purpose: the base .ship-perk rule is defined
       LATER in this file, so a bare .cart-ship-perk would lose the margin to it
       at equal specificity. Two classes settles it independent of order. */
    .cart-footer .cart-ship-perk {
      text-align: center;
      line-height: 1.35;
      /* v0.26.6: set off from the arithmetic with a hairline. Drawn as this
         element's own border-bottom rather than a separate <hr>, for the same
         reason .cart-grand-total draws its own top rule: the divider belongs to
         the nudge, so when x-show hides the nudge (shippingConfigured false)
         the rule disappears with it instead of orphaning a stray line above
         Zwischensumme. Padding keeps the rule off the text; margin sets the
         gap to the breakdown below. */
      margin: 0 0 0.75rem;
      padding-bottom: 0.75rem;
      border-bottom: 1px solid var(--border);
    }
    .cart-footer .cart-ship-perk.is-free { font-weight: 700; }
    .cart-checkout-btn {
      display: block;
      width: 100%;
      /* v0.26.6: the Gesamt row sets margin-bottom: 0 (it is the last row of
         the table), so without this the CTA sat flush against the total. The
         gap goes on the button rather than on Gesamt because the button is
         also what follows the bare Zwischensumme in the degraded
         !shippingConfigured layout — one rule covers both. */
      margin-top: 1.25rem;
      padding: 0.75rem;
      background: var(--blue);
      color: var(--white);
      border: none;
      border-radius: 50px;
      font-size: 0.9rem;
      font-weight: 600;
      cursor: pointer;
      transition: background 0.3s;
      font-family: 'Poppins', sans-serif;
    }
    .cart-checkout-btn:hover { background: var(--blue-hover); }
    .cart-checkout-btn:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }

    /* === Survey Card ===
       Custom Jiggi-voice survey widget (NOT PostHog default). Visual styling
       lives here, NOT inline — Alpine's :style="placementStyle" (string form)
       wipes the inline style attribute, which previously stripped background,
       padding, z-index, etc. Inline `:style` only carries the dynamic anchor. */
    .survey-card {
      max-width: 340px;
      background: #fff;
      color: #1c244b;
      padding: 1rem 1.1rem 0.9rem;
      border-radius: 14px;
      box-shadow: 0 4px 24px rgba(28, 36, 75, 0.18);
      /* Above the chat overlay (1101): the operator survey is designed to
         float over the chat; at the old 250 it rendered invisibly UNDER the
         immersive overlay while still owning ESC priority. */
      z-index: 1150;
      font-size: 0.92rem;
      line-height: 1.45;
    }

    /* === Checkout Overlay === */
    .checkout-overlay {
      position: fixed;
      inset: 0;
      height: 100dvh;   /* the visible viewport, like the drawer */
      background: rgba(70, 127, 247, 0.9);
      z-index: 300;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      color: var(--white);
      font-size: 1.1rem;
      font-weight: 500;
    }
    .checkout-spinner {
      width: 32px;
      height: 32px;
      border: 3px solid rgba(255,255,255,0.3);
      border-top-color: var(--white);
      border-radius: 50%;
      animation: spin 0.8s linear infinite;
      margin-bottom: 1rem;
    }
    @keyframes spin { to { transform: rotate(360deg); } }

    /* v0.29.1: companion badge ("Wechsel-Patch für die Chest Bag") — top-left twin of
       the sold-out pill, brand-coloured so it reads as belonging, not warning. */
    .card-badge {
      position: absolute;
      top: 12px;
      left: 12px;
      background: var(--accent, #E96B0A);
      color: var(--white);
      padding: 4px 12px;
      border-radius: 50px;
      font-size: 0.7rem;
      font-weight: 600;
      letter-spacing: 0.05em;
      text-transform: uppercase;
      max-width: calc(100% - 24px);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    /* v0.29.3 cart upsell row */
    .cart-upsell { margin: 0.75rem 0 0; padding: 0.75rem; border: 1px dashed var(--line, rgba(0,0,0,0.12)); border-radius: 12px; }
    .cart-upsell-title { margin: 0 0 0.5rem; font-size: 0.85rem; font-weight: 600; }
    .cart-upsell-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.5rem; }
    .cart-upsell-btn { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; padding: 0.4rem; background: transparent; border: 1px solid var(--line, rgba(0,0,0,0.12)); border-radius: 10px; cursor: pointer; font: inherit; color: inherit; }
    .cart-upsell-btn:hover, .cart-upsell-btn:focus-visible { border-color: var(--accent, #E96B0A); }
    .cart-upsell-btn img { width: 48px; height: 60px; object-fit: cover; border-radius: 6px; }
    .cart-upsell-name { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.75rem; font-weight: 600; }
    .cart-upsell-price { font-size: 0.75rem; color: var(--muted, #6b7280); }
    /* v0.30.11 bag teaser: one wide tile, thumb left, copy right, same frame. */
    .cart-upsell-btn--wide { flex-direction: row; justify-content: flex-start; gap: 0.6rem; text-decoration: none; }
    .cart-upsell-copy { display: flex; flex-direction: column; gap: 0.15rem; text-align: left; }

    /* === Sold Out Badge === */
    .sold-out-badge {
      position: absolute;
      top: 12px;
      right: 12px;
      background: var(--gray);
      color: var(--white);
      padding: 4px 12px;
      border-radius: 50px;
      font-size: 0.7rem;
      font-weight: 600;
      letter-spacing: 0.05em;
      text-transform: uppercase;
    }

    /* Hero fills the viewport (dvh: no iOS URL-bar jump, E8). The chat
       teaser renders in-flow beneath the brand block; the overlay + FAB are
       position:fixed (chat-stage block at the end of this file). */
    .hero { min-height: 100dvh; }

    /* ════════════════════════════════════════════════════════════════
       v0.14.2 — CHAT-STAGE INTERACTION STATES (Claude Design handoff)
       Ported from the design bundle (project/chat-stage/lab.css). Three
       production adaptations from the prototype:
         • The prototype faked a device viewport with
           `.dv { position:absolute; inset:0; overflow:hidden }` and placed
           the overlay / scrim / FAB *absolute* inside it. On the real site
           there is no such wrapper, so these become **position:fixed** —
           a true viewport modal + a scroll-away dock.
         • `.dv[data-device="mobile|desktop"]` device-frame switches map to
           **@media (max-width:640px) / (min-width:641px)** (repo convention).
         • The prototype's `.om-*` landing chrome is dropped — the repo
           already owns `.hero`, `.nav`, sections.
       Colour/type/radii/shadow all resolve against the design-system tokens
       added to tokens.css (--radius-pill, --shadow-card, --ease, …).
       Replaces the v0.14.x hero-chat presentation (chat-bar / chat-panel /
       dock pill), which was pruned from this file in the same release.
       ════════════════════════════════════════════════════════════════ */

    /* ─── Hero teaser: welcome → composer → chips (fresh) / resume (returning) ─── */
    .teaser { display: flex; flex-direction: column; align-items: center; gap: 1.1rem; width: 100%; max-width: 600px; margin: 0 auto; }
    .teaser-welcome { font-size: 1.02rem; font-weight: 300; line-height: 1.55; color: var(--navy); max-width: 500px; margin: 0; }
    .teaser-welcome b { font-weight: 600; }
    .teaser-composer { width: 100%; max-width: 520px; }

    /* ─── Chips (guided entry). Default = outline pill; .soft = blue-light fill
       (the design's Chip variant="soft", matching the repo suggestion pills). ─── */
    .chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.5rem; }
    .chip {
      min-height: 44px;   /* WCAG 2.5.5 touch target (repo standard) */
      padding: 0.5rem 0.9rem; border-radius: var(--radius-pill);
      border: 1px solid var(--border); background: var(--white); color: var(--navy);
      font-family: var(--font-sans); font-size: 0.8rem; font-weight: 500;
      cursor: pointer; transition: all var(--transition-fast); white-space: nowrap;
    }
    .chip:hover { border-color: var(--blue); color: var(--blue-text); background: var(--blue-light); }
    .chip.soft { background: var(--blue-light); border-color: rgba(70, 127, 247, 0.15); color: var(--blue-text); }
    .chip.soft:hover { background: var(--blue-text); color: var(--white); border-color: var(--blue-text); }   /* AA in the hover state too */

    /* ─── Composer — the core feature, kept high & visible (Malte's note).
       Rotating example questions cycle through the native :placeholder
       (driven by a JS interval). Locked = a lock glyph + tap nudges
       consent (replaces the old greyed/inert gating). ─── */
    .composer {
      display: flex; align-items: center; gap: 0.5rem; width: 100%;
      padding: 0.5rem 0.55rem 0.5rem 1.05rem;
      background: var(--white); border: 1px solid var(--border);
      border-radius: var(--radius-pill); box-shadow: 0 8px 30px rgba(70, 127, 247, 0.10);
      transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
    }
    .composer:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-light); }
    .composer.locked { cursor: pointer; }
    .composer.locked:hover, .composer.locked:focus-visible { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-light); outline: none; }
    /* Locked input is non-interactive so the click lands on the wrapper (nudge). */
    .composer.locked .composer-field input { pointer-events: none; }
    .composer-field { position: relative; flex: 1; min-width: 0; display: flex; align-items: center; height: 30px; }
    /* ─── Cmd/Ctrl+K hint chip (v0.14.12) — keyboard devices only. Hidden by
       default; shown (and the input padded so the rotating placeholder never
       underlaps it) only on fine-pointer/hover devices, where a keyboard is
       a fair assumption. The combo itself is wired in handleGlobalKey. ─── */
    .kbd-hint { display: none; }
    @media (hover: hover) and (pointer: fine) {
      .kbd-hint {
        position: absolute;
        right: 10px;
        top: 50%;
        transform: translateY(-50%);
        display: inline-flex;
        gap: 3px;
        pointer-events: none;
      }
      .kbd-hint kbd {
        font-family: var(--font-sans);
        font-size: 0.64rem;
        font-weight: 600;
        line-height: 1;
        color: var(--gray);
        background: var(--off-white);
        border: 1px solid var(--border);
        border-radius: 5px;
        padding: 3px 5px;
      }
      .composer-field:has(.kbd-hint) input { padding-right: 76px; }
      /* Resume card: no input to anchor to — sit in the header row's flow. */
      .resume-head .kbd-hint { position: static; transform: none; margin-left: auto; }
    }
    .composer-field input { width: 100%; border: none; outline: none; background: transparent; color: var(--navy); font-family: var(--font-sans); font-size: 0.92rem; }
    .composer-field input::placeholder { color: var(--gray); }   /* WCAG AA on white (repo BUG-10) */
    /* Locked input: gray fill so the rotating placeholder reads as a hint,
       not pre-typed text (text-fill-color also paints ::placeholder). */
    .composer-field input:disabled { cursor: pointer; -webkit-text-fill-color: var(--gray); opacity: 1; }
    .composer-send { width: 44px; height: 44px; border-radius: 50%; background: var(--blue); border: none; cursor: pointer; flex-shrink: 0; display: flex; align-items: center; justify-content: center; transition: opacity 0.15s, background var(--transition-base); }   /* 44px: WCAG 2.5.5 touch target */
    .composer-send:hover:not(:disabled) { background: var(--blue-hover); }
    .composer-send:disabled { opacity: 0.4; cursor: default; }
    .composer-send svg { width: 17px; height: 17px; fill: var(--white); }
    .composer-lock { width: 44px; height: 44px; border-radius: 50%; background: var(--blue-light); color: var(--blue); flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
    .composer-lock svg { width: 16px; height: 16px; }
    .composer-hint { display: flex; align-items: center; justify-content: center; gap: 0.4rem; margin: 0.55rem 0 0; font-size: 0.74rem; font-weight: 500; color: var(--blue-text); }
    .composer-hint svg { width: 13px; height: 13px; flex-shrink: 0; }

    /* ─── Returning-user resume card ─── */
    .resume-card { width: 100%; max-width: 500px; background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-panel); padding: 1.1rem 1.2rem; text-align: left; box-shadow: 0 8px 30px rgba(70, 127, 247, 0.10); }
    .resume-head { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.85rem; }
    .resume-av { width: 34px; height: 34px; border-radius: 50%; background: var(--white); border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
    .resume-head .t { font-size: 0.82rem; font-weight: 600; }
    .resume-head .s { font-size: 0.7rem; color: var(--blue-text); font-weight: 600; }   /* AA on white */
    .resume-ex { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.9rem; }
    .resume-row { font-size: 0.85rem; line-height: 1.45; }
    .resume-row .who { font-weight: 600; color: var(--navy); }
    .resume-row.user { color: var(--gray); }
    .resume-row.bot { color: var(--navy); background: var(--blue-light); border: 1px solid rgba(70, 127, 247, 0.15); border-radius: 10px; padding: 0.5rem 0.7rem; }
    .resume-row.bot .who { color: var(--blue-text); }   /* AA on the blue-light bubble */
    .resume-cta { width: 100%; min-height: 44px; border: none; border-radius: var(--radius-pill); background: var(--blue); color: var(--white); font-family: var(--font-sans); font-weight: 600; font-size: 0.85rem; cursor: pointer; transition: background var(--transition-base); }
    .resume-cta:hover { background: var(--blue-hover); }

    /* ─── Consent nudge: shake the (verbatim) repo banner when a locked
       composer is tapped. Class toggled on <html> for ~500ms. ─── */
    .cookie-shake .consent-banner { animation: c-shake 0.5s var(--ease); }
    @keyframes c-shake { 0%, 100% { transform: translateX(0); } 20% { transform: translateX(-7px); } 40% { transform: translateX(7px); } 60% { transform: translateX(-5px); } 80% { transform: translateX(5px); } }

    /* ─── Immersive chat overlay (scrim + near-fullscreen modal). FIXED to
       the real viewport. z-index sits above the cart scrim (~200) and nav. ─── */
    /* Tint only — a full-viewport backdrop blur costs continuous GPU work
       (the hero's SMIL loops keep dirtying the backdrop) for little gain. */
    .chat-scrim { position: fixed; inset: 0; z-index: 1100; background: rgba(28, 36, 75, 0.34); animation: overlay-fade 0.25s ease; }
    @keyframes overlay-fade { from { opacity: 0; } to { opacity: 1; } }
    /* text-align:left — the overlay lives INSIDE the centered .hero section
       and would inherit its text-align:center (bot replies read centered:
       manual-review finding #2, v0.14.5). */
    /* --pad-x: the desktop gutter shared by the transcript, the foot AND the
       quick-arrow placement (v0.14.8 — vw-based so child calc()s resolve
       against the same length; an 8% basis would re-resolve per element). */
    .chat-overlay { position: fixed; inset: 0; z-index: 1101; display: flex; flex-direction: column; background: var(--white); text-align: left; --pad-x: clamp(1rem, 8vw, 4rem); animation: overlay-in 0.32s var(--ease); }
    @keyframes overlay-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } }
    @media (min-width: 641px) {
      /* Desktop: a centred, rounded near-fullscreen panel so the scrim reads
         as an immersive modal rather than a literal full takeover. */
      .chat-overlay { inset: 3vh auto auto 50%; transform: translateX(-50%); width: min(720px, 94vw); height: 94vh; border-radius: var(--radius-panel); overflow: hidden; box-shadow: var(--shadow-modal); animation: overlay-in-d 0.32s var(--ease); }
      @keyframes overlay-in-d { from { opacity: 0; transform: translate(-50%, 14px); } to { opacity: 1; transform: translate(-50%, 0); } }
    }
    /* Solid white: the head never overlaps content (it sits above the
       scroller), so a backdrop blur would sample its own background. */
    .chat-overlay .chat-head { flex-shrink: 0; display: flex; align-items: center; gap: 0.75rem; padding: 0.85rem 1rem; border-bottom: 1px solid var(--border); background: var(--white); }
    /* Avatars carry the real brand mark (<use href="#nav-logo-shape">, so a
       customized logo follows into the chat — manual-review finding #8,
       v0.14.5) on a white disc instead of the old blue "O" monogram. */
    .chat-overlay .chat-head .av { width: 40px; height: 40px; border-radius: 50%; background: var(--white); border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; flex-shrink: 0; box-shadow: 0 0 0 3px var(--blue-light); }
    .chat-overlay .chat-head .av svg, .chat-empty .av-lg svg, .resume-av svg, .fab-peek .ph .av svg { width: 70%; height: 70%; display: block; }
    .chat-overlay .chat-head .meta h3 { margin: 0; font-size: 0.92rem; font-weight: 600; }
    .chat-overlay .chat-head .meta p { margin: 0; font-size: 0.72rem; color: var(--gray); display: flex; align-items: center; gap: 0.35rem; }
    .chat-overlay .chat-head .meta .online { width: 7px; height: 7px; border-radius: 50%; background: #28c840; display: inline-block; }
    /* Right-aligned action group ("Neuer Chat" + ×): a single auto margin on
       the wrapper, so the two buttons stay grouped whether or not Neuer Chat
       is visible (two sibling auto-margins would split the free space). */
    .chat-overlay .chat-head .hd-actions { margin-left: auto; display: flex; align-items: center; gap: 0.4rem; }
    .chat-overlay .chat-head .hd-actions .chat-new { margin-left: 0; }
    .chat-overlay .chat-head .close { width: 44px; height: 44px; border-radius: 50%; border: none; background: var(--off-white); color: var(--gray); font-size: 1.4rem; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all var(--transition-fast); }
    .chat-overlay .chat-head .close:hover { background: var(--blue-light); color: var(--navy); }
    /* The repo's .chat-messages scroller IS the overlay body (keeps x-ref,
       product rails, feedback + a11y intact) — restyled to the design's body. */
    /* overflow-x:hidden — overflow-y:auto alone computes overflow-x to auto,
       so a too-wide product rail used to pan the WHOLE transcript sideways
       (manual-review finding #2, v0.14.5; the rail scrolls itself). */
    /* --chat-pad-x: the transcript's horizontal inset. The card rails consume
       it (negative wrap margin + rail padding) to bleed edge-to-edge while
       keeping their first card aligned with the bubble (v0.18.0). */
    .chat-overlay .chat-messages { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; --chat-pad-x: 1rem; padding: 1.25rem var(--chat-pad-x); display: flex; flex-direction: column; gap: 0.85rem; background: var(--white); }
    .chat-overlay .chat-messages::-webkit-scrollbar { width: 7px; }
    .chat-overlay .chat-messages::-webkit-scrollbar-thumb { background: var(--gray-dim); border-radius: 99px; }
    @media (min-width: 641px) { .chat-overlay .chat-messages { --chat-pad-x: var(--pad-x); padding: 1.5rem var(--chat-pad-x); } }
    .chat-overlay .chat-foot { flex-shrink: 0; border-top: 1px solid var(--border); padding: 0.75rem; padding-bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px)); background: var(--white); display: flex; flex-direction: column; gap: 0.6rem; }   /* safe-area: clear the iPhone home indicator */
    @media (min-width: 641px) { .chat-overlay .chat-foot { padding: 0.85rem var(--pad-x); } }
    /* Quick replies in a wrap with side arrows (v0.14.6 #3) — same overlay-
       arrow idiom as the product rail, sized down for the 32px chips.
       v0.14.7: arrows are always visible (desktop included); disabled marks
       the edges. The row's 38px side padding keeps the first/last chip fully
       visible beside the arrows (the 36px arrow sits at -6px → covers up to
       30px; 38px leaves an 8px gap). */
    .quick-row-wrap { position: relative; }
    .quick-row { display: flex; gap: 0.45rem; overflow-x: auto; padding: 0 38px 0.1rem 38px; scrollbar-width: none; }
    .quick-row::-webkit-scrollbar { display: none; }
    .quick-row .chip { flex-shrink: 0; }
    .quick-arrow {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      z-index: 2;
      width: 36px;
      height: 36px;
      border-radius: 50%;
      border: 1px solid var(--border);
      background: rgba(255, 255, 255, 0.94);
      box-shadow: 0 4px 12px rgba(28, 36, 75, 0.16);
      color: var(--navy);
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 0;
      transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, opacity 0.2s;
    }
    .quick-arrow:first-of-type { left: -6px; }
    .quick-arrow:last-of-type { right: -6px; }
    /* Desktop (v0.14.8): the arrows live in the foot's empty --pad-x gutters,
       centered — they can never overlay a chip there, so the row drops its
       38px reserve. left = -(pad + 36)/2 puts the 36px arrow mid-gutter.
       Mobile keeps the overlay-the-row model (its 12px foot padding can't
       host a 36px arrow); the row padding provides the clearance there. */
    @media (min-width: 641px) {
      .quick-row { padding-left: 0; padding-right: 0; }
      .quick-arrow:first-of-type { left: calc((var(--pad-x) + 36px) / -2); }
      .quick-arrow:last-of-type { right: calc((var(--pad-x) + 36px) / -2); }
    }
    .quick-arrow:hover:not(:disabled) { border-color: var(--blue); color: var(--blue); }
    .quick-arrow:disabled { opacity: 0.35; cursor: not-allowed; }
    .quick-arrow svg { width: 15px; height: 15px; fill: currentColor; pointer-events: none; }
    .chat-empty { margin: auto; text-align: center; max-width: 320px; display: flex; flex-direction: column; align-items: center; gap: 0.9rem; padding: 2rem 0; }
    .chat-empty .av-lg { width: 64px; height: 64px; border-radius: 50%; background: var(--white); border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; box-shadow: 0 0 0 6px var(--blue-light); }
    .chat-empty h3 { margin: 0; font-size: 1.05rem; }
    .chat-empty p { margin: 0; font-size: 0.88rem; font-weight: 300; color: var(--gray); line-height: 1.55; }

    /* ─── Floating FAB dock (scroll-away keep-in-view entry point). FIXED. ─── */
    .fab-wrap { position: fixed; right: 16px; bottom: calc(16px + env(safe-area-inset-bottom, 0px)); z-index: 1090; display: flex; flex-direction: column; align-items: flex-end; gap: 0.65rem; }
    .fab-peek { width: 268px; max-width: calc(100vw - 32px); background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-panel); box-shadow: 0 18px 50px -12px rgba(28, 36, 75, 0.3); overflow: hidden; text-align: left; transform-origin: bottom right; animation: peek-in 0.26s var(--ease); }
    @keyframes peek-in { from { opacity: 0; transform: translateY(10px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } }
    .fab-peek .ph { display: flex; align-items: center; gap: 0.55rem; padding: 0.7rem 0.85rem; border-bottom: 1px solid var(--border); background: var(--off-white); }
    .fab-peek .ph .av { width: 30px; height: 30px; border-radius: 50%; background: var(--white); border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; }
    .fab-peek .ph .t { font-size: 0.78rem; font-weight: 600; }
    .fab-peek .ph .x { margin-left: auto; width: 44px; height: 44px; border: none; background: none; color: var(--gray); font-size: 1.2rem; cursor: pointer; border-radius: 50%; }   /* 44px touch target */
    .fab-peek .ph .x:hover { background: var(--white); color: var(--navy); }
    .fab-peek .pbody { padding: 0.8rem 0.85rem; }
    .fab-peek .lastline { font-size: 0.82rem; line-height: 1.45; color: var(--navy); margin: 0 0 0.2rem; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
    .fab-peek .lastline .tag { display: block; font-size: 0.64rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--blue-text); margin-bottom: 0.2rem; }   /* AA on white */
    .fab-peek .pcta { margin-top: 0.7rem; width: 100%; min-height: 44px; border: none; border-radius: var(--radius-pill); background: var(--blue); color: var(--white); font-family: var(--font-sans); font-weight: 600; font-size: 0.8rem; cursor: pointer; transition: background var(--transition-base); }
    .fab-peek .pcta:hover { background: var(--blue-hover); }
    .fab-peek .pchips { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.6rem; }
    .fab-btn { position: relative; height: 56px; min-width: 56px; border-radius: var(--radius-pill); border: none; background: var(--blue); color: var(--white); cursor: pointer; box-shadow: 0 14px 34px -8px rgba(70, 127, 247, 0.6); display: flex; align-items: center; gap: 0.6rem; padding: 0 0.5rem; transition: transform var(--transition-fast), box-shadow var(--transition-base); }
    .fab-btn:hover { transform: scale(1.04); }
    .fab-btn:active { transform: scale(0.97); }
    .fab-btn .ring { width: 44px; height: 44px; border-radius: 50%; background: rgba(255, 255, 255, 0.16); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
    .fab-btn .ring svg { width: 22px; height: 22px; }
    .fab-btn .lbl { font-size: 0.84rem; font-weight: 600; letter-spacing: 0.02em; padding-right: 0.8rem; white-space: nowrap; max-width: 0; overflow: hidden; opacity: 0; transition: max-width 0.3s var(--ease), opacity 0.25s var(--ease), padding 0.3s; }
    .fab-btn.labelled .lbl { max-width: 200px; opacity: 1; }
    /* Compositor-friendly pulse: a fixed ring animated via transform/opacity
       (box-shadow keyframes would rasterize the FAB region every frame). */
    .fab-pulse { position: absolute; inset: 0; border-radius: var(--radius-pill); box-shadow: 0 0 0 3px rgba(70, 127, 247, 0.5); opacity: 0; animation: fab-pulse 2.4s ease-out infinite; pointer-events: none; will-change: transform, opacity; }
    @keyframes fab-pulse { 0% { transform: scale(1); opacity: 0.55; } 70% { transform: scale(1.3); opacity: 0; } 100% { transform: scale(1.3); opacity: 0; } }

    /* Mobile: size AND position the overlay to the visual viewport
       (--chat-vh/--chat-top come from a JS visualViewport listener) so the
       foot composer rides above the on-screen keyboard and the head stays
       on-screen when iOS pans the visual viewport. Also guard the composer
       font size — iOS Safari auto-zooms inputs <16px. */
    @media (max-width: 640px) {
      .chat-overlay { top: var(--chat-top, 0); height: var(--chat-vh, 100%); bottom: auto; }
      .composer-field input { font-size: 16px; }
      /* iOS Safari clips fixed-element shadows at the visual-viewport edge
         (over the collapsed URL bar) — keep the glow's downward throw within
         the FAB's 16px bottom gap so no hard cut line shows (v0.14.6 #2).
         6px offset + 16px blur − 6px spread = 16px max extent. */
      .fab-btn { box-shadow: 0 6px 16px -6px rgba(70, 127, 247, 0.55); }
    }

    /* Pre-paint state gate (the <head> wedge sets html.consent-pending before
       first paint): already-consented fans must never flash the locked
       composer + cookie hint while Alpine loads. Scoped to the PRE-hydration
       window only ([data-chat-ready] lands in init()) so Alpine's x-show owns
       the state afterwards — e.g. a cross-tab withdraw re-locking live. */
    html:not(.consent-pending) .hero:not([data-chat-ready]) .composer.locked,
    html:not(.consent-pending) .hero:not([data-chat-ready]) .composer-hint { display: none; }

    /* Pre-consent mobile: the content-rich banner covers the bottom ~260px,
       so compact the hero chrome (the design bundle's banner-open treatment)
       — welcome → locked composer stay clear of the banner. The class is
       set pre-paint by the <head> wedge and cleared on any consent choice. */
    @media (max-width: 640px) {
      html.consent-pending .hero { justify-content: flex-start; }
      html.consent-pending .hero .brand-mark { width: 64px; height: 64px; margin-bottom: 0.6rem; }
      html.consent-pending .hero .persona,
      html.consent-pending .hero .tagline,
      html.consent-pending .hero .chips { display: none; }
      html.consent-pending .hero .divider { margin-bottom: 1rem; }
      /* The launch card STAYS (it is the launch message) — just tighten it. */
      html.consent-pending .hero .hero-launch-card { margin-bottom: 0.6rem; }
    }

    /* Reduced motion: kill the decorative chat-stage animations. */
    @media (prefers-reduced-motion: reduce) {
      .fab-pulse, .fab-peek, .chat-overlay, .chat-scrim,
      .cookie-shake .consent-banner { animation: none !important; }
    }

    /* ---------------------------------------------------------------
       PAngV price labelling (§ 3 Abs. 1 total price, § 6 shipping).
       Two forms, one place, used by landingpage.html, produkt.html and
       the chat-overlay partial (produkt.html links landing.css too, so
       produkt.css needs no copy):
         .price-note        block form — PDP offer price + section footnotes
         .price-note-inline micro form — nested inside a card/tile .price
       Both stay visually subordinate to the price: small + muted, and
       links use --blue-text (the AA-safe blue for text on light).
       --------------------------------------------------------------- */
    .price-note {
      font-size: 0.7rem;
      font-weight: 400;
      line-height: 1.4;
      color: var(--gray);
      margin-top: 0.35rem;
      margin-bottom: 1.5rem;
    }
    .price-note a {
      color: var(--blue-text);
      text-decoration: underline;
    }

    /* Nested in .price so the card gains no layout row: the parent sets a
       large bold blue price, so every one of those has to be reset here. */
    .price-note-inline {
      display: inline;
      margin-left: 0.4rem;
      font-size: 0.7rem;
      font-weight: 400;
      color: var(--gray);
      vertical-align: baseline;
      /* v0.26.9: `white-space: nowrap` REMOVED. It was safe when the note was
         the two words "inkl. MwSt."; the full sentence is far too long to hold
         on one line beside a price in a ~300px card, and nowrap would push the
         card into horizontal overflow rather than wrap. */
    }
    /* Same treatment as .price-note a — this is the same link on a smaller
       surface, so it must not read as a different affordance. */
    .price-note-inline a {
      color: var(--blue-text);
      text-decoration: underline;
    }


/* .ship-perk is now the CART DRAWER's class only (v0.26.9). It began as the PDP
   incentive (v0.26.4), was also used for a section-level line under #produkte,
   and both of those callers are gone — the PDP folded its shipping info into
   .price-note, and the grid moved the disclosure onto each card. The drawer
   ships on every page, so the rule stays here rather than in produkt.css.
   .grid-ship-perk was deleted with its markup. */
.ship-perk {
  margin: 0.2rem 0 0;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--blue-text);
}
