/* ==========================================================================
   Host An App — core.css
   --------------------------------------------------------------------------
   Foundations loaded by every page: self-hosted fonts, design tokens
   (:root, body.paper, dark mode), reset/base, layout primitives, shared
   components (buttons, badges, code, tables), header/footer chrome, reveals,
   and the global reduced-motion kill-switch. Surface bundles
   (landing/docs/documents/dash/admin) read these tokens and must not
   redeclare them. See AGENTS.md (CSS: modular stylesheets).
   ========================================================================== */

/* PAPER RESPONSIVE — shared chrome (header/footer). Landing rules live in landing.css. */

@media (max-width: 600px) {

    /* The sticky header must stay full-bleed (width: 100% + its own
       padding-inline) so the bar spans the whole mobile viewport;
       only non-sticky headers shrink to the column width. */
    .header:not(.header--sticky) {
        width: calc(100% - 28px);
    }

    .header-right {
        gap: 14px;
    }

    .system-status {
        display: none;
    }
    .footer {
        width: calc(100% - 28px);
    }
}
/* ==========================================================================
   Host An App — Design System (hand-written, zero dependencies, no Tailwind)
   --------------------------------------------------------------------------
   Single source of truth for every page: landing, docs, 404, login, and the
   server-rendered document shell (internal/render/layout.html) loaded by
   Markdown and Mermaid deployments. Self-hosted fonts live under
   /assets/fonts/ so the strict Content-Security-Policy on /<slug> routes
   cannot block them.

   Two themes live in this file:
     • DARK  (default)        — legacy: content deployed before the paper
       retheme still references these classes until it expires.
     • PAPER (body.paper)     — marketing, service, and rendered pages
       (landing, docs, login, 404, Markdown + Mermaid deployments). Light
       "paper" aesthetic. Adding class="paper" to <body> flips the shared
       design tokens, so most rules restyle automatically.
   ========================================================================== */

/* ----- Self-hosted fonts (latin subset) ----- */
@font-face { font-family: "Instrument Serif"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/instrument-serif-400.woff2") format("woff2"); }
@font-face { font-family: "Instrument Serif"; font-style: italic; font-weight: 400; font-display: swap; src: url("/assets/fonts/instrument-serif-400-italic.woff2") format("woff2"); }
@font-face { font-family: "Geist"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/assets/fonts/geist-var-latin.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/assets/fonts/inter-var-latin.woff2") format("woff2"); }
@font-face { font-family: "DM Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/dm-mono-400.woff2") format("woff2"); }
@font-face { font-family: "DM Mono"; font-style: normal; font-weight: 500; font-display: swap; src: url("/assets/fonts/dm-mono-500.woff2") format("woff2"); }
@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 300 700; font-display: swap; src: url("/assets/fonts/space-grotesk-var-latin.woff2") format("woff2"); }
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 100 800; font-display: swap; src: url("/assets/fonts/jetbrains-mono-var-latin.woff2") format("woff2"); }

/* ----- Design tokens (shared defaults; body.paper flips them to paper) ----- */
:root {
  /* Native widgets (scrollbars, form controls, autofill) follow the page
     theme, not the OS: paper pages are light until the resolver flips
     html[data-theme] before first paint, a page outside the paper theme
     is the legacy dark default, and the operator console declares its
     own body.admin-dark. */
  color-scheme: light;
  --bg-base: #030405;
  --bg-surface: #0A0B0E;
  --bg-surface-hover: #111318;
  --bg-deep: #050608;
  --border: #1E2128;
  --border-active: #323642;
  /* Structural border for dark surfaces (card edge, table rule): held to
     WCAG 1.4.11 non-text contrast. rgba(255,255,255,.4) composites to
     3.70:1 on --bg-base and 3.78:1 on --bg-surface. */
  --line-strong: rgba(255, 255, 255, .4);
  --text-bright: #F3F4F6;
  --text: #E5E7EB;
  --text-muted: #A8B0BC;
  --text-muted-2: #8A919E;
  --accent: #FF4F19;
  --accent-hover: #FF6B3D;
  --success: #00E676;
  --warning: #F5A623;
  --error: #FF3366;

  --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "DM Mono", ui-monospace, "SF Mono", "Menlo", "Consolas", monospace;
  --font-serif: "Instrument Serif", Georgia, serif;

  /* Design ramp (single source): radius steps, spacing steps, type ramp.
     New rules use these tokens; one-off px values outside the ramp need
     an inline comment saying why. */
  --r-xs: 4px;
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-xl: 22px;
  --r-pill: 999px;
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --fs-xs: 0.625rem;
  --fs-sm: 0.75rem;
  --fs-md: 0.8125rem;
  --fs-lg: 0.9375rem;
  --fs-xl: 1.125rem;
  --fs-2xl: 1.5rem;
  --fs-3xl: 2rem;
  --shadow-sm: 0 1px 2px rgba(23, 23, 21, .05);
  --shadow-md: 0 12px 30px rgba(0, 0, 0, .13);
  --shadow-lg: 0 18px 40px rgba(0, 0, 0, .06);
  --header-h: 3.5rem;
}

html:not(:has(body.paper)) { color-scheme: dark; }
html[data-theme="dark"] { color-scheme: dark; }
html:has(body.admin-dark) { color-scheme: dark; }

/* ----- Reset / base ----- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: var(--bg-base);
  color: var(--text-bright);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background-image:
    radial-gradient(circle at 50% 0%, rgba(255, 79, 25, 0.05) 0%, transparent 50%),
    radial-gradient(circle at 100% 100%, rgba(0, 230, 118, 0.02) 0%, transparent 50%);
  background-attachment: fixed;
}
img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; }
::selection { background: rgba(255, 79, 25, 0.3); color: var(--text-bright); }
[id] { scroll-margin-top: 5rem; }

/* ----- Scrollbar ----- */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--border-active); }

/* ----- Screen-reader only ----- */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ----- Shared text utilities (used by docs, api-docs and the admin UI) ----- */
.mono { font-family: var(--font-mono); font-size: 12px; }
.muted { color: var(--text-muted); }

/* ----- Layout primitives ----- */
.page-main { flex: 1 0 auto; }
.container { width: 100%; max-width: 72rem; margin-inline: auto; padding-inline: 1rem; }
@media (min-width: 640px) { .container { padding-inline: 1.5rem; } }

/* ==========================================================================
   Animations
   ========================================================================== */
@keyframes fadeUp { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }
@keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
@keyframes pulse-ring { 0% { transform: scale(0.8); opacity: 0.5; } 100% { transform: scale(2.5); opacity: 0; } }
@keyframes spin { to { transform: rotate(360deg); } }

.anim-fade-up { opacity: 0; animation: fadeUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
.anim-delay-1 { animation-delay: 0.1s; }
.anim-delay-2 { animation-delay: 0.2s; }

/* ==========================================================================
   Reusable components (docs)
   ========================================================================== */
.doc-card {
  background: var(--bg-surface);
  border: 1px solid var(--line-strong);
  border-radius: 1rem;
  padding: 1.5rem;
  transition: border-color 0.3s cubic-bezier(0.16, 1, 0.3, 1), background 0.3s cubic-bezier(0.16, 1, 0.3, 1), transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.doc-card--accent { border-color: rgba(181, 58, 10, 0.35); background: var(--orange-soft); }
.doc-card:hover { border-color: var(--border-active); background: var(--bg-surface-hover); transform: translateY(-2px); }

.code-block { background: #181817; border: 1px solid #30302e; border-radius: 14px; overflow: hidden; position: relative; }
.code-block pre { padding: 1.125rem 1.375rem; margin: 0; overflow-x: auto; font-family: var(--font-mono); font-size: 0.8125rem; line-height: 1.8; color: #deddd8; }
.code-block .code-header { padding: 0.625rem 1.375rem; border-bottom: 1px solid #30302e; display: flex; align-items: center; justify-content: space-between; background: #1d1d1b; }
.code-block .code-header > span { font-family: var(--font-mono); font-size: 0.6875rem; color: #9b9890; }

code { font-family: var(--font-mono); font-size: 0.875em; background: var(--bg-surface); border: 1px solid var(--border); padding: 0.15em 0.4em; border-radius: 4px; color: var(--text-bright); overflow-wrap: anywhere; }
pre code { background: none; border: 0; padding: 0; font-size: inherit; border-radius: 0; }

.hl-keyword { color: #FF79C6; }
.hl-string { color: #F1FA8C; }
.hl-comment { color: #8b9dc9; font-style: italic; }

.hl-property { color: #50FA7B; }

/* badges — shared block (.badge); the admin and dashboard badge blocks compose it. */
.badge { display: inline-flex; align-items: center; font-family: var(--font-mono); font-size: var(--fs-xs); padding: 0.25rem 0.5rem; border-radius: var(--r-pill); font-weight: 500; }
.badge-get { background: var(--get-soft); color: var(--get-ink); border: 1px solid rgba(32, 165, 107, 0.3); }
.badge-post { background: var(--post-soft); color: var(--post-ink); border: 1px solid rgba(181, 58, 10, 0.3); }
.badge-required { background: var(--post-soft); color: var(--post-ink); border: 1px solid rgba(181, 58, 10, 0.3); }
.badge-optional { background: var(--neutral-soft); color: var(--neutral-ink); border: 1px solid rgba(119, 116, 108, 0.3); }
.badge-success { background: var(--get-soft); color: var(--get-ink); border: 1px solid rgba(32, 165, 107, 0.3); }
.badge-error { background: var(--error-soft); color: var(--error-ink); border: 1px solid rgba(212, 61, 31, 0.3); }

.copy-btn { font-family: var(--font-mono); font-size: 0.625rem; background: #282826; border: 1px solid #41413f; color: #fff; padding: 0.375rem 0.75rem; border-radius: 8px; cursor: pointer; transition: background 0.15s ease, color 0.15s ease, transform 0.1s ease; position: absolute; top: 0.55rem; right: 0.625rem; z-index: 2; }
.copy-btn:hover { background: #343432; }
.copy-btn:active { transform: scale(0.95); }

.param-table-wrapper { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.param-table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }
.param-table th { text-align: left; padding: 0.625rem 0.875rem; border-bottom: 1px solid var(--line-strong); font-family: var(--font-mono); font-size: 0.6875rem; color: var(--text-muted-2); text-transform: uppercase; letter-spacing: 0.08em; }
.param-table td { padding: 0.75rem 0.875rem; border-bottom: 1px solid var(--line-strong); color: var(--text-bright); vertical-align: top; }
.param-table tr:last-child td { border-bottom: 0; }
.param-table td.muted { color: var(--text-muted); font-size: 0.8125rem; }
.param-table td.mono { font-family: var(--font-mono); color: var(--accent); font-size: 0.75rem; }

/* ==========================================================================
   PAPER THEME — landing + docs (body.paper)
   --------------------------------------------------------------------------
   A tiny publishing machine for the internet. Light paper background, ink
   text, orange accents, serif italic flourishes.
   ========================================================================== */

body.paper {
    /* paper palette */
    --paper: #f4f2ec;
    --paper-bright: #fbfaf7;
    --ink: #171715;
    /* Secondary ink. WCAG AA requires >= 4.5:1 against every surface it
       lands on: --paper (cream) and the lightest card composite
       (var(--bg-surface) over --paper ~ #faf9f6). #67645e measures 5.2:1
       on --paper, 5.5:1 on that composite and 5.8:1 on white; the earlier
       #6b6862 cleared --paper (4.96:1) but dropped to 4.2-4.5:1 on the
       cream cards and chip tints, and #77746c failed everywhere. */
    --muted: #67645e;
    --line: rgba(23, 23, 21, .13);
    /* Structural border (card edge, table row rule, table-head underline),
       held to WCAG 1.4.11 non-text contrast (>= 3:1). rgba(23,23,21,.5)
       composites to 3.32:1 on --paper; --line stays for purely decorative
       dividers. */
    --line-strong: rgba(23, 23, 21, .5);
    /* Burnt-orange brand accent, held to WCAG AA as text: #b53a0a is
       5.25:1 on --paper, 5.58:1 on the lightest card composite (#faf9f6)
       and 5.88:1 on white; white-on-accent fills clear AA the same way.
       --orange-soft is the wash behind accented links and callouts; at
       .11 the accent ink on it measured 4.47:1 (below AA), so it is .08. */
    --orange: #b53a0a;
    --orange-soft: rgba(181, 58, 10, .08);
    --green: #20a56b;
    --blue: #3f61ff;

    /* Paper's grotesque is Geist (the landing revamp's typeface); Inter stays
       as the fallback so a browser that cannot load the variable woff2 still
       gets the previous look rather than the system default. */
    --sans: Geist, Inter, system-ui, sans-serif;
    --serif: "Instrument Serif", Georgia, serif;
    --mono: "DM Mono", ui-monospace, monospace;

    /* flip the shared dark-theme tokens so shared rules restyle themselves */
    --bg-base: #f4f2ec;
    --bg-surface: rgba(255, 255, 255, .55);
    --bg-surface-hover: #fbfaf7;
    --bg-deep: #181817;
    --border: rgba(23, 23, 21, .13);
    --border-active: rgba(23, 23, 21, .38);
    --text-bright: #171715;
    --text: #2a2a27;
    --text-muted: #67645e;
    /* Fainter secondary ink, still held to AA: #6f6c64 measures 4.68:1 on
       --paper and 5.10:1 on the lightest card composite (was #9b9890,
       2.57:1). */
    --text-muted-2: #6f6c64;
    --accent: #b53a0a;
    /* Ink laid on an accent fill (publish/deploy buttons, the sign-in
       chip): white clears AA on #b53a0a (5.88:1). Declared as a token so
       the contrast guard can hold the pair. */
    --on-accent: #fff;
    /* Always-dark surfaces. `--ink` is the TEXT ink and flips to light in
       dark mode, so it must never back a fill that carries white text.
       `--ink-panel` is the fixed dark fill (primary buttons, the nav CTA,
       the CTA strip, active segmented tabs) and `--on-ink` its ink. */
    --ink-panel: #171715;
    --ink-panel-hover: #2a2a27;
    --on-ink: #fff;
    /* Badge/chip inks: the coloured fill tints are very light in the light
       theme, so the raw brand colours do not clear AA as text on them.
       These darkened inks do; the dark theme reuses the bright brand
       colours (their tints sit on a dark surface). */
    --get-ink: #12744a;
    --post-ink: #9c3208;
    --neutral-ink: #5c5952;
    --error-ink: #a82c10;
    --get-soft: rgba(32, 165, 107, .12);
    --post-soft: rgba(181, 58, 10, .12);
    --neutral-soft: rgba(119, 116, 108, .12);
    --error-soft: rgba(212, 61, 31, .12);
    /* Solid success fill + its ink (the payment return CTA, the "copied"
       chip). --green is a card-decoration tint and is too light to back
       white text; this is the darkened fill. */
    --success-fill: #17804f;
    --on-success: #fff;
    /* Translucent page-chrome surface (sticky header, sticky docs switch). */
    --header-bg: rgba(244, 242, 236, .92);
    /* Darker hover: #8f2f10 is 7.27:1 on --paper, so the hover state
       also clears AA with headroom. */
    --accent-hover: #8f2f10;
    --success: #20a56b;
    --warning: #b97a1f;
    --error: #d43d1f;
    --font-sans: var(--sans);
    --font-mono: var(--mono);
    --font-serif: var(--serif);
    --header-h: 80px;

    background: var(--paper);
    background-image: none;
    color: var(--ink);
    font-family: var(--sans);
    overflow-x: hidden;
}

body.paper button, body.paper input, body.paper textarea { font: inherit; }

body.paper ::selection { background: var(--orange); color: white; }

body.paper ::-webkit-scrollbar { width: 10px; }
body.paper ::-webkit-scrollbar-thumb { background: rgba(23, 23, 21, .18); border-radius: 6px; border: 3px solid var(--paper); }
body.paper ::-webkit-scrollbar-thumb:hover { background: rgba(23, 23, 21, .3); }

/* ----- Dark mode (html[data-theme="dark"] body.paper, resolved pre-paint) -----
   The attribute is on <html> (the pre-paint resolver stamps it before
   first paint); the theme itself is body.paper. The same token names,
   dark values: every paper rule restyles itself.
   Key ratios: ink #f3f1eb on #141412 = 16.3:1; muted #a9a7a0 = 7.7:1;
   accent #ff6b3d = 6.5:1; line-strong rgba(255,255,255,.4) = 3.83:1. */
html[data-theme="dark"] body.paper {
    --paper: #141412;
    --paper-bright: #1d1d1b;
    --ink: #f3f1eb;
    --muted: #a9a7a0;
    --line: rgba(255, 255, 255, .12);
    --line-strong: rgba(255, 255, 255, .4);
    --orange: #ff6b3d;
    --orange-soft: rgba(255, 107, 61, .14);
    --green: #2fe08d;
    --blue: #7d90ff;
    --bg-base: #141412;
    --bg-surface: rgba(255, 255, 255, .05);
    --bg-surface-hover: #1d1d1b;
    --bg-deep: #0c0d0f;
    --border: rgba(255, 255, 255, .12);
    --border-active: rgba(255, 255, 255, .32);
    --text-bright: #f3f1eb;
    --text: #d8d5cd;
    --text-muted: #a9a7a0;
    --text-muted-2: #a6a39b;
    --accent: #ff6b3d;
    --accent-hover: #ff8a63;
    /* Bright accent fills carry dark ink in dark mode (white-on-#ff6b3d is
       2.83:1; #141412 on it is 7.4:1). */
    --on-accent: #141412;
    --ink-panel: #0c0d0f;
    --ink-panel-hover: #1d1d1b;
    --on-ink: #f3f1eb;
    --get-ink: #56e7a4;
    --post-ink: #ffa184;
    --neutral-ink: #cfccc4;
    --error-ink: #ff9a9a;
    --get-soft: rgba(32, 165, 107, .14);
    --post-soft: rgba(255, 107, 61, .14);
    --neutral-soft: rgba(255, 255, 255, .1);
    --error-soft: rgba(255, 107, 61, .14);
    --header-bg: rgba(20, 20, 18, .92);
    --success: #2fe08d;
    --warning: #ffb547;
    --error: #ff6b7a;
    --success-fill: #2fe08d;
    --on-success: #141412;
    background: var(--paper);
    background-image: none;
    color: var(--ink);
}
html[data-theme="dark"] body.paper ::selection { background: var(--orange); color: #141412; }
html[data-theme="dark"] body.paper::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .2); border-color: var(--paper); }
html[data-theme="dark"] body.paper::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, .32); }
/* Paper grain and light-only washes are invisible on dark; keep them off. */
html[data-theme="dark"] body.paper::before { display: none; }
/* Theme toggle (marketing header + console header share the class). */
.theme-toggle { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; border-radius: 999px; border: 1px solid var(--border); background: transparent; color: var(--text-muted); cursor: pointer; font-size: 1rem; line-height: 1; transition: transform .3s cubic-bezier(.2, .8, .2, 1); }
.theme-toggle:hover { color: var(--text-bright); border-color: var(--border-active); }

/* The glyph rides the state the wiring already reflects: pressed = dark. */
.theme-toggle[aria-pressed="true"] { transform: rotate(180deg); }

/* Buttons — shared primitives per the bundle contract (AGENTS.md,
   "CSS: modular stylesheets"): every surface composes .button /
   .button-primary / .button-secondary, including the tenant and
   operator consoles, which never load landing.css where these used
   to live. Without the base fills here, console buttons fall back to
   UA form-control styling — unreadable in dark mode. */
.button {
    border: 0;
    cursor: pointer;

    padding: 15px 20px;
    border-radius: var(--r-md);

    font-size: 13px;
    font-weight: 700;

    /* Colour joins the lift so hover fills and border washes ease too. */
    transition:
        transform .2s ease,
        box-shadow .2s ease,
        background-color .15s ease,
        border-color .15s ease,
        color .15s ease;
}

.button-primary {
    background: var(--ink-panel);
    color: var(--on-ink);

    box-shadow: 0 12px 30px rgba(0, 0, 0, .13);
}

.button-secondary {
    background: var(--bg-surface);
    border: 1px solid var(--line);
    color: var(--ink);
}

.button:hover {
    transform: translateY(-3px);
}

/* Hover deepens the primary's fill (the token exists for exactly this) and
   gives the lift a longer throw than --shadow-md — the ramp has no deeper
   step, hence the literal. */
.button-primary:hover {
    background: var(--ink-panel-hover);
    box-shadow: 0 18px 44px rgba(0, 0, 0, .22);
}

.button-secondary:hover {
    background: var(--bg-surface-hover);
    border-color: var(--border-active);
}

/* Press lands between rest and the hover lift, so the button reads as
   pushed down rather than jumping. */
.button:active {
    transform: translateY(-1px) scale(.97);
}

.button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* ----- Dark-mode surface overrides --------------------------------------------------
   The dark scope above flips tokens; these rules cover surfaces that
   hardcode a light value instead of reading one, so they would otherwise
   stay light with light ink. Each is the dark counterpart of a literal
   light surface. ------------------------------------------------------------------ */
html[data-theme="dark"] body.paper .provider--google { background: #1d1d1b; }
html[data-theme="dark"] body.paper .provider--github { background: #f3f1eb; color: #141412; }
/* Status inks and the operator/tenant console surfaces below carry literal
   light-theme values (.dash-* / .adm-* blocks); everything else in the paper
   theme is token-driven, so it needs no override here. */
html[data-theme="dark"] body.paper .report-form__label { color: var(--muted); }
html[data-theme="dark"] body.paper .report-form__result.is-ok { color: #56e7a4; }
html[data-theme="dark"] body.paper .report-form__result.is-error { color: #ff8a68; }
html[data-theme="dark"] body.paper .dash-switch__summary { background: rgba(255, 255, 255, .08); }
html[data-theme="dark"] body.paper .dash-switch__summary:hover { background: rgba(255, 255, 255, .14); }
html[data-theme="dark"] body.paper .dash .button-secondary { background: rgba(255, 255, 255, .1); }
/* The console's status inks and header blur are literal light-theme values
   on .dash-page (the body element itself). */
html[data-theme="dark"] body.paper.dash-page {
    --dash-ok-ink: #56e7a4;
    --dash-warn-ink: #ffb547;
    --dash-accent-ink: #ff8a63;
    --dash-info-ink: #9fb0ff;
    --dash-danger-ink: #ff8a68;
    --dash-danger-hover: #ff9d80;
    --dash-header-blur: rgba(20, 20, 18, .88);
}

/* subtle paper grain */
body.paper::before {
    content: "";
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 1000;
    opacity: .035;
    background-image:
        url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
}

/* inline code chips on paper (dark panels reset this below) */
body.paper code {
    background: var(--bg-surface);
    border: 1px solid var(--line);
    color: var(--ink);
    font-family: var(--mono);
}

body.paper .code-block code,
body.paper .config-panel code,
body.paper .md-content pre code,
body.paper .mermaid-raw code,
body.paper .source-block code {
    background: none;
    border: 0;
    padding: 0;
    color: inherit;
    font-size: inherit;
}

/* dark panels keep their own dark tokens regardless of theme */
.config-panel, .machine, .code-block, .doc-terminal, .term {
    --text-bright: #deddd8;
    --text: #deddd8;
    --ink: #deddd8;
    --text-muted: #9b9890;
    /* Was #777 (3.97:1 on #181817, 3.52:1 on #232321 — both below AA);
       #8f8f8f measures 5.49:1 and 4.87:1. */
    --text-muted-2: #8f8f8f;
    --border: #30302e;
    --border-active: #41413f;
    --bg-surface: #232321;
    --bg-base: #181817;
    --paper: #1d1d1b;
    --paper-bright: #232321;
    --line: #30302e;
    --line-strong: rgba(255, 255, 255, .4);
    --accent: #ff6b3d;
    --orange: #ff6b3d;
    --orange-soft: rgba(255, 107, 61, .14);
    --on-accent: #141412;
    --get-ink: #56e7a4;
    --post-ink: #ff9d7a;
    --error-ink: #ff8a8a;
    --neutral-ink: #cfccc4;
    --get-soft: rgba(32, 165, 107, .14);
    --post-soft: rgba(255, 107, 61, .14);
    --neutral-soft: rgba(255, 255, 255, .1);
    --error-soft: rgba(255, 107, 61, .14);
}

/* =========================================================
   PAPER HEADER (shared by landing + docs)
========================================================= */

.header {
    width: min(1220px, calc(100% - 40px));
    height: 80px;
    margin: auto;

    display: flex;
    align-items: center;
    justify-content: space-between;

    position: relative;
    z-index: 100;
}

.header--sticky {
    position: sticky;
    top: 0;

    /* Full-bleed bar: the background must span the whole viewport so wide
       content (e.g. CSV tables up to 1400px) never sticks out beside it.
       The inner content stays aligned with the 1220px centered column via
       symmetric padding. */
    width: 100%;
    margin: 0;
    padding-inline: max(20px, calc((100% - 1220px) / 2));

    background: var(--header-bg);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);

    border-bottom: 1px solid var(--line);
    transition: box-shadow .2s ease;
}

/* Scrolled: the bar casts over the content passing under it (ScrollShadowJS,
   emitted by Footer, toggles the class). The ramp's ink shadow is invisible
   on the dark theme, which gets a black one instead. */
.header--sticky.is-scrolled { box-shadow: var(--shadow-lg); }
html[data-theme="dark"] .header--sticky.is-scrolled { box-shadow: 0 18px 40px rgba(0, 0, 0, .45); }

.brand {
    display: flex;
    align-items: center;
    gap: 11px;

    color: inherit;
    text-decoration: none;

    /* The brand is the anchor of the row: it never wraps to a second line
       and never shrinks, no matter how little room the controls leave. */
    flex-shrink: 0;
    white-space: nowrap;

    font-size: 18px;
    font-weight: 750;
    letter-spacing: -.045em;
}

.brand-mark {
    width: 32px;
    height: 32px;
    flex-shrink: 0; /* the tile is never squeezed out of shape */

    display: grid;
    place-items: center;

    background: var(--ink);
    color: var(--paper);

    border-radius: 10px;

    transform: rotate(-5deg);
    transition: transform .3s cubic-bezier(.2, .8, .2, 1);
}

.brand:hover .brand-mark {
    transform: rotate(5deg) scale(1.08);
}

/* Arrow glyph as inline SVG: immune to emoji font fallback (a bare ↗ text
   node renders as the color-emoji variant on some platforms). */
.brand-mark svg {
    width: 15px;
    height: 15px;

    display: block;
}

.header-right {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    min-width: 0; /* allow shrinking; the nav scrolls rather than the brand wrapping */
    /* row-gap only pays off when the controls wrap on narrow viewports
       (tablet/mobile with pricing, showcase, search and the toggle aboard);
       at wide widths nothing wraps and this is invisible. */
    row-gap: 6px;
    gap: 18px;
}

.system-status {
    font-family: var(--mono);
    font-size: 10px;
    color: var(--muted);

    display: flex;
    align-items: center;
    gap: 7px;
}

.status-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--green);
    flex-shrink: 0;

    box-shadow: 0 0 0 0 rgba(32, 165, 107, .5);
    animation: statusPulse 2.5s infinite;
}

.status-dot.is-error {
    background: var(--error);
    animation: none;
}

@keyframes statusPulse {
    70% { box-shadow: 0 0 0 8px rgba(32, 165, 107, 0); }
    100% { box-shadow: 0 0 0 0 rgba(32, 165, 107, 0); }
}

.header-link {
    color: var(--ink);
    text-decoration: none;
    font-size: 13px;
    font-weight: 600;

    /* Inline element: vertical padding enlarges only the tap target, never
       the line box, so the 80px header keeps its geometry while the touch
       area grows to ~44px tall. The horizontal padding stays at half the
       tightest header gap (body.docs .header-right uses 10px at <=640px) so
       adjacent links' hit boxes can never overlap. */
    padding: 14px 4px;
}

/* Inside the swipe strip the negative margin cancels the horizontal padding,
   so the strip's layout is unchanged; on simple headers (a lone Docs link in
   .header-right) the padding is just extra room and needs no cancellation. */
/* Collapse the horizontal padding between neighbouring links only — never
   at the strip's edges, where a negative margin would push the first/last
   hit box into the brand's tap area or past the header gutter. */
.nav .header-link + .header-link { margin-left: -8px; }

.header-link:hover { text-decoration: underline; text-underline-offset: 4px; }

/* The current nav entry on a bar of plain links: an inline text link has no
   pill to fill the way .nav-cta does, so it is marked with the brand accent
   and a thick underline. --accent on --paper already clears WCAG AA (see
   TestStylesheetContrastMeetsWCAG), so the text stays legible. */
.header-link.is-active {
    color: var(--accent);
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 4px;
}

/* =========================================================
   PAPER FOOTER — clean, two-zone layout
   Brand (+ tagline) on the left, grouped nav columns on the
   right. Legal and product links are vertical lists, not an
   inline dot-separated sentence, so the footer scans instead
   of shouting.
   ========================================================= */

.footer {
    width: min(1220px, calc(100% - 40px));
    margin: auto;
    padding: 40px 0 44px;
    border-top: 1px solid var(--line);
    font-family: var(--mono);
    font-size: 11.5px;
    line-height: 1.6;
    color: var(--muted);
}

.footer a {
    color: var(--muted);
    text-decoration: none;
    text-underline-offset: 3px;
    transition: color .15s ease;
}

.footer a:hover { color: var(--ink); text-decoration: underline; text-decoration-color: rgba(23,23,21,.22); }

.footer-heart { color: var(--orange); }

/* legacy alias — deployed pages still reference this until expiry */
.footer__brand {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}

.footer-made { font-size: 11px; }

/* Tap room for the footer link columns: the links are flex items, so the
   vertical padding is real layout — on phones the columns stack and the
   rows must be thumb-sized. */
.footer__links a { padding: 4px 0; }

.footer__title {
    font-family: var(--sans);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: -.03em;
    color: var(--ink);
}

.footer__tagline {
    margin-top: 2px;
    font-family: var(--sans);
    font-size: 12px;
    line-height: 1.5;
    color: var(--muted);
    max-width: 28ch;
}

/* Running Host An App release tag (vX.Y.Z), stamped into the landing footer
   at image build time. Doubles as a quick visual cache-freshness check: the
   tag shown must match the latest entry on /changelog. */
.footer__version {
    margin-top: 2px;
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: 0;
}

.footer__version a {
    color: var(--muted);
    text-decoration: underline;
    text-decoration-color: rgba(23,23,21,.18);
    text-underline-offset: 3px;
    font-variant-numeric: tabular-nums;
}

.footer__version a:hover { color: var(--ink); }

.footer__publisher {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--line);
    font-size: 10px;
    color: var(--muted);
}

.footer__publisher a { text-decoration: underline; text-decoration-color: rgba(23,23,21,.18); }

.footer__inner {
    display: flex;
    justify-content: space-between;
    gap: 48px;
    align-items: flex-start;
}

.footer__nav {
    display: flex;
    gap: 40px;
    flex-shrink: 0;
}

.footer__col {
    display: flex;
    flex-direction: column;
    gap: 0;
    min-width: 120px;
}

.footer__col-title {
    font-family: var(--mono);
    font-size: 10.5px;
    font-weight: 500;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--ink);
    margin-bottom: 10px;
}

.footer__links {
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.footer__links a {
    font-size: 11px;
    color: var(--muted);
}

.footer__links a:hover { color: var(--ink); }

/* legacy horizontal legal row — still supported, now unused */

@media (max-width: 760px) {

    .footer__inner {
        flex-direction: column;
        gap: 28px;
    }
    .footer__nav {
        width: 100%;
        gap: 28px;
    }
    .footer__col { min-width: 0; flex: 1; }
}

@media (max-width: 600px) { .footer { width: calc(100% - 28px); padding-top: 32px; } }

/* =========================================================
   REVEALS — hidden only once the page's head script has marked the
   document .js, so a browser without JavaScript paints the content
   outright (no script = no hiding, and a page that forgets the head
   script fails open rather than blank). Pages that mark up .reveal ship
   the one-line classList.add('js') script in the head; their observers
   then add .visible.
========================================================= */

.reveal {
    transition:
        opacity .8s cubic-bezier(.2, .8, .2, 1),
        transform .8s cubic-bezier(.2, .8, .2, 1);
}

.js .reveal {
    opacity: 0;
    transform: translateY(28px);
}

.reveal.visible {
    opacity: 1;
    transform: translateY(0);
}

@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;
    }
    .reveal { opacity: 1; transform: none; }
}

/* THEME FLIP — while ThemeToggleJS (and the console's and the operator
   console's own toggles) swap the theme, html.theme-anim gives every element
   a quarter-second colour transition so backgrounds, borders and ink
   cross-fade instead of snapping. Scoped to the class — and to
   no-preference, so the reduced-motion kill-switch above stays the last word
   — so nothing else inherits the timing. */
@media (prefers-reduced-motion: no-preference) {
    html.theme-anim *,
    html.theme-anim *::before,
    html.theme-anim *::after {
        transition: background-color .25s ease, border-color .25s ease, color .25s ease !important;
    }
}

/* Phone bar: below 520px the section links cannot fit beside the wordmark
   and the swipe strip starts clipping its tail — the sign-in chip and the
   theme toggle ran up to 27px past the viewport at 390–430px (measured).
   The strip's scrollbar is deliberately hidden, so a clipped chip read as
   a broken button rather than a scrollable row. Bars that carry a CTA keep
   exactly the two controls that matter (sign in, theme); the links stay
   one thumb-scroll away in the footer. Bars without a CTA (the gate/error
   bar) keep their lone Docs link. At 520px the full bar fits exactly
   (measured), so the cutoff sits just below it. */
@media (max-width: 519px) {
    .nav:has(.nav-cta) .header-link {
        display: none;
    }
}
