/*
 * ufoathome.org — the tool's own site.
 *
 * Light and dark, DETECTED and never picked: the same rule the components themselves follow for
 * language. There is no theme switch anywhere on this site, so every colour is a token defined
 * once for light and redefined once under `prefers-color-scheme: dark`, and nothing else in the
 * sheet names a colour directly.
 *
 * The scene the components draw stays dark in both, because it is a night sky and a sky that went
 * pale with the page would be lying about the subject — so `--stage-ground` is deliberately NOT
 * one of the tokens that flips.
 */

:root {
  color-scheme: light dark;

  --ink: #131a26;
  --ink-dim: #4c5a70;
  --ink-faint: #78859b;
  --ground: #f6f8fc;
  --ground-raised: #ffffff;
  --ground-sunken: #eaeff7;
  --line: #dde4ef;
  --line-strong: #c2ccdc;

  /* The amber a light seen at night has. On paper it has to darken to stay readable; on a solid
     button it stays the bright one, with dark text over it. */
  --glow: #9a6f12;
  --glow-solid: #f0bc44;
  --glow-on-solid: #1a1405;
  --glow-dim: #a07d29;
  --sky: #12608f;
  --sky-dim: #7ba8c4;

  --card-from: rgba(255, 255, 255, 0.9);
  --card-to: rgba(238, 243, 250, 0.9);
  --header-bg: rgba(246, 248, 252, 0.86);
  --backdrop: radial-gradient(120% 80% at 50% 120%, rgba(120, 160, 214, 0.22) 0%, transparent 60%),
              radial-gradient(80% 50% at 85% -10%, rgba(240, 188, 68, 0.16) 0%, transparent 70%);
  --ok: #1f7a44;
  --error: #c0392b;
  /* An attribute name in a code block, which --glow cannot be: that accent is tuned for prose at
     1rem and measures 3.9:1 on the sunken ground, where code is set at 0.82rem and so needs MORE
     contrast than prose, not less. Same hue, darkened until it clears 4.5:1. In dark mode --glow
     is already a pale gold and needs no help. */
  --code-attr: #8a5d00;
  --ok-bg: rgba(31, 122, 68, 0.12);
  --warn-bg: rgba(154, 111, 18, 0.14);
  --neutral-bg: rgba(120, 133, 155, 0.14);

  --prose: 40rem;
  --wide: 68rem;

  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #e8ecf4;
    --ink-dim: #a3adc2;
    --ink-faint: #6f7b93;
    --ground: #070a12;
    --ground-raised: #0e1422;
    --ground-sunken: #050710;
    --line: #1e2739;
    --line-strong: #2e3a52;

    --glow: #f5c451;
    --glow-solid: #f5c451;
    --glow-on-solid: #1a1405;
    --glow-dim: #a8873a;
    --sky: #7fd4ff;
    --sky-dim: #3d7f9e;

    --card-from: rgba(20, 27, 44, 0.7);
    --card-to: rgba(10, 14, 24, 0.7);
    --header-bg: rgba(7, 10, 18, 0.82);
    --backdrop: radial-gradient(120% 80% at 50% 120%, rgba(60, 92, 140, 0.28) 0%, transparent 60%),
                radial-gradient(80% 50% at 85% -10%, rgba(245, 196, 81, 0.07) 0%, transparent 70%);
    --ok: #8fd7a4;
    --error: #ff8a7a;
    --code-attr: var(--glow);
    --ok-bg: rgba(120, 200, 140, 0.14);
    --warn-bg: rgba(245, 196, 81, 0.14);
    --neutral-bg: rgba(140, 155, 185, 0.12);
  }
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 400 1.0625rem/1.65 var(--sans);
  -webkit-font-smoothing: antialiased;
}

/* The sky the whole page sits under: a horizon glow at the bottom, night above. Fixed, so the
   content scrolls through it rather than dragging it along. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: var(--backdrop);
  pointer-events: none;
}

a { color: var(--sky); text-decoration-color: var(--sky-dim); text-underline-offset: 0.15em; }
a:hover { color: var(--ink); text-decoration-color: currentColor; }

h1, h2, h3, h4 { font-family: var(--serif); font-weight: 600; line-height: 1.2; letter-spacing: -0.01em; }
h1 { font-size: clamp(2.1rem, 5.5vw, 3.4rem); margin: 0 0 0.4em; }
h2 { font-size: clamp(1.5rem, 3.2vw, 2rem); margin: 2rem 0 0.5em; }
h3 { font-size: 1.2rem; margin: 2rem 0 0.4em; }
p { margin: 0 0 1.1em; }
code, kbd { font-family: var(--mono); font-size: 0.875em; }
code { background: var(--ground-sunken); border: 1px solid var(--line); border-radius: 4px; padding: 0.1em 0.35em; }
kbd {
  background: var(--ground-raised); border: 1px solid var(--line-strong); border-bottom-width: 2px;
  border-radius: 4px; padding: 0.1em 0.4em; color: var(--ink);
}
pre {
  background: var(--ground-sunken); border: 1px solid var(--line); border-radius: 8px;
  padding: 1rem 1.1rem; overflow-x: auto; font-size: 0.85rem; line-height: 1.55; margin: 0 0 1.2em;
}
pre code { background: none; border: 0; padding: 0; font-size: inherit; }
hr { border: 0; border-top: 1px solid var(--line); margin: 3rem 0; }

/* ---------------------------------------------------------------- header */

.site-header {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap;
  padding: 0.7rem clamp(1rem, 4vw, 2.5rem);
  background: var(--header-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.site-header a { text-decoration: none; }
.brand {
  font-family: var(--serif); font-size: 1.15rem; font-weight: 600; color: var(--ink);
  display: inline-flex; align-items: baseline;
}
/* Optical spacing, not a flex gap: the @ is a letter inside the name, and a gap made it read as a
   word between two others. Slightly negative, because the serif's own @ is drawn well inside its
   advance width — its side bearings alone are already more air than the wordmark wants, so this
   trims a little off them and leaves the rest. */
.brand .at { color: var(--glow); margin: 0 -0.02em; }
.site-nav { display: flex; gap: 1.1rem; flex-wrap: wrap; margin-left: auto; align-items: center; }
.site-nav a { color: var(--ink-dim); font-size: 0.95rem; padding: 0.2rem 0; border-bottom: 2px solid transparent; }
.site-nav a:hover { color: var(--ink); }
.site-nav a[aria-current="page"] { color: var(--ink); border-bottom-color: var(--glow); }

/* ---------------------------------------------------------------- layout */

main { display: block; }
.wrap { max-width: var(--wide); margin: 0 auto; padding: 0 clamp(1rem, 4vw, 2.5rem); }
.prose { max-width: var(--prose); }
.prose-wide { max-width: 52rem; }
/* On a section's own wrap, the measure is narrowed WITHOUT narrowing the column. The rule above
   left it centring inside the viewport — .wrap keeps margin:0 auto — so a prose section began 8rem
   to the right of every plain one on the same page, its heading visibly indented from the page's
   own title. Constraining the children instead leaves the column where every other section's is,
   and lines all the headings up. Kept as-is anywhere else (a lede, a paragraph): those have no
   block children to constrain, and were never a column to begin with. */
.wrap.prose-wide { max-width: var(--wide); }
.wrap.prose-wide > * { max-width: 52rem; }
section.band { padding: 2rem 0; border-top: 1px solid var(--line); }
section.band:first-of-type { border-top: 0; }

.lede { font-size: 1.2rem; color: var(--ink-dim); line-height: 1.6; }
.small { font-size: 0.9rem; color: var(--ink-dim); }
.eyebrow {
  text-transform: uppercase; letter-spacing: 0.14em; font-size: 0.72rem; font-weight: 600;
  color: var(--glow-dim); margin: 0 0 0.8rem;
}
/* The way back to the documentation hub, sitting in the eyebrow slot of every page under it.
   Those pages are reached from the hub and the top navigation names only the hub, so without this
   a reader has nothing but the browser's own Back button. */
.crumb {
  color: var(--glow-dim); text-decoration: none; letter-spacing: inherit;
}
.crumb:hover { color: var(--glow); text-decoration: underline; }

/* ---------------------------------------------------------------- hero */

.hero { padding: clamp(1.75rem, 4vw, 3rem) 0 1.25rem; }
.hero h1 { max-width: 16ch; }
.hero .lede { max-width: 46ch; }
.hero-actions { display: flex; gap: 0.8rem; flex-wrap: wrap; margin-top: 1.8rem; }

.btn {
  display: inline-block; padding: 0.6rem 1.15rem; border-radius: 999px; font-weight: 600;
  font-size: 0.95rem; text-decoration: none; border: 1px solid var(--line-strong);
  color: var(--ink); background: var(--ground-raised); transition: border-color .15s, background .15s;
}
.btn:hover { border-color: var(--glow-dim); background: var(--ground-sunken); color: var(--ink); }
.btn-primary { background: var(--glow-solid); border-color: var(--glow-solid); color: var(--glow-on-solid); }
.btn-primary:hover { filter: brightness(1.08); color: var(--glow-on-solid); }

/* ---------------------------------------------------------------- component stage */

/* The card AROUND a player, not the sky inside it: the component's own toolbar sits in normal
   flow here and inherits the page's text colour, so painting this dark in light mode left dark
   text on a dark strip. The night belongs to the canvas, which paints its own. */
.stage {
  border: 1px solid var(--line-strong); border-radius: 12px; overflow: hidden;
  background: var(--ground-raised); margin: 1.5rem 0;
}
/* The editor draws its own frames and its own border; without a gutter the card's edge lands
   directly on them and the two read as one broken line. The carousel and the players are
   full-bleed on purpose — there the scene IS the content — so the padding is asked for rather
   than inherited. */
.stage-padded { padding: 0.9rem; }
.stage-padded rr0-sighting-editor { display: block; }
.stage rr0-sighting, .stage rr0-scene, .stage rr0-sighting-editor, .stage rr0-ufo { display: block; }
.stage-caption {
  padding: 0.7rem 1rem; border-top: 1px solid var(--line); color: var(--ink-dim);
  font-size: 0.87rem; background: var(--ground-raised);
}
/* Inside a padded card the caption is already inset and already on the card's own ground, so it
   needs neither the rule above it nor a background of its own. */
.stage-padded .stage-caption, .carousel .stage-caption {
  padding: 0.2rem 0 0; border-top: 0; background: none;
}
.loading { padding: 3rem 1rem; text-align: center; color: var(--ink-faint); font-size: 0.9rem; }

/* ---------------------------------------------------------------- cards */

.cards { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); margin: 1.5rem 0; }
.card {
  border: 1px solid var(--line); border-radius: 10px; padding: 1.1rem 1.2rem;
  background: linear-gradient(180deg, var(--card-from), var(--card-to));
}
.card h3 { margin: 0 0 0.35rem; font-size: 1.05rem; }
.card p { margin: 0; font-size: 0.92rem; color: var(--ink-dim); }
.card p + p { margin-top: 0.6rem; }

/* ------------------------------------------------------- the two things to do with it */

.uses { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); margin: 1.5rem 0; }
.use {
  display: block; text-decoration: none; color: inherit;
  border: 1px solid var(--line); border-radius: 12px; padding: 1.4rem 1.5rem;
  background: linear-gradient(180deg, var(--card-from), var(--card-to));
  transition: border-color .15s, transform .15s;
}
.use:hover { border-color: var(--glow-dim); color: inherit; transform: translateY(-2px); }
.use h3 { margin-top: 0; font-size: 1.25rem; }
.use p { color: var(--ink-dim); font-size: 0.95rem; }
.use-more { color: var(--glow); font-weight: 600; margin-bottom: 0; }

/* ---------------------------------------------------------------- carousel */

/* Padded like the editor's card, and for the same reason: the component draws its own frame, and
   the card's border landing directly on it read as one broken line. The scene keeps a rounded
   corner of its own so the two curves agree. */
.carousel .stage { margin-top: 0; padding: 0.9rem; }
/* Only the scene is the positioning context, so the chevrons sit against ITS edges and not against
   the caption's — a chevron centred on the whole card would drift as the caption's length changes
   from one reconstruction to the next. */
.carousel-viewport { position: relative; border-radius: 8px; overflow: hidden; }
.carousel-viewport rr0-sighting { display: block; }
.carousel-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  cursor: pointer; font: inherit; line-height: 1;
  width: 2.4rem; height: 2.4rem; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.25); background: rgba(7, 10, 18, 0.55);
  color: #fff; backdrop-filter: blur(4px); transition: background .15s, border-color .15s;
  z-index: 2;
}
/* Drawn, not typed. A "‹" carries its own side bearings and sits off the vertical centre of its
   own line box, differently in every font — centring it in a circle by flexbox centres the BOX and
   leaves the glyph visibly high and left. Two borders on a rotated square have no metrics to
   disagree with. */
.carousel-nav::before {
  content: ""; width: 0.5rem; height: 0.5rem;
  border-left: 2px solid currentColor; border-bottom: 2px solid currentColor;
}
.carousel-nav.is-prev::before { transform: rotate(45deg); margin-left: 0.18rem; }
.carousel-nav.is-next::before { transform: rotate(-135deg); margin-right: 0.18rem; }
.carousel-nav:hover { background: rgba(7, 10, 18, 0.8); border-color: var(--glow); }
.carousel-nav.is-prev { left: 0.6rem; }
.carousel-nav.is-next { right: 0.6rem; }

.carousel-dots {
  display: flex; justify-content: center; align-items: center; gap: 0.1rem; flex-wrap: wrap;
  padding: 0.7rem 0 0.3rem;
}
/* The BUTTON is a comfortable target and the dot inside it is small: seventeen dots five pixels
   apart are a row of things you cannot point at without hitting the neighbour. The visible mark
   stays the same size; only the thing you are aiming at grew. */
.carousel-dot {
  cursor: pointer; width: 1.5rem; height: 1.5rem; padding: 0; border: 0; background: none;
  display: flex; align-items: center; justify-content: center;
}
.carousel-dot::before {
  content: ""; width: 0.5rem; height: 0.5rem; border-radius: 50%;
  background: var(--line-strong); transition: background .15s, transform .15s;
}
.carousel-dot:hover::before { transform: scale(1.35); }
.carousel-dot[aria-current="true"]::before { background: var(--glow); transform: scale(1.35); }
/* While the reader is driving it, the sequence is visibly not. */
.carousel[data-auto="off"] .carousel-dot[aria-current="true"]::before { background: var(--sky); }

/* The edit link ends the description rather than sitting among the dots: it is about the
   reconstruction being described, not about moving between them. */
.carousel-edit { font-weight: 600; white-space: nowrap; }
/* The dash separates the two and belongs to neither, so it is a sibling rather than the link's own
   ::before — which would have made it part of what you click and part of what is underlined. */
.carousel-sep { color: var(--ink-faint); }

/* ---------------------------------------------------------------- demo catalogue */

.demo-group { margin: 3rem 0 0; }
.demo-group:first-child { margin-top: 0; }
.demo-group > h2 { margin-top: 0; }
.demo-group > p { color: var(--ink-dim); }
/* Small enough that several are on screen at once — the point of a catalogue is the mass of them,
   not one big one at a time. */
.demo-grid {
  display: grid; gap: 1rem; margin: 1.5rem 0 0;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
}
.demo-card {
  margin: 0; border: 1px solid var(--line); border-radius: 12px; overflow: hidden;
  background: linear-gradient(180deg, var(--card-from), var(--card-to));
  display: flex; flex-direction: column;
}
.demo-mount {
  aspect-ratio: 16 / 9; background: #050710; display: flex; align-items: center; justify-content: center;
}
.demo-mount rr0-scene { display: block; width: 100%; height: 100%; }
.demo-card figcaption { padding: 0.9rem 1rem 1rem; }
.demo-card h3 { margin: 0 0 0.3rem; font-size: 1.02rem; }
.demo-card figcaption p { margin: 0; font-size: 0.88rem; color: var(--ink-dim); line-height: 1.5; }
.demo-links { margin-top: 0.6rem !important; font-size: 0.85rem; }
.demo-links a { font-weight: 600; }
.demo-links span { color: var(--ink-faint); margin: 0 0.35rem; }

/* ---------------------------------------------------------------- section anchors */

/* Every section heading can be linked to; the mark that says so appears when the heading is
   pointed at, so it is there when wanted and invisible the rest of the time. It stays visible on
   keyboard focus, which is the only way somebody navigating without a pointer can reach it. */
.heading-anchor {
  margin-left: 0.4em; color: var(--ink-faint); text-decoration: none; font-weight: 400;
  opacity: 0; transition: opacity .15s;
}
h2:hover > .heading-anchor, h3:hover > .heading-anchor, .heading-anchor:focus-visible { opacity: 1; }
.heading-anchor:hover { color: var(--glow); }
/* A heading landed on from a link is not left under the sticky header. */
h2[id], h3[id] { scroll-margin-top: 4.5rem; }

/* ------------------------------------------------------- try-it blocks in the docs */

/* A worked example the reader can run rather than read: the field arrives holding a real recording,
   so the buttons do something before anything has been understood, and the reader's own URL goes in
   afterwards. */
.doc-try {
  border: 1px solid var(--line); border-radius: 10px; padding: 1rem 1.1rem;
  background: var(--ground-raised); margin: 1.2rem 0;
}
.doc-try label { display: block; font-weight: 600; margin-bottom: 0.4rem; font-size: 0.95rem; }
.doc-try input {
  width: 100%; font: inherit; font-size: 0.9rem; font-family: var(--mono);
  padding: 0.5rem 0.75rem; border-radius: 8px;
  border: 1px solid var(--line-strong); background: var(--ground); color: var(--ink);
}
.doc-try input:focus-visible { outline: 2px solid var(--glow); outline-offset: 1px; }
/* Marked from the script rather than by :invalid, which would also flag a field somebody is
   halfway through typing into. Both ends are marked — the field and what it produced — because
   the reader is looking at whichever of the two they last touched. */
.doc-try input[aria-invalid="true"] { border-color: var(--error); }
.doc-try .is-invalid { color: var(--error); font-style: italic; }
.doc-try [aria-disabled="true"] { opacity: 0.5; pointer-events: none; }
.doc-try button:disabled { opacity: 0.5; cursor: default; }
.doc-try pre { margin: 0.8rem 0 0; }
/* Wears the same frame as the <pre> it replaces once CodeMirror has loaded, so the snippet does not
   move or change shape under the reader when the highlighting arrives. Empty until then, hence the
   :empty rule — an 8px-tall bordered box under the field would look like a rendering failure. */
.code-view {
  margin: 0.8rem 0 0;
  background: var(--ground-sunken); border: 1px solid var(--line); border-radius: 8px;
  padding: 0.4rem 0.6rem; overflow: hidden;
}
.code-view:empty { display: none; }
.doc-try-out {
  margin: 0.8rem 0 0; overflow-x: auto;
}
.doc-try-out code {
  display: block; padding: 0.5rem 0.7rem; white-space: pre; font-size: 0.8rem;
}
.doc-try-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; margin: 0.9rem 0 0; }
.doc-try-label { font-weight: 600; margin-bottom: 0.2rem; }

/* ---------------------------------------------------------------- player form */

.player-inputs { display: grid; gap: 1.2rem; max-width: 46rem; margin: 0 0 1rem; }
.player-form label { display: block; font-weight: 600; margin-bottom: 0.4rem; }
.player-row { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.player-row input {
  flex: 1 1 18rem; min-width: 0; font: inherit; font-size: 0.95rem;
  padding: 0.55rem 0.8rem; border-radius: 999px;
  border: 1px solid var(--line-strong); background: var(--ground-raised); color: var(--ink);
}
.player-row input:focus-visible { outline: 2px solid var(--glow); outline-offset: 1px; }
.player-paste { border: 1px solid var(--line); border-radius: 10px; background: var(--ground-raised); }
.player-paste > summary { cursor: pointer; padding: 0.7rem 1rem; font-weight: 600; }
.player-paste-body { padding: 0 1rem 1rem; display: grid; gap: 0.8rem; justify-items: start; }
.player-paste-mount {
  width: 100%; border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
  background: var(--ground-sunken); min-height: 8rem;
}
.player-status { min-height: 1.4em; font-size: 0.92rem; color: var(--ink-dim); }
.player-status.is-error { color: var(--error); }

/* ---------------------------------------------------------------- steps */

/* A sequence, not a set of cards: these three are done in order, and cards said nothing about
   that. The row lays them out side by side while they fit and falls to a column when they do not,
   with the connector following — one rule, two orientations. */
.steps {
  list-style: none; counter-reset: step; margin: 1.5rem 0; padding: 0;
  display: flex; gap: 0;
}
/* `flex: 1 1 0`, not a width basis: a basis is along the MAIN axis, so `15rem` became a 240px
   minimum HEIGHT the moment the media query below turned the row into a column, and the steps
   stood a screen apart with the connector stretched between them. Sharing the width equally and
   letting the breakpoint do the switching is one rule for both orientations. */
.steps > li {
  counter-increment: step; position: relative; flex: 1 1 0; min-width: 0;
  padding: 0 1.4rem 0 0; margin: 0;
}
.steps > li::before {
  content: counter(step);
  display: flex; align-items: center; justify-content: center;
  width: 1.9rem; height: 1.9rem; border-radius: 50%; margin-bottom: 0.7rem;
  font: 600 0.95rem/1 var(--sans); color: var(--glow-on-solid); background: var(--glow-solid);
}
/* The line that makes it a sequence, drawn from each step's own number to the next one's. */
.steps > li::after {
  content: ""; position: absolute; left: 2.3rem; top: 0.95rem; right: 0.6rem; height: 2px;
  background: var(--line-strong);
}
.steps > li:last-child::after { display: none; }
.steps h3 { margin: 0 0 0.35rem; font-size: 1.05rem; }
.steps p { margin: 0; font-size: 0.93rem; color: var(--ink-dim); }

@media (max-width: 46rem) {
  .steps { flex-direction: column; gap: 1.2rem; }
  .steps > li { flex: none; padding: 0 0 0 3rem; }
  .steps > li::before { position: absolute; left: 0; top: 0; margin: 0; }
  .steps > li::after { left: 0.95rem; top: 2.2rem; right: auto; bottom: -1.2rem; width: 2px; height: auto; }
}

/* ---------------------------------------------------------------- lists, faq, roadmap */

ul.plain { list-style: none; padding: 0; }
ul.plain li { padding-left: 1.4rem; position: relative; margin-bottom: 0.55rem; }
ul.plain li::before { content: "·"; position: absolute; left: 0.45rem; color: var(--glow); font-weight: 700; }

.faq-item { border-top: 1px solid var(--line); padding: 1.4rem 0; }
.faq-item:last-child { border-bottom: 1px solid var(--line); }
.faq-item > h3 { margin-top: 0; }
.faq-item > *:last-child { margin-bottom: 0; }

/* The manual for a strip of eight panels, shaped like one. Without the page's script every
   .group-doc simply stands one after another, which is why the borders live on the block itself
   and the tab strip is added rather than assumed. */
.group-doc { border-top: 1px solid var(--line); padding: 1.4rem 0; }
.group-doc:last-child { border-bottom: 1px solid var(--line); }
.group-doc > h3 { margin-top: 0; }
.group-doc > *:last-child { margin-bottom: 0; }
.group-docs-tabs { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1.4rem; }
.group-docs-tab {
  font: inherit; font-weight: 600; font-size: 0.95rem; cursor: pointer;
  color: var(--ink-dim); background: transparent;
  border: 1px solid var(--line-strong); border-radius: 6px; padding: 0.4rem 0.9rem;
}
.group-docs-tab:hover { color: var(--ink); border-color: var(--ink-faint); }
.group-docs-tab[aria-selected="true"] { color: var(--ink); background: var(--ground-sunken); border-color: var(--ink-faint); }
.group-docs-tab:focus-visible { outline: 2px solid var(--glow); outline-offset: 1px; }
/* Once a tab strip names the eight, the heading inside the open one says the same word twice. It
   stays in the DOM all the same: it carries the id every #hash link to this section targets, and
   the anchor beside it. */
.group-docs.is-tabbed .group-doc { border: 0; padding: 0; }
.group-docs.is-tabbed .group-doc > h3 {
  position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%);
}

table { border-collapse: collapse; width: 100%; font-size: 0.9rem; margin: 0 0 1.2em; }
.table-scroll { overflow-x: auto; margin: 0 0 1.2em; }
.table-scroll table { margin: 0; min-width: 34rem; }
th, td { text-align: left; vertical-align: top; padding: 0.6rem 0.8rem; border-bottom: 1px solid var(--line); }
th { color: var(--ink); font-weight: 600; border-bottom-color: var(--line-strong); }
td { color: var(--ink-dim); }

.status { display: inline-block; font-size: 0.72rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.08em; padding: 0.15em 0.5em; border-radius: 4px; vertical-align: 0.1em; }
.status-done { background: var(--ok-bg); color: var(--ok); }
.status-next { background: var(--warn-bg); color: var(--glow); }
.status-later { background: var(--neutral-bg); color: var(--ink-faint); }

.timeline-step { border-left: 2px solid var(--line-strong); padding: 0 0 1.6rem 1.4rem; position: relative; }
.timeline-step:last-child { border-left-color: transparent; padding-bottom: 0; }
.timeline-step::before {
  content: ""; position: absolute; left: -0.36rem; top: 0.5rem; width: 0.6rem; height: 0.6rem;
  border-radius: 50%; background: var(--line-strong);
}
.timeline-step.is-done::before { background: var(--ok); }
.timeline-step.is-next::before { background: var(--glow); box-shadow: 0 0 0 4px var(--warn-bg); }
.timeline-step h3 { margin-top: 0; }
.timeline-step > *:last-child { margin-bottom: 0; }

/* ---------------------------------------------------------------- footer */

.site-footer {
  border-top: 1px solid var(--line); margin-top: 2.5rem; padding: 2rem 0 2.5rem;
  color: var(--ink-faint); font-size: 0.88rem;
}
.site-footer .wrap { display: flex; gap: 2rem; flex-wrap: wrap; justify-content: space-between; }
.site-footer a { color: var(--ink-dim); }
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li { margin-bottom: 0.35rem; }
.site-footer h4 { margin: 0 0 0.6rem; font-family: var(--sans); font-size: 0.75rem; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--ink-faint); }

/* ---------------------------------------------------------------- narrow screens */

@media (max-width: 34rem) {
  .demo-grid { grid-template-columns: 1fr; }
}

@media (max-width: 40rem) {
  body { font-size: 1rem; }
  .site-header { gap: 0.5rem; padding: 0.6rem 1rem; }
  .site-nav {
    gap: 0.9rem; width: 100%; margin-left: 0;
    /* Six tabs will not fit a phone's width, and wrapping them onto three lines eats the top of
       every page. One scrolling row keeps the header one line tall. */
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
  }
  .site-nav::-webkit-scrollbar { display: none; }
  .site-nav a { white-space: nowrap; font-size: 0.9rem; }
  .brand { font-size: 1.05rem; }
  section.band { padding: 2rem 0; }
  .hero { padding: 2rem 0 1.2rem; }
  h2 { margin-top: 1.8rem; }
  .lede { font-size: 1.08rem; }
  .hero-actions .btn { flex: 1 1 100%; text-align: center; }
  .carousel-nav { width: 2rem; height: 2rem; font-size: 1.2rem; }
  .carousel-nav.is-prev { left: 0.3rem; }
  .carousel-nav.is-next { right: 0.3rem; }
  .carousel-dot { width: 1.3rem; height: 1.3rem; }
  .stage-padded { padding: 0.5rem; }
  .stage { border-radius: 10px; }
  .cards, .uses { grid-template-columns: 1fr; }
  pre { font-size: 0.78rem; }
  .site-footer .wrap { gap: 1.5rem; }
}
