/* ═══════════════════════════════════════════════════════════════════════════════
   SWIPE-FEED STYLES
   Pairs with C:\Users\rossi\tracketing-dashboard\ui\insights.html

   Ported from a production, shipped swipe feed. Most of this file is ordinary
   styling you can rewrite freely. A handful of rules are LOAD-BEARING — they are
   the reason the feed feels like a phone app instead of a long web page — and each
   one is marked and explained where it appears. If a swipe starts feeling wrong,
   read the LOAD-BEARING comments before changing anything else.

   The five behaviours this stylesheet is responsible for:
     1. One card fills the screen and the feed snaps to it   (scroll-snap)
     2. Cards never get skipped by a fast flick               (scroll-snap-stop)
     3. Chapters slide sideways inside a card                 (the .chapters track)
     4. Charts have a real height to draw into                (.chartwrap / .chap-viz)
     5. Nothing sits under a phone's notch or home indicator  (safe-area insets)

   HOW TO MAKE IT YOURS: change the palette block below. That is the intended
   single edit point — nothing further down hard-codes a colour.
   ═══════════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════════
   1 · PALETTE  ←── TRACKETING: THIS IS THE BLOCK TO REPLACE
   ═══════════════════════════════════════════════════════════════════════════════
   The names below are deliberately the same names already used in
   C:\Users\rossi\tracketing-dashboard\board-insights.html, so you can paste that
   palette straight over the top of this one and the whole feed re-skins.

   The values shipped here are a neutral dark set, because a full-screen feed and
   its charts read better on a dark background. If you paste in the LIGHT admin
   palette instead, everything still works — every shade in this file is mixed
   from these tokens rather than hard-coded — but set the four tokens flagged
   "light palette" below as well.
   ─────────────────────────────────────────────────────────────────────────────── */
:root {
  /* ---- surfaces ---- */
  --bg:             #0A0E1A;   /* the card / feed background */
  --bg-deep:        #05070E;   /* the darker surround behind the phone frame + the notch */
  --surface:        #161C30;   /* a lifted panel: desktop cards */
  --surface2:       #1C2340;   /* a second, slightly lighter panel */

  /* ---- lines ---- */
  --border:         rgba(255, 255, 255, .09);   /* light palette: rgba(15,23,41,.10) */
  --border2:        rgba(255, 255, 255, .16);   /* light palette: rgba(15,23,41,.18) */

  /* ---- text ---- */
  --text-primary:   #EEF1FA;   /* headlines and numbers */
  --text-secondary: #9AA3BE;   /* supporting sentences */
  --text-muted:     #69728F;   /* axis labels, timestamps, resting nav icons */

  /* ---- brand + meaning ---- */
  --accent:         #7C8BFF;   /* your brand colour. NOTE: inside an insight card this
                                  token is overwritten per card with that card's trend
                                  colour — see the warning under "the accent rule" below.
                                  Use var(--brand) if you need brand colour inside a card. */
  --accent-light:   #EFF6FF;
  --accent-dark:    #1D4ED8;
  --positive:       #37E39B;   /* something going up */
  --negative:       #FF6B6B;   /* something going down */
  --amber:          #F6B84C;   /* flat, odd, or "worth a look" */

  /* Text drawn ON TOP of a solid --accent / --positive fill (buttons, badges).
     light palette: #FFFFFF */
  --on-accent:      #0A0E1A;

  /* ---- shape ---- */
  --radius:         12px;   /* buttons, small panels */
  --radius-sm:      8px;
  --radius-lg:      20px;   /* desktop cards */
  --radius-phone:   44px;   /* the phone-frame preview corner. 0 = square frame */
  --ease:           .2s ease;

  /* Drop shadow behind the phone frame. light palette: rgba(15,23,41,.18) */
  --shadow:         rgba(0, 0, 0, .8);

  /* ---- type ----
     TRACKETING: no web font is required. To use the families the rest of the site
     already uses, uncomment the font link in
     C:\Users\rossi\tracketing-dashboard\ui\insights.html and set these to
     "Space Grotesk" (display) and "Plus Jakarta Sans" (body). */
  --font-display:   system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body:      system-ui, -apple-system, "Segoe UI", sans-serif;

  /* ═══ the accent rule ══════════════════════════════════════════════════════
     Each insight card sets --accent on ITSELF to its own trend colour (green when
     the number is up, red when it is down, amber when it is flat). Everything
     inside that card — the big number, the glow, the chart line, the progress dot
     — then follows automatically. That is why one card can be green and the next
     red without a single extra rule.
     --brand freezes your real brand colour here at the top level, so anything
     inside a card that must stay brand-coloured can use var(--brand). */
  --brand:          #1D9E75;   /* TRACKETING green — the same --accent the board pages use
                                  (dashboard.html :39). Frozen here, NOT var(--accent): inside a
                                  card --accent is the card's own trend colour, so anything that
                                  must stay on-brand (the Subscribe button, focus rings) would
                                  otherwise turn red on a declining card. */

  /* ---- meaning → colour: THE NAMES THE DRAWINGS READ ----
     These are the only custom names the JavaScript looks up. The chart code reads
     them off :root the first time it draws and paints every line, bar, arc, dot
     and card accent with them.
     TRACKETING: re-map them if "up" is not good news in your product (ad spend,
     churn, cost per lead) - change them here and every card AND every drawing
     follows in the same edit. Leave one undefined and the drawings quietly keep
     their own stand-in green/red/amber while the words change colour, which is
     how a palette swap ends up only half-done. */
  --chart-up:       var(--positive);
  --chart-down:     var(--negative);
  --chart-flat:     var(--amber);
  --chart-neutral:  var(--text-muted);   /* the second line, the non-leading bars */
  --chart-track:    color-mix(in srgb, var(--text-primary) 12%, transparent);  /* the empty part of a bar */
  --chart-muted:    color-mix(in srgb, var(--text-primary) 28%, transparent);  /* a deliberately quiet segment */

  /* Up to seven things drawn side by side, one colour each. Ordered so the first
     two read as different at a glance and no two neighbours clash. */
  --chart-series-1: var(--positive);
  --chart-series-2: #38BDF8;
  --chart-series-3: var(--amber);
  --chart-series-4: #A78BFA;
  --chart-series-5: var(--negative);
  --chart-series-6: #2DD4BF;
  --chart-series-7: #818CF8;

  /* ---- derived shades: every faint line, track and tint in this file mixes off
          --text-primary, so the whole feed inverts correctly if you swap in a
          light palette. Nothing below this point hard-codes a colour. ---- */
  --hairline:       color-mix(in srgb, var(--text-primary)  8%, transparent);
  --hairline-soft:  color-mix(in srgb, var(--text-primary)  5%, transparent);
  --dot-idle:       color-mix(in srgb, var(--text-primary) 22%, transparent);

  /* ═══ two numbers this stylesheet leans on — keep both defined ═══════════════ */
  /* How much room the bottom nav takes. The nav, the cards and the rail all size
     themselves off it, so change it HERE, not in the .nav rule. (No JavaScript
     reads this one - the only names the scripts look up are the --chart-* set
     above.) */
  --nav-h:          78px;
  /* The MINIMUM share of the card the text block reserves. It is a floor, not a
     ceiling: a long summary grows the text block and the chart above shrinks to
     suit, so a sentence is never cut off. */
  --plate-h:        46%;
}


/* ═══════════════════════════════════════════════════════════════════════════════
   2 · BASE
   ═══════════════════════════════════════════════════════════════════════════════ */
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body { margin: 0; height: 100%; }

body {
  background: var(--bg-deep);
  color: var(--text-primary);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  font-synthesis: none;

  /* Centres the phone frame on a desktop screen. LOAD-BEARING on mobile:
     overflow:hidden stops the PAGE from scrolling, so the only thing that can
     scroll is the feed itself. Without it, a swipe drags the whole page and the
     snap points fight the browser's own pull-to-refresh. */
  display: grid;
  /* minmax(0, 1fr), not the default: a plain auto column is sized to its widest
     child's UNWRAPPED width, so one long sentence anywhere on the page silently
     makes the page wider than the screen. This caps the column at the screen. */
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  min-height: 100dvh;   /* dvh, not vh: vh on mobile measures the screen WITH the
                           browser's address bar hidden, so a vh-sized card is
                           taller than the visible area and the snap drifts. */
  overflow: hidden;
}

.noscript {
  max-width: 100%;
  padding: 24px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--text-secondary);
  text-align: center;
}


/* ═══════════════════════════════════════════════════════════════════════════════
   3 · THE PHONE FRAME
   Desktop preview: a phone-shaped window in the middle of the screen.
   Real phone (≤460px): full bleed, no frame, no rounded corners, no notch.
   ═══════════════════════════════════════════════════════════════════════════════ */
.phone {
  position: relative;
  width: min(420px, 100vw);
  height: min(880px, 100dvh);
  background: var(--bg);
  border-radius: var(--radius-phone);
  box-shadow: 0 40px 120px -20px var(--shadow), 0 0 0 1px var(--border);
  overflow: hidden;
  isolation: isolate;   /* keeps the card glow from bleeding outside the frame */
}

.notch {
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 132px; height: 26px;
  background: var(--bg-deep);
  border-radius: 0 0 16px 16px;
  z-index: 60;
}

@media (max-width: 460px) {
  body { place-items: stretch; }
  .phone { width: 100vw; height: 100dvh; border-radius: 0; box-shadow: none; }
  .notch { display: none; }   /* a real phone has a real notch; safe-area handles it */
}


/* ═══════════════════════════════════════════════════════════════════════════════
   4 · TOP BAR
   Floats over the feed on a fade-to-nothing gradient so card art runs underneath.
   ═══════════════════════════════════════════════════════════════════════════════ */
.topbar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 50;

  /* SAFE AREA (load-bearing): on a notched phone env(safe-area-inset-top) is the
     height of the notch strip. Adding it to the padding is what stops the wordmark
     rendering underneath the clock. On every other device it resolves to 0px and
     the layout is unchanged. */
  padding: calc(env(safe-area-inset-top, 0px) + 20px) 20px 14px;

  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--bg-deep) 72%, transparent) 0%, transparent 100%);

  /* The bar must not eat swipes meant for the card behind it. Only the wordmark
     itself takes pointer events again (below). */
  pointer-events: none;
}
.brand { display: flex; flex-direction: column; gap: 2px; pointer-events: auto; }
.brand b { font-family: var(--font-display); font-weight: 600; font-size: 16px; letter-spacing: -.01em; }
.brand span { font-size: 10.5px; color: var(--text-secondary); letter-spacing: .01em; }
.brand img, .brand svg { display: block; max-height: 22px; width: auto; }   /* if you swap in a logo */

/* The wordmark — the board pages' own logo, recoloured white-on-yellow for this dark screen.
   Height-driven with width:auto so the mark keeps its proportions whatever the file's pixel size;
   the intrinsic width/height on the <img> reserve the right box before it loads, so the board name
   underneath never jumps. It is the only branding that survives into a shared picture. */
.wordmark {
  display: block;
  height: 21px;
  width: auto;
  max-width: 148px;
  object-fit: contain;
  object-position: left center;
}

/* When the card on screen was published. Right-hand end of the bar, opposite the board name.
   --text-secondary, the same grey as that board name, so the two read as one pair across the bar:
   --text-muted is the palette's timestamp colour but is too dim to sit over card art, and
   --text-primary would compete with the headline, which is the thing to read first.
   Held on ONE line (a wrapped date would push the bar taller over the card behind it) and given a
   fixed reserve of space, so a longer month name cannot nudge the board name leftwards as you
   swipe from a card published in May to one published in September. */
.published {
  flex-shrink: 0;
  font-size: 10.5px;
  letter-spacing: .01em;
  color: var(--text-secondary);
  white-space: nowrap;
  text-align: right;
  min-width: 118px;
}


/* ═══════════════════════════════════════════════════════════════════════════════
   5 · THE FEED  ←── THE MOST LOAD-BEARING BLOCK IN THE FILE
   ═══════════════════════════════════════════════════════════════════════════════
   This is what makes the feed feel like a phone app rather than a web page.

   scroll-snap-type: y mandatory
       The feed can only come to rest with a card exactly filling the screen.
       No half-cards, ever.

   scroll-snap-align: start  +  scroll-snap-stop: always  (on the card)
       "always" is the important one: it forbids a hard flick from flying past
       three cards at once. That is not just taste — the JavaScript decides which
       card is "the current one" by watching how much of it is on screen, and a
       card that was blown past never registers. With snap-stop the person lands
       on every card and every card gets its moment.

   The hidden scrollbar is cosmetic; the smooth scroll behaviour is what makes the
   engine's own "jump to card N" call glide instead of teleport.
   ─────────────────────────────────────────────────────────────────────────────── */
.feed {
  height: 100%;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;             /* Firefox */

  /* OPTIONAL: uncomment if you ever detach the feed from the fixed-height phone
     frame. It stops an over-scroll at the top from triggering Android Chrome's
     pull-to-refresh mid-swipe. Not needed as shipped, because `body` above is
     overflow:hidden and there is no page behind the feed to hand the scroll to.
     overscroll-behavior-y: contain; */
}
.feed::-webkit-scrollbar { display: none; }   /* Safari / Chrome */

/* GOTCHA worth knowing: an element with a `display` value set in CSS ignores the
   plain `hidden` attribute. These two rules are what make the JavaScript's
   show/hide of the feed and the error state actually work. Delete either one and
   the error screen renders on top of a still-visible feed. */
.feed[hidden] { display: none; }

/* The quiet loading line. Pure CSS: it exists only while the feed has no cards in
   it and vanishes the moment the first card is inserted — no JavaScript, and no
   way for it to get stuck on screen. It is NOT a fake card: it shows no numbers
   and makes no claims. */
.feed:empty::after {
  content: "Loading your insights…";
  display: flex; align-items: center; justify-content: center;
  height: 100%;
  font-size: 13.5px;
  color: var(--text-muted);
}


/* ═══════════════════════════════════════════════════════════════════════════════
   6 · ONE INSIGHT CARD
   Layout, top to bottom:  chart  →  text block  →  (progress rail)  →  nav
   Each card is also a horizontal track of "chapters" you swipe sideways through.
   ═══════════════════════════════════════════════════════════════════════════════ */
.insight {
  position: relative;
  height: 100%;                 /* exactly one screen. This is what snapping snaps to. */
  scroll-snap-align: start;
  scroll-snap-stop: always;     /* see the LOAD-BEARING note above */
  overflow: hidden;
}

/* The horizontal chapter track: chapters sit side by side and the whole strip
   slides left by whole screens. Moving one transform is cheap and stays smooth on
   a mid-range phone; animating four separate panels is not. */
.chapters {
  display: flex; height: 100%; width: 100%;
  transition: transform .42s cubic-bezier(.22, 1, .36, 1);
  will-change: transform;
}
/* min-width, NOT width: a flex child would otherwise be allowed to shrink to fit
   and every chapter would squeeze onto one screen. */
.chapter { position: relative; min-width: 100%; height: 100%; display: flex; flex-direction: column; }

/* Ambient wash behind the chart in the card's own trend colour. Purely mood —
   it is the fastest read of "up or down" before anyone parses a number. */
.glow {
  position: absolute; inset: -30% -20% auto -20%; height: 78%; z-index: 0;
  background: radial-gradient(60% 70% at 50% 20%, color-mix(in srgb, var(--accent) 26%, transparent) 0%, transparent 70%);
  opacity: .9;
  transition: background .5s;
}

/* ── chart area (top) ──────────────────────────────────────────────────────────
   LOAD-BEARING: a chart drawn as SVG needs a container with a REAL, resolved
   height or it collapses to nothing. Two rules guarantee that:
     flex: 1 1 auto   → take whatever height the text block below doesn't need
     min-height       → but never less than this, however long the text runs
   The nested rule gives the chart element itself the full box. */
.chartwrap {
  position: relative; z-index: 1;
  flex: 1 1 auto;
  min-height: 180px;
  padding: 76px 22px 8px;       /* top padding clears the floating top bar */
  display: flex; align-items: center; justify-content: center;
}
.chartwrap .chart { width: 100%; height: 100%; }

/* ── text block (bottom) ───────────────────────────────────────────────────────
   Sizes itself to its own content — label, big number, headline, full summary —
   so a longer sentence is never clipped; the chart above simply gets less room.
   --plate-h is the minimum it reserves, not a maximum.
   The bottom padding clears the nav AND the phone's home indicator. */
.plate {
  position: relative; z-index: 2;
  flex: 0 0 auto;
  min-height: var(--plate-h);
  padding: 20px 22px calc(var(--nav-h) + 18px + env(safe-area-inset-bottom, 0px));
  display: flex; flex-direction: column; justify-content: flex-end;
  background: linear-gradient(180deg,
              transparent 0%,
              color-mix(in srgb, var(--bg) 82%, transparent) 30%,
              var(--bg) 74%);
}

.eyebrow {
  display: inline-flex; align-items: center; gap: 7px; align-self: flex-start;
  font-size: 11px; font-weight: 600; letter-spacing: .11em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 12px; transition: color .5s;
}
.eyebrow .meta { color: var(--text-secondary); font-weight: 500; letter-spacing: .02em; text-transform: none; }

/* The one number the card exists to deliver. clamp() keeps it big on a large
   phone and stops it wrapping on a small one. */
.metric {
  font-family: var(--font-display); font-weight: 700;
  line-height: .92; letter-spacing: -.03em;
  font-size: clamp(52px, 15vw, 66px);
  color: var(--accent);
  transition: color .5s;
  margin-bottom: 6px;
}
.metric small {
  font-family: var(--font-body); font-size: 13px; font-weight: 500; letter-spacing: 0;
  color: var(--text-secondary); display: block; margin-top: 8px;
}

.headline {
  font-family: var(--font-display); font-weight: 600; font-size: 22px;
  line-height: 1.16; letter-spacing: -.015em;
  margin: 2px 0 8px; color: var(--text-primary);
}
/* The bottom margin lifts this last line clear of the progress dots pinned below. */
.summary { font-size: 13.5px; line-height: 1.5; color: var(--text-secondary); margin: 0 0 5px; }


/* ── the deeper chapters (chapter 2, 3, 4 …) ───────────────────────────────────
   Same card, swiped sideways. These scroll on their own if they run long, which
   is why the scrollbar is hidden here too. */
.chapter-body {
  position: relative; z-index: 2; height: 100%;
  padding: 82px 22px calc(var(--nav-h) + 18px + env(safe-area-inset-bottom, 0px));
  overflow-y: auto; scrollbar-width: none;
}
.chapter-body::-webkit-scrollbar { display: none; }

.chap-eyebrow { font-size: 11px; font-weight: 600; letter-spacing: .11em; text-transform: uppercase; color: var(--accent); margin-bottom: 6px; transition: color .5s; }
.chap-title { font-family: var(--font-display); font-weight: 600; font-size: 20px; letter-spacing: -.01em; margin: 0 0 4px; }
.chap-sub { font-size: 13px; color: var(--text-secondary); margin: 0 0 18px; line-height: 1.45; }

/* Same rule as .chartwrap, different shape: a FIXED height here, because this
   chart sits in a scrolling column with no spare space to flex into. Give it a
   height or it draws nothing. */
.chap-viz { height: 240px; margin-bottom: 6px; }
.chap-viz .chart { width: 100%; height: 100%; }

.notecard { margin-top: 20px; border-left: 2px solid var(--accent); padding: 2px 0 2px 14px; transition: border-color .5s; }
.notecard b { font-family: var(--font-display); font-weight: 600; font-size: 13px; }
.notecard p { font-size: 12.5px; color: var(--text-secondary); line-height: 1.5; margin: 4px 0 0; }

/* A soft prompt at the end of a chapter — "there is more of this elsewhere".
   `.seo-cta` is the original class name from the source feed and is kept as an
   alias so a card builder written against either name styles correctly. */
.chapter-cta,
.seo-cta {
  margin-top: 22px; display: flex; align-items: center; gap: 8px; align-self: flex-start;
  font-size: 12.5px; font-weight: 500; color: var(--text-secondary);
  border: 1px dashed var(--border2); border-radius: var(--radius); padding: 9px 13px;
}
.chapter-cta b,
.seo-cta b { color: var(--text-primary); font-weight: 600; }


/* ── progress rail: which chapter am I on, and is there more? ──────────────────
   Pinned just above the nav. pointer-events:none so it never swallows a swipe. */
.rail {
  position: absolute; left: 22px; right: 22px;
  bottom: calc(var(--nav-h) + env(safe-area-inset-bottom, 0px));
  z-index: 4;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  pointer-events: none;
}
.dots { display: flex; gap: 6px; align-items: center; }
.dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--dot-idle); transition: all .3s; }
/* The current chapter's dot stretches into a bar — readable at a glance, and it
   reuses the card's own trend colour so the whole card stays one colour story. */
.dots i.on { width: 22px; border-radius: 4px; background: var(--accent); }

.hint { font-size: 11.5px; color: var(--text-secondary); display: flex; align-items: center; gap: 6px; opacity: .58; }
.hint svg { width: 13px; height: 13px; }
.hint.hide { opacity: 0; }   /* fades out once they have swiped once — the tip did its job */

/* Edge arrows: a mouse has no swipe, so desktop gets clickable chevrons instead.
   They appear on hover and never on a touch screen. */
.chev {
  position: absolute; top: 0; bottom: var(--plate-h); width: 40px; z-index: 5;
  display: none; align-items: center; justify-content: center;
  color: var(--text-secondary); cursor: pointer; background: transparent; border: 0;
}
.chev svg { width: 20px; height: 20px; }
.chev.l { left: 0; }
.chev.r { right: 0; }
.insight:hover .chev.show { display: flex; }
@media (hover: none) { .chev { display: none !important; } }


/* ── optional: the "there's more behind this" card ─────────────────────────────
   If your feed shows a short preview to people without full access, the card
   builder appends one final card using these classes (buildLockCard() in feed.js
   emits .insight--locked > .lock > .lock__icon / __title / __body / __cta - keep
   the two in step). Classes, not inline styles — an inline style beats every
   media query you will later write.
   Delete this block if every visitor sees everything. */
.insight--locked { position: relative; }   /* the containing block for .lock below */
.lock { position: absolute; inset: 0; display: flex; flex-direction: column;
        align-items: center; justify-content: center; text-align: center; padding: 32px; gap: 14px; }
.lock__icon { font-size: 44px; line-height: 1; }
.lock__title { font-family: var(--font-display); font-weight: 600; font-size: 22px; line-height: 1.3; color: var(--text-primary); margin: 0; }
.lock__body { font-size: 14px; line-height: 1.55; color: var(--text-secondary); margin: 0; max-width: 290px; }
.lock__cta { margin-top: 6px; background: var(--brand); color: var(--on-accent); text-decoration: none;
             font-family: var(--font-body); font-weight: 600; font-size: 15px; padding: 13px 24px; border-radius: var(--radius); }


/* ════════════════════════════════════════════════════════════════════════════
   7 · BOTTOM MENU — THE SHARED ONE, DRESSED FOR THIS PAGE

   The bar and the More sheet are the board pages' menu (assets/mobile-nav.js), built into
   .phone at run time. This page owns none of their layout — only their colours.

   That module is written for the LIGHT board pages and falls back to white when a colour is
   not supplied, so the two names below are not optional decoration: leave them out and a
   white bar lands at the bottom of a black screen. Everything else here repairs a value the
   module hard-codes for a light background.
   ════════════════════════════════════════════════════════════════════════════ */
#mnav, #mnav-sheet {
  --bg-nav:  var(--bg-deep);   /* the bar: the same near-black the old bar faded into */
  --bg-card: var(--surface);   /* the sheet: a lifted panel, so it reads as sitting on top */

  /* ⚠ --accent is the ONE token this page must not inherit. At :root it is the feed's own
     periwinkle, and INSIDE a card it is rewritten again to that card's trend colour (green when
     a number is up, red when it is down). Either would make the menu change colour for reasons
     that have nothing to do with the menu. Pin it to the brand green the board pages use, so
     one product has one nav colour. */
  --accent:  var(--brand);
}

/* The module paints these three for a light page. On black they show as bright grey blocks. */
#mnav-sheet .mnav-row:active { background: color-mix(in srgb, var(--text-primary) 10%, transparent); }
#mnav-sheet .mnav-close:active { background: color-mix(in srgb, var(--text-primary) 10%, transparent); }
#mnav-sheet .mnav-soon { color: var(--text-muted); background: color-mix(in srgb, var(--text-primary) 9%, transparent); }

/* The bar's drop shadow is built for a white page and is invisible on black; the hairline the
   module already draws is what separates it from the feed. */
#mnav { box-shadow: none; }

/* ═══════════════════════════════════════════════════════════════════════════════
   8 · THE LOAD-FAILURE SCREEN
   Covers the whole frame when the data does not arrive. Honest and plain: what
   happened, and one button. Never a fabricated card, never a blank screen.
   ═══════════════════════════════════════════════════════════════════════════════ */
.error {
  position: absolute; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg);
}
/* Same `hidden` gotcha as the feed — without this the error screen never hides. */
.error[hidden] { display: none; }

/* The "nothing yet" screen. Same placement as the failure panel, deliberately calmer: no alert colour,
   no retry button — there is nothing for the reader to fix. Keep these two visually distinct. */
.empty {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: var(--bg); z-index: 5;
}
.empty[hidden] { display: none; }
.empty__inner { text-align: center; padding: 24px; max-width: 300px; }
.empty__mark { font-size: 28px; line-height: 1; margin-bottom: 12px; color: var(--text-muted); }
.empty__title { font-family: var(--font-display); font-size: 20px; margin: 0 0 8px; }
.empty__body { font-size: 14px; color: var(--text-secondary); margin: 0; line-height: 1.5; }

.error__inner { text-align: center; padding: 24px; }
.error__mark {
  width: 48px; height: 48px; margin: 0 auto 14px; border-radius: 50%;
  background: color-mix(in srgb, var(--negative) 22%, transparent);
  color: var(--negative);
  font-family: var(--font-display); font-size: 26px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.error__title { font-family: var(--font-display); font-size: 20px; margin: 0 0 8px; }
.error__body { color: var(--text-secondary); font-size: 14px; margin: 0 0 18px; }
.error__retry {
  appearance: none; border: 0; cursor: pointer;
  background: var(--text-primary); color: var(--bg);
  font-family: var(--font-body); font-weight: 700; font-size: 14px;
  padding: 11px 22px; border-radius: var(--radius);
}
.error__retry:hover { filter: brightness(.94); }


/* ═══════════════════════════════════════════════════════════════════════════════
   9 · THE CHART LIBRARY
   ═══════════════════════════════════════════════════════════════════════════════
   These class names are a CONTRACT with the chart module — it writes them, this
   file styles them. Renaming one here silently breaks that chart type, so rename
   in both places or not at all.

   Every chart is drawn flat and still, then ANIMATED ON when the card settles: the
   chart module adds `is-drawn` and the transitions below run. That timing is the
   whole trick — the drawing lands as the card arrives, so the card feels alive
   rather than pre-loaded. Nothing here animates on a timer, so a chart that never
   settles simply sits there finished-but-static instead of flickering.

   Chart types are named for their SHAPE, not for any particular subject:
     trend  — a line over time        bars   — ranked horizontal bars
     donut  — a share-of-total ring   h2h    — two things compared
     race   — bars that reorder as time plays
     grid   — one small cell per item (a "small multiples" grid)
   ─────────────────────────────────────────────────────────────────────────────── */
.chart { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
.chart__svg { width: 100%; height: 100%; max-height: 100%; overflow: visible; }

/* static furniture */
.chart__grid       { stroke: var(--hairline); stroke-width: 1; }
.chart__unit,
.chart__xlab       { fill: var(--text-muted); font-family: var(--font-body); font-size: 11px; }
.chart__unit       { font-weight: 600; }
.chart__leg        { fill: var(--text-primary); font-family: var(--font-body); font-size: 11px; }
.chart__legval     { fill: var(--text-secondary); font-family: var(--font-display); font-size: 11.5px; font-weight: 600; }
.chart__barlab     { fill: var(--text-primary); font-family: var(--font-body); font-size: 13px; font-weight: 500; }
.chart__barval     { fill: color-mix(in srgb, var(--text-primary) 78%, transparent); font-family: var(--font-display); font-size: 12.5px; font-weight: 600; }
.chart__bardelta   { fill: var(--accent); font-family: var(--font-display); font-size: 11.5px; font-weight: 600; }
.chart__track      { fill: var(--hairline); }
.chart__donut-val  { fill: var(--text-primary); font-family: var(--font-display); font-size: 30px; font-weight: 700; }
.chart__donut-lab  { fill: var(--text-secondary); font-family: var(--font-body); font-size: 11px; }

/* ── the draw-on. Resting state first, `is-drawn` state second. ────────────────
   The line trick: a dashed outline as long as the line itself, offset out of
   sight, then slid back to zero — so the line appears to be drawn by hand. */
.chart__line { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 1.1s cubic-bezier(.4, 0, .2, 1); }
.chart.is-drawn .chart__line { stroke-dashoffset: 0; }

.chart__area { opacity: 0; transition: opacity .9s ease .25s; }          /* fill follows the line */
.chart.is-drawn .chart__area { opacity: 1; }

.chart__dot { opacity: 0; transition: opacity .35s ease .95s; }          /* end marker lands last */
.chart.is-drawn .chart__dot { opacity: 1; }

/* Bars grow from nothing to the width the chart module set as --w on each bar. */
.chart__bar { width: 0; transition: width .95s cubic-bezier(.22, .61, .36, 1); }
.chart.is-drawn .chart__bar { width: var(--w); }

.chart__barval, .chart__bardelta { opacity: 0; transition: opacity .4s ease .55s; }
.chart.is-drawn .chart__barval,
.chart.is-drawn .chart__bardelta { opacity: 1; }

/* Ring segments sweep in one after another; the module staggers them with --delay. */
.chart__arc { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset .85s cubic-bezier(.4, 0, .2, 1); transition-delay: var(--delay, 0ms); }
.chart.is-drawn .chart__arc { stroke-dashoffset: 0; }

.chart__legdot, .chart__leg, .chart__legval { opacity: 0; transition: opacity .4s ease; transition-delay: var(--delay, 0ms); }
.chart.is-drawn .chart__legdot,
.chart.is-drawn .chart__leg,
.chart.is-drawn .chart__legval { opacity: 1; }

/* Shown when a card asks for a chart shape that does not exist, or when the
   numbers behind one are missing. Deliberately plain: an honest "no chart here"
   beats a wrong chart, and this is the kit's most load-bearing behaviour - so the
   class names here must stay in step with emptyChart() in charts.js. */
.chart--empty { color: var(--text-secondary); font-size: 13px; }
.chart__empty-txt { color: var(--text-secondary); font-family: var(--font-body); font-size: 13px; line-height: 1.45; text-align: center; padding: 0 14px; }

/* ── race: bars that reorder while time plays, with play / scrub / speed ───────
   Rows are absolutely positioned and moved by transform, so a row that changes
   rank slides instead of jumping. */
.chart--race { flex-direction: column; align-items: stretch; gap: 8px; padding: 4px 2px; }
.race__unit { font-family: var(--font-body); font-size: 11px; font-weight: 600; color: var(--text-muted); }
.race__rows { position: relative; flex: 1 1 auto; min-height: 0; }
.race__row { position: absolute; left: 0; right: 0; top: 0; height: 34px; display: flex; align-items: center; gap: 8px; will-change: transform; }
.race__label { flex: 0 0 auto; width: 40px; font-family: var(--font-display); font-size: 13px; font-weight: 600; color: var(--text-primary); text-align: right; white-space: nowrap; overflow: hidden; }
.race__track { flex: 1 1 auto; position: relative; display: flex; align-items: center; }
.race__bar { height: 22px; min-width: 6%; border-radius: 6px; position: relative; display: flex; align-items: center; justify-content: flex-end; will-change: width; }
/* the marker riding the tip of each bar — an emoji, an initial, a small badge */
.race__head { position: absolute; right: -11px; display: inline-flex; align-items: center; justify-content: center; }
/* `--flag` is the original class name from the source feed, kept as an alias. */
.race__head--emoji,
.race__head--flag { font-size: 20px; line-height: 1; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .5)); }
.race__head--mono { width: 22px; height: 22px; border-radius: 50%; color: var(--on-accent); font-family: var(--font-display); font-weight: 700; font-size: 12px; box-shadow: 0 0 0 2px var(--bg); }
.race__val { margin-left: 12px; font-family: var(--font-display); font-size: 12.5px; font-weight: 600; color: var(--text-secondary); white-space: nowrap; }
.race__controls { flex: 0 0 auto; display: flex; align-items: center; gap: 10px; padding-top: 4px; }
.race__play { width: 30px; height: 30px; flex: 0 0 auto; border-radius: 50%; border: 0; cursor: pointer; background: color-mix(in srgb, var(--accent) 24%, transparent); display: inline-flex; align-items: center; justify-content: center; }
.race__play-ico { width: 0; height: 0; border-style: solid; border-width: 6px 0 6px 10px; border-color: transparent transparent transparent var(--accent); margin-left: 2px; }
.race__play.is-playing .race__play-ico { width: 9px; height: 11px; border: 0; background: var(--accent); margin: 0; box-shadow: inset 3px 0 0 var(--bg), inset -3px 0 0 var(--bg); }
.race__scrub { flex: 1 1 auto; accent-color: var(--accent); height: 4px; }
.race__speed { flex: 0 0 auto; min-width: 40px; border: 1px solid var(--border); background: transparent; color: var(--text-primary); font-family: var(--font-display); font-size: 12px; font-weight: 600; border-radius: 999px; padding: 4px 9px; cursor: pointer; }
.race__frame { flex: 0 0 auto; font-family: var(--font-display); font-size: 12px; font-weight: 600; color: var(--accent); min-width: 30px; }

/* ── grid: one small cell per item, with the ones that matter lit up ───────────
   The cells fade in one after another (the module staggers --delay), which reads
   as "here they all are" rather than "here is a table". */
/* SINGLE FIGURE — what a one-cell "grid" becomes. Nine stored cards hold a headline number and
   nothing else; through the grid layout they came out as a half-width tile with the label clipped
   to 12 characters, marooned in the empty other half (Rossi, 2026-08-24). Given the whole width
   they read as what they are: one number, and what it counts.
   Sized well below .metric-value on purpose — the card's hero number must stay the hero. */
.chart--figure { flex-direction: column; align-items: center; justify-content: center; gap: 11px; text-align: center; padding: 0 10px; }
.figure__rule { width: 42px; height: 3px; border-radius: 2px; }
.figure__val {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(32px, 11.5vw, 52px); line-height: 1; letter-spacing: -.02em;
}
/* What the number counts — "Likes", "Days" — sitting just after it (Rossi, 2026-08-25). Sized and
   coloured to read as a UNIT and not as a second number: same weight, well under half the size,
   and the palette's secondary text rather than the card's accent. `em` so it tracks the figure at
   every screen width instead of drifting out of proportion on a small phone. */
.figure__unit {
  font-size: .34em;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--text-secondary);
  margin-left: .28em;
}

/* max-width in ch, so the line breaks by READING LENGTH rather than by whatever the frame happens
   to be; wraps freely — this label is never truncated. */
.figure__label { font-size: 13px; line-height: 1.45; color: var(--text-secondary); max-width: 28ch; }
.figure__delta { font-size: 12px; color: var(--text-muted); }
/* Same entrance as every other chart: nothing is visible until the card settles and the engine adds
   `is-drawn`, so the figure arrives with the card rather than being there before it. */
.chart--figure .figure__rule,
.chart--figure .figure__val,
.chart--figure .figure__label,
.chart--figure .figure__delta {
  opacity: 0; transform: translateY(9px);
  transition: opacity .45s ease, transform .45s cubic-bezier(.22, .61, .36, 1);
}
.chart.is-drawn .figure__val    { transition-delay: 60ms; }
.chart.is-drawn .figure__label  { transition-delay: 120ms; }
.chart.is-drawn .figure__delta  { transition-delay: 170ms; }
.chart.is-drawn .figure__rule,
.chart.is-drawn .figure__val,
.chart.is-drawn .figure__label,
.chart.is-drawn .figure__delta { opacity: 1; transform: none; }

.chart--grid { flex-direction: column; align-items: stretch; gap: 12px; }
.grid__cap { display: flex; align-items: baseline; gap: 8px; }
.grid__cap-k { font-family: var(--font-display); font-size: 26px; font-weight: 700; }
.grid__cap-t { font-size: 12.5px; color: var(--text-secondary); }
/* charts.js writes --cols on this element (style="--cols:4") so a 4-cell or
   6-cell grid fills the row instead of leaving holes. 5 is only the fallback. */
.grid__cells { display: grid; grid-template-columns: repeat(var(--cols, 5), 1fr); gap: 8px; }
.grid__cell {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 9px 4px; border-radius: var(--radius);
  background: var(--hairline-soft); border: 1px solid var(--border);
  opacity: 0; transform: translateY(8px) scale(.96);
  transition: opacity .4s ease, transform .4s cubic-bezier(.22, .61, .36, 1);
  transition-delay: var(--delay, 0ms);
}
.chart.is-drawn .grid__cell { opacity: 1; transform: none; }
.grid__cell.is-hit {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: 0 0 16px -6px var(--accent);
}
/* `.grid__flag` is the original class name from the source feed, kept as an alias. */
.grid__icon,
.grid__flag { font-size: 20px; line-height: 1; }
.grid__code { font-family: var(--font-display); font-size: 11px; font-weight: 600; color: var(--text-primary); }
.grid__val { font-family: var(--font-display); font-size: 11px; font-weight: 600; }
.grid__cell.is-hit .grid__val { color: var(--accent); }
.grid__cell:not(.is-hit) .grid__val { color: var(--text-secondary); }
/* the optional change line under a cell's value */
.grid__delta { font-family: var(--font-display); font-size: 10px; font-weight: 600; color: var(--text-muted); }


/* ═══════════════════════════════════════════════════════════════════════════════
   10 · REDUCED MOTION
   Some people get motion sickness from sliding and drawing animations, and their
   device tells us so. Honour it: everything still WORKS and every number is still
   shown — it just arrives instantly instead of animating. Note the feed keeps
   snapping; only the smooth glide is dropped.
   ═══════════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .chapters, .glow, .dots i,
  .chart__line, .chart__area, .chart__dot, .chart__bar, .chart__arc,
  .chart__barval, .chart__bardelta, .grid__cell, .race__row, .race__bar {
    transition: none !important;
    animation: none !important;
  }
  .feed { scroll-behavior: auto; }
}


/* ═══════════════════════════════════════════════════════════════════════════════
   11 · BIG SCREENS
   ═══════════════════════════════════════════════════════════════════════════════
   There is deliberately no desktop takeover here.

   An earlier draft hid the phone frame above 769px and handed the page to a card
   grid built by a desktop.js that was never written - so a laptop showed a blank
   page. One feed, one set of cards, every screen size is the honest version: the
   base rule in section 2 (display:grid + place-items:center) already parks the
   phone frame in the middle of a big screen, the mouse wheel moves between cards,
   the on-card arrows and the left/right keys walk the chapters, and every number
   on screen came from the same fetch as it did on a phone.

   IF YOU LATER WANT A REAL DESKTOP GRID: write the renderer FIRST, then add the
   markup and the styles in the same commit. Styles for a renderer that does not
   exist are how the blank page happened.
   ═══════════════════════════════════════════════════════════════════════════════ */


/* ── the way in to a full study ────────────────────────────────────────────────
   Only a card with a whole study behind it carries this. It is a TAP target and
   never a swipe: sideways swipe already pages through a card's chapters, and one
   gesture with two meanings makes a feed unpredictable. Sized and coloured like a
   primary action rather than a link, because on a phone a text link inside a
   swipeable card is very easy to miss and very hard to hit. */
.deep-cta {
  display: inline-block;
  margin-top: 14px;
  padding: 11px 18px;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  line-height: 1;
  transition: transform var(--ease), filter var(--ease);
  /* The pager listens for drags on the card; this element handles its own. */
  touch-action: manipulation;
}
.deep-cta:hover { filter: brightness(1.08); }
.deep-cta:active { transform: translateY(1px); }
.deep-cta:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
