@charset "UTF-8";
/* ============================================================================
   Ganter Lab — server site design system (ADR 0011)
   No MudBlazor, no Bootstrap: a CSS custom-property token set drives a light/dark
   theme via [data-theme] (server-rendered from a cookie). The Identity scaffold
   pages use a handful of Bootstrap class names; those are styled to the tokens.
   ============================================================================ */

/* ---- Tokens: light (default) ----
   Neutrals are a single WARM family ("datasheet paper" — see the Instrument Ink
   direction): one hue, stepped by lightness, so the canvas never reads as a second
   tint fighting the brand blue. The accent (azul-sinal) and the reserved SCADA
   semantics (red/amber/green) are the only saturated colors and stay brand-locked. */
:root {
    --bg: #f4f2ec;
    --surface: #fcfbf7;
    --surface-2: #ece9e0;
    --border: #e3dfd5;
    --border-strong: #d2ccbe;
    --text: #1e1b15;
    --muted: #6b6458;
    --accent: #0369a1;
    --accent-hover: #075985;
    --accent-contrast: #ffffff;
    --accent-soft: #e0f2fe;
    /* Button ink: the neutral, high-contrast pair the button family fills with — the page's
       own ink as the fill, the page's own paper as the label. Its own tokens (not --text and
       --bg read inline) so a theme can retune the buttons without moving the body copy. */
    --ink-solid: #1e1b15;
    --ink-solid-hover: #3a352c;
    --ink-contrast: #fbf9f4;
    /* Semantic ink on this paper is read text, so each one clears 4.5:1 against both its own
       soft background and the page: the light theme used to sit near 3:1 and the notices that
       matter most (a purchase that cannot go through, a saving worth naming) were the palest. */
    --danger: #c92a2a;
    --danger-solid: #e03131;
    --danger-solid-hover: #c92a2a;
    --danger-contrast: #ffffff;
    --danger-bg: #fff0f0;
    --warning: #8a6400;
    --warning-bg: #fff8e6;
    --info-bg: #eaf5fd;
    --success: #17753a;
    --success-bg: #edf9f0;

    /* How tall the sticky bar stands right now. It wraps to a second row when the menu drops
       under the brand, and to a third when the actions do, so whatever has to clear it (anchor
       landings, the documentation rail) reads this instead of repeating the height it starts at. */
    --topbar-h: 52px;

    --radius: 14px;
    --radius-md: 10px;
    --radius-sm: 8px;
    /* The button family is fully rounded at every size, so the pill is a token and not a
       number repeated per variant. */
    --radius-pill: 999px;
    --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
    --shadow-md: 0 4px 12px rgba(16, 24, 40, 0.08), 0 2px 4px rgba(16, 24, 40, 0.05);
    --shadow-lg: 0 18px 48px rgba(16, 24, 40, 0.14), 0 4px 12px rgba(16, 24, 40, 0.08);

    --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --control-h: 2.75rem;
    --control-h-lg: 3rem;
    --content-max: 1080px;
    /* Footer band: one step off the canvas, shared by every frame that wears the site footer. */
    --land-band: #edeae2;

    /* Native widgets (the <select> options popup, scrollbars, form controls the site
       doesn't draw itself) follow the theme instead of defaulting to the OS light chrome. */
    color-scheme: light;
}

/* ---- Tokens: dark ----
   Warm-graphite "instrument ink": powder-coated equipment enclosure, not blue-black.
   One warm hue stepped by lightness (canvas → surface → inset → borders); the cool
   signal-blue accent is the single electric element against the inert warm chassis. */
[data-theme="dark"] {
    --bg: #131110;
    --surface: #24221e;
    --surface-2: #322f2a;
    --border: #3a362f;
    --border-strong: #4b453d;
    --text: #eceae4;
    --muted: #a39d90;
    --accent: #38bdf8;
    --accent-hover: #7dd3fc;
    --accent-contrast: #082f49;
    --accent-soft: #0e2a3e;
    /* The same pair flipped: warm off-white fill on the deep canvas, canvas as the label. */
    --ink-solid: #eceae4;
    --ink-solid-hover: #fbf9f4;
    --ink-contrast: #131110;
    --danger: #ff6b6b;
    --danger-solid: #ff6b6b;
    --danger-solid-hover: #ff8787;
    --danger-contrast: #350b0b;
    --danger-bg: #2a1717;
    --warning: #f0b429;
    --warning-bg: #2c2410;
    --info-bg: #0c2334;
    --success: #51cf66;
    --success-bg: #142a19;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45);
    --shadow-lg: 0 24px 60px rgba(0, 0, 0, 0.6), 0 6px 16px rgba(0, 0, 0, 0.45);

    --land-band: #0e0c0b;

    color-scheme: dark;
}

/* ---- Base ---- */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

body {
    font-family: var(--font-sans);
    background: var(--bg);
    color: var(--text);
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 { line-height: 1.2; margin: 0 0 0.75rem; font-weight: 650; letter-spacing: -0.01em; }
h1 { font-size: 1.7rem; }
h2 { font-size: 1.3rem; }
h3 { font-size: 1.1rem; }
p { margin: 0 0 1rem; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

hr { border: none; border-top: 1px solid var(--border); margin: 1.25rem 0; }
code { background: var(--surface-2); padding: 0.1em 0.35em; border-radius: var(--radius-sm); font-size: 0.9em; }

/* ============================================================================
   App shell (authenticated/main pages)
   ============================================================================ */
.app-shell { display: flex; flex-direction: column; min-height: 100vh; --land-max: var(--content-max); }

/* The one topbar class: every frame wraps the shared PublicTopbar in <header class="topbar">.
   The bar is full-bleed (its background and border run edge to edge), but the row inside it
   rides the same column as the page under it — so brand and menu line up with the content
   they sit above. Each frame declares the column it centers on through --land-max, the same
   column the shared footer rides; the fallback is the app column used by the authenticated
   pages. The background follows the frame's own canvas (--land-bg where the marketing pages
   set one), so the bar reads as a line over the page, never a second surface. Total height
   ~52px. */
/* The keyboard's way past the chrome, first in the Tab order of every public frame. Parked off
   the top edge until it takes focus, so it costs a page nothing until somebody is using it; fixed
   rather than absolute, so it lands in view wherever the focus was, and above the bar's own layer. */
.skip-link {
    position: fixed;
    left: 0.75rem;
    top: -4rem;
    z-index: 20;
    padding: 0.5rem 0.9rem;
    border-radius: var(--radius-sm);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    color: var(--text);
    font-weight: 600;
    box-shadow: var(--shadow-md);
}
.skip-link:focus { top: 0.6rem; text-decoration: none; }

.topbar {
    min-height: 52px;
    padding: 0;
    border-bottom: 1px solid var(--border);
    background: var(--land-bg, var(--bg));
    position: sticky;
    top: 0;
    z-index: 10;
}

.topbar-inner {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem 1rem;
    width: 100%;
    max-width: var(--land-max, var(--content-max));
    margin: 0 auto;
    padding: 0 1.5rem;
    min-height: 52px;
}

/* The wordmark is the brand face (docs/brand/brand.md): Inter Medium, slightly tight. */
.brand {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-display);
    font-weight: 500;
    font-size: 1.1rem;
    color: var(--text);
    letter-spacing: -0.01em;
}
.brand:hover { text-decoration: none; }

/* The G mark inside the brand lockup (BrandLink.razor) follows the theme accent. */
.brand-mark { width: 20px; height: 20px; flex-shrink: 0; }
.brand-mark path { stroke: var(--accent); }

.topnav { display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem; }
/* One nav size on the bar. It lands on the More disclosure itself, not just its summary: the
   details element's own line box has to match the sibling links, or its taller strut drops
   the label off their baseline. Scoped to the bar so the in-page menus reusing .topnav keep
   their own size. */
.topbar .topnav a,
.topbar .topnav-more { font-size: 0.8125rem; }
.topnav a {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.28rem;
    color: var(--muted);
    font-weight: 500;
}
.topnav a svg { width: 0.85em; height: 0.85em; flex-shrink: 0; opacity: 0.8; }
.topnav a:hover { color: var(--text); text-decoration: none; }
/* Current page: the label goes solid and takes an accent rule under it. */
.topnav a.active { color: var(--text); }
.topnav a.active::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -0.45rem;
    height: 2px;
    border-radius: 2px;
    background: var(--accent);
}

/* More: Integrators and the live demo sit in a native disclosure, so the bar stays a short
   read and the menu still works with no script (Enter/Space on the summary). A visit that
   already ran script also opens it on hover with a little intent either way, refuses the press
   that would shut a panel the hover just opened, and closes it on a click outside and on
   Escape (theme.js). */
.topnav-more { position: relative; }
.topnav-more > summary {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--muted);
    font-weight: 500;
    cursor: pointer;
    list-style: none;
}
.topnav-more > summary::-webkit-details-marker { display: none; }
.topnav-more > summary::marker { content: ""; }
.topnav-more > summary:hover,
.topnav-more[open] > summary,
.topnav-more.is-current > summary { color: var(--text); }
.topnav-more.is-current > summary::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -0.45rem;
    height: 2px;
    border-radius: 2px;
    background: var(--accent);
}
/* The bar's one caret, worn by the one label that opens a menu instead of going somewhere, so
   the mark says a single thing wherever it shows up. */
.topnav-more-caret {
    width: 0.38rem;
    height: 0.38rem;
    border-right: 1.6px solid currentColor;
    border-bottom: 1.6px solid currentColor;
    transform: rotate(45deg) translateY(-0.12em);
    opacity: 0.75;
    flex-shrink: 0;
}
.topnav-more[open] .topnav-more-caret { transform: rotate(225deg) translateY(-0.02em); }
.topnav-more-panel {
    position: absolute;
    top: calc(100% + 0.7rem);
    left: 0;
    z-index: 20;
    min-width: 11.5rem;
    padding: 0.35rem;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
}
.topnav-more-panel a {
    padding: 0.45rem 0.7rem;
    border-radius: var(--radius-sm);
    color: var(--text);
    font-weight: 500;
    white-space: nowrap;
}
.topnav-more-panel a:hover { background: var(--surface-2); }
.topnav-more-panel a.active { color: var(--text); background: var(--accent-soft); }
.topnav-more-panel a.active::after { content: none; }
/* The strip between the bar and the floating panel belongs to the panel, so a pointer on its
   way down never counts as having left the menu. It exists only while the panel is drawn. */
.topnav-more-panel::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: -0.7rem;
    height: 0.7rem;
}

/* Features: the same disclosure drawing as More, but the label is a link, so a click always
   navigates to the features overview and the label wears no caret. The panel opens while
   theme.js holds the hover mark (data-open) or while keyboard focus sits anywhere in the group:
   focusing the label brings the shortcuts into the Tab order with no script at all, and touch
   simply follows the link. */
.topnav-features > .topnav-more-panel { display: none; }
.topnav-features[data-open] > .topnav-more-panel,
.topnav-features:focus-within > .topnav-more-panel { display: flex; }
.topnav-features[data-open] > a,
.topnav-features:focus-within > a { color: var(--text); }

/* The menu keeps its distance from the lockup: a wider gutter on both sides of a hairline
   rule, so the brand reads as identity and not as the first entry of the navigation. */
.topbar-inner > .topnav { margin-left: 0.5rem; }
.topbar-inner > .topnav::before {
    content: "";
    align-self: center;
    width: 1px;
    height: 1.1rem;
    margin-right: 0.5rem;
    background: var(--border);
}

/* One height for the whole action row: the bare links and the call to action sit on a
   single band instead of three different sizes. */
.topbar-actions {
    --topbar-control-h: 1.875rem;
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.35rem;
}
.topbar-actions .culture-form, .topbar-actions .inline-form { display: inline-flex; }
.topbar-actions .button,
.topbar-actions .icon-button,
.topbar-actions .link-button,
.topbar-actions .user-chip,
.topbar-actions select,
.topbar-actions > a {
    display: inline-flex;
    align-items: center;
    min-height: var(--topbar-control-h);
    height: var(--topbar-control-h);
}
.topbar-actions .icon-button { width: var(--topbar-control-h); }
.topbar-actions .button { padding: 0 0.9rem; font-size: 0.8125rem; }
.topbar-actions .user-chip { padding: 0 0.8rem; }
/* The identity chip is already round-ended; the language select follows the same pill so the
   whole row reads as one family. */
.topbar-actions select { border-radius: var(--radius-pill); padding: 0 0.75rem; }
/* The bare links (sign in, sign out) carry the menu's weight: same height, no box. */
.topbar-actions .link-button,
.topbar-actions > a:not(.button):not(.user-chip) { padding: 0 0.4rem; color: var(--muted); font-weight: 500; font-size: 0.8125rem; }
.topbar-actions .link-button:hover,
.topbar-actions > a:not(.button):not(.user-chip):hover { color: var(--text); text-decoration: none; }

/* Narrow frames: the menu drops to its own row under the brand and the actions instead of
   splitting mid-list. No hamburger, no script. */
@media (max-width: 900px) {
    :root { --topbar-h: 64px; }
    .topbar-inner { row-gap: 0.55rem; }
    .topbar-inner > .topnav { order: 3; width: 100%; margin-left: 0; }
    .topbar-inner > .topnav::before { display: none; }
}
@media (max-width: 560px) {
    :root { --topbar-h: 100px; }
    .topbar-actions { flex-wrap: wrap; justify-content: flex-end; row-gap: 0.4rem; }
}

/* The same generous top band the public pages open with, so the account column does not start
   tight against the bar while the marketing pages breathe. It steps down with the frame. */
.content { flex: 1; width: 100%; max-width: var(--content-max); margin: 0 auto; padding: 4rem 1.5rem 5rem; }
@media (max-width: 980px) { .content { padding: 2.75rem 1.5rem 3.5rem; } }

/* ============================================================================
   Landing
   ============================================================================ */
/* Marketing canvas: flat, near-neutral graphite (off-white in light, near-black in
   dark), and CONTINUOUS — one uninterrupted surface from the topbar to the footer. The
   rhythm comes from vertical air and from the framed media the sections carry, not from
   alternating tints: two neutrals a hair apart read as a smudge, never as structure. The
   brand blue lives on components and accents — NEVER on the page background — so the long
   scroll doesn't read as "blue on blue". The hero keeps its own accent aurora (scoped to
   .land-hero) as the single blue glow up top. --land-band is the footer's own tone: the
   end of the page may settle a step away from the canvas, a section may not. */
.landing-shell {
    --land-bg: #f4f2ec;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    background: var(--land-bg);
}
[data-theme="dark"] .landing-shell {
    --land-bg: #131110;
}
/* Full-bleed canvas: marketing sections manage their own width (.land-wrap); text pages
   (downloads, releases, legal) opt back into a reading column via .land-page. */
.landing-main { flex: 1; width: 100%; }
/* The reading pages open on the same band of air the landing's first section does: the canvas is
   continuous under all of them, so the space at the top is the only thing that says a page began. */
.land-page { width: 100%; max-width: 1020px; margin: 0 auto; padding: 5.5rem 1.5rem 6.5rem; }
/* Pricing opts into the marketing measure: its row of cards is wider than a reading column, and
   the prose blocks inside it carry their own max-width anyway. */
.land-page-wide { max-width: var(--land-max); }
.landing h1 { font-size: 2.6rem; letter-spacing: -0.03em; }
.landing .tagline { font-size: 1.25rem; color: var(--muted); margin-bottom: 1.5rem; }
.feature-list { padding: 0; margin: 1.5rem 0; list-style: none; }
.feature-list li { position: relative; padding-left: 1.75rem; margin-bottom: 0.6rem; }
/* The check as an escape (\2713 = ✓): the one glyph a mis-decoded stylesheet turned into
   mojibake on the cards, now indifferent to how the sheet's bytes are read. */
.feature-list li::before { content: "\2713"; position: absolute; left: 0; color: var(--accent); font-weight: 700; }
.muted { color: var(--muted); font-size: 0.95rem; }

.landing-cta { display: flex; align-items: center; gap: 0.9rem; margin-top: 2rem; flex-wrap: wrap; }
.landing-cta .signed-in-note { margin: 0; color: var(--muted); }

/* ============================================================================
   Pricing (ADR 0021) — three static cards: free, monthly, and the long commitment
   whose 1-year / 3-year control reloads the page already on that term
   ============================================================================ */
/* Fixed track counts, not auto-fit: auto-fit changes the count with the viewport and lands on
   rows the copy was not written for. The row halves to 2 and then to 1 instead. */
.pricing-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.25rem; margin: 2rem 0 1.5rem; }
.pricing-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 1.5rem 1.35rem;
    display: flex;
    flex-direction: column;
}
.pricing-card h2 { font-size: 1.2rem; margin-bottom: 0.75rem; }
/* The bullet list absorbs the spare height so the call to action sits on the card foot. */
.pricing-card > .feature-list { flex: 1; }
.pricing-card form { margin: 0; }
.pricing-card-featured { border-color: var(--accent); box-shadow: var(--shadow-md); }
.pricing-amount { font-size: 1.75rem; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 0.1rem; }
.pricing-cadence { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.5rem; }
.pricing-save { margin-left: 0.5rem; color: var(--success); font-weight: 600; }

/* Two-option control on the featured card: the selected term is a plate, not a second
   primary button, so the subscribe CTA below stays the one filled action. */
.pricing-term-switch {
    display: inline-flex;
    align-items: stretch;
    gap: 0.2rem;
    margin: -0.15rem 0 0.85rem;
    padding: 0.18rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--surface-2);
    width: fit-content;
}
.pricing-term-switch button {
    appearance: none;
    min-height: 2rem;
    padding: 0 0.85rem;
    border: 1px solid transparent;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--muted);
    font: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
}
.pricing-term-switch button:hover { color: var(--text); }
.pricing-term-switch button[aria-pressed="true"] {
    background: var(--surface);
    border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
    color: var(--accent);
    box-shadow: var(--shadow-sm);
}
.pricing-term-switch button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* A card's call to action sits at its foot; the checkout notice is said once underneath. */
.pricing-cta { margin-top: 1.1rem; }

/* The three-up row is the widest layout the page has; it folds before the text does. */
@media (max-width: 900px) {
    .pricing-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
    .pricing-cards { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================================
   Subscription (Account → Manage, ADR 0021), and the read-only account facts
   the Profile page lists in the same shape
   ============================================================================ */
.subscription-card, .account-facts {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.6rem 1.5rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.25rem 1.5rem;
    margin: 1rem 0 1.5rem;
    max-width: 520px;
}
.subscription-card dt, .account-facts dt { color: var(--muted); font-weight: 500; }
.subscription-card dd, .account-facts dd { margin: 0; }
.status-badge {
    display: inline-block;
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    font-size: 0.8rem;
    font-weight: 600;
    border: 1px solid var(--border);
    background: var(--surface-2);
    vertical-align: middle;
}
.status-badge.status-active, .status-badge.status-trialing { background: var(--accent-soft); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
.status-badge.status-pastdue { background: var(--warning-bg); color: var(--warning); border-color: color-mix(in srgb, var(--warning) 40%, var(--border)); }
.status-badge.status-canceled { background: var(--danger-bg); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, var(--border)); }
/* A campaign still being written against one whose text and recipients are frozen: the pair where
   reading the wrong one costs the most, so the confirmed state carries the emphasis. */
.status-badge.status-draft { background: var(--surface-2); color: var(--muted); }
.status-badge.status-confirmed { background: var(--accent-soft); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }

/* Compact identity chip shown in the topbar when signed in. */
.user-chip {
    display: inline-flex;
    align-items: center;
    max-width: 16rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: 0.3rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--text);
    font-size: 0.875rem;
    font-weight: 550;
}
.user-chip:hover { text-decoration: none; border-color: var(--accent); }

/* ============================================================================
   Auth (login / register / status pages) — centered card
   ============================================================================ */
/* The same flat canvas the rest of the site runs on: no blue wash behind the card and no gradient
   under it. The brand color stays on what it means (the mark, links, the focus ring), and the card
   reads by its own edge and shadow against one continuous surface. */
.auth-shell {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--bg);
}
.auth-topbar {
    display: flex;
    align-items: center;
    min-height: 52px;
    padding: 0 1.75rem;
    border-bottom: 1px solid var(--border);
}
.auth-main { flex: 1; display: flex; align-items: center; justify-content: center; padding: 2.5rem 1.5rem; }

.auth-card {
    width: 100%;
    max-width: 430px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    padding: 2.9rem 2.6rem;
}
/* The card's title is the page's title: display type, on the same scale the public pages use one
   step down, so signing in does not land on a smaller-looking site. */
.auth-card h1 {
    font-family: var(--font-display);
    font-size: 1.9rem;
    font-weight: 640;
    letter-spacing: -0.028em;
    margin-bottom: 0.4rem;
}
.auth-subtitle { color: var(--muted); margin-bottom: 2rem; }
.auth-footer { margin: 1.5rem 0 0; text-align: center; color: var(--muted); font-size: 0.92rem; }
.legal-acceptance { margin: -0.2rem 0 1.3rem; color: var(--muted); font-size: 0.84rem; }
.legal-acceptance > div { display: flex; align-items: flex-start; gap: 0.6rem; margin-top: 0.7rem; }
.legal-acceptance > div:first-child { margin-top: 0; }
.legal-acceptance input { flex: none; margin-top: 0.24rem; accent-color: var(--accent); }
.legal-acceptance label { line-height: 1.45; }
.legal-acceptance p { margin: 0.45rem 0 0 1.65rem; }

/* The optional commercial-email choice, kept off the required block above it: the same row shape,
   its own band of space, so it never reads as a fourth line of what has to be accepted. */
.marketing-optin {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    margin: 0 0 1.4rem;
    color: var(--muted);
    font-size: 0.84rem;
}
.marketing-optin input { flex: none; margin-top: 0.24rem; accent-color: var(--accent); }
.marketing-optin label { line-height: 1.45; font-weight: 500; margin: 0; }
.auth-divider { display: flex; align-items: center; text-align: center; color: var(--muted); font-size: 0.82rem; margin: 1.4rem 0; gap: 0.75rem; }
.auth-divider::before, .auth-divider::after { content: ""; flex: 1; border-top: 1px solid var(--border); }
.form-row-between { display: flex; align-items: center; justify-content: space-between; gap: 1rem; font-size: 0.9rem; }
.external-logins { display: flex; flex-direction: column; gap: 0.6rem; }

/* ============================================================================
   Controls — buttons
   ============================================================================ */
/* One button family, deliberately quiet: a fully rounded pill at every size, a single
   weight, flat fills, and a hover that only moves the color. The Identity scaffold's .btn-*
   names ride the same rules, so the account pages read as the rest of the site. */
.button, .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-height: var(--control-h);
    padding: 0 1.4rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-weight: 550;
    cursor: pointer;
    text-decoration: none;
    transition: background-color .12s ease, border-color .12s ease, color .12s ease, transform .04s ease;
}
/* Apply / Show on the language, docs-version and integrator pickers is only for a visit with no
   script. Hidden by default so the button never paints: html starts with class no-js, and the
   first head script drops that class and stamps js before the stylesheet is requested. Without
   a script both stay as they were and html.no-js:not(.js) restores the button. The :not(.js)
   is what survives enhanced navigation, which copies class="no-js" back onto html without
   re-running the head script: a visitor who already ran script keeps js, so this rule cannot
   win. !important outranks .button and .topbar-actions .button, which both set
   display:inline-flex. */
[data-auto-apply-fallback] {
    display: none !important;
}
html.no-js:not(.js) [data-auto-apply-fallback] {
    display: inline-flex !important;
}
/* The stroke icons draw on a 24-grid with no width of their own; without this they
   collapse inside the flex button and the glyph disappears. */
.button svg, .btn svg { width: 18px; height: 18px; flex-shrink: 0; }
.button:hover, .btn:hover { background: var(--surface-2); text-decoration: none; }
.button:active, .btn:active { transform: translateY(1px); }
.button:disabled, .btn:disabled, .button[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; box-shadow: none; }
.button:disabled:hover, .btn:disabled:hover { transform: none; box-shadow: none; }

/* Primary: neutral and high contrast, never a colored fill — the page's ink carries the
   button and the page's paper carries the label, which is right in both themes on its own.
   The brand blue stays where it means something (links, the active nav mark, the focus
   ring, the check marks, the live dots, the mark itself), so the call to action reads by
   contrast instead of competing with every accent on the page. */
.button-primary, .btn-primary {
    background: var(--ink-solid);
    border-color: var(--ink-solid);
    color: var(--ink-contrast);
}
.button-primary:hover, .btn-primary:hover { background: var(--ink-solid-hover); border-color: var(--ink-solid-hover); }

/* Ghost: the primary's outline twin — same pill and weight, neutral line and label,
   no fill until it is hovered. */
.button-ghost {
    background: transparent;
    border-color: var(--border-strong);
    color: var(--text);
}
.button-ghost:hover { background: var(--surface-2); border-color: var(--ink-solid); }

/* Destructive: its own solid/contrast pair per theme (like the accent's) — --danger alone
   is the ALERT TEXT ink, light on purpose in dark mode, and can't carry a white label. */
.btn-danger { background: var(--danger-solid); border-color: var(--danger-solid); color: var(--danger-contrast); }
.btn-danger:hover {
    background: var(--danger-solid-hover);
    border-color: var(--danger-solid-hover);
    color: var(--danger-contrast);
}

/* Large: more room, same face — the size changes, the anatomy does not. */
.button-lg, .btn-lg { min-height: var(--control-h-lg); padding: 0 1.8rem; font-size: 1rem; }
.button-lg { white-space: nowrap; }

.btn-link { background: none; border: none; color: var(--accent); min-height: auto; padding: 0.4rem; }
.w-100 { width: 100%; }

/* Action rows: anchor-styled buttons sitting side by side (e.g. the 2FA page). */
a.btn { margin: 0 0.35rem 0.5rem 0; }

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

/* The square controls that sit in the same row as the buttons round with them, so the
   topbar reads as one family instead of a pill beside a rounded square. */
.icon-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.3rem;
    height: 2.3rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
}
.icon-button:hover { background: var(--surface-2); }

.link-button { background: none; border: none; color: var(--accent); cursor: pointer; font: inherit; padding: 0; }
.link-button:hover { text-decoration: underline; }
.inline-form { display: inline; margin: 0; }

/* ---- Culture selector ----
   The language applies itself, so the button beside it is only drawn for a visitor without a
   script (html.no-js:not(.js)). The two still read as one control when it is there: they sit
   tight together, and the button is sized to the compact footer row. */
.culture-form { margin: 0; display: inline-flex; align-items: center; gap: 0.3rem; }
.culture-form .button { min-height: 2rem; padding: 0 0.7rem; font-size: 0.8rem; }

/* ============================================================================
   Controls — forms (also the Identity scaffold's Bootstrap class names)
   ============================================================================ */
/* Base <select> (the bare element, not a class): every select on the site follows the
   theme, not just the Identity scaffold's Bootstrap-classed ones below. Same shape as
   the topbar's culture switch, which this rule now covers instead of duplicating. */
select {
    min-height: 2.3rem;
    padding: 0 0.55rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    cursor: pointer;
}

.form-control,
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="hidden"]),
select.form-select,
textarea {
    width: 100%;
    min-height: var(--control-h);
    padding: 0.55rem 0.8rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    transition: border-color .12s ease, box-shadow .12s ease;
}
.form-control::placeholder, input::placeholder { color: color-mix(in srgb, var(--muted) 75%, transparent); }
.form-control:focus, input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

.form-label, label { display: inline-block; margin-bottom: 0.35rem; color: var(--text); font-size: 0.9rem; font-weight: 550; }

/* The scaffold renders <input> then <label> (then the validation message); pulling the label to
   order -1 puts it on top while keeping the validation message below the input. */
.form-floating { display: flex; flex-direction: column; margin-bottom: 1rem; }
.form-floating > label { order: -1; margin: 0 0 0.35rem; color: var(--muted); font-weight: 500; }
.form-floating .text-danger { margin-top: 0.35rem; font-size: 0.85rem; }

/* ---- Password reveal (input + eye toggle) ---- */
.password-field { position: relative; }
.password-field input { padding-right: 2.9rem; }
.password-reveal {
    position: absolute;
    top: 50%;
    right: 0.45rem;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.1rem;
    height: 2.1rem;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    line-height: 0;
}
.password-reveal:hover { background: var(--surface-2); color: var(--text); }
.password-reveal .eye-hide { display: none; }
.password-reveal[aria-pressed="true"] .eye-show { display: none; }
.password-reveal[aria-pressed="true"] .eye-hide { display: inline; }

.checkbox { display: inline-flex; align-items: center; gap: 0.5rem; margin: 0; font-weight: 500; cursor: pointer; }
.checkbox input[type="checkbox"], input[type="checkbox"].form-check-input { width: 1.05rem; height: 1.05rem; accent-color: var(--accent); }

/* ---- Grid (scaffold) ----
   Every column name the scaffold pages wear answers to a rule here. A name with no rule fails in
   silence: the block keeps whatever width its own content happens to want, which is never the
   width the page asked for, and nothing on screen says so. */
.row { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.col-md-4, .col-lg-4, .col-lg-3 { flex: 1 1 240px; }
.col-lg-6, .col-xl-6, .col-lg-9, .col-md-8, .col-xl-8, .col-xl-9 { flex: 1 1 380px; }
.col-md-12 { flex: 1 1 100%; }
.mx-auto { margin-inline: auto; }
.flex-column { flex-direction: column; }
.row > [class*="col-"] { min-width: 0; }

/* ---- Checkbox row (scaffold) ----
   A choice and its label on one line, the same shape as the site's own .checkbox, so a consent
   row reads alike whether the page was scaffolded or written here. */
.form-check { display: flex; align-items: center; gap: 0.5rem; }
.form-check-label { margin: 0; font-weight: 500; }

/* Spacing */
.mb-0 { margin-bottom: 0; }
.mb-3 { margin-bottom: 1rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-3 { margin-top: 1rem; }
.mt-4 { margin-top: 1.5rem; }
.my-3 { margin-block: 1rem; }
.my-4 { margin-block: 1.5rem; }

/* A page that is only reading (the unsubscribe confirmation): one measure inside the app column,
   the same one the page heads and the release notes settle on. */
.prose { max-width: 46rem; }

/* ---- Alerts / status ---- */
.alert {
    padding: 0.8rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    margin-bottom: 1rem;
    background: var(--surface-2);
}
.alert p { margin-bottom: 0.35rem; }
.alert p:last-child { margin-bottom: 0; }
/* Each alert carries its own semantic text color, so success/info/warning/danger read as a
   consistent set (not warm-white text on four different tinted blocks). */
.alert-success { background: var(--success-bg); border-color: color-mix(in srgb, var(--success) 40%, var(--border)); color: var(--success); }
.alert-warning { background: var(--warning-bg); border-color: color-mix(in srgb, var(--warning) 40%, var(--border)); color: var(--warning); }
.alert-danger { background: var(--danger-bg); border-color: color-mix(in srgb, var(--danger) 40%, var(--border)); color: var(--danger); }
.alert-info { background: var(--info-bg); border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); color: var(--accent); }
.alert-info a { color: var(--accent); font-weight: 600; }

/* Text colors */
.text-danger { color: var(--danger); }
.text-success { color: var(--success); }
.text-secondary { color: var(--muted); }
.text-info { color: var(--accent); }

/* ============================================================================
   Manage account (sidebar + content)
   ============================================================================ */
/* The account's own page head, on the same display type the public pages use: the title, the line
   under it, and then a clear band before the rail and the pane start. */
.manage-head { margin-bottom: 2.5rem; }
.manage-head h1 {
    font-family: var(--font-display);
    font-size: clamp(1.9rem, 3.2vw, 2.5rem);
    font-weight: 640;
    letter-spacing: -0.028em;
    margin-bottom: 0.4rem;
}
.manage-subtitle { margin: 0; font-size: 1.05rem; }

.manage-grid { display: grid; grid-template-columns: 230px 1fr; gap: 2.5rem; align-items: start; }
@media (max-width: 760px) { .manage-grid { grid-template-columns: 1fr; } }

/* Page titles inside the manage content pane (the scaffold uses h3). */
.manage-content h3 { font-family: var(--font-display); font-size: 1.45rem; letter-spacing: -0.02em; margin-bottom: 1.1rem; }
.manage-content h4 { margin-top: 2rem; }

/* The scaffold's column names, read as widths: the pane is wider than any one form wants, so a
   half column stays a reading width and the ones that say they need more get proportionally more.
   A page that asks for the whole pane (the recovery-code sheet) takes it. */
.manage-content [class*="col-"] { max-width: 520px; }
.manage-content .col-md-8, .manage-content .col-xl-8 { max-width: 640px; }
.manage-content .col-xl-9 { max-width: 760px; }
.manage-content .col-md-12 { max-width: none; }

.nav { display: flex; gap: 0.25rem; list-style: none; padding: 0; margin: 0 0 1.5rem; }
.nav.flex-column { flex-direction: column; gap: 0.15rem; }
.nav-pills.flex-column {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 0.5rem;
    box-shadow: var(--shadow-sm);
}
.nav-item { list-style: none; }
.nav-link {
    display: block;
    padding: 0.55rem 0.8rem;
    border-radius: var(--radius-sm);
    color: var(--muted);
    font-weight: 500;
}
.nav-link:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
/* Where you are: the soft accent plate the docs rail already uses, not a solid block of brand
   color. The blue marks the entry; it does not become the loudest surface on the page. */
.nav-pills .nav-link.active { background: var(--accent-soft); color: var(--accent); }
[data-theme="dark"] .nav-pills .nav-link.active { background: var(--surface-2); }

/* Table */
.table { width: 100%; border-collapse: collapse; }
.table th, .table td { padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--border); text-align: left; }

/* Misc */
.recovery-code { font-family: ui-monospace, monospace; padding: 0.2rem 0.4rem; background: var(--surface-2); border-radius: var(--radius-sm); }
kbd { font-family: ui-monospace, monospace; padding: 0.15em 0.45em; background: var(--surface-2); border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: var(--radius-sm); font-size: 0.9em; }
ol.list { padding-left: 1.25rem; }
ol.list > li { margin-bottom: 0.75rem; }

/* Confirmed-email ✓ suffix (Manage/Email): anchored inside the disabled input. */
.input-group { position: relative; }
.input-group .input-group-text { position: absolute; right: 0.9rem; bottom: 0.7rem; color: var(--success); font-weight: 700; }
.visually-hidden {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ============================================================================
   Docs (operator documentation, ADR 0011) — sidebar + content + on-this-page rail
   ============================================================================ */
/* The docs run on a column of their own, wider than the marketing one because 260px of it is spent
   on the navigation rail before the article starts. One token carries that column through the whole
   frame: the bar above, the body here and the footer below all ride it, so the three cannot drift
   apart. */
.docs-layout {
    --land-max: 1280px;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
.docs-body { flex: 1; width: 100%; max-width: var(--land-max); margin: 0 auto; display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 3.5rem; padding: 0 1.5rem; }

/* The rail sticks under the bar, never behind it: the bar is painted over everything, so an
   entry that scrolled into that band would be unreadable and unclickable. */
.docs-sidebar { position: sticky; top: var(--topbar-h); align-self: start; max-height: calc(100vh - var(--topbar-h)); overflow-y: auto; padding: 3rem 0 2rem; }
.docs-nav-section { margin-bottom: 1.5rem; }
.docs-nav-section h3 { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 0 0 0.5rem; font-weight: 600; }
.docs-sidebar ul { list-style: none; margin: 0; padding: 0; }
.docs-sidebar a { display: block; padding: 0.4rem 0.7rem; border-radius: var(--radius-sm); color: var(--muted); font-size: 0.95rem; font-weight: 500; }
.docs-sidebar a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.docs-sidebar a.active { background: var(--accent-soft); color: var(--accent); }

/* A group in the rail. The row is the overview page's own link, so the header navigates; the
   twist at its right edge is what the <details> toggles, which is the whole of the interaction
   and needs no script. Labels stay flush with the ungrouped entries above and below. */
.docs-nav-group > summary { display: flex; align-items: center; list-style: none; cursor: pointer; }
.docs-nav-group > summary::-webkit-details-marker { display: none; }
.docs-nav-group > summary > a { flex: 1 1 auto; min-width: 0; }
.docs-nav-twist {
    flex: none;
    width: 0.4rem; height: 0.4rem;
    margin: 0 0.55rem 0 0.4rem;
    border-right: 1.5px solid var(--muted);
    border-bottom: 1.5px solid var(--muted);
    transform: rotate(-45deg);
    transition: transform 0.15s ease, border-color 0.15s ease;
}
.docs-nav-group[open] > summary .docs-nav-twist { transform: rotate(45deg); }
.docs-nav-group > summary:hover .docs-nav-twist { border-color: var(--text); }
/* The children hang off a hairline that draws the group as one block down the rail. */
.docs-nav-children {
    margin: 0.1rem 0 0.5rem 0.85rem;
    padding-left: 0.35rem;
    border-left: 1px solid var(--border);
}
.docs-nav-children a { font-size: 0.9rem; padding-top: 0.3rem; padding-bottom: 0.3rem; }
.docs-version-picker { display: grid; gap: 0.35rem; margin: 0 0 1.4rem; padding: 0 0.7rem; }
.docs-version-picker form { display: grid; gap: 0.35rem; margin: 0; }
.docs-version-picker label { color: var(--muted); font-size: 0.78rem; font-weight: 600; margin-bottom: 0; }
.docs-version-picker select { width: 100%; }
/* The picker applies itself, so this button is what stands in for a visitor without a script.
   Compact, and only as wide as its word, so the rail still reads as navigation on the rare visit
   that sees it. */
.docs-version-picker .button { justify-self: start; min-height: 2.2rem; padding: 0 0.85rem; font-size: 0.875rem; }

/* Sidebar search: the field replaces the section list while a query is active, so the rail shows
   one thing at a time. Results are links, styled apart from the plain navigation entries. */
.docs-search { padding: 0 0.7rem; margin: 0 0 1.2rem; }
.docs-search input { width: 100%; }
.docs-search-results { margin: 0.7rem 0 0; padding: 0; display: grid; gap: 0.2rem; }
.docs-search-results a { display: grid; gap: 0.15rem; padding: 0.5rem 0.7rem; }
.docs-search-title { color: var(--text); font-size: 0.95rem; font-weight: 600; }
.docs-search-snippet { color: var(--muted); font-size: 0.8rem; line-height: 1.35; }
/* Every line of the box that is not a hit: the three that stand in for the list (still fetching,
   the index never arrived, nothing matched) and the count of what the ceiling left out, at the foot
   of a list that was cut. All read as a line of the rail; only the failure takes the semantic ink,
   because only it asks the reader to do something. */
.docs-search-notice { color: var(--muted); font-size: 0.88rem; padding: 0.4rem 0.7rem; }
.docs-search-failed { color: var(--danger); }

.docs-main { padding: 3.5rem 0 3rem; min-width: 0; }
.docs-article-layout { display: grid; grid-template-columns: minmax(0, 1fr) 200px; gap: 2.5rem; align-items: start; }
.docs-version-banner, .docs-version-note {
    margin: 0 0 1.5rem;
    padding: 0.75rem 1rem;
    border: 1px solid color-mix(in srgb, var(--warning) 45%, var(--border));
    border-radius: var(--radius-sm);
    background: var(--warning-bg);
}
.docs-version-banner { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem; }
.docs-version-banner p, .docs-version-note { font-size: 0.9rem; }
.docs-version-banner p { margin: 0; }
.docs-version-banner a { font-weight: 600; }

.docs-content { min-width: 0; }
/* The article's title belongs to the frame, not to the prose: display type on the site's scale,
   with the reading text below it untouched. */
.docs-content h1 {
    font-family: var(--font-display);
    font-size: clamp(2.1rem, 3.4vw, 2.8rem);
    font-weight: 640;
    letter-spacing: -0.03em;
    line-height: 1.07;
    margin-bottom: 1rem;
}
/* The version tag under the h1: just the number in a small, tight line so it still reads
   beside the title rather than as its own paragraph. */
.docs-version-tag { margin: -0.5rem 0 1rem; font-weight: 600; }
.docs-lead { font-size: 1.2rem; color: var(--muted); margin-bottom: 2rem; text-wrap: pretty; }
.docs-content h2 { margin-top: 2.25rem; }
.docs-content h3 { margin-top: 1.5rem; }
/* Every heading is a link target: the "On this page" rail, the links written in the prose and the
   address someone pasted all scroll to one. The bar above is painted over the page, so a heading
   that stopped at the very top would sit behind it and the reader would start mid-paragraph. */
.docs-content :is(h1, h2, h3, h4) { scroll-margin-top: calc(var(--topbar-h) + 0.75rem); }
.docs-content ul, .docs-content ol { padding-left: 1.3rem; }
.docs-content li { margin-bottom: 0.4rem; }
.docs-content img { max-width: 100%; height: auto; border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--shadow-sm); }
.docs-content pre { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 1rem 1.1rem; overflow-x: auto; }
.docs-content pre code { background: none; padding: 0; font-size: 0.875rem; }
.docs-content blockquote { margin: 1rem 0; padding: 0.6rem 1rem; border-left: 3px solid var(--accent); background: var(--surface-2); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; color: var(--muted); }
.docs-content blockquote p:last-child { margin-bottom: 0; }
/* Almost every page carries a table, and some of them are wider than a phone. The table is its own
   scrolling block, so the sideways travel stays inside it and the page never slides under the
   reader's thumb; cells break inside an unbreakable literal (a URI, an address) rather than forcing
   the whole table wider. */
.docs-content table {
    display: block;
    width: max-content;
    max-width: 100%;
    overflow-x: auto;
    border-collapse: collapse;
    margin: 1rem 0;
}
.docs-content th, .docs-content td { padding: 0.55rem 0.75rem; border: 1px solid var(--border); text-align: left; overflow-wrap: anywhere; }
.docs-content th { background: var(--surface-2); }

.docs-actions { display: flex; gap: 0.6rem; margin: 0 0 2.5rem; flex-wrap: wrap; }
.docs-actions .button { min-height: 2.2rem; padding: 0 0.85rem; font-size: 0.875rem; }

/* Clear of the sticky bar, the same offset the anchors reserve for it. */
.docs-toc { position: sticky; top: calc(var(--topbar-h) + 0.75rem); align-self: start; font-size: 0.9rem; }
.docs-toc-title { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 0 0 0.6rem; font-weight: 600; }
.docs-toc ul { list-style: none; margin: 0; padding: 0; border-left: 1px solid var(--border); }
.docs-toc a { display: block; padding: 0.25rem 0 0.25rem 0.8rem; color: var(--muted); border-left: 2px solid transparent; margin-left: -1px; }
.docs-toc a:hover { color: var(--text); text-decoration: none; border-left-color: var(--accent); }

.docs-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem; margin: 1.25rem 0 3rem; }
.docs-card { display: flex; flex-direction: column; gap: 0.3rem; padding: 1.25rem 1.35rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--shadow-sm); }
.docs-card:hover { border-color: var(--accent); text-decoration: none; }
.docs-card-title { font-weight: 650; color: var(--text); }
.docs-card-desc { font-size: 0.9rem; color: var(--muted); }
/* The index heads a group's own grid with the group's link, one step quieter than the section
   above it, so the cards below read as the pages inside that group rather than beside it. */
.docs-index-group { font-size: 1rem; margin: 0 0 0.2rem; }
.docs-index-group a { color: var(--text); }
.docs-index-group + .docs-card-grid { margin-top: 0.75rem; }

@media (max-width: 1080px) {
    .docs-article-layout { grid-template-columns: minmax(0, 1fr); }
    .docs-toc { display: none; }
}
@media (max-width: 760px) {
    .docs-body { grid-template-columns: minmax(0, 1fr); gap: 0; }
    .docs-sidebar { position: static; max-height: none; padding: 1.5rem 0 0; }
    .docs-main { padding: 2.25rem 0 3.5rem; }
}

/* ============================================================================
   Blazor framework styles (validation + error boundary)
   ============================================================================ */
h1:focus { outline: none; }
.valid.modified:not([type=checkbox]) { outline: 1px solid var(--success); }
.invalid { border-color: var(--danger) !important; }
.validation-message { color: var(--danger); font-size: 0.85rem; }

.blazor-error-boundary { background: #b32121; padding: 1rem; color: white; border-radius: var(--radius-sm); }
.blazor-error-boundary::after { content: "An error has occurred."; }

/* ============================================================================
   Marketing (landing page + site frame) — the sales surface (ADR 0011).
   Same token system as everything above; freer visual language than the app:
   display type (Inter — the brand face per docs/brand/brand.md), scroll-reveal
   motion, illustrated feature sections. All motion respects reduced-motion and
   degrades to static content without JS.
   ============================================================================ */

/* Inter variable — the brand face (brand.md): the wordmark on every frame plus the
   marketing headlines. Body copy and UI stay on the system stack. */
@font-face {
    font-family: "InterVariable";
    src: url("fonts/InterVariable.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
}


:root {
    --font-display: "InterVariable", "Inter", var(--font-sans);
    --land-max: 1240px;
    --grid-line: color-mix(in srgb, var(--accent) 9%, transparent);
}
/* Dark grid is neutral graphite, not accent-tinted — see the desaturation note below. */
[data-theme="dark"] { --grid-line: color-mix(in srgb, var(--border-strong) 55%, transparent); }

.land-wrap { width: 100%; max-width: var(--land-max); margin: 0 auto; padding: 0 1.5rem; }

/* Anchor navigation: smooth, and never hidden under the sticky topbar. Motion is
   progressively disabled by the reduced-motion block at the end of this surface. */
html { scroll-behavior: smooth; }
.land-section, .land-final, .land-hero { scroll-margin-top: calc(var(--topbar-h) + 0.75rem); }

/* ---- Scroll reveal (landing.js adds html.js-anim, then .is-in per element) ---- */
html.js-anim .reveal { opacity: 0; transform: translateY(26px); }
html.js-anim .reveal.is-in {
    opacity: 1;
    transform: none;
    transition: opacity .8s cubic-bezier(.16, 1, .3, 1), transform .8s cubic-bezier(.16, 1, .3, 1);
    transition-delay: var(--reveal-delay, 0s);
}

/* ============================================================================
   Hero — centered: a short pitch column, then the product running big underneath
   ============================================================================ */
.land-hero { position: relative; overflow: hidden; padding: 6.5rem 0 6rem; }
.land-hero .land-wrap { position: relative; z-index: 1; }

/* Drifting aurora glow behind the hero — slow, subtle life, sitting on the same
   center line as the copy. Weaker in dark (large accent washes saturate the dark
   theme). */
.land-hero::after {
    content: "";
    position: absolute;
    top: -280px;
    left: 50%;
    margin-left: -330px;
    width: 660px;
    height: 660px;
    border-radius: 50%;
    background: radial-gradient(circle, color-mix(in srgb, var(--accent) 20%, transparent), transparent 65%);
    filter: blur(80px);
    animation: land-aurora 16s ease-in-out infinite alternate;
    pointer-events: none;
}
[data-theme="dark"] .land-hero::after {
    background: radial-gradient(circle, color-mix(in srgb, var(--accent) 10%, transparent), transparent 65%);
}
@keyframes land-aurora { to { transform: translate(-160px, 110px) scale(1.18); } }

/* Engineering grid, faded out radially — technical texture behind the pitch. */
.land-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(var(--grid-line) 1px, transparent 1px),
        linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
    background-size: 46px 46px;
    mask-image: radial-gradient(78% 62% at 50% 10%, black, transparent 78%);
    -webkit-mask-image: radial-gradient(78% 62% at 50% 10%, black, transparent 78%);
    pointer-events: none;
}

/* The pitch reads as one narrow, centered column: kicker, one-line headline, one
   support line, the CTA row and its reassurance. Everything else in the fold is the
   product itself. */
.land-hero-copy { max-width: 54rem; margin: 0 auto; text-align: center; }
.land-hero .land-cta-row { justify-content: center; margin-bottom: 1.1rem; }
.land-hero .land-trust { justify-content: center; gap: 0.35rem 1.25rem; font-size: 0.85rem; }

/* The real, pre-recorded dashboard canvas: centered and as wide as the fold allows,
   matted in a thin frame so the app's own chrome keeps its edge. The poster preserves
   the same layout when autoplay is blocked or reduced motion is requested. */
.land-hero-media { position: relative; max-width: 1060px; margin: 4rem auto 0; }
.hero-shot {
    position: relative;
    padding: 0.5rem;
    border: 1px solid var(--border);
    border-radius: calc(var(--radius) + 0.5rem);
    background: color-mix(in srgb, var(--surface) 70%, transparent);
    box-shadow: var(--shadow-lg);
}
.hero-shot video {
    display: block;
    width: 100%;
    aspect-ratio: 8 / 5;
    object-fit: cover;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.hero-video-toggle {
    position: absolute;
    right: 1.1rem;
    bottom: 1.1rem;
    z-index: 2;
    padding: 0.42rem 0.85rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--surface) 90%, transparent);
    color: var(--text);
    box-shadow: var(--shadow-sm);
    font: inherit;
    font-size: 0.78rem;
    font-weight: 650;
    cursor: pointer;
}
.hero-video-toggle:hover { border-color: var(--accent); }
.hero-video-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The section label, and nothing else: small, uppercase, tracked out. It carries no rule
   or dash in front of it — a mark repeated over every section stops being a mark. */
.land-kicker {
    display: inline-flex;
    align-items: center;
    font-size: 0.8rem;
    font-weight: 650;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 1.1rem;
}

.land-h1 {
    font-family: var(--font-display);
    font-size: clamp(2.6rem, 6.2vw, 4.4rem);
    font-weight: 620;
    letter-spacing: -0.038em;
    line-height: 1.02;
    margin: 0 0 1.4rem;
    text-wrap: balance;
}

/* The headline lands as one block (armed by js-anim, fired by the hero reveal). */
html.js-anim .land-h1 .hl { display: inline-block; opacity: 0; transform: translateY(16px); }
html.js-anim .reveal.is-in .land-h1 .hl { animation: land-rise .7s cubic-bezier(.16, 1, .3, 1) forwards; }
@keyframes land-rise { to { opacity: 1; transform: none; } }

.land-sub {
    font-size: clamp(1.1rem, 1.8vw, 1.32rem);
    color: var(--muted);
    max-width: 40rem;
    margin: 0 auto 2.4rem;
    text-wrap: pretty;
}

/* The marketing CTAs are the same buttons as the rest of the site (see Controls above):
   no lift, no glow, one primary and its outline twin. */
.land-cta-row { display: flex; flex-wrap: wrap; gap: 0.9rem; align-items: center; margin-bottom: 1.4rem; }

.land-trust { display: flex; flex-wrap: wrap; gap: 0.4rem 1.4rem; padding: 0; margin: 0 0 0.5rem; list-style: none; color: var(--muted); font-size: 0.9rem; }
.land-trust li { display: inline-flex; align-items: center; gap: 0.45rem; }
.land-trust svg { width: 15px; height: 15px; color: var(--accent); flex-shrink: 0; }

/* ---- Pillar cards: bullet trios presented as a row (band sections) ---- */
.pillar-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; }
.pillar-card {
    padding: 1.4rem 1.35rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
}
.pillar-card .pt-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    height: 2.4rem;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
    margin-bottom: 0.85rem;
}
.pillar-card .pt-icon svg { width: 18px; height: 18px; }
[data-theme="dark"] .pillar-card .pt-icon { background: var(--surface-2); }
.pillar-card strong { display: block; font-weight: 650; margin-bottom: 0.3rem; }
.pillar-card span { color: var(--muted); font-size: 0.94rem; }

/* ---- Full-width statement screenshot (dashboards section) ---- */
.big-shot { margin-top: 2.4rem; }
.big-shot img {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
}
.big-shot .shot-dark { display: none; }
[data-theme="dark"] .big-shot .shot-light { display: none; }
[data-theme="dark"] .big-shot .shot-dark { display: block; }

/* ============================================================================
   Dark theme, second desaturation pass: the brand light-blue stays on interactive
   elements (CTAs, links, the active tab) but leaves the purely decorative layers —
   kickers, section numbers, illustration strokes — so blue stops stacking on blue.
   ============================================================================ */
[data-theme="dark"] .land-kicker { color: var(--muted); }
[data-theme="dark"] .feat-num { color: var(--muted); }
[data-theme="dark"] .how-step::before { color: var(--muted); }
/* Illustrations keep the signal blue but desaturate it toward the warm neutral, so a
   large SVG doesn't shout while the small live marks (hero value, buttons) stay bright. */
[data-theme="dark"] .art-frame,
[data-theme="dark"] .mock-agent {
    --accent: color-mix(in srgb, #38bdf8 58%, #a39d90);
    --accent-soft: var(--surface-2);
}

/* Live/OK dot — semantic green (recording/running/connected), per the brand's
   reserved colors. */
.dot-live { width: 8px; height: 8px; border-radius: 50%; background: var(--success); box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 45%, transparent); animation: land-pulse 2.4s ease-out infinite; flex-shrink: 0; }
@keyframes land-pulse { 70% { box-shadow: 0 0 0 9px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }

/* Nothing running there yet: the live dot's quiet twin, an outline where the pulse would be, so a
   row of benches reads as one set of three states (done, running, waiting) instead of two marks and
   a stray character. It draws in the ink of the line it sits on, which is what carries the state. */
.dot-idle { display: inline-block; width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid currentColor; flex-shrink: 0; }

/* ---- Protocol chip grid (Connector section): the driver line-up, static &
   readable — a plain wrapped list of monospace pills, no motion. */
.chip-grid-label { color: var(--muted); font-size: 0.85rem; margin: 0 0 0.9rem; }
.chip-grid { display: flex; flex-wrap: wrap; gap: 0.55rem; margin: 0 0 2.2rem; padding: 0; list-style: none; }
.chip-grid li {
    padding: 0.4rem 0.85rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    font-family: ui-monospace, "Cascadia Mono", monospace;
    font-size: 0.85rem;
    color: var(--text);
}
.chip-grid li.chip-more { border-style: dashed; color: var(--muted); font-family: var(--font-sans); }

/* ============================================================================
   Section scaffolding
   ============================================================================ */
/* On a continuous canvas the space between sections IS the separation, so it is generous
   on purpose: what used to be a tint change is now a full band of air. */
.land-section { padding: 7rem 0; }
/* A signpost between two groups of features carries a head and nothing else. On the continuous
   canvas a full section's air around three lines would read as a gap, so it takes half the measure
   and sits closer to what it introduces than to what it closes. */
.land-section--divider { padding: 4rem 0 2.5rem; }

.land-section-head { max-width: 50rem; margin: 0 auto 3.6rem; text-align: center; }
/* Left-aligned variant: breaks the centered rhythm on alternating sections. */
.land-section-head--left { margin: 0 0 3.2rem; text-align: left; max-width: 52rem; }
.land-h2 {
    font-family: var(--font-display);
    font-size: clamp(2rem, 4vw, 3rem);
    font-weight: 640;
    letter-spacing: -0.03em;
    line-height: 1.08;
    margin-bottom: 1rem;
}
/* The lead is held to a reading measure wherever it lands, centered under a centered head
   and flush left under a left one. */
.land-lead { color: var(--muted); font-size: 1.12rem; max-width: 44rem; }
.land-section-head .land-lead { margin-inline: auto; }
.land-section-head--left .land-lead { margin-inline: 0; }

/* ---- Feature section: copy + media, alternating ---- */
.feat { display: grid; grid-template-columns: minmax(0, 10fr) minmax(0, 11fr); gap: 3.2rem 5rem; align-items: center; }
.feat + .feat { margin-top: 6.5rem; }
.feat--reverse .feat-media { order: -1; }

.feat-num {
    font-family: var(--font-display);
    font-size: 0.85rem;
    font-weight: 650;
    letter-spacing: 0.1em;
    color: var(--accent);
}
.feat-title {
    font-family: var(--font-display);
    font-size: clamp(1.75rem, 3.2vw, 2.4rem);
    font-weight: 630;
    letter-spacing: -0.028em;
    line-height: 1.1;
    margin: 0.5rem 0 0.9rem;
}
.feat-lead { color: var(--muted); font-size: 1.08rem; margin-bottom: 1.6rem; }

.feat-points { list-style: none; padding: 0; margin: 0 0 1.4rem; display: grid; gap: 0.85rem; }
.feat-points li { display: grid; grid-template-columns: auto 1fr; gap: 0.8rem; align-items: start; }
.feat-points .pt-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    margin-top: 0.1rem;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
    flex-shrink: 0;
}
.feat-points .pt-icon svg { width: 16px; height: 16px; }
.feat-points strong { display: block; font-weight: 650; }
.feat-points span { color: var(--muted); font-size: 0.95rem; }

.feat-links { display: flex; flex-wrap: wrap; align-items: center; gap: 1.1rem; }
.docs-link { display: inline-flex; align-items: center; gap: 0.4rem; font-weight: 600; white-space: nowrap; }
.docs-link svg { width: 15px; height: 15px; flex-shrink: 0; transition: transform .15s ease; }
.docs-link:hover svg { transform: translateX(3px); }

/* Stylized SVG illustrations (sections rendered as concept art). */
.art-frame {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background:
        radial-gradient(90% 90% at 20% 0%, var(--accent-soft), transparent 60%),
        var(--surface);
    box-shadow: var(--shadow-md);
    padding: 1.6rem;
}
.art-frame svg { display: block; width: 100%; height: auto; }
.art-frame--tight { padding: 1.1rem; }
[data-theme="dark"] .art-frame { background: var(--surface); }

/* ============================================================================
   Concept cards (Art.razor HTML cases) — small illustrative diagrams for ideas a
   screenshot can't compress: a code sample, the model→bench flow, an agent
   exchange. Deliberately NOT dressed as app windows: they read as illustrations,
   never as the product. Text inside is numeric/universal or localized.
   ============================================================================ */
.mock-num { font-variant-numeric: tabular-nums; }
.mock-chip {
    display: inline-flex;
    align-items: center;
    padding: 0.08rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    font-family: ui-monospace, monospace;
    font-size: 0.68rem;
    white-space: nowrap;
}
.mock-card {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
}
.mock-col { display: grid; gap: 0.45rem; align-content: start; }

/* ---- Logic: code editor + alarm ---- */
.mock-code { margin: 0 0 0.55rem; padding: 0.7rem 0.85rem; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); font-family: ui-monospace, "Cascadia Mono", monospace; font-size: 0.74rem; line-height: 1.75; overflow-x: auto; }
.mock-code .ln { display: inline-block; width: 1.4rem; color: var(--muted); opacity: 0.55; user-select: none; }
.mock-code .c-kw { color: var(--accent); font-weight: 600; }
.mock-code .c-str { color: var(--success); }
.mock-code .c-cm { color: var(--muted); opacity: 0.8; }
.mock-code .c-sq { text-decoration: underline wavy var(--danger); text-decoration-thickness: 1px; text-underline-offset: 3px; }
.mock-hint { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.7rem; color: var(--danger); font-family: ui-monospace, monospace; margin-bottom: 0.55rem; }
.mock-alarm { display: flex; align-items: center; gap: 0.55rem; padding: 0.5rem 0.7rem; border: 1px solid color-mix(in srgb, var(--warning) 45%, var(--border)); background: var(--warning-bg); border-radius: var(--radius-sm); font-family: ui-monospace, monospace; font-size: 0.74rem; }
.mock-alarm svg { width: 15px; height: 15px; color: var(--warning); flex-shrink: 0; }
.mock-alarm .t { margin-left: auto; color: var(--muted); font-size: 0.66rem; }

/* ---- Process: model tree + recipe steps ---- */
.mock-process { display: grid; grid-template-columns: 1fr 1.3fr; gap: 0.7rem; }
.process-model { align-self: start; justify-self: start; padding: 0.4rem 0.75rem; border: 1px solid var(--accent); color: var(--accent); background: var(--accent-soft); border-radius: var(--radius-sm); font-weight: 650; font-family: ui-monospace, monospace; font-size: 0.78rem; }
.process-bench { position: relative; margin-left: 1.1rem; display: flex; align-items: center; gap: 0.5rem; padding: 0.42rem 0.6rem; font-family: ui-monospace, monospace; font-size: 0.74rem; }
.process-bench::before { content: ""; position: absolute; left: -0.7rem; top: -1.4rem; bottom: 50%; width: 0.6rem; border-left: 1.5px solid var(--border-strong); border-bottom: 1.5px solid var(--border-strong); border-bottom-left-radius: 6px; }
.process-bench .st { margin-left: auto; font-size: 0.68rem; color: var(--muted); }
.process-bench .st.ok { color: var(--success); }
.process-step { display: flex; align-items: center; gap: 0.5rem; padding: 0.42rem 0.6rem; font-family: ui-monospace, monospace; font-size: 0.72rem; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); }
.process-step .n { color: var(--muted); }
.process-step .ok { margin-left: auto; color: var(--success); }
.process-step .run { margin-left: auto; }
.process-foot { display: flex; align-items: center; gap: 0.55rem; margin-top: 0.15rem; }
.pass-badge { width: 1.9rem; height: 1.9rem; border-radius: 50%; background: var(--success); color: #fff; display: inline-flex; align-items: center; justify-content: center; }
.pass-badge svg { width: 14px; height: 14px; }

/* ---- Agent: chat exchange + journal line ---- */
.mock-agent { display: grid; gap: 0.55rem; }
.mock-msg { max-width: 88%; padding: 0.55rem 0.75rem; border-radius: var(--radius-md); font-size: 0.78rem; }
.mock-msg.user { margin-left: auto; background: var(--accent); color: var(--accent-contrast); border-bottom-right-radius: 4px; }
.mock-msg.bot { background: var(--surface-2); border: 1px solid var(--border); border-bottom-left-radius: 4px; }
.mock-msg.bot ul { list-style: none; margin: 0 0 0.35rem; padding: 0; display: grid; gap: 0.3rem; }
.mock-msg.bot li { display: flex; align-items: center; gap: 0.45rem; font-family: ui-monospace, monospace; font-size: 0.72rem; }
.mock-msg.bot li svg { width: 13px; height: 13px; color: var(--success); flex-shrink: 0; }
.mock-msg.bot .done { color: var(--muted); font-size: 0.74rem; }
html.js-anim .reveal .mock-msg.bot li { opacity: 0; transform: translateY(6px); }
html.js-anim .reveal.is-in .mock-msg.bot li { animation: land-rise 0.5s forwards; }
html.js-anim .reveal.is-in .mock-msg.bot li:nth-child(1) { animation-delay: 0.7s; }
html.js-anim .reveal.is-in .mock-msg.bot li:nth-child(2) { animation-delay: 0.95s; }
html.js-anim .reveal.is-in .mock-msg.bot li:nth-child(3) { animation-delay: 1.2s; }
.agent-journal { display: flex; align-items: center; gap: 0.45rem; color: var(--muted); font-size: 0.7rem; }
.agent-journal svg { width: 13px; height: 13px; color: var(--accent); flex-shrink: 0; }
.agent-journal .t { margin-left: auto; font-family: ui-monospace, monospace; }

/* Concept-card collapse on narrow screens */
@media (max-width: 520px) {
    .mock-process { grid-template-columns: 1fr; }
}

/* Dark theme: neutral icon plates on the bullet/audience icons (the accent glyph
   stays); large soft-blue plates everywhere are what saturated the dark theme. */
[data-theme="dark"] .feat-points .pt-icon,
[data-theme="dark"] .audience-card .aud-icon { background: var(--surface-2); }
[data-theme="dark"] .process-model { background: transparent; }

/* ============================================================================
   Audiences ("built for") + how-it-works
   ============================================================================ */
/* Two clean rows of three (never a ragged 4+2). */
.audience-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
@media (max-width: 980px) { .audience-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .audience-grid { grid-template-columns: 1fr; } }
/* Informative cards, not links: no hover lift, accent border or raised shadow that
   would promise a click nothing answers. The entry reveal stays on .reveal. */
.audience-card {
    padding: 1.35rem 1.3rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
}
.audience-card .aud-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
    margin-bottom: 0.85rem;
}
.audience-card .aud-icon svg { width: 20px; height: 20px; }
.audience-card h3 { font-size: 1.02rem; margin-bottom: 0.35rem; }
.audience-card p { color: var(--muted); font-size: 0.92rem; margin: 0; }

.how-strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; counter-reset: how; }
.how-step {
    position: relative;
    padding: 1.5rem 1.4rem 1.4rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
}
.how-step::before {
    counter-increment: how;
    content: "0" counter(how);
    display: block;
    font-family: var(--font-display);
    font-size: 0.85rem;
    font-weight: 650;
    letter-spacing: 0.1em;
    color: var(--accent);
    margin-bottom: 0.6rem;
}
.how-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-width: 2.5rem;
    height: 2.5rem;
    padding: 0 0.5rem;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
    margin-bottom: 0.7rem;
}
.how-icon svg { width: 18px; height: 18px; flex-shrink: 0; }
.how-step h3 { font-size: 1.05rem; margin-bottom: 0.35rem; }
.how-step p { color: var(--muted); font-size: 0.93rem; margin: 0; }
/* The strip's footer, not a fifth card: it spans the grid under the cards (1 / -1 keeps the
   span at every column count), wears the cards' border and radius, and settles on the band
   tone a step off the canvas — no number, no accent wash, less padding. */
.how-agent {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem 0.9rem;
    padding: 0.8rem 1.4rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--land-band);
}
.how-agent .how-icon { margin-bottom: 0; }
.how-agent p { color: var(--muted); font-size: 0.93rem; margin: 0; }
.how-agent .docs-link { margin-left: auto; font-size: 0.93rem; }

/* ============================================================================
   Pricing teaser band + final CTA
   ============================================================================ */
.pricing-teaser {
    border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
    border-radius: calc(var(--radius) + 6px);
    background:
        radial-gradient(120% 160% at 85% -20%, var(--accent-soft), transparent 55%),
        var(--surface);
    box-shadow: var(--shadow-md);
    padding: 2.8rem 2.6rem;
    display: grid;
    grid-template-columns: minmax(0, 7fr) auto;
    gap: 2rem;
    align-items: center;
}
[data-theme="dark"] .pricing-teaser {
    background: radial-gradient(120% 160% at 85% -20%, color-mix(in srgb, var(--surface-2) 80%, transparent), transparent 55%), var(--surface);
    border-color: var(--border-strong);
}
/* Inside a card the display size steps back: the page's h2 scale is set against the full
   canvas, not against 2.8rem of card padding. */
.pricing-teaser .land-h2 { font-size: clamp(1.7rem, 3vw, 2.2rem); margin-bottom: 0.6rem; }
.pricing-teaser p { color: var(--muted); margin: 0; max-width: 38rem; }
.pricing-teaser-cta { display: flex; flex-direction: column; align-items: stretch; gap: 0.7rem; min-width: 15rem; }
.pricing-teaser-cta .muted { text-align: center; margin: 0; font-size: 0.85rem; }
/* One summarised price line over the button. It answers "how much" without becoming a second
   set of cards to keep aligned with the pricing page. */
.pricing-teaser-price { text-align: center; margin: 0; font-weight: 650; font-size: 0.98rem; }
.checkout-legal { margin: 0.75rem 0 0; color: var(--muted); font-size: 0.8rem; line-height: 1.4; }

.land-final { text-align: center; padding: 7rem 0 8rem; }
.land-final .land-h2 { font-size: clamp(2.2rem, 4.6vw, 3.4rem); }
.land-final .land-lead { max-width: 38rem; margin: 0 auto 2.4rem; }
.land-final .land-cta-row { justify-content: center; margin-bottom: 1rem; }
.land-final .land-trust { justify-content: center; }

/* ============================================================================
   Site footer (marketing frame)
   ============================================================================ */
/* The end of the page, and the one place a tone change still earns its keep: the footer
   settles a step off the canvas instead of continuing it. */
.site-footer { flex-shrink: 0; border-top: 1px solid var(--border); background: var(--land-band); }
.footer-grid {
    display: grid;
    grid-template-columns: minmax(0, 4fr) repeat(4, minmax(0, 2.5fr));
    gap: 2.5rem;
    padding: 4rem 0 2.6rem;
}
.footer-brand p { color: var(--muted); font-size: 0.92rem; max-width: 21rem; margin: 0.8rem 0 0; }
.footer-col h3 {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--muted);
    font-weight: 650;
    margin-bottom: 0.8rem;
}
.footer-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.45rem; }
.footer-col a { color: var(--text); font-size: 0.94rem; font-weight: 500; }
.footer-col a:hover { color: var(--accent); text-decoration: none; }
/* The cookie panel is opened by a button, because it opens a panel instead of going anywhere.
   It sits in the same list as the legal links, so it reads as one of them (the bar settles the
   same case for its own row). */
.footer-col .link-button { color: var(--text); font-size: 0.94rem; font-weight: 500; text-decoration: none; }
.footer-col .link-button:hover { color: var(--accent); text-decoration: none; }
.footer-bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem 1.5rem;
    padding: 1.1rem 0 1.4rem;
    border-top: 1px solid var(--border);
    color: var(--muted);
    font-size: 0.85rem;
}
.footer-bottom .spacer { flex: 1; }
.footer-bottom-controls {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
}
.footer-bottom-controls select {
    min-height: 2rem;
    height: 2rem;
    border-radius: var(--radius-pill);
    font-size: 0.8rem;
    padding: 0 0.7rem;
}

/* Three theme buttons in the footer (system / light / dark), like Cursor. The pressed
   face is keyed off data-theme-pref so the mark matches the stored choice, not the
   resolved appearance. */
.theme-picker {
    display: inline-flex;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    overflow: hidden;
    background: var(--surface);
}
.theme-pick {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    margin: 0;
    padding: 0;
    border: none;
    border-radius: 0;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
}
.theme-pick + .theme-pick { border-left: 1px solid var(--border); }
.theme-pick svg { width: 14px; height: 14px; }
.theme-pick:hover { background: var(--surface-2); color: var(--text); }
html[data-theme-pref="system"] .theme-pick[data-theme-set="system"],
html[data-theme-pref="light"] .theme-pick[data-theme-set="light"],
html[data-theme-pref="dark"] .theme-pick[data-theme-set="dark"] {
    background: var(--surface-2);
    color: var(--text);
}
.theme-pick:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
    z-index: 1;
}

/* ============================================================================
   Marketing responsive collapse
   ============================================================================ */
/* The air is proportional: a wide frame gets the full band between sections, a narrow one
   gets enough to still separate them without turning the page into scrolling. */
@media (max-width: 980px) {
    .land-hero { padding: 3.6rem 0 4rem; }
    .land-hero-media { margin-top: 2.6rem; }
    .land-section { padding: 4.8rem 0; }
    .land-section-head { margin-bottom: 2.6rem; }
    .land-section-head--left { margin-bottom: 2.4rem; }
    .land-final { padding: 4.8rem 0 5.6rem; }
    .feat { grid-template-columns: 1fr; gap: 2rem; }
    .feat + .feat { margin-top: 4rem; }
    .feat-media, .feat--reverse .feat-media { order: 2; }
    .how-strip { grid-template-columns: repeat(2, 1fr); }
    .pricing-teaser { grid-template-columns: 1fr; padding: 2.2rem 1.8rem; }
    .footer-grid { grid-template-columns: 1fr 1fr; gap: 2rem; }
    .land-page { padding: 3.25rem 1.5rem 4.25rem; }
    .page-head { margin-bottom: 2.25rem; }
}
@media (max-width: 560px) {
    .footer-grid { grid-template-columns: 1fr; }
    .footer-bottom-controls { width: 100%; justify-content: flex-end; }
    .how-strip { grid-template-columns: 1fr; }
    .land-section { padding: 3.6rem 0; }
    .land-final { padding: 3.6rem 0 4.4rem; }
    .land-hero { padding-bottom: 3.4rem; }
    .land-page { padding: 2.5rem 1.5rem 3.5rem; }
    .hero-shot { padding: 0.35rem; }
    /* On a phone the loop is small: the control shrinks with it instead of covering it. */
    .hero-video-toggle { right: 0.6rem; bottom: 0.6rem; padding: 0.3rem 0.5rem; font-size: 0.7rem; }
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    html.js-anim .reveal,
    html.js-anim .land-h1 .hl {
        opacity: 1;
        transform: none;
        transition: none;
        animation: none;
    }
    .land-hero::after,
    .dot-live {
        animation: none;
        transition: none;
    }
    .hero-video-toggle { transition: none; }
    .dot-live { box-shadow: none; }
}

/* ============================================================================
   Sales pages (pricing, downloads) — shared with the landing's token language
   ============================================================================ */
/* The page title is display type, a step under the landing's own h2 so the two read as one family
   at two scales, and it stands clear of what follows instead of leaning on it. */
.page-head { max-width: 46rem; margin-bottom: 3.25rem; }
.page-head h1 {
    font-family: var(--font-display);
    font-size: clamp(2.4rem, 5vw, 3.4rem);
    font-weight: 640;
    letter-spacing: -0.032em;
    line-height: 1.06;
    margin-bottom: 1rem;
}
.page-head .tagline { font-size: 1.2rem; color: var(--muted); margin: 0; max-width: 44rem; text-wrap: pretty; }

/* Compact check bullets (value strips on sales pages). */
.value-points { list-style: none; display: flex; flex-wrap: wrap; gap: 0.5rem 1.6rem; padding: 0; margin: 1.4rem 0 0; color: var(--muted); }
.value-points li { display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.95rem; }
.value-points svg { width: 15px; height: 15px; color: var(--accent); flex-shrink: 0; }

/* Native-details FAQ — accordion with zero JS (static SSR). */
.faq-grid { display: grid; gap: 0.75rem; margin: 1.5rem 0 0; max-width: 46rem; }
.faq-item {
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface);
    padding: 0;
    overflow: hidden;
}
.faq-item summary {
    list-style: none;
    cursor: pointer;
    padding: 1rem 1.2rem;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after { content: "+"; font-size: 1.25rem; color: var(--accent); line-height: 1; transition: transform .15s ease; }
.faq-item[open] summary::after { transform: rotate(45deg); }
.faq-item p { margin: 0; padding: 0 1.2rem 1.1rem; color: var(--muted); }

/* Download page: the platform card. */
.download-card {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 1.5rem;
    align-items: center;
    max-width: 620px;
    padding: 2.4rem;
    border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
    border-radius: var(--radius);
    background: radial-gradient(120% 160% at 90% -20%, var(--accent-soft), transparent 55%), var(--surface);
    box-shadow: var(--shadow-md);
    margin: 0 0 2rem;
}
[data-theme="dark"] .download-card {
    background: radial-gradient(120% 160% at 90% -20%, color-mix(in srgb, var(--surface-2) 80%, transparent), transparent 55%), var(--surface);
    border-color: var(--border-strong);
}
/* The same icon plate the landing draws everywhere else: a soft accent tile with an accent glyph,
   never a solid block of brand color. With the buttons neutral, a filled plate would be the one
   saturated rectangle left on the page and would read as the thing to press. */
.download-card .dl-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3.4rem;
    height: 3.4rem;
    border-radius: var(--radius-md);
    background: var(--accent-soft);
    color: var(--accent);
}
[data-theme="dark"] .download-card .dl-icon { background: var(--surface-2); }
.download-card .dl-icon svg { width: 26px; height: 26px; }
.download-card h2 { margin-bottom: 0.2rem; }
.download-card .muted { margin: 0; }
.download-card .dl-cta { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 0.9rem; align-items: center; }
@media (max-width: 560px) { .download-card { grid-template-columns: 1fr; } }

/* Download page: the release-notes block, a section of its own that is drawn whether or not a
   version has been published. Same reading width as the points above it. */
.release-summary { max-width: 620px; margin: 4rem 0 2rem; }
.release-summary h2 { font-family: var(--font-display); font-size: 1.5rem; letter-spacing: -0.02em; margin-bottom: 1.1rem; }
.release-summary-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 1rem; }
.release-summary-list li { padding-top: 1rem; border-top: 1px solid var(--border); }
.release-summary-list li:first-child { padding-top: 0; border-top: 0; }
.release-summary-list a { font-weight: 650; }
.release-summary-list p { margin: 0.35rem 0 0; color: var(--muted); font-size: 0.95rem; }
.release-summary .release-meta { margin: 0.15rem 0 0; }
.release-summary-all { margin: 1.25rem 0 0; }

/* Release notes (/releases): one entry per version, newest first. */
.release-entry { max-width: 46rem; padding: 1.75rem 0; border-top: 1px solid var(--border); }
.release-entry:first-of-type { border-top: 0; padding-top: 0; }
.release-entry h2 a { text-decoration: none; color: inherit; }
.release-entry h2 a:hover { text-decoration: underline; }
.release-meta { color: var(--muted); font-size: 0.9rem; margin: 0.15rem 0 1rem; }
.release-back { margin-top: 2rem; }

/* ============================================================================
   CookieConsent (self-hosted, ADR 0011) — theme bridge
   Map the vendored library's --cc-* tokens onto the Ganter design tokens so the
   consent notice and preferences panel follow the light/dark theme with no JS.
   Because the values reference theme-aware tokens (--surface, --text, --accent…),
   one block covers both themes. Scoped to html[data-theme] so it outranks the
   library's own :root defaults regardless of stylesheet load order.
   ============================================================================ */
html[data-theme] {
    --cc-font-family: var(--font-sans);
    --cc-modal-border-radius: var(--radius);
    --cc-btn-border-radius: var(--radius-pill);
    --cc-pm-toggle-border-radius: 1em;

    --cc-bg: var(--surface);
    --cc-primary-color: var(--text);
    --cc-secondary-color: var(--muted);
    --cc-link-color: var(--accent);
    --cc-separator-border-color: var(--border);
    --cc-overlay-bg: rgba(10, 8, 6, 0.55);
    --cc-z-index: 2147483000;

    /* The notice's accept button is the site's primary button: the same neutral ink pair. */
    --cc-btn-primary-bg: var(--ink-solid);
    --cc-btn-primary-color: var(--ink-contrast);
    --cc-btn-primary-border-color: var(--ink-solid);
    --cc-btn-primary-hover-bg: var(--ink-solid-hover);
    --cc-btn-primary-hover-color: var(--ink-contrast);
    --cc-btn-primary-hover-border-color: var(--ink-solid-hover);

    --cc-btn-secondary-bg: var(--surface-2);
    --cc-btn-secondary-color: var(--text);
    --cc-btn-secondary-border-color: var(--border);
    --cc-btn-secondary-hover-bg: var(--border);
    --cc-btn-secondary-hover-color: var(--text);
    --cc-btn-secondary-hover-border-color: var(--border-strong);

    --cc-toggle-on-bg: var(--accent);
    --cc-toggle-off-bg: var(--border-strong);
    --cc-toggle-on-knob-bg: #ffffff;
    --cc-toggle-off-knob-bg: var(--surface);
    --cc-toggle-enabled-icon-color: var(--accent-contrast);
    --cc-toggle-disabled-icon-color: var(--muted);
    --cc-toggle-readonly-bg: var(--surface-2);
    --cc-toggle-readonly-knob-bg: var(--muted);
    --cc-toggle-readonly-knob-icon-color: var(--surface);

    --cc-cookie-category-block-bg: var(--surface-2);
    --cc-cookie-category-block-border: var(--border);
    --cc-cookie-category-block-hover-bg: var(--border);
    --cc-cookie-category-block-hover-border: var(--border-strong);
    --cc-cookie-category-expanded-block-bg: var(--surface);
    --cc-cookie-category-expanded-block-hover-bg: var(--surface-2);
    --cc-section-category-border: var(--border);

    --cc-footer-bg: var(--surface-2);
    --cc-footer-color: var(--muted);
    --cc-footer-border-color: var(--border);

    --cc-webkit-scrollbar-bg: var(--border);
    --cc-webkit-scrollbar-hover-bg: var(--border-strong);
}

/* Integrator directory map. Global (not scoped) because the map spans a child component's
   container and Leaflet/marker-cluster elements the plugin injects at runtime. Hidden until
   integrator-map.js mounts the map, so the no-JS case shows nothing and no tiles are requested.
   position/z-index create a stacking context so Leaflet's own high pane z-indexes stay contained. */
.directory-map {
    position: relative;
    z-index: 0;
    margin: 0 0 1.5rem;
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
}

.directory-map-canvas {
    height: 420px;
}

.directory-map-popup strong {
    display: block;
}

.directory-map-popup a {
    display: inline-block;
    margin-top: 0.35rem;
}

/* ============================================================================
   Wide screens — past 1600px the site takes the room instead of banking it as margin
   ============================================================================ */
/* Every frame already declares the column it centers on, and the bar above it, the body under it
   and the footer at its end all read that one token. So widening the site is a matter of retuning
   three numbers here, not of touching a single layout: the tables, card grids and media that sit
   in those columns simply get more of it, while the reading measures stay where they were set
   (.land-page, .land-lead, the section heads, the account forms all carry their own max-width and
   are indifferent to this block). Nothing below the breakpoint moves. */
@media (min-width: 1600px) {
    :root {
        --land-max: 1600px;
        --content-max: 1440px;
    }

    /* The docs keep their head start over the marketing column, since the 260px rail comes out of
       it before the article does. */
    .docs-layout {
        --land-max: 1680px;
    }

    /* The extra width is for tables, code, cards and figures. Running prose keeps a reading
       measure, so a paragraph never stretches to the 130-character line the column now allows. */
    .docs-content > p,
    .docs-content > ul,
    .docs-content > ol,
    .docs-content > blockquote {
        max-width: 80ch;
    }
}
