/* Heartwood (Twigly Labs) — heartwood.family stylesheet. Plain CSS, no
   preprocessor, no framework (wp-14a-website.md D1). Built entirely on
   the design tokens in tokens.css (the site is the token system's first
   shipped consumer — see the repo's docs/ui/design-tokens.md); rules use only
   semantic `--hw-*` tokens, never raw hex. Light + dark themes come from
   the token layer. Also serves the generated docs pages (website/docs/),
   whose class contract is preserved in the docs section below. */

@import url("tokens.css");

/* Source Serif 4 (HW-313 Pass A) — the site never loaded the content
   voice `--hw-font-content` declares; these three subsets are the exact
   files the app ships (crates/ui/assets/fonts/), with the OFL licence
   file that must travel with them (fonts/OFL-source-serif-4.md).
   `font-display: swap` so the fallback serif renders immediately and the
   real face swaps in without blocking paint. */
@font-face {
  font-family: "Source Serif 4";
  src: url("fonts/source-serif-4-latin-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Source Serif 4";
  src: url("fonts/source-serif-4-latin-400-italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Source Serif 4";
  src: url("fonts/source-serif-4-latin-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--hw-font-ui);
  color: var(--hw-text-primary);
  background: var(--hw-bg-app);
  line-height: var(--hw-leading-base);
  font-size: var(--hw-text-lg);
}

/* ---- Type roles (HW-313 Pass A) ----
   Eight named roles, each with a bound line-height, so a bare heading
   never falls through to the UA stylesheet (the site's own audit found
   bare `h2`/`h3` rendering at 27px/21.06px — neither a step on the
   declared scale). Existing selectors/classes are the "role names";
   nothing in markup changes. */

h1,
h2,
h3 {
  color: var(--hw-text-primary);
  line-height: var(--hw-leading-tight);
}

/* title — page h1s outside the hero (features/download/privacy/eula/docs
   all open on a bare h1). Claims the previously-unused --hw-text-3xl
   step. */
main > h1 {
  font-size: var(--hw-text-3xl);
  line-height: var(--hw-leading-snug);
  margin: 0 0 var(--hw-space-4);
}

/* section — every main h2 not already given a specific size below. */
main h2 {
  font-size: var(--hw-text-2xl);
  line-height: var(--hw-leading-snug);
}

/* subsection — every main h3 not already given a specific size below. */
main h3 {
  font-size: var(--hw-text-xl);
  line-height: var(--hw-leading-tight);
}

/* body — main prose paragraphs read at the prose measure (~64ch at
   --hw-text-lg, down from ~100ch). Banners are display-block callouts,
   not reading prose, so they keep the full page measure. */
main > section > p:not(.alpha-banner):not(.draft-banner),
main > p:not(.alpha-banner):not(.draft-banner) {
  max-width: var(--hw-measure-prose);
}

a {
  color: var(--hw-text-link);
}

/* Designed in-content link treatment (design-precedent principle 10:
   default-styled underlines read native-HTML) — offset underline, calm
   at rest, stronger on hover. Nav/footer/button links opt out below via
   their own text-decoration rules. */
main a {
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  transition: text-decoration-color var(--hw-duration-fast) var(--hw-ease-standard);
}

main a:hover,
main a:focus {
  text-decoration-color: currentColor;
}

/* ---- Header / navigation ---- */

header.site-header {
  border-bottom: 1px solid var(--hw-border-default);
  padding: var(--hw-space-5) var(--hw-space-7);
  background: var(--hw-surface);
}

header.site-header nav {
  max-width: var(--hw-measure-page);
  margin: 0 auto;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--hw-space-5) var(--hw-space-7);
}

/* HW-457: anthropic.com layout pattern (structure only, never their
   wordmark/glyph/brand assets) — wordmark hard left, every nav
   destination grouped and pushed to the far right, one emphasized CTA
   at the very end. `margin-left: auto` on the group is what does the
   pushing; `.brand` stays a normal flex item so it never competes for
   the same right-hand alignment. */
header.site-header nav .nav-links {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--hw-space-5) var(--hw-space-7);
  margin-left: auto;
}

/* Quieted to VS-6 ("navigation is quieter than content, nav type <= body
   size"): the site's nav previously ran five accent-coloured semibold
   links above the fold, spending the accent budget (precedent #22)
   before a visitor reached any content. */
header.site-header nav a {
  color: var(--hw-text-secondary);
  font-weight: var(--hw-weight-regular);
  font-size: var(--hw-text-md);
  text-decoration: none;
}

header.site-header nav a:hover,
header.site-header nav a:focus {
  color: var(--hw-text-primary);
  text-decoration: underline;
}

/* Wordmark (HW-457): a small drawn mark plus a tightened, slightly
   larger word — reads as a lockup rather than a body-copy link. Own
   growth-rings geometry (see assets/icon/heartwood-icon-macos-small.svg),
   not the app icon file itself: two concentric broken rings, decorative
   only. */
header.site-header nav a.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--hw-space-2);
  color: var(--hw-text-primary);
  font-weight: var(--hw-weight-bold);
  font-size: var(--hw-text-xl);
  letter-spacing: -0.01em;
}

header.site-header nav a.brand:hover,
header.site-header nav a.brand:focus {
  text-decoration: none;
}

.brand-mark {
  width: 1.3em;
  height: 1.3em;
  flex: none;
  fill: none;
  stroke: var(--hw-accent);
  stroke-width: 2.1;
  stroke-linecap: round;
}

.brand-word {
  line-height: 1;
}

/* The header's one emphasized action (VS-7's accent budget) — same
   `.button` component the hero uses, sized down slightly to sit
   comfortably in the nav row. */
a.button.nav-cta {
  padding: var(--hw-space-2) var(--hw-space-5);
  font-size: var(--hw-text-md);
  /* `header.site-header nav a` out-specifies `a.button`, so without this
     the CTA inherits the nav's secondary text colour and reads as a
     disabled control against the accent fill. */
  color: var(--hw-on-accent);
  font-weight: var(--hw-weight-medium);
}

a.button.nav-cta:hover,
a.button.nav-cta:focus {
  text-decoration: none;
}

/* Wayfinding for the active page (HW-313 Pass B). Despite Pass A's own
   commit message claiming this hook already existed and only needed a
   markup consumer, it did not — `git show a55e458d` touches no
   `aria-current` selector, and this repo had none anywhere under
   website/. Adding it here, not assuming it. Primary color + the same
   weight the brand link already carries, so "you are here" reads without
   spending any of the nav's (deliberately quiet, VS-6) accent budget. */
header.site-header nav a[aria-current="page"] {
  color: var(--hw-text-primary);
  font-weight: var(--hw-weight-semibold);
}

/* ---- Phone header (HW-484) ----
   Below this width the split header's destinations cannot share a line
   with the wordmark, and flex wrapping alone produced three ragged rows.
   The nav collapses behind one disclosure instead.

   Progressive enhancement, same contract as tabs.js: the markup ships the
   full nav visible and the toggle hidden, and `assets/nav.js` adds
   `.js-nav` to the header. A reader without JavaScript keeps every
   destination reachable (wrapped, as before) rather than losing the nav
   behind a button nothing can press. */
.nav-toggle {
  display: none;
}

@media (max-width: 48rem) {
  header.site-header {
    padding: var(--hw-space-4) var(--hw-space-5);
  }

  header.site-header.js-nav .nav-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--hw-space-2);
    margin-left: auto;
    appearance: none;
    background: none;
    border: 1px solid var(--hw-border-default);
    border-radius: var(--hw-radius-md);
    /* >= 44px tall: the pointer here is a fingertip, not a cursor. */
    min-height: 2.75rem;
    padding: 0 var(--hw-space-4);
    font: inherit;
    font-size: var(--hw-text-md);
    color: var(--hw-text-secondary);
    cursor: pointer;
  }

  header.site-header.js-nav .nav-toggle[aria-expanded="true"] {
    color: var(--hw-text-primary);
    border-color: var(--hw-border-strong);
  }

  .nav-toggle-icon {
    width: 1.1rem;
    height: 1.1rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
  }

  header.site-header.js-nav .nav-links {
    display: none;
  }

  /* Taking the full flex line is what makes the closed header exactly one
     row: with the panel gone, only the brand and the toggle remain to
     lay out. */
  header.site-header.js-nav.nav-open .nav-links {
    display: flex;
    flex-basis: 100%;
    flex-direction: column;
    align-items: stretch;
    margin-left: 0;
    gap: 0;
    padding-top: var(--hw-space-4);
  }

  header.site-header.js-nav.nav-open .nav-links > a,
  header.site-header.js-nav.nav-open .nav-dropdown-panel a {
    display: flex;
    align-items: center;
    min-height: 2.75rem;
  }

  header.site-header.js-nav.nav-open .nav-cta {
    justify-content: center;
    margin-top: var(--hw-space-4);
  }

  /* Touch has no hover, and a popup anchored to a button inside a
     full-width column has nowhere to pop. The group is spelled out
     inline instead, so every destination is one tap away. */
  header.site-header.js-nav.nav-open .nav-dropdown > button {
    display: none;
  }

  header.site-header.js-nav.nav-open .nav-dropdown-panel {
    display: block;
    position: static;
    min-width: 0;
    padding: var(--hw-space-4) 0 0;
    background: none;
    border: none;
    box-shadow: none;
  }

  header.site-header.js-nav.nav-open .nav-dropdown-panel::before {
    display: none;
  }
}

/* HW-453: the manual light/dark toggle is removed — the site follows
   `prefers-color-scheme` only (tokens.css). No toggle CSS remains. */

/* Header docs dropdown (HW-453, anthropic.com "Learn" reference): a
   grouped menu for the docs/reference/technical destinations, keeping the
   header's primary nav to a small set of named destinations. Disclosure
   is CSS-only (`:focus-within`/`:hover`), plus a `hidden` attribute
   toggled by a few lines of inline-free JS for keyboard/touch activation
   (assets/nav.js) — no external dependency. */
.nav-dropdown {
  position: relative;
}

.nav-dropdown > button {
  appearance: none;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: var(--hw-text-secondary);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--hw-space-1);
}

.nav-dropdown > button:hover,
.nav-dropdown > button:focus-visible,
.nav-dropdown.open > button {
  color: var(--hw-text-primary);
}

.nav-dropdown-caret {
  width: 0.6rem;
  height: 0.6rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.nav-dropdown-panel {
  position: absolute;
  top: calc(100% + var(--hw-space-3));
  left: 0;
  z-index: 10;
  min-width: 16rem;
  background: var(--hw-surface);
  border: 1px solid var(--hw-border-default);
  border-radius: var(--hw-radius-md);
  box-shadow: var(--hw-shadow-low);
  padding: var(--hw-space-4);
}

.nav-dropdown-panel[hidden] {
  display: none;
}

/* Bridges the gap between the trigger and the panel: without it the
   pointer leaves `.nav-dropdown` on the way down and a hover-opened
   panel closes before it can be reached. */
.nav-dropdown-panel::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: calc(-1 * var(--hw-space-3));
  height: var(--hw-space-3);
}

/* Opens on hover too (HW-457), not just click — mouse-only
   (`hover: hover` excludes touch, where hover doesn't exist and the
   existing click handling in nav.js is the only path) and keyboard
   focus via `:focus-within`, both higher-specificity than the
   `[hidden]` rule above so they win regardless of the JS-toggled
   attribute. Escape still closes the click/keyboard-opened state via
   nav.js; a hover-opened panel closes itself the moment the pointer
   leaves. */
@media (hover: hover) {
  .nav-dropdown:hover > .nav-dropdown-panel {
    display: block;
  }
}

.nav-dropdown:focus-within > .nav-dropdown-panel {
  display: block;
}

.nav-dropdown-group + .nav-dropdown-group {
  margin-top: var(--hw-space-4);
}

.nav-dropdown-group-label {
  font-size: var(--hw-text-sm);
  font-weight: var(--hw-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--hw-text-muted);
  margin: 0 0 var(--hw-space-2);
}

.nav-dropdown-panel ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav-dropdown-panel li + li {
  margin-top: var(--hw-space-2);
}

.nav-dropdown-panel a {
  color: var(--hw-text-secondary);
  text-decoration: none;
  font-size: var(--hw-text-md);
}

.nav-dropdown-panel a:hover,
.nav-dropdown-panel a:focus-visible {
  color: var(--hw-text-primary);
  text-decoration: underline;
}

main {
  max-width: var(--hw-measure-page);
  margin: 0 auto;
  padding: var(--hw-space-8) var(--hw-space-7) var(--hw-section-lg);
}

/* Section rhythm (HW-313 Pass A): previously the only separation between
   index.html's sections was the UA <h2> margin (~22px) — no rule of any
   kind targeted <section>. Marked-up page-level sections get the larger
   tier; bare, repeated h2s inside one long page (privacy/eula/download/
   features/docs guides, none of which wrap their clauses in <section>)
   get the smaller tier — a 48-80px gap between every EULA clause would
   be the "more ink" mistake this whole pass is arguing against. */
main > section + section {
  margin-block-start: var(--hw-section-md);
}

/* HW-457: the docs hub's door panels moved one level deeper, inside
   `[data-tabs]`, so the section-rhythm rule above no longer reaches them
   (it matches `main > section + section`, and a panel's previous sibling
   is now the wrapper div, not `main`). Restated at the level that
   actually separates the wrapper from what precedes it, and between the
   stacked panels themselves for the no-JS reader who sees all three at
   once. */
main > .door-tabs {
  margin-block-start: var(--hw-section-md);
}

.door-tabs > .door-section + .door-section {
  margin-block-start: var(--hw-section-md);
}

/* That gap separates stacked sections in the no-JS layout. With the
   widget live exactly one panel is visible, so it would instead indent
   the content by an amount that depends on which tab was picked — every
   panel must start at the same place. */
.js-tabs > .door-section + .door-section {
  margin-block-start: 0;
}

main h2:not(:first-child) {
  margin-block-start: var(--hw-section-sm);
}

/* ---- Landing page ---- */

.hero {
  /* HW-453: use the horizontal space instead of a single centered
     column (Brad's brief, referencing anthropic.com's hero: large
     headline left, supporting copy in a right column). Below the
     `--hw-measure-page` breakpoint the two columns collapse to a
     single stack — the split only applies where there is width to
     spend on it. */
  padding-block: var(--hw-section-lg) 0;
  max-width: none;
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: var(--hw-space-8) var(--hw-space-9);
  align-items: start;
}

.hero-headline {
  position: relative;
  min-width: 0;
}

/* Decorative growth-rings motif (HW-457 item 8) — same broken-ring
   geometry as the app icon (assets/icon/heartwood-icon-macos.svg),
   redrawn here as a plain inline SVG so the hero reads as designed
   rather than a wall of text, without resorting to a product
   screenshot. Sits behind the headline (negative z-index, generous
   size, low opacity) so it adds visual weight without competing with
   the words for the squint test's "one primary thing" reading. */
.hero-rings {
  position: absolute;
  /* Clears the headline's own wrap box: the motif reads as a mark in the
     margin, never as a texture behind the words. */
  top: -7rem;
  right: -11rem;
  z-index: -1;
  width: 18rem;
  height: 18rem;
  color: var(--hw-accent);
  opacity: 0.13;
  pointer-events: none;
}

.hero-rings-core {
  fill: currentColor;
}

@media (max-width: 56rem) {
  .hero-rings {
    display: none;
  }
}

.hero-support {
  min-width: 0;
  padding-block-start: var(--hw-space-2);
}

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

.hero h1 {
  /* Fluid, but only downward (HW-313 decision 2): desktop stays exactly
     3rem/48px, the cap Brad set; clamp only scales it down for mobile,
     where a flat 48px genuinely breaks. */
  font-size: clamp(2rem, 1.25rem + 3.2vw, var(--hw-text-4xl));
  line-height: var(--hw-leading-display);
  margin: 0;
}

.tagline {
  font-family: var(--hw-font-content);
  font-size: var(--hw-text-xl);
  line-height: var(--hw-leading-base);
  color: var(--hw-text-secondary);
  margin: 0 0 var(--hw-space-6);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--hw-space-4);
  align-items: center;
}

a.button {
  display: inline-block;
  background: var(--hw-accent);
  color: var(--hw-on-accent);
  padding: var(--hw-space-3) var(--hw-space-6);
  border-radius: var(--hw-radius-lg);
  text-decoration: none;
  font-weight: var(--hw-weight-semibold);
  white-space: nowrap;
  box-shadow: var(--hw-shadow-low);
  transition: background var(--hw-duration-fast) var(--hw-ease-standard),
    box-shadow var(--hw-duration-fast) var(--hw-ease-standard);
}

a.button:hover,
a.button:focus {
  background: var(--hw-accent-hover);
  box-shadow: var(--hw-shadow-mid);
}

a.button.secondary {
  background: var(--hw-accent-subtle);
  color: var(--hw-accent);
  box-shadow: none;
}

a.button.secondary:hover,
a.button.secondary:focus {
  background: var(--hw-border-default);
}

/* ---- Card (HW-313 Pass A) ----
   One component, two modifiers, replacing what an audit found were
   eleven near-identical declarations (border/radius/surface/padding
   repeated with only incidental differences — one padding value, not
   the asymmetric two some of these carried). Radius claims the
   previously-unused --hw-radius-lg step (12px, up from 8px). Existing
   selectors below are this component's aliases, so no markup changes:
   `.trust-callout`, `.feature-list li`, `.download-list li`,
   `.door-grid li`, `.mcp-sidebar`, `.tool-card`, `.prompt-card`,
   `.resource-card` are all `.card`; `.pillar-grid li` and
   `.docs-index-list li` (below, Three-door IA section) are the flat
   modifier; `.trust-callout` and `.door-grid li.marquee` are the
   accented "marquee" modifier. */
.trust-callout,
.feature-list li,
.download-list li,
.door-grid li,
.mcp-sidebar,
.tool-card,
.prompt-card,
.resource-card {
  background: var(--hw-surface);
  border: 1px solid var(--hw-border-default);
  border-radius: var(--hw-radius-lg);
  padding: var(--hw-space-6);
}

.door-grid li.marquee {
  border-color: var(--hw-accent);
  box-shadow: var(--hw-shadow-low);
}

.trust-callout {
  box-shadow: var(--hw-shadow-low);
}

.trust-callout {
  border-left: 1px solid var(--hw-border-default);
}

.trust-callout h2 {
  margin-top: 0;
}

/* HW-453: "What Heartwood is" was a second boxed 3-card grid stacked
   directly under the doors grid — Brad: "too much happening". A flat,
   two-column row list (same hairline-separated pattern as
   `.docs-index-list`, no card chrome) varies the section rhythm instead
   of repeating the card language a second time on one screen. */
.pillar-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  column-gap: var(--hw-space-8);
  padding: 0;
  list-style: none;
  margin: var(--hw-space-6) 0 0;
}

.pillar-grid li {
  border-top: var(--hw-border-hairline) solid var(--hw-border-default);
  padding: var(--hw-space-5) 0;
}

.pillar-grid h3 {
  margin-top: 0;
  font-size: var(--hw-text-lg);
}

.pillar-grid p {
  margin-bottom: 0;
  font-size: var(--hw-text-md);
  color: var(--hw-text-secondary);
}

/* ---- Status badges (features / roadmap) ---- */

.badge {
  display: inline-block;
  font-size: var(--hw-text-sm);
  font-weight: var(--hw-weight-semibold);
  border-radius: var(--hw-radius-full);
  padding: 0 var(--hw-space-4);
  vertical-align: middle;
}

.badge.now {
  background: var(--hw-success-bg);
  color: var(--hw-success-fg);
  border: 1px solid var(--hw-success-border);
}

.badge.roadmap {
  background: var(--hw-info-bg);
  color: var(--hw-info-fg);
  border: 1px solid var(--hw-info-border);
}

/* HW-328 per-page sweep: was a single column of thirteen near-identical
   cards, each stretched to the full page measure (~90ch of body text
   per card — well past a comfortable line length) with a long scroll
   between "Here now" and the CTA at the foot of "On the roadmap". A
   two-up grid halves the line length per card and roughly halves the
   page's scroll depth; the min width (22rem) is chosen so it still
   drops to one column above the mobile-only breakpoints this site
   otherwise doesn't need. */
.feature-list {
  list-style: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: var(--hw-space-4);
}

.feature-list li {
  margin-bottom: 0;
}

.feature-list h3 {
  margin: 0 0 var(--hw-space-2);
  font-size: var(--hw-text-lg);
}

.feature-list p {
  margin: 0;
  font-size: var(--hw-text-md);
  color: var(--hw-text-secondary);
}

/* ---- Banners ---- */

.draft-banner,
.alpha-banner {
  border-radius: var(--hw-radius-sm);
  padding: var(--hw-space-4) var(--hw-space-5);
  font-weight: var(--hw-weight-bold);
  margin-bottom: var(--hw-space-8);
}

.draft-banner {
  background: var(--hw-warning-bg);
  color: var(--hw-warning-fg);
  border: 1px solid var(--hw-warning-border);
}

.alpha-banner {
  background: var(--hw-warning-bg);
  color: var(--hw-warning-fg);
  border: 1px solid var(--hw-warning-border);
  font-weight: var(--hw-weight-regular);
}

.alpha-banner strong {
  font-weight: var(--hw-weight-bold);
}

/* ---- Download page (HW-328 per-page sweep) ----
   Was a single full-width column — two short cards stretched to the
   full 60rem page measure, mostly empty padding to their right. A
   two-up grid (same `auto-fit`/`minmax` pattern `.pillar-grid` and
   `.door-grid` already use) gives both platforms an honest width for
   their content and, because CSS grid stretches row items to equal
   height by default, the macOS/Windows cards now align — no layout
   change beyond what the existing card system already does elsewhere
   on the site. */
.download-list {
  list-style: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--hw-space-5);
  margin: var(--hw-space-6) 0;
}

.download-list li {
  margin-bottom: 0;
}

.download-list h2 {
  margin-top: 0;
}

.download-slot {
  display: inline-block;
  background: var(--hw-surface-sunken);
  color: var(--hw-text-muted);
  padding: var(--hw-space-3) var(--hw-space-6);
  border-radius: var(--hw-radius-md);
  border: 1px dashed var(--hw-border-strong);
  font-weight: var(--hw-weight-semibold);
}

.download-list a.button {
  display: inline-block;
}

/* ---- Waitlist form (beta.html) ----
   No prior form on this site; built from the same tokens as the button
   and card components rather than inventing new raw values. The row
   collapses to one column below the same phone breakpoint the header
   nav uses. */
.waitlist-form {
  max-width: var(--hw-measure-prose);
  margin: var(--hw-space-6) 0 var(--hw-space-3);
}

.waitlist-form-label {
  display: block;
  font-weight: var(--hw-weight-semibold);
  margin-bottom: var(--hw-space-2);
}

.waitlist-form-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--hw-space-3);
}

.waitlist-form-input {
  flex: 1 1 16rem;
  min-width: 0;
  font: inherit;
  color: var(--hw-text-primary);
  background: var(--hw-surface);
  border: 1px solid var(--hw-border-default);
  border-radius: var(--hw-radius-md);
  padding: var(--hw-space-3) var(--hw-space-4);
}

.waitlist-form-input:focus-visible {
  outline: 2px solid var(--hw-accent);
  outline-offset: 1px;
}

.waitlist-form-row .button {
  flex: 0 0 auto;
  border: none;
  font-size: inherit;
  cursor: pointer;
}

.waitlist-consent {
  color: var(--hw-text-muted);
  font-size: var(--hw-text-sm);
  max-width: var(--hw-measure-prose);
}

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

footer.site-footer {
  border-top: 1px solid var(--hw-border-default);
  padding: var(--hw-space-7);
  color: var(--hw-text-muted);
  font-size: var(--hw-text-sm);
  text-align: center;
  background: var(--hw-surface);
}

footer.site-footer a {
  color: var(--hw-text-secondary);
}

/* ---- Docs pages (website/docs/, WP-14b) ----
   Generated reference + hand-written guides share this stylesheet;
   the class names below are their rendering contract — keep them. */

.docs-footer code {
  word-break: break-all;
}

code {
  background: var(--hw-surface-sunken);
  border-radius: var(--hw-radius-sm);
  padding: 0.05rem 0.3rem;
  font-size: 0.9em;
  font-family: var(--hw-font-mono);
}

/* Real code-block treatment (HW-457 item 6): a bare `<pre><code>` used to
   render as unbroken monospace body copy with no visual boundary —
   contained, scrollable, and visually distinct from prose now, the same
   surface/border language the rest of the site's cards use so it reads
   as a designed element rather than a terminal dump. `.connect-block`
   (getting-started-ai.html) keeps its own accent-bordered variant below;
   this is the plain default every other `<pre>` gets for free. */
pre {
  background: var(--hw-surface-sunken);
  border: 1px solid var(--hw-border-default);
  border-radius: var(--hw-radius-md);
  padding: var(--hw-space-5);
  margin: var(--hw-space-4) 0 var(--hw-space-6);
  overflow-x: auto;
  line-height: var(--hw-leading-base);
}

pre code {
  background: none;
  padding: 0;
  font-size: var(--hw-text-sm);
  border-radius: 0;
}

/* Inline code in prose is frequently a filesystem path or a config key
   longer than a phone is wide, and some are written with `&nbsp;` so a
   path never splits across a line mid-word on a desktop. That makes the
   whole run one unbreakable word, which pushes the page's scroll width
   past the viewport. Breaking is the lesser evil at this size; `<pre>`
   blocks keep their own horizontal scroll and are untouched, and so are
   table cells, which scroll inside `.table-scroll` instead. */
p > code,
li > code,
h1 > code,
h2 > code,
h3 > code,
h4 > code {
  overflow-wrap: anywhere;
}

/* Wide content scrolls inside its own box, never the page body: a schema
   table three levels deep is far wider than a phone, and letting it push
   the document's scroll width out drags every other element sideways
   with it. Nested tables get their own nested scroller, which is the
   correct behaviour — the inner table is what's too wide, not the outer.
   `min-width: 0` because a flex/grid item's automatic minimum size would
   otherwise refuse to shrink below its content and defeat the scroll. */
.table-scroll {
  overflow-x: auto;
  max-width: 100%;
  min-width: 0;
}

/* Schemas render as `.prop-tree` now, so what is left in a table is
   genuine two- or three-column prose (the connection block's client
   list). Prose wraps, so these fit their container instead of setting a
   minimum width no viewport can satisfy — the scroller above stays as a
   backstop for a long unbreakable token, not as the normal case. */
.table-scroll > .schema-table {
  width: 100%;
}

.schema-table td,
.schema-table th {
  overflow-wrap: anywhere;
}

/* ---- Schema property tree (HW-492), replacing nested schema tables ----
   Depth is indentation, not width: every level is a plain list that
   wraps, so a schema nested three deep still fits a phone. The rail on
   each nested level is what makes depth legible without a border box
   per level. */
.prop-tree {
  list-style: none;
  margin: var(--hw-space-3) 0 var(--hw-space-4);
  padding: 0;
}

.prop-tree .prop-tree {
  margin: var(--hw-space-2) 0 var(--hw-space-3);
  padding-left: var(--hw-space-5);
  border-left: var(--hw-border-hairline) solid var(--hw-border-default);
}

.prop {
  margin: 0 0 var(--hw-space-2);
  min-width: 0;
}

.prop-head {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--hw-space-2);
  line-height: var(--hw-leading-snug);
}

.prop-name {
  font-weight: var(--hw-weight-semibold);
  color: var(--hw-text-primary);
  overflow-wrap: anywhere;
}

/* Required is the exception worth marking; an "optional" chip on the
   majority of fields would be noise the reader has to filter out. */
.prop-req {
  font-size: var(--hw-text-xs);
  font-weight: var(--hw-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--hw-danger-fg);
}

.prop-in {
  font-size: var(--hw-text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--hw-text-muted);
}

.prop-desc {
  margin: var(--hw-space-1) 0 0;
  font-size: var(--hw-text-sm);
  color: var(--hw-text-secondary);
  max-width: var(--hw-measure-prose);
}

.prop-body {
  min-width: 0;
}

/* A nested object's own type heading sits between the property name and
   its children, so it reads as a caption rather than a new section. */
.prop-body > .schema-heading {
  margin: var(--hw-space-1) 0 0;
  font-size: var(--hw-text-sm);
  font-weight: var(--hw-weight-regular);
  color: var(--hw-text-muted);
}

.schema-table {
  border-collapse: collapse;
  margin: var(--hw-space-3) 0 var(--hw-space-5);
  width: 100%;
}

.schema-table th,
.schema-table td {
  border-width: var(--hw-border-hairline);
  border-style: solid;
  border-color: var(--hw-border-default);
  padding: var(--hw-space-2) var(--hw-space-4);
  text-align: left;
  vertical-align: top;
}

.schema-table th {
  background: var(--hw-surface-sunken);
}

.schema-heading {
  font-weight: var(--hw-weight-semibold);
  margin-bottom: var(--hw-space-2);
}

/* ---- Canonical DTO linking (HW-490) — a schema used from more than one
   route/tool renders once, in the "Shared schemas" appendix, and every
   use site (including the first) is one of these links instead of a full
   inline expansion. ---- */
.dto-link {
  text-decoration: none;
  border-bottom: 1px dashed var(--hw-border-default);
}

.dto-link:hover,
.dto-link:focus-visible {
  border-bottom-style: solid;
  border-bottom-color: var(--hw-accent);
}

.schema-appendix {
  margin-top: var(--hw-space-6);
}

.schema-appendix-body {
  padding-top: var(--hw-space-2);
}

.schema-entry {
  border-top: var(--hw-border-hairline) solid var(--hw-border-default);
  padding-top: var(--hw-space-3);
  margin-bottom: var(--hw-space-5);
  scroll-margin-top: var(--hw-space-4);
}

.schema-entry h3 {
  font-size: var(--hw-text-md);
  margin: 0 0 var(--hw-space-2);
}

.tagged-union {
  border-left: 3px solid var(--hw-accent);
  padding-left: var(--hw-space-5);
  margin: var(--hw-space-3) 0 var(--hw-space-5);
}

.union-variant {
  margin-bottom: var(--hw-space-5);
}

.variant-label {
  font-weight: var(--hw-weight-semibold);
  margin-bottom: var(--hw-space-2);
}

/* Most fields are optional, so this marker is the resting state and
   recedes: as a filled warning chip it out-shouted the REQUIRED marker
   beside it and turned a parameter list into a column of badges. */
.optional-marker {
  font-size: var(--hw-text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--hw-text-muted);
  margin-right: 0.2rem;
}

.array-marker {
  font-style: italic;
  color: var(--hw-text-muted);
  margin-right: 0.35rem;
}

/* HW-458: method badge (API reference route headings) — a small pill
   in front of the route path, same visual weight as `.tier-badge`. */
.method {
  display: inline-block;
  background: var(--hw-accent);
  color: var(--hw-on-accent);
  border-radius: var(--hw-radius-sm);
  padding: 0 0.45rem;
  font-size: var(--hw-text-sm);
  font-weight: var(--hw-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  vertical-align: middle;
  margin-right: 0.35rem;
}

/* One hue per HTTP verb, drawn from the status ramp already defined in
   both themes rather than a second palette — a reader scanning 249 rows
   finds the destructive ones by colour before reading a word. The verb
   name stays in the chip, so colour is reinforcement and never the only
   signal; every pair clears 4.5:1 in both themes. Read is informational,
   create is affirmative, replace/modify escalate, delete is danger. */
.method-get {
  background: var(--hw-info-bg);
  color: var(--hw-info-fg);
}

.method-post {
  background: var(--hw-success-bg);
  color: var(--hw-success-fg);
}

.method-put {
  background: var(--hw-warning-bg);
  color: var(--hw-warning-fg);
}

.method-patch {
  background: var(--hw-confidence-conflicted-bg);
  color: var(--hw-confidence-conflicted-fg);
}

.method-delete {
  background: var(--hw-danger-bg);
  color: var(--hw-danger-fg);
}

.subheading {
  font-weight: var(--hw-weight-semibold);
  color: var(--hw-text-muted);
  margin: var(--hw-space-5) 0 var(--hw-space-2);
  text-transform: uppercase;
  font-size: var(--hw-text-sm);
  letter-spacing: 0.05em;
}

.coverage-note {
  color: var(--hw-text-muted);
}

.docs-index-list {
  list-style: none;
  padding: 0;
}

/* card--flat: see the "Hub entries" rule further down (Three-door IA
   section) — this used to be a boxed `.card`-shaped declaration here,
   overridden ~150 lines later by the flat treatment that actually ships.
   Precedent #37: delete the superseded path, don't leave it dead. */

/* ---- MCP reference (TWI-94 capability-first rebuild; HW-458 restructured
   the always-expanded card grid into a sidebar of workflow groups plus a
   collapsed-by-default accordion per tool/prompt/resource) ---- */

.tier-badge {
  display: inline-block;
  background: var(--hw-surface-sunken);
  color: var(--hw-text-muted);
  border-radius: var(--hw-radius-sm);
  padding: 0 0.4rem;
  font-size: var(--hw-text-sm);
  font-weight: var(--hw-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  vertical-align: middle;
}

.mcp-connect table.schema-table {
  margin-bottom: var(--hw-space-5);
}

/* HW-458: two-column layout — a sticky group-nav sidebar (real content,
   not JS-dependent: plain in-page anchor links) beside the scrolling
   accordion list. Below the two-column breakpoint (matches
   .tabs-vertical's own fallback threshold) it drops to a single column,
   sidebar first. */
.mcp-layout {
  display: flex;
  align-items: flex-start;
  gap: var(--hw-space-8);
}

.mcp-content {
  flex: 1 1 0;
  min-width: 0;
}

.mcp-sidebar {
  flex: 0 0 16rem;
  position: sticky;
  top: var(--hw-space-6);
  max-height: calc(100vh - var(--hw-space-8));
  overflow-y: auto;
  margin: var(--hw-space-5) 0 var(--hw-space-7);
}

/* Same bounded, two-scroller treatment as the API reference: the group
   list stays put while the cards beside it scroll, so choosing a
   workflow group does not scroll the list of them off the screen. */
@media (min-width: 56.0625rem) {
  .mcp-layout {
    --mcp-chrome: 19rem;
    height: calc(100vh - var(--mcp-chrome));
    min-height: 30rem;
    align-items: stretch;
  }

  .mcp-sidebar {
    position: static;
    max-height: 100%;
    margin: 0;
    min-height: 0;
  }

  .mcp-content {
    height: 100%;
    overflow-y: auto;
    min-height: 0;
    padding-right: var(--hw-space-5);
  }
}

@media (max-width: 56rem) {
  .mcp-layout {
    display: block;
  }

  .mcp-sidebar {
    position: static;
    max-height: none;
    overflow-y: visible;
    width: 100%;
  }
}

.mcp-sidebar h2 {
  font-size: var(--hw-text-sm);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--hw-text-muted);
  margin: var(--hw-space-4) 0 var(--hw-space-2);
}

.mcp-sidebar ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--hw-space-2);
}

#mcp-search {
  width: 100%;
  padding: var(--hw-space-2) var(--hw-space-3);
  border: 1px solid var(--hw-border-default);
  border-radius: var(--hw-radius-sm);
  background: var(--hw-surface-sunken);
  color: inherit;
  font: inherit;
}

.mcp-group-heading {
  margin-top: var(--hw-space-7);
  border-bottom: 1px solid var(--hw-border-default);
  padding-bottom: var(--hw-space-2);
}

/* HW-490: expand/collapse-all, one small pair of controls per capability
   section — a 90-tool page of collapsed accordions had no bulk way to
   scan or print the lot without opening each one by hand. Deliberately
   not `a.button`: that component is a page's one emphasized action
   (marketing CTAs), not a utility toolbar control. */
.mcp-bulk-actions {
  display: flex;
  gap: var(--hw-space-3);
  margin: var(--hw-space-3) 0 var(--hw-space-4);
}

.mcp-bulk-actions button {
  font: inherit;
  font-size: var(--hw-text-sm);
  color: var(--hw-accent);
  background: none;
  border: 1px solid var(--hw-border-default);
  border-radius: var(--hw-radius-sm);
  padding: var(--hw-space-1) var(--hw-space-3);
  cursor: pointer;
}

.mcp-bulk-actions button:hover,
.mcp-bulk-actions button:focus-visible {
  background: var(--hw-surface-sunken);
  outline: none;
}

/* A tool/prompt/resource's description, visible under the collapsed name
   (HW-490) — it used to live only inside the collapsed card-body, so
   scanning the list to find a tool meant opening every card first. */
.card-summary-desc {
  display: block;
  margin: var(--hw-space-1) 0 0;
  font-size: var(--hw-text-sm);
  font-weight: var(--hw-weight-regular);
  color: var(--hw-text-secondary);
  max-width: var(--hw-measure-prose);
}

/* Stacked accordion list, not a multi-column card grid — full-width rows
   read better once each item's body is collapsed by default. */
.card-grid {
  display: flex;
  flex-direction: column;
  gap: var(--hw-space-3);
  margin-bottom: var(--hw-space-5);
}

.tool-card h3,
.prompt-card h3,
.resource-card h3 {
  margin-top: 0;
  margin-bottom: 0;
  display: inline;
}

/* `<details>`/`<summary>` accordion chrome (HW-458): native disclosure —
   no JS, keyboard-operable (Space/Enter toggles a focused `<summary>` for
   free), fully expandable with JavaScript disabled. `list-style: none` +
   the `::-webkit-details-marker` reset replace the browser's default
   triangle with `::before` so the same glyph rotates identically across
   engines. */
.tool-card,
.prompt-card,
.resource-card {
  padding: 0;
}

.tool-card > summary,
.prompt-card > summary,
.resource-card > summary {
  list-style: none;
  cursor: pointer;
  padding: var(--hw-space-4) var(--hw-space-6);
}

.tool-card > summary::-webkit-details-marker,
.prompt-card > summary::-webkit-details-marker,
.resource-card > summary::-webkit-details-marker {
  display: none;
}

.tool-card > summary::before,
.prompt-card > summary::before,
.resource-card > summary::before {
  content: "\25b8";
  display: inline-block;
  width: 1em;
  margin-right: var(--hw-space-2);
  color: var(--hw-text-muted);
  transition: transform 0.15s ease;
}

.tool-card[open] > summary::before,
.prompt-card[open] > summary::before,
.resource-card[open] > summary::before {
  transform: rotate(90deg);
}

.tool-card[open] > summary,
.prompt-card[open] > summary,
.resource-card[open] > summary {
  border-bottom: 1px solid var(--hw-border-default);
}

.tool-card .card-body,
.prompt-card .card-body,
.resource-card .card-body {
  padding: var(--hw-space-2) var(--hw-space-6) var(--hw-space-6);
}

.example-prompts {
  list-style: none;
  padding: 0;
  margin: 0;
}

.example-prompts li {
  font-style: italic;
  color: var(--hw-text-secondary);
  margin-bottom: var(--hw-space-2);
}

.empty-object {
  color: var(--hw-text-muted);
  font-style: italic;
}

/* ---- Three-door IA (TWI-96) ----
   The landing page's audience doors, the docs hub's door sections, the
   per-page door breadcrumb, and the docs-footer door links. */

.door-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--hw-space-6);
  padding: 0;
  list-style: none;
  margin: var(--hw-space-6) 0;
}

.door-grid li {
  display: flex;
  flex-direction: column;
}

/* Door glyphs (HW-313 Pass B, Brad: three door cards only, never the six
   pillars below). Sourced from the app's closed HW-258 icon set
   (crates/ui/src/icons.rs — Tree/Review/Source) so the site and the app
   draw the same hand for the same meaning; `website/` has no build-time
   dependency on `crates/` (governance.toml forbids it, and a static site
   has no Rust build step to add one to regardless), so this is a
   deliberate, documented duplication of exactly three glyphs' path data
   plus the `.hw-icon` paint rule, not a shared source — a future redraw
   of Tree/Review/Source in the app must be mirrored here by hand. Same
   contract as the app: decorative only (`aria-hidden`, `focusable`
   attributes set directly on each inlined `<svg>`), the heading text
   beside it is the label, never the glyph alone. `currentColor` (not the
   accent) so the doors stay in the nav's VS-6 register — the marquee
   door's accent border is the section's one deliberate accent spend, and
   three more accent-coloured glyphs above the fold would spend that
   budget a second time. */
.door-icon {
  flex: none;
  width: var(--hw-space-7);
  height: var(--hw-space-7);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  margin-bottom: var(--hw-space-3);
}

.door-icon .door-icon-solid {
  fill: currentColor;
  stroke: none;
}

.door-grid h3 {
  margin-top: 0;
  font-size: var(--hw-text-lg);
}

.door-grid p {
  font-size: var(--hw-text-md);
  color: var(--hw-text-secondary);
  margin: 0 0 var(--hw-space-4);
}

.door-grid .door-cta {
  margin: auto 0 0;
  font-weight: var(--hw-weight-semibold);
}

.door-crumb {
  max-width: var(--hw-measure-page);
  margin: 0 auto;
  padding: var(--hw-space-4) var(--hw-space-7) 0;
  font-size: var(--hw-text-sm);
  color: var(--hw-text-muted);
}

.door-crumb a {
  color: var(--hw-text-secondary);
  text-decoration: none;
}

.door-crumb a:hover,
.door-crumb a:focus {
  color: var(--hw-text-primary);
  text-decoration: underline;
}

.door-crumb span {
  margin: 0 var(--hw-space-1);
}

/* HW-454: same-page jump buttons ("goofy") removed; the docs hub's three
   doors are real page structure (the `<section id>` anchors below still
   exist for deep-linking from the header dropdown and other pages, just
   without an on-page button row repeating them). */

.door-section h2 {
  font-size: var(--hw-text-2xl);
  margin-bottom: var(--hw-space-2);
}

/* HW-457: the docs hub's three doors are now tabs (below); the lone
   `.door-section.door-marquee` accent-border modifier that used to single
   out the middle door is gone with it — nothing else referenced that
   selector (`.door-grid li.marquee`, on the landing page's door grid, is
   a different selector and stays). */

/* HW-457: the docs hub's three doors, wrapped in `[data-tabs]`, become a
   single-panel tab widget once assets/tabs.js runs (it adds `js-tabs` to
   the wrapper). Until then every `.door-section` panel below stays in
   normal flow, fully visible — the no-JS reader gets the real content,
   not a fallback, and the (inert without JS) tab strip stays hidden so
   there's nothing to click that wouldn't do anything. */
.tabs-strip {
  display: none;
  gap: var(--hw-space-2);
  border-bottom: 1px solid var(--hw-border-default);
  margin-bottom: var(--hw-space-6);
}

.js-tabs .tabs-strip {
  display: flex;
}

/* The selected tab already names the panel (and labels it via
   `aria-labelledby`), so repeating the heading inside is redundant. It
   stays in the markup for the no-JS layout, where the panels render as
   plain stacked sections and each one needs its own heading. */
.js-tabs .door-section > h2,
.js-tabs .mcp-capability-section > h2 {
  display: none;
}

/* The MCP reference runs the same widget: setup, tools, prompts and
   resources are four tabs rather than one continuous scroll that ran the
   connection guide straight into the tool detail. */
.mcp-tabs {
  margin-top: var(--hw-space-7);
}

.tabs-strip [role="tab"] {
  appearance: none;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  padding: var(--hw-space-3) var(--hw-space-4);
  margin-bottom: -1px;
  font: inherit;
  font-size: var(--hw-text-md);
  font-weight: var(--hw-weight-medium);
  color: var(--hw-text-secondary);
  cursor: pointer;
}

.tabs-strip [role="tab"]:hover {
  color: var(--hw-text-primary);
}

.tabs-strip [role="tab"][aria-selected="true"] {
  color: var(--hw-text-primary);
  font-weight: var(--hw-weight-semibold);
  border-bottom-color: var(--hw-accent);
}

.tabs-strip [role="tab"]:focus-visible {
  outline: 2px solid var(--hw-accent);
  outline-offset: 2px;
}

/* Arriving at a panel by deep link focuses it, and a ring drawn around
   a whole pane reads as an error state rather than a cursor position.
   Every panel holds its own focusable content (links, disclosures, the
   filter box), each with its own ring, so nothing is lost. */
[role="tabpanel"]:focus,
[role="tabpanel"]:focus-visible {
  outline: none;
}

/* Vertical variant (methodology.html, HW-457): the same [data-tabs]/
   tabs-strip widget the docs hub uses, laid out as a sidebar instead of a
   top strip — assets/tabs.js already reads `aria-orientation="vertical"`
   off the tablist to switch arrow-key navigation to Up/Down, so nothing
   here duplicates the widget, only its layout. `.tabs-vertical` is a
   modifier on the `.door-tabs` wrapper, kept separate from the docs hub's
   plain horizontal case so the two layouts never fight over the same
   selector. */
.js-tabs.tabs-vertical {
  display: flex;
  align-items: flex-start;
  gap: var(--hw-space-8);
}

.tabs-vertical .tabs-strip[aria-orientation="vertical"] {
  flex: 0 0 14rem;
  flex-direction: column;
  gap: var(--hw-space-1);
  border-bottom: none;
  border-right: var(--hw-border-hairline) solid var(--hw-border-default);
  margin-bottom: 0;
  padding-right: var(--hw-space-5);
}

/* Every row is the same height whatever its label does, so the list reads
   as one control rather than a ragged stack. */
.tabs-vertical .tabs-strip[aria-orientation="vertical"] [role="tab"] {
  border-bottom: none;
  border-left: 2px solid transparent;
  margin-bottom: 0;
  text-align: left;
  border-radius: 0 var(--hw-radius-sm) var(--hw-radius-sm) 0;
  display: flex;
  align-items: center;
  min-height: 2.75rem;
}

/* The focus ring is drawn inside the row's own box: an outset ring would
   change the row's footprint and break the uniform rhythm above. */
.tabs-vertical .tabs-strip[aria-orientation="vertical"] [role="tab"]:focus-visible {
  outline: 2px solid var(--hw-accent);
  outline-offset: -2px;
  border-radius: var(--hw-radius-sm);
}

.tabs-vertical .tabs-strip[aria-orientation="vertical"] [role="tab"][aria-selected="true"] {
  border-left-color: var(--hw-accent);
  background: var(--hw-surface-sunken);
}

/* The panel column is as tall as the tab list whatever the selected
   section says, so switching tabs never reflows the page around the
   reader. `--hw-measure-prose` still caps the line length — the column is
   wider than a comfortable measure, and full-bleed body text would be
   harder to read, not better. */
.tabs-vertical > .door-section {
  flex: 1 1 0;
  min-width: 0;
  min-height: 19rem;
}

.tabs-vertical > .door-section > p {
  max-width: var(--hw-measure-prose);
}

/* Below the site's two-column breakpoint a sidebar has nowhere to go —
   fall back to the same plain, fully-visible stacked layout the no-JS
   reader gets, rather than squeezing two columns into a narrow viewport. */
@media (max-width: 56rem) {
  .tabs-vertical.js-tabs {
    display: block;
  }

  .tabs-vertical .tabs-strip {
    display: none;
  }

  .tabs-vertical .door-section {
    display: block;
  }

  .tabs-vertical .door-section > h2 {
    display: block;
  }
}

/* ---- API reference (HW-458, redesigned HW-474) ----
   A flat list of 249 route rows inside its own inner scrollbar was
   unnavigable, so the sidebar is one collapsible <details> per OpenAPI
   resource, collapsed by default, with a filter box above. Navigation is
   plain anchors, so every operation has a shareable URL; api-nav.js adds
   `.js-api-nav` to show one route at a time. */

/* HW-499: every other page's `main` (and its `.door-crumb`) is capped at
   `--hw-measure-page` because it holds prose meant to read at a
   comfortable line length. This page is a two-pane, independently-
   scrolling reference (HW-492's one-screen design, immediately below) —
   squeezing it into that same 60rem cap just adds a third, awkward
   nested scrollbar on top of the two the design already accounts for.
   Let it use the viewport; `--hw-measure-prose` on individual paragraphs
   (the coverage note, the "Calling the API" tabs) still caps actual
   prose. */
body.api-reference main,
body.api-reference .door-crumb {
  max-width: none;
}

.api-reference-layout {
  display: grid;
  grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
  gap: var(--hw-space-8);
  align-items: start;
}

/* One screen, two independent scrollers (HW-492). The reference used to
   run six viewports tall, so a route's detail scrolled the whole
   document and the rail left with it — a reader lost their place in the
   list to read the thing they had just picked from it. Both panes are
   now bounded by the viewport and scroll separately, so the rail is
   always there and "how far through this route am I" is answered by the
   detail pane's own scrollbar. `--api-chrome` is the page furniture
   above the layout (header, crumb, h1, coverage note, and — HW-499 —
   the now-always-visible tab strip choosing among Reference/Calling the
   API/Build a tool). Reference is the default tab and wraps this layout
   directly, so only the strip itself — not either prose panel — sits
   above it. Measured against a real 1600x1000 render (Playwright,
   `.api-reference-layout`'s `getBoundingClientRect().top`), not
   estimated: re-measure the same way after any change to the chrome
   above this layout. */
@media (min-width: 56.0625rem) {
  .api-reference-layout {
    --api-chrome: 30rem;
    height: calc(100vh - var(--api-chrome));
    min-height: 30rem;
    /* Both panes must fill the row for their own overflow to bite;
       `align-items: start` would size them to content and neither would
       ever scroll. The explicit `minmax(0, 1fr)` row is the other half:
       an implicit row sizes to max-content, so the panes would stretch
       to the tallest route's full height and overflow the container
       instead of scrolling inside it. */
    grid-template-rows: minmax(0, 1fr);
    align-items: stretch;
  }

  .api-panels {
    height: 100%;
    overflow-y: auto;
    padding-right: var(--hw-space-5);
  }
}

.api-nav {
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 100%;
  max-height: 100%;
}

.api-filter {
  display: block;
  margin-bottom: var(--hw-space-2);
  font-size: var(--hw-text-sm);
  color: var(--hw-text-muted);
}

.api-filter-input {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  font-size: var(--hw-text-md);
  padding: var(--hw-space-2) var(--hw-space-4);
  color: var(--hw-text-primary);
  background: var(--hw-surface);
  border: 1px solid var(--hw-border-default);
  border-radius: var(--hw-radius-md);
}

.api-filter-input:focus-visible {
  outline: 2px solid var(--hw-accent);
  outline-offset: 1px;
}

.api-filter-input {
  margin-bottom: var(--hw-space-4);
}

.api-filter-empty {
  margin: 0 0 var(--hw-space-4);
  font-size: var(--hw-text-sm);
  color: var(--hw-text-muted);
}

.api-nav-groups {
  overflow-y: auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.api-group {
  flex-shrink: 0;
}

.api-group > summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: var(--hw-space-3);
  padding: var(--hw-space-2) var(--hw-space-3);
  border-radius: var(--hw-radius-sm);
  font-size: var(--hw-text-sm);
  font-weight: var(--hw-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--hw-text-muted);
}

.api-group > summary:hover {
  color: var(--hw-text-primary);
}

.api-group > summary:focus-visible {
  outline: 2px solid var(--hw-accent);
  outline-offset: 1px;
}

.api-group-count {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  font-weight: var(--hw-weight-regular);
  letter-spacing: 0;
}

.api-nav-list {
  list-style: none;
  margin: 0 0 var(--hw-space-3);
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* flex-shrink: 0 is load-bearing, not cosmetic (HW-465): this row is a
   flex item in a column flex container that scrolls under
   `.api-nav-groups`' overflow-y: auto. Without an explicit flex-shrink: 0,
   rows exceeding the available height shrink proportionally to fit
   instead of the container scrolling, collapsing each label to a ~1px
   sliver while its text stays present (and unreadable) in the DOM. */
.api-nav-row {
  display: flex;
  align-items: baseline;
  gap: var(--hw-space-2);
  flex-shrink: 0;
  min-height: auto;
  padding: var(--hw-space-2) var(--hw-space-3);
  border-radius: var(--hw-radius-sm);
  border-left: 2px solid transparent;
  font-size: var(--hw-text-sm);
  font-family: var(--hw-font-mono);
  color: var(--hw-text-secondary);
  text-decoration: none;
}

.api-nav-row:hover {
  color: var(--hw-text-primary);
  background: var(--hw-surface-sunken);
}

.api-nav-row:focus-visible {
  outline: 2px solid var(--hw-accent);
  outline-offset: -2px;
}

/* The old active state was an accent bar beside a label too faint to
   read. Bar, weight, colour and a filled background now move together. */
.api-nav-row[aria-current="true"] {
  color: var(--hw-text-primary);
  font-weight: var(--hw-weight-semibold);
  background: var(--hw-surface-sunken);
  border-left-color: var(--hw-accent);
}

/* The tail is the only part that tells one route from another, so it is
   never truncated: `api_nav_labels` has already dropped the prefix the
   group heading repeats, and what survives wraps rather than ellipsing.
   A two-line row costs less than a row a reader cannot identify. */
.api-nav-path {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The sidebar's method chips carry the verb ramp, not the accent: a
   column of 249 filled accent pills would spend the page's whole accent
   budget on wayfinding, and the accent is reserved for the one selected
   row. Selection is signalled by the row (bar, weight, fill) so the chip
   keeps its verb hue in every state — losing the colour on the row a
   reader is looking at would defeat the point of colouring it. */
.api-nav-row .method {
  flex: none;
  font-size: 0.72rem;
  min-width: 3.4rem;
  text-align: center;
  margin-right: 0;
}

.api-panels {
  min-width: 0;
}

.api-route {
  min-width: 0;
}

.api-route > p {
  max-width: var(--hw-measure-prose);
}

.api-route:focus {
  outline: none;
}

/* Until api-nav.js runs, every route stays in normal document flow — the
   no-JS reader gets all 249 operations as one readable document rather
   than an empty pane. */
.js-api-nav .api-route[hidden] {
  display: none;
}

/* The disclosure triangle and the heading share one line — an <h2> is a
   block, and left alone it drops below the marker. */
.api-preamble > summary {
  cursor: pointer;
}

.api-preamble > summary > h2 {
  display: inline;
  font-size: var(--hw-text-lg);
}

.api-preamble p {
  max-width: var(--hw-measure-prose);
}

/* Route detail sections (Parameters / Request body / Responses /
   Example): the same native <details> idiom the sidebar groups and the
   MCP capability cards already use, rather than a third disclosure
   pattern. Open on arrival — collapsing is for folding a long block away
   to see the shape of the rest, not for hiding the page's content. */
.api-section {
  border-top: var(--hw-border-hairline) solid var(--hw-border-default);
  padding-top: var(--hw-space-3);
  margin-bottom: var(--hw-space-5);
}

.api-section > summary {
  cursor: pointer;
  margin: 0 0 var(--hw-space-2);
}

.api-section > summary:focus-visible {
  outline: 2px solid var(--hw-accent);
  outline-offset: 2px;
  border-radius: var(--hw-radius-sm);
}

.api-section-body {
  min-width: 0;
}

.route-meta {
  color: var(--hw-text-muted);
  font-size: var(--hw-text-sm);
}

.response-status {
  margin: var(--hw-space-4) 0 var(--hw-space-2);
  font-weight: var(--hw-weight-semibold);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Below the two-column breakpoint the sidebar has nowhere to go: it
   becomes a plain block above the routes, scrolling with the page. */
@media (max-width: 56rem) {
  .api-reference-layout {
    display: block;
  }

  .api-nav {
    position: static;
    max-height: none;
    margin-bottom: var(--hw-space-7);
  }

  .api-nav-groups {
    overflow-y: visible;
  }
}

.gps-callout-actions {
  margin: var(--hw-space-4) 0 0;
}

/* The docs hub is where the header's one CTA lands, so it carries the
   product's availability state in plain sight: a reader arriving to "get
   started" must not have to infer from the absence of a download button
   that there is nothing to download. Quiet by design — a note, not an
   alert; the accent budget belongs to the page's real actions. */
.docs-availability {
  max-width: var(--hw-measure-prose);
  margin: 0 0 var(--hw-space-6);
  padding: var(--hw-space-4) var(--hw-space-5);
  border: 1px solid var(--hw-border-default);
  border-radius: var(--hw-radius-md);
  background: var(--hw-surface-sunken);
  color: var(--hw-text-secondary);
  font-size: var(--hw-text-md);
}

/* lede role: font-content, base leading, prose measure. */
.door-lede {
  font-family: var(--hw-font-content);
  color: var(--hw-text-secondary);
  font-size: var(--hw-text-lg);
  line-height: var(--hw-leading-base);
  margin: 0 0 var(--hw-space-5);
  max-width: var(--hw-measure-prose);
}

/* Hub entries: flat rows on the page's own surface (no boxes-in-boxes —
   the door section already owns the grouping), separated by hairlines,
   with the question as the designed lead-in. */
.docs-index-list li {
  background: none;
  border: none;
  border-radius: 0;
  border-top: var(--hw-border-hairline) solid var(--hw-border-default);
  padding: var(--hw-space-5) 0;
  margin-bottom: 0;
}

.docs-index-list li:last-child {
  border-bottom: var(--hw-border-hairline) solid var(--hw-border-default);
}

.docs-index-list p {
  margin: 0 0 var(--hw-space-2);
  color: var(--hw-text-secondary);
}

.docs-index-list p:last-child {
  margin-bottom: 0;
}

.docs-entry-question {
  font-weight: var(--hw-weight-semibold);
  color: var(--hw-text-primary);
  font-size: var(--hw-text-lg);
}

.llms-pointer {
  background: var(--hw-surface-sunken);
  border-radius: var(--hw-radius-md);
  padding: var(--hw-space-4) var(--hw-space-5);
  margin-top: var(--hw-space-5);
  color: var(--hw-text-secondary);
}

.connect-block {
  border: 1px solid var(--hw-accent);
  border-radius: var(--hw-radius-md);
  background: var(--hw-surface-sunken);
  padding: var(--hw-space-5) var(--hw-space-6);
  overflow-x: auto;
}

.starter-prompts li {
  font-size: var(--hw-text-lg);
  margin-bottom: var(--hw-space-3);
}

footer .door-links {
  margin-top: var(--hw-space-3);
}

footer .door-links a {
  color: var(--hw-text-secondary);
}

/* ---- Product screenshots (HW-328) ----
   Real captures of the real product (crates/ui, SSR'd through
   render-views with starter-fixture synthetic data — see
   e2e/visual/capture-website-screenshots.mjs), never a mockup. One
   frame, `.card`-shaped (same border/radius/shadow language, so a
   screenshot reads as part of this page rather than a pasted-in
   asset), with a muted caption stating plainly that it's a real
   screenshot of synthetic data — the honesty posture applied to
   images, not just copy. */
.screenshot-figure {
  border: 1px solid var(--hw-border-default);
  border-radius: var(--hw-radius-lg);
  box-shadow: var(--hw-shadow-low);
  overflow: hidden;
  margin: var(--hw-space-6) 0 0;
}

.screenshot-figure img {
  display: block;
  width: 100%;
  height: auto;
}

/* `.screenshot-themed` carries no `src` in the markup — only
   assets/theme.js assigns one (see its own comment). Hidden by
   default and revealed only once `html.js` proves that script ran;
   the `<noscript>` sibling beside every `.screenshot-themed` image is
   the real content for a no-JS view, and without this gate the
   src-less image rendered as a visible broken-image glyph stacked on
   top of it (caught by screenshotting the page with JS disabled). The
   background is a neutral fill for the (sub-frame, since this is a
   blocking <head> script) gap between first paint and theme.js
   assigning the real `src`. */
.screenshot-figure img.screenshot-themed {
  display: none;
  background: var(--hw-surface-sunken);
}

html.js .screenshot-figure img.screenshot-themed {
  display: block;
}

.screenshot-figure figcaption {
  padding: var(--hw-space-3) var(--hw-space-5);
  border-top: 1px solid var(--hw-border-default);
  background: var(--hw-surface);
  color: var(--hw-text-muted);
  font-size: var(--hw-text-sm);
}

/* Byte-budget note (HW-328): a CSS-only light/dark swap (two <img>
   elements, one hidden via `display: none`) was the first cut here and
   was wrong — a browser fetches an <img>'s `src` regardless of its
   computed `display`, so every page load downloaded BOTH theme
   variants of both screenshots (~400 KB) no matter which one actually
   rendered. `.screenshot-themed` images below carry no `src` at parse
   time at all — only `data-src-light`/`data-src-dark` — and
   assets/theme.js assigns exactly one, picked the same way it already
   picks `data-theme`. A real toggle click fetches the other variant on
   demand; a page that is never toggled fetches exactly one PNG per
   screenshot, not two. The `<noscript>` fallback beside each is the
   no-JS path (see assets/theme.js's own comment for why light was
   chosen as that fallback's fixed variant). */

/* ---- Entrance motion (HW-313 Pass A, round 8: "keep a small rise on
   entrances") ----
   The only motion this pass adds. Progressive enhancement: browsers
   without `animation-timeline: view()` simply skip the rule and render
   normally — no polyfill, no added script, no layout dependency.
   tokens.css already zeroes every duration under
   prefers-reduced-motion, so the media query below is belt-and-braces.

   Deliberately NOT `animation-fill-mode: both` (verified the hard way):
   `both`/`backwards` fill paints the `from` keyframe (opacity: 0)
   whenever the current render doesn't reflect real accumulated scroll —
   which is not just a print/PDF concern (covered defensively below
   anyway) but ANY full-page capture that resizes the viewport to the
   document's full height and renders once, rather than literally
   scrolling: reproduced with a plain `page.screenshot({ fullPage: true
   })` capture, which rendered "What Heartwood is" and "How it works" as
   blank space even after a real, event-firing scroll pass beforehand —
   the fullPage capture re-renders at a different viewport size and the
   timeline's pre-active phase reasserts. The default fill-mode (`none`)
   closes this entirely: outside the active range the element uses its
   ordinary (fully visible) style, so nothing is ever forced invisible
   before a real scroll reaches it — the rise still plays exactly the
   same WHILE a real scroll is inside the entry range, since fill-mode
   only governs the state outside that range. */
@media (prefers-reduced-motion: no-preference) {
  main > section,
  .trust-callout,
  .pillar-grid li,
  .feature-list li,
  .download-list li,
  .door-grid li,
  .tool-card,
  .prompt-card,
  .resource-card {
    animation: hw-rise var(--hw-duration-slow) var(--hw-ease-decelerate);
    animation-timeline: view();
    animation-range: entry 0% entry 30%;
  }
}

/* Belt-and-braces: print never fires a scroll event, so pin the resting
   state explicitly for print regardless of the above. */
@media print {
  main > section,
  .trust-callout,
  .pillar-grid li,
  .feature-list li,
  .download-list li,
  .door-grid li,
  .tool-card,
  .prompt-card,
  .resource-card {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

@keyframes hw-rise {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
