/* =============================================================
   BTM DESIGN TOKENS
   Beyond the Metric, Ghost theme.

   Canonical home is the tokens repo (C:\btm\tokens, btm-tokens.css).
   That repo has no code in it yet, so this is the working original.
   When it is created, this file becomes a copy of it.

   Plain CSS custom properties. No preprocessor and no build step,
   so the theme still deploys as a zip.

   THIS FILE IS NOW THE ONLY SOURCE OF COLOUR
   It is imported on line 1 of screen.css. As of 2026-08-18 screen.css
   contains no literal colour value at all. Its :root block holds only
   type, layout, and back compat aliases that published post content
   depends on, and every one of those aliases points back here.

   If you find yourself typing a hex into screen.css, add a token
   instead. A colour that exists in exactly one place is the entire
   point of the file.
   ============================================================= */

:root {

  /* -----------------------------------------------------------
     1. VOID RAMP, five steps
     The live site ships three greys inside 10 units of each other,
     which is invisible on a phone in daylight. Five steps, spaced
     far enough apart to survive it.

     The publication is read carefully and long form, so its ground
     is --base, not --void. Other surfaces sit elsewhere on the ramp.
     See the tokens repo for the full surface table.

     NOTE: this file is served to the public at /assets/css/tokens.css
     and CSS comments are NOT stripped. Never name an unreleased product
     in here. Handlebars comments are safe; these are not.
     ----------------------------------------------------------- */
  --void:    #07080B;   /* page edges, footer, figure paper ground */
  --base:    #0B0D12;   /* body background, the publication ground */
  --surface: #101219;   /* section bands */
  --card:    #171A22;   /* cards */
  --raised:  #1F232D;   /* hover, active, sticky nav */

  --base-rgb: 11 13 18;   /* --base, for translucent sticky surfaces */


  /* -----------------------------------------------------------
     2. FOREGROUND AND RULES
     Added 2026-08-18 for the screen.css refactor. The first token
     pass left these out on purpose, and the refactor cannot proceed
     without them because 30 headings are hardcoded #fff.

     Values are carried over from the live site unchanged. The brand
     system specifies a warm newsprint foreground (#F4F2EC) instead
     of this cool grey, and that is a live open question tied to the
     font decision. Deliberately not changed here, so this refactor
     only moves the ground ramp and the teal split.
     ----------------------------------------------------------- */
  --fg-rgb: 255 255 255;

  --text-strong: #FFFFFF;                    /* headings */
  --text:        #E4E4E7;                    /* body */
  --text-soft:   rgb(var(--fg-rgb) / 0.80);  /* emphasis */
  --text-quiet:  rgb(var(--fg-rgb) / 0.70);  /* pull quotes */
  /* RAISED 2026-08-19 at Martin's request, after reading the site on a
     phone. This deliberately overrides the "foreground values carried
     over from the live site unchanged" decision above; it is not drift.
     The dark ramp was far more timid than the light ramp already in
     block 8, which sits at 0.66 and 0.46, and it showed.
     Measured on --base #0B0D12:
       muted  0.50 -> 0.62   5.33:1 -> 7.70:1, clears AAA for body size
       faint  0.25 -> 0.46   2.19:1 -> 4.66:1, was below AA at any size
     --text-faint is only ever a text colour in this theme, never a
     border or a fill, so raising it cannot make a rule or a divider
     louder. Verified by grep before the change. */
  --text-muted:  rgb(var(--fg-rgb) / 0.62);  /* meta, captions, standfirsts */
  --text-faint:  rgb(var(--fg-rgb) / 0.46);  /* the quietest step, still legible */

  --rule:        rgb(var(--fg-rgb) / 0.08);  /* hairlines, card edges */
  --rule-strong: rgb(var(--fg-rgb) / 0.16);  /* section rules */
  --wash:        rgb(var(--fg-rgb) / 0.06);  /* inline code, quiet fills */


  /* -----------------------------------------------------------
     3. THE SEMANTIC COLOUR SPLIT
     Editorial Teal means you can click it.
     Signal Cyan means it is a measurement.
     Never mix the roles. This is what lets the interface move off
     cyan while ten posts' worth of published figures stay valid.
     ----------------------------------------------------------- */

  /* The blue scale. CANONICAL, from the Split Decision brand package v1.4.
     C:\btm\brand\BTM_Analytics_Split_Decision_Brand_Package_v1.4
     03_Brand_Tokens/btm-brand-tokens.css, and the full rules in
     C:\btm\brand\BTM_Blue_Implementation_Spec.md

     BTM is a blue-led identity, not a neutral identity with a blue logo.
     Identity, interface and measurement share one blue family and the role
     is carried by context and form rather than by hue. That replaces the
     teal-for-interface, cyan-for-data split.

     THE MEASUREMENT THAT MADE THIS FREE. Blue 500 against the locked figure
     cyan #00D4FF is deltaE 1.7, below the threshold of noticing. Ten
     published posts were exported against #00D4FF and that value already IS
     blue 500 to the eye, so nothing has to be re-exported.

     Contrast on --base #0B0D12: 13.04, 10.89, 8.34, 3.66, 2.97.
     The two low steps are for the light theme and are measured on
     --paper #FBFAF6 instead: 5.09 and 6.27. */
  --blue-300:  #67E4FD;   /* hover and soft highlight on dark */
  --blue-500:  #1ED3FB;   /* master signal. The interface default on dark. */
  --blue-600:  #16B9DC;   /* active and pressed, dense chart mark on dark */
  --blue-700:  #08758F;   /* interface on LIGHT, 5.09 on paper. Rules on dark. */
  --blue-800:  #06667D;   /* hover on light, 6.27 on paper. Never text on dark. */
  --blue-tint: #E5F8FC;   /* selected, hover and callout ground in light mode */
  --blue-wash: rgba(30, 211, 251, 0.08);  /* tinted panels on dark */
  --blue-rgb:  30 211 251;   /* --blue-500, for alpha derivations */

  /* A ground that never inverts, declared here and deliberately NOT
     overridden in the light block below. Same principle the figure
     frames already rely on: some surfaces exist because the artwork
     sitting on them was drawn for a dark ground, and flipping them
     destroys the artwork. The logo PNG is white type plus cyan on
     transparent, so on paper it is white on white. */
  --ground-fixed: #0B0D12;

  /* Shadows are cast by light, not by theme. Black in both. */
  --shadow-rgb: 0 0 0;

  /* Text on a filled --interface button. This FLIPS with the theme and the
     previous comment claiming otherwise was wrong, which cost real contrast:
     filled buttons set color: var(--void), a near black, and on the light
     theme's deep teal that measured 2.62:1. Only .subscribe-cta-btn carried a
     white override; .ww-btn and the header Subscribe did not.
       dark theme, interface #20D7B5, near black on it   10.92
       light theme, interface #127B67, white on it        5.18
     Every filled button uses this token now. Do not hard-code either value. */
  --on-interface: var(--void);

  /* Measurement blue. Same value as --fig-cyan on purpose, so a figure
     and the page around it can never drift apart. It is kept at #00D4FF
     rather than moved to --blue-500 because ten published posts export
     against this exact hex, and at deltaE 1.7 the two are the same
     colour anyway. The name says blue because the system is blue now. */
  --data-blue:     #00D4FF;
  --data-blue-rgb: 0 212 255;

  /* Role aliases. Author styles against these rather than the raw
     hues, so the split stays legible when reading the stylesheet. */
  --interface:       var(--blue-500);               /* 10.89 on --base, clears AAA */
  --interface-hover: var(--blue-300);               /* 13.04 on --base */
  --interface-quiet: var(--blue-700);               /* borders and rules, 3.66. Never text on dark. */
  --interface-wash:  var(--blue-wash);              /* tinted panels */
  --interface-line:  rgb(var(--blue-rgb) / 0.20);   /* underlines, card edges */
  /* REMOVED 2026-08-20. --interface-focus measured 2.88:1 on --surface in
     dark and 1.70:1 in light, both under the 3:1 a focus indicator needs,
     and the light theme block never redefined it. Focus rings use
     --interface, which is contrast checked in both themes and is what the
     Metrics Shelf filters already used. Do not reintroduce a translucent
     focus colour. */

  --data:      var(--data-blue);
  --data-wash: rgb(var(--data-blue-rgb) / 0.04);
  --data-line: rgb(var(--data-blue-rgb) / 0.18);


  /* -----------------------------------------------------------
     4. LOCKED FIGURE PALETTE
     Ten published posts depend on these exact values. Changing one
     means re-exporting every figure in every post. Do not touch.
     Plotly figures read these names so a figure and its page stay
     in step.
     ----------------------------------------------------------- */
  --fig-paper: #090c11;
  --fig-cyan:  #00d4ff;
  --fig-green: #00e68a;
  --fig-amber: #f4a940;
  --fig-red:   #ef4444;

  /* Interface verdict colours. --ui-green is to --fig-green exactly
     what --ui-red is to --fig-red: a different colour with a
     different job. The figure palette is locked to ten published
     posts and a UI chip must never borrow from it. 7.4:1 on
     --surface. */
  --ui-green:  #10B981;

  /* The interface red is a different red. #dc2626 is the current UI
     red on rules, card tags, and the error code. It is not
     --fig-red and the two must never be merged. Named here so the
     distinction is written down somewhere. */
  /* RAISED 2026-08-19. #dc2626 measured 4.02 on --base, 3.87 on
     --surface and 3.60 on --card, so it failed AA for normal text on
     every one of its ten usages in the dark theme. #F0616B is the same
     red one step brighter: 6.13, 5.90, 5.49. #dc2626 is kept as the
     LIGHT theme value in block 8, where it measures 4.83 on paper and
     passes, which is the same per-theme pattern --interface and --data
     already use. Still not --fig-red, still never to be merged with it. */
  --ui-red: #F0616B;


  /* -----------------------------------------------------------
     5. FLUID TYPE SCALE, 8 steps
     clamp(min, fluid, max). The fluid range runs 360px to 1280px.

     ROOT UNIT: screen.css sets html { font-size: 112.5% }, which is
     18px against a default browser base, so 1rem is 18px here and not
     16px. Every rem below is computed against 18px and the pixel result
     is in the comment. The percentage is deliberate: it scales with a
     reader who raises their own browser font size, which a hard 18px
     would override
     rather than deleting it. That holds 1rem at 18px by default and
     lets a reader's own font size preference scale the whole page.
     ----------------------------------------------------------- */
  --fs-micro:   clamp(0.611rem, 0.589rem + 0.109vw, 0.667rem);  /* 11 to 12px, eyebrows and mono labels */
  --fs-xs:      clamp(0.722rem, 0.700rem + 0.109vw, 0.778rem);  /* 13 to 14px, meta and captions */
  --fs-sm:      clamp(0.833rem, 0.812rem + 0.109vw, 0.889rem);  /* 15 to 16px, card excerpts */
  --fs-base:    clamp(0.944rem, 0.901rem + 0.217vw, 1.056rem);  /* 17 to 19px, body */
  --fs-lg:      clamp(1.167rem, 1.101rem + 0.326vw, 1.333rem);  /* 21 to 24px, lead and h3 */
  --fs-xl:      clamp(1.444rem, 1.292rem + 0.761vw, 1.833rem);  /* 26 to 33px, h2 */
  --fs-2xl:     clamp(1.778rem, 1.430rem + 1.739vw, 2.667rem);  /* 32 to 48px, post title */
  --fs-display: clamp(2.222rem, 1.440rem + 3.913vw, 4.222rem);  /* 40 to 76px, hero */

  /* Every numeric element gets tabular figures. Applied per element,
     stated once here. */
  --numerals: tabular-nums;

  /* Display face for billboard headers. Archivo Black carried the
     approved direction mock. Swapping the display face later is a
     one line change here plus the Google Fonts link in default.hbs. */
  --font-display: 'Archivo Black', Arial, sans-serif;


  /* -----------------------------------------------------------
     6. SPACING, 4px grid
     Stated in px on purpose. A 4px grid stays a 4px grid whatever
     the root font size does, so it survives the html font-size
     change described above without every gap shifting.
     ----------------------------------------------------------- */
  --space-3xs:   4px;
  --space-2xs:   8px;
  --space-xs:   12px;
  --space-sm:   16px;
  --space-md:   24px;
  --space-lg:   32px;
  --space-xl:   48px;
  --space-2xl:  64px;
  --space-3xl:  96px;   /* section band padding */
  --space-4xl: 128px;   /* hero and major band separation */


  /* -----------------------------------------------------------
     7. MOTION
     Primitives only. Every motion block still carries its own
     @supports (animation-timeline: view()) guard, its revealed
     state as the CSS default, and its own
     @media (prefers-reduced-motion: reduce) override. Those are
     block level rules and do not belong in the token layer.
     Animate transform and opacity only.
     ----------------------------------------------------------- */
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);     /* hover, colour, small ui */
  --ease-out:      cubic-bezier(0.22, 1, 0.36, 1);   /* entrances and reveals */
  --ease-in-out:   cubic-bezier(0.65, 0, 0.35, 1);   /* two way state changes */
  --ease-linear:   linear;                           /* scroll driven timelines only */

  --dur-fast:   120ms;
  --dur-base:   200ms;  /* matches the 0.2s already used across screen.css */
  --dur-mid:    320ms;
  --dur-slow:   500ms;
  --dur-reveal: 800ms;
}


/* =============================================================
   8. LIGHT THEME
   Instrument Dark is the identity, so light is an accessibility
   and daylight affordance, not a second brand. It inherits every
   structural token above and swaps only ground, foreground, and
   the two accent ramps.

   CONTRAST, RECOMPUTED 2026-08-21 ON ALL THREE GROUNDS.
   The old note here read "text 16.6, muted 7.5, faint 3.9" against
   paper only. Two of those three were wrong, and the faint figure was
   wrong in the direction that matters: it was 3.14, not 3.9, so the
   quietest step had never passed AA at any size, and four sessions
   reasoned from a number that said it had. --text-faint is now 0.60,
   raised in the block below. It is also why one ground is not enough
   to check against: the subscribe band and the footer sit on --void,
   which is a step darker than --base and is the tighter test.

                        --base    --void  --surface
     --text             16.61     15.06     17.96
     --text-soft        10.90     10.18     11.50
     --text-quiet        7.59      7.23      7.89
     --text-muted        6.11      5.87      6.30
     --text-faint        4.95      4.79      5.07
     --interface         7.08      6.42      7.65
     --interface-hover  11.82     10.71     12.78
     --data              5.25      4.76      5.68
     --ui-green          5.07      4.60      5.48
     --ui-red            4.47      4.05      4.83

   READ THE LAST ROW BEFORE USING --ui-red ON A BAND. It clears AA on
   paper and on a card, and FAILS on --void at 4.05. Error text on a
   void band therefore carries its own --surface ground rather than
   sitting on the band. See .sg-state in home.css.

   The two ramps are not symmetrical. Dark runs 0.62 / 0.46 for muted
   and faint, light runs 0.66 / 0.60, so the light theme's quiet steps
   sit closer together. That is forced rather than chosen: 0.46 is
   legible on the void and is not legible on paper, and the gap has to
   close somewhere for the quieter step to clear AA.

   Cyan #00D4FF is 1.6 on paper and therefore NEVER used as
   light-theme text; --data becomes a darkened cyan that clears AA
   while keeping the hue.

   FIGURES DO NOT INVERT. --fig-* stays locked and figure frames
   keep their dark paper in both themes, because ten published
   posts are exported against #090c11.

   Applied by [data-theme="light"] on <html>, set by the header
   toggle in assets/js/home.js, which also honours the reader's
   system preference on a first visit.
   ============================================================= */

[data-theme="light"] {
  /* BONE #F2EFE7, RAISED FROM #EDEBE4 ON 2026-08-21, and it is a contrast fix
     rather than a taste one. --interface on light is blue-700 #08758F, which
     measures 4.46:1 on #EDEBE4 and 4.63:1 on bone. 4.46 fails AA for normal
     text, and this band is where .hm-products, .hm-go, the hero eyebrow, the
     stat line and the whole footer live, so the front page carried eleven
     failing cells in light mode. Measured on the rendered page with every
     other stylesheet stripped: eleven failures at #EDEBE4, one at bone, and
     the one that survives is .hdr-lang-sep, which fails in BOTH themes and is
     a separate pre-existing thing.

     #EDEBE4 was invented here. Bone is the darkest light ground the brand
     package actually names, so this aligns the ramp and fixes the cell in the
     same move. Top Shelf made the identical change in page_sources.py.

     The cost: light bands get subtler, because bone against --base #F7F6F2 is
     deltaE 3.2, barely above the threshold of noticing. */
  --void:    #F2EFE7;   /* page edges, footer bands. Brand bone. */
  --base:    #F7F6F2;   /* body ground, warm paper */
  --surface: #FFFFFF;   /* section bands */
  --card:    #FFFFFF;   /* cards */
  --raised:  #F0EEE8;   /* hover, active, sticky nav */
  --base-rgb: 247 246 242;

  --fg-rgb: 12 14 18;
  --text-strong: #0B0D12;
  --text:        #14171C;
  --text-soft:   rgb(var(--fg-rgb) / 0.82);
  --text-quiet:  rgb(var(--fg-rgb) / 0.72);
  --text-muted:  rgb(var(--fg-rgb) / 0.66);
  /* 0.60, not the 0.46 dark mode uses. On the light ground 0.46 measured
     3.2:1 and this token lands on 12px microcopy, where WCAG 2.2 wants
     4.5:1. Measured: 0.56 gives 4.33 and still fails, 0.66 gives 6.1 but
     renders identically to --text-muted and costs the scale a step. 0.60
     measures 4.94 and stays quieter than muted at 6.1. */
  --text-faint:  rgb(var(--fg-rgb) / 0.60);

  --rule:        rgb(var(--fg-rgb) / 0.12);
  --rule-strong: rgb(var(--fg-rgb) / 0.22);
  --wash:        rgb(var(--fg-rgb) / 0.05);

  /* interface steps shift down the same blue scale */
  --interface:       var(--blue-700);   /* #08758F, 5.09 on paper */
  --interface-hover: var(--blue-800);   /* #06667D, 6.27 */
  /* White on the deep blue, where the dark theme uses near black on the
     bright one. See the note beside --on-interface. */
  --on-interface:    #FFFFFF;
  --interface-quiet: rgb(var(--blue-rgb) / 0.45);
  --interface-wash:  var(--blue-tint);
  --interface-line:  rgb(var(--blue-rgb) / 0.40);

  /* verdict colours step down for paper. Measured on --base #F7F6F2:
     #dc2626 is 4.47 and #047857 is 5.07. The dark values are far too
     light to survive here. */
  --ui-red:    #dc2626;
  --ui-green:  #047857;

  /* data steps to blue 700, the brand's own light-ground value */
  --data:      var(--blue-700);
  --data-wash: rgb(var(--blue-rgb) / 0.06);
  --data-line: rgb(0 112 140 / 0.28);
}
