/* style.css */
/*
Theme Name: Blocksy Child
Template: blocksy
Version: 1.1.0
*/

/* =====================================================
   Single product — title area

   Rewritten onto the design system. The previous rules were written for a
   light theme: they set #1a1a1a on the product title and price with
   !important, which renders near-black on the dark stage, and pulled Space
   Grotesk and IBM Plex Serif from Google Fonts on every page for a design
   that no longer uses either.
   ===================================================== */

/* Science meta line — sequence · formula · weight */
.pept-science-meta {
	font: var(--type-code-sm);
	color: var(--ink-faint);
	margin-bottom: var(--sp-xs);
	line-height: 1.6;
}

/* Breadcrumb meta line — PEPT.BIO · CLASS · RESEARCH COMPOUND */
.pept-product-meta {
	font: var(--type-code-sm);
	letter-spacing: .08em;
	color: var(--ink-faint);
	margin-bottom: var(--sp-md);
	text-transform: uppercase;
}

body.single-product .product_title.entry-title,
body.single-product h1.product_title {
	font: var(--type-display-lg);
	color: var(--ink);
}

/* Short description */
.woocommerce div.product .woocommerce-product-details__short-description,
.woocommerce div.product .woocommerce-product-details__short-description p {
	font: var(--type-body-md);
	color: var(--ink-muted);
	max-width: 60ch;
	margin-bottom: 0;
}

/* Product price — figures line up column-wise */
body.single-product .summary .price,
body.single-product .summary .price span,
body.single-product .summary .price ins span,
body.single-product .summary p.price .woocommerce-Price-amount {
	font: var(--type-display-lg);
	color: var(--ink);
	font-variant-numeric: tabular-nums;
}

body.single-product .summary del .woocommerce-Price-amount {
	font: var(--type-body-sm);
	color: var(--ink-faint);
}

#pept-data-panel {
	padding-left: var(--pf-gutter) !important;
	padding-right: var(--pf-gutter) !important;
}

/* assets/hn/components/foundation/foundation.css */
/* ==========================================================================
   Foundation — base element styles and type utilities (DESIGN.md roles).
   ========================================================================== */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font: var(--type-body-md);
  color: var(--ink);
  background: var(--c-canvas);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img, svg, video { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
:focus-visible { outline: 3px solid var(--c-focus-ring); outline-offset: 1px; }
code { font: var(--type-code-sm); padding: 2px 6px; border-radius: var(--r-sm); background: var(--surface-soft); }

/* ---- Type utilities — names match DESIGN.md tokens -------------------- */
.hn-display-xl     { font: var(--type-display-xl); margin: 0; text-wrap: balance; }
.hn-display-lg     { font: var(--type-display-lg); margin: 0; }
.hn-heading-lg     { font: var(--type-heading-lg); margin: 0; }
.hn-heading-md     { font: var(--type-heading-md); margin: 0; }
.hn-heading-sm     { font: var(--type-heading-sm); margin: 0; }
.hn-body-md        { font: var(--type-body-md); margin: 0; color: var(--ink-muted); }
.hn-body-strong    { font: var(--type-body-strong); margin: 0; }
.hn-body-sm        { font: var(--type-body-sm); margin: 0; color: var(--ink-muted); }
.hn-body-sm-strong { font: var(--type-body-sm-strong); margin: 0; }
.hn-caption-sm     { font: var(--type-caption-sm); margin: 0; color: var(--ink-muted); }
.hn-code-md        { font: var(--type-code-md); }
.hn-code-sm        { font: var(--type-code-sm); }

.hn-ink   { color: var(--ink); }
.hn-muted { color: var(--ink-muted); }
.hn-faint { color: var(--ink-faint); }

/* ---- WORLD: data-object numerals ---------------------------------------
   Numbers placed ON a stage are illustration: extruded, lit geometry with a
   cast shadow — never flat overlay text. Chrome numbers use display-lg.  */
.hn-numeral {
  font: var(--type-numeral);
  letter-spacing: -0.04em;
  margin: 0;
  font-variant-numeric: tabular-nums;
  color: var(--hn-artery-400);
}
.hn-numeral--cool {
  color: var(--hn-vein-400);
}

.hn-visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* assets/hn/tokens/colors.css */
/* ==========================================================================
   PEPT — colors

   TWO LAYERS, TWO PALETTES — never mixed.

   CHROME  (text, buttons, cards, nav, inputs) — the DESIGN.md palette:
           black on white, three grays, hairlines, one inverted dark surface.
           No brand colour. No gradients. No shadows.

   WORLD   (stages, pathways, atoms, avatar) — the anatomy palette:
           orange = activation / oxygenated / heat, blue = rest / return /
           cooling, white-cyan = electrical signal. This layer plays the
           role the llama plays in DESIGN.md: the only illustration.

   Stage-dependent aliases (ink, surface, line…) live in stages.css.
   ========================================================================== */

:root {
  /* ======== CHROME — verbatim from DESIGN.md ============================ */
  --c-primary: #000000; /* @kind color */
  --c-on-primary: #FFFFFF; /* @kind color */
  --c-ink: #000000; /* @kind color */
  --c-ink-deep: #090909; /* @kind color */ /* pressed primary */
  --c-charcoal: #525252; /* @kind color */ /* list items, secondary copy */
  --c-body: #737373; /* @kind color */ /* default paragraph colour */
  --c-mute: #A3A3A3; /* @kind color */ /* captions, comments, lowest emphasis */
  --c-canvas: #FFFFFF; /* @kind color */
  --c-surface-soft: #FAFAFA; /* @kind color */ /* snippet pills, search, chips */
  --c-surface-card: #FFFFFF; /* @kind color */
  --c-hairline: #E5E5E5; /* @kind color */
  --c-hairline-strong: #D4D4D4; /* @kind color */
  --c-on-dark: #FFFFFF; /* @kind color */
  --c-on-dark-mute: rgba(255, 255, 255, 0.7); /* @kind color */
  --c-surface-dark: #171717; /* @kind color */ /* the single inverted surface */
  --c-focus-ring: rgba(59, 130, 246, 0.5); /* @kind color */
  --c-link: #000000; /* @kind color */
  --c-link-mute: #737373; /* @kind color */
  --c-terminal-red: #FF5F56; /* @kind color */
  --c-terminal-yellow: #FFBD2E; /* @kind color */
  --c-terminal-green: #27C93F; /* @kind color */

  /* ======== WORLD ======================================================= */
  /* ---- Neutrals -------------------------------------------------------- */
  --hn-charcoal: #1A1A1A; /* @kind color */ /* avatar body, primary ink on light stages */
  --hn-graphite: #2A2D33; /* @kind color */
  --hn-slate: #4A4F58; /* @kind color */
  --hn-gray: #848A94; /* @kind color */ /* secondary ink */
  --hn-mist: #C9CCD1; /* @kind color */
  --hn-bone: #E9E3D8; /* @kind color */
  --hn-warm-white: #F7F3EA; /* @kind color */ /* paper */
  --hn-white: #FFFFFF; /* @kind color */

  /* ---- Artery ramp (oxygenated flow, activation, energy) --------------- */
  --hn-artery-100: #FFE6D6; /* @kind color */
  --hn-artery-200: #FFBF96; /* @kind color */
  --hn-artery-300: #FF9A5C; /* @kind color */
  --hn-artery-400: #FF7A2F; /* @kind color */ /* ORANGE GLOW — canonical accent */
  --hn-artery-500: #F2572A; /* @kind color */ /* tube core */
  --hn-artery-600: #D8401F; /* @kind color */
  --hn-artery-700: #A92E17; /* @kind color */ /* tube wall shadow */
  --hn-artery-900: #5E170B; /* @kind color */

  /* ---- Vein ramp (deoxygenated return, rest, cooling) ------------------ */
  --hn-vein-100: #DDF1FF; /* @kind color */
  --hn-vein-200: #A8DCFF; /* @kind color */
  --hn-vein-300: #80C8FF; /* @kind color */
  --hn-vein-400: #4DBBFF; /* @kind color */ /* BLUE GLOW — canonical accent */
  --hn-vein-500: #2E8BEA; /* @kind color */ /* tube core */
  --hn-vein-600: #1F6BD0; /* @kind color */
  --hn-vein-700: #164FA3; /* @kind color */ /* tube wall shadow */
  --hn-vein-900: #0B2656; /* @kind color */

  /* ---- Nerve (electrical signal) --------------------------------------- */
  --hn-nerve-core: #F4FBFF; /* @kind color */ /* the spark itself */
  --hn-nerve-arc: #B9EBFF; /* @kind color */
  --hn-nerve-glow: #6FD0FF; /* @kind color */
  --hn-nerve-sheath: #2E8BEA; /* @kind color */ /* the fibre when drawn on light stages */

  /* ---- Metabolic accents ----------------------------------------------- */
  --hn-heat: #FFB547; /* @kind color */ /* ATP spark core, heat halo centre */
  --hn-gold: #FFC247; /* @kind color */ /* value / the win */
  --hn-tissue: #F6D9CC; /* @kind color */ /* cell bodies, absorption spheres */
  --hn-tissue-edge: #F0B8A2; /* @kind color */

  /* ---- Colour-as-state washes (world tints for a beat, then back) ------ */
  --hn-state-win: #FFC247; /* @kind color */
  --hn-state-danger: #E5322B; /* @kind color */
  --hn-state-healthy: #3FC98A; /* @kind color */
  --hn-state-mechanism: #4DBBFF; /* @kind color */
  --hn-state-rejected: #9AA0A8; /* @kind color */

  /* ---- Alpha helpers (RGB triplets for rgb(var() / a)) ------------------ */
  --hn-artery-rgb: 255 122 47; /* @kind other */
  --hn-vein-rgb: 77 187 255; /* @kind other */
  --hn-nerve-rgb: 185 235 255; /* @kind other */
  --hn-charcoal-rgb: 26 26 26; /* @kind other */
  --hn-heat-rgb: 255 181 71; /* @kind other */
}

/* assets/hn/tokens/typography.css */
/* ==========================================================================
   PEPT — typography
   Source: guidelines/source/DESIGN-ollama.md (typography block, verbatim px).

   Display  ui-rounded     → SF Pro Rounded on Apple (the device's own face);
                             Nunito 400/500/600/700 everywhere else (bundled, OFL)
   Body     ui-sans-serif  → Inter (metrics match system-ui)
   Code     ui-monospace   → JetBrains Mono
   Native faces are tried first via local(); the bundled woff2 files are the
   fallback, so Apple renders its own rounded face and every other OS gets
   Nunito. No Apple font file is redistributed with this theme.

   The heading-to-body ratio is deliberately compressed: 36 → 30 → 24 → 20 → 16.
   No letter-spacing variation. No italic. No display-only weights.
   On a stage, captions are authored at these same px on a 1280×720 frame and
   the whole frame scales — the tokens never change, the camera does.
   ========================================================================== */

/* Self-hosted SF Pro Rounded removed from this build. Apple's font licence
   does not permit redistribution or web self-hosting. The local() sources
   below still pick the face up from the visitor's own device, and ui-rounded
   in --font-display resolves to it natively on Apple platforms. */
@font-face { font-family: "HN Rounded"; font-weight: 400; font-display: swap; src: local("SF Pro Rounded Regular"), local("SFProRounded-Regular"), url("https://pept.bio/wp-content/themes/blocksy-child/assets/hn/assets/fonts/nunito-latin-500-normal.woff2") format("woff2"); }
@font-face { font-family: "HN Rounded"; font-weight: 500; font-display: swap; src: local("SF Pro Rounded Medium"), local("SFProRounded-Medium"), url("https://pept.bio/wp-content/themes/blocksy-child/assets/hn/assets/fonts/nunito-latin-500-normal.woff2") format("woff2"); }
@font-face { font-family: "HN Rounded"; font-weight: 600; font-display: swap; src: local("SF Pro Rounded Semibold"), local("SFProRounded-Semibold"), url("https://pept.bio/wp-content/themes/blocksy-child/assets/hn/assets/fonts/nunito-latin-600-normal.woff2") format("woff2"); }
@font-face { font-family: "HN Rounded"; font-weight: 700; font-display: swap; src: local("SF Pro Rounded Bold"), local("SFProRounded-Bold"), url("https://pept.bio/wp-content/themes/blocksy-child/assets/hn/assets/fonts/nunito-latin-700-normal.woff2") format("woff2"); }
@font-face { font-family: "HN Sans"; font-weight: 400; font-display: swap; src: url("https://pept.bio/wp-content/themes/blocksy-child/assets/hn/assets/fonts/inter-latin-400-normal.woff2") format("woff2"); }
@font-face { font-family: "HN Sans"; font-weight: 500; font-display: swap; src: url("https://pept.bio/wp-content/themes/blocksy-child/assets/hn/assets/fonts/inter-latin-500-normal.woff2") format("woff2"); }
@font-face { font-family: "HN Sans"; font-weight: 600; font-display: swap; src: url("https://pept.bio/wp-content/themes/blocksy-child/assets/hn/assets/fonts/inter-latin-600-normal.woff2") format("woff2"); }
@font-face { font-family: "HN Mono"; font-weight: 400; font-display: swap; src: url("https://pept.bio/wp-content/themes/blocksy-child/assets/hn/assets/fonts/jetbrains-mono-latin-400-normal.woff2") format("woff2"); }
@font-face { font-family: "HN Mono"; font-weight: 500; font-display: swap; src: url("https://pept.bio/wp-content/themes/blocksy-child/assets/hn/assets/fonts/jetbrains-mono-latin-500-normal.woff2") format("woff2"); }

:root {
  /* ---- Families -------------------------------------------------------- */
  --font-display: ui-rounded, "HN Rounded", system-ui, -apple-system, sans-serif; /* @kind font */
  --font-sans: ui-sans-serif, system-ui, -apple-system, "HN Sans", "Segoe UI", Roboto, sans-serif; /* @kind font */
  --font-mono: ui-monospace, "HN Mono", SFMono-Regular, Menlo, Monaco, Consolas, monospace; /* @kind font */

  /* ---- Size scale (px) — the DESIGN.md scale, nothing added ------------- */
  --fs-12: 12px; /* @kind font */ /* caption-sm */
  --fs-14: 14px; /* @kind font */ /* body-sm, code-sm, button-md */
  --fs-16: 16px; /* @kind font */ /* body-md, code-md */
  --fs-18: 18px; /* @kind font */ /* heading-sm */
  --fs-20: 20px; /* @kind font */ /* heading-md */
  --fs-24: 24px; /* @kind font */ /* heading-lg */
  --fs-30: 30px; /* @kind font */ /* display-lg */
  --fs-36: 36px; /* @kind font */ /* display-xl — the page-top headline only */
  --fs-28: 28px; /* @kind font */ /* display-xl at ≤640px (responsive step from DESIGN.md) */

  /* ---- Weights --------------------------------------------------------- */
  --fw-regular: 400; /* @kind font */
  --fw-medium: 500; /* @kind font */
  --fw-semibold: 600; /* @kind font */

  /* ---- Roles (font shorthand) — names mirror DESIGN.md tokens ---------- */
  --type-display-xl: 500 36px/1.11 var(--font-display); /* @kind font */
  --type-display-lg: 500 30px/1.2  var(--font-display); /* @kind font */
  --type-heading-lg: 600 24px/1.33 var(--font-display); /* @kind font */
  --type-heading-md: 500 20px/1.4  var(--font-sans); /* @kind font */
  --type-heading-sm: 500 18px/1.56 var(--font-sans); /* @kind font */
  --type-body-md: 400 16px/1.5  var(--font-sans); /* @kind font */
  --type-body-strong: 500 16px/1.5  var(--font-sans); /* @kind font */
  --type-body-sm: 400 14px/1.43 var(--font-sans); /* @kind font */
  --type-body-sm-strong: 500 14px/1.43 var(--font-sans); /* @kind font */
  --type-caption-sm: 400 12px/1.33 var(--font-sans); /* @kind font */
  --type-code-md: 400 16px/1.5  var(--font-mono); /* @kind font */
  --type-code-sm: 400 14px/1.43 var(--font-mono); /* @kind font */
  --type-button-md: 500 14px/1    var(--font-sans); /* @kind font */

  /* World-layer data objects (numbers rendered as lit geometry on a stage).
     The only type that leaves the scale — it is illustration, not text. */
  --type-numeral: 700 128px/0.85 var(--font-display); /* @kind font */

  --tracking: 0; /* @kind font */
  --measure: 720px; /* @kind spacing */ /* the single reading column */
}

@media (max-width: 640px) {
  :root { --type-display-xl: 500 28px/1.15 var(--font-display); }
}

/* assets/hn/tokens/spacing.css */
/* ==========================================================================
   PEPT — spacing & layout
   Scale verbatim from DESIGN.md: 8px base with 2/4px fine steps, and one
   big rhythm token — section (88px) — used liberally between blocks.

   Tiles meet at SEAMS. --seam is the gap a pathway crosses between two
   tiles; --port-* are the only legal positions where a pathway may enter
   or exit a tile edge. Matching ports is what makes tiles lock together.
   ========================================================================== */

:root {
  --sp-xxs: 2px; /* @kind spacing */
  --sp-xs: 4px; /* @kind spacing */
  --sp-sm: 8px; /* @kind spacing */
  --sp-md: 12px; /* @kind spacing */
  --sp-lg: 16px; /* @kind spacing */
  --sp-xl: 24px; /* @kind spacing */
  --sp-xxl: 32px; /* @kind spacing */
  --sp-section: 88px; /* @kind spacing */ /* 64px ≤850px, 48px ≤640px — see media below */

  /* Containers */
  --measure: 720px; /* @kind spacing */ /* single reading column */
  --content-wide: 960px; /* @kind spacing */ /* card grids */
  --nav-h: 56px; /* @kind spacing */

  /* Cards (DESIGN.md: pricing-card padding 32) */
  --card-pad: var(--sp-xxl); /* @kind spacing */
  --card-pad-sm: var(--sp-lg); /* @kind spacing */

  /* Tiles (puzzle pieces) */
  --tile-pad: var(--sp-xl); /* @kind spacing */
  --seam: var(--sp-lg); /* @kind spacing */
  --tile-min: 240px; /* @kind spacing */

  /* Ports, as % of the edge. Only these three positions exist. */
  --port-a: 25%; /* @kind other */
  --port-b: 50%; /* @kind other */
  --port-c: 75%; /* @kind other */

  /* Stage frame — captions authored on 1280×720, then scaled */
  --frame-w: 1280px; /* @kind spacing */
  --frame-h: 720px; /* @kind spacing */
  --safe-x: 96px; /* @kind spacing */
  --safe-y: 72px; /* @kind spacing */

  /* Z layers: world below, chrome above */
  --z-stage: 0; /* @kind other */
  --z-pathways: 10; /* @kind other */
  --z-objects: 20; /* @kind other */
  --z-content: 30; /* @kind other */
  --z-chrome: 40; /* @kind other */
  --z-overlay: 50; /* @kind other */
}

@media (max-width: 850px) { :root { --sp-section: 64px; } }
@media (max-width: 640px) { :root { --sp-section: 48px; --card-pad: var(--sp-xl); } }

/* assets/hn/tokens/radius.css */
/* ==========================================================================
   PEPT — corner radius (verbatim from DESIGN.md)
   Two values do nearly all the work:
     full (9999) — everything interactive AND everything that flows
                   (buttons, inputs, snippets, chips, vessels, nodes)
     lg   (12)   — cards, tiles, terminal/readout cards
   Never soften a card to a pill. Never sharpen a pill to md.
   ========================================================================== */

:root {
  --r-none: 0px; /* @kind radius */ /* nav, footer, row dividers */
  --r-sm: 6px; /* @kind radius */ /* inline code chips */
  --r-md: 8px; /* @kind radius */ /* rare: dropdown panels */
  --r-lg: 12px; /* @kind radius */ /* cards, tiles, stage frame */
  --r-full: 9999px; /* @kind radius */ /* pills, vessels, dots */
}

/* assets/hn/tokens/anatomy.css */
/* ==========================================================================
   PEPT — anatomy dimensions
   Every pathway width is a ratio of one base diameter D, as on the core
   pathways sheet: artery 1.00D · vein 0.64D · capillary 0.36D · nerve 0.36D.

   Rescale a region: <div class="hn-anatomy" style="--hn-d: 28px">
   (derived values re-resolve on .hn-anatomy — custom properties resolve
   where they are declared, then inherit.)

   Lengths are registered so the motion engine can read them as px.
   ========================================================================== */

@property --hn-d          { syntax: "<length>"; inherits: true; initial-value: 40px; }
@property --w-artery      { syntax: "<length>"; inherits: true; initial-value: 40px; }
@property --w-vein        { syntax: "<length>"; inherits: true; initial-value: 25.6px; }
@property --w-capillary   { syntax: "<length>"; inherits: true; initial-value: 14.4px; }
@property --w-nerve       { syntax: "<length>"; inherits: true; initial-value: 14.4px; }
@property --w-nerve-fibre { syntax: "<length>"; inherits: true; initial-value: 2.4px; }
@property --cell-lg       { syntax: "<length>"; inherits: true; initial-value: 12px; }
@property --cell-sm       { syntax: "<length>"; inherits: true; initial-value: 6.4px; }
@property --cell-gap      { syntax: "<length>"; inherits: true; initial-value: 96px; }
@property --signal-len    { syntax: "<length>"; inherits: true; initial-value: 36px; }
@property --signal-gap    { syntax: "<length>"; inherits: true; initial-value: 180px; }

:root {
  --hn-d: 40px; /* @kind spacing */
}

:root,
.hn-anatomy {
  --w-artery: calc(var(--hn-d) * 1.00); /* @kind spacing */
  --w-vein: calc(var(--hn-d) * 0.64); /* @kind spacing */
  --w-capillary: calc(var(--hn-d) * 0.36); /* @kind spacing */
  --w-nerve: calc(var(--hn-d) * 0.36); /* @kind spacing */
  --w-nerve-fibre: calc(var(--hn-d) * 0.06); /* @kind spacing */ /* the lit filament */

  --cell-lg: calc(var(--hn-d) * 0.30); /* @kind spacing */ /* red cell / signal cell */
  --cell-sm: calc(var(--hn-d) * 0.16); /* @kind spacing */
  --cell-gap: calc(var(--hn-d) * 2.4); /* @kind spacing */ /* spacing of cells along flow */
  --signal-len: calc(var(--hn-d) * 0.9); /* @kind spacing */ /* length of a nerve spark */
  --signal-gap: calc(var(--hn-d) * 4.5); /* @kind spacing */

  --bend-radius: calc(var(--hn-d) * 1); /* @kind radius */ /* "vascular bend · radius 1D" */
  --collar-w: calc(var(--hn-d) * 0.28); /* @kind spacing */
  --node-size: calc(var(--hn-d) * 1.2); /* @kind spacing */ /* synapse / pulse badge */
  --lane-gap: calc(var(--hn-d) * 0.35); /* @kind spacing */ /* artery↔vein spacing in a band */
}

/* assets/hn/tokens/motion.css */
/* ==========================================================================
   PEPT — motion
   The system is ONE continuous piece. Nothing cuts; everything travels.

   FLOW SPEEDS are px/second of cell travel along a pathway — the cadence
   presets from the timing sheet:  calm 120 · steady 180 · active 300 · burst 600
   Pre-computed loop durations assume one flow period (--flow-period, 240px).

   TRANSITIONS are milliseconds: transition 1200 (camera travels between
   tiles/slides) · hold ≥1200 (minimum dwell on a station before moving on).
   ========================================================================== */

/* Registered so energy and camera values interpolate instead of snapping. */
@property --energy       { syntax: "<number>"; inherits: true; initial-value: 0.55; }
@property --irregularity { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --bias         { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --density      { syntax: "<number>"; inherits: true; initial-value: 0.6; }
@property --volume       { syntax: "<number>"; inherits: true; initial-value: 1; }
@property --cam-x        { syntax: "<length>"; inherits: true; initial-value: 0px; }
@property --cam-y        { syntax: "<length>"; inherits: true; initial-value: 0px; }
@property --wash-opacity { syntax: "<number>"; inherits: true; initial-value: 0; }

:root {
  /* ---- Flow speeds (px/s, unitless for JS) ----------------------------- */
  --flow-calm: 120; /* @kind other */
  --flow-steady: 180; /* @kind other */
  --flow-active: 300; /* @kind other */
  --flow-burst: 600; /* @kind other */
  --flow-signal: 900; /* @kind other */ /* nerve impulse — always fastest thing on screen */

  --flow-period: 240px; /* @kind other */
  --dur-calm: 2000ms; /* @kind other */ /* 240 / 120 */
  --dur-steady: 1333ms; /* @kind other */ /* 240 / 180 */
  --dur-active: 800ms; /* @kind other */ /* 240 / 300 */
  --dur-burst: 400ms; /* @kind other */ /* 240 / 600 */
  --dur-signal: 1400ms; /* @kind other */ /* one spark crossing a 1260px band */

  /* ---- Story transitions ----------------------------------------------- */
  --dur-transition: 1200ms; /* @kind other */ /* camera travel between stations — the seam */
  --dur-hold: 1200ms; /* @kind other */ /* minimum dwell; content lands inside this */
  --dur-ramp: 1200ms; /* @kind other */ /* energy ramp up / down between states */

  /* ---- Interface micro-motion ------------------------------------------ */
  --dur-instant: 80ms; /* @kind other */
  --dur-quick: 160ms; /* @kind other */
  --dur-base: 280ms; /* @kind other */
  --dur-slow: 560ms; /* @kind other */
  --stagger: 70ms; /* @kind other */ /* between sibling reveals on arrival */

  /* ---- Heartbeat (pulse badge, recovery breathing) --------------------- */
  --beat-rest: 1000ms; /* @kind other */ /* 60 bpm */
  --beat-active: 600ms; /* @kind other */ /* 100 bpm */
  --beat-surge: 400ms; /* @kind other */ /* 150 bpm */
  --breath: 4800ms; /* @kind other */ /* recovery / sleep swell */

  /* ---- Easing ---------------------------------------------------------- */
  --ease-flow: cubic-bezier(0.65, 0, 0.35, 1); /* @kind other */ /* camera travel: in-out, no stop */
  --ease-arrive: cubic-bezier(0.16, 1, 0.3, 1); /* @kind other */ /* content settling on arrival */
  --ease-depart: cubic-bezier(0.7, 0, 0.84, 0); /* @kind other */ /* content leaving before travel */
  --ease-pulse: cubic-bezier(0.3, 0, 0.2, 1); /* @kind other */ /* signal spikes */
  --ease-settle: cubic-bezier(0.34, 1.3, 0.64, 1); /* @kind other */ /* small overshoot: nodes, badges */
  --ease-linear: linear; /* @kind other */ /* flow loops only */
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-transition: 1ms; /* @kind other */
    --dur-ramp: 1ms; /* @kind other */
    --dur-slow: 1ms; /* @kind other */
    --dur-base: 1ms; /* @kind other */
    --stagger: 0ms; /* @kind other */
  }
}

/* assets/hn/tokens/effects.css */
/* ==========================================================================
   PEPT — effects (depth, glow, material)
   The "3D" is faked with three layers, always in this order:
     1. tube gradient  — dark wall → core → bright sheen → core → wall
     2. bloom          — coloured glow outside the tube, scaled by energy
     3. contact shadow — soft ellipse under any object that rests on the floor
   No hard drop shadows. No grey UI shadows. Light comes from above.
   ========================================================================== */

:root {
  /* ---- Tube materials (vertical cross-section of a horizontal pathway) - */
  --tube-artery: linear-gradient(180deg,
      var(--hn-artery-700) 0%,
      var(--hn-artery-500) 18%,
      var(--hn-artery-300) 34%,
      var(--hn-artery-400) 48%,
      var(--hn-artery-500) 72%,
      var(--hn-artery-700) 100%);
  --tube-vein: linear-gradient(180deg,
      var(--hn-vein-700) 0%,
      var(--hn-vein-500) 18%,
      var(--hn-vein-300) 34%,
      var(--hn-vein-400) 48%,
      var(--hn-vein-500) 72%,
      var(--hn-vein-700) 100%);
  --tube-nerve: linear-gradient(180deg,
      rgb(var(--hn-vein-rgb) / 0) 0%,
      rgb(var(--hn-vein-rgb) / 0.35) 30%,
      var(--hn-nerve-arc) 50%,
      rgb(var(--hn-vein-rgb) / 0.35) 70%,
      rgb(var(--hn-vein-rgb) / 0) 100%);
  --tube-muscle: linear-gradient(180deg,
      var(--hn-artery-700) 0%,
      #EF6A45 30%,
      #FFA27A 46%,
      #EF6A45 62%,
      var(--hn-artery-700) 100%);

  /* ---- Sheen (the specular stripe riding the top third of a tube) ------ */
  --sheen: linear-gradient(180deg, rgb(255 255 255 / 0) 0%, rgb(255 255 255 / 0.55) 50%, rgb(255 255 255 / 0) 100%); /* @kind color */

  /* ---- Blooms (full strength; dull states attenuate via .hn-energised) */
  --bloom-artery: 0 0 8px rgb(var(--hn-artery-rgb) / 0.55), 0 0 28px rgb(var(--hn-artery-rgb) / 0.35); /* @kind color */
  --bloom-vein: 0 0 8px rgb(var(--hn-vein-rgb) / 0.55),   0 0 28px rgb(var(--hn-vein-rgb) / 0.35); /* @kind color */
  --bloom-nerve: 0 0 6px rgb(var(--hn-nerve-rgb) / 0.8),   0 0 20px rgb(var(--hn-vein-rgb) / 0.5); /* @kind color */
  --bloom-heat: 0 0 14px rgb(var(--hn-heat-rgb) / 0.7),   0 0 44px rgb(var(--hn-artery-rgb) / 0.4); /* @kind color */

  /* ---- SVG filter-friendly glow radii ---------------------------------- */
  --glow-sm: 3px; /* @kind spacing */
  --glow-md: 8px; /* @kind spacing */
  --glow-lg: 18px; /* @kind spacing */

  /* ---- Contact shadow -------------------------------------------------- */
  --contact-shadow: radial-gradient(50% 50% at 50% 50%, rgb(0 0 0 / 0.22) 0%, rgb(0 0 0 / 0.08) 55%, rgb(0 0 0 / 0) 100%); /* @kind color */

  /* ---- Cells (flowing particles) --------------------------------------- */
  --cell-artery: radial-gradient(circle at 38% 35%, #FFF3E6 0 12%, #FFB27A 28%, var(--hn-artery-400) 55%, rgb(var(--hn-artery-rgb) / 0) 72%); /* @kind color */
  --cell-vein: radial-gradient(circle at 38% 35%, #F2FAFF 0 12%, var(--hn-vein-300) 30%, var(--hn-vein-500) 55%, rgb(var(--hn-vein-rgb) / 0) 72%); /* @kind color */
  --cell-signal: radial-gradient(circle, #FFFFFF 0 18%, var(--hn-nerve-arc) 34%, rgb(var(--hn-nerve-rgb) / 0) 70%); /* @kind color */

  /* ---- Depth ----------------------------------------------------------- */
  --perspective: 1400px; /* @kind other */
  --floor-tilt: 64deg; /* @kind other */
  --frost: blur(14px) saturate(1.2); /* @kind other */
}

/* assets/hn/tokens/stages.css */
/* ==========================================================================
   PEPT — stage lanes
   One stage per piece. The stage is the constant that makes continuity read:
   every tile, slide and transition sits on the same world.

   Set on any ancestor:  <div data-stage="dark-grid">
   Lanes: light-grid (1) · light-clean (2) · dark-grid (3) · dark-clean (4) · studio (5)

   Each lane defines the SAME variable set so components never branch on lane:
     --stage-top / --stage-horizon-c / --stage-floor   world gradient stops
     --stage-horizon                                   horizon height (% from top)
     --stage-grid / --stage-grid-major                 grid line colours (transparent = none)
     --stage-grid-size                                 minor cell
     --stage-glow                                      central bloom colour
     --ink … --line-strong, --btn-*                    CHROME aliases. Light lanes map to
                                                       the DESIGN.md palette as-is; dark lanes
                                                       map to its inverted surface and the
                                                       white pill-on-dark CTA.
     --flow-blend                                      how glows composite (screen on dark)
     --accent-sat                                      accent saturation multiplier
     --cell-hi                                         highlight on flowing cells
   ========================================================================== */

:root,
[data-stage="light-grid"] {
  /* 1 · LIGHT GRID — thin light-orange grid, educational register */
  --stage-top: #F8F4EC; /* @kind color */
  --stage-horizon-c: #FBF8F2; /* @kind color */
  --stage-floor: #F1EBE0; /* @kind color */
  --stage-horizon: 64%; /* @kind other */
  --stage-grid: rgb(255 122 47 / 0.13); /* @kind color */
  --stage-grid-major: rgb(255 122 47 / 0.24); /* @kind color */
  --stage-grid-size: 32px; /* @kind other */
  --stage-glow: rgb(255 255 255 / 0.85); /* @kind color */
  --stage-vignette: rgb(120 90 60 / 0.07); /* @kind color */

  /* chrome on a light lane — DESIGN.md as-is */
  --ink: var(--c-ink); /* @kind other */
  --ink-muted: var(--c-body); /* @kind other */
  --ink-charcoal: var(--c-charcoal); /* @kind other */
  --ink-faint: var(--c-mute); /* @kind other */
  --surface: var(--c-canvas); /* @kind other */
  --surface-soft: var(--c-surface-soft); /* @kind other */
  --line: var(--c-hairline); /* @kind other */
  --line-strong: var(--c-hairline-strong); /* @kind other */
  --btn-bg: var(--c-primary); /* @kind other */
  --btn-fg: var(--c-on-primary); /* @kind other */
  --btn-bg-active: var(--c-ink-deep); /* @kind other */

  --flow-blend: normal; /* @kind other */
  --accent-sat: 1; /* @kind other */
  --cell-hi: #FFFFFF; /* @kind color */
  --nerve-line: var(--hn-vein-500); /* @kind other */
  --stage-is-dark: 0; /* @kind other */
  color-scheme: light;
}

[data-stage="light-clean"] {
  /* 2 · LIGHT CLEAN — flat light soft blue, soft horizon, purest register */
  --stage-top: #CFE2F3; /* @kind color */
  --stage-horizon-c: #EEF5FC; /* @kind color */
  --stage-floor: #D8E8F6; /* @kind color */
  --stage-horizon: 62%; /* @kind other */
  --stage-grid: transparent; /* @kind color */
  --stage-grid-major: transparent; /* @kind color */
  --stage-grid-size: 32px; /* @kind other */
  --stage-glow: rgb(255 255 255 / 0.9); /* @kind color */
  --stage-vignette: rgb(30 70 120 / 0.08); /* @kind color */

  /* chrome on a light lane — DESIGN.md as-is */
  --ink: var(--c-ink); /* @kind other */
  --ink-muted: var(--c-body); /* @kind other */
  --ink-charcoal: var(--c-charcoal); /* @kind other */
  --ink-faint: var(--c-mute); /* @kind other */
  --surface: var(--c-canvas); /* @kind other */
  --surface-soft: var(--c-surface-soft); /* @kind other */
  --line: var(--c-hairline); /* @kind other */
  --line-strong: var(--c-hairline-strong); /* @kind other */
  --btn-bg: var(--c-primary); /* @kind other */
  --btn-fg: var(--c-on-primary); /* @kind other */
  --btn-bg-active: var(--c-ink-deep); /* @kind other */

  --flow-blend: normal; /* @kind other */
  --accent-sat: 1; /* @kind other */
  --cell-hi: #FFFFFF; /* @kind color */
  --nerve-line: var(--hn-vein-600); /* @kind other */
  --stage-is-dark: 0; /* @kind other */
  color-scheme: light;
}

[data-stage="dark-grid"] {
  /* 3 · DARK GRID — glowing grid on a deep blue cove, cinematic register */
  --stage-top: #030C1C; /* @kind color */
  --stage-horizon-c: #0E2C62; /* @kind color */
  --stage-floor: #071A3E; /* @kind color */
  --stage-horizon: 58%; /* @kind other */
  --stage-grid: rgb(77 187 255 / 0.16); /* @kind color */
  --stage-grid-major: rgb(77 187 255 / 0.38); /* @kind color */
  --stage-grid-size: 48px; /* @kind other */
  --stage-glow: rgb(60 140 255 / 0.45); /* @kind color */
  --stage-vignette: rgb(0 0 0 / 0.55); /* @kind color */

  /* chrome on a dark lane — DESIGN.md inverted surface + pill-on-dark */
  --ink: var(--c-on-dark); /* @kind other */
  --ink-muted: var(--c-on-dark-mute); /* @kind other */
  --ink-charcoal: rgba(255, 255, 255, 0.82); /* @kind color */
  --ink-faint: rgba(255, 255, 255, 0.45); /* @kind color */
  --surface: var(--c-surface-dark); /* @kind other */
  --surface-soft: #222222; /* @kind color */
  --line: rgba(255, 255, 255, 0.12); /* @kind color */
  --line-strong: rgba(255, 255, 255, 0.22); /* @kind color */
  --btn-bg: var(--c-canvas); /* @kind other */
  --btn-fg: var(--c-ink); /* @kind other */
  --btn-bg-active: #E5E5E5; /* @kind color */

  --flow-blend: screen; /* @kind other */
  --accent-sat: 1.08; /* @kind other */
  --cell-hi: #FFF4E8; /* @kind color */
  --nerve-line: var(--hn-nerve-glow); /* @kind other */
  --stage-is-dark: 1; /* @kind other */

  /* inversion flips on dark lanes: the look-here box becomes the light one */
  --invert-surface: var(--c-canvas); /* @kind color */
  --invert-ink: var(--c-ink); /* @kind color */
  --invert-ink-muted: var(--c-body); /* @kind color */
  --invert-ink-charcoal: var(--c-charcoal); /* @kind color */
  --invert-ink-faint: var(--c-mute); /* @kind color */
  --invert-line: var(--c-hairline); /* @kind color */
  --invert-surface-soft: var(--c-surface-soft); /* @kind color */
  --invert-btn-bg: var(--c-primary); /* @kind color */
  --invert-btn-fg: var(--c-on-primary); /* @kind color */
  color-scheme: dark;
}

[data-stage="dark-clean"] {
  /* 4 · DARK CLEAN — deep blue glow stage, no lines */
  --stage-top: #040E22; /* @kind color */
  --stage-horizon-c: #173F86; /* @kind color */
  --stage-floor: #0A2152; /* @kind color */
  --stage-horizon: 60%; /* @kind other */
  --stage-grid: transparent; /* @kind color */
  --stage-grid-major: transparent; /* @kind color */
  --stage-grid-size: 48px; /* @kind other */
  --stage-glow: rgb(70 150 255 / 0.55); /* @kind color */
  --stage-vignette: rgb(0 0 0 / 0.6); /* @kind color */

  /* chrome on a dark lane — DESIGN.md inverted surface + pill-on-dark */
  --ink: var(--c-on-dark); /* @kind other */
  --ink-muted: var(--c-on-dark-mute); /* @kind other */
  --ink-charcoal: rgba(255, 255, 255, 0.82); /* @kind color */
  --ink-faint: rgba(255, 255, 255, 0.45); /* @kind color */
  --surface: var(--c-surface-dark); /* @kind other */
  --surface-soft: #222222; /* @kind color */
  --line: rgba(255, 255, 255, 0.12); /* @kind color */
  --line-strong: rgba(255, 255, 255, 0.22); /* @kind color */
  --btn-bg: var(--c-canvas); /* @kind other */
  --btn-fg: var(--c-ink); /* @kind other */
  --btn-bg-active: #E5E5E5; /* @kind color */

  --flow-blend: screen; /* @kind other */
  --accent-sat: 1.08; /* @kind other */
  --cell-hi: #FFF4E8; /* @kind color */
  --nerve-line: var(--hn-nerve-glow); /* @kind other */
  --stage-is-dark: 1; /* @kind other */

  /* inversion flips on dark lanes: the look-here box becomes the light one */
  --invert-surface: var(--c-canvas); /* @kind color */
  --invert-ink: var(--c-ink); /* @kind color */
  --invert-ink-muted: var(--c-body); /* @kind color */
  --invert-ink-charcoal: var(--c-charcoal); /* @kind color */
  --invert-ink-faint: var(--c-mute); /* @kind color */
  --invert-line: var(--c-hairline); /* @kind color */
  --invert-surface-soft: var(--c-surface-soft); /* @kind color */
  --invert-btn-bg: var(--c-primary); /* @kind color */
  --invert-btn-fg: var(--c-on-primary); /* @kind color */
  color-scheme: dark;
}

[data-stage="studio"] {
  /* 5 · CLEAN STUDIO — warm seamless sweep, premium, neutral.
     Off-blue: the accents are deliberately desaturated so they read softer. */
  --stage-top: #E6DFD3; /* @kind color */
  --stage-horizon-c: #F4EFE7; /* @kind color */
  --stage-floor: #DCD2C3; /* @kind color */
  --stage-horizon: 66%; /* @kind other */
  --stage-grid: transparent; /* @kind color */
  --stage-grid-major: transparent; /* @kind color */
  --stage-grid-size: 32px; /* @kind other */
  --stage-glow: rgb(255 250 242 / 0.95); /* @kind color */
  --stage-vignette: rgb(90 70 45 / 0.14); /* @kind color */

  /* chrome on a light lane — DESIGN.md as-is */
  --ink: var(--c-ink); /* @kind other */
  --ink-muted: var(--c-body); /* @kind other */
  --ink-charcoal: var(--c-charcoal); /* @kind other */
  --ink-faint: var(--c-mute); /* @kind other */
  --surface: var(--c-canvas); /* @kind other */
  --surface-soft: var(--c-surface-soft); /* @kind other */
  --line: var(--c-hairline); /* @kind other */
  --line-strong: var(--c-hairline-strong); /* @kind other */
  --btn-bg: var(--c-primary); /* @kind other */
  --btn-fg: var(--c-on-primary); /* @kind other */
  --btn-bg-active: var(--c-ink-deep); /* @kind other */

  --flow-blend: normal; /* @kind other */
  --accent-sat: 0.78; /* @kind other */
  --cell-hi: #FFF8EE; /* @kind color */
  --nerve-line: #5E8FC0; /* @kind color */
  --stage-is-dark: 0; /* @kind other */
  color-scheme: light;
}

/* assets/hn/tokens/states.css */
/* ==========================================================================
   PEPT — state language
   Five axes, straight from the visual rules sheet:
     --energy        0–1   brightness + flow speed      "brightness equals intensity"
     --density       0–1   pulses per period            "pulse density equals activity"
     --volume        0.6–1.3 pathway width multiplier   "line width equals volume"
     --irregularity  0–1   wave amplitude / jitter      "irregularity equals stress"
     --bias          -1–1  blue (rest) ↔ orange (activation)

   Apply with data-energy="…" on any ancestor. Values are registered
   (@property in motion.css) so switching state RAMPS over --dur-ramp
   instead of snapping — the vibrant/dull shift is itself an animation.
   ========================================================================== */

[data-energy] {
  transition:
    --energy var(--dur-ramp) var(--ease-flow),
    --density var(--dur-ramp) var(--ease-flow),
    --volume var(--dur-ramp) var(--ease-flow),
    --irregularity var(--dur-ramp) var(--ease-flow),
    --bias var(--dur-ramp) var(--ease-flow);
}

[data-energy="lethargic"]      { --energy: 0.12; /* @kind other */ --density: 0.30; /* @kind other */ --volume: 0.80; /* @kind other */ --irregularity: 0; /* @kind other */    --bias: -0.35; /* @kind other */ }
[data-energy="tired"]          { --energy: 0.28; /* @kind other */ --density: 0.45; /* @kind other */ --volume: 0.90; /* @kind other */ --irregularity: 0; /* @kind other */    --bias: -0.20; /* @kind other */ }
[data-energy="rest"]           { --energy: 0.30; /* @kind other */ --density: 0.30; /* @kind other */ --volume: 0.95; /* @kind other */ --irregularity: 0; /* @kind other */    --bias: -0.85; /* @kind other */ }
[data-energy="baseline"]       { --energy: 0.55; /* @kind other */ --density: 0.60; /* @kind other */ --volume: 1.00; /* @kind other */ --irregularity: 0; /* @kind other */    --bias:  0; /* @kind other */    }
[data-energy="recovery"]       { --energy: 0.50; /* @kind other */ --density: 0.55; /* @kind other */ --volume: 1.00; /* @kind other */ --irregularity: 0; /* @kind other */    --bias: -0.30; /* @kind other */ }
[data-energy="focused"]        { --energy: 0.72; /* @kind other */ --density: 0.70; /* @kind other */ --volume: 1.00; /* @kind other */ --irregularity: 0; /* @kind other */    --bias:  0.15; /* @kind other */ }
[data-energy="energised"]      { --energy: 0.82; /* @kind other */ --density: 0.85; /* @kind other */ --volume: 1.10; /* @kind other */ --irregularity: 0; /* @kind other */    --bias:  0.30; /* @kind other */ --pulse-beat: var(--beat-active); /* @kind other */ }
[data-energy="stressed"]       { --energy: 0.74; /* @kind other */ --density: 0.80; /* @kind other */ --volume: 1.00; /* @kind other */ --irregularity: 0.60; /* @kind other */ --bias:  0.45; /* @kind other */ --pulse-beat: var(--beat-active); /* @kind other */ }
[data-energy="surge"]          { --energy: 1.00; /* @kind other */ --density: 1.00; /* @kind other */ --volume: 1.20; /* @kind other */ --irregularity: 0.12; /* @kind other */ --bias:  0.55; /* @kind other */ --pulse-beat: var(--beat-surge); /* @kind other */ }
[data-energy="overstimulated"] { --energy: 1.00; /* @kind other */ --density: 1.00; /* @kind other */ --volume: 1.15; /* @kind other */ --irregularity: 0.75; /* @kind other */ --bias:  0.65; /* @kind other */ --pulse-beat: var(--beat-surge); /* @kind other */ }

:root,
[data-energy],
.hn-energy-scope,
.hn-energised {
  /* Derived render values — components read these, never the raw axes.
     Re-declared on every scope so var(--energy) resolves locally
     (custom properties resolve where declared, then inherit). */
  --r-saturate: calc((0.35 + var(--energy) * 0.85) * var(--accent-sat, 1)); /* @kind other */
  --r-brightness: calc(0.88 + var(--energy) * 0.22); /* @kind other */
  --r-opacity: calc(0.58 + var(--energy) * 0.42); /* @kind other */
  --r-glow: calc(var(--energy) * var(--energy)); /* @kind other */ /* glow falls off fast when dull */
  --r-speed: calc(0.25 + var(--energy) * 1.75); /* @kind other */ /* multiplier on --flow-steady */
}

/* The shared filter every pathway/atom applies. */
.hn-energised {
  filter: saturate(var(--r-saturate)) brightness(var(--r-brightness));
  opacity: var(--r-opacity);
}

/* assets/hn/tokens/component-defaults.css */
/* ==========================================================================
   Component defaults — the inherited starting values components read.
   These live at :root (not on a class) so they register as design-system
   tokens and so any scope can retune a component without touching its CSS.
   Variants are declared as [data-*] scopes in the component stylesheets.
   ========================================================================== */

:root {
  /* ---- Pulse badge ------------------------------------------------------ */
  --pulse-size: var(--node-size); /* @kind spacing */
  --pulse-c: var(--hn-artery-400); /* @kind color */
  --pulse-c-rgb: var(--hn-artery-rgb); /* @kind other */
  --pulse-beat: var(--beat-rest); /* @kind other */

  /* ---- Atoms ----------------------------------------------------------- */
  --dot-c: var(--hn-artery-400); /* @kind color */
  --arrow-c: var(--hn-artery-400); /* @kind color */
  --ring-size: calc(var(--node-size) * 2.4); /* @kind spacing */
  --halo-size: calc(var(--node-size) * 1.6); /* @kind spacing */
  --meter-count: 4; /* @kind other */

  /* ---- Avatar ---------------------------------------------------------- */
  --line-c: var(--hn-artery-400); /* @kind color */
  --line-cool: var(--hn-vein-400); /* @kind color */

  /* ---- Controls -------------------------------------------------------- */
  --seg-n: 5; /* @kind other */
  --seg-i: 0; /* @kind other */
  --route-n: 6; /* @kind other */
  --route-progress: 0; /* @kind other */

  /* ---- Layout ---------------------------------------------------------- */
  --tile-cols: 3; /* @kind other */
  --cam-z: 1; /* @kind other */

  /* ---- Inversion (the one "look here" surface per view) ----------------
     Light stages invert to charcoal; the dark stages flip these back. */
  --invert-surface: var(--c-surface-dark); /* @kind color */
  --invert-ink: var(--c-on-dark); /* @kind color */
  --invert-ink-muted: var(--c-on-dark-mute); /* @kind color */
  --invert-ink-charcoal: rgba(255, 255, 255, 0.82); /* @kind color */
  --invert-ink-faint: rgba(255, 255, 255, 0.45); /* @kind color */
  --invert-line: rgba(255, 255, 255, 0.14); /* @kind color */
  --invert-surface-soft: #262626; /* @kind color */
  --invert-btn-bg: var(--c-canvas); /* @kind color */
  --invert-btn-fg: var(--c-ink); /* @kind color */
}

/* assets/hn/components/stage/stage.css */
/* ==========================================================================
   Stage — the world every piece lives inside.
   Markup:
   <section class="hn-stage" data-stage="dark-grid">
     <div class="hn-stage__backdrop" aria-hidden="true">
       <i class="hn-stage__wall"></i><i class="hn-stage__floor"></i>
       <i class="hn-stage__horizon"></i><i class="hn-stage__wash"></i>
     </div>
     <div class="hn-stage__content">…</div>
   </section>
   --cam-x / --cam-y (px) are written by the camera so grid layers parallax.
   ========================================================================== */

.hn-stage {
  /* --cam-x / --cam-y / --cam-z default at :root (registered in motion.css);
     the camera writes them inline on this element. */
  transition: --cam-x var(--dur-transition) var(--ease-flow), --cam-y var(--dur-transition) var(--ease-flow);
  position: relative;
  isolation: isolate;
  overflow: hidden;
  color: var(--ink);
  background: linear-gradient(180deg,
      var(--stage-top) 0%,
      var(--stage-horizon-c) var(--stage-horizon),
      var(--stage-floor) 100%);
}

.hn-stage--framed { border-radius: var(--r-lg); }
.hn-stage--fill   { position: fixed; inset: 0; }
.hn-stage--16x9   { aspect-ratio: 16 / 9; }
.hn-stage--9x16   { aspect-ratio: 9 / 16; }

.hn-stage__backdrop {
  position: absolute;
  inset: 0;
  z-index: var(--z-stage);
  pointer-events: none;
  overflow: hidden;
}
.hn-stage__backdrop > i { position: absolute; display: block; }

/* ---- Horizon bloom (every lane) ---------------------------------------- */
.hn-stage__horizon {
  left: -10%;
  right: -10%;
  top: calc(var(--stage-horizon) - 22%);
  height: 44%;
  background: radial-gradient(50% 50% at 50% 50%, var(--stage-glow) 0%, transparent 100%);
  transform: translateX(calc(var(--cam-x) * -0.04));
}

/* ---- Wall layer: flat grid (light-grid) or back-wall grid (dark-grid) --- */
.hn-stage__wall {
  inset: -2px;
  background-image:
    linear-gradient(var(--stage-grid-major) 1px, transparent 1px),
    linear-gradient(90deg, var(--stage-grid-major) 1px, transparent 1px),
    linear-gradient(var(--stage-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--stage-grid) 1px, transparent 1px);
  background-size:
    calc(var(--stage-grid-size) * 5) calc(var(--stage-grid-size) * 5),
    calc(var(--stage-grid-size) * 5) calc(var(--stage-grid-size) * 5),
    var(--stage-grid-size) var(--stage-grid-size),
    var(--stage-grid-size) var(--stage-grid-size);
  background-position:
    calc(var(--cam-x) * -0.35) calc(var(--cam-y) * -0.35);
}

/* ---- Floor layer: perspective grid receding to the horizon -------------- */
.hn-stage__backdrop > .hn-stage__floor {
  display: none;
  left: -60%;
  right: -60%;
  top: var(--stage-horizon);
  height: 140%;
  transform-origin: 50% 0;
  transform: perspective(620px) rotateX(var(--floor-tilt));
  background-image:
    linear-gradient(var(--stage-grid-major) 2px, transparent 2px),
    linear-gradient(90deg, var(--stage-grid-major) 2px, transparent 2px),
    linear-gradient(var(--stage-grid) 8px, transparent 8px),
    linear-gradient(90deg, var(--stage-grid) 8px, transparent 8px);
  background-size: 96px 96px;
  background-position:
    calc(var(--cam-x) * -0.9) calc(var(--cam-y) * -0.9),
    calc(var(--cam-x) * -0.9) calc(var(--cam-y) * -0.9),
    calc(var(--cam-x) * -0.9 - 3px) calc(var(--cam-y) * -0.9 - 3px),
    calc(var(--cam-x) * -0.9 - 3px) calc(var(--cam-y) * -0.9 - 3px);
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 22%, #000 100%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 22%, #000 100%);
}

/* ---- Colour-as-state wash (gold win, red danger, green healthy…) -------- */
.hn-stage__wash {
  inset: 0;
  background: radial-gradient(80% 70% at 50% var(--stage-horizon), var(--wash-color, transparent), transparent 100%);
  mix-blend-mode: var(--wash-blend, soft-light);
  opacity: var(--wash-opacity);
  transition: --wash-opacity var(--dur-ramp) var(--ease-flow), background var(--dur-ramp) var(--ease-flow);
}
[data-wash="win"]       { --wash-color: var(--hn-state-win);       --wash-opacity: 0.85; /* @kind other */ }
[data-wash="danger"]    { --wash-color: var(--hn-state-danger);    --wash-opacity: 0.8; /* @kind other */ }
[data-wash="healthy"]   { --wash-color: var(--hn-state-healthy);   --wash-opacity: 0.75; /* @kind other */ }
[data-wash="mechanism"] { --wash-color: var(--hn-state-mechanism); --wash-opacity: 0.8; /* @kind other */ }
[data-wash="rejected"]  { --wash-color: var(--hn-state-rejected);  --wash-opacity: 1; /* @kind other */ --wash-blend: saturation; /* @kind other */ }

/* ---- Vignette ----------------------------------------------------------- */
.hn-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-stage);
  pointer-events: none;
  background: radial-gradient(120% 90% at 50% 45%, transparent 55%, var(--stage-vignette) 100%);
}

.hn-stage__content {
  position: relative;
  z-index: var(--z-content);
  width: 100%;
  height: 100%;
}

/* ---- Lane specifics ----------------------------------------------------- */

/* 1 · light grid: flat graph paper, faded at the edges */
.hn-stage[data-stage="light-grid"] .hn-stage__wall,
.hn-stage:not([data-stage]) .hn-stage__wall {
  -webkit-mask-image: radial-gradient(90% 85% at 50% 50%, #000 55%, transparent 100%);
          mask-image: radial-gradient(90% 85% at 50% 50%, #000 55%, transparent 100%);
}

/* 2 · light clean + 4 · dark clean + 5 · studio: no lines */
.hn-stage[data-stage="light-clean"] .hn-stage__wall,
.hn-stage[data-stage="dark-clean"] .hn-stage__wall,
.hn-stage[data-stage="studio"] .hn-stage__wall { display: none; }

.hn-stage[data-stage="light-clean"] .hn-stage__horizon {
  top: calc(var(--stage-horizon) - 8%);
  height: 16%;
  background: radial-gradient(70% 50% at 50% 50%, var(--stage-glow) 0%, transparent 100%);
  filter: blur(6px);
}

/* 3 · dark grid: back wall grid above the horizon + glowing floor grid */
.hn-stage[data-stage="dark-grid"] .hn-stage__wall {
  bottom: calc(100% - var(--stage-horizon));
  opacity: 0.55;
  -webkit-mask-image: linear-gradient(0deg, #000 0%, transparent 85%);
          mask-image: linear-gradient(0deg, #000 0%, transparent 85%);
}
.hn-stage[data-stage="dark-grid"] .hn-stage__backdrop > .hn-stage__floor {
  display: block;
}
.hn-stage[data-stage="dark-grid"] .hn-stage__horizon,
.hn-stage[data-stage="dark-clean"] .hn-stage__horizon {
  mix-blend-mode: screen;
}

/* 4 · dark clean: a larger, deeper glow pool — the light IS the stage */
.hn-stage[data-stage="dark-clean"] .hn-stage__horizon {
  top: calc(var(--stage-horizon) - 38%);
  height: 76%;
  left: -20%;
  right: -20%;
}

/* 5 · studio: seamless sweep — floor curve catches light, wide vignette */
.hn-stage[data-stage="studio"] {
  background:
    radial-gradient(140% 60% at 50% calc(var(--stage-horizon) + 6%), rgb(255 252 246 / 0.55) 0%, transparent 60%),
    linear-gradient(180deg, var(--stage-top) 0%, var(--stage-horizon-c) var(--stage-horizon), var(--stage-floor) 100%);
}
.hn-stage[data-stage="studio"] .hn-stage__horizon {
  top: calc(var(--stage-horizon) - 30%);
  height: 60%;
  filter: blur(10px);
}

/* Glows composite additively on the dark lanes. */
.hn-stage[data-stage^="dark"] .hn-flow__bloom,
.hn-stage[data-stage^="dark"] .hn-glow-add { mix-blend-mode: screen; }

/* assets/hn/components/pathways/pathways.css */
/* ==========================================================================
   Pathways — artery, vein, capillary, nerve.
   Two renderers share one material language:

   A) .hn-vessel / .hn-nerve   — straight horizontal HTML pathways (CSS only).
      Fast, drop-in, ideal for bands, dividers, buttons, tile edges.

   B) .hn-flow                 — SVG pathways along ANY path (bends, loops,
      fan-outs, the journey spine). Stack these strokes on the same `d`:
        .hn-flow__bloom  .hn-flow__wall  .hn-flow__core  .hn-flow__sheen
        .hn-flow__cells  .hn-flow__cells--sm  (.hn-flow__signal for nerves)

   Kind is set with data-kind="artery|vein|capillary|nerve" (a theme scope, so
   the four material palettes register as design-system tokens). The BEM
   modifier classes remain for structure only.
   Energy comes from the nearest [data-energy] ancestor (see tokens/states.css).
   Direction: --dir: 1 (with the path) or -1 (against it). Veins default to -1.
   ========================================================================== */

/* ---- Shared kind variables --------------------------------------------- */
[data-kind="artery"] {
  --w: var(--w-artery);
  --c-wall: var(--hn-artery-700);
  --c-core: var(--hn-artery-500);
  --c-hi: var(--hn-artery-300);
  --c-glow: var(--hn-artery-400);
  --c-cell: var(--hn-artery-200);
  --tube: var(--tube-artery);
  --bloom: var(--bloom-artery);
  --dir: 1; /* @kind other */
}
[data-kind="vein"] {
  --w: var(--w-vein);
  --c-wall: var(--hn-vein-700);
  --c-core: var(--hn-vein-500);
  --c-hi: var(--hn-vein-300);
  --c-glow: var(--hn-vein-400);
  --c-cell: var(--hn-vein-100);
  --tube: var(--tube-vein);
  --bloom: var(--bloom-vein);
  --dir: -1; /* @kind other */
  --flow-direction: reverse; /* @kind other */
}
[data-kind="capillary"] {
  --w: var(--w-capillary);
  --c-wall: var(--hn-artery-600);
  --c-core: var(--hn-artery-400);
  --c-hi: var(--hn-artery-200);
  --c-glow: var(--hn-artery-300);
  --c-cell: #FFFFFF;
  --tube: var(--tube-artery);
  --bloom: var(--bloom-artery);
  --dir: 1; /* @kind other */
}
[data-kind="nerve"] {
  --w: var(--w-nerve);
  --c-wall: rgb(var(--hn-vein-rgb) / 0.25);
  --c-core: var(--nerve-line);
  --c-hi: var(--hn-nerve-core);
  --c-glow: var(--hn-nerve-glow);
  --c-cell: var(--hn-nerve-core);
  --bloom: var(--bloom-nerve);
  --dir: 1; /* @kind other */
}

/* ==========================================================================
   A) HTML vessel
   <div class="hn-vessel" data-kind="artery"><i class="hn-vessel__cells"></i></div>
   ========================================================================== */
.hn-vessel {
  position: relative;
  height: calc(var(--w) * var(--volume, 1));
  min-width: calc(var(--w) * 2);
  border-radius: var(--r-full);
  background: var(--tube);
  box-shadow: var(--bloom);
  overflow: hidden;
  filter: saturate(var(--r-saturate)) brightness(var(--r-brightness));
  opacity: var(--r-opacity);
  animation: hn-jitter calc(220ms + 400ms * (1 - var(--irregularity))) steps(2, jump-none) infinite;
}
.hn-vessel--square { border-radius: 0; }
.hn-vessel--open-l { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.hn-vessel--open-r { border-top-right-radius: 0; border-bottom-right-radius: 0; }

/* sheen: specular stripe on the upper third */
.hn-vessel::before {
  content: "";
  position: absolute;
  left: calc(var(--w) * 0.35);
  right: calc(var(--w) * 0.35);
  top: 17%;
  height: 18%;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.6) 12%, rgb(255 255 255 / 0.45) 88%, transparent);
  filter: blur(0.6px);
  opacity: 0.85;
}

/* flowing cells: two sizes, one period, travel via background-position */
.hn-vessel__cells {
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle calc(var(--w) * 0.19) at calc(var(--cell-gap) * 0.25) 52%,
      var(--cell-hi) 0 18%, var(--c-cell) 42%, var(--c-glow) 72%, transparent 100%),
    radial-gradient(circle calc(var(--w) * 0.1) at calc(var(--cell-gap) * 0.7) 40%,
      var(--cell-hi) 0 20%, var(--c-cell) 55%, transparent 100%);
  background-size: var(--cell-gap) 100%;
  background-repeat: repeat-x;
  opacity: calc(0.35 + 0.65 * var(--density));
  animation: hn-cells calc(var(--dur-steady) * 0.4 / var(--r-speed)) linear infinite;
}
[data-kind="vein"] .hn-vessel__cells { animation-direction: reverse; }
.hn-vessel[style*="--dir: -1"] .hn-vessel__cells,
.hn-vessel[data-dir="reverse"] .hn-vessel__cells { animation-direction: reverse; }

@keyframes hn-cells {
  from { background-position-x: 0; }
  to   { background-position-x: var(--cell-gap); }
}

@keyframes hn-jitter {
  0%   { transform: translateY(0); }
  50%  { transform: translateY(calc(var(--irregularity) * 2.5px)); }
  100% { transform: translateY(calc(var(--irregularity) * -2px)); }
}

/* Band: artery above, vein below — the default pairing ("intro band") */
.hn-band {
  display: grid;
  gap: var(--lane-gap);
}

/* ==========================================================================
   A) HTML nerve
   <div class="hn-nerve"><i class="hn-nerve__signal"></i></div>
   ========================================================================== */
.hn-nerve {
  position: relative;
  height: calc(var(--w) * var(--volume, 1));
  min-width: calc(var(--w) * 2);
  border-radius: var(--r-full);
  background: radial-gradient(60% 50% at 50% 50%, rgb(var(--hn-vein-rgb) / 0.22), transparent 100%);
  filter: saturate(var(--r-saturate)) brightness(var(--r-brightness));
  opacity: var(--r-opacity);
}
.hn-nerve::before {                       /* the fibre */
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  height: var(--w-nerve-fibre);
  transform: translateY(-50%);
  border-radius: var(--r-full);
  background: linear-gradient(90deg, transparent, var(--c-core) 6%, var(--c-core) 94%, transparent);
  box-shadow: var(--bloom);
}
.hn-nerve__signal {                        /* the sparks */
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(calc(var(--signal-len) * 0.5) calc(var(--w) * 0.34) at calc(var(--signal-gap) * 0.5) 50%,
      #FFFFFF 0 16%, var(--hn-nerve-arc) 36%, rgb(var(--hn-vein-rgb) / 0.45) 62%, transparent 100%);
  background-size: var(--signal-gap) 100%;
  background-repeat: repeat-x;
  opacity: calc(0.2 + 0.8 * var(--density));
  animation: hn-signal calc(var(--dur-signal) * 0.13 / var(--r-speed)) linear infinite;
}
@keyframes hn-signal {
  from { background-position-x: 0; }
  to   { background-position-x: var(--signal-gap); }
}

/* ==========================================================================
   B) SVG flow — any path
   ========================================================================== */
.hn-flow {
  filter: saturate(var(--r-saturate)) brightness(var(--r-brightness));
  opacity: var(--r-opacity);
}
.hn-flow path,
.hn-flow line,
.hn-flow polyline {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.hn-flow__bloom {
  stroke: var(--c-glow);
  stroke-width: calc(var(--w) * var(--volume, 1) * 1.7);
  opacity: calc(0.12 + 0.42 * var(--r-glow));
  filter: blur(var(--glow-lg));
}
.hn-flow__wall {
  stroke: var(--c-wall);
  stroke-width: calc(var(--w) * var(--volume, 1));
}
.hn-flow__core {
  stroke: var(--c-core);
  stroke-width: calc(var(--w) * var(--volume, 1) * 0.74);
}
.hn-flow__sheen {
  stroke: var(--c-hi);
  stroke-width: calc(var(--w) * var(--volume, 1) * 0.26);
  opacity: 0.9;
  transform: translate(0, calc(var(--w) * var(--volume, 1) * -0.16));
}
.hn-flow__spec {
  stroke: #FFFFFF;
  stroke-width: calc(var(--w) * var(--volume, 1) * 0.07);
  opacity: 0.55;
  transform: translate(0, calc(var(--w) * var(--volume, 1) * -0.2));
}

.hn-flow__cells,
.hn-flow__cells--sm,
.hn-flow__cells-glow {
  stroke-dasharray: 0 var(--cell-gap);
  animation: hn-dash calc(var(--dur-steady) * 0.4 / var(--r-speed)) linear infinite;
  animation-direction: var(--flow-direction, normal);
}
.hn-flow__cells-glow {
  stroke: var(--c-glow);
  stroke-width: calc(var(--cell-lg) * 1.8);
  opacity: calc(0.12 + 0.3 * var(--density));
}
.hn-flow__cells {
  stroke: var(--c-cell);
  stroke-width: var(--cell-lg);
  opacity: calc(0.3 + 0.7 * var(--density));
}
.hn-flow__cells--sm {
  stroke: var(--cell-hi);
  stroke-width: var(--cell-sm);
  stroke-dashoffset: calc(var(--cell-gap) * -0.5);
  animation-name: hn-dash-half;
  opacity: calc(0.2 + 0.8 * var(--density));
}

@keyframes hn-dash      { from { stroke-dashoffset: 0; } to { stroke-dashoffset: calc(var(--cell-gap) * -1); } }
@keyframes hn-dash-half { from { stroke-dashoffset: calc(var(--cell-gap) * -0.5); } to { stroke-dashoffset: calc(var(--cell-gap) * -1.5); } }

/* Nerve on SVG: faint sheath + bright fibre + travelling sparks */
[data-kind="nerve"] .hn-flow__wall  { stroke: rgb(var(--hn-vein-rgb) / 0.18); stroke-width: calc(var(--w) * 0.9); }
[data-kind="nerve"] .hn-flow__core  { stroke: var(--c-core); stroke-width: var(--w-nerve-fibre); }
[data-kind="nerve"] .hn-flow__bloom { stroke-width: calc(var(--w) * 0.9); }
.hn-flow__signal {
  stroke: #FFFFFF;
  stroke-width: calc(var(--w) * 0.34);
  stroke-dasharray: var(--signal-len) var(--signal-gap);
  opacity: calc(0.25 + 0.75 * var(--density));
  filter: drop-shadow(0 0 4px var(--hn-nerve-glow)) drop-shadow(0 0 10px var(--hn-vein-400));
  animation: hn-signal-dash calc(var(--dur-signal) * 0.33 / var(--r-speed)) linear infinite;
}
@keyframes hn-signal-dash {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: calc((var(--signal-len) + var(--signal-gap)) * -1); }
}

/* Capillary mesh: fine strands that cross-fade orange → blue */
.hn-mesh path {
  fill: none;
  stroke-width: 1.6;
  stroke-linecap: round;
}
.hn-mesh__strand { stroke: url(#hn-mesh-grad); opacity: 0.85; }
.hn-mesh__glow   { stroke: url(#hn-mesh-grad); stroke-width: 5; opacity: 0.25; filter: blur(3px); }
.hn-mesh__spark {
  stroke: #FFFFFF;
  stroke-width: 2.4;
  stroke-dasharray: 2 120;
  animation: hn-mesh-spark calc(var(--dur-steady) / var(--r-speed)) linear infinite;
}
@keyframes hn-mesh-spark { to { stroke-dashoffset: -122; } }

/* Traversal: a path drawing itself — used when the camera travels a seam */
.hn-flow--draw path {
  stroke-dasharray: var(--path-len, 2000) var(--path-len, 2000);
  stroke-dashoffset: var(--path-len, 2000);
  animation: hn-draw var(--dur-transition) var(--ease-flow) forwards;
}
@keyframes hn-draw { to { stroke-dashoffset: 0; } }

@media (prefers-reduced-motion: reduce) {
  .hn-vessel, .hn-vessel__cells, .hn-nerve__signal,
  .hn-flow__cells, .hn-flow__cells--sm, .hn-flow__cells-glow,
  .hn-flow__signal, .hn-mesh__spark { animation: none; }
}

/* JS-driven flow (FlowClock in components/motion/engine.js) — the engine
   writes stroke-dashoffset every frame, so the CSS loops step aside.      */
.hn-flow--js .hn-flow__cells,
.hn-flow--js .hn-flow__cells--sm,
.hn-flow--js .hn-flow__cells-glow,
.hn-flow--js .hn-flow__signal { animation: none; }

/* Layered rendering (UI kits): static tubes in one <svg class="hn-flow-layer">,
   moving cells in another. Energy filtering moves up to the <svg> box so the
   browser can composite it instead of repainting every stroke. */
.hn-flow-layer {
  filter: saturate(var(--r-saturate)) brightness(var(--r-brightness));
  opacity: var(--r-opacity);
}
.hn-flow-layer .hn-flow { filter: none; opacity: 1; }

/* assets/hn/components/atoms/atoms.css */
/* ==========================================================================
   Atoms — nodes, badges, dots, rings, connectors.
   Every atom is lit from within and obeys the local [data-energy].
   ========================================================================== */

/* ---- Pulse badge — activity indicator (heartbeat) ----------------------- */
.hn-pulse-badge {
  position: relative;
  display: inline-block;
  width: var(--pulse-size);
  height: var(--pulse-size);
  border-radius: 50%;
  background: radial-gradient(circle,
      #FFFFFF 0 9%, var(--hn-heat) 20%, var(--pulse-c) 42%,
      rgb(var(--pulse-c-rgb) / 0.35) 60%, rgb(var(--pulse-c-rgb) / 0) 72%);
  filter: saturate(var(--r-saturate)) brightness(var(--r-brightness));
  animation: hn-beat var(--pulse-beat) var(--ease-pulse) infinite;
}
.hn-pulse-badge::before,
.hn-pulse-badge::after {
  content: "";
  position: absolute;
  inset: 14%;
  border-radius: inherit;
  border: 1.5px solid rgb(var(--pulse-c-rgb) / 0.55);
  animation: hn-ripple calc(var(--pulse-beat) * 2) var(--ease-arrive) infinite;
}
.hn-pulse-badge::after { animation-delay: var(--pulse-beat); }
/* Variants ride data attributes so their values register as theme tokens.
   Only the pulse badge reads --pulse-*, so the scopes cannot leak. */
[data-tone="vein"] { --pulse-c: var(--hn-vein-400); /* @kind color */ --pulse-c-rgb: var(--hn-vein-rgb); /* @kind other */ }
[data-scale="sm"] { --pulse-size: calc(var(--node-size) * 0.55); /* @kind spacing */ }
[data-scale="lg"] { --pulse-size: calc(var(--node-size) * 2); /* @kind spacing */ }

@keyframes hn-beat {
  0%, 100% { transform: scale(1); }
  14%      { transform: scale(calc(1 + 0.12 * var(--energy))); }
  28%      { transform: scale(0.98); }
  42%      { transform: scale(calc(1 + 0.06 * var(--energy))); }
}
@keyframes hn-ripple {
  from { transform: scale(0.6); opacity: 0.9; }
  to   { transform: scale(1.9); opacity: 0; }
}

/* ---- Synapse node — signal transfer (orb + radiating dendrites) --------- */
.hn-synapse {
  position: relative;
  display: inline-block;
  width: var(--node-size);
  height: var(--node-size);
  filter: saturate(var(--r-saturate)) brightness(var(--r-brightness));
}
.hn-synapse::before {                         /* dendrites */
  content: "";
  position: absolute;
  inset: -45%;
  border-radius: 50%;
  background: repeating-conic-gradient(from 8deg,
      rgb(var(--hn-vein-rgb) / 0.95) 0deg 1.6deg, transparent 1.6deg 45deg),
    repeating-conic-gradient(from 30deg,
      rgb(var(--hn-vein-rgb) / 0.6) 0deg 1deg, transparent 1deg 45deg);
  -webkit-mask-image: radial-gradient(circle, #000 18%, rgb(0 0 0 / 0.4) 42%, transparent 68%);
          mask-image: radial-gradient(circle, #000 18%, rgb(0 0 0 / 0.4) 42%, transparent 68%);
  animation: hn-synapse-fire calc(var(--beat-rest) * 2.2 / var(--r-speed)) var(--ease-pulse) infinite;
}
.hn-synapse::after {                          /* soma */
  content: "";
  position: absolute;
  inset: 22%;
  border-radius: 50%;
  background: radial-gradient(circle at 36% 32%, #FFFFFF 0 10%, var(--hn-vein-300) 26%, var(--hn-vein-500) 58%, var(--hn-vein-700) 100%);
  box-shadow: var(--bloom-vein);
}
@keyframes hn-synapse-fire {
  0%, 70%, 100% { transform: scale(0.9); opacity: 0.7; }
  78%           { transform: scale(1.12); opacity: 1; }
}

/* ---- State dot — vibrant / calm / dormant ------------------------------- */
.hn-state-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: radial-gradient(circle at 38% 34%, #FFFFFF 0 14%, var(--dot-c) 52%, color-mix(in srgb, var(--dot-c) 70%, #000) 100%);
  box-shadow: 0 0 8px color-mix(in srgb, var(--dot-c) 70%, transparent);
  flex: none;
}
[data-state="calm"]    { --dot-c: var(--hn-vein-400); /* @kind color */ }
[data-state="dormant"] { --dot-c: #D9DCE0; /* @kind color */ }
.hn-state-dot--dormant { box-shadow: 0 0 0 1px var(--line-strong); }
.hn-state-dot--lg      { width: 18px; height: 18px; }

/* ---- Focus ring — "move in on a key area" ------------------------------ */
.hn-focus-ring {
  display: inline-block;
  width: var(--ring-size);
  height: var(--ring-size);
  border-radius: 50%;
  border: 2px dashed var(--hn-artery-400);
  animation: hn-spin 16s linear infinite;
}
.hn-focus-ring--lens {                        /* the magnifier from "zoom focus" */
  border: 2px solid rgb(255 255 255 / 0.9);
  box-shadow: 0 0 0 1px var(--line-strong), 0 10px 30px rgb(0 0 0 / 0.18), inset 0 0 24px rgb(255 255 255 / 0.25);
  animation: none;
  backdrop-filter: saturate(1.4) brightness(1.06);
}
@keyframes hn-spin { to { transform: rotate(360deg); } }

/* ---- Cadence trail — decaying dots (fatigue fade / momentum) ----------- */
.hn-cadence {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.hn-cadence > i {
  display: block;
  border-radius: 50%;
  background: radial-gradient(circle at 38% 34%, #FFF 0 12%, var(--hn-artery-300) 40%, var(--hn-artery-500) 100%);
  box-shadow: 0 0 8px rgb(var(--hn-artery-rgb) / 0.5);
}
/* the decay is written straight onto each dot — no per-dot variables */
.hn-cadence > i:nth-child(1) { width: 14px;   height: 14px;   opacity: 1; }
.hn-cadence > i:nth-child(2) { width: 11.8px; height: 11.8px; opacity: 0.83; }
.hn-cadence > i:nth-child(3) { width: 9.6px;  height: 9.6px;  opacity: 0.66; }
.hn-cadence > i:nth-child(4) { width: 7.4px;  height: 7.4px;  opacity: 0.49; }
.hn-cadence > i:nth-child(5) { width: 5.2px;  height: 5.2px;  opacity: 0.32; }

/* ---- Cadence meter — the timing-sheet boxes (calm 120 … burst 600) ----- */
.hn-cadence-meter {
  display: grid;
  gap: var(--sp-sm);
  padding: var(--sp-md) var(--sp-lg);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
}
.hn-cadence-meter__label { font: var(--type-code-sm); color: var(--ink); }
.hn-cadence-meter__track {
  position: relative;
  height: 14px;
  background-image: radial-gradient(circle 4.5px at 50% 50%, #FFF 0 25%, var(--hn-artery-400) 55%, rgb(var(--hn-artery-rgb) / 0) 100%);
  background-size: calc(100% / var(--meter-count)) 100%;
  background-repeat: repeat-x;
  animation: hn-cells-meter var(--meter-dur, 2s) linear infinite;
}
.hn-cadence-meter__track::before {
  content: "";
  position: absolute;
  inset: 5px 0;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, transparent, rgb(var(--hn-artery-rgb) / 0.25), transparent);
}
@keyframes hn-cells-meter { to { background-position-x: calc(100% / var(--meter-count)); } }

/* ---- Direction arrow ---------------------------------------------------- */
.hn-arrow {
  position: relative;
  display: inline-block;
  width: 96px;
  height: 4px;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, rgb(var(--hn-artery-rgb) / 0.2), var(--arrow-c));
}
.hn-arrow::after {
  content: "";
  position: absolute;
  right: -2px;
  top: 50%;
  width: 14px;
  height: 14px;
  border-top: 4px solid var(--arrow-c);
  border-right: 4px solid var(--arrow-c);
  border-radius: 2px;
  transform: translateY(-50%) rotate(45deg);
}

/* ---- Collar ring — support / join at a seam ---------------------------- */
.hn-collar {
  display: inline-block;
  width: var(--collar-w);
  height: calc(var(--w-artery) * 1.3);
  border-radius: 6px;
  background: linear-gradient(90deg,
      var(--hn-vein-700) 0%, var(--hn-vein-300) 35%, var(--hn-vein-100) 50%, var(--hn-vein-400) 70%, var(--hn-vein-700) 100%);
  box-shadow: 0 0 0 1px rgb(var(--hn-vein-rgb) / 0.35), var(--bloom-vein);
}
.hn-collar--double {
  width: calc(var(--collar-w) * 2.4);
  background:
    linear-gradient(90deg, var(--hn-vein-700) 0%, var(--hn-vein-300) 12%, var(--hn-vein-100) 18%, var(--hn-vein-500) 30%, transparent 30%, transparent 70%, var(--hn-vein-500) 70%, var(--hn-vein-100) 82%, var(--hn-vein-300) 88%, var(--hn-vein-700) 100%);
  box-shadow: none;
}

/* ---- Heat halo / cooling wave (metabolic atoms) ------------------------ */
.hn-halo {
  display: inline-block;
  width: var(--halo-size);
  height: var(--halo-size);
  border-radius: 50%;
  background:
    radial-gradient(circle, #FFF6E6 0 8%, var(--hn-heat) 22%, var(--hn-artery-400) 40%, rgb(var(--hn-artery-rgb) / 0.2) 58%, transparent 70%),
    repeating-radial-gradient(circle, rgb(var(--hn-artery-rgb) / 0.18) 0 1px, transparent 1px 9px);
  filter: saturate(var(--r-saturate)) brightness(var(--r-brightness));
  animation: hn-breathe var(--breath) var(--ease-flow) infinite;
}
.hn-halo--cool {
  background:
    radial-gradient(circle, #F2FAFF 0 8%, var(--hn-vein-300) 22%, var(--hn-vein-500) 40%, rgb(var(--hn-vein-rgb) / 0.2) 58%, transparent 70%),
    repeating-radial-gradient(circle, rgb(var(--hn-vein-rgb) / 0.18) 0 1px, transparent 1px 9px);
}
@keyframes hn-breathe {
  0%, 100% { transform: scale(0.94); }
  50%      { transform: scale(1.04); }
}

/* ---- Contact shadow — anything resting on the floor -------------------- */
.hn-contact {
  display: block;
  width: 70%;
  height: 14px;
  margin: -6px auto 0;
  background: var(--contact-shadow);
  filter: blur(2px);
  opacity: calc(0.6 + 0.4 * (1 - var(--stage-is-dark, 0)));
}

@media (prefers-reduced-motion: reduce) {
  .hn-pulse-badge, .hn-pulse-badge::before, .hn-pulse-badge::after,
  .hn-synapse::before, .hn-focus-ring, .hn-cadence-meter__track, .hn-halo { animation: none; }
}

/* assets/hn/components/controls/controls.css */
/* ==========================================================================
   Controls — CHROME layer. Specs verbatim from DESIGN.md components block.
   Flat. Pills. One colour of CTA (black on light lanes, white on dark lanes
   via --btn-bg / --btn-fg). No gradients, no shadows, no hover states —
   Default and Active/Pressed only, per the source system's policy.
   ========================================================================== */

/* ---- Button (button-primary / -secondary / -pill-on-dark / -disabled) --- */
.hn-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-sm);
  height: 36px;
  padding: 8px 20px;
  border: 0;
  border-radius: var(--r-full);
  font: var(--type-button-md);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.hn-btn > svg { width: 16px; height: 16px; flex: none; }
.hn-btn--icon { width: 36px; padding: 0; }

.hn-btn--primary          { background: var(--btn-bg); color: var(--btn-fg); }
.hn-btn--primary:active   { background: var(--btn-bg-active); }

.hn-btn--secondary        { background: var(--surface); color: var(--ink); box-shadow: inset 0 0 0 1px var(--line-strong); }
.hn-btn--secondary:active { background: var(--surface-soft); }

.hn-btn--on-dark          { background: var(--c-canvas); color: var(--c-ink); }
.hn-btn--on-dark:active   { background: #E5E5E5; }

.hn-btn:disabled,
.hn-btn[aria-disabled="true"] {
  background: var(--c-surface-soft);
  color: var(--c-mute);
  box-shadow: none;
  cursor: not-allowed;
}
.hn-btn:focus-visible { outline: 3px solid var(--c-focus-ring); outline-offset: 1px; }

/* ---- Links (link-inline / link-mute) ----------------------------------- */
.hn-link       { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
.hn-link--mute { color: var(--ink-muted); font: var(--type-body-sm); }

/* ---- Command tag (chip) — code-sm pill; optional world state-dot ------- */
.hn-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: 6px 12px;
  border-radius: var(--r-full);
  font: var(--type-code-sm);
  color: var(--ink);
  background: var(--surface-soft);
  white-space: nowrap;
}

/* ---- Install snippet — the signature code pill ------------------------- */
.hn-snippet {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  align-self: start;
  min-height: 48px;
  padding: 12px 12px 12px 20px;
  border-radius: var(--r-full);
  font: var(--type-code-md);
  color: var(--ink);
  background: var(--surface-soft);
}
.hn-snippet__code  { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.hn-snippet__code::before { content: "$ "; color: var(--ink-faint); }
.hn-snippet__copy {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--r-full);
  background: none;
  color: var(--ink-muted);
  cursor: pointer;
}
.hn-snippet__copy:focus-visible { outline: 3px solid var(--c-focus-ring); }

/* ---- Text input / search pill ----------------------------------------- */
.hn-input,
.hn-search {
  width: 100%;
  border: 0;
  border-radius: var(--r-full);
  color: var(--ink);
  outline: none;
}
.hn-input {
  height: 40px;
  padding: 8px 16px;
  font: var(--type-body-md);
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line);
}
.hn-input:focus { box-shadow: inset 0 0 0 1px var(--ink), 0 0 0 3px var(--c-focus-ring); }
.hn-search {
  height: 36px;
  padding: 8px 16px 8px 38px;
  font: var(--type-body-sm);
  background: var(--surface-soft) no-repeat 14px 50% / 16px
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23737373' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E");
}
.hn-search:focus { background-color: var(--surface); box-shadow: 0 0 0 3px var(--c-focus-ring); }
.hn-input::placeholder, .hn-search::placeholder { color: var(--ink-faint); }

/* ---- Segmented (stage switcher, energy picker) ------------------------- */
.hn-segmented {
  position: relative;
  display: inline-grid;
  grid-template-columns: repeat(var(--seg-n), minmax(0, 1fr));
  padding: var(--sp-xs);
  border-radius: var(--r-full);
  background: var(--surface-soft);
  box-shadow: inset 0 0 0 1px var(--line);
}
.hn-segmented::before {
  content: "";
  position: absolute;
  top: var(--sp-xs);
  bottom: var(--sp-xs);
  left: var(--sp-xs);
  width: calc((100% - 8px) / var(--seg-n));
  border-radius: var(--r-full);
  background: var(--btn-bg);
  transform: translateX(calc(100% * var(--seg-i)));
  transition: transform var(--dur-base) var(--ease-arrive);
}
.hn-segmented > button {
  position: relative;
  z-index: 1;
  height: 28px;
  padding: 0 var(--sp-md);
  border: 0;
  border-radius: var(--r-full);
  background: none;
  font: var(--type-button-md);
  color: var(--ink-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--dur-base) var(--ease-arrive);
}
.hn-segmented > button[aria-pressed="true"] { color: var(--btn-fg); }
.hn-segmented > button:focus-visible { outline: 3px solid var(--c-focus-ring); }

/* ---- Route — journey progress (flat: hairline track, ink fill) --------- */
.hn-route {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--route-n), 1fr);
  align-items: center;
  height: 24px;
  width: 200px;
  flex: none;
}
.hn-route::before,
.hn-route::after {
  content: "";
  position: absolute;
  left: calc(50% / var(--route-n));
  top: 50%;
  height: 2px;
  margin-top: -1px;
  border-radius: var(--r-full);
}
.hn-route::before { right: calc(50% / var(--route-n)); background: var(--line-strong); }
.hn-route::after {
  width: calc((100% - 100% / var(--route-n)) * var(--route-progress));
  background: var(--ink);
  transition: width var(--dur-transition) var(--ease-flow);
}
.hn-route > button {
  position: relative;
  z-index: 1;
  justify-self: center;
  width: 10px;
  height: 10px;
  padding: 0;
  border: 0;
  border-radius: var(--r-full);
  background: var(--surface);
  box-shadow: inset 0 0 0 1.5px var(--line-strong);
  cursor: pointer;
  transition: transform var(--dur-base) var(--ease-arrive), background var(--dur-base), box-shadow var(--dur-base);
}
.hn-route > button[data-passed="true"] { background: var(--ink); box-shadow: none; }
.hn-route > button[aria-current="step"] { transform: scale(1.4); background: var(--ink); box-shadow: 0 0 0 3px var(--surface), 0 0 0 4px var(--ink); }
.hn-route > button:focus-visible { outline: 3px solid var(--c-focus-ring); outline-offset: 2px; }

/* ---- Kbd ---------------------------------------------------------------- */
.hn-kbd {
  display: inline-grid;
  place-items: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border-radius: var(--r-sm);
  font: 400 12px/1 var(--font-mono);
  color: var(--ink-muted);
  background: var(--surface-soft);
  box-shadow: inset 0 0 0 1px var(--line);
}

/* assets/hn/components/layout/layout.css */
/* ==========================================================================
   Layout — CHROME containers (from DESIGN.md) + the JOURNEY model.

   Containers: nav · footer · section · card · card--dark · feature list ·
               faq row · cta strip · terminal/readout card · tile · specimen
   Depth rule (DESIGN.md): level 0 flat, level 1 = 1px hairline, level 2 =
   inverted dark fill. No drop shadows anywhere in chrome.

   THE CONTINUITY MODEL
   Everything is placed on one WORLD. Slides and tiles are STATIONS on it.
   A camera (.hn-journey__world transform) travels between stations along
   the pathways, so every change of slide is a camera move, never a cut.
   ========================================================================== */

/* ---- Primary nav (56px, flat) ------------------------------------------ */
.hn-nav {
  display: flex;
  align-items: center;
  gap: var(--sp-xl);
  height: var(--nav-h);
  padding: 0 var(--sp-xl);
  font: var(--type-body-sm-strong);
  color: var(--ink);
}
.hn-nav__brand { display: flex; align-items: center; gap: var(--sp-sm); color: var(--ink); text-decoration: none; }
.hn-nav__brand svg { width: 28px; height: 28px; }
.hn-nav__links { display: flex; gap: var(--sp-xl); }
.hn-nav__links a { color: var(--ink); text-decoration: none; }
.hn-nav__search { flex: 0 1 360px; margin: 0 auto; }
.hn-nav__actions { display: flex; align-items: center; gap: var(--sp-sm); margin-left: auto; }

/* ---- Footer ------------------------------------------------------------- */
.hn-footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--sp-sm) var(--sp-xl);
  padding: var(--sp-xxl) var(--sp-xl);
  border-top: 1px solid var(--line);
  font: var(--type-caption-sm);
  color: var(--ink-muted);
}
.hn-footer a { color: var(--ink-muted); text-decoration: none; }

/* ---- Section (88px air, single 720px column) --------------------------- */
.hn-section {
  width: min(100% - 2 * var(--sp-xl), var(--measure));
  margin: 0 auto;
  padding-block: var(--sp-section) 0;
}
.hn-section--wide { width: min(100% - 2 * var(--sp-xl), var(--content-wide)); }
.hn-section--center { text-align: center; }
.hn-section__title { margin: 0 0 var(--sp-xl); font: var(--type-display-lg); color: var(--ink); }
.hn-section__sub   { margin: 0 0 var(--sp-lg); font: var(--type-heading-lg); color: var(--ink); }

/* ---- Card (pricing-card) + the ONE inverted card ----------------------- */
.hn-card {
  display: grid;
  align-content: start;
  gap: var(--sp-lg);
  padding: var(--card-pad);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line);
}
.hn-card__title { margin: 0; font: var(--type-heading-md); }
.hn-card__desc  { margin: 0; font: var(--type-body-sm); color: var(--ink-muted); }
.hn-card__value { margin: 0; font: var(--type-display-lg); }
.hn-card__rule  { height: 1px; border: 0; margin: 0; background: var(--line); }
.hn-card__eyebrow { font: var(--type-body-sm-strong); color: var(--ink); }

/* Invert: dark on light lanes, light on dark lanes. One per view. */
.hn-card--invert {
  background: var(--invert-surface);
  color: var(--invert-ink);
  box-shadow: none;
}
/* One inversion scope for every box that flips (card, active tile). The
   light/dark swap lives in the --invert-* stage tokens, so this rule is a
   single attribute selector and its values register as theme tokens. */
[data-invert="true"] {
  --ink: var(--invert-ink);
  --ink-muted: var(--invert-ink-muted);
  --ink-charcoal: var(--invert-ink-charcoal);
  --ink-faint: var(--invert-ink-faint);
  --line: var(--invert-line);
  --surface-soft: var(--invert-surface-soft);
  --btn-bg: var(--invert-btn-bg);
  --btn-fg: var(--invert-btn-fg);
}

/* ---- Feature bullets ---------------------------------------------------- */
.hn-features { display: grid; gap: var(--sp-sm); margin: 0; padding: 0; list-style: none; }
.hn-features > li {
  display: grid;
  grid-template-columns: 16px 1fr;
  gap: var(--sp-sm);
  font: var(--type-body-sm);
  color: var(--ink-charcoal);
}
.hn-features > li::before { content: "✓"; color: var(--ink); font-weight: 500; }

/* ---- FAQ row (always expanded) ---------------------------------------- */
.hn-faq-row { padding: var(--sp-lg) 0; border-bottom: 1px solid var(--line); }
.hn-faq-row__q { margin: 0 0 var(--sp-xs); font: var(--type-heading-sm); color: var(--ink); }
.hn-faq-row__a { margin: 0; font: var(--type-body-md); color: var(--ink-muted); }

/* ---- CTA strip (dark) -------------------------------------------------- */
.hn-cta-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-lg);
  padding: var(--sp-xl) var(--sp-xxl);
  border-radius: var(--r-lg);
  background: var(--c-surface-dark);
  color: var(--c-on-dark);
  font: var(--type-heading-lg);
}

/* ---- Terminal / readout card ------------------------------------------ */
.hn-terminal {
  padding: var(--card-pad-sm);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line);
  font: var(--type-code-sm);
  color: var(--ink);
}
.hn-terminal__lights { display: flex; gap: var(--sp-xs); margin-bottom: var(--sp-lg); }
.hn-terminal__lights > i { width: 12px; height: 12px; border-radius: var(--r-full); background: var(--c-terminal-red); }
.hn-terminal__lights > i:nth-child(2) { background: var(--c-terminal-yellow); }
.hn-terminal__lights > i:nth-child(3) { background: var(--c-terminal-green); }
.hn-terminal__line { margin: 0; white-space: pre-wrap; }
.hn-terminal__line--comment { color: var(--ink-faint); }
.hn-terminal__line--cmd::before { content: "$ "; color: var(--ink-faint); }

/* ---- Specimen (label + mono dimension + descriptor) -------------------- */
.hn-specimen { display: grid; gap: var(--sp-xxs); align-content: start; }
.hn-specimen__name { display: flex; align-items: baseline; gap: var(--sp-md); font: var(--type-heading-sm); color: var(--ink); }
.hn-specimen__dim  { font: var(--type-code-sm); color: var(--ink); }
.hn-specimen__desc { font: var(--type-body-sm); color: var(--ink-muted); }

/* ---- Tiles (puzzle pieces) -------------------------------------------- */
.hn-tilegrid {
  display: grid;
  grid-template-columns: repeat(var(--tile-cols), minmax(var(--tile-min), 1fr));
  gap: var(--seam);
}
.hn-tile {
  position: relative;
  display: grid;
  align-content: space-between;
  gap: var(--sp-lg);
  min-height: 200px;
  padding: var(--tile-pad);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line);
  transition:
    background-color var(--dur-slow) var(--ease-arrive),
    color var(--dur-slow) var(--ease-arrive),
    box-shadow var(--dur-slow) var(--ease-arrive);
}
/* The active tile is the one "look here" — it inverts, exactly as the
   dark pricing card does. Only one tile is ever active. */
.hn-tile[data-active="true"] {
  background: var(--invert-surface);
  color: var(--invert-ink);
  box-shadow: none;
}
.hn-tile__index { font: var(--type-code-sm); color: var(--ink-faint); }
.hn-tile__title { margin: 0; font: var(--type-heading-md); color: var(--ink); }
.hn-tile__desc  { margin: 0; font: var(--type-body-sm); color: var(--ink-muted); }
.hn-tile:focus-visible { outline: 3px solid var(--c-focus-ring); outline-offset: 2px; }

/* Ports: WORLD element — a blue collar ring where a pathway crosses the edge.
   data-port = l|r|t|b + a|b|c   (a=25%, b=50%, c=75%) */
.hn-port {
  position: absolute;
  width: 10px;
  height: 26px;
  border-radius: 5px;
  background: linear-gradient(90deg, var(--hn-vein-700), var(--hn-vein-300) 40%, var(--hn-vein-100) 55%, var(--hn-vein-500) 80%, var(--hn-vein-700));
  box-shadow: 0 0 0 1px rgb(var(--hn-vein-rgb) / 0.3), 0 0 10px rgb(var(--hn-vein-rgb) / 0.45);
  z-index: 2;
  transform: translate(-50%, -50%);
}
.hn-port[data-port^="l"] { left: 0; }
.hn-port[data-port^="r"] { left: 100%; }
.hn-port[data-port^="t"] { top: 0;    width: 26px; height: 10px; }
.hn-port[data-port^="b"] { top: 100%; width: 26px; height: 10px; }
.hn-port[data-port^="l"][data-port$="a"], .hn-port[data-port^="r"][data-port$="a"] { top: var(--port-a); }
.hn-port[data-port^="l"][data-port$="b"], .hn-port[data-port^="r"][data-port$="b"] { top: var(--port-b); }
.hn-port[data-port^="l"][data-port$="c"], .hn-port[data-port^="r"][data-port$="c"] { top: var(--port-c); }
.hn-port[data-port^="t"][data-port$="a"], .hn-port[data-port^="b"][data-port$="a"] { left: var(--port-a); }
.hn-port[data-port^="t"][data-port$="b"], .hn-port[data-port^="b"][data-port$="b"] { left: var(--port-b); }
.hn-port[data-port^="t"][data-port$="c"], .hn-port[data-port^="b"][data-port$="c"] { left: var(--port-c); }

/* ---- Journey: viewport → world → stations ----------------------------- */
.hn-journey {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.hn-journey__world {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 0 0;
  will-change: transform;
  transition: transform var(--dur-transition) var(--ease-flow);   /* the seam */
}
.hn-journey__world > svg { position: absolute; left: 0; top: 0; max-width: none; overflow: visible; }

.hn-station {
  position: absolute;
  display: grid;
  align-content: center;
  pointer-events: none;
}
.hn-station[data-active="true"] { pointer-events: auto; }

/* Arrival choreography: .hn-reveal children land after the camera has
   covered ~55% of the seam, then stagger by --i. They leave before it moves. */
.hn-reveal {
  opacity: 0;
  transform: translateY(12px);
  filter: blur(6px);
  transition:
    opacity var(--dur-slow) var(--ease-depart),
    transform var(--dur-slow) var(--ease-depart),
    filter var(--dur-slow) var(--ease-depart);
}
.hn-station[data-active="true"] .hn-reveal {
  opacity: 1;
  transform: none;
  filter: none;
  transition-timing-function: var(--ease-arrive);
  transition-duration: calc(var(--dur-slow) * 1.4);
  transition-delay: calc(var(--dur-transition) * 0.55 + var(--i, 0) * var(--stagger));
}

/* ---- Caption (text on a station, authored at 1280×720 px) -------------- */
.hn-caption { display: grid; gap: var(--sp-lg); max-width: 440px; }
.hn-caption--center { justify-items: center; text-align: center; margin-inline: auto; }
.hn-caption__index { display: flex; align-items: center; gap: var(--sp-md); font: var(--type-code-sm); color: var(--ink-faint); }
.hn-caption__title { margin: 0; font: var(--type-display-xl); color: var(--ink); text-wrap: balance; }
.hn-caption__body  { margin: 0; font: var(--type-body-md); color: var(--ink-muted); text-wrap: pretty; }
.hn-caption__tags  { display: flex; flex-wrap: wrap; gap: var(--sp-sm); }

/* ---- Chrome bar over a journey ---------------------------------------- */
.hn-chrome {
  position: absolute;
  left: 0; right: 0;
  z-index: var(--z-chrome);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-xl);
  padding: 0 var(--sp-xl);
  min-height: var(--nav-h);
  pointer-events: none;
}
.hn-chrome > * { pointer-events: auto; }
.hn-chrome--top    { top: 0; }
.hn-chrome--bottom { bottom: var(--sp-md); }
.hn-chrome__meta   { font: var(--type-code-sm); color: var(--ink-muted); }

@media (max-width: 768px) {
  .hn-nav__links, .hn-nav__search { display: none; }
  .hn-tilegrid { --cols: 1; /* @kind other */ }
}

/* assets/hn/components/avatar/avatar.css */
/* ==========================================================================
   Avatar — the anonymous biological figure (faceless, charcoal, lit from
   within by orange nerve-lines). Same human, many states.
   The SVG ships in Avatar.jsx and assets/illustrations/avatar-bust.svg.
   ========================================================================== */

.hn-avatar {
  position: relative;
  display: inline-block;
  width: 240px;
  aspect-ratio: 1;
}
.hn-avatar svg { width: 100%; height: 100%; overflow: visible; display: block; }

.hn-avatar__lines path {
  fill: none;
  stroke: var(--line-c);
  stroke-width: 1.6;
  stroke-linecap: round;
  opacity: calc(0.35 + 0.65 * var(--energy));
}
.hn-avatar__lines--glow path {
  stroke-width: 5;
  opacity: calc(0.1 + 0.5 * var(--r-glow));
}
.hn-avatar__lines--cool path { stroke: var(--line-cool); opacity: calc(0.15 + 0.35 * (1 - var(--energy))); stroke-width: 1; }

/* a signal climbing the spine → neck → head */
.hn-avatar__signal path {
  fill: none;
  stroke: #FFFFFF;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-dasharray: 10 260;
  opacity: calc(0.2 + 0.8 * var(--density));
  animation: hn-avatar-signal calc(var(--dur-steady) * 1.6 / var(--r-speed)) linear infinite;
}
@keyframes hn-avatar-signal { from { stroke-dashoffset: 270; } to { stroke-dashoffset: 0; } }

.hn-avatar__overlay { fill: none; stroke: var(--line-c); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.hn-avatar__overlay--fill  { fill: var(--line-c); stroke: none; }
.hn-avatar__overlay--muted { stroke: var(--ink-muted); }
.hn-avatar__spin  { transform-box: view-box; transform-origin: 120px 76px; animation: hn-spin 9s linear infinite; }
.hn-avatar__throb { transform-box: fill-box; transform-origin: center; animation: hn-breathe var(--beat-active) var(--ease-pulse) infinite; }

.hn-avatar__label {
  display: block;
  margin-top: var(--sp-sm);
  text-align: center;
  font: var(--type-code-sm);
  letter-spacing: 0;
  color: var(--ink);
}

/* Portrait card (as in the state-portrait grid) */
.hn-portrait {
  display: grid;
  justify-items: center;
  padding: var(--sp-md) var(--sp-md) var(--sp-lg);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line);
}
.hn-portrait .hn-avatar { width: 100%; }

@media (prefers-reduced-motion: reduce) {
  .hn-avatar__signal path, .hn-avatar__spin, .hn-avatar__throb { animation: none; }
}

/* assets/hn/components/foundation/foundation.css */
/* ==========================================================================
   Foundation — base element styles and type utilities (DESIGN.md roles).
   ========================================================================== */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font: var(--type-body-md);
  color: var(--ink);
  background: var(--c-canvas);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img, svg, video { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
:focus-visible { outline: 3px solid var(--c-focus-ring); outline-offset: 1px; }
code { font: var(--type-code-sm); padding: 2px 6px; border-radius: var(--r-sm); background: var(--surface-soft); }

/* ---- Type utilities — names match DESIGN.md tokens -------------------- */
.hn-display-xl     { font: var(--type-display-xl); margin: 0; text-wrap: balance; }
.hn-display-lg     { font: var(--type-display-lg); margin: 0; }
.hn-heading-lg     { font: var(--type-heading-lg); margin: 0; }
.hn-heading-md     { font: var(--type-heading-md); margin: 0; }
.hn-heading-sm     { font: var(--type-heading-sm); margin: 0; }
.hn-body-md        { font: var(--type-body-md); margin: 0; color: var(--ink-muted); }
.hn-body-strong    { font: var(--type-body-strong); margin: 0; }
.hn-body-sm        { font: var(--type-body-sm); margin: 0; color: var(--ink-muted); }
.hn-body-sm-strong { font: var(--type-body-sm-strong); margin: 0; }
.hn-caption-sm     { font: var(--type-caption-sm); margin: 0; color: var(--ink-muted); }
.hn-code-md        { font: var(--type-code-md); }
.hn-code-sm        { font: var(--type-code-sm); }

.hn-ink   { color: var(--ink); }
.hn-muted { color: var(--ink-muted); }
.hn-faint { color: var(--ink-faint); }

/* ---- WORLD: data-object numerals ---------------------------------------
   Numbers placed ON a stage are illustration: extruded, lit geometry with a
   cast shadow — never flat overlay text. Chrome numbers use display-lg.  */
.hn-numeral {
  font: var(--type-numeral);
  letter-spacing: -0.04em;
  margin: 0;
  font-variant-numeric: tabular-nums;
  color: var(--hn-artery-400);
}
.hn-numeral--cool {
  color: var(--hn-vein-400);
}

.hn-visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* shell.css */
/* ==========================================================================
   PEPT — site shell

   Everything every page shares: the stage backdrop, the spine, the announcement
   bar, the nav, the footer, and the content defaults that make third-party
   markup (WooCommerce, WordPress) legible on the dark lane.

   Loaded site-wide, after the design system. Page-specific styling lives in
   front-page.css and whatever each later surface adds.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Base

   foundation.css sets body { background: var(--c-canvas) } = white, and
   --ink is only defined inside a [data-stage] scope, so it resolves to nothing
   on <body>. Pin the dark-clean base colour here for overscroll and for the
   moment before the fixed stage paints.
   -------------------------------------------------------------------------- */
body { background: #040E22; } /* dark-clean --stage-top */

.pf {
	position: relative;
	z-index: 1;
	min-height: 100vh;
	font: var(--type-body-md);
	color: var(--ink);
	/* One gutter for the whole site. Must clear .pf__spine (24px), so the
	   design's own 32px margin is the floor, not the 12px scale step. */
	--pf-gutter: var(--sp-xxl);
	/* the two intervals the page's sections alternate between */
	--sp-beat: 72px;
	--sp-act: 152px;

	/* Tints the shell repeats: a lift above the stage, and a translucent bar
	   sitting on it. Named so they are tunable in one place. */
	--pf-tint: rgb(255 255 255 / .04);
	--pf-tint-strong: rgb(255 255 255 / .06);
	--pf-bar-bg: rgb(4 14 34 / .72);
}

/* Blocksy ships a bare `h1 { color: var(--theme-heading-color…) }` in
   main.min.css. An element selector always beats an inherited colour, so every
   heading must state its own ink or it lands near-black on the dark stage.
   Kept before the component rules below so specific overrides still win. */
.pf :is(h1, h2, h3, h4, h5, h6) { color: var(--ink); }

/* The system's --ink-faint is 45% white, which measures 3.33:1 against the
   stage gradient at its horizon stop and fails WCAG AA for the 12-14px text it
   carries (eyebrows, meta lines, footer legal). 62% is the lowest alpha that
   clears 4.5:1 at every point along the gradient. Scoped to the site stage so
   the delivered token is unchanged for anything else. */
.pf[data-stage] { --ink-faint: rgba(255, 255, 255, .62); }

/* --------------------------------------------------------------------------
   The spine — one capillary down the left edge of every page. Decorative.
   -------------------------------------------------------------------------- */
.pf__spine {
	position: absolute;
	top: 0; bottom: 0; left: 0;
	width: 24px;
	pointer-events: none;
	/* A background element, as it was: it belongs with the pathways, and it
	   stays behind anything opaque the page is built from. The closing CTA is
	   a full-width white band and covers it for that stretch, which is right -
	   the line you see there is the blue one below. */
	z-index: var(--z-pathways);
}

/* The same capillary in vein blue, over the page instead of under it, showing
   only across the inverted band. Same path, same size, same place: the only
   difference is which rung of the scale it sits on and which stretch of the
   page it is allowed to draw. */
.pf__spine--invert { z-index: var(--z-content); }
.pf__spine svg { width: 24px; height: 100%; display: block; }
.pf__spine .hn-flow { --w: 6px; --w-artery: 6px; --w-capillary: 6px; --cell-gap: 64px; --cell-lg: 3px; --cell-sm: 1.5px; }

/* --------------------------------------------------------------------------
   Announcement bar
   -------------------------------------------------------------------------- */
.pf__bar {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--sp-sm);
	padding: var(--sp-xs) var(--sp-xxl) var(--sp-xs) var(--sp-md);
	background: var(--pf-tint-strong);
	border-bottom: 1px solid var(--line);
	font: var(--type-body-sm);
	position: relative;
}
.pf__bar a { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }

/* Below 600 the message, the link and the dismiss button did not fit on one
   line. Measured at 390: 405px of content in a 346px box, so the message broke
   after "12 week" and the link broke after "See kits", leaving the arrow on a
   line of its own - and the content ran 16px under the button, because the
   padding reserved 32px for a button that occupies 48.

   Three fixes, none of them to the copy. The type steps down to the caption
   size, which brings the pair to 304px and lets them sit on one line at 390.
   The link is nowrap, so wherever the row does run short the message takes the
   wrap and the link stays whole. And the padding reserves the button's real
   width. At 320 the message wraps to two lines, which is honest, and the row is
   never overlapped. */
@media (max-width: 599px) {
	.pf__bar {
		justify-content: flex-start;
		/* 32 on the left, because the spine is 24px wide at every viewport and
		   paints above the bar - the message was starting 12px inside it. Only
		   visible once the row stopped centring, which is why it survived.
		   52 on the right reserves the button's real 44 plus its 4px offset. */
		padding: var(--sp-sm) 52px var(--sp-sm) var(--sp-xxl);
		font: var(--type-caption-sm);
	}
	.pf__bar > span { min-width: 0; }
	.pf__bar a { white-space: nowrap; flex: none; }
}
.pf__bar button {
	position: absolute;
	right: var(--sp-xs);
	top: 50%;
	transform: translateY(-50%);
	/* 24px was the WCAG 2.5.8 floor exactly. 44px is the target; the button is
	   absolutely positioned so a larger box does not grow the bar. */
	width: 44px;
	height: 44px;
	border: 0;
	border-radius: var(--r-full);
	background: none;
	color: var(--ink);
	font: 400 18px/1 var(--font-sans);
	cursor: pointer;
}
.pf__bar[hidden] { display: none; }

/* --------------------------------------------------------------------------
   Header
   -------------------------------------------------------------------------- */
.pf__header {
	position: sticky;
	top: 0;
	z-index: var(--z-chrome);
	backdrop-filter: var(--frost);
	-webkit-backdrop-filter: var(--frost);
	background: var(--pf-bar-bg);
	border-bottom: 1px solid var(--line);
}
.pf__nav {
	display: flex;
	align-items: center;
	gap: var(--sp-lg);
	height: var(--nav-h);
	padding: 0 var(--sp-md);
	max-width: 1200px;
	margin: 0 auto;
	font: var(--type-body-sm-strong);
}
.pf__brand {
	display: flex;
	align-items: center;
	gap: var(--sp-xs);
	min-height: 44px;          /* hit area; the mark stays 28px visually */
	padding-right: var(--sp-xs);
	color: var(--ink);
	text-decoration: none;
}
.pf__brand img { height: 28px; width: 28px; display: block; border-radius: 6px; }
/* The mark is a square tile with rounded corners cropped out of a light
   backing, so the radius is doing real work: it clips the corner residue. */
.pf__brand span { font: 500 17px/1 var(--font-display); color: var(--ink); letter-spacing: .01em; }
.pf__navlinks { display: none; gap: var(--sp-lg); margin-left: var(--sp-sm); }
/* 44px, not the 20px line box the label happens to occupy. The row is already
   57px tall so the target grows inside it without moving anything: the negative
   block margin keeps the nav's own height where it was. Foundation size 3.5 on
   the spacing scale clears the underline-free label on both sides. */
.pf__navlinks a {
	display: inline-flex;
	align-items: center;
	height: 44px;
	min-width: 44px;
	justify-content: center;
	margin-block: -12px;
	padding: 0 var(--sp-md);
	color: var(--ink);
	text-decoration: none;
	border-radius: var(--r-sm);
}
/* The system fixes an icon button at 36. The menu is the only route to three
   pages on a phone, so in the chrome it takes the 44px floor. */
.pf__navactions { display: flex; align-items: center; gap: var(--sp-sm); margin-left: auto; }
.pf__navactions .hn-btn--icon { width: 44px; height: 44px; }
/* The cart button is a fixed 36px circle built for one icon. Sharing it with a
   count put 44px of content inside it - icon, 8px gap, 20px badge - and since
   the row is centred that spilled 4px off each side, so both the trolley and
   the number hung outside the circle and it read as a mistake rather than as a
   button.

   The count is a badge now, not a sibling: out of the flow, on the corner, and
   out of the icon's way. It was sitting at the vertical centre of the button
   before, which is what made it look like overflow rather than a marker. */
.pf__cartbtn { position: relative; }
.pf__cartcount {
	position: absolute;
	top: -5px;
	right: -5px;
	min-width: 18px;
	height: 18px;
	padding: 0 5px;
	border-radius: var(--r-full);
	background: var(--btn-bg);
	color: var(--btn-fg);
	/* The sans with tabular figures rather than the mono: at 11px the mono zero
	   carries a dot in its counter and read as a glyph rather than a number.
	   Tabular numerals give the alignment the mono was there for without it. */
	font: 500 11px/18px var(--font-sans);
	font-variant-numeric: tabular-nums;
	text-align: center;
	pointer-events: none;
}
.pf__menu-panel {
	display: grid;
	gap: var(--sp-xxs);
	padding: var(--sp-xs) var(--sp-md) var(--sp-md);
	max-width: 1200px;
	margin: 0 auto;
	border-top: 1px solid var(--line);
	font: var(--type-heading-sm);
}
.pf__menu-panel a {
	padding: var(--sp-sm) 0;
	color: var(--ink);
	text-decoration: none;
	border-bottom: 1px solid var(--line);
}
.pf__menu-panel a:last-child { border-bottom: 0; }
.pf__menu-panel[hidden] { display: none; }

/* --------------------------------------------------------------------------
   Page primitives — shared by any surface that needs a reading column.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   The rhythm

   Eight sections were separated by the same 88px, repeated until all of them
   carried equal weight. Six of the seven boundaries measured 0px, so the only
   thing distinguishing one section from the next was that same value, applied
   identically eight times. Nothing read as more important than anything else,
   which is the failure the layout guidance names directly.

   The fix is not more space, it is space that means something. Every section
   now declares its relationship to the one above it:

     --beat   it continues that thought
     --act    it opens a new movement

   Three movements, four beats, and the page reads as four arrivals rather than
   eight flats. The values are the spacing scale's own: the beat is a step under
   the section default, the act is not quite twice it, and the ratio between
   them is what the eye reads as cadence.

   Takes precedence over .pf__section's own padding-top by being declared after
   it at the same specificity. */
.pf__section { width: min(100% - 2 * var(--pf-gutter), var(--measure)); margin: 0 auto; padding-top: var(--sp-section); }
.pf__section--beat { padding-top: var(--sp-beat); }
.pf__section--act { padding-top: var(--sp-act); }
.pf__section--wide { width: min(100% - 2 * var(--pf-gutter), var(--content-wide)); }
.pf__h2 { margin: 0; font: var(--type-display-lg); color: var(--ink); text-wrap: balance; }

/* A full-bleed band.
   The band runs to both edges of the viewport; the content inside it stays in
   the reading column, because padding-inline of half the leftover width is
   exactly what centres a measure inside a wider box. So the closing statement
   gets the whole width as surface while its words still line up with every
   other section - the band changes, the column does not. */
.pf__section--bleed { width: auto; max-width: none; margin-inline: 0; }

/* --------------------------------------------------------------------------
   Third-party content (WooCommerce, WordPress) on the dark lane.

   These templates were written for a light theme and set dark text. Rather
   than restyle every WooCommerce component here, this gives the content column
   a gutter and pushes the inherited ink onto the design-system scale so the
   defaults are at least legible. Full per-component work comes with each
   surface's own pass.
   -------------------------------------------------------------------------- */
body:not(.home) .pf__main { padding: var(--sp-xxl) var(--pf-gutter) var(--sp-section); }

/* But not when the page brings its own column. .pf__section already subtracts
   --pf-gutter from its own width, so a page that uses one was guttered twice:
   the library had 262px of content at 390 where the front page had 326, the
   section's 32px sitting inside the main's. Shop, cart, account and checkout
   have no .pf__section, so they keep the padding and are unaffected. */
body:not(.home) .pf__main:has(> .pf__section) { padding-inline: 0; }
body:not(.home) .pf__main > * { max-width: var(--content-wide); margin-left: auto; margin-right: auto; }

/* The glow behind the steps panel is inset negative on purpose - it is meant to
   spill past the panel into the gutter. Past the *page* it is not: between
   roughly 560 and 800px the column is wide enough that the spill reached the
   viewport edge and the document scrolled sideways, which was live for as long
   as that panel has existed. Clip at the page edge rather than the column, so
   the gutter keeps its bleed and the document cannot be pushed wider.

   `clip` and not `hidden`: hidden makes the element a scroll container, which
   would break the sticky header and anything else relying on the scrollport.
   clip does not. */
.pf__main { color: var(--ink); overflow-x: clip; }

/* The system defines `calm` and `dormant` dots and leaves the rest on the
   artery default. The reviews use `active`, which reads orange because that
   default happens to be orange - true today, and silently wrong the moment the
   default moves. Named here so it is a decision rather than a coincidence. */
.hn-state-dot[data-state="active"] { --dot-c: var(--hn-artery-400); }
.pf__main p,
.pf__main li,
.pf__main dd { color: var(--ink-muted); }
/* Scoped so it cannot clobber a button that happens to be an <a>. The plain
   `.pf__main a` (0,1,1) outranked `.hn-btn--primary` (0,1,0) and painted the
   primary CTA's label the same white as its own background: contrast 1.0:1. */
.pf__main a:not(.hn-btn):not(.button):not(.wp-block-button__link) { color: var(--ink); }
.pf__main a.hn-btn { color: var(--btn-fg); }
.pf__main a.hn-btn--secondary,
.pf__main a.hn-btn--on-dark { color: var(--ink); }
.pf__main strong { color: var(--ink); }
.pf__main hr { border: 0; border-top: 1px solid var(--line); }
.pf__main table { color: var(--ink); border-color: var(--line); }
.pf__main th,
.pf__main td { border-color: var(--line); }
.pf__main label { color: var(--ink-charcoal); }

.pf__main input:not([type="checkbox"]):not([type="radio"]),
.pf__main select,
.pf__main textarea {
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--r-md);
	color: var(--ink);
	padding: 10px 12px;
	font: var(--type-body-sm);
}
.pf__main input::placeholder,
.pf__main textarea::placeholder { color: var(--ink-faint); }

/* WooCommerce and Blocksy both style buttons through long descendant chains
   (.woocommerce #respond input#submit, .woocommerce a.button, ...), which
   outrank a plain .pf__main button. Without matching that weight the theme's
   muted default wins and the primary action reads as disabled. */
.pf__main button,
.pf__main input[type="submit"],
.pf__main input.button,
.pf__main a.button,
.pf__main button.button,
.pf__main .wp-block-button__link,
.pf__main .woocommerce button.button,
.pf__main .woocommerce a.button,
.pf__main .woocommerce input.button,
.pf__main .woocommerce #respond input#submit,
.pf__main .wc-block-components-button {
	background: var(--btn-bg);
	color: var(--btn-fg);
	border: 0;
	border-radius: var(--r-full);
	padding: 11px 20px;
	font: var(--type-button-md);
	line-height: 1.2;
	cursor: pointer;
	text-decoration: none;
	display: inline-block;
}

/* Blocksy styles form buttons through `form:is(.login, .register, .edit-account,
   .lost_reset_password) button` at (0,1,2). A plain `.pf__main button` is (0,1,1)
   and loses, which made the primary action on the account forms read as disabled.
   This group is (0,2,2) and wins outright. */
.pf__main form :is(button, input[type="submit"], .button):not(.hn-btn--secondary),
.pf__main .woocommerce form :is(button, input[type="submit"], .button):not(.hn-btn--secondary) {
	background: var(--btn-bg);
	color: var(--btn-fg);
	box-shadow: none;
}

/* Secondary and quiet actions keep the surface treatment. */
.pf__main .hn-btn--secondary,
.pf__main button.secondary,
.pf__main .woocommerce button.button.alt,
.pf__main .woocommerce a.button.alt {
	background: var(--surface);
	color: var(--ink);
	box-shadow: inset 0 0 0 1px var(--line-strong);
}

/* Archive titles (shop, product categories) come from WooCommerce now that the
   Blocksy page hero is gone. They were never styled for this system. */
.pf__main .woocommerce-products-header__title,
.pf__main .page-title {
	font: var(--type-display-lg);
	color: var(--ink);
	margin: 0 0 var(--sp-lg);
}
.pf__main .woocommerce-products-header { margin-bottom: var(--sp-lg); }
.pf__main .woocommerce-result-count,
.pf__main .woocommerce-ordering { color: var(--ink-muted); font: var(--type-body-sm); }

/* WooCommerce notices and totals lean on tinted panels that vanish on a dark
   lane; give them a surface instead of a tint. */
.pf__main .woocommerce-message,
.pf__main .woocommerce-info,
.pf__main .woocommerce-error,
.pf__main .woocommerce-notice {
	background: var(--surface);
	color: var(--ink);
	border-radius: var(--r-lg);
	box-shadow: inset 0 0 0 1px var(--line);
	padding: var(--sp-md) var(--sp-lg);
	list-style: none;
}
.pf__main .woocommerce-message::before,
.pf__main .woocommerce-info::before,
.pf__main .woocommerce-error::before { display: none; }
.pf__main .woocommerce-error { box-shadow: inset 0 0 0 1px var(--hn-state-danger); }

/* --------------------------------------------------------------------------
   Shared page components — the kit card and grid, the reading primitives and
   the closing CTA. These are used by the front page AND the library/detail
   pages, so they live in the shell rather than in front-page.css; a page that
   is not the front page does not load that file.
   -------------------------------------------------------------------------- */
.pf__h1 {
	margin: 0;
	font: var(--type-display-xl);
	text-wrap: balance;
	max-width: 16ch;
}
.pf__lede {
	margin: 0;
	font: var(--type-body-md);
	color: var(--ink-muted);
	max-width: 40ch;
	text-wrap: pretty;
}
/* Was 36 at desktop and 48 below 760. One floor, everywhere: the system's 36 is
   a mouse target and the page's primary action should not shrink on the device
   with the most room. */
.pf__cta { display: flex; flex-wrap: wrap; gap: var(--sp-sm); }
.pf__cta .hn-btn { min-height: 44px; }
.pf__meta { margin: 0; font: var(--type-code-sm); color: var(--ink-faint); }
.pf__kit {
	text-decoration: none;
	color: var(--ink);
	transition: background var(--dur-base) var(--ease-flow);
}
.pf__kit:hover .pf__view { color: var(--ink); }
.pf__kit:focus-visible { outline: 3px solid var(--c-focus-ring); outline-offset: 2px; }
.pf__kit-top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-xs); }
.pf__kit-top .hn-chip { padding: 4px 10px; }
.pf__kit-media {
	/* width, not auto. The card is a grid and place-items:center stops its items
	   stretching, so the media box was sizing to whatever the arc inside it
	   happened to measure - a three-vessel arc came out 32px wider than the
	   space it had and hung over the padding. It fills the column now and the
	   arc centres inside it, which is what place-items was there to do. */
	width: 100%;
	/* the arc sizes against this box rather than against the viewport: the
	   featured pair is twice the width of a field card at the same screen, so
	   a viewport unit would give them the same scale and one of them would be
	   wrong. */
	container-type: inline-size;
	aspect-ratio: 3 / 2;
	border-radius: var(--r-md);
	overflow: hidden;
	display: grid;
	place-items: center;
	background: radial-gradient(62% 62% at 50% 46%, var(--vis-glow, transparent), transparent 72%), var(--surface-soft);
	box-shadow: inset 0 0 0 1px var(--line);
}
/* Blue reads as rest, orange as activation, so the wash behind the vessels
   follows the kit rather than being warm on every card. */
.pf__kit-media { --vis-glow: rgb(var(--hn-artery-rgb) / .16); }
.pf__kit-media[data-kit="sleep"],
.pf__kit-media[data-kit="recovery"],
.pf__kit-media[data-kit="heal"] { --vis-glow: rgb(var(--hn-vein-rgb) / .17); }

/* --------------------------------------------------------------------------
   Kit arcs

   A left-to-right sequence of vessel states. Everything is the design
   system's own .hn-vessel; only its axes are varied, so a new arc is data
   rather than new visual language.

     data-energy      brightness, speed, colour bias, irregularity
     --w              tube diameter  (line width equals volume)
     --cell-gap       pulse spacing  (pulse density equals activity)
     --frag-gap       separation between fragments (broken against whole)
     --offset         artery/vein divergence (out of sync against joined)
   -------------------------------------------------------------------------- */
.pf__vis {
	/* The design system scales its whole anatomy from --hn-d: tube diameter,
	   cell size, cell spacing and lane gap are all multiples of it. Dropping it
	   here shrinks the entire motif in one move, which is the system's own
	   mechanism rather than a set of overrides.

	   40px is a full-width band; an arc step is a third of a card and needs a
	   diameter where two or three cells still read across it. */
	/* Fluid against the card, not fixed. At a flat 22px the tall compositions -
	   GROW at full girth, the FOCUS ball - stood 85 and 89px high, which fits
	   the 111px frame a field card gets on a desktop and not the 73px one it
	   gets on a phone, so the shapes were clipped at the bottom. Everything
	   here is a multiple of this number, so dropping it scales the whole motif
	   at once and nothing has to be corrected piece by piece. */
	--hn-d: clamp(13px, 14.5cqw, 22px);
	width: 100%;
	display: flex;
	align-items: center;
	gap: clamp(9px, 4%, 26px);
	padding: 0 7%;
	/* The whole anatomy scale, re-declared against the --hn-d above.
	   Every value in tokens/anatomy.css is written as calc(var(--hn-d) * n) at
	   :root, where --hn-d is 40px, so they resolve there and inherit downward
	   as finished lengths. Declaring a smaller --hn-d here changes the tube and
	   leaves the wall, the cells and the spacing at root size - a thin vessel
	   with enormous cells, which reads as a broken component rather than as a
	   cascade rule.

	   It lives on the arc rather than on each composition so that nothing
	   inside an arc can forget it. Four compositions were written against this
	   scale and every one of them hit the same trap. */
	--w-artery:      calc(var(--hn-d) * 1);
	--w-vein:        calc(var(--hn-d) * .64);
	--w-capillary:   calc(var(--hn-d) * .36);
	--w-nerve:       calc(var(--hn-d) * .36);
	--w-nerve-fibre: calc(var(--hn-d) * .06);
	--cell-lg:       calc(var(--hn-d) * .30);
	--cell-sm:       calc(var(--hn-d) * .16);
	--cell-gap:      calc(var(--hn-d) * 2.4);
	--signal-len:    calc(var(--hn-d) * .9);
	--signal-gap:    calc(var(--hn-d) * 4.5);
	--bend-radius:   calc(var(--hn-d) * 1);
	--collar-w:      calc(var(--hn-d) * .28);
	--node-size:     calc(var(--hn-d) * 1.2);
	--lane-gap:      calc(var(--hn-d) * .35);
}
.pf__vis-step {
	flex: 1 1 0;
	min-width: 0;
	display: grid;
	gap: calc(var(--hn-d) * .5);

	/* Custom properties resolve where they are declared, then inherit as a
	   finished value. --w-artery and --cell-gap are declared at :root as
	   calc(var(--hn-d) * n), so they computed there against the 40px default
	   and arrive here already resolved — lowering --hn-d alone changes the
	   tube and nothing else. Re-declaring them on the scope that owns the new
	   --hn-d is the same pattern the system uses in states.css.
	   It sits on the step rather than the arc so a step can also override
	   --hn-d to change its own thickness (strength, burn). */
	--w-artery:      calc(var(--hn-d) * 1);
	--w-vein:        calc(var(--hn-d) * .64);
	--w-capillary:   calc(var(--hn-d) * .36);
	--w-nerve:       calc(var(--hn-d) * .36);
	--w-nerve-fibre: calc(var(--hn-d) * .06);
	--cell-lg:       calc(var(--hn-d) * .30);
	--cell-sm:       calc(var(--hn-d) * .16);
	--cell-gap:      calc(var(--hn-d) * 2.4);
	--signal-len:    calc(var(--hn-d) * .9);
	--signal-gap:    calc(var(--hn-d) * 4.5);
	--bend-radius:   calc(var(--hn-d) * 1);
	--collar-w:      calc(var(--hn-d) * .28);
	--node-size:     calc(var(--hn-d) * 1.2);
	--lane-gap:      calc(var(--hn-d) * .35);
}
.pf__vis-row {
	display: flex;
	align-items: center;
	gap: var(--frag-gap, 0%);
	min-width: 0;
}
/* the component's own floor of two tube widths would stop a fragment reading
   as a fragment */
.pf__vis-row .hn-vessel { min-width: 0; }

/* Out of sync: artery and vein drift apart, closing to the normal gap at zero. */
.pf__vis-row--artery { transform: translateY(calc(var(--offset, 0) * var(--hn-d) * -1.05)); }
.pf__vis-row--vein   { transform: translateY(calc(var(--offset, 0) * var(--hn-d) *  1.05)); }

/* Calm and motionless: the jitter is an animation, so a variable cannot stop it. */
.pf__vis-step--still .hn-vessel { animation: none; }
.pf__vis-step--still .hn-vessel__cells { animation: none; opacity: .12; }
.pf__vis-step--still .hn-vessel { filter: saturate(.55) brightness(.8); opacity: .7; }

/* --------------------------------------------------------------------------
   Breathing — a vessel that changes size

   The design system already registers animatable custom properties (@property
   in motion.css and anatomy.css), so a registered number interpolates
   smoothly rather than snapping.

   The scale rides the independent `scale` property, not `transform`: the
   vessel's jitter animation already owns `transform`, and a second animation
   writing to the same property would replace it rather than compose. `scale`
   and `transform` are separate properties and apply together, so the vessel
   keeps its jitter while it breathes.

   `scale: 1 <n>` leaves the length alone and moves the thickness, which is
   the dimension a vessel actually has to give.
   -------------------------------------------------------------------------- */
@property --pulse-scale { syntax: "<number>"; inherits: true; initial-value: 1; }

@keyframes pept-breathe {
	0%, 100% { --pulse-scale: .8; }
	50%      { --pulse-scale: 1; }
}

/* Two animations, listed together, and at raised specificity.
   This rule sits earlier in the file than the wave rule and ties with it on
   specificity, so the wave's `animation` SHORTHAND replaced this one outright
   and the strength and pulse arcs stopped breathing the moment the wave was
   added. A shorthand replaces the whole list, so the fix is to declare both
   animations in one place rather than to rely on file order.

   The delays are listed separately: the wave stays staggered across the steps
   so it travels, while the breath is in unison, which is what makes the three
   strength steps read as one muscle working rather than three. */
.pf__vis-step.pf__vis-step--breathe {
	animation:
		pept-wave    var(--arc-dur, 5.4s)     var(--ease-flow) infinite,
		pept-breathe var(--breathe-dur, 2.4s) var(--ease-flow) infinite;
	animation-delay:
		calc(var(--i, 0) * var(--arc-stagger, .5s)),
		0s;
}
.pf__vis-step--breathe .hn-vessel { scale: 1 var(--pulse-scale); }

/* --------------------------------------------------------------------------
   Choreography

   The steps already told the story left to right as seven still states. These
   make it happen: a wave of activation travels the arc, so the eye is carried
   through the progression instead of being asked to compare seven panels.

   The wave rides `filter` on the row, not on the vessel. Every vessel already
   carries an energy-driven filter of its own, and animating that would replace
   it and flatten the per-step states the arc is built from. A filter on the
   row composes with the one underneath, so the states survive and the wave
   passes over the top of them.

   It is one shared wave with per-arc timing rather than seven bespoke
   animations: what differs between the kits is how fast their story moves,
   not what moving looks like.
   -------------------------------------------------------------------------- */
/* A registered wave value rather than a direct opacity, so one animated
   property can drive two effects at once: the step brightens and swells as
   the wave passes. Two composited properties off one animation, which is what
   makes an amplified wave affordable where a filter was not.

   Opacity rather than filter: a filter animation re-rasterises the filtered
   subtree every frame. Measured, the same wave as a filter ran at 12fps
   against 21fps as opacity.

   The vessel's own opacity is energy-driven, so this multiplies with the
   per-step state instead of replacing it — the arc keeps its progression and
   the wave passes over the top. */
@property --wave { syntax: "<number>"; inherits: false; initial-value: 0; }

@keyframes pept-wave {
	0%, 14%   { --wave: 0; }
	40%, 60%  { --wave: 1; }
	86%, 100% { --wave: 0; }
}

.pf__vis-step {
	animation: pept-wave var(--arc-dur, 5.4s) var(--ease-flow) infinite;
	animation-delay: calc(var(--i, 0) * var(--arc-stagger, .5s));
	/* a deep trough and a swell, so the wave reads across a small card */
	opacity: calc(.42 + var(--wave) * .58);
	scale: calc(1 + var(--wave) * .07);
	transform-origin: center;
}
/* Off-screen cards hold at the start of the wave, so scrolling a card into
   view plays its story from the beginning rather than catching it mid-loop. */
/* Everything inside an off-screen card stops.
   This was written three times as a list of the animated classes, and went
   stale three times: first the merge on the arc, then the focus ball, then the
   grow block, each a new element animating off screen because nobody had
   remembered to add it. Enumerating was the mistake, not the omissions. A card
   that is not on screen has nothing worth animating anywhere inside it, so the
   rule says that instead of naming what it means. */
.pf__kit:not(.is-visible) * { animation-play-state: paused !important; }

/* Per-arc tempo. A kit whose benefit arrives fast moves fast. */
.pf__vis[data-vis="glow"]     { --arc-dur: 5.2s; --arc-stagger: .62s; }
.pf__vis[data-vis="recovery"] { --arc-dur: 6.4s; --arc-stagger: .54s; }
.pf__vis[data-merge]     { --arc-dur: 6.8s; --arc-stagger: .48s; }
.pf__vis[data-vis="pulse"]    { --arc-dur: 3.6s; --arc-stagger: .30s; }
.pf__vis[data-vis="sleep"]    { --arc-dur: 9.5s; --arc-stagger: .80s; }
.pf__vis[data-vis="strength"] { --arc-dur: 4.6s; --arc-stagger: .58s; }
.pf__vis[data-vis="burn"]     { --arc-dur: 4.2s; --arc-stagger: .52s; }
.pf__vis { animation-delay: inherit; }

/* SLEEP is the one arc whose story is about flow stopping, so it gets a decay
   on the flow itself rather than only the shared brightness wave: the cells
   run down to a standstill, hold, and ease back. */
@keyframes pept-sleep-flow {
	0%, 22%   { --energy: .30; }
	46%, 64%  { --energy: .05; }
	88%, 100% { --energy: .30; }
}
.pf__vis[data-vis="sleep"] .pf__vis-step {
	animation: pept-wave var(--arc-dur) var(--ease-flow) infinite,
	           pept-sleep-flow var(--arc-dur) var(--ease-flow) infinite;
	animation-delay: calc(var(--i, 0) * var(--arc-stagger));
}
/* The shorthand above resets animation-play-state to running, and this rule
   ties with the off-screen pause on specificity while coming later, so SLEEP's
   steps kept animating after they scrolled away. Re-applied here at higher
   specificity rather than by reordering, so neither rule depends on the other's
   position in the file. */
.pf__kit:not(.is-visible) .pf__vis[data-vis="sleep"] .pf__vis-step {
	animation-play-state: paused;
}

/* --------------------------------------------------------------------------
   Reactivity

   --near is pointer proximity to the card centre, 0 to 1, written by shell.js.
   --inview is how far into the viewport the card has scrolled, 0 to 1.
   Together they let the world answer the room rather than loop unattended.
   -------------------------------------------------------------------------- */
.pf__kit { --near: 0; --inview: 0; }

/* The proximity lift lives on the media, and only once the pointer is actually
   near. A filter declared on the card unconditionally put all ten cards into a
   filtered raster that re-rasterised whenever anything inside animated, which
   cost 35fps on its own — the wave was blamed first and was not the cause. */
.pf__kit.is-near .pf__kit-media {
	filter: brightness(calc(1.06 + var(--inview) * .06));
}
.pf__kit-media { transition: filter var(--dur-base) var(--ease-flow); }
/* One cheap reactive move. Recomputing animation-duration every pointer frame
   restarts the timeline, animating a box-shadow blur repaints, and a transform
   on all 78 cell layers simply to lean them a few pixels cost 12fps for
   something nobody can see. What is left is the media scaling, which is
   composited and reads immediately. */
.pf__kit .pf__kit-media { transform: scale(calc(1 + var(--near) * .04)); }

/* --------------------------------------------------------------------------
   Where the budget goes

   Measured on the grid, each of these cost roughly this much frame rate, and
   they compound because they nest:

     cell flow (background-position on 78 elements)   ~20fps
     the travelling wave (opacity on 26 steps)        ~13fps
     a transform on every cell layer                  ~12fps

   Converting the flow to a transform made it worse, not better: it promotes
   all 78 cell layers instead of repainting, and compositing 78 layers every
   frame costs more than the repaint did. Reverted.

   So the flow runs on the featured pair only. They are the two cards given
   the space to show it, and it is the same call the overdrive playbook makes:
   focus creates impact, excess creates noise. The smaller cards keep their
   jitter, their energy bloom, their breathing and the wave — everything
   except the travelling cells.
   -------------------------------------------------------------------------- */
.pf__kit:not(.pf__kit--featured) .hn-vessel__cells { animation: none; }

/* --------------------------------------------------------------------------
   Merging arcs — HEAL and RECOVERY

   Most arcs are a sequence of still states with a wave passing over them,
   which is right for a progression. These two are not progressions: their
   outcome is a single thing, broken pieces that become one vessel. A wave over
   a static tableau can never say that, and their damaged steps jittered
   forever because nothing ever released their irregularity.

   Scoped on `data-merge`, which the renderer emits for the arcs listed in
   pept_arc_merges(). Adding a third is one line there, not a new selector
   everywhere below.

   So the arc carries one animated number, --merge, and everything that
   separates the pieces is derived from it:

     the gap between steps        five groups become one run
     the gap inside each step     the cut pieces close
     the artery/vein divergence   the two channels find alignment
     each step's irregularity     the tremor stops on its own
     the joining ends' radius     pills become tube where they meet

   The last of those matters more than it looks. Two rounded vessels butted
   together read as two sausages touching; squaring only the ends that meet
   turns the merged run into a single pipe, while the outermost ends keep their
   round so it still reads as a complete vessel rather than a cut pipe.

   The hold at 1 is deliberate. A body does not un-heal, so the merged state is
   held for a quarter of the cycle and the return is the quiet part of the
   loop, not a snap back.
   -------------------------------------------------------------------------- */
@property --merge { syntax: "<number>"; inherits: true; initial-value: 0; }

@keyframes pept-arc-merge {
	0%       { --merge: 0; }
	58%, 84% { --merge: 1; }
	100%     { --merge: 0; }
}

.pf__vis[data-merge] {
	animation: pept-arc-merge var(--arc-dur, 6.8s) var(--ease-flow) infinite;
	gap: calc(clamp(9px, 4%, 26px) * (1 - var(--merge)));
}
.pf__vis[data-merge] .pf__vis-step {
	/* the tremor is released rather than switched off, so the harsh steps calm
	   into the merge instead of stopping dead */
	--irregularity: calc(var(--irr, 0) * (1 - var(--merge)));
	/* A pipe that changes bore halfway along reads as broken, not healed, so
	   the per-step volume blends toward one value as the pieces meet. The
	   swollen and depleted segments are part of the damaged reading and are
	   still there at merge 0. */
	--volume: calc(var(--vol, 1) + (1 - var(--vol, 1)) * var(--merge));

	/* Energy blends for the same reason and it matters more, because opacity,
	   saturation and brightness are all derived from it. Left alone, the merged
	   vessel kept a permanent dull band wherever a step's state happened to be
	   low - `tired` is energy 0.28, so its ceiling was 0.70 while the step
	   beside it reached 1.00, and no amount of wave passing over it lifted the
	   middle of the tube. Measured peaks before: 1.00 / 0.89 / 0.70 / 0.81 /
	   0.71 across heal. Converging on `baseline` because a healed vessel should
	   read as steady rather than surging. */
	--energy: calc(var(--en, .55) * (1 - var(--merge)) + .55 * var(--merge));
	/* comfortably past half the artery's height, so this is a true pill at
	   merge 0 and a true square at 1 */
	--join-r: calc(var(--hn-d) * .7 * (1 - var(--merge)));
}
/* The wave's size swell belongs to the arcs that are a progression - it reads
   as activity travelling along them. On a merging arc it does the opposite: it
   bulges the run at whatever point the crest has reached, so the vessel looks
   lumpy at exactly the moment it is supposed to look uniform. The brightness
   travel stays, because flow along a healed vessel is the point; the swell
   does not. */
.pf__vis[data-merge] .pf__vis-step { scale: 1; }

.pf__vis[data-merge] .pf__vis-row { gap: calc(var(--frag-gap, 0%) * (1 - var(--merge))); }
.pf__vis[data-merge] .pf__vis-row--artery {
	transform: translateY(calc(var(--offset, 0) * (1 - var(--merge)) * var(--hn-d) * -1.05));
}
.pf__vis[data-merge] .pf__vis-row--vein {
	transform: translateY(calc(var(--offset, 0) * (1 - var(--merge)) * var(--hn-d) * 1.05));
}
/* Only the ends that meet something square off. */
.pf__vis[data-merge] .hn-vessel.pf-join-r {
	border-top-right-radius: var(--join-r);
	border-bottom-right-radius: var(--join-r);
}
.pf__vis[data-merge] .hn-vessel.pf-join-l {
	border-top-left-radius: var(--join-r);
	border-bottom-left-radius: var(--join-r);
}

/* --------------------------------------------------------------------------
   GROW — one vessel, lengthening its stride

   One block, and it changes in place. Four copies side by side showed the same
   vessel four times, which turned the pulse into a comparison; the kit says
   one thing gets denser over time, so there is one thing.

   Length and girth are one number. --grow steps up three times, once per
   pulse, and everything else is derived from it: the block's width, the wall
   thickness, the cells inside the wall, and the energy that lights it. They
   cannot disagree because there is nothing to keep in sync.

   Two things worth knowing about how this is built.

   The block is an .hn-energy-scope, which is the system's own hook for a scope
   that sets --energy itself. Without it, the derived --r-opacity, --r-glow and
   the rest would have been resolved at :root and arrived as finished values,
   so animating --energy here would have changed nothing visible.

   Width is a layout property and the most expensive kind of animation there
   is. It is used here anyway, on a single element per card, because the
   alternative - scaling the block - would stretch the rounded ends and the
   cells inside the wall into ellipses, and a vessel that changes shape is not
   the same object as a vessel that changes size.
   -------------------------------------------------------------------------- */
@property --grow { syntax: "<number>"; inherits: true; initial-value: 0; }

@keyframes pept-grow {
	0%,  6%   { --grow: 0;   scale: 1; }
	17%       { --grow: .34; scale: 1.05; }   /* first pulse lands */
	25%       { --grow: .30; scale: 1; }
	39%       { --grow: .67; scale: 1.05; }   /* second */
	47%       { --grow: .62; scale: 1; }
	61%       { --grow: 1;   scale: 1.06; }   /* third */
	69%, 88%  { --grow: 1;   scale: 1; }
	100%      { --grow: 0;   scale: 1; }      /* the quiet part of the loop */
}

.pf__vis--solo { display: grid; place-items: center; padding: 0; }
.pf__solo {
	display: flex;
	flex-direction: column;
	align-items: stretch;
	gap: calc(var(--hn-d) * .5);
	width: calc(100% - var(--grow) * 50%);

	/* re-declared here because the base resolves at :root and would otherwise
	   inherit as a finished 22px no matter what --grow says */
	--w-artery: calc(var(--hn-d) * (1 + var(--grow) * .75));
	--w-vein:   calc(var(--hn-d) * .64 * (1 + var(--grow) * .75));
	--cell-lg:  calc(var(--hn-d) * .30 * (1 + var(--grow) * .8));
	--cell-sm:  calc(var(--hn-d) * .16 * (1 + var(--grow) * .8));
	--cell-gap: calc(var(--hn-d) * 2.4);
	--lane-gap: calc(var(--hn-d) * .35);

	--energy: calc(.16 + var(--grow) * .84);

	animation: pept-grow var(--arc-dur, 6.2s) var(--ease-flow) infinite;
}
/* The component floors a vessel at two wall-widths so a fragment can never
   collapse to a sliver. That floor is exactly what stopped this shortening:
   by the last pulse the wall was thick enough that the floor sat at 110px, so
   the vessel could not get any shorter than it was wide. */
.pf__solo .hn-vessel { min-width: 0; }

/* --------------------------------------------------------------------------
   BURN — the same vessel, run backwards

   GROW fills out and BURN draws down, and they are a pair, so they share a
   composition and a single number and differ only in which way it runs. Thick,
   short, dull and unsteady becomes thin, long, lit and firm.

   The unsteadiness is not a new mechanism. It is --irregularity, which the
   component's own jitter keyframes already read for their amplitude, so the
   vessel trembles through the same channel every other world element uses to
   show stress and goes still as the arc resolves.

   Energy rises the whole way: dull at the start is dull as in low, not dull as
   in dimmed, and the kit's outcome is a body that is lit up because it is
   working, so the light has to arrive with the firmness.
   -------------------------------------------------------------------------- */
@keyframes pept-burn {
	0%,  6%   { --grow: 0;   scale: 1; }
	17%       { --grow: .34; scale: .97; }   /* first contraction */
	25%       { --grow: .30; scale: 1; }
	39%       { --grow: .67; scale: .97; }   /* second */
	47%       { --grow: .62; scale: 1; }
	61%       { --grow: 1;   scale: .96; }   /* third */
	69%, 88%  { --grow: 1;   scale: 1; }
	100%      { --grow: 0;   scale: 1; }     /* the quiet part of the loop */
}

/* after .pf__solo, which sets the same properties in GROW's direction */
.pf__solo--burn {
	width: calc(50% + var(--grow) * 50%);

	/* mirror of GROW's (1 + grow * .75): thick at rest, drawn down to the
	   component's own wall width by the last pulse */
	--w-artery: calc(var(--hn-d) * (1.75 - var(--grow) * .75));
	--w-vein:   calc(var(--hn-d) * .64 * (1.75 - var(--grow) * .75));
	--cell-lg:  calc(var(--hn-d) * .30 * (1 + var(--grow) * .8));
	--cell-sm:  calc(var(--hn-d) * .16 * (1 + var(--grow) * .8));

	--energy: calc(.16 + var(--grow) * .84);
	--irregularity: calc((1 - var(--grow)) * .55);

	animation: pept-burn var(--arc-dur, 6.2s) var(--ease-flow) infinite;
}

/* --------------------------------------------------------------------------
   ENERGY — three vessels, three sharp pulses a second

   Deliberately the plainest arc here, because the statement is that the supply
   is steady. No growth, no size change: the vessel does not move at all except
   in how bright it burns, so the only thing this animates is opacity.

   At three beats a second the attack is about 30ms, which is what makes the
   pulse read as sharp rather than as a throb. It runs on the step, replacing
   the wave, so the arc's own tempo variables do not apply to it.
   -------------------------------------------------------------------------- */
@property --beat  { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --surge { syntax: "<number>"; inherits: true; initial-value: 0; }

/* 5s. 3Hz to 1.0s, 2Hz to 2.0s, 1Hz to 3.0s, then the glow from 3.0s to the
   end. Percentages are of the 5s, so one second is 20%. */
@keyframes pept-energy {
	0%      { --beat: 0; --surge: 0; }
	1%      { --beat: 1; }
	5%      { --beat: 0; }
	7.7%    { --beat: 1; }
	11.5%   { --beat: 0; }
	14.3%   { --beat: 1; }
	18%     { --beat: 0; }
	21%     { --beat: 1; }
	27%     { --beat: 0; }
	31%     { --beat: 1; }
	37%     { --beat: 0; }
	41%     { --beat: 1; }
	52%     { --beat: 0; }
	60%     { --beat: 0; --surge: 1; }
	100%    { --beat: 0; --surge: 1; }
}

/* The arc fills the media rather than sitting inside it at its content height.
   The wash has to reach the edges of the card to light it, and a wash
   positioned against a 77px-tall arc box can never cover a 597px-tall frame
   whatever percentage is put on it. Absolute against the frame is exact. */
.pf__kit-media { position: relative; }
.pf__vis--surge {
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	padding: 0;
	animation: pept-energy 5s linear infinite;
}
.pf__surge {
	--energy: calc(.34 + var(--beat) * .34 + var(--surge) * .32);
	display: flex;
	align-items: center;
	/* .35 is the system's own lane gap - the spacing it uses for two things
	   that belong together. At .9 the gap was very nearly the width of a
	   vessel, so three channels read as three separate objects rather than one
	   supply with three lines in it. */
	gap: calc(var(--hn-d) * .35);
	width: 100%;
}
.pf__surge-unit {
	display: flex;
	flex-direction: column;
	flex: 1 1 0%;
	min-width: 0;
	gap: calc(var(--hn-d) * .5);
}
.pf__surge-unit .hn-vessel { min-width: 0; }

/* The wash sits behind the vessels and reaches the edges of the frame, so the
   light reads as coming off them into the card rather than as a panel
   switching on. One element fading up is the cheapest way to light a whole
   frame, and the only way that does not put a filter or a box-shadow animation
   on six vessels at once. */
.pf__surge-wash {
	position: absolute;
	inset: 0;
	pointer-events: none;
	background:
		radial-gradient(72% 84% at 50% 50%,
			rgb(var(--hn-artery-rgb) / .58) 0%,
			rgb(var(--hn-artery-rgb) / .30) 38%,
			rgb(var(--hn-artery-rgb) / .10) 66%,
			transparent 88%),
		linear-gradient(0deg,
			rgb(var(--hn-artery-rgb) / .16), rgb(var(--hn-artery-rgb) / .16));
	opacity: var(--surge);
}

/* --------------------------------------------------------------------------
   FOCUS — one ball, sharpened by three pulses

   The only kit here that is not a sequence, so it is the only one that is not
   an arc. A ball split half warm and half cool starts low, flat, wobbly and
   cluttered, and three pulses sharpen it into one interlocked figure.

   The interlock is the ordinary construction rather than anything clever: a
   circle split down the middle, plus a circle of each colour sitting on the
   divide. Growing those two in as the ball sharpens is what turns a straight
   edge into the S, and it means the figure is built from the same two colours
   the rest of the system uses rather than from an image.

   Sharpening is one number. Blur, saturation, brightness, the wobble and the
   interlock dots are all derived from it, so the three pulses cannot disagree
   with each other.
   -------------------------------------------------------------------------- */
/* inherits, because the two interlock dots are children of the ball and read
   this value for their own opacity. Registered non-inheriting - which is the
   usual choice for a value an element animates for itself - they sat at the
   initial 0 forever and the figure never closed. */
@property --focus-sharp { syntax: "<number>"; inherits: true; initial-value: 0; }

@keyframes pept-focus {
	0%,  4%   { --focus-sharp: 0;   scale: 1; }
	10%       { --focus-sharp: .40; scale: 1.07; }   /* pulse one lands */
	16%       { --focus-sharp: .33; scale: 1; }
	25%       { --focus-sharp: .72; scale: 1.09; }   /* pulse two */
	31%       { --focus-sharp: .65; scale: 1; }
	40%       { --focus-sharp: 1;   scale: 1.12; }   /* pulse three */
	48%, 72%  { --focus-sharp: 1;   scale: 1.02; }   /* settled, and held */
	/* the release is the quiet part of the loop, over more than a quarter of
	   it. Snapping straight back from 1 to 0 undid the whole point of the arc
	   on the frame it looped. */
	100%      { --focus-sharp: 0;   scale: 1; }
}

@keyframes pept-focus-wobble {
	0%   { rotate: calc(var(--focus-wobble) * -1deg); }
	50%  { rotate: calc(var(--focus-wobble) * 1deg); }
	100% { rotate: calc(var(--focus-wobble) * -1deg); }
}

.pf__vis--orb {
	display: grid;
	place-items: center;
	padding: 0;
}
.pf__orb {
	--focus-wobble: calc((1 - var(--focus-sharp)) * 5);
	position: relative;
	width: calc(var(--hn-d) * 3.6);
	height: calc(var(--hn-d) * 3.6);
	border-radius: 50%;
	overflow: hidden;
	/* 500 is the core tone of each kind; --hn-artery on its own is a WIDTH
	   token and paints nothing, which is how the first attempt came out mud. */
	background: linear-gradient(90deg,
		var(--hn-artery-500) 0 50%, var(--hn-vein-500) 50% 100%);
	animation:
		pept-focus var(--arc-dur, 7s) var(--ease-flow) infinite,
		pept-focus-wobble calc(1.6s - var(--focus-sharp) * .9s) ease-in-out infinite;
	/* the only filter in the arcs, on one small element, and it is the whole
	   point here: an unsharp ball has to be genuinely soft, and no composited
	   property makes a soft edge. */
	filter: blur(calc((1 - var(--focus-sharp)) * 3.2px))
		saturate(calc(.42 + var(--focus-sharp) * .78))
		brightness(calc(.88 + var(--focus-sharp) * .34));
	box-shadow: 0 0 calc(var(--hn-d) * (1 + var(--focus-sharp) * 1.6))
		rgb(var(--hn-artery-rgb) / calc(.10 + var(--focus-sharp) * .34));
}
.pf__orb-dot {
	position: absolute;
	left: 50%;
	width: 50%;
	aspect-ratio: 1;
	border-radius: 50%;
	translate: -50% 0;
	opacity: var(--focus-sharp);
}
.pf__orb-dot--warm { top: 0;    background: var(--hn-artery-500); }
.pf__orb-dot--cool { bottom: 0; background: var(--hn-vein-500); }

/* Off-screen cards keep their layout but stop animating. The orb is included
   explicitly: it is not a .pf__vis-step, so the step-wide rule below never
   reached it, and it is the one element in the arcs carrying a filter - left
   running off screen it re-rasterised a blurred circle every frame for nobody. */
.pf__kit:not(.is-visible) .hn-vessel,
.pf__kit:not(.is-visible) .hn-vessel__cells { animation-play-state: paused; }
.pf__kit-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-xs); margin-top: var(--sp-xxs); }
.pf__price { font: var(--type-body-sm-strong); font-variant-numeric: tabular-nums; }
.pf__view { font: var(--type-body-sm); color: var(--ink-muted); }
.pf__table { width: 100%; min-width: 420px; border-collapse: separate; border-spacing: 0; font: var(--type-body-sm); }
.pf__table th, .pf__table td { padding: 18px 20px; text-align: left; border-bottom: 1px solid var(--glass-line); }

/* A comparison you have to drag sideways is not a comparison - the whole point
   is seeing both columns at once, and on a phone the reader could only ever
   hold one of them in view. Fixed layout and a share of the width each, so the
   cells wrap instead of pushing the table wider than the screen. The horizontal
   scroll on the wrapper stays as a backstop for very narrow screens, but it
   should never be needed. */
@media (max-width: 559px) {
	.pf__table { min-width: 0; }
	.pf__table th, .pf__table td { padding: 12px 9px; }
	/* 26% rather than 23: the labels are the one place a word cannot wrap
	   sensibly, and "Running" was breaking to "Runnin / g out" in the narrower
	   column */
	/* Four columns, not the three this was calibrated against. The first try
	   split it 22/32/22/24 and "twice_weekly" ran straight through into
	   "morning" - a long single-token value needs a column that can hold it, so
	   the frequency column is widened past the timing column that follows it.
	   Percentages are only a preference: the table is `fixed`, so these are
	   hints and the browser still resolves a collision by growing the box. */
	/* Measured, not guessed. `table-layout: fixed` with percentage widths
	   overflowed "twice_weekly" straight through "morning": a fixed layout
	   cannot resolve a collision by growing, so it spills. Automatic layout
	   lets the only unwrappable token set its own column, and the wrapper's
	   horizontal scroll stays as the backstop for the narrowest screens. */
	.pf__table { table-layout: auto; }
	.pf__table th, .pf__table td { overflow-wrap: anywhere; }
	/* a word may only break where it means to */
	.pf__table th, .pf__table td { overflow-wrap: normal; word-break: normal; }
	/* inset from the panel's own edge rather than flush against it */
	.pf__table th:first-child, .pf__table td:first-child { padding-left: 14px; }
	.pf__table th:last-child,  .pf__table td:last-child  { padding-right: 14px; }
}
/* The header row is a scope, not a sentence: monospace, small, spaced, quiet.
   It was body-strong at the same weight as the values under it, so the table
   had two competing voices and the reader had to work out which line was the
   label. The panel's own section headings already speak this way - this makes
   the table agree with them. */
.pf__table thead th {
	color: var(--ink-faint);
	font: var(--type-code-sm);
	letter-spacing: .06em;
	text-transform: uppercase;
	white-space: nowrap;
}
.pf__table tbody tr:last-child td { border-bottom: 0; }
/* The rounded edge and the cell hairlines meet here: the first and last cells
   of each row carry the corner so a collapsed border cannot square it off. */
.pf__table tbody tr:first-child td:first-child { border-top-left-radius: calc(var(--r-lg) - 1px); }
.pf__table tbody tr:first-child td:last-child { border-top-right-radius: calc(var(--r-lg) - 1px); }
.pf__table tbody tr:last-child td:first-child { border-bottom-left-radius: calc(var(--r-lg) - 1px); }
.pf__table tbody tr:last-child td:last-child { border-bottom-right-radius: calc(var(--r-lg) - 1px); }

/* --------------------------------------------------------------------------
   The comparison, as an argument rather than a grid

   It read as a document because nothing in it had a position. The row labels
   were set as loud as the answer, both value columns carried the same weight and
   the same colour, and the one device the rest of the page is built on - the
   vessel - appeared nowhere in it. A comparison whose two sides look identical
   is not comparing anything.

   So the PEPT column is the answer, and it is built like one: the brand's own
   accent as a surface, the vessel mark above its name, full ink while everything
   around it steps back. The outer panel and its hairline go with it - they were
   framing the same thing twice and the lit column does it better.

   No new colour, no new type, no new primitive. The accent, the ink levels and
   the vessel are the ones the page already runs on.
   -------------------------------------------------------------------------- */
/* The row labels are labels, separated from the values by size and ink rather
   than by case. Caps were tried and are off brand - the page never shouts, and a
   label does not need to be louder to be read as one, only smaller and quieter.
   The step down from 14px to 12px is what does the work; a 0.01em opening keeps
   the small size from tightening up. */
.pf__table tbody td:first-child {
	display: flex;
	flex-direction: column;
	gap: 2px;
	vertical-align: middle;
}
/* The name carries the row; the descriptor explains which one it is. Name in
   full ink at heading weight, subtitle in mono at caption scale - a clear two
   step, rather than one line of text with a smaller line of text under it. */
.pf__table-name {
	font: var(--type-body-sm-strong);
	color: var(--ink);
	letter-spacing: 0;
}
.pf__table-sub {
	font: var(--type-code-sm);
	color: var(--ink-faint);
	letter-spacing: .02em;
}
/* "Part" was set to `color: transparent` so the label column read as labels
   rather than as a headed column. A four-cell data table has four headers, and
   hiding one left the first column visually unlabelled while assistive tech
   still announced it. The word is visible again; the smaller size and fainter
   ink are what keep the column quiet, not its absence. */
.pf__table thead th:first-child { color: var(--ink-muted); }

/* The highlighted column is the first one.
   ==========================================================================
   It used to be the second, on the theory that the amount was "the answer" -
   inherited from when this table was a PEPT-versus-others comparison. On a
   composition table that is wrong twice over: the loudest cell was a dosage
   figure, and the compound's own name was left as the quietest thing in its
   own row.

   The Part column is what the row IS. It takes the surface and the full ink;
   everything to its right is a property of it and steps back. The blue stays
   out of the fill and lives in the badges, so the table's emphasis is carried
   by weight and surface rather than by a second colour competing with the
   panel above. */
.pf__table thead th:first-child,
.pf__table tbody td:first-child {
	background: rgb(255 255 255 / .07);
	color: var(--ink);
	font-weight: 500;
}
.pf__table thead th:first-child { box-shadow: inset 0 1px 0 var(--glass-edge); }

/* The properties. Quieter than the part they describe. */
.pf__table tbody td:nth-child(2),
.pf__table tbody td:nth-child(3) { color: var(--ink-muted); }

/* ---- Why: who each part is for ------------------------------------------
   Same pill as the panel's "Who it suits" row, at table scale, so the two
   read as one idea in two places. `is-active` is not used here: every entry
   is a statement about the compound, so there is no selected state to
   contrast against. */
/* The benefit is what the row is for, so it reads at full ink while the
   measurements beside it stay muted. Weight, not colour - the badges keep the
   accent to themselves. */
.pf__table tbody td:last-child { white-space: normal; }
.pf__table tbody td:last-child .pf__table-badge { font-weight: 500; }
.pf__table-why {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-xxs);
}
.pf__table-badge {
	display: inline-flex;
	align-items: center;
	padding: 3px 9px;
	border-radius: var(--r-full);
	background: rgb(255 255 255 / .10);
	box-shadow: inset 0 0 0 1px rgb(255 255 255 / .16);
	color: var(--ink);
	font: var(--type-code-sm);
	line-height: 1.4;
	white-space: nowrap;
}
.pf__table-why-none { color: var(--ink-faint); }

/* --------------------------------------------------------------------------
   Kit grid
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   Kit grid

   Two featured kits at double size, then the rest around them. The featured
   pair each span 2 x 2, which fills the first two rows of a four-column grid
   exactly; the remaining eight then make two more rows of four. Ten kits land
   as 4 x 4 with no ragged final row, which a flat four-column grid gave
   (4 / 4 / 2).

   Narrower screens cannot hold a 2 x 2 card legibly, so the featured pair
   becomes a full-width single-row card at two columns and an ordinary card at
   one.
   -------------------------------------------------------------------------- */
.pf__kitgrid {
	display: grid;
	gap: var(--sp-md);
	grid-template-columns: 1fr;
}
.pf__kit--featured { grid-column: span 1; }

/* minmax(0, 1fr) rather than 1fr: a bare 1fr is minmax(auto, 1fr), so a long
   unbreakable word in a card pushes its column wider than its share and the
   columns stop matching. */
@media (min-width: 560px) {
	.pf__kitgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.pf__kit--featured { grid-column: span 2; }
}

@media (min-width: 760px) {
	.pf__kitgrid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
	.pf__kit--featured { grid-column: span 2; grid-row: span 2; }
}

/* A featured card is two rows tall, so its media takes the slack instead of
   holding a fixed ratio and leaving a gap under it. */
.pf__kit--featured { grid-template-rows: auto 1fr auto auto; }
.pf__kit--featured .pf__kit-media { aspect-ratio: auto; min-height: 200px; }
/* the arc scales with the card, unlike the fixed pair it replaced */
.pf__kit--featured .pf__kit-media .pf__vis { --hn-d: 28px; }
.pf__kit--featured .hn-card__title { font: var(--type-display-lg); }
.pf__kit--featured .hn-card__desc { font: var(--type-body-md); }
.pf__kit--featured .pf__price { font: var(--type-heading-md); font-variant-numeric: tabular-nums; }

/* Two up on a phone, with the featured pair still taking a row each. One
   column abandoned the whole idea the desktop grid is built on - a lead pair
   and a field behind them - and turned ten cards into ten identical rows. Two
   columns keeps the field reading as a field; the pair keep the full width and
   a taller frame, so the hierarchy survives the narrower layout. */
@media (max-width: 559px) {
	.pf__kitgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.pf__kit--featured { grid-column: span 2; }
	.pf__kit--featured .pf__kit-media { aspect-ratio: 16 / 10; min-height: 0; }
	.pf__kit--featured .pf__kit-vessels { width: 52%; --w: 9px; --cell-gap: 56px; }
	.pf__kit--featured .hn-card__title { font: var(--type-display-lg); }
	.pf__kit--featured .hn-card__desc { font: var(--type-body-md); }
	.pf__kit--featured .pf__price { font: var(--type-heading-md); font-variant-numeric: tabular-nums; }
}
/* The transition had no target: every card is a link with no feedback at all.
   The system's own vocabulary for a tile is to advance or recede, not to lift,
   so this moves the surface one step and brings the label up to full ink. */
.pf__kit:hover { background: var(--surface-soft); }
/* Placeholder for product photography. The system's small-mark treatment is a
   vessel pair, which reads as deliberate where a lone glyph reads as a broken
   image. Swap for <img> when real kit photography exists. */
.pf__kit-vessels { width: 62%; --w: 5px; --cell-gap: 44px; }
/* --------------------------------------------------------------------------
   Comparison table
   -------------------------------------------------------------------------- */
/* The panel is the glass object, so it is the thing that is rounded. The column
   inside it is square, and the two never meet: the panel rounds at x=0 and x=720,
   the column sits between 167 and 441. The previous pass rounded the column
   inside a table it had just un-rounded, which put a 12px curve against a right
   angle on the same edge. */
/* Glass. The wrapper already carried a backdrop-filter, but the veil above it
   was so faint that the blur had almost nothing to act on and the panel read as
   a flat blue rectangle. Glass needs three things together: a translucent
   surface, a bright edge to catch the light, and a real drop so it separates
   from what it is floating over. Only the first was present.

   The veil and the edge are white at low alpha rather than new colours - white
   is already the system's ink on this lane, and the header's own bar is built
   the same way. `--glass-*` is scoped to the wrapper so nothing global moves.

   The radius is on the wrapper, which also scrolls horizontally, so the table
   gets its corners from `overflow: hidden` at the top and a soft mask below
   600px, where it scrolls. `border-collapse: separate` is what lets the cells
   keep their hairlines under a rounded edge without the corners clipping. */
.pf__tablewrap {
	--glass-veil: rgb(255 255 255 / .07);
	--glass-veil-2: rgb(255 255 255 / .03);
	--glass-edge: rgb(255 255 255 / .14);
	--glass-line: rgb(255 255 255 / .09);
	--glass-drop: rgb(4 14 34 / .20);

	margin-top: var(--sp-lg);
	overflow-x: auto;
	border-radius: var(--r-lg);
	background: linear-gradient(180deg, var(--glass-veil), var(--glass-veil-2));
	box-shadow:
		inset 0 0 0 1px var(--glass-edge),
		/* the lit top edge is the blue, not the white: it is the one place the
		   table takes the accent, and it replaces the orange that used to sit on
		   the Amount column. */
		inset 0 1px 0 rgb(77 187 255 / .34),
		0 8px 28px var(--glass-drop);
	backdrop-filter: var(--frost);
	-webkit-backdrop-filter: var(--frost);
}
@media (max-width: 600px) {
	/* Scrolling needs a soft edge: a hard `overflow: hidden` would clip the
	   first column's labels as they move under the corner. */
	-webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
	mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
}
/* --------------------------------------------------------------------------
   Closing CTA — the one inverted surface on the view

   The page runs dark for its whole length and then the lights come up on the
   last thing it says. The inversion is the system's own move: every stage
   defines --invert-surface, --invert-ink and --invert-ink-muted, described in
   stages.css as "the look-here box becomes the light one". Using them rather
   than writing white keeps this a citizen of the world it ends.

   It is the one element on the page that takes the full width. Everything
   above it sits in a measure and the last thing a visitor reads should not -
   so the band bleeds and the words stay in the column, because padding-inline
   of half the leftover width is exactly what centres a measure inside a wider
   box. The band changes; the column does not.

   No radius, now that it reaches both edges: a rounded corner on a full-bleed
   band reads as a mistake. No edge rule either, and no glow - against a dark
   page the inversion is the edge, a hairline in a light ink would be
   invisible, and the warm floor wash that suited a dark band turns to mud on
   white.
   -------------------------------------------------------------------------- */
.pf__cta-strip {
	position: relative;
	isolation: isolate;
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: center;
	gap: var(--sp-xl);
	background: var(--invert-surface);
	color: var(--invert-ink);
	/* In a column - the kit detail pages render this too - it stays a panel
	   inside the measure, as it always was. */
	padding: var(--card-pad);
	border-radius: var(--r-lg);
}

/* The strip's heading is persona-aware. Both strings are in the HTML; the
   documentElement[data-persona] attribute persona.js sets picks one. Unset —
   a first-time visitor, a crawler, or JavaScript off — shows the generic
   question, which is also the state the server rendered, so the response never
   varies by persona and the CDN still caches one copy per kit.

   `:not([data-persona])` is not used for the generic case: writing the default
   as the plain rule and the persona case as the exception means the lightest
   possible match path for the majority of visitors. */
.pf__cta-persona { display: none; }
html[data-persona] .pf__cta-generic { display: none; }
html[data-persona] .pf__cta-persona { display: block; }

/* The stack page's closing strip, at full page width.

   On the Landing the strip sits inside a `.pf__section--bleed`, so it is
   already edge to edge and the rule below finds it. On a stack page the strip
   is inside `<article class="pf__section--wide">` - a 960px column - so the
   same strip rendered as a rounded panel floating in the middle of the page.

   The breakout is the standard 50% / 50vw pair: `left: 50%` moves the strip's
   own left edge to the middle of its parent, and `-50vw` pulls it back to the
   viewport's left edge. It does not depend on the parent's width, so it keeps
   working whatever `--content-wide` becomes, and `overflow-x: clip` on the
   body keeps a rounding error from producing a horizontal scrollbar.

   The padding rule below then does the rest: it centres the words inside the
   full-width band, so the band changes and the column does not. */
.pf__section--wide > .pf__cta-strip {
	position: relative;
	left: 50%;
	width: 100vw;
	max-width: none;
	margin-left: -50vw;
}

/* Only when it is the whole width of the page does it stop being a panel:
   the padding that centres the words in a full-bleed band, and no radius,
   because a rounded corner on a band that reaches both edges reads as a
   mistake. Scoped rather than global so the detail page keeps its panel. */
.pf__section--bleed > .pf__cta-strip,
.pf__section--wide > .pf__cta-strip {
	padding: clamp(var(--sp-xxl), 7vw, 88px) max(var(--pf-gutter), calc(50% - var(--measure) / 2));
	border-radius: 0;

	/* Centred, not spread.

	   The strip's own layout is a two-column grid - text left, button right,
	   `1fr` between them. Inside a 960px column that reads as a considered
	   pair. Across a full-width band the same grid strands the text at one
	   edge and the button at the other, with over a thousand pixels of empty
	   white between them, and the reader has to cross the page to find the
	   action.

	   So the bleed variant stops being a row. It becomes one centred column:
	   the words at the reading measure, the button directly beneath them. With
	   two children in a single column, `justify-items: center` centres both and
	   `text-align: center` centres the lines within them - the grid does the
	   alignment, so no wrapper element is needed. */
	grid-template-columns: minmax(0, 1fr);
	justify-items: center;
	text-align: center;
	gap: var(--sp-xl);
}
/* The copy block takes the reading measure rather than the whole band, so the
   paragraph wraps at a length someone can read instead of running the width of
   the page. */
.pf__section--bleed > .pf__cta-strip > div:first-child,
.pf__section--wide > .pf__cta-strip > div:first-child { max-width: var(--measure); }
/* The heading keeps its own narrower cap. `text-wrap: balance` in the shared
   rule needs a measure to balance against, or it balances across the band. */
.pf__section--bleed > .pf__cta-strip h2,
.pf__section--wide > .pf__cta-strip h2 { max-width: 22ch; margin-inline: auto; }
.pf__cta-strip h2 {
	margin: 0;
	font: var(--type-display-lg);
	color: var(--invert-ink);
	/* no cap: the column beside the button is exactly the room this phrase
	   needs, and capping it only forced a break between "minutes" and "to" */
	text-wrap: balance;
}
.pf__cta-strip p {
	margin: var(--sp-md) 0 0;
	/* 4.74:1 on the white band - the muted ink the system pairs with it */
	color: var(--invert-ink-muted);
	max-width: 44ch;
}

/* The button keeps the vein blue on both surfaces, which is why it survives
   the inversion: 5.16:1 against its own label and 5.16:1 against the band. */
.pf__cta-strip .hn-btn {
	--btn-bg: var(--hn-vein-600);
	--btn-fg: #FFFFFF;
	--btn-bg-active: var(--hn-vein-700);
	/* the component fixes its height at 36px, so padding alone cannot grow it
	   and the label ends up in a target smaller than a fingertip */
	height: auto;
	min-height: 54px;
	padding: 15px 32px;
	font-size: 1.0625rem;
	font-weight: 600;
	white-space: nowrap;
	box-shadow:
		0 12px 28px -12px rgb(var(--hn-vein-rgb) / .55),
		inset 0 1px 0 rgb(255 255 255 / .16);
	transition: transform var(--dur-quick, 160ms) var(--ease-arrive, ease-out),
		box-shadow var(--dur-quick, 160ms) var(--ease-arrive, ease-out);
}
.pf__cta-strip .hn-btn:hover {
	transform: translateY(-2px);
	box-shadow:
		0 18px 36px -12px rgb(var(--hn-vein-rgb) / .68),
		inset 0 1px 0 rgb(255 255 255 / .22);
}
.pf__cta-strip .hn-btn:active { transform: translateY(0); }
/* the pale ring that suited the dark band all but vanishes here */
.pf__cta-strip .hn-btn:focus-visible {
	outline: 3px solid var(--hn-vein-600);
	outline-offset: 3px;
}

/* Stacked, the band keeps its bleed and the button goes full width, so the
   target stays large on a phone rather than shrinking to its label. */
@media (max-width: 700px) {
	.pf__cta-strip { grid-template-columns: minmax(0, 1fr); gap: var(--sp-xl); }
	.pf__cta-strip h2 { max-width: none; }
	.pf__cta-strip .hn-btn { width: 100%; }
}


/* --------------------------------------------------------------------------
   Library and detail furniture
   -------------------------------------------------------------------------- */
/* The library is a browse rather than a shelf. Front page shows ten at once
   because it is asking you to pick a direction; this page is where you actually
   read them, and at four across each arc was 167px wide - too small to tell a
   three-vessel arc from a two. One per row on a phone, two on a desk, which
   doubles the width every arc gets. */
.pf__library .pf__kitgrid { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 760px) {
	.pf__library .pf__kitgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.pf__library-head { display: grid; gap: var(--sp-md); margin-bottom: var(--sp-xl); }
.pf__library-head .pf__h1 { max-width: 20ch; }

.pf__kit--soon { cursor: default; }
.pf__kit-soon {
	font: var(--type-code-sm);
	color: var(--ink-faint);
	text-transform: uppercase;
	letter-spacing: .04em;
}

.pf__empty {
	display: grid;
	gap: var(--sp-md);
	justify-items: start;
	padding: var(--card-pad);
	border-radius: var(--r-lg);
	background: var(--surface);
	box-shadow: inset 0 0 0 1px var(--line);
}

.pf__back {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-xs);
	min-height: 44px;
	font: var(--type-body-sm-strong);
	color: var(--ink-muted);
	text-decoration: none;
}
.pf__back:hover { color: var(--ink); }

.pf__detail-head { display: grid; gap: var(--sp-md); justify-items: start; margin: var(--sp-md) 0 var(--sp-xl); }

.pf__facts {
	display: grid;
	gap: var(--sp-md);
	grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
	margin: 0 0 var(--sp-xl);
	padding: var(--sp-lg) var(--card-pad);
	border-radius: var(--r-lg);
	background: var(--surface);
	box-shadow: inset 0 0 0 1px var(--line);
}
.pf__facts > div { display: grid; gap: var(--sp-xxs); }
.pf__facts dt { font: var(--type-code-sm); color: var(--ink-faint); }
.pf__facts dd { margin: 0; font: var(--type-heading-md); color: var(--ink); font-variant-numeric: tabular-nums; }

.pf__detail > .pf__cta-strip { margin-top: var(--sp-xl); }


/* --------------------------------------------------------------------------
   Account area

   Blocksy wraps the account nav in .ct-acount-nav, which is a white panel by
   default, and the only thing colouring the links was the shell's generic
   .pf__main a rule, so they were white on white: the whole menu was invisible
   apart from the active item. The pept-account view markup
   (.pept-account__*) had no styling at all.
   -------------------------------------------------------------------------- */
.ct-woo-account { display: grid; gap: var(--sp-xl); align-items: start; }
@media (min-width: 760px) { .ct-woo-account { grid-template-columns: 220px 1fr; } }

.ct-acount-nav {
	background: var(--surface);
	border-radius: var(--r-lg);
	box-shadow: inset 0 0 0 1px var(--line);
	overflow: hidden;
}
.woocommerce-MyAccount-navigation ul { list-style: none; margin: 0; padding: 0; }
.woocommerce-MyAccount-navigation li { border-bottom: 1px solid var(--line); }
.woocommerce-MyAccount-navigation li:last-child { border-bottom: 0; }
.woocommerce-MyAccount-navigation a {
	display: flex;
	align-items: center;
	min-height: 44px;
	padding: 10px 20px;
	color: var(--ink-muted);
	text-decoration: none;
	font: var(--type-body-sm-strong);
}
.woocommerce-MyAccount-navigation a:hover { color: var(--ink); background: var(--surface-soft); }
/* Active state is carried by ink and surface only. An ::before marker here
   outranked Blocksy's own ::before, which is the per-item icon, and replaced
   the Dashboard icon with a dot. */
.woocommerce-MyAccount-navigation .is-active a {
	color: var(--ink);
	background: var(--surface-soft);
	font-weight: 600;
}

/* pept-account views */
.pept-account { display: grid; gap: var(--sp-lg); }
.pept-account__greeting { margin: 0; font: var(--type-heading-md); color: var(--ink); }
.pept-account__heading { margin: 0; font: var(--type-heading-sm); color: var(--ink); }
.pept-account__notice {
	margin: 0;
	padding: var(--sp-md) var(--sp-lg);
	border-radius: var(--r-lg);
	background: var(--surface);
	box-shadow: inset 0 0 0 1px var(--hn-artery-500);
	color: var(--ink);
	font: var(--type-body-sm);
}
.pept-account__stats {
	display: grid;
	gap: var(--sp-md);
	grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
	margin: 0;
}
.pept-account__stat {
	display: grid;
	gap: var(--sp-xxs);
	padding: var(--sp-lg) var(--sp-xl);
	border-radius: var(--r-lg);
	background: var(--surface);
	box-shadow: inset 0 0 0 1px var(--line);
}
.pept-account__stat dt { font: var(--type-code-sm); color: var(--ink-faint); }
.pept-account__stat dd {
	margin: 0;
	font: var(--type-display-lg);
	color: var(--ink);
	font-variant-numeric: tabular-nums;
}
.pept-account__list { list-style: none; margin: 0; padding: 0; display: grid; }
.pept-account__row { border-bottom: 1px solid var(--line); }
.pept-account__row:first-child { border-top: 1px solid var(--line); }
.pept-account__row-link {
	display: grid;
	grid-template-columns: auto 1fr auto auto;
	gap: var(--sp-md);
	align-items: center;
	min-height: 44px;
	padding: var(--sp-md) 0;
	color: var(--ink);
	text-decoration: none;
	font: var(--type-body-sm);
}
.pept-account__row-link:hover { color: var(--hn-artery-400); }
.pept-account__row-meta { color: var(--ink-faint); }
.pept-account__row-status { color: var(--ink-muted); text-transform: capitalize; }
.pept-account__row-total { font-variant-numeric: tabular-nums; }
.pept-account__empty { margin: 0; color: var(--ink-muted); font: var(--type-body-sm); }

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */
.pf__footer {
	margin-top: var(--sp-section);
	border-top: 1px solid var(--line);
	background: var(--pf-tint);
}
.pf__footer-grid {
	width: min(100% - 2 * var(--pf-gutter), var(--content-wide));
	margin: 0 auto;
	padding: var(--sp-xl) 0;
	display: grid;
	gap: var(--sp-lg);
	grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
	font: var(--type-body-sm);
	color: var(--ink-muted);
}
.pf__footer-grid > div,
.pf__footer-grid nav { display: grid; gap: var(--sp-xs); align-content: start; }
.pf__footer-brandrow { display: flex; align-items: center; gap: 10px; font: 500 17px/1 var(--font-display); color: var(--ink); }
.pf__footer-brandrow img { height: 28px; width: 28px; border-radius: 6px; }
.pf__footer-line { margin: 0; max-width: 26ch; }
.pf__footer-grid nav span { font: var(--type-body-sm-strong); color: var(--ink); }
.pf__footer-grid nav a { color: var(--ink-muted); text-decoration: none; }
.pf__footer-legal {
	width: min(100% - 2 * var(--pf-gutter), var(--content-wide));
	margin: 0 auto;
	padding: 0 0 var(--sp-lg);
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-xs) var(--sp-lg);
	font: var(--type-caption-sm);
	color: var(--ink-faint);
}

/* --------------------------------------------------------------------------
   ≥760px
   -------------------------------------------------------------------------- */
@media (min-width: 760px) {
	.pf__navlinks { display: flex; }
	.pf__menu { display: none; }
	.pf__menu-panel { display: none !important; }
	.pf__nav, .pf__menu-panel { padding-left: var(--sp-lg); padding-right: var(--sp-lg); }
}

@media (prefers-reduced-motion: reduce) {
	.pf__spine { display: none; }

	/* The arcs carry their whole story in the still states — the progression
	   reads left to right without any motion at all, which is how it was built
	   before any of this. Stopping the motion therefore loses the motion and
	   nothing else, so there is no reduced-motion alternative to design: the
	   static version already is one. */
	/* the same reasoning as the off-screen pause above: a list of classes here
	   is a list that will be out of date by the next composition */
	.pf__kit * { animation: none !important; }
	.pf__kit .pf__kit-media { transform: none !important; filter: none !important; }
	.pf__kit .hn-vessel { scale: none !important; }

	/* Stopping the wave leaves --wave at its initial 0, which is the trough, so
	   every step settled at opacity .42 against the 0.59-1.0 it sweeps through
	   while running. The reduced-motion arc was therefore not simply still, it
	   was darker and lower contrast than the one everyone else sees. Pinning
	   --wave mid-range removes the wave's contribution and leaves the per-step
	   states carrying the progression, which is what the still version is for. */
	.pf__vis-step { --wave: .5; }
}


/* --------------------------------------------------------------------------
   Browser surfaces. These carry no design system by default and are the
   cheapest signal that a page was built rather than assembled.
   -------------------------------------------------------------------------- */
::selection { background: var(--hn-artery-500); color: var(--c-on-dark); }

/* Default caret is black, which is invisible inside the dark form fields on
   the account, cart and checkout screens. */
.pf, .pf input, .pf textarea, .pf select { caret-color: var(--hn-artery-400); }

/* Default scrollbar renders light against the dark stage. */
html { scrollbar-color: var(--line-strong) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: var(--r-full); }
::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); }

/* ==========================================================================
   Persona sections

   The middle and bottom thirds of a stack or product page. Server-rendered
   generically, replaced on the client when the visitor has told us who they
   are. Nothing here changes the generic version - the persona variant is
   additive, which is why the fallback can be the real copy rather than a
   placeholder.
   ========================================================================== */
.pf__persona-details { margin-top: var(--sp-section); }
.pf__persona-inner { max-width: var(--measure); }

/* The kicker names who the page is speaking to. Quiet, because it is context
   rather than content - a page that shouts "FOR THE BIOHACKER" reads as a
   landing page, and this is meant to read as the same page, better aimed. */
.pf__persona-kicker {
	margin: 0 0 var(--sp-md);
	font: var(--type-caption-sm);
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--hn-artery-400);
}

.pf__persona-hook {
	margin: 0;
	font: var(--type-heading-sm);
	color: var(--ink);
	text-wrap: pretty;
}

.pf__persona-pains {
	margin: var(--sp-lg) 0 0;
	padding: 0;
	list-style: none;
	display: grid;
	gap: var(--sp-sm);
}
.pf__persona-pains li {
	position: relative;
	padding-left: var(--sp-lg);
	font: var(--type-body-md);
	color: var(--ink-muted);
}
/* a small artery tick, not a bullet - the system's own mark at list scale */
.pf__persona-pains li::before {
	content: "";
	position: absolute;
	left: 0;
	top: .62em;
	width: 6px;
	height: 6px;
	border-radius: var(--r-full);
	background: var(--hn-artery-400);
}

.pf__persona-facts {
	margin: var(--sp-xl) 0 0;
	display: grid;
	gap: var(--sp-md);
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.pf__persona-facts dt {
	font: var(--type-caption-sm);
	letter-spacing: .01em;
	color: var(--ink-faint);
	margin-bottom: 2px;
}
.pf__persona-facts dd { margin: 0; font: var(--type-body-sm-strong); color: var(--ink); }

/* The closing ask. It sits inside the existing cta-strip, so it inherits the
   band's surface and only carries the persona's own framing above the button. */
.pf__persona-inner--cta { display: grid; gap: var(--sp-lg); justify-items: start; }
.pf__persona-cta-line {
	margin: 0;
	max-width: 42ch;
	font: var(--type-body-sm);
	color: var(--invert-ink, var(--ink-muted));
	/* No opacity dimming. The strip override below sets the muted ink, which is
	   already 4.74:1 on the white band; a further .78 opacity took the painted
	   glyph to ~3.15:1 and failed AA. The CTA line stays quiet through its size
	   and colour, not through being faded. */
}

/* The visual, inside the middle third. It sits beside the resolution rather
   than after the argument: the reader has just been told how long and how much
   work, and this shows them the shape of it. Same surface treatment as the
   proof figure so the two read as the same object at different scales. */
.pf__persona-visual {
	margin: var(--sp-xl) 0 0;
	max-width: var(--measure);
}
.pf__persona-visual img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: var(--r-lg);
	background: rgb(255 255 255 / .035);
	box-shadow: inset 0 0 0 1px var(--line);
}
.pf__persona-visual-cap {
	margin-top: var(--sp-sm);
	font: var(--type-caption-sm);
	color: var(--ink-faint);
}

/* --------------------------------------------------------------------------
   Persona filter for the data panel

   The panel (ticket 07) renders every section once, server-side, inside
   .pf__persona-details. Sections open and close here, on the client, by the
   html[data-persona] attribute persona.js sets — never by the server, and
   never by moving data into the JSON bank.

   Direction: the base state is EVERY SECTION VISIBLE. With no cookie there is
   no data-persona, so the generic visitor and every crawler see the full
   panel. A known persona closes the sections its argument does not need.
   Personalisation removes depth; it never adds the argument. There must be no
   unconditional `.pf__persona-details [data-panel] { display: none }` rule
   here — that would hide depth from the generic reader and re-open it for
   the persona, which is the inversion.

   The names how | timing | suits | timeline are the ones the renderer emits
   (pept-data-panel.php). They are stable identifiers, not presentation.
   -------------------------------------------------------------------------- */

/* biohacker and bodybuilder read the whole label — nothing hidden for them. */

/* Longevity: the oversight argument — who it suits and when it will show. */
html[data-persona="longevity"] .pf__persona-details :is([data-panel="timing"], [data-panel="suits"]) { display: none; }

/* Athlete: the date he can train — timing and the first-win timeline. */
html[data-persona="athlete"] .pf__persona-details [data-panel="suits"] { display: none; }

/* GLP-1: safety and suitability — the dosing label is not the hesitation. */
html[data-persona="glp1"] .pf__persona-details [data-panel="suits"] { display: none; }

/* Sleepless: least is most — timing and the first-night timeline. */
html[data-persona="sleeper"] .pf__persona-details [data-panel="suits"] { display: none; }

/* Aesthete: quiet authority — suitability and when it shows, not the label. */
html[data-persona="aesthete"] .pf__persona-details [data-panel="timing"] { display: none; }

/* Explorer: near-silent — timing and the within-hours timeline. */
html[data-persona="intimacy"] .pf__persona-details [data-panel="suits"] { display: none; }



/* ===== PEPT BUILD BLOCK: ticket 08 — client renderer (END ticket 08) =====
   The client side of the persona frame. `persona.js` rebuilds the whole slot,
   so everything it builds should already be styled.

   One selector is genuinely new: the answer and the reassurance are the only
   pair the client wraps in an element the server's generic frame does not have,
   `div.pf__persona-support`. The bare class in the shell is (0,1,0) and so it
   loses to the shell's own `.pf__main p, .pf__main li` content defaults - the
   collision that once painted a FAQ answer white-on-white. The wrapper is
   therefore scoped to the section, the same (0,2,0) move the frame's rules
   already make.

   Nothing else is declared here on purpose. The client's field set, order,
   elements and classes are a mirror of the server's, so a second set of rules
   would be a second source of truth for the same tree. No token is re-declared
   and no value is invented: `--sp-xl` and `--sp-md` are the design system's own
   steps.

   PLACEMENT: this block sits immediately BEFORE ticket 05's marker. Ticket 05's
   own marker line closes a comment it never opened, so that line is an orphaned
   comment terminator; a block placed after it is swallowed and neither block's
   rules apply. Placed here, both blocks are parsed. Ticket 08's marker is
   likewise a closer that never opened, exactly as ticket 05 left its own.
   -------------------------------------------------------------------------- */

.pf__persona-details .pf__persona-support {
	margin-top: var(--sp-xl);
	display: grid;
	gap: var(--sp-md);
}

/* ===== PEPT BUILD BLOCK: ticket 05 — server frame and slot (END ticket 05) =====
   The server side of the persona frame: the biohacker default, written out in
   `single-stacks.php` before any personalisation runs.

   It exists because the two sides disagreed: the server rendered two slots and
   the client four, so the visitor with no cookie — which is every first-time
   visitor and every crawler — read a lesser page than the one who had taken the
   quiz. The field set is now the same on both sides; see the wiring contract.

   Read this with `.pf__main p, .pf__main li, .pf__main dd` (the shell's
   content defaults). That rule is (0,1,1) and each class here is (0,1,0), so on
   its own a `.pf__persona-*` rule loses and every one of these paragraphs would
   paint at the shell's muted ink rather than the frame's own. Two rules below
   are written against that, and both are commented where they are. The same
   collision once put a FAQ answer white-on-white at 1.0:1 — a colour that is
   present in the DOM, computed, and wrong is the failure mode here.

   Nothing in this block is a new component. Every value is a design-system
   token; `pf__persona-*` is the frame's own vocabulary and `pf__persona-facts`
   is the shape `persona.js` builds, so a swap replaces rather than duplicates.
   -------------------------------------------------------------------------- */

/* Who the frame is talking to. Deliberately above the hook: it answers "who is
   this for" before the argument starts, and the accent is the system's own
   signal for a persona-aware surface. */
.pf__persona-details p.pf__persona-kicker {
	margin: 0 0 var(--sp-md);
	font: var(--type-caption-sm);
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--hn-artery-300);
}

/* The opening line, at the weight of an answer rather than a heading. Scoped
   with the parent class because `.pf__main p` is (0,1,1) and this rule is
   (0,2,0) — the frame has already lost this one once. */
.pf__persona-details p.pf__persona-hook {
	margin: 0 0 var(--sp-lg);
	font: var(--type-heading-sm);
	color: var(--ink);
	text-wrap: pretty;
}

/* The present situation. It introduces the list under it, so the pair reads as
   one unit and only the last of the two carries the space below. */
.pf__persona-details p.pf__persona-problem {
	margin: 0;
	font: var(--type-body-md);
	color: var(--ink);
	max-width: var(--measure);
	text-wrap: pretty;
}

.pf__persona-details ul.pf__persona-pains {
	margin: var(--sp-sm) 0 var(--sp-lg);
	padding: 0;
	list-style: none;
	display: grid;
	gap: var(--sp-sm);
	max-width: var(--measure);
}
/* `li` and not a bare class: `.pf__main li` is (0,2,1), so this selector has to
   carry the element name as well or it ties and the ordering decides. */
.pf__persona-details ul.pf__persona-pains li {
	position: relative;
	padding-left: var(--sp-lg);
	font: var(--type-body-md);
	color: rgb(255 255 255 / .82);
}
/* the system's own mark at list scale: a small artery tick, not a bullet */
.pf__persona-details ul.pf__persona-pains li::before {
	content: "";
	position: absolute;
	left: 0;
	top: .62em;
	width: 6px;
	height: 6px;
	border-radius: var(--r-full);
	background: var(--hn-artery-400);
}

/* What the kit does about it - the turn of the argument, and the one paragraph
   in the frame marked as the pivot rather than as another statement. It carries
   the reading ink at body weight with the accent down its edge; the hook above
   it is already at heading weight, so a second heading-weight paragraph would
   only flatten the two into one another. */
.pf__persona-details p.pf__persona-resolution {
	margin: 0 0 var(--sp-lg);
	padding-left: var(--sp-lg);
	border-left: 2px solid var(--hn-artery-500);
	font: var(--type-body-md);
	color: var(--ink);
	max-width: var(--measure);
	text-wrap: pretty;
}

/* What that gets them, one line past the resolution. */
.pf__persona-details p.pf__persona-dream {
	margin: 0;
	font: var(--type-body-md);
	color: rgb(255 255 255 / .82);
	max-width: var(--measure);
}

/* The three facts, in the `dl` shape the client builds. `persona.js` writes
   `dl.pf__persona-facts` with one `div` per pair, which is the same tree these
   rules style — so the client's row inherits this treatment instead of
   arriving unstyled beside the server's. */
.pf__persona-facts {
	margin: var(--sp-xl) 0 0;
	display: grid;
	gap: var(--sp-md);
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.pf__persona-facts dt {
	font: var(--type-caption-sm);
	letter-spacing: .01em;
	color: var(--ink-faint);
	margin-bottom: 2px;
}
.pf__persona-facts dd { margin: 0; font: var(--type-body-sm-strong); color: var(--ink); }

/* The reader's own questions, still unanswered at this point in the page. They
   take the pain treatment so the two lists read as the same voice about
   different things — the second `ul` and not a second component. */
.pf__persona-details ul.pf__persona-objections {
	margin: var(--sp-xl) 0 0;
	padding: 0;
	list-style: none;
	display: grid;
	gap: var(--sp-sm);
	max-width: var(--measure);
}
.pf__persona-details ul.pf__persona-objections li {
	position: relative;
	padding-left: var(--sp-lg);
	font: var(--type-body-md);
	color: rgb(255 255 255 / .82);
}
.pf__persona-details ul.pf__persona-objections li::before {
	content: "";
	position: absolute;
	left: 0;
	top: .62em;
	width: 6px;
	height: 6px;
	border-radius: var(--r-full);
	background: var(--hn-artery-400);
}

/* The answer and the reassurance, as one block stepped in from the frame. The
   answer is a card — the first surface in the frame, and what makes it read as
   the reply rather than as one more claim. */
.pf__persona-support {
	margin-top: var(--sp-xl);
	display: grid;
	gap: var(--sp-md);
}
.pf__persona-details p.pf__persona-answer {
	margin: 0;
	padding: var(--sp-lg) var(--card-pad);
	border-radius: var(--r-lg);
	background: var(--surface);
	box-shadow: inset 0 0 0 1px var(--line);
	font: var(--type-body-md);
	color: var(--ink);
	max-width: var(--measure);
	text-wrap: pretty;
}
.pf__persona-details p.pf__persona-reassurance {
	margin: 0;
	font: var(--type-body-sm);
	color: rgb(255 255 255 / .82);
	max-width: var(--measure);
	text-wrap: pretty;
}

/* The persona's line above the closing button. It sits inside the inverted
   strip, so it takes the strip's own muted pair. Element-qualified on purpose:
   `.pf__cta-strip p` sets the strip's paragraph colour from the same token set,
   and a bare class would only tie with it. `opacity: 1` is not a typo: the base
   rule used to dim the line to .78, and this override set the colour but never
   reset the dimming, so the fade survived into the strip. The muted ink alone
   is 4.74:1 on the white band — full opacity, no fade. */
.pf__cta-strip p.pf__persona-cta-line {
	margin: 0;
	font: var(--type-body-sm);
	color: var(--invert-ink-muted);
	opacity: 1;
	max-width: 42ch;
}

/* ==========================================================================
   Stack page — the detail sections
   ========================================================================== */

/* The hero carries both an ask and a way to keep reading. One visitor has
   decided and one has not, and the page should not make the second one hunt. */
.pf__detail-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--sp-lg);
	margin-top: var(--sp-xl);
}
.pf__detail-more {
	font: var(--type-body-sm-strong);
	color: var(--ink-muted);
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-color: var(--line-strong);
}
.pf__detail-more:hover { color: var(--ink); }
.pf__detail-meta {
	margin: var(--sp-lg) 0 0;
	font: var(--type-code-sm);
	color: var(--ink-faint);
}

/* One attributed quote. Placed before the detail because it is the reason to
   keep reading, not the reward for having read. */
.pf__expert {
	margin: var(--sp-section) 0 0;
	padding: var(--sp-xl) 0 0;
	border-top: 1px solid var(--line);
	max-width: var(--measure);
}
.pf__expert blockquote {
	margin: 0;
	font: var(--type-heading-sm);
	color: var(--ink);
	text-wrap: pretty;
}
.pf__expert figcaption {
	display: flex;
	align-items: center;
	gap: var(--sp-md);
	margin-top: var(--sp-lg);
	font: var(--type-body-sm);
	color: var(--ink-muted);
}
.pf__expert-mark {
	display: grid;
	place-items: center;
	width: 40px;
	height: 40px;
	flex: none;
	border-radius: var(--r-full);
	background: rgb(var(--hn-artery-rgb) / .14);
	box-shadow: inset 0 0 0 1px rgb(var(--hn-artery-rgb) / .35);
	color: var(--hn-artery-400);
	font: var(--type-body-sm-strong);
	letter-spacing: .02em;
	text-transform: uppercase;
}
.pf__expert figcaption strong { display: block; color: var(--ink); font-weight: 500; }
.pf__expert-role { display: block; font: var(--type-caption-sm); color: var(--ink-faint); }

/* What a shelf cannot do. Numbered because these are a sequence of arguments,
   each answering the one before - not because numbers look like a system. */
.pf__different { margin-top: var(--sp-section); }

/* A trail, not a ladder.
   ==========================================================================
   Each point is a station on one hairline, with a lit node where it meets the
   copy. The rail is drawn with a background gradient on the list rather than
   an element per row, so the run between two nodes is continuous and the line
   stops at the first and last node instead of floating past them.

   The node is the design system's own StateDot - the one world element the law
   allows inside chrome - so the mark is a system component rather than a
   bullet I drew. It lights when the section is in view and is simply lit
   without script, which is why the default state is the lit one and the
   unlit state is what the `is-armed` class turns on. */
.pf__different-list {
	--trail-x: 8px;                     /* node centre, from the list's left edge */
	--trail-gap: var(--sp-xl);

	margin: var(--sp-xl) 0 0;
	padding: 0;
	list-style: none;
	display: grid;
	gap: var(--trail-gap);
	position: relative;
}
.pf__different-list li {
	display: grid;
	grid-template-columns: 40px minmax(0, 1fr);
	gap: var(--sp-lg);
	align-items: start;
	position: relative;
}
/* The run of rail between one node and the next. The first row carries none -
   there is nothing above the opening node to connect to, and a stub of line
   floating above it read as a fourth item that had lost its text. The last row
   carries none for the matching reason at the other end. */
.pf__different-list li:not(:first-child):not(:last-child)::before {
	content: "";
	position: absolute;
	left: var(--trail-x);
	top: calc(var(--sp-lg) * 1.1);
	bottom: calc(var(--trail-gap) * -1);
	width: 1px;
	background: linear-gradient(180deg, var(--line-strong), var(--line));
}
.pf__different-mark {
	display: grid;
	place-items: start center;
	padding-top: 0.35em;               /* sits on the title's first line */
}
.pf__different-mark .hn-state-dot { --dot-c: var(--hn-vein-400); }

/* The light travels. Each node comes up as its own row reaches the middle of
   the viewport, so the trail is lit behind the reader rather than all at once
   on load. `scale` and `opacity` only - no layout, no paint of the copy. */
.pf__different-list li .pf__different-mark .hn-state-dot {
	transition:
		opacity var(--dur-slow) var(--ease-arrive),
		scale var(--dur-slow) var(--ease-arrive);
}
.pf__different.is-armed .pf__different-list li .hn-state-dot { opacity: .28; scale: .7; }
.pf__different.is-armed .pf__different-list li.is-lit .hn-state-dot { opacity: 1; scale: 1; }

.pf__different-title { margin: 0; font: var(--type-heading-sm); color: var(--ink); }
.pf__different-body { margin: var(--sp-xs) 0 0; font: var(--type-body-md); color: var(--ink-muted); max-width: 58ch; }

/* The static version is already the reduced-motion alternative: every node is
   lit, and the trail is complete. Nothing is lost but the travelling light. */
@media (prefers-reduced-motion: reduce) {
	.pf__different.is-armed .pf__different-list li .hn-state-dot { opacity: 1; scale: 1; }
	.pf__different-list li .pf__different-mark .hn-state-dot { transition: none; }
}

/* Two up is too tight for a rail and its copy; stack the node above the words
   and keep the rail, so the trail survives the breakpoint. */
@media (max-width: 640px) {
	.pf__different-list li { grid-template-columns: 24px minmax(0, 1fr); gap: var(--sp-md); }
	.pf__different-list { --trail-x: 6px; }
}

/* --------------------------------------------------------------------------
   The data panel gets room to breathe above it.

   It sits directly under the persona hook, which is a paragraph, and the panel
   is a large glass slab - at the old spacing the two read as one block and the
   slab looked like an extension of the sentence. This is the panel's own top
   margin, so the gap belongs to the panel rather than to the text above it. */
#pept-data-panel { margin-top: var(--sp-section); }
@media (max-width: 640px) {
	/* A full section gap is 88px at desktop and reads as a change of subject;
	   over the shorter measure on a phone it reads as the page having ended.
	   Half of it keeps the panel clearly separate without the dead band. */
	#pept-data-panel { margin-top: var(--sp-xxl); }
}

/* One testimonial, not a wall. */
.pf__proof {
	margin: var(--sp-section) 0 0;
	padding: var(--sp-xl);
	border-radius: var(--r-lg);
	background: rgb(255 255 255 / .035);
	box-shadow: inset 0 0 0 1px var(--line);
	max-width: var(--measure);
}
.pf__proof blockquote { margin: 0; font: var(--type-heading-sm); color: var(--ink); text-wrap: pretty; }
.pf__proof figcaption { margin-top: var(--sp-md); font: var(--type-body-sm-strong); color: var(--ink-muted); }
.pf__proof-meta { color: var(--ink-faint); font-weight: 400; }
.pf__proof-meta::before { content: " \00b7 "; }

/* The same accordion the front page uses, so the two cannot drift. */
/* The FAQ runs the width of the panel above it.

   It was capped at `--measure` (720px, the single reading column) while the
   data panel that precedes it is `--content-wide` (960px). Two widths in one
   stack of sections reads as an accident rather than a decision - the reader
   sees the content column step inwards for no reason they can name. The
   questions are one line each and lose nothing by being wider; the answers keep
   their own 68ch cap, which is the thing that actually governs readability. */
.pf__faq { margin-top: var(--sp-section); max-width: var(--content-wide); }

/* --------------------------------------------------------------------------
   The FAQ accordion.

   The question is a white pill and the answer opens inside it, so the two read
   as one object rather than a question followed by a loose paragraph. Same
   construction as the Landing's accordion — one implementation, in shell.css,
   because both pages render `.pf__faq` and only one of them used to have the
   styles.

   The `[hidden]` rule is load-bearing. The answer ships hidden server-side so
   it cannot flash open before the script runs, and `display:none` here makes
   that independent of the browser's own hidden handling.
   -------------------------------------------------------------------------- */
.pf__faq { display: grid; gap: var(--sp-sm); }
.pf__faq-item,
.pf__faq-row {
	background: var(--invert-surface);
	color: var(--invert-ink);
	border-radius: var(--r-full);
	overflow: hidden;
}
/* An open row squares off: an answer inside a stadium wastes its own corners. */
.pf__faq-item:has(.pf__faq-q[aria-expanded="true"]),
.pf__faq-row:has(.pf__faq-q[aria-expanded="true"]) { border-radius: var(--r-lg); }

/* Two classes deep: the broad `.pf__main button` rule paints every button in
   main as a white pill, and it would otherwise win on specificity. */
.pf__faq .pf__faq-q {
	display: flex;
	width: 100%;
	align-items: center;
	justify-content: space-between;
	gap: var(--sp-md);
	padding: 14px 22px;
	border: 0;
	background: none;
	color: inherit;
	text-align: left;
	cursor: pointer;
	font: var(--type-heading-sm);
}
.pf__faq .pf__faq-q:hover { background: var(--c-surface-soft); }
.pf__faq .pf__faq-q:focus-visible { outline: 2px solid var(--hn-vein-600); outline-offset: -4px; }
.pf__faq .pf__glyph,
.pf__faq .pf__faq-glyph {
	font: 400 20px/1 var(--font-sans);
	color: var(--invert-ink-muted);
	flex: none;
	width: 24px;
	text-align: center;
	/* No rotation: the script swaps + for a minus, and rotating on top of that
	   turned the minus into a bare diagonal. */
}

/* The same 22px as the question, so the answer starts under the words rather
   than under the edge of the panel. Two classes deep for the same reason as
   the question: `.pf__main p` sets every paragraph's colour, which on a
   now-white panel left the answer white on white at 1.00:1. */
.pf__faq .pf__faq-a {
	margin: 0;
	padding: 0 22px 18px;
	font: var(--type-body-md);
	color: var(--invert-ink-muted);
	max-width: 68ch;
	text-wrap: pretty;
}
/* Three classes deep, and it has to be.
   `.pf__main p` sets every paragraph in main to `--ink-muted`, which on this
   lane is rgba(255, 255, 255, .7). The panel behind the answer is the inverted
   surface, so that pair rendered the answer white on white at 1.0:1 - present
   in the DOM, computed visible, and completely unreadable.
   `.pf__faq .pf__faq-a` is (0,2,0) and loses to `.pf__main p` (0,1,1) on the
   paragraph itself, so the rule has to reach the paragraph. This is the same
   collision the two comments above already record; it is what happens on any
   surface inverted against the page it sits in. */
.pf__faq .pf__faq-a p {
	margin: 0;
	color: var(--invert-ink-muted);
}
.pf__faq .pf__faq-a[hidden] { display: none; }

/* Kits that sit beside. Drawn from the catalogue, so it cannot go stale. */
.pf__beside { margin-top: var(--sp-section); }
.pf__beside-head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--sp-md);
	margin-bottom: var(--sp-lg);
}
.pf__beside-all { font: var(--type-body-sm-strong); color: var(--ink-muted); text-decoration: none; }
.pf__beside-all:hover { color: var(--ink); }
.pf__beside-grid {
	display: grid;
	gap: var(--sp-md);
	grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.pf__beside-card { text-decoration: none; color: var(--ink); }
.pf__beside-card .hn-chip { margin-bottom: var(--sp-sm); }

/* ==========================================================================
   Product data panel — "How you take it"
   Relocated from style.css, which is not in the CSS bundle and was frozen in
   Cloudflare's cache; shell.css is bundled, so the panel ships with the rest
   of the storefront.

   The panel is the INVERTED surface. Every other piece of chrome on this page
   — the FAQ pills, the closing strip — is white on the dark stage, and the
   first build put a dark, hairline-ruled table in the middle of them. It read
   as a spec sheet dropped into a shop. `data-invert` is the design system's own
   mechanism for this: it re-points the ink, line and soft-surface tokens at
   once, so nothing below has to know it flipped. The container then takes
   `--invert-surface` directly, because the system inverts everything except
   the base surface.

   Every value is a registered token. No hex, no new spacing, no new radius.
   ========================================================================== */
/* GLASS, and on a dark stage that means light-on-dark.

   The first pass made this panel an opaque white slab - the inverted surface,
   which is what every other card on the page uses. The glassmorphism asked for
   was applied to the compounds table instead of to the panel, so the thing the
   reader actually looks at stayed a flat white block and the effect was
   invisible. This is the object that needed it.

   `data-invert` is dropped in the markup at the same time: it re-points --ink
   at black, and glass on this lane has to be light. Without it the ink tokens
   fall back to the lane's own chrome, which is white.

   The three ingredients are the ones glass needs and the container had none of:
   a translucent surface, a lit edge, and a drop to float above the stage. */
#pept-data-panel {
	background: rgb(255 255 255 / .055);
	box-shadow:
		inset 0 0 0 1px rgb(255 255 255 / .14),
		inset 0 1px 0 rgb(255 255 255 / .22),
		0 12px 40px rgb(4 14 34 / .30);
	backdrop-filter: var(--frost);
	-webkit-backdrop-filter: var(--frost);
	border-radius: var(--r-lg);
	padding: var(--sp-xxl) var(--pf-gutter);
}

/* The panel's section rules divide glass, so they are hairlines of light
   rather than the lane's own line token, which is tuned for solid surfaces. */
.pf__data-panel-section + .pf__data-panel-section { border-top-color: rgb(255 255 255 / .10); }

.pf__data-panel-section { padding: var(--sp-xl) 0; }
.pf__data-panel-section + .pf__data-panel-section { border-top: 1px solid var(--line); }
.pf__data-panel-section:first-child { padding-top: 0; }
.pf__data-panel-section:last-child { padding-bottom: 0; }

.pf__data-panel .pf__data-panel-eyebrow {
	font: var(--type-code-sm);
	/* Measured on the rendered page, not on the flat fill.

	   Against a solid background any alpha of white has a known ratio, and 62%
	   looked safe. Over a backdrop-filter there is no solid background: the
	   pixel behind a glyph is whatever the blur happened to pull through, and
	   the detector read 1.2-1.3:1 here. This is the trap of glass - every
	   contrast figure computed against the fill is a guess about the backdrop.

	   Full white is the only value that cannot be wrong, because it does not
	   depend on what is behind the pane. The eyebrow stays quiet through its
	   size and letter-spacing, not through being faint.

	   Selector is two classes deep on purpose: the eyebrow is a <p>, and the
	   site-wide `.pf__main p` rule (0,1,1) was outranking the single class
	   (0,1,0) and repainting it --ink-muted (70% white = ~4.19:1 painted).
	   (0,2,0) wins the cascade, so the full-white ink actually lands. */
	color: var(--ink);
	margin: 0 0 var(--sp-lg);
	display: flex;
	align-items: center;
	gap: var(--sp-sm);
}
.pf__data-panel-eyebrow::after {
	content: "";
	flex: 1;
	height: 1px;
	background: var(--line);
}

.pf__data-panel-badges-section { padding-top: var(--sp-xl); }
.pf__data-panel-badges { display: flex; flex-wrap: wrap; gap: var(--sp-sm); }
.pf__data-panel-badge {
	display: inline-flex;
	align-items: center;
	gap: var(--sp-sm);
	padding: 8px 14px;
	border: 0;
	border-radius: var(--r-full);
	background: rgb(255 255 255 / .10);
	box-shadow:
		inset 0 0 0 1px rgb(255 255 255 / .18),
		inset 0 1px 0 rgb(255 255 255 / .26);
	color: var(--ink-muted);
	font: var(--type-code-sm);
	line-height: 1;
	white-space: nowrap;
}
.pf__data-panel-badge svg { width: 14px; height: 14px; flex: 0 0 14px; }
/* The accent carries the active state and the hairline becomes the accent, so
   the row reads as a set with some of it true rather than a list of buttons.
   The icon inherits currentColor, so it moves with its label. */
/* An active badge is stated in white on a lifted surface; the blue stays in
   the edge and the tint, where it reads as a state rather than as coloured
   type. An inactive one is grey and dimmed - visible, plainly not selected. */
.pf__data-panel-badge.is-active {
	color: var(--ink);
	background: rgb(255 255 255 / .22);
	box-shadow:
		inset 0 0 0 1px rgb(var(--hn-vein-rgb) / .85),
		inset 0 1px 0 rgb(255 255 255 / .40);
}
.pf__data-panel-badge:not(.is-active) { color: rgb(255 255 255 / .66); opacity: 1; }

/* ---------- When you will notice — the first-win timeline ---------------- */
.pf__data-panel-chart-wrap {
	border-radius: var(--r-lg);
	/* It has to lift off the panel, and it was not.

	   The first version was a card in a card, so I stripped it back to a
	   hairline - and took its background with it. Measured afterwards: the
	   chart region painted rgb(18,27,46), which is exactly what the panel
	   behind it paints, while the stat pills beside it paint rgb(42,50,67).
	   So the one region holding the curve was a hole in the panel and read as
	   a heavy dark block, and it did not match the cards it sits between.

	   Same veil as every other glass surface in this panel. It reads as a
	   region rather than a second card because it carries no second blur and no
	   drop - only the surface and the hairline. */
	/* .12 measured lighter than the stat pills it sits between (rgb(46,54,71)
	   against their rgb(42,50,67)), which inverted the hierarchy: the region
	   holding the curve outranked the numbers. It wants to be a region - lifted
	   off the panel, quieter than the cards. .07 lands between them. */
	background: rgb(255 255 255 / .07);
	box-shadow: inset 0 0 0 1px rgb(255 255 255 / .15);
	padding: var(--sp-lg);
}
.pf__data-panel-chart-head {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: var(--sp-md);
	margin-bottom: var(--sp-sm);
}
/* White for what is active, grey for what is merely visible. The peak value
   used to be blue: it sat inside the chart SVG's scope, where the curve sets
   --hn-vein-400, so the accent was inherited onto a text run. Blue is a glyph
   colour here, not a type colour - it is hard to read at caption size and it
   competes with the curve for the same signal. */
.pf__data-panel-chart-meta { font: var(--type-code-sm); color: rgb(255 255 255 / .70); }
.pf__data-panel-chart-meta--accent { color: var(--ink); }
.pf__data-panel-chart { width: 100%; height: 120px; display: block; }
/* The two labels inside the chart are SVG text, and the chart stretches
   horizontally to fill its container - so the wider the container, the more
   the glyphs stretch with it. At 390 the 1000-unit viewBox compresses them to
   about a quarter width and the character shapes stop being the shapes of
   letters; you can only read them because you already know what they say.

   Hiding them below 760 costs nothing: the stat pills directly beneath state
   "Onset 14 days" and "Peak Weeks 4-8" in clean type, and ONSET and PEAK
   WINDOW cannot mean anything but the two zones they sit on, which are
   already tinted and hatched. */
@media (max-width: 759px) {
	.pf__data-panel-chart text { display: none; }
}

/* Inset by the same 16px as the chart, so the first and last labels line up
   with the plot's own edges instead of the wrapper's. The wrapper scrolls at
   narrow widths, and a label sitting hard against that edge is the first thing
   to disappear under it. */
.pf__data-panel-axis {
	display: flex;
	justify-content: space-between;
	margin-top: var(--sp-xs);
	padding: 0 var(--sp-lg);
	font: var(--type-code-sm);
	/* 70% white measured 4.01:1 against the painted chart surface behind the
	   labels — under 4.5:1 at 14px. Full white is the only value that cannot be
	   wrong over the backdrop-filter, and it keeps the scale legible without
	   touching its size or spacing. */
	color: rgb(255 255 255);
}
/* The ticks are 1px anchors and every label is centred on its anchor, which is
   correct for the interior ones and wrong at the ends: the last anchor sits on
   the right edge, so half of "12w" hung outside the chart. The first and last
   labels align to their own edges instead, so the scale reads 0w ... 12w
   inside the plot rather than half overhanging it. */
.pf__data-panel-axis span { width: 1px; }
.pf__data-panel-axis span::before {
	content: attr(data-label);
	display: block;
	transform: translateX(-50%);
	white-space: nowrap;
}
.pf__data-panel-axis span:first-child::before { transform: translateX(0); }
.pf__data-panel-axis span:last-child::before { transform: translateX(-100%); }
.pf__data-panel-stat-pills {
	/* Two now, not three - the Course pill was removed as a duplicate of the
	   facts row. Flex rather than a fixed grid: a grid track count that no
	   longer matches the children leaves empty tracks, and this row should stay
	   even whatever a kit's panel ends up holding. Each pill takes an equal
	   share and they divide the width between them. */
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-sm);
	margin-top: var(--sp-md);
}
.pf__data-panel-stat-pill { flex: 1 1 180px; }
.pf__data-panel-stat-pill {
	display: flex;
	flex-direction: column;
	gap: var(--sp-xxs);
	padding: var(--sp-md) var(--sp-lg);
	border-radius: var(--r-md);
	background: rgb(255 255 255 / .10);
	box-shadow:
		inset 0 0 0 1px rgb(255 255 255 / .18),
		inset 0 1px 0 rgb(255 255 255 / .28);
}
.pf__data-panel-stat-pill .k { font: var(--type-code-sm); color: rgb(255 255 255 / .70); }
.pf__data-panel-stat-pill .v { font: var(--type-heading-sm); color: var(--ink); }

@media (max-width: 760px) {
	#pept-data-panel { padding: var(--sp-xl) var(--sp-lg); }
	/* No min-width, and no scrolling axis.

	   The chart carried `min-width: 480px` against a container of 262px at
	   390, so 55% of the plot was off-screen behind a horizontal scroll - and a
	   timeline you have to scroll is not a timeline, you cannot see the shape
	   of the thing. Worse, the axis labels are HTML rather than SVG: they sat
	   outside the scroll area, so the visible fragment read "8" while the plot
	   showed month ten.

	   The SVG scales to any width already - it is `preserveAspectRatio="none"`
	   over a 1000x120 viewBox, so the curve and the gridlines stretch to fit
	   and only the two SVG text labels (ONSET, PEAK WINDOW) stretch with them.
	   Letting it fill the container costs that slight stretch and buys a chart
	   the reader can see all of. */
	.pf__data-panel-chart-wrap { overflow-x: visible; }
	.pf__data-panel-chart { min-width: 0; }
	/* flex now, not grid - `grid-template-columns` would be inert here and the
	   pills would sit side by side on a phone at 160px each. */
	.pf__data-panel-stat-pills { flex-direction: column; }
	.pf__data-panel-stat-pill { flex: 0 0 auto; }
}

/* ---------- Motion -------------------------------------------------------
   The panel answers "when will I notice". The chart is the only element that
   carries that answer as a shape, so it is the only one that animates: the
   curve draws left to right, the direction the reader's own timeline runs. The
   cards are statements, not a sequence, so they state — a short rise, no more.

   `--chart-draw` is registered and unitless. An unregistered custom property
   is a token, not a number, and animating one snaps between keyframes instead
   of moving. `pathLength="1"` on the path normalises the dash maths to 0–1, so
   a redrawn curve needs no retuning here.

   Everything is opacity, translate or stroke-dashoffset: nothing in the layout
   path. The `is-visible` class shell.js toggles stops off-screen work, and the
   static version is already the reduced-motion alternative — every fact is a
   still value in a still card. */
@property --chart-draw {
	syntax: "<number>";
	inherits: false;
	initial-value: 0;
}
/* The base state is visible. `--panel-reveal` starts at 1 so a reader whose
   script never runs (JavaScript disabled, blocked, or an exception before this
   point) still gets every badge and pill. The hidden state is armed only by the
   reveal script itself: shell.js adds `pf-reveal` to <html> the moment it is
   about to observe, and only when IntersectionObserver exists and motion is
   allowed. Without that class nothing is ever hidden — the animation is the
   enhancement, not the content's only path to visibility. */
.pf__data-panel-section { --panel-reveal: 1; }
html.pf-reveal .pf__data-panel-section { --panel-reveal: 0; }
html.pf-reveal .pf__data-panel-section.is-visible { --panel-reveal: 1; }

.pf__data-panel-chart .curve {
	stroke-dasharray: 1;
	stroke-dashoffset: calc(1 - var(--chart-draw));
}
.pf__data-panel-section.is-visible .pf__data-panel-chart .curve {
	animation: pf-chart-draw var(--dur-slow) var(--ease-arrive) 120ms both;
}
.pf__data-panel-section.is-visible .pf__data-panel-chart .curve-fill {
	animation: pf-chart-fill var(--dur-slow) var(--ease-arrive) 380ms both;
}
@keyframes pf-chart-draw { from { --chart-draw: 0; } to { --chart-draw: 1; } }
@keyframes pf-chart-fill { from { opacity: 0; } to { opacity: 1; } }

.pf__data-panel-badge,
.pf__data-panel-stat-pill {
	opacity: var(--panel-reveal);
	translate: 0 calc((1 - var(--panel-reveal)) * 8px);
	transition:
		opacity var(--dur-base) var(--ease-arrive),
		translate var(--dur-base) var(--ease-arrive);
}
.pf__data-panel-badge:nth-child(3n + 2),
.pf__data-panel-stat-pill:nth-child(2) { transition-delay: 60ms; }
.pf__data-panel-badge:nth-child(3n + 3),
.pf__data-panel-stat-pill:nth-child(3) { transition-delay: 120ms; }

@media (prefers-reduced-motion: reduce) {
	.pf__data-panel-section,
	html.pf-reveal .pf__data-panel-section { --panel-reveal: 1; }
	.pf__data-panel-section.is-visible .pf__data-panel-chart .curve,
	.pf__data-panel-section.is-visible .pf__data-panel-chart .curve-fill { animation: none; }
	.pf__data-panel-chart .curve { stroke-dashoffset: 0; }
	.pf__data-panel-badge,
	.pf__data-panel-stat-pill { transition: none; }
}

