/* Grove marketing site — tokens and rules from references/design-brief.md.
   Monochrome + one accent, Inter body / Instrument Sans display, strict 4px
   grid (1px hairlines exempt as drawing details). */

:root {
  --bg: #ffffff;
  --bg-tint: #f5f5f4;
  --bg-hover: #ededec;
  /* Matches the app's own --bg-active (src/tokens.css) — the hero demo's
     selected sidebar row/tab needs a third tint one step past --bg-hover. */
  --bg-active: #e4e4e3;
  --text: #1a1a1a;
  --text-secondary: #6b6b6b;
  --text-faint: #a3a3a3;
  --border: #e2e2e1;
  --accent: #3e7a57;
  --accent-bg: #3e7a5714;
  /* Website-only tonal range, still derived from --accent — never used in the app itself. */
  --accent-deep: #1f4530;
  --accent-tint: #eaf3ee;
  --on-accent-deep: #f5f5f4;
  /* Bento card stages: a neutral mesh gradient biased toward the accent, plus
     the shadow that lifts each floating UI off it. Website-only, like the
     tonal range above — the app itself never uses these. */
  --mesh-hi: #ffffff;
  --mesh-a: #e6ece8;
  --mesh-b: #ccd5cf;
  --mesh-base-1: #f7f8f7;
  --mesh-base-2: #dfe4e1;
  --float-shadow: 0 12px 32px rgb(20 32 26 / 0.14), 0 2px 6px rgb(20 32 26 / 0.07);
  /* Scales the hero demo's grain/sun-glow effects (below). A translucent
     accent wash reads as a soft glow on a dark surface but as a flat,
     heavy tint on a light one, so light mode needs noticeably less of it
     to read the same way — dark mode stays at full strength. */
  --demo-glow-opacity: 0.45;
  /* .app-window's unspread outer ambient shadow (`0 0 40px ...`) radiates
     evenly on every side, so on a light background it reads as a flat
     green stroke tracing the window's outline rather than a glow — off
     in light mode, full strength in dark where it reads correctly. */
  --demo-outer-glow-opacity: 0;
}

/* Dark tokens apply via system preference (no-JS fallback) unless the user
   explicitly picked light; the [data-theme="dark"] block covers the explicit
   pick and must stay in sync with the media-query block. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #1e1e1e;
    --bg-tint: #262626;
    --bg-hover: #2e2e2e;
    --bg-active: #363636;
    --text: #e8e8e8;
    --text-secondary: #9a9a9a;
    --text-faint: #6b6b6b;
    --border: #333333;
    --accent: #6fae8a;
    --accent-bg: #6fae8a1f;
    --accent-deep: #0e1f16;
    --accent-tint: #16221c;
    --on-accent-deep: #eaf3ee;
    --mesh-hi: #353937;
    --mesh-a: #223029;
    --mesh-b: #141817;
    --mesh-base-1: #2a2d2b;
    --mesh-base-2: #171a19;
    --float-shadow: 0 12px 32px rgb(0 0 0 / 0.5), 0 2px 6px rgb(0 0 0 / 0.35);
    --demo-glow-opacity: 1;
    --demo-outer-glow-opacity: 1;
  }
}

:root[data-theme="dark"] {
  --bg: #1e1e1e;
  --bg-tint: #262626;
  --bg-hover: #2e2e2e;
  --bg-active: #363636;
  --text: #e8e8e8;
  --text-secondary: #9a9a9a;
  --text-faint: #6b6b6b;
  --border: #333333;
  --accent: #6fae8a;
  --accent-bg: #6fae8a1f;
  --accent-deep: #0e1f16;
  --accent-tint: #16221c;
  --on-accent-deep: #eaf3ee;
  --mesh-hi: #353937;
  --mesh-a: #223029;
  --mesh-b: #141817;
  --mesh-base-1: #2a2d2b;
  --mesh-base-2: #171a19;
  --float-shadow: 0 12px 32px rgb(0 0 0 / 0.5), 0 2px 6px rgb(0 0 0 / 0.35);
  --demo-glow-opacity: 1;
  --demo-outer-glow-opacity: 1;
}

/* Instrument Sans (display, H1/H2) and Inter (body, everywhere else) —
   both SIL OFL, self-hosted (no Google Fonts CDN request at runtime), single
   variable-weight files covering 400–700. The two format hints let
   non-variable-font browsers skip straight to the plain woff2 src. */
@font-face {
  font-family: "Instrument Sans";
  src:
    url("assets/fonts/InstrumentSans-Variable.woff2") format("woff2-variations"),
    url("assets/fonts/InstrumentSans-Variable.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Inter";
  src:
    url("assets/fonts/Inter-Variable.woff2") format("woff2-variations"),
    url("assets/fonts/Inter-Variable.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Scrollbar: thin themed thumb over a faint track tint. */
html {
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--text-faint) 70%, transparent) var(--bg-tint);
}

::-webkit-scrollbar {
  width: 10px;
}

::-webkit-scrollbar-track {
  background: var(--bg-tint);
}

::-webkit-scrollbar-thumb {
  background-color: color-mix(in srgb, var(--text-faint) 70%, transparent);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
}

html {
  scroll-behavior: smooth;
}

body {
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 24px;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

code {
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  background: var(--bg-tint);
  padding: 0 4px;
  border-radius: 4px;
}

::selection {
  background: var(--accent-bg);
}

/* ---------- Nav ---------- */

/* Edge-to-edge hairline bar at rest; scrolling past the top (see the
   .is-floating toggle in index.html/changelog.html) morphs it into an
   inset, rounded, floating pill and back — same ease as the rest of the
   site's motion (.reveal, the hero demo's pane rise). The stuck offset is
   driven by `top`, not margin-top: sticky elements pin their border box to
   `top`'s value regardless of margin, so margin-top alone leaves the bar
   flush against the viewport edge instead of gapped. Border stays 1px on
   every side throughout so only its color animates, rather than widths
   appearing/disappearing out of nowhere. */
.nav {
  position: sticky;
  top: 0;
  margin: 0 auto;
  /* A large fixed value rather than none/100% — max-width can't interpolate
     from the none keyword, so the floating state's narrower max-width would
     snap in instantly instead of easing like everything else. */
  max-width: 1600px;
  background-color: color-mix(in srgb, var(--bg) 70%, transparent);
  /* saturate() alongside blur is what reads as frosted glass rather than
     plain gaussian blur — same recipe as .app-window's glass mat below. */
  backdrop-filter: blur(16px) saturate(1.4);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  border: 1px solid transparent;
  border-bottom-color: var(--border);
  border-radius: 0;
  box-shadow: none;
  z-index: 10;
  transition:
    top 400ms cubic-bezier(0.2, 0.7, 0.3, 1),
    max-width 400ms cubic-bezier(0.2, 0.7, 0.3, 1),
    border-radius 400ms cubic-bezier(0.2, 0.7, 0.3, 1),
    border-color 400ms cubic-bezier(0.2, 0.7, 0.3, 1),
    box-shadow 400ms cubic-bezier(0.2, 0.7, 0.3, 1),
    background-color 400ms cubic-bezier(0.2, 0.7, 0.3, 1),
    backdrop-filter 400ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

.nav.is-floating {
  top: 24px;
  max-width: 960px;
  background-color: color-mix(in srgb, var(--bg) 62%, transparent);
  backdrop-filter: blur(24px) saturate(1.6);
  -webkit-backdrop-filter: blur(24px) saturate(1.6);
  border-color: color-mix(in srgb, var(--border) 60%, transparent);
  border-radius: 999px;
  /* A soft top catch-light on top of the float lift — same "light hitting
     the glass" cue as .app-window's inset highlight, kept neutral (mixed
     with white, not the accent) so the nav itself stays chrome, not themed. */
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, white 25%, transparent),
    var(--float-shadow);
}

.nav-inner {
  position: relative;
  max-width: 1152px;
  margin: 0 auto;
  padding: 12px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  color: var(--text);
  font-weight: 600;
  font-size: 16px;
}

.brand img {
  border-radius: 4px;
}

.nav-right {
  display: flex;
  align-items: center;
  gap: 16px;
}

/* Centered on .nav-inner itself, independent of the brand mark's and the
   toggle+CTA cluster's widths — matches the Figma nav, where the links sit
   in the header's true center rather than beside the right-hand controls. */
.nav-links {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 32px;
}

.nav-links a {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
  text-decoration: none;
}

.nav-links a:hover {
  color: var(--text);
}

.nav-links a[aria-current="page"] {
  color: var(--text);
}

.theme-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  background: none;
  border: none;
  border-radius: 8px;
  color: var(--text-secondary);
  cursor: pointer;
}

.theme-toggle:hover {
  background: var(--bg-hover);
  color: var(--text);
}

.theme-toggle .icon-sun {
  display: none;
}

[data-theme="dark"] .theme-toggle .icon-sun {
  display: block;
}

[data-theme="dark"] .theme-toggle .icon-moon {
  display: none;
}

.nav-cta {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--bg);
  text-decoration: none;
  background: var(--accent);
  padding: 8px 20px;
  border: none;
  border-radius: 999px;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.25),
    inset 0 -1px 3px rgb(0 0 0 / 0.15);
  transition: box-shadow 0.2s ease, filter 0.2s ease;
}

.nav-cta:hover {
  filter: brightness(1.08);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.3),
    inset 0 -1px 3px rgb(0 0 0 / 0.15);
}

/* ---------- Layout ---------- */

main {
  max-width: 1152px;
  margin: 0 auto;
  padding: 0 24px;
}

section {
  padding: 96px 0;
}

h2 {
  font-family: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 24px;
  line-height: 32px;
  font-weight: 600;
  margin-bottom: 32px;
}

h3 {
  font-size: 16px;
  line-height: 24px;
  font-weight: 600;
  margin-bottom: 8px;
}

/* ---------- Hero ---------- */

.hero {
  padding-top: 96px;
  text-align: center;
}

/* A bordered pill rather than plain accent text — states a fact (platform,
   version) instead of drawing attention like a status label would. */
.eyebrow {
  display: inline-block;
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 12px;
  margin-bottom: 16px;
}

/* The container is wide enough for the bento grid, so hero copy needs its own
   measure — otherwise the headline runs to a single overlong line. */
.hero h1 {
  font-family: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 48px;
  line-height: 56px;
  font-weight: 700;
  letter-spacing: -0.02em;
  max-width: 720px;
  margin: 0 auto 24px;
  text-wrap: balance;
}

.lede {
  font-size: 20px;
  line-height: 28px;
  color: var(--text-secondary);
  max-width: 560px;
  margin: 0 auto 32px;
}

/* ---------- Waitlist form ---------- */

.waitlist {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  max-width: 440px;
  margin: 0 auto;
}

.waitlist button {
  font: inherit;
  font-weight: 600;
  color: var(--bg);
  background: var(--accent);
  border: none;
  border-radius: 999px;
  padding: 8px 24px;
  height: 48px;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.25),
    inset 0 -2px 4px rgb(0 0 0 / 0.15),
    0 3px 10px color-mix(in srgb, var(--accent) 25%, transparent),
    0 0 16px color-mix(in srgb, var(--accent) 12%, transparent);
  transition: box-shadow 0.2s ease, filter 0.2s ease;
}

.waitlist button:hover {
  filter: brightness(1.08);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.3),
    inset 0 -2px 4px rgb(0 0 0 / 0.15),
    0 4px 14px color-mix(in srgb, var(--accent) 32%, transparent),
    0 0 20px color-mix(in srgb, var(--accent) 18%, transparent);
}

/* ---------- Hero app demo ----------
   A real HTML/CSS rendering of Grove's window (sidebar, tabs, breadcrumbs,
   three document panes) that auto-cycles in place of a screenshot, so it
   never goes stale the way a PNG does. Ported whole from a standalone
   Artifact build — every value already came from src/tokens.css and this
   file's own tokens (see references/flow-website.md). Icon ids are
   prefixed hi- (hero icon) so they can't collide with the bento sprite's
   bi- ids. Driven by the <script> at the bottom of index.html, which
   toggles data-view on .app-demo and is hover-pause / reduced-motion aware. */

/* Capped at its original design width rather than stretched to the hero's
   full ~1152px column — --sidebar-w and --frame-h are fixed px, tuned for
   this width, so letting the window grow wider than 880px would widen the
   sidebar's empty margin and flatten the frame's height-to-width ratio
   instead of just showing more of the same proportioned window. */
.app-demo {
  --sidebar-w: 216px;
  --frame-h: 520px;
  position: relative;
  max-width: 880px;
  margin: 64px auto 0;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  text-align: left;
}

/* A single soft accent-green glow behind the window, textured with an SVG
   feTurbulence noise tile. The noise is applied as a *mask* (intersected
   with the radial fade mask) rather than blended on top of the gradient —
   `background-blend-mode: overlay` was tried first, but overlay-blending
   white noise against the mostly-transparent gradient rendered as raw,
   untinted white speckle wherever the gradient itself had already faded
   out (i.e. most of this layer's area), showing up as a stray white haze
   especially visible in dark mode. Masking instead means the noise can
   only ever subtract opacity from the green gradient — it never paints
   its own color, so there's no white to leak through in either theme.
   Built from --accent so it stays correct in both themes; tight bleed
   keeps it close to the window rather than spilling into neighboring
   content. z-index: -1 with the negative-z-index stacking rule keeps it
   behind .app-window (a plain in-flow, non-positioned child) without
   needing to touch the window's own stacking. */
.app-demo::before {
  content: "";
  position: absolute;
  inset: -32px;
  z-index: -1;
  border-radius: 28px;
  background: radial-gradient(60% 60% at 50% 45%, color-mix(in srgb, var(--accent) 55%, transparent), transparent 72%);
  mask-image:
    radial-gradient(closest-side, black 40%, transparent 100%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.6 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  -webkit-mask-image:
    radial-gradient(closest-side, black 40%, transparent 100%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.6 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  mask-size: cover, 220px 220px;
  -webkit-mask-size: cover, 220px 220px;
  mask-repeat: no-repeat, repeat;
  -webkit-mask-repeat: no-repeat, repeat;
  mask-composite: intersect;
  -webkit-mask-composite: source-in;
  filter: blur(1px);
  opacity: var(--demo-glow-opacity);
  pointer-events: none;
}

/* A small, bright "sun" glow centered on the window's top edge — mostly
   tucked behind .app-window (which paints on top, being a plain in-flow
   child with z-index:auto vs. this pseudo's z-index:-1), with only its
   top sliver peeking out above the window, like a sunrise over a
   horizon. Separate from the ambient grain glow above: narrower, more
   concentrated. Every stop mixes with transparent rather than white, so
   the core is a brighter concentration of accent, never a solid opaque
   blob — on a light page background an opaque core reads as a flat
   painted shape rather than a glow. */
.app-demo::after {
  content: "";
  position: absolute;
  top: -50px;
  left: 50%;
  width: 560px;
  height: 160px;
  transform: translateX(-50%);
  z-index: -1;
  background: radial-gradient(
    closest-side,
    color-mix(in srgb, var(--accent) 75%, transparent) 0%,
    color-mix(in srgb, var(--accent) 45%, transparent) 45%,
    transparent 75%
  );
  filter: blur(18px);
  opacity: var(--demo-glow-opacity);
  pointer-events: none;
}

/* Frosted glass mat: .app-window is the translucent, blurred outer layer
   (padding gives it visible thickness so the blur actually shows — with
   zero padding there'd be no exposed surface for backdrop-filter to
   render, since the opaque sidebar/main fill the box edge-to-edge).
   .app-frame is an inset, fully opaque floating card (its own radius
   and shadow, same recipe as the bento cards' floating UI) so the
   demo's text stays crisp while the glass ring around it stays
   translucent.
   The top two inset layers simulate light falling on the glass from
   above: a crisp 1px catch-light line plus a broader soft wash bleeding
   down from the top edge. */
.app-window {
  padding: 14px;
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  border-radius: 20px;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(24px) saturate(1.4);
  -webkit-backdrop-filter: blur(24px) saturate(1.4);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--accent) 70%, white),
    inset 0 40px 60px -44px color-mix(in srgb, var(--accent) calc(60% * var(--demo-glow-opacity)), transparent),
    var(--float-shadow),
    0 0 40px color-mix(in srgb, var(--accent) calc(16% * var(--demo-outer-glow-opacity)), transparent);
}

.app-frame {
  display: flex;
  height: var(--frame-h);
  background: var(--bg);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: var(--float-shadow);
}

/* ------------------------------------------------------------ sidebar ---- */

.app-sidebar {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  width: var(--sidebar-w);
  background: var(--bg-tint);
  border-right: 1px solid var(--border);
}

/* The sidebar tint extends into the title-bar region, as it does in the app. */
.sb-lights {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  padding: 0 16px;
  flex-shrink: 0;
}

.sb-lights i {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--border);
}

.sb-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px 8px 16px;
  flex-shrink: 0;
}

.sb-vault {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
}

.sb-head span.ico {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 4px;
  color: var(--text-secondary);
}

.sb-tree {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  padding: 0 8px;
}

.sb-row {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 28px;
  padding-right: 4px;
  border-radius: 4px;
  color: var(--text-secondary);
  font-size: 12px;
  line-height: 16px;
  transition: background-color 200ms ease, color 200ms ease;
}

.sb-row.root { color: var(--text); font-weight: 600; }
.sb-row .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-row svg { flex-shrink: 0; }
.sb-row .chev { color: var(--text-faint); }

/* Dev is a header row, not a real indent level — its direct children (depth 1)
   sit flush under its own icon rather than stepping in a further 16px, then
   nest normally from there. Each further depth still steps by 16px, matching
   FileTree.tsx's real formula, just re-based so the ladder starts at Dev. */
.sb-row[data-depth="0"] { padding-left: 8px; }
.sb-row[data-depth="1"] { padding-left: 8px; }
.sb-row[data-depth="2"] { padding-left: 24px; }

/* A file row has no chevron, so its icon would sit 20px (chevron + gap) left
   of a sibling folder's icon at the same depth. The real app compensates with
   the same marginLeft:20 on FileIcon — reproduced here rather than guessed. */
.leaf-ico { margin-left: 20px; }

/* Selection follows the pane on show. */
.app-demo[data-view="md"] .sb-row[data-file="md"],
.app-demo[data-view="json"] .sb-row[data-file="json"],
.app-demo[data-view="canvas"] .sb-row[data-file="canvas"] {
  color: var(--text);
  background: var(--bg-active);
  font-weight: 500;
}

.sb-footer {
  flex-shrink: 0;
  padding: 4px 8px 8px;
  border-top: 1px solid var(--border);
}

.sb-settings {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  height: 28px;
  padding: 0 8px;
  border-radius: 4px;
  color: var(--text-secondary);
  font-size: 12px;
  line-height: 16px;
}

.sb-settings .hint { margin-left: auto; color: var(--text-faint); }

/* --------------------------------------------------------------- main ---- */

.app-main {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}

.app-tabs {
  display: flex;
  align-items: stretch;
  height: 36px;
  flex-shrink: 0;
  border-bottom: 1px solid var(--border);
}

.tab-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  flex-shrink: 0;
  color: var(--text-secondary);
  border-right: 1px solid var(--border);
}

.tab {
  position: relative;
  display: flex;
  align-items: center;
  gap: 4px;
  height: 36px;
  margin-bottom: -1px;
  padding: 0 12px;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid transparent;
  color: var(--text-secondary);
  font-size: 12px;
  line-height: 16px;
  transition: color 200ms ease, background-color 200ms ease;
}

.tab .label {
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tab .close { display: flex; color: var(--text-faint); }

.app-demo[data-view="md"] .tab[data-file="md"],
.app-demo[data-view="json"] .tab[data-file="json"],
.app-demo[data-view="canvas"] .tab[data-file="canvas"] {
  color: var(--text);
  font-weight: 500;
  background: var(--bg);
  border-bottom-color: var(--bg);
}

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

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
  min-height: 40px;
  flex-shrink: 0;
  padding: 4px 12px 4px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}

.crumbs {
  position: relative;
  flex: 1;
  min-width: 0;
  height: 24px;
}

.crumb-set {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  gap: 2px;
  overflow: hidden;
  color: var(--text-secondary);
  font-size: 12px;
  line-height: 16px;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 200ms ease, transform 200ms ease;
  pointer-events: none;
}

.crumb-set span { padding: 4px; white-space: nowrap; }
.crumb-set .current { color: var(--text); font-weight: 500; }
.crumb-set svg { flex-shrink: 0; color: var(--text-faint); }

.app-demo[data-view="md"] .crumb-set[data-file="md"],
.app-demo[data-view="json"] .crumb-set[data-file="json"],
.app-demo[data-view="canvas"] .crumb-set[data-file="canvas"] {
  opacity: 1;
  transform: none;
}

.header-actions {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 4px;
}

.view-toggle {
  position: relative;
  display: flex;
  align-items: center;
  gap: 4px;
  height: 32px;
  padding: 4px;
  border-radius: 4px;
  background: var(--bg-tint);
}

.view-toggle b {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 4px;
  color: var(--text-secondary);
}

.view-toggle b.on { color: var(--text); background: var(--bg-active); }

.more-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 4px;
  color: var(--text-secondary);
}

/* The canvas has no preview/source toggle in the app, so it hides here too. */
.app-demo[data-view="canvas"] .view-toggle { display: none; }

/* --------------------------------------------------------------- panes --- */

.pane-stack {
  position: relative;
  flex: 1;
  min-height: 0;
  background: var(--bg);
}

.app-pane {
  position: absolute;
  inset: 0;
  overflow: hidden;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 320ms ease, transform 320ms ease;
  pointer-events: none;
}

.app-demo[data-view="md"] .app-pane[data-pane="md"],
.app-demo[data-view="json"] .app-pane[data-pane="json"],
.app-demo[data-view="canvas"] .app-pane[data-pane="canvas"] {
  opacity: 1;
  transform: none;
  z-index: 1;
}

/* Staggered entrance. Base state is the finished state, so a pane that is not
   playing (reduced motion, or paused mid-cycle) still looks complete. */
@keyframes rise {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.app-pane.is-playing .rise {
  animation: rise 440ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
  animation-delay: calc(var(--i, 0) * 60ms);
}

/* Hover pauses the loop, not the pane. Freezing the entrance animations would
   strand a pane at opacity 0 for anyone whose pointer lands mid-transition —
   the timer is the only thing that should stop. */
.app-demo.is-paused .demo-step .bar { animation-play-state: paused; }

/* ------------------------------------------------- properties (shared) --- */

.doc-page { height: 100%; padding: 20px 24px; overflow: hidden; }

.props { font-size: 12px; line-height: 16px; }

.props-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 28px;
  color: var(--text-faint);
  font-weight: 500;
  letter-spacing: 0.04em;
}

.props-head .ico {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 4px;
  color: var(--text-secondary);
}

.props-divided {
  margin-bottom: 20px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.prop-row {
  display: grid;
  grid-template-columns: minmax(96px, 1fr) minmax(160px, 2fr) 24px;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  padding: 2px 0;
}

.prop-key,
.prop-val {
  min-width: 0;
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  line-height: 20px;
}

.prop-key { color: var(--text-secondary); }
.prop-val { color: var(--text); }
.prop-val.num { font-variant-numeric: tabular-nums; }

.tag-field {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  min-height: 28px;
  padding: 4px;
  border: 1px solid transparent;
  border-radius: 4px;
}

.tag {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  background: var(--bg-tint);
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  white-space: nowrap;
}

/* ------------------------------------------------------ markdown pane ---- */

.md-body { color: var(--text); }

.md-body h1 {
  margin: 0 0 12px;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 28px;
  line-height: 36px;
  font-weight: 700;
  letter-spacing: 0;
}

.md-body h2 {
  margin: 20px 0 12px;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 24px;
  line-height: 32px;
  font-weight: 600;
}

.md-body p {
  margin: 0 0 12px;
  font-size: 16px;
  line-height: 24px;
}

.tasks { list-style: none; margin: 0; padding: 0 0 0 4px; }

.tasks li {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 4px;
  font-size: 16px;
  line-height: 24px;
}

.cb {
  position: relative;
  flex: 0 0 16px;
  width: 16px;
  height: 16px;
  margin-top: 4px;
  border: 1px solid var(--text-secondary);
  border-radius: 4px;
  background: var(--bg);
}

.cb svg { position: absolute; inset: 0; opacity: 0; color: #fff; }

.cb.done { border-color: var(--accent); background: var(--accent); }
.cb.done svg { opacity: 1; }

/* A struck label is drawn, not just styled, so the beat reads as an action. */
.strike { position: relative; }

.strike::after {
  content: "";
  position: absolute;
  left: 0;
  top: 52%;
  height: 1px;
  width: 0;
  background: currentColor;
}

.strike.done { color: var(--text-secondary); }
.strike.done::after { width: 100%; }

/* The one narrative beat in this pane: a milestone gets checked off. */
@keyframes cb-fill {
  to { border-color: var(--accent); background: var(--accent); }
}
@keyframes cb-mark { to { opacity: 1; } }
@keyframes cb-pop {
  50% { transform: scale(1.18); }
}
@keyframes strike-draw { to { width: 100%; } }
@keyframes dim { to { color: var(--text-secondary); } }

.app-pane.is-playing .beat-cb {
  animation: cb-fill 200ms ease-out 2000ms forwards, cb-pop 280ms ease-out 2000ms;
}
.app-pane.is-playing .beat-cb svg { animation: cb-mark 160ms ease-out 2120ms forwards; }
.app-pane.is-playing .beat-strike { animation: dim 300ms ease-out 2200ms forwards; }
.app-pane.is-playing .beat-strike::after { animation: strike-draw 320ms ease-out 2200ms forwards; }

/* ---------------------------------------------------------- json pane ---- */

.grp-head {
  display: flex;
  align-items: center;
  gap: 4px;
  min-height: 32px;
  padding-right: 8px;
  border-radius: 4px;
  font-size: 12px;
  line-height: 16px;
}

.grp-head .disc {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  color: var(--text-faint);
  transition: transform 240ms ease;
}

.grp-head .gname { color: var(--text); font-weight: 500; }
.grp-head .gcount { margin-left: auto; color: var(--text-faint); font-variant-numeric: tabular-nums; }

.grp-body {
  padding-left: 24px;
  overflow: hidden;
  max-height: 0;
  opacity: 0;
}

.grp-body .prop-val { font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace; }

@keyframes grp-open {
  from { max-height: 0; opacity: 0; }
  to { max-height: 132px; opacity: 1; }
}
@keyframes disc-turn { to { transform: rotate(90deg); } }

.app-pane.is-playing .beat-grp { animation: grp-open 360ms ease-out 2600ms forwards; }
.app-pane.is-playing .beat-disc { animation: disc-turn 240ms ease-out 2600ms forwards; }

/* Boolean switch — the app's control, not a checkbox. */
.tg {
  position: relative;
  width: 40px;
  height: 24px;
  margin: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-hover);
}

.tg i {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 8px;
  background: var(--bg);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.24);
}

@keyframes tg-on {
  to { border-color: var(--accent); background: var(--accent); }
}
@keyframes tg-slide {
  to { left: 20px; }
}

.app-pane.is-playing .beat-tg { animation: tg-on 220ms ease-out 1700ms forwards; }
.app-pane.is-playing .beat-tg i { animation: tg-slide 260ms cubic-bezier(0.2, 0.8, 0.3, 1) 1700ms forwards; }

/* -------------------------------------------------------- canvas pane ---- */

.canvas-wrap {
  position: relative;
  height: 100%;
  background-color: var(--bg);
  background-image:
    linear-gradient(var(--border) 1px, transparent 1px),
    linear-gradient(90deg, var(--border) 1px, transparent 1px);
  background-size: 16px 16px;
}

.canvas-wrap svg.surface {
  display: block;
  width: 100%;
  height: 100%;
  color: var(--text-secondary);
}

.cv-shape rect,
.cv-shape ellipse,
.cv-shape polygon {
  fill: var(--bg);
  stroke: currentColor;
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}

.cv-label {
  fill: var(--text);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 14px;
  text-anchor: middle;
}

.cv-arrow {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.cv-sel rect { fill: none; stroke: var(--accent); stroke-width: 1; vector-effect: non-scaling-stroke; }
.cv-port { fill: var(--bg); stroke: var(--accent); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.cv-port.hot { fill: var(--accent); }

.cv-shape, .cv-sel, .cv-port, .cv-halo { opacity: 1; }

@keyframes cv-in {
  from { opacity: 0; transform: scale(0.94); }
  to { opacity: 1; transform: none; }
}
@keyframes cv-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes cv-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes cv-halo { from { opacity: 0; transform: scale(0.4); } to { opacity: 1; transform: none; } }

.app-pane.is-playing .cv-shape {
  transform-box: fill-box;
  transform-origin: center;
  animation: cv-in 400ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
  animation-delay: calc(var(--i, 0) * 110ms);
}

.app-pane.is-playing .cv-arrow {
  stroke-dasharray: 1;
  animation: cv-draw 520ms ease-out both;
  animation-delay: calc(440ms + var(--i, 0) * 200ms);
}

.app-pane.is-playing .cv-sel { animation: cv-fade 200ms ease-out 1600ms both; }

.app-pane.is-playing .cv-port {
  transform-box: fill-box;
  transform-origin: center;
  animation: cv-halo 260ms cubic-bezier(0.2, 0.8, 0.3, 1) both;
  animation-delay: calc(1700ms + var(--i, 0) * 60ms);
}

.cv-toolbar,
.cv-zoom {
  position: absolute;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.cv-toolbar { top: 12px; left: 50%; transform: translateX(-50%); }
.cv-zoom { right: 12px; bottom: 12px; gap: 6px; color: var(--text-secondary); font-size: 12px; }

.cv-toolbar b,
.cv-zoom b {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 4px;
  color: var(--text-secondary);
}

.cv-toolbar b.on { color: var(--accent); background: var(--accent-bg); }
.cv-toolbar hr { width: 1px; height: 20px; margin: 0 2px; border: none; background: var(--border); }
.cv-zoom .pct { font-variant-numeric: tabular-nums; }

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

.demo-steps {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 20px;
}

.demo-step {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 12px;
  border: 1px solid transparent;
  border-radius: 8px;
  overflow: hidden;
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  line-height: 16px;
  color: var(--text-faint);
  background: none;
  cursor: pointer;
  transition: color 200ms ease, border-color 200ms ease;
}

.demo-step:hover { color: var(--text-secondary); border-color: var(--border); }
.demo-step:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }
.demo-step[aria-pressed="true"] { color: var(--text); border-color: var(--border); }
.demo-step svg { flex-shrink: 0; }

.demo-step .bar {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 1px;
  width: 0;
  background: var(--accent);
}

@keyframes step-fill { from { width: 0; } to { width: 100%; } }
.demo-step.is-timing .bar { animation: step-fill var(--dwell, 5600ms) linear forwards; }

/* ---------- Bento features ----------
   Five cards, each with a looping demo of the feature it names. Every class
   here is prefixed (bento- for the section, bt- for tile internals) so the
   generic names these need — tile, stage, doc, node, edge — can't collide
   with the rest of the site. */

.bento-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 64px;
  margin-bottom: 40px;
}

.bento-head h2 {
  font-size: 34px;
  line-height: 42px;
  max-width: 380px;
  margin-bottom: 0;
  text-wrap: balance;
}

.bento-head p {
  font-size: 15px;
  line-height: 23px;
  color: var(--text-secondary);
  max-width: 440px;
  padding-top: 4px;
}

.bento-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 470px 404px;
  gap: 24px;
}

/* No card padding: the gradient runs edge to edge and the copy carries its own
   inset, so the visual never sits in an inset panel-within-a-panel. */
.bento-tile {
  display: flex;
  flex-direction: column;
  background: var(--bg-tint);
  border: 1px solid var(--border);
  border-radius: 16px;
  overflow: hidden;
}

.bento-tile.is-wide {
  grid-column: span 2;
}

.bento-copy {
  padding: 18px 20px 20px;
  background: var(--bg);
}

.bento-copy h3 {
  font-size: 17px;
  line-height: 24px;
  margin-bottom: 6px;
}

.bento-copy p {
  font-size: 14px;
  line-height: 21px;
  color: var(--text-secondary);
}

.bento-stage {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 28px;
  background:
    radial-gradient(115% 85% at 10% 4%, var(--mesh-hi) 0%, transparent 58%),
    radial-gradient(95% 75% at 92% 14%, var(--mesh-a) 0%, transparent 62%),
    radial-gradient(120% 105% at 74% 104%, var(--mesh-b) 0%, transparent 68%),
    linear-gradient(158deg, var(--mesh-base-1) 0%, var(--mesh-base-2) 100%);
}

.bento-float {
  position: relative;
  z-index: 1;
  width: 100%;
  background: var(--bg);
  border-radius: 12px;
  box-shadow: var(--float-shadow);
}

/* ----- Tile 1: slash commands ----- */

/* Tall enough that the slash menu — a popover anchored under the caret — lands
   inside the document card instead of spilling onto the stage. */
.bt-doc {
  max-width: 480px;
  min-height: 258px;
  padding: 18px 20px 22px;
}

.bt-doc-h {
  font-size: 15px;
  line-height: 22px;
  font-weight: 700;
  margin-bottom: 8px;
}

.bt-doc-p {
  font-size: 13px;
  line-height: 20px;
  color: var(--text-secondary);
  margin-bottom: 10px;
}

/* Choosing "Checklist" converts the line the caret is on, so this line
   collapses the moment the checklist lands — the list then occupies the
   caret's position instead of appearing below an orphaned empty line. */
.bt-line {
  position: relative;
  height: 20px;
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 20px;
  color: var(--text);
  animation: bt-line-collapse 9s steps(1, end) infinite;
}

@keyframes bt-line-collapse {
  0%, 53% { height: 20px; }
  54%, 96% { height: 0; }
  97%, 100% { height: 20px; }
}

.bt-slash {
  opacity: 0;
  animation: bt-slash-in 9s linear infinite;
}

@keyframes bt-slash-in {
  0%, 8% { opacity: 0; }
  12%, 50% { opacity: 1; }
  54%, 100% { opacity: 0; }
}

/* Blink drives opacity, the collapse drives width — two properties, so the
   second animation doesn't cancel the first. */
.bt-caret {
  display: inline-block;
  width: 1.5px;
  height: 14px;
  vertical-align: -2px;
  background: var(--accent);
  animation: bt-caret-blink 1.05s step-end infinite, bt-caret-hide 9s steps(1, end) infinite;
}

@keyframes bt-caret-blink {
  50% { opacity: 0; }
}

@keyframes bt-caret-hide {
  0%, 53% { width: 1.5px; }
  54%, 96% { width: 0; }
  97%, 100% { width: 1.5px; }
}

.bt-menu {
  position: absolute;
  top: 26px;
  left: 0;
  width: 210px;
  padding: 5px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--float-shadow);
  opacity: 0;
  transform: translateY(4px) scale(0.98);
  transform-origin: top left;
  animation: bt-menu-in 9s ease-out infinite;
}

@keyframes bt-menu-in {
  0%, 13% { opacity: 0; transform: translateY(4px) scale(0.98); }
  18%, 46% { opacity: 1; transform: translateY(0) scale(1); }
  52%, 100% { opacity: 0; transform: translateY(-2px) scale(0.99); }
}

.bt-menu-hi {
  position: absolute;
  left: 5px;
  right: 5px;
  top: 5px;
  height: 26px;
  border-radius: 7px;
  background: var(--accent-bg);
  animation: bt-menu-hi-move 9s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}

@keyframes bt-menu-hi-move {
  0%, 24% { transform: translateY(0); }
  34%, 100% { transform: translateY(52px); }
}

.bt-menu-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  height: 26px;
  padding: 0 8px;
  font-size: 12px;
  color: var(--text-secondary);
}

.bt-menu-item svg {
  flex: none;
  width: 13px;
  height: 13px;
  color: var(--text-faint);
}

.bt-menu-item:nth-child(4) { color: var(--text); }
.bt-menu-item:nth-child(4) svg { color: var(--accent); }

.bt-task {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
  font-size: 13px;
  line-height: 20px;
  color: var(--text-secondary);
  opacity: 0;
  animation: bt-task-in 9s ease-out infinite;
}

/* No leading gap: the first item sits exactly where the collapsed caret was. */
.bt-task-1 { margin-top: 0; }
.bt-task-2 { animation-delay: 0.1s; }
.bt-task-3 { animation-delay: 0.2s; }

@keyframes bt-task-in {
  0%, 54% { opacity: 0; transform: translateY(4px); }
  60%, 94% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(0); }
}

.bt-box {
  position: relative;
  flex: none;
  width: 13px;
  height: 13px;
  border: 1.5px solid var(--text-faint);
  border-radius: 3.5px;
  animation: bt-box-on 9s ease-out infinite;
}

.bt-box svg {
  position: absolute;
  inset: 1px;
  width: 8px;
  height: 8px;
  opacity: 0;
  stroke-dasharray: 16;
  stroke-dashoffset: 16;
  animation: bt-box-draw 9s ease-out infinite;
}

@keyframes bt-box-on {
  0%, 68% { border-color: var(--text-faint); background: transparent; }
  74%, 94% { border-color: var(--accent); background: var(--accent-bg); }
  100% { border-color: var(--text-faint); background: transparent; }
}

@keyframes bt-box-draw {
  0%, 70% { opacity: 0; stroke-dashoffset: 16; }
  78%, 94% { opacity: 1; stroke-dashoffset: 0; }
  100% { opacity: 0; stroke-dashoffset: 16; }
}

.bt-box-idle {
  flex: none;
  width: 13px;
  height: 13px;
  border: 1.5px solid var(--text-faint);
  border-radius: 3.5px;
}

.bt-task-text { position: relative; }

.bt-task-text::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  animation: bt-strike 9s ease-out infinite;
}

@keyframes bt-strike {
  0%, 72% { transform: scaleX(0); }
  82%, 94% { transform: scaleX(1); }
  100% { transform: scaleX(0); }
}

/* ----- Tile 2: canvas -----
   Shapes and arrows share one SVG coordinate space, so the connectors actually
   terminate on the shapes instead of being positioned independently. */

.bt-canvas {
  position: relative;
  width: 100%;
  height: 100%;
}

.bt-board {
  display: block;
  width: 100%;
  height: 100%;
}

.bt-node {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  animation: bt-node-in 10s cubic-bezier(0.34, 1.3, 0.5, 1) infinite;
}

.bt-node rect {
  fill: var(--bg);
  stroke: var(--border);
  stroke-width: 1.5;
}

.bt-node text {
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 12px;
  font-weight: 500;
  fill: var(--text-secondary);
  text-anchor: middle;
  dominant-baseline: middle;
}

.bt-node.is-accent rect { stroke: var(--accent); }
.bt-node.is-accent text { fill: var(--accent); }
.bt-node.is-note rect { fill: var(--accent-tint); stroke: none; }
.bt-node.is-note text { fill: var(--accent-deep); font-size: 11px; }

:root[data-theme="dark"] .bt-node.is-note text { fill: var(--accent); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bt-node.is-note text { fill: var(--accent); }
}

.bt-node:nth-of-type(2) { animation-delay: 0.15s; }
.bt-node:nth-of-type(3) { animation-delay: 0.3s; }
.bt-node:nth-of-type(4) { animation-delay: 0.45s; }

@keyframes bt-node-in {
  0%, 3% { opacity: 0; transform: translateY(7px) scale(0.92); }
  13%, 100% { opacity: 1; transform: translateY(0) scale(1); }
}

.bt-edge {
  fill: none;
  stroke: var(--text-faint);
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-dasharray: 200;
  stroke-dashoffset: 200;
  animation: bt-edge-draw 10s ease-in-out infinite;
}

.bt-edge-2 { animation-delay: 0.5s; }

/* Holds drawn through 100% and cuts back on the loop boundary — a fade-out
   leaves the board visibly blank, which reads as the diagram dying. */
@keyframes bt-edge-draw {
  0%, 20% { stroke-dashoffset: 200; opacity: 0; }
  24% { opacity: 1; }
  44%, 100% { stroke-dashoffset: 0; opacity: 1; }
}

.bt-head {
  fill: var(--text-faint);
  opacity: 0;
  animation: bt-head-in 10s ease-out infinite;
}

.bt-head-2 { animation-delay: 0.5s; }

@keyframes bt-head-in {
  0%, 42% { opacity: 0; }
  48%, 100% { opacity: 1; }
}

/* Toolbar — the active tool tracks whatever is being drawn. */
.bt-tools {
  position: absolute;
  bottom: 0;
  left: 50%;
  z-index: 2;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--float-shadow);
}

.bt-tool {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  color: var(--text-faint);
}

.bt-tool svg { width: 14px; height: 14px; }

.bt-tool-hi {
  position: absolute;
  top: 4px;
  left: 4px;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--accent-bg);
  animation: bt-tool-move 10s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}

@keyframes bt-tool-move {
  0%, 2% { transform: translateX(0); }
  8%, 18% { transform: translateX(56px); }
  26%, 46% { transform: translateX(112px); }
  54%, 100% { transform: translateX(0); }
}

/* ----- Tile 3: command palette ----- */

.bt-palette {
  max-width: 320px;
  overflow: hidden;
}

.bt-pal-bar {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 11px 12px;
  border-bottom: 1px solid var(--border);
}

.bt-pal-bar > svg {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--text-faint);
}

/* line-height must match the box height, or the inherited body leading drops
   the query text below the search icon's centre line. */
.bt-pal-q {
  position: relative;
  flex: 1;
  height: 18px;
  font-size: 13px;
  line-height: 18px;
  color: var(--text);
}

.bt-pal-ph {
  position: absolute;
  inset: 0;
  line-height: 18px;
  color: var(--text-faint);
  animation: bt-ph-out 14s linear infinite;
}

/* The placeholder returns only in the gap between the two queries, so it never
   sits underneath typed text. */
@keyframes bt-ph-out {
  0%, 4% { opacity: 1; }
  8%, 46% { opacity: 0; }
  50% { opacity: 1; }
  54%, 96% { opacity: 0; }
  99%, 100% { opacity: 1; }
}

.bt-pal-typed {
  position: absolute;
  inset: 0;
  width: 0;
  line-height: 18px;
  white-space: nowrap;
  overflow: hidden;
}

.bt-pal-q1 { animation: bt-type-1 14s steps(7, end) infinite; }

@keyframes bt-type-1 {
  0%, 6% { width: 0; }
  18%, 44% { width: 4.6em; }
  48%, 100% { width: 0; }
}

.bt-pal-q2 {
  color: var(--accent);
  font-weight: 600;
  animation: bt-type-2 14s steps(1, end) infinite;
}

@keyframes bt-type-2 {
  0%, 52% { width: 0; }
  56%, 94% { width: 1.2em; }
  97%, 100% { width: 0; }
}

.bt-pal-kbd {
  flex: none;
  font-size: 10px;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--bg-tint);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 2px 5px;
}

.bt-pal-groups {
  position: relative;
  height: 106px;
}

.bt-pal-group {
  position: absolute;
  inset: 0;
  padding: 5px;
  opacity: 0;
}

.bt-pal-files { animation: bt-grp-a 14s ease-out infinite; }
.bt-pal-actions { animation: bt-grp-b 14s ease-out infinite; }

@keyframes bt-grp-a {
  0%, 8% { opacity: 0; }
  14%, 44% { opacity: 1; }
  48%, 100% { opacity: 0; }
}

@keyframes bt-grp-b {
  0%, 54% { opacity: 0; }
  60%, 94% { opacity: 1; }
  97%, 100% { opacity: 0; }
}

.bt-pal-hi {
  position: absolute;
  left: 5px;
  right: 5px;
  top: 5px;
  height: 32px;
  border-radius: 7px;
  background: var(--accent-bg);
}

.bt-pal-files .bt-pal-hi {
  animation: bt-pal-hi-move 14s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}

@keyframes bt-pal-hi-move {
  0%, 24% { transform: translateY(0); }
  32%, 100% { transform: translateY(64px); }
}

.bt-pal-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  height: 32px;
  padding: 0 8px;
  font-size: 12.5px;
  color: var(--text);
}

.bt-pal-row svg {
  flex: none;
  width: 13px;
  height: 13px;
  color: var(--text-faint);
}

.bt-pal-actions .bt-pal-row svg { color: var(--accent); }

.bt-pal-path {
  margin-left: auto;
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  font-size: 10.5px;
  color: var(--text-faint);
}

/* ----- Tile 4: config files ----- */

.bt-cfg {
  max-width: 300px;
  overflow: hidden;
}

.bt-cfg-head {
  position: relative;
  height: 38px;
  border-bottom: 1px solid var(--border);
}

.bt-cfg-name {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  font-size: 12.5px;
  font-weight: 600;
  opacity: 0;
  animation: bt-cfg-swap 10.5s ease-in-out infinite;
}

.bt-cfg-name svg {
  flex: none;
  width: 13px;
  height: 13px;
  color: var(--accent);
}

.bt-cfg-name:nth-of-type(2),
.bt-cfg-body:nth-of-type(2) { animation-delay: 3.5s; }

.bt-cfg-name:nth-of-type(3),
.bt-cfg-body:nth-of-type(3) { animation-delay: 7s; }

.bt-cfg-bodies {
  position: relative;
  height: 108px;
}

.bt-cfg-body {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px;
  opacity: 0;
  animation: bt-cfg-swap 10.5s ease-in-out infinite;
}

@keyframes bt-cfg-swap {
  0% { opacity: 0; transform: translateY(6px); }
  5%, 30% { opacity: 1; transform: translateY(0); }
  35%, 100% { opacity: 0; transform: translateY(-6px); }
}

.bt-cfg-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  line-height: 16px;
  color: var(--text);
}

.bt-k { color: var(--text-secondary); }

.bt-v {
  margin-left: auto;
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  color: var(--text-faint);
}

.bt-mask { letter-spacing: 0.12em; }

.bt-tick {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 13px;
  height: 13px;
  border: 1.5px solid var(--accent);
  border-radius: 3.5px;
  background: var(--accent-bg);
  color: var(--accent);
}

.bt-tick svg { width: 9px; height: 9px; }

.bt-dot {
  flex: none;
  width: 13px;
  height: 13px;
  border: 1.5px solid var(--text-faint);
  border-radius: 3.5px;
}

.bt-keyico {
  flex: none;
  width: 13px;
  height: 13px;
  color: var(--text-faint);
}

/* ----- Tile 5: HTML preview ----- */

.bt-browser {
  max-width: 300px;
  overflow: hidden;
}

.bt-br-chrome {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
}

.bt-br-chrome i {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--border);
}

.bt-br-url {
  flex: 1;
  margin-left: 4px;
  height: 16px;
  padding: 0 6px;
  border-radius: 4px;
  background: var(--bg-tint);
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  font-size: 9.5px;
  line-height: 16px;
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
}

/* A rendered page wireframe — nav then hero — laid in as the page paints. */
.bt-br-page {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px;
}

.bt-br-el {
  opacity: 0;
  animation: bt-paint-in 7s ease-out infinite;
}

.bt-br-el:nth-child(2) { animation-delay: 0.14s; }

@keyframes bt-paint-in {
  0% { opacity: 0; transform: translateY(5px); }
  12%, 100% { opacity: 1; transform: translateY(0); }
}

.bt-br-nav {
  display: flex;
  align-items: center;
  gap: 6px;
  padding-bottom: 9px;
  border-bottom: 1px solid var(--border);
}

.bt-br-logo {
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 4px;
  background: var(--accent);
}

.bt-br-link {
  width: 20px;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
}

.bt-br-link:first-of-type { margin-left: 4px; }

.bt-br-btn {
  margin-left: auto;
  width: 34px;
  height: 13px;
  border: 1px solid var(--accent);
  border-radius: 4px;
  background: var(--accent-bg);
}

.bt-br-hero {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 2px 0 4px;
}

.bt-br-h {
  width: 74%;
  height: 10px;
  border-radius: 4px;
  background: var(--text-faint);
  opacity: 0.55;
}

.bt-br-h.is-short { width: 52%; }

.bt-br-sub {
  width: 88%;
  height: 5px;
  margin-top: 2px;
  border-radius: 3px;
  background: var(--border);
}

.bt-br-cta {
  width: 52px;
  height: 16px;
  margin-top: 4px;
  border-radius: 5px;
  background: var(--accent);
}

/* ---------- FAQ ---------- */

.faq details {
  border-bottom: 1px solid var(--border);
}

.faq details:last-of-type {
  border-bottom: none;
}

.faq summary {
  font-weight: 500;
  padding: 16px 0;
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.faq summary::-webkit-details-marker {
  display: none;
}

.faq summary::after {
  content: "+";
  color: var(--text-faint);
  font-weight: 400;
}

.faq details[open] summary::after {
  content: "–";
}

.faq summary:hover {
  color: var(--accent);
}

.faq details p {
  color: var(--text-secondary);
  padding-bottom: 16px;
}

/* The container widened to fit the bento grid, but running prose still needs a
   readable measure — full-width body copy is ~180 characters a line. Summary
   rows and headings are exempt: those are meant to span the container. */
.faq details p,
.changelog-header p,
.release li {
  max-width: 68ch;
}

/* ---------- Changelog ---------- */

.changelog-header {
  padding: 64px 0 0;
  border-bottom: none;
}

.changelog-header h1 {
  font-family: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 32px;
  line-height: 40px;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-bottom: 8px;
}

.changelog-header p {
  color: var(--text-secondary);
}

/* Two-column layout: a narrow rail carries the version/badge/date, the
   wide column carries the title + bullets — makes use of the page's
   1152px width instead of one plain centered stack, and reads closer to
   a real product changelog (Stripe/Linear) as more releases land. */
.release {
  display: grid;
  grid-template-columns: 160px 1fr;
  gap: 32px;
  padding: 48px 0;
}

.release-rail {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.release-rail h2 {
  font-size: 16px;
  line-height: 24px;
  font-weight: 500;
  color: var(--text-secondary);
  margin-bottom: 0;
}

.badge {
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  color: var(--accent);
  background: var(--accent-bg);
  padding: 0 8px;
  border-radius: 8px;
}

.release-date {
  font-size: 12px;
  line-height: 16px;
  color: var(--text-faint);
}

.release h3 {
  font-family: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 24px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 16px;
}

.release ul {
  list-style: none;
}

.release li {
  position: relative;
  color: var(--text-secondary);
  padding-left: 20px;
  margin-bottom: 8px;
}

.release li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 10px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--text-faint);
}

/* ---------- Footer CTA ----------
   Plain background, not a dark panel — the section break itself (the
   standard border-bottom every section gets) is enough separation, and it
   keeps the page's one accent moment on the button, not a whole block. */

.footer-cta {
  text-align: center;
}

.footer-cta img {
  border-radius: 8px;
  margin-bottom: 24px;
}

.footer-cta h2 {
  margin-bottom: 12px;
}

.footer-cta .cta-sub {
  font-size: 16px;
  line-height: 24px;
  color: var(--text-secondary);
  max-width: 480px;
  margin: 0 auto 24px;
}

/* ---------- Footer ---------- */

.footer {
  border-top: 1px solid var(--border);
  background: var(--bg-tint);
  padding: 64px 24px 0;
}

.footer-inner {
  max-width: 1152px;
  margin: 0 auto;
}

.footer-row {
  display: flex;
  justify-content: space-between;
  gap: 48px;
  padding-bottom: 48px;
}

.footer-brand {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 280px;
}

.footer-brand .brand {
  font-size: 16px;
}

.footer-brand p {
  font-size: 14px;
  line-height: 20px;
  color: var(--text-secondary);
}

.footer-links {
  display: flex;
  gap: 64px;
}

.footer-links h3 {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  margin-bottom: 12px;
}

.footer-links ul {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.footer-links a {
  font-size: 14px;
  line-height: 20px;
  color: var(--text-secondary);
  text-decoration: none;
}

.footer-links a:hover {
  color: var(--text);
}

.footer-bottom {
  border-top: 1px solid var(--border);
  padding: 24px 0;
  font-size: 12px;
  line-height: 20px;
  color: var(--text-faint);
}

/* ---------- Tablet ---------- */

@media (max-width: 980px) {
  .bento-head {
    flex-direction: column;
    gap: 16px;
  }

  .bento-head h2 {
    max-width: none;
    font-size: 28px;
    line-height: 36px;
  }

  /* Two columns with the markdown card across the top; explicit row heights
     stop applying so each card can size to its own visual. */
  .bento-grid {
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: none;
  }

  .bento-tile {
    min-height: 400px;
  }

  .app-demo {
    --sidebar-w: 180px;
    --frame-h: 460px;
  }
}

/* ---------- Mobile ---------- */

@media (max-width: 640px) {
  .nav-links {
    display: none;
  }

  section {
    padding: 48px 0;
  }

  .hero {
    padding-top: 64px;
  }

  .hero h1 {
    font-size: 32px;
    line-height: 40px;
  }

  .lede {
    font-size: 16px;
    line-height: 24px;
  }

  .app-demo {
    --frame-h: 400px;
  }

  .app-sidebar {
    display: none;
  }

  .doc-page {
    padding: 16px;
  }

  .md-body h1 {
    font-size: 24px;
    line-height: 32px;
  }

  .md-body h2 {
    font-size: 20px;
    line-height: 28px;
  }

  .bento-grid {
    grid-template-columns: 1fr;
    gap: 16px;
  }

  .bento-tile,
  .bento-tile.is-wide {
    grid-column: span 1;
    min-height: 360px;
  }

  .bento-stage {
    padding: 20px;
  }

  .footer-row {
    flex-direction: column;
    gap: 32px;
  }

  .footer-links {
    gap: 40px;
  }

  .release {
    grid-template-columns: 1fr;
    gap: 8px;
  }

  .release-rail {
    flex-direction: row;
    align-items: center;
  }
}

/* ---------- Scroll reveal ----------
   Sections and cards fade up into place the first time they cross into the
   viewport (IntersectionObserver in index.html). Base state is the
   finished, visible state — .will-reveal is what the script opts an
   element into, so a no-JS visitor never sees hidden content. */

.reveal.will-reveal {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 500ms cubic-bezier(0.2, 0.7, 0.3, 1),
    transform 500ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

.reveal.will-reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* Gaps wider than the transition itself so each item visibly finishes
   settling before the next one starts, rather than all blurring together. */
.bento-grid .bento-tile:nth-child(2) { transition-delay: 140ms; }
.bento-grid .bento-tile:nth-child(3) { transition-delay: 280ms; }
.bento-grid .bento-tile:nth-child(4) { transition-delay: 420ms; }
.bento-grid .bento-tile:nth-child(5) { transition-delay: 560ms; }

.faq details:nth-of-type(2) { transition-delay: 110ms; }
.faq details:nth-of-type(3) { transition-delay: 220ms; }
.faq details:nth-of-type(4) { transition-delay: 330ms; }
.faq details:nth-of-type(5) { transition-delay: 440ms; }
.faq details:nth-of-type(6) { transition-delay: 550ms; }
.faq details:nth-of-type(7) { transition-delay: 660ms; }

/* ---------- Reduced motion ----------
   Each bento loop rests on the single frame that reads clearest on its own. */

@media (prefers-reduced-motion: reduce) {
  /* The hero demo drives its own cross-fade with JS + CSS transitions rather
     than the bento cards' pure-keyframe loops, so it needs a blanket freeze
     instead of per-property overrides — the script itself also checks this
     query and stops auto-advancing (see index.html). */
  .app-demo * {
    animation: none !important;
    transition-duration: 1ms !important;
  }

  .bt-line, .bt-slash, .bt-caret, .bt-menu, .bt-menu-hi, .bt-task, .bt-box,
  .bt-box svg, .bt-task-text::after, .bt-node, .bt-edge, .bt-head, .bt-tool-hi,
  .bt-pal-ph, .bt-pal-typed, .bt-pal-group, .bt-pal-hi, .bt-cfg-name,
  .bt-cfg-body, .bt-br-el {
    animation: none !important;
  }

  /* Card 1 rests on the slash menu open at the caret, so the finished
     checklist is hidden — showing both at once contradicts itself. */
  .bt-menu { opacity: 1; transform: none; }
  .bt-menu-hi { transform: translateY(52px); }
  .bt-slash { opacity: 1; }
  .bt-task { display: none; }

  .bt-node, .bt-head, .bt-br-el { opacity: 1; transform: none; }
  .bt-edge { stroke-dashoffset: 0; opacity: 1; }
  .bt-tool-hi { transform: translateX(0); }

  .bt-pal-ph { opacity: 0; }
  .bt-pal-q1 { width: 4.6em; }
  .bt-pal-q2 { width: 0; }
  .bt-pal-files { opacity: 1; }
  .bt-pal-files .bt-pal-hi { transform: translateY(64px); }

  .bt-cfg-name:nth-of-type(1),
  .bt-cfg-body:nth-of-type(1) { opacity: 1; transform: none; }

  .reveal.will-reveal {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  .nav {
    transition: none !important;
  }
}
