/* ══════════════════════════════════════════════════════════════════════════
   TGI-VIZ — data-visualisation layer for The Gathering Inn platform
   No build step. Paste-ready. Light-surface only (the platform has no dark mode).

   EVERY value below was produced by the dataviz validator, not by eye:
     node scripts/validate_palette.js "#034D9D,#DB560D,#139DC7,#CD8211,#7B0795,#14A85F" \
          --mode light --surface "#FFFFFF"
   → ALL CHECKS PASS. Worst adjacent CVD ΔE 21.2 (protan) · normal-vision ΔE 25.9.
   Do not substitute a raw brand hex into a series slot; see the note on navy.
   ══════════════════════════════════════════════════════════════════════════ */

.viz {
  /* ── Categorical series slots — assigned in fixed order, NEVER cycled ──────
     Each is the TGI brand hue (OKLCH hue angle held exactly) re-stepped in
     lightness so it lands inside the L 0.43–0.77 band, clears the C ≥ 0.10
     chroma floor, and clears 3:1 against the card surface.
     drift from brand: orange 1.7 · green 1.2 · cyan 3.7 · gold 5.5 · plum 6.0 · navy 7.2 (ΔE OKLab×100) */
  --s1: #034D9D;  /* navy   H 256.3  L .430 C .145   8.02:1 on #FDFCFA · 8.19:1 on #FFF */
  --s2: #DB560D;  /* orange H  42.6  L .620 C .181   3.83:1 · 3.93:1 */
  --s3: #139DC7;  /* cyan   H 226.0  L .649 C .121   3.07:1 · 3.15:1 */
  --s4: #CD8211;  /* gold   H  68.5  L .670 C .143   3.01:1 · 3.09:1 */
  --s5: #7B0795;  /* plum   H 318.3  L .430 C .204   8.88:1 · 9.11:1 */
  --s6: #14A85F;  /* green  H 154.0  L .644 C .158   3.01:1 · 3.09:1 */
  /* SERIES CAP: 3 in any all-pairs form (scatter, bubble, choropleth, small
     multiples) — slots 1-3 pass --pairs all (worst CVD ΔE 22.6, normal 23.1).
     Slot 4 puts gold beside orange: all-pairs CVD ΔE 4.6 → FAIL. Fold to
     "Other" or facet. 6 is the hard ceiling in adjacent forms (stack/bar/line). */

  /* ── Sequential ramp · cyan (magnitude: occupancy heat, choropleth) ────────
     One hue, light→dark, monotone L. 100 recedes to the surface by design. */
  --seq-100: #CEEEFC; --seq-200: #7BD3F7; --seq-300: #3FB3DD; --seq-400: #3191B3;
  --seq-500: #24708B; --seq-600: #165065; --seq-700: #093341;

  /* ── Ordinal ramp · cyan, 4 steps (HUD SPM 2 return buckets) ───────────────
     validate_palette.js --ordinal → PASS: monotone L, every ΔL = .10 (≥.06),
     light end 2.41:1 on #FFF (≥2.0). Ordinal, NOT categorical: swapping
     0-180 / 181-365 / 366-730 would change the meaning, so the order must be
     visible IN the colour. */
  --ord-1: #2DB4E1;  /* 0–180 days   L .72  2.41:1 */
  --ord-2: #2293B8;  /* 181–365      L .62  3.53:1 */
  --ord-3: #187391;  /* 366–730      L .52  5.38:1 */
  --ord-4: #0E556C;  /* did not return L .42 8.29:1 */

  /* ── Diverging · orange ↔ cyan (warm/cool poles, neutral gray midpoint) ────
     For deviation from a baseline: occupancy vs 100%, burn vs pace. */
  --div-warm-3: #8F3A12; --div-warm-2: #BA4F1C; --div-warm-1: #E86426;
  --div-mid:    #E0E6E8;                                  /* TGI mist — NEUTRAL, never a hue */
  --div-cool-1: #2CA0C7; --div-cool-2: #2180A0; --div-cool-3: #16617A;

  /* ── Status — RESERVED. Never reused as "series 7". Always icon + label. ───
     The shipped TGI status tokens fail WCAG text contrast; these are the same
     four hues re-stepped so a status word is legible as TEXT, not only as a fill. */
  --st-good:     #12894F;  /* 4.34:1 on #FDFCFA · 4.45:1 on #FFF  (was #2EA864 = 2.97:1) */
  --st-warning:  #9B6109;  /* 4.99:1 · 5.11:1                     (was #E19225 = 2.46:1) */
  --st-serious:  #B4501B;  /* 4.99:1 · 5.12:1                     (was #D55E27 = 3.75:1) */
  --st-critical: #B4241B;  /* 6.38:1 · 6.54:1                     (was #C0392B = 5.30:1) */
  /* The ORIGINAL brand tokens stay legal as large FILLS (≥3:1 needed, not 4.5):
     #2EA864 fill 3.04:1 on #FFF · #D55E27 fill 3.85:1. Use brand for the fill,
     these for the word beside it. */

  /* ── Ink & chrome ─────────────────────────────────────────────────────────
     Text NEVER wears a series colour. A colored mark beside the text carries identity. */
  --viz-surface:  #FFFFFF;   /* card — the surface every ratio above was measured against */
  --viz-plane:    #FDFCFA;   /* page */
  --viz-ink:      #271107;   /* 17.51:1 — primary */
  --viz-ink-2:    #4A5862;   /*  7.15:1 — secondary */
  --viz-muted:    #68767F;   /*  4.68:1 on #FFF · 4.57:1 on #FDFCFA — axis / label TEXT.
                                TGI's --tgi-stone #7A8A94 is 3.57:1: legal as a MARK (≥3:1),
                                illegal as small text (needs 4.5:1). Two tokens, on purpose —
                                the shipped build uses #7A8A94 for both. */
  --viz-grid:     #E0E6E8;   /* hairline, solid, recessive — not data, exempt from 3:1 */
  --viz-baseline: #C3CDD2;
  --viz-deemph:   #7A8A94;   /* 3.48:1 — sparkline context stroke (a mark, not text) */
  --viz-gap:      2px;       /* the surface gap. white does the separating, never a border. */
}

/* ── Numerals ──────────────────────────────────────────────────────────────
   tabular-nums ONLY where digits must align vertically. A big standalone
   number takes proportional figures — tabular makes "121" look loose at 30px. */
.viz-hero, .viz-stat-v, .viz-val-lg { font-variant-numeric: proportional-nums lining-nums; }
.viz table td, .viz table th,
.viz-axis text, .viz-tick, .viz-col-num { font-variant-numeric: tabular-nums lining-nums; }

/* The hero figure stays in the UI sans. Cabin's fallback chain is
   Georgia,serif — one failed webfont and every KPI renders in a serif.
   The viz layer pins the sans so that can never happen. */
.viz-hero { font-family: 'Roboto', system-ui, -apple-system, 'Segoe UI', sans-serif;
            font-size: clamp(40px, 6vw, 56px); font-weight: 700; line-height: 1.02;
            color: var(--viz-ink); letter-spacing: -.015em; }

/* ── Card, header, provenance ─────────────────────────────────────────────── */
.viz-card { background: var(--viz-surface); border: 1px solid var(--tgi-mist, #E0E6E8);
            border-radius: 20px; padding: 20px 22px 18px; }
.viz-hd { display: flex; align-items: baseline; justify-content: space-between;
          gap: 12px; flex-wrap: wrap; margin-bottom: 4px; }
.viz-hd h3 { font-family: 'Cabin', system-ui, sans-serif; font-size: 17px; font-weight: 600;
             color: var(--tgi-navy, #1A4174); margin: 0; }
.viz-sub { font-size: 13px; color: var(--viz-ink-2); line-height: 1.5; margin: 0 0 14px; }

/* Provenance: one superscript key on the figure, one source line in the footer.
   The chart itself stays uncluttered; a funder can still trace every number. */
.viz-src-key { font-size: 10px; font-weight: 700; color: var(--viz-muted);
               vertical-align: super; margin-left: 2px; cursor: help;
               text-decoration: underline dotted var(--viz-baseline); text-underline-offset: 2px; }
.viz-prov { margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--viz-grid);
            font-size: 11.5px; line-height: 1.65; color: var(--viz-muted); }
.viz-prov b { color: var(--viz-ink-2); font-weight: 600; }
.viz-prov li { list-style: none; display: flex; gap: 7px; }
.viz-prov li::before { content: attr(data-key); font-weight: 700; color: var(--viz-ink-2);
                       flex: 0 0 auto; min-width: 12px; }
.viz-asof { font-family: 'Roboto Condensed', system-ui, sans-serif; font-size: 10px;
            letter-spacing: .16em; text-transform: uppercase; color: var(--viz-muted); }

/* ── Legend — always present at ≥2 series. Mirrors the mark. ───────────────── */
.viz-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 0 0 12px; padding: 0; }
.viz-legend li { display: flex; align-items: center; gap: 7px; font-size: 12.5px;
                 color: var(--viz-ink-2); list-style: none; }
.viz-key { width: 12px; height: 12px; border-radius: 3px; flex: 0 0 auto; }   /* bars / areas */
.viz-key.line { width: 16px; height: 2px; border-radius: 2px; }                /* lines */

/* ── THE THREE ABSENCES. They must never look alike. ───────────────────────── */

/* 1 · UNAVAILABLE — cannot be computed from the data we hold.
      The frame is drawn so the reader sees the SHAPE of the answer that would
      appear. Inert hatch, never a spinner, never a dash, never 0. */
.viz-unavail { position: relative; border: 1px dashed var(--viz-baseline); border-radius: 14px;
               background-image: repeating-linear-gradient(135deg,
                 rgba(122,138,148,.10) 0 6px, transparent 6px 12px);
               padding: 18px; }
.viz-unavail-tag { display: inline-flex; align-items: center; gap: 6px;
  font-family: 'Roboto Condensed', system-ui, sans-serif; font-size: 10px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--st-warning);
  background: #FFF; border: 1px solid rgba(155,97,9,.35); border-radius: 99px; padding: 4px 10px; }
.viz-unavail-why { font-size: 13.5px; color: var(--viz-ink-2); line-height: 1.6; margin: 10px 0 0; }
.viz-unavail-ask { font-size: 13px; color: var(--viz-ink); line-height: 1.6; margin: 10px 0 0;
  padding: 10px 12px; background: var(--viz-plane); border-left: 3px solid var(--st-warning);
  border-radius: 0 8px 8px 0; }

/* 2 · WITHHELD — the number exists and is deliberately not shown (safety / DV /
      small cell). Different word, different mark, no hatch: nothing is missing. */
.viz-withheld { display: inline-flex; align-items: center; gap: 7px; font-size: 13px;
  color: var(--viz-ink-2); background: var(--viz-plane); border: 1px solid var(--viz-grid);
  border-radius: 10px; padding: 9px 12px; }
.viz-withheld b { color: var(--viz-ink); font-weight: 600; }

/* 3 · MEASURED ZERO — a real result. Solid, full-contrast, the digit is printed. */
.viz-zero { color: var(--viz-ink); font-weight: 600; }
.viz-zero-mark { display: inline-block; width: 3px; height: 14px; border-radius: 2px;
                 background: var(--viz-baseline); vertical-align: -2px; margin-right: 7px; }

/* ── Bullet meter (contract burn) — a ratio against a limit, with a pace tick ── */
.viz-bullet { display: grid; grid-template-columns: minmax(120px, 1.4fr) 1fr auto;
              gap: 10px 14px; align-items: center; padding: 9px 0; min-height: 44px; }
.viz-bullet + .viz-bullet { border-top: 1px solid var(--viz-grid); }
.viz-bullet-l { font-size: 13.5px; color: var(--viz-ink); line-height: 1.35; }
.viz-bullet-l small { display: block; font-size: 11.5px; color: var(--viz-muted); }
.viz-bullet-v { font-size: 13.5px; font-weight: 600; color: var(--viz-ink);
                font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
@media (max-width: 560px) { .viz-bullet { grid-template-columns: 1fr auto; }
                            .viz-bullet svg { grid-column: 1 / -1; } }

/* ── Table — the WCAG-clean twin every chart must have ────────────────────── */
.viz-tablewrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.viz table { width: 100%; border-collapse: collapse; font-size: 13px; }
.viz table th { text-align: left; font-family: 'Roboto Condensed', system-ui, sans-serif;
  font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase; color: var(--viz-muted);
  font-weight: 700; padding: 8px 12px 8px 0; border-bottom: 1px solid var(--viz-baseline); }
.viz table td { padding: 11px 12px 11px 0; border-bottom: 1px solid var(--viz-grid);
                color: var(--viz-ink); vertical-align: middle; }
.viz table td.num, .viz table th.num { text-align: right; }
.viz-toggle { font-family: 'Roboto Condensed', system-ui, sans-serif; font-size: 11px;
  letter-spacing: .1em; text-transform: uppercase; font-weight: 700; color: var(--tgi-navy, #1A4174);
  background: none; border: 1px solid var(--tgi-mist, #E0E6E8); border-radius: 99px;
  padding: 8px 14px; min-height: 44px; cursor: pointer; }
.viz-toggle[aria-pressed="true"] { background: var(--tgi-navy, #1A4174); color: #FFF;
                                    border-color: var(--tgi-navy, #1A4174); }

/* ── Tooltip ──────────────────────────────────────────────────────────────── */
.viz-tip { position: fixed; z-index: 9999; pointer-events: none; opacity: 0;
  transform: translate(-50%, -100%) translateY(-10px);
  background: var(--tgi-navy-dk, #0D2747); color: #FFF; border-radius: 10px;
  padding: 9px 12px; font-size: 12.5px; line-height: 1.45; max-width: 260px;
  box-shadow: 0 10px 30px rgba(13,39,71,.28); transition: opacity .12s ease; }
.viz-tip[data-on] { opacity: 1; }
.viz-tip b { font-size: 15px; font-weight: 700; display: block;
             font-variant-numeric: tabular-nums; }          /* value leads, label follows */
.viz-tip i { font-style: normal; color: rgba(255,255,255,.78); }
.viz-tip .k { display: inline-block; width: 14px; height: 2px; border-radius: 2px;
              vertical-align: 3px; margin-right: 6px; }      /* line key, not a box */

/* ── Hit targets: the area is always bigger than the mark ─────────────────── */
.viz-hit { fill: transparent; cursor: pointer; }
.viz .mark:focus-visible, .viz-hit:focus-visible { outline: 2px solid var(--tgi-navy, #1A4174);
                                                    outline-offset: 2px; }
.viz .mark { transition: opacity .12s ease; }
.viz-plot:hover .mark:not(:hover) { opacity: .55; }

/* ── Reduced motion: a genuinely good static experience, not a disabled one ──
   Motion is removed; the FINAL state is what paints, immediately and in full. */
@media (prefers-reduced-motion: reduce) {
  .viz *, .viz *::before, .viz *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; }
  .viz-plot:hover .mark:not(:hover) { opacity: 1; }         /* dimming is motion-adjacent noise */
  .viz .mark { stroke-width: 2.5px; }                        /* identity leans harder on form */
}

/* ── Forced colours / print: hue is gone, so texture and labels must carry ── */
@media (forced-colors: active) {
  .viz .mark { forced-color-adjust: none; }
  .viz-unavail { border-style: dashed; background-image: none; }
}
@media print {
  .viz-card { border: 1px solid #999; break-inside: avoid; }
  .viz-tip { display: none; }
  .viz-prov { color: #000; }
}


/* ── Wrappers that shipped without a rule (class audit, 2026-07-28) ───────── */
.viz-ledger{background:var(--tgi-white);border:1px solid var(--tgi-mist);border-radius:var(--r);padding:17px 19px}
.viz-twin{background:var(--tgi-white);border:1px solid var(--tgi-mist);border-radius:var(--r);padding:17px 19px}
.viz-spark{display:inline-block;vertical-align:middle;line-height:0}
