/* ==========================================================================
   ZONETTA TRADING — BASE STYLESHEET
   Direction: HELD LINE — modern white skin (2026-09-05)
   File: /assets/base-v3.css   (requires /assets/tokens-v2.css to be loaded first.
         EVERY content change renames this file: /assets/* is cached immutable
         for a year, so a changed body under an old name never reaches a returning
         visitor. v2 on 2026-09-05, v3 on 2026-09-06.)

   NO COLOUR VALUES IN THIS FILE. Ever. Not a hex, not an oklch(), not a
   color-mix(). If a colour is missing, add a semantic token to tokens.css —
   do not inline a literal here.

   WHAT THIS IS. A white, clean page in the tone of a 2026 broker site, with
   no JavaScript and a CSP of script-src 'none'; style-src 'self'. Every
   behaviour on it is native HTML or CSS:
     - a compact sticky header (--masthead-h, 60px) at every width, white at
       86% over a backdrop blur, with a hairline; wordmark left, the amber
       "Free checklist" pill right, the seven links between them from 1100px;
     - below 1100px the seven links live in a native popover sheet opened by
       a "Menu" button (popovertarget — Escape, light-dismiss, focus return
       and the top layer come from the platform), animated with
       @starting-style and transition-behavior: allow-discrete; where the
       popover API is missing the button is hidden and the list renders
       wrapped, as the previous skin did;
     - a cross-document view transition (@view-transition) fades one page
       into the next; reduced motion turns it off;
     - components are sized by CONTAINER queries (cards, the shelf, the
       footer columns, the button pair, the portrait, the ad slot). Viewport
       queries survive only for page layout: the header breakpoint, the ad
       rail, the >=1440px marginalia and the table edge fade;
     - fluid type and fluid section rhythm via clamp() (tokens.css §3–4);
       text-wrap: balance on headings and pretty on running text; tabular
       lining numerals on every figure.

   THE ONE IDEA: price moves, one amber level holds still. On the white page
   that amber line (amber-ink, 7.05:1) is the SECTION DIVIDER: the rule under
   the hero and every `hr.rule` between sections, plus the footer's top edge.
   It is always 1px. Subordinate structure (rows inside a table, items inside
   a list, the header's bottom edge) uses --hairline, a neutral.

   ONE VOICE. Poppins everywhere — h1 and h2 at 300, h3 and copy at 400,
   UI at 500. The display serif is gone; --f-display is an alias. Mono
   survives only on numbers and data.

   BOXES, SPARINGLY. Cards exist in exactly three places: the product shelf
   and catalogue lists (white face, --card-edge hairline, 16px radius, two
   columns once the CONTAINER is 40rem wide), the inset callout (amber tint,
   4px raw-amber edge, 12px radius) and the ad slot (section 25) — the one
   element that casts a shadow, because it is the one thing on the page
   that must lift. No gradient with a hue shift. Three equal thirds are
   BANNED: group with 7/5, 6/3/3, or hairlines.

   LINES ARE AMBER-INK, BARS ARE RAW AMBER. See the header of tokens.css.

   SECTIONS 23 (the daily data block, /gex/) AND 24 (the drill, lesson five
   of the candlestick course) ARE UNCHANGED in their rules; section 26 adds
   to them from outside (a sticky first column, tabular figures, print).
   ========================================================================== */


/* --------------------------------------------------------------------------
   0. WEBFONT  (Poppins 300 / 400 / 500, latin subset, self-hosted)

   0a. THE FALLBACK FACE — live now, in every build. A local() face needs no
   font-src and no network. Its metrics are adjusted so that Segoe UI /
   Roboto / Arial occupy the same width and height as Poppins, which is ~7%
   wider than any of them: the page is laid out once, and the swap to the
   real face moves nothing (CLS ≈ 0). Tune the four numbers against the real
   woff2 once it exists (Chrome DevTools > Fonts, or fontkit); these are the
   published Poppins metrics rounded.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Poppins Fallback";
  src: local("Segoe UI"), local("Roboto"), local("Arial");
  size-adjust: 107%;
  ascent-override: 105%;
  descent-override: 35%;
  line-gap-override: 10%;
}

/* 0b. THE REAL FACES — three self-hosted Poppins faces (latin subset, OFL,
   about 8 KB each) under /assets/fonts/, allowed by `font-src 'self'` in
   www/_headers. Poppins has no variable axis (checked 2026-09-05): static
   faces, three files. tokens.css never contains a url(); this is the only
   place one is allowed. Switched on for production on 2026-09-05. */

@font-face {
  font-family: "Poppins";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("/assets/fonts/poppins-300.woff2") format("woff2");
}
@font-face {
  font-family: "Poppins";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/poppins-400.woff2") format("woff2");
}
@font-face {
  font-family: "Poppins";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/poppins-500.woff2") format("woff2");
}


/* --------------------------------------------------------------------------
   1. RESET
   -------------------------------------------------------------------------- */

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

* { margin: 0; }

/* NO `overflow-x` ON html OR body. `overflow: clip` does not create a scroll
   container, and a clipped root leaves `scroll(root block)` and `view()` with
   no scrollport to bind to — every scroll-driven timeline on the site then
   reports a null progress and freezes at its START value. The horizontal
   safety net lives on `.page` instead, where it clips nothing that matters and
   costs no timeline. */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  background-color: var(--surface-page);
  scrollbar-gutter: stable;         /* a short page (legal, 404) must not jolt the cross-document fade */
  interpolate-size: allow-keywords; /* lets the drill's <details> animate to `auto` (section 26) */
}

/* Every anchor target clears the sticky header. Mandatory the moment the
   header is sticky at all widths: without it the skip link's #main and every
   section id land under the bar. */
[id] { scroll-margin-block-start: calc(var(--masthead-h) + var(--sp-4)); }

body {
  min-height: 100dvh;               /* dvh, never vh: in-app webviews mis-report height */
  background-color: var(--surface-page);
  color: var(--text-primary);
  font-family: var(--f-text);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-body);
  font-synthesis: none;             /* faux-bold is the loudest cheapness tell. No -webkit-font-smoothing:
                                       antialiased — it thins Poppins on macOS, the opposite of "full" */
}

img, svg, video { display: block; max-inline-size: 100%; height: auto; }

input, button, select, textarea { font: inherit; color: inherit; }

::selection { background-color: var(--select-bg); color: var(--select-fg); }


/* --------------------------------------------------------------------------
   2. THE MACRO GRID
   One declaration drives every page. Prose sits in `content`; section rules,
   tables and chart figures break out to `full`.
   -------------------------------------------------------------------------- */

.page {
  --content-w: min(var(--content-max), 100% - 2 * var(--gutter));

  /* clip, not hidden: hidden would create a scroll container and break both
     position:sticky and the scroll-driven timelines. On the wrapper rather
     than on the root, so the root scrollport stays intact. */
  overflow-x: clip;

  display: grid;
  grid-template-columns:
    [full-start] minmax(var(--gutter), 1fr)
    [content-start] var(--content-w) [content-end]
    minmax(var(--gutter), 1fr) [full-end];

  min-height: 100dvh;
  align-content: start;   /* a short page (legal, 404) on a tall monitor must not stretch its rows */

  /* Chart-grid substrate: every section sits inside a chart pane.
     Horizontals every 96px; two verticals on the content-column edges.
     Not fixed-attachment — a fixed background re-rasters when the mobile
     URL bar collapses, which is exactly the frame budget we are protecting. */
  background-image:
    repeating-linear-gradient(to bottom, var(--grid-h) 0 1px, transparent 1px 96px),
    linear-gradient(to right,
      var(--grid-v) 0 1px,
      transparent 1px calc(100% - 1px),
      var(--grid-v) calc(100% - 1px) 100%);
  background-size: 100% 96px, var(--content-w) 100%;
  background-position: left top, center top;
  background-repeat: repeat, no-repeat;
}

.page > * {
  grid-column: full-start / full-end;
  display: grid;
  grid-template-columns: subgrid;
}

.page > * > * { grid-column: content-start / content-end; }

/* Declared after the rule above so it wins on order, not on !important. */
.bleed { grid-column: full-start / full-end; }

@supports not (grid-template-columns: subgrid) {
  .page > * { display: block; }
  .page > * > * {
    max-width: var(--content-max);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }
  .bleed { max-width: none; padding-inline: 0; }
  .section--band { display: block; max-width: none; padding-inline: 0; }
  .section--band > * {
    max-width: var(--content-max);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }
}


/* --------------------------------------------------------------------------
   3. TYPOGRAPHY
   Poppins, one voice: h1 and h2 Light (300) and big, h3 Regular, copy
   Regular, UI Medium. Nothing heavier — font-synthesis is off, so a missing
   weight falls to the nearest real one. Sizes are the fluid tokens.
   -------------------------------------------------------------------------- */

h1, h2, h3 {
  text-wrap: balance;
  text-box: trim-both cap alphabetic;   /* no hand-measured Firefox fallback, by decision */
}

h1 {
  font-family: var(--f-head);
  font-weight: 300;                 /* Light, big: the forex.com headline weight */
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--tr-h1);
  color: var(--text-primary);
  max-inline-size: 24ch;            /* a 56px Light line longer than this reads as a paragraph */
}

@media (min-width: 1200px) { h1 { letter-spacing: var(--tr-display); } }

h2 {
  font-family: var(--f-head);
  font-weight: 300;
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  letter-spacing: var(--tr-h2);
  color: var(--text-primary);
}

h3 {
  font-family: var(--f-head);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  letter-spacing: var(--tr-h3);
  color: var(--text-primary);
}

/* Orphan control on everything that runs, not only on <p>. Ignored by an
   engine that does not know it. */
p, li, dd, dt, figcaption, blockquote, td { text-wrap: pretty; }

p { max-inline-size: var(--measure); }

p + p { margin-block-start: var(--sp-5); }

.lead {
  font-weight: 300;
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  max-inline-size: var(--measure-lead);
  color: var(--text-secondary);
}

.prose > * + * { margin-block-start: var(--sp-5); }
.prose h3 { margin-block-start: var(--sp-7); }

/* THE VOICE SWITCH. Emphasis in prose is carried by changing voice, not by
   bolding: a mono, amber-ink, tabular numeral inside a sans line. */
.num {
  font-family: var(--f-mono);
  font-size: 0.92em;
  font-variant-numeric: lining-nums tabular-nums slashed-zero;
  letter-spacing: 0;
  color: var(--level);
}

/* Every number, level, score, timestamp and table cell. */
.num, .data, .spec__value, .table td, .table th, time, .zone-score__value {
  font-variant-numeric: lining-nums tabular-nums slashed-zero;
}

.data {
  font-family: var(--f-mono);
  font-size: var(--fs-data);
  line-height: var(--lh-mono);
}

.micro {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
}

.label {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-label);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
}

/* A section heading is a short, wide, light line — nothing under it. The
   48x4 amber accent bar of the first white candidate was dropped on
   2026-09-05: the 2026 broker set has no ornament under a heading, and the
   brand hue already reaches the eye through the eyebrow above it and the
   pill below it. Bringing it back is one background-image declaration here
   plus the print and forced-colours rules of section 26. */
.section > h2 { max-inline-size: 28ch; }

/* Section eyebrow: index + label, amber-ink. Never an icon, never an emoji.
   Stays at 12px: 14px uppercase with a numeral reads as a table header. */
.eyebrow {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--level);
  margin-block-end: var(--sp-4);
}

/* Safari-only enhancement; ignored elsewhere. */
blockquote, .pullquote { hanging-punctuation: first last; }

em, cite, i.cite { font-style: italic; }   /* italic is for citations and instrument names only */

abbr[title] { text-decoration: none; border-block-end: 1px dotted var(--hairline); }


/* --------------------------------------------------------------------------
   4. LINKS AND FOCUS
   -------------------------------------------------------------------------- */

a {
  color: var(--text-primary);
  text-decoration: underline;
  text-decoration-color: var(--level);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  transition: text-decoration-thickness var(--t-state) var(--ease),
              color var(--t-state) var(--ease);
}

a:hover { text-decoration-thickness: 2px; }

a:active { color: var(--level); }

/* Two-tone, because a bare amber ring is invisible on the amber-filled
   button and on the meter. `outline: none` appears nowhere in this file. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 1px var(--focus-inner);
}

.skip {
  position: absolute;
  inset-inline-start: var(--sp-2);
  inset-block-start: var(--sp-2);
  z-index: 20;
  transform: translateY(-200%);
  background-color: var(--level-solid);
  color: var(--on-level);
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  text-decoration: none;
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--r-pill);
}

.skip:focus-visible { transform: translateY(0); }

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


/* --------------------------------------------------------------------------
   5. THE MASTHEAD
   One compact row, sticky at every width: 60px (--masthead-h), the page
   colour at 86% over a 12px backdrop blur, a neutral hairline underneath.
   Wordmark left; the amber "Free checklist" pill right; from 1100px the
   seven links sit between them in one row. Below 1100px the links live in
   a native POPOVER sheet under the bar, opened by a "Menu" button with a
   three-line icon — the platform supplies Escape, light-dismiss, focus
   return and the top layer, so there is still no JavaScript on this site.
   The mandatory disclosure sits in its own strip under the bar, not inside
   the sticky element (a sticky 104px on a phone eats a sixth of the screen).
   -------------------------------------------------------------------------- */

.masthead {
  position: sticky;                 /* sticky, not fixed: fixed fights in-app webviews */
  inset-block-start: 0;
  z-index: 10;
  background-color: var(--surface-glass);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-block-end: var(--hair) solid var(--hairline); /* grey: on white an amber edge read as a warning stripe */
  padding-block-start: env(safe-area-inset-top);
  view-transition-name: masthead;   /* correct now that the bar is the same height on every page */
}

/* Solid while the sheet is open — a blur under a blur is mud — and for
   anyone who asked the OS for less transparency. */
.masthead:has(.masthead__nav:popover-open) { background-color: var(--surface-page); }

@media (prefers-reduced-transparency: reduce) {
  .masthead { -webkit-backdrop-filter: none; backdrop-filter: none; background-color: var(--surface-page); }
}

.masthead__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-block-size: var(--masthead-h);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);          /* it is a navigation target, so it is a target */
  font-family: var(--f-head);
  font-weight: 500;
  font-size: var(--fs-lead);
  letter-spacing: var(--tr-h3);
  color: var(--text-primary);
  text-decoration: none;
  white-space: nowrap;
}

.wordmark__mark {
  flex: none;
  inline-size: 1.75rem;
  block-size: 1.75rem;
  margin-inline-end: var(--sp-2);
}

.wordmark__text { white-space: nowrap; }   /* the name never breaks beside the mark */

/* The SVG's presentation attributes point at --surface-inset and
   --text-on-inset, which on white resolve to the amber tint: a 28px mark that
   is nearly invisible. CSS beats a presentation attribute, so the mark is
   re-inked here: navy disc, white ZT, amber bar — the OG-image mark. */
.wordmark__mark text {
  font-family: var(--f-head);
  font-size: 260px;
  font-weight: 500;
  fill: var(--text-on-band);
}
.wordmark__mark circle:first-of-type { fill: var(--surface-band); }

/* On a phone the bar cannot hold mark + name + pill + Menu; see the 599px
   block below for what gives way (the pill), and why. */
@media (max-width: 599px) {
  /* Below 600px the bar cannot hold mark + name + pill + Menu (~560px needed).
     The NAME wins: it is the brand's first appearance on the first screen.
     The amber pill is hidden here; the hero repeats the same offer as a
     52px button a few lines down, and the popover menu keeps every link. */
  .masthead .masthead__cta { display: none; }   /* two classes: .btn's display comes later in the file */
  .masthead__toggle { margin-inline-start: auto; }
}

.wordmark:hover { text-decoration: underline; text-decoration-color: var(--level); text-underline-offset: 4px; }

/* The standfirst has no home in a 60px bar. The selector stays so that no
   page breaks; the element stays in the DOM (it is the page's tagline in
   the accessibility tree). */
.masthead__dateline { display: none; }

/* The header pill: the primary offer, 40px tall (the bar cannot carry a
   52px button), pushed to the trailing edge. */
.masthead__cta {
  margin-inline-start: auto;
  min-height: var(--tap-hd);
  padding-inline: var(--sp-4);
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-micro);
  white-space: nowrap;
}

/* The "Menu" button: a real button with a visible word and a three-line
   icon drawn as inline SVG (no icon font). The UA exposes aria-expanded
   on a popovertarget button by itself. */
.masthead__toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-block-size: var(--tap);
  padding-inline: var(--sp-2);
  margin-inline-end: calc(-1 * var(--sp-2));   /* the word, not the padding, aligns with the gutter */
  background: none;
  border: 0;
  border-radius: var(--r-ctl);
  color: var(--text-primary);
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-label);
  line-height: 1;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  cursor: pointer;
}

.masthead__icon { inline-size: 20px; block-size: 20px; flex: none; }
.masthead__icon line {
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  transform-box: fill-box;
  transform-origin: center;
  transition: transform var(--t-nav) var(--ease), opacity var(--t-nav) var(--ease);
}

/* Three lines become a cross while the sheet is open. */
.masthead:has(.masthead__nav:popover-open) .masthead__icon line:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.masthead:has(.masthead__nav:popover-open) .masthead__icon line:nth-child(2) { opacity: 0; }
.masthead:has(.masthead__nav:popover-open) .masthead__icon line:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* THE SEVEN LINKS. One element, three renderings:
   (1) below 1100px in a popover-capable engine: a sheet pinned under the
       bar, one link per row at lead size;
   (2) from 1100px: in flow, one row beside the wordmark, popover behaviour
       switched off by giving the closed element `display: block`;
   (3) in an engine without the popover API: the button is hidden and the
       list wraps under the wordmark, exactly as the previous skin did. */
.masthead__nav ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--sp-5);
}

.masthead__nav li { scroll-snap-align: start; }

.masthead__nav a {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-label);
  letter-spacing: 0;
  color: var(--text-secondary);
  text-decoration: none;
  white-space: nowrap;
  border-block-end: 2px solid transparent;
  transition: color var(--t-state) var(--ease), border-color var(--t-state) var(--ease);
}

.masthead__nav a:hover { color: var(--text-primary); border-block-end-color: var(--level); }

.masthead__nav a[aria-current="page"] { color: var(--level); border-block-end-color: var(--level); }

/* Kept from the scroller this replaced so nothing that names it breaks. */
.masthead__nav { flex: 1 0 100%; }
.masthead__nav::-webkit-scrollbar { display: none; }

/* (1) THE SHEET — only where :popover-open exists, so an old engine never
   sees `position: fixed` on a list it cannot open. */
@media (max-width: 1099px) {
  @supports selector(:popover-open) {
    .masthead__nav[popover] {
      position: fixed;
      inset: calc(var(--masthead-h) + env(safe-area-inset-top)) 0 auto 0;
      inline-size: auto;
      block-size: auto;
      max-block-size: calc(100dvh - var(--masthead-h));
      overflow: auto;
      overscroll-behavior: contain;
      margin: 0;
      padding: var(--sp-2) var(--gutter) var(--sp-6);
      border: 0;
      border-block-end: var(--hair) solid var(--hairline);
      background-color: var(--surface-page);
      color: var(--text-primary);
      box-shadow: 0 24px 48px -24px var(--card-shadow);

      /* entry and exit: fade + 8px drop; display and overlay are discrete
         and need allow-discrete to wait for the fade */
      opacity: 0;
      translate: 0 -8px;
      transition: opacity var(--t-nav) var(--ease),
                  translate var(--t-nav) var(--ease),
                  display var(--t-nav) allow-discrete,
                  overlay var(--t-nav) allow-discrete;
    }
    .masthead__nav:popover-open { opacity: 1; translate: 0 0; }
    @starting-style {
      .masthead__nav:popover-open { opacity: 0; translate: 0 -8px; }
    }

    .masthead__nav[popover]::backdrop {
      background-color: var(--scrim);
      transition: background-color var(--t-nav) var(--ease),
                  display var(--t-nav) allow-discrete,
                  overlay var(--t-nav) allow-discrete;
    }
    @starting-style {
      .masthead__nav:popover-open::backdrop { background-color: transparent; }
    }

    .masthead__nav[popover] ul { display: grid; gap: 0; }
    .masthead__nav[popover] a {
      min-height: var(--tap-cta);
      font-weight: 400;
      font-size: var(--fs-lead);
      color: var(--text-primary);
      border-block-end: var(--hair) solid var(--hairline-raised);
    }
    .masthead__nav[popover] li:last-child a { border-block-end-color: transparent; }
    .masthead__nav[popover] a:hover { color: var(--level); }
    .masthead__nav[popover] a[aria-current="page"] { border-block-end-color: var(--hairline-raised); }
  }
}

/* (2) ONE ROW on a wide screen: wordmark, the seven links, the pill. The
   closed popover is given display: block, which takes it out of the top
   layer and into the flex row; every UA popover default is reset. */
@media (min-width: 1100px) {
  .masthead__toggle { display: none; }
  .masthead__nav,
  .masthead__nav[popover] {
    display: block;
    position: static;
    inset: auto;
    inline-size: auto;
    block-size: auto;
    max-block-size: none;
    overflow: visible;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    flex: 1 1 auto;
    order: 2;
    opacity: 1;
    translate: none;
    transition: none;
  }
  .masthead__nav ul { flex-wrap: nowrap; justify-content: flex-end; gap: var(--sp-4); }
  .wordmark { order: 1; }
  .masthead__cta { order: 3; margin-inline-start: var(--sp-3); }
}

/* (3) No popover API: hide the inert button; the list wraps under the
   wordmark as it did before. Two-row header, still sticky, still fine. */
@supports not selector(:popover-open) {
  .masthead__toggle { display: none; }
  .masthead__inner { flex-wrap: wrap; }
  .masthead__nav { display: block; }
}

/* The mandatory disclosure, set to be read rather than hidden.
   Properly set disclosure reads as confidence; hidden 9px grey reads as evasion.
   It sits immediately under the masthead rather than inside the sticky
   element. It is still above the fold on every page, still real rendered
   text, and it still scrolls under nothing. */
.disclosure-strip {
  background-color: var(--surface-raised);
  border-block-end: var(--hair) solid var(--hairline);
}

.disclosure {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  padding-block: var(--sp-2);
  max-inline-size: none;
}

/* Chrome: the scroll-progress hairline on the masthead's bottom edge.
   Transform only — never animate `top`, which forces layout every frame. */
.masthead__progress {
  grid-column: full-start / full-end;
  block-size: 2px;
  margin-block-end: -2px;
  background-color: var(--level);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

@supports (animation-timeline: scroll(root block)) {
  .masthead__progress {
    animation: zt-progress linear both;
    animation-timeline: scroll(root block);
  }
}

@keyframes zt-progress { from { transform: scaleX(0); } to { transform: scaleX(1); } }


/* --------------------------------------------------------------------------
   6. THE LEVEL LINE — the section divider
   Every hr.rule is the held line, in amber-ink (7.05:1 on white). It is the
   brand's one signature that survives the white page intact.
   -------------------------------------------------------------------------- */

.rule {
  border: 0;
  border-block-start: var(--hair) solid var(--level);
  block-size: 0;
  margin: 0;
}

/* The one rule adjacent to the page's opening statement carries the view
   transition. Names must be unique per document, so ONLY this one has it. */
.rule--held { view-transition-name: held-line; }

/* Every section is a size container: the cards, the shelf and the button
   pair inside it ask how wide THEIR column is, not how wide the window is.
   Inline-size containment on a subgrid item is safe here because the .page
   tracks never depend on content. */
.section { padding-block: var(--rhythm-a); container-type: inline-size; }
.section--tight { padding-block: var(--rhythm-a) var(--sp-7); }
.section--tall  { padding-block: var(--rhythm-c); }

/* A section on the cool-grey field, edge to edge — forex.com's below-the-fold
   rhythm. It is a full-bleed subgrid so its children still sit in the content
   column. Never two bands in a row; never more than three on a page. A rule
   touching a band is redundant and is hidden. */
.section--band {
  grid-column: full-start / full-end;
  display: grid;
  grid-template-columns: subgrid;
  background-color: var(--surface-raised);
  container-type: normal;   /* full-bleed: its width is the window's, which is not what the cards should read */
}
.section--band > .prose, .section--band > .spec, .section--band > .actions { container-type: inline-size; }
.section--band > * { grid-column: content-start / content-end; }
.rule:has(+ .section--band),
.section--band + .rule,
.section--band + .adslot--inline + .rule { display: none; }


/* --------------------------------------------------------------------------
   7. THE HEAD BLOCK (page opening)
   -------------------------------------------------------------------------- */

.pagehead { padding-block: clamp(var(--sp-7), 32px + 2.5vw, var(--sp-8)) var(--sp-6); container-type: inline-size; }   /* bottom air: a hero pill must never touch the section rule */

.pagehead h1 { margin-block-end: var(--sp-5); }

.pagehead .lead { margin-block-end: var(--sp-6); }

.dateline {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-block-end: var(--sp-4);
}


/* --------------------------------------------------------------------------
   8. BUTTONS AND CALLS TO ACTION
   -------------------------------------------------------------------------- */

/* The solid pill: raw amber carrying navy text (6.56:1). 52px tall — the
   primary CTA height — wherever it appears; the ad slot's CTA opts down to
   48px in section 25 and the header pill to 40px in section 5. Uppercase
   14–15px Medium with a little air: the owner's forex.com taste, at 2026
   height. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-cta);
  padding: 0 var(--sp-6);
  background-color: var(--level-solid);      /* raw amber fill... */
  color: var(--on-level);                     /* ...always carrying navy text */
  border: 0;
  border-radius: var(--r-pill);
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-label);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  cursor: pointer;
  transition: background-color var(--t-state) var(--ease),
              transform var(--t-state) var(--ease);
}

.btn:hover { background-color: var(--level-solid-hi); }

.btn:active { background-color: var(--level-solid-lo); transform: translateY(1px); }

/* Full width on a phone, shrink-wrapped once the CONTAINER (the .actions
   paragraph, or the section) is 30rem wide. */
.btn--block { display: flex; inline-size: 100%; min-height: var(--tap-cta); }

@container (min-width: 30rem) { .btn--block { display: inline-flex; inline-size: auto; } }

/* Secondary pill: outlined in amber-ink, for the ad slot and for a band. */
.btn--outline {
  background-color: transparent;
  color: var(--level);
  box-shadow: inset 0 0 0 1.5px var(--level);
}
.btn--outline:hover  { background-color: var(--surface-inset); }
.btn--outline:active { background-color: var(--level-tint); }

/* Secondary action: a text link that is visibly a link at rest (WCAG 1.4.1)
   — an amber-ink underline (the grey one disappeared on a phone) that
   thickens on hover. min-height gives it a 44px tap box. */
.link-next {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-body);
  color: var(--text-primary);
  text-decoration-color: var(--level);
  text-decoration-thickness: 1px;
}

.link-next:hover { text-decoration-color: var(--level); text-decoration-thickness: 2px; }

/* The hero's secondary action as an outlined pill, so a phone shows the
   forex.com pair: one solid pill, one outlined, stacked full width. Opt-in
   per page (add the class to the first .link-next in .pagehead .actions). */
.link-next--pill {
  justify-content: center;
  min-height: var(--tap-cta);
  padding: 0 var(--sp-6);
  border-radius: var(--r-pill);
  color: var(--level);
  text-decoration: none;
  box-shadow: inset 0 0 0 1.5px var(--level);
  transition: background-color var(--t-state) var(--ease);
}
.link-next--pill:hover { background-color: var(--surface-inset); text-decoration: none; }
.link-next--pill { display: flex; inline-size: 100%; }
@container (min-width: 30rem) { .link-next--pill { display: inline-flex; inline-size: auto; } }

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4) var(--sp-6);
  margin-block-start: var(--sp-6);
  max-inline-size: none;   /* a <p>, so it inherits the measure; two hero pills need 720px side by side */
  container-type: inline-size;
}

/* The hero pair — solid pill + outlined pill — sits closer than a pill and a
   text link would. */
.actions:has(> .btn + .link-next--pill) { gap: var(--sp-3) var(--sp-4); }

/* The fine print that follows a call to action ("One page. Free. Your
   address is used…") is a paragraph, not a label: sentence case at 14px.
   Five lines of tracked capitals was the most tiring passage on the phone. */
.actions + .micro {
  font-weight: 400;
  font-size: var(--fs-label);
  line-height: var(--lh-body);
  letter-spacing: 0;
  text-transform: none;
  max-inline-size: var(--measure);
}


/* --------------------------------------------------------------------------
   9. THE EMAIL CAPTURE COMPONENT
   A boxed field with a hairline border; the amber tint glows on focus.

   LAUNCH BEHAVIOUR — read before editing:
   At launch the checklist offer is a LINK to the existing Google Form, not a
   POST. Two reasons, both hard: (1) the site's CSP allows form-action for
   docs.google.com only, and forms.gle is a separate host that would be
   blocked; (2) a GET submission would put a subscriber's email address into
   a URL query string. A link out is unaffected by CSP and leaks nothing.

   When an email provider is chosen, this component becomes a real form and
   the change is one line — the `action` attribute:

     <form class="signup" action="https://ESP-ENDPOINT" method="post">
       <label class="signup__label" for="email">Email address</label>
       <input class="signup__input" id="email" name="email" type="email"
              autocomplete="email" inputmode="email" required
              placeholder="you@example.com">
       <button class="btn btn--block" type="submit">Send me the checklist</button>
       <p class="signup__note micro">One email with the checklist ... unsubscribe in one click.</p>
     </form>
   -------------------------------------------------------------------------- */

.signup { max-inline-size: var(--measure-lead); }

.signup__label {
  display: block;
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-block-end: var(--sp-2);
}

.signup__input {
  display: block;
  width: 100%;
  min-height: var(--tap-cta);
  padding: var(--sp-3) var(--sp-4);
  font-family: var(--f-text);
  font-size: 1rem;                    /* 16px MINIMUM or iOS zooms on focus */
  color: var(--text-primary);
  background-color: var(--surface-page);
  border: var(--hair) solid var(--graphic);     /* 3.11:1 — a form control's edge must clear 3:1; --hairline (2.08) does not */
  border-radius: var(--r-ctl);
  transition: border-color var(--t-state) var(--ease),
              box-shadow var(--t-state) var(--ease);
}

.signup__input::placeholder { color: var(--text-muted); }

.signup__input:focus { border-color: var(--level); box-shadow: 0 0 0 3px var(--surface-inset); }

.signup .btn { margin-block-start: var(--sp-5); }

.signup__note { margin-block-start: var(--sp-3); max-inline-size: none; }


/* --------------------------------------------------------------------------
   10. TABLES
   Neutral hairlines top, bottom and between rows; the header row sits on the
   field. No amber on a table — the level line is spent on sections only.
   No zebra striping — zebra reads as a spreadsheet template.
   -------------------------------------------------------------------------- */

.table-wrap {
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-snap-type: x proximity;
}

/* A table wider than its scrollport (min-width 30rem in a 335px phone column)
   hides its last column behind an overlay scrollbar nobody can see until they
   touch it. Four background layers say "there is more" without a script:
   two shadows pinned to the edges (attachment: scroll) and two covers that
   travel with the content (attachment: local) and hide the shadow on the
   side that has nothing left to show. Nothing is clipped, and when the table
   fits, the covers sit exactly on the shadows and nothing is visible at all.
   --table-field is the colour of the ground the table sits on: the page by
   default, the raised field inside a band. The shadow is a token mix. */
.table-wrap {
  --table-field: var(--surface-page);
  background:
    linear-gradient(to right, var(--table-field) 45%, transparent) left / 40px 100% no-repeat local,
    linear-gradient(to left, var(--table-field) 45%, transparent) right / 40px 100% no-repeat local,
    linear-gradient(to right, color-mix(in srgb, var(--text-primary) 18%, transparent), transparent) left / 14px 100% no-repeat scroll,
    linear-gradient(to left, color-mix(in srgb, var(--text-primary) 18%, transparent), transparent) right / 14px 100% no-repeat scroll;
}
.section--band .table-wrap { --table-field: var(--surface-raised); }

.table {
  width: 100%;
  min-width: 30rem;
  border-collapse: collapse;
  border-block: var(--hair) solid var(--hairline);
  font-size: var(--fs-data);
  font-family: var(--f-text);
}

/* THE CAPTION SITS OUTSIDE THE SCROLLPORT, AS A SIBLING OF .table-wrap.
   A <caption> is laid out at the TABLE's width, and the table carries
   `min-width: 30rem`. Inside a 280px scrollport at 320px that means the
   caption wraps its text at 480px and the right 200px of every line of it —
   including the "session not updated" warning on /gex/ — sits off screen
   behind a sideways scroll a reader has no reason to perform on a line of
   prose. So the caption is a <p class="table-cap"> before the wrapper, and the
   table is tied to it with aria-labelledby, which is an accessible name by the
   same rules a <caption> would have given it.
   The `.table caption` rule is kept so a table that still carries one is not
   unstyled — but do not add new ones. */
.table caption,
.table-cap {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  text-align: start;
  padding-block-end: var(--sp-3);
  max-inline-size: none;
}

.table-cap { padding-block-end: 0; margin-block-end: var(--sp-3); }

.table th {
  text-align: start;
  font-weight: 500;
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  background-color: var(--surface-raised);
  border-block-end: var(--hair) solid var(--hairline);
  padding: var(--sp-3) var(--sp-4);
  white-space: nowrap;
}

.table td {
  padding: var(--sp-3) var(--sp-4);
  border-block-start: var(--hair) solid var(--hairline);
  color: var(--text-secondary);
  vertical-align: baseline;
}

/* The first column names the instrument or the level and must never break
   inside a word ("Nasdaq- / 100 futures" was the one squeezed spot on the
   phone). It gets a floor; the table scrolls if the others need it. */
.table th:first-child, .table td:first-child { min-inline-size: 9.5rem; }

/* A three-column table that must FIT a phone (the podcast archive: date,
   title, length) opts out of the scroll floor and the first-column floor. */
.table--narrow { min-width: 0; }
.table--narrow th:first-child, .table--narrow td:first-child { min-inline-size: 0; white-space: nowrap; }

.table tbody tr { transition: background-color var(--t-state) var(--ease); }
@media (hover: hover) {
  .table tbody tr:hover { background-color: var(--surface-hover); }
}

.table .is-level { font-family: var(--f-mono); color: var(--level); text-align: end; }

/* THE UNIT RULE. There is no code path on this site that emits a bare number,
   and none that emits the forbidden forex slang for a minimum price increment.
   A price always arrives as instrument + value + unit + tick size. */
.table .unit, .unit {
  display: block;
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
}


/* --------------------------------------------------------------------------
   11. THE SPECIFICATION LIST
   Grouping without cards: hairlines and a two-column definition list.
   -------------------------------------------------------------------------- */

/* Mobile first: a single stacked column, term above value, one hairline per
   entry. A two-column definition list at 375px leaves ~140px for the value
   and sets it four words to the line — that is the tell of a desktop layout
   squeezed onto a phone. */
.spec {
  border-block-start: var(--hair) solid var(--hairline);
  margin: 0;
}

.spec dt {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  padding-block-start: var(--sp-4);
}

.spec dd {
  margin: 0;
  padding-block: var(--sp-2) var(--sp-4);
  border-block-end: var(--hair) solid var(--hairline);
  color: var(--text-secondary);
  font-size: var(--fs-body);
  max-inline-size: var(--measure);
}

@media (min-width: 720px) {
  .spec {
    display: grid;
    grid-template-columns: minmax(9rem, 14rem) 1fr;
    column-gap: var(--sp-6);
  }
  .spec dt {
    grid-column: 1;
    padding-block: var(--sp-4);
    border-block-end: var(--hair) solid var(--hairline);
  }
  .spec dd {
    grid-column: 2;
    padding-block: var(--sp-4);
  }
}

/* CARD MODIFIER for the catalogue and the product shelf: each term/value
   pair becomes one white card — forex.com's "short heading + two lines" card,
   minus the icon. Built from the two existing elements with no markup
   change: the dt is the card's top half (an h3-size Poppins 400 heading, NOT
   the uppercase micro-label of the plain list) and the dd its bottom half;
   the borders meet at the seam. One column in a narrow container; two
   columns once the CONTAINER is 40rem wide (not the viewport — inside a
   rail layout the column is narrower than the window), placed with
   `grid-auto-flow: row dense` so odd pairs land in column 1 and even pairs
   in column 2 with dt and dd stacked — the two cards of a row share their
   row heights, so their bottoms align. 16px radius, hairline, no shadow:
   0 of ~90 cards measured on 12 broker sites cast one. */
.spec--cards { border-block-start: 0; max-inline-size: none; margin-block-start: var(--sp-6); }

.spec--cards dt {
  font-family: var(--f-head);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  letter-spacing: 0;
  text-transform: none;
  text-wrap: balance;
  color: var(--text-primary);
  padding: var(--sp-5) var(--sp-5) 0;
  background-color: var(--surface-page);
  border: var(--hair) solid var(--card-edge);
  border-block-end: 0;
  border-radius: var(--r-card) var(--r-card) 0 0;
}

.spec--cards dd {
  padding: var(--sp-3) var(--sp-5) var(--sp-5);
  background-color: var(--surface-page);
  border: var(--hair) solid var(--card-edge);
  border-block-start: 0;
  border-radius: 0 0 var(--r-card) var(--r-card);
  max-inline-size: none;
}

.spec--cards dd + dt { margin-block-start: var(--sp-4); }

/* A status chip inside a card sits on its own line under the copy, never
   mid-sentence. `display: table` shrink-wraps it without a wrapper. */
.spec--cards dd .chip { display: table; margin-block-start: var(--sp-3); }
.spec--cards dd:has(.chip) { padding-block-end: var(--sp-4); }

@media (min-width: 720px) {
  .spec--cards { display: block; }
  .spec--cards dt { padding: var(--sp-5) var(--sp-5) 0; border-block-end: 0; }
  .spec--cards dd { padding: var(--sp-3) var(--sp-5) var(--sp-5); }
}

@container (min-width: 40rem) {
  .spec--cards {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-flow: row dense;
    column-gap: var(--sp-5);
    row-gap: 0;
  }
  .spec--cards dt:nth-of-type(odd),  .spec--cards dd:nth-of-type(odd)  { grid-column: 1; }
  .spec--cards dt:nth-of-type(even), .spec--cards dd:nth-of-type(even) { grid-column: 2; }
  .spec--cards dd { margin-block-end: var(--sp-5); }
  .spec--cards dd + dt { margin-block-start: 0; }
}


/* --------------------------------------------------------------------------
   12. THE PROMISE LIST
   Used for "what this site will never do". Amber-ink dash, no bullets, and
   never a check glyph — a check is green in every reader's head.
   -------------------------------------------------------------------------- */

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

.promises li {
  position: relative;
  padding-inline-start: var(--sp-5);
  padding-block: var(--sp-3);
  max-inline-size: var(--measure);
  border-block-end: var(--hair) solid var(--hairline);
}

.promises li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: calc(0.7em + var(--sp-3));
  inline-size: 12px;
  block-size: var(--hair);
  background-color: var(--level);
}

.promises li:last-child { border-block-end: 0; }


/* --------------------------------------------------------------------------
   13. STATUS CHIPS
   The absence of a price is styled as a specification, not an omission.
   -------------------------------------------------------------------------- */

/* Tinted pills — the one place forex.com's badge language fits. The border
   follows the text colour, so a chip never needs a border token of its own.
   muted on field 6.52; teal-ink on teal tint 6.94; amber-ink on amber tint 6.30. */
.chip {
  display: inline-block;
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  padding: var(--sp-1) var(--sp-3);
  border: var(--hair) solid currentColor;
  border-radius: var(--r-pill);
  color: var(--text-muted);
  background-color: var(--surface-raised);
}

.chip--live    { color: var(--state); background-color: var(--state-tint); }
.chip--pending { color: var(--level); background-color: var(--level-tint); }


/* --------------------------------------------------------------------------
   13b. THE SHELF
   One row per built product: name, status chip, one sentence, one line of
   distribution fact. On the white page each row is a CARD — white face,
   --card-edge hairline, 8px radius, no shadow — the one list on the site
   that is allowed boxes, because a product catalogue is what cards are for.

   Once the CONTAINER is 36rem wide the chip moves into its own column and
   every status in a shelf lands on one vertical axis — one axis for real,
   because the column is a track of the LIST (subgrid), not of each row.
   That is the entire point: twelve products have to read as one instrument
   panel. DOM order and visual order agree at every width.
   -------------------------------------------------------------------------- */

.shelf {
  list-style: none;
  padding: 0;
  margin: 0;
  border-block-start: 0;
}

.shelf > li {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-5);
  background-color: var(--surface-page);
  border: var(--hair) solid var(--card-edge);
  border-radius: var(--r-card);
}

.shelf > li + li { margin-block-start: var(--sp-4); }

.shelf > li > * { max-inline-size: var(--measure); }

/* The row's rhythm is the grid gap and nothing else. Without this the generic
   `p + p` margin also fires between the chip, the sentence and the meta line,
   and it fires on desktop too, where the chip is not even in that column. */
.shelf > li > p + p { margin-block-start: 0; }

.shelf .chip { justify-self: start; }

.shelf .micro { max-inline-size: none; }

@container (min-width: 36rem) {
  .shelf {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(9rem, max-content);
    column-gap: var(--sp-6);
  }
  .shelf > li {
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
    column-gap: var(--sp-6);
  }
  .shelf > li > * { grid-column: 1; }
  .shelf .chip { grid-column: 2; grid-row: 1; justify-self: end; }
  /* No subgrid: each row measures its own chip column, as before. */
  @supports not (grid-template-columns: subgrid) {
    .shelf { display: block; }
    .shelf > li { grid-template-columns: 1fr minmax(9rem, max-content); }
  }
}


/* --------------------------------------------------------------------------
   14. THE ZONE SCORE METER  (brand component 1)
   Seven 6x22px slots, 4px gaps. Filled = raw amber with an amber-ink edge
   (the edge is what clears 3:1 on white). Empty = transparent with a 1px
   --mark-edge border. A 1px teal-ink rule spans only the filled portion.

   IT DISPLAYS AN OUTPUT AND NOTHING ELSE.
   No factor names. No weights. No thresholds. No tooltip. No text nodes
   inside the meter. Nothing in a data- attribute. Nothing in JSON-LD.
   A static site has no server-side hiding place: whatever is in the DOM is
   in the crawl, and with training crawlers that is unretractable.

   IT IS STATIC. It never animates, in any theme, at any breakpoint.

   Markup, exactly:
     <span class="zone-score" role="img" aria-label="Zone score 5 of 7">
       <i class="is-filled"></i> x5 ... <i></i> x2
     </span>
   -------------------------------------------------------------------------- */

.zone-score {
  display: inline-flex;
  align-items: flex-end;
  gap: 4px;
  vertical-align: middle;
  position: relative;
}

/* --mark-edge, not --hairline. An empty slot is 6x22px: at the hairline's
   contrast it disappears and a 5/7 meter reads as five free-floating amber
   ticks with no denominator, which is the one thing this mark exists to say.
   --mark-edge is pinned at 3.08:1 in BOTH themes. */
.zone-score i {
  display: block;
  width: 6px;
  height: 22px;
  border: var(--hair) solid var(--mark-edge);
  background-color: transparent;
}

.zone-score i.is-filled {
  background-color: var(--level-solid);
  border-color: var(--level);
}

/* The confirmation rule spans only the filled portion. Applied by the page
   only where the outcome warrants it; it encodes no threshold in its name.

   The span is a CLASS, not a custom property. `style-src 'self'` blocks the
   style attribute, so a per-instance `--filled-span` can never be set and the
   rule would always compute to width 0 — i.e. never render at all. The set of
   values is tiny and known at authoring time, so it is enumerated:
   n slots of 4px with 4px gaps span 8n - 4 pixels. */
.zone-score--rule::after {
  content: "";
  position: absolute;
  inset-block-end: -5px;
  inset-inline-start: 0;
  height: var(--hair);
  width: 0;
  background-color: var(--state);
}

.zone-score--rule.zone-score--4::after { width: 36px; }   /* 6px segments, 4px gaps: 10n - 4 */
.zone-score--rule.zone-score--5::after { width: 46px; }
.zone-score--rule.zone-score--6::after { width: 56px; }
.zone-score--rule.zone-score--7::after { width: 66px; }

.zone-score__value {
  font-family: var(--f-mono);
  font-size: var(--fs-data);
  color: var(--text-secondary);
  margin-inline-start: var(--sp-3);
  vertical-align: middle;
}

.score-line { display: flex; align-items: center; max-inline-size: none; }


/* --------------------------------------------------------------------------
   15. FIGURES AND CAPTIONS  (brand component 2 lives here too)
   Bounded by hairlines top and bottom only. Never a card, never a shadow.
   -------------------------------------------------------------------------- */

.figure {
  margin: 0;
  padding-block: var(--sp-5);
  border-block: var(--hair) solid var(--hairline);
}

.figure__head {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-block-end: var(--sp-4);
}

.figure__body { width: 100%; }

.figure figcaption {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--text-secondary);
  max-inline-size: var(--measure);
  margin-block-start: var(--sp-4);
}

/* The outcome line. Failure is typographic in this brand, never chromatic:
   the band border switches to dashed and the verdict is written out under a
   short amber-ink rule. */
.figure__outcome {
  font-family: var(--f-text);
  font-weight: 400;
  font-size: var(--fs-label);          /* 14px sentence case: a verdict is read, a label is glanced at */
  line-height: var(--lh-body);
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-muted);
  border-block-start: var(--hair) solid var(--level);
  padding-block-start: var(--sp-3);
  margin-block-start: var(--sp-4);
  max-inline-size: none;
}

/* Authored SVG re-themes for free because it reads the same tokens.

   The cap is load-bearing, not taste. Figures are authored on a 360-unit
   viewBox so that a 375px phone renders them close to 1:1, which is where the
   11px labels below are legible. A figure inside a `full` breakout would
   otherwise stretch to --content-max and scale those labels to about 33px.
   Capping at the measure keeps the plate aligned with the text column and
   holds label size around 20px at the widest. Stroke weights do not drift
   with it: every stroke the pipeline emits carries
   vector-effect="non-scaling-stroke", so the level line is exactly 1px at
   every viewport, which is the whole premise of the direction.
   Generated by /tools/make_chart_svg.py. */
.chart { width: 100%; height: auto; max-inline-size: 40rem; margin-inline: auto; }
/* Band FILLS are the raw (-solid) tokens at 14%: on white that is the same
   amber tint / teal tint as the inset and the chips, so the site has ONE amber
   tint. Band STROKES are the ink variants — the edge is what defines a zone
   and it must clear 3:1 (7.05 / 8.06). The path is navy at 1.5: a 1.25px
   secondary-ink polyline at 2x DPR looked thinner than the axis on white. */
.chart .axis   { stroke: var(--graphic); stroke-width: 1; }
.chart .path   { stroke: var(--text-primary); stroke-width: 1.5; fill: none;
                 stroke-linejoin: round; stroke-linecap: round; }
.chart .level  { stroke: var(--level); stroke-width: 1.5; }
.chart .supply { fill: var(--level-solid); fill-opacity: .14; stroke: var(--level); stroke-width: 1; }
.chart .demand { fill: var(--state-solid); fill-opacity: .14; stroke: var(--state); stroke-width: 1; }
.chart .broke  { stroke-dasharray: 3 2; }
.chart .tag {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  fill: var(--text-muted);
}
.chart .tag--level { fill: var(--level); }
.chart .tag--state { fill: var(--state); }


/* --------------------------------------------------------------------------
   16. THE PORTRAIT BLOCK
   Styled placeholder at the correct dimensions until a real photograph
   exists. There is no stock photo, no avatar and no illustration of a
   person anywhere in this system, by decision.
   -------------------------------------------------------------------------- */

.portrait {
  inline-size: clamp(11rem, 30cqi, 15rem);       /* 176px in a phone column, 240px in a wide one, by CONTAINER */
  aspect-ratio: 4 / 5;
  background-color: var(--surface-raised);
  border: var(--hair) solid var(--mark-edge);   /* a deliberate empty frame, not a faint smudge */
  border-radius: var(--r);
  overflow: hidden;                              /* a future <img> clips to the radius */
  display: grid;
  place-content: center;
  padding: var(--sp-4);
  flex: 0 0 auto;
}

.portrait p {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  text-align: center;
  max-inline-size: none;
}

.portrait img { inline-size: 100%; block-size: 100%; object-fit: cover; }

.byline {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--sp-5);
  container-type: inline-size;
}

.byline > .prose { flex: 1 1 20rem; }


/* --------------------------------------------------------------------------
   17. INSET PANEL
   The callout: amber tint with a 4px raw-amber leading edge, rounded on the
   far side. forex.com's callout box, without the icon. The same tokens drive
   .levels__banner on /gex/ (section 23).
   -------------------------------------------------------------------------- */

.inset {
  background-color: var(--surface-inset);
  color: var(--text-on-inset);
  padding: var(--sp-5) var(--sp-6);
  border-inline-start: 4px solid var(--level-solid);   /* a bar, so raw amber; no text on it */
  border-radius: 0 var(--r) var(--r) 0;
}

.inset p { color: inherit; }


/* --------------------------------------------------------------------------
   18. THE MARGINALIA RAIL
   Appears at >= 1024px and only ever carries real content: dates, tick
   counts, section index, source notes. It never carries decoration.
   -------------------------------------------------------------------------- */

.note {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  border-inline-start: 2px solid var(--hairline);
  padding-inline-start: var(--sp-4);
  margin-block: var(--sp-5);          /* air on both sides: below 1440px it sits in the flow */
  max-inline-size: none;
}

/* The rail only appears once there is genuinely room for it beside the
   content column — never by stealing width from a phone. */
@media (min-width: 1440px) {
  /* The dateline STAYS IN FLOW at every width. An earlier draft lifted it
     into the left margin with position:absolute, which threw it 114px above
     the heading it belongs to and left the h2 glued to the first paragraph
     (its spacing came from the note). Measured 2026-09-05; not worth a rail. */
  .has-rail { position: relative; }
  .has-rail > .note { margin-block-start: var(--sp-4); }
}


/* --------------------------------------------------------------------------
   19. THE FOOTER
   White, with the held line on top — its last appearance on the page. The
   folio sets the mandatory disclosure as a running foot rather than burying it.
   -------------------------------------------------------------------------- */

.footer {
  border-block-start: var(--hair) solid var(--level);   /* THE LEVEL LINE, last appearance */
  background-color: var(--surface-raised);              /* the light footer of the 2026 set, on the field */
  margin-block-start: var(--rhythm-b);
}

.footer__inner { padding-block: var(--sp-7) var(--sp-8); container-type: inline-size; }

.folio {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  justify-content: space-between;
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  border-block-end: var(--hair) solid var(--hairline);
  padding-block-end: var(--sp-4);
  max-inline-size: none;
}

.footer__cols {
  display: grid;
  gap: var(--sp-6);
  padding-block: var(--sp-6) var(--sp-7);
  border-block-end: var(--hair) solid var(--hairline);   /* spans the footer; the motto's own rule stopped at 30ch and looked broken */
}

@container (min-width: 36rem) {
  /* 6/3/3, never three equal thirds. */
  .footer__cols { grid-template-columns: 2fr 1fr 1fr; gap: var(--sp-7); }
}

/* A ten-link list one per row is two screens of scroll on a phone and a
   480px column on a desktop; two columns halve it at every width and every
   link keeps its 44px tap box. */
.footer__nav ul { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--sp-5); }
.footer__nav li + li { margin-block-start: 0; }

.footer__nav h2 {
  font-family: var(--f-text);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-block-end: var(--sp-3);
  text-wrap: initial;
}

.footer__nav ul { list-style: none; padding: 0; margin: 0; }

.footer__nav li + li { margin-block-start: var(--sp-1); }

.footer__nav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  font-family: var(--f-text);
  font-weight: 400;
  font-size: var(--fs-label);
  letter-spacing: 0;
  color: var(--text-secondary);
  /* A RESTING UNDERLINE, not a transparent one. Link-ness must never depend on
     colour alone (WCAG 1.4.1), and --text-secondary is deliberately only one
     step from body text. The hairline underline is quiet enough not to shout
     and present enough to say "this is a link". */
  text-decoration-color: var(--hairline);
}

.footer__nav a:hover { color: var(--text-primary); text-decoration-color: var(--level); }

.colophon {
  font-family: var(--f-text);
  font-size: var(--fs-micro);
  line-height: 1.6;
  letter-spacing: 0;                 /* 68ch of tracked uppercase is the one thing forex.com never does */
  color: var(--text-muted);
  margin-block-start: var(--sp-6);
  max-inline-size: 68ch;
}

.motto {
  font-family: var(--f-head);
  font-weight: 300;
  font-size: var(--fs-h2);           /* the one line of brand voice on the page; at h3 size it was missed */
  line-height: 1.2;
  color: var(--text-primary);
  margin-block-start: var(--sp-7);
  max-inline-size: 30ch;
}

.colophon--fine { text-transform: none; letter-spacing: 0; margin-block-start: var(--sp-4); }


/* --------------------------------------------------------------------------
   20. SPACING UTILITIES
   The CSP forbids inline style attributes (style-src 'self' does not cover
   them), so vertical spacing that varies per instance is expressed here.
   Keep this list short — it is a pressure valve, not a utility framework.
   -------------------------------------------------------------------------- */

.mt-3 { margin-block-start: var(--sp-3); }
.mt-5 { margin-block-start: var(--sp-5); }
.mt-6 { margin-block-start: var(--sp-6); }
.mt-7 { margin-block-start: var(--sp-7); }


/* --------------------------------------------------------------------------
   21. MOTION
   Three behaviours plus one piece of chrome. Nothing else moves.
   A reveal must never be the thing that makes content appear: every
   scroll-driven rule is wrapped in @supports so an unsupporting browser
   shows fully visible, un-clipped content.
   -------------------------------------------------------------------------- */

/* THE RESTING STATE IS DRAWN, AND THE ANIMATION ONLY EVER HIDES-THEN-REVEALS.
   `stroke-dashoffset: 0` is the base value and the keyframes carry their own
   `from`, with NO fill-mode. So an animation that never runs — an unsupported
   timeline, a timeline that never activates, a stylesheet that half-loads, a
   figure the browser decides not to animate — leaves a fully painted chart
   instead of a blank one. The old code declared the hidden state as the base
   and let the animation reveal it, which is exactly what the header of this
   section forbids: a reveal must never be the thing that makes content appear.

   Both animated elements carry pathLength="100" in the markup, which
   normalises the dash maths to a hundred units. That is exact at every
   viewport width and immune to vector-effect="non-scaling-stroke" moving dash
   lengths into screen space. There is no magic 2000 left to get wrong. */
.draw .path, .draw .level,
.draw--load .path, .draw--load .level { stroke-dashoffset: 0; }

@media (prefers-reduced-motion: no-preference) {

  /* THE HERO. A plain load-triggered keyframe, NO animation-timeline. The hero
     figure sits inside the initial viewport, where view() range progress is
     unreliable and can paint a partial stroke — and where a root that is not a
     scrollport leaves the progress null forever. */
  .draw--load .path, .draw--load .level {
    stroke-dasharray: 100;
    animation: zt-draw var(--t-draw) var(--ease);
  }

  /* Figures genuinely below the fold may ride the scroll. Same normalisation,
     same visible resting state; only the timeline differs. Nothing uses this
     today — it is here so the next below-the-fold figure has one correct way
     to do it and does not reinvent the broken one. */
  @supports (animation-timeline: view()) {
    .draw .path, .draw .level {
      stroke-dasharray: 100;
      animation: zt-draw var(--t-draw) var(--ease);
      animation-timeline: view();
      animation-range: entry 15% cover 40%;
    }
  }
}

@keyframes zt-draw {
  from { stroke-dashoffset: 100; }
  to   { stroke-dashoffset: 0; }
}

/* Page to page: a cross-document fade on --t-nav (the UA default is 250ms).
   The header carries its own name (section 5) so it holds still while the
   page beneath it changes. file:// never runs this; https does. */
@view-transition { navigation: auto; }

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--t-nav);
  animation-timing-function: var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  /* The hero renders at full stroke; the scroll hairline is removed entirely;
     view transitions are disabled. Every STATE change survives intact. */
  .draw .path, .draw .level,
  .draw--load .path, .draw--load .level { stroke-dasharray: none; stroke-dashoffset: 0; }
  .masthead__progress { display: none; }
  @view-transition { navigation: none; }
}


/* --------------------------------------------------------------------------
   22. PRINT
   -------------------------------------------------------------------------- */

@media print {
  @page { margin: 18mm; }
  .masthead__nav, .masthead__toggle, .masthead__cta, .masthead__progress, .skip, .disclosure-strip { display: none; }
  .masthead { position: static; -webkit-backdrop-filter: none; backdrop-filter: none; background-color: transparent; }
  .page { background-image: none; }
  .adslot { display: none; }

  /* Paper keeps the fills that carry meaning: a 6/7 meter must not print as
     seven empty slots, a pill must still look like a button. */
  .zone-score, .btn, .chip, .inset, .levels__banner, .levels__mark {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
  .figure, .spec--cards dd, .shelf > li, .table-wrap, .inset { break-inside: avoid; }
  h2, h3 { break-after: avoid; }
  .prose a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: var(--fs-micro);
    color: var(--text-muted);
  }
  .table-wrap { background: none; }   /* no scroll shadows on paper */
}


/* --------------------------------------------------------------------------
   23. THE DAILY DATA BLOCK  — used by /gex/ and by nothing else yet
   NEW SECTION. Added because /gex/ needs a state the rest of the site does
   not have: the difference between "the morning run completed" and "it did
   not". No new colour, no new radius, no card, no shadow. The stale banner
   is the existing .inset treatment (inset surface + a 2px level line on the
   leading edge); the state marks are --state and --level, used exactly as
   the colour law already assigns them: teal is state that is true right now,
   amber is the thing you must look at.

   ONE ATTRIBUTE DRIVES EVERYTHING. data-state="fresh|stale" on the .levels
   element is the only switch. Every sentence that is only true in one of
   those two states is hidden by DEFAULT IN BOTH DIRECTIONS, so a page whose
   renderer never ran claims nothing at all rather than claiming freshness.

   NOTHING HERE ANIMATES. No breathing dot, no pulse. This site publishes one
   file at 08:00 ET and nothing on it is live; a throbbing indicator would be
   a simulation of liveness, which the brand refuses. Staleness is therefore
   carried by colour AND by a label, so it survives prefers-reduced-motion,
   forced-colours and a monochrome print with no change of meaning.
   -------------------------------------------------------------------------- */

.levels__status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-5);
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--text-muted);
  border-block-end: var(--hair) solid var(--hairline);
  padding-block-end: var(--sp-3);
  max-inline-size: none;
}

.levels__mark {
  inline-size: 8px;
  block-size: 8px;
  flex: 0 0 auto;
  background-color: var(--graphic);      /* neutral until a state is stamped */
}

.levels[data-state="fresh"] .levels__mark  { background-color: var(--state); }
.levels[data-state="stale"] .levels__mark  { background-color: var(--level); }
.levels[data-state="fresh"] .levels__state { color: var(--state); }
.levels[data-state="stale"] .levels__state { color: var(--level); }

/* Copy belonging to exactly one state. Hidden until a state is stamped. */
.when-fresh, .when-stale { display: none; }
.levels[data-state="fresh"] .when-fresh { display: revert; }
.levels[data-state="stale"] .when-stale { display: revert; }

/* Elements that need a block box when revealed restate it AFTER the generic
   rule above at the same specificity, so source order decides and no
   !important is needed anywhere in this file. */
.levels[data-state="stale"] .levels__banner { display: block; }
.levels[data-state="stale"] .levels__flag   { display: block; }

.levels__banner {
  background-color: var(--surface-inset);
  color: var(--text-on-inset);
  border-inline-start: 4px solid var(--level-solid);   /* DECLARED DEVIATION from the original 2px --level: mirrors .inset (section 17) so /gex/'s stale banner and the home-page callout are one component */
  border-radius: 0 var(--r) var(--r) 0;
  padding: var(--sp-4) var(--sp-5);
  margin-block: var(--sp-5);
  max-inline-size: var(--measure);
}

.levels__banner-head {
  display: block;
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-label);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--level);                   /* 6.30:1 on the amber tint, 6.98:1 on brand navy */
  margin-block-end: var(--sp-2);
}

/* The SESSION NOT UPDATED line that the table caption gains when stale. */
.levels__flag {
  color: var(--level);
  margin-block-start: var(--sp-2);
}

/* Marks a cell the morning file had no value for. Never left blank: an empty
   cell reads as a rendering bug, and on this page it is information. */
.levels .is-absent { color: var(--text-muted); }

/* Row headers inside a levels table. Section 10 styles every `th` as a COLUMN
   header — raised fill plus the level line underneath — which is correct in a
   thead and wrong in a tbody, where it would draw an amber line under every
   single row and turn the level line into wallpaper. Scoped to .levels so no
   other table on the site changes. Row dividers stay --hairline, exactly as
   section 10 requires; only the semantics are recovered. */
.levels .table tbody th {
  background-color: transparent;
  border-block-start: var(--hair) solid var(--hairline);
  border-block-end: 0;
  color: var(--text-secondary);
  font-size: var(--fs-data);
  letter-spacing: 0;
  text-transform: none;
  white-space: normal;
  vertical-align: baseline;
}


/* --------------------------------------------------------------------------
   24. THE DRILL  — used by lesson five of the candlestick course, and by
   nothing else on this site.
   THE FIRST INTERACTIVE COMPONENT HERE. Read this header before editing it.

   TWO MECHANISMS, DELIBERATELY UNCONNECTED.

   The reveal is a native <details>. Its closed state is a DOM attribute plus
   the UA stylesheet, not a rule in this file, and that is the entire reason it
   was chosen over every :checked and :target variant: an answer hidden by
   author CSS is on screen the instant a stylesheet fails to arrive. Measured.
   <details> is also the only technique on the shortlist that already announces
   itself correctly ("button, collapsed"), is already keyboard-operable, is
   already out of the accessibility tree while closed, and already does not
   print. Nothing else does all five.

   The clock is a checkbox driving a strip of digits. It starts no reveal,
   gates no answer, marks no chart and blocks nothing. That is not timidity.
   A reveal on a ten-second timer is caught by the reduced-motion clamp in
   section 21, which overrides animation-duration and transition-duration but
   NOT the two delay properties — so a reduced-motion reader would watch the
   clock hit zero at once and then wait ten silent seconds for the answer.
   WCAG 2.2 SC 2.2.1 calls a ten-second limit on reaching content a failure,
   and every pause, extend or stop control that could excuse one needs script.
   So the clock paces the reader and owns nothing. The DURABLE reason is
   2.2.1, not the clamp: do not "fix" the clamp and re-gate the answer.

   NO SMIL, ANYWHERE ON THIS SITE. It would give a countdown that ignores the
   reduced-motion clamp with no CSS-level way to stop it; its begin="id.click"
   silently never fires when the referenced id contains a hyphen, which every
   id in this house does; and a fired <set fill="freeze"> keeps its value
   across a restart, so the second press shows the answer during the replay.
   Three reasons, any one sufficient. The build gate blocks it.

   NO @property + counter() FALLING DIGITS. It works and its failure mode is
   silent: on an engine that does not re-evaluate counter-reset against an
   animating registered integer, the number sits at ten for ten seconds. A
   clock that lies is worse than no clock.

   THE DIGITS CARRY NO CONTENT, which is how this satisfies section 21's law
   that a reveal must never be the thing that makes content appear. A browser
   that never runs the animation shows a resting "10" and a working drill.
   -------------------------------------------------------------------------- */

/* .drill rides .figure for its padding and its two hairlines. Ten stacked
   drills would otherwise print a doubled rule between every pair. */
.drill + .drill { border-block-start: 0; }


/* -- the clock row --------------------------------------------------------
   The input is clipped by .visually-hidden, so it is still focusable and
   still operable by Space; only its pixels are gone. The LABEL is the tap
   target, at --tap with real padding — not a parent with min-height, which
   grows the row and not the hit area. */

.drill__clock {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-4);
  margin-block-start: var(--sp-5);
  max-inline-size: var(--measure);
}

/* Not .btn. Section 8's amber fill is the primary call to action and belongs
   to the offer; ten filled amber buttons down a lesson would spend that
   emphasis on a stopwatch. Outline only. */
.drill__start {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  min-height: var(--tap);
  padding-inline: var(--sp-4);
  border: var(--hair) solid var(--level);
  border-radius: var(--r-ctl);
  color: var(--text-primary);
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-label);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color var(--t-state) var(--ease),
              border-color var(--t-state) var(--ease);
}

.drill__start:hover { background-color: var(--surface-hover); }

/* The control that receives focus has no pixels, so its ring is drawn on the
   label. `outline: none` still appears nowhere in this file. */
.drill__go:focus-visible + .drill__start {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 1px var(--focus-inner);
}

/* Ticked means "running or run". Untick to reset, tick again to re-run. It is
   a toggle and it is labelled as one; two presses to run it twice is the
   stated behaviour, not a defect to route around. */
.drill__go:checked + .drill__start {
  border-color: var(--hairline);
  color: var(--text-muted);
}


/* -- the digits -----------------------------------------------------------
   Eleven numerals on a strip, translated one cell per second by steps(10,
   end): the strip holds "10" for the first second, walks 9 down to 1, and
   lands on "0" at completion, where `forwards` holds it. Idle reads 10 and
   finished reads 0, so the two states are never the same picture.

   The cell height is a literal px and so is the type size, on purpose: the
   translate distance must be an exact multiple of the cell or the digits
   drift, and no --fs-* token is a 26px mono. This file already sets a literal
   11px on .chart .tag for the same reason.

   aria-hidden on the window, and it stays that way. There is no zero-script
   way to announce a changing value — a live region fires on DOM mutation and
   nothing here mutates the DOM — so a reader who cannot see the count is not
   handed eleven loose digits instead. The label carries the honest word
   "visual" and that is the whole accommodation available. */

.drill__well {
  display: block;
  flex: 0 0 auto;
  inline-size: 3.5ch;
  block-size: 36px;
  overflow: hidden;
  border-block-end: var(--hair) solid var(--hairline);
}

.drill__strip { display: block; }

.drill__strip > span {
  display: block;
  block-size: 36px;
  font-family: var(--f-mono);
  font-size: 26px;
  line-height: 36px;
  font-variant-numeric: lining-nums tabular-nums slashed-zero;
  color: var(--level);          /* 7.69:1 dark, 6.46:1 light, on the page field */
  text-align: center;
}

/* translateY, never inset or block-size: section 5 already records that
   animating a layout property forces layout every frame. The 360px is ten
   cells of 36px and nothing else — change one and change both. 10s is a
   literal, not a token, because tokens.css clamps every --t-* to 1ms under
   reduced motion and this animation is removed there instead. */
.drill__go:checked ~ .drill__well .drill__strip {
  animation: zt-drill-count 10s steps(10, end) forwards;
}

@keyframes zt-drill-count {
  from { transform: translateY(0); }
  to   { transform: translateY(-360px); }
}

/* One of these two sentences is shown, never neither. With CSS disabled both
   render, which is why they are worded so that reading both is still true. */
.drill__clock-off { display: none; }


/* -- the reveal -----------------------------------------------------------
   base.css had no details/summary rules before this section; these are the
   only ones and they are scoped to .drill__ so <details> cannot leak out into
   the rest of the site as a general accordion. IT IS FOR DRILL ANSWERS. The
   moment it becomes a frequently-asked-questions stack this site turns into a
   pile of collapsed boxes. */

.drill__reveal {
  margin-block-start: var(--sp-5);
  border-block-start: var(--hair) solid var(--hairline);
}

/* display:block, not flex. A <summary> at display:flex has a history of
   losing its disclosure semantics in WebKit, and block is enough: dropping
   off display:list-item is what removes the UA marker. The tap target is
   padding, so the focus ring wraps the real box. */
.drill__summary {
  display: block;
  list-style: none;
  min-height: var(--tap);
  padding-block: var(--sp-4);
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-label);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--text-primary);
  cursor: pointer;
}

.drill__summary::-webkit-details-marker { display: none; }

/* A TEXT glyph, not a clip-path shape and not a background-color triangle.
   The marker is the only expand and collapse affordance left once the UA one
   is gone, and in Windows High Contrast a painted shape can vanish while a
   character cannot. It also swaps on a DOM state, so it is correct with
   motion off and correct on paper. */
.drill__summary::before {
  content: "+";
  color: var(--level);
  margin-inline-end: var(--sp-3);
  font-variant-numeric: tabular-nums;
}

.drill__reveal[open] > .drill__summary::before { content: "\2212"; }

.drill__summary:hover { color: var(--level); }

/* The level line hugs the label text, not the block. */
.drill__summary-text {
  border-block-end: var(--hair) solid var(--level);
  padding-block-end: 3px;
}

.drill__answer {
  padding-block: var(--sp-4) var(--sp-5);
  color: var(--text-secondary);
}

/* The verdict voice, same as .figure__outcome: small caps-style label,
   amber-ink. NOT bold — emphasis in this brand is a change of voice. */
.drill__name {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-label);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--level);
  margin-block-end: var(--sp-4);
}


/* -- reduced motion -------------------------------------------------------
   The whole clock row goes, which also takes its checkbox out of the tab
   order, so no focusable control is left that visibly does nothing. The drill
   is untouched: the plate, the prompt and the answer are all still there,
   with no clock on top of them, and the intro sentence swaps to one that says
   so rather than promising a count that is not on the page.

   IF THIS IS EVER REVERSED, the correct reversal is exactly one line:
     .drill__strip { animation-duration: 10s !important; }
   because the global `*` clamp in section 21 is !important and specificity
   (0,1,0) beats (0,0,0). Do NOT weaken the global clamp to run a stopwatch. */

@media (prefers-reduced-motion: reduce) {
  .drill__clock,
  .drill__clock-on { display: none; }
  .drill__clock-off { display: inline; }
}


/* -- print ----------------------------------------------------------------
   Paper prints what the screen shows. A closed answer stays closed, because
   a closed <details> is content-visibility:hidden in the UA stylesheet and
   does not render — so a printed lesson five is a clean ten-question
   worksheet with no answers on it. That is the right default and the lesson
   copy says so out loud, because a reader who expects an answer key and gets
   a blank sheet will assume the page is broken.

   break-inside is on .drill, not on .drill__reveal: the thing that actually
   breaks across a sheet is the plate, not the disclosure. */

@media print {
  .drill__clock { display: none; }
  .drill__clock-off { display: inline; }
  .drill { break-inside: avoid; }
}



/* ------------------------------------------------------------------
   zt-fix-heading-flow
   A heading that is followed by a block in normal flow had no space
   beneath it: h2 carries no margin, and `.prose > * + *` only spaces
   the prose children against EACH OTHER, never the heading above them.
   Measured at 62 headings across the site sitting flush against the
   next block, so the first line of body copy collided with the
   heading's descenders.
   `.chip` is excluded on purpose: inside `.shelf` it is grid-placed
   (grid-column 2 / grid-row 1), so it is not in flow and must not be
   pushed down.
   ------------------------------------------------------------------ */
:is(h1, h2, h3) + :is(.prose, .byline, .promises, .spec, .table-wrap,
                      .signup, ul, ol, dl, figure, blockquote, p:not(.chip)) {
  margin-block-start: var(--sp-4);
}

/* Poppins Light at h1/h2 size needs more room than 16px or its
   descenders crowd the first line of body copy. h3 is close to body
   size, so 16px is correct there. */
:is(h1, h2) + :is(.prose, .byline, .promises, .spec, .table-wrap,
                  .signup, ul, ol, dl, figure, blockquote, p:not(.chip)) {
  margin-block-start: var(--sp-5);
}


/* ------------------------------------------------------------------
   zt-fix-tag-halo
   Chart labels sit inside the plot area, so a price path or a level
   will sometimes run straight through them. Measured on 7 labels
   across /, /about/, /method/ and /gex/.
   Rather than hand-placing every label — which breaks again the moment
   the daily renderer emits a figure with different geometry — each
   label paints a halo of the page background behind its own glyphs
   first. This is how map labels stay readable over roads.
   `non-scaling-stroke` keeps the halo at a constant 3 screen pixels
   whatever the viewBox scale, so it never swells on a phone.
   ------------------------------------------------------------------ */
.chart text {
  paint-order: stroke fill;
  stroke: var(--surface-page);
  stroke-width: 3px;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* -- Ad slots --------------------------------------------------------------
   Decision of 26 Aug 2026: the site is built ad-ready, with no ad running yet.
   NEVER on /course/, /start/, /tools/ or /checklist/ -- that is the buying path.
   While empty the slot occupies nothing, so there is no layout shift today. Once
   it holds a child it gains breathing room and a hairline rule above it, so the
   reader can see where paid material starts and my own writing stops.
   No inline style anywhere: style-src is 'self'. */
.ad-slot:empty { display: none; }
.ad-slot {
  margin-block: var(--sp-7);
  padding-block-start: var(--sp-5);
  border-block-start: var(--hair) solid var(--hairline);
  text-align: center;
}


/* --------------------------------------------------------------------------
   25. THE AD SLOT  (.adslot — not .ad-slot, which is the empty placeholder)
   A bordered box, deliberately unlike anything editorial: the only element
   with a border on all four sides in the darker --hairline (the shelf cards
   use the fainter --card-edge) AND a 4px raw-amber top bar, AND the one
   shadow on the page — it is the one thing that must lift. It carries a
   small solid amber square in front of its label. It is its own CONTAINER:
   in a narrow column it stacks; once it is 36rem wide (the inline copy in
   the content column) the pill moves to the right of the copy.
   Two instances live in the markup — a rail copy inside section 02 and an
   inline copy between sections 03 and 04 — each hidden at the breakpoint
   where the other shows, so a screen reader meets exactly one.
   Own products say "From Zonetta"; a third party says "Sponsored" through
   .adslot--sponsored and is NEVER placed on /course/, /start/, /tools/ or
   /checklist/ — that is the buying path.
   -------------------------------------------------------------------------- */

.adslot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-5);
  background-color: var(--surface-page);
  border: var(--hair) solid var(--hairline);          /* 2.08:1 — visibly a box; --card-edge is too faint for a box that must be recognised */
  border-block-start: 4px solid var(--level-solid);   /* the amber top BAR: a fill, not a rule */
  border-radius: var(--r-card);
  box-shadow: 0 12px 32px -16px var(--card-shadow);   /* THE shadow. Nothing else on the page has one */
  max-inline-size: 22rem;
  container: adslot / inline-size;
  font-variant-numeric: lining-nums tabular-nums;
}

@container adslot (min-width: 36rem) {
  .adslot {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "eyebrow cta" "title cta" "copy cta";
    align-items: center;
    column-gap: var(--sp-6);
    row-gap: var(--sp-2);
  }
  .adslot__eyebrow { grid-area: eyebrow; }
  .adslot__title   { grid-area: title; }
  .adslot__copy    { grid-area: copy; }
  .adslot__cta     { grid-area: cta; margin: 0; }
}

.adslot__eyebrow {
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--level);
  padding-inline-start: var(--sp-4);
  position: relative;
  max-inline-size: none;
}

/* A small solid amber square in front of the label: the "this is a placement"
   mark. Raw amber is legal here because it is a fill, and the eyebrow text
   next to it carries the meaning at 7.05:1. */
.adslot__eyebrow::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.35em;
  inline-size: 8px;
  block-size: 8px;
  background-color: var(--level-solid);
  outline: var(--hair) solid var(--level);
}

.adslot__title {
  font-family: var(--f-head);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  color: var(--text-primary);
  text-wrap: balance;
  max-inline-size: none;
}

.adslot__copy {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--text-secondary);
  max-inline-size: none;
}

.adslot__cta { margin-block-start: var(--sp-2); min-height: 48px; }   /* the one button that opts down from 52px: it is not the page's primary action */

/* Third parties sit on the field, own products on white; the eyebrow goes
   neutral so "Sponsored" is never dressed in the brand's accent. */
.adslot--sponsored { background-color: var(--surface-raised); }
.adslot--sponsored .adslot__eyebrow { color: var(--text-muted); }
.adslot--sponsored .adslot__eyebrow::before { background-color: var(--graphic); outline-color: var(--graphic); }

/* Placement. Mobile-first: the inline copy is the one that shows, with 32px
   of air — the band edge above it and the rule below do the separating. */
.adslot--rail { display: none; }
.adslot--inline { margin-block: var(--sp-6); max-inline-size: none; }

@media (min-width: 1024px) {
  /* The inline copy yields to the rail copy only where a rail copy exists;
     an inline-only page (no .has-rail section) keeps its slot on desktop. */
  main:has(.adslot--rail) .adslot--inline { display: none; }
  .adslot--rail { display: flex; }

  /* The rail: a .has-rail section that ACTUALLY CARRIES a rail slot becomes
     prose + a 288px column. Every existing child stays in column 1 in DOM
     order; only the slot goes to column 2, spanning every row, sticky under
     the masthead. GATED ON THE SLOT'S PRESENCE: 18 pages carry .has-rail
     sections with no slot (the /gex/ tables among them), and an unconditional
     grid would hand each of them an empty 336px column. A browser without
     :has() never sees the rail, which is the safe fallback. */
  .has-rail:has(> .adslot--rail) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 18rem;
    column-gap: var(--sp-7);
    align-items: start;
  }
  .has-rail:has(> .adslot--rail) > * { grid-column: 1; }
  .has-rail > .adslot--rail {
    grid-column: 2;
    grid-row: 1 / span 20;   /* larger than any section's child count */
    position: sticky;
    inset-block-start: calc(var(--masthead-h) + var(--sp-5));   /* clears the sticky header by one step */
    margin-block-start: var(--sp-8);        /* aligns with the h2, below the eyebrow */
  }
  /* The >=1440px .note is absolutely positioned and leaves the grid; nothing to do. */
}

@media (max-width: 1023px) {
  .has-rail > .adslot--rail { display: none; }
}


/* --------------------------------------------------------------------------
   26. 2026 ADDITIONS THAT TOUCH EARLIER SECTIONS FROM OUTSIDE
   Written here rather than inside sections 23 and 24 so that their rules
   stay byte-for-byte what /gex/ and lesson five were verified against.
   -------------------------------------------------------------------------- */

/* Tabular lining numerals on every figure that is not already covered by
   section 3: the levels status line, the chips, the dateline, the notes. */
.levels, .levels__status, .chip, .dateline, .note, .folio, .colophon, .figure__outcome {
  font-variant-numeric: lining-nums tabular-nums;
}

/* The daily levels table on a phone: the row header stays put while the
   figures scroll under it (Schwab keeps 17px cells and scrolls the wrapper;
   forex.com shrinks to 12px — the anti-pattern). Section 23 paints the
   tbody row headers transparent; a sticky cell needs a face. */
.levels .table th:first-child,
.levels .table td:first-child {
  position: sticky;
  inset-inline-start: 0;
  background-color: var(--surface-page);
  /* The cell floats over figures that scroll under it; the shadow on its
     right edge says so (the wrap's own left shadow is hidden beneath it). */
  box-shadow: 6px 0 8px -6px color-mix(in srgb, var(--text-primary) 24%, transparent);
}
.levels .table thead th:first-child { background-color: var(--surface-raised); }

/* The failure case: when a figure's chart carries a broken band, the
   verdict's rule is dashed to match — no per-page class needed. */
.figure:has(.chart .broke) .figure__outcome { border-block-start-style: dashed; }

/* The drill's answer opens with a short slide instead of a jump. The
   content is never hidden by author CSS — a closed <details> stays a
   closed <details> — and an engine without ::details-content opens it
   instantly, which is the section-24 law. */
.drill__reveal::details-content {
  block-size: 0;
  overflow: clip;
  transition: block-size var(--t-nav) var(--ease),
              content-visibility var(--t-nav) allow-discrete;
}
.drill__reveal[open]::details-content { block-size: auto; }

/* Windows High Contrast removes background paints. The meter's filled slots
   and the state mark are meaning, so they are re-drawn in the system ink;
   the ad slot's bar becomes a border. */
@media (forced-colors: active) {
  .zone-score i.is-filled, .levels__mark { forced-color-adjust: none; background-color: CanvasText; }
  .adslot { border-block-start-width: 4px; }
  .masthead { -webkit-backdrop-filter: none; backdrop-filter: none; }
}

/* An optional static chip above a page's h1 for the owner's own launches —
   the IBKR "New" pattern. No markup uses it on the home; it ships so that a
   launch needs one <p class="notice-chip"> and no CSS. */
.notice-chip {
  display: inline-block;
  font-family: var(--f-text);
  font-weight: 500;
  font-size: var(--fs-micro);
  line-height: var(--lh-mono);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--level);
  background-color: var(--level-tint);
  border: var(--hair) solid currentColor;
  border-radius: var(--r-pill);
  padding: var(--sp-1) var(--sp-3);
  margin-block-end: var(--sp-4);
}
