/* Built by design-system/sync.sh from:
     00-fonts.css
     01-tokens.css
     themes/marketing.css
     02-base.css
     03-components.css
     05-status.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; }

/* ── 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: Status page layer
   ---------------------------------------------------------
   GENERATED FILE. Do not edit inside an app.
   Source: design-system/css/05-status.css
   ---------------------------------------------------------
   The public status page. Shares the marketing theme on
   purpose: a visitor who arrives here from the website should
   recognise the same product, not a different one.

   No scroll choreography here. Someone opening a status page
   is checking whether something is broken, and making them
   wait for content to animate in is the wrong instinct.
   ========================================================= */

body.app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* ── Topbar ───────────────────────────────────────────────── */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  min-height: var(--topbar-h);
  padding: var(--space-sm) var(--space-lg);
  background: var(--bg-shell);
  border-bottom: 1px solid var(--border-soft);
}
.topbar-start { display: flex; align-items: center; gap: var(--space-sm); min-width: 0; }
.topbar-end { display: flex; align-items: center; gap: var(--space-xs); flex-shrink: 0; }

.topbar-brand-text { display: flex; flex-direction: column; min-width: 0; }
.topbar-domain {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-snug);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topbar-tagline { font-size: var(--fs-xs); color: var(--text-muted); }
.topbar-user { font-size: var(--fs-xs); color: var(--text-muted); }

/* White-label logo. Constrained rather than trusted: a customer can
   upload any dimensions and the header still has to hold its shape. */
.brand-logo { max-height: 32px; max-width: 180px; width: auto; object-fit: contain; }

.private-badge {
  padding: 2px var(--space-xs);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
}

.theme-toggle-btn {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  background: transparent;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  color: var(--text-muted);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.theme-toggle-btn:hover { background: var(--bg-card); color: var(--text-main); }

/* ── Layout ───────────────────────────────────────────────── */
.page-body { flex: 1; padding-block: var(--space-xl); }
.container {
  width: 100%;
  max-width: var(--wrap-narrow);
  margin-inline: auto;
  padding-inline: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.section-title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  color: var(--text-soft);
  margin-bottom: var(--space-sm);
}

/* ── Overall banner ───────────────────────────────────────
   The one thing a visitor came for. Deliberately the largest
   element on the page. */
.overall-banner {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-lg);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-snug);
}
.overall-banner .banner-text { flex: 1; min-width: 0; }
.banner-icon { display: grid; place-items: center; flex-shrink: 0; }

.banner-operational { background: var(--ok-fill);      border-color: var(--ok-line);      color: var(--ok-ink); }
.banner-degraded    { background: var(--warn-fill);    border-color: var(--warn-line);    color: var(--warn-ink); }
.banner-down        { background: var(--bad-fill);     border-color: var(--bad-line);     color: var(--bad-ink); }
.banner-maintenance { background: var(--info-fill);    border-color: var(--info-line);    color: var(--info-ink); }
.banner-unknown     { background: var(--neutral-fill); border-color: var(--neutral-line); color: var(--neutral-ink); }

/* ── Maintenance ──────────────────────────────────────────── */
.maintenance-windows-section { display: flex; flex-direction: column; gap: var(--space-xs); }
.maintenance-banner {
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--info-line);
  border-left: 3px solid var(--info-ink);
  border-radius: var(--radius-md);
  background: var(--bg-card);
}
.maintenance-banner-active { background: var(--info-fill); }
.maintenance-banner-label { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--info-ink); }
.maintenance-banner-message { margin-top: var(--space-2xs); font-size: var(--fs-sm); color: var(--text-muted); }
.maintenance-tag {
  padding: 1px var(--space-2xs);
  border-radius: var(--radius-sm);
  background: var(--info-fill);
  color: var(--info-ink);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
}

/* ── Services ─────────────────────────────────────────────── */
.services-list {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.service-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid var(--border-soft);
}
.service-row:last-child { border-bottom: 0; }
.service-info { display: flex; align-items: center; gap: var(--space-xs); min-width: 0; }
.service-name { font-size: var(--fs-md); font-weight: var(--fw-medium); }
.service-right { flex-shrink: 0; }
.no-services { padding: var(--space-lg); text-align: center; color: var(--text-muted); font-size: var(--fs-sm); }

/* ── Incidents ────────────────────────────────────────────── */
.incidents-section { display: flex; flex-direction: column; }
.incident-card {
  padding: var(--space-md);
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-left: 3px solid var(--warn-ink);
  border-radius: var(--radius-lg);
}
.incident-card + .incident-card { margin-top: var(--space-xs); }
.incident-resolved { border-left-color: var(--ok-ink); opacity: 0.85; }

.incident-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-sm);
  flex-wrap: wrap;
}
.incident-title-row { display: flex; align-items: center; gap: var(--space-xs); flex-wrap: wrap; min-width: 0; }
.incident-title { font-size: var(--fs-md); font-weight: var(--fw-semibold); }
.incident-meta { display: flex; align-items: center; gap: var(--space-xs); flex-shrink: 0; }
.incident-time { font-size: var(--fs-xs); color: var(--text-soft); font-family: var(--font-mono); }

.incident-description,
.incident-impact,
.incident-root-cause {
  margin-top: var(--space-xs);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: var(--lh-relaxed);
}
.incident-impact strong, .incident-root-cause strong { color: var(--text-main); font-weight: var(--fw-semibold); }

.incident-events {
  margin-top: var(--space-md);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--border-soft);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
.incident-event { display: flex; gap: var(--space-sm); align-items: baseline; }
/* Monospace so a column of timestamps aligns instead of shimmering. */
.event-time {
  flex-shrink: 0;
  min-width: 11ch;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--text-soft);
}
.event-message { font-size: var(--fs-sm); color: var(--text-muted); }

/* ── Incident history ─────────────────────────────────────── */
.history-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  cursor: pointer;
  user-select: none;
}
.history-header .section-title { margin-bottom: 0; }
.toggle-icon {
  color: var(--text-soft);
  font-size: var(--fs-xs);
  transition: transform var(--dur-med) var(--ease-out);
}
.history-header[aria-expanded="true"] .toggle-icon { transform: rotate(90deg); }
.history-body { margin-top: var(--space-sm); }

/* Height is animated from JS, because the hidden attribute cannot be
   transitioned. Scoped to html.js so that without the bundle the section
   still expands, just instantly. */
@media (prefers-reduced-motion: no-preference) {
  html.js .history-body { overflow: hidden; transition: height var(--dur-med) var(--ease-out); }
}

/* ── Refresh bar ──────────────────────────────────────────
   A thin progress line at the very bottom showing when the page
   will next reload itself. Bottom rather than top so it never
   competes with the status banner for attention. */
.refresh-bar-wrap {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: transparent;
  pointer-events: none;
  z-index: var(--z-sticky);
}
.refresh-bar {
  height: 100%;
  width: 0;
  background: var(--accent);
  opacity: 0.55;
}

/* ── Footer ───────────────────────────────────────────────── */
.app-footer {
  padding: var(--space-lg);
  text-align: center;
  font-size: var(--fs-xs);
  color: var(--text-soft);
  border-top: 1px solid var(--border-soft);
}

/* ── Error page ───────────────────────────────────────────── */
.error-page {
  flex: 1;
  display: grid;
  place-items: center;
  padding: var(--space-2xl) var(--space-lg);
}
.error-card {
  max-width: 420px;
  padding: var(--space-2xl);
  text-align: center;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}
.error-code {
  font-family: var(--font-display);
  font-size: var(--fs-4xl);
  font-weight: var(--fw-bold);
  line-height: 1;
  color: var(--accent);
  letter-spacing: var(--ls-tight);
}
.error-card h1 { margin-top: var(--space-sm); font-size: var(--fs-xl); }
.error-card p { margin-top: var(--space-xs); color: var(--text-muted); font-size: var(--fs-sm); }
.error-card .btn { margin-top: var(--space-lg); }

/* ── Responsive ───────────────────────────────────────────── */
@media (max-width: 560px) {
  .topbar { padding: var(--space-sm) var(--space-md); }
  .container { padding-inline: var(--space-md); }
  .overall-banner { font-size: var(--fs-md); padding: var(--space-md); }
  .incident-header { flex-direction: column; gap: var(--space-xs); }
  .topbar-user { display: none; }
  .incident-event { flex-direction: column; gap: 0; }
  .event-time { min-width: 0; }
}

