/* ==========================================================================
   Blokes vs Bookies — site components.

   Built strictly on tokens.css. No raw hex, size or radius in this file.
   Idiom follows the website design handoff: Oswald condensed uppercase
   headings, Space Grotesk body, pill buttons, SQUARE cards, and the two
   signature textures (the 64px vertical hairline grid and the bloom).

   Two rules worth restating because breaking them is a defect, not a
   preference:
     - There is not a single box-shadow in this system. Depth is the
       ground -> raised colour step plus 1px hairlines. An inset hairline is
       not elevation.
     - --field-up / --field-down (green / red) are reserved for win-loss on
       graded results. Never decorative, never a validation state, never a CTA.
   ========================================================================== */

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

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

body {
  margin: 0;
  font-family: var(--body);
  font-size: var(--text-medium);
  line-height: var(--text-lh);
  background-color: var(--ground);
  color: var(--bone);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, svg { max-width: 100%; display: block; }
img { border-radius: var(--radius-image); }
a { color: inherit; text-decoration: none; transition: color var(--dur-fast) ease-in-out; }
p, ul, ol, figure { margin: 0; }
button { font: inherit; cursor: pointer; }
input, select { font: inherit; }

/* --- Focus ---------------------------------------------------------------
   The design source shipped `outline: none` on every interactive element with
   no replacement. That is a known accessibility defect and is fixed here, not
   reproduced. One visible ring, everywhere.                                */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}
[data-theme="light"] .bvb-feature :focus-visible,
.bvb-feature :focus-visible { outline-color: var(--feature-text, var(--bone)); }

/* --- Display type --------------------------------------------------------
   Authored in sentence case, uppercased by CSS. Never type in caps: the
   transform is a presentation concern so copy stays editable.              */

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--display);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  color: var(--bone);
  text-wrap: pretty;
}

h1 { font-size: var(--text-h1); line-height: var(--text-h1-lh); letter-spacing: var(--text-h1-ls); }
h2 { font-size: var(--text-h2); line-height: var(--text-h2-lh); letter-spacing: var(--text-h2-ls); }
h3 { font-size: var(--text-h3); line-height: var(--text-h3-lh); letter-spacing: var(--text-h3-ls); }
h4 { font-size: var(--text-h4); line-height: var(--text-h4-lh); letter-spacing: var(--text-h4-ls); }
h5 { font-size: var(--text-h5); line-height: var(--text-h5-lh); letter-spacing: var(--text-h5-ls); }
h6 { font-size: var(--text-h6); line-height: var(--text-h6-lh); letter-spacing: var(--text-h6-ls); }

/* On the LIGHT theme only, H1 and H2 carry the blue — the identity reaches
   past accent-only without costing body copy any contrast. */
[data-theme="light"] h1,
[data-theme="light"] h2 { color: var(--accent); }

.text-large   { font-size: var(--text-large); }
.text-medium  { font-size: var(--text-medium); }
.text-regular { font-size: var(--text-regular); }
.text-small   { font-size: var(--text-small); }
.text-tiny    { font-size: var(--text-tiny); }

.muted  { color: var(--w60); }
.accent { color: var(--accent); }
/* Numerals: tabular, bold, and in the BODY family — not the display face. */
.mono, .num, td.num, [data-numeral] { font-variant-numeric: tabular-nums; }
.num, [data-numeral] { font-family: var(--body); font-weight: 700; }
.nowrap { white-space: nowrap; }
.center { text-align: center; }
.hidden { display: none !important; }
/* Components that set their own display would otherwise beat the UA rule for
   [hidden] and stay on screen. */
[hidden] { display: none !important; }
.mt-6   { margin-top: 1.25rem; }
.mt-10  { margin-top: 1.75rem; }
.mt-16  { margin-top: 2.25rem; }

/* --- Layout ---------------------------------------------------------------
   Every section carries the signature grid; hero sections carry the bloom
   above it. Consecutive sections are separated by a hairline, never a gap.  */

.section {
  position: relative;
  padding: var(--section-py) var(--section-px);
  background-color: var(--ground);
  background-image: var(--grid), var(--rule);
  /* clip, NOT hidden: the ambient wash below is an oversized transformed box
     and would otherwise add scrollable overflow. `hidden` would contain it too
     but creates a scroll container, which silently kills `position: sticky` on
     the lesson rail inside. `clip` contains it without that side effect. */
  overflow: clip;
}
.section--tight { padding-block: var(--section-py-tight); }
.section--wash  { background-color: var(--wash); }
.section > *    { position: relative; z-index: 1; }
.section + .section { border-top: var(--card-divider-width) solid var(--hair-soft); }

/* Ambient wash — a single soft bloom that drifts across the section on a very
   long cycle. It is transform-only so it stays on the compositor, and it sits
   at z-index 0 under the content layer. Atmosphere, not an event: if you can
   tell it is moving while reading, it is turned up too far. */
.section::before {
  content: "";
  position: absolute;
  inset: -12%;
  z-index: 0;
  pointer-events: none;
  background-image: var(--ambient);
  animation: bvb-wander var(--wander) ease-in-out infinite alternate;
}
@keyframes bvb-wander {
  from { transform: translate3d(-3%, -2%, 0) scale(1); }
  to   { transform: translate3d(3%, 2%, 0) scale(1.15); }
}
/* The feature flood is already a solid colour statement — a wash on top of it
   just muddies the one panel that is meant to be flat. */
.bvb-feature::before { content: none; }

.hero { background-image: var(--bloom), var(--grid), var(--rule); }

/* A route is one or two sections and does not become a scroll journey. The
   opening section of a marketing route fills the viewport once, and everything
   after it is tight. Content-led routes (the calculators, the ledger tables, a
   lesson, the terms) do not opt in — forcing a full screen there would waste
   one, not save one. */
main[data-fit] { display: flex; flex-direction: column; }
.section--fill {
  min-height: calc(100vh - var(--nav-h) - var(--footer-h));
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* Below the lg breakpoint a full-height lock just strands content, so it lifts. */
@media (max-width: 991px) { .section--fill { min-height: 0; } }

.container { margin-inline: auto; max-width: var(--container-xxl); width: 100%; }
.container--lg { max-width: var(--container-lg); }
.container--xl { max-width: var(--container-xl); }

/* minmax(0, 1fr) throughout: a bare 1fr floors at min-content, so one long
   unbreakable string in a card blows the whole track out of the container. */
.grid { display: grid; gap: 20px; }
.grid-2, .grid-3, .grid-4 { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 480px) {
  .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 768px) {
  .grid-2, .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 992px) {
  .grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.split { display: grid; gap: 32px; align-items: center; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 992px) { .split { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(40px, 6vw, 88px); } }

.section-head { max-width: var(--container-lg); margin-inline: auto; text-align: center; }
.section-head p:not(.eyebrow) {
  color: var(--w60);
  margin-top: 1rem;
  font-size: var(--text-medium);
  text-wrap: pretty;
}

.lead, .lede {
  font-size: var(--lede-size);
  line-height: var(--lede-lh);
  color: var(--w60);
  text-wrap: pretty;
}
/* Display type runs at line-height .92, so a paragraph set straight after a
   heading collides with its descenders. */
h1 + .lead, h1 + p, h2 + .lead, h2 + p, h3 + .lead, h3 + p { margin-top: 20px; }
.section-head h2 + p { margin-top: 1rem; }

/* --- Eyebrow / badge ------------------------------------------------------ */

.eyebrow {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-size: 15px;
  line-height: 1.2;
  color: var(--accent);
  margin-bottom: 16px;
}
/* The home hero eyebrow is the one grey exception. */
.eyebrow--quiet { color: var(--w60); }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  border-radius: var(--radius-badge);
  border: var(--card-border-width) solid var(--hair);
  padding: 3px 12px;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: var(--text-small);
  line-height: 1.5;
  color: var(--w60);
  max-width: 100%;
}
/* Cards and plans are flex columns, so a badge would otherwise stretch the
   full width and stop reading as a chip. */
.card > .badge, .plan > .badge, .lock-gate > .badge { align-self: flex-start; }
.badge--accent { border-color: var(--accent); color: var(--accent); }
.badge--good   { border-color: var(--field-up);   color: var(--field-up); }
.badge--warn   { border-color: var(--gold);       color: var(--gold); }
.badge--stop   { border-color: var(--field-down); color: var(--field-down); }

/* --- Buttons --------------------------------------------------------------
   Pill, 1px border, display family, uppercase, tracked. Hover is a colour
   shift only — there is no pressed state and no scale-down anywhere.
   Vertical padding is the spec's; the horizontal is opened up and a
   min-height added so a 100px-radius pill still clears a 40px tap target. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  white-space: nowrap;
  border-radius: var(--radius-button);
  padding: 6px 20px;
  min-height: 40px;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: var(--text-regular);
  border: var(--card-border-width) solid transparent;
  transition: all var(--dur-fast) ease-in-out;
}
.btn--primary {
  background-color: var(--accent);
  border-color: var(--accent);
  color: var(--color-accent-text);
}
.btn--primary:hover { background-color: var(--accent-lt); border-color: var(--accent-lt); color: var(--color-accent-text); }

/* Secondary fills faintly with the accent on light so it reads as a control
   rather than an outline; on dark it stays transparent. */
.btn--secondary {
  background-color: transparent;
  border-color: var(--accent);
  color: var(--accent);
}
[data-theme="light"] .btn--secondary { background-color: color-mix(in srgb, var(--accent), transparent 92%); }
.btn--secondary:hover { background-color: color-mix(in srgb, var(--accent), transparent 86%); border-color: var(--accent); color: var(--accent); }

.btn--alternate { background-color: var(--bone); border-color: var(--bone); color: var(--ground); }
.btn--alternate:hover { background-color: var(--w40); border-color: var(--w40); }

.btn--ghost { background: none; border-color: transparent; padding-inline: 0; min-height: 0; color: var(--accent); }
.btn--ghost:hover { background: none; color: var(--accent-lt); }

.btn--sm { padding: 4px 14px; min-height: 32px; font-size: var(--text-small); }
.btn--block { width: 100%; }
.btn[disabled], .btn--disabled { opacity: 0.5; pointer-events: none; }

.btn-row { display: flex; flex-wrap: wrap; gap: 12px; }

/* --- Surfaces -------------------------------------------------------------
   A card is a colour step with an inset hairline. Square, always. */

.card, .panel {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 24px;
  border-radius: var(--radius-card);
  border: 0;
  background-color: var(--raised);
  box-shadow: inset 0 0 0 1px var(--hair-soft);
}
[data-theme="light"] .card,
[data-theme="light"] .panel { box-shadow: inset 0 0 0 1px var(--hair); }
@media (min-width: 768px) { .card, .panel { padding: 28px; } }
.card h3, .card h4, .card h5 { margin: 0; }
.card p { color: var(--w60); font-size: var(--text-regular); text-wrap: pretty; }

/* Feature cell — a rule above, no fill. Used in 3/4-up grids. */
.card--feature {
  background: none;
  box-shadow: none;
  border-top: var(--card-divider-width) solid var(--hair);
  border-radius: 0;
  padding: 18px 0 0;
  gap: 0.6rem;
  transition: border-color var(--dur-fast) ease-in-out;
}
a.card--feature:hover { border-top-color: var(--accent); }
a.card--feature:hover h4 { color: var(--accent); }
[data-theme="light"] .card--feature { box-shadow: none; }

.divider { height: var(--card-divider-width); background-color: var(--hair); border: 0; margin: 0; }

/* Terminal-style stacked rows, hairline-separated. */
.rows { display: flex; flex-direction: column; gap: var(--card-divider-width); background-color: var(--hair-soft); }
.rows > * { display: flex; align-items: center; gap: 1rem; padding: 12px 14px; background-color: var(--ground); }
.rows__no { font-family: var(--display); font-weight: var(--font-weight-bold); font-size: var(--text-h6); min-width: 2rem; color: var(--accent); }

.pulse { width: 9px; height: 9px; border-radius: var(--radius-badge); background-color: var(--accent); animation: bvb-pulse var(--pulse) ease-in-out infinite; }
@keyframes bvb-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent), transparent 55%); }
  50%      { box-shadow: 0 0 0 11px color-mix(in srgb, var(--accent), transparent 100%); }
}

/* --- The feature flood ----------------------------------------------------
   A full-bleed accent panel. It is a NESTED THEME, not a colour override:
   the veils are re-struck against the flood so every descendant that reads
   --w60 / --w40 / --hair fixes itself at once. Ink alphas over #2438F5
   measure 2.0:1 — a contrast failure. The values below measure 5.8:1 and
   4.9:1. Keep them at or above that.                                       */

.bvb-feature {
  background-color: var(--feature);
  background-image: none;
  color: var(--feature-text, var(--bone));
  --w60: color-mix(in srgb, var(--feature-text, #fff) 88%, transparent);
  --w40: color-mix(in srgb, var(--feature-text, #fff) 78%, transparent);
  --w20: color-mix(in srgb, var(--feature-text, #fff) 44%, transparent);
  --hair: color-mix(in srgb, var(--feature-text, #fff) 34%, transparent);
  --hair-soft: color-mix(in srgb, var(--feature-text, #fff) 22%, transparent);
  --bone: var(--feature-text, #fff);
  --accent: var(--feature-text, #fff);
  --accent-lt: var(--feature-text, #fff);
}
/* On the dark scope the flood colour is nearly the page, so it steps up to
   raised instead of flooding. */
[data-theme="cyan"] .bvb-feature { background-color: var(--raised); }
[data-theme="light"] .bvb-feature h1,
[data-theme="light"] .bvb-feature h2 { color: var(--feature-text, var(--bone)); }
.bvb-feature h1, .bvb-feature h2, .bvb-feature h3,
.bvb-feature h4, .bvb-feature h5, .bvb-feature h6 { color: var(--bone); }
.bvb-feature p { color: var(--w60); }
[data-theme="light"] .bvb-feature .btn--secondary { background-color: transparent; border-color: var(--w40); color: var(--bone); }
[data-theme="light"] .bvb-feature .btn--secondary:hover { background-color: color-mix(in srgb, var(--feature-text, #fff), transparent 88%); }
[data-theme="light"] .bvb-feature .btn--primary { background-color: var(--bone); border-color: var(--bone); color: var(--feature); }
[data-theme="light"] .bvb-feature .btn--primary:hover { background-color: var(--w60); border-color: var(--w60); }

/* The proof band inside the flood: the claim, then the ledger figures behind
   a rule that reads as a column divider on wide viewports. */
.record-band { display: grid; gap: clamp(24px, 4vw, 56px); align-items: center; }
@media (min-width: 768px) { .record-band { grid-template-columns: minmax(0, auto) minmax(0, 1fr); } }
.record-band__claim {
  font-family: var(--display); font-weight: 700; text-transform: uppercase;
  font-size: var(--text-h5); line-height: 1.05; color: var(--bone);
  max-width: 22ch; margin: 0;
}
@media (min-width: 768px) {
  .record-band > .grid { border-left: 1px solid var(--hair); padding-left: clamp(28px, 4vw, 56px); }
}

/* --- StatSlot -------------------------------------------------------------
   A performance figure that has NOT come from the graded ledger renders a
   short rule — never a 0, an em-dash, a "TBD" or an invented number. When
   real data arrives the rule is swapped for a tabular figure; the rule stays
   as the loading and no-data state.                                        */

.stat { display: flex; flex-direction: column; gap: 10px; }
.stat__value {
  font-family: var(--body);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: var(--text-h4);
  line-height: 1;
  color: var(--bone);
}
.stat__label { color: var(--w60); font-size: var(--text-regular); text-wrap: pretty; }
.stat--good .stat__value { color: var(--field-up); }
.stat--bad  .stat__value { color: var(--field-down); }

.bvb-slot { display: block; width: 46px; height: 4px; background-color: var(--w40); margin-bottom: 12px; }

/* --- Honest image slot ----------------------------------------------------
   No photography has ever been supplied. Rather than dress the page with
   stock imagery, each slot states what belongs there. Swapping in a real
   photograph is a one-line change per slot.                                */

.bvb-shot {
  position: relative;
  display: flex;
  align-items: flex-end;
  background: linear-gradient(150deg, var(--raised), var(--ink));
  box-shadow: inset 0 0 0 1px var(--hair-soft);
  overflow: hidden;
}
[data-theme="light"] .bvb-shot { background: linear-gradient(150deg, var(--raised-2), var(--raised)); }
.bvb-shot::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(70% 60% at 78% 18%, color-mix(in srgb, var(--accent), transparent 92%) 0%, transparent 70%);
}
.bvb-shot span {
  position: relative;
  z-index: 1;
  padding: 18px 20px;
  font-size: 12.5px;
  line-height: 1.45;
  letter-spacing: 0.02em;
  color: var(--w40);
}

/* --- The hero market field ------------------------------------------------
   Stepped price lines drifting over the signature grid. Red and green here
   are the win-loss coding of the public ledger, so the hero says "wins and
   losses, both published" rather than decorating. The drift is LINEAR and
   infinite — never eased, so it reads as continuous rather than animated.  */

.bvb-field { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
.bvb-field-scroll { display: flex; width: 200%; height: 100%; animation: bvb-drift var(--drift) linear infinite; }
.bvb-field-scroll > svg { flex: 0 0 50%; height: 100%; display: block; }
@keyframes bvb-drift { from { transform: translateX(0); } to { transform: translateX(-50%); } }

.bvb-field-vignette { position: absolute; inset: 0; background: linear-gradient(180deg, var(--ground) 0%, transparent 22%, transparent 74%, var(--ground) 100%); }
/* Offset left so the headline sits on near-solid ground while the colour
   still reads at the edges. */
.bvb-field-scrim { position: absolute; inset: 0; background: radial-gradient(72% 62% at 42% 50%, var(--ground) 0%, color-mix(in srgb, var(--ground), transparent 6%) 52%, transparent 88%); }
.bvb-marker { position: absolute; top: 0; bottom: 0; left: 72%; width: 1px; background: var(--hair); }
.bvb-marker span {
  position: absolute; top: 41%; left: 50%;
  width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px;
  border-radius: var(--radius-badge);
  background: var(--accent);
  animation: bvb-pulse var(--pulse) ease-in-out infinite;
}
[data-theme="light"] .bvb-field { opacity: 0.42; }

/* The same field behind a page that is mostly reading rather than a poster
   headline. Turned down and slowed, so it stays weather rather than an event. */
.bvb-field--quiet { opacity: 0.5; --drift: 96s; }
[data-theme="light"] .bvb-field--quiet { opacity: 0.24; }

@media (prefers-reduced-motion: reduce) {
  .bvb-field-scroll { animation: none; }
  .bvb-marker span, .pulse { animation: none; }
  html { scroll-behavior: auto; }
}

/* --- The mark -------------------------------------------------------------
   One official mark, square. Rendered as a CSS mask so it takes the theme
   colour. 44px in the nav, 34px in the footer. Do not letterbox it.        */

.mark {
  display: block;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  background-color: var(--bone);
  -webkit-mask: url("/assets/mark/bvb-arrow-mono.svg") center / contain no-repeat;
  mask: url("/assets/mark/bvb-arrow-mono.svg") center / contain no-repeat;
}
.mark--sm { width: 34px; height: 34px; background-color: var(--w40); }
a:hover > .mark { background-color: var(--accent); }

/* --- Navbar ---------------------------------------------------------------
   Sticky, 76px, translucent ink with a blur and a hairline under it.       */

.navbar {
  position: sticky;
  top: 0;
  z-index: 999;
  display: flex;
  align-items: center;
  gap: 1rem;
  height: var(--nav-h);
  padding-inline: var(--section-px);
  background-color: color-mix(in srgb, var(--ink), transparent 6%);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-bottom: var(--card-border-width) solid var(--hair-soft);
}

.nav-links { display: none; align-items: center; gap: 2px; list-style: none; margin: 0 0 0 auto; padding: 0; }
@media (min-width: 992px) { .nav-links { display: flex; } }
.nav-links a {
  display: block;
  padding: 9px 14px;
  border-radius: var(--radius-button);
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 14px;
  white-space: nowrap;
  color: var(--w60);
  transition: color var(--dur-fast) ease-in-out, background-color var(--dur-fast) ease-in-out;
}
.nav-links a:hover { color: var(--accent); }
.nav-links a[aria-current="page"] {
  color: var(--accent);
  background-color: color-mix(in srgb, var(--accent), transparent 90%);
}

/* --- Nav dropdown ---------------------------------------------------------
   Hover on pointer, focus-within on keyboard. No JS state at all: nothing to
   leave stuck open when a route changes, and Escape works because blurring
   the link closes it. The panel is a shortcut into the section — the parent
   link still goes to the section's own page.                               */

.nav-item { position: relative; }
.nav-item > a { display: flex; align-items: center; gap: 6px; }
.nav-item__caret {
  width: 9px; height: 6px; opacity: 0.6;
  transition: transform var(--dur-fast) var(--ease-out);
}
.nav-item:hover .nav-item__caret, .nav-item:focus-within .nav-item__caret { transform: rotate(180deg); }

.nav-menu {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 50;
  min-width: 19rem;
  /* The 8px is the reach: the gap between the link and the panel is inside the
     panel's own box, so the pointer never crosses dead space on its way down. */
  padding-top: 8px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              visibility 0s linear var(--dur-fast);
}
.nav-item:hover > .nav-menu, .nav-item:focus-within > .nav-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition-delay: 0s;
}
/* A flat plate, no grid image: the section ruling showing through a floating
   panel reads as transparency rather than as texture. */
.nav-menu > ul {
  list-style: none; margin: 0; padding: 8px;
  background-color: var(--ink);
  border: var(--card-border-width) solid var(--hair);
}
/* Nine products in one column is a scroll, so the wide panel columns them and
   the group heads do the sorting. Centred under its link and clamped to the
   viewport: anchored left it runs off the right edge on a 1000px window. */
.nav-menu--wide {
  min-width: 46rem;
  max-width: calc(100vw - 2 * var(--section-px));
  left: 50%;
  transform: translateX(-50%) translateY(-6px);
}
.nav-item:hover > .nav-menu--wide, .nav-item:focus-within > .nav-menu--wide {
  transform: translateX(-50%) translateY(0);
}
.nav-menu--wide > ul { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px 8px; align-items: start; }
/* The trailing "compare every product" row spans the panel and reads as a
   footer to it rather than a fourth column. */
.nav-menu--wide > ul > li:last-child {
  grid-column: 1 / -1;
  border-top: var(--card-divider-width) solid var(--hair-soft);
  margin-top: 4px; padding-top: 4px;
}
.nav-menu__group > ul { list-style: none; margin: 0; padding: 0; }
.nav-menu__head {
  display: block; padding: 10px 12px 6px;
  font-family: var(--display); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.08em; font-size: var(--text-tiny); color: var(--w40);
}
.nav-menu a {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 0 10px; padding: 9px 12px; border-radius: 0;
  text-transform: none; letter-spacing: 0; white-space: normal;
  transition: background-color var(--dur-fast) ease-in-out, color var(--dur-fast) ease-in-out;
}
.nav-menu a:hover { background-color: var(--raised); color: var(--accent); }
.nav-menu__label { font-family: var(--display); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; font-size: var(--text-small); color: var(--bone); }
.nav-menu a:hover .nav-menu__label { color: var(--accent); }
.nav-menu__note { grid-column: 1 / -1; font-family: var(--body); font-size: var(--text-tiny); color: var(--w40); line-height: 1.4; }
/* first baseline, not centre: a name that wraps to two lines would otherwise
   drag its price down to sit against the second one. */
.nav-menu__meta {
  font-family: var(--body); font-variant-numeric: tabular-nums;
  font-size: var(--text-tiny); color: var(--w40);
  align-self: first baseline; white-space: nowrap;
}
/* The underline sweep belongs to top-level nav links only. */
.nav-menu a::after { content: none; }

/* In the drawer there is no hover — the panel is just an indented sub-list. */
.nav-mobile .nav-menu,
.nav-mobile .nav-menu--wide {
  position: static; opacity: 1; visibility: visible; transform: none;
  min-width: 0; max-width: none; left: auto; padding: 0 0 8px 14px;
}
.nav-mobile .nav-menu > ul { background: none; border: 0; padding: 0; display: block; }
.nav-mobile .nav-menu--wide > ul > li:last-child { margin-top: 0; padding-top: 6px; }
/* .nav-mobile a is uppercase and wins on source order — the price is a number
   and has no business being shouted. */
.nav-mobile .nav-menu a { padding: 8px 0; text-transform: none; letter-spacing: 0; }
.nav-mobile .nav-item__caret { display: none; }
.nav-mobile .nav-menu__note { display: none; }

.nav-actions { display: none; align-items: center; gap: 10px; margin-left: 14px; }
@media (min-width: 992px) { .nav-actions { display: flex; } }

/* Theme toggle — a 40px icon pill. Light is the default and is not derived
   from the OS; this control is the only way into dark. */
.theme-toggle {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: var(--radius-button);
  border: var(--card-border-width) solid var(--hair);
  background: transparent;
  color: var(--w60);
  transition: all var(--dur-fast) ease-in-out;
}
.theme-toggle:hover { color: var(--accent); border-color: var(--accent); }
.theme-toggle svg { width: 20px; height: 20px; fill: currentColor; }
.theme-toggle .icon-dark { display: none; }
[data-theme="cyan"] .theme-toggle .icon-dark  { display: block; }
[data-theme="cyan"] .theme-toggle .icon-light { display: none; }

.nav-toggle {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: 44px; height: 44px; margin-left: auto;
  border: var(--card-border-width) solid var(--hair);
  border-radius: var(--radius-button);
  background: none;
  color: var(--bone);
}
@media (min-width: 992px) { .nav-toggle { display: none; } }
.nav-toggle span { margin: 2.5px 0; height: 2px; width: 18px; background-color: currentColor; }

.nav-mobile {
  display: none;
  position: absolute; top: 100%; left: 0; right: 0;
  padding: 20px var(--section-px) 28px;
  border-bottom: var(--card-border-width) solid var(--hair-soft);
  background-color: var(--ink);
  background-image: var(--grid);
}
.nav-mobile.is-open { display: block; }
@media (min-width: 992px) { .nav-mobile.is-open { display: none; } }
.nav-mobile ul { list-style: none; margin: 0 0 20px; padding: 0; display: flex; flex-direction: column; }
.nav-mobile li + li { border-top: var(--card-divider-width) solid var(--hair-soft); }
.nav-mobile a {
  display: block; padding: 12px 0;
  font-family: var(--display); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.08em; font-size: var(--text-h6); color: var(--w60);
}
.nav-mobile a[aria-current="page"] { color: var(--accent); }
/* At 375px the three controls will not sit on one line, and nowrap pills push
   past the gutter rather than wrapping. Let them share the row instead. */
.nav-mobile .btn-row { gap: 10px; align-items: center; }
.nav-mobile .btn-row .btn { flex: 1 1 auto; justify-content: center; }
.nav-mobile .theme-toggle { flex: 0 0 auto; }

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

/* --- Home hero ------------------------------------------------------------
   The one place on the site that is staged rather than laid out.

   Centred and symmetrical, which nothing else here is: every other route is
   left-aligned working copy, so symmetry is what marks this as the front of
   house. The stage holds only five things and the climb runs the full width
   underneath as the horizon. Space is the material — the gaps are the design,
   so resist filling them.

   It breaks the 1240px container on purpose. A poster-scale headline inside a
   fixed measure is just a big headline in a box.                            */

/* Every vertical measurement here is in vh, because the composition has one
   hard requirement: the headline and the horizon share the opening frame. A
   px gap that looks generous at 1080 is what pushes the climb off a laptop. */
.hero--home {
  padding-inline: 0;
  padding-block: clamp(20px, 3.5vh, 48px);
  gap: clamp(18px, 2.6vh, 44px);
  justify-content: center;
}
.hero--home .hero__stage {
  width: 100%;
  max-width: 1600px;
  margin-inline: auto;
  padding-inline: var(--section-px);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(9px, 1.15vh, 18px);
}

.hero--home h1 {
  /* Smaller than --text-hero: this headline is a sentence, not a two-word
     stamp. At the token size each half wrapped to two lines and the horizon
     went off screen. Sized so both halves set on one line each. */
  font-size: max(30px, min(6.4vw, 9vh, 118px));
  line-height: var(--text-hero-lh);
  letter-spacing: var(--text-hero-ls);
  /* Oswald is condensed, so the two lines set as one block with no gap between
     them — they read as a single stamped statement rather than two headings. */
  text-wrap: balance;
}
/* A wide measure on purpose. Narrow enough to be a column and this becomes
   five stacked lines that push the horizon off screen; the copy earns more
   room than a book measure here because it only has to be read once. */
.hero--home .hero__lede {
  max-width: min(76ch, 62vw);
  font-size: clamp(15px, 1.2vw, 19px);
  line-height: 1.5;
  margin-top: clamp(4px, 0.8vh, 12px);
}
.hero--home .hero__cta { justify-content: center; margin-top: clamp(6px, 1.1vh, 16px); }
.hero--home .hero__note { max-width: 62ch; }

/* The horizon. Full-bleed to the viewport edge — a chart that stops at a
   container margin is a widget; one that runs off both edges is a landscape. */
.hero__climb {
  width: 100%;
  margin: 0;
  padding-inline: clamp(12px, 2.5vw, 40px);
}
.hero__climb-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  max-width: 1440px;
  margin: 0 auto 6px;
}
.hero__climb-range { display: inline-flex; align-items: center; gap: 0.5rem; }
.hero__climb .climb { max-width: 1440px; margin-inline: auto; }
.hero__climb figcaption {
  max-width: 1440px;
  margin: 10px auto 0;
  text-align: center;
}
/* The band lands last, after the copy has settled. */
.hero__climb { animation: bvb-enter var(--dur-enter-line) var(--ease-out) 880ms backwards; }

@media (max-width: 991px) {
  .hero__climb-head { flex-direction: column; align-items: flex-start; gap: 0.25rem; }
}
@media (prefers-reduced-motion: reduce) { .hero__climb { animation: none; } }

.hero__grid { display: grid; gap: 32px; grid-template-columns: minmax(0, 1fr); align-items: center; width: 100%; }
@media (min-width: 992px) { .hero__grid { grid-template-columns: 1.05fr .95fr; gap: clamp(40px, 6vw, 88px); } }
.hero__grid > * { min-width: 0; }
.hero p.lead { max-width: 34rem; margin-top: 20px; }

/* --- Pathway -------------------------------------------------------------- */

.pathway { display: flex; flex-direction: column; }
.pathway__step {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.5rem 2rem;
  padding: 14px 0;
  border-top: var(--card-divider-width) solid var(--hair);
  transition: background-color var(--dur-fast) ease-in-out;
}
.pathway__step:hover { background-color: var(--raised); }
@media (min-width: 768px) { .pathway__step { grid-template-columns: 5rem 1fr 8rem 7rem; align-items: baseline; } }
.pathway__step:last-child { border-bottom: var(--card-divider-width) solid var(--hair); }
.pathway__no { font-family: var(--display); font-weight: var(--font-weight-bold); text-transform: uppercase; color: var(--accent); }
.pathway__title { font-family: var(--display); font-weight: var(--font-weight-bold); text-transform: uppercase; font-size: var(--text-h6); }
.pathway__desc { color: var(--w60); font-size: var(--text-regular); margin-top: 0.25rem; text-wrap: pretty; }
.pathway__banked { font-family: var(--body); font-weight: 700; font-variant-numeric: tabular-nums; color: var(--accent); }

/* --- Tables --------------------------------------------------------------- */

.table-wrap { overflow-x: auto; }
table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-regular);
  font-variant-numeric: tabular-nums;
  min-width: 44rem;
}
table.data th, table.data td {
  text-align: left;
  padding: 0.7rem 1rem 0.7rem 0;
  border-bottom: var(--card-divider-width) solid var(--hair-soft);
  vertical-align: top;
}
table.data th {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: var(--text-small);
  color: var(--w40);
  white-space: nowrap;
  border-bottom-color: var(--hair);
}
table.data td.num, table.data th.num { text-align: right; white-space: nowrap; }
/* Only the final column loses its gutter — otherwise a right-aligned number
   runs straight into the next column's text. */
table.data th:last-child, table.data td:last-child { padding-right: 0; }
/* Status chips must not break mid-label inside a table row. */
table.data .badge { white-space: nowrap; }
/* Win / loss. The only place these two colours are allowed. */
.pos { color: var(--field-up); }
.neg { color: var(--field-down); }

/* --- Pricing -------------------------------------------------------------- */

/* Deliberately compact: four tiers and three feeds sit side by side, so every
   pixel of card height is a pixel of page scroll. The tier name drops to h5
   and the feature list to the small size — the prices stay the loudest thing
   in the card. */
.plan {
  display: flex; flex-direction: column; gap: 0.75rem; padding: 20px;
  border-radius: var(--radius-card);
  background-color: var(--raised);
  box-shadow: inset 0 0 0 1px var(--hair-soft);
}
[data-theme="light"] .plan { box-shadow: inset 0 0 0 1px var(--hair); }
.plan--featured { box-shadow: inset 0 0 0 1px var(--accent); }
.plan > h3, .plan > h4 { font-size: var(--text-h5); }
.plan > p { font-size: var(--text-small); color: var(--w60); text-wrap: pretty; }
.plan__price { font-family: var(--body); font-weight: 700; font-variant-numeric: tabular-nums; font-size: var(--text-h5); line-height: 1; color: var(--bone); }
.plan__price small { font-family: var(--body); font-weight: 400; font-size: var(--text-small); color: var(--w60); }
.plan__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; font-size: var(--text-small); color: var(--w60); }
.plan__list li { display: flex; gap: 0.6rem; align-items: flex-start; text-wrap: pretty; }
.plan__list li::before { content: "+"; color: var(--accent); font-weight: 600; line-height: 1.4; }
.plan__list li.is-off { color: var(--w40); }
.plan__list li.is-off::before { content: "\2013"; color: var(--w40); }
.plan__foot { margin-top: auto; padding-top: 0.5rem; }

/* --- Products -------------------------------------------------------------
   Nine products do not go in one nine-wide price grid, so the page is three
   bands — the course, the feeds, everything — each with its own head. Within
   a band the cards are equal: no tier is dressed as "most popular" unless the
   config marks it featured.

   The card is a LINK, not a poster with a button on it. `.product__hit` is a
   stretched overlay so the whole card opens the product page, and the buy
   button sits above it in the stack so it still goes to checkout.          */

.product-band + .product-band { margin-top: clamp(48px, 7vh, 88px); }
.product-band__head { max-width: var(--container-lg); }
.product-band__head h2 { margin-top: 0.6rem; font-size: var(--text-h3); }
.product-band__head p:not(.eyebrow) { margin-top: 0.9rem; color: var(--w60); text-wrap: pretty; }

.product-grid { display: grid; gap: 1px; margin-top: clamp(20px, 3vh, 34px); grid-template-columns: minmax(0, 1fr); }
/* The 1px gap plus a hairline ring on each card gives one shared rule between
   neighbours instead of two abutting borders — the band reads as a single
   plate that has been divided, which is what it is. */
@media (min-width: 640px) { .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1080px) {
  .product-grid--narrow { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .product-grid--wide   { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.product {
  position: relative;
  display: flex; flex-direction: column; gap: 0.85rem;
  padding: 26px 24px 24px;
  background-color: var(--raised);
  box-shadow: 0 0 0 1px var(--hair-soft);
  transition: background-color var(--dur-fast) ease-in-out,
              box-shadow var(--dur-fast) ease-in-out,
              transform var(--dur-fast) var(--ease-out);
}
[data-theme="light"] .product { box-shadow: 0 0 0 1px var(--hair); }
.product:hover {
  background-color: var(--raised-2);
  box-shadow: 0 0 0 1px var(--accent);
  /* Lifts by one hairline and no more. The card is a plate in a wall of
     plates; a real elevation would break the wall. */
  transform: translateY(-2px);
  z-index: 1;
}
.product--featured { box-shadow: 0 0 0 1px var(--accent); }

/* Stretched link. Sits under the content so text stays selectable, and under
   the footer buttons so those keep their own destination. */
.product__hit { position: absolute; inset: 0; z-index: 0; }
/* The content sits above the overlay so it paints normally, but passes the
   pointer straight through to it — otherwise only the card's padding would be
   clickable. The buy button opts back in, because it has its own destination. */
.product > *:not(.product__hit) { position: relative; z-index: 1; pointer-events: none; }
.product__foot .btn { pointer-events: auto; }

.product__head { display: flex; flex-direction: column; align-items: flex-start; gap: 0.7rem; }
.product__name { font-size: var(--text-h4); transition: color var(--dur-fast) ease-in-out; }
.product:hover .product__name { color: var(--accent); }
.product__price {
  font-family: var(--body); font-weight: 700; font-variant-numeric: tabular-nums;
  font-size: clamp(30px, 3.2vw, 42px); line-height: 1; color: var(--bone);
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.4rem;
}
.product__price small { font-family: var(--body); font-weight: 400; font-size: var(--text-small); color: var(--w60); }
/* A waitlist price is what it will cost at launch, not a charge taken today —
   the card has to say so itself, not only in a code comment. */
.product__at {
  font-family: var(--display); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.08em; font-size: var(--text-tiny); color: var(--gold);
  border: var(--card-border-width) solid var(--gold);
  border-radius: var(--radius-badge); padding: 3px 10px;
}
.product__summary { font-size: var(--text-regular); color: var(--w60); text-wrap: pretty; }
.product__list {
  list-style: none; margin: 0; padding: 0.4rem 0 0;
  display: flex; flex-direction: column; gap: 0.45rem;
  font-size: var(--text-small); color: var(--w60);
  border-top: var(--card-divider-width) solid var(--hair-soft);
}
.product__list li { display: flex; gap: 0.6rem; align-items: flex-start; text-wrap: pretty; }
.product__list li::before { content: "+"; color: var(--accent); font-weight: 600; line-height: 1.4; }
.product__list li.is-more { color: var(--w40); }
.product__list li.is-more::before { content: none; }
.product__foot {
  margin-top: auto; padding-top: 1rem;
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap;
}
.product__more {
  font-family: var(--display); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.08em; font-size: var(--text-tiny); color: var(--w40);
  transition: color var(--dur-fast) ease-in-out;
}
.product:hover .product__more { color: var(--accent); }

/* --- A product's own page -------------------------------------------------
   Same staged opening as the home hero — the market field behind it, the name
   at display scale — but left-aligned, because everything past the front door
   on this site is working copy rather than a poster.                       */

.hero--product { padding-block: clamp(28px, 6vh, 72px); justify-content: center; }
.hero--product .product-hero { display: grid; gap: clamp(24px, 4vw, 56px); grid-template-columns: minmax(0, 1fr); align-items: start; }
@media (min-width: 992px) { .hero--product .product-hero { grid-template-columns: 1.25fr 0.75fr; } }
.hero--product h1 { font-size: max(34px, min(6vw, 9vh, 92px)); line-height: var(--text-hero-lh); letter-spacing: var(--text-hero-ls); }
.hero--product .lead { max-width: 44ch; margin-top: 1.25rem; }
.product-hero__price {
  font-family: var(--body); font-weight: 700; font-variant-numeric: tabular-nums;
  font-size: clamp(38px, 4.6vw, 62px); line-height: 1; color: var(--bone);
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.5rem;
}
.product-hero__price small { font-family: var(--body); font-weight: 400; font-size: var(--text-medium); color: var(--w60); }
.product-hero__aside {
  padding: 22px;
  background-color: var(--raised);
  box-shadow: 0 0 0 1px var(--hair);
  display: flex; flex-direction: column; gap: 1rem;
}
.product-hero__aside dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 0.55rem 1rem; font-size: var(--text-small); }
.product-hero__aside dt { color: var(--w40); }
.product-hero__aside dd { margin: 0; color: var(--bone); text-align: right; }

.spec { display: grid; gap: 0.55rem; list-style: none; margin: 0; padding: 0; font-size: var(--text-regular); color: var(--w60); }
.spec li { display: flex; gap: 0.6rem; align-items: flex-start; text-wrap: pretty; }
.spec li::before { content: "+"; color: var(--accent); font-weight: 600; line-height: 1.4; }
.spec--off li::before { content: "\2013"; color: var(--w40); }
.spec--off li { color: var(--w40); }

.billing-toggle {
  display: inline-flex; gap: 4px; padding: 4px;
  border-radius: var(--radius-button);
  border: var(--card-border-width) solid var(--hair);
}
.billing-toggle button {
  border: 0; background: none; color: var(--w60);
  border-radius: var(--radius-button); padding: 6px 18px;
  font-family: var(--display); font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
  font-size: var(--text-small);
  transition: all var(--dur-fast) ease-in-out;
}
.billing-toggle button[aria-pressed="true"] { background-color: var(--accent); color: var(--color-accent-text); }

/* --- Course --------------------------------------------------------------- */

/* Nine parts run two-up, so the row form is kept narrow: number, then the
   title block, with the badges wrapping underneath rather than claiming a
   fixed third column. */
.part-card {
  display: grid; gap: 0.5rem 1rem; grid-template-columns: 2.5rem minmax(0, 1fr);
  padding: 14px 0;
  border-top: var(--card-divider-width) solid var(--hair);
  transition: background-color var(--dur-fast) ease-in-out;
  align-items: start;
}
.part-card:hover { background-color: var(--raised); }
.part-card__meta { grid-column: 2; }
/* The chapter list is orientation, not reading — one line, then it stops. */
.part-card__sub.text-small {
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
  overflow: hidden;
}
.part-card:last-of-type { border-bottom: var(--card-divider-width) solid var(--hair); }
.grid > .part-card:nth-last-child(2):nth-child(odd) { border-bottom: var(--card-divider-width) solid var(--hair); }
.part-card__no { font-family: var(--body); font-weight: 700; font-variant-numeric: tabular-nums; font-size: var(--text-h5); color: var(--w20); line-height: 1; }
.part-card__title { font-family: var(--display); font-weight: var(--font-weight-bold); text-transform: uppercase; font-size: var(--text-h6); color: var(--bone); transition: color var(--dur-fast) ease-in-out; }
.part-card:hover .part-card__title { color: var(--accent); }
.part-card__sub { color: var(--w60); font-size: var(--text-regular); margin-top: 0.4rem; text-wrap: pretty; }
.part-card__meta { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.part-card.is-locked .part-card__title { color: var(--w40); }
.part-card.is-locked:hover .part-card__title { color: var(--w60); }

/* Watch progress on a part card. Rendered only when there is progress, so a
   course with nothing watched shows no empty rails. */
.part-card__bar {
  grid-column: 1 / -1;
  height: 3px;
  border-radius: 3px;
  background: var(--hair-soft);
  overflow: hidden;
}
.part-card__bar > span {
  display: block;
  height: 100%;
  width: var(--pct, 0%);
  background: var(--accent);
}

.lesson-body { max-width: var(--container-lg); }
.lesson-body p { font-size: var(--text-medium); color: var(--w60); margin-bottom: 1.25rem; text-wrap: pretty; }
.lesson-body p strong { color: var(--bone); font-weight: 600; }
.lesson-body ol, .lesson-body ul { color: var(--w60); font-size: var(--text-medium); padding-left: 1.25rem; margin: 0 0 1.25rem; display: flex; flex-direction: column; gap: 0.6rem; }
.lesson-body h3 { margin: 0 0 1.25rem; }
.lesson-body h4 { margin: 2rem 0 0.75rem; font-size: var(--text-h5); }

.lesson-nav { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.lesson-layout { display: grid; gap: 32px; grid-template-columns: minmax(0, 1fr); align-items: start; }
@media (min-width: 992px) { .lesson-layout { grid-template-columns: 15rem 1fr; gap: clamp(40px, 6vw, 88px); } }
.rail { position: sticky; top: calc(var(--nav-h) + 20px); display: flex; flex-direction: column; gap: 0.6rem; }
.rail a { font-size: var(--text-regular); color: var(--w40); }
.rail a:hover, .rail a.is-active { color: var(--accent); }

.video-slot {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  overflow: hidden;
  border-radius: var(--radius-image);
  background: linear-gradient(150deg, var(--raised), var(--ink));
  box-shadow: inset 0 0 0 1px var(--hair-soft);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.75rem;
}
[data-theme="light"] .video-slot { background: linear-gradient(150deg, var(--raised-2), var(--raised)); }
.video-slot iframe, .video-slot video { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.video-slot__play {
  width: 3.25rem; height: 3.25rem; border-radius: var(--radius-button);
  border: var(--card-border-width) solid var(--w40);
  display: grid; place-items: center; color: var(--w40);
}
.video-slot__label { font-size: 12.5px; color: var(--w40); text-align: center; padding-inline: 1rem; line-height: 1.45; }

.callout {
  border-left: 2px solid var(--accent);
  background-color: var(--raised);
  padding: 1rem 1.25rem;
  margin: 1.75rem 0;
  display: flex; flex-direction: column; gap: 0.5rem;
}
.callout__title { font-family: var(--display); font-weight: 600; text-transform: uppercase; font-size: var(--text-small); letter-spacing: 0.12em; color: var(--accent); }
.callout p { margin: 0; color: var(--w60); font-size: var(--text-regular); text-wrap: pretty; }
/* Warn / stop keep the gold and the ledger red respectively — a stop callout
   is the one non-graded use of the loss colour the pack allows, because it is
   the responsible-gambling voice, not a decorative choice. */
.callout--warn { border-left-color: var(--gold); }
.callout--warn .callout__title { color: var(--gold); }
.callout--stop { border-left-color: var(--field-down); }
.callout--stop .callout__title { color: var(--field-down); }
.callout--good { border-left-color: var(--field-up); }
.callout--good .callout__title { color: var(--field-up); }

.maths {
  box-shadow: inset 0 0 0 1px var(--hair);
  border-radius: var(--radius-card);
  padding: 1.25rem;
  margin: 1.75rem 0;
  display: flex; flex-direction: column; gap: 0.9rem;
}
.maths__title { font-family: var(--display); font-weight: 600; text-transform: uppercase; font-size: var(--text-small); letter-spacing: 0.12em; color: var(--gold); }
.maths__formula {
  font-family: var(--body); font-weight: 700; font-variant-numeric: tabular-nums;
  font-size: var(--text-h6); text-align: center; padding: 0.9rem 0; color: var(--bone);
  border-top: var(--card-divider-width) solid var(--hair-soft);
  border-bottom: var(--card-divider-width) solid var(--hair-soft);
  overflow-x: auto;
}
.maths__lines { font-variant-numeric: tabular-nums; font-size: var(--text-regular); color: var(--w60); display: flex; flex-direction: column; gap: 0.4rem; }
.maths__lines b { color: var(--bone); font-weight: 600; }

.lock-gate {
  border-radius: var(--radius-card);
  background-color: var(--raised);
  box-shadow: inset 0 0 0 1px var(--hair);
  padding: 2rem;
  text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 1rem;
}

/* --- Calculators ----------------------------------------------------------
   The deck shows one calculator at a time (see calc-tabs.js). Until the script
   claims it with .is-deck the grid renders all eight, so a JS failure degrades
   to the plain stacked page rather than to a blank one. */

.tab-bar { display: flex; flex-wrap: wrap; gap: 8px; }
.tab-bar .badge { transition: all var(--dur-fast) ease-in-out; }
.tab-bar .badge:hover { border-color: var(--accent); color: var(--accent); }
.tab-bar .badge[aria-selected="true"] {
  background-color: var(--accent);
  border-color: var(--accent);
  color: var(--color-accent-text);
}
.grid.is-deck { display: block; }

.calc {
  position: relative;
  display: flex; flex-direction: column; gap: 1.25rem; padding: 24px;
  border-radius: var(--radius-card);
  background-color: var(--raised);
  box-shadow: inset 0 0 0 1px var(--hair-soft);
  scroll-margin-top: calc(var(--nav-h) + 20px);
}
/* Tier-locked calculator: the card stays visible but inert behind the gate. */
.calc.is-locked > :not(.calc__lock) { filter: blur(3px); opacity: 0.45; pointer-events: none; user-select: none; }
.calc__lock {
  position: absolute; inset: 0; z-index: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.75rem; padding: 24px; text-align: center;
  border-radius: var(--radius-card);
}
[data-theme="light"] .calc { box-shadow: inset 0 0 0 1px var(--hair); }
.calc__grid { display: grid; gap: 1rem; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 480px) { .calc__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.field { display: flex; flex-direction: column; gap: 0.35rem; }
.field label { font-family: var(--display); font-size: var(--text-small); color: var(--w60); text-transform: uppercase; font-weight: 600; letter-spacing: 0.08em; }
.field input, .field select {
  width: 100%;
  border-radius: var(--radius-form);
  border: var(--card-border-width) solid var(--hair);
  background-color: var(--ground);
  color: var(--bone);
  padding: 0.6rem 0.8rem;
  font-size: var(--text-regular);
  font-variant-numeric: tabular-nums;
  transition: border-color var(--dur-fast) ease-in-out;
}
.field input:hover, .field select:hover { border-color: var(--accent); }
.field input:focus-visible, .field select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }

.result { display: flex; flex-direction: column; gap: 0.55rem; border-top: var(--card-divider-width) solid var(--hair); padding-top: 1rem; }
.result__row { display: flex; justify-content: space-between; gap: 1rem; font-variant-numeric: tabular-nums; font-size: var(--text-regular); }
.result__row span:first-child { color: var(--w60); }
.result__row b { font-weight: 600; color: var(--bone); }
.result__row--headline { font-family: var(--body); font-weight: 700; font-size: var(--text-h6); }
.result__row--headline span:first-child { color: var(--w60); }

/* --- FAQ / accordion ------------------------------------------------------
   Rows divided by a hairline — no card, no radius. */

details.faq { border-bottom: var(--card-divider-width) solid var(--hair-soft); padding: 0; }
details.faq summary {
  cursor: pointer; list-style: none;
  font-family: var(--display); font-weight: var(--font-weight-bold); text-transform: uppercase;
  font-size: var(--text-h6); color: var(--bone);
  padding: 16px 0;
  display: flex; justify-content: space-between; gap: 1rem; align-items: center;
  transition: color var(--dur-fast) ease-in-out;
}
details.faq summary:hover { color: var(--accent); }
details.faq summary::-webkit-details-marker { display: none; }
details.faq summary::after {
  content: "";
  width: 12px; height: 12px; flex-shrink: 0;
  border-right: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--dur-accordion) ease-in-out;
}
details.faq[open] summary::after { transform: rotate(225deg) translate(-2px, -2px); }
details.faq p { color: var(--w60); font-size: var(--text-regular); margin: 0 0 16px; text-wrap: pretty; max-width: 60ch; }

/* --- Footer ---------------------------------------------------------------
   One row, not columns. Mark, then the mandatory responsible-gambling line;
   the secondary links sit right.                                           */

.footer {
  padding: 26px var(--section-px);
  background-color: var(--ink);
  background-image: none;
  border-top: var(--card-border-width) solid var(--hair-soft);
}
.footer__row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px 24px; flex-wrap: wrap;
  margin-inline: auto; max-width: var(--container-xxl);
}
.footer__brand { display: flex; align-items: center; gap: 18px; }
.footer__links { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }
.footer__links a, .footer__links span { font-size: 13.5px; color: var(--w40); }
.footer__links a:hover { color: var(--accent); }

/* The mandatory responsible-gambling line. Every public surface, no exceptions.
   Carries the current NCPF call to action, which is long enough that it wraps
   rather than forcing nowrap — legibility of this line outranks the footer
   sitting on one row. */
.rg-line {
  font-family: var(--display);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 13px;
  line-height: 1.45;
  color: var(--w40);
  margin: 0;
  text-wrap: pretty;
}
.rg-line a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.rg-line a:hover { color: var(--accent); }
/* The mark tops out against the first line of the RG copy once it wraps. */
.footer__brand { align-items: flex-start; }
/* Give the line room to sit on one line on a wide viewport; below lg the
   footer stacks anyway and it wraps naturally. */
@media (min-width: 992px) { .footer__row { flex-wrap: nowrap; } .rg-line { white-space: nowrap; } }
@media (min-width: 992px) and (max-width: 1200px) { .rg-line { white-space: normal; } }

/* ==========================================================================
   MOTION

   Two moves, no library, no scroll listener, nothing that can hide content.
   Each extends something the system already does rather than adding a new
   vocabulary:

     1. Routes and theme swaps cross-fade (native view transitions).
     2. A hairline charges with the accent on hover.

   Rule 2 is the signature. The hairline is already the system's only depth
   cue, so the interaction is that rule lighting up — not a shadow, not a
   lift, not a scale. Nothing here introduces one.

   Scroll reveals were tried and rejected; see below for why.
   ========================================================================== */

/* --- 1. Route transitions -------------------------------------------------
   A same-origin navigation cross-fades rather than flashing the background.
   The whole document is one snapshot: the nav and footer are pixel-identical
   across routes, so they read as standing still while the page changes under
   them. No transform on the root — sliding the sticky nav is exactly the
   cheap effect this system avoids.

   Browsers without view transitions navigate normally. Nothing to fall back
   to and nothing to polyfill.                                              */

@view-transition { navigation: auto; }

::view-transition-old(root) { animation: bvb-fade-out var(--dur-page) var(--ease-standard) both; }
::view-transition-new(root) { animation: bvb-fade-in  var(--dur-page) var(--ease-out) both; }
@keyframes bvb-fade-out { to   { opacity: 0; } }
@keyframes bvb-fade-in  { from { opacity: 0; } }

/* --- 2. No scroll reveal --------------------------------------------------
   Deliberately absent, and this is the second time the decision has been
   taken — so the reasoning is recorded rather than rediscovered.

   A scroll-driven `view()` reveal was built and reverted. It renders the
   course hub BLANK. `main[data-fit]` is a flex column whose sections carry
   content past the document's scrollable height, so a subject's `entry` phase
   never resolves: the animation pins at its `from` keyframe and every part
   card sits at opacity 0 with no scroll position that recovers it.

   The failure is silent, it only appears below the fold, and it takes the
   product page with it. An IntersectionObserver version trades that for a
   different blank page whenever the script fails. Neither is worth a fade.

   If this is ever revisited: the layout overflow is the bug to fix first.
   ------------------------------------------------------------------------ */

/* --- 3. The charging hairline ---------------------------------------------
   Course parts and pathway steps are separated by a 1px rule. On hover that
   rule fills with the accent from the left. It is drawn as a pseudo-element
   over the border rather than by recolouring it, so the row's height never
   changes and neighbouring rows do not shift.                              */

.part-card, .pathway__step { position: relative; }
.part-card::before, .pathway__step::before {
  content: "";
  position: absolute;
  top: calc(var(--card-divider-width) * -1);
  left: 0; right: 0;
  height: var(--card-divider-width);
  background-color: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-accordion) var(--ease-out);
}
.part-card:hover::before, .pathway__step:hover::before { transform: scaleX(1); }
/* A locked part is not a destination, so its rule stays cold. */
.part-card.is-locked:hover::before { transform: scaleX(0); }

/* Nav links get the same idea at link scale: a rule sweeps under the label.
   The current page already carries a filled pill and does not also get one. */
.nav-links a { position: relative; }
.nav-links a::after {
  content: "";
  position: absolute;
  left: 14px; right: 14px; bottom: 7px;
  height: 1.5px;
  background-color: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-fast) var(--ease-out);
}
.nav-links a:hover::after { transform: scaleX(1); }
.nav-links a[aria-current="page"]::after { content: none; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
  .part-card::before, .pathway__step::before, .nav-links a::after { transition: none; }
}

/* --- Page entrance --------------------------------------------------------
   One orchestrated arrival per page, not scattered effects.

   The move is a WIPE, not a fade. Everything else in this system is mechanical
   — square cards, hairlines, linear drift — so the arrival is a short, flat
   settle rather than anything that floats.

   It moves opacity and position ONLY. An earlier version wiped a clip-path
   across each line, which is a fine gesture on a block or an image and a bad
   one on type: any frame caught mid-wipe shows glyphs sliced down the middle,
   and a half-drawn letter reads as a webfont that failed rather than as a
   reveal. Never animate the SHAPE of text.

   `backwards` fill is what holds an element in its from-state during its
   delay. The resting style is the visible one, so anything that stops the
   animation from running leaves the page fully readable rather than blank —
   the rule this file learned the hard way.

   The stagger is nth-child rather than an inline --i, so it needs no JS and
   cannot desync from the markup.                                           */

/* The tempo is deliberately unhurried: a held beat, the eyebrow, then the two
   headline lines with real air between them, and only then the body copy. The
   gap between child 1 and child 3 is where the headline lands — children 3+
   wait for it rather than racing it. Durations live in tokens.css. */
.enter > *          { animation: bvb-enter var(--dur-enter) var(--ease-out) backwards; }
.enter > *:nth-child(1) { animation-delay: var(--enter-hold); }   /* eyebrow */
.enter > *:nth-child(2) { animation-delay:  600ms; }              /* h1 opts out below */
.enter > *:nth-child(3) { animation-delay: 1120ms; }              /* lead, after the headline */
.enter > *:nth-child(4) { animation-delay: 1270ms; }
.enter > *:nth-child(5) { animation-delay: 1400ms; }
.enter > *:nth-child(6) { animation-delay: 1520ms; }
.enter > *:nth-child(7) { animation-delay: 1640ms; }
.enter > *:nth-child(8) { animation-delay: 1760ms; }

/* A headline that stages itself opts out of the parent's wipe — two nested
   wipes on the same text reads as a wobble. Scoped with :has so it only
   applies where there are spans to carry the beat; every other page's h1 is a
   direct child too, and excluding those left the headline frozen while the
   copy around it moved. */
.enter > h1:has(> span) { animation: none; }
.enter > h1 > span {
  display: block;
  animation: bvb-enter var(--dur-enter-line) var(--ease-out) backwards;
}
/* The claim lands, is allowed to sit alone for a beat, and only then is
   answered by the turn. The pause between the two IS the line. */
.enter > h1 > span:nth-child(1) { animation-delay:  600ms; }
.enter > h1 > span:nth-child(2) { animation-delay:  880ms; }

/* No clip-path. A left-to-right wipe sounds right for a price board, but on
   TEXT it cuts glyphs down the middle: catch the headline mid-animation and a
   letter is half-drawn, which reads as a font that failed to load rather than
   as a reveal. Every frame here shows complete letterforms —
   the type only ever changes its opacity and its position, never its shape. */
@keyframes bvb-enter {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* The panel lands with the second half of the headline, so the two columns
   read as one arrival rather than a relay. */
.hero__grid > .panel { animation: bvb-enter var(--dur-enter-line) var(--ease-out) 880ms backwards; }

/* The market field is the room the hero sits in — it comes up first and slower
   than anything on top of it, so the page feels like it opens rather than
   assembles. It is still rising while the headline lands, which is what keeps
   the opening cinematic rather than staged: the background never finishes on
   the same beat as the foreground. */
.hero .bvb-field { animation: bvb-fade-in var(--dur-room) var(--ease-standard) backwards; }

@media (prefers-reduced-motion: reduce) {
  .enter > *, .enter > h1 > span,
  .hero__grid > .panel, .hero .bvb-field { animation: none; }
}

/* --- The climb ------------------------------------------------------------
   The signature. Everything else on the page is deliberately quiet so this
   can be the one loud thing.

   Drawn in accent only. The rungs are hairline stems down to the baseline —
   the same 64px ruling language as the section grid, so the chart sits inside
   the system rather than on top of it. Square caps and mitre joins: this is a
   price tick, not an infographic, and a rounded join would smooth away the
   volatility that is the entire point.                                      */

.climb { display: block; width: 100%; height: auto; overflow: visible; }

.climb__grid { stroke: var(--hair-soft); stroke-width: 1; }
.climb__axis,
.climb__when {
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 11px;
  fill: var(--w40);
}
.climb__when { text-anchor: middle; }

.climb__area { fill: color-mix(in srgb, var(--accent), transparent 92%); stroke: none; }
.climb__line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: miter;
  stroke-linecap: square;
}
.climb__stem { stroke: var(--hair); stroke-width: 1; }
.climb__dot  { fill: var(--ground); stroke: var(--accent); stroke-width: 2; }
.climb__dot.is-end { fill: var(--accent); }

/* Each rung links to the part of the course that gets you to it. The hit area
   is an invisible column over the dot, stem and label — a 4px circle is not a
   target, and the dot must not grow just to be clickable. */
.climb__hit { fill: transparent; }
.climb__link { cursor: pointer; }
.climb__link:hover .climb__dot,
.climb__link:focus-visible .climb__dot { fill: var(--accent); }
.climb__link:hover .climb__when,
.climb__link:focus-visible .climb__when { fill: var(--accent); }
.climb__link:hover .climb__stem,
.climb__link:focus-visible .climb__stem { stroke: var(--accent); }
.climb__link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Hover card. Sits above the dot, comes up rather than fades in, and is
   pointer-events:none so it can never eat the click on the dot underneath. */
.climb__tip {
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition: opacity var(--dur-fast, 160ms) var(--ease-out),
              transform var(--dur-fast, 160ms) var(--ease-out);
}
.climb__link:hover .climb__tip,
.climb__link:focus-visible .climb__tip { opacity: 1; transform: translateY(0); }
.climb__tip-box {
  fill: var(--ground);
  stroke: var(--accent);
  stroke-width: 1.5;
}
.climb__tip-text {
  font-family: var(--display);
  font-weight: 600;
  font-size: 14px;
  text-anchor: middle;
  fill: var(--accent);
}

/* Two weights on one curve: full accent to the last rung their tier unlocks,
   faint past it. Stop colours live here so both themes get them for free. */
.climb__stop-done  { stop-color: var(--accent); stop-opacity: 1; }
.climb__stop-ahead { stop-color: var(--accent); stop-opacity: 0.3; }

/* The one figure that matters. Every other rung is read off the axis — seven
   money labels would be a table again, and the destination is the argument. */
.climb__total {
  font-family: var(--body);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: 26px;
  text-anchor: end;
  fill: var(--bone);
}

/* One orchestrated moment: the line draws itself once. That is all.

   The rungs, the figures and the axis are NEVER animated. They are the
   information, and information does not get hidden behind an animation that
   might not run — a staggered fade looked better for two seconds and cost the
   whole chart in any environment where the animation never ticks.

   Note the fill mode is `none`, deliberately. The resting style below is the
   FINISHED state (fully drawn), and the from-state lives only inside the
   keyframes. So if animations never execute the line is simply already there,
   which is the correct picture rather than an empty box. `forwards`/`both`
   would invert that and leave a blank chart.

   The late start is bought with EASING, not an animation-delay. A delay would
   need `backwards` to stop the finished line flashing before it redraws, and
   `backwards` is precisely what would leave the chart blank in the failure
   mode above. `--ease-hold` barely moves for its first third instead, so the
   line is still nearly unplotted when the panel wipes open, then runs. Same
   picture, none of the risk. */
@media (prefers-reduced-motion: no-preference) {
  .climb__line { animation: bvb-draw var(--dur-draw) var(--ease-hold); }
}
@keyframes bvb-draw {
  from { stroke-dasharray: 1; stroke-dashoffset: 1; }
  to   { stroke-dasharray: 1; stroke-dashoffset: 0; }
}

/* The band's viewBox is nearly twice as wide, so SVG-unit type inside it
   renders proportionally smaller once scaled to fit. These are the same
   optical sizes as the panel, restated in the band's units. */
.climb--band .climb__axis,
.climb--band .climb__when { font-size: 15px; }
.climb--band .climb__total { font-size: 40px; }
.climb--band .climb__line { stroke-width: 2.5; }
.climb--band .climb__dot { stroke-width: 2.5; }

@media (max-width: 640px) {
  .climb__total { font-size: 20px; }
  .climb__axis, .climb__when { font-size: 10px; letter-spacing: 0.06em; }
  .climb--band .climb__total { font-size: 34px; }
  .climb--band .climb__axis, .climb--band .climb__when { font-size: 17px; }
}

/* --- Film grain -----------------------------------------------------------
   One fixed tile of fractal noise over the whole viewport. It does not scroll,
   so it reads as the surface the page is printed on rather than as something
   sitting on the content — which is what stops flat colour fields from looking
   like untextured fills. Above the nav on purpose: grain that stops at the
   nav's edge draws a line exactly where there should not be one.

   Inert by construction — fixed, pointer-events: none, and no hit area.     */

body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 1001;
  pointer-events: none;
  background-image: var(--grain);
  opacity: var(--grain-opacity);
}

@media (prefers-reduced-motion: reduce) {
  /* The grain is static and stays; only the drift is motion. */
  .section::before { animation: none; }
}

/* --- Utility -------------------------------------------------------------- */

.skip-link { position: absolute; left: -9999px; }
.skip-link:focus {
  /* Above the grain (1001) — the one control that must never be filmed over. */
  left: 1rem; top: 1rem; z-index: 1002;
  background-color: var(--accent); color: var(--color-accent-text);
  padding: 0.5rem 1rem; border-radius: var(--radius-button);
}
