/* ==========================================================================
   CRVD.KITCHEN — Release 0.2 (Global Visual System — Sprint 18.2)
   Direction A3 "Magazine" (Warm Charcoal foundation) for the dark product
   environment, combined with the A2 warm-paper treatment scoped to the
   Background Reader only. Two complementary environments, one token system.
   See docs/02-Infrastructure-Architecture for the Sprint 18/18.1/18.2 record.
   ========================================================================== */

/* PRIVACY HARDENING -- self-hosted fonts, replacing the former
   fonts.googleapis.com/fonts.gstatic.com runtime request (see the
   maintainability note in prototype/product/assets/fonts/README.md for
   provenance and update instructions). Same families, same weights, same
   Google Fonts binaries -- fetched once from the exact CSS2 request this
   product already used (Oswald:wght@400;500;600;700&Roboto:ital,wght@0,
   400;0,500;0,700;1,400), verified as genuine WOFF2 (file signature 'wOF2')
   before being committed. Both Oswald and Roboto are served by Google as
   variable fonts for this weight range: the four Oswald weights, and the
   three Roboto normal weights, each collapse to a single shared binary per
   subset -- exactly mirroring Google's own CSS2 output, not a simplification
   introduced here. Only the "latin" and "latin-ext" subsets are vendored
   (this product is German-first; both ranges together cover ä/ö/ü/ß and
   the wider Latin-diacritic set) -- the cyrillic/greek/vietnamese subsets
   Google's API also offers are not fetched, since nothing in this product
   uses them and vendoring them would be pure unused weight. */
@font-face{ font-family:'Oswald'; font-style:normal; font-weight:400;
  font-display:swap; src:url('assets/fonts/oswald-normal-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face{ font-family:'Oswald'; font-style:normal; font-weight:400;
  font-display:swap; src:url('assets/fonts/oswald-normal-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face{ font-family:'Oswald'; font-style:normal; font-weight:500;
  font-display:swap; src:url('assets/fonts/oswald-normal-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face{ font-family:'Oswald'; font-style:normal; font-weight:500;
  font-display:swap; src:url('assets/fonts/oswald-normal-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face{ font-family:'Oswald'; font-style:normal; font-weight:600;
  font-display:swap; src:url('assets/fonts/oswald-normal-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face{ font-family:'Oswald'; font-style:normal; font-weight:600;
  font-display:swap; src:url('assets/fonts/oswald-normal-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face{ font-family:'Oswald'; font-style:normal; font-weight:700;
  font-display:swap; src:url('assets/fonts/oswald-normal-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face{ font-family:'Oswald'; font-style:normal; font-weight:700;
  font-display:swap; src:url('assets/fonts/oswald-normal-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face{ font-family:'Roboto'; font-style:italic; font-weight:400;
  font-display:swap; src:url('assets/fonts/roboto-italic-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face{ font-family:'Roboto'; font-style:italic; font-weight:400;
  font-display:swap; src:url('assets/fonts/roboto-italic-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face{ font-family:'Roboto'; font-style:normal; font-weight:400;
  font-display:swap; src:url('assets/fonts/roboto-normal-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face{ font-family:'Roboto'; font-style:normal; font-weight:400;
  font-display:swap; src:url('assets/fonts/roboto-normal-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face{ font-family:'Roboto'; font-style:normal; font-weight:500;
  font-display:swap; src:url('assets/fonts/roboto-normal-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face{ font-family:'Roboto'; font-style:normal; font-weight:500;
  font-display:swap; src:url('assets/fonts/roboto-normal-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face{ font-family:'Roboto'; font-style:normal; font-weight:700;
  font-display:swap; src:url('assets/fonts/roboto-normal-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
@font-face{ font-family:'Roboto'; font-style:normal; font-weight:700;
  font-display:swap; src:url('assets/fonts/roboto-normal-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }

:root{
  /* ---- backgrounds: dark product environment ---- */
  --canvas:#0F0D12;          /* page canvas -- C · Vessel base, warm near-black (Constitution §2.1) */
  --nav-bg:#08060B;          /* topbar / bottom nav -- one step darker than canvas */
  --surface:#1E1B22;         /* primary surface: cards, recipe cards, shefindings */
  --surface-2:#262129;       /* secondary surface: inputs, textareas */
  --surface-elevated:#2C2530; /* sheets, modals -- sits above primary surface */
  --surface-premium:#241620;  /* PL8 surfaces -- restrained, not neon */

  /* ---- backgrounds: reading environment (Background Reader only) ---- */
  --editorial-paper:#F4EEE0;      /* warm cream paper surface */
  --editorial-ink:#221E1A;        /* primary text on paper */
  --editorial-ink-secondary:#584E40; /* secondary text on paper */

  /* ---- text: dark product environment ---- */
  --text-primary:#F5F1EC;
  --text-body:#C9C0C4;
  --text-secondary:#A79FA4;
  --muted:#948C93;
  --text-disabled:#5C5560;

  /* ---- borders: dark product environment ---- */
  --hairline:#2E272D;          /* subtle divider -- decorative list rows only */
  --border-default:#6A6666;    /* actionable boundaries -- clears 3:1 vs surface */
  --border-strong:#7C7678;     /* hover / emphasis */
  --border-interactive:#E219B5; /* selected / focused actionable boundary */
  --border-premium:rgba(226,25,181,0.35); /* PL8 surfaces only */
  --editorial-divider:rgba(34,30,26,0.15); /* dividers on the paper surface */

  /* ---- actions ---- */
  /* --magenta-brand is the raw brand value and is never remapped. --magenta is
     rescoped to editorial ink inside the Reader, so anything that genuinely
     needs brand magenta on a Reader surface must reference --magenta-brand.
     Purely an alias: no rendered value changes anywhere. */
  --magenta-brand:#E219B5;
  /* THE SIGNAL -- shared CSS mask source (Signal Material + Motion
     sprint; Free/PL8 no longer an insertion point as of the Signal
     Consolidation sprint -- those decorative instances were removed
     outright). A CSS-only duplicate of SIGNAL_SVG's exact path/rect
     coordinates (app.js), used ONLY as a mask shape for material
     layers -- never as the rendered mark itself. Duplicated rather
     than referenced by id specifically because SIGNAL_SVG is inserted
     multiple times in the same document (Hero, Menschen, entry,
     ceremony) and HTML ids must be unique -- an id-based mask
     reference would collide. SIGNAL_SVG itself (app.js) is NEVER
     modified by this token; if that geometry ever changes, this
     string must be updated to match by hand. */
  --signal-mask-src:url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><path d="M 168.02 163.43 A 93 93 0 1 1 188.94 127.19" fill="none" stroke="white" stroke-width="14" stroke-linecap="butt"/><rect x="173.54" y="139.50" width="14" height="14" fill="white" transform="rotate(30 180.54 146.50)"/></svg>');
  /* THE SIGNAL -- canonical Satin Magenta material body (Signal
     Governance sprint, DESIGN_CONSTITUTION.md §1.2/DH-13). One shared
     gradient recipe so every instance draws from the literal same
     definition rather than a copy-pasted value that could quietly
     drift out of sync -- "material varies in intensity, never in
     type." Consumers set their OWN opacity on top of this (Hero .11,
     Menschen 1) to vary intensity without touching the recipe itself. */
  --signal-satin-gradient:linear-gradient(155deg,
    #4a0f3d 0%, #7a1763 24%, #b81f95 44%, #86185f 64%, #3c0c32 100%);
  --magenta:var(--magenta-brand);  /* primary actions, selected nav, focus, PL8 */
  --magenta-text-safe:#E534BD; /* small magenta text on dark surfaces (badge-pl8, chef-note) --
                                   base --magenta measured 4.16-4.35:1 on real rendered dark
                                   surfaces, short of AA for text under 18px; this lightened
                                   variant verified 4.59:1+ */
  --action-destructive:#D05778; /* verified 4.59:1 on canvas */
  /* On --surface-elevated (#2C2530) #D05778 falls to 3.76:1. Dialogs use
     this lighter step of the same hue: 5.36:1 on the sheet surface. */
  --action-destructive-on-surface:#E47B97;
  --presence-active:#4FA876; /* muted sage, not neon -- active-presence dot only, not a
                                 second UI accent; magenta remains the sole action colour */
  /* Sprint 18.3 -- --accent-editorial / --accent-editorial-paper (gold/brass)
     removed per Founder correction: no decorative accent colors beyond
     magenta, anywhere. Hierarchy on dark surfaces and on paper is now
     carried by --text-primary (white / dark ink) and --text-body / --muted
     (warm grey / brown-grey) only, plus weight and underline for emphasis. */

  --surface-selected:rgba(226,25,181,0.12); /* single shared tint for active toggle/chip states */

  --font-d:'Oswald','Arial Narrow',Impact,sans-serif;
  --font-c:'Roboto',-apple-system,"Segoe UI",Arial,sans-serif;
  --patient:cubic-bezier(0.65,0,0.35,1);
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px; --s7:48px; --s8:64px; --s9:80px;
  --radius:14px;
  --preview-banner-h:56px;
  /* Sprint 19.2 -- shared mobile content safe-area: clears the bottom nav
     (~64px) and the PL8 FAB, which floats above it at bottom:86px with its
     own ~48px height (obstruction reaches ~134px from the screen bottom).
     Applies via .app-content's own padding, so every screen gets it -- not
     a per-recipe fix. Overridden by the desktop rule below 1024px, where
     there's no bottom nav or low-riding FAB to clear. */
  --mobile-safe-bottom:190px;
  /* MOBILE BOTTOM NAVIGATION -- scroll-aware retreat (Constitution 5.2,
     DH-14). html.nav-retreated is written by one owner only (appNav in
     app.js). --nav-offset is the one distance a bottom-anchored surface
     follows: the bar's height above the safe area while the navigation is
     retreated, 0 while it is in view -- the safe area itself is never given
     up. The bar itself travels further (its whole height plus the dynamic-
     toolbar allowance, see the retreat rule): it must leave the screen, a
     follower must stay on it. --nav-motion is the one timing every follower
     shares. */
  --nav-bar-h:58px;
  --nav-offset:0px;
  --nav-motion-ms:220ms;
  --nav-motion:var(--nav-motion-ms) cubic-bezier(.22,.61,.36,1);
}
@media (min-width:480px){ :root{ --preview-banner-h:36px; } }

*{ box-sizing:border-box; }
/* HORIZONTALE RANDSTABILITÄT.
   Bewusst NICHT overflow-x:hidden — das wäre eine Maske über der Geometrie
   und würde absichtliche horizontale Komponenten (Table Stories) gefährden.
   overscroll-behavior-x steuert ausschließlich das RANDVERHALTEN: es
   unterbindet horizontales Ketten und Gummiband am Dokumentrand, lässt
   vertikales Scrollen unberührt und lässt Komponenten mit eigenem
   horizontalem Scroller weiterhin normal scrollen.
   Adressiert die Fehlerklassen E (Gummiband) und F (bleibende
   Verschiebung), NICHT A/B (Layout-Überlauf) — die sind über das
   Viewport-Tor in reviews/ separat abgesichert.
   AM GERÄT BESTÄTIGT (iPhone, aktuelle iOS-Generation, 2026-09-09): ohne
   diese Zeile blieb die Seite horizontal verschiebbar, mit ihr ist sie
   verankert. Am Schreibtisch ist der Defekt nicht reproduzierbar — die
   Layout-Messung war über alle Breiten hinweg grün, während das Gerät
   fehlschlug. Wer diese Zeile entfernen will, braucht dafür einen neuen
   Test auf einem echten iPhone, keine Desktop-Messung. */
html{ overscroll-behavior-x:none; }
html,body{ margin:0; padding:0; background:var(--canvas); color:var(--text-primary); font-family:var(--font-c); }
body{ -webkit-font-smoothing:antialiased; padding-top:var(--preview-banner-h); }
h1,h2,h3,h4{ margin:0; font-family:var(--font-d); text-transform:uppercase; }
p{ margin:0; }
a{ color:inherit; }
button{ font-family:inherit; }
img{ max-width:100%; display:block; }

/* ---------- persistent public-preview marker (infrastructure, not product design) ---------- */
#preview-banner{ position:fixed; top:0; left:0; right:0; z-index:50; min-height:var(--preview-banner-h);
  display:flex; align-items:center; justify-content:center; text-align:center;
  padding:6px max(14px, env(safe-area-inset-right)) 6px max(14px, env(safe-area-inset-left));
  background:var(--surface-2); border-bottom:1px solid var(--magenta);
  color:var(--text-body); font-size:11.5px; line-height:1.4; }
#preview-banner strong{ color:var(--text-primary); }
.preview-note{ font-size:12px; line-height:1.5; color:var(--muted); text-align:left; margin:0 0 20px; }

/* Privacy closeout -- quiet, non-blocking Privacy Notice update card (dashboard) and the
   small Imprint / Privacy Notice link line for public pages that end without one. */
.pn-update{ margin:14px 0 20px; padding:14px 16px; border:1px solid var(--hairline); border-radius:12px; background:var(--surface); }
.pn-update-t{ margin:0 0 6px; font-size:14px; color:var(--text-primary); }
.pn-update-l{ margin:0 0 8px; padding-left:18px; font-size:13px; line-height:1.5; color:var(--text-secondary); }
.pn-update-a{ margin:0; display:flex; flex-wrap:wrap; gap:4px 16px; }
.legal-links{ margin:28px 0 8px; text-align:center; font-size:12px; color:var(--muted); }

/* ---------- shared components ---------- */
.btn{ font-family:var(--font-c); font-weight:600; font-size:14px; letter-spacing:0.01em; border-radius:999px;
  padding:14px 28px; cursor:pointer; border:none; display:inline-block; text-decoration:none; min-height:46px;
  transition:opacity 240ms var(--patient), transform 160ms var(--patient), border-color 200ms var(--patient); }
.btn-primary{ background:var(--magenta); color:var(--canvas); }
.btn-primary:hover{ opacity:0.9; transform:translateY(-1px); }
.btn-ghost{ background:transparent; color:var(--text-primary); border:1px solid var(--hairline); }
.btn-ghost:hover{ border-color:var(--border-strong); }
.btn-quiet{ background:none; border:none; color:var(--muted); text-decoration:underline; font-size:13px; cursor:pointer; padding:6px 2px; }
.btn-quiet:hover{ color:var(--text-primary); }
.btn:focus-visible, a:focus-visible, input:focus-visible{ outline:2px solid var(--magenta); outline-offset:2px; }
.btn-block{ width:100%; text-align:center; }

.card{ background:var(--surface); border:1px solid var(--hairline); border-radius:var(--radius); padding:var(--s5); }
.kicker{ font-family:var(--font-c); font-size:12px; font-weight:600; letter-spacing:0.14em; text-transform:uppercase; color:var(--muted); margin:0 0 10px; }
.badge{ display:inline-block; font-size:11px; font-weight:600; letter-spacing:0.08em; text-transform:uppercase; padding:4px 10px; border-radius:999px; }
.badge-free{ background:var(--surface-2); color:var(--text-secondary); }
.badge-pl8{ background:var(--surface-premium); color:var(--magenta-text-safe); }
.field{ margin-bottom:var(--s5); text-align:left; }
.field label{ display:block; font-size:12.5px; color:var(--muted); margin-bottom:6px; }
.field input{ width:100%; background:var(--surface-2); border:1px solid var(--hairline); border-radius:10px; padding:13px 14px;
  color:var(--text-primary); font-size:15px; font-family:var(--font-c); }
.field input::placeholder{ color:var(--muted); }
.field input:focus{ outline:2px solid var(--magenta); outline-offset:1px; border-color:var(--magenta); }

.hairline{ height:1px; background:var(--hairline); border:none; margin:var(--s5) 0; }

/* Post-signup ceremony (Refinement Sprint): the threshold moment between
   "completing the form" and "being inside," rebuilt so the Signal tells
   its own story instead of sitting beside a caption. Sole visual hero --
   fixed, full-bleed, opaque, above the entire app shell (preview banner
   included), so there is nothing else on screen to compete with it. No
   dialog chrome, nothing to dismiss; it ends itself. */
.ceremony-overlay{ position:fixed; inset:0; z-index:100; display:flex; align-items:center;
  justify-content:center; overflow:hidden; background:var(--canvas);
  opacity:0; transition:opacity 500ms var(--patient); }
.ceremony-overlay.ceremony-in{ opacity:1; }
/* Vessel atmosphere -- same locked vignette values as the homepage hero,
   scoped to this overlay so the ceremony visibly belongs to the same
   world rather than being a plain dialog on a flat ground. */
.ceremony-vignette{ position:absolute; inset:0; z-index:0; pointer-events:none;
  background:radial-gradient(1100px 800px at 50% 46%, rgba(255,255,255,.022) 0%, transparent 45%, rgba(0,0,0,.84) 100%); }
.ceremony-grain{ position:absolute; inset:0; z-index:0; pointer-events:none; opacity:.14; mix-blend-mode:soft-light;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='%75%72%6C%28%23n%29'/%3E%3C/svg%3E");
  background-size:120px 120px; }

.ceremony-signal{ position:relative; z-index:1; width:min(72vw,460px); height:min(72vw,460px);
  transition:opacity 150ms var(--patient); }
.ceremony-signal svg{ position:absolute; inset:0; width:100%; height:100%; display:block; overflow:visible; }
/* Fix Ceremony -> PACE Transition Artifact: the ring/seat and statement
   are the only high-contrast shapes in the overlay -- the overlay's own
   flat background already matches the PACE canvas exactly, so once
   these are hidden there's nothing left to visibly cross-fade against
   the new scene underneath. Fast and in parallel with the container's
   own opacity fade, not sequential -- no added time. */
.ceremony-overlay.ceremony-out .ceremony-signal,
.ceremony-overlay.ceremony-out .ceremony-text{ opacity:0; }
/* Ring tone is the existing ink token mixed with the existing canvas token --
   no new color, no glow -- so the ring reads as the same warm-near-black
   atmosphere catching light, not a grey shape laid on top of it. */
.ceremony-ring{ fill:none; stroke:color-mix(in srgb, var(--text-primary) 44%, var(--canvas) 56%);
  stroke-width:14; stroke-linecap:butt; }
/* The square starts displaced radially outward at the gap's own angle (30
   deg) -- "clearly visible outside the gap area" -- and travels inward to
   the exact resting position used everywhere else in the product (the
   locked construction, untouched). Rotation is fixed at all times; only
   the approach moves. */
.ceremony-seat{ fill:color-mix(in srgb, var(--text-primary) 44%, var(--canvas) 56%);
  transform-box:fill-box; transform-origin:50% 50%;
  transform:translate(13.9px,8px) rotate(30deg);
  transition:transform 950ms var(--patient), fill 550ms var(--patient); }
.ceremony-seat.arrived{ transform:translate(0,0) rotate(30deg); }
.ceremony-seat.peak{ fill:var(--magenta-brand); transition:transform 950ms var(--patient), fill 260ms var(--patient); }

/* Typography sits inside the ring's own negative space -- framed by the
   symbol, not placed beside it. */
.ceremony-text{ position:absolute; inset:14%; z-index:2; display:flex; flex-direction:column;
  align-items:center; justify-content:center; text-align:center; pointer-events:none;
  transition:opacity 150ms var(--patient); }
/* Setup line recedes (lower rest opacity) so the statement below it reads
   as the clear peak, not a second line of equal weight. */
.ceremony-quiet{ font-family:var(--font-c); font-size:clamp(11px,1.6vw,13px); font-weight:600;
  letter-spacing:0.14em; text-transform:uppercase; color:var(--muted);
  margin:0 0 14px; opacity:0; transition:opacity 600ms var(--patient); }
.ceremony-overlay.ceremony-in .ceremony-quiet{ opacity:.6; }
.ceremony-statement{ font-family:var(--font-d); font-weight:700; text-transform:uppercase;
  font-size:clamp(25px,5.2vw,39px); line-height:1.06; letter-spacing:-0.008em;
  color:var(--text-primary); margin:0; opacity:0; transform:translateY(6px);
  transition:opacity 700ms var(--patient), transform 700ms var(--patient); }
.ceremony-statement.statement-in{ opacity:1; transform:translateY(0); }

@media (prefers-reduced-motion:reduce){
  .ceremony-overlay, .ceremony-quiet, .ceremony-statement, .ceremony-seat{ transition:none !important; }
  .ceremony-overlay{ opacity:1; }
  .ceremony-overlay .ceremony-quiet{ opacity:.6; }
  .ceremony-overlay .ceremony-statement{ opacity:1; transform:none; }
  .ceremony-seat{ transform:translate(0,0) rotate(30deg); }
}

/* ==========================================================================
   PUBLIC WEBSITE — full-bleed, natural scroll, no app frame
   ========================================================================== */
/* overflow-x:CLIP, not hidden. Per spec `overflow-x:hidden` forces the
   computed `overflow-y` to `auto`, which makes this element a SCROLL
   CONTAINER -- and position:sticky sticks to its nearest scrolling
   ancestor. Because #public-site is a scroll container that never
   actually scrolls (the window does), every sticky descendant silently
   never activated: the Held Stage travelled with the document exactly as
   if sticky were absent. `clip` gives the same horizontal-overflow
   protection without establishing a scroll container, so sticky resolves
   against the viewport as intended. Verified: the stage's image travels
   0px across the whole scene with clip, ~1170px with hidden. */
#public-site{ display:none; position:relative; width:100%; overflow-x:clip; }
/* width:100% / height:100%, not 100vw/100vh: on a fixed inset:0 box the
   viewport units are redundant, and 100vw deliberately INCLUDES the
   classic scrollbar -- which made this decorative canvas ~15px wider
   than the document on desktop. inset:0 already sizes it correctly. */
#public-site canvas#pub-net{ position:fixed; inset:0; width:100%; height:100%; z-index:0; pointer-events:none; }
#public-site .veil{ position:fixed; inset:0; z-index:1; pointer-events:none;
  /* C · Vessel vignette, locked values (Constitution §2.1): minimal centre
     lift, hard fall to a near-black edge wash -- layered over the existing
     Sprint 14 hero glow/field canvas, not replacing it. */
  background:radial-gradient(1100px 800px at 50% 40%, rgba(255,255,255,.022) 0%, transparent 45%, rgba(0,0,0,.84) 100%); }
#public-site .pub-grain{ position:fixed; inset:0; z-index:1; pointer-events:none;
  opacity:.14; mix-blend-mode:soft-light;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='%75%72%6C%28%23n%29'/%3E%3C/svg%3E");
  background-size:120px 120px; }

.pub-nav{ position:fixed; top:var(--preview-banner-h); left:0; right:0; z-index:5; display:flex; align-items:center; justify-content:space-between;
  padding:18px max(24px, env(safe-area-inset-right)) 18px max(24px, env(safe-area-inset-left));
  background:linear-gradient(to bottom, rgba(16,14,19,0.92), transparent); }
/* Hero Refinement Sprint (all breakpoints): the large centred hero
   wordmark is now the homepage's single brand anchor, at every breakpoint
   -- the small nav-corner instance is retired, not just hidden on mobile. */
.pub-nav .wordmark{ display:none; }
.wordmark-dot{ fill:var(--magenta); }
.pub-nav .pub-nav-actions{ display:flex; gap:10px; align-items:center; }
.pub-nav .btn{ padding:10px 20px; font-size:13px; }
/* display:inline-block, not none -- logged-out mobile now offers the same
   two auth actions as desktop. This was previously hidden here and
   un-hidden only at >=641px, which left mobile with "Kostenlos mitmachen"
   alone and no way to reach the login screen from the public header.
   Desktop's own proven value is simply promoted to the base rule; the
   markup, order, handlers, hierarchy and tokens are all unchanged. */
.pub-nav .pub-login{ background:none; border:none; color:var(--muted); font-size:13px; cursor:pointer; display:inline-block; }
/* Hero Refinement Sprint: the hero's own centred primary CTA is removed
   (see .pub-hero .actions below), so the nav CTA is now the sole
   "Kostenlos mitmachen" on the homepage -- visible at every breakpoint,
   not just desktop. */

/* Public Experience Redesign, Phase 2 -- editorial scale and space.
   Vertical rhythm moves from a flat 96px to viewport-relative breathing
   room, so spacing scales with the composition instead of staying constant
   while type grows. Peak sections get more air than quiet ones: the space
   is part of the hierarchy, not a uniform gutter. */
.pub-section{ position:relative; z-index:2; width:100%; padding:clamp(112px,17vh,208px) max(24px, env(safe-area-inset-right)) clamp(112px,17vh,208px) max(24px, env(safe-area-inset-left)); }
/* Peak scenes are given a viewport floor rather than only padding. A scene
   that cannot be scrolled past without at least most of a screen to itself
   reads as a scene; one sized purely by its content reads as a block. This
   is the single change that turns sections into movements -- but it is a
   MIN-height, not a fixed height, so nothing is padded into emptiness:
   content taller than the floor simply grows past it. */
.pub-section.pub-peak{ padding-top:clamp(136px,23vh,280px); padding-bottom:clamp(136px,23vh,280px);
  min-height:82svh; display:flex; flex-direction:column; justify-content:center; }
/* Hero -> Story -> Free Community pacing (Transition Refinement Sprint):
   deliberately wider than the section default so each boundary reads as a
   pause, not a page break -- during the extra scroll distance the fixed
   Signal (unchanged, still hero-scoped CSS) is the only thing on screen,
   which is what makes it feel like it's guiding the page rather than
   sitting behind it. Composition/spacing only; the Signal itself is not
   touched here. */
#pub-story{ padding-top:120px; padding-bottom:120px; }
#pub-free{ padding-top:120px; }
.pub-section-inner{ max-width:680px; margin:0 auto; }
.pub-section-inner.wide{ max-width:820px; }
/* 680px physically cannot hold 60px+ type without wrapping every two or
   three words -- the old column width was itself a cause of the "document,
   not editorial" feel. Major statements now get 900px. */
.pub-section-inner.pub-wide{ max-width:900px; }
.pub-hero{ position:relative; min-height:calc(100svh - var(--preview-banner-h)); display:flex; align-items:center; overflow:hidden; }
/* The Signal, atmospheric background use (Constitution §1.2/§2.1): large,
   behind the foreground content, masked so it fades at its own edges
   rather than sitting on the surface like an illustration. Partially
   cropped by position is correct -- §5.1: "a fragment is correct for a
   mark whose meaning is incompleteness."
   MATERIAL, brought into the Satin Magenta family (Signal Governance
   sprint, DH-13): the flat near-white fill is retired the same way as
   the Menschen instance -- inline <svg> set to opacity:0 below, a
   masked ::before takes over as the visible material. "The material
   should match. The prominence should not": SAME gradient recipe as
   Menschen (same tonal stops, same magenta family -- material varies
   in intensity, never in type, per §1.2), but at a fraction of the
   opacity, and with NO sheen layer at all -- Hero's choreography
   structure is explicitly out of scope this sprint, and a sheen would
   need a new GSAP tween to trigger it meaningfully; staying static
   here is the conservative reading of "do not make the Hero louder."
   Never louder than the wordmark remains true: --so/--sc below are
   completely unchanged (still recede-only, still scroll-driven by the
   existing Hero choreography), and the new ::before's own low opacity
   keeps the mark's peak intensity well under where the near-white
   .075 fill used to sit perceptually. */
/* THE SIGNAL'S STAGE. On desktop it generates no box at all
   (display:contents), so .pub-hero-signal keeps resolving against
   .pub-hero exactly as before -- desktop composition untouched. Below
   640px it becomes the viewport-locked, clipping containing block that
   the fixed mark used to lack. Decorative only: it must never enlarge
   document geometry, and with overflow:clip it cannot. */
.pub-signal-stage{ display:contents; }
@media (max-width:640px){
  .pub-signal-stage{ display:block; position:fixed; inset:0; z-index:1;
    overflow:clip; pointer-events:none; }
}
.pub-hero-signal{ position:absolute; top:6%; right:-20%; z-index:1; pointer-events:none;
  width:min(84vw,820px); height:min(84vw,820px);
  /* --so/--sc MUST be declared here, not left to the var() fallback.
     GSAP reads the computed start value when it builds a tween, and an
     undeclared custom property computes to the empty string, which GSAP
     parses as 0 -- producing a tween that runs 0 -> target, i.e. the
     Signal fading UP into view. That is both backwards and a governance
     breach (recede-only). Declaring them gives GSAP a real 1 to start
     from. Verified live: without these, --so animated 0 -> 0.28. */
  --so:1; --sc:1;
  opacity:var(--so,1);
  /* CROP-MASK, corrected (Signal Scale + Proportion sprint): 55%/78% ->
     78%/97%. Root cause of the perceived weight difference against the
     "Du nimmst deinen Platz ein." ceremony Signal (which has no mask at
     all) was never geometry -- the stroke-to-viewBox ratio is 14/200
     (7%) in every instance, byte-identical. It was this mask: computed
     against the radial-gradient's farthest-corner sizing basis, the
     ring's own stroke sits at ~65.8% of that basis, which the OLD
     55%/78% stops placed inside the fade zone -- measured effective
     ring alpha ~53%, silently thinning the stroke by roughly half
     before opacity/--so are even applied. 78%/97% (already proven
     correct on the Menschen instance) keeps the ring's own radius
     entirely inside the fully-opaque zone -- ring alpha 100% -- and
     only fades the empty box corners beyond it, which was the mask's
     actual intended job all along. Opacity/scale/position are
     untouched -- those are the axes allowed to vary by context; this
     fixes only the accidental ratio distortion. */
  -webkit-mask-image:radial-gradient(circle at 50% 50%, black 78%, transparent 97%);
  mask-image:radial-gradient(circle at 50% 50%, black 78%, transparent 97%);
  transform-origin:50% 50%;
  transform:translate(var(--px,0px), calc(var(--py,0px) + var(--sy,0px))) scale(var(--sc,1));
  animation:signalBreathe 19s cubic-bezier(.22,.61,.36,1) infinite; }
.pub-hero-signal svg{ width:100%; height:100%; display:block; opacity:0; }
.pub-hero-signal::before{
  content:''; position:absolute; inset:0;
  -webkit-mask-image:var(--signal-mask-src); mask-image:var(--signal-mask-src);
  -webkit-mask-size:100% 100%; mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  background:var(--signal-satin-gradient);
  opacity:.11; }
/* Signal scroll variables (Premium Hero sprint). --so (opacity) and --sc
   (scale) are written by the Hero choreography in app.js so the Signal
   outlasts the rest of the composition and then recedes -- "bridge by
   persistence": it is the last thing standing, and it never brightens or
   grows to earn that. Both are recede-only (never above 1), so the Signal
   can never become louder than its resting state and never forms a second
   ceremonial peak.
   Critically, GSAP writes only these VARIABLES -- never `transform`
   itself. CSS below stays the single transform owner, composing scroll
   (--sc), pointer parallax (--px/--py) and the breathing keyframe into
   one expression. That is what keeps pointer parallax working while the
   scroll timeline runs, with no competing writers.
   --sy remains supported as a translate contributor but is currently
   unused; the Premium choreography moves the Signal by scale/opacity
   only. Elements reusing this keyframe (.entry-signal-inner,
   .ceremony-signal-inner) never set any of these and fall through to the
   defaults, so they are completely unaffected. */
@keyframes signalBreathe{
  0%,100%{ transform:translate(var(--px,0px), calc(var(--py,0px) + var(--sy,0px))) scale(var(--sc,1)) rotate(0deg); }
  50%{ transform:translate(var(--px,0px), calc(var(--py,0px) + var(--sy,0px))) scale(calc(1.03 * var(--sc,1))) rotate(3deg); } }
/* Persistent Signal (Mobile Hero & Persistent Signal Integration, approved):
   below 640px the Signal becomes a fixed, viewport-anchored layer instead
   of scrolling away with the hero -- "the Signal remains the stage" as the
   user moves into the next section. Geometry, colour, mask and the
   breathing/parallax motion are all unchanged; only position and scale
   move, per the stated rule (composition/scale/positioning/continuity
   only). Desktop is untouched -- still position:absolute, scoped to the
   hero, as implemented in the Front Door Production Integration Sprint. */
@media (max-width:640px){
  /* GLOBAL VIEWPORT FIX. The stage is what is fixed now; the mark is
     absolute inside it and keeps top/right/width/height byte-for-byte.
     Same composition, same bleed off the right edge (§5.1), same
     signalBreathe transform -- but the box that can be reached by a
     horizontal pan is the viewport, not 155vw. */
  .pub-hero-signal{ position:absolute; top:2%; right:-38%; width:155vw; height:155vw; }
}
/* Centered-brand hero wordmark, now universal (Hero Refinement Sprint):
   was mobile-only (Mobile Hero Recomposition Exploration), now the single
   brand anchor at every breakpoint since the nav-corner instance is gone.
   Height reduced ~19% from the prior 64px to give the kicker, headline,
   message and "Mehr erfahren" more room -- geometry, position and colour
   otherwise unchanged. */
.hero-brand-wordmark{ display:flex; justify-content:center; margin-bottom:21px; }
.hero-brand-wordmark svg{ display:block; width:auto; height:52px; }
.pub-hero .pub-section-inner{ position:relative; z-index:2; text-align:center; }
.pub-hero h1{ font-size:clamp(38px,6.5vw,70px); line-height:1.05; letter-spacing:0.008em; margin:22px 0 0; }
.pub-hero .muted-line{ color:var(--muted); font-weight:500; }
.pub-hero .lede{ font-size:17px; line-height:1.6; opacity:0.9; margin:22px auto 0; max-width:460px; }
.pub-hero .actions{ margin-top:36px; display:flex; gap:14px; justify-content:center; flex-wrap:wrap; }
.pub-hero .reassurance{ margin-top:18px; font-size:12.5px; color:var(--muted); }
/* Presence in the hero must read as atmosphere, not a claim -- quieter
   than the dashboard's presence line (Vessel Integration Sprint): smaller,
   more muted, last in the content stack, never competing with the CTA. */
.pub-hero-presence{ justify-content:center; margin-top:28px; font-size:12px; opacity:.8; }
/* Green communicates active presence specifically -- scoped to the public
   hero only. The dashboard's presence dot (Vessel Integration Sprint)
   keeps its original muted --text-secondary treatment; not part of this
   sprint's scope. */
.pub-hero-presence .presence-dot{ background:var(--presence-active); }
/* Sprint 14 final -- the CRVD.KITCHEN opening shot. No traveling element,
   no keyframes, no glow/halo, no loop. One class toggle drives three
   staggered one-way settles that all start together and finish in order
   purely by differential duration: headline first, "TREND." a beat later,
   primary CTA last (material shadow depth + one clipped reflection
   sweep). Entirely disabled under prefers-reduced-motion -- the page then
   paints directly in its final, settled state. Secondary CTA and "KEIN"
   are never touched. */

/* "TREND." -- refined satin material treatment only (Founder-approved
   final pass): a tight, warm gradient plus a very fine vertical grain,
   clipped to the letterforms. This gradient itself never animates or
   sweeps -- only its brightness settles, alongside the rest of the
   system, via the media-query block below. */
.pub-hero .hero-trend{
  font-weight:500;
  background-image:
    repeating-linear-gradient(90deg, rgba(255,255,255,0.05) 0px, rgba(255,255,255,0.05) 1px, rgba(0,0,0,0.045) 1px, rgba(0,0,0,0.045) 2px),
    linear-gradient(100deg, #887F84 0%, #9B928E 45%, #A29A96 55%, #887F84 100%);
  -webkit-background-clip:text; background-clip:text; color:transparent; -webkit-text-fill-color:transparent;
}

/* Primary CTA -- resting material shadow (never a colored glow) plus the
   one-shot reflection sweep. The sweep element is clipped by the button's
   own overflow:hidden, so it is only ever visible while crossing the
   button and invisible at both ends -- never a resting shine, never
   repeats. Scoped to .pub-hero so no other button on the site is
   affected. */
.pub-hero .btn-primary{ position:relative; overflow:hidden;
  box-shadow:0 10px 24px -8px rgba(35,10,20,0.45), 0 2px 6px rgba(20,6,12,0.3), inset 0 1px 0 rgba(255,255,255,0.10); }
.pub-hero .btn-primary::after{
  content:''; position:absolute; top:-60%; left:0; width:34%; height:220%;
  background:linear-gradient(100deg, transparent 0%, rgba(255,255,255,0.24) 50%, transparent 100%);
  transform:translateX(340%) rotate(10deg);
  pointer-events:none;
}

@media (prefers-reduced-motion: no-preference){
  .pub-hero .hero-headline-settle{ transition:opacity 800ms var(--patient), filter 800ms var(--patient); }
  .pub-hero .hero-trend{ transition:opacity 1100ms var(--patient), filter 1100ms var(--patient); }
  .pub-hero .btn-primary{ transition:box-shadow 1600ms var(--patient); }
  .pub-hero .btn-primary::after{ transition:transform 1200ms var(--patient); }

  .pub-hero.hero-arriving .hero-headline-settle{ opacity:0.85; filter:brightness(0.92); }
  .pub-hero.hero-arriving .hero-trend{ opacity:0.8; filter:brightness(0.88); }
  .pub-hero.hero-arriving .btn-primary{ box-shadow:0 2px 6px -3px rgba(20,6,12,0.15), inset 0 0 0 rgba(255,255,255,0); }
  .pub-hero.hero-arriving .btn-primary::after{ transform:translateX(-260%) rotate(10deg); }

  /* ARRIVAL -> SCROLL OWNERSHIP HANDOFF (Premium Hero sprint).
     The arrival above animates opacity/filter for up to ~1.6s. The scroll
     choreography animates opacity on the same elements. A visitor who
     scrolls immediately after load would otherwise have both writing the
     same properties at once, which flickers. So the moment app.js builds
     the scroll timeline it stamps .hero-scroll-owned here, and every
     arrival transition is dropped: arrival keeps its visual result (it is
     one-way and lands on the resting state) but stops being an animator.
     Exactly one owner at any moment, decided by a class rather than by
     whether the user happened to wait. */
  .pub-hero.hero-scroll-owned .hero-headline-settle,
  .pub-hero.hero-scroll-owned .hero-trend,
  .pub-hero.hero-scroll-owned .btn-primary,
  .pub-hero.hero-scroll-owned .btn-primary::after{ transition:none; }
}

/* Recession, section arrival (Hero Recession Sprint): staggered by child
   position rather than as one block -- kicker, then heading, then the
   rest of the section's body, 0/90/180ms apart. Same one-way,
   differential-duration grammar the Hero arrival already uses. Works
   across every .pub-section unmodified because each one already opens
   kicker -> heading -> body, in that DOM order. */
.pub-section-inner.pub-reveal > *{ opacity:0; transform:translateY(22px); transition:opacity 700ms var(--patient), transform 700ms var(--patient); }
.pub-section.seen .pub-section-inner.pub-reveal > *{ opacity:1; transform:translateY(0); }
.pub-section.seen .pub-section-inner.pub-reveal > *:nth-child(2){ transition-delay:90ms; }
.pub-section.seen .pub-section-inner.pub-reveal > *:nth-child(n+3){ transition-delay:180ms; }

/* ---------------------------------------------------------------------- *
 * PUBLIC EDITORIAL SCALE (Public Experience Redesign, Phase 2)
 *
 * The page previously ran 70 (hero) then 34/38/38/38/38/38 then 50 -- one
 * peak followed by six identical mid-weight sections. That flatness, not
 * missing animation, is what made everything after the Hero feel like a
 * different (and lesser) product.
 *
 * The rhythm is now deliberate:
 *   PEAK   Die Frage    ~64   the differentiating idea
 *   QUIET  Das Essen    ~28   the image carries the section
 *   MID    Die Menschen ~44   voices carry the section
 *   PEAK   Frei         ~56   /  QUIET  PL8 ~30  -- hierarchy as scale
 *   FINAL  Einladung    ~72   deliberately the largest type on the page
 *
 * The finale out-scaling the Hero is intentional: the conversion moment
 * should be the loudest thing a visitor reaches, not a smaller echo of the
 * opening. All of these are one-line declarations so the rhythm can be
 * read, and adjusted, in a single place. */
.pub-h-peak{ font-size:clamp(38px,6vw,64px); line-height:1.04; letter-spacing:0.006em; margin:18px 0 0; }
/* Frei sits just below Die Frage. Two peaks at an identical size read as
   two equal claims; the differentiating idea has to stay the taller one. */
#pub-frei .pub-h-peak{ font-size:clamp(34px,5.2vw,56px); }
.pub-h-mid{ font-size:clamp(30px,4.6vw,44px); line-height:1.1; margin:16px 0 0; }
.pub-h-quiet{ font-size:clamp(22px,2.9vw,28px); line-height:1.25; margin:0; }
.pub-h-sub{ font-size:clamp(21px,2.9vw,30px); line-height:1.3; margin:14px 0 0; font-weight:500; }
.pub-h-final{ font-size:clamp(40px,7vw,72px); line-height:1.02; letter-spacing:0.004em; margin:18px 0 0; }

/* Supporting copy scales with the statements it supports -- a 64px
   headline over 15px body is what made the old sections read as cramped. */
/* The gap between a statement and its support is doing hierarchy work:
   too tight and they read as one paragraph, so it scales with the
   viewport rather than sitting at a fixed 22px. */
.pub-lede-lg{ font-size:clamp(17px,1.55vw,20px); line-height:1.6; color:var(--text-body);
  max-width:34em; margin:clamp(30px,4.6vh,52px) 0 0; }
.pub-aside{ font-size:14.5px; line-height:1.6; color:var(--text-secondary);
  margin:clamp(22px,3vh,32px) 0 0; max-width:40em; }

/* ---------------------------------------------------------------------- *
 * THE HELD STAGE -- Phase 1 (Frage -> Essen).
 *
 * Replaces the travelling-Aperture architecture. Previously each scene
 * had its OWN position:sticky stage, which only behaves as a stage once
 * that section's top reaches the viewport top -- entry and exit were
 * therefore ordinary document flow and the food container visibly
 * travelled upward before and after the staged moment.
 *
 * Now ONE sticky stage spans both scenes: it locks at the start of Frage
 * and releases only after Essen. Both scenes are absolutely positioned
 * layers inside it, so nothing in the stage is ever positioned as a
 * function of scrollY and no content container can travel.
 *
 * The stage itself must never receive a transform -- .veil / .pub-grain /
 * #pub-net are position:fixed siblings, and a transformed ancestor would
 * become their containing block (the documented boxed-vignette bug
 * class). GSAP animates only the LAYERS inside the stage, never the
 * stage or the wrapper.
 *
 * .pub-held-wrap supplies scroll duration only and has no visual
 * treatment. Menschen is not part of this stage yet (Phase 2). */
/* 230vh -> 290vh (Held Stage Phase 2, Menschen integration). The
   existing Frage->Food timeline is untouched -- its tween positions in
   initHeldStage are exactly as before; extending the wrapper only gives
   the newly-appended food-recession + 3-voice + Signal-return + release
   tweens their own room after it, inside Founder's approved 285-300vh
   target. Verified live against real measured pixel ranges, not just
   computed from this value -- see the sprint report for actual numbers. */
/* 260vh -> 285vh (Refinement sprint): the increase is entirely the new
   Signal-as-final-beat content (photograph further darkens, a brief
   unscheduled pause, the Signal emerges and settles) -- voice pacing
   itself was tightened, not lengthened; see initHeldStage. */
/* 285vh -> 300vh (Visual Polish sprint): Founder's read was that the
   Voice 1 -> 2 transition felt rushed. Fix is longer HOLD per voice
   (initHeldStage timeline duration grew 2.38->2.50, +5%), and this
   height grew by the same proportion so the extra timeline duration
   becomes more reading time at the same pacing, not the same duration
   spread thinner. 300vh lands exactly at the previously Founder-
   approved 285-300vh ceiling from the prior sprint -- no new range
   opened. */
/* 300vh -> 318vh (Food -> Signal Handoff sprint): the old pre-emerge
   food-darkening step (~0.20 timeline units) is removed outright and
   reallocated into a new post-emerge Food Recession beat plus a
   genuine Signal Solo Hold (initHeldStage timeline duration grew
   2.50->2.65, +6%) -- net growth is deliberately small because most of
   the new beats' time was reclaimed from the step they replaced, not
   added on top of an unchanged timeline. Height grew by the same ~6%
   so the Solo Hold gets real reading distance rather than the same
   pacing spread thinner. */
/* 318vh -> ~190vh -> ~300vh (Held-Stage Story Refinement v2): the food-
   emergence and dual-voice content stays retired; timeline duration
   1.60->2.45. Explicitly a TEST value, not a locked standard -- the v1
   single-line beats read as too thin, so v2 trades a longer section for
   real editorial reading time (each beat now carries an eyebrow,
   headline and subline, HOLD-dominant) and a genuinely multi-stage
   Signal climax. Frage's own budget (0.00-0.43) is untouched, and the
   vh-per-unit rate is held constant so its own scroll-pixel feel is
   unaffected by the section's total growth. See initHeldStage's
   timeline comment in app.js for the beat-by-beat reasoning. */
.pub-held-wrap{ position:relative; z-index:2; width:100%; height:300vh; }
/* --stage-dim composes into filter:brightness() -- the PRE-REVEAL beat
   (initHeldStage) nudges this to ~0.93 for an "almost subconscious"
   darkening right before the Signal arrives, no new visual object.
   Declared here (not left to the var() fallback) for the same reason
   --so/--essen-blur are declared elsewhere in this file: GSAP reads the
   computed start value when building a tween, and an undeclared custom
   property resolves to the empty string, which breaks the filter
   shorthand entirely rather than just leaving brightness at 1. */
.pub-held-stage{ position:sticky; top:0; height:100svh; width:100%; overflow:hidden;
  --stage-dim:1; filter:brightness(var(--stage-dim,1)); }
.pub-held-layer{ position:absolute; inset:0; display:flex; flex-direction:column;
  align-items:center; justify-content:center;
  padding:0 max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left)); }
/* Frage sits above the sequence layer in paint order; both are dark by
   default now that there is no photograph beneath either of them. */
.pub-held-frage{ z-index:2; }
/* Headline lines as blocks so each can be given its own timing. They
   replace the previous <br/>, so the visual line break is unchanged. */
.pub-h-peak .hl-line{ display:block; }

/* THE SEQUENCE, v2 -- editorial triplets into THE SIGNAL (Held-Stage
   Story Refinement). Each beat is now eyebrow -> headline -> subline
   (not a single line) at ONE consistent structural position -- text
   block slightly left of optical center, left-aligned, a vertical
   3-dot progress indicator balancing the composition on the right.
   Same grammar as before: absolutely positioned beats, cross-composited
   by ONE scrubbed GSAP timeline, no pin. See initHeldStage's timeline
   comment in app.js for the exact beat-by-beat timing. */
.pub-held-signal-sequence{ z-index:1; }
.pub-held-story-beat{ position:absolute; inset:0; display:flex;
  align-items:center; justify-content:center;
  padding:0 max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left)); }
/* The centered flex parent + an asymmetric margin on the child is what
   produces "slightly left of optical center" -- the (block + margin) box
   is centered as a unit, but the extra right margin shifts the visible
   text left within it. Deterministic across viewport widths, no magic
   percentage offset to maintain. Fixed across all three beats -- never
   repositioned per beat, per Founder's explicit "stability of the
   composition is important." */
.pub-held-story-copy{ width:100%; max-width:600px;
  margin-right:clamp(48px,9vw,150px); text-align:left; }
.pub-held-story-copy > *{ opacity:0; }
/* Eyebrow: identical properties to .kicker (Frage's own), a dedicated
   class rather than multi-classing so this block's spacing is never
   coupled to .kicker's own margin if that ever changes elsewhere. */
.pub-held-story-eyebrow{ font-family:var(--font-c); font-size:12px; font-weight:600;
  letter-spacing:0.14em; text-transform:uppercase; color:var(--muted); margin:0 0 10px; }
/* Headline: sentence case, NOT the global h1-h4{text-transform:uppercase}
   rule -- a deliberate, SCOPED opt-out for this sequence only (Founder:
   "FRAGE remains the larger, declarative brand statement. The three new
   beats should feel more personal, editorial and human."). Nothing about
   the global typography system changes; only this one selector overrides
   it. Smaller than Frage's own .pub-h-peak (38-64px) since these screens
   now carry three lines of hierarchy, not one dominant headline. */
.pub-held-story-headline{ text-transform:none; font-family:var(--font-d); font-weight:600;
  line-height:1.16; letter-spacing:0.002em; color:var(--text-primary);
  font-size:clamp(28px,4vw,44px); margin:0; }
.pub-held-story-subline{ font-size:15.5px; line-height:1.6; color:var(--text-secondary);
  max-width:420px; margin:14px 0 0; }

/* PROGRESS DOTS -- pure indicator, never interactive (Founder-approved
   rejection of click-to-jump: it would need scrollTo() against a
   natively-scrolled, non-pinned wrapper, fighting Lenis's own RAF loop
   -- exactly the "artificial scrollTo logic" ruled out during planning).
   aria-hidden in the markup; pointer-events:none here as a second,
   independent guarantee it can never intercept a click even if that
   markup attribute were ever dropped by a future edit.
   Driven entirely by initHeldStage's SAME timeline via --dot-active/
   --dot-scale -- no separate ScrollTrigger, no listener, so reverse-
   scroll reverses it for free. */
.pub-held-progress{ position:absolute; top:50%; right:clamp(24px,5vw,64px);
  transform:translateY(-50%); z-index:2; opacity:0;
  display:flex; flex-direction:column; align-items:center; gap:22px;
  pointer-events:none; }
.pub-held-progress-dot{ position:relative; width:7px; height:7px; border-radius:50%;
  background:var(--hairline); opacity:0.6;
  transform:scale(var(--dot-scale,1)); }
.pub-held-progress-dot::after{ content:''; position:absolute; inset:0; border-radius:50%;
  background:var(--magenta); opacity:var(--dot-active,0); }

/* THE SIGNAL -- BRAND PUNCTUATION, relocated (Held-Stage Story
   Refinement). This is the page's ONE deliberate full Signal reveal in
   the public-page storytelling (the Hero's own Signal stays ambient
   background glow, untouched and locked). The three beats fall away, a
   genuine silence holds the empty dark stage, an almost-subconscious
   pre-reveal dim passes, THEN the Signal arrives as the sequence's
   resolution (see initHeldStage's timeline comment for the full
   multi-stage climax). "The statements fall away, the room goes dark,
   then CRVD's mark remains."
   SIZE, tested at a modest contextual increase for THIS reveal only
   (Held-Stage Story Refinement v2): min(42vh,450px) -> min(46vh,500px)
   desktop. Founder-approved as a TEST, not a new canonical size --
   "more authority, not a different Signal." Geometry, ring weight, mask
   (78%/97%, unchanged) and the Satin Magenta material recipe
   (--signal-satin-gradient, --signal-mask-src) are completely
   untouched -- this is a sizing constant only. Mobile deliberately NOT
   increased (min(37vh,260px), see the mobile block below) -- mobile
   screens are already tight, and the climax there comes from timing/
   contrast/silence, not added size.
   Scale reset 0.94 -> 0.93 (Held-Stage Story Refinement v2, matching
   Founder's "0.92-0.94" spec more precisely than v1's single value) --
   same signalBreathe reuse and --so/--sc/--sy discipline as every other
   Signal instance, no bounce, no pulse, no spin, ever. */
.pub-held-story-signal-beat{ position:relative; }
.pub-held-story-signal-inner{ width:min(46vh,500px); height:min(46vh,500px);
  position:relative; opacity:var(--so,0);
  -webkit-mask-image:radial-gradient(circle at 50% 50%, black 78%, transparent 97%);
  mask-image:radial-gradient(circle at 50% 50%, black 78%, transparent 97%);
  transform-origin:50% 50%;
  transform:translate(var(--px,0px), calc(var(--py,0px) + var(--sy,0px))) scale(var(--sc,0.93));
  animation:signalBreathe 19s cubic-bezier(.22,.61,.36,1) infinite; }
.pub-held-story-signal-inner svg{ width:100%; height:100%; display:block; opacity:0; }
.pub-held-story-signal-inner::before,
.pub-held-story-signal-inner::after{
  content:''; position:absolute; inset:0;
  -webkit-mask-image:var(--signal-mask-src); mask-image:var(--signal-mask-src);
  -webkit-mask-size:100% 100%; mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat; }
.pub-held-story-signal-inner::before{
  background:var(--signal-satin-gradient); }
.pub-held-story-signal-inner::after{
  background:linear-gradient(115deg,
    transparent 0%, transparent 42%, rgba(255,241,248,.65) 50%, transparent 58%, transparent 100%);
  background-size:280% 280%;
  background-position: calc(-90% + var(--sheen,0) * 260%) calc(-90% + var(--sheen,0) * 260%);
  opacity:var(--sheen-op,0); }
  /* mix-blend-mode:soft-light REMOVED (Food -> Signal Handoff artifact-
     cleanup sprint). A mask-image + mix-blend-mode combination on the
     same element is a well-documented cross-browser compositing risk --
     the blend forces an isolated compositing group for its stacking
     context, and some engines flatten that group against an opaque
     backdrop rather than a true transparent one, which reads as exactly
     the "rectangular dark panel bounded to the element's own box"
     Founder described, most visible once everything behind it (the
     food) has faded low enough to expose it. Plain alpha at .65 base +
     the --sheen-op envelope still reads as a soft highlight during its
     brief one-shot window; losing the blend-into-material quality is a
     small, acceptable trade against a persistent background artifact
     that was visible far outside the sheen's own beat. */

/* FREI . PL8 -- the gap does the work of the old section break. */
.pub-beat-gap{ height:clamp(84px,15vh,178px); }
.pub-beat-quiet{ opacity:0.88; max-width:44em; }
.pub-actions-mid{ margin-top:clamp(48px,7vh,78px); }

/* THE SIGNAL AS ENVIRONMENTAL BRAND ARCHITECTURE -- removed (Signal
   Consolidation sprint). Two partial rings previously sat here as
   static background decoration (Visual Polish sprint, ref 03), then
   got a quieted Satin material (Signal Material + Motion sprint).
   Founder's call: repeating the mark here weakens the one strong
   brand-punctuation moment (the Menschen beat) rather than reinforcing
   it -- "we already have enough THE SIGNAL moments on the page." Not
   replaced with any other decorative treatment; Frei/PL8 now carries
   itself on typography, spacing and motion alone, same as before any
   Signal fragments were ever added here. .pub-section.pub-peak's
   overflow:hidden was also removed -- it existed solely to contain
   these rings and nothing else in this section can overflow. */

.pub-columns{ display:flex; gap:36px; flex-wrap:wrap; justify-content:center; text-align:left; margin-top:36px; }
.pub-col{ max-width:220px; }
.pub-col .b-label{ font-size:12px; font-weight:600; letter-spacing:0.14em; text-transform:uppercase; color:var(--text-primary); margin:0 0 8px; }
.pub-col p{ font-size:14.5px; line-height:1.6; color:var(--text-body); }

.pub-statement{ font-family:var(--font-c); font-style:italic; font-weight:400; font-size:clamp(19px,3vw,24px); line-height:1.5;
  color:var(--text-primary); max-width:560px; margin:24px auto 0; }
/* Finale's italic line as the visual pivot (Visual Polish sprint, ref
   04) -- Founder named it directly: "may use CRVD Magenta if that
   improves the hierarchy." It does: this is the one line in the whole
   sequence marking the turn from case-being-made to action-being-
   offered, and magenta is what makes that turn legible at a glance
   rather than just at reading speed. Scoped to #pub-finale only --
   .pub-statement has exactly one other use in the file (this one), so
   this could have been the base rule, but scoping keeps the base rule
   honest for any future non-finale reuse. */
#pub-finale .pub-statement{ color:var(--magenta-brand); margin-top:clamp(28px,4vh,40px); }

.pub-steps{ display:flex; flex-direction:column; gap:32px; margin-top:40px; text-align:left; }
.pub-step{ display:flex; gap:20px; align-items:flex-start; }
.pub-step-num{ font-family:var(--font-d); font-weight:700; font-size:32px; color:var(--magenta); opacity:0.8; line-height:1; flex-shrink:0; width:52px; }
.pub-step h3{ font-size:17px; margin:2px 0 6px; }
.pub-step p{ font-size:14px; line-height:1.6; color:var(--text-body); margin:0; }

.pub-ticker{ margin-top:32px; text-align:left; }
.pub-tick-line{ font-size:14px; line-height:1.6; color:var(--text-body); margin:0 0 10px; }
.pub-tick-line .who{ color:var(--text-primary); font-weight:600; }
.pub-tick-line .when{ color:var(--text-secondary); font-size:12px; }

.pub-finale{ min-height:calc(100svh - var(--preview-banner-h)); display:flex; align-items:center; text-align:center; }
.pub-finale .pub-lede-lg{ margin-left:auto; margin-right:auto; }
/* RECOMPOSED AS ONE CENTERED FINAL FRAME (Visual Polish sprint, ref 04).
   The section was already vertically/horizontally centered via the
   rules above -- what was actually missing was finish, not containment:
   .pub-lede-lg and .pub-statement already carry their own narrower
   max-widths (34em / 560px) independent of the section container, which
   is what gives the reference's "controlled narrow block" feel for the
   supporting copy while the headline keeps the wider .pub-wide container
   it needs to hold its authored 2-line break (a narrower container was
   tried and rejected here -- it forced "Heute isst du sowieso." and "Die
   Frage ist nur, wie." to each wrap a second time, turning 2 lines into
   4). The real gaps were: the action row had no explicit layout of its
   own (two inline-block buttons just wrapped at whatever gap their HTML
   whitespace happened to produce), and .reassurance had NEVER been
   styled outside .pub-hero's own scope -- on the Finale it was rendering
   as a full-size, unmuted paragraph, which is very likely the real
   source of "feels scattered": the last line was reading as loud as the
   lines before it instead of trailing off. */
#pub-finale .actions{ display:flex; align-items:center; justify-content:center;
  gap:18px; flex-wrap:wrap; }
#pub-finale .reassurance{ margin-top:22px; font-size:13px; color:var(--muted); }

.pub-footer{ position:relative; z-index:2; padding:32px max(24px, env(safe-area-inset-right)) max(48px, env(safe-area-inset-bottom)) max(24px, env(safe-area-inset-left));
  text-align:center; color:var(--muted); font-size:12.5px; }

/* The >=641px .pub-login{display:inline-block} override that lived here is
   removed, not merely neutralised: it existed only to reverse the base
   rule's display:none, and that base rule is now inline-block for every
   width. Leaving it would be a no-op rule implying a breakpoint
   difference that no longer exists. */
@media (min-width:768px){
  .pub-section-inner{ max-width:760px; }
}
@media (min-width:1024px){
  .pub-section-inner{ max-width:820px; }
  .pub-section-inner.wide{ max-width:920px; }
  .pub-section-inner.pub-wide{ max-width:940px; }
  .pub-columns{ justify-content:center; }
  .pub-steps{ flex-direction:row; gap:44px; }
  .pub-step{ flex-direction:column; gap:14px; }
  .pub-step-num{ width:auto; font-size:38px; }
}

/* Mobile is recomposed, not scaled down (Public Experience Redesign,
   Phase 2). The peak/quiet rhythm is preserved -- it is the point of the
   design -- but compressed, because at 390px the desktop ratios would
   leave a 64px headline occupying most of the viewport with nothing to
   anchor it. Vertical rhythm tightens proportionally so the page does not
   become a sequence of near-empty screens on a small device: generous is
   the goal, sparse is not. */
@media (max-width:640px){
  .pub-section{ padding-top:clamp(84px,12vh,124px); padding-bottom:clamp(84px,12vh,124px); }
  /* No viewport floor on mobile: an 82svh minimum would strand short
     scenes as near-empty screens on a phone. Padding alone carries the
     rhythm here. */
  .pub-section.pub-peak{ padding-top:clamp(100px,15vh,152px); padding-bottom:clamp(100px,15vh,152px); min-height:0; }
  .pub-h-peak{ font-size:clamp(31px,8.6vw,40px); }
  .pub-h-final{ font-size:clamp(33px,9.6vw,46px); }
  .pub-h-mid{ font-size:clamp(26px,7.4vw,34px); }
  .pub-h-sub{ font-size:clamp(19px,5.4vw,23px); }
  .pub-h-quiet{ font-size:clamp(20px,5.6vw,24px); }
  .pub-lede-lg{ font-size:16px; }
  /* Das Essen mobile -- independently composed, not shrunk. No pin (the
     scene never had one), shorter scene height (160vh vs 240vh -- less
     travel needed since there's no separate settle phase to fit), a
     shorter sticky stage (88svh: Safari's collapsing address bar changes
     100svh mid-scroll on iOS, and 88svh leaves a visible margin so that
     shift is never perceptible on the one element in the page tall
     enough for it to matter), and a genuinely portrait contained box
     (4:5) since the source photograph is landscape and a 16:10 box at
     390px width leaves the dish a thin, unappetising band. */
  /* Held stage on mobile: same EXPERIENCE (stage locks, Frage holds,
     food resolves in place), no pin, native touch untouched. 100svh --
     not vh -- because Safari's collapsing address bar changes vh
     mid-scroll, which on a full-viewport locked stage would be a visible
     resize of the photograph. Shorter wrapper: touch flicks cover more
     distance per gesture, so the same journey needs less travel. */
  /* 215vh -> 235vh, proportional to the desktop increase (260->285vh)
     for the Refinement sprint's new Signal-as-final-beat content. Same
     narrative, cheaper mechanism throughout -- verified live against
     real measured pixel ranges. */
  /* 235vh -> 247vh, proportional to the desktop increase (285->300vh)
     for the Visual Polish sprint's longer voice holds. */
  /* 247vh -> 262vh, proportional to the desktop increase (300->318vh)
     for the Food -> Signal Handoff sprint's recession + solo hold.
     262vh -> 155vh -> ~245vh (Held-Stage Story Refinement v2),
     proportional to the desktop change (190->300vh). Same TEST-value
     caveat as desktop, same build() timeline reused for both
     breakpoints -- no separate mobile narrative. */
  .pub-held-wrap{ height:245vh; }
  .pub-held-stage{ height:100svh; }
  /* Mobile composition: the desktop "slightly left of optical center"
     trick (a centered parent + an asymmetric right margin) stops making
     sense once the viewport itself is narrow -- Founder's own spec for
     mobile is a standard left content margin, not a percentage offset.
     padding-right reserves room for the progress dots (below) so the
     text column and the dots never collide. */
  .pub-held-story-beat{ justify-content:flex-start;
    padding-right:56px; }
  .pub-held-story-copy{ margin-right:0; max-width:100%; }
  .pub-held-story-subline{ max-width:100%; }
  /* Progress dots, mobile: smaller and closer to the edge -- "remains
     visible but quiet," never competing with the copy column. */
  .pub-held-progress{ right:14px; gap:16px; }
  .pub-held-progress-dot{ width:6px; height:6px; }
  /* Same reduction as the Hero's own mobile Signal, same reasoning:
     proportional to the desktop 46vh->37vh cut so the mark reads at the
     same relative dominance on both breakpoints. Deliberately NOT
     increased alongside the desktop 450->500px test -- mobile screens
     are already tight, and the climax there comes from timing/contrast/
     silence, not size (Founder-approved: "no size increase on mobile").
     No positional drift (opacity + scale only) -- a continuous
     background-thread drift is the one part of this appearance most
     likely to feel unstable on iOS Safari's own scroll/zoom handling. */
  .pub-held-story-signal-inner{ width:min(37vh,260px); height:min(37vh,260px); }
  .pub-beat-gap{ height:clamp(48px,7vh,72px); }
  /* Full-width primary CTA -- the mid-page conversion point should not be
     a small pill on the surface where most visitors will read this. */
  .pub-actions-mid .btn{ width:100%; }
}

/* ==========================================================================
   MEMBER PRODUCT — responsive PWA shell. No device frame at any width.
   ========================================================================== */
#app{ display:none; min-height:100vh; background:var(--canvas); }
/* GLOBAL VIEWPORT FIX -- the member shell is exactly the viewport wide, and
   decorative light may bleed inside it but may never enlarge the document.
   Measured cause on Konto at 320px: two purely decorative layers pushed
   scrollWidth to 345 -- .acct-sig::before (the Signal halo, inset:-30%,
   +17px) and .acct-thread::before (the region wash, inset right -24px,
   +8px). Neither is content; both are light. The 25px they added is what
   made the fixed topbar and bottom nav size to 346 instead of 320, which
   is the header/nav drift, and what let the page be dragged sideways.
   overflow-x:CLIP, not hidden -- identical reasoning to #public-site's own
   note above: `hidden` would force overflow-y to auto and turn this into a
   scroll container, breaking every position:sticky inside the member area.
   `clip` gives the same protection without establishing one. The same
   overflow existed on desktop (KONTO's .acct-sig::before comment records a
   measured 29px at 1440) and is closed by the same line. */
.app-shell{ display:flex; flex-direction:column; min-height:100vh;
  overflow-x:clip; }

/* ---------------------------------------------------------------------- *
 * SAFARI-RÜCKFALL — der Viewport-Fix stand vollständig auf overflow:clip,
 * und das unterstützt iOS Safari erst ab 16. Auf älteren iPhones verwirft
 * der Browser alle drei Deklarationen, sie fallen auf `visible` zurück und
 * der ursprüngliche Defekt ist vollständig da. Gemessen auf Konto bei
 * 320px: 320/320 mit clip, 345/320 ohne — plus Topbar und Bottom-Nav bei
 * 346 statt 320, also genau die berichtete Kopf-/Navigationsdrift.
 *
 * .pub-signal-stage darf dort schlicht auf `hidden` fallen: eine rein
 * dekorative feste Ebene ohne sticky-Nachkommen.
 *
 * .app-shell darf das NICHT — `hidden` erzwingt overflow-y:auto, macht die
 * Hülle zum Scroll-Container und bricht den sticky Topbar (dieselbe
 * Begründung wie bei #public-site). Statt die Wirkung zu maskieren, wird
 * dort die URSACHE entschärft: die beiden dekorativen Lichtebenen ragen
 * unter altem Safari nicht mehr über den Viewport hinaus. Der Anschnitt
 * wird enger, die Seite bleibt heil — und moderne Browser sehen weiterhin
 * die volle Atmosphäre.
 * ---------------------------------------------------------------------- */
@supports not (overflow: clip) {
  .pub-signal-stage{ overflow:hidden; }
  /* Community trägt dieselbe Klasse dekorativer Lichtebenen wie Konto und
     verlässt sich ebenso auf die Hüllen-Klammer. Ohne sie ragen Signal,
     Halo und die Grundwaschgänge über den Viewport: gemessen 447/320.
     Auch hier wird der Anschnitt zurückgenommen statt maskiert. */
  .cf-sig{ right:4px; }
  .cf-sig::before{ inset:-12%; }
  .cf-feed::before{ inset:-10px 0 -20px; }
  .cf-post.cf-own .cf-bubble::before{ inset:-14px -8px -12px -10px; }
  /* Und als Riegel darüber: der INHALTSBEREICH wird geklammert, nicht die
     Hülle. .app-content enthält keinen sticky-Nachkommen — .app-topbar ist
     sein Geschwister, kein Kind — also ist `hidden` hier unbedenklich und
     wird von jedem Browser unterstützt. Damit kann keine dekorative Ebene
     dieses Bereichs das Dokument mehr aufweiten, ohne dass jede einzelne
     einzeln nachgezogen werden muss. */
  .app-content{ overflow-x:hidden; }
  /* .acct-sig ist mobil 138px breit und sitzt an right:0 innerhalb der
     16px-Polsterung von .app-content — mehr als ~11% Halo passen dort
     nicht, ohne den Viewport zu überschreiten. */
  .acct-sig::before{ inset:-10%; }
  /* Rechts bündig statt 24px darüber hinaus; links bleibt der Anschnitt. */
  .acct-thread::before{ inset:-24px -8px -16px -8px; }
}

/* Sprint 4 -- Registration & Entry atmosphere. Independent of the public
   homepage's own particle canvas (that code is untouched) -- same visual
   language, own small implementation, scoped to the entry screens only. */
.entry-shell{ position:relative; min-height:calc(100vh - 65px - var(--preview-banner-h)); display:flex; align-items:center; overflow:hidden; }
/* Same correction as #pub-net -- see its comment. */
#entry-net{ position:fixed; inset:0; width:100%; height:100%; z-index:0; pointer-events:none; }
.entry-veil{ position:fixed; inset:0; z-index:1; pointer-events:none;
  background:radial-gradient(1100px 800px at 50% 46%, transparent 0%, var(--canvas) 82%); }
.entry-grid{ position:relative; z-index:2; width:100%; max-width:420px; margin:0 auto; padding:48px 24px;
  display:flex; flex-direction:column; gap:32px; text-align:center; }
.entry-story .kicker{ margin-bottom:14px; }
.entry-story h1{ font-family:var(--font-d); font-weight:700; font-size:clamp(28px,5vw,40px); line-height:1.1; text-transform:uppercase; margin:0 0 14px; }
.entry-story .muted-line{ color:var(--muted); font-weight:500; }
.entry-story .entry-lede{ font-size:14.5px; color:var(--text-body); margin:0 0 12px; }
.entry-story .entry-next{ font-size:13.5px; color:var(--text-body); line-height:1.6; max-width:360px; margin:0 auto; }
.entry-story .entry-next strong{ color:var(--text-primary); }
.entry-form{ text-align:center; }
/* Vessel Login Hero Rework: on the register/entry screens, the Signal is
   the hero -- not atmosphere, the actual story ("the detached square is
   the seat you're about to take"). Considerably more visible than the
   homepage hero's own background use since it has to be legible as
   storytelling, not felt as texture (opacity .16 here vs. Hero's low-
   intensity Satin Magenta material, DESIGN_CONSTITUTION.md §1.2/DH-13 --
   this instance still uses the flat fill this file predates, out of
   scope for the Signal Governance sprint). Same construction, same
   breathing motion (continuity, not a new animation), just louder. */
.entry-story{ position:relative; }
.entry-signal{ position:absolute; inset:0; z-index:-1; pointer-events:none;
  display:flex; align-items:center; justify-content:center; }
.entry-signal-inner{ width:min(78vw,640px); height:min(78vw,640px);
  color:rgba(245,241,236,.16);
  /* CROP-MASK, corrected (Signal Scale + Proportion sprint): 55%/80% ->
     78%/97%, same fix and same reasoning as .pub-hero-signal -- the old
     stops placed the ring's own stroke radius inside the mask's fade
     zone (measured ring alpha ~57%), thinning it well below its actual
     7%-of-viewBox weight. 78%/97% keeps the ring fully opaque and only
     fades the empty corners. Base fill alpha (.16) is untouched --
     opacity is an allowed context variable, this fixes only the ratio
     distortion. */
  -webkit-mask-image:radial-gradient(circle at 50% 50%, black 78%, transparent 97%);
  mask-image:radial-gradient(circle at 50% 50%, black 78%, transparent 97%);
  animation:signalBreathe 19s cubic-bezier(.22,.61,.36,1) infinite; }
.entry-signal-inner svg{ width:100%; height:100%; display:block; }

@media (min-width:1024px){
  .entry-grid{ max-width:960px; flex-direction:row; align-items:center; gap:72px; text-align:left; padding:48px max(48px, env(safe-area-inset-right)) 48px max(48px, env(safe-area-inset-left)); }
  .entry-story{ flex:1; }
  .entry-story .entry-next{ margin:0; }
  .entry-form{ flex:0 0 380px; text-align:center; }
}

/* Editorial Header (PACE Intro Final Refinement follow-up): the header
   was reading as generic app chrome regardless of logo size -- a
   floating glass toolbar with a colored chip. Fixed by giving the bar
   real vertical air (14px -> 24px) and quieting the FREE badge, not by
   scaling the mark further. Horizontal padding/structure unchanged. */
.app-topbar{ display:flex; align-items:center; justify-content:space-between;
  padding:24px max(20px, env(safe-area-inset-right)) 24px max(20px, env(safe-area-inset-left));
  border-bottom:1px solid var(--hairline); position:sticky; top:var(--preview-banner-h); background:rgba(16,14,19,0.92); backdrop-filter:blur(6px); z-index:10; }
.app-brand{ background:none; border:none; color:var(--text-primary); font-family:var(--font-d); font-weight:700; font-size:14px; letter-spacing:0.06em; text-transform:uppercase; cursor:pointer; padding:0; }
.app-brand .m{ color:var(--magenta); }
/* Real wordmark SVG -- the brand anchor for the entire app shell (16px
   -> 19px -> 22px -> 24px mobile / 26px -> 30px desktop across passes).
   Still clearly secondary to THE SIGNAL's much larger scale at both
   sizes; most of this pass's presence increase comes from the header's
   own breathing room, not further scaling the mark itself. */
.app-brand-svg{ display:flex; align-items:center; }
.app-brand-svg svg{ display:block; height:24px; width:auto; }
.app-brand-svg .wordmark-dot{ fill:var(--magenta); }
@media (min-width:1024px){
  .app-brand-svg svg{ height:30px; }
}
.app-topbar-right{ display:flex; align-items:center; gap:16px; }
/* Editorial Header: the FREE chip read as a SaaS plan-tier badge --
   filled pill, high contrast. In the topbar only, it drops to a quiet
   tracked-caps label (no fill, no padding, muted color) so it doesn't
   compete with the brand mark. Uppercase/letterspacing kept -- that
   typographic treatment is itself editorial, the filled shape was the
   actual tell. Scoped to .app-topbar so .badge-free stays unchanged
   everywhere else it's used (dashboard, etc). */
.app-topbar .badge-free{ background:transparent; color:var(--muted); padding:0; }
.app-icon-btn{ width:38px; height:38px; border-radius:50%; border:1px solid var(--hairline); background:var(--surface); color:var(--text-primary);
  display:flex; align-items:center; justify-content:center; cursor:pointer; }
.app-icon-btn svg{ width:18px; height:18px; }
/* Ceremony -> PACE Vessel Transition: on arrival straight out of the
   ceremony, the topbar holds off and fades up softly once the PACE
   content itself has landed, instead of slamming back in at full
   contrast the instant the overlay is gone. */
@keyframes topbarSoftIn{ from{ opacity:0; transform:translateY(-6px); } to{ opacity:1; transform:translateY(0); } }
.app-topbar-soft{ animation:topbarSoftIn 700ms var(--patient) 850ms backwards; }

.app-body{ flex:1; display:flex; }
.app-content{ flex:1; min-width:0; padding:var(--s5) max(16px, env(safe-area-inset-right)) var(--mobile-safe-bottom) max(16px, env(safe-area-inset-left)); }
.app-content-inner{ max-width:640px; margin:0 auto; }

/* Ceremony -> PACE Vessel Transition: PACE is the second beat of the
   ceremony, not a new screen. Same Vessel atmosphere (vignette/grain)
   as the ceremony overlay, carried across the cut instead of dropping
   to a flat canvas. THE SIGNAL stays present in its settled resting
   state -- identical geometry, no travel, no peak -- as a quiet
   throughline connecting the two screens; PACE's own question is the
   hero here, so the Signal sits low-opacity and masked behind it,
   never competing. Composition is deliberately centered in the
   available height instead of stranded at the top. */
.pace-arrival{ position:relative; display:flex; align-items:center; justify-content:center;
  min-height:62vh; padding:var(--s6) 0; }
/* Fix PACE Intro Container: vignette/grain were scoped to .pace-arrival's
   own (narrower, shorter) box, clipped by its overflow -- the gradient's
   dark falloff terminated at that box's edges, reading as a framed panel
   sitting on the flatter canvas outside it. Full-viewport + fixed (the
   same pattern the ceremony overlay and homepage hero already use) so
   the atmosphere is the room, not a card placed on top of it. */
.pace-vignette{ position:fixed; inset:0; z-index:0; pointer-events:none;
  background:radial-gradient(1100px 800px at 50% 46%, rgba(255,255,255,.022) 0%, transparent 45%, rgba(0,0,0,.55) 100%); }
.pace-grain{ position:fixed; inset:0; z-index:0; pointer-events:none; opacity:.12; mix-blend-mode:soft-light;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='%75%72%6C%28%23n%29'/%3E%3C/svg%3E");
  background-size:120px 120px; }
.pace-signal{ position:absolute; inset:0; z-index:0; pointer-events:none; display:flex; align-items:center; justify-content:center; }
/* PACE Intro Final Refinement: smaller and dimmer still (420px/.1 opacity
   -> 340px/.07), and the breathing animation is removed entirely -- a
   still, quiet trace of the ceremony, not a second living element next
   to the PACE question. Noticed, not read. */
.pace-signal-inner{ width:min(52vw,340px); height:min(52vw,340px); color:rgba(245,241,236,.07);
  /* CROP-MASK, corrected (Signal Proportion Consistency sprint): 55%/80%
     -> 78%/97%, same root-cause fix as .pub-hero-signal and
     .entry-signal-inner -- the old stops placed the ring's own stroke
     radius inside the mask's fade zone, thinning it well below its
     actual 7%-of-viewBox weight. 78%/97% keeps the ring fully opaque
     and only fades the empty corners. Opacity (.07), size, and the
     removed breathing animation are all untouched. */
  -webkit-mask-image:radial-gradient(circle at 50% 50%, black 78%, transparent 97%);
  mask-image:radial-gradient(circle at 50% 50%, black 78%, transparent 97%); }
.pace-signal-inner svg{ width:100%; height:100%; display:block; }
.pace-content{ position:relative; z-index:1; max-width:480px; text-align:center; padding:0 var(--s4); }
.pace-question{ font-family:var(--font-d); font-weight:700; text-transform:uppercase;
  font-size:clamp(28px,5vw,40px); line-height:1.1; margin:0 0 18px; color:var(--text-primary); }
.pace-lede{ font-size:14.5px; color:var(--text-body); line-height:1.6; margin:0 0 36px; }
/* PACE Intro Final Refinement: the CTA reads as the third beat, not a
   slab competing with the question above it -- intrinsic width instead
   of full-block, and a touch less vertical weight. Same magenta, same
   wording, same button language; only width/height/spacing change. */
.pace-cta{ width:auto; min-width:220px; padding:12px 30px; min-height:44px; }

/* ==========================================================================
   RECOGNITION SCREEN — Direction A, editorial sequence.
   Replaces the 2x2 selection grid. Four situations read as a vertical
   editorial sequence; no cards, no boxes, no filled surfaces. Whitespace
   is the only separator. Mobile is the base composition; desktop adds an
   asymmetric two-zone split where the question stays present (sticky)
   while the situations scroll past it.

   Atmosphere layers are position:fixed and must never sit under a
   transformed ancestor -- render() applies .screen-anim-static to this
   screen for exactly that reason. Grain uses the encoded filter
   reference publish_preview.py's asset-resolution gate requires.
   ========================================================================== */
.rec{ position:relative; }
.rec-vignette{ position:fixed; inset:0; z-index:0; pointer-events:none;
  background:radial-gradient(1100px 800px at 50% 46%, rgba(255,255,255,.022) 0%, transparent 45%, rgba(0,0,0,.55) 100%); }
.rec-grain{ position:fixed; inset:0; z-index:0; pointer-events:none; opacity:.12; mix-blend-mode:soft-light;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='%75%72%6C%28%23n%29'/%3E%3C/svg%3E");
  background-size:120px 120px; }
.rec-grid{ position:relative; z-index:1; max-width:1240px; margin:0 auto; }

.rec-ask{ position:relative; }
.rec-question{ font-family:var(--font-d); font-weight:700; text-transform:uppercase;
  font-size:clamp(28px,4.5vw,44px); line-height:1.1; letter-spacing:0.004em;
  color:var(--text-primary); margin:0 0 var(--s4); }
.rec-support{ font-family:var(--font-c); font-size:13.5px; line-height:1.6;
  color:var(--muted); margin:0; max-width:38ch; }
/* Signal is desktop-only -- see the min-width:1024px block. On mobile the
   reading column occupies the full width and a ring behind 38-word German
   paragraphs costs legibility without adding continuity; the canvas,
   vignette and grain already carry the Vessel world. */
.rec-signal{ display:none; }

.rec-list{ margin-top:var(--s7); }

/* Each complete situation is one recognition target: a real button for
   keyboard and screen-reader correctness, styled as editorial text rather
   than as a control. The always-present transparent left border reserves
   the space the selected rule occupies, so selection causes no layout
   shift. */
.rec-item{ display:block; width:100%; text-align:left; background:none; border:none;
  border-left:2px solid transparent; padding:0 0 0 var(--s4); margin:0;
  font-family:inherit; color:inherit; cursor:pointer;
  transition:border-color 240ms var(--patient); }
.rec-item + .rec-item{ margin-top:var(--s8); }
.rec-anchor{ display:block; font-family:var(--font-d); font-weight:700;
  font-size:clamp(20px,2.4vw,26px); line-height:1.22; letter-spacing:0.002em;
  color:var(--text-primary); margin-bottom:var(--s2);
  transition:color 240ms var(--patient); }
.rec-body{ display:block; font-family:var(--font-c); font-size:15.5px; line-height:1.65;
  color:var(--text-body); max-width:62ch; transition:color 240ms var(--patient); }
/* Hover is deliberately almost nothing -- it previews the left rule that
   selection uses, teaching the mechanic. No lift, no shadow, no fill. */
.rec-item:hover{ border-left-color:var(--hairline); }
.rec-item:focus-visible{ outline:2px solid var(--magenta); outline-offset:8px; }
.rec-item.is-recognized{ border-left-color:var(--magenta); }
/* Recognized: the true one stays lit and the others settle back. They
   remain fully readable and fully selectable -- recession, never
   elimination, and nothing reads as a wrong answer. */
.rec.has-selection .rec-item:not(.is-recognized) .rec-anchor,
.rec.has-selection .rec-item:not(.is-recognized) .rec-body{ color:var(--text-secondary); }

.rec-closing{ font-family:var(--font-c); font-size:12.5px; color:var(--muted);
  margin:var(--s7) 0 0; padding-left:var(--s4); }
.rec-continue{ margin-top:var(--s5); padding-left:var(--s4);
  opacity:0; visibility:hidden; transition:opacity 420ms var(--patient); }
.rec.has-selection .rec-continue{ opacity:1; visibility:visible; }
.rec-continue-btn{ width:auto; min-width:200px; padding:12px 30px; min-height:44px; }

@media (min-width:1024px){
  /* Desktop amplifies: the question becomes a persistent editorial anchor
     rather than a heading that scrolls away. Not a sidebar -- no rule, no
     panel, no background. */
  .rec-grid{ display:grid; grid-template-columns:38% 1fr; gap:var(--s9); align-items:start; }
  /* min-height is tied to the viewport, not to the right column's content
     height -- the situation copy is explicitly working/revisable, so
     sizing the left zone off it would make this layout brittle to a
     content edit. Centering the text within that height is what turns
     the space below the question into deliberate framing instead of
     leftover room the Signal happens to get clipped against. */
  .rec-ask{ position:sticky; top:calc(var(--preview-banner-h) + 128px); padding-right:var(--s6);
    min-height:56vh; display:flex; flex-direction:column; justify-content:center; }
  .rec-list{ margin-top:0; }
  .rec-signal{ display:flex; position:absolute; inset:0; z-index:-1;
    align-items:center; justify-content:flex-start; pointer-events:none; }
  .rec-signal-inner{ width:min(27vw,440px); height:min(27vw,440px);
    margin-left:-14%; color:rgba(245,241,236,.06);
    /* CROP-MASK, corrected (Signal Proportion Consistency sprint): same
       55%-based-stops root-cause fix as every other instance -- see
       .pub-hero-signal's comment for the full reasoning. Opacity (.06),
       size, and position (margin-left:-14%) untouched. */
    -webkit-mask-image:radial-gradient(circle at 50% 50%, black 78%, transparent 97%);
    mask-image:radial-gradient(circle at 50% 50%, black 78%, transparent 97%); }
  .rec-signal-inner svg{ width:100%; height:100%; display:block; }
}

/* ----------------------------------------------------------------------
   ADAPTATION PROOF -- one descending editorial column. The member has
   finished deciding; this screen is about receiving something, so there is
   no two-zone split and no grid. Composition encodes interaction state:
   Recognition's two zones held a question against options, a single column
   says the deciding is over.

   THE SIGNAL is absent by design -- ceremonial peak, then atmospheric
   memory on Recognition, then zero. Vessel continuity is carried by the
   canvas, vignette, grain and typography alone.
   ---------------------------------------------------------------------- */
.ap{ position:relative; }
/* .app-content reserves --mobile-safe-bottom (190px) for the bottom nav and
   the PL8 FAB. This screen renders neither -- render()'s isMode branch hides
   the nav, and 'welcome' isn't in FAB_SCREENS -- so that clearance was pure
   dead space, leaving ~63px of empty scroll below a composition that already
   fits the viewport. Scoped with :has() instead of touching the global rule.
   Safe-area inset preserved. */
@media (max-width:1023px){
  .app-content:has(.ap){ padding-bottom:max(var(--s5), env(safe-area-inset-bottom)); }
}
/* .app-shell is min-height:100vh, but the preview banner is fixed above it
   and body carries a matching padding-top -- so the shell always ends one
   banner-height past the viewport and every screen scrolls by that much.
   Subtracting it is the idiom the codebase already uses for full-height
   elements (.pub-hero, .entry-shell); the variable is responsive (56px
   mobile / 36px from 480px), so this stays correct at both. Scoped to this
   screen: the banner is prototype-only scaffolding, and the screens that
   legitimately scroll are unaffected either way. #app and .app-shell both
   declare min-height:100vh, so both need it -- correcting only one leaves
   the other setting the page height. */
#app:has(.ap), .app-shell:has(.ap){ min-height:calc(100vh - var(--preview-banner-h)); }
/* Same fixed atmosphere as Recognition. Fixed layers require this screen
   to be in render()'s staticScreen opt-out -- .screen-anim's translateY
   would otherwise become their containing block and scope them to the
   wrapper's content box (the diagnosed boxed-vignette artifact). */
.ap-vignette{ position:fixed; inset:0; z-index:0; pointer-events:none;
  background:radial-gradient(1100px 800px at 50% 46%, rgba(255,255,255,.022) 0%, transparent 45%, rgba(0,0,0,.55) 100%); }
.ap-grain{ position:fixed; inset:0; z-index:0; pointer-events:none; opacity:.12; mix-blend-mode:soft-light;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='%75%72%6C%28%23n%29'/%3E%3C/svg%3E");
  background-size:120px 120px; }

/* Centred column, left-aligned text: centring the text would break the
   editorial register, centring the column keeps the descent calm without
   drifting into dashboard asymmetry. */
.ap-col{ position:relative; z-index:1; max-width:600px; margin:0 auto; padding-top:var(--s5); }

/* Below Recognition's question scale -- the journey's type scale descends
   deliberately as the entrance sequence winds down. */
.ap-headline{ font-family:var(--font-d); font-weight:700; text-transform:uppercase;
  font-size:clamp(26px,3.4vw,38px); line-height:1.12; letter-spacing:0.004em;
  color:var(--text-primary); margin:0 0 var(--s6); }

/* The artifact is content, not a component: no border, no filled card, no
   nested button. The whole thing is the tap target, and it carries the
   visual weight of the screen. */
.ap-artifact{ display:block; width:100%; text-align:left; background:none; border:none;
  padding:0; margin:0; font-family:inherit; color:var(--text-primary); cursor:pointer;
  transition:transform 220ms var(--patient); }
.ap-artifact:hover{ transform:translateY(-2px); }
.ap-artifact:focus-visible{ outline:2px solid var(--magenta); outline-offset:10px; }

/* The only intentional magenta at rest. Same weight and colour as the
   Recognition screen's selected-state rule, one step down the causal
   chain: there magenta meant "this is your current situation", here it
   means "this exists because of that". Sits above the image so the member
   reads why before what. */
.ap-proof{ display:block; border-left:2px solid var(--magenta); padding-left:var(--s4);
  font-family:var(--font-c); font-size:15px; line-height:1.55; color:var(--text-body);
  max-width:46ch; margin-bottom:var(--s5); }

.ap-thumb{ display:block; height:210px; border-radius:var(--radius); position:relative; overflow:hidden;
  background:linear-gradient(135deg, var(--surface-elevated), var(--canvas)); margin-bottom:var(--s4); }
.ap-thumb::after{ content:''; position:absolute; inset:0;
  background:radial-gradient(60% 60% at 30% 30%, rgba(255,255,255,.05), transparent 70%); }

/* Largest type inside the artifact, but deliberately under the headline --
   the frame still outranks the object it frames. */
.ap-name{ display:block; font-family:var(--font-d); font-weight:700; text-transform:uppercase;
  font-size:clamp(21px,2.4vw,27px); line-height:1.15; letter-spacing:0.002em; margin-bottom:var(--s2); }
.ap-meta{ display:block; font-family:var(--font-c); font-size:13px; color:var(--muted); }

/* Quiet by construction: text weight, no slab, no magenta. Onboarding
   should stop rather than announce that it has finished. */
/* Underline via text-decoration rather than a border, so it stays tight to
   the word while the vertical padding still carries the 44px tap target --
   a bordered box would float the rule away from the text. */
.ap-onward{ display:inline-block; background:none; border:none;
  padding:14px 0; margin-top:var(--s6);
  font-family:var(--font-c); font-size:14px; color:var(--muted); cursor:pointer;
  text-decoration:underline; text-underline-offset:6px; text-decoration-thickness:1px;
  text-decoration-color:var(--hairline);
  transition:color 200ms var(--patient), text-decoration-color 200ms var(--patient); }
.ap-onward:hover{ color:var(--text-primary); text-decoration-color:var(--border-strong); }
.ap-onward:focus-visible{ outline:2px solid var(--magenta); outline-offset:4px; }

@media (min-width:1024px){
  /* Desktop amplifies through surrounding space only -- the column keeps
     its intrinsic width at every width, and nothing is added to fill the
     canvas at 1920.

     Vertical placement is centred in the space actually available rather
     than set by a fixed top padding: a fixed padding put the composition
     23px off the bottom at 1280x800 and left 303px of room below it at
     1920x1080 -- the same number in two very different rooms.

     The upward bias comes from .app-content's own asymmetric padding
     (32px top / 64px bottom), so the column settles above true centre for
     free. An explicit margin was tried first and rejected: it adds layout
     height, and at 1280x800 there are only ~39px of slack, so it
     reintroduced a scrollbar on the one size with no room to spare. */
  .app-content:has(.ap){ display:flex; flex-direction:column; justify-content:center; }
  .ap-col{ padding-top:0; }
  .ap-thumb{ height:250px; }
}

/* bottom nav — mobile / tablet */
.app-bottomnav{ position:fixed; left:0; right:0; bottom:0; z-index:10; display:flex; justify-content:space-around;
  background:rgba(16,14,19,0.96); backdrop-filter:blur(8px); border-top:1px solid var(--hairline);
  padding:8px max(8px, env(safe-area-inset-right)) max(8px, env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left)); }
.app-bottomnav button{ background:none; border:none; color:var(--muted); font-size:11px; display:flex; flex-direction:column; align-items:center; gap:4px;
  padding:6px 10px; cursor:pointer; min-width:56px; min-height:48px; }
.app-bottomnav button svg{ width:20px; height:20px; }
.app-bottomnav button.active{ color:var(--text-primary); }
.app-bottomnav button.active svg{ color:var(--magenta); }

/* Scroll-aware retreat. The navigation and every follower keep their place in
   the layout, so content never moves. Mobile/tablet only -- the desktop
   sidebar has no retreat -- and never under reduced motion, where it simply
   stays. The translate property, not transform, so no existing transform or
   its transition is overridden.

   The retreated state is a real hidden state of the ONE wrapper, not a
   position: the bar travels down and fades as one unit (icons, labels,
   background, border and safe-area padding are all inside it), and when the
   motion ends it becomes visibility:hidden -- nothing painted, no hit area,
   out of the focus order. Why: iOS Safari paints the page beneath and around
   its dynamic bottom toolbar, below the fixed-position containing block, and
   how far that visible band reaches changes with the toolbar state -- no CSS
   distance can guarantee the bar has left it. The bar's background matches
   the page canvas, so a bar resting partly inside that band showed only its
   light icons and labels. Coming back, it is visible at once and travels up. */
.app-bottomnav{ transition:translate var(--nav-motion), opacity var(--nav-motion), visibility 0s linear 0s; }
.cf-composer{ transition:translate var(--nav-motion); }
@media (max-width:1023px) and (prefers-reduced-motion:no-preference){
  html.nav-retreated{ --nav-offset:var(--nav-bar-h); }
  /* Retreat distance: the bar's own height (100%, safe-area padding
     included) plus the dynamic-toolbar allowance (100lvh - 100svh). iOS
     Safari minimises its toolbar exactly while the member scrolls down, and
     the visible screen then reaches below the fixed-position containing
     block -- a bar moved by its own height alone stayed partly in view.
     The allowance is 0 wherever the toolbar is static (desktop), so nothing
     else moves; the first declaration is the fallback without lvh/svh. */
  html.nav-retreated .app-bottomnav{ translate:0 100%; translate:0 calc(100% + 100lvh - 100svh);
    opacity:0; visibility:hidden; pointer-events:none;
    transition:translate var(--nav-motion), opacity var(--nav-motion), visibility 0s linear var(--nav-motion-ms); }
  /* The composer clears the bar with its own bottom padding; it drops by the
     bar's height and keeps its safe-area clearance. */
  html.nav-retreated .cf-composer{ translate:0 var(--nav-offset); }
}

/* sidebar nav — desktop */
.app-sidebar{ display:none; }

.screen-anim{ animation:screenIn 320ms var(--patient); }
/* Fix PACE Intro Screen Animation Opt-Out: no animation, no transform --
   PACE intro only, so its fixed-position atmosphere layers never get a
   transformed containing block. See render() for where this is applied. */
.screen-anim-static{ animation:none; transform:none; }
@keyframes screenIn{ from{ opacity:0; transform:translateY(8px); } to{ opacity:1; transform:translateY(0); } }

/* ---------------------------------------------------------------------- *
 * Sprint 13 -- Motion: guidance (Dashboard), recognition (Recipe Detail).
 * Each fires once, in its own place; none of them persist or repeat.
 * The Mode-grid entry that used to belong here (.mode-card-in /
 * modeCardIn) was the old 2x2 grid's animation -- removed as dead code
 * once Direction A replaced that grid (Legacy Personalization Decoupling
 * audit; the old grid's own removal predates this sprint).
 * ---------------------------------------------------------------------- */
.mode-belonging-in{ animation:belongingIn 450ms var(--patient) backwards; animation-delay:900ms; }
@keyframes belongingIn{ from{ opacity:0; transform:translateY(6px); } to{ opacity:1; transform:translateY(0); } }

/* ---- Cooking Memory: the finish controls ----------------------------------
   The memory offer, its confirmation and the quiet links (Rückgängig,
   Nicht mehr merken) -- small type, no card, neutral focus. Recipe Detail
   V1 owns the finish itself (.rd-finish) and the remembered line (.rd-mem). */
.cm-offer{ display:inline-flex; align-items:center; min-height:44px; padding:0; background:none; border:0; cursor:pointer;
  color:var(--text-secondary); font:inherit; font-size:13px; text-decoration:underline; text-decoration-color:rgba(245,241,236,.35); text-underline-offset:3px;
  animation:cmIn 420ms var(--patient) both; }
.cm-offer:hover{ color:var(--text-primary); }
.cm-offer[aria-disabled="true"]{ cursor:default; opacity:.7; }
.cm-saved{ display:flex; flex-wrap:wrap; align-items:center; column-gap:14px; margin:0; font-size:13px; color:var(--text-secondary); }
.cm-link{ display:inline-flex; align-items:center; min-height:44px; padding:0; background:none; border:0; cursor:pointer;
  color:var(--muted); font:inherit; font-size:12.5px; text-decoration:underline; text-underline-offset:3px; }
.cm-link:hover, .cm-link.is-strong{ color:var(--text-primary); }
.cm-link[aria-disabled="true"]{ cursor:default; opacity:.6; }
.cm-status{ margin:0; font-size:12.5px; color:var(--text-secondary); }
.cm-status.is-empty{ display:none; }
.cm-offer:focus-visible, .cm-link:focus-visible{ outline:2px solid rgba(245,241,236,.6); outline-offset:3px; border-radius:4px; }
@keyframes cmIn{ from{ opacity:0; } to{ opacity:1; } }
@media (prefers-reduced-motion: reduce){ .cm-offer{ animation:none; } }

/* dashboard -- Sprint 18.2 hierarchy: Wochenfokus (1) > free primary path (2)
   > Community (3) > PL8 teaser (4). Free and PL8 deliberately no longer share
   a treatment: the free path reads stronger through fill+accent-bar, PL8
   reads quieter through the restrained premium surface, so PL8 never
   visually outweighs the free experience. */
.dash-greeting{ font-size:13px; color:var(--text-secondary); margin-bottom:4px; }
.dash-continuity{ font-size:15px; margin-bottom:var(--s6); }
.dash-grid{ display:grid; grid-template-columns:1fr; gap:24px; }
.ritual-card{ background:linear-gradient(160deg, var(--surface-elevated) 0%, var(--surface) 100%); border:1px solid var(--hairline); border-radius:var(--radius); padding:var(--s6); cursor:pointer; }
.ritual-card h3{ font-size:18px; margin:6px 0 8px; }
.ritual-card p:not(.kicker){ font-size:14px; color:var(--text-body); line-height:1.55; }
.presence{ display:flex; align-items:center; gap:8px; margin:10px 0 22px; font-size:13px; color:var(--text-secondary); font-family:var(--font-c); }
.presence-dot{ width:7px; height:7px; border-radius:50%; background:var(--text-secondary); flex-shrink:0; animation:presencePulse 3.4s cubic-bezier(.22,.61,.36,1) infinite; }
@keyframes presencePulse{ 0%,100%{ opacity:.4; transform:scale(0.85); } 50%{ opacity:1; transform:scale(1); } }
.ritual-progress{ display:flex; gap:6px; margin-top:14px; }
.ritual-progress span{ flex:1; height:4px; border-radius:2px; background:var(--hairline); }
.ritual-progress span.done{ background:var(--magenta); }
.quicklinks{ display:grid; grid-template-columns:1fr 1fr; gap:16px; }
.quicklink{ background:var(--surface); border:1px solid var(--hairline); border-radius:var(--radius); padding:16px; text-align:left; cursor:pointer; color:var(--text-primary); }
.quicklink:hover{ border-color:var(--border-strong); }
/* the primary free action: distinguished by fill + a left accent bar, not a
   tint-box -- deliberately a different pattern than the PL8 teaser below. */
.quicklink-primary{ background:var(--surface-2); border:1px solid var(--hairline); border-left:3px solid var(--magenta); padding-left:13px; }
.quicklink-primary h4{ font-weight:700; }
.quicklink .kicker{ margin-bottom:4px; }
.quicklink h4{ font-size:15px; }
.pl8-teaser-card{ border:1px solid var(--hairline); border-radius:var(--radius); padding:var(--s5); background:var(--surface-premium); cursor:pointer; }
.pl8-teaser-card:hover{ border-color:var(--border-premium); }
.pl8-teaser-card h4{ font-size:15px; margin:6px 0 6px; }
.pl8-teaser-card p{ font-size:13.5px; color:var(--text-body); line-height:1.5; }

/* recipes -- Sprint 18.2: thumbnail gradients moved off the magenta glow
   (which read as decorative noise, not food) to a restrained warm charcoal
   gradient plus a soft warm highlight, closer to gentle kitchen light than
   a colored UI accent. */
/* Sprint 19.1 -- visual hierarchy pass. Surface roles used across Recipes:
   --surface-elevated marks the one recommended/primary tile (feature tile,
   facts tile); --surface marks ordinary content tiles (recipe cards);
   everything else (fit lists, ingredients, steps, the PL8 aside) stays flat
   against the canvas and is separated by spacing and hairlines, not boxes. */
.recipe-card{ background:var(--surface); border:1px solid var(--hairline); border-radius:var(--radius); overflow:hidden; cursor:pointer; transition:border-color 200ms var(--patient); }
.recipe-card:hover{ border-color:var(--border-strong); }
.recipe-card .thumb{ height:130px; background:linear-gradient(135deg, var(--surface-elevated), var(--canvas)); position:relative; }
.recipe-card .thumb::after{ content:''; position:absolute; inset:0; background:radial-gradient(60% 60% at 30% 30%, rgba(255,255,255,0.04), transparent 70%); }
.recipe-card .body{ padding:16px; }
.recipe-card h4{ font-size:15px; font-weight:700; margin:6px 0 6px; }
.recipe-card .meta{ font-size:12.5px; color:var(--text-secondary); }

/* ======================================================================
   RECIPE DISCOVERY V1 -- Rezepte: the whole collection, food first.
   START's object grammar, not Story's paper: on the dark canvas the visual
   is the one object (tonal lift, directional light from above, one lit top
   edge, START's plate radius); the name and the meta sit on the canvas below
   it, so the page is a calm run of dishes rather than a grid of boxed cards.

   MAGENTA ON REZEPTE (Founder direction, refinement pass): the cards rest
   neutral. Magenta is light that arrives when a card is approached -- hover,
   keyboard focus, a finger on it -- reflected into the plate and along its
   edge, never a border, fill or word colour. The one exception at rest is
   the PL8 access marker a Free member sees on a PL8 recipe: deliberate PL8
   emphasis (Constitution 2.3), small, and gone once the member has PL8.
   ====================================================================== */
.rx{ --hi:245,241,236; --lit:214,198,226; --act:226,25,181; }
.rx-h1{ font-family:var(--font-d); font-weight:500; text-transform:none; color:var(--text-primary);
  font-size:clamp(28px, 8vw, 38px); line-height:1.04; letter-spacing:-.005em; margin:8px 0 22px; }
.rx-list{ list-style:none; margin:0; padding:0; display:grid; grid-template-columns:minmax(0, 1fr); gap:30px; }
.rx-item{ min-width:0; }

/* RECIPE STORE V1 (P4) -- the three quiet, restrained async states
   (catalogue loading/error/empty, Recipe body loading/error): the same
   secondary-color status-line language already used by Cooking Memory
   (.cm-status), no spinner, no new interaction pattern. */
.rx-state{ color:var(--text-secondary); font-size:14.5px; line-height:1.5; margin:0; }
.rx-retry{ all:unset; cursor:pointer; color:var(--text-primary); text-decoration:underline; text-underline-offset:2px; }
.rx-retry:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:2px; border-radius:2px; }

/* the card: one native button, the whole card is the target */
.rx-card{ display:block; width:100%; padding:0; margin:0; background:none; border:0; border-radius:18px;
  color:inherit; font:inherit; text-align:left; cursor:pointer; -webkit-tap-highlight-color:transparent; }
/* the focus indicator stays a clear neutral ring -- the magenta light below
   is the atmosphere, never the only sign of focus */
.rx-card:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:5px; }

/* the visual: 4:3, one geometry for photograph and stand-in alike */
.rx-visual{ position:relative; display:flex; align-items:center; justify-content:center; box-sizing:border-box;
  aspect-ratio:4 / 3; padding:22px 18px; border-radius:18px; overflow:hidden; container-type:inline-size;
  background:radial-gradient(105% 96% at 50% 0%, rgba(var(--hi), .13), rgba(var(--hi), 0) 60%),
    radial-gradient(90% 80% at 10% 108%, rgba(var(--lit), .07), rgba(var(--lit), 0) 70%),
    linear-gradient(165deg, #322C38 0%, #1A161E 100%);
  box-shadow:inset 0 1px 0 rgba(var(--hi), .14), inset 0 0 0 1px rgba(var(--hi), .04),
    0 30px 52px -34px rgba(var(--lit), .2), 0 22px 40px -26px rgba(var(--act), 0);
  transition:transform 220ms var(--patient), box-shadow 340ms var(--patient); }
/* The reflected light: magenta rising from the lower right into the plate,
   a warmer top edge, and a hairline of magenta along the edge where the
   light catches it. Invisible at rest; it arrives, it does not flash. */
.rx-visual::after{ content:''; position:absolute; inset:0; z-index:1; pointer-events:none; opacity:0; border-radius:inherit;
  background:radial-gradient(78% 104% at 90% 106%, rgba(var(--act), .17), rgba(var(--act), 0) 66%),
    radial-gradient(90% 80% at 50% 0%, rgba(var(--hi), .09), rgba(var(--hi), 0) 62%);
  box-shadow:inset 0 1px 0 rgba(255, 210, 242, .24), inset 0 0 0 1px rgba(var(--act), .2);
  transition:opacity 340ms var(--patient); }

/* engaged: pointer, keyboard, finger -- lift, reflected light, a soft glow
   under the object */
.rx-card:focus-visible .rx-visual,
.rx-card.is-pressed .rx-visual{
  box-shadow:inset 0 1px 0 rgba(var(--hi), .16), inset 0 0 0 1px rgba(var(--hi), .04),
    0 30px 52px -34px rgba(var(--lit), .2), 0 22px 40px -26px rgba(var(--act), .38); }
.rx-card:focus-visible .rx-visual::after,
.rx-card.is-pressed .rx-visual::after{ opacity:1; }
.rx-card:focus-visible .rx-visual{ transform:translateY(-2px); }
@media (hover:hover){
  .rx-card:hover .rx-visual{ transform:translateY(-2px);
    box-shadow:inset 0 1px 0 rgba(var(--hi), .16), inset 0 0 0 1px rgba(var(--hi), .04),
      0 30px 52px -34px rgba(var(--lit), .2), 0 22px 40px -26px rgba(var(--act), .38); }
  .rx-card:hover .rx-visual::after{ opacity:1; }
}
.rx-card:active .rx-visual, .rx-card.is-pressed .rx-visual{ transform:scale(.985); }

/* with a photograph: it fills the frame; the plate's light stays above it */
.rx-visual.has-img{ padding:0; }
.rx-img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position:50% 50%; }
.rx-visual.has-img::before{ content:''; position:absolute; inset:0; z-index:1; pointer-events:none; border-radius:inherit;
  box-shadow:inset 0 1px 0 rgba(var(--hi), .14); }

/* without one: the ingredients as type -- START's editorial stand-in */
.rx-words{ display:flex; flex-direction:column; align-items:center; gap:1px; max-width:100%; }
.rx-w{ display:block; max-width:100%; text-align:center; overflow-wrap:break-word; hyphens:manual;
  font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:clamp(20px, 8.6cqw, 32px);
  line-height:1.06; letter-spacing:.01em; color:rgba(var(--hi), .8); opacity:calc(1 - var(--n) * .15); }

/* ---- PL8 access marker (Free member, PL8 recipe) ----
   A small lit object resting on the corner of the plate, slightly toward the
   member: a dark body with its own magenta light, a lit top edge, a magenta
   edge where the light catches, a shadow that lifts it off the plate. Lock +
   visible "PL8" -- never the lock alone. Compact: it marks the recipe, it
   does not cover the food and it carries no copy. */
.rx-lock{ position:absolute; top:12px; right:12px; z-index:2; display:inline-flex; align-items:center; gap:6px;
  height:28px; padding:0 11px 0 9px; border-radius:10px;
  font-family:var(--font-c); font-size:11px; font-weight:700; letter-spacing:.14em; line-height:1; color:#FFEAF8;
  background:radial-gradient(130% 150% at 22% -12%, rgba(var(--act), .52), rgba(var(--act), 0) 62%),
    linear-gradient(165deg, #3A2436 0%, #1C141D 100%);
  box-shadow:inset 0 1px 0 rgba(255, 214, 242, .42), inset 0 0 0 1px rgba(var(--act), .46),
    0 8px 16px -8px rgba(0, 0, 0, .72), 0 0 22px -6px rgba(var(--act), .5);
  transition:transform 220ms var(--patient), box-shadow 340ms var(--patient); }
.rx-lock-i{ flex:none; width:12px; height:12px; color:#FF8BDF; }
.rx-lock-t{ transform:translateY(.5px); }

/* A locked recipe answers a little more: the reflected light gathers toward
   the marker (upper right) instead of rising from below, and the marker
   itself brightens and comes forward -- card, light, access, one gesture.
   Desirable, plainly restricted, never disabled-looking. */
.rx-card.is-locked .rx-visual::after{
  background:radial-gradient(84% 96% at 92% -6%, rgba(var(--act), .24), rgba(var(--act), 0) 64%),
    radial-gradient(78% 104% at 90% 106%, rgba(var(--act), .1), rgba(var(--act), 0) 66%),
    radial-gradient(90% 80% at 50% 0%, rgba(var(--hi), .08), rgba(var(--hi), 0) 62%); }
.rx-card.is-locked:focus-visible .rx-visual,
.rx-card.is-locked.is-pressed .rx-visual{
  box-shadow:inset 0 1px 0 rgba(var(--hi), .16), inset 0 0 0 1px rgba(var(--hi), .04),
    0 30px 52px -34px rgba(var(--lit), .2), 0 22px 44px -24px rgba(var(--act), .5); }
.rx-card.is-locked:focus-visible .rx-lock,
.rx-card.is-locked.is-pressed .rx-lock{ transform:translateY(-1px);
  box-shadow:inset 0 1px 0 rgba(255, 224, 246, .55), inset 0 0 0 1px rgba(var(--act), .7),
    0 10px 18px -8px rgba(0, 0, 0, .75), 0 0 30px -4px rgba(var(--act), .78); }
@media (hover:hover){
  .rx-card.is-locked:hover .rx-visual{
    box-shadow:inset 0 1px 0 rgba(var(--hi), .16), inset 0 0 0 1px rgba(var(--hi), .04),
      0 30px 52px -34px rgba(var(--lit), .2), 0 22px 44px -24px rgba(var(--act), .5); }
  .rx-card.is-locked:hover .rx-lock{ transform:translateY(-1px);
    box-shadow:inset 0 1px 0 rgba(255, 224, 246, .55), inset 0 0 0 1px rgba(var(--act), .7),
      0 10px 18px -8px rgba(0, 0, 0, .75), 0 0 30px -4px rgba(var(--act), .78); }
}

/* name, then one quiet row of facts */
.rx-text{ display:flex; flex-direction:column; gap:8px; padding:14px 2px 0; }
.rx-name{ font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:clamp(19px, 5.3vw, 21px);
  line-height:1.08; color:var(--text-primary); overflow-wrap:break-word; }
.rx-meta{ display:flex; flex-wrap:wrap; align-items:center; gap:6px 16px; font-size:12.5px; line-height:1.2;
  color:var(--muted); font-variant-numeric:tabular-nums; }
.rx-m{ display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }
.rx-m .hm-g{ flex:none; width:13px; height:13px; }

@media (min-width:640px){
  .rx-list{ grid-template-columns:repeat(2, minmax(0, 1fr)); gap:36px 24px; }
}
@media (min-width:1024px){
  .rx-h1{ font-size:44px; margin:4px 0 30px; }
  .rx-list{ grid-template-columns:repeat(3, minmax(0, 1fr)); gap:44px 28px; }
}
/* Reduced motion: the light still answers (it carries the state), but it
   appears instead of travelling, and nothing moves. */
@media (prefers-reduced-motion: reduce){
  .rx-visual, .rx-visual::after, .rx-lock{ transition:none; }
  .rx-card:hover .rx-visual, .rx-card:focus-visible .rx-visual, .rx-card:active .rx-visual,
  .rx-card.is-pressed .rx-visual, .rx-card.is-locked:hover .rx-lock,
  .rx-card.is-locked:focus-visible .rx-lock, .rx-card.is-locked.is-pressed .rx-lock{ transform:none; }
}

/* ======================================================================
   RECIPE DETAIL V1 -- GET THIS DISH COOKED.
   The expanded version of the Discovery card: the same 4:3 object on the
   dark canvas, then everything in cooking order. Spacing is the separator
   (no dividers, no tiles). Type is set for reading at arm's length.
   Magenta: none at rest. On an open recipe it is the finish -- "Fertig."
   and the light that answers it -- plus restrained response on the portion
   buttons. On a locked PL8 recipe it is the access boundary.
   ====================================================================== */
.app-content-inner.rd{ --hi:245,241,236; --lit:214,198,226; --act:226,25,181; max-width:640px; }
/* the shared back control, sized here: its icon is an unsized svg */
.rd .bt-detail-back{ display:inline-flex; align-items:center; gap:7px; min-height:44px; padding:0; margin:0 0 12px; }
.rd .bt-detail-back svg{ flex:none; width:17px; height:17px; }

.rd-visual{ margin:0 0 20px; }
/* Detail's frame is fixed at 4:3 against a source photograph that is
   usually closer to square, so cover crops only top/bottom, never left/right
   -- there is no horizontal slack for object-position to use here. A small
   upward shift gives the dish a touch more headroom above it and trims a
   little of the empty surface below, which reads as better centred for
   food photography generally (subject sits slightly above true-centre, the
   near edge of the plate/bowl toward the bottom of frame) -- a generic
   Detail-surface default, not specific to this one Recipe. Scoped to
   .rd-visual .rx-img only: Rezepte's card crop (rx-img elsewhere) is
   untouched. */
.rd-visual .rx-img{ object-position:50% 42%; }
.rd-title{ font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:clamp(30px, 8.4vw, 40px);
  line-height:1.02; margin:0; color:var(--text-primary); overflow-wrap:break-word; }
.rd-facts{ margin:12px 0 0; font-size:13.5px; line-height:1.4; color:var(--text-secondary); font-variant-numeric:tabular-nums; }
.rd-desc{ margin:14px 0 0; font-size:16px; line-height:1.6; color:var(--text-body); max-width:52ch; }

/* what CRVD already remembers: one quiet line */
.rd-mem{ display:flex; flex-wrap:wrap; align-items:center; column-gap:16px; margin:14px 0 0; }
.rd-mem-line{ margin:0; font-size:13px; color:var(--text-secondary); }

.rd-h2{ font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:22px; line-height:1.1;
  letter-spacing:.01em; color:var(--text-primary); margin:0; }

/* ---- Zutaten + portions ---- */
.rd-ing{ margin:36px 0 0; }
.rd-ing-head{ display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:8px 16px; margin:0 0 10px; }
.rd-portions{ display:inline-flex; align-items:center; gap:8px; font-size:14px; color:var(--text-secondary); }
.rd-step{ display:inline-flex; align-items:center; justify-content:center; width:44px; height:44px; padding:0; border:0; border-radius:50%;
  font:inherit; font-size:20px; line-height:1; color:var(--text-primary); cursor:pointer; -webkit-tap-highlight-color:transparent;
  background:radial-gradient(90% 90% at 50% 0%, rgba(var(--hi), .12), rgba(var(--hi), 0) 70%), linear-gradient(165deg, #2C2731 0%, #1B171F 100%);
  box-shadow:inset 0 1px 0 rgba(var(--hi), .14), inset 0 0 0 1px rgba(var(--hi), .05);
  transition:transform 160ms var(--patient), box-shadow 260ms var(--patient); }
@media (hover:hover){
  .rd-step:not([aria-disabled="true"]):hover{ box-shadow:inset 0 1px 0 rgba(var(--hi), .18), inset 0 0 0 1px rgba(var(--act), .32),
    0 8px 20px -12px rgba(var(--act), .55); }
}
.rd-step:not([aria-disabled="true"]):active{ transform:scale(.94); }
.rd-step[aria-disabled="true"]{ opacity:.35; cursor:default; }
.rd-step:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:3px; }
.rd-portions-n{ min-width:1.3em; text-align:center; font-family:var(--font-d); font-weight:600; font-size:22px;
  color:var(--text-primary); font-variant-numeric:tabular-nums; }

/* amount | ingredient -- the eye runs down the amounts */
.rd-ing-list{ list-style:none; margin:0; padding:0; }
.rd-ing-list li{ display:grid; grid-template-columns:6.4em minmax(0, 1fr); column-gap:14px; padding:8px 0;
  font-size:16px; line-height:1.4; }
.rd-q{ font-weight:600; color:var(--text-primary); font-variant-numeric:tabular-nums; }
.rd-n{ color:var(--text-body); overflow-wrap:break-word; }
.rd-note-i{ display:block; margin-top:1px; font-size:13.5px; line-height:1.4; color:var(--text-secondary); overflow-wrap:break-word; }
.rd-opt{ display:inline-block; margin-left:6px; padding:0 7px; border:1px solid var(--border-default); border-radius:999px; font-size:11.5px;
  line-height:18px; letter-spacing:.02em; color:var(--text-secondary); vertical-align:1px; white-space:nowrap; }
.rd-ing-list li.is-optional .rd-n{ color:var(--text-secondary); }
.rd-ing-list li.rd-scaled-hint{ padding-top:2px; }
.rd-scaled-hint .rd-n{ font-size:13px; color:var(--text-secondary); }

/* ---- Shopping List entry point (Recipe Detail) ---- */
.rd-shop{ margin:16px 0 0; }
.rd-shop-state{ margin:0 0 10px; font-size:14px; line-height:1.4; color:var(--text-body); }
.rd-shop-row{ display:flex; flex-wrap:wrap; align-items:center; gap:8px 12px; }
.rd-shop-go.is-quiet{ border-color:var(--hairline); color:var(--text-body); }
.rd-shop-go[disabled]{ opacity:.55; cursor:default; }
.rd-shop-quiet{ display:inline-flex; align-items:center; min-height:44px; padding:0 6px; border:0; background:none; color:var(--text-secondary);
  font:inherit; font-size:14px; text-decoration:underline; text-underline-offset:3px; cursor:pointer; }
.rd-shop-quiet:hover{ color:var(--text-primary); }
.rd-shop-quiet:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:2px; border-radius:4px; }
.rd-shop-msg.is-err{ color:#F0A2A2; }
.rd-shop-go{ display:inline-flex; align-items:center; justify-content:center; min-height:48px; padding:0 22px; border-radius:999px;
  border:1px solid var(--border-default); background:transparent; color:var(--text-primary); font:inherit; font-size:15px; font-weight:600;
  cursor:pointer; -webkit-tap-highlight-color:transparent; transition:border-color 200ms var(--patient), transform 160ms var(--patient); }
@media (hover:hover){ .rd-shop-go:hover{ border-color:var(--border-strong); } }
.rd-shop-go:active{ transform:scale(.98); }
.rd-shop-go:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:3px; }
.rd-shop-msg{ margin:10px 0 0; font-size:13.5px; line-height:1.5; color:var(--text-secondary); max-width:46ch; }
.rd-shop-msg:empty{ display:none; }
@media (max-width:479px){ .rd-shop > .rd-shop-go, .rd-shop-row > .rd-shop-go{ width:100%; } }

/* ---- Nährwerte pro Portion (Recipe V2) ---- */
.rd-nut{ margin:36px 0 0; padding:20px 18px 16px; border-radius:16px; background:var(--surface); box-shadow:inset 0 0 0 1px var(--hairline); }
.rd-nut-head{ display:flex; align-items:baseline; justify-content:space-between; gap:12px; margin:0 0 10px; }
.rd-nut-per{ font-size:13px; color:var(--text-secondary); }
.rd-nut-main{ display:flex; align-items:baseline; gap:8px; margin:0 0 4px; }
.rd-nut-invariant{ margin:0 0 14px; font-size:12.5px; line-height:1.4; color:var(--text-secondary); }
.rd-nut-kcal{ font-family:var(--font-d); font-weight:600; font-size:40px; line-height:1; color:var(--text-primary); font-variant-numeric:tabular-nums; }
.rd-nut-unit{ font-size:14px; color:var(--text-secondary); }
.rd-nut-grid{ display:grid; grid-template-columns:repeat(3, minmax(0, 1fr)); gap:14px 16px; margin:0; padding:14px 0 0; border-top:1px solid var(--hairline); }
.rd-nut-cell{ min-width:0; }
.rd-nut-cell dt{ font-size:12.5px; line-height:1.3; color:var(--text-secondary); overflow-wrap:break-word; }
.rd-nut-cell dd{ margin:2px 0 0; font-size:17px; font-weight:600; color:var(--text-primary); font-variant-numeric:tabular-nums; }
.rd-nut-foot{ margin:14px 0 0; font-size:12.5px; line-height:1.5; color:var(--text-secondary); }

/* ---- Zubereitung ---- */
.rd-prep{ margin:44px 0 0; }
.rd-prep .rd-h2{ margin:0 0 20px; }
.rd-steps{ list-style:none; counter-reset:rd-step; margin:0; padding:0; }
.rd-steps li{ counter-increment:rd-step; display:grid; grid-template-columns:2em minmax(0, 1fr); column-gap:12px;
  margin:0 0 24px; font-size:17px; line-height:1.62; color:var(--text-body); }
.rd-steps li::before{ content:counter(rd-step); font-family:var(--font-d); font-weight:600; font-size:28px; line-height:1;
  color:var(--text-primary); padding-top:1px; }

/* ---- the finish ---- */
.rd-finish{ position:relative; margin:40px 0 0; padding:8px 0 0; }
/* the light that answers "Fertig.": arrives once, then stays, low */
.rd-finish::before{ content:''; position:absolute; left:-24px; right:-24px; top:-40px; height:260px; pointer-events:none; opacity:0;
  background:radial-gradient(55% 60% at 22% 34%, rgba(var(--act), .15), rgba(var(--act), 0) 70%);
  transition:opacity 700ms var(--patient); }
.rd-finish.is-done::before{ opacity:1; }
.rd-done{ position:relative; display:inline-flex; align-items:center; justify-content:center; min-height:48px; padding:0 26px;
  border:0; border-radius:999px; cursor:pointer; font-family:var(--font-d); font-weight:600; font-size:18px; letter-spacing:.02em;
  color:var(--text-primary); -webkit-tap-highlight-color:transparent;
  background:radial-gradient(90% 120% at 50% 0%, rgba(var(--hi), .12), rgba(var(--hi), 0) 70%), linear-gradient(165deg, #2E2934 0%, #1B171F 100%);
  box-shadow:inset 0 1px 0 rgba(var(--hi), .16), inset 0 0 0 1px rgba(var(--hi), .06);
  transition:transform 160ms var(--patient), box-shadow 320ms var(--patient), color 320ms var(--patient); }
@media (hover:hover){
  .rd-finish:not(.is-done) .rd-done:hover{ box-shadow:inset 0 1px 0 rgba(var(--hi), .2), inset 0 0 0 1px rgba(var(--act), .34),
    0 10px 26px -12px rgba(var(--act), .5); }
}
.rd-finish:not(.is-done) .rd-done:active{ transform:scale(.97); }
.rd-done:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:4px; }
.rd-finish.is-done .rd-done{ cursor:default; color:#FFEAF8;
  background:radial-gradient(120% 140% at 30% 0%, rgba(var(--act), .46), rgba(var(--act), 0) 65%), linear-gradient(165deg, #3A2436 0%, #1C141D 100%);
  box-shadow:inset 0 1px 0 rgba(255, 214, 242, .42), inset 0 0 0 1px rgba(var(--act), .5), 0 0 26px -6px rgba(var(--act), .55); }
/* the chef's note: the human reward, and it stays */
.rd-note{ position:relative; margin:18px 0 0; font-family:var(--font-d); font-weight:500; text-transform:uppercase;
  font-size:clamp(20px, 5.6vw, 24px); line-height:1.2; color:var(--text-primary); max-width:22em; }
.rd-note:empty{ display:none; }
.rd-finish .cm-finish{ position:relative; margin-top:6px; }
.rd-finish.is-arriving .rd-note{ animation:rdNoteIn 520ms var(--patient) both; }
.rd-finish.is-arriving .cm-offer{ animation-delay:460ms; }
@keyframes rdNoteIn{ from{ opacity:0; transform:translateY(8px); } to{ opacity:1; transform:none; } }

/* ---- PL8 boundary (Free member, PL8 recipe) ----
   Discovery's lock, grown into the place where the ingredients would begin:
   a dark surface lit from its upper right, a magenta edge where the light
   catches, the lock + PL8 mark lifted off it, one line, one way onward.
   PROTOTYPE UI ONLY -- the protected content is not rendered here, but it
   still ships in app.js. */
.rd-lock{ position:relative; overflow:hidden; margin:32px 0 0; padding:26px 22px 24px; border-radius:18px;
  background:radial-gradient(90% 80% at 88% -10%, rgba(var(--act), .24), rgba(var(--act), 0) 62%),
    radial-gradient(70% 90% at 0% 110%, rgba(var(--lit), .06), rgba(var(--lit), 0) 70%),
    linear-gradient(165deg, #2C2430 0%, #18141B 100%);
  box-shadow:inset 0 1px 0 rgba(255, 214, 242, .22), inset 0 0 0 1px rgba(var(--act), .22), 0 30px 60px -36px rgba(var(--act), .45); }
.rd-lock-mark{ display:inline-flex; align-items:center; gap:8px; height:36px; padding:0 14px 0 12px; border-radius:12px;
  font-family:var(--font-c); font-size:13px; font-weight:700; letter-spacing:.14em; line-height:1; color:#FFEAF8;
  background:radial-gradient(130% 150% at 22% -12%, rgba(var(--act), .55), rgba(var(--act), 0) 62%), linear-gradient(165deg, #3A2436 0%, #1C141D 100%);
  box-shadow:inset 0 1px 0 rgba(255, 214, 242, .45), inset 0 0 0 1px rgba(var(--act), .5),
    0 10px 20px -10px rgba(0, 0, 0, .75), 0 0 28px -6px rgba(var(--act), .6); }
.rd-lock-mark .rx-lock-i{ flex:none; width:15px; height:15px; color:#FF8BDF; }
.rd-lock-h{ font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:clamp(22px, 6vw, 26px); line-height:1.1;
  color:var(--text-primary); margin:18px 0 0; }
.rd-lock-p{ font-size:15.5px; line-height:1.6; color:var(--text-body); margin:10px 0 0; max-width:40ch; }
.rd-lock-go{ display:inline-flex; align-items:center; min-height:48px; margin:22px 0 0; padding:0 24px; border:0; border-radius:999px;
  cursor:pointer; font-family:var(--font-c); font-size:15px; font-weight:600; color:var(--canvas); background:var(--magenta-brand);
  box-shadow:0 10px 28px -12px rgba(var(--act), .7), inset 0 1px 0 rgba(255, 255, 255, .28);
  transition:transform 160ms var(--patient), box-shadow 260ms var(--patient); }
@media (hover:hover){ .rd-lock-go:hover{ transform:translateY(-1px); box-shadow:0 14px 34px -12px rgba(var(--act), .85), inset 0 1px 0 rgba(255, 255, 255, .3); } }
.rd-lock-go:active{ transform:scale(.98); }
.rd-lock-go:focus-visible{ outline:2px solid rgba(var(--hi), .9); outline-offset:4px; }

/* ---- 1440: two working columns, the same order ----
   Left: what it is and what you need. Right: how, and the finish. The
   ingredients sit beside the steps they belong to. */
@media (min-width:1024px){
  .app-content-inner.rd{ max-width:1100px; }
  .rd-grid{ display:grid; grid-template-columns:minmax(0, 1fr) minmax(0, 1.12fr); column-gap:64px; align-items:start; }
  .rd .bt-detail-back{ margin-bottom:18px; }
  .rd-title{ font-size:clamp(34px, 3vw, 44px); }
  .rd-prep{ margin-top:0; }
  .rd-prep .rd-h2{ margin-bottom:24px; }
  /* Tested both: the left column (visual + ingredients) is taller than the
     viewport, so it cannot stay in view; the preparation column is short, so
     IT stays beside the ingredients while the member scrolls down them --
     under the app's own sticky header (65px + preview banner, as the sidebar
     uses). A preparation column taller than the viewport simply scrolls. */
  .rd:not(.is-locked) .rd-cook{ position:sticky; top:calc(65px + var(--preview-banner-h) + 28px); }
  .rd.is-locked .rd-lock{ margin-top:0; padding:34px 32px 32px; }
}

@media (prefers-reduced-motion: reduce){
  .rd-step, .rd-done, .rd-finish::before, .rd-lock-go{ transition:none; }
  .rd-finish.is-arriving .rd-note, .rd-finish.is-arriving .cm-offer{ animation:none; }
  .rd-step:active, .rd-done:active, .rd-lock-go:hover, .rd-lock-go:active{ transform:none; }
}


/* THE CONTEXTUAL QUESTION. It sits directly beneath the stepper that caused
   it and is ruled off from it by one hairline -- it belongs to that
   interaction, not to the page. No card, no toast, no banner, no modal:
   nothing blocks the recipe, and the member can keep scaling straight
   through it. The eyebrow-scale question with two quiet text actions is the
   same editorial grammar the Account surfaces already use. */
.portion-ask{ margin:calc(var(--s5) * -1) 0 var(--s5); }
.portion-ask.is-on{ margin:calc(var(--s4) * -1) 0 var(--s5);
  padding:var(--s4) 0 0; border-top:1px solid var(--hairline); max-width:34rem; }
.portion-ask-q{ font-family:var(--font-d); font-weight:500; font-size:18px;
  line-height:1.25; color:var(--text-primary); margin:0 0 4px; }
.portion-ask-sub{ font-size:13px; line-height:1.5; color:var(--text-secondary); margin:0; }
.portion-ask-actions{ display:flex; align-items:center; gap:var(--s5); margin-top:var(--s3); }
.portion-ask-yes, .portion-ask-no{ position:relative; background:none; border:none;
  cursor:pointer; font-family:var(--font-d); font-weight:500; font-size:15px;
  letter-spacing:0.02em; min-height:44px; padding:0; }
.portion-ask-yes{ color:var(--text-primary); }
/* The magenta blade again -- the same indicator vocabulary as the rails,
   marking the affirmative action without a filled button shouting on a
   recipe page. */
.portion-ask-yes::before{ content:''; position:absolute; left:-11px; top:50%;
  width:2px; height:16px; margin-top:-8px; background:var(--magenta);
  border-radius:1px; box-shadow:0 0 8px 0 rgba(226,25,181,0.45);
  transition:height 200ms var(--patient); }
.portion-ask-yes:hover::before, .portion-ask-yes:focus-visible::before{ height:24px; }
.portion-ask-no{ color:var(--muted); font-weight:400; }
.portion-ask-no:hover{ color:var(--text-secondary); }
.portion-ask-ok{ font-size:11.5px; letter-spacing:0.14em; text-transform:uppercase;
  font-weight:600; color:var(--muted); margin:0; }
.portion-ask-err{ font-size:12.5px; color:var(--magenta); margin:var(--s2) 0 0; }

@media (prefers-reduced-motion:reduce){
  .portion-ask-yes::before{ transition:none; }
}

/* Sprint 20.23 -- Community's Hintergrund tab and its chapter-discovery
   styles (.community-tabs/.community-tab/.community-hintergrund/
   .community-intro-*/.community-chapters/.community-chapter-link) were
   removed entirely: Community is single-view again, the story now lives at
   its own primary nav destination. */
.compose-box{ background:var(--surface-2); border-radius:10px; padding:var(--s4); margin:0 0 var(--s6); }
/* Phase 4C -- topic select reuses the textarea's own bottom-border
   treatment for visual consistency rather than introducing a new field
   style; no per-topic color, no icon, deliberately lightweight. */
.compose-topic-select{ width:auto; max-width:100%; background:none; border:none; border-bottom:1px solid var(--hairline); border-radius:0;
  padding:2px 0 8px; margin-bottom:8px; color:var(--text-secondary); font-family:var(--font-c); font-size:12.5px; cursor:pointer; }
.compose-topic-select:focus{ outline:2px solid var(--magenta); outline-offset:2px; border-bottom-color:var(--magenta); }
.compose-box textarea{ width:100%; background:none; border:none; border-bottom:1px solid var(--hairline); border-radius:0; padding:4px 0 10px; color:var(--text-primary);
  font-family:var(--font-c); font-size:14.5px; min-height:42px; resize:vertical; }
.compose-box textarea:focus{ outline:2px solid var(--magenta); outline-offset:2px; border-bottom-color:var(--magenta); }
/* Phase 4C -- feed topic filter. Plain text buttons, one flex row; the
   only visual distinction for the active one is weight/color, reusing
   the same tokens .who/.ack.acked already use -- no per-topic color,
   no badge, no count. */
.topic-filter{ display:flex; flex-wrap:wrap; gap:14px; margin:0 0 18px; }
.topic-filter-btn{ background:none; border:none; padding:0; font-family:inherit; font-size:13px; color:var(--muted); cursor:pointer; }
.topic-filter-btn.is-active{ color:var(--text-primary); font-weight:600; }
.feed-item{ padding:20px 0; border-bottom:1px solid var(--hairline); }
.feed-item .who{ font-weight:600; font-size:14.5px; }
.feed-item .when{ font-size:12px; color:var(--muted); margin-left:8px; }
.feed-item .topic-label{ font-size:12px; color:var(--muted); margin-left:8px; }
.feed-item p{ font-size:14.5px; line-height:1.6; color:var(--text-body); margin-top:6px; }
.feed-item .ack{ margin-top:8px; font-size:12.5px; color:var(--muted); background:none; border:none; padding:0; font-family:inherit; display:block; text-align:left; }
.feed-item button.ack{ cursor:pointer; }
.feed-item .ack.acked{ color:var(--text-secondary); }
/* Direct Moderator Removal (Community Moderation Truth foundation) --
   restrained at rest, clearly destructive on hover/focus. Exact same
   token pair and transition already used for "Konto dauerhaft löschen"
   (.acct-mod-quiet .btn-quiet) -- no new color introduced. Overrides
   .btn-quiet's own :hover rule (color:var(--text-primary)) for this one
   button only, so hovering Entfernen reads as destructive, never as the
   ordinary quiet-link hover every other .btn-quiet in this row shares. */

/* ==========================================================================
   COMMUNITY — SPRINT 1 (approved direction B3.1)
   ==========================================================================
   One shared room. Foreign contributions sit on a common left axis; own
   contributions step INWARD, never across to the other side. Everything
   here is scoped to cf-* on purpose: these are conversational surfaces,
   not a reusable card system, and nothing below may be borrowed by
   another screen. Whitespace is the separator (Constitution 2.5) --
   there is not a single border or divider in this block.
   Magenta is spent exactly once, on Teilen / Anonym teilen.
   ========================================================================== */
/* The room reserves the pinned composer's measured height (see
   render()), so the last contribution's actions are always reachable
   no matter which of the composer's three states is showing. */
.cf-room{ position:relative; padding-bottom:calc(var(--cf-comp-h, 150px) + 28px); }

/* ---- ROOM HEAD -----------------------------------------------------
   Kicker and statement, nothing else. The presence sentence was removed
   by Founder decision and is deliberately not replaced by another
   number. */
.cf-head{ position:relative; padding-top:2px; }
.cf-head .kicker{ font-size:10px; letter-spacing:0.2em; margin:0 0 9px; }
.cf-title{ font-family:var(--font-d); font-weight:500; text-transform:none;
  font-size:clamp(27px, 7.4vw, 34px); line-height:1.04; letter-spacing:0.004em;
  color:var(--text-primary); margin:0 0 var(--s5); max-width:8.2em; }

/* THE SIGNAL — the room's belonging anchor (Constitution 1.3 allows
   compositional dominance on a community surface, never on a utility
   one). Complete and uncropped so the gap and the tangential square --
   the only things separating it from a generic open circle -- stay
   readable. The LIGHT is stronger than the LINE: what should read is a
   glow with a mark inside it, not a mark with a glow. Never attached to
   an individual contribution, and never behind contribution text. */
.cf-sig{ position:absolute; z-index:0; pointer-events:none;
  color:var(--magenta); opacity:0.15;
  width:148px; height:148px; top:-8px; right:-10px; }
.cf-sig svg{ width:100%; height:100%; display:block; }
.cf-sig::before{ content:''; position:absolute; inset:-38%; border-radius:50%;
  background:radial-gradient(closest-side,
    rgba(226,25,181,0.26) 0%, rgba(226,25,181,0.10) 44%, rgba(226,25,181,0) 78%); }
.cf-head > *:not(.cf-sig){ position:relative; z-index:1; }

/* ---- CATEGORIES — editorial word-selector, not chips ----------------
   Borrowed from Konto's Focus selector, whose own note applies verbatim:
   "boxes would make four peers look like four buttons". The underline is
   NEUTRAL, never magenta: on this screen magenta means teilen, and a
   filter is not that. 44px hit area, absorbed by a negative block margin
   so the row keeps its quiet rhythm. */
.cf-topics{ display:flex; gap:20px; align-items:baseline; margin:0 0 var(--s4);
  overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none; }
.cf-topics::-webkit-scrollbar{ display:none; }
.cf-t{ position:relative; flex:none; background:none; border:none; cursor:pointer;
  padding:13px 0 14px; margin:-6px 0; font-family:var(--font-d); font-weight:400;
  font-size:13px; letter-spacing:0.05em; text-transform:uppercase;
  color:#655E69; white-space:nowrap; min-height:44px;
  transition:color 240ms var(--patient); }
.cf-t::after{ content:''; position:absolute; left:0; right:100%; bottom:7px;
  height:1px; background:var(--text-secondary);
  transition:right 320ms var(--patient); }
.cf-t:hover{ color:var(--text-secondary); }
.cf-t.is-on{ color:var(--text-primary); font-weight:500; font-size:15px; }
.cf-t.is-on::after{ right:0; }
.cf-t:focus-visible{ outline:2px solid var(--magenta); outline-offset:4px; }

/* ---- THE CONVERSATION ----------------------------------------------
   One ground of light under the whole room, transparent at every edge
   and masked away top and bottom -- the same device as Konto's own
   region wash, and the reason the room has depth without a single
   border. Decorative only; .app-shell's overflow-x:clip is what
   guarantees it can never enlarge the document. */
.cf-feed{ position:relative; }
.cf-feed::before{ content:''; position:absolute; z-index:0; pointer-events:none;
  inset:-10px -16px -20px;
  background:
    radial-gradient(64% 44% at 58% 70%, rgba(226,25,181,0.050) 0%, rgba(226,25,181,0) 72%),
    radial-gradient(70% 40% at 26% 12%, rgba(214,198,226,0.026) 0%, rgba(214,198,226,0) 74%);
  -webkit-mask-image:linear-gradient(180deg, transparent 0%, #000 12%, #000 86%, transparent 100%);
  mask-image:linear-gradient(180deg, transparent 0%, #000 12%, #000 86%, transparent 100%); }
/* Whitespace is the separator (Constitution 2.5): with the surfaces made
   quieter, the gap between contributions is what now does the dividing. */
.cf-post{ position:relative; z-index:1; margin-bottom:24px; }
.cf-post:last-child{ margin-bottom:0; }

/* METADATA — one compact group, mixed case, ungapped. Uppercase with
   letter-spacing is what gave 11px text the optical density of a
   heading; without it the line recedes without becoming illegible.
   --muted, not --text-disabled: Konto's own measurement puts 10-11px at
   --text-disabled near 3.2:1 on this ground, which is below AA. Quiet is
   a decision, illegible is a defect. */
.cf-meta{ display:flex; align-items:center; gap:7px; margin:0 0 6px;
  padding-left:2px; flex-wrap:wrap; }
.cf-mk{ width:20px; height:20px; border-radius:50%; flex:none; display:flex;
  align-items:center; justify-content:center; font-size:8px; font-weight:600;
  letter-spacing:0.03em; color:#CFC7CC; }
/* Three quiet tones, derived from the DISPLAYED name only -- a property
   of something already public. An anonymous contribution never gets one:
   one identical neutral marker for every anonymous contribution by
   anyone, so there is nothing here to correlate on. */
.cf-mk-a{ background:#332A36; } .cf-mk-b{ background:#293430; } .cf-mk-c{ background:#2F2E3A; }
.cf-mk-anon{ background:rgba(245,241,236,0.045); color:#6E6670; }
.cf-nm{ font-size:12.5px; font-weight:500; color:var(--text-secondary); }
.cf-du{ font-size:12.5px; font-weight:600; color:var(--text-secondary); }
.cf-mt{ font-size:11.5px; color:var(--muted); letter-spacing:0; }

/* THE SURFACE — depth without a border, and now SIZED BY ITS CONTENT.
   Every contribution used to become a full-column bar, which at 119
   contributions read as a stack of identical dark rectangles rather than
   as people saying different-length things. width:fit-content lets a
   one-line thought stay a one-line thought while a long one still reaches
   the reading measure -- that difference is the room's rhythm.
   min-width stops a two-word contribution from becoming a speech balloon;
   max-width keeps the longest one readable. */
.cf-bubble{ position:relative; width:fit-content; max-width:100%;
  min-width:min(100%, 8.5rem);
  border-radius:4px 17px 17px 17px; padding:10px 14px;
  /* Less mass, not less size. On a 390px phone a 17px sentence fills the
     column no matter what fit-content does -- physics, and shrinking the
     text to fake rhythm would contradict "text is the hero". So the wall
     of bars is broken by taking CONTRAST out of the surface instead:
     the words keep their weight, the container loses its. */
  background:linear-gradient(178deg,
    rgba(245,241,236,0.036) 0%, rgba(245,241,236,0.015) 100%); }
.cf-bubble p{ margin:0; font-size:17px; line-height:1.55; color:var(--text-primary);
  overflow-wrap:anywhere; }

/* OWN CONTRIBUTION — Treatment II. Steps inward, never to the other
   side. The dark-magenta tone plus a ground of light that is transparent
   at every edge says "das bin ich"; it must never say "this one matters
   more", which is why it stays far below Teilen in salience and gains no
   second accent, no icon and no Signal of its own. */
.cf-post.cf-own{ margin-left:24px; }
.cf-post.cf-own .cf-bubble{ border-radius:17px 17px 4px 17px;
  background:linear-gradient(178deg,
    rgba(226,25,181,0.100) 0%, rgba(226,25,181,0.042) 100%); }
/* The atmosphere now hangs off the BUBBLE, not off the full-width post:
   with content-sized surfaces a bloom anchored to the row would float in
   empty space to the right of a short contribution. */
.cf-post.cf-own .cf-bubble::before{ content:''; position:absolute; z-index:-1;
  pointer-events:none; inset:-14px -20px -12px -22px;
  background:radial-gradient(72% 62% at 40% 52%,
    rgba(226,25,181,0.10) 0%, rgba(226,25,181,0) 72%);
  -webkit-mask-image:linear-gradient(180deg, transparent 0%, #000 20%, #000 80%, transparent 100%);
  mask-image:linear-gradient(180deg, transparent 0%, #000 20%, #000 80%, transparent 100%); }
.cf-tomb .cf-bubble{ background:rgba(245,241,236,0.028); }
.cf-tomb-line{ font-size:14.5px; line-height:1.6; color:var(--muted); font-style:italic; }

/* ---- ACKNOWLEDGMENT — beta ------------------------------------------
   A state dot, a number, one word. The dot is tonal and carries state
   only: empty means you have not, filled means you have. Not a like, not
   a reaction, no picker, no pill. The full canonical sentence lives in
   the accessible name; the visible copy is the compact form.
   Acknowledged uses --text-secondary and NEVER --text-primary
   (Constitution 4.2 / R-3: "calm and human, not celebratory"). */
.cf-act{ display:flex; align-items:center; gap:18px; margin-top:6px;
  padding-left:2px; flex-wrap:wrap; }
.cf-ack{ display:inline-flex; align-items:center; gap:10px; min-height:44px;
  margin:-9px 0; padding:0; background:none; border:none; font-family:inherit;
  color:var(--muted); text-align:left;
  transition:color 200ms var(--patient); }
button.cf-ack{ cursor:pointer; }
/* PRESS is answered before the network is: the dot compresses under the
   thumb in 90ms, which is what makes the control feel received rather
   than laggy. The confirmation impact is a one-shot animation on top
   (see communityMotion.ack). */
.cf-dot{ width:10px; height:10px; border-radius:50%; flex:none;
  transform-origin:50% 50%;
  border:1.25px solid var(--muted);
  transition:background-color 200ms var(--patient), border-color 200ms var(--patient),
             transform 90ms var(--patient); }
button.cf-ack:active .cf-dot{ transform:scale(.78); transition-duration:70ms; }
/* The count is replaced, not morphed: one number leaves, the next arrives.
   6px and 160ms -- enough to register as "that changed", not enough to
   read as a counter animating. */
@keyframes cfCountIn{ from{ opacity:0; transform:translateY(-5px); } to{ opacity:1; transform:none; } }
.cf-ackt b{ display:inline-block; }
.cf-ackt b.is-new{ animation:cfCountIn 160ms var(--patient); }
.cf-ackt{ font-size:11.5px; line-height:1; }
.cf-ackt b{ font-family:var(--font-d); font-weight:500; font-size:15px;
  font-variant-numeric:tabular-nums; letter-spacing:0.01em; margin-right:5px; }
.cf-ack.is-on{ color:var(--text-secondary); }
.cf-ack.is-on .cf-dot{ background:var(--text-secondary); border-color:var(--text-secondary); }
button.cf-ack:hover{ color:var(--text-secondary); }
.cf-ack:focus-visible{ outline:2px solid var(--magenta); outline-offset:4px; }
/* A member cannot acknowledge their own contribution: the count stays as
   information, with no control and no state dot to imply otherwise. */
.cf-ack-self{ color:var(--muted); }

/* Secondary actions sit below the contribution in rank, as asked. */
/* Contrast fix (cutover): #5C5560 measured ~2.69:1 on the canvas. --muted
   is 5.92:1 there and still reads as the quietest rank in the room. */
.cf-sec{ background:none; border:none; padding:0; font-family:inherit;
  font-size:11.5px; color:var(--muted); cursor:pointer;
  min-height:44px; margin:-9px 0; display:inline-flex; align-items:center;
  transition:color 200ms var(--patient); }
.cf-sec:hover{ color:var(--text-secondary); }
.cf-sec:focus-visible{ outline:2px solid var(--magenta); outline-offset:4px; }
.cf-sec.is-static{ cursor:default; }
.cf-sec.is-static:hover{ color:var(--text-secondary); }
.cf-sec-remove:hover, .cf-sec-remove:focus-visible{ color:var(--action-destructive); }
.cf-state{ font-size:12.5px; line-height:1.5; color:var(--muted); margin:0 0 10px; }
.cf-notice{ font-size:12.5px; line-height:1.5; color:var(--text-secondary);
  margin:0 0 var(--s4); }

/* ---- COMPOSER -------------------------------------------------------
   Pinned to the bottom of the room, ruled rather than boxed, and in its
   collapsed state one line: an invitation, not a second content block
   competing with the conversation. */
.cf-composer{ position:fixed; left:0; right:0; bottom:0; z-index:9;
  padding:18px max(16px, env(safe-area-inset-right))
          calc(66px + max(8px, env(safe-area-inset-bottom)))
          max(16px, env(safe-area-inset-left));
  /* Pixel-based, not percentage: a percentage fade scales with the
     composer's own height, so the expanded state (which is several
     hundred pixels tall) stayed translucent far enough down that the
     conversation read straight through it. A fixed 26px lead-in is
     opaque from the same place in every one of the composer's states. */
  background:linear-gradient(180deg, rgba(15,13,18,0) 0%, var(--canvas) 26px); }
.cf-comp-in{ max-width:640px; margin:0 auto;
  border-top:1px solid var(--hairline); padding-top:13px; }
.cf-composer.is-open .cf-comp-in{ max-height:72vh; overflow-y:auto; }
.cf-invite{ width:100%; display:block; text-align:left; background:none;
  border:none; padding:0; font-family:inherit; cursor:pointer; min-height:56px; }
.cf-invite-t{ display:block; font-family:var(--font-d); font-weight:400;
  font-size:15.5px; line-height:1.2; letter-spacing:0.004em;
  color:var(--text-secondary); transition:color 220ms var(--patient); }
.cf-invite:hover .cf-invite-t{ color:var(--text-primary); }
.cf-invite-s{ display:block; margin-top:4px; font-size:9px; font-weight:600;
  letter-spacing:0.2em; text-transform:uppercase; color:var(--muted); }
.cf-invite:focus-visible{ outline:2px solid var(--magenta); outline-offset:4px; }

.cf-field{ margin:0 0 var(--s4); }
.cf-lbl{ display:block; font-size:9.5px; font-weight:600; letter-spacing:0.2em;
  text-transform:uppercase; color:var(--muted); margin:0 0 8px; }
.cf-choice{ display:flex; flex-wrap:wrap; gap:6px 18px; align-items:baseline; }
.cf-opt{ background:none; border:none; padding:11px 0 12px; margin:-6px 0;
  min-height:44px; font-family:var(--font-d); font-weight:400; font-size:13.5px;
  letter-spacing:0.04em; color:#655E69; cursor:pointer; position:relative;
  transition:color 220ms var(--patient); }
.cf-opt::after{ content:''; position:absolute; left:0; right:100%; bottom:8px;
  height:1px; background:var(--text-secondary); transition:right 300ms var(--patient); }
.cf-opt.is-on{ color:var(--text-primary); font-weight:500; }
.cf-opt.is-on::after{ right:0; }
.cf-opt:hover{ color:var(--text-secondary); }
.cf-opt:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }
.cf-hint{ font-size:11.5px; line-height:1.5; color:var(--muted); margin:8px 0 0; }
.cf-input{ width:100%; background:none; border:none;
  border-bottom:1px solid var(--hairline); border-radius:0; padding:9px 0 10px;
  /* 16px, not 15: iOS Safari zooms into any focused field below 16px and
     does not zoom back out after the keyboard closes. */
  color:var(--text-primary); font-family:var(--font-c); font-size:16px; }
.cf-input::placeholder{ color:var(--muted); }
.cf-input:focus{ outline:2px solid var(--magenta); outline-offset:2px;
  border-bottom-color:var(--magenta); }
/* ---- THE WRITING AREA ------------------------------------------------
   Not a form field. The bright magenta outline stacked on a magenta
   underline read as technical validation chrome, and a fixed 66px box
   handed the member an empty rectangle before they had written anything.
   At rest there is no box at all. Focus is answered ATMOSPHERICALLY --
   a faint ground rises behind the words and one quiet baseline appears --
   and saturated magenta stays where it belongs: on Teilen. */
.cf-write{ position:relative; border-radius:10px;
  transition:background-color 260ms var(--patient); }
.cf-write::after{ content:''; position:absolute; left:0; right:0; bottom:0;
  height:1px; background:var(--hairline);
  transform:scaleX(1); transform-origin:left center;
  transition:background-color 260ms var(--patient); }
.cf-write:focus-within{ background:rgba(245,241,236,0.026); }
.cf-write:focus-within::after{ background:var(--text-secondary); }
.cf-text{ display:block; width:100%; background:none; border:none;
  border-radius:0; padding:8px 10px 10px; margin:0;
  color:var(--text-primary); font-family:var(--font-c); font-size:16px;
  line-height:1.55; resize:none; overflow:hidden;
  min-height:2.6em; max-height:32vh;
  transition:height 140ms var(--patient); }
.cf-text::placeholder{ color:var(--muted); }
/* Pointer focus is carried by the tonal ground above; keyboard focus keeps
   a real ring, because an atmospheric cue alone is not a focus indicator
   for someone navigating by Tab. */
.cf-text:focus{ outline:none; }
.cf-text:focus-visible{ outline:2px solid var(--text-secondary); outline-offset:2px; }
.cf-text:disabled{ opacity:0.45; }
.cf-comp-foot{ display:flex; align-items:center; justify-content:space-between;
  gap:var(--s4); margin-top:var(--s3); }
.cf-comp-quiet{ display:flex; align-items:center; gap:18px; }
/* THE one magenta on this screen. */
.cf-share{ flex:none; background:var(--magenta); color:var(--canvas);
  border:none; border-radius:999px; padding:13px 22px; min-height:44px;
  line-height:1.2;
  font-family:var(--font-c); font-size:13.5px; font-weight:600;
  letter-spacing:0.01em; cursor:pointer;
  transition:opacity 220ms var(--patient); }
.cf-share:hover{ opacity:0.9; }
.cf-share:disabled{ opacity:0.5; cursor:default; }
.cf-share:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }
/* The guideline gate keeps its existing .compose-box block; inside the
   pinned composer it needs no second surface of its own. */
.cf-comp-in .compose-box{ background:none; padding:0; margin:0; }

/* ---- DISCOVERY ------------------------------------------------------
   The room is a conversation by default. What used to be a permanent
   four-item filter row is now one quiet control; state appears only once
   there IS state, and it appears as a sentence, not as controls. */
.sr-only{ position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0; }

.cf-bar{ display:flex; align-items:center; gap:var(--s4); flex-wrap:wrap;
  margin:0 0 var(--s4); }
.cf-disc-trigger{ display:inline-flex; align-items:center; gap:8px;
  background:none; border:none; padding:11px 0 12px; margin:-6px 0; min-height:44px;
  font-family:var(--font-c); font-size:12.5px; color:var(--muted); cursor:pointer;
  transition:color 200ms var(--patient); }
.cf-disc-trigger svg{ width:15px; height:15px; flex:none; }
.cf-disc-trigger:hover{ color:var(--text-primary); }
.cf-disc-trigger:focus-visible{ outline:2px solid var(--magenta); outline-offset:4px; }
.cf-bar-state{ flex:1 1 12rem; min-width:0; margin:0; font-size:12px;
  line-height:1.45; color:var(--text-secondary); }
.cf-bar-n{ font-family:var(--font-d); font-weight:500; font-size:14px;
  font-variant-numeric:tabular-nums; color:var(--text-primary); }

.cf-disc{ margin:0 0 var(--s5); }
.cf-disc-search{ display:flex; align-items:center; gap:10px;
  border-bottom:1px solid var(--hairline); padding-bottom:10px; margin-bottom:var(--s4); }
.cf-disc-search svg{ width:17px; height:17px; flex:none; color:var(--muted); }
.cf-search{ flex:1; min-width:0; background:none; border:none; padding:10px 0;
  min-height:44px; color:var(--text-primary); font-family:var(--font-c);
  font-size:16px; }
.cf-search::placeholder{ color:#5C5560; }
.cf-search:focus{ outline:none; }
.cf-disc-search:focus-within{ border-bottom-color:var(--text-secondary); }
/* Tags: quiet words in a soft tonal well, never coloured badges. The one
   selected state is tonal too -- categories may not spend the accent. */
.cf-tagwrap{ display:flex; flex-wrap:wrap; gap:8px; }
.cf-tag{ background:rgba(245,241,236,.035); border:none; border-radius:999px;
  padding:10px 14px; min-height:44px; font-family:var(--font-c); font-size:12.5px;
  color:var(--text-secondary); cursor:pointer;
  transition:background-color 200ms var(--patient), color 200ms var(--patient); }
.cf-tag:hover{ background:rgba(245,241,236,.065); color:var(--text-primary); }
.cf-tag:active{ transform:scale(.97); }
.cf-tag{ transition:background-color 200ms var(--patient), color 200ms var(--patient),
                    transform 90ms var(--patient); }
/* SELECTED IS UNMISTAKABLE — through tone, weight and a light edge, never
   through a colour of its own. Eight accent colours would be eight new
   accents; this is one system used harder. */
.cf-tag.is-on{ background:rgba(245,241,236,.145); color:var(--text-primary);
  font-weight:600; box-shadow:inset 0 0 0 1px rgba(245,241,236,.16); }
.cf-tag.is-on:hover{ background:rgba(245,241,236,.175); }
/* At the ceiling the unselected ones recede rather than lying about being
   available. */
.cf-tag.is-full{ opacity:.45; }
.cf-tag:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }

/* Tags on a contribution: words inside the existing meta line. */
/* 44px hit area without 44px of visual weight -- the same negative-margin
   pattern the rest of this product uses. A tag word is 14px tall on the
   line; the tappable box around it is not (Constitution 5.1). */
.cf-mtag{ background:none; border:none; padding:0; font-family:inherit;
  display:inline-flex; align-items:center; min-height:44px; margin:-15px 0;
  font-size:11.5px; color:var(--muted); cursor:pointer; text-decoration:underline;
  text-decoration-color:transparent; text-underline-offset:3px;
  transition:color 180ms var(--patient), text-decoration-color 180ms var(--patient); }
.cf-mtag:hover{ color:var(--text-secondary); text-decoration-color:currentColor; }
.cf-mtag:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }
.cf-sep{ font-size:11.5px; color:#4E4853; }

.cf-more{ display:block; width:100%; margin:var(--s5) 0 0; background:none;
  border:none; padding:14px 0; min-height:48px; font-family:var(--font-d);
  font-weight:400; font-size:13px; letter-spacing:.05em; text-transform:uppercase;
  color:var(--muted); cursor:pointer; transition:color 200ms var(--patient); }
.cf-more:hover{ color:var(--text-primary); }
.cf-more:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }
.cf-empty{ padding:var(--s5) 0 var(--s6); }
.cf-empty-l{ font-family:var(--font-d); font-weight:400; font-size:19px;
  line-height:1.3; color:var(--text-secondary); margin:0 0 8px; }

/* ---- COMPOSER META LINE ---------------------------------------------
   Three words where seven fields used to be. Each opens exactly one row
   beneath it; only one is ever open. */
/* ---- METADATA CONTROLS ----------------------------------------------
   Editorial, not pills. Three signals do the work: display type (this is
   not prose), a faint permanent underline (this line is a control), and a
   caret where a list opens. A tonal ground appears only while one is
   open, so at rest the line still weighs almost nothing. */
.cf-metabar{ display:flex; align-items:center; gap:4px; flex-wrap:wrap;
  margin-top:12px; }
.cf-ctl{ position:relative; display:inline-flex; align-items:center; gap:6px;
  background:none; border:none; cursor:pointer;
  padding:9px 8px 10px; margin:-5px 0; min-height:44px; border-radius:7px;
  font-family:var(--font-d); font-weight:400; font-size:13.5px;
  letter-spacing:0.03em; color:var(--text-secondary);
  transition:color 200ms var(--patient), background-color 220ms var(--patient); }
.cf-ctl-t{ position:relative; }
.cf-ctl-t::after{ content:''; position:absolute; left:0; right:0; bottom:-3px;
  height:1px; background:rgba(245,241,236,0.20);
  transition:background-color 220ms var(--patient); }
.cf-ctl.is-empty{ color:var(--muted); }
.cf-ctl:hover{ color:var(--text-primary); background:rgba(245,241,236,0.035); }
.cf-ctl:hover .cf-ctl-t::after{ background:rgba(245,241,236,0.42); }
.cf-ctl.is-open{ color:var(--text-primary); background:rgba(245,241,236,0.055); }
.cf-ctl.is-open .cf-ctl-t::after{ background:var(--text-secondary); }
.cf-ctl:focus-visible{ outline:2px solid var(--magenta); outline-offset:2px; }
.cf-caret{ width:0; height:0; flex:none; margin-top:1px;
  border-left:3.5px solid transparent; border-right:3.5px solid transparent;
  border-top:4px solid currentColor; opacity:.55;
  transition:transform 240ms var(--patient), opacity 200ms var(--patient); }
.cf-ctl.is-open .cf-caret{ transform:rotate(180deg); opacity:.9; }
/* The discovery layer's secondary filter keeps the old chip treatment. */
.cf-chip{ background:none; border:none; padding:10px 0 11px; margin:-6px 0;
  min-height:44px; font-family:var(--font-c); font-size:11.5px;
  color:var(--muted); cursor:pointer; position:relative;
  transition:color 200ms var(--patient); }
.cf-chip.is-set{ color:var(--text-secondary); }
.cf-chip.is-open{ color:var(--text-primary); }
.cf-chip::after{ content:''; position:absolute; left:0; right:100%; bottom:8px;
  height:1px; background:var(--text-secondary); transition:right 280ms var(--patient); }
.cf-chip.is-open::after{ right:0; }
.cf-chip:hover{ color:var(--text-primary); }
.cf-chip:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }
.cf-panel{ margin-top:var(--s3); padding-top:var(--s3);
  border-top:1px solid rgba(245,241,236,.05); }

/* ---- TABLET --------------------------------------------------------- */
@media (min-width:641px){
  .cf-post.cf-own{ margin-left:48px; }
  .cf-sig{ width:176px; height:176px; }
  .cf-search{ font-size:15px; }
  .cf-bubble{ max-width:min(100%, 32rem); }
}

/* ---- DESKTOP --------------------------------------------------------
   The conversation stays deliberately narrow -- Konto's own measure --
   and the whitespace that opens on the right is not leftover space: it
   is where the Signal lives. No second content column is introduced to
   fill it. The composer stops being pinned and becomes the last act of
   the column. */
@media (min-width:1024px){
  .cf-head, .cf-topics, .cf-feed, .cf-composer .cf-comp-in{ max-width:35rem; }
  .cf-head, .cf-topics, .cf-feed{ margin-left:0; }
  .cf-title{ font-size:clamp(34px, 3.4vw, 44px); max-width:14em; }
  .cf-post.cf-own{ margin-left:96px; }
  /* Content-sized on desktop too: a 30rem ceiling inside a 35rem column
     means even the longest contribution leaves the column breathing. */
  /* 27rem inside a 35rem column: every contribution keeps a right margin,
     and a shorter one is visibly shorter rather than one of three widths. */
  .cf-bubble{ max-width:min(100%, 27rem); }
  .cf-feed::before{ inset:-18px -300px -28px -20px;
    background:
      radial-gradient(52% 56% at 76% 52%, rgba(226,25,181,0.075) 0%, rgba(226,25,181,0) 74%),
      radial-gradient(44% 32% at 12% 10%, rgba(214,198,226,0.026) 0%, rgba(214,198,226,0) 74%); }
  /* Left edge clear of the conversation column: the mark is never behind
     contribution text, and never a logo parked beside the feed. */
  .cf-sig{ width:250px; height:250px; top:300px; right:auto;
    left:calc(35rem + 3.5rem); }
  .cf-room{ padding-bottom:0; }
  .cf-bar, .cf-disc{ max-width:35rem; }
  .cf-composer{ position:static; padding:0; background:none;
    margin-top:var(--s6); }
  .cf-composer .cf-comp-in{ margin:0; }
  .cf-composer.is-open .cf-comp-in{ max-height:none; overflow:visible; }
}

.feed-item .btn-remove-post{ color:var(--text-disabled); }
.feed-item .btn-remove-post:hover,
.feed-item .btn-remove-post:focus-visible{ color:var(--action-destructive); }

/* Shared editorial body type. Used by the Story Article and, unchanged since
   Sprint 16, by the legal and community-guideline screens on the dark ground
   (.background-body is read by 50+ call sites in app.js) -- it is deliberately
   NOT Story-only and is kept here verbatim. */
.background-body{ font-size:16.5px; line-height:1.9; color:var(--text-body); margin-bottom:36px; max-width:56ch; }
.background-closing{ margin-top:32px; font-size:12.5px; color:var(--muted); font-style:italic; }


/* ======================================================================
   STORY V1 -- one editorial environment for Hub and Article.

   Environment, not a new palette: #F4EEE0 is the Reader's existing paper
   (--editorial-paper, Constitution). Story gives that material more spatial
   responsibility -- it is the ground of the whole screen at every width, not
   a panel floating on the dark canvas. It stays an environmental material:
   never an accent, never a CTA, never a status colour, never used outside
   Story.

   Tokens are rescoped once on .story (--text-primary, --text-body, --muted,
   --hairline, --magenta), so every shared component that lands here --
   .bt-detail-back, .btn-quiet, .background-body -- re-themes with no
   per-rule duplication. Inside the Reader --magenta IS editorial ink; the
   Hub's single deliberate accent restores the brand value explicitly, once
   (.story-feature-action). Constitution 2.3: one accent, one job, once per
   screen.

   No cards: entries are type, whitespace and hairlines on the paper. No
   numerals, no act markers, no per-entry arrows, no Signal.
   ====================================================================== */

/* The ground. .app-content-reader is set by render() only when
   state.screen === 'background', so this belongs to Story alone. The
   two-class selector wins over .app-content's own padding in every
   breakpoint regime without !important. */
.app-body .app-content-reader{ background:var(--editorial-paper); padding:0; }

.story{
  --story-ink-line:rgba(34,30,26,0.16);
  /* Story's surfaces are the paper lifted toward the light, never a second
     beige and never a white box: one material, two states. */
  --story-surface:#F9F4E8;
  --story-surface-lit:#FCF8F0;
  /* The featured frame: one square at every width. The square concentrates
     the image on the person -- face, upper body, gesture -- instead of
     documenting the room, and it behaves identically on 390 and 1440, so a
     photograph is judged once. The zoom tightens that square around the
     focal point; the file itself is never cut. */
  --story-feature-ratio:1/1;
  --story-feature-zoom:1.3;
  background:var(--editorial-paper);
  --text-primary:var(--editorial-ink);
  --text-body:var(--editorial-ink-secondary);
  --muted:#7A7064;
  --hairline:var(--editorial-divider);
  --magenta:var(--editorial-ink);
  color:var(--editorial-ink);
  min-height:100%;
  padding:18px max(20px, env(safe-area-inset-right)) var(--mobile-safe-bottom) max(20px, env(safe-area-inset-left));
}
/* --mobile-safe-bottom (190px) reserves room for the bottom navigation. A
   signed-out visitor arriving from the public homepage has no bottom nav, so
   that clearance was 190px of empty paper -- unnoticeable on the dark canvas,
   very visible now that paper is the ground. Same :has() idiom the welcome
   screen already uses for the same reason. Safe-area inset preserved. */
body:not(:has(.app-bottomnav)) .story{ padding-bottom:max(56px, env(safe-area-inset-bottom)); }
.story-inner{ max-width:1080px; margin:0 auto; }
/* The shared back control, given an explicit box here. Its icon is an
   unsized <svg> (viewBox only), so in a plain block it expands to the column
   -- the same defect .essen/.wt already correct on their own screens. Scoped
   to Story for the same reason they scoped theirs: Recipe Detail and the
   Guidelines use this class too and are not part of this slice. */
.story .bt-detail-back{ display:inline-flex; align-items:center; gap:7px; min-height:44px;
  padding:0; margin:0 0 16px; }
.story .bt-detail-back svg{ flex:none; width:17px; height:17px; }

/* ---- the hero: statement · photograph · featured Story ----
   One composition. On mobile it is one vertical rhythm; at desktop the
   photograph becomes the hinge between the editorial statement (left) and the
   Story it opens (right). Deliberately not three equal columns. */
.story-hero{ margin:0 0 6px; }

/* ---- masthead ---- */
.story-mast{ padding-top:8px; margin:0 0 16px; }
.story-eyebrow{ font-family:var(--font-c); font-size:11px; font-weight:700; letter-spacing:0.22em;
  text-transform:uppercase; color:var(--muted); margin:0 0 14px; }
.story-mast-title{ font-size:clamp(26px,7.2vw,34px); line-height:1.06; max-width:18ch; margin:0;
  color:var(--text-primary); }
.story-mast-intro{ font-family:var(--font-c); font-size:16px; line-height:1.6; color:var(--text-body);
  margin:14px 0 0; max-width:42ch; }

/* ---- the image slot ----
   One component for every Story image. With a real file it is the photograph;
   without one it is an intentional editorial area with print crop marks -- it
   shows the composition the future photograph will occupy and never pretends
   to be that photograph. */
/* A photograph, not a media container: no border, no card, no surrounding
   box, no tonal field behind it -- only the crop. The tinted field belongs to
   the empty state alone. The single restrained radius is the same softening
   the Product's own objects use (Start's doorway, Konto's entries), so the
   image belongs to CRVD's surfaces instead of sitting on top of them. */
.story-fig{ display:block; position:relative; overflow:hidden; }
.story-fig-img{ display:block; width:100%; height:100%; object-fit:cover; }
/* Landscape, not portrait: an editorial photograph in a composition, never an
   avatar, headshot module or profile card. 5:4 rather than 4:3 after the first
   real photograph: a portrait source needs the slightly taller frame to keep
   headroom, the gesture and the room around the subject in one image without
   turning the Hub into a portrait. One frame at every width -- cover means the
   crop is identical on 390 and 1440, so a photo is judged once. */
.story-fig--feature{ aspect-ratio:var(--story-feature-ratio); border-radius:14px; }
/* Presentation-only magnification around the image's own focal point (set
   per image, see storyImage()). This is what makes the square concentrate on
   the person: everything outside it -- ceiling, scaffolding, oven, the person
   in the foreground -- simply falls outside the frame. The file is untouched. */
.story-fig--feature .story-fig-img{ transform:scale(var(--story-feature-zoom,1)); }
.story-fig--hero, .story-fig--inline{ aspect-ratio:3/2; border-radius:14px; }
.story-fig.is-empty{ background:linear-gradient(176deg,#EFE7D6 0%,#E7DECB 100%); }
.story-fig.is-empty::before{ content:''; position:absolute; inset:14px; pointer-events:none;
  background:
    linear-gradient(var(--story-ink-line),var(--story-ink-line)) left    top/18px 1px no-repeat,
    linear-gradient(var(--story-ink-line),var(--story-ink-line)) left    top/1px 18px no-repeat,
    linear-gradient(var(--story-ink-line),var(--story-ink-line)) right   top/18px 1px no-repeat,
    linear-gradient(var(--story-ink-line),var(--story-ink-line)) right   top/1px 18px no-repeat,
    linear-gradient(var(--story-ink-line),var(--story-ink-line)) left  bottom/18px 1px no-repeat,
    linear-gradient(var(--story-ink-line),var(--story-ink-line)) left  bottom/1px 18px no-repeat,
    linear-gradient(var(--story-ink-line),var(--story-ink-line)) right bottom/18px 1px no-repeat,
    linear-gradient(var(--story-ink-line),var(--story-ink-line)) right bottom/1px 18px no-repeat; }
.story-fig-note{ position:absolute; left:28px; bottom:24px; font-family:var(--font-c); font-size:10.5px;
  font-weight:700; letter-spacing:0.18em; text-transform:uppercase; color:rgba(34,30,26,0.42); }

/* ---- the control ----
   The heading carries the button and the button stretches across its entry,
   so the whole area is tappable while the document outline stays real
   headings. Focus draws around the entry, not around the words. */
.story-open{ display:inline; background:none; border:0; margin:0; padding:0; font:inherit; color:inherit;
  letter-spacing:inherit; text-transform:inherit; text-align:left; cursor:pointer;
  transition:color 140ms var(--patient); }
.story-open::after{ content:''; position:absolute; inset:0; }
.story-open:focus-visible{ outline:2px solid var(--editorial-ink); outline-offset:4px; }
.story-feature:has(.story-open:focus-visible){ outline:2px solid var(--editorial-ink); outline-offset:8px; }
/* the entry now has its own edge, so the ring hugs the surface instead of
   floating away from it */
.story-entry:has(.story-open:focus-visible){ outline:2px solid var(--editorial-ink); outline-offset:4px; }
.story-feature:has(.story-open:focus-visible) .story-open:focus-visible,
.story-entry:has(.story-open:focus-visible) .story-open:focus-visible{ outline:none; }

.story-label{ font-family:var(--font-c); font-size:10.5px; font-weight:700; letter-spacing:0.2em;
  text-transform:uppercase; color:var(--muted); margin:0 0 10px; }

/* ---- featured Story ---- */
.story-feature{ position:relative; margin:0 0 54px; }
.story-feature-media{ margin:0 0 16px; }
.story-feature-title{ margin:0; font-size:clamp(34px,9.6vw,46px); line-height:0.98; }
.story-feature-teaser{ font-family:var(--font-c); font-size:16.5px; line-height:1.6; color:var(--text-body);
  margin:14px 0 0; max-width:40ch; }
/* The one deliberate accent on this screen. */
.story-feature-action{ display:inline-block; margin:20px 0 0; padding-bottom:5px; font-family:var(--font-c);
  font-size:12px; font-weight:700; letter-spacing:0.18em; text-transform:uppercase;
  color:var(--magenta-brand); border-bottom:1.5px solid var(--magenta-brand); }

/* ---- the collection: editorial surfaces on paper ----
   The same grammar the Product uses elsewhere (GROUND / SURFACE / LIGHT /
   EDGE / MOTION), spoken in paper instead of in the dark canvas:

   GROUND   the paper the whole screen sits on stays continuous -- no entry
            is a box drawn ON the page, each is a lighter sheet lying on it.
   SURFACE  one tonal lift out of --editorial-paper itself (no second beige,
            no white box): #F9F4E8, about 2% lighter than the ground.
   LIGHT    one warm radial from the top-left corner, transparent long before
            any edge -- the room's light falling on a sheet, not a gradient
            fill.
   EDGE     a single lit top edge, the paper equivalent of the 1px highlight
            Start's objects carry. No outline, no drop shadow, no ring.
   MOTION   only in answer to touch: the sheet lightens, its edge brightens,
            the title goes to full ink, and a press settles it by 0.5%.

   Typography still leads: the surface is a quiet material under it, which is
   what keeps this an editorial page rather than four UI cards. */
.story-section-label{ font-family:var(--font-c); font-size:10.5px; font-weight:700; letter-spacing:0.2em;
  text-transform:uppercase; color:var(--muted); margin:0 0 14px; padding-top:26px;
  border-top:1px solid var(--hairline); }
.story-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:12px; }
.story-entry{ position:relative; isolation:isolate; padding:22px 20px 24px; border-radius:14px;
  background:
    radial-gradient(118% 92% at 0% 0%, rgba(255,251,240,0.9) 0%, rgba(255,251,240,0) 58%),
    var(--story-surface);
  box-shadow:inset 0 1px 0 rgba(255,255,255,0.72);
  transition:background-color 260ms var(--patient), box-shadow 260ms var(--patient),
    transform 200ms var(--patient); }
.story-entry-title{ margin:0; font-size:clamp(22px,5.8vw,27px); line-height:1.08; }
.story-entry-teaser{ font-family:var(--font-c); font-size:15px; line-height:1.55; color:var(--text-body);
  margin:11px 0 0; max-width:46ch; }
@media (hover:hover){
  /* the sheet answers the hand: tone, edge, ink -- no underline, no arrow,
     no second accent */
  .story-entry:hover{ background-color:var(--story-surface-lit);
    box-shadow:inset 0 1px 0 rgba(255,255,255,0.95); }
  .story-entry:hover .story-open{ color:#0F0D0B; }
  .story-feature:hover .story-feature-action{ background:var(--magenta-brand); color:var(--editorial-paper);
    padding-left:6px; padding-right:6px; }
}
.story-entry:has(.story-open:active){ transform:scale(0.995); }
.story-feature-action{ transition:background-color 160ms var(--patient), color 160ms var(--patient); }

.story-closing{ font-family:var(--font-c); font-size:13.5px; line-height:1.5; color:var(--muted);
  margin:48px 0 0; padding-top:22px; border-top:1px solid var(--hairline); }
.story-closing::before{ content:'— '; }

/* ---- Story Article: the Reader ----
   The Hub is discovery, the Article is reading, so the Article gets quieter
   rather than more decorated: the same paper, the same ink, the same
   .background-body at 16.5px/1.9 -- and none of the Hub's surfaces. Hierarchy
   comes from type, space, imagery and rhythm.

   The column is a three-track grid (margin · measure · margin) instead of a
   centred box: every block sits in the measure, and an image that wants more
   room simply takes the outer tracks. At 390 the outer tracks are zero, so
   the same rule produces a single column with no breakout and no overflow. */
.story-read{ display:grid; grid-template-columns:1fr min(640px, 100%) 1fr; max-width:1080px;
  margin:0 auto; padding-top:6px; }
.story-read > *{ grid-column:2; }

/* the opening: where you are, which Story, then reading begins */
.story-read-head{ margin:0 0 34px; }
.story-article-title{ font-size:clamp(30px,8.4vw,40px); line-height:1.02; margin:0; max-width:17ch;
  color:var(--text-primary); }
.story-lead{ font-family:var(--font-c); font-size:clamp(17.5px,4.6vw,19px); line-height:1.6;
  color:var(--text-primary); margin:20px 0 0; max-width:44ch; }

/* body rhythm -- scoped to the Reader so the legal and guideline screens,
   which share .background-body, keep the spacing they were designed with */
.story-read .background-body{ margin-bottom:28px; }
/* Consecutive paragraphs sit about half a line apart (line-height 1.9 x 16.5px
   = 31px; gap 28px - 14px = 14px), so a longform Story reads as one article
   while each paragraph stays plainly separate. The Reader is a grid, whose
   items do not collapse margins, so the tightening is applied only BETWEEN two
   paragraphs: the space before an H2, quote, image or the footer keeps its
   28px + own margin exactly as designed. */
.story-read .background-body + .background-body{ margin-top:-14px; }
.story-read .background-body:last-of-type{ margin-bottom:0; }

/* A section heading is a pause, not a new screen: a short editorial rule, a
   step up in scale, and space that binds it to the text it introduces. */
.story-h2{ font-size:clamp(18px,4.4vw,21px); line-height:1.16; margin:52px 0 18px;
  color:var(--text-primary); max-width:36ch; }
.story-h2::before{ content:''; display:block; width:34px; height:1px; margin:0 0 20px;
  background:var(--story-ink-line); }

/* A pull quote interrupts on purpose: scale and space carry it, so it needs
   no bar, no quotation marks, no card -- and no accent colour. */
.story-quote{ margin:46px 0; }
.story-quote p{ font-family:var(--font-c); font-style:italic; font-size:clamp(20px,5.2vw,24px);
  line-height:1.4; color:var(--text-primary); margin:0; max-width:30ch; }

.story-figure{ margin:38px 0; }
.story-figure--hero{ margin:0 0 38px; }
.story-fig-cap{ font-family:var(--font-c); font-size:12.5px; line-height:1.5; color:var(--muted);
  margin:10px 0 0; max-width:46ch; }

/* the continuation */
.story-foot{ margin-top:54px; padding-top:26px; border-top:1px solid var(--hairline); }
.story-next{ display:block; width:100%; padding:6px 0 4px; background:none; border:0; text-align:left;
  color:inherit; cursor:pointer; }
.story-next-k{ display:block; font-family:var(--font-c); font-size:10.5px; font-weight:700;
  letter-spacing:0.2em; text-transform:uppercase; color:var(--muted); margin-bottom:10px; }
.story-next-t{ display:block; font-family:var(--font-d); text-transform:uppercase;
  font-size:clamp(22px,5.6vw,26px); line-height:1.08; color:var(--text-primary);
  transition:color 140ms var(--patient); }
@media (hover:hover){ .story-next:hover .story-next-t{ color:#0F0D0B; } }
.story-next:focus-visible{ outline:2px solid var(--editorial-ink); outline-offset:6px; }
.story-end-back{ margin:0; }
/* STORY 05 ONLY -- one quiet transition into PL8 after the article. Generous
   space above so the article's last line still ends the story; type only (no
   card, no fill, no accent colour): a short headline, one line, one action. */
.story-pl8{ margin-top:88px; }
.story-pl8-h{ font-family:var(--font-d); font-size:clamp(20px,5vw,23px); line-height:1.15; color:var(--text-primary); margin:0; }
.story-pl8-p{ font-family:var(--font-c); font-size:15px; line-height:1.55; color:var(--text-body); margin:8px 0 0; }
.story-pl8-act{ display:inline-flex; align-items:center; min-height:44px; margin:10px 0 0; padding:0; background:none; border:0;
  border-bottom:1px solid var(--story-ink-line); font-family:var(--font-c); font-size:11px; font-weight:700; letter-spacing:.2em;
  text-transform:uppercase; color:var(--text-primary); cursor:pointer; }
.story-pl8-act:focus-visible{ outline:2px solid var(--editorial-ink); outline-offset:6px; }

/* ---- 1440: an editorial composition, not the mobile column stretched ---- */
@media (min-width:1024px){
  .story{ padding:36px 56px 88px; }
  /* statement | (photograph · featured Story). The outer split is deliberately
     uneven -- the statement is the quieter half -- and the photograph sits
     between the two headlines, carrying the composition. */
  /* The square is shorter than the upright frame it replaces, so the image
     column widens slightly to keep the photograph anchoring the composition.
     Proportions only -- the composition itself is unchanged. */
  .story-hero{ display:grid; grid-template-columns:minmax(0,0.85fr) minmax(0,2.37fr); column-gap:64px;
    align-items:center; padding-top:18px; margin-bottom:76px; }
  .story-mast{ padding:0; margin:0; }
  .story-eyebrow{ margin-bottom:16px; }
  .story-mast-title{ font-size:clamp(30px,2.6vw,38px); line-height:1.04; max-width:13ch; }
  .story-mast-intro{ margin:16px 0 0; font-size:16.5px; max-width:34ch; }
  .story-feature{ display:grid; grid-template-columns:minmax(0,1.2fr) minmax(0,1fr); column-gap:56px;
    align-items:center; margin:0; }
  .story-feature-media{ margin:0; }
  /* Related, not competing: the featured Story is the larger voice, the
     editorial statement the environment around it. */
  .story-feature-title{ font-size:clamp(36px,3.1vw,46px); }
  .story-feature-teaser{ font-size:17px; margin-top:15px; }
  .story-section-label{ margin-bottom:20px; }
  /* Two columns, but not a 2x2 grid: the right column steps down, so the four
     surfaces read as an editorial sequence rather than four aligned modules.
     Each sheet also stops short of its column, which keeps the paper ground
     visible between and around them. */
  .story-list{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); column-gap:44px; row-gap:40px; }
  .story-entry{ padding:30px 32px 34px; max-width:470px; }
  .story-entry:nth-child(2n){ margin-top:64px; }
  .story-entry:nth-child(2n+1){ justify-self:start; }
  .story-entry:nth-child(2n){ justify-self:end; }
  .story-entry-title{ font-size:clamp(26px,2.1vw,31px); }
  .story-entry-teaser{ font-size:15.5px; }
  .story-closing{ margin-top:64px; }
  /* Reading measure stays constrained -- the extra width becomes breathing
     room, an opening composition and room for imagery, never longer lines. */
  .story-read{ grid-template-columns:1fr min(640px, 100%) 1fr; padding-top:18px; }
  .story-read-head{ margin-bottom:44px; }
  .story-article-title{ font-size:clamp(36px,3.2vw,46px); }
  .story-lead{ margin-top:22px; font-size:19px; }
  .story-h2{ margin:60px 0 20px; }
  .story-quote{ margin:56px 0; }
  /* One editorial offset: the quote steps out of the measure into the left
     margin instead of being decorated. */
  .story-quote{ grid-column:1 / 3; justify-self:end; width:min(720px, 100%); }
  .story-quote p{ max-width:26ch; }
  /* Imagery may take the outer tracks; text never does. */
  .story-figure--hero, .story-figure--wide{ grid-column:1 / -1; justify-self:center;
    width:min(820px, 100%); }
  .story-foot{ margin-top:64px; }
}

/* ---- motion: arrival only, never ambient ---- */
@keyframes story-rise{ from{ opacity:0; transform:translateY(9px); } to{ opacity:1; transform:none; } }
@keyframes story-reveal{ from{ opacity:0; transform:scale(1.012); } to{ opacity:1; transform:none; } }
.story-hub .story-eyebrow{ animation:story-rise 460ms var(--patient) 40ms both; }
.story-hub .story-mast-title{ animation:story-rise 520ms var(--patient) 100ms both; }
.story-hub .story-mast-intro{ animation:story-rise 520ms var(--patient) 160ms both; }
.story-hub .story-feature-media{ animation:story-reveal 700ms var(--patient) 200ms both; }
/* The Reader settles once, at the opening only, and is then still: nothing
   animates while reading, and no image or paragraph waits for a scroll. */
.story-article .story-read-head > *{ animation:story-rise 420ms var(--patient) both; }
.story-article .story-label{ animation-delay:60ms; }
.story-article .story-article-title{ animation-delay:110ms; }
.story-article .story-lead{ animation-delay:160ms; }
@media (prefers-reduced-motion: reduce){
  .story-hub .story-eyebrow,
  .story-hub .story-mast-title,
  .story-hub .story-mast-intro,
  .story-hub .story-feature-media,
  .story-article .story-read-head > *{ animation:none; }
  /* The surfaces keep their material (tone, light, edge) -- only the movement
     goes: no transition, no press travel. Hierarchy never depended on it. */
  .story-open, .story-feature-action, .story-entry{ transition:none; }
  .story-entry:has(.story-open:active){ transform:none; }
}


/* PACE (bodytype) */
.bt-grid{ display:grid; grid-template-columns:1fr; gap:14px; }
.bt-card{ background:var(--surface); border:1px solid var(--hairline); border-radius:var(--radius); padding:var(--s5); text-align:left; cursor:pointer; }
.bt-card:hover{ border-color:var(--border-strong); }
.bt-card.selected{ border-color:var(--magenta); background:var(--surface-premium); }
.bt-card h3{ font-size:19px; margin:6px 0 8px; }
.bt-card p{ font-size:14px; color:var(--text-body); line-height:1.55; }
.bt-detail-back{ background:none; border:none; color:var(--muted); font-size:13px; cursor:pointer; margin-bottom:var(--s4); padding:6px 0; }
.bt-trait{ margin-bottom:var(--s5); }
/* Sprint 18.2 -- trait labels are ordinary informational text (Mindset,
   Stärken, Herausforderungen), not an action -- moved off magenta onto the
   same quiet kicker treatment used everywhere else. */
.bt-trait h4{ font-size:13px; color:var(--muted); letter-spacing:0.14em; text-transform:uppercase; margin-bottom:6px; }
.bt-trait p{ font-size:14.5px; line-height:1.6; color:var(--text-body); }

/* premium */
.pl8-hero{ text-align:center; padding:var(--s6) 0; }
.pl8-hero h1{ font-size:clamp(28px,6vw,42px); margin:10px 0; }
.pl8-feature-list{ display:grid; grid-template-columns:1fr; gap:12px; margin:var(--s6) 0; text-align:left; }
.pl8-feature{ display:flex; gap:12px; align-items:flex-start; }
.pl8-feature svg{ flex-shrink:0; width:20px; height:20px; color:var(--magenta); margin-top:2px; }
.pl8-feature h4{ font-size:14.5px; margin-bottom:4px; }
.pl8-feature p{ font-size:13.5px; color:var(--text-body); line-height:1.5; }
.pl8-price-card{ background:var(--surface-premium); border:1px solid var(--border-premium); border-radius:var(--radius); padding:var(--s6); text-align:center; }

.ai-tabs{ display:flex; gap:8px; margin-bottom:var(--s5); border-bottom:1px solid var(--hairline); }
.ai-tabs button{ background:none; border:none; color:var(--muted); font-size:13.5px; font-weight:600; padding:10px 4px; margin-right:16px; cursor:pointer; border-bottom:2px solid transparent; }
.ai-tabs button.active{ color:var(--text-primary); border-bottom-color:var(--magenta); }
.ai-callout{ background:var(--surface-premium); border:1px solid var(--border-premium); border-radius:var(--radius); padding:var(--s5); margin-bottom:var(--s5); }
.ai-callout p{ font-size:13.5px; color:var(--text-body); line-height:1.55; }
.shopping-list{ list-style:none; margin:0; padding:0; }
.shopping-list li{ display:flex; align-items:center; gap:10px; padding:9px 0; border-bottom:1px solid var(--hairline); font-size:14px; }
.shopping-list input{ width:16px; height:16px; }
.mealplan-day{ display:flex; justify-content:space-between; padding:12px 0; border-bottom:1px solid var(--hairline); font-size:14px; }
.mealplan-day .d{ color:var(--text-secondary); width:80px; flex-shrink:0; }

/* account */
.account-row{ display:flex; justify-content:space-between; align-items:center; padding:12px 0; border-bottom:1px solid var(--hairline); font-size:14.5px; gap:var(--s4); }
.account-row .v{ color:var(--text-secondary); font-size:13.5px; }
/* This specific row's value is now a full situation sentence rather than a
   short type name (Legacy Personalization Decoupling, Phase 2) -- let it
   wrap and right-align instead of forcing a single line. A dedicated class
   keeps this scoped to that one row; every other .account-row (short
   values like "Mitgliedschaft") keeps the default single-line vertical
   centering untouched. */
.account-row-wrap{ align-items:flex-start; }
.account-row-wrap .v{ text-align:right; max-width:60%; line-height:1.45; }

/* ---------------------------------------------------------------------- *
 * ACCOUNT PERSONALIZATION -- Slice 1
 *
 * The personal state surface. Deliberately NOT a card in the conventional
 * sense: no fill, no border, no radius, no shadow. A near-black tonal lift
 * that emerges from the page ground, closed by a single hairline. Reuses
 * existing tokens throughout -- no new colour is introduced by this feature.
 * ---------------------------------------------------------------------- */
/* ONE GRID. The card previously bled ±24px past every other divider via
   negative margins, so its bottom hairline sat 48px wider than the receipt's
   and the section rules -- measured 146→1294 against 170→1270. It now shares
   the content column exactly, so every line on the screen starts and ends on
   the same two edges. */
/* VISUAL PASS -- the identity plate no longer ENDS, it DISSOLVES. The ground
   moved onto its own layer so that layer can be masked away toward the bottom,
   which lets the closing hairline go entirely: the surface fades into the
   canvas instead of being ruled off it. One less horizontal band, and the
   difference between a composed plate and a boxed card. */
/* overflow:hidden is gone with the crop it used to disguise: the Signal is a
   complete symbol sitting inside the plate now, so nothing needs clipping --
   and clipping is what forced the ground to stop on a hard vertical edge. */
.acct-card{ position:relative;
  margin:0 0 var(--s7);
  padding:var(--s6) 0 var(--s8); }
/* NO RECTANGLE. Two soft radials that are already transparent at every edge,
   so the plate has no border, no corner and nothing to read as a container --
   it is light in a room, not a card on a page. The magenta bloom is anchored
   where the Signal actually sits, which is what makes the mark look like the
   source of it rather than an object placed on top of it. */
.acct-card::after{ content:''; position:absolute; inset:0 -16px; z-index:0;
  pointer-events:none;
  background:
    radial-gradient(84% 52% at 74% 76%, rgba(226,25,181,0.15) 0%, rgba(226,25,181,0) 70%),
    radial-gradient(78% 46% at 34% 22%, rgba(214,198,226,0.055) 0%, rgba(214,198,226,0) 72%);
  /* Fades at top and bottom so the light has no beginning and no end. With
     the horizontal bleed above, the plate meets the canvas on all four sides
     without a single edge -- there is no rectangle left to read as a card. */
  -webkit-mask-image:linear-gradient(180deg, transparent 0%, #000 16%, #000 76%, transparent 100%);
  mask-image:linear-gradient(180deg, transparent 0%, #000 16%, #000 76%, transparent 100%); }
.acct-card > *{ position:relative; z-index:2; }
/* MEASURE. The approved pitch was composed at ~390px. Applied to a 1100px
   container the type sprawled and the tonal ground spread to invisibility,
   which is what read as "flat". Constraining the copy restores the pitch's
   proportions on every viewport and leaves the right of the card as
   deliberate negative space for the Signal to occupy. */
.acct-card-inner{ position:relative; z-index:2; max-width:34rem; }

/* THE SIGNAL -- membership, never identity (Design Constitution §1.2,
   hierarchy rule LOCKED). One instance, cropped, bleeding off the right edge,
   radial-masked so it fades at its own edges, behind all content, and never
   in a CTA's visual field. Arrives AFTER the content, which is what keeps it
   atmospheric rather than structural. */
/* THE SIGNAL. Previously 45% of it sat outside the card and was destroyed by
   overflow:hidden at the far edge of an 1100px container -- it read as
   accidentally clipped, which is exactly what it was. Now sized to the card's
   own height and placed so roughly three quarters of the arc is on-surface:
   a composed fragment that leaves the frame on purpose (§5.1), occupying the
   negative space the measure constraint opens up on its right. Opacity stays
   at the specified .115. */
/* THE SIGNAL, CORRECTED (Founder direction 5). It was 420px and deliberately
   bled off the right edge; even at 86% on-surface it read as a symbol that
   had been chopped rather than one that had been placed. The direction is
   now the opposite: a COMPLETE symbol living quietly in the environment.
   Reduced to 172-200px, entirely inside the card with real margin around it,
   and the edge mask is gone -- there is no crop left to disguise. Opacity
   rises from .115 to .17 because a smaller mark at the old value would have
   disappeared; it is still far below the name, the anchor and the Focus row,
   so it gains readability without gaining rank. */
/* VISUAL PASS -- the Signal stops being something parked in leftover space
   and becomes the plate's atmospheric anchor: larger, and now EMITTING. The
   bloom behind it is what turns the right of the composition from empty into
   inhabited. It gains presence, never rank -- still the dimmest object on the
   surface, still complete and uncropped, still never an avatar. */
.acct-sig{ position:absolute; z-index:1; pointer-events:none;
  /* Centred via calc, not transform, so the entrance keyframe owns transform
     outright and the mobile override can reposition without fighting it. */
  width:clamp(190px, 19vw, 268px); height:clamp(190px, 19vw, 268px);
  top:calc(50% - clamp(95px, 9.5vw, 134px)); right:clamp(20px, 5vw, 84px);
  opacity:0; color:var(--magenta);
  animation:acctSigIn 1900ms var(--patient) 350ms forwards; }
/* A close halo, not a wide wash: the plate's own ground carries the broad
   atmosphere. Measured deliberately -- at -58% the halo reached 155px past
   the mark and pushed the document 29px wider than the viewport at 1440. */
.acct-sig::before{ content:''; position:absolute; inset:-30%; border-radius:50%;
  background:radial-gradient(closest-side,
    rgba(226,25,181,0.22) 0%, rgba(226,25,181,0.08) 44%, rgba(226,25,181,0) 78%);
  transform:translate3d(var(--sig-px,0px),
    calc(var(--sig-y,0px) * 0.5 + var(--sig-py,0px)), 0); }
/* When GSAP drives the arrival it owns opacity outright. The CSS keyframe is
   the fallback path only -- and parking the mark at rest here also fixes a
   real defect of that keyframe: innerHTML is rebuilt on every state change,
   so the Signal re-faded in each time a member touched a Focus word. */
.acct.acct-gsap .acct-sig{ animation:none; opacity:.17; }
/* DEPTH DRIFT. Two elements, two owners, deliberately: the wrapper's entrance
   keyframe owns transform outright, so the scroll-linked drift lives on the
   svg inside it and the two can never fight. JS writes only --sig-y (see
   initAccountMotion); this rule is the sole place a transform is composed.
   At rest --sig-y is 0px, so the approved composition is exactly what a
   member sees before they scroll. */
/* Three inputs, ONE composed transform, and JS writes none of it directly:
   --sig-y is the scroll-linked depth drift, --sig-px/--sig-py the pointer
   lean. Composing them here is what keeps the wrapper's transform free for
   the arrival, so the entrance and the responses can never fight. */
.acct-sig svg{ width:100%; height:100%; display:block;
  transform:translate3d(var(--sig-px, 0px),
    calc(var(--sig-y, 0px) + var(--sig-py, 0px)), 0); }
@keyframes acctSigIn{
  from{ opacity:0; transform:translate(12px,-6px) scale(.965); }
  to{ opacity:.17; transform:none; } }

/* VISUAL PASS -- editorial scale. The member's name is now unambiguously the
   largest thing on the screen; every label around it was pulled down a step
   so the contrast is carried by TYPE, before any line or container. */
.acct-name{ font-family:var(--font-d); font-weight:500;
  font-size:clamp(40px, 8.4vw, 64px); line-height:0.98;
  letter-spacing:0.004em; color:var(--text-primary); margin:0 0 12px;
  min-height:clamp(40px, 8.4vw, 64px); }
.acct-since{ font-size:10.5px; letter-spacing:0.185em; text-transform:uppercase;
  color:var(--muted); font-weight:500; margin:0 0 var(--s6);
  display:flex; align-items:center; gap:var(--s3); flex-wrap:wrap; }
/* PRODUCT-TRUTH CLOSEOUT -- account-identity metadata, deliberately NOT
   .acct-since's own uppercase/letter-spaced treatment: an email reads as
   noise once spaced out and its case is meaningful, unlike a member-since
   date. Same quiet weight and colour, normal case, its own line.
   :has() tightens the gap above ONLY when an email line actually follows,
   so a member with no email (session genuinely missing it) sees the exact
   original spacing before "Deine Woche" -- nothing shifts for that case.
   break-all is the deliberate overflow guard for a long, unbroken address on
   a narrow viewport. */
.acct-since:has(+ .acct-email){ margin-bottom:var(--s3); }
.acct-email{ font-size:12px; letter-spacing:0.01em; color:var(--muted);
  font-weight:500; margin:0 0 var(--s6); word-break:break-all; }
.acct-lbl{ font-size:10px; letter-spacing:0.2em; text-transform:uppercase;
  color:var(--muted); font-weight:600; margin:0 0 9px; }
.acct-anchor{ font-family:var(--font-d); font-weight:400;
  font-size:clamp(22px, 4.6vw, 29px); line-height:1.22;
  letter-spacing:0.002em; color:var(--text-primary); margin:0 0 4px; max-width:14em; }
.acct-anchor-empty{ color:var(--text-secondary); }
/* 44px hit area without 44px of visual weight: the negative block margin
   absorbs the extra height so spacing reads unchanged, while the tappable
   box is full size. Measured at 27px before this. */
.acct-edit{ background:none; border:none; padding:0; color:var(--muted);
  font-family:var(--font-c); font-size:12.5px; cursor:pointer;
  text-decoration:underline; text-underline-offset:3px; letter-spacing:normal;
  text-transform:none;
  display:inline-flex; align-items:center; min-height:44px; margin:-9px 0; }
.acct-edit:hover{ color:var(--text-secondary); }
/* .acct-split (the in-card horizontal rule) was removed by the visual pass:
   the identity plate is separated by space now, not by bands. */
/* CAUSE <-> EFFECT. Pointing at the adaptation that this sentence produces
   lights the sentence up, and vice versa. The cue is a hairline GROWING out
   of the label -- deliberately neutral, never magenta: on this screen magenta
   means "CRVD is adapting this", and a hover state is not that. The plate
   clips at its own edge, so the cue lives inside the type rather than in a
   margin that would be cut off. */
.acct-beat-situation{ position:relative; margin-bottom:var(--s7); }
.acct-beat-situation .acct-lbl,
.acct-domains-lead{ transition:color 300ms var(--patient); }
.acct-beat-situation .acct-lbl::after{ content:''; display:inline-block;
  vertical-align:middle; width:0; height:1px; margin-left:0;
  background:var(--border-strong);
  transition:width 320ms var(--patient), margin-left 320ms var(--patient); }
.acct-beat-situation.is-linked .acct-lbl{ color:var(--text-secondary); }
.acct-beat-situation.is-linked .acct-lbl::after{ width:26px; margin-left:10px; }
/* RECOGNITION. One beat, once, when the member has just changed their week:
   the sentence they rewrote and the adaptation it produces acknowledge each
   other. A one-shot lift, never a loop. */
.acct-beat-situation.is-recognised .acct-anchor{ animation:acctRecognise 1100ms var(--patient); }
@keyframes acctRecognise{
  0%{ opacity:1; } 22%{ opacity:.55; } 60%{ opacity:1; } 100%{ opacity:1; } }

/* FOCUS -- an editorial word-selector, not chips. The active option steps up
   in size and weight and grows a magenta underline that wipes in from the
   left. No box, no fill: boxes would make five peers look like five buttons,
   which is what the previous chip treatment got wrong. */
.focus-sel{ display:flex; flex-wrap:wrap; gap:5px var(--s5); align-items:baseline; }
.focus-word{ position:relative; background:none; border:none; cursor:pointer;
  padding:10px 0 11px; font-family:var(--font-d); font-weight:400; font-size:15px;
  letter-spacing:0.05em; text-transform:uppercase; color:#6E6670;
  transition:color 300ms var(--patient), font-size 300ms var(--patient); }
.focus-word::after{ content:''; position:absolute; left:0; right:100%; bottom:3px;
  height:1px; background:var(--magenta); transition:right 340ms var(--patient); }
.focus-word:hover{ color:var(--text-secondary); }
/* TACTILE. The press is acknowledged in 60ms -- before the write leaves the
   browser -- so the control feels precise rather than laggy, while the word
   still only *steps forward* once the state is real. Nothing moves, nothing
   scales: the acknowledgement is tonal, which is what keeps five peers from
   reading as five buttons. */
.focus-word:active{ color:var(--text-primary); transition-duration:60ms; }
.focus-word.is-active{ color:var(--text-primary); font-weight:500; font-size:19px; }
.focus-word.is-active::after{ right:0; }
/* min-height reserves one wrapped line so switching Focus never shifts the
   layout, but 2.4em left an obvious dead band under a one-line description. */
.focus-desc{ font-size:13px; line-height:1.5; color:var(--text-secondary);
  margin:var(--s3) 0 0; min-height:1.6em; }
/* Local Focus confirmation. Deliberately NOT an adaptation dot: Focus has no
   active consumer, so it may not use that semantic
   (PERSONALIZATION_CONTEXT_CONTRACT v1.1, Invariant 10). */
.focus-ok{ font-size:11.5px; letter-spacing:0.14em; text-transform:uppercase;
  color:var(--muted); font-weight:600; margin:var(--s2) 0 0;
  opacity:0; transition:opacity 400ms var(--patient); }
.focus-ok.is-on{ opacity:1; }

/* ADAPTATION RECEIPT. The dot means exactly one thing: CRVD is actively
   adapting this. Never a bullet, never decoration, never a status indicator.
   This is the payoff of the whole composition, so it carries the largest body
   type on the screen and the most air around it -- weight bought with type and
   space, not with a container. It stays one truthful line; nothing here scales
   with how full the section looks. */
.acct-receipt{ padding:var(--s2) 0 var(--s6); margin-bottom:var(--s6);
  border-bottom:1px solid var(--hairline); }
.acct-receipt .acct-lbl{ margin-bottom:var(--s4); }
.acct-receipt ul{ margin:0; padding:0; list-style:none; max-width:34rem; }
.acct-receipt li{ display:grid; grid-template-columns:auto 1fr; gap:14px;
  align-items:start; padding:2px 0; font-size:15px; line-height:1.58;
  color:var(--text-primary); }
/* The halo is the dot's own meaning made legible -- "actively adapting" -- not
   decoration bolted onto it, and it is deliberately static: a pulse would turn
   a semantic mark into a status light. */
.adapt-dot{ width:5px; height:5px; border-radius:50%; background:var(--magenta);
  margin-top:9px; flex:none; box-shadow:0 0 7px 1px rgba(226,25,181,0.34); }

/* ---------------------------------------------------------------------- *
 * ESSEN & KOCHEN (Slice 2) -- the deeper-personalization layer.
 *
 * Instruments, not form rows. The whole surface uses exactly three devices
 * already established on this screen: the .acct-lbl eyebrow, the editorial
 * word-selector borrowed from Focus, and open space. No control gets a box,
 * because a box around each of three would make them read as three settings
 * rather than one instrument panel. Existing tokens only; magenta appears on
 * exactly one thing at a time -- the current selection.
 * ---------------------------------------------------------------------- */
/* ENTRY POINT on Account. Full-width so it is unmistakably tappable, but
   ruled rather than boxed so it belongs to the same grid as every divider
   above it. The name carries the weight; the values underneath are the
   payload, which is why they are shown rather than a chevron. */
/* The entry point is a READOUT of the instruments behind it, not a nav row.
   Three miniature rails, each showing where that member's answer actually
   sits -- so Account carries the instrument language rather than another
   headline/copy/divider stanza, and the link states its own contents. An
   undeclared instrument shows its rail with no blade, which reads as an
   empty gauge: honest, and an invitation. */
.kitchen-entry{ display:grid; grid-template-columns:1fr auto; align-items:center;
  gap:var(--s5) var(--s7); width:100%; text-align:left; background:none;
  border:none; border-bottom:1px solid var(--hairline); cursor:pointer;
  padding:var(--s2) 0 var(--s6); margin:0 0 var(--s6); color:inherit;
  font-family:inherit; }
.kitchen-entry .acct-lbl{ display:block; grid-column:1; margin-bottom:6px; }
.kitchen-entry-text{ grid-column:1; grid-row:2; }
.kitchen-entry-name{ display:block; font-family:var(--font-d); font-weight:500;
  font-size:23px; line-height:1.2; letter-spacing:0.004em; color:var(--text-primary);
  transition:color 260ms var(--patient); }
.kitchen-entry-sub{ display:block; margin-top:7px; font-size:13.5px;
  line-height:1.5; color:var(--text-secondary); }
/* A factual body reading on the overview. Typeset, not tiled: display type
   and tabular figures give the number enough presence to be read at a
   glance, while it stays inside the domain's existing subline and never
   becomes a stat card. Quieter than the detail screen by design -- there
   the value is the protagonist, here it is a statement of where things
   stand. */
.acct-stat{ display:inline-flex; align-items:baseline; gap:4px;
  font-family:var(--font-d); font-weight:500; font-size:19px; line-height:1.2;
  color:var(--text-primary); font-variant-numeric:tabular-nums;
  margin-right:var(--s3); }
.acct-stat-u{ font-family:var(--font-c); font-size:10.5px; font-weight:500;
  letter-spacing:0.16em; text-transform:uppercase; color:var(--muted); }
.acct-stat-meta{ font-size:13px; color:var(--text-secondary); }
.kitchen-entry:hover .kitchen-entry-name{ color:var(--magenta); }

.kitchen-mini-row{ position:relative; height:9px; }
.kitchen-mini-row::before{ content:''; position:absolute; left:0; right:0; top:4px;
  height:1px; background:var(--hairline); }
.kitchen-mini-b{ position:absolute; top:0; width:2px; height:9px; margin-left:-1px;
  background:var(--magenta); border-radius:1px;
  box-shadow:0 0 6px 0 rgba(226,25,181,0.45);
  transition:left 420ms var(--patient); }
.kitchen-entry:hover .kitchen-mini-row::before{ background:var(--text-disabled); }

@media (max-width:640px){
  .kitchen-entry{ gap:var(--s4) var(--s5); }
}

.essen-title{ font-family:var(--font-d); font-weight:500; font-size:29px;
  line-height:1.14; letter-spacing:0.008em; color:var(--text-primary);
  margin:var(--s2) 0 var(--s3); max-width:15em; }
.essen-lede{ font-size:14px; line-height:1.6; color:var(--text-secondary);
  margin:0 0 var(--s8); max-width:34rem; }
.essen-loading{ font-size:14px; color:var(--muted); margin-top:var(--s7); }

/* One rule between instruments, none above the first and none below the
   last: the separation is the space, the rule only confirms it. */
.kitchen-inst{ padding:0 0 var(--s7); margin:0 0 var(--s7);
  border-bottom:1px solid var(--hairline); max-width:34rem; }
.kitchen-inst:last-of-type{ border-bottom:none; padding-bottom:var(--s5); }

/* ====================================================================== *
 * CRVD PERSONALIZATION INSTRUMENTS -- THE CALIBRATED RAIL
 *
 * One grammar, three instances. Every personalization instrument in the
 * product is built from the same four parts, in the same order:
 *
 *   READOUT   the answer, in display type, large. Rolls behind a mask when
 *             it changes -- the value is replaced, never cross-faded.
 *   RAIL      a hairline track sitting in a shallow trough. The trough is
 *             one inset highlight and one shadow, not a box: depth without
 *             a container.
 *   TICKS     calibration marks rising from the rail. Their SPACING CARRIES
 *             MEANING -- on the time instrument they sit at their true
 *             position on a 0-60 minute scale, so the rail is a measurement
 *             scale rather than four equidistant words.
 *   BLADE     a magenta indicator that travels to the active tick and
 *             settles there.
 *
 * MAGENTA, deliberately: the blade, the active tick and the bloom are the
 * CRVD identity colour used as a precision indicator (Founder direction 7).
 * They are never dots. The magenta DOT keeps its single meaning -- CRVD is
 * actively adapting this -- and lives only in the adaptation receipt. Two
 * different forms, two different places, no collision.
 *
 * Adding a fourth instrument later means choosing points and a readout.
 * It does not mean designing a new control.
 * ====================================================================== */
.kitchen-inst-head{ display:flex; align-items:flex-end;
  justify-content:space-between; gap:var(--s4); margin:0 0 var(--s5); }

/* READOUT -- masked. The box is fixed height with overflow hidden, so the
   outgoing value leaves upward while the incoming one arrives from below:
   a value is replaced, not faded. tabular-nums so 8 and 1 occupy identical
   width and nothing beside the readout can shift. */
.kitchen-read{ position:relative; overflow:hidden; height:46px; min-width:2.2em;
  display:block; }
.kitchen-read-v{ display:block; font-family:var(--font-d); font-weight:500;
  font-size:40px; line-height:46px; letter-spacing:0.01em;
  color:var(--text-primary); font-variant-numeric:tabular-nums;
  white-space:nowrap; }
.kitchen-read.is-unset .kitchen-read-v{ color:var(--text-disabled); font-weight:400; }
/* The word readouts (leftovers) carry the same weight as the numerals but
   cannot use their scale without wrapping. */
.kitchen-read.is-word{ min-width:0; }
.kitchen-read.is-word .kitchen-read-v{ font-size:26px; }
/* margin-right:auto, not space-between across three children: the unit
   belongs to the numeral it qualifies and must sit against it. Left to
   space-between it floated into the middle of the row, reading as a third
   independent element. */
.kitchen-unit{ font-family:var(--font-c); font-size:12px; font-weight:500;
  letter-spacing:0.16em; text-transform:uppercase; color:var(--muted);
  padding-bottom:9px; white-space:nowrap; margin-right:auto; margin-left:-6px; }

/* RAIL + TROUGH. 44px tall so the whole track is a comfortable touch strip,
   with the line itself optically centred inside it. */
/* Inset by half a tick box. A tick at left:0% / left:100% carries a 44px hit
   column centred on the mark, so at the raw edges those columns hung 22px
   past the content and pushed the document 15px wider than the viewport --
   measured 405 against 390. Insetting the rail by 22px puts the outermost
   hit columns flush with the content edge instead of past it, and the scale
   reads as deliberately inset rather than running off the page. */
/* touch-action:pan-y is the whole scroll-intent solution, and it is
   declarative rather than guessed: the browser keeps VERTICAL panning for
   itself, so the page still scrolls normally with a finger on the rail,
   and hands us every HORIZONTAL gesture without our ever calling
   preventDefault on a scroll. No movement thresholds, no direction
   sniffing, no scroll-blocking listener. */
.kitchen-rail{ position:relative; height:52px; margin:0 22px var(--s4);
  touch-action:pan-y; cursor:grab; }
.kitchen-rail.is-held{ cursor:grabbing; }
/* The track brightens under the hand -- the instrument acknowledges being
   held before the value has changed at all. */
.kitchen-rail.is-held::before{ background:var(--border-default); }
.kitchen-rail:hover::before{ background:var(--text-disabled); }
.kitchen-rail::before{ content:''; position:absolute; left:0; right:0; top:25px;
  height:1px; background:var(--hairline);
  transition:background-color 200ms var(--patient); }
/* The trough: one hairline of light above the track and a soft shadow below
   it. Barely visible on its own -- what it does is make the blade look like
   it is sitting IN something. */
.kitchen-rail::after{ content:''; position:absolute; left:0; right:0; top:26px;
  height:6px; pointer-events:none;
  background:linear-gradient(180deg, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0) 100%); }

/* TICKS. Each is a real button -- the rail is operable, not a readout with
   decoration. The visible mark is the ::after; the button itself is an
   invisible column wide enough to hit comfortably. */
/* pointer-events:none is deliberate and load-bearing. The rail owns every
   pointer gesture, so a tap and a drag are the same code path and can never
   both fire for one touch (which would have written twice). The buttons stay
   in the DOM, focusable and clickable BY KEYBOARD -- Enter/Space dispatches
   a click without any pointer event -- so the calibration marks remain the
   accessible, labelled, individually reachable control they were. */
.kitchen-tick{ position:absolute; top:0; height:52px; width:44px;
  margin-left:-22px; padding:0; border:none; background:none;
  display:block; pointer-events:none; }
.kitchen-tick::after{ content:''; position:absolute; left:50%; width:1px;
  margin-left:-0.5px; top:19px; height:7px; background:var(--text-disabled);
  transition:height 260ms var(--patient), top 260ms var(--patient),
             background-color 260ms var(--patient); }
.kitchen-tick.is-major::after{ top:16px; height:10px; background:var(--muted); }
.kitchen-rail:hover .kitchen-tick::after{ background:var(--text-secondary); }
.kitchen-tick.is-on::after{ top:13px; height:16px; background:var(--magenta); }
/* Under the hand every mark lifts slightly -- the whole scale responds, not
   just the one being aimed at. */
.kitchen-rail.is-held .kitchen-tick::after{ top:17px; height:11px; }
.kitchen-rail.is-held .kitchen-tick.is-on::after{ top:13px; height:16px; }
.kitchen-tick:focus-visible::after{ background:var(--magenta); top:13px; height:16px; }
/* Minor calibration marks between the labelled points -- texture that makes
   the rail read as a measured scale. Not operable, not tab stops. */
.kitchen-minor{ position:absolute; top:23px; width:1px; height:4px;
  background:var(--hairline); pointer-events:none; }

/* BLADE. A bar, never a dot -- the dot is spoken for. Sits astride the rail
   with a soft bloom beneath it so the active point has energy without
   anything glowing permanently. */
/* THE HANDLE. Still a blade, never a circular thumb -- but now unmistakably
   a thing to take hold of: 3px wide and 34px tall it stands clear of the
   16px marks, and it carries its own bloom so it reads as sitting ABOVE the
   scale rather than drawn onto it. The ::before is a 44px invisible grab
   column, which is what makes it catchable without making it look heavy. */
.kitchen-blade{ position:absolute; top:9px; left:0; width:3px; height:34px;
  margin-left:-1.5px; background:var(--magenta); pointer-events:none;
  border-radius:1.5px;
  box-shadow:0 0 14px 2px rgba(226,25,181,0.55), 0 0 3px 0 rgba(226,25,181,0.9);
  transition:height 180ms var(--patient), top 180ms var(--patient),
             width 180ms var(--patient), box-shadow 180ms var(--patient); }
.kitchen-blade::before{ content:''; position:absolute; top:-9px; bottom:-9px;
  left:50%; width:44px; margin-left:-22px; }
/* A soft disc of light behind the handle: local depth, so the blade looks
   lit rather than painted. Grows when held. */
.kitchen-blade::after{ content:''; position:absolute; left:50%; top:50%;
  width:34px; height:34px; margin:-17px 0 0 -17px; border-radius:50%;
  background:radial-gradient(circle, rgba(226,25,181,0.28) 0%, rgba(226,25,181,0) 70%);
  transition:transform 200ms var(--patient), opacity 200ms var(--patient); }
.kitchen-rail:hover .kitchen-blade{ height:38px; top:7px; }
.kitchen-rail.is-held .kitchen-blade{ width:4px; margin-left:-2px; height:42px; top:5px;
  box-shadow:0 0 20px 3px rgba(226,25,181,0.65), 0 0 4px 0 rgba(226,25,181,1);
  transition-duration:90ms; }
.kitchen-rail.is-held .kitchen-blade::after{ transform:scale(1.5); }
.kitchen-blade.is-hidden{ opacity:0; }

.kitchen-labels{ position:relative; height:15px; margin:0 22px var(--s4); }
.kitchen-lab{ position:absolute; transform:translateX(-50%);
  font-family:var(--font-d); font-weight:400; font-size:12.5px;
  letter-spacing:0.04em; color:var(--text-disabled); white-space:nowrap;
  transition:color 260ms var(--patient); }
/* The end labels align to the rail's ends rather than centring on them.
   Centring is fine for a digit, but "Lieber frisch" is 78px wide and half of
   it hung off the page. Aligning the outer edges also reads more precisely:
   the scale is bounded by its labels. */
.kitchen-lab:first-child{ transform:none; }
.kitchen-lab:last-child{ transform:translateX(-100%); }
.kitchen-lab.is-on{ color:var(--text-primary); font-weight:500; }

/* STEPPER -- kept alongside the rail on portions only, as the precise and
   unambiguous path (and the obvious keyboard one). Secondary to the rail by
   size and weight: the rail is the instrument, these adjust it. */
.kitchen-step{ display:flex; align-items:center; gap:var(--s3); }
.kitchen-step-btn{ width:44px; height:44px; flex:none; border-radius:50%;
  border:1px solid var(--hairline); background:none; color:var(--text-secondary);
  font-size:18px; line-height:1; cursor:pointer; display:flex;
  align-items:center; justify-content:center;
  transition:border-color 200ms var(--patient), color 200ms var(--patient),
             background-color 200ms var(--patient); }
.kitchen-step-btn:hover:not(:disabled){ color:var(--text-primary); border-color:var(--border-strong); }
.kitchen-step-btn:active:not(:disabled){ background:rgba(226,25,181,0.12);
  border-color:var(--magenta); color:var(--text-primary); transition-duration:70ms; }
.kitchen-step-btn:disabled{ opacity:.28; cursor:default; }

.kitchen-hint{ font-size:13px; line-height:1.5; color:var(--text-secondary);
  margin:0; min-height:1.5em; }
/* Reserved height, always present, so a confirmation or an error appearing
   never moves the instrument below it. */
.kitchen-msg{ font-size:11.5px; letter-spacing:0.14em; text-transform:uppercase;
  font-weight:600; margin:var(--s3) 0 0; min-height:1.2em;
  opacity:0; transition:opacity 400ms var(--patient); }
.kitchen-msg.is-ok{ opacity:1; color:var(--muted); }
.kitchen-msg.is-error{ opacity:1; color:var(--magenta); letter-spacing:0.04em;
  text-transform:none; font-size:12.5px; font-weight:400; }

.essen-foot{ font-size:12.5px; line-height:1.6; color:var(--muted);
  margin:var(--s7) 0 0; max-width:32rem; }

/* .bt-detail-back measures 43x73 here -- 1px under the touch minimum, and it
   wraps because the icon and label are inline in a narrow column. Scoped to
   this screen rather than corrected on the shared class: .bt-detail-back is
   also used by Recipe Detail and the Guidelines, and this slice is not the
   place to change how those look. */
.essen .bt-detail-back, .wt .bt-detail-back{ display:inline-flex; align-items:center; gap:6px;
  min-height:44px; padding:0; margin-bottom:var(--s5); }
.essen .bt-detail-back svg, .wt .bt-detail-back svg{ flex:none; }

@media (max-width:640px){
  /* The reserved confirmation slot plus a 48px rule gap left ~75px of dead
     space under every instrument on a 390px screen. The instruments keep
     their separation; the surplus goes. */
  .kitchen-inst{ padding-bottom:var(--s5); margin-bottom:var(--s5); }
  .essen-lede{ margin-bottom:var(--s7); }
  .essen-title{ font-size:26px; }
  .kitchen-read{ height:42px; }
  .kitchen-read-v{ font-size:34px; line-height:42px; }
  .kitchen-read.is-word .kitchen-read-v{ font-size:22px; }
}

@media (min-width:1024px){
  /* Desktop gets breathing room, not a second column: three instruments in
     two columns would read as a settings grid, which is the one thing this
     surface must not be. */
  .essen-title{ font-size:34px; }
  .kitchen-inst{ padding-bottom:var(--s8); margin-bottom:var(--s8); }
}

/* ====================================================================== *
 * P1 -- THREE-TIER ACCOUNT COMPOSITION
 *
 * The screen had nine equal eyebrow headings in identical type. Replacing
 * them with nine differently styled headings would have solved nothing, so
 * there are now exactly THREE announcements -- Du / Deine Küche / Dein Konto --
 * and they are a different typographic species from everything beneath
 * them: display face, sentence case, several steps larger. Everything that
 * used to be a chapter is now either a field label inside a tier or lives
 * behind the collapsed account area.
 *
 * Hierarchy comes from type, scale, proximity and whitespace. No cards, no
 * tiles, no containers added anywhere.
 * ====================================================================== */
/* VISUAL PASS -- THE TIER MARK IS THE QUIETEST TYPE ON THE SCREEN.
   The three announcements were previously the brightest thing a member saw,
   which is exactly what made the page read as a document with chapters. The
   relationship is now inverted: the mark is a dim label in the margin, and
   the member's own name, their own sentence and their own numbers carry the
   light. Hierarchy from type and contrast, before any rule or container. */
/* A DIFFERENT SPECIES FROM EVERY OTHER LABEL. Uppercase letter-spaced type is
   what every micro-label on this screen already is, so a tier set that way
   would just be a bigger eyebrow. The tier is therefore the one heading in
   mixed-case display type -- large enough to structure the page, dim enough
   that it never outshines the member's own words. */
.acct-tier{ font-family:var(--font-d); font-weight:500;
  font-size:clamp(26px, 5.8vw, 33px); text-transform:none;
  line-height:1.06; letter-spacing:0.012em; color:var(--text-disabled);
  margin:0 0 var(--s5); }
/* CLEANUP PASS -- the first tier keeps its level and loses its ink. Kicker,
   tier and name were three introductions in a row; the name is the one that
   says something. The heading stays in the accessibility tree so the outline
   still has three tiers, and on desktop the rail column stays reserved so all
   three tiers still share one axis. */
.acct-tier-quiet{ position:absolute; width:1px; height:1px; margin:-1px;
  padding:0; overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%);
  white-space:nowrap; border:0; }
.acct-tier-block{ margin:var(--s9) 0 0; }
.acct-tier-block.acct-du{ margin-top:0; }
/* The eyebrows that survive are demoted so they cannot compete with the
   member's own words -- but only to --muted. At --text-disabled a 10px
   uppercase label measured about 3.2:1 on this ground, which is below AA for
   text that small; quiet is a design decision, illegible is a defect. */
.acct-card .acct-lbl{ color:var(--muted); }

/* ---------------------------------------------------------------------- *
 * THE TRACE -- the relationship made visible.
 *
 * One vertical line runs from the first adaptation down through the domains.
 * The magenta markers sit ON it; each domain hangs off it by a plain hairline
 * node. Cause and effect stop being two stacked sections and become one
 * connected object, which is the whole claim of this tier. It is also the
 * device that replaces the horizontal rules: the page is now organised by a
 * single vertical thread and by space, not by equal horizontal bands.
 * ---------------------------------------------------------------------- */
/* CLEANUP PASS -- FELT, NOT DIAGRAMMED. A continuous rule at hairline weight
   read as a timeline: evenly present between every node, which is what a
   process diagram looks like. The long line is now barely there -- an
   atmospheric seam rather than a connector -- and the connection gathers
   strength only AROUND the markers, where it means something. Same single
   device, a fraction of the contrast, no extra marks. */
.acct-thread{ --thread-pad:34px; --thread-x:5px;
  position:relative; padding-left:var(--thread-pad); }
/* ONE GROUND UNDER THE WHOLE REGION, and no rectangle anywhere in it. A very
   faint wash, transparent at every edge and masked away top and bottom, so
   what CRVD adapts, what it knows and the instruments themselves sit in one
   volume of light instead of three unrelated places on black. Far below the
   identity plate's strength -- the member is still the brightest thing on
   the screen. */
.acct-thread::before{ content:''; position:absolute; z-index:0;
  inset:-24px -24px -16px -16px; pointer-events:none;
  background:
    radial-gradient(66% 46% at 62% 74%, rgba(226,25,181,0.055) 0%, rgba(226,25,181,0) 72%),
    radial-gradient(70% 40% at 34% 16%, rgba(214,198,226,0.026) 0%, rgba(214,198,226,0) 74%);
  -webkit-mask-image:linear-gradient(180deg, transparent 0%, #000 14%, #000 84%, transparent 100%);
  mask-image:linear-gradient(180deg, transparent 0%, #000 14%, #000 84%, transparent 100%); }
.acct-thread > *:not(.acct-thread-line){ position:relative; z-index:1; }
.acct-thread-line{ z-index:1; }
/* The editorial statements keep their measure; the instrument surfaces below
   are free to use the width the tier actually has. */
.acct-receipt-list, .acct-nothing-yet{ max-width:34rem; }
.acct-domains{ max-width:38rem; }
.acct-thread-line{ position:absolute; left:var(--thread-x); top:9px; bottom:0;
  width:1px; transform-origin:top center;
  background:linear-gradient(180deg,
    rgba(245,241,236,0.075) 0%, rgba(245,241,236,0.020) 20%,
    rgba(245,241,236,0.013) 60%, transparent 94%); }

/* Receipt, no longer self-announcing: it sits directly under "Deine Küche",
   which is what it is. The dot keeps its single meaning, and now also keeps
   its place -- it is the point where the effect meets the trace. */
.acct-receipt-list{ list-style:none; margin:0 0 var(--s8); padding:0; }
.acct-receipt-list li{ position:relative; padding:0 0 var(--s5); }
.acct-receipt-list li:last-child{ padding-bottom:0; }
.adapt-effect{ display:block; font-family:var(--font-d); font-weight:400;
  font-size:clamp(19px, 4vw, 23px); line-height:1.26; letter-spacing:0.004em;
  color:var(--text-primary); max-width:17em; }
/* THE ORIGIN. Not a caption -- the way back to the answer that produced the
   line above it. Same fact the screen already held, finally in the order the
   relationship runs: you told CRVD something, so this changed. */
/* 44px of hit area without 44px of visual weight -- the same negative-margin
   pattern .acct-edit and .btn-quiet already use on this screen. */
.adapt-src{ background:none; border:none; padding:0; margin:4px 0 -12px;
  display:inline-flex; align-items:center; min-height:44px; cursor:pointer;
  font-family:var(--font-c); font-size:10.5px; font-weight:600;
  letter-spacing:0.185em; text-transform:uppercase; color:var(--muted);
  transition:color 260ms var(--patient); }
.adapt-src-from{ font-weight:400; letter-spacing:0.12em; margin-right:.45em;
  color:var(--text-disabled); }
.adapt-src::after{ content:''; display:inline-block; width:0; height:1px;
  margin-left:0; background:var(--border-strong);
  transition:width 320ms var(--patient), margin-left 320ms var(--patient); }
.adapt-src:hover, .adapt-src:focus-visible{ color:var(--text-secondary); }
.adapt-src:hover::after, .adapt-src:focus-visible::after{ width:22px; margin-left:10px; }
.acct-receipt-list li.is-linked .adapt-src{ color:var(--text-secondary); }
.acct-receipt-list li.is-recognised .adapt-effect{
  animation:acctRecognise 1100ms var(--patient); }

.acct-nothing-yet{ font-size:15px; line-height:1.6; color:var(--text-secondary);
  margin:0 0 var(--s8); max-width:30em; }
.acct-domains-lead{ font-size:10px; letter-spacing:0.2em; text-transform:uppercase;
  color:var(--muted); font-weight:600; margin:0 0 var(--s5); }

/* TIER 3 -- one quiet disclosure. Native <details>: keyboard-operable and
   announced by assistive technology without a line of JavaScript, and
   without adding a sixth interaction model to a screen this refactor is
   removing models from. */
/* VISUAL PASS -- the lid. The rule across the page is gone: a full-width
   hairline is precisely what made this read as one more section instead of
   a layer that is closed. What says "closed" now is a drawn mark that opens,
   and the space above it. */
.acct-admin{ margin:var(--s9) 0 0; }
.acct-admin-summary{ list-style:none; cursor:pointer;
  display:grid; grid-template-columns:1fr auto; align-items:center;
  column-gap:var(--s5); min-height:44px; padding:0; }
.acct-admin-summary::-webkit-details-marker{ display:none; }
.acct-admin-summary:focus-visible{ outline:2px solid var(--magenta); outline-offset:6px; }
.acct-tier-admin{ display:block; grid-column:1; margin:0 0 5px;
  transition:color 220ms var(--patient); }
.acct-admin-summary:hover .acct-tier-admin{ color:var(--text-secondary); }
.acct-admin-hint{ display:block; grid-column:1; font-size:14px; color:var(--muted); }
.acct-admin[open] .acct-admin-hint{ color:var(--text-disabled); }
/* A drawn plus that becomes a minus. Two 1px strokes, no glyph, no icon set:
   the same restraint as every other mark on this screen. */
.acct-admin-mark{ grid-column:2; grid-row:1 / span 2; position:relative;
  width:16px; height:16px; align-self:center; }
.acct-admin-mark::before, .acct-admin-mark::after{ content:''; position:absolute;
  left:0; top:7.5px; width:16px; height:1px; background:var(--text-disabled);
  transition:transform 380ms var(--patient), background 220ms var(--patient); }
.acct-admin-mark::after{ transform:rotate(90deg); }
.acct-admin[open] .acct-admin-mark::after{ transform:rotate(0deg); }
.acct-admin-summary:hover .acct-admin-mark::before,
.acct-admin-summary:hover .acct-admin-mark::after{ background:var(--text-secondary); }
/* Opens FROM a hidden state INTO its real resting state. The rule below adds
   nothing that has to finish: if the animation never runs, the panel is
   already visible at full height. That is deliberate -- this tier holds the
   only route to signing out. */
.acct-admin-body{ margin-top:var(--s6); }
.acct-admin[open] .acct-admin-body{ animation:acctLidOpen 420ms var(--patient); }
@keyframes acctLidOpen{
  from{ opacity:0; transform:translateY(-10px); }
  to{ opacity:1; transform:none; } }

/* ---------------------------------------------------------------------- *
 * THE ADMINISTRATIVE MODULES.
 *
 * Deliberately the ONE contained language on this screen. DU and CRVD & DU
 * are open, editorial and spatial because they are the member; this layer is
 * administration, and letting it be modular is what makes that difference
 * legible instead of flattening everything into one texture.
 *
 * These are not cards. The surface is lifted about two percent off the
 * canvas, has no border, no shadow and a single soft magenta breath in one
 * corner -- the same light as the identity plate, at a fraction of its
 * strength, so this layer still belongs to the same product. Hierarchy
 * inside a module comes from type and space; the response lives on the rows.
 * ---------------------------------------------------------------------- */
.acct-mods{ display:grid; gap:var(--s4); }
.acct-mod{ position:relative; overflow:hidden;
  border-radius:18px; padding:var(--s5) var(--s5) var(--s5);
  background:
    radial-gradient(120% 140% at 88% 0%, rgba(226,25,181,0.045) 0%, rgba(226,25,181,0) 58%),
    rgba(245,241,236,0.022); }
.acct-mod-h{ font-family:var(--font-c); font-size:10px; font-weight:600;
  letter-spacing:0.2em; text-transform:uppercase; color:var(--muted);
  margin:0 0 var(--s4); }
.acct-mod-row{ margin:0; }
.acct-mod-row + .acct-mod-row{ margin-top:2px; }
/* 44px of hit area, and a real response: the row lights and steps forward. */
.acct-mod-row .btn-quiet{ display:inline-flex; align-items:center;
  min-height:44px; margin:-9px 0; text-underline-offset:4px;
  transition:color 220ms var(--patient), transform 220ms var(--patient); }
.acct-mod-row .btn-quiet:hover,
.acct-mod-row .btn-quiet:focus-visible{ color:var(--text-primary); transform:translateX(3px); }
.acct-mod-note{ font-size:13.5px; line-height:1.6; color:var(--text-secondary);
  margin:var(--s3) 0 0; }
/* Read-only state inside a module: no rules, the pair is held by alignment. */
.acct-mod .account-row{ border-bottom:none; padding:5px 0; font-size:14.5px; }
.acct-mod .account-row .v{ color:var(--text-primary);
  font-variant-numeric:tabular-nums; }
.acct-mod-sub{ margin-top:var(--s4); padding-top:var(--s4);
  border-top:1px solid rgba(245,241,236,0.06); }
/* Reachable, never promoted. */
.acct-mod-quiet{ margin-top:var(--s4); }
.acct-mod-quiet .btn-quiet{ color:var(--text-disabled); }
.acct-mod-quiet .btn-quiet:hover,
.acct-mod-quiet .btn-quiet:focus-visible{ color:var(--action-destructive); }

@media (max-width:640px){
  .acct-tier{ margin-bottom:var(--s4); }
  .acct-tier-block{ margin-top:var(--s8); }
  .acct-thread{ --thread-pad:26px; --thread-x:4px; }
  .acct-admin{ margin-top:var(--s8); }
}
/* ---------------------------------------------------------------------- *
 * DESKTOP -- CONTROLLED ASYMMETRY.
 *
 * The tier marks HANG in a left rail, right-aligned against a shared axis,
 * and the content runs in its own measure beside them. That single move is
 * what stops the desktop screen reading as a stack of centred sections: the
 * eye gets a vertical axis to follow and the content sits off it, with the
 * Signal weighting the far right of the identity plate.
 *
 * This is composition only -- it is NOT the P2 sticky architecture, which
 * stays deliberately unimplemented. Nothing here scrolls independently.
 * ---------------------------------------------------------------------- */
@media (min-width:1024px){
  .acct-tier-block{ display:grid;
    grid-template-columns:10.5rem minmax(0, 1fr); column-gap:var(--s7); }
  .acct-tier-block > .acct-tier{ grid-column:1; text-align:right;
    align-self:start; margin:0; padding-top:14px; }
  .acct-tier-block > .acct-tier-body{ grid-column:2; min-width:0; }
  .acct-du > .acct-tier{ padding-top:26px; }
  .acct-admin-summary{ grid-template-columns:10.5rem minmax(0, 1fr) auto;
    column-gap:var(--s7); align-items:start; }
  .acct-tier-admin{ grid-column:1; grid-row:1 / span 2; text-align:right;
    margin:0; padding-top:2px; }
  .acct-admin-hint{ grid-column:2; grid-row:1; }
  .acct-admin-mark{ grid-column:3; grid-row:1 / span 2; align-self:start; }
  /* DESKTOP -- the two domains sit side by side at UNEQUAL widths, sized to
     what they actually hold: three readings against one measurement. Not a
     pair of matched cards, and never height-matched -- align-items:start
     lets each surface end where its own content ends. */
  .acct-domains{ max-width:none; display:grid;
    grid-template-columns:1.42fr 0.78fr; column-gap:var(--s4);
    align-items:start; }
  .acct-domains-lead{ grid-column:1 / -1; }
  .acct-thread [data-domain="essen"]{ grid-column:1; margin-bottom:0; }
  /* The narrow surface stacks: name above measurement. Side by side inside
     296px the name column fell to about 120px and threw its arrow onto a
     line of its own. */
  .acct-thread [data-domain="koerper"]{ grid-column:2;
    margin-left:0; padding-left:var(--s5); margin-bottom:0;
    border-radius:16px; grid-template-columns:1fr; }
  .acct-thread [data-domain="koerper"] .kitchen-entry-text{ grid-column:1; grid-row:1; }
  .acct-thread [data-domain="koerper"] .dom-readout{ grid-column:1; grid-row:2;
    margin-top:var(--s4); align-items:flex-start; }
  .acct-thread .kitchen-entry-name{ font-size:27px; }
  /* The 48px column gap was costing the name the width its own arrow needs. */
  .acct-thread .kitchen-entry{ column-gap:var(--s5); }
  .dom-line{ grid-template-columns:auto clamp(80px, 10vw, 150px); }
  .acct-admin-body{ margin-left:calc(10.5rem + var(--s7)); max-width:44rem; }
  /* Two columns, asymmetric on purpose and never height-matched: Aktivität
     carries the most content and gets the wider column; the modules simply
     end where their content ends. align-items:start is what keeps this from
     becoming a dashboard grid. */
  .acct-mods{ grid-template-columns:1.15fr 0.85fr; align-items:start;
    gap:var(--s4); }
}

/* ---------------------------------------------------------------------- *
 * DOMAINS REGION (Slice 3). Two deeper-personalization domains is exactly
 * where an Account screen turns into a settings list, so they are presented
 * as ONE titled region -- "Was du CRVD beigebracht hast" -- rather than two
 * rows that happen to sit together. The heading does the framing; each
 * domain then states its own contents through its own instrument readout.
 * ---------------------------------------------------------------------- */
.acct-domains{ margin:0; }
.acct-domains > .acct-lbl{ margin-bottom:var(--s4); }
.acct-domains .kitchen-entry:last-child{ margin-bottom:0; }

/* ---------------------------------------------------------------------- *
 * THE TRACE, CONTINUED -- everything that hangs off the line.
 *
 * The marker sits ON the trace; the domains hang off it by a neutral node.
 * That distinction is the semantics drawn rather than written: magenta only
 * where CRVD is actively adapting, plain hairline everywhere else.
 * ---------------------------------------------------------------------- */
.acct-thread .adapt-dot{ position:absolute;
  left:calc(var(--thread-x) - 3px - var(--thread-pad)); top:9px;
  width:7px; height:7px; margin-top:0;
  box-shadow:0 0 10px 1px rgba(226,25,181,0.40); }
/* Where the connection is actually FELT: a short brightening of the seam
   through each marker, fading out above and below it. Neutral, never
   magenta -- the marker owns the only magenta on this axis. */
.acct-receipt-list li::before{ content:''; position:absolute;
  left:calc(var(--thread-x) - var(--thread-pad)); top:-16px;
  width:1px; height:56px; pointer-events:none;
  background:linear-gradient(180deg, transparent 0%,
    rgba(245,241,236,0.16) 42%, rgba(245,241,236,0.05) 74%, transparent 100%); }

/* ====================================================================== *
 * THE PERSONALIZATION SURFACE -- the third spatial language on this screen.
 *
 * Three related languages, deliberately not one component repeated:
 *   DU          light in a room. No edge anywhere, no surface at all.
 *   DEINE KÜCHE INSTRUMENT SURFACES. Lit from the side the rails sit on,
 *               anchored to the trace by one squared-off corner, and awake
 *               to being approached.
 *   DEIN KONTO  compact modules. Even radius, even fill, static.
 *
 * What separates a Küche surface from an Account module: the light is
 * DIRECTIONAL (it gathers where the instrument is, so the rails read as part
 * of the surface rather than drawn on top of it), the shape is ASYMMETRIC
 * (the corner facing the trace is nearly square -- the surface hangs off the
 * seam), and the surface RESPONDS. An Account module does none of that,
 * because administration is not personal and should not breathe.
 *
 * P3 NOTE: each surface is its own positioned, rounded, self-contained box
 * with the domain's real values inside it. That makes it a usable ORIGIN for
 * a later expand-into-detail transition -- the surface can grow into the
 * detail screen rather than being replaced by it. Nothing here assumes that
 * yet; it just does not stand in its way.
 * ====================================================================== */
.acct-thread .kitchen-entry{ position:relative;
  border-bottom:none; margin:0 0 var(--s5);
  /* Bleeds LEFT so the surface's own content lands on exactly the same edge
     as the editorial statements above it, while the box itself stops a few
     pixels clear of the trace. The trace stays outside; the surface hangs
     off it. */
  margin-left:calc(10px - var(--thread-pad));
  padding:var(--s5) var(--s5) var(--s5) calc(var(--thread-pad) - 10px);
  border-radius:5px 22px 22px 22px;
  background:
    radial-gradient(72% 128% at 96% 46%, rgba(226,25,181,0.075) 0%, rgba(226,25,181,0) 66%),
    linear-gradient(102deg, rgba(245,241,236,0.032) 0%, rgba(245,241,236,0.015) 52%,
      rgba(245,241,236,0.006) 100%);
  box-shadow:inset 0 1px 0 rgba(245,241,236,0.045);
  transition:background 340ms var(--patient), box-shadow 340ms var(--patient),
    transform 340ms var(--patient); }
.acct-thread .kitchen-entry:last-child{ margin-bottom:0; }
/* AWAKE. The light gathers, the edge catches a little more, and the surface
   lifts by a single pixel -- calm, and unmistakably a response. */
.acct-thread .kitchen-entry:hover,
.acct-thread .kitchen-entry:focus-visible,
.acct-thread .kitchen-entry.is-linked{
  transform:translateY(-1px);
  background:
    radial-gradient(76% 132% at 96% 46%, rgba(226,25,181,0.135) 0%, rgba(226,25,181,0) 68%),
    linear-gradient(102deg, rgba(245,241,236,0.050) 0%, rgba(245,241,236,0.024) 52%,
      rgba(245,241,236,0.010) 100%);
  box-shadow:inset 0 1px 0 rgba(245,241,236,0.085),
    0 14px 34px -22px rgba(226,25,181,0.55); }
.acct-thread .kitchen-entry:active{ transform:translateY(0); opacity:.92; }

/* KÖRPER stays subordinate: a quieter ground, less light, a tighter shape.
   It is private personal context, not a second instrument panel. */
.acct-thread [data-domain="koerper"]{
  border-radius:5px 16px 16px 16px;
  background:
    radial-gradient(64% 120% at 96% 50%, rgba(226,25,181,0.030) 0%, rgba(226,25,181,0) 66%),
    linear-gradient(102deg, rgba(245,241,236,0.022) 0%, rgba(245,241,236,0.010) 60%,
      rgba(245,241,236,0.004) 100%); }
.acct-thread [data-domain="koerper"]:hover,
.acct-thread [data-domain="koerper"]:focus-visible,
.acct-thread [data-domain="koerper"].is-linked{
  background:
    radial-gradient(68% 124% at 96% 50%, rgba(226,25,181,0.065) 0%, rgba(226,25,181,0) 68%),
    linear-gradient(102deg, rgba(245,241,236,0.036) 0%, rgba(245,241,236,0.017) 60%,
      rgba(245,241,236,0.007) 100%);
  box-shadow:inset 0 1px 0 rgba(245,241,236,0.065),
    0 12px 28px -22px rgba(226,25,181,0.34); }

/* ONE INSTRUMENT, NOT TEXT PLUS A GRAPHIC.
   Each cooking answer now sits on the row of the rail that describes it,
   sharing a baseline with it: "Für 4" and its blade are one reading. The
   value is right-aligned against the rail so the pair reads as a single
   object rather than as a label and a picture that happen to be near each
   other. No headers, no legend, no box -- three readings. */
.dom-readout{ grid-column:2; grid-row:1 / span 2; display:flex;
  flex-direction:column; gap:11px; align-items:stretch; }
.dom-line{ display:grid; grid-template-columns:auto clamp(72px, 17vw, 150px);
  align-items:center; column-gap:var(--s4); }
.dom-val{ font-size:14px; line-height:1.2; color:var(--text-body);
  text-align:right; white-space:nowrap;
  transition:color 300ms var(--patient); }
.dom-val-open{ color:var(--text-disabled); }
.kitchen-entry:hover .dom-val, .kitchen-entry:focus-visible .dom-val,
.kitchen-entry.is-linked .dom-val{ color:var(--text-primary); }
.dom-line .kitchen-mini-row{ position:relative; height:13px; }
.dom-line .kitchen-mini-row::before{ content:''; position:absolute;
  left:0; right:0; top:6px; height:1px; background:var(--hairline);
  transition:background 300ms var(--patient); }
.dom-line .kitchen-mini-b{ height:13px; width:2px;
  transition:left 420ms var(--patient), height 260ms var(--patient),
    box-shadow 260ms var(--patient), top 260ms var(--patient); }
.kitchen-entry:hover .kitchen-mini-b,
.kitchen-entry:focus-visible .kitchen-mini-b,
.kitchen-entry.is-linked .kitchen-mini-b{
  height:19px; top:-3px; box-shadow:0 0 11px 1px rgba(226,25,181,0.62); }
.kitchen-entry:hover .dom-line .kitchen-mini-row::before,
.kitchen-entry.is-linked .dom-line .kitchen-mini-row::before{
  background:var(--border-strong); }

/* KÖRPER states itself in the same column, and states a fact rather than a
   position. The overview rail was removed deliberately: one mark inside the
   member's own recorded range is a movement story told by a picture, and
   this surface answers "where do I stand", not "where am I heading". */
.dom-readout-single{ justify-content:center; align-items:flex-end; gap:4px; }
.dom-measure{ display:inline-flex; align-items:baseline; gap:5px;
  font-family:var(--font-d); font-weight:500;
  font-size:clamp(26px, 6vw, 34px); line-height:1;
  color:var(--text-primary); font-variant-numeric:tabular-nums;
  transition:color 300ms var(--patient); }
.dom-measure-meta{ font-size:13px; color:var(--text-secondary); }
/* The affordance lives on the name, not in a chevron column: a settings list
   has chevrons, an editorial entry has a direction. */
.kitchen-entry-name{ display:block; }
.kitchen-entry-name::after{ content:'\2192'; display:inline-block;
  margin-left:.42em; font-size:.68em; opacity:.28;
  transition:opacity 260ms var(--patient), transform 260ms var(--patient); }
.kitchen-entry:hover .kitchen-entry-name::after,
.kitchen-entry:focus-visible .kitchen-entry-name::after,
.kitchen-entry.is-linked .kitchen-entry-name::after{
  opacity:.85; transform:translateX(5px); }
.acct-thread .kitchen-entry-name{ font-size:clamp(24px, 5.2vw, 30px); }
.acct-thread .kitchen-entry-sub{ margin-top:9px; font-size:14px; }
.acct-thread .kitchen-entry:active{ opacity:.82; }

@media (max-width:640px){
  /* MOBILE: the reading STACKS under the domain rather than squeezing into a
     second column. Side by side, a 358px row left the name about 130px and
     pushed its arrow onto a line of its own. Stacked, the name gets the full
     measure and each reading gets a full-width rail -- which reads more like
     an instrument panel here, not less. */
  /* Stacked, the instrument sits in the LOWER half of the surface, so the
     light moves with it -- the rails stay inside their own light rather than
     next to it. */
  /* The surface costs vertical room, so it gives some back: tighter padding
     and a shorter gap before the domains keep the page close to where it was
     before the surfaces existed. */
  .acct-receipt-list{ margin-bottom:var(--s7); }
  .acct-thread .kitchen-entry{ grid-template-columns:1fr; margin-bottom:var(--s5);
    padding-top:var(--s4); padding-bottom:var(--s4);
    background:
      radial-gradient(84% 62% at 78% 82%, rgba(226,25,181,0.075) 0%, rgba(226,25,181,0) 70%),
      linear-gradient(168deg, rgba(245,241,236,0.032) 0%, rgba(245,241,236,0.014) 58%,
        rgba(245,241,236,0.006) 100%); }
  .acct-thread .kitchen-entry:hover,
  .acct-thread .kitchen-entry:focus-visible,
  .acct-thread .kitchen-entry.is-linked{
    background:
      radial-gradient(88% 66% at 78% 82%, rgba(226,25,181,0.135) 0%, rgba(226,25,181,0) 72%),
      linear-gradient(168deg, rgba(245,241,236,0.050) 0%, rgba(245,241,236,0.023) 58%,
        rgba(245,241,236,0.010) 100%); }
  .acct-thread [data-domain="koerper"]{
    background:
      radial-gradient(70% 56% at 74% 84%, rgba(226,25,181,0.030) 0%, rgba(226,25,181,0) 70%),
      linear-gradient(168deg, rgba(245,241,236,0.022) 0%, rgba(245,241,236,0.010) 62%,
        rgba(245,241,236,0.004) 100%); }
  .acct-thread .kitchen-entry-text{ grid-column:1; grid-row:1; }
  .acct-thread .kitchen-entry-name{ font-size:24px; }
  .dom-readout{ grid-column:1; grid-row:2; margin-top:var(--s4); }
  .dom-line{ grid-template-columns:7.6em 1fr; column-gap:var(--s4); }
  .dom-val{ font-size:13.5px; text-align:left; }
  .dom-readout-single{ align-items:flex-start; margin-top:var(--s3); }
}
/* ====================================================================== *
 * THE MEASURE -- Weight (Slice 3).
 *
 * Same family as the calibrated rail, inverted. On the discrete rails the
 * scale stands still and the blade travels, because eight answers fit on
 * screen. Weight has 2 200 possible answers, so the relationship flips the
 * way it does on a real instrument: THE INDEX IS FIXED AND THE TAPE MOVES
 * UNDER IT. Same blade, same bloom, same trough, same drag and settle.
 *
 * The tape visibly runs past both edges of its window, which is the whole
 * affordance -- nothing that continues beyond its frame reads as static.
 * ====================================================================== */
.wt-measure{ margin:var(--s6) 0 var(--s7); max-width:34rem; }
/* Once measurements exist the hero number is already stated above, so the
   instrument stops repeating it: the same control, in a recording role
   rather than a display one. Shouting "77,9 kg" twice in a row read as a
   bug, not as emphasis. */

.wt-readout{ display:flex; align-items:baseline; gap:8px; margin:0 0 var(--s4); }
.wt-value{ font-family:var(--font-d); font-weight:500; font-size:52px; line-height:1;
  letter-spacing:0.01em; color:var(--text-primary); font-variant-numeric:tabular-nums; }
.wt-unit{ font-family:var(--font-c); font-size:13px; font-weight:500; letter-spacing:0.16em;
  text-transform:uppercase; color:var(--muted); }

/* The window is the instrument's aperture. Its edges fade the tape out so
   the scale reads as continuing beyond the frame rather than being cut. */
.wt-window{ position:relative; height:76px; overflow:hidden; cursor:grab;
  touch-action:pan-y; margin:0 0 var(--s5);
  -webkit-mask-image:linear-gradient(90deg, transparent 0%, black 11%, black 89%, transparent 100%);
  mask-image:linear-gradient(90deg, transparent 0%, black 11%, black 89%, transparent 100%); }
.wt-window.is-held{ cursor:grabbing; }
.wt-tape{ position:absolute; top:0; left:0; height:76px; will-change:transform; }
.wt-mark{ position:absolute; top:30px; width:1px; height:9px; background:var(--hairline); }
.wt-mark.is-whole{ top:27px; height:12px; background:var(--text-disabled); }
.wt-mark.is-major{ top:22px; height:17px; background:var(--muted); }
.wt-num{ position:absolute; top:46px; transform:translateX(-50%);
  font-family:var(--font-d); font-weight:400; font-size:12px; letter-spacing:0.04em;
  color:var(--text-disabled); }
/* The index -- the same blade as the rails, standing still. */
.wt-index{ position:absolute; left:50%; top:14px; width:3px; height:34px; margin-left:-1.5px;
  background:var(--magenta); border-radius:1.5px; pointer-events:none;
  box-shadow:0 0 14px 2px rgba(226,25,181,0.55), 0 0 3px 0 rgba(226,25,181,0.9);
  transition:height 180ms var(--patient), top 180ms var(--patient), box-shadow 180ms var(--patient); }
.wt-index::after{ content:''; position:absolute; left:50%; top:50%;
  width:34px; height:34px; margin:-17px 0 0 -17px; border-radius:50%;
  background:radial-gradient(circle, rgba(226,25,181,0.26) 0%, rgba(226,25,181,0) 70%);
  transition:transform 200ms var(--patient); }
.wt-window:hover .wt-index{ height:38px; top:12px; }
.wt-window.is-held .wt-index{ height:44px; top:9px;
  box-shadow:0 0 20px 3px rgba(226,25,181,0.65), 0 0 4px 0 rgba(226,25,181,1);
  transition-duration:90ms; }
.wt-window.is-held .wt-index::after{ transform:scale(1.5); }

/* DORMANT vs ENGAGED. With a record above it the instrument arrives as an
   ACTION, not a display: its readout collapses to nothing so it cannot
   repeat the authoritative value as a second headline. It is still fully
   visible and fully draggable -- only the number defers. The moment the
   member engages, the readout opens and takes prominence, because from then
   on the draft is the number that matters. */
.wt-measure.is-action .wt-readout{ height:0; opacity:0; overflow:hidden;
  margin:0; transition:height 280ms var(--patient), opacity 200ms var(--patient),
  margin 280ms var(--patient); }
.wt-measure.is-action.is-engaged .wt-readout{ height:46px; opacity:1; margin:0 0 var(--s4); }
.wt-measure.is-action.is-engaged .wt-value{ color:var(--text-primary); }

/* PRECISION ENTRY. One calibrated trough holding the stepper, the number and
   its unit -- the same recessed ground as the rail, so typing a value reads
   as another way of driving the instrument rather than a form pasted below
   it. The <input> keeps its native semantics untouched. */
.wt-entry{ display:flex; align-items:stretch; gap:0; margin:0 0 var(--s5);
  border-top:1px solid var(--hairline); border-bottom:1px solid var(--hairline);
  background:linear-gradient(180deg, rgba(0,0,0,0.34) 0%, rgba(0,0,0,0) 62%); }
.wt-step{ width:52px; flex:none; min-height:56px; background:none; border:none;
  border-right:1px solid var(--hairline); color:var(--text-secondary);
  font-size:19px; line-height:1; cursor:pointer;
  transition:color 180ms var(--patient), background-color 180ms var(--patient); }
.wt-entry .wt-step:last-child{ border-right:none; border-left:1px solid var(--hairline); }
.wt-step:hover{ color:var(--text-primary); }
.wt-step:active{ background:rgba(226,25,181,0.12); color:var(--text-primary); transition-duration:70ms; }
.wt-entry-field{ flex:1; min-width:0; display:flex; flex-direction:column;
  justify-content:center; padding:5px var(--s4) 6px; cursor:text; }
.wt-entry-lbl{ font-size:9.5px; letter-spacing:0.2em; text-transform:uppercase;
  color:var(--muted); font-weight:600; }
.wt-entry-row{ display:flex; align-items:baseline; gap:6px; }
/* The INPUT carries the 44px target itself, not just its label wrapper.
   Measured at 198x28 before this: tapping the surrounding label did focus
   it, but the control a member actually aims at was under the minimum. */
.wt-entry input{ width:100%; min-width:0; min-height:44px; background:none;
  border:none; padding:0; color:var(--text-primary); font-family:var(--font-d);
  font-weight:500; font-size:22px; line-height:44px;
  font-variant-numeric:tabular-nums; }
.wt-entry input:focus{ outline:none; }
.wt-entry-field:focus-within{ background:rgba(226,25,181,0.06); }
.wt-entry-field:focus-within .wt-entry-lbl{ color:var(--magenta); }
.wt-entry input:focus-visible{ outline:none; }
.wt-entry-unit{ font-family:var(--font-c); font-size:11px; font-weight:500;
  letter-spacing:0.16em; text-transform:uppercase; color:var(--muted); }
/* Native spinners removed: the stepper beside the field IS the spinner, and
   two of them in one trough would be one too many. */
.wt-entry input::-webkit-outer-spin-button,
.wt-entry input::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
.wt-entry input[type=number]{ -moz-appearance:textfield; }

/* COMMIT. A ruled action bar, not a filled slab -- the instrument stays the
   protagonist. The blade at the leading edge is the same vocabulary as the
   rail index, and it extends on hover, press and keyboard focus, so the
   control is unmistakably actionable without shouting. The word is always
   present; nothing here is an icon-only mystery. */
.wt-commit{ display:flex; align-items:center; gap:var(--s4); width:100%;
  min-height:56px; padding:0 var(--s4) 0 0; margin:0 0 var(--s2);
  background:none; border:none; border-bottom:1px solid var(--hairline);
  cursor:pointer; text-align:left; font-family:inherit; position:relative; }
.wt-commit-blade{ width:3px; height:22px; flex:none; border-radius:1.5px;
  background:var(--magenta); box-shadow:0 0 10px 1px rgba(226,25,181,0.45);
  transition:height 220ms var(--patient), box-shadow 220ms var(--patient); }
.wt-commit-label{ font-family:var(--font-d); font-weight:500; font-size:17px;
  letter-spacing:0.02em; color:var(--text-primary); flex:1;
  transition:color 200ms var(--patient); }
.wt-commit-date{ font-size:12px; color:var(--muted); white-space:nowrap; }
.wt-commit:hover .wt-commit-blade,
.wt-commit:focus-visible .wt-commit-blade{ height:34px; box-shadow:0 0 16px 2px rgba(226,25,181,0.6); }
.wt-commit:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }
.wt-commit:active .wt-commit-blade{ height:40px; transition-duration:80ms; }
.wt-commit.is-pending{ cursor:default; }
.wt-commit.is-pending .wt-commit-label{ color:var(--text-secondary); }
.wt-commit:disabled{ cursor:default; }

.wt-measure .kitchen-msg{ margin-top:var(--s3); }

/* LATEST -- the answer first, then the one step before it. No verdict, no
   colour coding, no arrow: the delta is a fact stated quietly. */
.wt-latest{ margin:var(--s5) 0 0; }
.wt-latest-v{ font-family:var(--font-d); font-weight:500; font-size:56px; line-height:1;
  color:var(--text-primary); font-variant-numeric:tabular-nums; margin:0 0 6px; }
.wt-latest-u{ font-family:var(--font-c); font-size:13px; font-weight:500;
  letter-spacing:0.16em; text-transform:uppercase; color:var(--muted); margin-left:9px; }
.wt-latest-meta{ font-size:13px; color:var(--text-secondary); margin:0; }
.wt-delta{ color:var(--text-primary); font-variant-numeric:tabular-nums; }

/* THE LEDGER -- a measurement log, not a chart. There are no axes, no line
   and no area: each row states its own date and value, and carries one mark
   whose position within the member's OWN recorded range shows how that entry
   sat against the others. Calibration, not analytics. */
.wt-ledger{ margin:var(--s6) 0 0; max-width:34rem; }
.wt-ledger ul{ list-style:none; margin:var(--s4) 0 0; padding:0; }
.wt-row{ display:grid; grid-template-columns:auto 1fr auto auto; align-items:center;
  gap:var(--s4); padding:11px 0; border-bottom:1px solid var(--hairline); }
.wt-row:last-child{ border-bottom:none; }
.wt-row-date{ font-size:12.5px; color:var(--text-secondary); white-space:nowrap; }
.wt-row-scale{ position:relative; height:14px; min-width:40px; }
.wt-row-scale::before{ content:''; position:absolute; left:0; right:0; top:6px;
  height:1px; background:var(--hairline); }
.wt-row-mark{ position:absolute; top:2px; width:1px; height:9px; margin-left:-0.5px;
  background:var(--text-disabled); }
.wt-row.is-latest .wt-row-mark{ width:2px; margin-left:-1px; height:13px; top:0;
  background:var(--magenta); box-shadow:0 0 7px 0 rgba(226,25,181,0.45); }
.wt-row-kg{ font-family:var(--font-d); font-weight:400; font-size:16px;
  color:var(--text-secondary); font-variant-numeric:tabular-nums; white-space:nowrap; }
.wt-row.is-latest .wt-row-kg{ color:var(--text-primary); font-weight:500; }
/* Vertical negative margin only. A negative RIGHT margin pulled the 44px hit
   box 12px past the content column and widened the document to 394 against a
   390 viewport -- the bottom nav stretched with it. The hit area stays full
   size; it just stays inside the column. */
.wt-row-del{ width:44px; height:44px; margin:-11px 0; padding:0;
  background:none; border:none; cursor:pointer; color:var(--text-disabled);
  display:flex; align-items:center; justify-content:center;
  transition:color 200ms var(--patient); }
.wt-row-del:hover{ color:var(--magenta); }
.wt-row-del svg{ width:15px; height:15px; }

@media (max-width:640px){
  .wt-value{ font-size:44px; }
  .wt-latest-v{ font-size:46px; }
  .wt-window{ height:70px; }
  .wt-tape{ height:70px; }
}
@media (min-width:1024px){
  /* The instrument gains presence rather than width: a wider aperture shows
     more of the scale, which is the thing a bigger canvas can actually add. */
  .wt-measure, .wt-ledger{ max-width:38rem; }
  .wt-value{ font-size:62px; }
  .wt-latest-v{ font-size:68px; }
  .wt-window{ height:88px; }
  .wt-tape{ height:88px; }
  .wt-num{ top:54px; }
}

/* .btn-quiet is 27px tall by default -- measured on "Hintergrund" and
   "Community-Richtlinien". Same 44px-hit-area-without-44px-of-weight pattern
   as .acct-edit above, so the tier's visual demotion is unchanged. Scoped to
   this screen deliberately: the base class has 33 call sites across the
   product and widening the fix is a separate decision, not this pass. */
.acct .acct-admin-body .btn-quiet{
  display:inline-flex; align-items:center; min-height:44px; margin:-9px 0; }

/* The modules arrive one after another, out of the lid rather than all at
   once -- the same "animate FROM, never TO" rule as the panel itself, so a
   module whose animation never runs is simply already in place. */
.acct-admin[open] .acct-mod{ animation:acctLidOpen 460ms var(--patient) backwards; }
.acct-admin[open] .acct-mod:nth-child(2){ animation-delay:60ms; }
.acct-admin[open] .acct-mod:nth-child(3){ animation-delay:120ms; }
.acct-admin[open] .acct-mod:nth-child(4){ animation-delay:180ms; }

/* ---------------------------------------------------------------------- *
 * MOTION SYSTEM (Motion & Premium Depth pass) -- native only. Lenis was
 * evaluated for this screen and deliberately not adopted; the reasoning is
 * recorded at initAccountMotion() in app.js.
 *
 * Two mechanisms, one job each, and CSS owns every transform:
 *   .is-entering  arrival beats, applied once per VISIT (not per render)
 *   --sig-y       the Signal's depth drift, written by a rAF-coalesced read
 * (.acct-reveal, the below-the-fold scroll arrival this list once included,
 * was removed -- see the note further down, where its observer used to sit.)
 * JS writes a class or a custom property. It never writes style.transform --
 * same discipline the public site's GSAP surfaces already follow.
 * ---------------------------------------------------------------------- */
/* ARRIVAL. Content lands onto a surface that is already there: the card's
   ground, its hairlines and the section rules never animate, so nothing reads
   as a box sliding into place. The beats follow the screen's own semantic
   order -- person, situation, focus, then the adaptation as the consequence
   of the three above it. Last beat is fully in at ~960ms. */
/* FALLBACK ONLY. When GSAP is present it owns the arrival outright (see
   initAccountMotion) and .acct-gsap suppresses this path, so the two systems
   can never animate the same opacity from two directions. */
.acct.is-entering .kicker,
.acct.is-entering .acct-beat,
.acct.is-entering .acct-thread{
  opacity:0; transform:translateY(9px);
  animation:acctRise 620ms var(--patient) forwards; }
.acct.is-entering .acct-beat-person{ animation-delay:70ms; }
.acct.is-entering .acct-beat-situation{ animation-delay:150ms; }
.acct.is-entering .acct-beat-focus{ animation-delay:230ms; }
.acct.is-entering .acct-thread{ animation-delay:340ms; }
@keyframes acctRise{ to{ opacity:1; transform:none; } }

/* ESSEN & KOCHEN arrival. Same grammar as the Account beats -- content lands
   onto a stable surface, in reading order, once per visit. The instruments
   are the beats here. Deliberately NOT gated on scroll: all three sit within
   one viewport at both reference sizes, so a member would be waiting for
   controls already in front of them. */
.essen.is-entering .kicker,
.essen.is-entering .essen-title,
.essen.is-entering .essen-lede,
.essen.is-entering .kitchen-inst,
.essen.is-entering .essen-foot{
  opacity:0; transform:translateY(9px);
  animation:acctRise 620ms var(--patient) forwards; }
.essen.is-entering .essen-title{ animation-delay:60ms; }
.essen.is-entering .essen-lede{ animation-delay:120ms; }
.essen.is-entering .kitchen-inst:nth-of-type(1){ animation-delay:210ms; }
.essen.is-entering .kitchen-inst:nth-of-type(2){ animation-delay:280ms; }
.essen.is-entering .kitchen-inst:nth-of-type(3){ animation-delay:350ms; }
.essen.is-entering .essen-foot{ animation-delay:440ms; }

/* Weight uses the same arrival grammar as Essen & Kochen. */
.wt.is-entering .kicker,
.wt.is-entering .essen-title,
.wt.is-entering .essen-lede,
.wt.is-entering .wt-latest,
.wt.is-entering .wt-measure,
.wt.is-entering .wt-ledger,
.wt.is-entering .essen-foot{
  opacity:0; transform:translateY(9px);
  animation:acctRise 620ms var(--patient) forwards; }
.wt.is-entering .essen-title,
.wt.is-entering .wt-latest{ animation-delay:60ms; }
.wt.is-entering .essen-lede{ animation-delay:120ms; }
.wt.is-entering .wt-measure{ animation-delay:210ms; }
.wt.is-entering .wt-ledger{ animation-delay:300ms; }
.wt.is-entering .essen-foot{ animation-delay:400ms; }

/* The .acct-reveal scroll arrival was removed with its observer: the tier it
   gated holds the only route to signing out, and nothing that critical should
   depend on a callback firing. See initAccountMotion for the full reasoning. */

/* ---------------------------------------------------------------------- *
 * K1-D -- PERMANENT ACCOUNT DELETION SHEET.
 *
 * Built entirely from primitives this product already has: the sheet, the
 * kicker, .btn-block, and the same bordered field box the name sheet uses.
 * No danger zone, no red panel, no warning iconography -- the seriousness
 * is carried by the copy and by the fact that the member has to type their
 * password, which is exactly where it belongs. The destructive button is
 * the QUIET one and "Abbrechen" is the primary: on an irreversible action
 * the safe choice should be the one that is easiest to hit.
 * ---------------------------------------------------------------------- */
.sheet-delete{ text-align:left; }
.del-title{ font-family:var(--font-d); font-weight:500; font-size:22px;
  line-height:1.18; color:var(--text-primary); margin:0 0 10px;
  text-transform:none; }
.del-body{ font-size:14px; line-height:1.6; color:var(--text-body); margin:0 0 var(--s4); }
.del-list{ margin:0 0 var(--s4); padding:0 0 0 18px; }
.del-list li{ font-size:14px; line-height:1.6; color:var(--text-body); }
.del-note{ font-size:13px; line-height:1.55; color:var(--muted); margin:0 0 var(--s5); }
.del-error{ font-size:13.5px; line-height:1.5; color:var(--action-destructive);
  margin:var(--s3) 0 var(--s4); }
/* The destructive control: reachable, never inviting. Its border only picks
   up the destructive colour on hover/focus, so the resting sheet has no red
   in it at all. */
.del-go{ border-color:var(--hairline); color:var(--action-destructive);
  margin-bottom:10px; }
.del-go:hover:not([disabled]), .del-go:focus-visible{ border-color:var(--action-destructive); }
.del-go[disabled]{ opacity:.6; cursor:default; color:var(--muted); }
.sheet-delete .btn[disabled]{ opacity:.6; cursor:default; }
.sheet-delete .report-reason-box{ margin-bottom:var(--s2); }

/* ---------------------------------------------------------------------- *
 * P3 -- THE TRANSITION SURFACE.
 *
 * The ONE transient node in the domain transition (see the P3 block in
 * app.js). It carries the Account surface's own ground and radius across the
 * screen swap and dissolves as the detail resolves, so opening a domain
 * reads as that surface opening rather than as a new page arriving.
 *
 * Nothing on it is interactive, it is hidden from assistive technology, and
 * xferKill() removes it on every render and on resize -- it can never
 * outlive the screen that created it.
 * ---------------------------------------------------------------------- */
.xfer-surface{ position:fixed; z-index:6; pointer-events:none;
  background-repeat:no-repeat; background-size:100% 100%;
  will-change:left, top, width, height, opacity; }
/* The detail screens sit above the transition surface, so the content is
   never veiled by the thing that is delivering it. */
.essen, .wt{ position:relative; z-index:7; }

.name-sheet-input, .report-reason-detail input[type="text"]{
  width:100%; background:none; border:none; padding:2px 0; color:var(--text-primary);
  font-family:var(--font-c); font-size:15px; }
.report-reason-detail input[type="text"]::placeholder{ color:var(--muted); }
.report-reason-detail input[type="text"]:focus-visible{ outline:2px solid var(--magenta); outline-offset:2px; }

@media (max-width:640px){
  /* The tall bottom band is deliberate negative space, not padding left over:
     it is the region the Signal occupies, and the only place on a 390px plate
     where a complete symbol can sit without crossing a line of type. */
  .acct-card{ margin-bottom:var(--s6); padding:var(--s5) 0 158px; }
  /* Anchored to the top-right corner on mobile rather than vertically
     centred: the card is tall and narrow here, so a centred symbol would
     sit behind the Focus row instead of beside the name. Complete and
     uncropped here too. Size and position are measured, not guessed -- at
     132px the ring ran straight through the situation headline. The card's
     one genuinely empty region is x248-352 / y186-290: right of "Passt
     nicht mehr?" (which ends at x100), right of the Focus eyebrow's ink,
     and above the Focus row itself (whose words start at y290). 104px fits
     that void whole, so the complete symbol sits in the composition's
     negative space instead of across its type. */
  /* Recomposed for the editorial name scale: the plate's genuinely empty
     region moved down and left when the name grew, so the mark moves with
     it. Complete and uncropped here too -- it is placed in the composition's
     negative space, never across its type. */
  .acct-sig{ width:138px; height:138px; top:auto; bottom:6px; right:0; }
  /* Five uppercase words are tight below ~390px; a smaller inline gap keeps
     them on one or two clean rows instead of one orphaned word. */
  .focus-sel{ gap:2px var(--s4); }
  /* Padding grows as the type shrinks so the 44px hit area survives the
     smaller mobile font size. */
  .focus-word{ font-size:14px; padding:13px 0 14px; }
  .focus-word.is-active{ font-size:18px; }
}
@media (prefers-reduced-motion:reduce){
  /* A complete, composed, static screen -- not the animated one with its
     motion removed. Every beat at its final state, the Signal at its
     specified opacity, the settings tier revealed outright, and --sig-y
     pinned to 0 so no scroll position can displace the composition. */
  .acct.is-entering .kicker,
  .acct.is-entering .acct-beat,
  .acct.is-entering .acct-thread,
  .essen.is-entering .kicker,
  .essen.is-entering .essen-title,
  .essen.is-entering .essen-lede,
  .essen.is-entering .kitchen-inst,
  .essen.is-entering .essen-foot,
  .wt.is-entering .kicker,
  .wt.is-entering .essen-title,
  .wt.is-entering .essen-lede,
  .wt.is-entering .wt-latest,
  .wt.is-entering .wt-measure,
  .wt.is-entering .wt-ledger,
  .wt.is-entering .essen-foot{
    opacity:1; transform:none; animation:none; transition:none; }
  .acct-sig{ opacity:.17; animation:none; }
  .acct-sig svg{ transform:none; }
  .acct-sig::before{ transform:none; }
  .focus-word, .focus-word::after, .focus-ok{ transition:none; }
  /* The composition stays complete and fully legible: the trace is drawn,
     the markers are on it, the instruments show the member's real values.
     Only the movement between states is removed -- including the one-shot
     recognition beat, which is an animation and nothing else. */
  .acct-thread-line{ transform:none; }
  .acct-admin[open] .acct-admin-body,
  .acct-admin[open] .acct-mod{ animation:none; }
  .acct-mod-row .btn-quiet, .dom-val, .dom-measure{ transition:none; }
  .acct-beat-situation.is-recognised .acct-anchor,
  .acct-receipt-list li.is-recognised .adapt-effect{ animation:none; }
  .acct-beat-situation .acct-lbl, .acct-beat-situation .acct-lbl::after,
  .adapt-src, .adapt-src::after, .acct-admin-mark::before,
  .acct-admin-mark::after, .dom-line .kitchen-mini-row::before,
  .kitchen-entry-name::after{ transition:none; }
  /* Slice 2 -- the instruments stay fully functional and fully legible; only
     the movement between their states is removed. The confirmation and the
     error still appear, they simply do not fade. */
  .kitchen-tick, .kitchen-tick::after, .kitchen-lab, .kitchen-step-btn,
  .kitchen-msg, .kitchen-entry-name, .kitchen-mini-b,
  .wt-index, .wt-index::after, .wt-row-del,
  .wt-commit-blade, .wt-commit-label, .wt-step, .wt-entry-field{ transition:none; }
  /* The readout still opens on engagement -- it just does so instantly. */
  .wt-measure.is-action .wt-readout{ transition:none; }
}

/* sheets */
.sheet-backdrop{ position:fixed; inset:0; background:rgba(0,0,0,0.6); z-index:20; display:flex; align-items:flex-end; justify-content:center; }
.sheet{ background:var(--surface-elevated); border-top-left-radius:20px; border-top-right-radius:20px; padding:var(--s6) var(--s5) max(var(--s6), env(safe-area-inset-bottom));
  width:100%; max-width:520px; text-align:center;
  /* Defensive only -- cancel/delete sheets never approach this; the
     report sheet, with six rows plus a possible detail field, is the one
     sheet that could theoretically outgrow a short mobile viewport.
     Internal scroll, never viewport-chrome clipping. */
  max-height:85vh; overflow-y:auto; }
.sheet-handle{ width:36px; height:4px; border-radius:2px; background:var(--hairline); margin:0 auto var(--s5); }

/* Phase 4B canonical design pass -- the report sheet now inherits
   CRVD.KITCHEN's existing single-choice grammar (the Recognition/PACE
   picker: .rec-item / .rec.has-selection) at compact sheet scale, rather
   than the radio-dot/divider-ladder language the previous pass invented.
   Deliberately NOT .rec-item directly -- that component's own spacing
   (var(--s8) = 64px between items) and type scale (a 20-26px headline
   plus a 15.5px body paragraph per row) are sized for a full-screen
   onboarding moment, not a compact moderation sheet. This reproduces the
   same INTERACTION language (always-reserved 2px left rule, hover
   previews it in --hairline, selection lights it in --magenta, once any
   selection exists every other row recedes to --text-secondary, no
   layout shift on selection, no font-weight change) at the sheet's own
   type scale -- same grammar, different dimensions, exactly as
   requested. */
.sheet-left{ text-align:left; }
.report-sheet-title{ font-family:var(--font-d); font-weight:700; text-transform:uppercase;
  font-size:19px; letter-spacing:0.01em; color:var(--text-primary); margin:0; }
.report-sheet-instruction{ font-family:var(--font-c); font-size:13.5px; line-height:1.5;
  color:var(--text-secondary); margin:6px 0 0; }

/* Phase 4B, second design pass -- bordered-choice-row system, replacing
   the flat left-rule pattern the Founder rejected in live review. A
   <button> cannot legally contain a <textarea>, so each choice is now a
   plain .report-reason-box (the bordered surface) wrapping an unstyled
   .report-reason-label button plus, only for the selected "Etwas
   anderes" row, a sibling .report-reason-detail section inside that same
   box -- the expansion reads as part of that choice's own surface, not
   an element appended after the list. */
.report-reasons{ margin:var(--s4) 0; }
.report-reasons > div + div{ margin-top:6px; }
.report-reason-box{ border:1px solid var(--border-strong); border-radius:10px;
  background:none;
  transition:border-color 200ms var(--patient), background-color 200ms var(--patient); }
/* Visible but restrained -- the border is already the resting-state
   affordance (deliberately border-strong by default, not the usual
   --hairline this app's cards start from, so all six choices read as
   obviously bordered/tappable without needing hover to reveal them).
   Hover therefore adds a distinct, separate signal: a faint neutral
   wash, never magenta -- magenta is reserved for the selected state
   alone, so hover can never be mistaken for it. */
.report-reason-box:hover{ background:rgba(255,255,255,0.03); }
.report-reason-box.is-selected{ border-color:var(--magenta); background:var(--surface-selected); }
/* Recession, never elimination -- every other choice stays fully
   readable and fully selectable once one is chosen; both its border and
   its label soften a step (border-strong -> hairline, text-primary ->
   text-secondary) rather than just one, so the receded state reads
   clearly as "still here, just not the answer" instead of vanishing. */
.report-reasons.has-selection .report-reason-box:not(.is-selected){ border-color:var(--hairline); }

.report-reason-label{ display:flex; align-items:center; width:100%; min-height:44px;
  text-align:left; background:none; border:none; padding:0 var(--s3);
  font-family:var(--font-c); font-size:14.5px; line-height:1.4; color:var(--text-primary);
  cursor:pointer; transition:color 200ms var(--patient); }
.report-reason-label:focus-visible{ outline:2px solid var(--magenta); outline-offset:2px; }
.report-reasons.has-selection .report-reason-box:not(.is-selected) .report-reason-label{ color:var(--text-secondary); }

/* Quiet internal separator between the label and the detail field --
   still visually one surface (same box, same border, same radius), just
   two zones within it. */
.report-reason-detail{ border-top:1px solid var(--hairline); padding:8px var(--s3) 10px; }
.report-reason-detail textarea{ width:100%; background:none; border:none; border-radius:0;
  padding:2px 0; color:var(--text-primary); font-family:var(--font-c); font-size:16px;
  min-height:44px; resize:vertical; }
.report-reason-detail textarea::placeholder{ color:var(--muted); }
.report-reason-detail textarea:focus-visible{ outline:2px solid var(--magenta); outline-offset:2px; }

/* Actions sit directly beside the choices, not centered below a large
   empty gap. Desktop: one left-aligned row, Melden at its own intrinsic
   width, Abbrechen immediately beside it and visually subordinate
   (.btn-quiet, not a second .btn-block). Mobile: Melden goes full width,
   Abbrechen drops to its own quiet line beneath -- a pure CSS breakpoint,
   no JS viewport branch. */
.report-actions{ display:flex; align-items:center; gap:var(--s4); margin-top:var(--s4); }
@media (max-width:640px){
  .report-actions{ flex-direction:column; align-items:flex-start; gap:var(--s3); }
  .report-actions .btn-primary{ width:100%; }
}

@media (prefers-reduced-motion:reduce){
  .pub-reveal, .screen-anim, .presence-dot, .pub-hero-signal, .entry-signal-inner, .ceremony-signal-inner, .pub-held-story-signal-inner, .app-topbar-soft{ transition-duration:0.01ms !important; animation-duration:0.01ms !important; }
  .pub-section-inner.pub-reveal > *{ transition-duration:0.01ms !important; transition-delay:0ms !important; animation-duration:0.01ms !important; }
  /* Held stage UN-STACKED. initHeldStage() never runs under reduced
     motion (heroStackReady() gates on the same query), so without this
     the layers would stay absolutely stacked and unreachable inside
     ~300vh of dead scroll. Return both scenes to ordinary document
     flow: wrapper and stage collapse to auto height, layers become
     static blocks read top to bottom. Fully readable, nothing hidden,
     no dead range. */
  .pub-held-wrap{ height:auto; }
  .pub-held-stage{ position:static; height:auto; overflow:visible; filter:none; }
  .pub-held-layer{ position:static; inset:auto; padding:clamp(96px,14vh,168px) max(24px, env(safe-area-inset-right)); }
  /* Sequence UN-STACKED (Held-Stage Story Refinement v2). Same
     reasoning as the Frage un-stack above: initHeldStage never runs
     here, so the three beats and the Signal must not remain absolutely
     positioned/opacity:0 (unreachable). All three beats become ordinary
     stacked blocks, read top to bottom in the same order they'd appear
     in the animated sequence -- no hold/recede choreography to
     reproduce statically, just full readability at once. Centered, not
     left-aligned: this matches the same un-stacked convention every
     other section of this page falls back to under reduced motion
     (Frage included) -- the animated version's asymmetric editorial
     composition is a motion-era effect, not something reduced motion
     needs to preserve. */
  .pub-held-signal-sequence{ position:static; inset:auto;
    padding:clamp(56px,9vh,96px) max(24px, env(safe-area-inset-right)) clamp(96px,14vh,168px);
    display:flex; flex-direction:column; gap:clamp(36px,5.5vh,56px); align-items:center; }
  .pub-held-story-beat{ position:static; inset:auto; padding:0; }
  .pub-held-story-copy{ margin-right:0; text-align:center; }
  .pub-held-story-copy > *{ opacity:1; }
  .pub-held-story-subline{ margin-left:auto; margin-right:auto; }
  /* Progress dots serve no purpose without scroll-driven state --
     purely decorative chrome, not content, so hiding them here doesn't
     conflict with "no hidden content" (that principle governs the
     statements and the Signal, not a UI indicator). */
  .pub-held-progress{ display:none; }
  /* THE SIGNAL under reduced motion: shown once, un-stacked into the
     same static flow as the statements above it, at its settled
     scale/opacity, no sheen. --sheen-op's own var(--sheen-op,0)
     fallback already resolves to 0 whenever GSAP never runs (this
     media query's whole branch), so no explicit override is needed to
     suppress the sheen. "No hidden content." */
  .pub-held-story-signal-inner{ transform:none; animation:none; opacity:1; --so:1; --sc:1; }
}

/* ---------- Tablet ---------- */
@media (min-width:641px){
  .app-content-inner{ max-width:600px; }
  /* .quicklinks' 3-column override was sized for the Dashboard's former
     3-item quicklink row (Rezepte/Community/PL8); with exactly 2 items
     since Dashboard Step 4, it left an empty third cell at tablet widths.
     .quicklinks has no other markup consumer in the file, so removing the
     override here is safe -- the base 2-column rule (already re-asserted
     at >=1024px) now applies unbroken from 0 up. */
  .bt-grid{ grid-template-columns:1fr 1fr; }
}

/* ---------- Desktop: sidebar navigation, wide grids, no bottom nav ---------- */
@media (min-width:1024px){
  .app-bottomnav{ display:none; }
  .app-sidebar{ display:flex; flex-direction:column; width:232px; flex-shrink:0; border-right:1px solid var(--hairline);
    padding:var(--s6) var(--s4); gap:4px; position:sticky; top:calc(65px + var(--preview-banner-h)); align-self:flex-start; height:calc(100vh - 65px - var(--preview-banner-h)); }
  .app-sidebar button{ display:flex; align-items:center; gap:12px; background:none; border:none; color:var(--muted); font-size:14px;
    padding:12px 14px; border-radius:10px; cursor:pointer; text-align:left; }
  .app-sidebar button svg{ width:19px; height:19px; }
  .app-sidebar button.active{ background:var(--surface); color:var(--text-primary); }
  .app-sidebar button.active svg{ color:var(--magenta); }
  .app-content{ padding:var(--s6) var(--s7) var(--s8); }
  .app-content-inner{ max-width:1100px; }
  .dash-grid{ grid-template-columns:2fr 1fr; align-items:start; }
  .quicklinks{ grid-template-columns:1fr 1fr; }
  .bt-grid{ grid-template-columns:1fr 1fr; max-width:760px; }
  .pl8-feature-list{ grid-template-columns:1fr 1fr; }
}

/* ---- COMMUNITY MICRO-FEEDBACK ---------------------------------------
   One question per interaction: does the member immediately know it
   landed? Answered with the smallest device that answers it -- a press
   state, a tonal change, a word, a single 260ms pulse. Not with the same
   animation everywhere. */
/* ANERKENNEN, confirmed. A single restrained ring of energy leaving the
   dot: acknowledgement received, not a score celebrated. 300ms, once,
   never on reversal. */
@keyframes cfAckPulse{
  0%   { opacity:.55; transform:translate(-50%,-50%) scale(.5); }
  70%  { opacity:.14; }
  100% { opacity:0;   transform:translate(-50%,-50%) scale(3.1); }
}
.cf-ack{ position:relative; }
.cf-ack .cf-pulse{ position:absolute; left:11px; top:50%; width:10px; height:10px;
  margin:0; border-radius:50%; pointer-events:none;
  background:radial-gradient(circle, rgba(226,25,181,.55) 0%, rgba(226,25,181,0) 70%);
  transform:translate(-50%,-50%) scale(.5); opacity:0;
  animation:cfAckPulse 300ms var(--patient); }
/* Reversal is deliberately quieter: a contraction and back to outline. */
@keyframes cfAckOff{ 0%{ transform:scale(1); } 45%{ transform:scale(.72); } 100%{ transform:scale(1); } }
.cf-dot.is-off{ animation:cfAckOff 220ms var(--patient); }

/* Press states for the rest of the room's controls. Nothing animated on
   arrival -- only in response to a finger. */
.cf-share:active{ transform:scale(.975); }
.cf-share{ transition:opacity 220ms var(--patient), transform 90ms var(--patient); }
.cf-opt:active, .cf-chip:active, .cf-t:active, .cf-disc-trigger:active,
.cf-sec:active, .cf-mtag:active{ opacity:.62; transition-duration:60ms; }
/* A report that has landed is a fact, not a control: it changes word and
   tone in place and stops being pressable. */
.cf-sec.is-static{ color:var(--text-secondary); }
/* A contribution leaving the room does so visibly rather than blinking
   out of existence. */
@keyframes cfPostOut{ to{ opacity:0; transform:translateY(-6px); } }
.cf-post.is-leaving{ animation:cfPostOut 220ms var(--patient) forwards; }

/* ======================================================================
   COMMUNITY — TISCHE
   ======================================================================
   Community Home is a place, not a dashboard. Each table states its name,
   what it is for, and the last thing said there. No post counts: a number
   tells a member nothing about whether they want to sit down, and this
   client honestly cannot know the total anyway.
   ====================================================================== */
.ct-lede{ font-size:13px; line-height:1.55; color:var(--muted); margin:8px 0 0;
  max-width:26em; }

/* THE SIGNAL, STRUCTURAL. On Home it is not decoration parked in leftover
   space: the tables begin where its light does. Larger, lower, and the
   list is deliberately laid over its lower arc so the rooms read as
   emerging from it rather than sitting beside it. Never a radial menu --
   the mark is belonging, not navigation chrome. */
.ct-home .ct-sig{ width:230px; height:230px; top:-26px; right:-56px; opacity:.14; }
.ct-tables{ list-style:none; margin:var(--s6) 0 0; padding:0; position:relative; }
.ct-tables::before{ content:''; position:absolute; z-index:0; pointer-events:none;
  inset:-40px -24px 20px -20px;
  background:radial-gradient(58% 42% at 76% 0%,
    rgba(226,25,181,0.085) 0%, rgba(226,25,181,0) 72%);
  -webkit-mask-image:linear-gradient(180deg, #000 0%, #000 42%, transparent 92%);
  mask-image:linear-gradient(180deg, #000 0%, #000 42%, transparent 92%); }
.ct-tables > li{ position:relative; z-index:1; }
.ct-tables > li + li{ margin-top:2px; }

/* A table is a place to walk into, so its whole area is the control --
   but it is ruled by space, not boxed by a card. */
.ct-table{ display:block; width:100%; text-align:left; background:none;
  border:none; cursor:pointer; font-family:inherit;
  padding:18px 14px 20px; margin:0 -14px; border-radius:12px; min-height:44px;
  transition:background-color 240ms var(--patient); }
.ct-table:hover{ background:rgba(245,241,236,0.030); }
.ct-table:focus-visible{ outline:2px solid var(--magenta); outline-offset:-2px; }
.ct-head-row{ display:flex; align-items:baseline; gap:12px; }
.ct-name{ font-family:var(--font-d); font-weight:500; font-size:23px;
  line-height:1.15; letter-spacing:0.004em; color:var(--text-primary);
  transition:color 220ms var(--patient); }
.ct-table:hover .ct-name{ color:#FFFDFA; }
.ct-purpose{ display:block; margin-top:5px; font-size:12.5px; line-height:1.5;
  color:var(--muted); }
/* Activity, relative and wordless: three marks that fill as a table gets
   busier. Not a statistic, not a badge, not a number. */
.ct-pulse{ display:inline-flex; align-items:flex-end; gap:2px; flex:none; }
.ct-pulse i{ display:block; width:3px; border-radius:1px;
  background:rgba(245,241,236,0.16); }
.ct-pulse i:nth-child(1){ height:5px; }
.ct-pulse i:nth-child(2){ height:8px; }
.ct-pulse i:nth-child(3){ height:11px; }
.ct-pulse-1 i:nth-child(1),
.ct-pulse-2 i:nth-child(1), .ct-pulse-2 i:nth-child(2),
.ct-pulse-3 i{ background:var(--text-secondary); }
.ct-pulse-3 i:nth-child(3){ background:var(--magenta); opacity:.62; }
.ct-last{ display:block; margin-top:12px; font-size:13px; line-height:1.5;
  color:var(--text-secondary); }
.ct-last-w{ color:var(--muted); }
.ct-last-w::after{ content:' · '; color:#4E4853; }
.ct-last-empty{ color:var(--muted); font-style:italic; }

/* ---- INSIDE A TABLE ------------------------------------------------- */
.ct-room-head{ padding-top:18px; }
.ct-back{ display:inline-flex; align-items:center; gap:8px; background:none;
  border:none; padding:11px 0 12px; margin:-6px 0 6px; min-height:44px;
  font-family:var(--font-c); font-size:12px; letter-spacing:0.02em;
  color:var(--muted); cursor:pointer; transition:color 200ms var(--patient); }
.ct-back:hover{ color:var(--text-primary); }
.ct-back:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }
.ct-back-i{ width:7px; height:7px; flex:none; border-left:1.4px solid currentColor;
  border-bottom:1.4px solid currentColor; transform:rotate(45deg); }
.ct-room-title{ font-size:clamp(25px, 6.6vw, 31px); max-width:9em; }
/* The table a global result belongs to: a word, and the way there. */
.cf-mtable{ background:none; border:none; padding:0; font-family:inherit;
  display:inline-flex; align-items:center; min-height:44px; margin:-15px 0;
  font-size:11.5px; color:var(--text-secondary); cursor:pointer;
  text-decoration:underline; text-decoration-color:rgba(245,241,236,.22);
  text-underline-offset:3px; transition:color 180ms var(--patient); }
.cf-mtable:hover{ color:var(--text-primary); }
.cf-mtable:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }

@media (min-width:641px){
  .ct-home .ct-sig{ width:280px; height:280px; }
  .ct-name{ font-size:26px; }
}
@media (min-width:1024px){
  .ct-tables, .ct-home .cf-head{ max-width:35rem; }
  .ct-home .ct-sig{ width:300px; height:300px; top:150px; right:auto;
    left:calc(35rem + 3rem); opacity:.15; }
  .ct-tables::before{ inset:-40px -320px 20px -20px;
    background:radial-gradient(46% 52% at 88% 40%,
      rgba(226,25,181,0.085) 0%, rgba(226,25,181,0) 74%); }
  .ct-name{ font-size:28px; }
}
/* Reduced motion: the swap between house and table is a cut, not a slide.
   The View Transition path is skipped entirely by canAnimate(). */
@media (prefers-reduced-motion: reduce){
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*){
    animation:none !important; }
}

/* ======================================================================
   COMMUNITY CUTOVER — Table deck · Conversations · Dialog contract
   ======================================================================
   Community → Table → Conversation → Contribution against the real
   conversation contracts. The deck is ported from the approved Table
   Stories review (reviews/community-table-stories-clean.html); the rest
   reuses the cf-* room grammar. Magenta stays on publishing, the Signal's
   material, the selected nav and focus. */

/* ---- TABLE STORIES DECK ------------------------------------------------ */
/* PHYSICAL IPHONE POLISH. The deck used to centre a 62vw Signal at 71% of
   the stage, which puts its right edge at ~102% of the viewport on every
   phone: the locked geometry was cut by the stage clip (8px at 402, 17px at
   320). The mark now keeps a guaranteed inset (--ts-edge, which also covers
   the depth echo's few percent beyond the ring), and the diameter is scaled
   to the composition rather than to the viewport alone. */
.ts{ --ts-d:clamp(172px, 50vw, 216px);
  --ts-edge:calc(max(18px, env(safe-area-inset-right)) + var(--ts-d) * .04);
  --ts-pad:max(16px, env(safe-area-inset-left)); --ts-gap:16px; --ts-item:86.3%;
  position:relative; margin:4px 0 0; }
@media (min-width:641px){ .ts{ --ts-d:320px; } }
@media (min-width:1024px){ .ts{ --ts-d:352px; --ts-pad:0px; --ts-gap:48px; --ts-item:35rem; } }
@media (min-width:1280px){ .ts{ --ts-d:384px; } }
/* The stage follows its content: the ring plus a breath, never a fixed 150px
   of empty band above and below the table copy. 17rem is the tallest story
   (two-line name, two-line purpose, two-line preview, "Platz nehmen"). */
.ts-stage{ position:relative; overflow:hidden; height:max(calc(var(--ts-d) + 72px), 17rem);
  margin-left:calc(-1 * max(16px, env(safe-area-inset-left)));
  margin-right:calc(-1 * max(16px, env(safe-area-inset-right))); }
@media (min-width:641px){ .ts-stage{ height:calc(var(--ts-d) + 96px); } }
@media (min-width:1024px){ .ts-stage{ margin-left:0; margin-right:0; height:calc(var(--ts-d) + 96px); } }
.ts-sig{ --ts-rot:0deg; --ts-resp:0;
  position:absolute; z-index:1; pointer-events:none;
  width:var(--ts-d); height:var(--ts-d);
  left:min(calc(71% - var(--ts-d) / 2), calc(100% - var(--ts-d) - var(--ts-edge)));
  top:calc(50% - var(--ts-d) / 2); }
@media (min-width:1024px){ .ts-sig{ left:min(38rem, calc(100% - var(--ts-d) - 40px)); } }
.ts-sig > *{ position:absolute; inset:0; }
.ts-sig::before{ content:''; position:absolute; inset:-22%; border-radius:50%; opacity:calc(1 - var(--mute, 0) * .45);
  background:radial-gradient(closest-side, rgba(226,25,181,.11) 0%,
    rgba(226,25,181,.04) 46%, rgba(226,25,181,0) 78%); }
.ts-rot{ position:absolute; inset:0; transform:rotate(var(--ts-rot)); will-change:transform; }
.ts-ring{ position:absolute; inset:0; background:var(--signal-satin-gradient);
  -webkit-mask-image:var(--signal-mask-src); mask-image:var(--signal-mask-src);
  -webkit-mask-size:100% 100%; mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat; }
.ts-obj .ts-ring{ opacity:calc(1 - var(--mute, 0) * .38); }
.ts-hi .ts-rot{
  -webkit-mask-image:conic-gradient(from 0deg, transparent 0deg 15deg, #000 50deg 80deg, transparent 115deg);
  mask-image:conic-gradient(from 0deg, transparent 0deg 15deg, #000 50deg 80deg, transparent 115deg); }
.ts-hi .ts-ring{ filter:brightness(1.24) saturate(1.04);
  opacity:calc(.66 + var(--ts-resp) * .2); }
.ts-shade .ts-rot{
  -webkit-mask-image:conic-gradient(from 0deg, transparent 0deg 175deg, #000 225deg 265deg, transparent 315deg);
  mask-image:conic-gradient(from 0deg, transparent 0deg 175deg, #000 225deg 265deg, transparent 315deg); }
.ts-shade .ts-ring{ background:#060408; opacity:.38; }
.ts-env .ts-ring{ background:none; opacity:.22; }
.ts-env .ts-ring::before{ content:''; position:absolute; inset:0;
  transform:rotate(calc(-1 * var(--ts-rot)));
  background:radial-gradient(72% 72% at 80% 84%, rgba(240,90,205,.9) 0%, rgba(240,90,205,0) 62%); }
.ts-depth{ inset:-12%; opacity:.34;
  background:radial-gradient(closest-side, rgba(226,25,181,0) 78%,
    rgba(226,25,181,.15) 86%, rgba(226,25,181,0) 95%);
  -webkit-mask-image:conic-gradient(from 0deg, transparent 0deg 70deg, #000 130deg 200deg, transparent 260deg);
  mask-image:conic-gradient(from 0deg, transparent 0deg 70deg, #000 130deg 200deg, transparent 260deg); }
.ts-sheen .ts-ring{ background:none; }
.ts-sheen .ts-ring::after{ content:''; position:absolute; top:-30%; bottom:-30%;
  width:34%; left:-40%; opacity:0; transform:skewX(-14deg);
  background:linear-gradient(100deg, rgba(255,255,255,0) 0%,
    rgba(255,255,255,.32) 48%, rgba(255,255,255,0) 100%); }
.ts.is-arrived .ts-sheen .ts-ring::after{ animation:tsSheen 1150ms var(--patient) 280ms 1 both; }
@keyframes tsSheen{ 0%{ left:-40%; opacity:0; } 14%{ opacity:1; } 100%{ left:104%; opacity:0; } }
.ts-track{ position:absolute; inset:0; z-index:2; display:flex; gap:var(--ts-gap);
  padding:0 var(--ts-pad); scroll-padding:0 var(--ts-pad);
  overflow-x:auto; overflow-y:hidden; scroll-snap-type:x mandatory;
  overscroll-behavior-x:contain; -webkit-overflow-scrolling:touch; scrollbar-width:none; }
.ts-track::-webkit-scrollbar{ display:none; }
.ts-track::after{ content:''; flex:none; width:calc(100% - var(--ts-item)); }
.ts-track:focus-visible{ outline:2px solid var(--magenta); outline-offset:-2px; }
.ts-story{ flex:none; width:var(--ts-item); height:100%;
  scroll-snap-align:start; scroll-snap-stop:always;
  display:flex; align-items:center; will-change:opacity; }
.ts-copy{ width:100%; max-width:35rem; }
.ts-name{ display:block; margin:0;
  font-family:var(--font-d); font-weight:500; text-transform:none;
  font-size:clamp(28px, 8.2vw, 34px); line-height:1.06; letter-spacing:.004em;
  color:var(--text-primary); }
.ts-nb{ white-space:nowrap; }
@media (min-width:1024px){ .ts-name{ font-size:clamp(46px, 3.9vw, 56px); } }
.ts-purpose{ margin:10px 0 0; font-size:14.5px; line-height:1.5;
  color:var(--text-secondary); max-width:24em; }
/* Two lines at most: a long opening must not push "Platz nehmen" out of the
   stage it is clipped to. */
.ts-copy .ct-last{ margin-top:14px; max-width:26em;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow:hidden; }
.ts-enter{ display:inline-flex; align-items:center; gap:10px; min-height:44px;
  margin:12px 0 0; padding:0; background:none; border:0; cursor:pointer;
  font-family:var(--font-d); font-weight:500; font-size:14px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--text-primary); }
.ts-enter svg{ width:14px; height:14px; transform:rotate(180deg); color:var(--text-secondary); }
.ts-enter:focus-visible{ outline:2px solid var(--magenta); outline-offset:4px; }
.ts-nav{ display:flex; align-items:center; justify-content:center; gap:8px; margin:6px 0 0; }
.ts-dots{ display:flex; align-items:center; }
.ts-dot{ width:26px; height:44px; padding:0; border:0; background:none; cursor:pointer;
  display:inline-flex; align-items:center; justify-content:center; }
.ts-dot i{ display:block; width:6px; height:6px; border-radius:50%;
  background:var(--text-secondary); opacity:var(--o, .28); }
.ts-dot:focus-visible{ outline:2px solid var(--magenta); outline-offset:-10px; border-radius:8px; }
.ts-step{ display:none; }
@media (min-width:1024px){
  .ts-nav{ justify-content:flex-start; gap:14px; margin-top:12px; }
  .ts-step{ display:flex; width:44px; height:44px; }
  .ts-next svg{ transform:rotate(180deg); }
  .ts-step[disabled]{ opacity:.35; cursor:default; }
}

/* ---- A TABLE ------------------------------------------------------------ */
.cm-start-row{ margin:var(--s5) 0 var(--s3); }
/* A secondary act, not the publish act: outlined and neutral. Magenta is
   spent inside the dialog, on the actual "Gespräch beginnen". */
.cm-start{ display:inline-flex; align-items:center; min-height:44px; padding:11px 20px;
  border-radius:999px; background:none; border:1px solid var(--border-strong);
  color:var(--text-primary); font-family:var(--font-c); font-size:13.5px; font-weight:600;
  cursor:pointer; transition:border-color 200ms var(--patient), background-color 200ms var(--patient); }
.cm-start:hover{ background:rgba(245,241,236,.04); border-color:var(--text-secondary); }
.cm-start:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }
.cm-list{ list-style:none; margin:var(--s3) 0 0; padding:0; }
.cm-row + .cm-row{ margin-top:2px; }
.cm-conv{ display:block; width:calc(100% + 28px); text-align:left; background:none; border:none;
  cursor:pointer; font-family:inherit; padding:16px 14px 17px; margin:0 -14px;
  border-radius:12px; min-height:44px; transition:background-color 240ms var(--patient); }
.cm-conv:hover{ background:rgba(245,241,236,.03); }
.cm-conv:focus-visible{ outline:2px solid var(--magenta); outline-offset:-2px; }
.cm-row-open{ display:block; font-family:var(--font-d); font-weight:400; font-size:19px;
  line-height:1.3; letter-spacing:.004em; color:var(--text-primary); overflow-wrap:anywhere; }
.cm-row-open.is-deleted, .cm-opening.is-deleted{ font-family:var(--font-c); font-style:italic;
  font-weight:400; color:var(--text-secondary); }
.cm-row-meta{ display:flex; flex-wrap:wrap; align-items:center; gap:4px 7px; margin-top:8px; }
.cm-who{ display:inline-flex; align-items:center; gap:7px; }
.cm-anon-own{ font-size:11.5px; color:var(--muted); }
.cm-flag{ font-size:11.5px; color:var(--text-secondary); }
.cm-row-tomb{ padding:14px 0; }
.cm-row-tomb .cf-tomb-line{ margin:0; }
.cm-note{ font-size:12.5px; line-height:1.5; color:var(--text-secondary); margin:12px 0 0; }

/* ---- A CONVERSATION ----------------------------------------------------- */
.cm-opening{ font-family:var(--font-d); font-weight:500; text-transform:none;
  font-size:clamp(24px, 6.4vw, 32px); line-height:1.12; letter-spacing:.004em;
  color:var(--text-primary); margin:6px 0 12px; max-width:22em; overflow-wrap:anywhere; }
.cm-view h1:focus{ outline:none; }
.cm-starter{ margin-bottom:0; }
.cm-conv-act{ margin-top:12px; }
.cm-posts{ margin-top:var(--s5); }
.cm-name-l{ margin-top:14px; }
.cm-msg{ font-size:13px; line-height:1.5; color:var(--text-primary); margin:10px 0 0;
  padding-left:10px; border-left:2px solid var(--action-destructive); }
.cm-msg[hidden], .cm-dlg-error[hidden]{ display:none; }
/* Contrast fix (cutover): unselected choices were #655E69 (~3:1). */
.cf-opt{ color:var(--muted); }

/* ---- ARCHIVE ------------------------------------------------------------ */
.cm-archive{ margin-top:var(--s7); max-width:35rem; }
.cm-archive-h{ font-family:var(--font-c); font-size:11px; font-weight:600; letter-spacing:.2em;
  text-transform:uppercase; color:var(--muted); margin:0 0 6px; }
.cm-archive-l{ font-size:13px; line-height:1.5; color:var(--text-secondary); margin:0 0 4px; }
.cm-archive-list{ list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:0 22px; }
.cm-archive-link{ background:none; border:none; padding:0; min-height:44px; cursor:pointer;
  font-family:var(--font-d); font-size:15px; letter-spacing:.03em; color:var(--text-secondary);
  text-decoration:underline; text-decoration-color:rgba(245,241,236,.22); text-underline-offset:4px; }
.cm-archive-link:hover{ color:var(--text-primary); }
.cm-archive-link:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }

/* ---- PL8 THRESHOLD — the sixth position of the Table Stories -------------
   Approved in reviews/pl8-seat-threshold.html (PL8_SEAT_THRESHOLD_EXPLORATION
   §2–§5), within Design Constitution 2.6 / 3.2 (DH-14). After Table 5 the
   Signal completes its turn and the Seat is the access point. Not a Table.
   Free: lock at the Seat, bolt across the opening, cold Seat, quieter ring
   -- and the words "Zugang gesperrt". PL8 / Founder: the Seat lit, no lock.
   Every cue is neutral or the canonical Satin material: no red, no second
   accent. Community Home carries no Nebula at rest; its one field exists
   only for the 800ms entry. */
.ts-sig{ --lock:0; --bolt:0; --cold:0; --seat-lit:0; --mute:0;
  --pl8-seat-mask:url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><rect x="173.54" y="139.50" width="14" height="14" fill="white" transform="rotate(30 180.54 146.50)"/></svg>'); }
.ts-bolt{ opacity:calc(var(--bolt) * .72); }
.ts-bolt svg{ position:absolute; inset:0; width:100%; height:100%; }
.ts-bolt path{ fill:none; stroke:var(--text-secondary); stroke-width:2.6; }
.ts-seat .ts-ring, .ts-seatcold .ts-ring{ -webkit-mask-image:var(--pl8-seat-mask); mask-image:var(--pl8-seat-mask); }
.ts-seat .ts-ring{ filter:brightness(calc(1 + var(--seat-lit) * .32)) saturate(1.05); opacity:var(--seat-lit); }
.ts-seatcold .ts-ring{ filter:brightness(.34) saturate(.5); opacity:var(--cold); }
.ts-lockpos{ position:absolute; left:77.5%; top:66%; width:11%; aspect-ratio:1 / 1;
  transform:translate(-50%, -50%) rotate(calc(-1 * var(--ts-rot))); }
.ts-lock{ display:block; width:100%; height:100%; overflow:visible; color:var(--text-primary);
  opacity:calc(var(--lock) * .94); transform:scale(calc(.84 + var(--lock) * .16)); }
.ts-dot-seat i{ border-radius:0; width:5px; height:5px; }

/* Threshold copy — neutral. Access state is carried by words, not colour. */
.th-status{ margin:0 0 10px; font-size:10px; font-weight:600; letter-spacing:.2em;
  text-transform:uppercase; color:var(--text-secondary); }
.th-note{ margin:12px 0 0; font-size:13px; line-height:1.5; color:var(--muted); }
.th-more{ display:inline-flex; align-items:center; gap:8px; min-height:44px; margin:6px 0 0;
  padding:0; background:none; border:0; cursor:pointer; font-family:inherit; font-size:13px; color:var(--text-secondary); }
.th-more svg{ width:13px; height:13px; transform:rotate(180deg); }
.th-more:focus-visible{ outline:2px solid var(--magenta); outline-offset:4px; }
/* The Seat is also the control (entitled only): it covers Seat and lock. */
.th-seat{ --hit:max(56px, calc(var(--ts-d) * .22));
  position:absolute; z-index:3; width:var(--hit); height:var(--hit); padding:0; border:0;
  background:none; border-radius:50%; cursor:pointer; visibility:hidden;
  left:calc(min(calc(71% - var(--ts-d) / 2), calc(100% - var(--ts-d) - var(--ts-edge))) + var(--ts-d) * .849 - var(--hit) / 2);
  top:calc(50% - var(--ts-d) / 2 + var(--ts-d) * .701 - var(--hit) / 2); }
@media (min-width:1024px){ .th-seat{ left:calc(min(38rem, calc(100% - var(--ts-d) - 40px)) + var(--ts-d) * .849 - var(--hit) / 2); } }
.ts.at-th .th-seat{ visibility:visible; }
.th-seat:focus-visible{ outline:2px solid var(--magenta); outline-offset:0; }

/* ---- PL8 ENTRY — the Round 5 cinematic transit ----------------------------
   Design Constitution 3.2 / 3.3 / 2.6 (DH-15); layers and light values from
   reviews/pl8-seat-threshold.html. A fixed layer below the topbar and bottom
   nav, created only for the entitled entry and removed when PL8 is painted.
   The Signal copy is transformed as one object; its layers are the stage
   Signal's own plus the review's energy and Nebula light. Nebula stays light:
   no text, control, border or surface colour. */
.p8-transit{ position:fixed; inset:0; z-index:9; pointer-events:none; overflow:hidden; }
.p8-transit-in{ position:absolute; inset:0;
  -webkit-mask-image:radial-gradient(circle at var(--cx, 50%) var(--cy, 50%), #000 0, #000 max(0px, calc(var(--ir, 0px) - 28px)), transparent var(--ir, 0px));
  mask-image:radial-gradient(circle at var(--cx, 50%) var(--cy, 50%), #000 0, #000 max(0px, calc(var(--ir, 0px) - 28px)), transparent var(--ir, 0px)); }
.p8-transit-in > .app-content{ position:absolute; margin:0; will-change:transform; }
.p8-transit .ts-sig{ left:var(--tl); top:var(--tt); width:var(--tw); height:var(--tw);
  transform:translate(var(--tx, 0px), var(--ty, 0px)) scale(var(--tsc, 1)); will-change:transform; }
.p8-transit .ts-sig::before{ opacity:calc(var(--tring, 1) * .8); }
.p8-transit .ts-body{ opacity:var(--tring, 1); }
.p8-transit .ts-body > *{ position:absolute; inset:0; }
.p8-transit .ts-sig > .ts-halo2{ inset:-22%; border-radius:50%;
  opacity:calc((var(--energ, 0) + var(--depthx, 0) * .5) * var(--tring, 1) * .8);
  background:radial-gradient(closest-side, rgba(226,25,181,.2) 0%, rgba(226,25,181,.08) 46%, rgba(226,25,181,0) 78%); }
.p8-transit .ts-sig > .ts-well{ inset:7%; border-radius:50%; opacity:var(--well, 0);
  background:radial-gradient(closest-side, rgba(9,7,11,.96) 0%, rgba(9,7,11,.72) 62%, rgba(9,7,11,0) 100%); }
.p8-transit .ts-energy .ts-rot{
  -webkit-mask-image:conic-gradient(from calc((120 - var(--spread, 0) - 14) * 1deg), transparent 0deg, #000 14deg, #000 calc((var(--spread, 0) * 2 + 14) * 1deg), transparent calc((var(--spread, 0) * 2 + 28) * 1deg));
  mask-image:conic-gradient(from calc((120 - var(--spread, 0) - 14) * 1deg), transparent 0deg, #000 14deg, #000 calc((var(--spread, 0) * 2 + 14) * 1deg), transparent calc((var(--spread, 0) * 2 + 28) * 1deg)); }
.p8-transit .ts-energy .ts-ring{ filter:brightness(1.3) saturate(1.05); opacity:var(--energy, 0); }
.p8-transit .ts-seatpool i{ position:absolute; left:90.27%; top:73.25%; width:36%; height:36%;
  transform:translate(-50%, -50%); border-radius:50%; opacity:var(--pool, 0);
  background:radial-gradient(closest-side, rgba(226,25,181,.34) 0%, rgba(226,25,181,.12) 46%, rgba(226,25,181,0) 100%); }
.p8-transit .ts-sheen .ts-ring::after{ animation:none; }
.p8-transit .ts-sheen2 .ts-ring{ background:none; }
.p8-transit .ts-sheen2 .ts-ring::after{ content:''; position:absolute; top:-30%; bottom:-30%;
  width:34%; left:calc(-40% + var(--sh2, 0) * 144%); opacity:var(--sh2o, 0); transform:skewX(-14deg);
  background:linear-gradient(100deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.32) 48%, rgba(255,255,255,0) 100%); }
/* Nebula — depth inside the opening (it briefly outlasts the ring), the
   light source behind the Seat, reflection, inner edge, refraction. */
.p8-transit .ts-sig > .ts-ndepth{ inset:7%; border-radius:50%; overflow:hidden;
  opacity:calc(var(--ndepth, 0) * var(--ndt, 1) * .7); }
.p8-transit .ts-ndepth i{ position:absolute; inset:0; display:block; border-radius:50%; }
.p8-transit .ts-ndepth .nd-far{ transform:scale(var(--npar, 1));
  background:radial-gradient(circle at var(--ndx, 74%) var(--ndy, 70%), rgba(72,56,190,.36) 0%, rgba(44,34,130,.20) 24%, rgba(18,14,44,.10) 46%, rgba(18,14,44,0) 70%); }
.p8-transit .ts-ndepth .nd-haze{ inset:-10%;
  background:radial-gradient(60% 50% at 36% 32%, rgba(118,98,255,.12) 0%, rgba(118,98,255,0) 70%),
    radial-gradient(44% 40% at 66% 70%, rgba(72,56,190,.14) 0%, rgba(72,56,190,0) 70%); }
.p8-transit .ts-ndepth .nd-rim{ background:radial-gradient(closest-side, rgba(226,25,181,0) 72%, rgba(226,25,181,.12) 93%, rgba(226,25,181,0) 100%); }
.p8-transit .ts-sig > .ts-nkey{ inset:auto; left:90.27%; top:73.25%; width:30%; height:30%;
  transform:translate(-50%, -50%); border-radius:50%; opacity:var(--nkey, 0);
  background:radial-gradient(closest-side, rgba(150,132,255,.40) 0%, rgba(118,98,255,.16) 38%, rgba(118,98,255,0) 100%); }
.p8-transit .ts-nrefl{ opacity:var(--nrefl, 0); mix-blend-mode:screen; filter:blur(calc(var(--vel, 0) * 1.4px)); }
.p8-transit .ts-nrefl .ts-rot{
  -webkit-mask-image:conic-gradient(from 0deg, transparent 0deg, #000 20deg, transparent 50deg, transparent 92deg, rgba(0,0,0,.75) 118deg, transparent 148deg, transparent 196deg, #000 226deg, transparent 258deg, transparent 298deg, rgba(0,0,0,.65) 322deg, transparent 350deg);
  mask-image:conic-gradient(from 0deg, transparent 0deg, #000 20deg, transparent 50deg, transparent 92deg, rgba(0,0,0,.75) 118deg, transparent 148deg, transparent 196deg, #000 226deg, transparent 258deg, transparent 298deg, rgba(0,0,0,.65) 322deg, transparent 350deg); }
.p8-transit .ts-nrefl .ts-ring{ background:none; }
.p8-transit .ts-nrefl .ts-ring::before{ content:''; position:absolute; inset:0; transform:rotate(calc(-1 * var(--ts-rot)));
  background:radial-gradient(54% 54% at var(--nlx, 88%) var(--nly, 80%), rgba(196,184,255,.95) 0%, rgba(132,112,255,.5) 34%, rgba(118,98,255,0) 72%); }
.p8-transit .ts-nrim{ opacity:var(--nrim, 0); mix-blend-mode:screen;
  -webkit-mask-image:radial-gradient(closest-side, transparent 85%, #000 86.5%, rgba(0,0,0,.45) 89.5%, transparent 92.5%);
  mask-image:radial-gradient(closest-side, transparent 85%, #000 86.5%, rgba(0,0,0,.45) 89.5%, transparent 92.5%); }
.p8-transit .ts-nrim .ts-ring{ background:#9684FF; }
.p8-transit .ts-nfringe{ opacity:var(--nfringe, 0); transform:scale(1.016); mix-blend-mode:screen; }
.p8-transit .ts-nfringe .ts-ring{ background:#7662FF; }
@media (prefers-reduced-motion: reduce){ .p8-transit{ display:none; } }

/* ---- PL8 COMMUNITY — the stage and Testküche -----------------------------
   Inside PL8 exactly one contained Nebula field sits behind the Signal, and
   only on belonging moments (the Testküche root, the 2. Fassung, Erprobt) --
   never behind reading or writing surfaces, never in a control, border or
   state. Magenta stays the Signal's; every Testküche control is neutral. */

/* The stage: the Signal as the anchor of PL8 (Constitution 1.3: dominant
   only where the surface is about belonging). Larger than on any utility
   surface, always whole -- the Seat, its meaning, is never cropped -- and
   tucked up beside the short back link so the Testküche stays close to the
   top. The light field is a closest-side ellipse that reaches zero inside
   the stage, so nothing lights the text below it. */
.p8-stage{ --ts-d:clamp(196px, 54vw, 236px); position:relative; pointer-events:none;
  height:calc(var(--ts-d) + 8px); margin:-30px 0 4px; }
.p8-light{ position:absolute; top:0; bottom:0; right:calc(var(--ts-d) * -.35); width:calc(var(--ts-d) * 1.9); max-width:calc(100% + var(--ts-d) * .35);
  background:radial-gradient(closest-side, rgba(132,96,255,.22) 0%, rgba(132,96,255,.09) 46%, rgba(132,96,255,0) 100%); }
.p8-stage .p8-sig{ left:auto; right:4px; top:4px; }
.p8-stage.is-return{ --ts-d:clamp(148px, 40vw, 176px); margin-top:-24px; }
.p8-stage.is-locked .p8-light{ opacity:.6; }
@media (min-width:1024px){
  .p8-stage{ --ts-d:260px; max-width:38rem; margin-top:-20px; }
  .p8-light{ right:-40px; max-width:none; }
  .p8-stage.is-return{ --ts-d:190px; }
}
@media (min-width:1280px){
  /* Desktop: the Signal stands beside the reading column instead of above it. */
  .tk .p8-head{ position:relative; }
  .tk .p8-stage.is-root{ --ts-d:300px; position:absolute; top:10px; left:calc(36rem + 3rem); width:420px; height:340px; max-width:none; margin:0; }
  .tk .p8-stage.is-root .p8-sig{ right:auto; left:calc(50% - var(--ts-d) / 2); top:calc(50% - var(--ts-d) / 2); }
  .tk .p8-stage.is-root .p8-light{ right:auto; left:0; width:100%; }
}
.p8-access{ max-width:35rem; margin:var(--s5) 0 0; font-size:13px; line-height:1.55; color:var(--text-secondary); }
.p8-access p + p{ margin-top:8px; }

/* Testküche — editorial rhythm: rules, type size and space; no cards, no
   dashboard, no progress. Reading width stays one calm column. */
.tk > *{ max-width:36rem; }
.tk .p8-head > *:not(.p8-stage){ max-width:36rem; }
.tk .cf-title{ max-width:15em; }
.tk-lede{ font-size:15px; line-height:1.55; color:var(--text-secondary); margin:12px 0 0; max-width:32em; }
.tk-label{ font-size:10px; font-weight:600; letter-spacing:.2em; text-transform:uppercase; color:var(--muted); margin:44px 0 14px; }
.tk-state{ font-size:11px; font-weight:600; letter-spacing:.22em; text-transform:uppercase; color:var(--text-primary); margin:34px 0 0; }
.p8-head .tk-state{ margin:6px 0 10px; }
.tk-stage{ font-size:10px; font-weight:600; letter-spacing:.2em; text-transform:uppercase; color:var(--text-secondary); margin:0 0 8px; }
.tk-h2{ font-family:var(--font-d); font-weight:500; text-transform:none; color:var(--text-primary); font-size:clamp(22px, 6vw, 27px); line-height:1.12; margin:0; }
.tk-p{ font-size:15px; line-height:1.6; color:var(--text-secondary); margin:10px 0 0; }
.tk-meta{ font-size:12.5px; line-height:1.5; color:var(--muted); margin:10px 0 0; }
.tk-small{ font-size:12.5px; line-height:1.5; color:var(--muted); margin:36px 0 0; }
.tk-dish{ margin:22px 0 0; padding:16px 0 0; border-top:1px solid var(--hairline); }
.tk-quote{ font-family:var(--font-d); font-weight:400; text-transform:none; color:var(--text-primary);
  font-size:clamp(24px, 6.6vw, 31px); line-height:1.18; margin:10px 0 0; max-width:22em; }
.tk-quote.is-flush{ margin-top:0; }
.tk-quote-by{ font-size:12.5px; color:var(--muted); margin:10px 0 0; }
.tk-rule{ border:0; border-top:1px solid var(--hairline); margin:32px 0 0; }
.tk-actions{ display:flex; flex-wrap:wrap; align-items:center; gap:4px 22px; margin:18px 0 0; }
.tk-actions.is-tight{ margin-top:4px; }
.tk-actions .ts-enter, .tk-actions .th-more{ margin:0; }
.tk-inline{ background:none; border:0; padding:0; min-height:0; font:inherit; color:var(--text-primary);
  text-decoration:underline; text-underline-offset:3px; cursor:pointer; }
.tk-inline:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; }

/* People — initials only: no links, no counts, no status. */
.tk-i{ flex:none; width:30px; height:30px; border-radius:50%; display:inline-flex; align-items:center; justify-content:center;
  border:1px solid var(--hairline); color:var(--text-secondary); font-size:10.5px; font-weight:600; letter-spacing:.04em; box-sizing:border-box; }
.tk-i.is-you{ color:var(--text-primary); border-color:rgba(245,241,236,.42); }
.tk-i.is-anon{ border-style:dashed; }
.tk-people{ display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin:16px 0 0; }
.tk-people-t{ font-size:12px; color:var(--muted); margin-left:4px; }
.tk-person{ display:grid; grid-template-columns:52px 1fr; gap:14px; align-items:center; margin:22px 0 0; }
.tk-person .tk-i{ width:52px; height:52px; font-size:15px; }
.tk-person-n{ font-family:var(--font-d); font-weight:500; font-size:21px; line-height:1.1; color:var(--text-primary); margin:0; }
.tk-person-c{ font-size:13px; line-height:1.45; color:var(--text-secondary); margin:3px 0 0; }
.tk-voice{ display:flex; align-items:center; gap:10px; font-size:12.5px; color:var(--muted); margin:6px 0 12px; }
.tk-voice .tk-i{ width:26px; height:26px; font-size:9.5px; }
.tk-said{ margin-top:14px; }

/* Lists, the lifecycle, findings */
.tk-list{ list-style:none; margin:0; padding:0; counter-reset:tk; }
.tk-list li{ position:relative; padding:0 0 14px 30px; font-size:15px; line-height:1.55; color:var(--text-secondary); counter-increment:tk; }
.tk-list li::before{ content:counter(tk); position:absolute; left:0; top:0; font-family:var(--font-d); font-size:15px; color:var(--muted); }
.tk-list b{ color:var(--text-primary); font-weight:600; }
.tk-seq{ list-style:none; margin:0; padding:0; border-top:1px solid var(--hairline); }
.tk-seq-i{ display:grid; grid-template-columns:6.5rem 1fr; gap:14px; padding:14px 0; border-bottom:1px solid var(--hairline); }
.tk-seq-k{ font-size:10px; font-weight:600; letter-spacing:.16em; line-height:1.4; text-transform:uppercase; color:var(--muted); margin:4px 0 0; }
.tk-seq-t{ font-family:var(--font-d); font-weight:500; font-size:18px; line-height:1.2; color:var(--text-secondary); margin:0; }
.tk-seq-s{ font-size:13.5px; line-height:1.5; color:var(--muted); margin:4px 0 0; }
.tk-seq-i.is-now .tk-seq-k, .tk-seq-i.is-now .tk-seq-t{ color:var(--text-primary); }
.tk-seq-i.is-now .tk-seq-s{ color:var(--text-secondary); }
.tk-seq-i.is-past .tk-seq-t{ color:var(--muted); }
@media (max-width:359px){ .tk-seq-i{ grid-template-columns:1fr; gap:2px; } }
.tk-find{ list-style:none; margin:0; padding:0; }
.tk-find li{ padding:22px 0; border-top:1px solid var(--hairline); }
.tk-find li:last-child{ border-bottom:1px solid var(--hairline); }
.tk-find-n{ font-size:10px; font-weight:600; letter-spacing:.2em; color:var(--muted); margin:0 0 8px; }
.tk-find-q{ font-family:var(--font-d); font-weight:500; font-size:clamp(21px, 5.8vw, 25px); line-height:1.18; color:var(--text-primary); margin:0; }
.tk-find-t{ font-size:14.5px; line-height:1.55; color:var(--text-secondary); margin:8px 0 0; }
.tk-from{ font-size:13px; line-height:1.5; color:var(--muted); margin:12px 0 0; padding-left:12px; border-left:1px solid var(--hairline); }
.tk-from.is-yours{ color:var(--text-primary); border-left-color:rgba(245,241,236,.55); }
.tk-since{ font-family:var(--font-d); font-weight:400; font-size:clamp(19px, 5.2vw, 22px); line-height:1.25; color:var(--text-secondary); margin:0 0 10px; }
.tk-credits{ font-size:15px; line-height:1.8; color:var(--text-secondary); margin:0; }

/* Kitchen notes — the sentence carries the note; metadata stays below it. */
.tk-notes{ list-style:none; margin:0; padding:0; border-top:1px solid var(--hairline); }
.tk-note{ display:grid; grid-template-columns:30px 1fr; gap:12px; padding:16px 0; border-bottom:1px solid var(--hairline); }
.tk-note-m{ font-size:11.5px; color:var(--muted); margin:0 0 4px; }
.tk-note-t{ font-size:15px; line-height:1.55; color:var(--text-primary); margin:0; }
.tk-note-t.is-quiet{ color:var(--muted); font-size:14px; }
.tk-note-c{ font-size:13.5px; line-height:1.5; color:var(--text-secondary); margin:6px 0 0; }
.tk-note-w{ font-size:12px; color:var(--muted); margin:6px 0 0; }
.tk-answer{ grid-column:2; margin:12px 0 0; padding:0 0 0 14px; border-left:1px solid rgba(245,241,236,.22); }
.tk-foryou, .tk-watch{ margin:24px 0 0; padding:16px 0 16px 16px; border-left:1px solid rgba(245,241,236,.3); }
.tk-foryou-k{ font-size:10px; font-weight:600; letter-spacing:.2em; text-transform:uppercase; color:var(--text-secondary); margin:0 0 8px; }
.tk-foryou-k em{ font-style:normal; font-weight:500; letter-spacing:.02em; text-transform:none; color:var(--muted); margin-left:8px; }
.tk-foryou .tk-actions{ margin-top:8px; }
.tk-entry{ display:block; width:100%; text-align:left; background:none; border:0; padding:22px 0; cursor:pointer;
  border-top:1px solid var(--hairline); border-bottom:1px solid var(--hairline); color:inherit; font:inherit; }
.tk-entry:focus-visible{ outline:2px solid var(--magenta); outline-offset:4px; }
.tk-entry-t{ display:block; font-family:var(--font-d); font-weight:500; font-size:clamp(22px, 6vw, 27px); line-height:1.12; color:var(--text-primary); }
.tk-entry-s{ display:block; font-size:14.5px; line-height:1.55; color:var(--text-secondary); margin:8px 0 0; }
.tk-entry-go{ display:inline-flex; align-items:center; gap:10px; margin:14px 0 0; font-family:var(--font-d); font-weight:500;
  font-size:14px; letter-spacing:.14em; text-transform:uppercase; color:var(--text-primary); }
.tk-entry-go svg{ width:14px; height:14px; transform:rotate(180deg); color:var(--text-secondary); }

/* The recipe — still a cooking product: readable without Community chrome. */
.tk-sub{ font-size:13px; font-weight:600; color:var(--text-primary); margin:24px 0 6px; }
.tk-ingr{ list-style:none; margin:0; padding:0; columns:2; column-gap:22px; }
.tk-ingr li{ font-size:15px; line-height:1.85; color:var(--text-secondary); break-inside:avoid; }
.tk-ingr b{ color:var(--text-primary); font-weight:600; }
.tk-tag{ display:inline-block; font-size:9.5px; font-weight:600; letter-spacing:.14em; text-transform:uppercase; color:var(--text-secondary);
  border:1px solid var(--hairline); border-radius:999px; padding:1px 7px; margin-left:4px; vertical-align:1px; white-space:nowrap; line-height:1.5; }

/* Zurück aus deiner Küche — one thought per screen, one thumb. */
.tk .cf-title.tk-ask{ font-size:clamp(28px, 8vw, 36px); line-height:1.1; max-width:14em; }
.tk-talk{ animation:tkStep 240ms cubic-bezier(.22,.61,.36,1) both; }
@keyframes tkStep{ from{ opacity:0; } to{ opacity:1; } }
.tk-ask-h{ font-family:var(--font-d); font-weight:500; text-transform:none; color:var(--text-primary); font-size:clamp(24px, 6.6vw, 30px); line-height:1.12; margin:30px 0 0; }
.tk-ask-s{ font-size:14.5px; line-height:1.55; color:var(--text-secondary); margin:10px 0 0; max-width:30em; }
.tk-q{ font-size:14px; color:var(--text-primary); margin:24px 0 10px; }
.tk-choices{ display:flex; flex-direction:column; align-items:stretch; gap:10px; margin:24px 0 0; max-width:24rem; }
.tk-choice, .tk-chip{ border:1px solid var(--hairline); background:none; color:var(--text-primary); font:inherit; cursor:pointer;
  border-radius:999px; transition:background-color 160ms ease, border-color 160ms ease; }
.tk-choice{ min-height:54px; padding:0 20px; text-align:left; font-size:16px; }
.tk-chips{ display:flex; flex-wrap:wrap; gap:8px; margin:18px 0 0; }
.tk-chip{ min-height:44px; padding:0 16px; font-size:14px; color:var(--text-secondary); }
.tk-chip:disabled{ opacity:.4; cursor:default; }
.tk-choice[aria-pressed="true"], .tk-chip[aria-pressed="true"]{ color:var(--text-primary); border-color:rgba(245,241,236,.55); background:rgba(245,241,236,.1); }
.tk-choice:focus-visible, .tk-chip:focus-visible{ outline:2px solid var(--magenta); outline-offset:2px; }
@media (hover:hover){ .tk-choice:hover, .tk-chip:hover{ border-color:rgba(245,241,236,.32); } }
.tk-text{ display:block; width:100%; box-sizing:border-box; min-height:96px; resize:vertical; margin:22px 0 0; border:0; border-radius:16px;
  background:rgba(245,241,236,.07); color:var(--text-primary); font:inherit; font-size:16px; line-height:1.5; padding:12px 14px; }
.tk-text:focus{ outline:none; background:rgba(245,241,236,.1); }
.tk-text:focus-visible{ box-shadow:inset 0 0 0 1px rgba(245,241,236,.35); }

/* Deine Küchen — a quiet memory, not a dashboard. */
.tk-mems{ list-style:none; margin:30px 0 0; padding:0; }
.tk-mem{ padding:24px 0; border-top:1px solid var(--hairline); }
.tk-mem:last-child{ border-bottom:1px solid var(--hairline); }
.tk-mem-k{ font-size:10px; font-weight:600; letter-spacing:.2em; text-transform:uppercase; color:var(--muted); margin:0 0 8px; }
.tk-mem-p{ font-size:15px; line-height:1.55; color:var(--text-secondary); margin:8px 0 0; }
.tk-mem-q{ font-size:14.5px; line-height:1.55; color:var(--text-primary); margin:10px 0 0; }
.tk-mem-now{ display:grid; grid-template-columns:4rem 1fr; gap:10px; margin:16px 0 0; }
.tk-mem-now span{ font-size:10px; font-weight:600; letter-spacing:.2em; text-transform:uppercase; color:var(--text-secondary); margin-top:4px; }
.tk-mem-now p{ font-size:15px; line-height:1.5; color:var(--text-primary); margin:0; }
.tk-mem-then{ font-size:13.5px; line-height:1.5; color:var(--muted); margin:6px 0 0 calc(4rem + 10px); }

/* Local development review control — never Product UI, never deployed data. */
.tk-dev{ margin:48px 0 0; padding:12px 14px; border:1px dashed rgba(245,241,236,.22); border-radius:10px; font-size:12px; color:var(--muted); }
.tk-dev summary{ cursor:pointer; min-height:32px; display:flex; align-items:center; }
.tk-dev p{ margin:6px 0 10px; line-height:1.5; }
.tk-dev-row{ display:flex; flex-wrap:wrap; gap:6px; }
.tk-dev-row button{ min-height:32px; padding:0 10px; border-radius:6px; border:1px solid rgba(245,241,236,.18); background:none; color:var(--text-secondary); font:inherit; cursor:pointer; }
.tk-dev-row button[aria-pressed="true"]{ background:rgba(245,241,236,.12); color:var(--text-primary); }

@media (prefers-reduced-motion: reduce){
  .tk-talk{ animation:none; }
  .tk-choice, .tk-chip{ transition:none; }
}



@media (min-width:1024px){
  .cm-start-row, .cm-list, .cm-conv-head, .cm-posts{ max-width:35rem; }
  .cm-opening{ font-size:clamp(32px, 3vw, 40px); }
}

/* ---- DIALOG CONTRACT ---------------------------------------------------- */
.sheet-backdrop.is-dialog .sheet{ max-height:85vh; max-height:85dvh; overscroll-behavior:contain; }
@media (min-width:768px){
  .sheet-backdrop.is-dialog{ align-items:center; padding:24px; }
  .sheet-backdrop.is-dialog .sheet{ border-radius:20px; padding:var(--s6); }
  .sheet-backdrop.is-dialog .sheet-handle{ display:none; }
}
.cm-dialog{ position:relative; isolation:isolate; }
.cm-dialog:focus{ outline:none; }
.cm-field{ margin-top:var(--s4); }

/* ---- GESPRÄCH BEGINNEN — sitting down at a Table -------------------------
   Not a form. The Table is named first, the opening is written in the
   typography it will carry in the conversation list, attribution is one
   tonal line, and the only Magenta is the start action once there is
   something to start. Near-black tonal ground, no field boxes, no cards. */
.cm-dialog[data-kind="startConversation"]{
  background:linear-gradient(180deg, #1B171E 0%, #141116 42%, #110F13 100%);
  box-shadow:inset 0 1px 0 rgba(245,241,236,.07); }
.cm-dialog[data-kind="startConversation"] .sheet-handle{ background:rgba(245,241,236,.16); margin-bottom:var(--s4); }
.cm-open-top{ display:flex; align-items:center; justify-content:space-between; gap:var(--s3); margin:0 0 4px; }
/* The Table is the context of the whole act: it wraps, never truncates. */
.cm-open-table{ margin:0; min-width:0; font-family:var(--font-c); font-size:11px; font-weight:600;
  line-height:1.45; letter-spacing:.18em; text-transform:uppercase; color:var(--muted); }
.cm-open-cancel{ flex:none; background:none; border:none; padding:0 4px; margin-right:-4px;
  min-height:44px; min-width:44px; font-family:var(--font-c); font-size:13px;
  color:var(--text-secondary); cursor:pointer; transition:color 200ms var(--patient); }
.cm-open-cancel:hover{ color:var(--text-primary); }
.cm-open-cancel:focus-visible{ outline:2px solid var(--magenta); outline-offset:2px; border-radius:6px; }
.cm-open-title{ font-family:var(--font-d); font-weight:700; text-transform:uppercase;
  font-size:18px; line-height:1.15; letter-spacing:.04em; color:var(--text-primary); margin:0; }
.cm-open-desc{ font-size:13.5px; line-height:1.5; color:var(--text-secondary); margin:6px 0 0; max-width:30em; }
.cm-open-write{ position:relative; margin-top:var(--s5); }
/* One clean writing surface: no baseline, no box. The typography, the
   cursor and the composition carry it. Keyboard navigation alone gets a
   neutral ring (html.kbd-nav, see app.js) -- never on touch or mouse. */
html.kbd-nav .cm-open-write:focus-within{ outline:2px solid var(--text-secondary);
  outline-offset:6px; border-radius:6px; }
/* overflow-y:auto by default: if the grow step has not run yet (a reopened
   draft, a rotation) the opening scrolls rather than hiding words under its
   baseline. growStartInput() turns it off once the text fits. */
.cm-open-input{ display:block; width:100%; box-sizing:border-box; resize:none; overflow-y:auto;
  background:none; border:none; border-radius:0; margin:0; padding:2px 0 12px;
  min-height:calc(2 * 1.25em + 14px); max-height:40vh; max-height:40dvh;
  font-family:var(--font-d); font-weight:400; font-size:clamp(22px, 6.4vw, 26px);
  line-height:1.25; letter-spacing:.004em; color:var(--text-primary); overflow-wrap:anywhere; }
.cm-open-input::placeholder{ color:var(--muted); }
.cm-open-input:focus{ outline:none; }
.cm-open-input:focus-visible{ outline:none; }
.cm-open-input:disabled{ opacity:.55; }
.cm-open-count{ margin:8px 0 0; text-align:right; font-size:12px; color:var(--muted);
  font-variant-numeric:tabular-nums; }
.cm-open-count.is-near{ color:var(--text-primary); }
.cm-open-attr{ display:flex; flex-wrap:wrap; align-items:baseline; gap:0 16px; margin-top:var(--s3); }
.cm-open-lead{ font-size:12.5px; line-height:1.4; color:var(--text-secondary); }
.cm-open-choice{ gap:0 16px; }
.cm-dialog .cm-open-hint{ margin:2px 0 0; color:var(--muted); }
.cm-open-name{ margin-top:var(--s4); }
.cm-open-name .cm-open-lead{ display:block; }
.cm-open-foot{ margin-top:var(--s5); display:flex; justify-content:flex-end; }
.cm-open-go{ min-width:12rem; }
/* An inert start is not yet the moment Magenta marks: tonal until the
   opening has words, then the one accent. The state change is also the
   control becoming operable, never colour alone. */
.cm-dialog .cm-open-go[disabled]{ background:rgba(245,241,236,.08); color:var(--text-secondary); opacity:1; }
.cm-dialog .cm-open-go[aria-busy="true"]{ background:var(--magenta); color:var(--canvas); opacity:.7; }
@media (max-width:640px){ .cm-open-foot .cm-open-go{ width:100%; } }

/* ---- CONTRIBUTION COMPOSER — the writing bubble -------------------------
   Joining a conversation, not completing a form: one rounded, borderless,
   tonal writing surface is the whole invitation, with no ruled lines above
   or below it. The text is written inside that same surface, which grows
   with it. Attribution and actions are secondary and appear under the
   bubble only while writing. Magenta stays on Beitragen, and only once
   there is something to contribute. Same grammar for the first
   contribution and every later one. */
.cm-composer .cf-comp-in{ border-top:none; padding-top:0; }
.cm-composer:focus{ outline:none; }
.cm-bubble{ position:relative; border-radius:22px; padding:0 16px; cursor:text;
  background:rgba(245,241,236,.07);
  transition:background-color 220ms var(--patient), box-shadow 220ms var(--patient); }
.cm-bubble:hover{ background:rgba(245,241,236,.085); }
/* Active is a state of the surface itself: it lifts in tone. No border, no
   accent frame -- Magenta stays on Beitragen. */
.cm-bubble:focus-within{ background:rgba(245,241,236,.1); }
/* Keyboard navigation alone gets a neutral focus ring (html.kbd-nav, see
   app.js); touch and mouse typing never do. */
html.kbd-nav .cm-bubble:focus-within{ outline:2px solid var(--text-secondary); outline-offset:2px; }
/* 16px stays: the iOS focus-zoom threshold. Height is set by autoGrowCompose. */
.cm-composer .cm-bubble-input{ padding:10px 0; min-height:44px; line-height:1.5; font-size:16px; }
.cm-composer .cm-bubble-input::placeholder{ color:var(--text-secondary); }
.cm-composer .cm-bubble-input:focus-visible{ outline:none; }
.cm-comp-more{ margin-top:10px; padding:0 4px; }
.cm-comp-more[hidden]{ display:none; }
.cm-composer .cm-comp-attr{ margin-top:0; }
.cm-composer .cm-comp-hint{ margin:2px 0 0; color:var(--muted); }
.cm-composer .cm-comp-name{ margin-top:10px; }
.cm-composer .cf-comp-foot{ margin-top:8px; }
.cm-composer .cm-comp-go[disabled]{ background:rgba(245,241,236,.08); color:var(--text-secondary); opacity:1; }
.cm-composer .cm-comp-go[aria-busy="true"]{ background:var(--magenta); color:var(--canvas); opacity:.7; }

/* ---- KEYBOARD-AWARE WRITING SURFACES -----------------------------------
   html.is-keyboard and the --kb-* variables exist only while a Community
   text field has focus AND a software keyboard covers part of the screen
   (communityKeyboard in app.js). The surface is then laid out against the
   visible viewport: its lower edge on the keyboard, its height capped to
   what is visible. Without the class nothing here applies, so the approved
   composition is untouched the moment the keyboard is gone. */
html.is-keyboard .sheet-backdrop.is-dialog{ top:var(--kb-top, 0px); bottom:auto; height:var(--kb-vh, 100%); }
/* The fixed preview banner sits above every surface; the visible height it
   leaves is what the sheet may use. --preview-banner-h is 0 once the banner
   is retired, so this needs no follow-up then. */
html.is-keyboard .sheet-backdrop.is-dialog .sheet{
  max-height:calc(var(--kb-vh, 100vh) - var(--preview-banner-h, 0px) - 12px); }
@media (min-width:768px){
  html.is-keyboard .sheet-backdrop.is-dialog .sheet{
    max-height:calc(var(--kb-vh, 100vh) - var(--preview-banner-h, 0px) - 60px); }
}
@media (max-width:1023px){
  /* The bottom navigation is behind the keyboard: no clearance for it. */
  html.is-keyboard .cf-composer{ bottom:var(--kb-inset, 0px); padding-bottom:12px; }
  html.is-keyboard .cf-composer.is-open .cf-comp-in{
    max-height:calc(var(--kb-vh, 100vh) - var(--preview-banner-h, 0px) - 48px); }
}
.cm-dlg-state{ margin-top:var(--s4); }
/* An error is text first: readable colour, a rule beside it, role=alert.
   Never colour alone. */
.cm-dlg-error{ font-size:13.5px; line-height:1.5; color:var(--text-primary); margin:var(--s4) 0 0;
  padding:6px 0 6px 12px; border-left:2px solid var(--action-destructive-on-surface); }
.cm-dlg-actions{ margin-top:var(--s5); display:flex; flex-direction:column; gap:10px; }
.cm-destructive{ border-color:var(--hairline); color:var(--action-destructive-on-surface); }
.cm-destructive:hover:not([disabled]), .cm-destructive:focus-visible{ border-color:var(--action-destructive-on-surface); }
.cm-dialog .btn[disabled], .cm-dialog .cf-share[disabled], .cm-dialog .btn-quiet[disabled]{ opacity:.55; cursor:default; }
.cm-dialog .cf-hint{ color:var(--text-secondary); }
.cm-dialog .btn-quiet{ min-height:44px; color:var(--text-secondary); }
.cm-dialog .compose-box{ background:none; padding:0; margin:var(--s4) 0 0; }
.sheet .del-error{ color:var(--action-destructive-on-surface); }
@media (max-width:640px){
  .report-actions .cf-share{ width:100%; }
}
/* Dialogs carry no Nebula in any context: reading and writing surfaces stay
   free of it (Design Constitution 2.6). The former PL8 dialog light was
   removed with the PL8 Community shell; data-material stays inert. */

@media (prefers-reduced-motion: reduce){
  .ts.is-arrived .ts-sheen .ts-ring::after{ animation:none; }
  .cf-post.is-leaving{ animation:none; opacity:.4; }
  .cf-ackt b.is-new, .cf-dot.is-off{ animation:none; }
}

/* ---- START — the member home (Start Home V3.1) ---------------------------
   V3.1: fewer things at once. Metadata becomes a glyph and a number (the
   CRVD ring glyphs, homeGlyph), a peeking recipe shows only its name, the
   week is an object of seven places, PL8 is set apart by material.

   One page, not a stack of modules. Start answers, in one movement: what is
   today, what can I cook, where do I continue, what is being said, and — very
   quietly, near the end — PL8. Density over whitespace: one shared vertical
   rhythm (--hm-gap), no section surfaces, no explanations the member does not
   need. Type carries the page: the recipe name is the largest object on
   Start, the ingredients are the editorial material that stands in for
   photography (cropped at the edge, layered, moving with the finger and the
   scroll), and every doorway is one line. Konto/Community grammar throughout.
   Magenta: the Signal and the PL8 threshold's own action only. Nebula: one
   contained field behind the Signal inside the compact PL8 object (§2.6,
   DH-16). Native CSS/JS; none of the motion under reduced motion. */
.home{ --hm-g:max(16px, env(safe-area-inset-left)); --hm-gap:32px; }
.hm-k{ display:block; font-family:var(--font-c); font-size:10px; font-weight:600; letter-spacing:.2em; text-transform:uppercase; color:var(--muted); }
.hm-k em{ font-style:normal; font-weight:500; letter-spacing:.02em; text-transform:none; margin-left:8px; }
.hm-text{ display:inline-flex; align-items:center; gap:7px; min-height:44px; padding:0; background:none; border:0; cursor:pointer;
  font-family:var(--font-d); font-weight:500; font-size:11.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--text-secondary); }
.hm-text svg, .hm-arrow svg{ width:12px; height:12px; transform:rotate(180deg); }
/* QUIET DIRECTION (V3.6). Ordinary navigation on Start is one plain chevron: a
   direction, not a symbol. THE SIGNAL (ring, opening, seat) belongs to identity
   and interaction objects -- PL8, the Tables, the Recipe memory -- and never
   to "next". 44px target, neutral, brightens and steps a hair forward when the
   row is touched, hovered or focused. */
.hm-arw{ flex:none; display:inline-flex; align-items:center; justify-content:center; width:44px; height:44px; color:var(--text-secondary);
  transition:color 220ms ease, transform 300ms var(--patient); }
.hm-arw svg{ display:block; width:22px; height:22px; stroke:currentColor; stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round; }
.hm-arw.is-mini{ width:14px; height:14px; }
.hm-arw.is-mini svg{ width:14px; height:14px; stroke-width:1.9; }
[data-ot].is-pressed .hm-arw, [data-ot]:focus-visible .hm-arw{ color:var(--text-primary); transform:translateX(2px); }
@media (hover:hover){ [data-ot]:hover .hm-arw{ color:var(--text-primary); transform:translateX(2px); } }
.hm-hero-meta .hm-arw, .hm-cont .hm-arw:not(.is-mini), .hm-focus .hm-arw{ margin-right:-13px; }
@media (hover:hover){ .hm-text:hover{ color:var(--text-primary); } }
.home button:focus-visible, .hm-rail:focus-visible{ outline:2px solid var(--magenta); outline-offset:3px; border-radius:6px; }

/* Top: one line for the day and the week, then the greeting — no larger than
   it needs to be, because the recipe below is the object that matters. */
.hm-topline{ display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:40px; }
.hm-date{ margin:0; font-family:var(--font-c); font-size:10.5px; font-weight:600; letter-spacing:.18em; text-transform:uppercase; color:var(--muted); }
.hm-weekbtn{ display:inline-grid; grid-template-columns:repeat(7, 12px); gap:4px; align-items:center; height:44px; min-width:44px; padding:0 2px;
  background:none; border:0; cursor:pointer; }
/* Each day is the CRVD ring (see homeDayGlyph): open, faint ahead, filled
   where the member took part. The week reads as a rhythm, not a chart. */
.hm-mark{ display:block; width:12px; height:12px; color:rgba(245,241,236,.32); transition:opacity 300ms ease; }
.hm-mark .hm-dg{ display:block; width:100%; height:100%; }
.hm-mark .hm-dg path{ stroke-width:1.75; }
.hm-mark.is-today .hm-dg path{ stroke-width:2.1; }
.hm-mark.is-on{ color:rgba(245,241,236,.94); }
.hm-mark.is-ahead{ color:rgba(245,241,236,.14); }
.hm-mark.is-today{ color:var(--text-primary); }
.hm-weekbtn[aria-expanded="true"] .hm-mark{ opacity:.4; }
/* The week is a rhythm collapsed, a sentence expanded. Nothing of the
   expanded state is shown while it is closed. */
.hm-weekpanel{ display:grid; grid-template-rows:0fr; opacity:0; transition:grid-template-rows 360ms cubic-bezier(.22,.61,.36,1), opacity 240ms ease; }
.hm-weekpanel.is-open{ grid-template-rows:1fr; opacity:1; }
.hm-weekpanel-in{ min-height:0; overflow:hidden; }
/* The open week is one object: a quiet matte surface holding the seven
   places, one sentence, and the next useful thing (Wochenfokus). */
.hm-week{ margin:6px 0 14px; padding:14px 16px 4px; border-radius:18px;
  background:linear-gradient(180deg, rgba(245,241,236,.04), rgba(245,241,236,.015)); box-shadow:inset 0 0 0 1px rgba(245,241,236,.07); }
.hm-week-head{ display:flex; align-items:baseline; justify-content:space-between; gap:12px; }
.hm-rhythm{ list-style:none; display:grid; grid-template-columns:repeat(7, minmax(0, 1fr)); max-width:440px; margin:16px -4px 0; padding:0; }
.hm-day{ display:flex; flex-direction:column; align-items:center; gap:8px; color:rgba(245,241,236,.32); }
.hm-day .hm-dg{ display:block; width:30px; height:30px; }
.hm-day .hm-dg path{ stroke-width:1.15; }
.hm-day.is-on{ color:rgba(245,241,236,.94); }
.hm-day.is-ahead{ color:rgba(245,241,236,.13); }
.hm-day.is-today{ color:var(--text-primary); }
.hm-day.is-today .hm-dg-ring{ stroke-width:1.6; }
.hm-day-l{ font-family:var(--font-c); font-size:9.5px; font-weight:600; letter-spacing:.16em; text-transform:uppercase; color:var(--muted); }
.hm-day.is-today .hm-day-l{ color:var(--text-primary); }
.hm-weekline{ font-family:var(--font-d); font-weight:400; font-size:19px; line-height:1.25; color:var(--text-primary); margin:16px 0 0; max-width:20em; }
.hm-weeknote{ font-size:11px; color:var(--muted); margin:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* Wochenfokus: the next useful thing in the week, inside the same object. */
.hm-focus{ display:flex; align-items:center; justify-content:space-between; gap:12px; width:100%; min-height:60px; margin:14px 0 0; padding:6px 0;
  background:none; border:0; border-top:1px solid rgba(245,241,236,.07); color:inherit; font:inherit; text-align:left; cursor:pointer; }
.hm-focus-t{ display:block; min-width:0; }
.hm-focus-line{ display:block; font-size:14px; line-height:1.4; color:var(--text-body); margin-top:3px; }
/* Reveal: the seven places arrive one after another, then the words. */
.hm-weekpanel .hm-day .hm-dg{ transform:scale(.35); opacity:0;
  transition:transform 560ms cubic-bezier(.22,.61,.36,1) calc(90ms + var(--i) * 50ms), opacity 380ms ease calc(90ms + var(--i) * 50ms); }
.hm-weekpanel .hm-day-l, .hm-weekpanel .hm-weekline, .hm-weekpanel .hm-weeknote, .hm-weekpanel .hm-focus{ opacity:0; transform:translateY(6px);
  transition:opacity 420ms ease 420ms, transform 560ms cubic-bezier(.22,.61,.36,1) 420ms; }
.hm-weekpanel.is-open .hm-day .hm-dg, .hm-weekpanel.is-open .hm-day-l, .hm-weekpanel.is-open .hm-weekline,
.hm-weekpanel.is-open .hm-weeknote, .hm-weekpanel.is-open .hm-focus{ opacity:1; transform:none; }
.hm-title{ font-family:var(--font-d); font-weight:500; text-transform:none; color:var(--text-primary);
  font-size:clamp(28px, 8vw, 38px); line-height:1.04; letter-spacing:-.005em; margin:8px 0 0; }

/* Today: the recipe owns the first viewport. The name is the largest type on
   the page; the ingredients stand beside it as a cropped column of material,
   drifting a little as the page scrolls. */
.hm-hero{ --hs:0; position:relative; display:grid; grid-template-columns:minmax(0, 1fr) auto; column-gap:14px; align-items:start;
  width:100%; margin:18px 0 0; padding:0; text-align:left; background:none; border:0; color:inherit; font:inherit; cursor:pointer; }
.hm-hero-main{ display:block; min-width:0; }
.hm-hero-name{ display:block; font-family:var(--font-d); font-weight:600; text-transform:uppercase; color:var(--text-primary);
  font-size:clamp(34px, 9.8vw, 46px); line-height:1.02; letter-spacing:.005em; margin-top:7px; }
/* Rotation brings names the composition was not first drawn for: a name
   with a very long word sets a step smaller so the word fits whole; only a
   word that still cannot fit may break (never automatic hyphenation, which
   split short words like "Bacon" to fill lines). */
.hm-hero-name, .hm-slide-name{ overflow-wrap:break-word; }
.hm-hero-name.is-long{ font-size:clamp(30px, 8.6vw, 40px); }
.hm-hero.is-quiet .hm-hero-name{ text-transform:none; font-weight:500; font-size:clamp(26px, 7.4vw, 34px); }
.hm-hero-proof{ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
  font-size:14px; line-height:1.45; color:var(--text-body); margin:9px 0 0; max-width:28em; }
.hm-hero-meta{ grid-column:1 / -1; display:flex; align-items:center; justify-content:space-between; gap:12px; margin:8px 0 0; }
/* Practical metadata: a glyph and a number, quieter than any sentence. */
.hm-meta{ display:inline-flex; align-items:center; gap:5px; font-size:12px; line-height:1; letter-spacing:.02em; color:var(--muted); font-variant-numeric:tabular-nums; white-space:nowrap; }
.hm-meta .hm-g{ flex:none; width:13px; height:13px; }
.hm-meta b{ margin-left:7px; font-family:var(--font-c); font-size:9.5px; font-weight:600; letter-spacing:.16em; color:var(--text-secondary); }
.hm-hero-ing{ display:grid; justify-items:start; width:min(34vw, 152px); margin-right:calc(-1 * var(--hm-g)); margin-top:26px; overflow:hidden;
  translate:0 calc(var(--hs) * -20px); pointer-events:none;
  -webkit-mask-image:linear-gradient(90deg, #000 0, #000 calc(100% - 30px), transparent 100%);
  mask-image:linear-gradient(90deg, #000 0, #000 calc(100% - 30px), transparent 100%); }
.hm-hero-ing .hm-w{ display:block; white-space:nowrap; font-family:var(--font-d); font-weight:500; text-transform:uppercase;
  font-size:calc(18px - var(--n) * .7px); line-height:1.3; letter-spacing:.015em;
  color:rgba(245,241,236,.42); opacity:calc((1 - var(--n) * .13) * (1 - var(--hs) * .5)); }
/* With a photograph: the same cropped-material slot, filled instead of
   fading type -- no mask (fading a photo to transparent reads as broken,
   not editorial), a fixed 4:3 so the image has a frame to fill. */
.hm-hero-ing.has-img{ display:block; -webkit-mask-image:none; mask-image:none; aspect-ratio:4 / 3; border-radius:14px; }
/* Shared by both Start slots (hero thumbnail, carousel plate); each parent
   sizes and positions it, this only ever sets how the photograph itself
   fills that box -- the same object-fit:cover contract as .rx-img. */
.hm-visual-img{ display:block; object-fit:cover; object-position:50% 50%; }
.hm-hero-ing.has-img .hm-visual-img{ width:100%; height:100%; border-radius:inherit; }
/* The continuation belongs to today, so it hangs directly off the recipe. */
.hm-cont{ display:flex; align-items:center; justify-content:space-between; gap:10px; width:100%; min-height:52px; margin:12px 0 0; padding:0;
  background:none; border:0; border-top:1px solid var(--hairline); color:var(--text-primary); font:inherit; text-align:left; cursor:pointer; }
/* Where it lives above what it is: the area word ("Community", "PL8") is
   the one brighter part of the label, so the destination reads at a glance
   without a heading. Two lines inside the same 52px row. */
.hm-cont-t{ display:flex; flex-direction:column; align-items:flex-start; gap:3px; min-width:0; }
.hm-cont-k{ flex:none; font-family:var(--font-c); font-size:9.5px; font-weight:600; letter-spacing:.18em; text-transform:uppercase; color:var(--muted); white-space:nowrap; }
.hm-cont-name{ max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:15px; line-height:1.25; color:var(--text-primary); }
.hm-cx{ color:var(--text-secondary); }
/* Cooking Memory's own quiet third line: what the recipe says about keeping
   it -- never a claim that anything is left. */
.hm-cont-sub{ max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; line-height:1.3; color:var(--muted); }
/* Destination capsule (homeGoCapsule): the row's action, named. Neutral
   contrast and geometry only -- a clear secondary action, never a CTA. The
   whole row stays the tap target; the capsule is what the eye finds. */
.hm-go{ flex:none; display:inline-flex; align-items:center; gap:6px; height:36px; padding:0 11px 0 14px; border-radius:999px;
  border:1px solid rgba(245,241,236,.24); background:rgba(245,241,236,.05); color:var(--text-primary);
  font-family:var(--font-c); font-size:10px; font-weight:600; letter-spacing:.16em; text-transform:uppercase; white-space:nowrap;
  transition:background-color 200ms ease, border-color 200ms ease; }
[data-ot].is-pressed .hm-go, [data-ot]:focus-visible .hm-go{ background:rgba(245,241,236,.1); border-color:rgba(245,241,236,.38); }
@media (hover:hover){ [data-ot]:hover .hm-go{ background:rgba(245,241,236,.1); border-color:rgba(245,241,236,.38); } }
.hm-go-btn{ flex:none; display:inline-flex; align-items:center; min-height:44px; padding:0; background:none; border:0; cursor:pointer; }
.hm-cx-sep{ color:var(--muted); }

/* Discovery: one recipe owns the swipe, the next one is only a promise. */
.hm-discover{ margin:var(--hm-gap) 0 0; }
.hm-discover-head{ display:flex; align-items:center; justify-content:space-between; gap:12px; }
.hm-arrows{ display:none; gap:8px; }
@media (hover:hover) and (pointer:fine){ .hm-arrows{ display:flex; } }
/* Rail arrows scroll, they do not open anything: plain chevrons, no ring. */
.hm-arrow{ width:44px; height:44px; border-radius:50%; display:inline-flex; align-items:center; justify-content:center; cursor:pointer;
  background:none; border:0; color:var(--text-primary); transition:opacity 200ms ease, background-color 200ms ease; }
@media (hover:hover){ .hm-arrow:not(:disabled):hover{ background:rgba(245,241,236,.06); } }
.hm-arrow:not(.is-next) svg{ transform:none; }
.hm-arrow:disabled{ opacity:.28; cursor:default; }
.hm-tabs{ display:flex; gap:20px; min-width:0; overflow-x:auto; scrollbar-width:none; }
.hm-tabs::-webkit-scrollbar, .hm-rail::-webkit-scrollbar{ display:none; }
.hm-tab{ position:relative; flex:none; min-height:44px; padding:0; background:none; border:0; cursor:pointer; font:inherit; font-size:13px; color:var(--muted); transition:color 220ms ease; }
.hm-tab::after{ content:''; position:absolute; left:0; right:0; bottom:7px; height:1.5px; background:var(--text-primary);
  transform:scaleX(0); transform-origin:0 50%; transition:transform 320ms cubic-bezier(.22,.61,.36,1); }
.hm-tab[aria-pressed="true"]{ color:var(--text-primary); }
.hm-tab[aria-pressed="true"]::after{ transform:scaleX(1); }
.hm-rail{ list-style:none; display:flex; gap:12px; margin:6px calc(-1 * var(--hm-g)) 0; padding:0 var(--hm-g) 8px;
  background:radial-gradient(46% 60% at 40% 46%, rgba(var(--act), .05), rgba(var(--act), 0) 70%);
  overflow-x:auto; overflow-y:hidden; overscroll-behavior-x:contain; scroll-snap-type:x mandatory; scroll-padding-inline:var(--hm-g); scrollbar-width:none; }
/* overflow-y:hidden is load-bearing: overflow-x:auto alone computes Y to
   auto, and the 6px disclosure shift of a peeking plate made the rail 2px
   vertically scrollable -- enough for iOS to let the finger drag and
   rubber-band the whole rail up and down. Hidden, vertical intent passes to
   the page and the rail stays on its axis. scroll-snap-stop:always makes a
   fling stop at the next recipe instead of skipping several. */
.hm-slide{ flex:0 0 78%; scroll-snap-align:start; scroll-snap-stop:always; --p:0; --pa:0; --k:0; }
.hm-slide.is-door{ flex-basis:38%; }
.hm-slide-btn{ position:relative; display:flex; flex-direction:column; width:100%; height:100%; padding:0; text-align:left; cursor:pointer;
  background:none; border:0; color:inherit; font:inherit; }
/* The plate: the one surface on Start, because the recipe needs one -- an
   OBJECT lit by the room, in the grammar Konto's Küche entries use (light
   belongs to the object; directional light, one lit top edge, a glow under it
   rather than a shadow). Scroll-linked focus, no timers: --pa (0 at rest, 1
   away from it) and --p (signed) are written per frame by homeRailPaint, so the
   plate at rest has the most presence -- light from above, a brighter edge, a
   fuller glow, full scale -- and its neighbours recede calmly (a hair smaller
   and lower, dimmer, the light leaning back toward the one in focus).
   MAGENTA'S ONE JOB ON START (--act): ACTIVE RESPONSE. The brand colour is not
   a decoration and not a state of the card; it is ONE LIGHT travelling through
   the row -- Konto's own treatment (a radial at .075-.15, a bloom beneath).
   --k (homeRailPaint) is each plate's share of it by true distance from the
   focus: full on the plate in focus, about half a step out on the next one,
   a whisper beyond. A receiver's light leans toward the source (--p). It is
   light, depth and reflection, never a fill, border or word colour. The
   plate stays dark; the words stay neutral. When the scroll stops, everything
   has already settled: there is nothing left to animate. The plate is its own container (cqw), so its words size to THEIR
   plate on any viewport with no per-recipe offsets. */
.hm-slide-btn{ transition:translate 340ms var(--patient); }
.hm-slide-words{ position:relative; display:flex; flex-direction:column; align-items:center; justify-content:center; box-sizing:border-box;
  height:150px; padding:28px 16px 14px; border-radius:18px; overflow:hidden; container-type:inline-size;
  background:radial-gradient(105% 96% at calc(50% - var(--p) * 24%) 0%, rgba(var(--hi), calc(.15 - var(--pa) * .06)), rgba(var(--hi), 0) 60%),
    radial-gradient(74% 108% at calc(90% - var(--p) * 55%) 100%, rgba(var(--act), calc(var(--k) * .135)), rgba(var(--act), 0) 66%),
    radial-gradient(90% 80% at 10% 108%, rgba(var(--lit), .07), rgba(var(--lit), 0) 70%),
    linear-gradient(165deg, #322C38 0%, #1A161E 100%);
  box-shadow:inset 0 1px 0 rgba(var(--hi), calc(.15 - var(--pa) * .07)), inset 0 0 0 1px rgba(var(--hi), .04),
    0 34px 58px -34px rgba(var(--lit), calc(.22 - var(--pa) * .08)),
    0 22px 40px -26px rgba(var(--act), calc(var(--k) * .42));
  scale:calc(1 - var(--pa) * .02); translate:0 calc(var(--pa) * 3px); transform-origin:50% 100%;
  filter:brightness(calc(1 - var(--pa) * .12)); }
/* Awake to being approached (Konto's Küche entries do the same): the light
   gathers at the top and the plate lifts two pixels. Pointer and keyboard. On
   the plate in focus the response also deepens its Magenta light a touch --
   the same material, so focus + hover never over-brightens. */
.hm-slide-words::after{ content:''; position:absolute; inset:0; pointer-events:none; opacity:0; transition:opacity 340ms var(--patient);
  background:radial-gradient(90% 80% at 50% 0%, rgba(var(--hi), calc(.11 - (1 - var(--pa)) * .06)), rgba(var(--hi), 0) 62%),
    radial-gradient(74% 108% at 90% 100%, rgba(var(--act), calc((1 - var(--pa)) * .05)), rgba(var(--act), 0) 66%); }
.hm-slide-btn:focus-visible .hm-slide-words::after{ opacity:1; }
@media (hover:hover){ .hm-slide-btn:hover{ translate:0 -2px; } .hm-slide-btn:hover .hm-slide-words::after{ opacity:1; } }
/* With a photograph: the same plate -- same size, radius, lift, glow -- with
   the photo filling it edge to edge instead of the resting gradient (still
   painted, just no longer visible beneath an opaque image) and one thin
   inset highlight standing in for it, so the plate still reads as lit
   ("the plate's light stays above it", rx-visual.has-img's own rule). The
   hover/focus glow above (::after) is unaffected -- it already paints above
   any plate content. */
.hm-slide-words.has-img{ padding:0; }
.hm-slide-words.has-img::before{ content:''; position:absolute; inset:0; z-index:1; pointer-events:none; border-radius:inherit;
  box-shadow:inset 0 1px 0 rgba(var(--hi), .14); }
/* Teaser, not discovery: a restrained blur (smallest value that reads as
   intentional rather than a loading state -- 2px) so the dish, its colours
   and the bowl are still plainly recognisable, with just enough softness to
   leave a reason to open the Recipe for the sharp version. Scoped to the
   image itself, inside .hm-slide-words' own overflow:hidden -- .hm-slide-n
   and .hm-slide-text live outside this element entirely (siblings in
   homeSlidesHtml, not descendants) and are never touched. blur() samples
   past the element's own edge as transparent, which would otherwise show as
   a faint soft fringe at the plate's rounded corners; scaling the image up
   slightly pushes that fringe outside the clipped frame before it can be
   seen, with no visible zoom at this size. The fixed 150px plate height
   forces a much narrower vertical slice of a square photograph than
   Detail's 4:3 gets, so the same upward shift that centres Detail needs to
   go further here to keep the food mass in frame rather than the emptier
   lower bowl -- a generic Start-teaser default, not specific to this one
   Recipe. */
.hm-slide-words.has-img .hm-visual-img{ position:absolute; inset:0; width:100%; height:100%;
  object-position:50% 38%; filter:blur(2px); transform:scale(1.04); }
/* The words are the plate's identity: a centered stack, large and editorial,
   composed inside the plate -- never offset, never clipped, never dependent on
   overflow. One ingredient word each; the size follows the plate width and a
   hyphenated compound may break at its hyphen. The first word carries most,
   the rest recede; the plate out of focus recedes a touch more. Secondary to
   the recipe name below, but plainly readable. */
.hm-slide-words .hm-w{ display:block; max-width:100%; text-align:center; overflow-wrap:break-word; hyphens:manual;
  font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:clamp(21px, 10.8cqw, 33px); line-height:1.04; letter-spacing:.01em;
  color:rgba(var(--hi), .8); opacity:calc((1 - var(--n) * .14) * (1 - var(--pa) * .1)); }
.hm-slide-n{ position:absolute; top:12px; right:14px; font-family:var(--font-c); font-size:10.5px; letter-spacing:.12em; color:var(--muted); }
.hm-slide-text{ display:flex; flex-direction:column; gap:6px; padding:11px 2px 2px; transform:translateX(calc(var(--p) * 18px)); }
.hm-slide-name{ font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:19px; line-height:1.05; color:var(--text-primary);
  opacity:calc(1 - var(--pa) * .55); }
/* What only the recipe at rest shows: gone on a peeking plate, arriving
   with the swipe (--pa 1 -> 0). */
.hm-slide-more{ display:flex; align-items:center; gap:12px; min-width:0; min-height:14px;
  opacity:calc(1 - var(--pa) * 1.8); transform:translateY(calc(var(--pa) * 6px)); }
.hm-slide-why{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; color:var(--text-secondary); }
.hm-slide.is-door .hm-slide-btn{ align-items:center; justify-content:center; gap:8px; height:150px; border-radius:18px; border:1px solid rgba(245,241,236,.1); }
.hm-slide.is-door .hm-arw{ width:24px; height:24px; }
.hm-door{ font-family:var(--font-d); font-weight:500; font-size:15px; letter-spacing:.12em; text-transform:uppercase; color:var(--text-primary); text-align:center; }

/* Community: a voice passing through Start. One quote, one line under it,
   one way in — the Community itself owns the depth. */
.hm-pulse{ margin:var(--hm-gap) 0 0; }
.hm-pulse-quote{ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; width:100%; min-height:44px; margin:-3px 0 -9px; padding:9px 0;
  background:none; border:0; text-align:left; cursor:pointer; font-family:var(--font-d); font-weight:400; text-transform:none;
  font-size:clamp(19px, 5.6vw, 23px); line-height:1.2; color:var(--text-primary); }
.hm-pulse-foot{ display:flex; align-items:center; justify-content:space-between; gap:12px; margin:2px 0 0; }
.hm-pulse-meta{ font-size:12px; color:var(--muted); margin:0; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.hm-pulse-quiet{ font-size:14.5px; line-height:1.5; color:var(--text-secondary); margin:6px 0 0; }

/* PL8: a compact premium object, set apart from Start by material, not by
   size. Its own Nebula surface (depth field from the Signal's side, a key
   light, one reflected top edge -- §2.6 / DH-16 values), the PL8 identity in
   the Nebula reflection tone, and one Magenta action. Neutral until reached;
   the environment resolves once, then stays still. */
.hm-p8{ margin:var(--hm-gap) 0 0; }
.hm-p8-btn{ position:relative; isolation:isolate; display:grid; grid-template-columns:auto minmax(0, 1fr) auto; align-items:center; column-gap:12px;
  width:100%; padding:13px 13px 13px 11px; border-radius:18px; overflow:hidden; cursor:pointer; text-align:left; color:inherit; font:inherit;
  border:0; background:#0E0C14; box-shadow:inset 0 0 0 1px rgba(150,132,255,.12);
  transition:box-shadow 1200ms cubic-bezier(.22,.61,.36,1), translate 340ms var(--patient); }
.hm-p8-env{ position:absolute; inset:0; z-index:-1; pointer-events:none; opacity:.3;
  background:
    radial-gradient(150px 120px at 42px 50%, rgba(72,56,190,.58) 0%, rgba(44,34,130,.34) 42%, rgba(18,14,44,0) 100%),
    radial-gradient(240px 90px at 88% 130%, rgba(118,98,255,.18) 0%, rgba(118,98,255,0) 70%),
    linear-gradient(100deg, #120E2C 0%, #100D1F 50%, #0E0C14 100%);
  transition:opacity 1400ms cubic-bezier(.22,.61,.36,1), filter 340ms var(--patient); }
.hm-p8-env::before{ content:''; position:absolute; left:0; right:0; top:0; height:1px;
  background:linear-gradient(90deg, rgba(196,184,255,0) 0%, rgba(196,184,255,.62) 14%, rgba(132,112,255,.24) 58%, rgba(132,112,255,0) 100%);
  opacity:0; transform:scaleX(.25); transform-origin:0 50%;
  transition:opacity 900ms ease 250ms, transform 1500ms cubic-bezier(.22,.61,.36,1) 250ms; }
.hm-p8.is-lit .hm-p8-env{ opacity:1; }
.hm-p8.is-lit .hm-p8-env::before{ opacity:1; transform:none; }
.hm-p8.is-lit .hm-p8-btn{ box-shadow:inset 0 0 0 1px rgba(150,132,255,.24); }
.hm-p8-stage{ position:relative; display:block; width:60px; height:60px; }
/* The contained Nebula field behind the Signal, plus its key light. */
.hm-p8-light{ position:absolute; left:50%; top:50%; width:200px; height:200px; margin:-100px 0 0 -100px; pointer-events:none;
  background:radial-gradient(closest-side, rgba(132,96,255,.22) 0%, rgba(132,96,255,.09) 46%, rgba(132,96,255,0) 100%);
  opacity:0; transform:scale(.8); transition:opacity 1400ms cubic-bezier(.22,.61,.36,1), transform 1400ms cubic-bezier(.22,.61,.36,1); }
.hm-p8-stage::after{ content:''; position:absolute; left:3px; top:1px; width:24px; height:20px; border-radius:50%; pointer-events:none;
  background:radial-gradient(closest-side, rgba(150,132,255,.40), rgba(150,132,255,0)); opacity:0; transition:opacity 1400ms ease 300ms; }
.hm-p8.is-lit .hm-p8-light{ opacity:1; transform:none; }
.hm-p8.is-lit .hm-p8-stage::after{ opacity:1; }
.hm-p8 .hm-p8-sig{ --ts-d:56px; position:absolute; left:2px; right:auto; top:2px; width:56px; height:56px; }
.hm-p8-body{ display:block; min-width:0; }
.hm-p8 .hm-k{ color:#C4B8FF; }
.hm-p8-line{ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
  font-family:var(--font-d); font-weight:500; text-transform:none; color:var(--text-primary);
  font-size:14.5px; line-height:1.2; margin-top:5px; }
.hm-p8-sub{ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; font-size:12px; line-height:1.35; color:rgba(245,241,236,.66); margin-top:3px; }
.hm-p8-go{ flex:none; display:inline-flex; align-items:center; gap:6px; padding:9px 11px; border-radius:999px;
  border:1px solid rgba(226,25,181,.6); background:rgba(226,25,181,.12); color:var(--magenta);
  font-family:var(--font-c); font-size:10px; font-weight:600; letter-spacing:.14em; text-transform:uppercase; white-space:nowrap;
  transition:background-color 240ms ease; }
.hm-p8-go svg{ width:11px; height:11px; transform:rotate(180deg); }
@media (hover:hover){ .hm-p8-btn:hover .hm-p8-go{ background:rgba(226,25,181,.22); } .hm-p8.is-lit .hm-p8-btn:hover .hm-p8-light{ transform:scale(1.07); } .hm-p8-btn:hover{ translate:0 -1px; } .hm-p8-btn:hover .hm-p8-env{ filter:brightness(1.12); } }
.hm-p8-btn:focus-visible .hm-p8-env, .hm-p8-btn:active .hm-p8-env{ filter:brightness(1.12); }
.hm-p8-btn:active .hm-p8-go{ background:rgba(226,25,181,.24); }

/* PL8 on Start -- the access state, carried by The Signal itself (the PL8
   threshold's own language: bolt across the opening, cold Seat, a small lock at
   the Seat, a quieter ring -- and, entitled, the Seat lit and no lock). The
   class is set from the real entitlement (pl8AccessState) when the banner is
   built; nothing here decides access. LOCKED is present but not yet revealed;
   INTERACTION (hover / focus / press) lets part of what is behind it show --
   never the entitled look; OPEN is the resolved Signal with a contained bloom.
   The Nebula stays inside the object in every state, and nothing loops. The
   access variables are typed so they can settle over time, not snap. */
@property --mute{ syntax:'<number>'; inherits:true; initial-value:0; }
@property --lock{ syntax:'<number>'; inherits:true; initial-value:0; }
@property --bolt{ syntax:'<number>'; inherits:true; initial-value:0; }
@property --cold{ syntax:'<number>'; inherits:true; initial-value:0; }
@property --seat-lit{ syntax:'<number>'; inherits:true; initial-value:0; }
.hm-p8-sig{ transition:--mute 520ms var(--patient), --lock 520ms var(--patient), --bolt 520ms var(--patient), --cold 520ms var(--patient), --seat-lit 520ms var(--patient), filter 520ms var(--patient); }
.hm-p8.is-locked .hm-p8-sig{ --mute:1; --lock:1; --bolt:1; --cold:1; filter:brightness(.9) saturate(.8); }
.hm-p8.is-open .hm-p8-sig{ --seat-lit:1; filter:drop-shadow(0 0 9px rgba(226,25,181,.32)); }
.hm-p8.is-lit.is-locked .hm-p8-env{ opacity:.7; }
.hm-p8.is-lit.is-locked .hm-p8-light{ opacity:.55; }
.hm-p8.is-lit.is-locked .hm-p8-stage::after{ opacity:.5; }
.hm-p8.is-locked .hm-p8-btn:focus-visible .hm-p8-sig, .hm-p8.is-locked .hm-p8-btn:active .hm-p8-sig{ --mute:.4; --lock:.65; --bolt:.35; --cold:.55; filter:none; }
.hm-p8.is-locked .hm-p8-btn:focus-visible .hm-p8-env, .hm-p8.is-locked .hm-p8-btn:active .hm-p8-env{ filter:brightness(1.45); }
@media (hover:hover){
  .hm-p8.is-locked .hm-p8-btn:hover .hm-p8-sig{ --mute:.4; --lock:.65; --bolt:.35; --cold:.55; filter:none; }
  .hm-p8.is-locked .hm-p8-btn:hover .hm-p8-env{ filter:brightness(1.45); }
}

/* ---- Chapters: one page, three spatial moments (V3.5, corrected V3.6) ----
   TODAY, DISCOVER and BEYOND TODAY are one continuous page. Chapters are
   separated by space, and by LIGHT that belongs to the objects in them -- never
   by a container fill. (V3.5/V3.6 first drew DISCOVER as one dark well; it read
   as a black block. Konto's depth is light on objects and fades into the page;
   Start follows that, see "V3.6 -- the light Start sits in" below.) Within a
   chapter things sit close (--hm-in); between chapters the page breathes. No
   Nebula, no Magenta: only PL8 is lit in colour. */
.home{ --hm-in:22px; --lit:214,198,226; --hi:245,241,236; --act:226,25,181; }
.hm-well{ margin:28px 0 0; padding:40px 0 46px; }
.hm-well .hm-discover{ margin-top:0; }
.hm-well .hm-pulse{ margin-top:36px; }
.hm-beyond .hm-p8{ margin-top:10px; }

/* Story: the editorial sibling of PL8 -- the same object family (radius,
   width, kicker / line / action rhythm), a quieter material: a warm-neutral dark
   surface lit from the left by the room's own light and one reflected top edge.
   No Nebula, no Magenta, no Signal; the action is the neutral destination
   capsule. */
.hm-story{ --arc:64px; position:relative; isolation:isolate; overflow:hidden; display:flex; align-items:center; justify-content:space-between; gap:12px; width:100%; min-height:86px; margin:10px 0 0; padding:14px 13px 14px 64px; border-radius:18px;
  background:radial-gradient(70% 130% at 0% 50%, rgba(var(--lit), .085) 0%, rgba(var(--lit), 0) 72%), linear-gradient(165deg, #25212B 0%, #17141B 100%);
  border:0; box-shadow:inset 0 1px 0 rgba(var(--hi), .09), inset 0 0 0 1px rgba(var(--hi), .05);
  color:inherit; font:inherit; text-align:left; cursor:pointer; transition:transform 160ms ease, background-color 300ms var(--patient); }
.hm-story::before{ content:''; position:absolute; z-index:-1; left:0; top:0; bottom:0; width:64px; pointer-events:none; opacity:.85;
  background:
    radial-gradient(circle calc(var(--arc) * 1.05) at calc(var(--arc) * .3) 58%, rgba(84,66,74,0) 0 62%, rgba(214,188,176,.34) 65%, rgba(122,98,108,.2) 76%, rgba(112,90,100,0) 100%),
    radial-gradient(circle calc(var(--arc) * 1.5) at calc(var(--arc) * -.15) 64%, rgba(84,66,74,0) 0 60%, rgba(204,176,168,.26) 63%, rgba(104,84,94,.17) 76%, rgba(96,78,88,0) 100%),
    radial-gradient(70% 120% at 0% 0%, rgba(228,204,192,.26), rgba(228,204,192,0) 68%);
  -webkit-mask-image:linear-gradient(90deg, #000 0%, #000 34%, transparent 100%); mask-image:linear-gradient(90deg, #000 0%, #000 34%, transparent 100%);
  transition:opacity 340ms var(--patient); }
.hm-story:focus-visible::before, .hm-story.is-pressed::before{ opacity:1; }
@media (hover:hover){ .hm-story:hover::before{ opacity:1; } }
.hm-story-body{ display:block; min-width:0; }
.hm-story .hm-k{ color:var(--muted); }
.hm-story-line{ display:block; margin-top:5px; font-family:var(--font-d); font-weight:500; font-size:20px; line-height:1.15; letter-spacing:.005em; color:rgba(var(--hi), .9);
  transition:color 300ms var(--patient); }
@media (hover:hover){ .hm-story:hover .hm-story-line{ color:var(--text-primary); } }
.hm-story:focus-visible .hm-story-line{ color:var(--text-primary); }
.hm-story.is-pressed{ transform:scale(.985); }
/* ---- V3.6 -- the light Start sits in (one grammar with Konto and Community) -
   GROUND: the dark canvas is continuous; nothing on Start is a darker box.
   SURFACE: an object separates from the ground by tonal lift + directional
   light + one lit top edge (Konto's Küche entries; the plate; the Story
   doorway). LIGHT: belongs to an object or a composition and is transparent
   before it reaches any edge (Konto's .acct-card::after); on Free Start it is
   the warm neutral (--lit) Konto's second radial uses -- Magenta stays where
   an object is identity (Konto's Signal, PL8's action), Nebula stays PL8's.
   EDGE: a 1px lit top edge on objects; no outlines except a doorway hairline.
   MOTION: only in answer to real interaction (scroll focus, hover, touch),
   short, settled, absent under reduced motion. SIGNAL: identity and
   interaction only, never direction. Start is the editorial dialect: light
   pools behind compositions and objects that respond to the swipe; Konto is
   the calm dialect (settings, static); Community is the conversational one. */

/* TODAY sits in a lit space: a pool behind the recipe and greeting, a quieter
   reflection where the ingredient column stands, a floor of light under the
   hero. Masked on all four sides so the light has no beginning and no end. */
.hm-a{ position:relative; isolation:isolate; }
.hm-a::before{ content:''; position:absolute; z-index:-1; pointer-events:none;
  inset:-150px calc(-1 * var(--hm-g)) -34px;
  background:
    radial-gradient(72% 50% at 24% 36%, rgba(var(--lit), .17) 0%, rgba(var(--lit), .05) 46%, rgba(var(--lit), 0) 74%),
    radial-gradient(46% 44% at 86% 32%, rgba(var(--hi), .10) 0%, rgba(var(--hi), 0) 72%),
    radial-gradient(66% 24% at 38% 96%, rgba(var(--lit), .10) 0%, rgba(var(--lit), 0) 74%);
  -webkit-mask-image:linear-gradient(180deg, transparent 0%, #000 24%, #000 76%, transparent 100%), linear-gradient(90deg, transparent 0%, #000 14%, #000 86%, transparent 100%);
  -webkit-mask-composite:source-in;
  mask-image:linear-gradient(180deg, transparent 0%, #000 24%, #000 76%, transparent 100%), linear-gradient(90deg, transparent 0%, #000 14%, #000 86%, transparent 100%);
  mask-composite:intersect; }
/* The ingredient column belongs to the composition: secondary, but lit
   enough to be read as material rather than as disabled type. */
.hm-hero-ing .hm-w{ color:rgba(var(--hi), .52); }

/* DISCOVER: the light of the shelves -- a pool behind the plates, one where
   the eye lands after them, a floor reflection; all masked on four sides. */
.hm-well{ position:relative; isolation:isolate; }
.hm-well::before{ content:''; position:absolute; z-index:-1; pointer-events:none;
  inset:-70px calc(-1 * var(--hm-g)) -50px;
  background:
    radial-gradient(56% 46% at 30% 38%, rgba(var(--lit), .21) 0%, rgba(var(--lit), .07) 50%, rgba(var(--lit), 0) 76%),
    radial-gradient(40% 40% at 82% 30%, rgba(var(--hi), .12) 0%, rgba(var(--hi), 0) 74%),
    radial-gradient(52% 26% at 26% 92%, rgba(var(--lit), .12) 0%, rgba(var(--lit), 0) 76%);
  -webkit-mask-image:linear-gradient(180deg, transparent 0%, #000 22%, #000 78%, transparent 100%), linear-gradient(90deg, transparent 0%, #000 16%, #000 84%, transparent 100%);
  -webkit-mask-composite:source-in;
  mask-image:linear-gradient(180deg, transparent 0%, #000 22%, #000 78%, transparent 100%), linear-gradient(90deg, transparent 0%, #000 16%, #000 84%, transparent 100%);
  mask-composite:intersect; }
/* Community / Am Tisch: the same room, a change of mode -- objects (the
   shelves) give way to a voice. It is ruled the way Community rules its tables
   (a hairline that fades out, no box), lit by the same neutral pool, and it is
   still until approached. Approaching the voice or its doorway -- pointer,
   focus or touch -- lets a little of Konto's/Community's Magenta belonging
   light rise toward the destination (.hm-pulse-lit, the ct-tables radial's
   dialect): a conversation opening, never a second resting accent. The
   carousel keeps the only Magenta at rest. No card. */
.hm-pulse{ position:relative; isolation:isolate; }
.hm-pulse::before{ content:''; position:absolute; z-index:-1; pointer-events:none; inset:-46px -32px -30px;
  background:radial-gradient(64% 62% at 24% 46%, rgba(var(--lit), .17) 0%, rgba(var(--lit), .05) 52%, rgba(var(--lit), 0) 78%);
  -webkit-mask-image:linear-gradient(180deg, transparent 0%, #000 26%, #000 74%, transparent 100%), linear-gradient(90deg, transparent 0%, #000 18%, #000 82%, transparent 100%);
  -webkit-mask-composite:source-in;
  mask-image:linear-gradient(180deg, transparent 0%, #000 26%, #000 74%, transparent 100%), linear-gradient(90deg, transparent 0%, #000 18%, #000 82%, transparent 100%);
  mask-composite:intersect; }
.hm-pulse::after{ content:''; position:absolute; left:0; right:0; top:-20px; height:1px; pointer-events:none;
  background:linear-gradient(90deg, rgba(var(--hi), .14), rgba(var(--hi), .05) 46%, rgba(var(--hi), 0) 82%); }
.hm-pulse-lit{ position:absolute; z-index:-1; pointer-events:none; inset:-40px -40px -34px -10px; opacity:0;
  background:radial-gradient(52% 64% at 84% 68%, rgba(var(--act), .115) 0%, rgba(var(--act), .035) 52%, rgba(var(--act), 0) 78%);
  -webkit-mask-image:linear-gradient(180deg, transparent 0%, #000 30%, #000 70%, transparent 100%), linear-gradient(90deg, transparent 0%, #000 20%, #000 80%, transparent 100%);
  -webkit-mask-composite:source-in;
  mask-image:linear-gradient(180deg, transparent 0%, #000 30%, #000 70%, transparent 100%), linear-gradient(90deg, transparent 0%, #000 20%, #000 80%, transparent 100%);
  mask-composite:intersect; transition:opacity 420ms var(--patient); }
.hm-pulse:has(:focus-visible) .hm-pulse-lit, .hm-pulse:has(.is-pressed) .hm-pulse-lit{ opacity:1; }
@media (hover:hover){ .hm-pulse:has(.hm-pulse-quote:hover, .hm-go-btn:hover) .hm-pulse-lit{ opacity:1; } }
.hm-well .hm-cont{ background:radial-gradient(64% 50% at 0% 50%, rgba(var(--lit), .07) 0%, rgba(var(--lit), 0) 100%); }

/* Cooking Memory on Start: a remembered state, not a notification -- the same
   row and hairline as every continuation, lifted quietly from the left. */
.hm-cont.is-mem{ background:radial-gradient(64% 50% at 0% 50%, rgba(var(--lit), .085) 0%, rgba(var(--lit), 0) 100%); }
.hm-cont.is-mem .hm-cont-k{ color:var(--text-secondary); }
.hm-cont.is-mem .hm-cont-sub{ color:var(--muted); }

@media (min-width:1024px){
  /* A wider room behind the hero and the shelves on desktop. */
  .hm-a::before{ inset:-170px -48px -40px; }
  .hm-well::before{ inset:-80px -48px -60px; }
  .hm-pulse::before{ inset:-50px -48px -34px; }
}

/* Arrival — once, and then the page is still. The ingredients arrive from
   the edge as the recipe name rises; the week settles last. */
@keyframes hmRise{ from{ opacity:0; transform:translateY(12px); } }
@keyframes hmWordIn{ from{ opacity:0; transform:translateX(34px); } }
@keyframes hmMarkIn{ from{ transform:scale(.3); opacity:0; } }
.home.is-arriving .hm-title{ animation:hmRise 560ms cubic-bezier(.22,.61,.36,1) 40ms both; }
.home.is-arriving .hm-hero-main{ animation:hmRise 640ms cubic-bezier(.22,.61,.36,1) 130ms both; }
.home.is-arriving .hm-hero-ing .hm-w{ animation:hmWordIn 760ms cubic-bezier(.16,.84,.3,1) calc(200ms + var(--n) * 70ms) both; }
.home.is-arriving .hm-cont, .home.is-arriving .hm-b{ animation:hmRise 640ms cubic-bezier(.22,.61,.36,1) 360ms both; }
.home.is-arriving .hm-mark.is-on{ animation:hmMarkIn 480ms cubic-bezier(.22,.61,.36,1) calc(420ms + var(--i) * 45ms) both; }
/* The voice from the Tables enters when it is reached, not on load. */
.hm-pulse .hm-pulse-quote, .hm-pulse .hm-pulse-foot, .hm-pulse .hm-pulse-quiet{ opacity:0; transform:translateY(10px);
  transition:opacity 700ms ease, transform 700ms cubic-bezier(.22,.61,.36,1); }
.hm-pulse .hm-pulse-foot{ transition-delay:120ms; }
.hm-pulse.is-in .hm-pulse-quote, .hm-pulse.is-in .hm-pulse-foot, .hm-pulse.is-in .hm-pulse-quiet{ opacity:1; transform:none; }

@media (min-width:641px){
  .hm-slide{ flex-basis:300px; }
  .hm-slide.is-door{ flex-basis:170px; }
  .hm-hero-ing{ width:220px; }
  .hm-hero-ing .hm-w{ font-size:calc(22px - var(--n) * .8px); }
}
@media (min-width:1024px){
  .home{ --hm-gap:48px; }
  /* The chapters stack on the continuous page; BEYOND pairs PL8 and Story. */
  .hm-well{ margin:40px 0 0; padding:52px 0 56px; }
  .hm-well .hm-c{ max-width:calc(50% - 24px); }
  /* BEYOND: two bounded objects, stacked -- PL8 the deeper, larger one, Story
     the quieter sibling of the same family. Both keep their Nebula / light
     INSIDE their own edge (overflow:hidden). The last row resolves into the
     page through its margin-bottom. Text columns align (154px). */
  .hm-beyond{ display:block; margin:24px 0 40px; }
  .hm-beyond .hm-p8{ margin:0; }
  .hm-beyond .hm-story{ margin:14px 0 0; }
  .hm-p8-btn{ column-gap:32px; padding:26px 26px 26px 26px; min-height:136px; }
  .hm-p8-env{ background:
    radial-gradient(340px 230px at 122px 50%, rgba(72,56,190,.56) 0%, rgba(44,34,130,.3) 46%, rgba(18,14,44,0) 100%),
    radial-gradient(46% 130% at 44% 62%, rgba(86,68,214,.16) 0%, rgba(86,68,214,0) 72%),
    radial-gradient(38% 120% at 90% 36%, rgba(96,78,224,.08) 0%, rgba(96,78,224,0) 74%),
    radial-gradient(420px 150px at 78% 118%, rgba(118,98,255,.16) 0%, rgba(118,98,255,0) 70%),
    linear-gradient(100deg, #140F31 0%, #110E24 50%, #0F0D18 100%); }
  .hm-p8-stage{ width:96px; height:96px; }
  .hm-p8 .hm-p8-sig{ --ts-d:88px; left:4px; top:4px; width:88px; height:88px; }
  .hm-p8-light{ width:300px; height:300px; margin:-150px 0 0 -150px; }
  .hm-p8-stage::after{ left:8px; top:4px; width:36px; height:28px; }
  .hm-p8-line{ font-size:26px; line-height:1.14; margin-top:9px; }
  .hm-p8-go{ padding:12px 16px; font-size:10.5px; }
  .hm-story{ --arc:170px; min-height:96px; padding:18px 26px 18px 154px; }
  .hm-story::before{ width:250px; }
  .hm-story-line{ font-size:24px; margin-top:7px; }
  .hm-title{ font-size:44px; }
  .hm-hero{ column-gap:40px; margin-top:22px; }
  .hm-hero-name, .hm-hero-name.is-long{ font-size:60px; }
  .hm-hero-ing{ width:260px; margin-right:0; margin-top:34px; }
  .hm-hero-ing .hm-w{ font-size:calc(26px - var(--n) * 1px); }
  .hm-hero-proof, .hm-cont, .hm-hero-meta{ max-width:46ch; }
  .hm-slide{ flex-basis:calc((100% - 2 * 12px) / 3.2); }
  /* Several plates are fully in view here: swipe disclosure is a phone
     mechanism, so every visible recipe shows its reason and time. */
  .hm-slide-more{ opacity:1; transform:none; }
  .hm-slide-name{ opacity:calc(1 - var(--pa) * .12); transform:none; }
  .hm-slide.is-door{ flex-basis:190px; }
  .hm-p8-btn{ padding:15px; }
  .hm-p8-line{ font-size:16px; }
}
@media (prefers-reduced-motion: reduce){
  .home.is-arriving .hm-title, .home.is-arriving .hm-hero-main, .home.is-arriving .hm-hero-ing .hm-w,
  .home.is-arriving .hm-cont, .home.is-arriving .hm-b, .home.is-arriving .hm-mark.is-on{ animation:none; }
  .hm-weekpanel, .hm-p8-light, .hm-p8-btn, .hm-p8-env, .hm-p8-env::before, .hm-p8-stage::after, .hm-tab, .hm-tab::after, .hm-story, .hm-mark,
  .hm-arw, .hm-slide-btn, .hm-slide-words::after, .hm-pulse-lit{ transition:none; }
  .hm-p8-sig, .hm-p8-env{ transition:none; }
  [data-ot].is-pressed .hm-arw, [data-ot]:focus-visible .hm-arw{ transform:none; }
  .hm-story.is-pressed{ transform:none; }
  .hm-story::before{ transition:none; }
  .hm-p8-btn:hover{ translate:none; }
  .hm-slide-words{ scale:none; translate:none; }
  .hm-slide-btn:hover{ translate:none; }
  .hm-weekpanel .hm-day .hm-dg, .hm-weekpanel .hm-day-l, .hm-weekpanel .hm-weekline, .hm-weekpanel .hm-weeknote, .hm-weekpanel .hm-focus{ transition:none; transform:none; opacity:1; }
  .hm-slide-text, .hm-slide-more{ transform:none; }
  .hm-slide-more{ opacity:1; }
  .hm-hero-ing{ translate:none; }
  .hm-hero-ing .hm-w{ opacity:calc(1 - var(--n) * .13); }
  .hm-pulse .hm-pulse-quote, .hm-pulse .hm-pulse-foot, .hm-pulse .hm-pulse-quiet{ opacity:1; transform:none; transition:none; }
}

/* ---- WOCHENFOKUS (START V3.6.1) ------------------------------------------
   One weekly object, not seven form rows: seven day states share ONE quiet
   surface (the Story material family: warm-neutral dark, light from the left,
   one lit top edge). The day itself is the control; there is no repeated
   button. Ring = the Signal's open-ring vocabulary as a day STATE, never
   seven Signals. Magenta has one job here: today, once marked, and the
   member's own approach (hover / focus / press); every other marked day
   settles into neutral light. Motion is state feedback only. */
.wf{ --lit:214,198,226; --hi:245,241,236; --act:226,25,181; }
.wf-h1{ font-size:24px; margin:0 0 12px; }
.wf-support{ color:var(--text-body); font-size:14.5px; line-height:1.6; margin:0 0 24px; }
.wf-week{ list-style:none; margin:0; padding:20px 6px 14px; display:grid; grid-template-columns:repeat(7, minmax(0, 1fr)); border-radius:18px; position:relative; isolation:isolate; overflow:hidden;
  background:radial-gradient(90% 120% at 10% 0%, rgba(var(--lit), .085) 0%, rgba(var(--lit), 0) 70%), linear-gradient(165deg, #25212B 0%, #17141B 100%);
  box-shadow:inset 0 1px 0 rgba(var(--hi), .09), inset 0 0 0 1px rgba(var(--hi), .05); }
.wf-day{ display:flex; justify-content:center; min-width:0; }
.wf-day-btn, .wf-day-ro{ display:flex; flex-direction:column; align-items:center; gap:9px; width:100%; min-height:76px; padding:4px 0 2px; margin:0; background:none; border:0; color:inherit; font:inherit; }
.wf-day-btn{ cursor:pointer; border-radius:14px; -webkit-tap-highlight-color:transparent; }
.wf-day-btn:focus-visible{ outline:2px solid var(--magenta); outline-offset:1px; }
.wf-g{ display:block; width:clamp(32px, 9.4vw, 44px); aspect-ratio:1; color:rgba(var(--hi), .3); transition:color 320ms var(--patient), filter 320ms var(--patient); }
.wf-ring{ transition:stroke-width 320ms var(--patient); }
.wf-chk{ opacity:0; transition:opacity 320ms var(--patient); }
.wf-day.is-on .wf-chk{ opacity:1; }
.wf-day.is-past .wf-g{ color:rgba(var(--hi), .15); }
.wf-day.is-past.is-on .wf-g, .wf-day.is-future.is-on .wf-g{ color:rgba(var(--hi), .82); }
.wf-day.is-today .wf-g{ color:rgba(var(--hi), .94); }
.wf-day.is-today .wf-ring{ stroke-width:1.5; }
.wf-day.is-today.is-on .wf-g{ color:var(--magenta); filter:drop-shadow(0 0 7px rgba(var(--act), .34)); }
.wf-day-btn:focus-visible .wf-g, .wf-day-btn:active .wf-g{ color:rgba(var(--act), .86); }
@media (hover:hover){ .wf-day-btn:hover .wf-g{ color:rgba(var(--act), .86); } }
.wf-day-l{ font-family:var(--font-c); font-size:9.5px; font-weight:600; letter-spacing:.16em; text-transform:uppercase; color:var(--muted); }
.wf-day.is-today .wf-day-l{ color:var(--text-primary); }
.wf-day.is-past .wf-day-l{ opacity:.7; }
.wf-day.is-pending .wf-g{ color:rgba(var(--hi), .11); }
.wf-day.is-busy{ opacity:.7; }
.wf-msg{ min-height:0; margin:10px 2px 0; font-size:12.5px; color:var(--text-secondary); }
.wf-msg:empty{ display:none; }
/* The contextual surface: a calm acknowledgement, never a reward. */
.wf-today{ position:relative; isolation:isolate; overflow:hidden; margin:12px 0 0; padding:20px 22px 22px; border-radius:18px;
  background:radial-gradient(70% 130% at 0% 50%, rgba(var(--lit), .085) 0%, rgba(var(--lit), 0) 72%), linear-gradient(165deg, #25212B 0%, #17141B 100%);
  box-shadow:inset 0 1px 0 rgba(var(--hi), .09), inset 0 0 0 1px rgba(var(--hi), .05); animation:wfIn 260ms var(--patient) both; }
.wf-today.is-marked::after{ content:''; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:radial-gradient(48% 110% at 100% 50%, rgba(var(--act), .075) 0%, rgba(var(--act), 0) 72%); }
.wf-today-k{ margin:0; font-family:var(--font-c); font-size:9.5px; font-weight:600; letter-spacing:.18em; text-transform:uppercase; color:var(--muted); }
.wf-today-h{ margin:8px 0 0; font-family:var(--font-d); font-weight:500; text-transform:none; font-size:clamp(22px, 6.4vw, 28px); line-height:1.12; color:var(--text-primary); }
.wf-today-p{ margin:8px 0 0; font-size:14px; line-height:1.5; color:var(--text-secondary); max-width:30em; }
.wf-today.is-quiet .wf-today-h{ color:var(--text-secondary); }
.wf-skel{ height:1em; border-radius:6px; background:rgba(var(--hi), .05); color:transparent; }
.wf-link{ background:none; border:0; padding:0; font:inherit; color:var(--text-primary); text-decoration:underline; text-decoration-color:rgba(var(--hi), .3); text-underline-offset:3px; cursor:pointer; }
@keyframes wfIn{ from{ opacity:0; transform:translateY(4px); } }
/* Optional calendar hand-off: secondary, neutral, one honest action. */
.wf-cal{ margin-top:22px; }
.wf-cal-btn{ display:flex; align-items:center; justify-content:space-between; gap:12px 16px; flex-wrap:wrap; width:100%; min-height:52px; padding:0; background:none; border:0; text-align:left; color:inherit; font:inherit; cursor:pointer; }
.wf-cal-btn:focus-visible{ outline:2px solid var(--magenta); outline-offset:4px; border-radius:10px; }
.wf-cal-k{ font-size:13px; color:var(--text-secondary); }
.wf-note{ margin:8px 0 0; font-size:12px; line-height:1.5; color:var(--muted); max-width:34em; }
/* The compact START week while the private state is not known yet: neutral, never a false "empty". */
.hm-mark.is-pending, .hm-day.is-pending{ color:rgba(245,241,236,.14); }
@media (prefers-reduced-motion: reduce){
  .wf-g, .wf-ring, .wf-chk{ transition:none; }
  .wf-today{ animation:none; }
}


/* ======================================================================
   SHOPPING LIST V1 (Phase 16). Same tokens and rhythm as Recipe Detail:
   one calm column on a phone, a list plus a quiet rail from 1024px. No
   score, no ring, no category chrome -- the list is alphabetical.
   ====================================================================== */
.rx-head{ display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:4px 16px; }
.rx-utils{ display:contents; }
.rx-shop{ display:inline-flex; align-items:center; gap:8px; min-height:44px; padding:0 4px; border:0; background:none; color:var(--text-body);
  font:inherit; font-size:15px; font-weight:600; cursor:pointer; text-decoration:underline; text-underline-offset:4px; text-decoration-color:var(--border-default); }
.rx-shop:hover{ color:var(--text-primary); }
.rx-shop:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:3px; border-radius:6px; }
.rx-shop-n{ display:inline-flex; align-items:center; justify-content:center; min-width:22px; height:22px; padding:0 6px; border-radius:999px;
  background:var(--surface-2); color:var(--text-primary); font-size:12.5px; font-variant-numeric:tabular-nums; }

.app-content-inner.sl{ --hi:245,241,236; --lit:214,198,226; --act:226,25,181; max-width:640px; }
.sl .bt-detail-back{ display:inline-flex; align-items:center; gap:7px; min-height:44px; padding:0; margin:0 0 12px; }
.sl .bt-detail-back svg{ flex:none; width:17px; height:17px; }
.sl-h1{ margin:0 !important; overflow-wrap:break-word; }
.sl-meta{ margin:8px 0 0; font-size:14px; color:var(--text-secondary); font-variant-numeric:tabular-nums; }
.sl-actions{ display:flex; flex-wrap:wrap; align-items:center; gap:8px 16px; margin:20px 0 0; }
.sl-plan{ display:inline-flex; align-items:center; justify-content:center; min-height:48px; padding:0 24px; border:0; border-radius:999px;
  background:var(--magenta); color:var(--canvas); font:inherit; font-size:15px; font-weight:700; cursor:pointer; -webkit-tap-highlight-color:transparent;
  transition:transform 160ms var(--patient), opacity 200ms var(--patient); }
.sl-plan:active{ transform:scale(.98); }
.sl-plan[disabled]{ background:rgba(245,241,236,.08); color:var(--text-secondary); cursor:default; transform:none; }
.sl-plan:focus-visible{ outline:2px solid rgba(var(--hi), .9); outline-offset:4px; }
.sl-dialog .cm-open-title{ margin-top:14px; }
.sl-plan-h{ font-size:13.5px; line-height:1.4; color:var(--text-secondary); }
@media (max-width:479px){ .sl-plan{ width:100%; } }

.sl-grid{ margin:24px 0 0; }
.sl-rail{ margin:36px 0 0; }
@media (min-width:1024px){
  .app-content-inner.sl{ max-width:1000px; }
  .sl-grid{ display:grid; grid-template-columns:minmax(0, 1fr) 280px; column-gap:56px; align-items:start; }
  .sl-rail{ margin-top:0; position:sticky; top:calc(65px + var(--preview-banner-h) + 28px); }
  .sl .bt-detail-back{ margin-bottom:18px; }
}

.sl-add{ display:flex; gap:8px; align-items:stretch; margin:0 0 18px; }
.sl-add-i{ flex:1 1 auto; min-width:0; min-height:48px; }
.sl-add-go{ flex:none; min-height:48px; padding:0 18px; border:1px solid var(--border-default); border-radius:999px; background:transparent; color:var(--text-primary);
  font:inherit; font-size:15px; font-weight:600; cursor:pointer; }
.sl-add-go:hover:not([disabled]){ border-color:var(--border-strong); }
.sl-add-go[disabled]{ opacity:.55; cursor:default; }
.sl-add-go:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:3px; }
.sl-msg{ margin:0 0 14px; font-size:14px; line-height:1.5; color:var(--text-secondary); }
.sl-msg.is-err{ color:#F0A2A2; }

.sl-list{ list-style:none; margin:0; padding:0; }
.sl-item{ display:grid; grid-template-columns:48px minmax(0, 1fr) auto; column-gap:6px; align-items:start; padding:2px 0; border-bottom:1px solid var(--hairline); }
.sl-item.is-manual .sl-body{ align-self:center; padding:12px 0; }
.sl-check{ position:relative; width:48px; height:48px; padding:0; border:0; background:none; cursor:pointer; display:flex; align-items:center; justify-content:center; -webkit-tap-highlight-color:transparent; }
.sl-check::before{ content:''; display:block; width:24px; height:24px; border-radius:8px; border:1.5px solid var(--border-default); box-sizing:border-box;
  transition:background 160ms var(--patient), border-color 160ms var(--patient); }
.sl-check .sl-tick{ position:absolute; width:16px; height:16px; color:var(--canvas); opacity:0; }
.sl-check.is-on::before{ background:var(--magenta); border-color:var(--magenta); }
.sl-check.is-on .sl-tick{ opacity:1; }
.sl-check:focus-visible{ outline:2px solid rgba(var(--hi), .9); outline-offset:-4px; border-radius:12px; }
.sl-body{ min-width:0; padding:12px 0; text-align:left; }
.sl-toggle{ display:grid; grid-template-columns:5.6em minmax(0, 1fr); column-gap:12px; align-items:baseline; width:100%; margin:0; border:0; background:none; font:inherit;
  color:inherit; cursor:pointer; min-height:48px; box-sizing:border-box; }
.sl-toggle:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:2px; border-radius:6px; }
.sl-q{ font-size:16px; font-weight:600; color:var(--text-primary); font-variant-numeric:tabular-nums; overflow-wrap:anywhere; }
.sl-n{ font-size:16px; line-height:1.4; color:var(--text-body); overflow-wrap:anywhere; }
.sl-sub{ grid-column:2; display:flex; flex-wrap:wrap; gap:2px 10px; margin-top:3px; font-size:13.5px; line-height:1.4; color:var(--text-secondary); }
.sl-src{ overflow-wrap:anywhere; }
.sl-opt-tag{ display:inline-block; padding:0 7px; border:1px solid var(--border-default); border-radius:999px; font-size:11.5px; line-height:18px; letter-spacing:.02em; white-space:nowrap; }
.sl-x{ align-self:center; min-height:44px; padding:0 8px; border:0; background:none; color:var(--text-secondary); font:inherit; font-size:13.5px; text-decoration:underline; text-underline-offset:3px; cursor:pointer; }
.sl-x:hover{ color:var(--text-primary); }
.sl-x:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:2px; border-radius:4px; }
.sl-item.is-done .sl-q, .sl-item.is-done .sl-n{ color:var(--text-secondary); text-decoration:line-through; text-decoration-color:rgba(167,159,164,.55); }

.sl-detail{ grid-column:2 / -1; padding:0 0 14px; }
.sl-srcs{ list-style:none; margin:0; padding:0; font-size:14px; line-height:1.45; color:var(--text-body); }
.sl-srcs li{ padding:3px 0; overflow-wrap:anywhere; }
.sl-srcs-n{ color:var(--text-primary); }
.sl-srcs-note{ display:block; color:var(--text-secondary); font-size:13.5px; }
.sl-target{ margin:12px 0 0; }
.sl-target-l{ display:block; font-size:13px; color:var(--text-secondary); margin:0 0 6px; }
.sl-target-row{ display:flex; flex-wrap:wrap; align-items:center; gap:8px 10px; }
.sl-target-i{ width:6.5em; min-height:44px; text-align:right; font-variant-numeric:tabular-nums; }
.sl-target-u{ font-size:15px; color:var(--text-body); }
.sl-target-h{ margin:8px 0 0; font-size:12.5px; color:var(--text-secondary); }
.sl-mini{ display:inline-flex; align-items:center; justify-content:center; min-height:44px; padding:0 16px; border:1px solid var(--border-default); border-radius:999px; background:transparent;
  color:var(--text-primary); font:inherit; font-size:14px; font-weight:600; cursor:pointer; }
.sl-mini.is-quiet{ border-color:transparent; color:var(--text-secondary); text-decoration:underline; text-underline-offset:3px; font-weight:500; padding:0 8px; }
.sl-mini:hover{ border-color:var(--border-strong); }
.sl-mini:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:2px; }

.sl-alldone{ margin:8px 0 0; font-family:var(--font-d); font-weight:500; text-transform:uppercase; font-size:22px; color:var(--text-primary); }
.sl-done{ margin:24px 0 0; }
.sl-done-h{ display:inline-flex; align-items:center; gap:10px; min-height:48px; padding:0; border:0; background:none; color:var(--text-secondary);
  font:inherit; font-size:15px; font-weight:600; cursor:pointer; }
.sl-done-h::after{ content:''; width:8px; height:8px; border-right:1.5px solid currentColor; border-bottom:1.5px solid currentColor; transform:rotate(45deg); margin:-3px 0 0 2px; transition:transform 160ms var(--patient); }
.sl-done-h[aria-expanded="true"]::after{ transform:rotate(-135deg); margin-top:3px; }
.sl-done-h:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:2px; border-radius:6px; }
.sl-done-n{ font-variant-numeric:tabular-nums; }

.sl-empty{ margin:12px 0 0; padding:28px 22px; border-radius:18px; background:var(--surface); box-shadow:inset 0 0 0 1px var(--hairline); }
.sl-empty-h{ margin:0; font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:clamp(22px, 6vw, 26px); line-height:1.1; color:var(--text-primary); }
.sl-empty-p{ margin:10px 0 20px; font-size:15.5px; line-height:1.6; color:var(--text-body); max-width:38ch; }

.sl-h2{ margin:0 0 6px; font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:18px; letter-spacing:.01em; color:var(--text-primary); }
.sl-rec-list{ list-style:none; margin:0; padding:0; }
.sl-rec-list li{ display:flex; align-items:center; justify-content:space-between; gap:12px; padding:2px 0; border-bottom:1px solid var(--hairline); }
.sl-rec-t{ min-width:0; display:flex; flex-direction:column; align-items:flex-start; padding:0 0 8px; }
.sl-rec-n{ display:inline-flex; align-items:center; min-height:44px; box-sizing:border-box; padding:6px 0 2px; color:var(--text-primary); text-decoration:none; overflow-wrap:anywhere; font-size:15px; }
.sl-rec-n:hover{ text-decoration:underline; }
.sl-rec-s{ margin:0; font-size:13px; color:var(--text-secondary); }
.sl-clear{ margin:18px 0 0; }
.sl-clear-q{ margin:0 0 8px; font-size:14px; color:var(--text-body); }
.sl-clear-row{ display:flex; gap:8px; }

/* dialogs */
.sl-opt-list{ list-style:none; margin:14px 0 0; padding:0; }
.sl-opt{ display:flex; align-items:center; gap:14px; min-height:52px; padding:4px 0; cursor:pointer; }
.sl-opt-i{ flex:none; width:24px; height:24px; margin:0; accent-color:var(--magenta); }
.sl-opt-t{ display:flex; flex-direction:column; min-width:0; }
.sl-opt-n{ font-size:16px; color:var(--text-primary); overflow-wrap:anywhere; }
.sl-opt-q{ font-size:13.5px; color:var(--text-secondary); }
.sl-plan-f{ display:grid; grid-template-columns:minmax(0, 1.4fr) minmax(0, 1fr); gap:12px; margin:16px 0 0; }
.sl-plan-c .cf-input{ width:100%; min-height:48px; box-sizing:border-box; }
.sl-plan-l{ display:block; font-size:13px; color:var(--text-secondary); margin:0 0 6px; }
.sl-dialog .sl-plan-h{ margin:10px 0 0; min-height:1.4em; color:#F0A2A2; }
.sl-plan-note{ margin:6px 0 0; font-size:13px; line-height:1.5; color:var(--text-secondary); }
@media (max-width:359px){ .sl-plan-f{ grid-template-columns:1fr; } }
@media (prefers-reduced-motion: reduce){ .sl-plan, .sl-check::before, .sl-done-h::after{ transition:none; } }

/* Retail package OPTIONS in the expanded item detail (Phase 18a): quiet, informational, no table. */
.sl-ro{ margin:12px 0 0; padding:12px 0 0; border-top:1px solid var(--hairline); }
.sl-ro-h{ margin:0 0 4px; font-size:13px; font-weight:600; letter-spacing:.02em; color:var(--text-secondary); }
.sl-ro-list{ list-style:none; margin:0; padding:0; }
.sl-ro-list li{ display:flex; flex-direction:column; padding:6px 0; }
.sl-ro-l{ font-size:15px; line-height:1.35; color:var(--text-primary); overflow-wrap:anywhere; font-variant-numeric:tabular-nums; }
.sl-ro-s{ font-size:13px; line-height:1.4; color:var(--text-secondary); overflow-wrap:anywhere; }
.sl-ro-n{ margin:4px 0 0; font-size:12.5px; color:var(--text-secondary); }

/* Purchase Confirmation (Phase 19): quiet, optional, supermarket-friendly. No score, no accounting chrome. */
.sl-buy-open{ display:inline-flex; align-items:center; min-height:48px; padding:0 8px; border:0; background:none; color:var(--text-primary); font:inherit; font-size:15px; font-weight:600;
  text-decoration:underline; text-underline-offset:4px; text-decoration-color:var(--border-default); cursor:pointer; }
.sl-buy-open:hover{ text-decoration-color:var(--border-strong); }
.sl-buy-open:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:2px; border-radius:6px; }
.sl-buy-note{ margin:10px 0 0; font-size:13.5px; line-height:1.4; color:var(--text-secondary); }
.sl-buy-list{ list-style:none; margin:14px 0 0; padding:0; }
.sl-buy-row{ padding:14px 0; border-top:1px solid var(--hairline); }
.sl-buy-h{ display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:2px 12px; }
.sl-buy-n{ font-size:16px; font-weight:600; color:var(--text-primary); overflow-wrap:anywhere; }
.sl-buy-need{ font-size:13.5px; color:var(--text-secondary); font-variant-numeric:tabular-nums; }
.sl-buy-in{ display:flex; align-items:center; gap:10px; margin:10px 0 0; }
.sl-buy-l{ font-size:13px; color:var(--text-secondary); }
.sl .sl-buy-i, .sl-dialog .sl-buy-i{ width:7em; min-height:48px; box-sizing:border-box; padding:0 12px; border:1px solid var(--border-default); border-radius:10px; background:var(--surface-2); color:var(--text-primary); font:inherit; font-size:16px; text-align:right; font-variant-numeric:tabular-nums; }
.sl .sl-buy-i:focus, .sl-dialog .sl-buy-i:focus{ outline:2px solid rgba(var(--hi), .85); outline-offset:1px; border-color:transparent; }
.sl-buy-u{ font-size:15px; color:var(--text-body); }
.sl-buy-ca{ display:flex; align-items:center; gap:10px; min-height:44px; font-size:14px; color:var(--text-body); cursor:pointer; }
.sl-buy-ca input{ width:22px; height:22px; accent-color:var(--magenta); }
.sl-buy-act{ display:flex; flex-wrap:wrap; gap:4px 8px; margin:4px 0 0; }
.sl-buy-sum{ margin:8px 0 0; font-size:15px; color:var(--text-primary); font-variant-numeric:tabular-nums; }
.sl-buy-res{ margin:2px 0 0; font-size:14px; color:var(--text-secondary); font-variant-numeric:tabular-nums; }
.sl-buy-res.is-remainder{ color:var(--text-primary); }
.sl-buy-stale{ margin:8px 0 0; font-size:13.5px; line-height:1.45; color:var(--text-secondary); }
.sl-buy-err{ margin:4px 0 0; font-size:13.5px; color:#F0A2A2; }
.sl-buy-empty{ margin:14px 0 0; font-size:15px; line-height:1.55; color:var(--text-body); }
.sl-buy-sumry{ margin:18px 0 0; padding:14px 0 0; border-top:1px solid var(--hairline); }

/* Vorrat / Pantry (Phase 20): quiet, Member-confirmed. No expiry, no scores, no categories. */
.sl-pan-list{ list-style:none; margin:0; padding:0; }
.sl-pan-offer{ padding:12px 0; border-top:1px solid var(--hairline); }
.sl-pan-offer:first-child{ border-top:0; }
.sl-pan-q{ margin:8px 0 0; font-size:16px; font-weight:600; color:var(--text-primary); }
.sl-pan-ok{ margin:6px 0 0; font-size:14px; color:var(--text-body); }
.sl-pan-items{ margin-top:12px; }
.sl-pan-row{ padding:14px 0; border-bottom:1px solid var(--hairline); }
.sl-pan-qty{ font-size:16px; font-weight:600; color:var(--text-primary); font-variant-numeric:tabular-nums; }
.sl-pan-when{ margin:2px 0 0; font-size:13px; color:var(--text-secondary); }

/* Pantry -> Recipe matching (Phase 23): factual, quiet. No scores, no percentages, no badges, no warning styling. */
.pm-cta{ margin:20px 0 4px; }
.pm-go{ max-width:100%; text-align:center; }
.pm{ margin:20px 0 8px; padding:18px 16px 8px; border-radius:18px; background:var(--surface); box-shadow:inset 0 0 0 1px var(--hairline); }
.pm-top{ display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:4px 12px; }
.pm-h{ margin:0; font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:clamp(20px, 5.5vw, 24px); line-height:1.1; color:var(--text-primary); overflow-wrap:anywhere; min-width:0; }
.pm-note, .pm-empty{ margin:10px 0 0; font-size:14.5px; line-height:1.55; color:var(--text-body); }
.pm-empty{ padding:6px 0 14px; max-width:46ch; }
.pm-list{ list-style:none; margin:8px 0 0; padding:0; }
.pm-card{ padding:14px 0 10px; border-top:1px solid var(--hairline); min-width:0; }
.pm-head{ display:flex; flex-direction:column; align-items:flex-start; gap:3px; width:100%; min-height:44px; padding:0; margin:0; border:0; background:none; color:inherit; font:inherit; text-align:left; cursor:pointer; -webkit-tap-highlight-color:transparent; }
.pm-head:focus-visible{ outline:2px solid rgba(var(--hi), .85); outline-offset:3px; border-radius:6px; }
.pm-name{ font-size:17px; font-weight:600; line-height:1.3; color:var(--text-primary); overflow-wrap:anywhere; max-width:100%; }
.pm-facts{ font-size:13.5px; color:var(--text-secondary); overflow-wrap:anywhere; }
.pm-line{ font-size:15px; color:var(--text-body); }
.pm-status{ font-size:15px; font-weight:600; color:var(--text-primary); overflow-wrap:anywhere; }
.pm-age{ margin:6px 0 0; font-size:13px; color:var(--text-secondary); }
.pm-serv{ display:flex; flex-wrap:wrap; align-items:center; gap:2px 8px; margin:8px 0 0; font-size:14px; color:var(--text-secondary); }
.pm-serv-k{ font-size:14px; }
.pm-serv-busy{ font-size:13px; color:var(--text-secondary); }
.pm-ings{ list-style:none; margin:10px 0 0; padding:0; }
.pm-ing{ display:grid; grid-template-columns:minmax(0, 1fr) auto; gap:1px 12px; padding:9px 0; border-top:1px solid var(--hairline); }
.pm-ing:first-child{ border-top:0; }
.pm-ing-n{ font-size:15px; color:var(--text-primary); overflow-wrap:anywhere; min-width:0; }
.pm-ing-s{ font-size:13.5px; color:var(--text-secondary); text-align:right; white-space:nowrap; }
.pm-ing.is-missing .pm-ing-s, .pm-ing.is-incompatible .pm-ing-s{ color:var(--text-body); }
.pm-ing-d{ grid-column:1 / -1; font-size:13px; line-height:1.4; color:var(--text-secondary); font-variant-numeric:tabular-nums; overflow-wrap:anywhere; }
.pm-act{ margin:6px 0 0; }
.rd-pm{ margin:24px 0 0; }
.rd-pm-h{ margin:0; font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:18px; line-height:1.1; color:var(--text-primary); }
.rd-pm-p{ margin:8px 0 0; font-size:13.5px; line-height:1.5; color:var(--text-secondary); max-width:46ch; }
@media (max-width:359px){ .pm{ padding:16px 12px 6px; } .pm-ing-s{ white-space:normal; } }
.pm .rx-retry{ display:inline-flex; align-items:center; min-height:44px; padding:0 4px; }

/* Manual Pantry entry (Phase 25B): one quiet add form on the Vorrat surface; explicit ADD with a preview. */
.pan-add-cta{ margin:16px 0 0; }
.pan-add{ margin:16px 0 4px; padding:18px 16px 12px; border-radius:18px; background:var(--surface); box-shadow:inset 0 0 0 1px var(--hairline); }
.pan-add-h{ margin:0 0 10px; font-family:var(--font-d); font-weight:600; text-transform:uppercase; font-size:clamp(20px, 5.5vw, 24px); line-height:1.1; color:var(--text-primary); }
.pan-add .sl-buy-l{ display:block; margin:10px 0 4px; }
.pan-add .cf-input{ box-sizing:border-box; min-height:48px; padding:0 12px; border:1px solid var(--border-default); border-radius:10px; background:var(--surface-2); color:var(--text-primary); font:inherit; font-size:16px; }
.pan-add .pan-add-sel{ width:100%; max-width:100%; text-overflow:ellipsis; }
.pan-add .pan-add-qi{ width:100%; }
.pan-add-row{ display:grid; grid-template-columns:minmax(0, 1fr) minmax(0, 1fr); gap:0 12px; }
.pan-add-q, .pan-add-u{ min-width:0; }
.pan-add-preview{ margin:8px 0 0; font-size:14px; line-height:1.45; color:var(--text-body); font-variant-numeric:tabular-nums; overflow-wrap:anywhere; }
.pan-add-fixed-unit{ min-height:48px; display:flex; align-items:center; padding:0 4px; margin:0; font-size:15px; color:var(--text-body); }

/* Release watch notice (docs/02-Infrastructure-Architecture/release-and-update-contract.md): minimal, non-modal,
   sits under the preview banner so it never covers navigation; shown only when a newer deployed build is confirmed. */
#crvd-update{ position:fixed; z-index:60; left:50%; transform:translateX(-50%);
  top:calc(var(--preview-banner-h) + 8px); max-width:min(92vw, 420px);
  display:flex; align-items:center; gap:12px; padding:8px 8px 8px 14px;
  background:var(--surface-elevated); color:var(--text-primary);
  border:1px solid var(--border-default); border-radius:var(--radius);
  font-family:var(--font-c); font-size:14px; }
#crvd-update button{ font-family:var(--font-c); font-weight:600; font-size:13px; border-radius:999px;
  border:1px solid var(--border-interactive); background:none; color:var(--text-primary);
  padding:6px 12px; cursor:pointer; }
#crvd-update button:focus-visible{ outline:2px solid var(--border-interactive); outline-offset:2px; }
