/* SCOPED. Generated from openpixels/website/index.html's <style> block by
   scripts/scope-css.py -- every selector anchored to `.site-chrome`, which
   the app is not inside.

   THE PALETTE IS ALIASED, NOT PICKED. Read this before editing a colour.

   This block used to hold hand-picked hexes -- `--text: #14161a`, `--muted:
   rgba(20,22,26,.70)` and so on -- flipped to light "because the site was
   written dark and the app runs light". That reasoning has one hole in it:
   the app does not run light. It follows the reader's OS, and on a dark one
   this block painted near-black text on the app's #111 ground. Measured on
   the live page: seven elements at a contrast ratio of 1.02-1.04 to 1, where
   1.00 is the same colour. The lede, the call to action and all five trust
   items were invisible. In the light theme the same block put the amber at
   1.78:1, which fails too -- it was chosen against the old site's near-black
   ground and never rechecked against a near-white one.

   So every name below is now an alias onto the design system's semantic
   layer, which is the layer that flips. A surface built on it goes dark for
   free; a surface built on raw hex does not go dark at all. Do not put a
   colour literal back in here -- re-point the alias instead. */

.site-chrome {
    --bg: transparent;
    --bg-2: transparent;
    --surface: var(--surface-card);
    --surface-2: var(--bg-subtle);
    --border: var(--border-hairline);
    --border-2: var(--border-strong);
    --text: var(--text-strong);
    --muted: var(--text-body);
    /* --text-muted, not --text-faint: the five trust items under the hero
       were the faint one, and measured 3.34:1 even in the light theme. */
    --faint: var(--text-muted);
    /* The one value still picked rather than aliased, and the one thing on
       this page that cannot be fixed here. `#f2a93b` is a raw hex in no
       palette -- ACCENT-SLOTS.md records openpixels as the suite's only
       product with no slot at all, and amber (slot 9) already belongs to
       openvideofix. It needs a slot minted by the deepening method before it
       can be re-pointed centrally. Until then it is at least made legible on
       both grounds: the light theme gets a darker amber, because the bright
       one reads at 1.78:1 on #f2f2f2.

       Darkened again on 20 September 2026, from #b06f05 to #996104. The
       previous value cleared AA on a white card (4.10:1) but not on the
       page's own #f2f2f2 ground (3.66:1), which is where the wordmark's full
       stop and the eyebrow bullets actually sit. The accent is text in three
       places -- that full stop, a highlighted word in a heading, and the "+"
       on a FAQ row -- so "it is only a logotype and therefore exempt" covers
       one of the three and not the others. Now 4.61:1 on the page and 5.16:1
       on a card; the dark theme keeps #f2a93b, which is 9.45:1 on #111. */
    --accent: #996104;
    --accent-ink: var(--text-inverse);
    --good: var(--success-fg);
    --radius: var(--radius-lg);
    --w: 1080px;
  }

/* Both dark blocks, character-identical, as colors.css requires: `.oa-dark`
   is opt-in on a container and `.oa-auto` follows the OS, and a rule added
   to one and not the other is a theme that works in exactly one of the two
   ways a reader can arrive at it. */
:root.oa-dark .site-chrome { --accent: #f2a93b; }
@media (prefers-color-scheme: dark) {
  :root.oa-auto .site-chrome { --accent: #f2a93b; }
}

/* The same two values again on :root, and the repetition is load-bearing.
   `--accent` was declared only inside `.site-chrome`, which the landing page's
   <body> does not carry -- so `.site-brand .dot { color: var(--accent) }` was
   an unresolved variable there. That is not a fallback to something sensible:
   the declaration is invalid at computed-value time and the element inherits
   instead, so the full stop took the wordmark's own colour and rendered black
   in light and white in dark. It has been live that way. The design system is
   explicit that the trailing stop is the product's accent and the only place
   the accent appears in the chrome, so a black one is the mark set wrong.

   Both definitions stay. Custom properties resolve from the nearest ancestor
   that declares one, and privacy.html carries `.site-chrome` on its own <body>
   plus an inline `:root { --accent: #f2a93b }` that loads after this file. Drop
   the `.site-chrome` copy and that inline one wins on sub-pages, putting the
   bright amber back on a light ground at 1.78:1 -- the exact measurement this
   file's header records as the reason for the darker value. So :root serves
   the landing page and .site-chrome serves the sub-pages, and neither can
   reach the other. */
:root { --accent: #996104; }
:root.oa-dark { --accent: #f2a93b; }
@media (prefers-color-scheme: dark) {
  :root.oa-auto { --accent: #f2a93b; }
}
/* The page's ground. Two bare element selectors in a stylesheet that is
   otherwise scoped to `.site-chrome` -- deliberately, because a ground cannot
   be scoped to part of a document.

   The landing page never needed it: the app's own app.css sets
   `body { background: var(--bg-page) }` and paints behind everything. But
   privacy.html loads this stylesheet and no app at all, so its html and body
   were both `rgba(0,0,0,0)` and the browser painted its own canvas behind
   them -- white, in both themes. In dark mode that was white text on white:
   the eyebrow, "Privacy policy", the date and every paragraph measured
   1.00:1, which is the same colour. Only the two elements with grounds of
   their own -- the header and the short-version card -- could be read. It was
   live that way, and it is exactly the failure the design skill warns about:
   the viewer paints its own ground behind the page, so a transparent body
   borrows the host's theme.

   On `html` and not on `body`, which is not a detail. privacy.html carries
   `class="site-chrome"` on its own `<body>` and its own
   `body { background: var(--bg) }` in an inline block that loads after this
   file -- and `.site-chrome` sets `--bg: transparent`, deliberately, because
   on the landing page the app paints the ground. So a `body` rule here loses
   twice over: on document order, and on a variable that is transparent by
   design. The `html` element has no such rule and its background is what
   paints the canvas regardless, so this is the one place the ground can be
   set without unpicking either of those decisions.

   `color-scheme` belongs with it: it is what makes the scrollbar and any form
   control follow the theme instead of staying light under a dark page. */
html { color-scheme: light dark; background: var(--bg-page); }

.site-chrome * { box-sizing: border-box; }
.site-chrome { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
.site-chrome, .site-chrome { margin: 0; padding: 0; }
.site-chrome {
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-sans);
    line-height: 1.6;
    font-size: 16px;
    /* `clip`, not `hidden`. They clip identically, but `hidden` makes this a
       scroll container -- the other axis computes to `auto` -- and a sticky
       child then sticks to *it* rather than to the viewport. The site header
       moved inside this element (APP-102) and stopped sticking: measured at
       top: -1200px after a 1200px scroll. `clip` creates no scroll container,
       so the header sticks and the page still cannot scroll sideways. */
    overflow-x: clip;
  }
.site-chrome img { max-width: 100%; height: auto; display: block; }
/* Links take the link token, not the accent. As the accent they measured
   3.66:1 on the light ground — twelve of them, every link in the footer and
   the body copy. The accent's job on this page is the full stop in the
   wordmark. */
/* Links in copy, not buttons. A button is an <a> too, and this selector is
   more specific than `.site-btn-primary` — so the moment the site header
   moved inside `.site-chrome` (APP-102) this rule started painting the
   primary button's label with the link colour, on top of the button's own
   inverse background. White on white in dark, near-black on near-black in
   light: 1.00:1 and 1.10:1, invisible in both, and the button is the one
   control the header exists for.

   `.btn` is excluded for the same reason: the page's own call to action,
   "Open OpenPixels", is an <a class="btn btn-primary"> and was equally
   invisible. Both button families are named rather than matched by a
   pattern, so a third one added later fails loudly here instead of quietly
   on the page. */
.site-chrome a:not(.site-btn):not(.btn) { color: var(--text-link); text-decoration-thickness: 1px; text-underline-offset: 2px; }
.site-chrome a:not(.site-btn):not(.btn):hover { color: var(--text-link-hover); }
.site-chrome .wrap { max-width: var(--w); margin: 0 auto; padding: 0 24px; }
/* ---------- header ---------- */
/* The one colour literal the aliasing pass missed, and it only bit on a page
   nobody re-checked. `rgba(14,15,17,0.82)` is a fixed dark bar: it does not
   flip, so in the light theme it kept a near-black ground while the text on
   it came from `--text`, which does flip -- near-black on near-black. On
   privacy.html the back link measured 1.08:1 and was invisible; the wordmark
   was 1.76:1. The landing page never showed it because its header carries
   `.site-header`, which sets its own ground two hundred lines below. Same
   expression as that one, so the two headers cannot drift apart again. */
.site-chrome header {
    position: sticky; top: 0; z-index: 50;
    background: color-mix(in srgb, var(--bg-page) 92%, transparent);
    backdrop-filter: saturate(160%) blur(12px);
    border-bottom: 1px solid var(--border);
  }
.site-chrome .bar { display: flex; align-items: center; justify-content: space-between; gap: 16px; height: 60px; }
.site-chrome .brand { display: flex; align-items: center; gap: 10px; font-size: 15.5px; font-weight: var(--weight-medium); letter-spacing: -0.01em; color: var(--text); text-decoration: none; }
.site-chrome .brand img { width: 24px; height: 24px; border-radius: 7px; }
.site-chrome .brand .muted { color: var(--faint); font-weight: 500; }
/* The accent's one job in the chrome. Same rule as `.site-brand .dot` on the
   landing page; a sub-page setting the name in type carries the full stop
   too, or the two headers read as different products. */
.site-chrome .brand .dot { color: var(--accent); }

/* Headings take the system's medium, not the browser's bold.

   Only three Geist faces ship -- 400, 500 and mono 400 -- because the full
   family is 3.9 MB. 700 is deliberately absent, so a heading left at the UA
   default does not fall back to a lighter real weight: the browser
   synthesises a bold by smearing the 500, which is the blurry, slightly wrong
   heading you see and cannot quite name.

   The landing page never showed it. It loads the app's bundle, which carries
   the design system's base layer and sets heading weights; privacy.html loads
   site-tokens.css, which is tokens and @font-face and deliberately nothing
   else -- that restraint is the whole point of tokens.css, and this is its
   cost on a page with no app under it. */
.site-chrome h1, .site-chrome h2, .site-chrome h3,
.site-chrome h4, .site-chrome h5, .site-chrome h6 { font-weight: var(--weight-medium); }
.site-chrome nav.links { display: flex; align-items: center; gap: 22px; }
.site-chrome nav.links a { color: var(--muted); font-size: 14px; text-decoration: none; }
.site-chrome nav.links a:hover { color: var(--text); }
@media (max-width: 760px) {.site-chrome nav.links { display: none; }}
.site-chrome .btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    border-radius: 9px; padding: 10px 18px; font-size: 14.5px; font-weight: var(--weight-medium);
    text-decoration: none; border: 1px solid transparent; white-space: nowrap;
    transition: transform .08s ease, background .15s ease, border-color .15s ease;
  }
.site-chrome .btn:active { transform: translateY(1px); }
/* The inverse pair, as every other primary button in the suite. On the
   accent it was 4.1:1, and it is the page's largest call to action. */
.site-chrome .btn-primary { background: var(--surface-inverse); color: var(--text-inverse); }
.site-chrome .btn-primary:hover { background: color-mix(in srgb, var(--surface-inverse) 86%, var(--bg-page)); }
.site-chrome .btn-ghost { background: transparent; color: var(--text); border-color: var(--border-2); }
/* These two were written for a dark-only site: white overlays are
   invisible on a light ground. */
.site-chrome .btn-ghost:hover { border-color: var(--text-strong); background: var(--surface-hover); }
.site-chrome .btn-lg { padding: 14px 26px; font-size: 16px; border-radius: 11px; }
/* ---------- hero ---------- */
.site-chrome .hero { padding: 76px 0 8px; }
.site-chrome .eyebrow {
    display: inline-flex; align-items: center; gap: 8px;
    font-size: 12px; letter-spacing: 0.09em; text-transform: uppercase;
    /* The accent stays on the dot below and off the words. That is the
       system's rule -- an accent is a tint chip or a dot, never a run of
       text -- and it is also the only way this line reaches AA: as amber it
       measured 1.78:1 on the light ground, and 3.66:1 even after darkening. */
    color: var(--text-muted); font-weight: var(--weight-medium); margin-bottom: 18px;
  }
.site-chrome .eyebrow::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.site-chrome h1 {
    font-size: clamp(34px, 6.2vw, 60px); line-height: 1.06;
    letter-spacing: -0.033em; font-weight: var(--weight-medium); margin: 0 0 22px; max-width: 17ch;
  }
.site-chrome h1 .hl { color: var(--accent); }
.site-chrome .lede { font-size: clamp(17px, 2.1vw, 20px); color: var(--muted); max-width: 60ch; margin: 0 0 30px; }
.site-chrome .cta-row { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 22px; }
.site-chrome .trust { display: flex; flex-wrap: wrap; gap: 8px 20px; list-style: none; padding: 0; margin: 0 0 8px; }
.site-chrome .trust li { display: flex; align-items: center; gap: 7px; font-size: 13.5px; color: var(--faint); }
.site-chrome .tick { width: 15px; height: 15px; flex: none; color: var(--good); }
/* ---------- section furniture ---------- */
/* The suite's rhythm: 56px on a phone, 96px from 900 up. 74 was neither,
   and audit-page.mjs measures it against 96 with an 8px tolerance. */
.site-chrome section { padding: var(--space-14) 0; }
@media (min-width: 900px) { .site-chrome section { padding: var(--space-24) 0; } }
.site-chrome section + section { border-top: 1px solid var(--border); }
.site-chrome h2 { font-size: clamp(25px, 3.6vw, 36px); line-height: 1.14; letter-spacing: -0.025em; margin: 0 0 14px; max-width: 22ch; }
.site-chrome .sub { color: var(--muted); font-size: 17px; max-width: 62ch; margin: 0 0 34px; }
.site-chrome .sub:last-child { margin-bottom: 0; }
/* ---------- compare slider ---------- */
.site-chrome .compare-shell { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 40px; align-items: center; }
@media (max-width: 880px) {.site-chrome .compare-shell { grid-template-columns: 1fr; gap: 28px; }}
.site-chrome .compare {
    position: relative; width: 100%; aspect-ratio: 576 / 720; max-width: 460px;
    border-radius: var(--radius); overflow: hidden; border: 1px solid var(--border);
    touch-action: pan-y; cursor: ew-resize; user-select: none; background: #000;
  }
.site-chrome .compare img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.site-chrome .compare .clip { position: absolute; inset: 0; width: 50%; overflow: hidden; }
.site-chrome .compare .clip img { width: var(--cw, 460px); max-width: none; }
.site-chrome .handle {
    position: absolute; top: 0; bottom: 0; left: 50%; width: 2px;
    background: rgba(255,255,255,0.92); transform: translateX(-1px); pointer-events: none;
  }
.site-chrome .knob {
    position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
    width: 38px; height: 38px; border-radius: 50%; background: rgba(255,255,255,0.95);
    display: grid; place-items: center; box-shadow: 0 2px 12px rgba(0,0,0,0.45); pointer-events: none;
  }
.site-chrome .knob svg { width: 19px; height: 19px; color: #111; }
.site-chrome .tag {
    position: absolute; bottom: 12px; font-size: 11.5px; font-weight: var(--weight-medium); letter-spacing: 0.04em;
    text-transform: uppercase; padding: 5px 10px; border-radius: 6px;
    background: rgba(0,0,0,0.62); color: #fff; backdrop-filter: blur(4px); pointer-events: none;
  }
.site-chrome .tag.l { left: 12px; }
.site-chrome .tag.r { right: 12px; }
.site-chrome .compare:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.site-chrome .measure { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin: 0 0 22px; }
.site-chrome .metric { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 15px 16px; }
.site-chrome .metric .n { font-size: 25px; font-weight: var(--weight-medium); letter-spacing: -0.02em; }
.site-chrome .metric .n.ok { color: var(--good); font-size: 19px; }
.site-chrome .metric .l { font-size: 12.5px; color: var(--faint); margin-top: 2px; }
.site-chrome .note { font-size: 13.5px; color: var(--faint); line-height: 1.55; }
/* ---------- feature grid ---------- */
.site-chrome .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 900px) {.site-chrome .grid { grid-template-columns: repeat(2, 1fr); }}
@media (max-width: 620px) {.site-chrome .grid { grid-template-columns: 1fr; }}
.site-chrome .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 22px; }
.site-chrome .card h3 { font-size: 16.5px; margin: 0 0 7px; letter-spacing: -0.01em; }
.site-chrome .card p { font-size: 14.5px; color: var(--muted); margin: 0; }
.site-chrome .ico { width: 30px; height: 30px; border-radius: 8px; background: rgba(242,169,59,0.13); color: var(--accent); display: grid; place-items: center; margin-bottom: 14px; }
.site-chrome .ico svg { width: 17px; height: 17px; }
/* ---------- split ---------- */
.site-chrome .split { display: grid; grid-template-columns: 1fr 1fr; gap: 52px; align-items: center; }
@media (max-width: 880px) {.site-chrome .split { grid-template-columns: 1fr; gap: 32px; }}
.site-chrome .shot { border-radius: var(--radius); border: 1px solid var(--border); overflow: hidden; background: var(--surface); }
.site-chrome .shot img { width: 100%; height: auto; }
.site-chrome .phone { max-width: 300px; margin: 0 auto; }
/* ---------- table ---------- */
.site-chrome .tablewrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); }
.site-chrome table { border-collapse: collapse; width: 100%; min-width: 560px; font-size: 14.5px; }
.site-chrome th, .site-chrome td { text-align: left; padding: 14px 18px; border-bottom: 1px solid var(--border); }
.site-chrome thead th { font-size: 12px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--faint); font-weight: var(--weight-medium); background: var(--bg-2); }
.site-chrome tbody tr:last-child td { border-bottom: none; }
.site-chrome td:first-child { color: var(--muted); }
/* The affirmative cells carry their meaning in the word and the weight, not
   in a hue. As --success-fg they measured 4.41:1 on the light ground — eight
   cells, just under AA, and the kind of miss no layout check sees. Green is
   still right for a status pill; it is not right for a column of body text
   somebody has to read across. */
.site-chrome .yes { color: var(--text-strong); font-weight: var(--weight-medium); }
.site-chrome .no { color: var(--faint); }
/* ---------- callout ---------- */
.site-chrome .callout { background: var(--surface); border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: 10px; padding: 22px 24px; }
.site-chrome .callout p { margin: 0 0 12px; color: var(--muted); font-size: 15.5px; }
.site-chrome .callout p:last-child { margin: 0; }
.site-chrome .callout strong { color: var(--text); }
/* ---------- faq ---------- */
.site-chrome .faq { display: grid; gap: 10px; }
.site-chrome details { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 0 20px; }
.site-chrome details[open] { background: var(--surface-2); }
.site-chrome summary { cursor: pointer; padding: 16px 0; font-weight: var(--weight-medium); font-size: 15.5px; list-style: none; display: flex; justify-content: space-between; align-items: center; gap: 16px; }
.site-chrome summary::-webkit-details-marker { display: none; }
.site-chrome summary::after { content: "+"; color: var(--accent); font-size: 20px; font-weight: 400; flex: none; }
.site-chrome details[open] summary::after { content: "\2212"; }
.site-chrome details p { margin: 0 0 16px; color: var(--muted); font-size: 15px; }
/* ---------- footer ---------- */
.site-chrome footer { border-top: 1px solid var(--border); padding: 44px 0 60px; }
.site-chrome .foot { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 24px; }
.site-chrome .foot-col h4 { font-size: 12px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--faint); margin: 0 0 12px; font-weight: var(--weight-medium); }
.site-chrome .foot-col a, .site-chrome .foot-col span { display: block; color: var(--muted); font-size: 14px; text-decoration: none; margin-bottom: 8px; }
.site-chrome .foot-col a:hover { color: var(--text); }
.site-chrome .colophon { margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--border); color: var(--faint); font-size: 13px; }

/* The app is a router in this same document. The copy is for someone who
   has just arrived; once they are working it goes, and in an installed
   PWA it never appears at all. */
/* The wrapper around the header and the hero groups them for the rule below
   and must generate no box of its own: a `position: sticky` child cannot
   outlive its parent's box, and this parent is only as tall as the header
   plus the hero — so a real box let the header scroll away at 751px instead
   of sticking. `display: contents` removes the box while leaving the element
   for the selector to hit, and the rule below still wins on specificity and
   hides the pair on every route but home. */
.site-chrome.chrome-top { display: contents; }

body[data-route]:not([data-route="home"]) .site-chrome {
  display: none;
}

@media (display-mode: standalone) {
  /* Installed, there is no marketing anything — including the header and
     hero added for the web page. The app draws its own again. */
  .site-chrome,
  .site-header,
  .site-hero {
    display: none;
  }
}

/* The site hero above the app is the page's h1 — the one in the served
   HTML, so the one a crawler reads, and the one the standard's shape check
   counts. The app's own tagline right under it would say the same thing
   twice. Installed to a home screen there is no marketing chrome at all,
   and the app needs its own. */
body[data-route="home"] #app .hero { display: none; }
@media (display-mode: standalone) {
  body[data-route="home"] #app .hero { display: revert; }
}

/* On the home route in a browser tab, the sections below say all of this at
   greater length -- the three promises are the "What it does" grid, the
   tips card is most of the FAQ. Carrying both is what pushed the app's
   first control down the page. */
body[data-route="home"] #app .promises,
body[data-route="home"] #app .tips-card {
  display: none;
}

@media (display-mode: standalone) {
  body[data-route="home"] #app .promises,
  body[data-route="home"] #app .tips-card {
    display: revert;
  }
}


/* ============================================================ site header
   1200 content, 64 tall, sticky, three zones. Measured by
   scripts/measure-header.mjs against the live URL, not by eye. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--bg-page) 92%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: var(--border-width, 1px) solid var(--border-hairline);
}
.site-header .container {
  max-width: 1200px;
  margin: 0 auto;
  height: 64px;
  padding: 0 var(--space-6);
  display: flex;
  align-items: center;
  gap: var(--space-8);
}
.site-brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: var(--text-strong);
  font-weight: var(--weight-medium);
  font-size: 17px;
  letter-spacing: var(--logo-tracking, -0.04em);
  line-height: 1;
  flex: none;
}
.site-brand svg { display: block; border-radius: 6px; }
.site-brand .prefix { color: var(--text-muted); }
/* The one place the accent appears in the chrome. */
.site-brand .dot { color: var(--accent); }

.site-nav {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  /* flex:1, not margin-right:auto. Both push the actions right today; only
     this one keeps doing it when the tab list is long enough to need the
     space, and it is what measure-header.mjs asserts. */
  flex: 1;
}
.site-nav a {
  font-size: 13px;
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
}
.site-nav a:hover { color: var(--text-strong); }

.header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex: none;
}
.header-actions > a:not(.site-btn) {
  font-size: 13px;
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
}
.header-actions > a:not(.site-btn):hover { color: var(--text-strong); }

/* One solid button and no more; everything else in the zone is a quiet
   text link. */
.site-btn {
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  font-size: 13px;
  font-weight: var(--weight-medium);
  text-decoration: none;
  white-space: nowrap;
}
.site-btn-primary {
  background: var(--surface-inverse);
  color: var(--text-inverse);
}
.site-btn-primary:hover {
  background: color-mix(in srgb, var(--surface-inverse) 86%, var(--bg-page));
}

/* The app's own controls, once they have been moved in here. */
#app-controls-slot { display: inline-flex; align-items: center; gap: var(--space-3); }
#app-controls-slot .ported-controls { display: inline-flex; align-items: center; gap: var(--space-3); }

/* Below 760 the tabs take a second row rather than disappearing: a phone
   with a brand, a button and no way to reach "Pricing" is the failure this
   replaces. The negative margin and matching padding let the row scroll
   edge to edge instead of stopping inside the gutter. */
@media (max-width: 760px) {
  .site-header .container {
    flex-wrap: wrap;
    height: auto;
    padding-block: 10px;
    gap: 0 var(--space-4);
  }
  .site-nav {
    order: 3;
    flex-basis: 100%;
    gap: 18px;
    padding-block: 8px;
    overflow-x: auto;
    scrollbar-width: none;
    margin-inline: calc(var(--space-6) * -1);
    padding-inline: var(--space-6);
  }
  .site-nav::-webkit-scrollbar { display: none; }
  .header-actions { margin-left: auto; }
}

/* The header covers the top of whatever an in-page link jumps to, so every
   target lands with its eyebrow tucked underneath it. A little more than
   the header's own height, so the heading has air. */
.site-chrome section, .site-hero, #app { scroll-margin-top: 5.5rem; }
@media (max-width: 760px) { .site-chrome section, .site-hero, #app { scroll-margin-top: 8rem; } }

/* ============================================================ hero
   No buttons: the app is directly beneath it. No accent word either. */
.site-hero { padding: var(--space-14) 0 var(--space-12); }
/* Centred. The three blocks keep their own measures -- 18ch on the headline,
   60ch on the lede -- so the lines stay a readable length; centring is
   `margin-inline: auto` on those measures rather than letting the text run the
   full 1200px, which would give a 100-character line nobody can track back. */
/* `--w` is declared on `.site-chrome` and the hero sits outside it, so without
   the fallback this max-width is invalid at computed-value time and the wrap
   runs the full window. Found by tisha. */
.site-hero .wrap { max-width: var(--w, 1080px); margin: 0 auto; padding: 0 var(--space-6); text-align: center; }
.site-hero h1 {
  font-size: clamp(32px, 5.4vw, 52px);
  line-height: 1.07;
  letter-spacing: -0.03em;
  font-weight: var(--weight-medium);
  color: var(--text-strong);
  margin: 0 auto var(--space-4);
  max-width: 18ch;
  /* Centred type shows a ragged last line far more than flush-left does. */
  text-wrap: balance;
}
.site-hero .lede {
  font-size: clamp(16px, 2vw, 19px);
  color: var(--text-body);
  max-width: 60ch;
  margin: 0 auto var(--space-4);
  text-wrap: pretty;
}
.site-hero .hero-note { font-size: 14px; color: var(--text-muted); margin: 0; }
@media (min-width: 900px) {
  .site-hero { padding: var(--space-24) 0 var(--space-20); }
}

/* ============================================================ footer */
.site-footer .footer-group h4 { color: var(--text-strong); }
.footer-base { color: var(--text-faint); }

/* Licence on the left, the X and GitHub icons on the right; wraps under on a phone. */
.footer-base.colophon { display: flex; justify-content: space-between; align-items: center; gap: 12px 16px; flex-wrap: wrap; }
.foot-social { display: flex; align-items: center; gap: 14px; }
.foot-social a { display: inline-flex; color: var(--text-muted); transition: color .15s; }
.foot-social a:hover { color: var(--text-strong); }

/* The language row. Eight links, each naming its own language in that
   language -- "Chinese (Simplified)" written in English is no use to the one
   person who needs the control. Wraps on a phone; the current language is
   marked rather than removed, so the row does not change width as you move
   between locales. */
.lang-row { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 0 0 var(--space-4); font-size: 13px; }
.lang-row a { color: var(--text-muted); text-decoration: none; }
.lang-row a:hover { color: var(--text-strong); text-decoration: underline; }
.lang-row a[aria-current="true"] { color: var(--text-strong); font-weight: var(--weight-medium); }
