/* ============================================================================
   Task98 — The Register in Numbers (analytics.html)

   An EXTERNAL stylesheet, unlike the rest of this site, which inlines its CSS.
   That is deliberate: this page carries a Content-Security-Policy with
   `style-src 'self'`, and a policy that has to allow 'unsafe-inline' to let its
   own page render is theatre. The other pages keep their inline blocks, which
   is why the CSP is scoped to /analytics* and not to the whole site.

   Every token below is copied verbatim from index.html / founder.html /
   privacy.html. Nothing here re-picks a colour or invents a scale.
   ============================================================================ */

:root {
  --sky-top: #0A1E73;
  --sky-mid: #4A2E86;
  --sunset: #C8401F;
  --sun: #F47B2A;
  --gold: #FFC24A;
  --aravali: #1976D2;
  --nilgiri: #388E3C;
  --shivalik: #D32F2F;
  --udaygiri: #FFA000;
  --ink: #14213D;
  --paper: #FFFFFF;

  /* --nilgiri is 4.1:1 on white and FAILS WCAG AA as text. This is its
     text-safe partner (5.1:1), used for positive deltas. --nilgiri stays a
     fill colour; --udaygiri (2.0:1) and --gold (1.7:1) are fills only, never
     text on a light background. */
  --nilgiri-ink: #2E7D32;

  --muted: #5A6478;
  --line: #E6E9F0;
  --cell: #E0E5EE;
  --cardbg: #FBFCFE;
  --band: #F4F7FB;
  --skel: #EDF1F7;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Nirmala UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  color: var(--ink);
  background: var(--paper);
  line-height: 1.6;
}
a { color: var(--aravali); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- Top bar (founder.html) ---------- */
.topbar { background: #0C1430; color: #B9C2D8; padding: 11px 24px; font-size: 0.88rem; text-align: center; }
.topbar a { color: #DCE4F5; font-weight: 600; }

/* ---------- Hero (founder.html) ---------- */
.hero {
  background: linear-gradient(180deg, var(--sky-top) 0%, var(--sky-mid) 55%, var(--sunset) 88%, var(--sun) 100%);
  color: #fff;
  text-align: center;
  padding: 56px 24px 60px;
}
.hero .kicker {
  font-size: clamp(0.75rem, 2vw, 0.86rem);
  letter-spacing: 0.16em; text-transform: uppercase;
  opacity: 0.82; font-weight: 600; margin-bottom: 14px;
}
.hero h1 { font-size: clamp(1.9rem, 5vw, 3rem); font-weight: 800; letter-spacing: -0.02em; margin-bottom: 14px; }
.hero .sub { font-size: clamp(1rem, 2.2vw, 1.12rem); opacity: 0.86; max-width: 560px; margin: 0 auto 34px; font-weight: 500; }

/* The two headline numbers. They differ legitimately, so they sit together. */
.pair { display: flex; align-items: stretch; justify-content: center; margin-bottom: 22px; }
.pair .n { padding: 0 34px; flex: 0 1 300px; }
.pair .rule { width: 1px; background: rgba(255,255,255,0.25); }
.pair .v {
  font-size: clamp(2.2rem, 7vw, 3.4rem); font-weight: 800; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.pair .l { font-size: 0.86rem; font-weight: 700; opacity: 0.9; }
.pair .sl { font-size: 0.74rem; opacity: 0.7; line-height: 1.4; }
/* On a phone the two numbers stay SIDE BY SIDE at a smaller size (user
   decision, mockup change 1) — stacking them cost most of a screen of hero
   before any scrolling. The vertical rule stays between them. */
@media (max-width: 719px) {
  .pair { margin-bottom: 18px; }
  .pair .n { padding: 0 12px; flex: 1 1 0; min-width: 0; }
  .pair .v { font-size: 1.7rem; }
  .pair .l { font-size: 0.74rem; }
  .pair .sl { font-size: 0.66rem; }
}

/* ---------- Freshness chip ---------- */
/* The dot NEVER animates. A pulsing "live" dot on an hourly snapshot is a lie. */
.chip { display: inline-flex; align-items: center; gap: 7px; padding: 6px 12px; border-radius: 999px; font-size: 0.78rem; font-weight: 600; }
.chip .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.chip.on-hero { background: rgba(255,255,255,0.14); border: 1px solid rgba(255,255,255,0.35); color: #fff; }
.chip.on-page { background: #F1F4FA; border: 1px solid var(--line); color: var(--muted); }
.d-fresh { background: var(--nilgiri); }
.d-stale { background: var(--udaygiri); }
.d-live { background: #4ADE80; }

/* ---------- The heartbeat ---------- */
/* The rule above said the dot NEVER animates, because a pulsing "live" dot on an
   hourly snapshot is a lie. That rule is not repealed — its precondition changed.
   The number really is live now, and `.pulse` is applied ONLY while the poll is
   answering: the moment it fails, setChip drops back to d-stale and the dot goes
   still. A beating dot remains a claim about the data, and it is still only ever
   made when the data supports it.

   Lub-dub inside the first ~0.63s of a 3s cycle, then a rest. ONE class drives
   BOTH the thump and the ring on ONE element: two animations applied at different
   moments start at different times and drift into a permanent offset. */
@keyframes beat {
  0% { transform: scale(1); }     5% { transform: scale(1.18); }  11% { transform: scale(1); }
  16% { transform: scale(1.11); } 21% { transform: scale(1); }   100% { transform: scale(1); }
}
@keyframes ring {
  0% { transform: scale(0.72); opacity: 0.6; }
  12% { opacity: 0.38; }
  34% { transform: scale(1.75); opacity: 0; }
  100% { transform: scale(1.75); opacity: 0; }
}
/* A separate NAME, not a re-timing of `ring`: CSS matches animations by name, so
   shortening the existing one would update it in place and keep its original
   start time — instantly "already finished", firing animationend within a frame
   and tearing off the class before the emphatic beat ever drew. */
@keyframes ringOnce {
  0% { transform: scale(0.7); opacity: 0.75; }
  100% { transform: scale(2); opacity: 0; }
}
@keyframes beatOnce {
  0% { transform: scale(1); }    18% { transform: scale(1.5); }  36% { transform: scale(1); }
  54% { transform: scale(1.28); } 72% { transform: scale(1); }  100% { transform: scale(1); }
}
.pulse { animation: beat 3s ease-in-out infinite; transform-origin: center; position: relative; }
.pulse::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  border: 2px solid currentColor; animation: ring 3s ease-out infinite; pointer-events: none;
}
.pulse.once { animation: beatOnce 0.75s ease-in-out 1; }
.pulse.once::after { animation: ringOnce 0.75s ease-out 1; }
@media (prefers-reduced-motion: reduce) {
  .pulse, .pulse::after, .pulse.once, .pulse.once::after { animation: none; }
  .pulse::after { opacity: 0.3; }
}

/* ---------- House stripe (index.html) ---------- */
.houses { display: flex; height: 6px; }
.houses span { flex: 1; }
.houses .aravali { background: var(--aravali); }
.houses .nilgiri { background: var(--nilgiri); }
.houses .shivalik { background: var(--shivalik); }
.houses .udaygiri { background: var(--udaygiri); }

/* ---------- Sections (index.html) ---------- */
section { padding: 56px 24px; max-width: 1040px; margin: 0 auto; }
h2 { font-size: clamp(1.5rem, 3.5vw, 2rem); font-weight: 800; text-align: center; margin-bottom: 12px; letter-spacing: -0.01em; }
.section-sub { text-align: center; color: var(--muted); max-width: 620px; margin: 0 auto 36px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 22px; }
.card { border: 1px solid var(--line); border-radius: 16px; padding: 26px 24px; background: var(--cardbg); }
.card .icon { width: 44px; height: 44px; border-radius: 12px; display: flex; align-items: center; justify-content: center; margin-bottom: 16px; }
.card .icon svg { width: 24px; height: 24px; }
.card h3 { font-size: 1.08rem; font-weight: 700; margin-bottom: 6px; }
.card p { font-size: 0.94rem; color: var(--muted); }
.ic-blue { background: rgba(25,118,210,0.12); } .ic-blue svg { fill: var(--aravali); }

/* ---------- Tabs (Amendment 1) ---------- */
/* Hidden until analytics.js stamps html.js. With scripts off there is no tab
   bar and both panels render stacked — the page stays the honest no-JS
   document it was. The [hidden] attribute (set by JS on the unselected panel)
   also keeps that panel out of print, so a printout is the selected view, not
   two views stacked. */
.tabs-wrap { display: none; }
html.js .tabs-wrap { display: block; border-bottom: 1px solid var(--line); background: var(--paper); }
.tabs { display: flex; gap: 4px; justify-content: center; max-width: 1040px; margin: 0 auto; padding: 0 24px; }
.tab {
  appearance: none; background: transparent; border: 0; border-bottom: 3px solid transparent;
  padding: 16px 22px; font: 700 0.98rem/1.2 inherit; color: var(--muted); cursor: pointer;
  display: flex; align-items: center; gap: 9px; white-space: nowrap;
}
.tab svg { width: 19px; height: 19px; fill: currentColor; opacity: 0.85; }
.tab:hover { color: var(--ink); }
.tab[aria-selected="true"] { color: var(--sky-top); border-bottom-color: var(--sky-top); }
.tab:focus-visible { outline: 2px solid var(--aravali); outline-offset: -4px; border-radius: 6px; }
.tab .soon {
  font-size: 0.66rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  background: #F1F4FA; border: 1px solid var(--line); color: var(--muted);
  border-radius: 999px; padding: 2px 7px;
}
@media (max-width: 560px) {
  .tab { padding: 14px 12px; font-size: 0.9rem; }
  .tab .soon { display: none; }
}
[hidden] { display: none !important; }

/* ---------- Pull quote (founder.html) ---------- */
.pullquote { border-left: 4px solid var(--gold); padding: 12px 0 12px 18px; margin: 0 0 30px; color: #3C465C; font-size: 0.96rem; }
.pullquote b { color: var(--ink); }
/* Mockup change 2: the same words twice in the markup — full paragraph on
   desktop, a <details> fold on a phone. Exactly one renders per breakpoint. */
.pq-fold { display: none; }
.pq-fold summary { cursor: pointer; font-weight: 700; color: var(--ink); }
.pq-fold[open] summary { margin-bottom: 6px; }
@media (max-width: 719px) {
  .pq-desk { display: none; }
  .pq-fold { display: block; padding: 10px 0 10px 16px; margin-bottom: 22px; }
}

/* ---------- Callout (privacy.html) ---------- */
.callout { border-left: 4px solid var(--aravali); background: #F4F8FE; padding: 14px 18px; border-radius: 0 12px 12px 0; font-size: 0.9rem; margin-bottom: 20px; }

/* ---------- Section header row ---------- */
.sec-head { display: flex; justify-content: space-between; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 18px; }
.btn-ghost { border: 1px solid var(--line); background: #fff; color: var(--ink); border-radius: 999px; padding: 8px 15px; font: 600 0.82rem/1 inherit; cursor: pointer; display: inline-flex; gap: 7px; align-items: center; }
.btn-ghost:hover { background: #F1F4FA; }
.btn-ghost[disabled] { color: #8A94A8; cursor: not-allowed; }
.btn-solid { background: var(--sky-top); color: #fff; border: 0; border-radius: 999px; padding: 10px 22px; font: 700 0.88rem/1 inherit; cursor: pointer; }

/* ---------- Segmented controls (derived from index.html .reel-lang) ---------- */
/* fieldset ships a UA default of min-inline-size:min-content. Without this
   override the nowrap pills refuse to shrink and push the whole PAGE sideways
   on a phone — found in the mockup, not in theory. */
fieldset { border: 0; min-width: 0; min-inline-size: 0; max-width: 100%; }
legend { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); margin-bottom: 8px; }
.seg { display: flex; gap: 3px; padding: 3px; border-radius: 999px; background: #F1F4FA; border: 1px solid var(--line); overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.seg::-webkit-scrollbar { display: none; }
.seg label { padding: 8px 15px; border-radius: 999px; font: 700 0.86rem/1 inherit; color: var(--muted); cursor: pointer; white-space: nowrap; min-height: 32px; display: inline-flex; align-items: center; transition: background 0.12s; }
@media (hover: hover) { .seg label:hover { background: #E7ECF6; } }
.seg input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.seg input:checked + label { background: var(--sky-top); color: #fff; }
.seg input:focus-visible + label { outline: 2px solid var(--aravali); outline-offset: 2px; }
/* No opacity on the disabled state — it would drop the reason text below AA. */
.seg input:disabled + label { color: #8A94A8; cursor: not-allowed; background: transparent; }
@media (hover: hover) { .seg input:disabled + label:hover { background: transparent; } }
.reason { font-size: 0.78rem; color: var(--muted); margin-top: 8px; }
@media (max-width: 719px) {
  .seg label { min-height: 34px; }
}

/* ---------- Chart toolbar (mockup change 5) ---------- */
/* The four option groups live in a bar under the graph. Each button opens a
   sheet anchored above the bar holding the group's radio fieldset, so a pick
   redraws the chart in the same glance. The open state is aria-expanded — the
   style rides the semantics, so they cannot drift apart. */
.ctlbar { position: relative; display: flex; flex-wrap: wrap; gap: 8px; border-top: 1px solid var(--line); margin-top: 14px; padding-top: 12px; }
.ctl { display: inline-flex; }
.ctl-btn {
  border: 1px solid var(--line); background: var(--band); color: var(--muted); border-radius: 999px;
  padding: 7px 12px; font: 600 0.8rem/1 inherit; cursor: pointer;
  display: inline-flex; gap: 6px; align-items: center; white-space: nowrap;
}
.ctl-btn b { color: var(--ink); }
.ctl-btn .car { font-size: 0.62rem; color: var(--muted); }
.ctl-btn[aria-expanded="true"] { border-color: var(--sky-top); background: #fff; box-shadow: inset 0 0 0 1px var(--sky-top); }
.ctl-lab { text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.64rem; font-weight: 700; color: var(--muted); }
@media (max-width: 719px) {
  .ctl-lab { display: none; }   /* the sheet's legend still names the group */
  .ctl-btn { padding: 6px 11px; font-size: 0.78rem; }
}
.pop {
  position: absolute; bottom: calc(100% + 8px); left: 0; right: 0; z-index: 20;
  background: #fff; border: 1px solid var(--line); border-radius: 14px; padding: 14px;
  box-shadow: 0 10px 28px rgba(20, 33, 61, 0.16);
}
@media (min-width: 720px) { .pop { right: auto; min-width: 340px; max-width: 100%; } }
/* Inside a sheet the pills WRAP instead of scrolling — a sheet has no width to
   spare, and a scroll-with-mask inside a popover reads as cut off. */
.pop .seg { max-width: 100%; flex-wrap: wrap; border-radius: 18px; overflow-x: visible; }

/* ---------- KPI grid ---------- */
/* NOT .grid: auto-fit minmax(260px) inside 992px yields three columns, so four
   cards would render 3 + 1. This is deterministic. */
.kpi { display: grid; gap: 14px; grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 22px 0; }
@media (min-width: 720px) { .kpi { gap: 22px; grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.kpi .card { padding: 18px; border-top-width: 3px; }
.k1 { border-top-color: var(--aravali); }
.k2 { border-top-color: var(--nilgiri); }
.k3 { border-top-color: var(--shivalik); }
.k4 { border-top-color: var(--udaygiri); }
.kpi .lab { display: flex; align-items: center; gap: 7px; font-size: 0.82rem; font-weight: 600; color: var(--muted); margin-bottom: 8px; }
.kpi .lab svg { width: 18px; height: 18px; opacity: 0.75; flex: none; }
.kpi .val { font-size: clamp(1.55rem, 5.2vw, 2.05rem); font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; line-height: 1.15; }
.kpi .foot { font-size: 0.8rem; color: var(--muted); margin-top: 4px; }
.up { color: var(--nilgiri-ink); font-weight: 700; }
.down { color: var(--sunset); font-weight: 700; }
/* Mockup change 4: tighter cards on a phone — same four cards, same grid. */
@media (max-width: 719px) {
  .kpi { gap: 10px; margin: 16px 0; }
  .kpi .card { padding: 12px; border-radius: 12px; }
  .kpi .lab { font-size: 0.74rem; margin-bottom: 6px; }
  .kpi .val { font-size: 1.35rem; }
  .kpi .foot { font-size: 0.72rem; }
}

/* ---------- Panel + chart ---------- */
.panel { background: #fff; border: 1px solid var(--line); border-radius: 18px; padding: 20px; }
.chart-wrap { position: relative; height: 300px; }
@media (min-width: 720px) { .chart-wrap { height: 420px; } }
/* Chart.js writes an inline `width` on the canvas. max-width is a different
   property, so this constrains it without !important and guarantees the canvas
   can never widen the PAGE — belt and braces alongside the resize handler. */
.chart-wrap canvas { max-width: 100%; }
.cap { font-size: 0.82rem; color: var(--muted); margin-top: 10px; }
.skip { display: inline-block; font-size: 0.82rem; margin-bottom: 12px; }
.fail { text-align: center; padding: 44px 24px; }
.fail h3 { font-size: 1.1rem; font-weight: 700; margin-bottom: 6px; }
.fail p { color: var(--muted); font-size: 0.92rem; margin-bottom: 16px; }

/* ---------- Insights ---------- */
.insights { display: grid; gap: 22px; grid-template-columns: 1fr; margin-top: 22px; }
@media (min-width: 992px) { .insights { grid-template-columns: 2fr 1fr; } }
.tw { max-height: 340px; overflow: auto; border: 1px solid var(--line); border-radius: 12px; }
table { width: 100%; border-collapse: collapse; font-size: 0.92rem; }
caption { caption-side: top; text-align: left; font-size: 0.82rem; color: var(--muted); padding: 0 0 10px; }
th, td { padding: 10px 12px; border-bottom: 1px solid var(--cell); text-align: left; }
th { position: sticky; top: 0; background: var(--band); font-weight: 700; font-size: 0.82rem; color: var(--muted); z-index: 1; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.tl { display: flex; flex-direction: column; gap: 16px; position: relative; padding-left: 20px; }
.tl:before { content: ""; position: absolute; left: 4px; top: 6px; bottom: 6px; width: 2px; background: var(--line); }
.tl .it { position: relative; }
.tl .dot { position: absolute; left: -20px; top: 5px; width: 10px; height: 10px; border-radius: 50%; box-shadow: 0 0 0 2px #fff; }
.tl .dot.hollow { background: #fff; box-shadow: 0 0 0 2px var(--line); }
/* Dot colours as CLASSES, never style attributes: this page ships
   `style-src 'self'` with no 'unsafe-inline', and CSP strips style ATTRIBUTES
   (CSSOM assignment is exempt, attributes are not) — inline-styled dots would
   render white-on-white on the deployed page while looking fine locally. */
.tl .dot-a { background: var(--aravali); }
.tl .dot-n { background: var(--nilgiri); }
.tl .dot-s { background: var(--shivalik); }
.tl .dot-u { background: var(--udaygiri); }
/* The next target pulses (user decision). The global reduced-motion rule
   below stills it, so this is the one deliberate exception to "dots never
   animate" — it marks a goal, not a false liveness claim. --sunset is the
   text colour (AA on white); --sun is only ever the ring. */
.tl .it.next h4 { color: var(--sunset); }
.tl .dot.dot-next { background: #fff; box-shadow: 0 0 0 2px var(--sun); animation: pulse 1.6s ease-in-out infinite; }
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 2px var(--sun); }
  50% { box-shadow: 0 0 0 6px rgba(244, 123, 42, 0.4); }
}
.panel-title { font-size: 1.05rem; }
.card-title { font-size: 1.05rem; display: block; margin-bottom: 16px; }
.sec-head.tight { margin-bottom: 8px; }
.tbl-btns { display: inline-flex; gap: 8px; }
/* Mockup changes 3 + 6: one CSV button per breakpoint. The labelled button
   stays in the chip row on desktop; the phone gets a short one beside the
   table it exports. */
.csv-m { display: none; }
@media (max-width: 719px) {
  #csvBtn { display: none; }
  .csv-m { display: inline-flex; }
}
/* Mockup change 7: the milestones card is a <details>. Open on desktop; the
   script folds it at load on a phone. The summary keeps the next target
   visible even when folded. */
details.ms summary { cursor: pointer; list-style: none; display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 0; }
details.ms summary::-webkit-details-marker { display: none; }
details.ms summary::after { content: "▾"; color: var(--muted); font-size: 0.8rem; flex: none; }
details.ms[open] summary::after { content: "▴"; }
details.ms[open] summary { margin-bottom: 16px; }
.ms-next { font-size: 0.78rem; font-weight: 600; color: var(--sunset); margin-left: auto; white-space: nowrap; }
.tl .dt { font-size: 0.76rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.tl h4 { font-size: 0.95rem; font-weight: 700; }
.tl p { font-size: 0.86rem; color: var(--muted); }

/* ---------- Skeletons ---------- */
.skel { background: var(--skel); border-radius: 6px; height: 1.9rem; width: 60%; }

/* ---------- CTA (founder.html) + badges (index.html) ---------- */
.cta { text-align: center; }
.badges { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
.badge { display: inline-flex; align-items: center; gap: 10px; background: var(--sky-top); border: 1px solid var(--sky-top); color: #fff; border-radius: 14px; padding: 12px 22px; font-weight: 600; font-size: 0.98rem; }
.badge svg { width: 22px; height: 22px; flex: none; fill: #fff; }
a.badge { color: #fff; text-decoration: none; transition: opacity 0.15s ease; }
a.badge:hover { opacity: 0.92; text-decoration: none; }
.badge .small { display: block; font-size: 0.68rem; font-weight: 400; opacity: 0.8; line-height: 1.1; text-align: left; }
.badge .big { display: block; font-size: 0.98rem; line-height: 1.2; text-align: left; }

/* ---------- Footer ---------- */
/* Task108 — the rules moved to site-footer.css, which every page now loads.
   The old comment here argued that a page with no .version line cannot drift;
   the answer turned out to be the opposite one — give every page the version
   line and put every page under the checkSiteVersion gate. */

/* ---------- Across India (Task98 Part A) ---------- */
.maphead { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.maphead .tot { font-weight: 700; font-size: 0.95rem; }
.maphead .tot #geoCrumb { color: var(--sky-top); }
.mapgrid { display: grid; grid-template-columns: 1fr; gap: 16px; align-items: start; }
@media (min-width: 720px) { .mapgrid { grid-template-columns: 2fr 1fr; gap: 20px; } }
.mapgrid > * { min-width: 0; }
/* touch-action:manipulation kills the browser's double-tap page zoom, which the
   drill-in gesture would otherwise fight (design §6.5). */
.mapstage svg { width: 100%; height: auto; display: block; max-height: min(72vh, 620px); touch-action: manipulation; }

/* ---------- The hover readout (desktop) ---------- */
/* The pointer already knows what it is over; before this, the page answered
   underneath the map and only while nothing was selected. The card sits in the
   emptiest corner INSIDE the frame and steps aside when the shape being pointed
   at is under it. Its left/top are written by JS through the CSSOM — a style
   ATTRIBUTE would be stripped by this page's style-src 'self'. */
.mapstage { position: relative; }
.hovcard {
  position: absolute; top: 18px; right: 18px; width: 238px; max-width: 48%;
  background: rgba(255, 255, 255, 0.96);
  border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 14px 13px;
  box-shadow: 0 8px 22px rgba(20, 33, 61, 0.10);
  pointer-events: none;
  z-index: 2;
  transition: top 0.15s ease, left 0.15s ease;
}
@media (prefers-reduced-motion: reduce) { .hovcard { transition: none; } }
.hovcard .hc-h { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.hovcard .hc-name { font-size: 0.84rem; font-weight: 700; line-height: 1.25; }
.hovcard .hc-pin {
  margin-left: auto; font-size: 0.58rem; font-weight: 800; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--sky-top); background: #EEF3FF;
  border-radius: 999px; padding: 2px 7px; flex: none;
}
.hovcard .hc-n { font-size: 1.8rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1.1; font-variant-numeric: tabular-nums; }
.hovcard .hc-w { font-size: 0.78rem; color: var(--muted); }
.hovcard .hc-lead { margin-top: 9px; padding-top: 8px; border-top: 1px solid var(--line); font-size: 0.74rem; color: var(--muted); }
.hovcard .hc-hint { font-size: 0.78rem; color: var(--muted); }
.hovcard.is-idle .hc-h, .hovcard.is-idle .hc-n,
.hovcard.is-idle .hc-w, .hovcard.is-idle .hc-lead { display: none; }
.hovcard:not(.is-idle) .hc-hint { display: none; }
/* Touch has the strip above the map. A card that no pointer can ever reach must
   not occupy the frame — and this query must stay in step with cardActive() in
   analytics-map.js, which is the same test in JS. */
@media (hover: none), (max-width: 719px) { .hovcard { display: none; } }

/* The phone's answer to hover: the tapped thing, its count and its leading line,
   pinned ABOVE the map. Desktop has the rail and the caption instead. */
.tstrip { display: none; }
@media (max-width: 719px) {
  .tstrip { display: flex; justify-content: space-between; align-items: center; gap: 10px;
    border: 1px solid var(--line); border-radius: 12px; background: var(--band); padding: 8px 12px; margin-bottom: 10px; }
  .tstrip b { font-size: 0.92rem; display: block; line-height: 1.3; }
  .tstrip .ts-lead { font-size: 0.72rem; color: var(--muted); display: block; }
  .tstrip .ts-n { font-size: 1.4rem; font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; flex: none; }
}

/* Fills are CLASSES, never style attributes — the CSP strips style attributes on
   the deployed page while they look fine locally (the Part B lesson). Fixed band
   edges, never quantiles (§6.9): b0 none-yet hatch, b1 fewer-than-5, then the
   hero sky read bottom-to-top; bx = no JNV in the register's list. */
.rg { stroke: #fff; stroke-width: 0.8; cursor: pointer; }
.rg:hover { stroke: var(--ink); stroke-width: 1.4; }
/* Fillers carry no role and no handler, so they must not advertise a click
   they will not answer (QA L-3) — on some drill-ins the filler is most of the
   frame. */
.rg.bx { cursor: default; }
.rg.bx:hover { stroke: #B9C0CC; stroke-width: 0.8; }
.rg.sel { stroke: var(--ink); stroke-width: 2; }
.rg:focus-visible { outline: 2px solid var(--aravali); outline-offset: 2px; }
.b0 { fill: url(#hatch); }
.b1 { fill: #FFF3D6; }
.b2 { fill: #FFD07A; }
.b3 { fill: #F47B2A; }
.b4 { fill: #C8401F; }
.b5 { fill: #4A2E86; }
.b6 { fill: #0A1E73; }
.bx { fill: #EDEFF3; stroke: #B9C0CC; stroke-dasharray: 4 3; }
.hatch-bg { fill: #F4F7FB; }
.hatch-line { stroke: #DCE3EE; stroke-width: 1.6; }

.maprail { display: flex; flex-direction: column; gap: 14px; }
.readout { border: 1px solid var(--line); border-radius: 14px; background: var(--cardbg); padding: 16px; }
.readout .n { font-size: 2rem; font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.readout .w { font-size: 0.85rem; color: var(--muted); }
.readout .foot { font-size: 0.76rem; color: var(--muted); margin-top: 4px; }
/* The strip above the map is the phone's readout — rendering both put the same
   number on screen twice and pushed everything else below the fold. */
@media (max-width: 719px) { .readout { display: none; } }

/* Top-JNV list: the rail's tail and the map's ocean were dead pixels; this is
   the comparison the page exists to invite, in the space they were wasting. */
.toplist { border: 1px solid var(--line); border-radius: 14px; background: var(--cardbg); padding: 14px 16px; }
.tl-h { display: block; font-size: 0.66rem; font-weight: 800; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 10px; }
.toplist ol { list-style: none; counter-reset: tl; margin: 0; padding: 0; }
.toplist li { counter-increment: tl; display: grid; grid-template-columns: 1.1rem 1fr auto;
  gap: 4px 8px; align-items: baseline; padding: 5px 0; border-top: 1px solid var(--line); font-size: 0.86rem; }
.toplist li:first-child { border-top: 0; }
.toplist li::before { content: counter(tl); font-size: 0.72rem; font-weight: 700; color: var(--muted); }
.toplist li.on { font-weight: 700; }
.toplist li.on .tl-n { color: var(--sky-top); }
.tl-n { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tl-s { grid-column: 2; font-size: 0.72rem; color: var(--muted); margin-top: -2px; }
.tl-c { font-weight: 700; font-variant-numeric: tabular-nums; }
.lead { border: 1px solid #E7E0C9; border-radius: 14px; background: #FFFBEE; padding: 16px; }
.lead .kick { font-size: 0.66rem; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; color: #8A6D1A;
  display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.lead .kick svg { width: 14px; height: 14px; fill: #C9A227; flex: none; }
.lead b { font-size: 1.02rem; }
.lead .cnt { font-size: 1.5rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1.2; }
.lead p { font-size: 0.82rem; color: #6B5B22; }
.nudge { border: 1px solid #D6E4F7; border-radius: 14px; background: #F4F8FE; padding: 16px; }
.nudge b { display: block; margin-bottom: 4px; font-size: 0.95rem; }
.nudge p { font-size: 0.84rem; color: var(--muted); }
.legend { display: flex; flex-wrap: wrap; gap: 8px 14px; border: 1px solid var(--line); border-radius: 14px; padding: 12px 14px; background: var(--cardbg); }
.sw { display: inline-flex; align-items: center; gap: 6px; font-size: 0.74rem; color: var(--muted); white-space: nowrap; }
.sw i { width: 14px; height: 14px; border-radius: 4px; border: 1px solid rgba(20, 33, 61, 0.15); display: inline-block; flex: none; }
.sw i.hatch { background: repeating-linear-gradient(45deg, #F4F7FB, #F4F7FB 3px, #DCE3EE 3px, #DCE3EE 4px); }
.sw i.novj { background: #EDEFF3; border-style: dashed; border-color: #B9C0CC; }
.sw i.f1 { background: #FFF3D6; } .sw i.f2 { background: #FFD07A; } .sw i.f3 { background: #F47B2A; }
.sw i.f4 { background: #C8401F; } .sw i.f5 { background: #4A2E86; } .sw i.f6 { background: #0A1E73; }

details.tblfold { border: 1px solid var(--line); border-radius: 16px; background: #fff; padding: 14px 16px; margin-top: 20px; }
details.tblfold summary { cursor: pointer; font-weight: 700; list-style: none; display: flex; justify-content: space-between; align-items: center; }
details.tblfold summary::-webkit-details-marker { display: none; }
details.tblfold summary::after { content: "▾"; color: var(--muted); font-size: 0.8rem; }
details.tblfold[open] summary::after { content: "▴"; }
details.tblfold[open] summary { margin-bottom: 12px; }
.fnote { font-size: 0.76rem; color: var(--muted); margin-top: 10px; }
.attr { font-size: 0.76rem; color: var(--muted); text-align: center; margin-top: 18px; }
/* The ± pair needs a hover-capable pointer; on touch, drill-in is the zoom. */
@media (hover: none) { .zoomctl { display: none; } }

.hide { display: none !important; }
/* No-JS: the S4 failure card must not show — "our server did not answer" would
   be a false statement about a server that was never asked, over a Try-again
   button that cannot work. The <noscript> callout is the whole no-JS story.
   The map's card follows the same rule. */
html:not(.js) #growthFallback { display: none !important; }
html:not(.js) #geoFallback { display: none !important; }

/* ---------- Focus ---------- */
/* Every interactive thing must show where the keyboard is. The segmented
   controls style their own ring via input:focus-visible + label; this covers
   the buttons, links and the scrollable table region. */
a:focus-visible,
button:focus-visible,
.tw:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--aravali);
  outline-offset: 2px;
  border-radius: 4px;
}
.topbar a:focus-visible,
footer a:focus-visible,
.hero a:focus-visible { outline-color: #FFC24A; }

/* ---------- Motion ---------- */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------- Print ---------- */
/* A screenshot or printout of this page into a batch group is a real
   distribution path, so it gets designed rather than left to chance. */
@media print {
  .topbar, .ctlbar, .btn-ghost, .btn-solid, footer .links, .skip, .hovcard { display: none !important; }
  .tw { max-height: none; overflow: visible; }
  .panel, .card { break-inside: avoid; }
  body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}
