/* Ostgate marketing site.
   Design canon: docs/SITE-CONTENT.md.
   Base tokens from design-md/vercel (mesh gradient deleted); mono-eyebrow and
   hairline-rule discipline grafted from design-md/opencode.ai; dark mode derived
   by mapping the light ladder onto design-md/linear.app's charcoal ladder.

   No webfonts and no external requests of any kind: a site whose pitch is "nothing
   leaves your machine" should not phone a CDN to render itself. */

/* ---------------------------------------------------------------- tokens */

:root {
  color-scheme: light dark;

  /* Warm ladder taken from design-md/claude: a tinted cream canvas, not grey-white.
     The neutral is what makes a page look designed; the accent alone never does. */
  --canvas: #fffefb;
  --canvas-soft: #faf9f5;
  --canvas-soft-2: #f5f0e8;

  /* The polarity-flipped band and every screenshot frame use design-md/warp's warm
     near-charcoal instead of a cold near-black, so the dark moments belong to the
     same family as the cream rather than reading as a different site. */
  --band: #2b2622;
  --band-ink: #f7f5f0;
  --band-body: #c9c0ad;
  --band-hairline: #3f3a36;

  --ink: #141413;
  --body: #3d3d3a;
  --mute: #6c6a64;
  --hairline: #e6dfd8;
  --hairline-strong: #cfc6b8;

  /* Verdigris: the patina on a bronze gate, and already the app's AccentColor.
     It is the single chromatic voice here, carrying the CTA, links and the mono
     eyebrows. Nothing else on the page is allowed colour. */
  --accent: #12736b;
  --accent-hover: #0d5c55;
  --primary: #12736b;
  --on-primary: #fffefb;
  --link: #12736b;

  /* State trio, shared with the app's asset catalogue. */
  --state-open: #198c48;
  --state-wait: #b4761a;
  --state-fail: #b4342a;

  --shadow-1: inset 0 0 0 1px #14141314;
  --shadow-2: 0 1px 1px #1414130a, 0 2px 2px #1414130f, inset 0 0 0 1px #14141314;
  --shadow-3: 0 2px 2px #1414130f, 0 8px 8px -8px #14141314, inset 0 0 0 1px #14141314;
  --shadow-4: 0 2px 2px #1414130f, 0 12px 24px -8px #14141317, inset 0 0 0 1px #14141314;

  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 40px;
  --s-8: 48px;
  --s-9: 64px;
  --s-10: 96px;

  --r-xs: 4px;
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 12px;
  --r-xl: 16px;
  --r-pill: 100px;

  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --measure: 68ch;
  --page: 1080px;
}

/* Dark: design-md/warp's warm charcoal, deepened by one step so the page sits
   below the cards. Shadows do not read on a dark ground, so elevation collapses to
   surface lift plus a hairline. */
@media (prefers-color-scheme: dark) {
  :root {
    --canvas: #2b2622;
    --canvas-soft: #211d1a;
    --canvas-soft-2: #332e2a;

    --band: #332e2a;
    --band-ink: #f7f5f0;
    --band-body: #aea69c;
    --band-hairline: #4a443f;

    --ink: #f7f5f0;
    --body: #c9c0ad;
    --mute: #aea69c;
    --hairline: #3f3a36;
    --hairline-strong: #554e48;

    --accent: #3fb8ac;
    --accent-hover: #5fd0c4;
    --primary: #3fb8ac;
    --on-primary: #141413;
    --link: #3fb8ac;

    --state-open: #4dcc73;
    --state-wait: #e8a55a;
    --state-fail: #e08078;

    --shadow-1: inset 0 0 0 1px #ffffff14;
    --shadow-2: inset 0 0 0 1px #ffffff14;
    --shadow-3: inset 0 0 0 1px #ffffff1f;
    --shadow-4: inset 0 0 0 1px #ffffff1f;
  }
}

/* ------------------------------------------------------------------ base */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* The nav is sticky at 64px, so in-page anchors must stop clear of it. Both
     properties are set: scroll-padding covers the scroll container, scroll-margin
     on the targets themselves is what actually lands correctly everywhere. */
  scroll-padding-top: 84px;
  /* An instant teleport between sections read as the page coming apart. Gliding
     there keeps the reader oriented, and the reduced-motion block below turns it
     back off for anyone who asked for that. */
  scroll-behavior: smooth;
}

section[id] { scroll-margin-top: 84px; }

body {
  margin: 0;
  background: var(--canvas-soft);
  color: var(--body);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 24px;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--ink); color: var(--canvas); }

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

img { max-width: 100%; height: auto; display: block; }

/* Display type: sentence case, weight 600 ceiling, negative tracking dialled to
   ~-2.5%. The canon's -5% is tuned to Geist's geometry and crushes counters on
   SF and Inter at hero size. */
h1, h2, h3 {
  color: var(--ink);
  font-weight: 600;
  margin: 0;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2.125rem, 1.4rem + 3vw, 3rem);
  line-height: 1.06;
  letter-spacing: -0.025em;
}

h2 {
  font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2rem);
  line-height: 1.2;
  letter-spacing: -0.025em;
}

h3 {
  font-size: 1.125rem;
  line-height: 1.4;
  letter-spacing: -0.02em;
}

p { margin: 0; max-width: var(--measure); }

code, kbd, samp {
  font-family: var(--mono);
  font-size: 0.8125em;
  background: var(--canvas-soft-2);
  border: 1px solid var(--hairline);
  border-radius: var(--r-xs);
  padding: 0.1em 0.35em;
  color: var(--ink);
}

:focus-visible {
  outline: 2px solid var(--link);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: var(--s-4);
  top: var(--s-4);
  z-index: 10;
  background: var(--canvas);
  color: var(--ink);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-3);
}

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

.wrap {
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--s-5);
}

/* Deliberately tighter than the canon's 192px section rhythm. For this audience
   a lot of white space reads as a lack of content, not as confidence. */
section {
  padding-block: clamp(var(--s-7), 4.2vw, var(--s-9));
  border-top: 1px solid var(--hairline);
}

/* Mono eyebrow: the voice of the platform, and the cheap honest place to put
   technical specifics. Never used for body prose. */
.eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 var(--s-4);
}

.lede {
  font-size: 1.125rem;
  line-height: 1.6;
  margin-top: var(--s-4);
}

.section-head { margin-bottom: var(--s-6); }
.section-head p { margin-top: var(--s-3); }

/* ------------------------------------------------------------------ nav */

.nav {
  position: sticky;
  top: 0;
  z-index: 5;
  background: color-mix(in srgb, var(--canvas-soft) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--hairline);
}

.nav-inner {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  height: 64px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--ink);
  font-weight: 600;
  letter-spacing: -0.02em;
  font-size: 1rem;
}
.brand:hover { text-decoration: none; }
/* Glyph in verdigris, wordmark in ink: the mark is the only place the accent
   appears without being an action, which is what makes it read as a mark. */
.brand svg { width: 20px; height: 20px; color: var(--accent); }

.nav-links {
  display: flex;
  align-items: center;
  gap: var(--s-1);
  margin-left: auto;
}

/* `:not(.btn)` matters: a bare `.nav-links a` rule is more specific than `.btn-nav`
   and would repaint the CTA's label in the muted body colour. */
.nav-links a:not(.btn) {
  color: var(--body);
  font-size: 14px;
  letter-spacing: -0.01em;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
}
.nav-links a:not(.btn):hover {
  color: var(--ink);
  background: var(--canvas-soft-2);
  text-decoration: none;
}

/* Below tablet width the five nav items plus the CTA do not fit, and the CTA is
   what gets pushed off the edge. Drop the three marketing links: every one of
   them is a jump to a section the visitor scrolls past anyway, and keep the
   legal link and the CTA, so Privacy stays reachable from the page chrome rather
   than only from the footer, which is what OAuth verification looks for. */
@media (max-width: 720px) {
  .nav-secondary { display: none; }
  .nav-inner { gap: var(--s-3); }
}

/* Nav-scale buttons use the 6px radius. The 100px marketing pill lives only in
   the hero and download bands: the canon forbids mixing the two scales in one
   field of view, so the nav CTA is deliberately the small one. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  font-family: inherit;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid transparent;
  white-space: nowrap;
}

.btn-nav {
  height: 32px;
  padding-inline: var(--s-3);
  font-size: 14px;
  border-radius: var(--r-sm);
  background: var(--primary);
  color: var(--on-primary);
}
.btn-nav:hover { text-decoration: none; opacity: 0.88; }

.btn-lg {
  height: 48px;
  padding-inline: var(--s-5);
  font-size: 16px;
  border-radius: var(--r-pill);
  background: var(--primary);
  color: var(--on-primary);
}
.btn-lg:hover { text-decoration: none; opacity: 0.88; }

.btn-lg-secondary {
  background: var(--canvas);
  color: var(--ink);
  border-color: var(--hairline-strong);
}
.btn-lg-secondary:hover { background: var(--canvas-soft-2); }

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

.hero {
  border-top: 0;
  padding-block: clamp(var(--s-8), 5.5vw, var(--s-10));
}
.hero h1 { max-width: 20ch; }
.hero .lede { max-width: 58ch; }

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-top: var(--s-7);
}

.hero-meta {
  font-family: var(--mono);
  font-size: 12px;
  line-height: 20px;
  color: var(--mute);
  margin-top: var(--s-5);
}

/* The single technical flourish on the page: a real command, framed like a
   terminal. It does more for credibility with a DevOps reader than any adjective,
   and it is the payoff line of the ProxyCommand feature. */
.cmd {
  margin-top: var(--s-6);
  background: var(--band);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-4);
  padding: var(--s-4) var(--s-5);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 22px;
  color: var(--band-ink);
  overflow-x: auto;
  max-width: 44rem;
}
.cmd .prompt { color: var(--accent); user-select: none; }
.cmd .comment { color: var(--band-body); }

/* --------------------------------------------------- requirements strip */

.strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 1px;
  background: var(--hairline);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  overflow: hidden;
}

.strip div {
  background: var(--canvas);
  padding: var(--s-5);
}

.strip dt {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
  margin-bottom: var(--s-2);
}

.strip dd {
  margin: 0;
  color: var(--ink);
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: -0.01em;
}

/* -------------------------------------------------------------- feature */

.rows {
  border-top: 1px solid var(--hairline);
}

.row {
  display: grid;
  grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);
  gap: var(--s-5);
  padding-block: var(--s-5);
  border-bottom: 1px solid var(--hairline);
  align-items: baseline;
}

.row h3 { font-size: 1rem; letter-spacing: -0.015em; }
.row p { font-size: 15px; line-height: 24px; }

@media (max-width: 720px) {
  .row { grid-template-columns: 1fr; gap: var(--s-2); }
}

/* ---------------------------------------------------------- screenshots */

.shots {
  display: grid;
  gap: var(--s-5);
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

/* Screenshots carry the whole decorative budget of this page: the canon's mesh
   gradient was deleted and this is what replaces its job. Frame is ink-dark in
   both polarities so the app's own chrome is the thing that reads. */
.shot {
  margin: 0;
  background: var(--band);
  border-radius: var(--r-xl);
  padding: var(--s-3);
  box-shadow: var(--shadow-3);
}

.shot img,
.shot .placeholder {
  border-radius: var(--r-md);
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  display: block;
}

.shot .placeholder {
  background: #0b0b0b;
  border: 1px dashed #3a3a3a;
  color: #7a7a7a;
  font-family: var(--mono);
  font-size: 12px;
  display: grid;
  place-content: center;
  text-align: center;
  padding: var(--s-4);
}

.shot figcaption {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #8a8a8a;
  padding: var(--s-3) var(--s-2) var(--s-1);
}

/* -------------------------------------------------------- flipped band */

/* Switching the surface to the deep ink is the chief depth cue between bands. */
.band {
  background: var(--band);
  color: var(--band-body);
  border-top: 0;
}
.band h2, .band h3 { color: var(--band-ink); }
.band .eyebrow { color: var(--band-body); }
.band code { background: #ffffff14; color: var(--band-ink); }

.steps {
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  background: var(--band-hairline);
  border: 1px solid var(--band-hairline);
  border-radius: var(--r-lg);
  overflow: hidden;
}

.steps > div {
  background: var(--band);
  padding: var(--s-6);
}

.steps .n {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--band-body);
  margin-bottom: var(--s-3);
}

.steps h3 { margin-bottom: var(--s-2); font-size: 1rem; }
.steps p { font-size: 14px; line-height: 22px; max-width: none; }

/* ------------------------------------------------------- scope table */

.table-scroll { overflow-x: auto; }

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  line-height: 22px;
  min-width: 34rem;
}

th, td {
  text-align: left;
  vertical-align: top;
  padding: var(--s-4);
  border-bottom: 1px solid var(--hairline);
}

thead th {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
  font-weight: 400;
  border-bottom: 1px solid var(--hairline-strong);
}

tbody th {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  color: var(--ink);
  white-space: nowrap;
  padding-right: var(--s-6);
}

/* On a phone the scope table becomes stacked blocks rather than a sideways scroll.
   This is the section a verification reviewer reads most carefully, so it must not
   be the one that requires dragging the page sideways to finish a sentence. */
@media (max-width: 720px) {
  .table-scroll { overflow-x: visible; }
  table { min-width: 0; display: block; }
  thead { display: none; }
  tbody, tr, th, td { display: block; }
  tr { border-bottom: 1px solid var(--hairline); padding-block: var(--s-4); }
  tr:last-child { border-bottom: 0; }
  tbody th {
    padding: 0 0 var(--s-2);
    white-space: normal;
    border-bottom: 0;
  }
  td { padding: 0; border-bottom: 0; }
}

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

.cards {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}

.card {
  background: var(--canvas);
  border-radius: var(--r-md);
  padding: var(--s-5);
  box-shadow: var(--shadow-2);
}
.card h3 { font-size: 0.9375rem; margin-bottom: var(--s-2); letter-spacing: -0.01em; }
.card p { font-size: 14px; line-height: 22px; }

/* Capped near the reading measure: a full-bleed box holding a 68ch paragraph reads
   as a layout mistake rather than as emphasis. */
.note {
  background: var(--canvas-soft-2);
  border-radius: var(--r-md);
  padding: var(--s-5);
  box-shadow: var(--shadow-1);
  margin-top: var(--s-6);
  max-width: calc(var(--measure) + var(--s-5) * 2);
}
.note p { font-size: 14px; line-height: 22px; }
.note p + p { margin-top: var(--s-3); }

/* ----------------------------------------------------------------- faq */

details {
  border-bottom: 1px solid var(--hairline);
  padding-block: var(--s-4);
}
details summary {
  cursor: pointer;
  color: var(--ink);
  font-weight: 500;
  font-size: 15px;
  letter-spacing: -0.01em;
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: var(--s-4);
}
details summary::-webkit-details-marker { display: none; }
details summary::after {
  content: "+";
  font-family: var(--mono);
  color: var(--mute);
  flex: none;
}
details[open] summary::after { content: "–"; }
details p { margin-top: var(--s-3); font-size: 14px; line-height: 22px; }

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

footer {
  border-top: 1px solid var(--hairline);
  padding-block: var(--s-8);
  background: var(--canvas-soft);
}

.footer-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-5);
  align-items: baseline;
  justify-content: space-between;
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-5);
  font-size: 14px;
}
.footer-links a { color: var(--body); }
.footer-links a:hover { color: var(--ink); }

.legal {
  font-size: 12px;
  line-height: 20px;
  color: var(--mute);
  margin-top: var(--s-6);
  max-width: var(--measure);
}
.legal p + p { margin-top: var(--s-2); }

/* ------------------------------------------------------- legal pages */

.doc { padding-block: var(--s-9); border-top: 0; }
.doc h1 { margin-bottom: var(--s-3); }
.doc h2 {
  font-size: 1.25rem;
  margin-top: var(--s-8);
  margin-bottom: var(--s-3);
  padding-top: var(--s-5);
  border-top: 1px solid var(--hairline);
}
.doc h3 { margin-top: var(--s-6); margin-bottom: var(--s-2); font-size: 1rem; }
.doc p, .doc li { font-size: 15px; line-height: 26px; }
.doc p + p, .doc ul + p, .doc p + ul { margin-top: var(--s-4); }
.doc ul { padding-left: var(--s-5); margin: var(--s-4) 0 0; max-width: var(--measure); }
.doc li + li { margin-top: var(--s-2); }
.doc .updated {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--mute);
  margin-bottom: var(--s-7);
}

@media print {
  .nav, .skip { display: none; }
  body { background: #fff; color: #000; }
}

/* ============================================================== motion & depth

   The page was flat: no state in the nav, no hover, no arrival. Everything below
   is subtle on purpose. The audience is DevOps, so movement has to feel like a
   well-built instrument responding, not like a marketing site performing. Every
   effect is short (under 250ms), and the whole block is disabled outright under
   prefers-reduced-motion at the end. */

.btn,
.nav-links a,
.card,
.row,
.strip div,
.steps > div,
details summary,
.shot {
  transition:
    background-color 160ms ease,
    border-color 160ms ease,
    color 160ms ease,
    box-shadow 200ms ease,
    transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Buttons take a real press, which is the cheapest way to make a page feel built. */
.btn-lg:hover { background: var(--accent-hover); }
.btn-lg-secondary:hover { background: var(--canvas-soft-2); border-color: var(--accent); color: var(--accent); }
.btn-nav:hover { background: var(--accent-hover); }
.btn:active { transform: translateY(1px); }

/* Inactive until the first release: a non-link element that keeps the button's
   shape but reads as unavailable, takes no pointer events and no hover state. */
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}
.release-status {
  font-family: var(--mono);
  font-size: 12px;
  line-height: 20px;
  color: var(--mute);
  margin-top: var(--s-3);
}

/* --------------------------------------------------------------- nav state */

.nav { transition: box-shadow 200ms ease, background-color 200ms ease; }
.nav.is-scrolled {
  box-shadow: 0 1px 0 var(--hairline), 0 10px 24px -20px #14141359;
}

/* The active item gets an accent rule that grows from the centre. An underline
   that animates in reads as navigation; a colour swap alone reads as a bug. */
.nav-links a:not(.btn) { position: relative; }
.nav-links a:not(.btn)::after {
  content: "";
  position: absolute;
  left: var(--s-3);
  right: var(--s-3);
  bottom: 2px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.nav-links a:not(.btn):hover::after { transform: scaleX(0.45); }
.nav-links a:not(.btn).is-active { color: var(--ink); }
.nav-links a:not(.btn).is-active::after { transform: scaleX(1); }
.nav-links a:not(.btn):hover { text-decoration: none; }

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

/* A faint engineering grid, masked so it fades out before it becomes wallpaper,
   plus a soft verdigris wash in the corner. This is what replaces the deleted
   mesh gradient: atmosphere that belongs to a tool rather than to a brand deck. */
.hero { position: relative; overflow: hidden; }
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--hairline) 1px, transparent 1px),
    linear-gradient(90deg, var(--hairline) 1px, transparent 1px);
  background-size: 48px 48px;
  background-position: center top;
  /* Masked to the figure side. Behind the headline it fought the type; behind the
     schematic it reads as the engineering paper the diagram is drawn on. */
  -webkit-mask-image: radial-gradient(64% 72% at 78% 34%, #000 0%, transparent 74%);
  mask-image: radial-gradient(64% 72% at 78% 34%, #000 0%, transparent 74%);
  opacity: 0.7;
}
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(55% 60% at 86% 22%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 72%);
}
.hero > .wrap { position: relative; z-index: 1; }

/* ---------------------------------------------------------- surface lifting */

.card:hover,
.shot:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-4);
}

/* Feature rows pick up a warm surface and an accent edge, so scanning the list
   has a focus rather than being an undifferentiated wall. */
.row {
  position: relative;
  padding-inline: var(--s-4);
  margin-inline: calc(var(--s-4) * -1);
  border-radius: var(--r-md);
}
.row::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  bottom: 8px;
  width: 2px;
  border-radius: 2px;
  background: var(--accent);
  transform: scaleY(0);
  transform-origin: center;
  transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.row:hover { background: var(--canvas); }
.row:hover::before { transform: scaleY(1); }

.strip div:hover { background: var(--canvas-soft-2); }
.steps > div:hover { background: color-mix(in srgb, var(--band-ink) 5%, var(--band)); }

/* ------------------------------------------------------------------ terminal */

/* A blinking block cursor. One small living detail, and the right one for this
   audience: it is the thing their own screen does all day. */
.cmd::after {
  content: "";
  display: inline-block;
  width: 0.55em;
  height: 1.05em;
  margin-left: 0.15em;
  vertical-align: text-bottom;
  background: var(--accent);
  animation: ostgate-blink 1.15s steps(1) infinite;
}
@keyframes ostgate-blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

/* ----------------------------------------------------------------- reveal */

/* Added by script, so with JavaScript off nothing is ever hidden. */
.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 480ms cubic-bezier(0.22, 1, 0.36, 1) var(--reveal-delay, 0ms),
    transform 480ms cubic-bezier(0.22, 1, 0.36, 1) var(--reveal-delay, 0ms);
}
.reveal.is-in { opacity: 1; transform: none; }

/* --------------------------------------------------------------------- faq */

details summary::after { transition: transform 200ms ease, color 160ms ease; }
details summary:hover { color: var(--accent); }
details[open] summary { color: var(--accent); }
details[open] p { animation: ostgate-rise 260ms cubic-bezier(0.22, 1, 0.36, 1); }
@keyframes ostgate-rise {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* --------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* ================================================================= schematic

   The hero diagram. Hairline construction, mono labels, one moving part. It is
   the page's argument: the mechanism drawn, rather than described. */

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: var(--s-8);
  align-items: center;
}
.hero-figure { margin: 0; }
.schematic { width: 100%; height: auto; display: block; }

.schematic .zone rect {
  fill: color-mix(in srgb, var(--accent) 4%, transparent);
  stroke: var(--hairline-strong);
  stroke-width: 1;
  stroke-dasharray: 3 4;
}
.schematic .zlabel {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  fill: var(--mute);
}

.schematic .node rect {
  fill: var(--canvas);
  stroke: var(--hairline-strong);
  stroke-width: 1.2;
}
.schematic .gate rect { stroke: var(--accent); stroke-width: 1.6; }
.schematic .ntitle {
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
  fill: var(--ink);
  text-anchor: middle;
}
.schematic .nsub {
  font-family: var(--mono);
  font-size: 10.5px;
  fill: var(--mute);
  text-anchor: middle;
}
.schematic .gate .nsub { fill: var(--accent); }

.schematic .wire { stroke: var(--hairline-strong); stroke-width: 1.4; }
.schematic .wlabel {
  font-family: var(--mono);
  font-size: 10px;
  fill: var(--mute);
  text-anchor: start;
}
.schematic .wlabel.dim { fill: var(--hairline-strong); }

/* One moving part, and it disappears into the gate because the gate is painted
   after it: the packet is checked, then continues. */
.schematic .packet {
  fill: var(--accent);
  animation: ostgate-travel 3.4s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
@keyframes ostgate-travel {
  0%        { transform: translateY(0);     opacity: 0; }
  8%        { opacity: 1; }
  46%, 54%  { transform: translateY(84px);  opacity: 1; }
  92%       { transform: translateY(166px); opacity: 1; }
  100%      { transform: translateY(166px); opacity: 0; }
}

@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; gap: var(--s-7); }
  .hero-figure { max-width: 460px; }
}

/* ================================================================== protocol

   The byte-layout block and the close-code triage. This section is the reason a
   reader bookmarks the page, so it gets the most technical typesetting on it:
   mono everywhere, field widths proportional to real byte counts. */

.proto {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: var(--s-8);
  align-items: start;
}

/* Field widths are set from --w, the field's actual size in bytes, so the picture
   is to scale rather than decorative. */
.bytes {
  display: flex;
  gap: 2px;
  margin-bottom: var(--s-4);
}
.fld {
  flex: var(--w) 1 0;
  min-width: 0;
  background: var(--canvas);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-xs);
  padding: var(--s-3);
  display: grid;
  gap: 2px;
}
.fld:last-child { border-color: var(--accent); }
.fname {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink);
}
.ftype, .fval {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--mute);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fval { color: var(--accent); }

.bytes-note {
  font-size: 14px;
  line-height: 22px;
  color: var(--body);
  margin-bottom: var(--s-6);
}

.codes { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-4); }
.codes li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s-3);
  background: var(--canvas);
  border-radius: var(--r-md);
  padding: var(--s-4);
  box-shadow: var(--shadow-2);
}
.codes .dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-top: 6px;
}
.codes .fail .dot { background: var(--state-fail); }
.codes .open .dot { background: var(--state-open); }
.codes .wait .dot { background: var(--state-wait); }
.codes b {
  display: block;
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  margin-bottom: var(--s-2);
  letter-spacing: -0.01em;
}
.codes code { margin-right: 2px; }
.codes p { font-size: 13px; line-height: 21px; margin-top: var(--s-3); }

@media (max-width: 900px) {
  .proto { grid-template-columns: 1fr; gap: var(--s-7); }
}

/* =================================================================== compare

   The before/after. This audience reads two commands faster than two paragraphs,
   and the asymmetry is the whole argument. */

.compare {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--s-4);
  margin-top: var(--s-7);
}
.pane {
  background: var(--canvas);
  border-radius: var(--r-md);
  padding: var(--s-5);
  box-shadow: var(--shadow-2);
}
.pane.accent { box-shadow: var(--shadow-2), inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent); }
.pane pre {
  margin: 0 0 var(--s-4);
  background: var(--band);
  color: var(--band-ink);
  border-radius: var(--r-sm);
  padding: var(--s-4);
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 21px;
  overflow-x: auto;
}
.pane-note { font-size: 13px; line-height: 21px; }
.prompt { color: var(--accent); user-select: none; }
.comment { color: var(--band-body); }

/* ====================================================================== copy

   Injected by script onto anything marked data-copy. Devs reach for this before
   they reach for a selection. */

[data-copy] { position: relative; }
.copy-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  height: 26px;
  padding-inline: 10px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--band-body);
  background: color-mix(in srgb, var(--band-ink) 8%, transparent);
  border: 1px solid var(--band-hairline);
  border-radius: var(--r-xs);
  cursor: pointer;
  opacity: 0;
  transition: opacity 160ms ease, color 160ms ease, border-color 160ms ease;
}
[data-copy]:hover .copy-btn,
.copy-btn:focus-visible { opacity: 1; }
.copy-btn:hover { color: var(--band-ink); border-color: var(--accent); }
.copy-btn.is-done { color: var(--accent); border-color: var(--accent); opacity: 1; }

/* ================================================================== scenarios

   A step-through, not a picture. Tabs pick the topology, the numbered steps walk
   the route, and the diagram lives on a charcoal stage so it reads as an
   instrument panel instead of more cream. */

.explorer {
  background: var(--canvas);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-3);
  overflow: hidden;
}

.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  padding: var(--s-3) var(--s-3) 0;
  border-bottom: 1px solid var(--hairline);
}
.tab {
  position: relative;
  appearance: none;
  background: none;
  border: 0;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--mute);
  padding: var(--s-3) var(--s-4);
  cursor: pointer;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  transition: color 160ms ease, background-color 160ms ease;
}
.tab::after {
  content: "";
  position: absolute;
  left: var(--s-4);
  right: var(--s-4);
  bottom: -1px;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
}
.tab:hover { color: var(--ink); background: var(--canvas-soft-2); }
.tab[aria-selected="true"] { color: var(--ink); }
.tab[aria-selected="true"]::after { transform: scaleX(1); }

/* The tallest scenario sets the height for all of them, measured once by script,
   so switching tabs does not move the page either. */
.explorer { transition: min-height 240ms ease; }
.panel { padding: var(--s-6); }
.panel-head { margin-bottom: var(--s-5); }
.panel-head h3 { margin-bottom: var(--s-2); font-size: 1.0625rem; }
.panel-head p { font-size: 14px; line-height: 23px; }

/* The stage. Charcoal, with the same faint engineering grid as the hero, so the
   topology reads as a schematic on paper rather than as a flat drawing. */
.stage {
  background: var(--band);
  border-radius: var(--r-md);
  padding: var(--s-5) var(--s-5) var(--s-4);
  overflow-x: auto;
  box-shadow: inset 0 0 0 1px var(--band-hairline);
  /* Faint on purpose. At hairline strength and 40px pitch this read as a
     checkerboard rather than as paper. */
  background-image:
    linear-gradient(color-mix(in srgb, var(--band-ink) 5%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--band-ink) 5%, transparent) 1px, transparent 1px);
  background-size: 28px 28px;
}

/* `hidden` is an HTML attribute and has no effect on an SVG element, so the packet
   has to be hidden explicitly. */
.stage .packet-h[hidden] { display: none; }
/* The viewBox now carries its own padding, so the focus ring and the active
   node's scale have room to render instead of being clipped by the viewport. */
.stage .flow { min-width: 640px; margin: 0; }

.stage .node rect {
  fill: color-mix(in srgb, var(--band-ink) 7%, var(--band));
  stroke: var(--band-hairline);
  stroke-width: 1.2;
}
.stage .ntitle { fill: var(--band-ink); }
.stage .nsub, .stage .flabel { fill: var(--band-body); }
.stage .wire { stroke: var(--band-hairline); stroke-dasharray: 4 4; }

/* Dim what is not being talked about. This is what makes it a walkthrough rather
   than a diagram with a caption. */
.stage .node, .stage .seg { transition: opacity 240ms ease; }
.is-enhanced .stage .node { opacity: 0.45; }
.is-enhanced .stage .seg { opacity: 0.35; }
.stage .node.is-active, .stage .seg.is-active { opacity: 1; }
.stage .node rect, .stage .node .nsub { transition: fill 240ms ease, stroke 240ms ease; }
.stage .node.is-active rect {
  stroke: var(--accent);
  stroke-width: 1.6;
  fill: color-mix(in srgb, var(--accent) 16%, var(--band));
}
.stage .node.is-active .nsub { fill: var(--accent); }
.stage .seg.is-active .wire { stroke: var(--accent); }

.stage .packet-h { fill: var(--accent); }
.stage .packet-h.is-running { animation: ostgate-hop 900ms cubic-bezier(0.55, 0, 0.45, 1); }
@keyframes ostgate-hop {
  from { transform: translateX(0); opacity: 0; }
  20%  { opacity: 1; }
  to   { transform: translateX(46px); opacity: 1; }
}

.steps-list {
  list-style: none;
  margin: var(--s-5) 0 0;
  padding: 0;
  display: grid;
  gap: 2px;
}
.step {
  width: 100%;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s-4);
  text-align: left;
  appearance: none;
  background: none;
  border: 0;
  border-radius: var(--r-md);
  padding: var(--s-3) var(--s-4);
  cursor: pointer;
  font: inherit;
  transition: background-color 180ms ease;
}
.step { cursor: default; }
.is-enhanced .step { cursor: pointer; }
.is-enhanced .step:hover { background: var(--canvas-soft-2); }
.step .step-n {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--hairline-strong);
  padding-top: 3px;
  transition: color 180ms ease;
}
.step b {
  display: block;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--body);
  transition: color 180ms ease;
}
/* Every step's detail is always visible. Collapsing them meant that clicking a
   step changed the panel's height and shoved the rest of the page down, which is
   the one thing an explorer must never do: the reader is pointing at something,
   and it moved. Selection is shown by colour and surface, which cost no layout. */
.step .step-body > span {
  display: block;
  font-size: 13px;
  line-height: 21px;
  color: var(--mute);
  margin-top: var(--s-2);
  transition: color 180ms ease;
}
.step[aria-current="step"] { background: var(--canvas-soft-2); }
.step[aria-current="step"] .step-n { color: var(--accent); }
.step[aria-current="step"] b { color: var(--ink); }
.step[aria-current="step"] .step-body > span { color: var(--body); }

@media (max-width: 720px) {
  .panel { padding: var(--s-4); }
  .stage { padding: var(--s-4) var(--s-4) var(--s-3); }
}

/* The ssh alias lives in the feature list, not in the hero. This is a GUI product,
   and a terminal command above the fold, with no app screenshot beside it, made it
   look like a command-line tool. */
.row-wide .cmd { margin-top: var(--s-4); max-width: 34rem; }
/* Output, as opposed to what you typed: dimmer, and never picked up by the copy
   button, which only takes the command itself. */
.cmd .out { color: var(--band-body); }

/* ------------------------------------------------- diagram as a control

   The nodes are buttons, so they have to behave like buttons: a pointer, a hover
   state, a focus ring, and a small pop when selected. Scoped to .is-enhanced
   because without script they are not controls at all. */

.is-enhanced .stage .node,
.is-enhanced .stage .seg { cursor: pointer; }
.is-enhanced .stage .node:hover { opacity: 0.8; }
.is-enhanced .stage .seg:hover { opacity: 0.6; }
.is-enhanced .stage .node.is-active:hover,
.is-enhanced .stage .seg.is-active:hover { opacity: 1; }

.stage .node {
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 240ms ease, transform 240ms cubic-bezier(0.22, 1, 0.36, 1);
}
.is-enhanced .stage .node.is-active { transform: scale(1.035); }

/* The default focus ring is clipped by the SVG viewport, so focus is drawn on the
   node's own rect instead. */
.stage .node:focus,
.stage .seg:focus { outline: none; }
.stage .node:focus-visible rect {
  stroke: var(--accent);
  stroke-width: 2;
  stroke-dasharray: 3 3;
}
.stage .seg:focus-visible .wire { stroke: var(--accent); }
