/* ═══════════════════════════════════════════════════════════════════════
   GENERATED FILE — DO NOT EDIT
   ═══════════════════════════════════════════════════════════════════════
   Built by scripts/build-web-css.cjs from:
     src/theme/brand-tokens.css   (Tier 0 — shared verbatim with the app)
     src/theme/web-tokens.css     (Tier 1 — web semantic layer)
     src/theme/web.css            (Tier 2 — recipes)

   Edit those, then run: npm run build:web-css
   ═══════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   hppy Brand Tokens — Tier 0
   ═══════════════════════════════════════════════════════════════════════

   THE rebrand surface. Every value a rebranding touches lives in the
   BRAND block below; everything after it is the neutral system scale
   (spacing, type, radius, motion) that a rebrand normally keeps.

   Two consumers share this file *verbatim*:

     app     `src/theme/variables.css` imports it and maps these
             primitives onto the Ionic palette and the app's semantic
             tokens.
     web     `scripts/build-web-css.cjs` concatenates it with
             `src/theme/web.css` into `public/assets/hppy.css`, which the
             standalone HTML pages under `public/` load. Those pages have
             no Ionic runtime.

   Two rules keep it shareable — breaking either breaks the web pages:

     1. No `--ion-*` references. Ionic does not exist on the static pages.
     2. No media queries. Light and dark values sit side by side as flat
        `-x` / `-x-dark` pairs; each consumer decides how to switch,
        because the app switches on `body` (per platform) while the web
        pages switch on `:root`.

   What deliberately stays in variables.css: the neutral ramp
   (medium/light/dark + the step scale), everything built on Ionic's
   surfaces (glass, chrome, semantic colors), the z-index scale (its
   values are dictated by Ionic's overlay stack) and the app-only
   component sizes. Those are app plumbing, not brand.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
    /* ═══════════════════════════════════════════════════════════════════
       MARKE 2026 — Brand Styleguide, Ausgabe 1.0
       ═══════════════════════════════════════════════════════════════════
       Two colors and one typeface, straight from the guide. Live on the
       web pages under public/.

       The APP's two brand hues now point here: --hppy-brand-primary takes
       navy in light and safran in dark, --hppy-brand-secondary the other
       one of the pair. Only --hppy-brand-font is still pre-rebrand (Inter,
       not Poppins) in the block below — the same one-line edit, held back
       because a typeface swap has to be read across dozens of screens.

       Contrast:
         white on navy   13.20  AAA     navy on safran  7.49  AAA
         navy on white   13.20  AAA     white on safran 1.76  FAIL

       Hence the guide's rule: safran is a SURFACE and accent color, never
       text on white. Text on safran is always navy; safran as a text color
       only on navy or black. */

    /* RAL 5022 Nachtblau — 60 % of any layout: surfaces, headers, body text */
    --hppy-brand-navy: #222d5a;
    --hppy-brand-navy-rgb: 34, 45, 90;
    --hppy-brand-navy-70: #4e5680;
    --hppy-brand-navy-45: #7a80a6;
    --hppy-brand-navy-20: #b7bacc;

    /* Signalgelb #FAB800 — 30 %: buttons, accents, highlights.
       Never at the same weight as navy; it only works as the exception.

       Was RAL 1017 Safrangelb (#f6a950) until the 2026-08 recolor. The token
       keeps the `safran` name — it is the slot for "the warm brand color" and
       renaming it would touch every consumer — but the hue is now the purer,
       more saturated yellow. It reads brighter and, being darker in luminance
       than the old tone, it also gains contrast: navy on it is 7.49 (AAA)
       where safran was 6.73 (AA).

       The -70/-45/-20 steps are straight mixes toward white at those
       percentages; RAL 1017's hand-tuned steps went with the old hue. */
    --hppy-brand-safran: #fab800;
    --hppy-brand-safran-rgb: 250, 184, 0;
    --hppy-brand-safran-70: #fccd4d;
    --hppy-brand-safran-45: #fddf8c;
    --hppy-brand-safran-20: #fef1cc;

    /* Neutrals — the remaining 10 %: whitespace and rules */
    --hppy-brand-white: #ffffff;
    --hppy-brand-paper: #f4f4f7;
    --hppy-brand-rule: #dfe1e6;

    /* Hausschrift Poppins — SemiBold 600 for headings, Regular 400 for
       body. The guide's fallbacks (Verdana Bold / Arial) are the last
       resort; build-web-css.cjs self-hosts the real thing. */
    --hppy-brand-web-font: 'Poppins', Verdana, Arial, Helvetica, sans-serif;

    /* Brand surfaces. The wordmark sits on one of these two, per the
       guide's logo variants (page 4): "Positiv auf Safrangelb" carries the
       light theme, "Negativ auf Nachtblau" the dark one. The tonal shift is
       small on purpose — a flat brand field, not a rainbow. */
    --hppy-gradient-safran: linear-gradient(135deg, var(--hppy-brand-safran) 0%, var(--hppy-brand-safran-70) 100%);
    --hppy-gradient-navy: linear-gradient(135deg, var(--hppy-brand-navy) 0%, #2e3a6b 100%);

    /* ═══════════════════════════════════════════════════════════════════
       APP PALETTE — driving the Ionic app; primary and secondary are on the
       2026 brand, the typeface and the status hues are not
       ═══════════════════════════════════════════════════════════════════ */

    /* ── Typeface ──
       Not the platform system font: Inter was chosen so type renders
       identically on iOS, Android and the web pages. The app loads it via
       `@fontsource-variable/inter`, the static pages via an @font-face in
       web.css pointing at the same file. */
    --hppy-brand-font: 'Inter Variable', 'Inter', system-ui, Helvetica, Arial, sans-serif;

    /* ── Hues ──
       Each hue carries the five values Ionic needs (base, rgb, contrast,
       shade, tint) in both themes. The `-dark` variants are brightened so
       they hold up on a dark surface — a rebrand must supply both. */

    /* primary — brand color, CTAs, active states.
       ON THE 2026 BRAND. The two themes take different hues rather than one
       hue at two brightnesses, because the guide's two colors are not
       interchangeable: safran is a surface color and fails on white at
       1.96:1, so light mode carries navy and dark mode carries safran —
       the same swap the wordmark makes between its two variants (page 4).

       Contrast: white on navy 13.20 AAA · navy on safran 7.49 AAA ·
       safran on the dark surface (#121212) 10.63 AAA.

       Shade and tint follow Ionic's own derivation, 12 % black / 10 % white,
       so they sit where the framework expects. They are NOT the guide's
       -70/-45/-20 steps above, which are a separate ramp for fills. */
    --hppy-brand-primary: #222d5a;
    --hppy-brand-primary-rgb: 34, 45, 90;
    --hppy-brand-primary-contrast: #ffffff;
    --hppy-brand-primary-contrast-rgb: 255, 255, 255;
    --hppy-brand-primary-shade: #1e284f;
    --hppy-brand-primary-tint: #38426b;

    /* Contrast is navy, not black: the guide's rule is that text on safran
       is always Nachtblau. */
    --hppy-brand-primary-dark: #fab800;
    --hppy-brand-primary-dark-rgb: 250, 184, 0;
    --hppy-brand-primary-dark-contrast: #222d5a;
    --hppy-brand-primary-dark-contrast-rgb: 34, 45, 90;
    --hppy-brand-primary-dark-shade: #dca200;
    --hppy-brand-primary-dark-tint: #fbbf1a;

    /* The guide's -70 step of whichever color primary currently is. NOT the
       same thing as -shade/-tint: those are Ionic's pressed/hover states and
       stay inside the hue, this is the designed ramp toward the light
       neutral (page 6) and cannot be derived with color-mix. It exists so a
       two-stop accent can run primary → primary-70 in both themes instead of
       borrowing secondary, which drifts once secondary is a different hue. */
    --hppy-brand-primary-70: var(--hppy-brand-navy-70);
    --hppy-brand-primary-dark-70: var(--hppy-brand-safran-70);

    /* secondary — accent, highlights.
       ON THE 2026 BRAND. The guide has two colors, so secondary is the other
       one: primary takes navy and secondary takes safran. In dark mode
       primary has already claimed safran, so secondary steps to the -70
       tone — otherwise the two-stop gradients built on primary → secondary
       (the section-title tick, the EmptyState wash) collapse into a flat
       band. Those two stops are then exactly --hppy-gradient-safran.

       Contrast is navy in BOTH themes, never white: navy on safran is
       7.49, on safran-70 8.79. White on safran is 1.76 and the guide rules
       it out outright. */
    --hppy-brand-secondary: #fab800;
    --hppy-brand-secondary-rgb: 250, 184, 0;
    --hppy-brand-secondary-contrast: #222d5a;
    --hppy-brand-secondary-contrast-rgb: 34, 45, 90;
    --hppy-brand-secondary-shade: #dca200;
    --hppy-brand-secondary-tint: #fbbf1a;

    --hppy-brand-secondary-dark: #fccd4d;
    --hppy-brand-secondary-dark-rgb: 252, 205, 77;
    --hppy-brand-secondary-dark-contrast: #222d5a;
    --hppy-brand-secondary-dark-contrast-rgb: 34, 45, 90;
    --hppy-brand-secondary-dark-shade: #deb444;
    --hppy-brand-secondary-dark-tint: #fcd25f;

    /* tertiary */
    --hppy-brand-tertiary: #5260ff;
    --hppy-brand-tertiary-rgb: 82, 96, 255;
    --hppy-brand-tertiary-contrast: #ffffff;
    --hppy-brand-tertiary-contrast-rgb: 255, 255, 255;
    --hppy-brand-tertiary-shade: #4854e0;
    --hppy-brand-tertiary-tint: #6370ff;

    --hppy-brand-tertiary-dark: #7482ff;
    --hppy-brand-tertiary-dark-rgb: 116, 130, 255;
    --hppy-brand-tertiary-dark-contrast: #000000;
    --hppy-brand-tertiary-dark-contrast-rgb: 0, 0, 0;
    --hppy-brand-tertiary-dark-shade: #6672e0;
    --hppy-brand-tertiary-dark-tint: #828eff;

    /* ── Status hues ──
       Part of the palette, but they carry meaning (positive / caution /
       destructive) — a rebrand should re-tune rather than reassign them. */

    --hppy-brand-success: #2dd36f;
    --hppy-brand-success-rgb: 45, 211, 111;
    --hppy-brand-success-contrast: #ffffff;
    --hppy-brand-success-contrast-rgb: 255, 255, 255;
    --hppy-brand-success-shade: #28ba62;
    --hppy-brand-success-tint: #42d77d;

    --hppy-brand-success-dark: #2fdf75;
    --hppy-brand-success-dark-rgb: 47, 223, 117;
    --hppy-brand-success-dark-contrast: #000000;
    --hppy-brand-success-dark-contrast-rgb: 0, 0, 0;
    --hppy-brand-success-dark-shade: #29c467;
    --hppy-brand-success-dark-tint: #44e283;

    --hppy-brand-warning: #ffc409;
    --hppy-brand-warning-rgb: 255, 196, 9;
    --hppy-brand-warning-contrast: #000000;
    --hppy-brand-warning-contrast-rgb: 0, 0, 0;
    --hppy-brand-warning-shade: #e0ac08;
    --hppy-brand-warning-tint: #ffca22;

    --hppy-brand-warning-dark: #ffd534;
    --hppy-brand-warning-dark-rgb: 255, 213, 52;
    --hppy-brand-warning-dark-contrast: #000000;
    --hppy-brand-warning-dark-contrast-rgb: 0, 0, 0;
    --hppy-brand-warning-dark-shade: #e0bb2e;
    --hppy-brand-warning-dark-tint: #ffd948;

    --hppy-brand-danger: #eb445a;
    --hppy-brand-danger-rgb: 235, 68, 90;
    --hppy-brand-danger-contrast: #ffffff;
    --hppy-brand-danger-contrast-rgb: 255, 255, 255;
    --hppy-brand-danger-shade: #cf3c4f;
    --hppy-brand-danger-tint: #ed576b;

    --hppy-brand-danger-dark: #ff4961;
    --hppy-brand-danger-dark-rgb: 255, 73, 97;
    --hppy-brand-danger-dark-contrast: #ffffff;
    --hppy-brand-danger-dark-contrast-rgb: 255, 255, 255;
    --hppy-brand-danger-dark-shade: #e04055;
    --hppy-brand-danger-dark-tint: #ff5b71;



    /* Text over any gradient or colored fill. White in both themes — the
       backdrop is the brand color, not the page. */
    --hppy-color-on-gradient: #ffffff;

    /* ═══════════════════════════════════════════════════════════════════
       SYSTEM SCALE — shared by app and web, rarely touched by a rebrand
       ═══════════════════════════════════════════════════════════════════ */

    /* Spacing (strict 4px base grid; numeric names carry their px value) */
    --hppy-space-3xs: 1px;
    --hppy-space-2xs: 2px;
    --hppy-space-xs: 4px;
    --hppy-space-sm: 8px;
    --hppy-space-12: 12px;
    /* between sm(8) and md(16) */
    --hppy-space-md: 16px;
    --hppy-space-20: 20px;
    /* between md(16) and lg(24) */
    --hppy-space-lg: 24px;
    --hppy-space-xl: 32px;
    --hppy-space-2xl: 48px;
    --hppy-space-3xl: 64px;

    /* Typography scale (2px increments at small end; numeric names for the
       native in-between sizes: 13 ≈ iOS footnote, 15 ≈ iOS subheadline) */
    --hppy-font-micro: 8px;
    /* date-tile micro labels (weekday/time) */
    --hppy-font-2xs: 10px;
    --hppy-font-xs: 11px;
    --hppy-font-sm: 12px;
    --hppy-font-13: 13px;
    --hppy-font-md: 14px;
    --hppy-font-15: 15px;
    --hppy-font-lg: 16px;
    --hppy-font-xl: 18px;
    --hppy-font-2xl: 20px;
    --hppy-font-3xl: 22px;
    --hppy-font-4xl: 24px;
    /* marketing display sizes — landing hero only, above the app's ramp
       because a web hero is read at arm's length, not in the hand */
    --hppy-font-display: 28px;
    --hppy-font-display-lg: 34px;

    /* Overline labels (uppercase micro-headers: ABOUT, CHAT, section
       headers) — one spacing app-wide; size is font-sm for labels with
       a gradient tick, font-xs for plain overlines */
    --hppy-overline-spacing: 0.06em;

    /* Font weights */
    --hppy-weight-regular: 400;
    --hppy-weight-medium: 500;
    --hppy-weight-semibold: 600;
    --hppy-weight-bold: 700;
    --hppy-weight-extrabold: 800;

    /* Border Radius (4px grid + the two platform-native sheet radii) */
    --hppy-radius-xs: 4px;
    --hppy-radius-sm: 8px;
    --hppy-radius-md: 12px;
    --hppy-radius-lg: 16px;
    --hppy-radius-xl: 20px;
    /* cards, modals, toasts */
    --hppy-radius-2xl: 24px;
    /* centered dialogs */
    --hppy-radius-sheet: 28px;
    /* M3 bottom sheets */
    --hppy-radius-ios-sheet: 14px;
    /* iOS action-sheet pill groups */
    --hppy-radius-full: 9999px;

    /* Shadows — the dark-mode ramp is stronger (light-tuned blacks vanish
       on dark surfaces); each consumer re-declares the -dark values. */
    --hppy-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
    --hppy-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12);
    --hppy-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.16);
    --hppy-shadow-sm-dark: 0 1px 3px rgba(0, 0, 0, 0.4);
    --hppy-shadow-md-dark: 0 4px 12px rgba(0, 0, 0, 0.5);
    --hppy-shadow-lg-dark: 0 8px 24px rgba(0, 0, 0, 0.6);

    /* Text Shadows (for text over images / gradient surfaces) */
    --hppy-text-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
    --hppy-text-shadow-md: 0 2px 6px rgba(0, 0, 0, 0.4);
    --hppy-text-shadow-lg: 0 1px 4px rgba(0, 0, 0, 0.8), 0 0 12px rgba(0, 0, 0, 0.5);

    /* ── Motion / Animation ──
       Two related sets: --hppy-transition-* are ready-made shorthands for
       `transition:`, while --hppy-duration-* / --hppy-ease-* are the parts
       that @keyframes animations compose (see shared/styles/motion.css). */
    --hppy-transition-fast: 150ms ease-out;
    --hppy-transition-normal: 250ms ease-in-out;
    --hppy-transition-slow: 400ms ease-in-out;

    /* Duration ramp — animations snap to these rather than inventing
       one-off values (the app previously held 0.22/0.26/0.28/0.34/0.36s
       as separate hand-tuned constants) */
    --hppy-duration-xs: 150ms;
    --hppy-duration-sm: 200ms;
    --hppy-duration-md: 250ms;
    --hppy-duration-lg: 300ms;
    --hppy-duration-xl: 350ms;
    --hppy-duration-2xl: 440ms;
    --hppy-duration-slow: 400ms;
    --hppy-duration-spin: 700ms;
    --hppy-duration-ambient: 2s;

    /* Easing curves — named by intent. The app had 12 hand-typed
       cubic-beziers, several differing only in the third decimal. */
    --hppy-ease-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);
    --hppy-ease-in: cubic-bezier(0.55, 0, 0.55, 0.2);
    --hppy-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
    --hppy-ease-emphasized: cubic-bezier(0.25, 0.8, 0.25, 1);
    /* overshoot family — spring settles past its target then back */
    --hppy-ease-spring: cubic-bezier(0.32, 1.24, 0.58, 1);
    --hppy-ease-spring-strong: cubic-bezier(0.28, 1.4, 0.55, 1);
    --hppy-ease-spring-soft: cubic-bezier(0.2, 0.82, 0.36, 1);

    /* Minimum touch target (44pt HIG / ~48dp M3) — a platform floor, so
       the web pages hold to it too */
    --hppy-size-btn-touch: 44px;

    /* Z-Index Scale
       The 999xx levels sit above Ionic's overlay stack (ion-modal et al.
       start at 20000) — required for the app-level fixed banners/toasts.
       The web pages only ever need the base level. */
    --hppy-z-base: 1;
    --hppy-z-sticky: 100;
    --hppy-z-overlay: 500;
    --hppy-z-modal: 1000;
    --hppy-z-banner: 99997;
    /* offline banner */
    --hppy-z-toast: 99998;
    /* AppToast */
    --hppy-z-notification: 99999;
    /* InAppNotification */

    /* Blur ramp — one ladder shared by every glass family (see the
       glass/scrim/chrome tokens in variables.css) */
    --hppy-blur-xs: blur(2px);
    --hppy-blur-sm: blur(4px);
    --hppy-blur-md: blur(8px);
    --hppy-blur-lg: blur(12px);
    --hppy-blur-xl: blur(24px) saturate(150%);
    --hppy-blur-2xl: blur(40px) saturate(190%);
    --hppy-blur-3xl: blur(60px) saturate(220%) brightness(1.06);
}

/* ═══════════════════════════════════════════════════════════════════════
   hppy Web Tokens — Tier 1 for the standalone pages in public/
   ═══════════════════════════════════════════════════════════════════════

   The web counterpart of variables.css: it maps the Tier 0 brand
   primitives onto surfaces, text and accents, and switches them for dark
   mode. Like variables.css it is exempt from `lint:css` — a token file is
   the one place raw color values belong.

   Recipes live next door in web.css, which IS linted and must reference
   these tokens rather than any literal value.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Typeface ──
   Poppins, the Hausschrift from the styleguide: Regular 400 for body,
   SemiBold 600 for headings. Two static cuts rather than a variable font —
   Poppins ships no variable build, and the guide only calls for these two.
   build-web-css.cjs copies both woff2 out of node_modules. Latin subset;
   it covers German including „quotes". */
@font-face {
    font-family: 'Poppins';
    font-style: normal;
    font-display: swap;
    font-weight: 400;
    src: url('/assets/fonts/poppins-latin-400-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Poppins';
    font-style: normal;
    font-display: swap;
    font-weight: 600;
    src: url('/assets/fonts/poppins-latin-600-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ═══════════════════════════════════════════════════════════════════════
   Tier 1 — Semantic layer
   ═══════════════════════════════════════════════════════════════════════
   Names match the app's semantic tokens (`--hppy-color-surface`,
   `-text-primary`, …) so a recipe can move between the two worlds
   unchanged. The *values* now come from the 2026 styleguide, which the app
   has not adopted yet.

   The two themes are the guide's two logo variants (page 4):

     light   "Positiv auf Safrangelb" — safran brand surface, navy wordmark
     dark    "Negativ auf Nachtblau"  — navy brand surface, safran wordmark

   Page and card stay neutral in light mode so safran keeps the 30 % share
   the guide asks for: it carries the hero and the CTA, nothing else.

   Every text token below clears 4.5:1 against both the page background
   and the card surface, in both themes. Two values are tuned rather than
   taken straight from the guide:

     light muted     the guide's Text-Grau #6b7086 lands at 4.46:1 on
                     Papier — 0.04 short of AA. Darkened one notch.
     dark secondary  the navy ramp stops at 20 % (#b7bacc); dark mode needs
                     a step lighter still, so it is derived.

   ── Theme switching ──
   Three states, and the CSS has to express all three:

     system   no `data-theme` attribute — follow prefers-color-scheme
     light    `data-theme="light"` on <html> — stay light even at night
     dark     `data-theme="dark"` on <html> — stay dark even by day

   So the dark palette is declared twice: once inside the media query
   guarded by `:not([data-theme="light"])` (so an explicit light choice
   beats the OS), and once unguarded under `[data-theme="dark"]` (so an
   explicit dark choice beats a light OS). Plain CSS has no way to share
   one declaration block across a media query boundary — **the two dark
   blocks must be kept in sync by hand.**

   The attribute is set by the inline snippet every page carries in
   <head>; see docs/DESIGN-SYSTEM.md § Theme switching. */

:root {
    color-scheme: light;

    /* Page and card stay neutral — Weißraum is the guide's remaining 10 % */
    --hppy-color-bg: var(--hppy-brand-paper);
    --hppy-color-surface: var(--hppy-brand-white);
    --hppy-color-border: var(--hppy-brand-rule);

    /* Text — navy carries the copy, 12.0:1 on Papier */
    --hppy-color-text-primary: var(--hppy-brand-navy);
    --hppy-color-text-secondary: var(--hppy-brand-navy-70);
    --hppy-color-text-muted: #656a80;

    /* Accents. Safran is a surface color only: as text on Papier it would
       sit at 1.6:1, which the guide forbids outright. So links and badge
       text are navy, and safran does its work behind them. */
    --hppy-color-primary: var(--hppy-brand-navy);
    --hppy-color-primary-rgb: var(--hppy-brand-navy-rgb);
    --hppy-color-secondary: var(--hppy-brand-safran);
    --hppy-color-danger: var(--hppy-brand-danger);
    --hppy-color-link: var(--hppy-brand-navy);
    /* tint behind a badge or icon — safran at low alpha on a light surface */
    --hppy-color-accent-wash: rgba(var(--hppy-brand-safran-rgb), 0.22);

    /* Hover fill for a card. The same safran hint as the wash, but mixed
       INTO the surface rather than laid over it: a card that overlaps the
       hero (the landing page's release teaser) would otherwise let the
       brand field show through and read as a rendering fault. Declared
       once — `--hppy-color-surface` inside it resolves per theme at use
       time, so the dark blocks below inherit the right value. */
    --hppy-color-surface-hover: color-mix(in srgb, var(--hppy-brand-safran) 12%, var(--hppy-color-surface));

    /* The brand surface: hero band and wordmark. This is the pair that
       flips between the guide's two logo variants. */
    --hppy-color-hero-bg: var(--hppy-gradient-safran);
    --hppy-color-on-hero: var(--hppy-brand-navy);
    --hppy-color-wordmark: var(--hppy-brand-navy);

    /* CTA — safran fill, navy label (7.5:1 AAA). Identical in both themes:
       the guide draws every call to action this way. */
    --hppy-color-cta-bg: var(--hppy-brand-safran);
    --hppy-color-cta-fg: var(--hppy-brand-navy);

    --hppy-gradient-brand: var(--hppy-gradient-safran);

    /* Chips sitting on the brand surface. Tuned per theme, because unlike
       the app's `media` glass these sit on safran in one theme and navy in
       the other — one translucency cannot serve both. */
    --hppy-chip-on-hero-bg: rgba(var(--hppy-brand-navy-rgb), 0.1);
    --hppy-chip-on-hero-bg-hover: rgba(var(--hppy-brand-navy-rgb), 0.18);
    --hppy-chip-on-hero-ring: rgba(var(--hppy-brand-navy-rgb), 0.18);
    --hppy-chip-on-hero-active-bg: var(--hppy-brand-navy);
    --hppy-chip-on-hero-active-fg: var(--hppy-brand-safran);
}

/* ── Dark palette, copy 1 of 2 — the OS asked for it, and the visitor has
   not overridden it with an explicit light choice. ── */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;

        /* Navy is the page itself here — the guide calls this the main
           digital application. The card lifts off it by a hair. */
        --hppy-color-bg: var(--hppy-brand-navy);
        --hppy-color-surface: #2c3763;
        --hppy-color-border: var(--hppy-brand-navy-70);

        --hppy-color-text-primary: var(--hppy-brand-white);
        --hppy-color-text-secondary: #c9cbd9;
        --hppy-color-text-muted: var(--hppy-brand-navy-20);

        /* On navy, safran is a legal text color (7.5:1) — so here it can
           do the job navy does in light mode. */
        --hppy-color-primary: var(--hppy-brand-safran);
        --hppy-color-primary-rgb: var(--hppy-brand-safran-rgb);
        --hppy-color-secondary: var(--hppy-brand-safran);
        --hppy-color-danger: var(--hppy-brand-danger-dark);
        --hppy-color-link: var(--hppy-brand-safran);
        --hppy-color-accent-wash: rgba(var(--hppy-brand-safran-rgb), 0.18);

        /* "Negativ auf Nachtblau": navy field, safran wordmark, white copy */
        --hppy-color-hero-bg: var(--hppy-gradient-navy);
        --hppy-color-on-hero: var(--hppy-brand-white);
        --hppy-color-wordmark: var(--hppy-brand-safran);

        /* CTA is unchanged — safran fill, navy label, in both themes */

        --hppy-gradient-brand: var(--hppy-gradient-safran);

        --hppy-chip-on-hero-bg: rgba(255, 255, 255, 0.12);
        --hppy-chip-on-hero-bg-hover: rgba(255, 255, 255, 0.2);
        --hppy-chip-on-hero-ring: rgba(255, 255, 255, 0.22);
        --hppy-chip-on-hero-active-bg: var(--hppy-brand-safran);
        --hppy-chip-on-hero-active-fg: var(--hppy-brand-navy);

        --hppy-shadow-sm: var(--hppy-shadow-sm-dark);
        --hppy-shadow-md: var(--hppy-shadow-md-dark);
        --hppy-shadow-lg: var(--hppy-shadow-lg-dark);
    }
}

/* ── Dark palette, copy 2 of 2 — the visitor chose dark explicitly, so it
   wins regardless of what the OS reports. Keep in sync with copy 1. ── */
:root[data-theme="dark"] {
    color-scheme: dark;

    /* Navy is the page itself here — the guide calls this the main
       digital application. The card lifts off it by a hair. */
    --hppy-color-bg: var(--hppy-brand-navy);
    --hppy-color-surface: #2c3763;
    --hppy-color-border: var(--hppy-brand-navy-70);

    --hppy-color-text-primary: var(--hppy-brand-white);
    --hppy-color-text-secondary: #c9cbd9;
    --hppy-color-text-muted: var(--hppy-brand-navy-20);

    /* On navy, safran is a legal text color (6.7:1) — so here it can
       do the job navy does in light mode. */
    --hppy-color-primary: var(--hppy-brand-safran);
    --hppy-color-primary-rgb: var(--hppy-brand-safran-rgb);
    --hppy-color-secondary: var(--hppy-brand-safran);
    --hppy-color-danger: var(--hppy-brand-danger-dark);
    --hppy-color-link: var(--hppy-brand-safran);
    --hppy-color-accent-wash: rgba(var(--hppy-brand-safran-rgb), 0.18);

    /* "Negativ auf Nachtblau": navy field, safran wordmark, white copy */
    --hppy-color-hero-bg: var(--hppy-gradient-navy);
    --hppy-color-on-hero: var(--hppy-brand-white);
    --hppy-color-wordmark: var(--hppy-brand-safran);

    /* CTA is unchanged — safran fill, navy label, in both themes */

    --hppy-gradient-brand: var(--hppy-gradient-safran);

    --hppy-chip-on-hero-bg: rgba(255, 255, 255, 0.12);
    --hppy-chip-on-hero-bg-hover: rgba(255, 255, 255, 0.2);
    --hppy-chip-on-hero-ring: rgba(255, 255, 255, 0.22);
    --hppy-chip-on-hero-active-bg: var(--hppy-brand-safran);
    --hppy-chip-on-hero-active-fg: var(--hppy-brand-navy);

    --hppy-shadow-sm: var(--hppy-shadow-sm-dark);
    --hppy-shadow-md: var(--hppy-shadow-md-dark);
    --hppy-shadow-lg: var(--hppy-shadow-lg-dark);
}

/* ═══════════════════════════════════════════════════════════════════════
   hppy Web Recipes — Tier 2 for the standalone pages in public/
   ═══════════════════════════════════════════════════════════════════════

   The app renders through Ionic; the pages under `public/` (landing, the
   release-party page, the /grp /cat /ven /ext deep-link bridges, and the
   legal + support documents) are plain HTML with no framework. Together with
   brand-tokens.css (Tier 0) and web-tokens.css (Tier 1) this file is
   their whole design system.

   Build: `scripts/build-web-css.cjs` concatenates the three into
   `public/assets/hppy.css`. Never edit that output; edit here and run
   `npm run build:web-css`.

   Pages carry no <style> block of their own. If a page genuinely needs a
   delta, add it here under a page-scoped section instead, so a restyle
   stays a one-file job.

   This file is linted: no raw colors, sizes or curves — tokens only.

   Class names are e2e selectors (`.btn-primary`, `#hero-title`, 
   `.error-card`) — extend them, never rename them. `.venue-*` is an alias
   of `.event-*` because /ven grew its own copy of the same card.
   ═══════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   Base
   ═══════════════════════════════════════════════════════════════════════ */

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

/* `color-scheme` is set in web-tokens.css alongside each palette, so UA
   widgets (scrollbars, form controls) follow an explicit theme choice
   rather than the OS. Do not re-declare it here — same element, and this
   file wins the cascade. */

body {
    font-family: var(--hppy-brand-web-font);
    background: var(--hppy-color-bg);
    color: var(--hppy-color-text-primary);
    /* Zeilenabstand 1,4 — Vorgabe des Styleguides */
    line-height: 1.4;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

img {
    max-width: 100%;
}

/* ── Accessibility ──
   The app gets these from variables.css; a standalone page inherits
   nothing, so they are repeated here. */

@media (prefers-reduced-motion: reduce) {

    /* stylelint-disable declaration-no-important --
       an accessibility opt-out has to beat every animation on the page,
       including inline ones; same construct as variables.css */
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }

    /* stylelint-enable declaration-no-important */
}

a:focus-visible,
button:focus-visible,
[role="button"]:focus-visible {
    outline: 2px solid var(--hppy-color-primary);
    outline-offset: 2px;
    border-radius: var(--hppy-radius-sm);
}

a:focus:not(:focus-visible),
button:focus:not(:focus-visible) {
    outline: none;
}

/* ═══════════════════════════════════════════════════════════════════════
   Tier 2 — Recipes
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Page shell ──
   One width for every bridge page. The landing page runs slightly wider
   (--page-w) because it carries marketing copy rather than a single card. */
.container,
.page {
    width: 100%;
    max-width: var(--page-w, 440px);
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100dvh;
}

.page {
    --page-w: 480px;
}

/* ── Theme toggle ──
   A three-way segmented control (light / dark / system) that floats in the
   hero's top-right corner. Built on real radio inputs: the browser then
   supplies group semantics and arrow-key navigation for free, and the
   checked state is expressible in CSS without JavaScript.

   It sits on the brand field, which is safran in one theme and navy in the
   other — so unlike the app's `media` glass its tokens do flip. The active
   segment inverts to the opposite brand color. */
.theme-toggle {
    position: absolute;
    top: var(--hppy-space-12);
    right: var(--hppy-space-12);
    z-index: var(--hppy-z-sticky);
    display: flex;
    gap: var(--hppy-space-3xs);
    padding: var(--hppy-space-3xs);
    background: var(--hppy-chip-on-hero-bg);
    backdrop-filter: var(--hppy-blur-md);
    -webkit-backdrop-filter: var(--hppy-blur-md);
    border-radius: var(--hppy-radius-full);
    box-shadow: inset 0 0 0 1px var(--hppy-chip-on-hero-ring);
}

.theme-toggle input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.theme-toggle label {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--hppy-size-btn-touch);
    height: var(--hppy-size-btn-touch);
    border-radius: var(--hppy-radius-full);
    color: var(--hppy-color-on-hero);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--hppy-transition-fast), color var(--hppy-transition-fast);
}

.theme-toggle label svg {
    width: 20px;
    height: 20px;
}

.theme-toggle label:hover {
    background: var(--hppy-chip-on-hero-bg-hover);
}

.theme-toggle input:checked + label {
    background: var(--hppy-chip-on-hero-active-bg);
    color: var(--hppy-chip-on-hero-active-fg);
}

/* The input is off-screen, so the focus ring has to be drawn on its label */
.theme-toggle input:focus-visible + label {
    outline: 2px solid var(--hppy-color-on-hero);
    outline-offset: 2px;
}

/* Screen-reader-only label text — the control is icon-only by design */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: calc(var(--hppy-space-3xs) * -1);
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ── Hero band ── */
.hero {
    position: relative;
    background: var(--hppy-color-hero-bg);
    color: var(--hppy-color-on-hero);
    padding: var(--hppy-space-3xl) var(--hppy-space-lg) var(--hppy-space-2xl);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--hppy-space-2xl);
    text-align: center;
}

/* ── Wortmarke ──
   The logo is inline SVG so it inherits --hppy-color-wordmark: navy on the
   safran field, safran on the navy one — the guide's two variants (page 4).

   No rounding, no tile, and explicitly no shadow: page 4 lists "Effekte /
   Schatten" under Nicht erlaubt.

   The viewBox is cropped to the measured glyph box (2.26:1), so the width
   below really is the mark's width — the source file carried a wide empty
   margin that made the Schutzraum impossible to reason about.

   Sizes clear the minimum on page 5 (digital: 80px wide). The hero's `gap`
   IS the Schutzraum: x is ~40 % of the wordmark height, so at 160-200px
   wide the mark is 70-88px tall and x is 28-35px — inside the 48px the
   hero leaves on every side. */
.hero-logo {
    width: 160px;
    color: var(--hppy-color-wordmark);
}

.page .hero-logo {
    width: 200px;
}

.hero-logo svg {
    display: block;
    width: 100%;
    height: auto;
}

.hero-text h1,
.hero h1 {
    font-size: var(--hppy-font-3xl);
    font-weight: var(--hppy-weight-semibold);
    line-height: 1.25;
    margin-bottom: var(--hppy-space-sm);
}

.hero-text p,
.hero > p {
    font-size: var(--hppy-font-md);
    font-weight: var(--hppy-weight-regular);
    line-height: 1.5;
    opacity: 0.92;
    max-width: 320px;
}

/* ── Content card ──
   Pulled up over the hero's bottom edge, which is why it needs a stacking
   context of its own. */
.event-card,
.venue-card,
.error-card,
.loader-card {
    position: relative;
    z-index: var(--hppy-z-base);
    margin: calc(var(--hppy-space-lg) * -1) var(--hppy-space-md) 0;
    background: var(--hppy-color-surface);
    border: 1px solid var(--hppy-color-border);
    border-radius: var(--hppy-radius-lg);
    overflow: hidden;
}

.event-title-row,
.venue-title-row {
    padding: var(--hppy-space-20);
}

.event-title-row--centered {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

.event-badge,
.venue-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--hppy-space-xs);
    padding: var(--hppy-space-xs) var(--hppy-space-12);
    background: var(--hppy-color-accent-wash);
    border-radius: var(--hppy-radius-full);
    font-size: var(--hppy-font-xs);
    font-weight: var(--hppy-weight-semibold);
    letter-spacing: var(--hppy-overline-spacing);
    color: var(--hppy-color-text-primary);
    margin-bottom: var(--hppy-space-sm);
}

.event-badge svg,
.venue-badge svg {
    width: 12px;
    height: 12px;
}

.event-name,
.venue-name {
    font-size: var(--hppy-font-2xl);
    font-weight: var(--hppy-weight-semibold);
    line-height: 1.25;
}

.event-description,
.venue-description,
.group-meta {
    font-size: var(--hppy-font-md);
    color: var(--hppy-color-text-secondary);
    line-height: 1.55;
    margin-top: var(--hppy-space-xs);
}

.event-description,
.venue-description {
    padding: 0 var(--hppy-space-20) var(--hppy-space-md);
    margin-top: 0;
}

/* Group avatar — the /grp card's centered initial or photo */
.group-avatar {
    width: 80px;
    height: 80px;
    border-radius: var(--hppy-radius-full);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--hppy-font-4xl);
    font-weight: var(--hppy-weight-semibold);
    color: var(--hppy-brand-white);
    margin-bottom: var(--hppy-space-12);
    box-shadow: var(--hppy-shadow-md);
}

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

/* ── Info rows ── */
.info-list {
    border-top: 1px solid var(--hppy-color-border);
}

.info-row {
    display: flex;
    align-items: center;
    gap: var(--hppy-space-12);
    padding: var(--hppy-space-12) var(--hppy-space-20);
}

.info-row + .info-row {
    border-top: 1px solid var(--hppy-color-border);
}

.info-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    color: var(--hppy-color-text-secondary);
}

.info-content {
    flex: 1;
    min-width: 0;
}

.info-label {
    font-size: var(--hppy-font-xs);
    font-weight: var(--hppy-weight-semibold);
    color: var(--hppy-color-text-muted);
    text-transform: uppercase;
    letter-spacing: var(--hppy-overline-spacing);
}

.info-value {
    font-size: var(--hppy-font-15);
    font-weight: var(--hppy-weight-medium);
}

/* ── Buttons ── */
.cta-section {
    display: flex;
    flex-direction: column;
    gap: var(--hppy-space-12);
    padding: var(--hppy-space-lg) var(--hppy-space-md) 0;
}

.btn-primary,
.btn-secondary {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--hppy-space-sm);
    width: 100%;
    min-height: var(--hppy-size-btn-touch);
    border: none;
    border-radius: var(--hppy-radius-md);
    text-decoration: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.btn-primary {
    padding: var(--hppy-space-md) var(--hppy-space-lg);
    background: var(--hppy-color-cta-bg);
    color: var(--hppy-color-cta-fg);
    font-size: var(--hppy-font-lg);
    font-weight: var(--hppy-weight-semibold);
    box-shadow: var(--hppy-shadow-sm);
    transition: opacity var(--hppy-transition-fast), transform var(--hppy-transition-fast);
}

.btn-primary:active {
    opacity: 0.9;
    transform: scale(0.98);
}

.btn-primary svg {
    width: 20px;
    height: 20px;
}

.btn-secondary {
    padding: var(--hppy-space-12) var(--hppy-space-lg);
    background: var(--hppy-color-surface);
    border: 1px solid var(--hppy-color-border);
    color: var(--hppy-color-text-secondary);
    font-size: var(--hppy-font-md);
    font-weight: var(--hppy-weight-medium);
    transition: background var(--hppy-transition-fast);
}

.btn-secondary:active {
    background: var(--hppy-color-border);
}

.btn-secondary svg {
    width: 16px;
    height: 16px;
}

/* ── Loading ── */
.loader {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--hppy-space-12);
    padding: var(--hppy-space-2xl) 0;
}

.loader-card {
    padding: var(--hppy-space-2xl);
    text-align: center;
}

.loader-text {
    font-size: var(--hppy-font-md);
    color: var(--hppy-color-text-secondary);
}

.spinner {
    width: 32px;
    height: 32px;
    border: 3px solid var(--hppy-color-border);
    border-top-color: var(--hppy-color-primary);
    border-radius: var(--hppy-radius-full);
    margin: 0 auto;
    animation: hppy-web-spin var(--hppy-duration-spin) linear infinite;
}

/* .loader centers with `gap`; .loader-card is plain flow and needs the space */
.loader-card .spinner {
    margin-bottom: var(--hppy-space-md);
}

@keyframes hppy-web-spin {
    to {
        transform: rotate(360deg);
    }
}

/* ── Error ── */
.error-card {
    padding: var(--hppy-space-xl) var(--hppy-space-lg);
    text-align: center;
}

.error-icon {
    width: 48px;
    height: 48px;
    color: var(--hppy-color-secondary);
    margin: 0 auto var(--hppy-space-12);
}

.error-card h3 {
    font-size: var(--hppy-font-xl);
    font-weight: var(--hppy-weight-semibold);
    margin-bottom: var(--hppy-space-sm);
}

.error-card p {
    font-size: var(--hppy-font-md);
    color: var(--hppy-color-text-secondary);
    line-height: 1.5;
}

/* ── Footer ── */
.footer {
    margin-top: auto;
    padding: var(--hppy-space-xl) var(--hppy-space-md) var(--hppy-space-xl);
    text-align: center;
}

.footer p,
.footer-copy {
    font-size: var(--hppy-font-sm);
    color: var(--hppy-color-text-muted);
    line-height: 1.6;
}

.footer a,
.footer-copy a {
    color: var(--hppy-color-link);
    text-decoration: underline;
    text-underline-offset: 2px;
    font-weight: var(--hppy-weight-semibold);
}

.footer a:hover,
.footer-copy a:hover {
    text-decoration: underline;
}

.footer-links {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--hppy-space-20);
    margin-bottom: var(--hppy-space-md);
}

.footer-links a {
    font-size: var(--hppy-font-13);
    color: var(--hppy-color-text-secondary);
    text-decoration: none;
    font-weight: var(--hppy-weight-regular);
}

.footer-links a:hover {
    color: var(--hppy-color-link);
}

.footer-socials {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
    gap: var(--hppy-space-xs);
    margin-left: var(--hppy-space-xs);
}

.footer-social-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--hppy-color-text-secondary);
    text-decoration: none !important;
    padding: 2px;
    border-radius: var(--hppy-radius-sm);
    transition: color var(--hppy-duration-sm) var(--hppy-ease-out), transform var(--hppy-duration-sm) var(--hppy-ease-out);
}

.footer-social-link:hover {
    color: var(--hppy-color-link);
    transform: translateY(-1px);
    text-decoration: none !important;
}

.footer-social-link svg {
    width: 18px;
    height: 18px;
    display: block;
}

/* ── Entrance animation ──
   Staggered by nth-child so a page can drop elements in without hand-set
   delays. Suppressed entirely under prefers-reduced-motion, above. */
.fade-in,
.fade-up {
    animation: hppy-web-fade-up var(--hppy-duration-lg) var(--hppy-ease-out) both;
}

@keyframes hppy-web-fade-up {
    from {
        opacity: 0;
        transform: translateY(8px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.fade-in:nth-child(2),
.fade-up:nth-child(2) {
    animation-delay: 60ms;
}

.fade-in:nth-child(3),
.fade-up:nth-child(3) {
    animation-delay: 120ms;
}

.fade-in:nth-child(4),
.fade-up:nth-child(4) {
    animation-delay: 180ms;
}

/* ═══════════════════════════════════════════════════════════════════════
   Page section — landing (/landing)
   ═══════════════════════════════════════════════════════════════════════ */

/* The landing hero keeps the base padding and the base 48px Schutzraum.
   The floating-logo animation that used to live here is gone: the guide
   asks for a calm mark with room around it, and a bobbing wordmark eats
   into its own clear space for no gain. */

.page .hero h1 {
    font-size: var(--hppy-font-display);
    font-weight: var(--hppy-weight-semibold);
    line-height: 1.2;
    margin-bottom: 0;
}

/* Follows the release teaser, which carries the hero overlap — so this
   block sits in normal flow rather than being pulled up. */
.features {
    display: flex;
    flex-direction: column;
    gap: var(--hppy-space-2xs);
    padding: 0 var(--hppy-space-md);
    margin-top: var(--hppy-space-lg);
    position: relative;
    z-index: var(--hppy-z-base);
}

.feature-card {
    background: var(--hppy-color-surface);
    padding: var(--hppy-space-20);
    display: flex;
    align-items: flex-start;
    gap: var(--hppy-space-md);
}

.feature-card:first-child {
    border-radius: var(--hppy-radius-lg) var(--hppy-radius-lg) 0 0;
}

.feature-card:last-child {
    border-radius: 0 0 var(--hppy-radius-lg) var(--hppy-radius-lg);
}

.feature-icon {
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--hppy-radius-md);
    background: var(--hppy-color-accent-wash);
}

.feature-icon svg {
    width: 20px;
    height: 20px;
    color: var(--hppy-color-text-primary);
}

.feature-text h2 {
    font-size: var(--hppy-font-15);
    font-weight: var(--hppy-weight-semibold);
    margin-bottom: var(--hppy-space-xs);
}

.feature-text p {
    font-size: var(--hppy-font-13);
    color: var(--hppy-color-text-secondary);
    line-height: 1.5;
}

/* ── Release-Party teaser ──
   The first item under the hero, linking to /release — so it is the card
   pulled up over the hero's bottom edge, and it needs a stacking context
   of its own the way .event-card does.

   The invitation is a square poster, so it goes into a fixed square well
   and the copy takes the rest of the row. Whole card is the link. */
.release-teaser {
    position: relative;
    z-index: var(--hppy-z-base);
    display: flex;
    align-items: center;
    gap: var(--hppy-space-md);
    margin: calc(var(--hppy-space-lg) * -1) var(--hppy-space-md) 0;
    padding: var(--hppy-space-12);
    background: var(--hppy-color-surface);
    border: 1px solid var(--hppy-color-border);
    border-radius: var(--hppy-radius-lg);
    color: var(--hppy-color-text-primary);
    text-decoration: none;
    transition: background var(--hppy-transition-fast), transform var(--hppy-transition-fast);
}

/* Opaque on purpose — the card overlaps the hero, so a translucent wash
   would show the brand field through it. */
.release-teaser:hover {
    background: var(--hppy-color-surface-hover);
    transform: scale(1.01);
}

.release-teaser img {
    width: 96px;
    height: 96px;
    flex-shrink: 0;
    border-radius: var(--hppy-radius-md);
    object-fit: cover;
}

/* min-width:0 so a long word cannot push the square well out of the row */
.release-teaser-text {
    min-width: 0;
}

.release-teaser-eyebrow {
    display: block;
    font-size: var(--hppy-font-2xs);
    font-weight: var(--hppy-weight-semibold);
    color: var(--hppy-color-text-muted);
    text-transform: uppercase;
    letter-spacing: var(--hppy-overline-spacing);
}

.release-teaser-title {
    display: block;
    font-size: var(--hppy-font-xl);
    font-weight: var(--hppy-weight-semibold);
    margin: var(--hppy-space-2xs) 0 var(--hppy-space-xs);
    line-height: 1.2;
}

.release-teaser-copy {
    display: block;
    font-size: var(--hppy-font-13);
    color: var(--hppy-color-text-secondary);
    line-height: 1.5;
}

.release-teaser-more {
    display: block;
    margin-top: var(--hppy-space-sm);
    font-size: var(--hppy-font-13);
    font-weight: var(--hppy-weight-semibold);
    color: var(--hppy-color-link);
}

.features--overlap {
    margin-top: calc(var(--hppy-space-lg) * -1);
}

.store-section {
    padding: var(--hppy-space-xl) var(--hppy-space-md) 0;
    text-align: center;
}

.store-section h2 {
    font-size: var(--hppy-font-13);
    font-weight: var(--hppy-weight-medium);
    color: var(--hppy-color-text-muted);
    text-transform: uppercase;
    letter-spacing: var(--hppy-overline-spacing);
    margin-bottom: var(--hppy-space-md);
}

.store-badges {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--hppy-space-12);
}

.store-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--hppy-space-sm);
    padding: var(--hppy-space-12) var(--hppy-space-20);
    background: var(--hppy-color-surface);
    border: 1px solid var(--hppy-color-border);
    border-radius: var(--hppy-radius-md);
    color: var(--hppy-color-text-primary);
    text-decoration: none;
    transition: background var(--hppy-transition-fast), transform var(--hppy-transition-fast);
}

.store-badge:hover {
    background: var(--hppy-color-border);
    transform: scale(1.03);
}

.store-badge svg {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
}

.store-badge-text {
    text-align: left;
    line-height: 1.2;
}

.store-badge-label {
    display: block;
    font-size: var(--hppy-font-2xs);
    color: var(--hppy-color-text-muted);
}

.store-badge-name {
    font-size: var(--hppy-font-md);
    font-weight: var(--hppy-weight-semibold);
}

/* ── Social Section ── */
.social-section {
    padding: var(--hppy-space-lg) var(--hppy-space-md) 0;
    text-align: center;
}

.social-section h2 {
    font-size: var(--hppy-font-13);
    font-weight: var(--hppy-weight-medium);
    color: var(--hppy-color-text-muted);
    text-transform: uppercase;
    letter-spacing: var(--hppy-overline-spacing);
    margin-bottom: var(--hppy-space-md);
}

.social-badges {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--hppy-space-12);
}

.social-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--hppy-space-sm);
    padding: var(--hppy-space-12) var(--hppy-space-20);
    background: var(--hppy-color-surface);
    border: 1px solid var(--hppy-color-border);
    border-radius: var(--hppy-radius-md);
    color: var(--hppy-color-text-primary);
    text-decoration: none;
    transition: background var(--hppy-transition-fast), transform var(--hppy-transition-fast);
}

.social-badge:hover {
    background: var(--hppy-color-border);
    transform: scale(1.03);
}

.social-badge svg {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
}

.social-badge-text {
    text-align: left;
    line-height: 1.2;
}

.social-badge-label {
    display: block;
    font-size: var(--hppy-font-2xs);
    color: var(--hppy-color-text-muted);
}

.social-badge-name {
    font-size: var(--hppy-font-md);
    font-weight: var(--hppy-weight-semibold);
}

@media (min-width: 600px) {
    .page .hero {
        padding: var(--hppy-space-3xl) var(--hppy-space-xl) var(--hppy-space-3xl);
    }

    .page .hero h1 {
        font-size: var(--hppy-font-display-lg);
    }

    .page .hero > p {
        font-size: var(--hppy-font-lg);
    }
}

/* ═══════════════════════════════════════════════════════════════════════
   Page section — release party (/release)
   ═══════════════════════════════════════════════════════════════════════
   Built almost entirely from existing recipes: the .page shell from the
   landing page, .event-card + .info-list for the facts, .faq-item and
   .contact-card from the document pages, .store-section for the badges.
   Only the invitation and the rhythm of the sections below it are new.

   The page deliberately has NO .hero band: the invitation already carries
   the wordmark, the safran field and every fact, so a hero above it would
   say the same thing twice. */

/* The invitation IS the page header — full bleed to the page column, hard
   against the top edge. Nothing is pulled up over it: the poster's bottom
   line ("Einlass nur für geladene Gäste + 1") has to stay readable. */
.release-invite {
    display: block;
    width: 100%;
    height: auto;
}

/* .event-card is drawn to hang over a hero's bottom edge. There is no hero
   here, so the facts card sits in normal flow under the poster — and its
   own border already separates it, hence no rule on top of the list. */
.release-facts {
    margin-top: var(--hppy-space-md);
}

.release-facts .info-list {
    border-top: none;
}

/* Same vertical rhythm as .store-section, but left-aligned prose */
.release-section {
    padding: var(--hppy-space-xl) var(--hppy-space-md) 0;
}

.release-section > h2 {
    font-size: var(--hppy-font-xl);
    font-weight: var(--hppy-weight-semibold);
    margin-bottom: var(--hppy-space-md);
}

/* .faq-item and .contact-card were drawn for the prose pages, where they
   carry their own margins. Here the section supplies the spacing. */
.release-section .faq-item:last-child {
    margin-bottom: 0;
}

.release-section .contact-card {
    margin-top: 0;
}

.release-section .faq-item a {
    color: var(--hppy-color-link);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* ═══════════════════════════════════════════════════════════════════════
   Page section — documents (/legal/*, /support)
   ═══════════════════════════════════════════════════════════════════════
   Prose pages. They used to carry their own palette (a violet #6C5CE7 on
   #fafafa) which read as a different product the moment a user tapped
   "Impressum" in the landing footer. */

.doc {
    max-width: 720px;
    margin: 0 auto;
    padding: var(--hppy-space-lg) var(--hppy-space-md) var(--hppy-space-3xl);
    line-height: 1.7;
}

.doc h1 {
    font-size: var(--hppy-font-4xl);
    font-weight: var(--hppy-weight-semibold);
    margin-bottom: var(--hppy-space-sm);
}

.doc h2 {
    font-size: var(--hppy-font-xl);
    font-weight: var(--hppy-weight-semibold);
    color: var(--hppy-color-link);
    margin-top: var(--hppy-space-xl);
    margin-bottom: var(--hppy-space-sm);
}

.doc h3 {
    font-size: var(--hppy-font-lg);
    font-weight: var(--hppy-weight-semibold);
    margin-bottom: var(--hppy-space-xs);
}

.doc p,
.doc li {
    margin-bottom: var(--hppy-space-12);
}

.doc ul,
.doc ol {
    padding-left: var(--hppy-space-lg);
    margin-bottom: var(--hppy-space-12);
}

/* In light mode the link color IS the body color — navy, because safran on
   Papier sits at 1.6:1 and the guide forbids it. The underline therefore
   carries the affordance, which is the accessible default anyway. */
.doc a {
    color: var(--hppy-color-link);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.doc .meta {
    font-size: var(--hppy-font-md);
    color: var(--hppy-color-text-secondary);
    margin-bottom: var(--hppy-space-lg);
}

.doc-nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--hppy-space-md);
    margin-top: var(--hppy-space-2xl);
    padding-top: var(--hppy-space-md);
    border-top: 1px solid var(--hppy-color-border);
}

.doc-nav a {
    font-size: var(--hppy-font-13);
    color: var(--hppy-color-text-secondary);
    text-decoration: none;
}

.doc-nav a:hover {
    color: var(--hppy-color-link);
}

.faq-item {
    margin-bottom: var(--hppy-space-lg);
    padding: var(--hppy-space-md);
    background: var(--hppy-color-surface);
    border: 1px solid var(--hppy-color-border);
    border-radius: var(--hppy-radius-md);
}

.faq-item h3 {
    margin-top: 0;
}

.faq-item p {
    margin-bottom: 0;
    font-size: var(--hppy-font-15);
    color: var(--hppy-color-text-secondary);
}

.contact-card {
    margin-top: var(--hppy-space-xl);
    padding: var(--hppy-space-lg);
    background: var(--hppy-color-surface);
    border: 1px solid var(--hppy-color-border);
    border-radius: var(--hppy-radius-md);
    text-align: center;
}

.contact-card p {
    color: var(--hppy-color-text-secondary);
}

/* `a.` is load-bearing: plain `.contact-email` loses to `.doc a` above,
   which would paint the label link-blue on the gradient fill. */
a.contact-email {
    display: inline-block;
    margin-top: var(--hppy-space-12);
    padding: var(--hppy-space-12) var(--hppy-space-xl);
    min-height: var(--hppy-size-btn-touch);
    background: var(--hppy-color-cta-bg);
    color: var(--hppy-color-cta-fg);
    font-size: var(--hppy-font-lg);
    font-weight: var(--hppy-weight-semibold);
    border-radius: var(--hppy-radius-md);
    text-decoration: none;
    box-shadow: var(--hppy-shadow-sm);
    transition: opacity var(--hppy-transition-fast);
}

a.contact-email:hover {
    opacity: 0.9;
}

/* Numbered steps — /legal/account-deletion */
.step-list {
    counter-reset: steps;
    list-style: none;
    padding-left: 0;
}

.step-list li {
    counter-increment: steps;
    position: relative;
    padding-left: var(--hppy-space-2xl);
    margin-bottom: var(--hppy-space-20);
}

.step-list li::before {
    content: counter(steps);
    position: absolute;
    left: 0;
    top: 0;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--hppy-color-cta-bg);
    color: var(--hppy-color-cta-fg);
    border-radius: var(--hppy-radius-full);
    font-size: var(--hppy-font-sm);
    font-weight: var(--hppy-weight-bold);
}

.warning-box {
    margin: var(--hppy-space-20) 0;
    padding: var(--hppy-space-md);
    background: color-mix(in srgb, var(--hppy-color-danger) 10%, var(--hppy-color-bg));
    border-left: 4px solid var(--hppy-color-danger);
    border-radius: var(--hppy-radius-sm);
}

.warning-box strong {
    color: var(--hppy-color-danger);
}

.data-table {
    width: 100%;
    border-collapse: collapse;
    margin: var(--hppy-space-md) 0;
}

.data-table th,
.data-table td {
    text-align: left;
    padding: var(--hppy-space-sm) var(--hppy-space-12);
    border-bottom: 1px solid var(--hppy-color-border);
}

.data-table th {
    font-size: var(--hppy-font-sm);
    color: var(--hppy-color-text-secondary);
    text-transform: uppercase;
    letter-spacing: var(--hppy-overline-spacing);
}

.badge-delete,
.badge-keep {
    display: inline-block;
    padding: var(--hppy-space-2xs) var(--hppy-space-sm);
    border-radius: var(--hppy-radius-xs);
    font-size: var(--hppy-font-13);
    font-weight: var(--hppy-weight-semibold);
}

.badge-delete {
    background: color-mix(in srgb, var(--hppy-color-danger) 15%, var(--hppy-color-bg));
    color: var(--hppy-color-danger);
}

.badge-keep {
    background: rgba(var(--hppy-color-primary-rgb), 0.15);
    color: var(--hppy-color-link);
}
