/* ==========================================================================
   Hakuro — stylesheet
   Shared by every page via _layouts/default.html, plus branding.html.
   Organized in three parts:
   1. Design tokens + reusable components (safe to use anywhere)
   2. Site layout: the shared header/footer chrome from _layouts/default.html,
      the one-pager's one-off index.html sections, and the .legal page
      template used by terms.html and 404.html (built entirely on part 1's
      tokens/components)
   3. Style-guide-only scaffolding (documentation chrome from the
      branding.html catalog page itself — not needed anywhere else)
   ========================================================================== */

:root {
  /* ---- Sham (brand green ramp) ---- */
  --sham-025: #63f7c7;
  --sham-050: #24c791;
  --sham-100: #1e9970; /* true brand green */
  --sham-200: #1c7358;
  --sham-250: #1c604a; /* midpoint of sham-200/300 — CTA band background */
  --sham-300: #1c4d3c;
  --sham-400: #1e2b26;
  --sham-600: #141c19; /* body background */
  --sham-700: #101715; /* linear RGB midpoint of 600/800 — same method this ramp's own sage-700 turns out to sit at relative to sage-600/800 */
  --sham-800: #0c1210; /* darkest plane */

  /* ---- Sage (neutral, green-tinted) ---- */
  --sage-100: #f0f4f2;
  --sage-150: #e1e7e4; /* default offwhite text color */
  --sage-200: #d1d9d6;
  --sage-300: #b0bcb8; /* default body text */
  --sage-400: #8fa09a;
  --sage-500: #647470;
  --sage-600: #384843; /* borders/dividers */
  --sage-700: #2e3d38;
  --sage-800: #1e2e29;

  /* ---- Status (reserved, use sparingly) ---- */
  --terra-100: #c45c52;
  --terra-400: #7a3530;
  --terra-800: #180e0d;
  --amber-100: #c4853a;
  --amber-400: #7a5228;
  --amber-800: #191008;

  /* ---- Border radius: exactly 3 tiers ---- */
  --radius-m: 0.75rem;   /* medium — buttons and most bordered elements */
  --radius-l: 2.5rem;    /* large — card corners only */
  --radius-pill: 999px;  /* fully rounded — tag pills */

  /* ---- Spacing scale (layout rhythm: section/component padding, flex/grid gaps,
     block margins). Bespoke pixel geometry tied to a specific decorative effect
     — animation offsets, illustrative node positions, deck/stack transforms —
     is intentionally left as literal px, since it's part of that effect's own
     math rather than generic page spacing. ---- */
  --space-2xs: 0.25rem; /* 4px */
  --space-xs: 0.5rem; /* 8px */
  --space-sm: 0.75rem; /* 12px */
  --space-md: 1rem; /* 16px */
  --space-lg: 1.5rem; /* 24px */
  --space-xl: 2rem; /* 32px */
  --space-2xl: 3rem; /* 48px */
  --space-3xl: 4rem; /* 64px */
  --space-4xl: 5rem; /* 80px */
  --space-5xl: 7.5rem; /* 120px */
  --space-5xl: 7.5rem; /* 120px */
  --space-8xl: 10rem; /* 160px */

  /* ---- Container: exactly two shared content widths, used sitewide instead
     of one-off max-widths per selector. --container-w is the wide measure —
     every section-level row (header, footer, grids/lists spanning a full
     section) aligns to it. --container-w-narrow is the narrow measure — for
     centered text-only blocks (headings, prose, lists of copy) where a full
     -width line would be too long to read comfortably. Change widths here,
     not per-selector. ---- */
  --container-w: 1080px;
  --container-w-narrow: 640px;

  /* ---- Type ---- */
  --font-base: "Inter", sans-serif;
  --font-serif: "Source Serif 4", serif;
}

* {
  box-sizing: border-box;
}

/* In-page anchor links (nav, CTA buttons, footer) get a fluid but fast
   native smooth-scroll — this only affects hash-link/scrollIntoView
   navigation, not manual wheel/trackpad/keyboard scrolling. scroll-padding-
   top stops the sticky header from covering the target's heading; --header-h
   is kept in sync with the header's real rendered height by js/app.js
   (rather than a guessed pixel value) since it can vary slightly by
   viewport/content. */
html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h, 72px) + var(--space-md));
}

body {
  margin: 0;
  font-family: var(--font-base);
  background: var(--sham-600);
  color: var(--sage-300);
  line-height: 1.5;
  font-size: 1rem;
  overflow-x: hidden;
  /* Sticky footer: full-height column so a short page (e.g. 404) still pushes
     the footer to the bottom of the viewport. */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* Grows to fill leftover height, keeping the footer at the bottom on short pages. */
main {
  flex: 1 0 auto;
  /* Clip (not hidden/auto) any decorative horizontal overflow inside the page —
     e.g. the .framed fanned outlines' ±20px offset on very narrow columns — so
     it can never add a horizontal scroll / dead space on the right. `clip`
     doesn't create a scroll container, so it can't affect sticky positioning. */
  overflow-x: clip;
}

h1,
h2 {
  font-family: var(--font-serif);
  color: var(--sage-400);
  font-weight: 600;
  margin: 0 0 0.4em;
}

/* Centralized heading sizes — one size per level, sitewide, set to the
   largest size previously used for that level in any single section. No
   section-specific font-size overrides for h1-h4 should exist below this;
   if a section needs a different size, it needs a different tag/class, not
   a per-section override of the same tag. */
h1 {
  font-size: 4.5rem;
  line-height: 1.2;
}

h2 {
  font-size: 1.7rem;
  color: var(--sage-300);
}

/* h3's default is what card titles used to look like as a one-off .card h3
   override — now every h3 gets it, since after the h2/h3 promotion below the
   only h3s left in the page are card titles. */
h3 {
  font-family: var(--font-base);
  font-size: 1.3rem;
  color: var(--sage-300);
  font-weight: 500;
  margin: 0 0 var(--space-md) 0;
}

h4 {
  font-size: 1.05rem;
}

.wrap {
  max-width: var(--container-w);
  margin: 0 auto;
  padding: 0 var(--space-lg);
}


/* ==========================================================================
   1. DESIGN TOKENS + REUSABLE COMPONENTS
   ========================================================================== */

/* ---------- Typography scale ---------- */
.type-display {
  font-family: var(--font-serif);
  color: var(--sage-150);
}

.type-h2 {
  font-family: var(--font-serif);
  color: var(--sage-150);
}

.type-body {
  font-family: var(--font-base);
  color: var(--sage-300);
}

.type-small {
  font-family: var(--font-base);
  color: var(--sage-300);
}

.type-tiny {
  font-family: var(--font-base);
  font-size: 0.8125rem; /* 13px */
  color: var(--sage-400);
}

.w400 {
  font-weight: 400;
}

.w500 {
  font-weight: 500;
}

.w600 {
  font-weight: 600;
}

/* Visually hidden but still readable by screen readers/AT — use whenever an
   icon alone carries meaning that sighted users get from context/layout. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- Buttons (+ layered-echo and stacked-layer modifiers) ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-base);
  font-weight: 500;
  padding: var(--space-sm) var(--space-lg);
  border-radius: var(--radius-m);
  text-decoration: none;
  cursor: pointer;
  border: none;
  transition: background-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
}

.btn-fill {
  background: var(--sham-100);
  color: var(--sham-800);
}

.btn-fill:hover {
  background: var(--sham-050);
}

/* Dark variant — combine with .btn-fill (<a class="btn btn-fill
   btn-fill-dark">): near-black fill instead of the bright sham-100 one, for
   use against lighter/mid-tone section backgrounds (e.g. .cta-band's
   sham-300) where the default light fill blends in rather than standing
   out. Hover still lightens one step, same convention as every other
   button/link on the site. */
.btn-fill-dark {
  background: var(--sham-800);
  color: var(--sage-300);
}

.btn-fill-dark:hover {
  background: var(--sham-600);
}

/* Outline drawn as an inset box-shadow rather than a real border: a real
   border adds to the element's rendered size (even with box-sizing:
   border-box, since these buttons are auto-sized, not given an explicit
   width/height for border-box to redistribute into), which made .btn-line
   buttons look a hair larger than .btn-fill ones sitting next to them.
   box-shadow paints inward without affecting layout at all, so both styles
   end up pixel-identical in size. */
.btn-line {
  background: transparent;
  color: var(--sage-300);
  box-shadow: inset 0 0 0 1px var(--sage-400);
}

.btn-line:hover {
  color: var(--sage-200);
  box-shadow: inset 0 0 0 1px var(--sage-300);
}

/* Same outlined/transparent structure as .btn-line, just green by default
   instead of on hover — used for the header's CTA, which needs to read as
   on-brand without the fully-filled weight of .btn-fill. */
.btn-line-green {
  color: var(--sham-100);
  box-shadow: inset 0 0 0 1px var(--sham-200);
}

.btn-line-green:hover {
  color: var(--sham-050);
  box-shadow: inset 0 0 0 1px var(--sham-100);
}

.btn-ghost {
  background: var(--sham-400);
  color: var(--sage-200);
}

.btn-ghost:hover {
  background: var(--sham-300);
}

.btn-icon {
  width: 16px;
  height: 16px;
  margin-right: var(--space-xs);
  flex-shrink: 0;
}

/* Trailing variant — icon after the label instead of before (Book-a-x CTAs).
   Nudges right on hover instead of scaling/bouncing, matching the rest of
   the system's fast, non-bouncy hover transitions. */
.btn-icon-trail {
  margin-right: 0;
  margin-left: var(--space-xs);
  transition: transform 0.15s ease-out;
}

.btn:hover .btn-icon-trail {
  transform: translateX(3px);
}

/* Fanned-outline technique shared with .framed below (declarations and the
   scroll-driven offset live there). Like .framed, this must be a bare wrapper
   around the real button rather than applied to the button itself: an
   element's own in-flow/flex content does not reliably paint above its own
   negative-z-index pseudo-elements (the same painting-order issue documented
   on .framed below), so the outline copies can end up rendering over the
   button's own label. Wrap it: <span class="btn-layered"><a class="btn ...">. */
.btn-layered {
  position: relative;
  z-index: 0;
  display: inline-flex;
}

/* Stacked-layer effect: a real top element (the button itself here; a plain
   labeled box in the standalone "Layered stack" motif below) sits solid above
   two receding, wider, fainter ::before/::after bars peeking out below it.
   Shared between .btn-stack and .stratum-wrap since the technique and
   geometry ratio are identical, only the real top element's size differs.
   Distance is scroll-driven the same way as .framed/.btn-layered below, via
   --scroll-t. */
.btn-stack,
.stratum-wrap {
  position: relative;
  z-index: 0;
  display: inline-flex;
  --scroll-t: 1;
}

.btn-stack {
  margin-bottom: 22px;
}

.btn-stack::before,
.btn-stack::after,
.stratum-wrap::before,
.stratum-wrap::after {
  content: "";
  position: absolute;
  z-index: -1;
  box-sizing: border-box;
  border-radius: var(--radius-m);
  pointer-events: none;
}

.btn-stack::before,
.stratum-wrap::before {
  opacity: 0.35;
  border: 2px solid var(--sage-700);
}

.btn-stack::after,
.stratum-wrap::after {
  opacity: 0.6;
  border: 2px solid var(--sage-600);
}

.btn-stack::before {
  height: 40px;
  left: calc(-16px * var(--scroll-t));
  right: calc(-16px * var(--scroll-t));
  bottom: calc(-22px * var(--scroll-t));
}

.btn-stack::after {
  height: 40px;
  left: calc(-8px * var(--scroll-t));
  right: calc(-8px * var(--scroll-t));
  bottom: calc(-11px * var(--scroll-t));
}

/* Dark variant — combine with .btn-stack (<span class="btn-stack
   btn-stack-dark">): echo bars shifted one step darker each (sage-700/
   sage-800 instead of sage-700/sage-600) so they read clearly against a
   mid-tone background like .cta-band's sham-300 instead of nearly matching
   it, staying visually consistent with .btn-fill-dark's near-black fill
   sitting on top of them. Darkest (sage-800) on ::after, the nearer/more
   opaque bar right below the button; lighter (sage-700) on ::before, the
   fainter bar further out. */
.btn-stack-dark::before {
  border-color: var(--sage-700);
}

.btn-stack-dark::after {
  border-color: var(--sage-800);
}

.stratum-wrap {
  margin: 0 auto;
}

.stratum-wrap::before {
  height: 34px;
  left: calc(-26px * var(--scroll-t));
  right: calc(-26px * var(--scroll-t));
  bottom: calc(-30px * var(--scroll-t));
}

.stratum-wrap::after {
  height: 34px;
  left: calc(-13px * var(--scroll-t));
  right: calc(-13px * var(--scroll-t));
  bottom: calc(-15px * var(--scroll-t));
}

.stratum-top {
  width: 114px;
  height: 34px;
  background: var(--sham-100);
  color: var(--sham-800);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  letter-spacing: 0.05em;
  border-radius: var(--radius-m);
}

/* ---------- Tags & pills ---------- */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-md);
  border-radius: var(--radius-pill);
  font-weight: 500;
  border: 1px solid var(--sage-600);
  color: var(--sage-200);
  background: transparent;
}

.pill-accent {
  border-color: var(--sham-100);
  color: var(--sham-050);
}

.pill .pill-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

/* ---------- Cards ----------
   .card is the base — apply it wherever a bordered/filled content card is
   needed (see .value-item below for an example of combining it with a
   page-specific context class). Modifiers below (.card-horizontal,
   .card-accent, .card-outline) cover the variations shown in the Cards demo;
   add more modifiers here rather than hand-rolling a one-off card elsewhere. */
.card {
  background: var(--sham-400);
  /* No border at all — a visible line reads as a hard outline. Edge
     definition instead comes from a hairline INSET shadow, barely lighter
     than the card's own fill: it crisps the corner (no anti-aliased mush)
     without reading as an outline, and being inset (not offset/directional)
     it doesn't imply a light source or elevation the way a bevel would. The
     second, outer shadow layer is intentionally tiny/low-opacity — just
     enough to separate the card from the page at a subconscious level, not
     a visible drop shadow that would read as the card floating above it. */
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.06),
    0 1px 3px rgba(0, 0, 0, 0.12);
  border-radius: var(--radius-l);
  padding: var(--space-2xl);
  width: 220px;
}

/* Icons in cards sit raw on the card's own background — no badge box, border
   or fill behind them. Shared base, reused (with per-context size/margin) by
   .card, .value-item and .property-row. */
.card-icon {
  color: var(--sham-100);
  display: block;
  flex-shrink: 0;
}

.card .card-icon {
  width: 32px;
  height: 32px;
  margin-bottom: var(--space-md);
}

.card h4 {
  font-family: var(--font-serif);
  color: var(--sage-150);
  margin: 0 0 0.35rem;
}

.card p {
  color: var(--sage-400);
  margin: 0;
  line-height: 1.45;
}

.card .card-btn {
  margin-top: var(--space-md);
  padding: var(--space-xs) var(--space-md);
}

/* Modifier: icon and text laid out side by side instead of stacked. */
.card-horizontal {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
}

.card-horizontal .card-icon {
  margin-bottom: 0;
}

/* Child pattern: a title + trailing badge/tag row, used in place of a bare h3/h4. */
.card .card-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-xs);
  margin-bottom: var(--space-xs);
}

.card .card-top h3,
.card .card-top h4 {
  margin: 0;
}

/* Layout wrapper for laying several cards out side by side — not a card itself. */
.card-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
}

/* Modifier: accent background. */
.card-accent {
  background: var(--sham-300);
  border-color: var(--sham-300);
}

.card-accent p {
  color: var(--sage-200);
}

/* Modifier: border-only, no fill. */
.card-outline {
  background: var(--sham-400);
  border: 2px solid var(--sham-100);
}

/* ---------- Scrolling logo marquee ---------- */
.marquee {
  overflow: hidden;
  width: 100%;
  -webkit-mask-image: linear-gradient(90deg, transparent, black 8%, black 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, black 8%, black 92%, transparent);
}

.marquee-track {
  display: flex;
  gap: var(--space-xl);
  width: max-content;
  animation: scroll-left 16s linear infinite;
}

.marquee-logo {
  width: 88px;
  height: 44px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--sage-600);
  border-radius: var(--radius-m);
  color: var(--sage-400);
  font-family: monospace;
  letter-spacing: 0.04em;
}

@keyframes scroll-left {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

/* ---------- Image treatment & static backgrounds ----------
   Shared 220×140 placeholder-block sizing, reused by every static demo visual
   below (.img-block, .pattern-block, .crosshatch-block, .starfield-block,
   .contour-block, .ripple-block all use it identically) — and by a couple of
   index.html's pillar visuals, which override the size at their point of use. */
.img-block,
.pattern-block,
.crosshatch-block,
.starfield-block,
.contour-block,
.ripple-block {
  width: 220px;
  height: 140px;
  border-radius: var(--radius-m);
  border: 1px solid var(--sage-700);
}

.img-block {
  background: linear-gradient(135deg, var(--sham-300), var(--sham-400));
}

.pattern-dots {
  background-color: var(--sham-800);
  background-image: radial-gradient(var(--sage-600) 1.5px, transparent 1.5px);
  background-size: 16px 16px;
}

.crosshatch-block {
  background-color: var(--sham-800);
  background-image:
    radial-gradient(var(--sage-600) 1px, transparent 1px),
    radial-gradient(var(--sham-300) 1px, transparent 1px);
  background-size: 12px 12px, 18px 18px;
  background-position: 0 0, 6px 9px;
}

.starfield-block {
  background-color: var(--sham-800);
  background-image:
    radial-gradient(2px 2px at 20px 30px, var(--sage-300), transparent),
    radial-gradient(1.5px 1.5px at 60px 80px, var(--sham-050), transparent),
    radial-gradient(2px 2px at 100px 20px, var(--sage-400), transparent),
    radial-gradient(1px 1px at 140px 65px, var(--sage-300), transparent),
    radial-gradient(2px 2px at 185px 100px, var(--sham-025), transparent),
    radial-gradient(1.5px 1.5px at 40px 115px, var(--sage-400), transparent),
    radial-gradient(1px 1px at 165px 40px, var(--sage-300), transparent),
    radial-gradient(2px 2px at 205px 70px, var(--sham-050), transparent),
    radial-gradient(1px 1px at 90px 110px, var(--sage-400), transparent);
  animation: twinkle 3s ease-in-out infinite;
}

@keyframes twinkle {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.55;
  }
}

/* ---------- Starfield section background ----------
   Same dot-field idea as .starfield-block, scaled up to tile across a whole
   section instead of one small box. Two independently-tiled layers (different
   tile size, offset and animation timing each) so stars don't all fade in
   sync — only some dim/brighten at any given moment. */
.starfield-bg {
  position: relative;
  isolation: isolate;
}

.starfield-bg > * {
  position: relative;
  z-index: 1;
}

.starfield-bg::before,
.starfield-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.starfield-bg::before {
  background-image:
    radial-gradient(1.5px 1.5px at 15px 25px, var(--sage-300), transparent),
    radial-gradient(1px 1px at 90px 60px, var(--sham-050), transparent),
    radial-gradient(1.5px 1.5px at 150px 15px, var(--sage-400), transparent),
    radial-gradient(1px 1px at 40px 100px, var(--sham-025), transparent),
    radial-gradient(1px 1px at 120px 115px, var(--sage-300), transparent);
  background-size: 180px 140px;
  animation: twinkle-a 4s ease-in-out infinite;
}

.starfield-bg::after {
  background-image:
    radial-gradient(1px 1px at 60px 20px, var(--sage-400), transparent),
    radial-gradient(1.5px 1.5px at 20px 80px, var(--sham-050), transparent),
    radial-gradient(1px 1px at 130px 70px, var(--sage-300), transparent),
    radial-gradient(1px 1px at 165px 125px, var(--sham-025), transparent),
    radial-gradient(1.5px 1.5px at 95px 45px, var(--sage-400), transparent);
  background-size: 210px 160px;
  background-position: 45px 35px;
  animation: twinkle-b 5.5s ease-in-out infinite;
  animation-delay: 1.4s;
}

@keyframes twinkle-a {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.3;
  }
}

@keyframes twinkle-b {
  0%,
  100% {
    opacity: 0.85;
  }
  40% {
    opacity: 0.2;
  }
  70% {
    opacity: 0.85;
  }
}

.contour-block {
  position: relative;
  overflow: hidden;
  background: var(--sham-800);
}

/* Rings rest muted (sage-700) and sweep outer-to-inner (r5 → r1) on a shared
   4.2s loop. Each ring eases up from muted to a bright peak and back down
   over a 0.6s window (0.3s rise, 0.3s fall) — peaks are only 0.4s apart, so
   each ring is still easing down while the next is already easing up: that
   overlap (not a separate discrete "dim" keyframe) is what reads as a
   smoothly traveling glow with a trailing/leading fade, rather than a
   strobe. Rings shrink slightly (scale) the brighter they get. r1 (center)
   is the last to peak, at 1.9s in; the cycle then holds muted for 2s before
   looping. Every ring needs its own @keyframes since each one's rise/peak/
   fall timing is unique — a single shared keyframes + per-ring
   animation-delay can't express overlapping asymmetric windows like this. */
.contour-ring {
  position: absolute;
  left: 60%;
  top: 65%;
  transform: translate(-50%, -50%);
  border-radius: 3px;
  border: 2px solid var(--sage-700);
  animation-duration: 4.2s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

.contour-ring.r1 {
  width: 36px;
  height: 22px;
  animation-name: contour-pulse-r1;
}

.contour-ring.r2 {
  width: 74px;
  height: 46px;
  animation-name: contour-pulse-r2;
}

.contour-ring.r3 {
  width: 116px;
  height: 74px;
  animation-name: contour-pulse-r3;
}

.contour-ring.r4 {
  width: 164px;
  height: 106px;
  animation-name: contour-pulse-r4;
}

.contour-ring.r5 {
  width: 210px;
  height: 140px;
  animation-name: contour-pulse-r5;
}

@keyframes contour-pulse-r5 {
  0% { border-color: var(--sage-700); transform: translate(-50%, -50%) scale(1); box-shadow: none; }
  7.14% { border-color: var(--sham-025); transform: translate(-50%, -50%) scale(0.93); box-shadow: 0 0 4px var(--sham-025); }
  14.29% { border-color: var(--sage-700); transform: translate(-50%, -50%) scale(1); box-shadow: none; }
  100% { border-color: var(--sage-700); transform: translate(-50%, -50%) scale(1); box-shadow: none; }
}

@keyframes contour-pulse-r4 {
  0%, 9.52% { border-color: var(--sage-700); transform: translate(-50%, -50%) scale(1); box-shadow: none; }
  16.67% { border-color: var(--sham-025); transform: translate(-50%, -50%) scale(0.93); box-shadow: 0 0 4px var(--sham-025); }
  23.81%, 100% { border-color: var(--sage-700); transform: translate(-50%, -50%) scale(1); box-shadow: none; }
}

@keyframes contour-pulse-r3 {
  0%, 19.05% { border-color: var(--sage-700); transform: translate(-50%, -50%) scale(1); box-shadow: none; }
  26.19% { border-color: var(--sham-025); transform: translate(-50%, -50%) scale(0.93); box-shadow: 0 0 4px var(--sham-025); }
  33.33%, 100% { border-color: var(--sage-700); transform: translate(-50%, -50%) scale(1); box-shadow: none; }
}

@keyframes contour-pulse-r2 {
  0%, 28.57% { border-color: var(--sage-700); transform: translate(-50%, -50%) scale(1); box-shadow: none; }
  35.71% { border-color: var(--sham-025); transform: translate(-50%, -50%) scale(0.93); box-shadow: 0 0 4px var(--sham-025); }
  42.86%, 100% { border-color: var(--sage-700); transform: translate(-50%, -50%) scale(1); box-shadow: none; }
}

@keyframes contour-pulse-r1 {
  0%, 38.10% { border-color: var(--sage-700); transform: translate(-50%, -50%) scale(1); box-shadow: none; }
  45.24% { border-color: var(--sham-025); transform: translate(-50%, -50%) scale(0.93); box-shadow: 0 0 4px var(--sham-025); }
  52.38%, 100% { border-color: var(--sage-700); transform: translate(-50%, -50%) scale(1); box-shadow: none; }
}

.ripple-block {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--sham-800);
}

.ripple-core {
  width: 12px;
  height: 12px;
  background: var(--sham-100);
  z-index: 2;
}

.ripple-ring {
  position: absolute;
  border: 2px solid var(--sham-100);
  width: 12px;
  height: 12px;
  animation: ripple 2.4s ease-out infinite;
}

.ripple-ring.r2 {
  animation-delay: 0.8s;
}

.ripple-ring.r3 {
  animation-delay: 1.6s;
}

@keyframes ripple {
  0% {
    width: 12px;
    height: 12px;
    opacity: 0.9;
  }
  100% {
    width: 190px;
    height: 120px;
    opacity: 0;
  }
}

/* ---------- Framing: fanned double-outline behind any element ----------
   The outline layers are pure pseudo-elements (::before/::after), never
   separate layer divs. `.framed` itself must stay a bare wrapper with no
   background of its own — an element's own background always paints before
   its own negative-z-index pseudo-elements (CSS painting order), so if the
   card/image were `.framed` directly, the outlines would paint on top of its
   fill. Wrapping it one level up sidesteps that: the real card/image becomes
   a normal in-flow child, which paints after the wrapper's negative-z-index
   pseudo-elements — genuinely behind it. .btn-layered (above, in Buttons)
   reuses this exact same pseudo-element technique for the button family, so
   the declarations are shared here via one selector list rather than
   repeated for both classes. */
.framed {
  position: relative;
  z-index: 0;
  display: inline-block;
  max-width: 100%; /* never exceed its column, so a wide child (pillar visual) shrinks instead of overflowing */
  border-radius: var(--radius-m);
}

.framed::before,
.framed::after,
.btn-layered::before,
.btn-layered::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius-m);
  border: 1.5px solid var(--sage-700);
  z-index: -1;
  pointer-events: none;
}

/* Both .framed and .btn-layered scale their offset by --scroll-t instead of using a
   fixed translate (see js/scroll-effects.js): --scroll-t is 0 while the element is
   just entering view at the bottom of the viewport (outlines sit flush behind it),
   reaches 1 — the original static 10px/20px offset — once the element is vertically
   centered in the viewport, and keeps growing past 1 as it continues scrolling on
   toward and past the top. The 1 fallback keeps this identical to a static rule if
   JS never runs. */
.framed,
.btn-layered {
  --scroll-t: 1;
}

.framed::before,
.btn-layered::before {
  transform: translate(calc(10px * var(--scroll-t)), calc(-10px * var(--scroll-t)));
}

.framed::after,
.btn-layered::after {
  transform: translate(calc(20px * var(--scroll-t)), calc(-20px * var(--scroll-t)));
  opacity: 0.6;
}

/* ---------- Divider ---------- */
.hairline {
  height: 1px;
  background: var(--sage-600);
  width: 100%;
}

/* ---------- Section header pattern (eyebrow + title + subtitle) ---------- */
.header-pattern .eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--sham-050);
  font-weight: 700;
  margin-bottom: var(--space-xs);
}

.header-pattern h3 {
  margin-bottom: 0.3rem;
}

.header-pattern p {
  color: var(--sage-400);
  margin: 0;
}

/* ---------- Animation style: data-constellation + organic-blob network ---------- */
.constellation-block {
  width: 240px;
  height: 160px;
  border-radius: var(--radius-m);
  border: 1px solid var(--sage-700);
  background: var(--sham-800);
}

.constellation-block .link {
  stroke: var(--sage-600);
  stroke-width: 1.5;
}

.constellation-block .pulse {
  stroke: var(--sham-100);
  stroke-width: 2;
  stroke-dasharray: 6 120;
  animation: pulse-travel 3s linear infinite;
}

.constellation-block .node {
  fill: var(--sham-100);
}

.constellation-block .node.dim {
  fill: var(--sage-600);
}

/* ---------- Chip block: square core + orthogonal traces + capsule blobs ----------
   For "The brain thinks in games": a CPU-like square core (.chip-core, a lucide
   cpu glyph) routes Manhattan (90°) circuit traces out to 7 organic blob nodes
   filled with real hero game capsules (same .blob-node/.thumb/.sq-* components as
   the hero field), plus a few traces that dead-end in small solder-point circles.
   The trace SVG uses a 360×240 viewBox that maps 1:1 to the block, so bends stay a
   true 90° and the traces meet the fixed-px blob/core edges exactly; routes are
   authored crossing-free (verified geometrically).

   Animation: no travelling pulse — instead each whole trace AND its end node
   (blob or solder circle) briefly glow together, one at a time, chasing clockwise
   round the chip. Each element carries an inline --i (0-9, its position in the
   loop); the shared 3s keyframes flash for a brief window and animation-delay
   (--i * 0.3s) staggers them into quick succession that repeats seamlessly. */
.chip-block {
  position: relative;
  width: 360px;
  height: 240px;
  border-radius: var(--radius-m);
  border: 1px solid var(--sage-700);
  background: var(--sham-800);
  overflow: hidden;
}

.chip-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* Each trace/pin/blob sets its own animation-duration + (negative) animation-delay
   inline, so every line runs on its own random cadence and phase — the glow reads
   as random twinkles rather than a clockwise chase. Resting colours below also
   cover the reduced-motion (animation disabled) case. */
.chip-trace {
  fill: none;
  stroke: var(--sage-600);
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.5;
  opacity: 0.55;
  animation-name: chip-glow;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

.chip-nub {
  fill: var(--sham-050);
}

.chip-pin {
  fill: var(--sham-800);
  stroke: var(--sage-500);
  stroke-width: 1.5;
  animation-name: chip-pin-glow;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

/* Trace flashes bright for a brief window, otherwise rests dim (sage-600). */
@keyframes chip-glow {
  0%, 6% { stroke: var(--sage-600); opacity: 0.55; filter: none; }
  11% { stroke: var(--sham-025); opacity: 1; filter: drop-shadow(0 0 3px var(--sham-025)) drop-shadow(0 0 7px var(--sham-050)); }
  16%, 100% { stroke: var(--sage-600); opacity: 0.55; filter: none; }
}

/* Solder-point circle glows in sync with its trace. */
@keyframes chip-pin-glow {
  0%, 6% { fill: var(--sham-800); stroke: var(--sage-500); filter: none; }
  11% { fill: var(--sham-025); stroke: var(--sham-025); filter: drop-shadow(0 0 4px var(--sham-025)); }
  16%, 100% { fill: var(--sham-800); stroke: var(--sage-500); filter: none; }
}

/* Capsule blob glows in sync with its trace (scoped so hero blobs are untouched;
   duration + delay set inline per blob to match its trace). */
.chip-block .blob-node {
  animation-name: chip-blob-glow;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

@keyframes chip-blob-glow {
  0%, 6% { box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); }
  11% { box-shadow: 0 0 8px var(--sham-050), 0 0 18px var(--sham-100), 0 2px 10px rgba(0, 0, 0, 0.3); }
  16%, 100% { box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); }
}

.chip-core {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 56px;
  height: 56px;
  transform: translate(-50%, -50%);
  border-radius: 6px;
  border: 1.5px solid var(--sham-050);
  background: var(--sham-800);
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
  z-index: 2;
}

/* inner die outline */
.chip-core::before {
  content: "";
  position: absolute;
  inset: 7px;
  border: 1px solid var(--sage-600);
  border-radius: 3px;
}

/* ---------- Neural block: hero-style blob network (pillar placeholder) ----------
   Same idea as .constellation-block above — a small self-contained SVG
   illustration, no JS — but nodes are real .blob-node squircles with
   icon-glyph placeholders (reusing the real hero's own classes) instead of
   plain SVG rects, and .pulse reuses the hero's glow styling (same
   drop-shadow bloom) riding on .constellation-block's dash-travel animation
   rather than the hero's own JS-driven pulse spawning. Structure is a
   hand-authored tree, not procedurally generated like the real hero: 1
   center node, 5 branches off it, and a few of those branches have their
   own sub-branch. Sizes step down 70% per generation (40px / 28px / 20px).
   Every edge has a .pulse, and each one is drawn child-to-parent (reversed
   from its matching .link) so the dash-travel animation reads as flowing
   inward toward the center node from every branch, rather than outward. */
.neural-block {
  position: relative;
  width: 240px;
  height: 160px;
  border-radius: var(--radius-m);
  border: 1px solid var(--sage-700);
  background: var(--sham-800);
  overflow: hidden;
}

.neural-block-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.neural-block .link {
  stroke: var(--sage-600);
  stroke-width: 1.5;
  opacity: 0.55;
}

.neural-block .pulse {
  stroke: var(--sham-025);
  stroke-linecap: round;
  stroke-width: 1.75;
  stroke-dasharray: 6 120;
  opacity: 0.95;
  filter: drop-shadow(0 0 2px var(--sham-025)) drop-shadow(0 0 6px var(--sham-050));
  animation: pulse-travel 3s linear infinite;
}

@keyframes pulse-travel {
  to {
    stroke-dashoffset: -252;
  }
}

/* ---------- Handshake block: human hand meets AI hand (pillar visual) ----------
   Concept for "AI reasoning meets human expertise": a detailed, organic human
   hand (left, one light grey line) is met by a low-poly "machine" hand (right,
   brand-green facets that draw on one segment at a time). Every stroke uses
   pathLength="1" so a keyframe animating stroke-dashoffset 1 -> 0 draws any path
   fully regardless of real length.

   Timeline (shared 7.4s clock, NO animation-delay): each element carries its own
   @keyframes (hd_hN and hd_gN, set via inline animation-name) that bakes in its
   draw start + duration but shares one synchronized tail — every stroke holds fully
   drawn, then all UNDRAW together (offset 0 -> 1) over the same 1.5s window
   (~73% -> ~93%) before the loop restarts. Per-element keyframes rather than a
   shared keyframe + delay are what make the staggered draw-on and the
   simultaneous undraw coexist (delay would phase-shift the undraw too). Same
   approach the contour-ring stack uses. The human hand draws ~2x quicker than
   the green hand's segments; the whole clasp bobs gently (.hs-shake). */
.handshake-block {
  width: 360px;
  max-width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  border-radius: var(--radius-m);
  border: 1px solid var(--sage-700);
  background: var(--sham-800);
}

.hs-shake {
  transform-box: fill-box;
  transform-origin: center;
  animation: hs-bob 1.7s ease-in-out infinite;
}

.hs-human-line {
  fill: none;
  stroke: var(--sage-300);
  stroke-width: 1.6;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.hs-human-detail {
  fill: none;
  stroke: var(--sage-400);
  stroke-width: 1;
  stroke-linecap: round;
  opacity: 0.8;
}

.hs-poly-line {
  fill: none;
  stroke: var(--sham-050);
  stroke-width: 1.6;
  stroke-linejoin: round;
  stroke-linecap: round;
  filter: drop-shadow(0 0 2px var(--sham-050)) drop-shadow(0 0 6px var(--sham-100));
}

.hs-poly-facet {
  fill: none;
  stroke: var(--sham-100);
  stroke-width: 1;
  stroke-linecap: round;
  opacity: 0.7;
}

.hs-poly-node {
  fill: var(--sham-025);
  opacity: 0;
  filter: drop-shadow(0 0 3px var(--sham-050));
  animation: hs-node-pop 7.4s ease-in-out infinite;
}

/* Draw-on: each element hides (offset 1), draws (-> 0) in its own window, holds,
   then all undraw together (-> 1) over ~73%->93%. animation-name is per element
   (inline); the shared timing lives here. */
.draw {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation-duration: 7.4s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

/* Human hand — draws ~2x quicker (0.8s outline) */
@keyframes hd_h1 { 0%{stroke-dashoffset:1} 10.81%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_h2 { 0%,4.73%{stroke-dashoffset:1} 12.16%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_h3 { 0%,6.76%{stroke-dashoffset:1} 14.19%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }

/* Green AI hand — segments draw quickly (1.0s) with a tight 0.07s stagger, so
   the whole hand (fingers included) appears nearly together and finishes early */
@keyframes hd_g1  { 0%,14.86%{stroke-dashoffset:1} 28.38%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_g2  { 0%,15.81%{stroke-dashoffset:1} 29.32%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_g3  { 0%,16.76%{stroke-dashoffset:1} 30.27%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_g4  { 0%,17.70%{stroke-dashoffset:1} 31.22%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_g5  { 0%,18.65%{stroke-dashoffset:1} 32.16%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_g6  { 0%,19.59%{stroke-dashoffset:1} 33.11%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_g7  { 0%,20.54%{stroke-dashoffset:1} 34.05%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_g8  { 0%,21.49%{stroke-dashoffset:1} 35.00%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_g9  { 0%,22.43%{stroke-dashoffset:1} 35.95%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_g10 { 0%,23.38%{stroke-dashoffset:1} 36.89%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_g11 { 0%,24.32%{stroke-dashoffset:1} 37.84%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }
@keyframes hd_g12 { 0%,25.27%{stroke-dashoffset:1} 38.78%{stroke-dashoffset:0} 72.97%{stroke-dashoffset:0} 93.24%{stroke-dashoffset:1} 100%{stroke-dashoffset:1} }

/* Vertex dots pop in with the fingers, fade out with the synchronized undraw */
@keyframes hs-node-pop {
  0%, 26% { opacity: 0; }
  40%, 72.97% { opacity: 0.9; }
  93.24%, 100% { opacity: 0; }
}

@keyframes hs-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(3px); }
}

@media (prefers-reduced-motion: reduce) {
  .draw {
    stroke-dashoffset: 0;
    opacity: 1;
    animation: none;
  }
  .hs-poly-node {
    opacity: 0.9;
    animation: none;
  }
  .hs-shake {
    animation: none;
  }
  /* Chip block: no chasing glow — hold every trace/node in its resting state. */
  .chip-trace,
  .chip-pin,
  .chip-block .blob-node {
    animation: none;
  }
  /* Scroll-reveal effects: keep the subhead in its resting state and skip the
     icon bounce. (js/scroll-effects.js also bails out under reduced motion, so
     .reveal-ready is normally never even added — this is a belt-and-suspenders
     guard.) */
  .reveal-ready .heading p {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .property-row .card-icon.is-bounced {
    animation: none;
  }
}

/* Off-screen animation pause: decorative blocks (the pillar visuals here, more
   on branding.html) keep running their looping CSS animations forever, even
   scrolled out of view. js/scroll-effects.js watches each one with an
   IntersectionObserver and sets [data-anim-paused] while it's off-screen; this
   halts every animation inside it so no frames are spent on visuals nobody can
   see. The blob-wander hero field is gated the same way, but from JS since it's
   a requestAnimationFrame loop rather than a CSS animation. */
[data-anim-paused],
[data-anim-paused] * {
  animation-play-state: paused !important;
}

.hero-demo {
  position: relative;
  width: 560px;
  max-width: 100%;
  height: 260px;
  background: var(--sham-800);
  border-radius: var(--radius-m);
  border: 1px solid var(--sage-700);
  overflow: hidden;
}

.hero-demo .headline {
  position: absolute;
  top: 85px;
  left: 0;
  width: 100%;
  text-align: center;
  font-family: var(--font-serif);
  font-weight: 700;
  color: var(--sage-700);
  z-index: 0;
  margin: 0;
  letter-spacing: 0.02em;
}

.hero-demo svg.network {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
}

.hero-demo .network .link {
  stroke: var(--sage-600);
  stroke-width: 1.5;
  opacity: 0.55;
}

/* Visual style only — src/js/blob-wander.js spawns these dynamically on any
   line and drives their x1/y1/x2/y2 itself each frame, so no animation is
   declared here. Stroke color comes from a per-pulse gradient set inline by
   the JS (fades along the pulse's own length, not a flat color — see
   createPulseUnit), which always wins over any stroke declared here, so
   none is set in this rule. Rounded caps turn the segment into a soft
   capsule instead of a hard-edged rectangle. Glow is two chained
   drop-shadows (a tight hot halo at the same bright core color, plus a
   wider softer one a step down the ramp) — layering blur radii like this
   reads as a real bloom rather than a flat blur. mix-blend-mode: screen
   makes overlapping pulses (or a pulse crossing a base line) add light
   instead of just alpha-blending, so crossings brighten like real light
   does. */
.hero-demo .network .pulse {
  stroke-linecap: round;
  stroke-width: 1.75;
  opacity: 0.95;
  mix-blend-mode: screen;
  filter: drop-shadow(0 0 2px var(--sham-025)) drop-shadow(0 0 6px var(--sham-050));
}

.hero-node {
  position: absolute;
  z-index: 2;
  will-change: transform;
}

.hero-demo.wide {
  width: 640px;
  height: 280px;
}

.hero-demo.wide .headline {
  top: 100px;
}

.hero-demo .d1 {
  top: 22px;
  left: 25px;
  width: 66px;
  height: 66px;
}

.hero-demo .d2 {
  top: 8px;
  left: 125px;
  width: 46px;
  height: 46px;
}

.hero-demo .d3 {
  top: 38px;
  left: 205px;
  width: 78px;
  height: 78px;
}

.hero-demo .d4 {
  top: 12px;
  left: 315px;
  width: 46px;
  height: 46px;
}

.hero-demo .d5 {
  top: 28px;
  left: 415px;
  width: 68px;
  height: 68px;
}

.hero-demo .d6 {
  top: 10px;
  left: 515px;
  width: 46px;
  height: 46px;
}

.hero-demo .d7 {
  top: 55px;
  left: 575px;
  width: 62px;
  height: 62px;
}

.hero-demo .d8 {
  top: 135px;
  left: 55px;
  width: 54px;
  height: 54px;
}

.hero-demo .d9 {
  top: 125px;
  left: 165px;
  width: 46px;
  height: 46px;
}

.hero-demo .d10 {
  top: 150px;
  left: 255px;
  width: 72px;
  height: 72px;
}

.hero-demo .d11 {
  top: 118px;
  left: 375px;
  width: 46px;
  height: 46px;
}

.hero-demo .d12 {
  top: 140px;
  left: 465px;
  width: 66px;
  height: 66px;
}

.hero-demo .d13 {
  top: 195px;
  left: 555px;
  width: 54px;
  height: 54px;
}

.hero-demo .d14 {
  top: 205px;
  left: 35px;
  width: 46px;
  height: 46px;
}

.tile {
  position: relative;
  width: 100%;
  height: 100%;
}

.thumb {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
}

/* Position/size intentionally left to the generic .thumb rule (cover, centered)
   — matching how the real hero's blob-node thumbnails are cropped, with no
   per-image custom framing. Appids pinned in tools/fetch-capsules.py's
   PINNED_APPIDS (so they survive that script's regenerate cycle even if
   they fall out of that day's top-sellers list) — swap the id here and add
   the matching PINNED_APPIDS entry together, never one without the other. */
.thumb-kaz {
  background-image: url('../img/capsules/730.jpg');
}

.thumb-aoe {
  background-image: url('../img/capsules/381210.jpg');
}

.thumb-peak {
  background-image: url('../img/capsules/3240220.jpg');
}

.thumb-raft {
  background-image: url('../img/capsules/359550.jpg');
}

.icon-glyph {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--sage-300);
}

.icon-glyph svg {
  width: 44%;
  height: 44%;
}

.tile-b {
  overflow: hidden;
  background: var(--sham-800);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}

.tile-b .icon-glyph {
  color: var(--sham-100);
}

/* Background color variants for icon tiles in the "Organic blobs" .hero-demo. */
.tile-icon-a {
  background: var(--sham-100);
}

.tile-icon-a .icon-glyph {
  color: var(--sham-800);
}

.tile-icon-b {
  background: var(--sham-050);
}

.tile-icon-b .icon-glyph {
  color: var(--sham-800);
}

.tile-icon-c {
  background: var(--sage-600);
}

.tile-icon-c .icon-glyph {
  color: var(--sage-100);
}

.sq-1 {
  border-radius: 42% 58% 53% 47% / 48% 52% 42% 58%;
}

.sq-2 {
  border-radius: 62% 38% 46% 54% / 55% 42% 58% 45%;
}

.sq-3 {
  border-radius: 50% 50% 66% 34% / 38% 62% 44% 56%;
}

.sq-4 {
  border-radius: 36% 64% 40% 60% / 60% 38% 62% 40%;
}

/* ---------- Chat style (sourced citation snippet) ----------
   Colors/radii on .stage-card/.chat-user/.chat-assistant/.cite-chip below are
   deliberately literal px values, not spacing-scale tokens: they're faithful
   reproductions of computed styles measured on the live app (app.hakuro.dev), so
   they should stay pinned to those exact real-world numbers rather than snap to
   this stylesheet's own scale. Font size is the one exception — it follows the
   sitewide uniform default like everything else non-heading. .chat-assistant is
   the one deliberate departure: it's set in the serif type, not Inter, to read
   as the "considered answer" against the sans-serif .chat-user question. */
.stage-card {
  width: 100%;
  max-width: 500px;
  max-height: 400px;
  overflow: hidden;
  background: var(--sham-400);
  border: 1px solid var(--sage-700);
  border-radius: var(--radius-m);
  padding: 1.1rem;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.35);
  font-family: var(--font-base);
}

@keyframes blink {
  50% {
    opacity: 0;
  }
}

.chat-user {
  background: var(--sham-300);
  color: var(--sage-200);
  border-radius: var(--radius-m);
  padding: 14px;
  line-height: 1.4;
  max-width: 85%;
  margin-left: auto;
  margin-bottom: 0.9rem;
}

.chat-assistant {
  color: var(--sage-300);
  font-family: var(--font-serif);
  line-height: 21px;
}

.chat-assistant b {
  color: var(--sage-150);
  font-weight: 600;
}

.cite-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--sage-150);
  font-weight: 350;
  text-decoration: none;
}

.cite-chip img {
  width: 36px;
  height: 17px;
  object-fit: cover;
  flex-shrink: 0;
}

.cite-chip svg {
  width: 10px;
  height: 10px;
  opacity: 0.7;
}

/* ---------- Chat to-do list (assistant recommendations, in their own labelled box) ----------
   Sits inside .chat-assistant like the rest of the answer, but the to-do
   panel drops back to the sans-serif base type: it's UI chrome (a literal
   checklist), not the model's own "considered answer" prose. Animation for
   the typing/reveal/check-off sequence lives further down, grouped with the
   other animated-chat keyframes. */
.stage-label {
  font-family: var(--font-base);
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--sage-500);
  margin: 0 0 0.4rem;
}

.stage-block + .stage-block {
  margin-top: 0.75rem;
}

.todo-box {
  font-family: var(--font-base);
  border: 1px solid var(--sage-700);
  border-radius: var(--radius-m);
  padding: 0.75rem 0.9rem;
}

.todo-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.todo-item {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
}

.todo-toggle {
  position: relative;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-top: 0.15rem;
  border-radius: 50%;
  border: 1.5px solid var(--sham-100);
}

.todo-toggle .todo-icon-add,
.todo-toggle .todo-icon-done {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 11px;
  height: 11px;
  transform: translate(-50%, -50%);
}

.todo-toggle .todo-icon-add {
  color: var(--sham-100);
}

.todo-toggle .todo-icon-done {
  color: var(--sham-800);
  opacity: 0;
}

.todo-title {
  color: var(--sage-200);
  font-weight: 600;
  line-height: 1.35;
}

/* ---------- Animated chat: typed question, then answer revealed piece by piece ----------
   One shared 9s cycle, looping forever. The user line types out first (0–20% of the
   cycle); the assistant's reveal-seg pieces then fade in one at a time via staggered
   animation-delay (same keyframe, same 9s duration — only the delay differs per piece,
   the same pattern used elsewhere in this system, e.g. the tally-chase accent). */
.hero-chat-user-text {
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  vertical-align: bottom;
  width: 0;
  max-width: 100%;
  animation: hero-type 9s infinite;
}

@keyframes hero-type {
  0% {
    width: 0;
    animation-timing-function: steps(39, end);
  }
  20% {
    width: 44ch; /* the line is 39 characters; 44ch leaves safe buffer so `overflow:hidden` never clips a glyph */
  }
  97% {
    width: 44ch;
  }
  100% {
    width: 0;
  }
}

.hero-chat-cursor {
  display: inline-block;
  width: 8px;
  height: 1em;
  background: var(--sham-100);
  margin-left: 2px;
  vertical-align: text-bottom;
  animation: blink 0.9s steps(1) infinite, hero-cursor-window 9s infinite;
}

@keyframes hero-cursor-window {
  0% {
    visibility: visible;
  }
  19% {
    visibility: visible;
    animation-timing-function: steps(1, end);
  }
  21% {
    visibility: hidden;
  }
  100% {
    visibility: hidden;
  }
}

.reveal-seg {
  opacity: 0;
  animation: hero-reveal 9s ease-out infinite;
}

span.reveal-seg {
  display: inline-block;
}

@keyframes hero-reveal {
  0% {
    opacity: 0;
    transform: translateY(4px);
  }
  4% {
    opacity: 1;
    transform: translateY(0);
  }
  97% {
    opacity: 1;
    transform: translateY(0);
  }
  100% {
    opacity: 0;
    transform: translateY(4px);
  }
}

.chat-assistant .reveal-seg:nth-child(1) {
  animation-delay: 1.8s;
}

.chat-assistant .reveal-seg:nth-child(2) {
  animation-delay: 2.2s;
}

.chat-assistant .reveal-seg:nth-child(3) {
  animation-delay: 2.6s;
}

.chat-assistant .reveal-seg:nth-child(4) {
  animation-delay: 3.0s;
}

.chat-assistant .reveal-seg:nth-child(5) {
  animation-delay: 3.4s;
}

/* ---------- Animated to-do demo: typed question, staggered insight/to-do reveal, then checking each item off ----------
   One-shot, unlike the looping hero-chat cards. The *settled* end state — full
   question, everything revealed, the middle row checked — is the plain default
   here, so with JS off (or DEBUG animations disabled) the card just shows the
   finished result. js/app.js adds `.is-animating` to rewind the card to its
   empty start, then walks it forward on timers (typing → is-typed, per-element
   .is-revealed, .is-checked on the middle row), replaying each time the
   switcher's panel 1 becomes the active tab; the whole run fits inside the
   ~7.5s before the switcher auto-advances. Timers rather than keyframe delays
   drive the stagger so the run still finishes if the switcher auto-advances
   and display:none's the panel partway through — and each step is a plain
   class toggle, so a hidden element just lands on its final state with no
   animation to reset. Reveal/typing steps are rewound by `.is-animating` and
   restored per element; the check-off is layered straight on top of the
   middle row's default checked look. */
.todo-demo-question {
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  vertical-align: bottom;
  width: max-content; /* settled default: exactly the text's own width, no trailing gap */
  max-width: 100%;
}

.todo-demo-cursor {
  display: inline-block;
  width: 8px;
  height: 1em;
  background: var(--sham-100);
  margin-left: 2px;
  vertical-align: text-bottom;
  visibility: hidden; /* only shown while typing (see .is-animating below) */
}

.todo-demo-reveal {
  opacity: 1; /* settled default; .is-animating rewinds to hidden */
}

/* ---- rewound start state (JS only) ---- */
.todo-demo.is-animating .todo-demo-question {
  width: 0;
}

/* Type out to the question's real rendered width (measured in js/app.js and
   handed over as --type-w) instead of a fixed ch value: Inter is proportional,
   so a ch target overshot the text and the cursor kept drifting right after the
   last letter was in. The step count matches the character count (--type-steps)
   so each jump reveals ~one glyph and the last step lands exactly on the final
   letter. */
.todo-demo.is-typing .todo-demo-question {
  animation: todo-demo-type 1.5s steps(var(--type-steps, 32), end) forwards;
}

/* Hold the full text statically once done — otherwise .is-animating's width:0
   above would snap the question back to empty when .is-typing drops. */
.todo-demo.is-typed .todo-demo-question {
  width: max-content;
}

.todo-demo.is-animating .todo-demo-cursor {
  visibility: visible;
  animation: blink 0.9s steps(1) 2; /* blinks through the ~1.5s type, then .is-typed hides it */
}

.todo-demo.is-typed .todo-demo-cursor {
  visibility: hidden;
  animation: none;
}

@keyframes todo-demo-type {
  from { width: 0; }
  to { width: var(--type-w, 44ch); }
}

.todo-demo.is-animating .todo-demo-reveal {
  opacity: 0;
  transform: translateY(4px);
}

.todo-demo.is-animating .todo-demo-reveal.is-revealed {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

/* Check-off: only the middle row is checked in the settled default (the other
   two rows keep the base unchecked look). While animating, all rows start
   unchecked and .is-checked — added to the middle row alone on a timer — fills
   it in. */
.todo-demo .todo-item:nth-child(2) .todo-toggle {
  background: var(--sham-100);
}

.todo-demo .todo-item:nth-child(2) .todo-icon-add {
  opacity: 0;
}

.todo-demo .todo-item:nth-child(2) .todo-icon-done {
  opacity: 1;
}

.todo-demo.is-animating .todo-item .todo-toggle {
  background: transparent;
}

.todo-demo.is-animating .todo-item .todo-icon-add {
  opacity: 1;
}

.todo-demo.is-animating .todo-item .todo-icon-done {
  opacity: 0;
}

.todo-demo.is-animating .todo-item.is-checked .todo-toggle {
  background: var(--sham-100);
  transition: background 0.25s ease;
}

.todo-demo.is-animating .todo-item.is-checked .todo-icon-add {
  opacity: 0;
  transition: opacity 0.25s ease;
}

.todo-demo.is-animating .todo-item.is-checked .todo-icon-done {
  opacity: 1;
  transition: opacity 0.25s ease;
}


/* ==========================================================================
   2. SITE LAYOUT
   Header/footer chrome shared by every page via _layouts/default.html, the
   one-pager's one-off index.html section layout, and other page-specific
   layouts (e.g. .legal). All built entirely on the tokens and reusable
   components in part 1 above.
   ========================================================================== */

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--sham-800);
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Horizontal gutter that only bites once the viewport is narrower than the
     content + gutters: max-width is padded out by 2× the gutter, so on normal
     (wide) screens the content edge still lands on the --container-w line, exactly
     matching the body sections — but below that width there's always a gutter so
     the logo/CTA never touch the screen edge. */
  padding: var(--space-sm) var(--space-lg);
  max-width: calc(var(--container-w) + 2 * var(--space-lg));
  margin: 0 auto;
}

.logo {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-family: var(--font-serif);
  font-weight: 600;
  text-decoration: none;
}

.logo .mark {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  color: var(--sage-300);
}

.logo-text {
  font-size: 22px;
  font-family: var(--font-serif);
  color: var(--sage-300);
  font-weight: 600;
}

nav.main-nav ul {
  list-style: none;
  display: flex;
  gap: var(--space-lg);
  margin: 0;
  padding: 0;
}

nav.main-nav a {
  text-decoration: none;
  color: var(--sage-300);
  transition: color 0.12s ease;
}

nav.main-nav a:hover {
  color: var(--sage-200);
}

.header-right {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
}

.login-link {
  text-decoration: none;
  color: var(--sage-200);
  transition: color 0.12s ease;
}

.login-link:hover {
  color: var(--sage-150);
}

/* Hamburger — hidden on desktop, shown ≤800px (see media query). Sits just
   left of the Book a demo CTA and opens .mobile-menu, which carries the nav
   links + Login that are otherwise hidden at that width. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--sage-300);
  cursor: pointer;
}

.nav-toggle:hover {
  color: var(--sage-200);
}

.nav-toggle .nav-toggle-icon {
  width: 24px;
  height: 24px;
}

/* Swap menu ↔ close glyph on open state. */
.nav-toggle-close {
  display: none;
}

.nav-toggle[aria-expanded="true"] .nav-toggle-open {
  display: none;
}

.nav-toggle[aria-expanded="true"] .nav-toggle-close {
  display: block;
}

/* Dropdown panel — off on desktop; the media query turns it into a collapsible
   panel below the bar. Content aligns to the same gutter as .header-inner. */
.mobile-menu {
  display: none;
}

/* ---------- Shared section heading ---------- */
.heading {
  text-align: center;
  max-width: var(--container-w-narrow);
  margin: 0 auto var(--space-4xl);
}

.heading h1 {
  margin-bottom: var(--space-xs);
}

.heading p {
  color: var(--sage-400);
  margin-top: var(--space-lg)
}

/* Scroll-reveal: the subhead <p> emerges from under the <h1> — it starts
   with its top gap collapsed (translated up by its own margin, so it sits
   right beneath the h1) and invisible, then fades in while sliding down to
   its resting position as the heading enters view. Driven by .is-visible,
   added by js/scroll-effects.js. Gated on .reveal-ready (also set by that
   script) so no-JS / reduced-motion visitors always see the p in place. */
.reveal-ready .heading p {
  opacity: 0;
  transform: translateY(calc(-1 * var(--space-lg)));
}

/* Transition lives on the revealed state, not the hidden one, so applying the
   hidden state (when .reveal-ready is first added) snaps instantly instead of
   animating a fade-OUT; only the emerge into place is animated. */
.reveal-ready .heading.is-visible p {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

/* Shared section padding rhythm — every full-width content section below (pillars included) uses this. */
.hero,
.pillars,
.value-add,
.use-cases,
.properties,
.cta-band,
.faq {
  padding: var(--space-8xl) var(--space-lg);
}

/* Shared CTA button row — identical in both .hero and .cta-band, so defined once. */
.cta-row {
  display: flex;
  gap: var(--space-md);
  justify-content: center;
}

/* ---------- Hero: organic node-graph cluster surrounding centered copy ----------
   teamworkgraph.com-style: nodes are placed by real randomized sampling (fixed
   seed, so it's reproducible) within an organic, irregularly-lobed annular
   region — not a grid, and not filling the full rectangle — leaving a clear,
   irregularly-shaped gap in the middle for the headline. Every node connects
   to its 4-5 nearest neighbors (a real k-nearest-neighbor graph, topped up so
   no node ends up under 4), so both placement and connections follow the same
   organic randomness rather than a fixed frame/grid pattern. */
.hero {
  position: relative;
  overflow: hidden;
  min-height: 85vh;
  background: var(--sham-600);
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hero-blobs {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform-origin: 50% 50%;
  will-change: transform, filter;
  z-index: 0;
  pointer-events: none;
}

/* width/height:100% is required in addition to inset:0 here: .hero-lines is an
   <svg> (a replaced element with a 1:1 intrinsic aspect ratio from its square
   viewBox), and inset:0 alone does not override that intrinsic ratio — without
   an explicit size the browser rendered it as a perfect square instead of
   stretching to .hero-blobs's actual (wide, non-square) box, which silently
   threw off every line coordinate relative to the node divs. */
.hero-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform-origin: 50% 50%;
  will-change: transform, filter;
}

.hero-copy {
  position: relative;
  z-index: 2;
  max-width: var(--container-w-narrow);
  margin: 0 auto;
}

.hero .eyebrow {
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sham-050);
  font-weight: 700;
  margin-bottom: var(--space-sm);
}

.hero h1 {
  margin: 0 0 var(--space-lg);
  color: var(--sage-300);
}

.hero .sub {
  color: var(--sage-300);
  margin: 0 auto var(--space-lg);
}

/* Connecting lines: the real KNN-graph edges (~130 of them, ~4-5 per node),
   drawn as percentage-coordinate SVG (viewBox 0 0 100 100, preserveAspectRatio
   none) so it tracks the node divs' own top/left % positioning exactly.
   non-scaling-stroke keeps line thickness constant despite the distorted
   (non-square) coordinate system. About a quarter of edges get the traveling
   "pulse" treatment — a bright dash animating along the line via
   stroke-dashoffset — reusing the same technique as the data-constellation
   mechanic in the branding catalog. */
.hero-lines {
  z-index: 0;
}

.hero-lines line {
  stroke: var(--sage-600);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  opacity: 0.55;
}

/* Visual style only — src/js/blob-wander.js spawns these dynamically on any
   line (about 5/sec) and drives their x1/y1/x2/y2 itself each frame, so no
   animation is declared here. Stroke color comes from a per-pulse gradient
   set inline by the JS (fades along the pulse's own length, not a flat
   color — see createPulseUnit), which always wins over any stroke declared
   here, so none is set in this rule. Rounded caps turn the segment into a
   soft capsule instead of a hard-edged rectangle. Glow is two chained
   drop-shadows (a tight hot halo at the same bright core color, plus a
   wider softer one a step down the ramp) — layering blur radii like this
   reads as a real bloom rather than a flat blur. No mix-blend-mode (dropped
   for perf — it forces per-frame backdrop blending instead of isolated
   compositing): against this near-black background, a near-opaque core
   color already reads visually close to screen-blended, since screen(color,
   near-black) ≈ color. Only real loss is that overlapping pulses no longer
   extra-brighten where they cross, which is rare and brief. */
.hero-lines line.pulse {
  stroke-linecap: round;
  stroke-width: 1.75;
  opacity: 0.95;
  filter: drop-shadow(0 0 2px var(--sham-025)) drop-shadow(0 0 6px var(--sham-050));
}

/* Blob field: ~58 nodes, each a squircle-clipped thumbnail or icon tile, drawn
   above the lines. Position/size/animation-delay are set per node via inline
   style (generated); only the reusable per-variant rules live here. */
/* translate(-50%,-50%) centers the node exactly on its own top/left point
   (the browser computes the offset from the node's own actual rendered size,
   no assumed-viewport-size approximation needed) — top/left is set to the
   SAME percentage the matching SVG line endpoint uses, so the two always
   meet exactly regardless of viewport width. */
.blob-node {
  position: absolute;
  z-index: 1;
  overflow: hidden;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
  transform: translate(-50%, -50%);
}

.bg-1 {
  background: var(--sage-600);
}

.bg-2 {
  background: var(--sage-500);
}

.bg-3 {
  background: var(--sage-700);
}
.bg-4 {
  background: var(--sham-300);
}
.bg-5 {
  background: var(--amber-400);
}
.bg-6 {
  background: var(--terra-400);
}

/* Use cases value-prop switcher demo panel reuses the same .stage-card chat
   component as the Pillars interface pillar — but unlike there, it sits
   directly on the switcher's own card background rather than being framed
   as its own separate card, so it reads as part of the switcher rather than
   a nested demo box. */
.switcher-stage .stage-card {
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
}

.switcher-stage .chat-user {
  width: 100%;
  max-width: 100%;
}

/* ---------- Pillars (data ecosystem / industry expertise / model / platform) ----------
   Four alternating copy+visual rows, each making one differentiator concrete.
   Visuals reuse existing design-system placeholder blocks (starfield, contour,
   constellation) plus the real .stage-card chat mockup for the interface
   pillar, each wrapped in .framed for the same fanned-outline treatment used
   elsewhere on the page. */
.pillars {
  background: var(--sham-600); /* matches .hero so bottom-row blob nodes never sit on a color seam */
  padding-top: var(--space-xl);
}

.pillar-list {
  max-width: var(--container-w);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-5xl);
}

.pillar-row {
  display: flex;
  align-items: center;
  gap: var(--space-4xl);
}

.pillar-row.reverse {
  flex-direction: row-reverse;
}

.pillar-copy {
  flex: 1 1 50%;
}

.pillar-visual {
  flex: 1 1 50%;
  display: flex;
  justify-content: center;
}

.pillar-copy h2 {
  margin-bottom: var(--space-xs);
}

.pillar-subtitle {
  color: var(--sham-050);
  font-weight: 500;
  margin: 0 0 var(--space-sm);
}

.pillar-copy > p:last-child {
  color: var(--sage-400);
  margin: 0;
}

/* Placeholder visuals sized up 50% (from the shared design-system defaults) for
   more presence in this section. max-width:100% + aspect-ratio (instead of a
   fixed height) lets them shrink to fit very narrow columns rather than overflow
   the viewport — on normal screens they still render at their full design size. */
.pillar-visual .starfield-block,
.pillar-visual .contour-block {
  width: 330px;
  max-width: 100%;
  height: auto;
  aspect-ratio: 330 / 210;
}

.pillar-visual .constellation-block,
.pillar-visual .neural-block,
.pillar-visual .chip-block {
  width: 360px;
  max-width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
}

.pillar-visual .contour-ring.r1 {
  width: 54px;
  height: 33px;
}

.pillar-visual .contour-ring.r2 {
  width: 111px;
  height: 69px;
}

.pillar-visual .contour-ring.r3 {
  width: 174px;
  height: 111px;
}

.pillar-visual .contour-ring.r4 {
  width: 246px;
  height: 159px;
}

.pillar-visual .contour-ring.r5 {
  width: 315px;
  height: 210px;
}

/* ---------- Value add ---------- */
/* .value-add and .use-cases sit inside .topo-wrap (see index.html), which
   supplies both the shared plain background color (so the seam between
   the two sections stays invisible) and the tiled topographic pattern —
   a wrapper rather than a background on each section individually, so the
   pattern tiles continuously across both instead of restarting per
   section. The source SVG's shapes are unstyled (default black fill), so
   filter:invert flips them to white before the wrapper's own low opacity
   fades them down to a subtle line-work overlay against --sham-800. */
.topo-wrap {
  position: relative;
  background-color: var(--sham-800);
}

.topo-wrap::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /*background-image: url('../img/topo-icon-pattern.svg');*/
  background-repeat: repeat;
  opacity: 1;
}

.topo-wrap > * {
  position: relative;
  z-index: 1;
}

.value-add {
  /* darker plane, differentiated from .pillars above it */
  /* .use-cases right below shares this same background — tighten the shared
     seam so the two don't read as one oversized gap of empty color. */
    padding-top: var(--space-4xl);
}

.value-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-xl);
  max-width: var(--container-w);
  margin: 0 auto;
}

/* Combines with the base .card (see Cards, above) — this only adds the
   deltas specific to sitting in the Value add grid: fills the grid cell
   instead of the card default's fixed width, a larger icon, transparent
   fill with a real hairline border instead of the base card's inset-shadow
   trick (redundant once there's an actual border), and extra breathing
   room between the title and body copy. */
.value-item {
  width: auto;
  background: transparent;
  border: 1px solid var(--sage-700);
  box-shadow: none;
}

/* Each value-item icon sits inside an irregular, rounded "blob" — the same
   organic squircle shapes used by the hero's .blob-node field (.sq-1/2/3, each
   card a different one). The blob is filled with the green the icon used to be
   (--sham-100), and the glyph itself flips to the dark section background green
   (--sham-800, the .topo-wrap fill behind these transparent cards) so it reads
   as cut out of the shape. */
.value-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  margin-bottom: var(--space-md);
  background: var(--sham-100);
}

.value-item .card-icon {
  width: 32px;
  height: 32px;
  margin-bottom: 0; /* spacing now lives on the .value-icon wrapper */
  color: var(--sham-800);
}

/* ---------- Use cases: value-prop switcher (immutable.com-style) ----------
   A horizontal bar of selectable pills sits above a 1/3 (sales copy) + 2/3
   (chat demo) content area; picking a pill updates both sides together.
   CSS-only, same philosophy as the FAQ accordion: real <input type="radio">
   elements (one shared `name`) drive which panels show, via the general
   sibling combinator. Visually hidden but still focusable/keyboard-operable —
   arrow keys move between radios natively (real radio-group behavior), and
   clicking any pill label checks its input. Because panels are toggled with
   display:none/block (not opacity/visibility), the demo's typing/reveal
   animation genuinely restarts every time you switch to it. */
.use-cases {
  /* Mirrors .value-add's padding-bottom trim above — same shared background,
     so the seam between the two should feel like one section, not a gap. */
  padding-bottom: var(--space-xl);
}

/* No longer a .card — the tab bar and copy/stage content sit directly on
   the section's own background (the topo pattern shows through), rather
   than floating on a filled panel. Just a width/centering wrapper now. */
.use-cases-card {
  width: 100%;
  max-width: var(--container-w);
  margin: 0 auto;
  padding-bottom: var(--space-3xl);
}

.switcher-radio {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.switcher-tabbar {
  position: relative; /* anchors .switcher-progress */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xl);
  margin-bottom: var(--space-2xl);
  border-bottom: 1px solid var(--sage-600);
}

/* Auto-advance progress fill: js/app.js positions/sizes this to match the
   active tab's label and animates its scaleX from 0 to 1 over the 5s dwell
   time, restarting on every tab change (auto-advance or manual click) —
   the "border bottom filling from empty to full" indicator. Replaces a
   static full-color active-tab border, since the growing fill itself is
   now what shows which tab is active. */
.switcher-progress {
  position: absolute;
  bottom: -1px;
  left: 0;
  height: 2px;
  width: 0;
  background: var(--sham-100);
  transform-origin: left center;
  transform: scaleX(0);
  pointer-events: none;
}

/* Typography matches h2 (serif, 1.7rem, 600) — the tab labels read as
   section headings you pick between, not as small nav-pill text. */
.switcher-tabbtn {
  display: inline-block;
  cursor: pointer;
  white-space: nowrap;
  padding: var(--space-sm) 0 var(--space-xl) 0;
  border-bottom: 1px solid transparent;
  color: var(--sage-300);
  font-family: var(--font-serif);
  font-size: 1.7rem;
  font-weight: 600;
  transition: color 0.12s ease, border-color 0.12s ease;
}

.switcher-tabbtn:hover {
  color: var(--sage-200);
}

.switcher-content {
  display: flex;
  align-items: stretch; /* copy column stretches to the fixed-height stage so it can center within it */
  gap: var(--space-2xl);
}

.switcher-copy {
  flex: 1 1 33%;
  /* Vertically center the active title + text within the stage's fixed
     height, so the copy sits mid-height of the 700px stage card. */
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Uses the base .card (see Cards, above) for background/radius/padding/edge
   treatment — same as filled cards elsewhere on the site — and only adds the
   switcher-specific layout: it flexes to two-thirds of the row, keeps a fixed
   height, and top-aligns the stage mockup inside it. flex-basis (from `flex`)
   overrides .card's own fixed width. */
.switcher-stage {
  flex: 1 1 67%;
  /* Fixed height (incl. padding, box-sizing:border-box) so the card stays
     the same size regardless of which tab's content is shown. */
  height: 500px;
  display: flex;
  justify-content: center;
  align-items: flex-start; /* stage-card sits at the top of the card */
}

.switcher-panel {
  display: none; /* both copy and stage panels share this class; shown per data-panel below */
}

.switcher-copy h2 {
  margin-bottom: var(--space-sm);
}

.switcher-copy p {
  color: var(--sage-300);
  margin: 0 0 var(--space-md);
}

.switcher-copy ul {
  margin: 0;
  padding-left: 1.1em;
  color: var(--sage-400);
}

.switcher-copy li {
  margin-bottom: var(--space-xs);
}

#uc-1:checked ~ .switcher-content .switcher-panel[data-panel="1"],
#uc-2:checked ~ .switcher-content .switcher-panel[data-panel="2"],
#uc-3:checked ~ .switcher-content .switcher-panel[data-panel="3"] {
  display: block;
}

#uc-1:checked ~ .switcher-tabbar label[for="uc-1"],
#uc-2:checked ~ .switcher-tabbar label[for="uc-2"],
#uc-3:checked ~ .switcher-tabbar label[for="uc-3"] {
  color: var(--sage-150); /* persistent active indicator; .switcher-progress shows the countdown */
}

#uc-1:focus-visible ~ .switcher-tabbar label[for="uc-1"],
#uc-2:focus-visible ~ .switcher-tabbar label[for="uc-2"],
#uc-3:focus-visible ~ .switcher-tabbar label[for="uc-3"] {
  outline: 2px solid var(--sham-050);
  outline-offset: 4px;
}

/* ---------- Why Hakuro / properties ---------- */
.properties {
  background: var(--sham-600);
}

.property-list {
  max-width: var(--container-w-narrow);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
}

.property-row {
  display: flex;
  gap: var(--space-2xl);
  align-items: center;
  padding: var(--space-xl) 0;
  border-bottom: 1px solid var(--sage-800);
}

.property-row:first-child {
  padding-top: 0;
}

.property-row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.property-row .card-icon {
  width: 28px;
  height: 28px;
  transform-origin: center; /* grow/rise about the icon's own center */
}

/* A short one-off bounce as each property icon passes the vertical center of
   the viewport: it grows, brightens and rises briefly, then settles back to
   rest. js/scroll-effects.js toggles .is-bounced via an IntersectionObserver
   whose root is a band across the viewport's middle, and removes it again on
   exit so the bounce can replay each time the icon re-crosses center. Eased
   (not springy) to stay in keeping with the brand's restrained motion. */
@keyframes property-icon-bounce {
  0% {
    transform: translateY(0) scale(1);
    filter: brightness(1);
  }
  40% {
    transform: translateY(-8px) scale(1.5);
    filter: brightness(1.4);
  }
  100% {
    transform: translateY(0) scale(1);
    filter: brightness(1);
  }
}

.property-row .card-icon.is-bounced {
  animation: property-icon-bounce 0.6s ease-in-out;
}

.property-row h2 {
  margin-bottom: var(--space-xs);
}

.property-row p {
  color: var(--sage-500);
  margin: 0;
}

/* Generic-AI-vs-Hakuro comparison table: content sourced from the deck's own
   "core value" slide. Row labels are icon-only (title lives outside the
   card); each cell leads with a cross (generic AI) or check (Hakuro) instead
   of a column divider, so the Hakuro column reads as the clear win at a
   glance without needing a vertical rule to separate the two. */

.comparison-card {
  width: 100%;
  max-width: var(--container-w);
  margin: 0 auto;
  margin-top: var(--space-5xl);
  background: var(--sage-400);
}

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

.comparison-table {
  width: 100%;
  min-width: 560px;
  border-collapse: collapse;
}

.comparison-table th,
.comparison-table td {
  text-align: left;
  vertical-align: top;
  padding: var(--space-lg) var(--space-md);
  border-bottom: 1px solid var(--sage-600);
}

.comparison-table thead th {
  font-family: var(--font-base);
  color: var(--sage-800);
  font-weight: 500;
  padding-top: 0;
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--sage-600);
}

.comparison-table thead th:last-child {
  color: var(--sham-200);
  font-weight: 500;
}

.comparison-table thead th:first-child,
.comparison-table tbody th {
  width: 56px;
  text-align: center;
}

.comparison-table tbody th {
  vertical-align: middle;
}

.comparison-table tbody th .card-icon {
  width: 20px;
  height: 20px;
  margin: 0 auto;
  color: var(--sage-700);
}

.comparison-table tbody td {
  color: var(--sage-700);
}

.compare-cell {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.comparison-table tr:last-child th,
.comparison-table tr:last-child td {
  border-bottom: none;
}

.compare-mark {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.compare-cross {
  color: var(--terra-400);
}

.compare-check {
  color: var(--sham-200);
}

/* ---------- CTA band ---------- */
.cta-band {
  text-align: center;
  background: var(--sham-250);
  color: var(--sham-600);
}

.cta-mark {
  display: block;
  width: 48px;
  height: 48px;
  margin: 0 auto var(--space-md);
}

.cta-band h1{
  color: var(--sham-700);
}
.cta-band p {
  margin: 0 0 var(--space-lg);
  color: var(--sham-700);
}

/* ---------- FAQ ---------- */
.faq {
  background: var(--sham-600);
}

.faq-list {
  max-width: var(--container-w-narrow);
  margin: 0 auto;
}

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

.faq-list details[open] {
  padding-bottom: var(--space-lg); /* gap between the answer and the divider below, while open */
}

/* Padding lives on the summary itself (not on .faq-list details) so the
   clickable row includes the space above/below the question text, not just
   the text's own baseline — clicking that padding still lands inside
   <summary>, which is what actually toggles the disclosure. The answer
   below has its own separate padding, so clicking it never toggles. */
.faq-list summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-lg) 0;
  transition: color 120ms ease;
}

.faq-list summary:hover {
  color: var(--sage-200);
}

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

.faq-icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  color: var(--sage-400);
  margin-left: var(--space-md);
  transition: color 120ms ease;
}

.faq-list summary:hover .faq-icon {
  color: var(--sage-300);
}

.faq-icon-up {
  display: none;
}

.faq-list details[open] .faq-icon-down {
  display: none;
}

.faq-list details[open] .faq-icon-up {
  display: block;
}

/* overflow:hidden + no padding/margin of its own so js/app.js can animate
   height cleanly from 0 to its natural content height (the "fitting"
   open/close animation) — with box-sizing:border-box, any padding here
   would stop height:0 from actually reaching 0. The gap below the answer
   (before the divider) lives on details[open] above instead. */
.faq-list .answer {
  overflow: hidden;
  color: var(--sage-400);
  margin: 0;
}

/* ---------- Footer ---------- */
.site-footer {
  padding: var(--space-xl) var(--space-lg);
  background: var(--sham-800);
}

.footer-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  max-width: var(--container-w);
  margin: 0 auto;
}

.footer-brand {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.footer-inner .logo {
  color: var(--sage-400);
  gap: 7px; /* header logo's gap:icon-size ratio (8px : 22px) scaled to the footer's 18px icon */
}

.footer-inner .logo .mark {
  width: 18px;
  height: 18px; /* icon:text 1:1 ratio from the header logo, scaled to the footer's 18px text */
}

.footer-inner .logo-text {
  font-size: 18px;
}

.footer-copyright {
  color: var(--sage-500);
  margin: 0;
}

/* "by [PixCapital logo]" next to the Hakuro wordmark — the "by" text and the
   logo are both sized to the regular footer text (1rem); the logo colour is
   baked into the SVG as --sage-400 (#8fa09a), which the "by" matches. */
.footer-by {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 1rem;
  color: var(--sage-400);
}

.footer-by a {
  display: inline-flex; /* keep the linked logo aligned, no baseline gap */
}

.footer-by-logo {
  display: block;
  height: 1em; /* match the footer text height */
  width: auto;
}

.footer-links {
  display: flex;
  gap: var(--space-lg);
}

.footer-links a {
  color: var(--sage-400);
  text-decoration: none;
  transition: color 0.12s ease;
}

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

@media (max-width: 900px) {
  .blob-node:nth-child(4n) {
    display: none; /* thin out the field on narrow screens */
  }
  .switcher-content {
    flex-direction: column;
  }
  .switcher-stage {
    /* Stacked layout — don't force the desktop fixed height. */
    height: auto;
  }
}

@media (max-width: 800px) {
  .pillar-row,
  .pillar-row.reverse {
    flex-direction: column;
  }
  /* Hero field: blurred + dimmed so the headline reads cleanly over it. This is
     the no-JS fallback; js/app.js sets the same inline (and animates it on
     scroll), overriding these when it runs. */
  .hero-blobs,
  .hero-lines {
    filter: blur(3.5px);
    opacity: 0.4;
  }
  /* Center menu + Login collapse into the hamburger, next to the CTA. */
  nav.main-nav,
  .login-link {
    display: none;
  }
  .nav-toggle {
    display: inline-flex;
  }
  .header-right {
    gap: var(--space-sm);
  }
  .mobile-menu {
    display: block;
    max-width: calc(var(--container-w) + 2 * var(--space-lg));
    margin: 0 auto;
    padding: 0 var(--space-lg);
    max-height: 0;
    overflow: hidden;
    visibility: hidden;
    border-top: 1px solid transparent;
    transition: max-height 0.25s ease, visibility 0.25s ease;
  }
  .mobile-menu.open {
    max-height: 400px;
    visibility: visible;
    border-top-color: var(--sage-700);
  }
  .mobile-menu a {
    display: block;
    padding: var(--space-sm) 0;
    color: var(--sage-300);
    text-decoration: none;
  }
  .mobile-menu a + a {
    border-top: 1px solid var(--sage-800);
  }
  .mobile-menu a:hover {
    color: var(--sage-200);
  }
  .value-grid {
    grid-template-columns: 1fr;
  }
  /* Footer: the desktop space-between row pushes .footer-links off the right
     edge on narrow screens (the source of the horizontal overflow / the gap on
     the right). Stack it and let the brand row wrap instead. */
  .footer-inner {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-lg);
  }
  .footer-brand,
  .footer-links {
    flex-wrap: wrap;
  }
  /* Use cases: on mobile the tab switcher is dropped — every case is shown at
     once (copy + its own demo), stacked in order, with no auto-advance. The
     copy/stage columns become display:contents so their panels flow into one
     column, and `order` interleaves each case's copy directly above its demo. */
  .switcher-tabbar {
    display: none;
  }
  .switcher-content {
    flex-direction: column;
  }
  .switcher-copy,
  .switcher-stage {
    display: contents;
  }
  .switcher-panel {
    display: block;
  }
  .switcher-copy .switcher-panel[data-panel="1"] { order: 1; }
  .switcher-stage .switcher-panel[data-panel="1"] { order: 2; }
  .switcher-copy .switcher-panel[data-panel="2"] { order: 3; }
  .switcher-stage .switcher-panel[data-panel="2"] { order: 4; }
  .switcher-copy .switcher-panel[data-panel="3"] { order: 5; }
  .switcher-stage .switcher-panel[data-panel="3"] { order: 6; }
  /* Let each demo card grow to its content instead of being clipped to the
     desktop 400px cap (which was cutting the to-do demo off on narrow screens). */
  .switcher-stage .stage-card {
    max-height: none;
  }
  /* Divider after each case's demo, so the next case's copy reads as a new item.
     Not after the last case (nothing follows it). */
  .switcher-stage .switcher-panel {
    padding-bottom: var(--space-xl);
    border-bottom: 1px solid var(--sage-700);
  }
  .switcher-stage .switcher-panel[data-panel="3"] {
    padding-bottom: 0;
    border-bottom: none;
  }
  /* Comparison table: drop the icon/label column and tighten padding so the two
     value columns fit without a horizontal scrollbar. */
  .comparison-card {
    padding: var(--space-md);
  }
  .comparison-table {
    min-width: 0;
  }
  .comparison-table thead th:first-child,
  .comparison-table tbody th {
    display: none;
  }
  .comparison-table th,
  .comparison-table td {
    padding: var(--space-md) var(--space-sm);
  }
}

/* ---------- Legal pages (terms.html, 404.html) ---------- */
.legal {
  padding: var(--space-5xl) var(--space-lg);
}

.legal-content {
  max-width: var(--container-w);
  margin: 0 auto;
  /
}

.legal-content h1 {
  margin-bottom: var(--space-xs);
}

.legal-updated {
  color: var(--sage-500);
  margin-bottom: var(--space-2xl);
}

.legal-content h2 {
  margin-top: var(--space-2xl);
}

.legal-content ul {
  padding-left: 1.2em;
}

.legal-content li {
  margin-bottom: var(--space-xs);
}

.legal-content a {
  color: var(--sham-050);
  transition: color 0.12s ease;
}

.legal-content a:hover {
  color: var(--sham-025);
}


/* ==========================================================================
   Beta participation page (beta.html)
   Reuses index.html's design-system components (.heading, .value-grid/.card
   .value-item, .comparison-table, .cta-band, .faq, .pill, buttons). The rules
   below only add the few page-specific pieces those components don't cover.
   ========================================================================== */

/* Shared padding rhythm for this page's plain content sections — matches the
   sitewide section rhythm (.hero/.pillars/…). CTA and FAQ keep their own. */
.beta-section {
  padding: var(--space-8xl) var(--space-lg);
}

/* ---- 1. Intro ---- */
.beta-hero {
  text-align: center;
  background: var(--sham-600);
  padding: var(--space-8xl) var(--space-lg) var(--space-5xl);
}

.beta-hero .pill {
  margin-bottom: var(--space-lg);
}

.beta-hero h1 {
  max-width: var(--container-w-narrow);
  margin: 0 auto;
  color: var(--sage-300);
}

.beta-lead {
  max-width: var(--container-w-narrow);
  margin: var(--space-lg) auto 0;
}

.beta-lead p {
  color: var(--sage-300);
  margin: 0 0 var(--space-md);
}

.beta-lead p:last-child {
  margin-bottom: 0;
}

/* ---- 2. Seats remaining (bright accent band) ---- */
.seats-band {
  text-align: center;
  background: var(--sham-100); /* true brand green — the one bright plane on the page */
  color: var(--sham-800);
  padding: var(--space-4xl) var(--space-lg);
}

.seats-inner {
  max-width: var(--container-w-narrow);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}

.seats-icon {
  width: 44px;
  height: 44px;
  color: var(--sham-800);
}

.seats-count {
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: 4rem;
  line-height: 1;
  margin: var(--space-xs) 0 0;
  color: var(--sham-800);
}

.seats-count span {
  color: var(--sham-300); /* the "/ 50" reads as a quieter denominator */
}

.seats-label {
  font-weight: 600;
  margin: 0;
  color: var(--sham-800);
}

.seats-bar {
  width: 100%;
  max-width: 320px;
  height: 8px;
  margin-top: var(--space-xs);
  border-radius: var(--radius-pill);
  background: var(--sham-300); /* darker track against the bright band */
  overflow: hidden;
}

.seats-bar-fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--sham-800);
}

.seats-sub {
  max-width: 46ch;
  margin: var(--space-xs) 0 0;
  color: var(--sham-700);
}

/* ---- 3 + 5. Topo-wrapped sections: tighten the shared seam so the two read
   as one continuous plane (mirrors .value-add / .use-cases on index). ---- */
.perks {
  padding-bottom: var(--space-2xl);
}

.commit {
  padding-top: var(--space-2xl);
}

/* Subtly highlight the headline perk ("Full access to the platform") — an
   accent-green border and a faint green wash lift it above its two sibling
   tiles without shouting, matching the brand's "a flash of accent reads as
   signal" restraint. */
.value-item.is-featured {
  border-color: var(--sham-200);
  background: rgba(30, 153, 112, 0.06); /* --sham-100 (#1e9970) at low opacity */
}

/* Bulleted commit cards — index's .value-item never carries a list, so this is
   additive and only affects this page. */
.value-item ul {
  margin: 0;
  padding-left: 1.1em;
  color: var(--sage-400);
}

.value-item li {
  margin-bottom: var(--space-xs);
}

.value-item li:last-child {
  margin-bottom: 0;
}

/* ---- 7. Expectations table ---- */
.expectations {
  background: var(--sham-600);
}

/* The base .comparison-card carries a big top margin (on index it follows the
   property list); here it sits right under the heading, so drop it. */
.expectations .comparison-card {
  margin-top: 0;
}

/* The expectations table reuses index's icon-only first column, but its
   positive column is "Yes" (the 2nd), not the last — so recolour the headers:
   green on "Yes", neutral on "No" (overriding the base last-column green that
   assumes the winning column is last). */
.comparison-labeled .comparison-table thead th:nth-child(2) {
  color: var(--sham-200);
}

.comparison-labeled .comparison-table thead th:last-child {
  color: var(--sage-800);
}

.expectations-note {
  max-width: var(--container-w);
  margin: var(--space-lg) auto 0;
  color: var(--sage-500);
  text-align: center;
}

/* ---- 8. Steps to get started ---- */
.steps-section {
  background: var(--sham-800);
}

.steps {
  list-style: none;
  max-width: var(--container-w-narrow);
  margin: 0 auto;
  padding: 0;
}

.step {
  position: relative;
  display: flex;
  gap: var(--space-lg);
  padding-bottom: var(--space-xl);
}

.step:last-child {
  padding-bottom: 0;
}

/* Thin connecting line down the markers (the brand's "accent line joining the
   dots" motif). Runs from just below one marker to the next; completed steps
   colour their outgoing segment with the accent green to read as progress. */
.step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 19px; /* centre of the 40px marker, minus half the 2px line */
  top: 40px;
  bottom: 0;
  width: 2px;
  background: var(--sage-700);
}

.step.is-done:not(:last-child)::before {
  background: var(--sham-200);
}

.step-marker {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-base);
  font-weight: 600;
  color: var(--sage-300);
  background: var(--sham-800); /* matches the section so the connector passes behind, not through */
  box-shadow: inset 0 0 0 1px var(--sage-600);
}

.step.is-done .step-marker {
  color: var(--sham-800);
  background: var(--sham-100);
  box-shadow: none;
}

.step-check {
  width: 20px;
  height: 20px;
}

.step-body {
  padding-top: var(--space-xs);
}

.step-body h3 {
  margin: 0;
  color: var(--sage-200);
}

.step.is-done .step-body h3 {
  color: var(--sage-400); /* completed steps recede a little */
}

/* ==========================================================================
   3. STYLE-GUIDE-ONLY SCAFFOLDING
   Documentation chrome from the branding.html catalog page itself —
   swatches, demo frames, captions, subsection labels. Not needed on index.html
   unless you're building another reference/catalog page.
   ========================================================================== */

.intro {
  padding: var(--space-2xl) var(--space-lg) var(--space-xl);
  border-bottom: 1px solid var(--sage-600);
}

.intro p {
  max-width: var(--container-w-narrow);
  color: var(--sage-400);
}

.intro code {
  background: var(--sham-400);
  padding: 0.1em 0.4em;
  border-radius: var(--radius-m);
  color: var(--sham-025);
}

.demo-section {
  padding: var(--space-2xl) var(--space-lg);
  border-bottom: 1px solid var(--sage-600);
}

.demo-section > .wrap > h2 {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
}

.demo-section .tag {
  font-family: var(--font-base);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sham-050);
}

.demo-section > .wrap > .desc {
  color: var(--sage-400);
  max-width: var(--container-w-narrow);
  margin-bottom: var(--space-lg);
}

.demo-canvas {
  background: var(--sham-800);
  border: 1px solid var(--sage-700);
  border-radius: var(--radius-m);
  padding: var(--space-xl);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xl);
}

.caption {
  margin-top: var(--space-md);
  color: var(--sham-050);
}

.caption b {
  color: var(--sage-200);
}

.subsection {
  margin-top: var(--space-xl);
}

.subsection:first-of-type {
  margin-top: 0;
}

.subsection .sub-label {
  font-family: var(--font-base);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--sage-400);
  margin: 0 0 var(--space-xs);
}

.subsection .desc {
  color: var(--sage-400);
  max-width: var(--container-w-narrow);
  margin: 0 0 var(--space-md);
}

.swatch-group {
  margin-bottom: var(--space-lg);
}

.swatch-group h4 {
  font-family: var(--font-base);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--sage-400);
  margin: 0 0 var(--space-xs);
}

.swatch-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.swatch {
  width: 96px;
}

.swatch .chip {
  height: 56px;
  border-radius: var(--radius-m);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.swatch .label {
  color: var(--sage-400);
  margin-top: var(--space-xs);
}

.swatch .hex {
  color: var(--sage-200);
  font-family: monospace;
}

.type-row {
  margin-bottom: var(--space-lg);
}

.type-row .meta {
  color: var(--sage-400);
  font-family: monospace;
  margin-bottom: var(--space-2xs);
}

@media (max-width: 700px) {
  .demo-canvas {
    flex-direction: column;
    align-items: flex-start;
  }
}
