@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;
    /* The site's one column. The bar, the page and the footer all read it, and no page, frame or
       page stylesheet declares a width of its own, so the three can never drift apart. */
    --content-max: 1240px;
    /* The measure running prose is held to wherever a page is text, whatever the column does:
       a line the eye can still return from. */
    --read-max: 80ch;
    /* 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; }

/* ============================================================================
   The one site frame (SiteFrame): one bar, one column, one footer
   ============================================================================ */
/* Every page but the sign-in ones is drawn inside this. The shell holds the bar at the top and
   the footer at the end; the column between them is what the page fills, and it is declared here
   and nowhere else, so no page can open on edges of its own. */
.site-shell { display: flex; flex-direction: column; min-height: 100vh; }
.site-column { width: 100%; max-width: var(--content-max); margin: 0 auto; padding: 0 1.5rem; }
/* The frame's own column is what grows to push the footer to the bottom of a short page; the same
   class inside the footer is only the footer's row of links riding the same edges. */
.site-shell > .site-column { flex: 1; }
/* An area that navigates itself (the documentation) hands the frame a rail, and it stands beside
   the article inside that same column rather than beside the column. */
.site-column--rail { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 3.5rem; }
/* The band of air a page opens and ends on, identical on every page of the site. */
.site-main { min-width: 0; padding: 4rem 0 5rem; }
@media (max-width: 980px) { .site-main { padding: 2.75rem 0 3.5rem; } }

/* The one topbar class: the 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. It reads the one column token, the same one the page and the footer read. The
   background is the page's own canvas, so the bar is a line over the page and 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(--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(--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); }

/* A mark of the program (Shared/ProgramGlyph, Shared/AreaMark) stands in the 1em box the program's
   icon font gives a glyph, in the ink of the text, until what holds it sizes and colours it. */
svg.ui-glyph { width: 1em; height: 1em; flex: none; }

.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, for the accounts allowed in, the marketing area) 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; }
}

/* ============================================================================
   Landing and the public pages
   ============================================================================ */
/* 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. It is the page's own
   canvas (--bg), so a page begins where its air begins and nowhere else. 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. */

/* Where a page is running text (downloads, release notes, the legal documents, the whole
   documentation) prose reads on a measure and everything else takes the column: paragraphs,
   lists and quotations stop at a line the eye can return from, while cards, tables and grids
   use the full width the frame gives them. */
.text-page :is(p, ul, ol, blockquote),
.docs-content > :is(p, ul, ol, blockquote) { max-width: var(--read-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, ADR 0040) — four static cards: free, monthly, the long commitment
   whose 1-year / 3-year control reloads the page already on that term, and the perpetual license
   ============================================================================ */
/* 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.
   Each card is the same six parts (drawing, name, sentence, price, call to action, list), and it
   lends them to the row's own rows (subgrid), so a part starts at the same height in every card of
   a row whatever the neighbours hold: the buttons line up, and so do the lists under them. The
   gap between the cards of a folded row is the row's; inside a card the parts keep their own air. */
.pricing-cards { display: grid; grid-template-columns: repeat(4, 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.75rem 1.5rem 1.6rem;
    display: grid;
    grid-row: span 6;
    grid-template-rows: subgrid;
    row-gap: 0;
    min-width: 0;
}
.pricing-card form { margin: 0; }
.pricing-card-featured { border-color: var(--accent); box-shadow: var(--shadow-md); }
/* The drawing that heads the card (Components/Landing/PlanBadge*): line work in the accent, the
   same box for the four cards so each drawing stands with the same presence. */
.plan-badge { display: block; width: 80px; height: 72px; margin: 0 0 1.25rem; color: var(--accent); }
/* The plan's name in the brand's display face, and under it the one sentence that says what the
   plan is for. */
.pricing-card h2 {
    margin: 0 0 0.35rem;
    font-family: var(--font-display);
    font-size: 1.85rem;
    font-weight: 500;
    letter-spacing: -0.02em;
    line-height: 1.1;
}
.pricing-tagline { margin: 0 0 1.5rem; color: var(--muted); font-size: 0.98rem; line-height: 1.4; }
/* The price opens its row, so the amounts share one line across the row. A card with no amount (the
   free card, a license not on sale yet) says its cadence on the foot of the row instead, on the line
   of the cadences beside it. */
.pricing-price { align-self: start; margin-bottom: 1.25rem; }
.pricing-price:not(:has(.pricing-amount)) { align-self: end; }
.pricing-amount {
    margin: 0 0 0.2rem;
    font-family: var(--font-display);
    font-size: 2.1rem;
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
}
.pricing-cadence { margin: 0; color: var(--muted); font-size: 0.9rem; }
.pricing-save {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.05rem 0.55rem;
    border-radius: var(--radius-pill);
    background: var(--success-bg);
    color: var(--success);
    font-size: 0.78rem;
    font-weight: 650;
    vertical-align: 0.05em;
}
/* What is included, under a hairline, in short lines. */
.pricing-card > .feature-list {
    align-self: start;
    margin: 0;
    padding-top: 1.35rem;
    border-top: 1px solid var(--border);
    font-size: 0.95rem;
}
.pricing-card > .feature-list li { margin-bottom: 0.5rem; text-wrap: balance; }
.pricing-card > .feature-list li:last-child { margin-bottom: 0; }

/* The currency every card is priced in. It governs the whole row, so it stands above the row
   rather than inside one card, drawn as the same quiet plate the term switch is, with the one
   sentence a payer needs beside it. */
.pricing-currency {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 0.4rem 1rem;
    margin: 2rem 0 0;
}
.pricing-currency + .pricing-cards { margin-top: 0.75rem; }
.pricing-currency form { display: inline-flex; align-items: center; gap: 0.5rem; margin: 0; }
.pricing-currency label { color: var(--muted); font-size: 0.85rem; font-weight: 600; }
.pricing-currency select {
    min-height: 2.15rem;
    padding: 0 0.6rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--surface-2);
    color: var(--text);
    font: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
}
.pricing-currency select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pricing-currency-note { margin: 0; color: var(--muted); font-size: 0.85rem; }

/* Two-option control on the featured card, under the price it changes: 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.9rem 0 0;
    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 right under its price, the width of the card; the checkout notice is
   said once underneath, a step smaller. The place stays when a card has nothing to put in it, so the
   list below keeps its line. */
.pricing-cta { display: flex; flex-direction: column; gap: 0.6rem; align-self: start; margin-bottom: 1.5rem; }
.pricing-cta:empty { margin-bottom: 0; }
.pricing-cta .button { width: 100%; padding: 0 0.85rem; font-size: 0.95rem; text-align: center; }
.pricing-cta .checkout-legal { margin: 0; font-size: 0.78rem; }

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

/* The common questions close the page as a section of their own: a rule across the width of the
   card row opens it, the title stands on the left in the cards' display face, and the questions
   stand in the shared panel on the right (.faq-panel, with the site's accordion below), so the
   section belongs to the cards without becoming a fifth one. Under the two-up width the title
   stands above the panel. */
.pricing-faq {
    display: grid;
    grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
    gap: 1.5rem clamp(2rem, 5vw, 4.5rem);
    align-items: start;
    margin: 4.5rem 0 1rem;
    padding-top: 2.75rem;
    border-top: 1px solid var(--border);
}
.pricing-faq-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: clamp(1.75rem, 3vw, 2.25rem);
    font-weight: 500;
    letter-spacing: -0.02em;
    line-height: 1.1;
    text-wrap: balance;
}
@media (max-width: 1100px) {
    .pricing-faq { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 560px) {
    .pricing-faq { margin-top: 3.5rem; padding-top: 2.25rem; }
}

/* ============================================================================
   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; }
/* The money-back guarantee while it is still open: a reading width, with the written request it
   hands over as the button under it. */
.subscription-guarantee { max-width: 520px; }
/* The perpetual license's explanation and the installer line under its table: the same reading
   width, since each is a sentence and not a row of the table. */
.subscription-note { max-width: 520px; }
.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; }
/* The separate transfer consent, on the registrations that ask for it: one required box, with its
   refusal indented under the label. */
.legal-acceptance { margin: -0.2rem 0 1.1rem; color: var(--muted); font-size: 0.84rem; }
.legal-acceptance > div { display: flex; align-items: flex-start; gap: 0.6rem; }
.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: the same row shape as the required box where one is drawn
   above it, with its own band of space before the button, so it never reads as something that 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; }

/* The Terms and Privacy notice under the create button. Pressing the button is the acceptance, so
   the sentence is read beside the press instead of ticked: small, quiet and centred on the button,
   its two documents in the site's ordinary link colour. */
.auth-legal-notice {
    margin: 0.85rem 0 0;
    text-align: center;
    color: var(--muted);
    font-size: 0.8rem;
    line-height: 1.45;
}
.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; }
/* The Microsoft Store logo (StoreLogo) keeps its own colors and square proportion, a little larger
   than the line icons so the bag reads at button size. */
.button svg.store-logo { width: 20px; height: 20px; }
.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. It is the button of every action
   other than a download: 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, and the download buttons below), so an ordinary 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); }

/* Download: the one button the brand blue fills. Every call to action that takes the visitor to
   the installer (the bar, the hero, the final call, the download page, pricing, the comparisons)
   wears it on top of its own size, so the way to get the product reads the same on every page and
   in both themes: the accent, its hover and the text on accent that docs/brand/brand.md gives each
   theme. Declared after the primary so it wins at the same specificity. */
.button-download {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-contrast);
}
.button-download:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: var(--accent-contrast);
}

/* 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%; }

/* What only makes sense at a desk steps aside on a phone-sized screen. Every link to the live
   demo wears it (the bar, the hero, the final call, the comparisons): the demo is a whole station
   drawn in the browser, and on a phone it opens something that cannot be used there. !important
   because the utility has to outrank the display each component sets (.button, .topnav a). */
@media (max-width: 760px) {
    .hide-narrow { display: none !important; }
}

/* ---- Getting the product: one call to action, a face per platform ----
   DownloadCallToAction draws a face for each kind of device and names in data-for the platforms each
   one serves; the head script (App.razor) names the visitor's platform on html before the first
   paint, and only the face for it is drawn. No script means no attribute, and then the Windows face
   stands. A face lends its buttons to the row it sits in (display: contents), so the row lays them
   out exactly as it laid out the one button the face stands in for. */
.cta-face { display: none; }
html:not([data-platform]) .cta-face[data-for~="windows"],
html[data-platform="windows"] .cta-face[data-for~="windows"],
html[data-platform="mac"] .cta-face[data-for~="mac"],
html[data-platform="other"] .cta-face[data-for~="other"],
html[data-platform="mobile"] .cta-face[data-for~="mobile"] { display: contents; }

/* Where the demo is the main button (a Mac, or another computer that cannot run the installer), a
   demo link the page draws beside it, or the bar's own entry, would offer the same thing twice. */
html[data-platform="mac"] [data-cta-kind="demo"]:not(.cta-face *),
html[data-platform="other"] [data-cta-kind="demo"]:not(.cta-face *) { display: none; }

/* The way around the main button, for the visitor it does not suit (a Store blocked at work, the
   Windows installer fetched from a Mac): a plain link beside it, a step down in size. */
.cta-alt { font-size: 0.9rem; font-weight: 500; }

/* A phone's face says what the product runs on before the button that sends the link on. */
.cta-note {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    color: var(--muted);
    font-weight: 550;
}
.cta-note svg { width: 18px; height: 18px; flex-shrink: 0; }

/* In a pricing card the face stacks under the price: the button takes the width, and what goes with
   it is centred on it, spaced by the stack's own gap. */
.pricing-card .cta-alt { text-align: center; }
.pricing-card .cta-note { justify-content: center; }

/* 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. */
.manage-content [class*="col-"] { max-width: 520px; }
.manage-content .col-xl-8 { max-width: 640px; }
.manage-content .col-xl-9 { max-width: 760px; }

/* The account pages' buttons: an icon and a label, set apart from the text in a row of their own.
   Whatever disables, invalidates or deletes is written last in its row, in the danger fill. A form
   inside the row lends its button to the row instead of breaking it. */
.manage-content .account-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin: 1rem 0 1.5rem; }
.manage-content .account-actions > form { display: contents; }
.manage-content .account-actions a.btn { margin: 0; }
.manage-content .alert .account-actions { margin: 0.6rem 0 0; }
.manage-content td .account-actions { margin: 0; }
.manage-content .btn-sm { min-height: 2rem; padding: 0 0.85rem; font-size: 0.8125rem; }
.manage-content .btn-sm svg { width: 15px; height: 15px; }
/* The scaffold's hidden attribute has to win over the button's own display, or a control a script
   holds back (the map pin's second button) paints anyway. */
.manage-content [hidden] { display: none !important; }
/* Copying needs a script, so the button is only drawn where one runs (the head script stamps js). */
.manage-content [data-copy] { display: none; }
html.js .manage-content [data-copy] { display: inline-flex; }

/* The profile: one row per fact, its name, value and state on the left and the button that changes
   it on the right. On a phone the button drops under the value. */
.account-rows {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0 1.25rem;
    margin: 1rem 0 1.5rem;
    max-width: 640px;
}
.account-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 1.25rem;
    row-gap: 0.2rem;
    align-items: center;
    padding: 0.9rem 0;
}
.account-row + .account-row { border-top: 1px solid var(--border); }
.account-row dt { grid-column: 1; color: var(--muted); font-weight: 500; font-size: 0.875rem; }
.account-row dd { margin: 0; }
.account-row-value { grid-column: 1; overflow-wrap: anywhere; }
.account-row-value .status-badge { margin-left: 0.4rem; }
.account-row-action { grid-column: 2; grid-row: 1 / span 2; }
.account-row-action .btn { margin: 0; white-space: nowrap; }
@media (max-width: 560px) {
    .account-row { grid-template-columns: minmax(0, 1fr); }
    .account-row-action { grid-column: 1; grid-row: auto; margin-top: 0.5rem; }
}

/* Personal data: the copy and the closing of the account, each a block of its own. */
.account-block { max-width: 640px; }
.account-block + .account-block { border-top: 1px solid var(--border); margin-top: 2rem; }
.account-block h4 { margin-top: 1.5rem; }

/* The recovery codes: a grid that reads like the sheet they are written down on. */
.recovery-codes {
    display: grid;
    grid-template-columns: repeat(2, max-content);
    gap: 0.6rem 2rem;
    list-style: none;
    width: fit-content;
    margin: 0 0 1rem;
    padding: 1rem 1.5rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.recovery-codes .recovery-code { font-size: 1.05rem; letter-spacing: 0.08em; }
@media (max-width: 380px) { .recovery-codes { grid-template-columns: max-content; } }

.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 documentation adds a rail to the site's one frame and nothing else: the column, the bar
   above it and the footer below it are the same ones every other page rides, and the 260px the
   rail spends come out of that column rather than widening it. */
/* 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); }

/* An area in the rail, and a group inside one: the same disclosure at both levels. The row is the
   heading 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 area as one block down the rail; a group inside
   it draws its own, one step further in. */
.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; }
/* The caption over a run of a group's pages (their `category`): small, semibold and muted like the
   section headings, but in the case it is written in, since a name such as "PLCs" is spelled by its
   case. Flush with the links it heads. A label, not an entry: it takes no hover. */
.docs-nav-caption {
    padding: 0.6rem 0.7rem 0.15rem;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--muted);
}
.docs-nav-caption:first-child { padding-top: 0.2rem; }
.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-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. A word of a cell is never split: a column is at least as wide as its longest
   word, because a cell allowed to break anywhere lets the table squeeze a column down to a few
   letters ("Fie ld"). Only a literal in code (a URI, an address) breaks inside itself, 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: break-word; }
.docs-content th code, .docs-content td code { 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 documentation home opens on its three ways in, which stand apart from the area cards under
   them: a size up and on the accent's soft wash, so the first thing a reader meets is where to
   begin. The row stretches its three cards across the column rather than leaving a gap at its end. */
.docs-card-grid-start { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.docs-card-start {
    gap: 0.4rem;
    padding: 1.5rem 1.6rem;
    background: var(--accent-soft);
    border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
    box-shadow: var(--shadow-md);
}
.docs-card-start .docs-card-title { font-size: 1.12rem; }
.docs-card-start .docs-card-desc { font-size: 0.95rem; }
/* 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) {
    .site-column--rail { grid-template-columns: minmax(0, 1fr); gap: 0; }
    .docs-sidebar { position: static; max-height: none; padding: 1.5rem 0 0; }
}

/* ============================================================================
   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, the product shown in real stills of its own screens. 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;
}


/* The landing is drawn like a technical datasheet (docs/brand/brand.md): the warm paper of the light
   theme, the same drawing on graphite in the dark one, and three voices of text (--text for titles
   and benefits, --text-2 for leads, --muted for captions and notes). The product's own recordings
   sit on cards that stay dark in both themes, on the very ground the clips are recorded on
   (--card-ground), so a clip fills its card without a seam and stands off the paper; in the dark
   theme the card's edge is what separates it from the page. What is drawn over a clip wears the
   dark theme's accent and ink in both themes (--card-accent, --card-ink, and the balloons of a
   conversation over a clip, --card-ask and --card-answer), because the card under it is dark in
   both. Captions, keys and figure labels are set in mono, which ships with Windows and falls back
   to each system's own. */
:root {
    --font-display: "InterVariable", "Inter", var(--font-sans);
    --font-mono: "Cascadia Mono", "Cascadia Code", ui-monospace, "SF Mono", Consolas, monospace;
    --grid-line: color-mix(in srgb, var(--accent) 9%, transparent);
    --text-2: #3d3830;
    --card-ground: #121110;
    --card-edge: transparent;
    --card-radius: 20px;
    --card-shadow: 0 22px 48px rgba(30, 27, 21, 0.2), 0 4px 12px rgba(30, 27, 21, 0.12);
    --card-accent: #38bdf8;
    --card-ink: #eceae4;
    --card-ask: #0e2a3e;
    --card-answer: #24221e;
    --card-answer-edge: #4b453d;
}
/* 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);
    --text-2: #c9c4b8;
    --card-edge: #3a362f;
    --card-shadow: 0 24px 60px rgba(0, 0, 0, 0.55), 0 6px 16px rgba(0, 0, 0, 0.4);
}

/* 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. The older
   addresses into the landing land on a card or a row of a block, so those stop below the bar too. */
html { scroll-behavior: smooth; }
.land-section, .land-final, .land-hero { scroll-margin-top: calc(var(--topbar-h) + 0.75rem); }
.land-still, .spec-table tr { scroll-margin-top: calc(var(--topbar-h) + 1.5rem); }

/* ---- 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
   ============================================================================ */
/* The hero begins where the bar ends. The negative margin takes back the opening air the frame
   gives every page (.site-main), and the padding gives it back to the copy, so the pitch stands
   where it always did while the texture the hero paints behind it runs up to the bar's own line:
   with that air left flat, the bar plus the air read as a black band above the textured hero.
   The hero clips only up and down, where the aurora rises past the bar's line: its sides stay open,
   because the assistant's window stands over the still's corner and past the hero's own edge
   (.land-stage-assistant), and a side clip would cut that window. `clip` on one axis leaves the
   other visible; `hidden` would turn it into a scroller. */
.land-hero { position: relative; overflow-x: visible; overflow-y: clip; margin-top: -4rem; padding: 6.5rem 0 6rem; }
/* The copy and the media stand over the grid and the aurora the hero paints behind them. */
.land-hero > * { position: relative; z-index: 1; }

/* Drifting aurora glow behind the pitch — slow, subtle life, sitting on the same center line as
   the copy. Weaker in dark (large accent washes saturate the dark theme). Never wider than the
   hero, and never cut by it: it has faded to nothing before the hero's top and bottom, which clip,
   and at the far end of its drift it fades out past the hero's open sides: a glow a box had to cut
   prints that box's edge on the canvas. The offsets are the element's own percentages, so the same
   rule holds at every width. */
.land-hero::after {
    content: "";
    position: absolute;
    top: 0;
    left: 50%;
    width: min(660px, 100%);
    aspect-ratio: 1;
    margin-left: calc(-1 * min(330px, 50%));
    border-radius: 50%;
    background: radial-gradient(circle, color-mix(in srgb, var(--accent) 20%, transparent), transparent 70%);
    transform: translateY(-15%);
    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 70%);
}
@keyframes land-aurora {
    from { transform: translateY(-15%); }
    to { transform: translate(-24%, 2%) scale(1.18); }
}

/* Engineering grid — technical texture behind the pitch. It meets the bar's line at the top,
   where the hero begins, and fades out before the hero's other edges. Two masks, intersected: the
   radial keeps the texture to the pitch and lets it go at the bottom, the horizontal ramp lets it
   go at the sides, so the grid sinks back into the canvas instead of stopping on a line. The radial
   alone still holds the grid at a fifth of its strength where the box ends, and a grid cut on a
   line reads as a panel: on a phone the box shows as a lighter rectangle, which is the one thing
   the continuous canvas must never do. */
.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;
    -webkit-mask-image:
        radial-gradient(78% 62% at 50% 10%, black, transparent 78%),
        linear-gradient(to right, transparent, black 12%, black 88%, transparent);
    -webkit-mask-composite: source-in;
    mask-image:
        radial-gradient(78% 62% at 50% 10%, black, transparent 78%),
        linear-gradient(to right, transparent, black 12%, black 88%, transparent);
    mask-composite: intersect;
    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; }

/* One frame for the picture of the fold and for every clip on the page. The recordings are dark,
   so the mat is what gives a dark rectangle a home on a light page: a thin border, the page's own
   radius and a shadow that lifts it off the canvas. A clip's card replaces the mat with its own
   dark ground (.video-card, below); a still of the program wears the Store's frame instead
   (.land-screen, further down). */
.video-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);
}
/* The two controls of a clip, pause and full screen, sit in the frame's corner and show only while
   the pointer is over the frame or the focus is inside it: the clip is a picture first, and a pill
   standing on every frame would be read before the picture. */
.video-controls {
    position: absolute;
    right: 1.1rem;
    bottom: 1.1rem;
    z-index: 2;
    display: flex;
    gap: 0.4rem;
    opacity: 0;
    transition: opacity 0.18s ease;
}
.video-shot:hover .video-controls,
.video-shot:focus-within .video-controls { opacity: 1; }
.video-toggle,
.video-expand {
    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;
}
.video-toggle:hover,
.video-expand:hover { border-color: var(--accent); }
.video-toggle:focus-visible,
.video-expand:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* In full screen the clip is letterboxed on black, never cropped. */
.video-shot video:fullscreen {
    object-fit: contain;
    background: #000;
    border: 0;
    border-radius: 0;
}

/* A clip's card (LandingVideo): no mat, the card is the frame, on the dark ground the clips are
   recorded on, in both themes. The clip's own 16:9 box (.clip-frame) sizes it, so the clip fills the
   card whole, with no band above or below and nothing of it cut. Declared after the mat it
   replaces. */
.video-card {
    padding: 0;
    overflow: hidden;
    border: 1px solid var(--card-edge);
    border-radius: var(--card-radius);
    background: var(--card-ground);
    box-shadow: var(--card-shadow);
}
/* The clip's own 16:9 box: the poster (the frame the clip holds at its end) and the clip over it,
   transparent until it has a frame. */
.clip-frame { position: relative; isolation: isolate; width: 100%; aspect-ratio: 16 / 9; }
.clip-poster,
.clip-frame > video {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.clip-frame > video { background: transparent; }

/* 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;
}

/* The landing's headings wear the brand display (docs/brand/brand.md): Inter Medium with the
   slight negative tracking of the wordmark, the same at every heading size of the page (the
   claims, .land-claim, and the section titles, .land-h2). */

.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 (the integrator directory's band) ---- */
.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; }

/* ============================================================================
   Dark theme, second desaturation pass: the brand light-blue stays on interactive
   elements (CTAs, links, the active tab) and on the datasheet's own marks (the
   instrument bubbles and dimension lines), but leaves the purely decorative kickers,
   so blue stops stacking on blue.
   ============================================================================ */
[data-theme="dark"] .land-kicker { color: var(--muted); }

/* ============================================================================
   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; }

.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: 500;
    letter-spacing: -0.01em;
    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; }

/* ---- Points with a glyph each (the download page, the comparisons, the directory) ---- */
.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; }

.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); }

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

/* ============================================================================
   The landing's blocks (docs/marketing/landing-brief.md): the Store's language. Every claim is a
   Store caption in two lines, the second in the brand blue; every picture is a real still of the
   program in the Store's dark frame with its blue edge light; the words stand beside the picture,
   which takes the wider column so it reads at the size the page shows it. The visitor's own AI
   assistant is a window of its own, outside the program, joined to it by the MCP link.
   ============================================================================ */

/* ---- The claim: a Store caption in two lines ---- */
.land-claim {
    font-family: var(--font-display);
    font-size: clamp(2rem, 4vw, 3rem);
    font-weight: 500;
    letter-spacing: -0.015em;
    line-height: 1.04;
    margin: 0 0 1.1rem;
}
.land-claim > span { display: block; }
.land-claim-accent { color: var(--accent); }
/* The hero's claim is the page's largest word, and the offer under its sentence is the answer to
   "how much does it cost to try", said before the button. */
.land-claim--hero { font-size: clamp(2.8rem, 6.6vw, 5rem); margin-bottom: 1.3rem; }
.land-offer { margin: -1.2rem auto 1.7rem; font-weight: 650; color: var(--text); font-size: 1.05rem; }

/* ---- The still: a real screen in the Store's frame ----
   The dark ground the program is captured on, the brand-blue edge light the Store images use, and
   the page's own card shadow; the same in both themes, since the picture is dark in both. */
.land-still { margin: 0; }
.land-still + .land-still { margin-top: 1.4rem; }
.land-screen {
    position: relative;
    overflow: hidden;
    border-radius: var(--card-radius);
    border: 1.5px solid color-mix(in srgb, var(--card-accent) 55%, transparent);
    background: var(--card-ground);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--card-accent) 12%, transparent),
        0 0 48px color-mix(in srgb, var(--card-accent) 26%, transparent),
        var(--card-shadow);
}
.land-screen img { display: block; width: 100%; height: auto; }
.land-still-caption {
    margin: 0.7rem 0 0;
    font-family: var(--font-mono);
    font-size: 0.78rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
}

/* ---- The hero stage: the still, and the assistant's window over its corner ----
   The window stands over the bottom-left corner of the screen, with the MCP link running from its
   edge into the screen. It reaches past the screen's edge by half the column's gutter (.site-column's
   1.5rem), so where the column fills the browser it still keeps air, and its shadow, inside the
   browser's edge. On a narrow screen the window stands under the picture instead. */
.land-hero-media { position: relative; margin: 3.5rem auto 0; }
.land-stage { position: relative; }
.land-stage-assistant {
    position: absolute;
    left: -0.75rem;
    bottom: -1.5rem;
    width: min(22rem, 42%);
}
.land-stage-assistant .land-mcp {
    position: absolute;
    left: 100%;
    top: 1rem;
    margin-left: 0.3rem;
    white-space: nowrap;
}
@media (max-width: 760px) {
    .land-stage-assistant { position: static; width: 100%; margin-top: 1rem; }
    .land-stage-assistant .land-mcp { display: none; }
}

/* ---- The top that answers the ad ----
   The page carries one claim and one stage per top (LandingTop); the head script (LandingTopScript)
   names the top the address chose on html before the first paint, and only that top's claim and
   stage are drawn. A document it never marked (no campaign, another campaign, no script) draws the
   default. The stages' pictures are lazy, so a stage not drawn is never fetched. */
.land-top-claim > span { display: block; }
html[data-top]:not([data-top="ask"]) [data-top-variant="ask"],
html:not([data-top="test"]) [data-top-variant="test"],
html:not([data-top="device"]) [data-top-variant="device"] { display: none; }

/* ---- The assistant's window: an app of its own ---- */
.land-assistant {
    width: 100%;
    overflow: hidden;
    border: 1px solid var(--border-strong);
    border-radius: 14px;
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    font-size: 0.92rem;
    text-align: left;
}
.land-assistant-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.1rem 0.6rem;
    padding: 0.55rem 0.9rem;
    border-bottom: 1px solid var(--border);
    background: var(--surface-2);
}
.land-assistant-dots { display: inline-flex; gap: 5px; }
.land-assistant-dots i { width: 9px; height: 9px; border-radius: 50%; background: var(--border-strong); }
.land-assistant-title { font-weight: 650; white-space: nowrap; }
/* The assistants it stands for, on their own line under the title, so the window's head never
   wraps the names into a column beside it. */
.land-assistant-names { flex-basis: 100%; padding-left: 2.05rem; color: var(--muted); font-size: 0.76rem; }
.land-assistant-body { display: grid; gap: 0.6rem; padding: 0.9rem; }
.land-bubble {
    margin: 0;
    max-width: 92%;
    padding: 0.6rem 0.85rem;
    border-radius: 12px;
    line-height: 1.4;
}
.land-bubble--ask {
    justify-self: end;
    background: var(--accent);
    color: var(--accent-contrast);
    border-bottom-right-radius: 4px;
}
.land-bubble--answer {
    justify-self: start;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-bottom-left-radius: 4px;
}
/* The balloons appear one after the other once the window is in view: the request, then the answer.
   Without a script, or with reduced motion, they stand in their final state. */
html.js-anim .reveal .land-bubble { opacity: 0; transform: translateY(8px); }
html.js-anim .reveal.is-in .land-bubble { animation: land-bubble .5s cubic-bezier(.16, 1, .3, 1) forwards; }
html.js-anim .reveal.is-in .land-bubble--ask { animation-delay: .5s; }
html.js-anim .reveal.is-in .land-bubble--answer { animation-delay: 1.3s; }
html.js-anim .reveal.is-in .land-bubble--second.land-bubble--ask { animation-delay: 2.1s; }
html.js-anim .reveal.is-in .land-bubble--second.land-bubble--answer { animation-delay: 2.9s; }
@keyframes land-bubble { to { opacity: 1; transform: none; } }

/* ---- The MCP link: a line from the window into the program, labelled by the standard ---- */
.land-mcp {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-mono);
    font-size: 0.74rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    color: var(--accent);
}
.land-mcp-line { display: inline-block; width: 2.6rem; height: 2px; border-radius: 1px; background: var(--accent); }
.land-mcp-label { padding: 0.15rem 0.55rem; border: 1.5px solid var(--accent); border-radius: var(--radius-pill); }
/* Dropping from a window onto the screen under it, the link runs edge to edge: a line from the
   window's bottom to the label and a second one from the label to the screen's top. */
.land-mcp--down { flex-direction: column; gap: 0.3rem; }
.land-mcp--down .land-mcp-line,
.land-mcp--down::after { width: 2px; height: 1.6rem; }
.land-mcp--down::after { content: ""; border-radius: 1px; background: var(--accent); }

/* ---- The proof strip: four facts under the hero, no picture ---- */
.land-proof {
    position: relative;
    margin-top: -2rem;
    padding: 1.4rem 0;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
}
.land-proof-list {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.8rem 2.6rem;
    margin: 0;
    padding: 0;
    list-style: none;
    font-weight: 600;
}
.land-proof-list li { display: inline-flex; align-items: center; gap: 0.55rem; }
.land-proof-list svg { width: auto; height: 18px; flex-shrink: 0; color: var(--accent); }
.land-proof-list a { color: inherit; text-decoration: none; border-bottom: 1px solid var(--border-strong); }
.land-proof-list a:hover { border-color: var(--accent); }

/* ---- A block: the words beside the picture ---- */
.land-block {
    display: grid;
    grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
    gap: 2.5rem clamp(2rem, 4vw, 4rem);
    align-items: center;
}
.land-block--joined { padding-top: 0; }
.land-block-words .land-lead { margin: 0 0 1.4rem; }
.land-points {
    display: grid;
    gap: 0.7rem;
    margin: 0 0 1.4rem;
    padding: 0;
    list-style: none;
}
.land-points li { position: relative; padding-left: 1.4rem; color: var(--text-2); }
.land-points li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.5em;
    width: 0.55rem;
    height: 0.55rem;
    border: 1.5px solid var(--accent);
    border-radius: 50%;
}
@media (max-width: 900px) {
    .land-block { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
    .land-block-media { order: -1; }
}

/* ---- The assistant's block: the window, the link, then the station's own pages ---- */
/* The window and the MCP link under it share one width, so the link stands on the window's centre
   at every width and drops from it onto the screen it commands; it takes back the grid's gap on
   both sides, so its lines meet the window and the screen. */
.land-agent-stage { display: grid; gap: var(--agent-gap); --agent-gap: 1.2rem; --agent-window: 30rem; }
.land-agent-stage .land-assistant { width: 100%; max-width: var(--agent-window); }
.land-agent-stage .land-mcp--down { width: 100%; max-width: var(--agent-window); margin-block: calc(-1 * var(--agent-gap)); }
.land-agent-stage .land-still + .land-still { margin-top: 0; }

/* ---- The catalog: the installer's drivers, grouped as the program groups them ---- */
.land-catalog {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 1.6rem 2rem;
    padding: 1.8rem 2rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--card-radius);
    background: var(--surface);
    box-shadow: var(--shadow-md);
}
.land-catalog-group h3 {
    margin: 0 0 0.6rem;
    font-family: var(--font-mono);
    font-size: 0.78rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
}
.land-catalog-group ul { display: grid; gap: 0.3rem; margin: 0; padding: 0; list-style: none; font-weight: 550; }
@media (max-width: 700px) {
    .land-catalog { grid-template-columns: repeat(2, minmax(0, 1fr)); padding: 1.4rem 1.2rem; }
}

/* ---- A still of the station in a browser: the browser's bar over the picture ---- */
.land-browser-bar {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.45rem 0.8rem;
    border-bottom: 1px solid #2a2823;
    background: #1c1a17;
    color: #a39d90;
    font-family: var(--font-mono);
    font-size: 0.74rem;
}
.land-browser-dots { display: inline-flex; gap: 5px; }
.land-browser-dots i { width: 9px; height: 9px; border-radius: 50%; background: #3a362f; }
.land-browser-address {
    flex: 1;
    padding: 0.2rem 0.7rem;
    border-radius: var(--radius-pill);
    background: #121110;
    text-align: center;
}
/* Under the bar the page starts on its own line, the way a browser draws its viewport. */
.land-still--browser .land-screen img { border-top: 1px solid #2a2823; }

/* ---- "It runs on your computer": the head over the specification table ---- */
.land-included-head .land-lead { margin: 0; }


/* ---- "Also included": the specification table ----
   One row per subject: the key in mono, the value in one line of plain words, 1 px rules between the
   rows. On a phone the key stands over its value. */
.land-included-head { max-width: 52rem; margin: 0 0 2.2rem; }
.spec-table { width: 100%; border-collapse: collapse; border-top: 1px solid var(--border-strong); }
.spec-table th,
.spec-table td {
    padding: 1.1rem 0;
    border-bottom: 1px solid var(--border-strong);
    text-align: left;
    vertical-align: baseline;
}
.spec-table th {
    width: 30%;
    padding-right: 2rem;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
}
.spec-table td { font-size: 1.02rem; }

/* ---- The price, beside the short questions ----
   The price on the left, the way a datasheet closes on its ordering line: the free limit said
   calmly, then the figure, the button and the note on one ruled line. The questions on the right, in
   the site's own accordion. */
.land-price {
    display: grid;
    grid-template-columns: minmax(0, 6fr) minmax(0, 5fr);
    gap: 3rem clamp(2.5rem, 5vw, 5rem);
    align-items: start;
}
.price-sheet .land-h2 { margin-bottom: 1.1rem; }
.price-sheet-body { margin: 0 0 1.8rem; color: var(--text-2); }
.price-sheet-cta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.8rem 1.25rem;
    padding-top: 1.4rem;
    border-top: 1px solid var(--border-strong);
}
.price-sheet-cta .pricing-teaser-price { flex-basis: 100%; text-align: left; font-size: 1.1rem; }
.price-sheet-cta .muted { margin: 0; font-size: 0.88rem; }
.land-faq-title {
    margin: 0.4rem 0 1rem;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
}

/* ============================================================================
   Pricing teaser band (the integrator directory) + the landing's closing call
   ============================================================================ */
.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 3rem; }
.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; }
.land-final .contact-link { margin-top: 1.4rem; }

/* "Talk to us" (ContactLink): a plain link with the mail glyph, under the closing call and under
   the footer's brand. */
.contact-link { display: inline-flex; align-items: center; gap: 0.5rem; font-weight: 600; }
.contact-link svg { width: 17px; height: 17px; flex-shrink: 0; }

/* ============================================================================
   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; }
/* The address to write to reads as one of the footer's links, with the mail glyph in the accent. */
.footer-brand .contact-link { margin-top: 1rem; color: var(--text); font-size: 0.94rem; font-weight: 500; }
.footer-brand .contact-link svg { width: 16px; height: 16px; color: var(--accent); }
.footer-brand .contact-link:hover { color: var(--accent); text-decoration: none; }
.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) {
    /* The same take-back of the frame's opening air as above, at the narrow frame's own value. */
    .land-hero { margin-top: -2.75rem; padding: 3.75rem 0 4rem; }
    /* A narrow hero has no room for the aurora's drift: at the far end of it the glow would reach
       the box's side and be cut there. It stands still instead. */
    .land-hero::after { animation: none; }
    .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 2rem; }
    /* A block on a narrow screen stacks, the picture before the words (the block rules above). */
    .land-block--joined { padding-top: 0; }
    .land-price { grid-template-columns: minmax(0, 1fr); }
    .pricing-teaser { grid-template-columns: 1fr; padding: 2.2rem 1.8rem; }
    .footer-grid { grid-template-columns: 1fr 1fr; gap: 2rem; }
    .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; }
    .land-section { padding: 3.6rem 0; }
    .land-final { padding: 3.6rem 0 1rem; }
    .land-hero { padding-bottom: 3.4rem; }
    .video-shot { padding: 0.35rem; }
    .video-card { padding: 0; border-radius: var(--radius); }
    /* On a phone the clip is small: the controls shrink with it instead of covering it. */
    .video-controls { right: 0.6rem; bottom: 0.6rem; }
    .video-toggle, .video-expand { padding: 0.3rem 0.5rem; font-size: 0.7rem; }
    .land-claim--hero { font-size: clamp(2.2rem, 11vw, 2.8rem); }
    .land-catalog { grid-template-columns: minmax(0, 1fr); }
    /* The specification table on a phone: the key stands over its value. */
    .spec-table th, .spec-table td { display: block; width: auto; padding-right: 0; }
    .spec-table th { padding: 1rem 0 0.25rem; border-bottom: 0; }
    .spec-table td { padding-top: 0; }
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .faq-panel details.faq-item summary, .faq-panel details.faq-item summary::after { transition: none; }
    html.js-anim .reveal,
    html.js-anim .reveal .land-bubble {
        opacity: 1;
        transform: none;
        transition: none;
        animation: none;
    }
    .land-hero::after {
        animation: none;
        transition: none;
    }
    .video-controls { transition: 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-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); }
/* The questions of a page stand together in one panel (the pricing page's common questions, the
   landing's short ones beside the price), drawn as a pricing card is drawn (the same surface, edge,
   corner and lift) and ruled between questions like a datasheet's notes. Each question's mark is a
   ring in the accent, the instrument bubble's stroke, with a plus that turns into a cross while its
   answer is open. */
.faq-panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}
.faq-panel .faq-item { border: 0; border-radius: 0; background: transparent; }
.faq-panel .faq-item + .faq-item { border-top: 1px solid var(--border); }
.faq-panel .faq-item summary {
    padding: 1.2rem 1.5rem;
    font-size: 1.02rem;
    line-height: 1.4;
    transition: background-color .15s ease;
}
.faq-panel .faq-item summary:hover { background: var(--surface-2); }
.faq-panel .faq-item summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.faq-panel .faq-item summary::after {
    content: "";
    flex-shrink: 0;
    width: 1.6rem;
    height: 1.6rem;
    border: 1.5px solid var(--accent);
    border-radius: 50%;
    background:
        linear-gradient(var(--accent), var(--accent)) center / 0.7rem 1.5px no-repeat,
        linear-gradient(var(--accent), var(--accent)) center / 1.5px 0.7rem no-repeat;
    box-sizing: border-box;
    transition: transform .15s ease, background-color .15s ease;
}
.faq-panel .faq-item[open] summary::after { transform: rotate(45deg); background-color: var(--accent-soft); }
[data-theme="dark"] .faq-panel .faq-item[open] summary::after { background-color: var(--surface-2); }
.faq-panel .faq-item p {
    max-width: 44rem;
    padding: 0 4.6rem 1.35rem 1.5rem;
    color: var(--text-2);
    line-height: 1.6;
}
@media (max-width: 560px) {
    .faq-panel .faq-item summary { padding: 1.05rem 1.15rem; font-size: 1rem; }
    .faq-panel .faq-item p { padding: 0 1.15rem 1.2rem; }
}

/* Download page: the platform card. */
.download-card {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 1.5rem;
    align-items: center;
    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. */
.release-summary { 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; }

/* Installation guide (/downloads/install): the notices a direct download meets, one numbered step
   each, in words alone. The number wears the same soft accent plate as the download page's icon,
   never a solid block of brand color, which would read as the thing to press. The browser's
   sentences each open on the alert glyph in the danger red, the color a browser's own warning
   wears, so the reader recognises the notice; the words stay in the body color. */
.install-reassure { margin: 1rem 0 0; color: var(--muted); max-width: 44rem; text-wrap: pretty; }
.install-steps { list-style: none; counter-reset: install-step; padding: 0; margin: 0 0 2.5rem; display: grid; gap: 2.4rem; }
.install-steps > li { counter-increment: install-step; position: relative; padding-left: 3.2rem; }
.install-steps > li::before {
    content: counter(install-step);
    position: absolute;
    left: 0;
    top: 0;
    width: 2.2rem;
    height: 2.2rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
[data-theme="dark"] .install-steps > li::before { background: var(--surface-2); }
.install-steps h2 {
    font-family: var(--font-display);
    font-size: 1.35rem;
    letter-spacing: -0.015em;
    line-height: 2.2rem;
    margin: 0 0 0.35rem;
}
.install-steps p { margin: 0 0 0.8rem; }
.install-warning { display: flex; align-items: flex-start; gap: 0.6rem; }
.install-warning svg { flex: none; width: 20px; height: 20px; margin-top: 0.15em; color: var(--danger); }
@media (max-width: 560px) { .install-steps > li { padding-left: 2.8rem; } }

/* Store guide (/downloads/store): the same numbered steps, then the way past a Store that did not
   open: the button that opens it again and the listing on the Store's website side by side, and the
   installer a step down, for the computer whose Store is blocked. */
.store-retry { margin: 0 0 2.5rem; }
.store-retry h2 { font-family: var(--font-display); font-size: 1.35rem; letter-spacing: -0.015em; margin: 0 0 0.35rem; }
.store-retry p { margin: 0 0 1rem; max-width: 44rem; }
.store-retry-actions { display: flex; flex-wrap: wrap; gap: 0.9rem; align-items: center; margin: 0 0 1.2rem; }
.store-retry .store-installer { font-size: 0.95rem; }

/* Download page: the installer, for whoever looks for it there while the Store leads the Windows
   face; a quiet line under the card, never a second button beside the Store's. */
.dl-installer { margin: 1rem 0 0; font-size: 0.95rem; }

/* Release notes (/releases): one entry per version, newest first. */
.release-entry { 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-preview { margin: 0 0 2.5rem; padding: 1.25rem 1.5rem; border: 1px solid var(--border); border-radius: var(--radius); }
.release-preview > h2 { margin: 0 0 0.25rem; }
.release-preview > .muted { margin: 0 0 0.5rem; }
.release-preview .release-entry:first-of-type { padding-top: 1rem; }
.release-entry h3 a { text-decoration: none; color: inherit; }
.release-entry h3 a:hover { text-decoration: underline; }
.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);
    /* The library rounds the category blocks of the preferences panel with the button token too, and
       a pill on a block that clips its content cuts the first letter of the description's last line
       inside the corner: the blocks take the site's card radius, and the buttons keep the pill below. */
    --cc-btn-border-radius: var(--radius);
    --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);
}

html[data-theme] #cc-main .cm__btn,
html[data-theme] #cc-main .pm__btn,
html[data-theme] #cc-main .cm__btn--close,
html[data-theme] #cc-main .pm__close-btn {
    border-radius: var(--radius-pill);
}

/* ---- The notice: a bar along the bottom edge, on every page and every screen ----
   analytics.js asks the library for its bottom bar; the composition is the site's. The bar spans
   the screen and its row rides the one column (--content-max and the column's 1.5rem edges). It
   stands on the dark ground of the landing's product cards in both themes (--card-ground, with
   --card-ink for the words and --card-accent for focus), so it reads apart from the page and from
   the footer under it: on the paper the ground itself does it, and on the graphite of the dark
   theme, where the two grounds are close, a rule in the brand colour runs along its top edge. The
   library's --cc-* tokens are set again on the bar, because the page's own pair would not read on
   that ground. The title stands on a line of its own above the description, and the three choices
   keep the library's order and its equal weight: Accept all and Reject non-essential wear the same
   light fill and share one width, Configure is the quieter one, an outline. Where the row has room
   the words sit on the left and the choices on the right; where it has not, the choices drop under
   the words. Words longer than the room the bar gives them scroll inside it, their lower edge faded
   while more of them waits below (analytics.js marks it with data-more). Every selector starts
   html[data-theme] #cc-main .cm--bar, which outranks the vendored stylesheet loading after this
   one; the few !important ones answer the library's !important phone rules. */
html[data-theme] {
    --consent-bar-shadow: 0 -8px 28px rgba(16, 24, 40, 0.18);
}
html[data-theme="dark"] {
    --consent-bar-shadow: 0 -10px 28px rgba(0, 0, 0, 0.5);
}
html[data-theme] #cc-main .cm--bar {
    --cc-bg: var(--card-ground);
    --cc-primary-color: var(--card-ink);
    --cc-secondary-color: color-mix(in srgb, var(--card-ink) 80%, var(--card-ground));
    --cc-link-color: var(--card-accent);
    --cc-btn-primary-bg: var(--card-ink);
    --cc-btn-primary-color: var(--card-ground);
    --cc-btn-primary-border-color: var(--card-ink);
    --cc-btn-primary-hover-bg: #ffffff;
    --cc-btn-primary-hover-color: var(--card-ground);
    --cc-btn-primary-hover-border-color: #ffffff;
    --cc-btn-secondary-bg: transparent;
    --cc-btn-secondary-color: var(--card-ink);
    --cc-btn-secondary-border-color: color-mix(in srgb, var(--card-ink) 45%, transparent);
    --cc-btn-secondary-hover-bg: color-mix(in srgb, var(--card-ink) 12%, transparent);
    --cc-btn-secondary-hover-color: var(--card-ink);
    --cc-btn-secondary-hover-border-color: color-mix(in srgb, var(--card-ink) 70%, transparent);
    /* left: 0 and right: 0 already place it; the library's 100vw runs under a desktop scrollbar. */
    width: auto;
    border-top: 0;
    color: var(--cc-primary-color);
    box-shadow: var(--consent-bar-shadow);
}
html[data-theme="dark"] #cc-main .cm--bar {
    border-top: 2px solid var(--card-accent);
}
html[data-theme] #cc-main .cm--bar .cm__body {
    flex-flow: row wrap;
    align-items: center;
    gap: 1rem 2.5rem;
    max-width: var(--content-max);
    margin: 0 auto;
    padding: 1.375rem 1.5rem calc(1.375rem + env(safe-area-inset-bottom, 0px));
}
html[data-theme] #cc-main .cm--bar .cm__texts {
    display: block;
    flex: 1 1 32rem;
    min-width: 0;
    /* The title and four lines and the top of a fifth, which is what says there is more to scroll to. */
    max-height: 8.6em;
    max-height: 5.75lh;
    padding: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--card-ink) 35%, transparent) transparent;
    font-size: 0.9375rem;
    line-height: 1.5;
}
html[data-theme] #cc-main .cm--bar .cm__texts[data-more] {
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.1em), transparent);
    mask-image: linear-gradient(to bottom, #000 calc(100% - 1.1em), transparent);
}
html[data-theme] #cc-main .cm--bar .cm__title,
html[data-theme] #cc-main .cm--bar .cm__desc {
    display: block;
    margin: 0;
    padding: 0;
}
html[data-theme] #cc-main .cm--bar .cm__title {
    margin-bottom: 0.3rem;
    color: var(--cc-primary-color);
    font-size: 1.2em;
    font-weight: 650;
    line-height: 1.3;
    letter-spacing: -0.01em;
}
html[data-theme] #cc-main .cm--bar .cm__desc {
    max-height: none;
    overflow: visible;
    font-size: 1em;
    line-height: inherit;
}
/* One row of three: the equal pair in two columns of one width, Configure at its own. Alone on its
   line and wider than it, the row narrows and the labels wrap instead of running off the bar. */
html[data-theme] #cc-main .cm--bar .cm__btns {
    flex: 0 1 auto;
    max-width: 100%;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
    gap: 0.625rem;
    padding: 0;
}
html[data-theme] #cc-main .cm--bar .cm__btn-group {
    display: contents;
}
/* .cm__btns .cm__btn: one class more than the library's rule spacing two buttons of a bar. */
html[data-theme] #cc-main .cm--bar .cm__btns .cm__btn {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 8rem;
    min-height: 2.875rem;
    margin: 0;
    padding: 0.5rem 1.5rem;
    font-size: 0.9375rem;
    font-weight: 600;
    line-height: 1.2;
    overflow-wrap: anywhere;
}
html[data-theme] #cc-main .cm--bar .cm__btns .cm__btn:focus-visible {
    outline: 2px solid var(--card-accent);
    outline-offset: 2px;
}

/* A phone: the title on its line and the words under it, whole up to five lines, which holds the
   short notice in every language, and the three choices on one row under them in three columns of
   one width, where a long label wraps inside its own button. Compact, because the hero's call to
   action sits a little above the bar on a 375 × 812 phone; the sticky header carries the same
   action wherever the bar does reach it. */
@media (max-width: 640px) {
    html[data-theme] #cc-main .cm--bar .cm__body {
        align-items: stretch;
        gap: 0.625rem;
        padding: 0.75rem 1.25rem calc(0.75rem + env(safe-area-inset-bottom, 0px)) !important;
    }
    html[data-theme] #cc-main .cm--bar .cm__texts {
        flex: none;
        max-height: 8.75em;
        max-height: 6.25lh;
        font-size: 0.8125rem;
        line-height: 1.4;
    }
    html[data-theme] #cc-main .cm--bar .cm__title {
        margin-bottom: 0.15rem;
        font-size: 1.15em;
    }
    html[data-theme] #cc-main .cm--bar .cm__title,
    html[data-theme] #cc-main .cm--bar .cm__desc,
    html[data-theme] #cc-main .cm--bar .cm__btns {
        padding-left: 0 !important;
        padding-right: 0 !important;
    }
    html[data-theme] #cc-main .cm--bar .cm__btns {
        flex: none;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 0.5rem;
        border-top: 0 !important;
    }
    html[data-theme] #cc-main .cm--bar .cm__btn-group {
        display: contents !important;
    }
    html[data-theme] #cc-main .cm--bar .cm__btns .cm__btn {
        min-width: 0;
        min-height: 2.5rem;
        margin: 0 !important;
        padding: 0.3rem 0.4rem;
        font-size: 0.8125rem;
    }
}
/* The narrowest phones: the equal pair keeps its row and Configure takes the next one. */
@media (max-width: 340px) {
    html[data-theme] #cc-main .cm--bar .cm__btns {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    html[data-theme] #cc-main .cm--bar .cm__btns .cm__btn[data-role="show"] {
        grid-column: 1 / -1;
    }
}
/* A screen with little height, a phone turned sideways: the title and two lines under it, and beside
   a phone's width the words share the row with the choices wherever the two fit. */
@media (max-height: 480px) {
    html[data-theme] #cc-main .cm--bar .cm__body {
        padding-top: 0.625rem !important;
        padding-bottom: calc(0.625rem + env(safe-area-inset-bottom, 0px)) !important;
    }
    html[data-theme] #cc-main .cm--bar .cm__texts {
        max-height: 4.9em;
        max-height: 3.25lh;
    }
}
@media (max-height: 480px) and (min-width: 641px) {
    html[data-theme] #cc-main .cm--bar .cm__texts {
        flex-basis: 16rem;
    }
}

/* While the bar shows, the page stays usable under it: the document ends the bar's height later, so
   its last line scrolls clear of the bar, and whatever takes focus is scrolled clear of it too.
   analytics.js measures the bar into --consent-bar-h; show--consent is the class the library marks
   a shown notice with. */
html.show--consent {
    scroll-padding-bottom: var(--consent-bar-h, 0px);
}
html.show--consent body {
    padding-bottom: var(--consent-bar-h, 0px);
}

/* ============================================================================
   The site's chat (SiteChatPanel.razor, js/site-chat.js)
   ============================================================================ */
/* A quiet button in the bottom-right corner of every public page, and the side panel it opens, in
   the look of the landing's assistant window: the visitor's question in a bubble of the accent, the
   answer in one of the surface. Both are drawn hidden and stay so until the script shows them, and
   whatever the panel shows or hides it does through the hidden attribute, which the rules below
   always honour. While the cookie bar shows, the button and the panel's lower edge stand the bar's
   height higher (--consent-bar-h, measured by analytics.js), so neither covers the bar's choices
   and the bar covers neither. */
.site-chat [hidden] { display: none !important; }

.site-chat-launch {
    position: fixed;
    right: 1.25rem;
    bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px));
    z-index: 30;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 2.75rem;
    max-width: calc(100vw - 2.5rem);
    padding: 0 1.15rem 0 0.95rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-md);
    font: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    transition: border-color .12s ease, box-shadow .12s ease;
}
.site-chat-launch svg { flex: none; width: 18px; height: 18px; color: var(--accent); }
.site-chat-launch:hover { border-color: var(--accent); box-shadow: var(--shadow-lg); }
.site-chat-launch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
html.show--consent .site-chat-launch { bottom: calc(var(--consent-bar-h, 0px) + 1rem); }

.site-chat-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 40;
    display: flex;
    flex-direction: column;
    width: min(27rem, 100vw);
    border-left: 1px solid var(--border-strong);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    color: var(--text);
    font-size: 0.92rem;
}
html.show--consent .site-chat-panel { bottom: var(--consent-bar-h, 0px); }

/* The head: the chat's mark on a tile of the accent (the app tile's shape and colours), the title
   and what the answers come from, and the close button, on the inset tone the assistant window's
   title bar wears. */
.site-chat-head {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.9rem 0.8rem 0.85rem 1.15rem;
    border-bottom: 1px solid var(--border);
    background: var(--surface-2);
}
.site-chat-badge {
    flex: none;
    display: inline-grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 0.55rem;
    background: var(--accent);
    color: var(--accent-contrast);
}
.site-chat-badge svg { width: 20px; height: 20px; }
.site-chat-heading { flex: 1; min-width: 0; align-self: center; }
.site-chat-heading h2 { margin: 0 0 0.2rem; font-size: 1.02rem; }
.site-chat-heading p { margin: 0; color: var(--muted); font-size: 0.8rem; line-height: 1.4; }
.site-chat-close {
    flex: none;
    display: inline-grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--muted);
    cursor: pointer;
}
.site-chat-close svg { width: 18px; height: 18px; }
.site-chat-close:hover { background: var(--border); color: var(--text); }
.site-chat-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The conversation: it alone scrolls, so the field stays where the visitor types. */
.site-chat-scroll {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding: 1rem 1.15rem;
    overflow-y: auto;
    overscroll-behavior: contain;
}
.site-chat-log {
    display: grid;
    align-content: start;
    gap: 0.6rem;
}

/* The empty conversation: the mark, the welcome and the example questions, centred in the room the
   conversation will take; each example is a quiet button that reads as the question it sends. */
.site-chat-empty {
    margin: auto 0;
    display: grid;
    justify-items: center;
    gap: 0.85rem;
    padding: 0.5rem 0 1rem;
    text-align: center;
}
.site-chat-empty-mark {
    display: inline-grid;
    place-items: center;
    width: 3rem;
    height: 3rem;
    border-radius: 0.75rem;
    background: var(--accent-soft);
    color: var(--accent);
}
.site-chat-empty-mark svg { width: 26px; height: 26px; }
.site-chat-empty p { max-width: 30ch; margin: 0; color: var(--muted); line-height: 1.5; }
.site-chat-examples {
    display: grid;
    gap: 0.45rem;
    width: 100%;
    max-width: 20rem;
    margin: 0.2rem 0 0;
    padding: 0;
    list-style: none;
}
.site-chat-example {
    width: 100%;
    min-height: 2.6rem;
    padding: 0.5rem 0.95rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: 0.9rem;
    line-height: 1.35;
    text-align: center;
    cursor: pointer;
    transition: border-color .12s ease, background-color .12s ease;
}
.site-chat-example:hover { border-color: var(--accent); background: var(--accent-soft); }
.site-chat-example:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.site-chat-bubble {
    max-width: 92%;
    padding: 0.6rem 0.85rem;
    border-radius: 12px;
    line-height: 1.45;
    overflow-wrap: anywhere;
}
.site-chat-bubble p,
.site-chat-bubble ul,
.site-chat-bubble ol { margin: 0 0 0.5rem; }
.site-chat-bubble ul,
.site-chat-bubble ol { padding-left: 1.2rem; }
.site-chat-bubble li + li { margin-top: 0.15rem; }
.site-chat-bubble > :last-child { margin-bottom: 0; }
.site-chat-bubble code {
    padding: 0.05rem 0.3rem;
    border-radius: 4px;
    background: var(--surface);
    font-size: 0.85em;
}
.site-chat-bubble--ask {
    justify-self: end;
    background: var(--accent);
    color: var(--accent-contrast);
    border-bottom-right-radius: 4px;
    white-space: pre-wrap;
}
.site-chat-bubble--answer {
    justify-self: start;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-bottom-left-radius: 4px;
}
.site-chat-thinking { color: var(--muted); }
@media (prefers-reduced-motion: no-preference) {
    .site-chat-thinking { animation: site-chat-thinking 1.4s ease-in-out infinite; }
}
@keyframes site-chat-thinking { 50% { opacity: 0.45; } }

/* The pages an answer read, under it, each a link to the documentation in the page's language. */
.site-chat-sources {
    margin-top: 0.6rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--border);
    font-size: 0.82rem;
}
.site-chat-sources > span { display: block; margin-bottom: 0.2rem; color: var(--muted); font-weight: 600; }
.site-chat-sources ul { display: grid; gap: 0.15rem; margin: 0; padding: 0; list-style: none; }

/* The foot: the field, with a refusal and the way to start over above it and the link to the
   privacy policy, small and always there, under it. */
.site-chat-foot {
    display: grid;
    gap: 0.65rem;
    padding: 0.9rem 1.15rem calc(0.95rem + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--border);
}
.site-chat-note {
    margin: 0;
    padding: 0.55rem 0.75rem;
    border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border));
    border-radius: var(--radius-sm);
    background: var(--info-bg);
    font-size: 0.86rem;
    line-height: 1.45;
}
.site-chat-restart { justify-self: start; font-size: 0.88rem; font-weight: 600; }
.site-chat-form { display: grid; gap: 0.5rem; }
.site-chat-form textarea {
    min-height: 4.75rem;
    max-height: 10rem;
    resize: none;
    background: var(--bg);
    line-height: 1.45;
}
.site-chat-form-row { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; }
.site-chat-count { color: var(--muted); font-size: 0.78rem; font-variant-numeric: tabular-nums; }
.site-chat-send { min-height: 2.5rem; padding: 0 1.15rem; }
.site-chat-send svg { width: 16px; height: 16px; }
.site-chat-policy { margin: -0.2rem 0 0; font-size: 0.76rem; line-height: 1.3; }
.site-chat-policy a { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.site-chat-policy a:hover { color: var(--text); }

/* A phone: the button keeps to the corner and the panel takes the whole width. */
@media (max-width: 640px) {
    .site-chat-launch {
        right: 0.75rem;
        bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
    }
    html.show--consent .site-chat-launch { bottom: calc(var(--consent-bar-h, 0px) + 0.75rem); }
    .site-chat-panel { width: 100%; border-left: 0; }
    /* Sixteen pixels or more, or a phone zooms the page in when the field takes focus. */
    .site-chat-form textarea { font-size: 1rem; }
}

/* 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
   ============================================================================ */
/* The bar, the page and the footer all read the one column token, so widening the site is a matter
   of retuning that number here and nothing else: the tables, card grids and media in the column
   simply get more of it, while the reading measures (the prose measure, .land-lead, the section
   heads, the account forms) are indifferent to this block. Nothing below the breakpoint moves. */
@media (min-width: 1600px) {
    :root {
        --content-max: 1600px;
    }
}

/* ============================================================================
   The documentation's Learn entry (Pages/Docs/DocsHome.razor): what the program does in one
   sentence, the path of someone who starts, then one block per area of the program's Welcome page,
   each beside its scene, adapted from the one the Welcome page draws for it (learn-scenes.css)
   ============================================================================ */
.docs-learn-head { margin: 0 0 3.25rem; }
.docs-learn-head .land-kicker { margin-bottom: 0.7rem; }
.docs-learn-head .docs-lead { max-width: 62ch; margin-bottom: 1.1rem; }
.docs-learn > section > h2 {
    font-family: var(--font-display);
    font-size: clamp(1.45rem, 2.3vw, 1.85rem);
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.15;
    margin: 0 0 1.25rem;
}

/* ---- The path of someone who starts: three numbered steps, each the link to its page ---- */
.docs-learn-start { margin: 0 0 4.5rem; }
.docs-content .docs-steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 1rem;
}
.docs-content .docs-steps li { margin: 0; }
.docs-step {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    height: 100%;
    padding: 1.3rem 1.4rem 2.8rem;
    background: var(--accent-soft);
    border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
}
.docs-step:hover { border-color: var(--accent); text-decoration: none; }
.docs-step-number {
    display: inline-grid;
    place-items: center;
    width: 2rem;
    height: 2rem;
    margin-bottom: 0.4rem;
    border-radius: 50%;
    background: var(--accent);
    color: var(--accent-contrast);
    font-weight: 700;
    font-size: 0.95rem;
}
.docs-step-title { font-weight: 650; font-size: 1.1rem; color: var(--text); }
.docs-step-desc { font-size: 0.95rem; color: var(--muted); }
.docs-step-go { position: absolute; left: 1.4rem; bottom: 1rem; display: inline-flex; color: var(--accent); }
.docs-step-go svg { width: 18px; height: 18px; transition: transform .15s ease; }
.docs-step:hover .docs-step-go svg { transform: translateX(3px); }

/* ---- One block per area: the words on the left, the scene on the right ----
   The words take two fifths of the column and the scene the other three, in every area and always
   on the same side, so the words and the drawing read together. The scene is slimmed to read at
   that width with the site's own type (learn-scenes.css), never shrunk. On a column too narrow for
   the two side by side, the scene goes under the words at the column's width, so the area is named
   before it is shown. */
.docs-learn-areas { margin: 0 0 3.5rem; container: docs-areas / inline-size; }
.docs-area {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.25rem;
    padding: 2rem 0;
    border-top: 1px solid var(--border);
}
.docs-area-words { max-width: 64ch; }
.docs-area-scene { min-width: 0; max-width: 36rem; }
@container docs-areas (min-width: 48rem) {
    .docs-area { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); gap: 2rem; align-items: center; }
    .docs-area-scene { max-width: none; }
}
.docs-area-name {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 0.45rem;
    font-size: 0.92rem;
    font-weight: 650;
    color: var(--muted);
}
.docs-area-name svg { width: 18px; height: 18px; flex: none; color: var(--accent); }
.docs-content h3.docs-area-claim {
    font-family: var(--font-display);
    font-size: clamp(1.35rem, 2vw, 1.65rem);
    font-weight: 600;
    letter-spacing: -0.015em;
    line-height: 1.15;
    margin: 0 0 0.6rem;
}
.docs-area-lead { margin: 0 0 1rem; color: var(--text-2); text-wrap: pretty; }
.docs-area-links { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 1.25rem; margin: 0; }
.docs-area-reference { font-size: 0.92rem; color: var(--muted); text-decoration: underline; text-underline-offset: 3px; }
.docs-area-reference:hover { color: var(--text); }

/* ---- The reference: the pages no area above holds, and the way to every page ---- */
.docs-learn-reference { padding-top: 2.25rem; border-top: 1px solid var(--border); }
.docs-learn-reference .docs-card-grid { margin: 1rem 0 1.5rem; }

/* ---- A page's own videos, above its body (DocsArticle), played in the page (VideoPlayer) ---- */
.docs-content .docs-watch { margin: -1.25rem 0 2.25rem; }
.docs-watch-video { margin: 0 0 1.25rem; max-width: 46rem; }
.docs-video-meta { margin: 0.35rem 0 0; font-size: 0.85rem; color: var(--muted); }
.docs-watch-link { display: inline-flex; align-items: center; gap: 0.45rem; font-weight: 650; }
.docs-watch-link svg { width: 18px; height: 18px; flex: none; }

/* On a phone the rail stands above the article and would fill the whole first screen; on this
   entry the article comes first, so the reader meets where to start, and the rail follows it as
   the complete listing. The skip link still lands on the article. */
@media (max-width: 760px) {
    .site-column--rail:has(.docs-learn) > .docs-sidebar { order: 1; }
}

/* ---- A clip in a landing block: the Store's frame, like the stills beside it ----
   Three blocks play a clip instead of a still (LandingVideo); there the card wears the same
   brand-blue edge light as a still of the program, so the reader sees one family of pictures,
   and a still that follows a clip keeps the air two stills keep. */
.land-block-media .video-card {
    border: 1.5px solid color-mix(in srgb, var(--card-accent) 55%, transparent);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--card-accent) 12%, transparent),
        0 0 48px color-mix(in srgb, var(--card-accent) 26%, transparent),
        var(--card-shadow);
}
.land-block-media .video-shot + .land-still { margin-top: 1.4rem; }

/* ============================================================================
   Video players (Shared/VideoPlayer.razor, js/video-player.js): the video's still with a play
   button, a link to YouTube until it is pressed, then YouTube's privacy-enhanced player in the same
   16:9 frame, so nothing moves when it starts.
   ============================================================================ */
.video-player { position: relative; aspect-ratio: 16 / 9; border-radius: var(--radius-md); overflow: hidden; background: #000; box-shadow: var(--shadow-md); }
.video-player-start { position: absolute; inset: 0; display: block; color: #fff; text-decoration: none; }
.video-player-start img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .25s ease, filter .25s ease; }
.video-player-start::after { content: ""; position: absolute; inset: 0; background: linear-gradient(to top, rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0) 45%); pointer-events: none; }
.video-player-button {
    position: absolute; left: 50%; top: 50%; width: 4.5rem; height: 4.5rem; margin: -2.25rem 0 0 -2.25rem;
    display: grid; place-items: center; border-radius: var(--radius-pill);
    background: var(--accent); color: var(--accent-contrast); box-shadow: var(--shadow-lg);
    transition: transform .2s ease;
}
.video-player-button svg { width: 1.9rem; height: 1.9rem; margin-left: 0.2rem; }
.video-player-label { position: absolute; left: 1.1rem; right: 1.1rem; bottom: 0.9rem; z-index: 1; font-weight: 650; font-size: 1.05rem; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6); }
.video-player-start:hover img { transform: scale(1.02); filter: brightness(1.05); }
.video-player-start:hover .video-player-button { transform: scale(1.08); }
.video-player-start:focus-visible { outline: 3px solid var(--accent); outline-offset: -3px; }
.video-player-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.video-player-note { margin: 0.45rem 0 0; font-size: 0.8rem; color: var(--muted); }
@media (prefers-reduced-motion: reduce) {
    .video-player-start img, .video-player-button { transition: none; }
    .video-player-start:hover img, .video-player-start:hover .video-player-button { transform: none; }
}

/* ============================================================================
   The tutorial videos (Pages/Tutorials.razor): the complete course in one player with the panel
   that says what it holds beside it, across the whole column, then one row per series, four cards
   wide, scrolling sideways inside the row when it outgrows the column (Previous and Next shown
   only then, by video-player.js)
   ============================================================================ */
.tut-course + .tut-series, .tut-series + .tut-series { margin-top: 3.5rem; }
.tut-title { margin: 0; font-family: var(--font-display); font-size: clamp(1.35rem, 2.1vw, 1.7rem); font-weight: 600; letter-spacing: -0.01em; }
.tut-course > .tut-title { margin-bottom: 1rem; }
.tut-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.5rem 1.25rem; margin: 0 0 1rem; }

/* ---- The stage: the player in the larger share, the panel in the rest. The panel takes no height
   of its own (contain: size) and stretches to the player's, so its outline scrolls inside it rather
   than making the row taller; stacked on a narrow screen it takes its own height again. ---- */
.tut-stage { display: grid; grid-template-columns: minmax(0, 1.75fr) minmax(17rem, 1fr); gap: 1.5rem; align-items: stretch; }
.tut-stage-player { min-width: 0; }
.tut-now {
    display: flex; flex-direction: column; min-width: 0; contain: size; overflow: hidden;
    border: 1px solid var(--border); border-radius: var(--radius-md);
    background: var(--surface); box-shadow: var(--shadow-sm);
}
.tut-now-head { flex: none; padding: 1.35rem 1.5rem 1.1rem; }
.tut-now-item[hidden] { display: none; }
.tut-now-kicker { margin: 0 0 0.4rem; font-size: 0.75rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent); }
.tut-now-title { margin: 0 0 0.6rem; font-size: 1.2rem; line-height: 1.3; font-weight: 650; text-wrap: balance; }
.tut-now-text { margin: 0 0 0.75rem; font-size: 0.92rem; color: var(--muted); line-height: 1.55; text-wrap: pretty; }
.tut-now-links { display: flex; flex-wrap: wrap; gap: 0.35rem 1.1rem; }
.tut-now-link { font-size: 0.9rem; white-space: normal; }
.tut-outline { flex: 1; min-height: 0; overflow-y: auto; padding: 0.9rem 0.9rem 1rem; border-top: 1px solid var(--border); scrollbar-width: thin; }
.tut-outline-label { margin: 0 0.6rem 0.35rem; font-size: 0.75rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.tut-outline-series { margin: 0.5rem 0.6rem 0.25rem; font-size: 0.85rem; font-weight: 650; color: var(--text); }
.tut-outline-list { margin: 0; padding: 0; list-style: none; }
.text-page .tut-outline-list { max-width: none; }
.tut-lesson {
    display: flex; align-items: baseline; gap: 0.7rem; padding: 0.5rem 0.6rem; border-radius: var(--radius-sm);
    color: var(--text); text-decoration: none;
}
.tut-lesson:hover { background: var(--surface-2); }
.tut-lesson[aria-current] { background: var(--accent-soft); color: var(--accent); }
.tut-lesson-number { flex: none; min-width: 1.4rem; font-size: 0.85rem; font-weight: 650; color: var(--muted); font-variant-numeric: tabular-nums; }
.tut-lesson[aria-current] .tut-lesson-number { color: var(--accent); }
.tut-lesson-title { font-size: 0.92rem; font-weight: 600; line-height: 1.4; }
@media (max-width: 980px) {
    .tut-stage { grid-template-columns: minmax(0, 1fr); }
    .tut-now { contain: none; overflow: visible; }
    .tut-outline { overflow: visible; }
}

/* ---- A series: its cards in a row, four across the column ---- */
.tut-row-steps { display: inline-flex; gap: 0.4rem; margin-left: auto; }
.tut-row-step {
    display: inline-grid; place-items: center; width: 2.25rem; height: 2.25rem; padding: 0;
    border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
    background: var(--surface); color: var(--text); cursor: pointer;
}
.tut-row-step[hidden] { display: none; }
.tut-row-step:hover { border-color: var(--accent); color: var(--accent); }
.tut-row-step svg { width: 1.05rem; height: 1.05rem; }
.tut-row {
    --tut-gap: 1.5rem;
    /* --tut-cols is the row's own count up to four (Tutorials.razor), so a short row shares the
       column evenly and a long one shows four and scrolls. */
    display: grid; grid-auto-flow: column; gap: var(--tut-gap);
    grid-auto-columns: calc((100% - (var(--tut-cols, 4) - 1) * var(--tut-gap)) / var(--tut-cols, 4));
    margin: 0; padding: 0.25rem 0.1rem 0.9rem; list-style: none;
    overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scrollbar-width: thin;
}
.text-page .tut-row { max-width: none; }
@media (max-width: 1100px) { .tut-row { grid-auto-columns: calc((100% - (min(var(--tut-cols, 4), 3) - 1) * var(--tut-gap)) / min(var(--tut-cols, 4), 3)); } }
@media (max-width: 800px) { .tut-row { grid-auto-columns: calc((100% - var(--tut-gap)) / 2); } }
@media (max-width: 560px) { .tut-row { grid-auto-columns: 82%; } }
.tut-row > li { scroll-snap-align: start; margin: 0; min-width: 0; }
.tut-card { display: block; color: var(--text); text-decoration: none; }
.tut-card-still { position: relative; display: block; aspect-ratio: 16 / 9; border-radius: var(--radius-md); overflow: hidden; background: var(--surface-2); box-shadow: var(--shadow-sm); }
.tut-card-still img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .25s ease; }
/* In the still's corner, so it never covers the cover's own title. */
.tut-card-play {
    position: absolute; right: 0.55rem; bottom: 0.55rem; width: 2.4rem; height: 2.4rem;
    display: grid; place-items: center; border-radius: var(--radius-pill);
    background: rgba(0, 0, 0, 0.55); color: #fff; transition: background .2s ease;
}
.tut-card-play svg { width: 1.3rem; height: 1.3rem; margin-left: 0.15rem; }
.tut-card-title { display: block; margin: 0.7rem 0 0; font-weight: 650; line-height: 1.35; text-wrap: pretty; }
.tut-card:hover .tut-card-still img { transform: scale(1.03); }
.tut-card:hover .tut-card-play, .tut-card[aria-current] .tut-card-play { background: var(--accent); color: var(--accent-contrast); }
.tut-card:hover .tut-card-title { color: var(--accent); }
.tut-card[aria-current] .tut-card-still { outline: 3px solid var(--accent); outline-offset: -3px; }
.tut-card:focus-visible { outline: none; }
.tut-card:focus-visible .tut-card-still { outline: 3px solid var(--accent); outline-offset: -3px; }
.tut-card-desc {
    margin: 0.4rem 0 0; font-size: 0.9rem; line-height: 1.5; color: var(--muted);
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden;
}
.tut-card-meta { margin: 0.35rem 0 0; font-size: 0.85rem; }
@media (prefers-reduced-motion: reduce) {
    .tut-card-still img { transition: none; }
    .tut-card:hover .tut-card-still img { transform: none; }
    .tut-row { scroll-behavior: auto; }
}
