/* Built by design-system/sync.sh from:
     00-fonts.css
     01-tokens.css
     themes/marketing.css
     02-base.css
     03-components.css
     04-marketing.css
   Edit the sources, not this file. */

/* =========================================================
   Lunolyte Design System: Fonts
   ---------------------------------------------------------
   GENERATED FILE. Do not edit inside an app.
   Source: design-system/css/00-fonts.css
   ---------------------------------------------------------
   Geist and Geist Mono, self-hosted. Every app runs CSP
   font-src 'self', so a font CDN is not reachable, and a
   monitoring product whose pages stall on a third-party host
   is not a good look regardless.

   The url() is relative to this stylesheet, not to the page,
   so the same declaration works whether an app serves its
   static files from /public or from the root.

   If the woff2 files are absent these rules simply fail and
   every app falls back to the platform UI face. Nothing
   breaks, the type just looks like the system default.
   ========================================================= */

@font-face {
  font-family: 'Geist';
  src: url('fonts/Geist.woff2') format('woff2-variations'),
       url('fonts/Geist.woff2') format('woff2');
  font-weight: 100 900;          /* single variable file covers the range */
  font-style: normal;
  font-display: swap;            /* show fallback text immediately, swap when loaded */
  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: 'Geist Mono';
  src: url('fonts/GeistMono.woff2') format('woff2-variations'),
       url('fonts/GeistMono.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  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;
}

/* =========================================================
   Lunolyte Design System: Core tokens
   ---------------------------------------------------------
   GENERATED FILE. Do not edit inside an app.
   Source: design-system/css/01-tokens.css
   Apply changes there and run design-system/sync.sh
   ---------------------------------------------------------
   Everything here is app-independent. Accent colours and the
   surface palette live in the per-app theme file that loads
   immediately before this one, so this file may reference
   --accent / --bg-* but never defines them.
   ========================================================= */

:root {
  /* ── Type family ──────────────────────────────────────
     Geist for everything, Geist Mono for anything the eye has
     to scan character by character: timestamps, incident ids,
     URLs, status codes. One family keeps the estate coherent;
     the matching mono is why this pair was chosen over Inter.

     The fallbacks are live until the woff2 files are added to
     design-system/assets/fonts. See 00-fonts.css. */
  --font-ui:      'Geist', system-ui, -apple-system, 'Segoe UI Variable Text', 'Segoe UI', sans-serif;
  --font-display: 'Geist', system-ui, -apple-system, 'Segoe UI Variable Display', 'Segoe UI', sans-serif;
  --font-mono:    'Geist Mono', ui-monospace, 'Cascadia Code', 'SF Mono', 'Fira Mono', monospace;

  /* ── Type scale ───────────────────────────────────────
     Ratio-based, 16px base. The two largest are fluid so a
     marketing hero can breathe without a media query.
     Rule: no raw px font-size anywhere downstream. */
  --fs-2xs:  0.6875rem;  /* 11 dense table meta */
  --fs-xs:   0.75rem;    /* 12 pills, captions */
  --fs-sm:   0.8125rem;  /* 13 labels, secondary */
  --fs-md:   0.875rem;   /* 14 default UI body */
  --fs-base: 1rem;       /* 16 prose */
  --fs-lg:   1.125rem;   /* 18 card titles */
  --fs-xl:   1.375rem;   /* 22 page titles */
  --fs-2xl:  1.75rem;    /* 28 stat values */
  --fs-3xl:  clamp(2rem,   1.40rem + 2.6vw, 3rem);
  --fs-4xl:  clamp(2.5rem, 1.50rem + 4.5vw, 4.5rem);

  --fw-regular:  400;
  --fw-medium:   500;
  --fw-semibold: 600;
  --fw-bold:     700;

  --lh-tight:   1.15;
  --lh-snug:    1.30;
  --lh-normal:  1.50;
  --lh-relaxed: 1.65;

  --ls-tight:  -0.022em;   /* large display text only */
  --ls-snug:   -0.011em;
  --ls-normal:  0;
  --ls-wide:    0.06em;    /* uppercase eyebrows */

  /* ── Space ────────────────────────────────────────────
     4px grid. These existed before but were never used;
     downstream files must use them instead of raw px. */
  --space-3xs: 2px;
  --space-2xs: 4px;
  --space-xs:  8px;
  --space-sm:  12px;
  --space-md:  16px;
  --space-lg:  24px;
  --space-xl:  40px;
  --space-2xl: 64px;
  --space-3xl: 96px;

  /* ── Radius ───────────────────────────────────────────── */
  --radius-sm:   6px;
  --radius-md:   10px;
  --radius-lg:   14px;
  --radius-xl:   20px;
  --radius-full: 999px;

  /* ── Elevation ────────────────────────────────────────── */
  --shadow-soft:  0 1px 3px rgba(0,0,0,0.45), 0 1px 2px rgba(0,0,0,0.30);
  --shadow-card:  0 4px 24px rgba(0,0,0,0.55), 0 1px 6px rgba(0,0,0,0.40);
  --shadow-lift:  0 12px 40px rgba(0,0,0,0.55);
  --shadow-modal: 0 24px 64px rgba(0,0,0,0.65);

  /* ── Layering ─────────────────────────────────────────
     Named so nothing downstream invents a z-index. */
  --z-base:     0;
  --z-raised:   10;
  --z-sticky:   100;
  --z-sidebar:  200;
  --z-dropdown: 300;
  --z-modal:    400;
  --z-toast:    500;

  /* ── Motion ───────────────────────────────────────────── */
  --dur-fast: 120ms;
  --dur-med:  200ms;
  --dur-slow: 320ms;
  --ease-out:    cubic-bezier(0.20, 0.80, 0.20, 1);
  --ease-in-out: cubic-bezier(0.40, 0.00, 0.20, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* ── Layout ───────────────────────────────────────────── */
  --sidebar-w:   240px;
  --topbar-h:    60px;
  --wrap-max:    1180px;
  --wrap-narrow: 760px;
  --wrap-prose:  680px;

  /* ── Status colours ───────────────────────────────────
     Identical across every app so "operational" never means
     two different greens. Each is a fill/ink pair. */
  --ok-fill:      rgba(34,197,94,0.12);
  --ok-line:      rgba(34,197,94,0.28);
  --ok-ink:       #4ADE80;
  --warn-fill:    rgba(249,115,22,0.12);
  --warn-line:    rgba(249,115,22,0.30);
  --warn-ink:     #FB923C;
  --bad-fill:     rgba(239,68,68,0.12);
  --bad-line:     rgba(239,68,68,0.30);
  --bad-ink:      #F87171;
  --info-fill:    rgba(168,85,247,0.12);
  --info-line:    rgba(168,85,247,0.30);
  --info-ink:     #C084FC;
  --neutral-fill: rgba(148,163,184,0.10);
  --neutral-line: rgba(148,163,184,0.22);
  --neutral-ink:  #94A3B8;
}

/* =========================================================
   Lunolyte theme: Marketing + Status
   ---------------------------------------------------------
   GENERATED FILE. Do not edit inside an app.
   Source: design-system/css/themes/marketing.css
   ---------------------------------------------------------
   The public face. Marketing and the status pages deliberately
   share one identity: a visitor who lands on a status page
   should recognise it as the same product as the website.

   This is the only theme with a light mode, because status
   pages honour a per-customer light/dark/auto setting.
   ========================================================= */

:root {
  color-scheme: dark;

  --bg-page:        #060911;
  --bg-shell:       #0A0F1A;
  --bg-card:        #0E1524;
  --bg-card-strong: #141D30;
  --bg-input:       #080D18;

  --text-main:  #E8EEF6;
  --text-muted: #8FA3BC;
  --text-soft:  #556882;

  --accent:           #22D3EE;
  --accent-strong:    #67E8F9;
  --accent-ink:       #06222B;   /* text on an accent fill */
  --accent-soft:      rgba(34,211,238,0.10);
  --accent-glow:      rgba(34,211,238,0.28);

  --border-soft:   rgba(148,196,220,0.10);
  --border-strong: rgba(148,196,220,0.22);

  --focus: var(--accent-strong);

  /* Ambient glow behind heroes and auth cards. Two stops so it
     reads as light falling on the page rather than a flat blob. */
  --glow-1: rgba(34,211,238,0.16);
  --glow-2: rgba(99,102,241,0.12);
}

/* Explicit light choice, and the OS preference when the page has
   made no choice. The two blocks are identical on purpose; a
   status page renders data-theme only for an explicit setting, and
   auto mode depends on the attribute being ABSENT. Never emit
   data-theme="auto", because html:not([data-theme]) would stop matching
   and every auto-mode visitor would be pinned to dark. */
:root[data-theme="light"],
:root:not([data-theme]) {
  --light-bg-page:        #F5F8FC;
  --light-bg-shell:       #FFFFFF;
  --light-bg-card:        #FFFFFF;
  --light-bg-card-strong: #EEF3F9;
  --light-bg-input:       #FFFFFF;
  --light-text-main:      #0C1B2A;
  --light-text-muted:     #47617A;
  --light-text-soft:      #7B92A6;
  --light-accent:         #0891B2;
  --light-accent-strong:  #0E7490;
  --light-accent-ink:     #FFFFFF;
  --light-accent-soft:    rgba(8,145,178,0.08);
  --light-accent-glow:    rgba(8,145,178,0.18);
  --light-border-soft:    rgba(12,27,42,0.09);
  --light-border-strong:  rgba(12,27,42,0.18);
  --light-shadow-card:    0 4px 24px rgba(12,27,42,0.10), 0 1px 4px rgba(12,27,42,0.06);
  --light-glow-1:         rgba(8,145,178,0.12);
  --light-glow-2:         rgba(99,102,241,0.08);
}

:root[data-theme="light"] {
  color-scheme: light;
  --bg-page:        var(--light-bg-page);
  --bg-shell:       var(--light-bg-shell);
  --bg-card:        var(--light-bg-card);
  --bg-card-strong: var(--light-bg-card-strong);
  --bg-input:       var(--light-bg-input);
  --text-main:      var(--light-text-main);
  --text-muted:     var(--light-text-muted);
  --text-soft:      var(--light-text-soft);
  --accent:         var(--light-accent);
  --accent-strong:  var(--light-accent-strong);
  --accent-ink:     var(--light-accent-ink);
  --accent-soft:    var(--light-accent-soft);
  --accent-glow:    var(--light-accent-glow);
  --border-soft:    var(--light-border-soft);
  --border-strong:  var(--light-border-strong);
  --shadow-card:    var(--light-shadow-card);
  --glow-1:         var(--light-glow-1);
  --glow-2:         var(--light-glow-2);
  --ok-fill:   rgba(22,163,74,0.10);
  --ok-ink:    #15803D;
  --warn-fill: rgba(234,88,12,0.10);
  --warn-ink:  #C2410C;
  --bad-fill:  rgba(220,38,38,0.10);
  --bad-ink:   #B91C1C;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    color-scheme: light;
    --bg-page:        var(--light-bg-page);
    --bg-shell:       var(--light-bg-shell);
    --bg-card:        var(--light-bg-card);
    --bg-card-strong: var(--light-bg-card-strong);
    --bg-input:       var(--light-bg-input);
    --text-main:      var(--light-text-main);
    --text-muted:     var(--light-text-muted);
    --text-soft:      var(--light-text-soft);
    --accent:         var(--light-accent);
    --accent-strong:  var(--light-accent-strong);
    --accent-ink:     var(--light-accent-ink);
    --accent-soft:    var(--light-accent-soft);
    --accent-glow:    var(--light-accent-glow);
    --border-soft:    var(--light-border-soft);
    --border-strong:  var(--light-border-strong);
    --shadow-card:    var(--light-shadow-card);
    --glow-1:         var(--light-glow-1);
    --glow-2:         var(--light-glow-2);
    --ok-fill:   rgba(22,163,74,0.10);
    --ok-ink:    #15803D;
    --warn-fill: rgba(234,88,12,0.10);
    --warn-ink:  #C2410C;
    --bad-fill:  rgba(220,38,38,0.10);
    --bad-ink:   #B91C1C;
  }
}

/* =========================================================
   Lunolyte Design System: Reset + base typography
   ---------------------------------------------------------
   GENERATED FILE. Do not edit inside an app.
   Source: design-system/css/02-base.css
   ========================================================= */

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

html {
  /* 16px base. The type scale is rem-based, so this is the one
     place a global size change would ripple from. */
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg-page);
  color: var(--text-main);
  font-family: var(--font-ui);
  font-size: var(--fs-md);
  line-height: var(--lh-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-synthesis-weight: none;
}

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-snug);
  color: var(--text-main);
  text-wrap: balance;
}

p { margin: 0; }

/* Long-form copy reads better with a wider measure than UI text. */
p, li { text-wrap: pretty; }

a {
  color: var(--accent);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}
a:hover { color: var(--accent-strong); }

img, svg, video { max-width: 100%; }
img, video { height: auto; display: block; }

code, kbd, pre, samp { font-family: var(--font-mono); font-size: 0.925em; }

button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }

table { border-collapse: collapse; width: 100%; }

hr {
  border: 0;
  border-top: 1px solid var(--border-soft);
  margin: var(--space-lg) 0;
}

/* ── Focus ────────────────────────────────────────────────
   One ring, everywhere. Before the overhaul only inputs had a
   focus style, which left keyboard users with no idea where
   they were on any page in the estate. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }

/* ── Motion ───────────────────────────────────────────────
   Covers every animation and transition in every downstream
   file at once, including any added later. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Utilities ───────────────────────────────────────────
   Deliberately few. This is not a utility framework; anything
   used more than a handful of times becomes a component. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.wrap {
  width: 100%;
  max-width: var(--wrap-max);
  margin-inline: auto;
  padding-inline: var(--space-lg);
}
.wrap-narrow { max-width: var(--wrap-narrow); }
.wrap-prose  { max-width: var(--wrap-prose); }

.text-muted { color: var(--text-muted); }
.text-soft  { color: var(--text-soft); }
.text-mono  { font-family: var(--font-mono); }

/* Horizontal scroll containers: wide tables and code must
   scroll inside themselves, never make the page scroll. */
.scroll-x { overflow-x: auto; max-width: 100%; }

/* =========================================================
   Lunolyte Design System: Shared components
   ---------------------------------------------------------
   GENERATED FILE. Do not edit inside an app.
   Source: design-system/css/03-components.css
   ---------------------------------------------------------
   Every app gets these verbatim. App-specific layout (sidebar,
   status page chrome, marketing hero) lives in that app's own
   layer, which loads after this one.
   ========================================================= */

/* ── Wordmark ─────────────────────────────────────────────
   The shimmer sweeps twice on load and then stops. An
   infinitely shimmering logo reads as broken within a week and
   burns GPU on every page in the estate.

   The solid colour is declared OUTSIDE the animation so a
   reduced-motion visitor, or any browser without
   background-clip:text, still sees a legible wordmark rather
   than transparent nothing. */
.wordmark {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
  color: var(--text-main);
}

@supports (background-clip: text) or (-webkit-background-clip: text) {
  @media (prefers-reduced-motion: no-preference) {
    .wordmark {
      background: linear-gradient(
        100deg,
        var(--text-main) 0%,
        var(--text-main) 38%,
        var(--accent-strong) 50%,
        var(--text-main) 62%,
        var(--text-main) 100%
      );
      background-size: 220% 100%;
      background-position: 120% 0;
      -webkit-background-clip: text;
      background-clip: text;
      color: transparent;
      animation: wordmark-shimmer 2.6s var(--ease-in-out) 2;
    }
  }
}

@keyframes wordmark-shimmer {
  from { background-position: 120% 0; }
  to   { background-position: -40% 0; }
}

/* ── Brand mark ───────────────────────────────────────────── */
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--text-main);
}
.brand-mark {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-strong) 100%);
  color: var(--accent-ink);
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: var(--fs-md);
  letter-spacing: 0;
}
.brand-text { display: flex; flex-direction: column; line-height: 1.1; }
.brand-name { font-weight: var(--fw-semibold); font-size: var(--fs-md); }
.brand-tag {
  font-size: var(--fs-2xs);
  color: var(--text-soft);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
}

/* ── Buttons ──────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  height: 40px;
  padding-inline: var(--space-md);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-main);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  line-height: 1;
  white-space: nowrap;
  transition:
    background var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out),
    transform var(--dur-fast) var(--ease-out);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn[disabled], .btn[aria-disabled="true"] {
  opacity: 0.5;
  pointer-events: none;
  transform: none;
}
.btn svg { width: 16px; height: 16px; flex-shrink: 0; }

.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: var(--fw-semibold);
}
.btn-primary:hover { background: var(--accent-strong); color: var(--accent-ink); }

.btn-secondary {
  background: var(--bg-card-strong);
  border-color: var(--border-strong);
  color: var(--text-main);
}
.btn-secondary:hover { background: var(--bg-card); border-color: var(--accent); color: var(--text-main); }

.btn-ghost { color: var(--text-muted); }
.btn-ghost:hover { background: var(--bg-card); color: var(--text-main); }

.btn-danger { background: var(--bad-ink); color: #2A0A0A; font-weight: var(--fw-semibold); }
.btn-danger:hover { background: #FCA5A5; color: #2A0A0A; }

.btn-sm { height: 32px; padding-inline: var(--space-sm); font-size: var(--fs-sm); }
.btn-lg { height: 48px; padding-inline: var(--space-lg); font-size: var(--fs-base); }
.btn-full { width: 100%; }

/* ── Cards ────────────────────────────────────────────────── */
/* A bare .card carries its own padding. Most cards in the portals hold
   content directly rather than a .card-body, and without this their text
   sat flush against the border. The :has() rules drop it again when the
   card uses the structured head/body/foot parts, which bring their own,
   or when it wraps a table that must reach the card's edge.

   Where :has() is unsupported those cards simply get roomier, never
   broken, which is the right way round for a progressive enhancement. */
.card {
  padding: var(--space-lg);
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}
/* Only the structured parts cancel the padding, because they bring their
   own. A card holding a table keeps its padding: these cards usually carry
   a heading too, and zeroing the padding for the table's sake pushed that
   heading flat against the border. */
.card:has(> .card-head),
.card:has(> .card-body),
.card:has(> .card-foot) { padding: 0; }

/* A card can still put a bare heading next to a structured body. The body
   pads itself, the card no longer pads at all, and the heading was left
   sitting against the border. It gets the same inset here, and the body
   below it tightens up so the two do not stack two full gaps apart. */
.card:has(> .card-body) > h1,
.card:has(> .card-body) > h2,
.card:has(> .card-body) > h3,
.card:has(> .card-body) > .section-title {
  margin: 0;
  padding: var(--space-lg) var(--space-lg) 0;
}
.card > h1 + .card-body,
.card > h2 + .card-body,
.card > h3 + .card-body,
.card > .section-title + .card-body { padding-top: var(--space-sm); }

/* Stacked sections need air between them, but only where the container
   actually stacks them: a grid or flex parent already spaces its own children
   with gap, and a second gap on top of that lands on every child but the
   first, knocking a row out of line.

   CSS cannot ask a parent what layout mode it is in, so a container that
   stacks its children in normal flow says so by carrying .flow. Expressing it
   the other way round, by naming the grids to exclude, means every grid that
   is ever added has to be remembered; the marketing feature grid was not, and
   every card in it but the first picked up a stray margin. */
.flow > * + * { margin-top: var(--space-lg); }

/* A section title introduces the block underneath it rather than standing
   apart from it, so it keeps the tighter gap a section header sets. */
.flow > .section-title + * { margin-top: var(--space-sm); }
.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--border-soft);
}
.card-title { font-size: var(--fs-lg); font-weight: var(--fw-semibold); }
.card-sub { font-size: var(--fs-sm); color: var(--text-muted); margin-top: var(--space-3xs); }
.card-body { padding: var(--space-lg); }
.card-foot {
  padding: var(--space-md) var(--space-lg);
  border-top: 1px solid var(--border-soft);
  display: flex;
  gap: var(--space-xs);
  justify-content: flex-end;
}

/* Interactive card: only for cards that are genuinely a link. */
.card-link { transition: border-color var(--dur-med) var(--ease-out), transform var(--dur-med) var(--ease-out); }
.card-link:hover { border-color: var(--border-strong); transform: translateY(-2px); }

/* The anchored tier in a pricing set. Contrast effect: this only
   works if the surrounding options are visibly plainer. */
.card-featured {
  border-color: var(--accent);
  box-shadow: var(--shadow-lift), 0 0 0 1px var(--accent-soft);
}

/* ── Pills and badges ─────────────────────────────────────
   Tone is a closed vocabulary. A database value that maps to
   nothing falls back to neutral, so an unknown status renders
   as a plain pill instead of unstyled text. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: 3px var(--space-xs);
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: 1.4;
  white-space: nowrap;
}
.tone-ok      { background: var(--ok-fill);      border-color: var(--ok-line);      color: var(--ok-ink); }
.tone-warn    { background: var(--warn-fill);    border-color: var(--warn-line);    color: var(--warn-ink); }
.tone-bad     { background: var(--bad-fill);     border-color: var(--bad-line);     color: var(--bad-ink); }
.tone-info    { background: var(--info-fill);    border-color: var(--info-line);    color: var(--info-ink); }
.tone-neutral { background: var(--neutral-fill); border-color: var(--neutral-line); color: var(--neutral-ink); }
.tone-accent  { background: var(--accent-soft);  border-color: var(--border-strong); color: var(--accent); }

.dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}
/* Only a live "something is wrong" state pulses, and it stops
   under reduced motion via the global rule in 02-base. */
.dot-live { animation: dot-pulse 2.4s var(--ease-in-out) infinite; }
@keyframes dot-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

/* ── Eyebrow ──────────────────────────────────────────────── */
.eyebrow {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  color: var(--accent);
}

/* ── Forms ────────────────────────────────────────────────── */
.field { display: flex; flex-direction: column; gap: var(--space-2xs); }
.field + .field { margin-top: var(--space-md); }
.field-label { font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-muted); }
.field-hint  { font-size: var(--fs-xs); color: var(--text-soft); }
.field-error { font-size: var(--fs-xs); color: var(--bad-ink); }

.input {
  width: 100%;
  height: 40px;
  padding-inline: var(--space-sm);
  background: var(--bg-input);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  color: var(--text-main);
  font-size: var(--fs-md);
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.input::placeholder { color: var(--text-soft); }
.input:hover { border-color: var(--border-strong); }
.input:focus { border-color: var(--accent); outline: none; }
.input:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
textarea.input { height: auto; min-height: 96px; padding-block: var(--space-xs); resize: vertical; }

select.input {
  appearance: none;
  padding-right: var(--space-xl);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238FA3BC' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-sm) center;
  background-size: 16px;
  cursor: pointer;
}
/* The list itself is drawn by the OS and cannot be styled, but the options
   inherit these, which stops a dark page from dropping open a white menu. */
select.input option { background: var(--bg-card); color: var(--text-main); }

/* Date and time inputs render a native picker button that defaults to a
   dark glyph, invisible against a dark field. invert flips it, and the
   whole control opens the picker rather than just that small target. */
.input[type="date"],
.input[type="time"],
.input[type="datetime-local"] { cursor: pointer; }
.input[type="date"]::-webkit-calendar-picker-indicator,
.input[type="time"]::-webkit-calendar-picker-indicator,
.input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  filter: invert(0.7);
  cursor: pointer;
  opacity: 0.8;
}
.input[type="date"]::-webkit-calendar-picker-indicator:hover,
.input[type="time"]::-webkit-calendar-picker-indicator:hover,
.input[type="datetime-local"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }

/* ── Empty state ──────────────────────────────────────────── */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-2xl) var(--space-lg);
  text-align: center;
  color: var(--text-muted);
}
.empty-title { font-size: var(--fs-base); font-weight: var(--fw-semibold); color: var(--text-main); }
.empty-desc  { font-size: var(--fs-sm); max-width: 42ch; }

/* ── Banner ───────────────────────────────────────────────── */
.banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-size: var(--fs-md);
}
.banner-title { font-weight: var(--fw-semibold); }
.banner-body  { font-size: var(--fs-sm); opacity: 0.85; }

/* ── Signup ───────────────────────────────────────────────── */
/* A wider auth card, because picking a plan needs room that signing in does
   not. Everything else about it is the same card. */
.auth-card-wide { max-width: 34rem; }

.auth-foot {
  margin-top: var(--space-md);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  text-align: center;
}

/* Monthly against yearly. Radios, not links: the first version navigated to
   change the interval, which reloaded the page and threw away the company
   name and email the visitor had already typed. */
.seg {
  display: flex;
  gap: var(--space-3xs);
  padding: var(--space-3xs);
  background: var(--bg-input);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
}
.seg-option {
  flex: 1;
  padding: var(--space-2xs) var(--space-xs);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  text-align: center;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.seg-option { cursor: pointer; }
.seg-option:hover { color: var(--text-main); }
.seg-option:has(input:checked) {
  background: var(--bg-card-strong);
  color: var(--text-main);
  font-weight: var(--fw-semibold);
}
.seg-option:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Both prices are in the page and the chosen interval decides which is shown,
   so switching costs no request and loses nothing that was typed. */
.signup-form .amount-yearly,
.signup-form:has(#interval-yearly:checked) .amount-monthly { display: none; }
.signup-form:has(#interval-yearly:checked) .amount-yearly  { display: inline; }

.plan-options {
  display: grid;
  gap: var(--space-2xs);
  margin-block: var(--space-md);
}

/* The radio keeps the keyboard and the accessibility tree; the label is what
   gets drawn. :has reads the checked state, so which plan is selected has one
   source of truth instead of a class the server also has to guess at. */
.plan-option {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-3xs) var(--space-xs);
  padding: var(--space-sm) var(--space-md);
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.plan-option:hover { border-color: var(--border-strong); }
.plan-option:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.plan-option:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.plan-option input,
.seg-option input {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.plan-option-name  { font-weight: var(--fw-semibold); color: var(--text-main); }
.plan-option-price { font-weight: var(--fw-semibold); color: var(--text-main); text-align: right; }
.plan-option-per   { font-size: var(--fs-xs); font-weight: var(--fw-regular); color: var(--text-soft); }
.plan-option-meta  { grid-column: 1 / -1; font-size: var(--fs-xs); color: var(--text-soft); }

/* ── Section rhythm ───────────────────────────────────────── */
.section { padding-block: var(--space-3xl); }
.section-alt {
  background: var(--bg-shell);
  border-block: 1px solid var(--border-soft);
}
/* The measure is set on the text elements themselves, never on this
   wrapper. `ch` resolves against the element's OWN font-size, so a
   max-width here would be computed at body size (14px) and then applied
   to a 48px heading, squeezing a two-line headline into three or four. */
.section-head { margin-bottom: var(--space-xl); }
.section-head h2 {
  max-width: 22ch;
  font-size: var(--fs-3xl);
  letter-spacing: var(--ls-tight);
  margin-top: var(--space-sm);
}
.section-head .lede {
  max-width: 60ch;
  margin-top: var(--space-sm);
  font-size: var(--fs-base);
  color: var(--text-muted);
  line-height: var(--lh-relaxed);
}
.section-head-center { text-align: center; }
.section-head-center h2,
.section-head-center .lede { margin-inline: auto; }

/* ── Auth screens ─────────────────────────────────────────
   Shared by all three signed-out surfaces: both portals and the private
   status page. It lived in the portal layer, which the status app does
   not load, so that login screen rendered unstyled. */
.auth-wrap { min-height: 100vh; display: grid; place-items: center; padding: var(--space-lg); }
.auth-card {
  position: relative;
  width: 100%;
  max-width: 400px;
  padding: var(--space-xl);
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}
.auth-wrap::before {
  content: '';
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(40rem 30rem at 50% 0%, var(--glow-1), transparent 65%);
}
.auth-title { margin-top: var(--space-md); font-size: var(--fs-xl); }
.auth-subtitle { margin-top: var(--space-xs); font-size: var(--fs-sm); color: var(--text-muted); line-height: var(--lh-relaxed); }
.auth-form { margin-top: var(--space-lg); }
.auth-card .btn-full { margin-top: var(--space-lg); }
.auth-icon {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  margin-top: var(--space-lg);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
}
.auth-icon svg { width: 22px; height: 22px; }

.auth-hint { margin-top: var(--space-md); font-size: var(--fs-xs); color: var(--text-soft); }
body.auth-page { display: grid; place-items: stretch; }

/* ── Grid ─────────────────────────────────────────────────── */
.grid { display: grid; gap: var(--space-md); }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

@media (max-width: 900px) {
  .grid-3, .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .grid-2, .grid-3, .grid-4 { grid-template-columns: minmax(0, 1fr); }
}

/* =========================================================
   Lunolyte Design System: Marketing layer
   ---------------------------------------------------------
   GENERATED FILE. Do not edit inside an app.
   Source: design-system/css/04-marketing.css
   ---------------------------------------------------------
   Loaded only by the public marketing site. This is the one
   surface where scroll choreography belongs; the portals get
   none of it, because motion in a screen full of tables is
   noise rather than polish.
   ========================================================= */

/* Sticky-header offset for in-page anchors, so a jumped-to
   heading never hides behind the topbar. */
:where(h1, h2, h3, [id]) { scroll-margin-top: calc(var(--topbar-h) + var(--space-lg)); }

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* ── Ambient glow ─────────────────────────────────────────
   Fixed, behind everything, cheap. Two offset radials so the
   page reads as lit from above rather than tinted. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(60rem 40rem at 15% -10%, var(--glow-1), transparent 65%),
    radial-gradient(50rem 35rem at 85% 5%,  var(--glow-2), transparent 60%);
}

/* ── Topbar ───────────────────────────────────────────────── */
.topbar {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--bg-page) 78%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid var(--border-soft);
}
.topbar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  width: 100%;
}
.topbar .wordmark { font-size: var(--fs-lg); }

.nav-links { display: flex; align-items: center; gap: var(--space-lg); }
.nav-links a {
  color: var(--text-muted);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
}
.nav-links a:hover { color: var(--text-main); }
.nav-cta { display: flex; align-items: center; gap: var(--space-xs); }

.nav-toggle {
  display: none;
  width: 40px; height: 40px;
  align-items: center; justify-content: center;
  background: transparent;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  color: var(--text-muted);
}

/* ── Hero ─────────────────────────────────────────────────── */
.hero {
  padding-top: var(--space-3xl);
  padding-bottom: var(--space-2xl);
  text-align: center;
}
.hero h1 {
  font-size: var(--fs-4xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
  margin-top: var(--space-md);
  max-width: 18ch;
  margin-inline: auto;
}
.hero h1 .accent {
  background: linear-gradient(100deg, var(--accent) 0%, var(--accent-strong) 60%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hero-lede {
  margin: var(--space-lg) auto 0;
  max-width: 56ch;
  font-size: var(--fs-base);
  color: var(--text-muted);
  line-height: var(--lh-relaxed);
}
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm);
  margin-top: var(--space-xl);
}
.hero-note {
  margin-top: var(--space-md);
  font-size: var(--fs-sm);
  color: var(--text-soft);
}

/* ── Status page mock ─────────────────────────────────────
   Mirrors the real status-app markup and colours so the
   homepage shows the actual product, not an illustration of it.
   Status colours come from the shared tokens, which is exactly
   why they match the live page. */
.hero-visual {
  position: relative;
  max-width: 620px;
  margin: var(--space-2xl) auto 0;
}
.hero-visual::before {
  content: '';
  position: absolute;
  inset: -12% -6% 10%;
  background: radial-gradient(50% 50% at 50% 40%, var(--accent-glow), transparent 70%);
  filter: blur(48px);
  z-index: -1;
}
.mock {
  background: var(--bg-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lift);
  overflow: hidden;
  text-align: left;
}
.mock-top {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid var(--border-soft);
  background: var(--bg-card-strong);
}
.mock-body { padding: var(--space-md); display: flex; flex-direction: column; gap: var(--space-sm); }
.mock-banner {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  background: var(--ok-fill);
  border: 1px solid var(--ok-line);
  color: var(--ok-ink);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-md);
}
.mock-rows { display: flex; flex-direction: column; }
.mock-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-sm) 0;
  font-size: var(--fs-sm);
  border-bottom: 1px solid var(--border-soft);
}
.mock-row:last-child { border-bottom: 0; }
.mock-incident {
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--warn-line);
  border-left: 3px solid var(--warn-ink);
  border-radius: var(--radius-md);
  background: var(--bg-card-strong);
}
.mock-incident-top { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs); }
.mock-incident-title { font-size: var(--fs-sm); font-weight: var(--fw-semibold); }
.mock-incident-time { margin-top: var(--space-2xs); font-size: var(--fs-xs); color: var(--text-soft); }

/* ── Logo / trust strip ───────────────────────────────────── */
/* A grid rather than a wrapping flex row: once each item carries a
   description the widths differ enough that flex-wrap leaves an orphan on
   its own line. Fixed columns keep the four aligned as a set. */
.trust {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-lg);
  margin-top: var(--space-2xl);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--border-soft);
}
.trust-label {
  grid-column: 1 / -1;
  text-align: center;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  color: var(--text-soft);
}
.trust-item {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--space-xs);
  text-align: left;
}
.trust-item svg { width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px; color: var(--accent); }
.trust-item-name { display: block; font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-main); }
/* Availability sits with the check itself. Two of these four are not on
   every plan, and a strip that implies otherwise is a promise the
   pricing page then contradicts. */
.trust-item-note { display: block; font-size: var(--fs-xs); color: var(--text-soft); margin-top: 1px; }

@media (max-width: 900px) {
  .trust { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 520px) {
  .trust { grid-template-columns: minmax(0, 1fr); }
}

/* ── Steps ────────────────────────────────────────────────── */
.steps {
  --step-size: 34px;   /* the rail is positioned from this, so they cannot drift apart */
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-lg);
}
.step { position: relative; }
/* Connector rail from each circle to the next. The circles are left
   aligned within their column, not centred, so the rail is measured
   from the circle rather than from the column midpoint.
   The same --space-sm breathing room sits on both ends: touching the
   next circle while standing clear of the previous one reads as a
   misalignment even when the geometry is exact. */
.step:not(:last-child)::before {
  content: '';
  position: absolute;
  top: calc(var(--step-size) / 2);                    /* centre of the circle */
  left: calc(var(--step-size) + var(--space-sm));
  right: calc(var(--space-sm) - var(--space-lg));     /* crosses the grid gap */
  height: 1px;
  background: var(--border-strong);
}
.step-num {
  display: grid;
  place-items: center;
  width: var(--step-size); height: var(--step-size);
  margin-bottom: var(--space-sm);
  border-radius: 50%;
  background: var(--accent-soft);
  border: 1px solid var(--border-strong);
  color: var(--accent);
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: var(--fs-sm);
}
.step h3 { font-size: var(--fs-base); }
.step p { margin-top: var(--space-2xs); font-size: var(--fs-sm); color: var(--text-muted); }

@media (max-width: 820px) {
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The rail only makes sense on a single unbroken row. */
  .step::before { display: none; }
}
@media (max-width: 520px) {
  .steps { grid-template-columns: minmax(0, 1fr); }
}

/* ── Feature cards ────────────────────────────────────────── */
.feature { padding: var(--space-lg); }
.feature-icon {
  display: grid;
  place-items: center;
  width: 38px; height: 38px;
  margin-bottom: var(--space-sm);
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  border: 1px solid var(--border-strong);
  color: var(--accent);
}
.feature-icon svg { width: 18px; height: 18px; }
.feature { display: flex; flex-direction: column; }
.feature h3 { font-size: var(--fs-base); }
.feature p { margin-top: var(--space-2xs); font-size: var(--fs-sm); color: var(--text-muted); line-height: var(--lh-relaxed); }
/* Which plans include this. flex-end pins it to the bottom so the notes
   line up across a row of cards with different copy lengths. */
.feature-note {
  align-self: flex-start;
  margin-top: auto;
  padding-top: var(--space-md);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--text-soft);
}

/* ── Pricing ──────────────────────────────────────────────── */
.price-card { display: flex; flex-direction: column; padding: var(--space-lg); }
.price-name { font-size: var(--fs-base); font-weight: var(--fw-semibold); }
.price-tagline {
  margin-top: var(--space-3xs);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  /* Two lines' worth of room whether the sentence needs them or not. One
     tagline wraps before the others do at narrower widths, and without this
     that card's whole feature list sits a line lower than its neighbours,
     which is exactly the row-by-row comparison the list exists to allow. */
  min-height: calc(2 * var(--lh-normal) * 1em);
}
.price-amount {
  display: flex;
  align-items: baseline;
  gap: var(--space-2xs);
  margin-top: var(--space-md);
}
.price-num {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
}
.price-per { font-size: var(--fs-sm); color: var(--text-soft); }

/* Sits under the amount rather than beside it, because it qualifies the price
   and should not read as part of it. Dutch and EU rules want the VAT basis
   stated at the price itself, not in a footnote under the grid. */
/* The value sits at the end of its own line rather than under the name. On its
   own line it doubled the height of the card for no extra meaning. */
.price-feature-val {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-main);
  text-align: right;
}

.price-vat {
  margin-top: var(--space-3xs);
  font-size: var(--fs-xs);
  color: var(--text-soft);
}
/* flex: 1 makes the list absorb the height difference between plans, so
   the divider and the button land on the same line in every card no
   matter how many features each one lists. */
.price-features {
  flex: 1;
  list-style: none;
  margin: var(--space-lg) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
.price-features li { display: flex; align-items: baseline; gap: var(--space-xs); font-size: var(--fs-sm); }
.price-feature-name { flex: 1; min-width: 0; }
.price-features svg { width: 15px; height: 15px; flex-shrink: 0; margin-top: 2px; }
.price-features .yes { color: var(--ok-ink); }
.price-features .no { color: var(--text-soft); }
.price-features li.muted { color: var(--text-soft); }
.price-actions { margin-top: var(--space-lg); padding-top: var(--space-lg); border-top: 1px solid var(--border-soft); }

.price-badge {
  position: absolute;
  top: calc(-1 * var(--space-sm));
  left: 50%;
  transform: translateX(-50%);
  padding: 3px var(--space-sm);
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  white-space: nowrap;
}
/* The wrapper only exists to hang the badge off, so it has to pass the
   grid's stretched height straight through to the card. Without this
   the featured card sizes to its own content and the row loses its
   shared baseline. */
.price-wrap { position: relative; display: flex; }
.price-wrap > .card { flex: 1; }
.price-card { height: 100%; }

/* ── FAQ ──────────────────────────────────────────────────── */
.faq { display: flex; flex-direction: column; gap: var(--space-xs); }
.faq-item {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
}
.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  cursor: pointer;
  list-style: none;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: '+';
  flex-shrink: 0;
  font-size: var(--fs-lg);
  line-height: 1;
  color: var(--accent);
  transition: transform var(--dur-med) var(--ease-out);
}
.faq-item[open] summary::after { transform: rotate(45deg); }
.faq-item[open] { border-color: var(--border-strong); }
.faq-answer {
  padding: 0 var(--space-md) var(--space-md);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: var(--lh-relaxed);
}

/* Native <details> snaps open with no way to transition it, so the
   height is animated in JS. Scoped to html.js: without the script the
   element still opens and closes perfectly well, just instantly.
   The wrapper carries the animation so the answer keeps its padding
   collapsed along with the rest of the box. */
@media (prefers-reduced-motion: no-preference) {
  html.js .faq-answer-wrap {
    overflow: hidden;
    transition: height var(--dur-med) var(--ease-out);
  }
}

/* ── CTA band ─────────────────────────────────────────────── */
.cta-band {
  padding: var(--space-2xl) var(--space-lg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xl);
  background:
    radial-gradient(70% 120% at 50% 0%, var(--accent-soft), transparent 70%),
    var(--bg-card);
  text-align: center;
}
.cta-band h2 { font-size: var(--fs-3xl); letter-spacing: var(--ls-tight); }
.cta-band p { margin: var(--space-sm) auto 0; max-width: 52ch; color: var(--text-muted); }
.cta-band .hero-actions { margin-top: var(--space-lg); }

/* ── Prose ────────────────────────────────────────────────── */
.prose { max-width: var(--wrap-prose); }
.prose h1 { font-size: var(--fs-3xl); }
.prose h2 { font-size: var(--fs-xl); margin-top: var(--space-xl); }
.prose p, .prose li { margin-top: var(--space-sm); color: var(--text-muted); line-height: var(--lh-relaxed); }
.prose ul { padding-left: var(--space-lg); }
.prose strong { color: var(--text-main); font-weight: var(--fw-semibold); }
.prose .updated { font-size: var(--fs-sm); color: var(--text-soft); }
.legal-notice {
  margin-top: var(--space-xl);
  padding: var(--space-md);
  border: 1px solid var(--warn-line);
  border-radius: var(--radius-md);
  background: var(--warn-fill);
  color: var(--warn-ink);
  font-size: var(--fs-sm);
}

/* ── Waitlist ─────────────────────────────────────────────── */
.waitlist { display: flex; gap: var(--space-xs); max-width: 440px; margin: var(--space-lg) auto 0; }
.waitlist .input { flex: 1; }
@media (max-width: 480px) { .waitlist { flex-direction: column; } }

.flash {
  max-width: 440px;
  margin: var(--space-lg) auto 0;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  font-size: var(--fs-md);
}
.flash-success { background: var(--ok-fill);  border: 1px solid var(--ok-line);  color: var(--ok-ink); }
.flash-error   { background: var(--bad-fill); border: 1px solid var(--bad-line); color: var(--bad-ink); }

/* ── Footer ───────────────────────────────────────────────── */
.footer {
  margin-top: var(--space-3xl);
  padding-block: var(--space-2xl) var(--space-lg);
  border-top: 1px solid var(--border-soft);
  background: var(--bg-shell);
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr repeat(3, minmax(0, 1fr));
  gap: var(--space-xl);
}
.footer-about { font-size: var(--fs-sm); color: var(--text-muted); margin-top: var(--space-sm); max-width: 34ch; }
.footer-col h4 {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  color: var(--text-soft);
  margin-bottom: var(--space-sm);
}
.footer-col a { display: block; padding-block: var(--space-3xs); font-size: var(--fs-sm); color: var(--text-muted); }
.footer-col a:hover { color: var(--text-main); }
.footer-bottom {
  margin-top: var(--space-xl);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border-soft);
  font-size: var(--fs-sm);
  color: var(--text-soft);
}

@media (max-width: 820px) {
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ── Reveal on scroll ─────────────────────────────────────
   The pre-reveal state is applied ONLY under html.js, which the
   bundle sets on boot. If the script fails to load the content
   is simply visible, never a blank page. */
@media (prefers-reduced-motion: no-preference) {
  html.js .reveal {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
  }
  html.js .reveal.in { opacity: 1; transform: none; }
}

/* ── Mobile nav ───────────────────────────────────────────── */
@media (max-width: 860px) {
  .nav-toggle { display: inline-flex; }
  .nav-links {
    position: absolute;
    top: var(--topbar-h);
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--space-xs);
    background: var(--bg-shell);
    border-bottom: 1px solid var(--border-soft);
    box-shadow: var(--shadow-card);
  }
  .nav-links:not(.open) { display: none; }
  .nav-links a { padding: var(--space-sm); border-radius: var(--radius-md); }
  .nav-links a:hover { background: var(--bg-card); }
  .nav-cta .btn-ghost { display: none; }
}

