/* The site shares the dashboard's palette so the product and its page look like
   one thing. Series colours are the validated dark-surface steps, and the accent
   is UI chrome only — text never wears a series colour. */
:root {
  color-scheme: dark;
  --bg: #0b1120; --panel: #111c30; --raised: #16233c; --border: #2b3a52;
  --border-soft: #1e2b42; --text: #e8eef8; --muted: #a9b8ce; --accent: #22d3ee;
  --feature: #3987e5; --bug: #d95926; --good: #0ca30c;
  --ease: cubic-bezier(.22, 1, .36, 1);
  --measure: 68ch;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0; color: var(--text);
  background:
    radial-gradient(1100px 560px at 10% -10%, #16264180 0%, transparent 60%),
    radial-gradient(900px 480px at 92% 2%, #1b2f4a66 0%, transparent 55%), var(--bg);
  background-attachment: fixed;
  font: 16px/1.65 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
}
.wrap { max-width: 1060px; margin: auto; padding: 0 24px; }
a { color: var(--accent); text-decoration-color: #22d3ee55; text-underline-offset: 3px; }
a:hover { text-decoration-color: currentColor; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

/* ---- top bar ---- */
.nav { position: sticky; top: 0; z-index: 10; backdrop-filter: blur(10px);
  background: #0b1120cc; border-bottom: 1px solid var(--border-soft); }
.nav .wrap { display: flex; align-items: center; gap: 20px; height: 58px; }
.brand { font-weight: 650; letter-spacing: -.02em; color: var(--text); text-decoration: none; }
.brand span { color: var(--accent); }
.nav nav { margin-left: auto; display: flex; gap: 20px; flex-wrap: wrap; }
.nav nav a { color: var(--muted); text-decoration: none; font-size: 14px; }
.nav nav a:hover { color: var(--text); }

/* ---- hero ---- */
.hero { padding: 76px 0 56px; }
.eyebrow { font-size: 12px; letter-spacing: .16em; text-transform: uppercase; color: var(--accent); margin: 0 0 14px; }
.hero h1 { font-size: clamp(34px, 5.2vw, 56px); line-height: 1.08; letter-spacing: -.035em;
  margin: 0 0 20px; max-width: 18ch; }
.hero p.lede { font-size: clamp(17px, 2vw, 20px); color: var(--muted); max-width: var(--measure); margin: 0 0 30px; }
.hero strong { color: var(--text); font-weight: 600; }
.cta { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
.btn { display: inline-flex; align-items: center; gap: 8px; min-height: 46px; padding: 0 20px;
  border-radius: 10px; font-size: 15px; font-weight: 550; text-decoration: none;
  border: 1px solid var(--border); background: var(--panel); color: var(--text);
  transition: transform .18s var(--ease), border-color .18s var(--ease), background .18s var(--ease); }
.btn:hover { transform: translateY(-2px); border-color: var(--accent); background: var(--raised); }
.btn.primary { background: linear-gradient(180deg, #1f4f86, #163a63); border-color: #2f5d86; }
.install { display: flex; align-items: center; gap: 10px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px; background: #060d1a; border: 1px solid var(--border-soft); border-radius: 10px;
  padding: 12px 16px; color: var(--text); overflow-x: auto; }
.install .prompt { color: var(--muted); user-select: none; }

/* ---- pipeline strip, the same idea the dashboard draws ---- */
/* A pipeline is one line by nature. Wrapping it stranded the last stage after a
   long connector, so on narrow screens it scrolls instead of breaking. */
.strip { display: flex; align-items: center; gap: 0; margin: 40px 0 0;
  overflow-x: auto; padding-bottom: 6px; scrollbar-width: thin; }
.strip .stage { border: 1px solid var(--border-soft); background: var(--panel); border-radius: 10px;
  padding: 9px 13px; font-size: 12px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted);
  white-space: nowrap; }
.strip .stage.end { border-color: #2f5d86; color: var(--text); box-shadow: 0 0 0 1px #1d3a56; }
.strip .link { flex: 1 1 18px; min-width: 18px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, #33486a, var(--accent), #33486a);
  background-size: 220% 100%; animation: flow 2.8s linear infinite; }
@keyframes flow { from { background-position: 100% 0; } to { background-position: -120% 0; } }

/* ---- sections ---- */
section { padding: 56px 0; border-top: 1px solid var(--border-soft); }
section h2 { font-size: clamp(24px, 3vw, 32px); letter-spacing: -.03em; margin: 0 0 14px; }
section > .wrap > p { color: var(--muted); max-width: var(--measure); }
.grid { display: grid; gap: 18px; margin-top: 32px; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.card { background: linear-gradient(180deg, var(--raised), var(--panel)); border: 1px solid var(--border);
  border-radius: 14px; padding: 22px; position: relative; overflow: hidden;
  transition: transform .18s var(--ease), border-color .18s var(--ease); }
.card:hover { transform: translateY(-3px); border-color: var(--accent); }
.card::after { content: ""; position: absolute; inset: 0 0 auto 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent); opacity: .4; }
.card h3 { margin: 0 0 8px; font-size: 16px; letter-spacing: -.01em; }
.card p { margin: 0; color: var(--muted); font-size: 14.5px; }
.card code { font-size: 13px; }

/* The claim the project actually rests on gets its own treatment. */
.feature-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 36px; align-items: center; margin-top: 32px; }
.quiet { color: var(--muted); }
pre, .term { background: #060d1a; border: 1px solid var(--border-soft); border-radius: 12px;
  padding: 18px; overflow-x: auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13.5px; line-height: 1.7; }
/* .term is a div, not a pre, so it needs this said out loud — without it the
   transcript collapsed into one run-on paragraph. Scrolling beats wrapping here:
   a command broken mid-line reads as two commands. */
.term { white-space: pre; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em;
  background: #ffffff0f; border-radius: 4px; padding: .12em .38em; }
pre code { background: none; padding: 0; font-size: inherit; }
.term .c { color: var(--muted); }
.term .ok { color: var(--good); }
.term .no { color: #ff8a8a; }
.term .k { color: var(--accent); }

footer { border-top: 1px solid var(--border-soft); padding: 34px 0 56px; color: var(--muted); font-size: 14px; }
footer a { color: var(--muted); }

/* ---- docs pages ---- */
.doc { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 44px; padding: 44px 0 72px; }
.toc { position: sticky; top: 78px; align-self: start; font-size: 14px; }
.toc p { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); margin: 0 0 12px; }
.toc a { display: block; padding: 6px 10px; margin-left: -10px; border-radius: 8px;
  color: var(--muted); text-decoration: none; }
.toc a:hover { background: #ffffff0a; color: var(--text); }
.toc a[aria-current="page"] { background: #16233c; color: var(--text); box-shadow: inset 2px 0 0 var(--accent); }
/* Prose holds the measure; figures, tables and code do not. Capping the whole
   column squeezed a wide flowchart down until its labels were unreadable. */
.prose { max-width: none; }
.prose > p, .prose > ul, .prose > ol, .prose > blockquote,
.prose > h1, .prose > h2, .prose > h3 { max-width: var(--measure); }
.prose h1 { font-size: clamp(28px, 4vw, 38px); letter-spacing: -.03em; margin: 0 0 18px; }
.prose h2 { font-size: 22px; letter-spacing: -.02em; margin: 40px 0 12px; padding-top: 14px; border-top: 1px solid var(--border-soft); }
.prose h3 { font-size: 17px; margin: 28px 0 8px; }
.prose table { width: 100%; border-collapse: collapse; margin: 18px 0; font-size: 14.5px; display: block; overflow-x: auto; }
.prose th, .prose td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--border-soft); vertical-align: top; }
.prose th { color: var(--muted); font-weight: 550; font-size: 12px; letter-spacing: .06em; text-transform: uppercase; }
.prose blockquote { margin: 18px 0; padding: 2px 0 2px 16px; border-left: 2px solid var(--border); color: var(--muted); }
.prose ul, .prose ol { padding-left: 22px; }
.prose li { margin: 6px 0; }
.prose img, .prose svg { max-width: 100%; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
@media (max-width: 860px) {
  .feature-row { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .doc { grid-template-columns: minmax(0, 1fr); gap: 26px; }
  .toc { position: static; }
  .toc a { display: inline-block; margin: 0 6px 6px 0; border: 1px solid var(--border-soft); }
  .hero { padding: 56px 0 40px; }
  .strip .stage { padding: 8px 11px; font-size: 11px; }
  .strip .link { flex: 0 0 16px; min-width: 16px; }
  .nav nav { gap: 14px; }
}

/* ============================================================
   Effects. Every one of them is armed by the `js` class, which
   site.js only adds when the visitor has not asked for reduced
   motion — so this whole block is inert for anyone who has.
   ============================================================ */

/* Reading progress. Purely ambient, hence aria-hidden in the markup. */
.progress { position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 20;
  background: linear-gradient(90deg, var(--feature), var(--accent));
  transform: scaleX(0); transform-origin: 0 50%; }

/* The bar sits under the nav until you scroll, then the nav lifts off the page. */
.nav { transition: box-shadow .25s var(--ease), background .25s var(--ease); }
body.scrolled .nav { background: #0b1120ee; box-shadow: 0 8px 28px -22px #000, 0 1px 0 #ffffff0d inset; }

/* Reveal. Without .js the elements never get the start state, so they simply show. */
.js .reveal { opacity: 0; transform: translateY(14px);
  transition: opacity .6s var(--ease), transform .6s var(--ease); transition-delay: var(--delay, 0ms); }
.js .reveal.in { opacity: 1; transform: none; }

/* The hero animates on load rather than on scroll — it is already in view. */
.js .hero > * { animation: rise .7s var(--ease) both; }
.js .hero > *:nth-child(2) { animation-delay: .06s; }
.js .hero > *:nth-child(3) { animation-delay: .12s; }
.js .hero > *:nth-child(4) { animation-delay: .18s; }
.js .hero > *:nth-child(5) { animation-delay: .24s; }
.js .hero > *:nth-child(6) { animation-delay: .30s; }
@keyframes rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }

/* A quiet sheen across the headline, once, so the first line has some life. */
.hero h1 { background: linear-gradient(100deg, var(--text) 30%, #bfe9f7 48%, var(--text) 66%);
  background-size: 280% 100%; -webkit-background-clip: text; background-clip: text; color: transparent; }
.js .hero h1 { animation: rise .7s var(--ease) both, sheen 2.6s var(--ease) .5s 1; }
@keyframes sheen { from { background-position: 120% 0; } to { background-position: -40% 0; } }

/* Cards light from the cursor. The fallback position keeps the glow centred
   before the pointer has ever been over the card. */
.card::before { content: ""; position: absolute; inset: -1px; border-radius: inherit;
  background: radial-gradient(240px circle at var(--mx, 50%) var(--my, 0%), #22d3ee24, transparent 70%);
  opacity: 0; transition: opacity .25s var(--ease); pointer-events: none; }
.card:hover::before { opacity: 1; }

/* The transcript replays in order: the sequence IS the argument, so showing it
   arrive beats showing it already finished. */
.js .term[data-replay] .line { opacity: 0; transform: translateY(4px); }
.js .term[data-replay].play .line { animation: line .34s var(--ease) both; animation-delay: var(--step, 0ms); }
@keyframes line { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

/* The strip's stages settle in one after another, left to right. */
.js .strip .stage { animation: rise .5s var(--ease) both; }
.js .strip .stage:nth-of-type(2) { animation-delay: .07s; }
.js .strip .stage:nth-of-type(3) { animation-delay: .14s; }
.js .strip .stage:nth-of-type(4) { animation-delay: .21s; }
.js .strip .stage:nth-of-type(5) { animation-delay: .28s; }
.js .strip .stage:nth-of-type(6) { animation-delay: .35s; }
.strip .stage { transition: border-color .2s var(--ease), color .2s var(--ease), transform .2s var(--ease); }
.strip .stage:hover { border-color: var(--accent); color: var(--text); transform: translateY(-2px); }

/* Copy button on the install line. */
.install { position: relative; }
.copy { margin-left: auto; flex: none; font: inherit; font-size: 12px; cursor: pointer;
  border: 1px solid var(--border); background: var(--panel); color: var(--muted);
  border-radius: 7px; padding: 5px 11px; min-height: 30px;
  transition: color .18s var(--ease), border-color .18s var(--ease); }
.copy:hover { color: var(--text); border-color: var(--accent); }
.copy[data-state="ok"] { color: var(--good); border-color: var(--good); }
.copy[data-state="fail"] { color: var(--bug); border-color: var(--bug); }

/* Buttons pick up a sweep on hover, clipped to the shape. */
.btn { position: relative; overflow: hidden; }
.btn::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 38%, #ffffff1f 50%, transparent 62%);
  transform: translateX(-120%); transition: transform .55s var(--ease); }
.js .btn:hover::after { transform: translateX(120%); }

@media (prefers-reduced-motion: reduce) {
  .progress { display: none; }
  .hero h1 { background: none; color: var(--text); }
}

/* Each transcript line is its own block so it can arrive on its own. */
.term .line { display: block; }

/* Rendered flowcharts sit as figures in the prose, on their own surface so the
   diagram reads as a panel rather than as loose lines on the page. */
.prose figure.mermaid { margin: 22px 0; padding: 18px; background: #0d1626;
  border: 1px solid var(--border-soft); border-radius: 12px; overflow-x: auto; text-align: center; }
/* No max-width on the SVG: with useMaxWidth off, mermaid sizes it to its own
   layout, and capping it here scaled the whole drawing — labels included — down
   to a few pixels. The figure scrolls instead. */
.prose figure.mermaid svg { max-width: none; height: auto; }
/* Until mermaid has run, the source is hidden rather than flashing as raw text. */
.prose figure.mermaid:not([data-processed]) { color: transparent; min-height: 80px; }
