/* ============================================================================
   चारा सुरक्षा — production ops dashboard
   White professional theme: light surfaces, dark slate ink, restrained
   amber/green accents. Signal ramp stays a vegetation ramp (see tokens).
   Organisation: 1. tokens → 2. base/layout → 3. components → 4. responsive.
   ========================================================================== */

/* ------------------------------------------------------------------ tokens */
:root{
  --ink:#1F2E36;
  --ink-dim:#4E5E66;
  --ink-faint:#7E8B92;
  --bg:#F4F6F8;
  --bg-2:#FFFFFF;
  --panel:#FFFFFF;
  --panel-2:#F4F6F7;
  --line:#D8DFE3;
  --line-soft:#E7ECEF;

  /* fodder / deficit signal scale — a VEGETATION ramp: lush green → yellowing
     → yellow → parched ochre, i.e. what the land itself looks like as fodder
     runs out. Darkened for contrast on white surfaces. Mirrored in web/app.js
     COLORS (map fill + legend). */
  --ok:#35875F;         /* पुरेसा — lush */
  --mild:#7FA032;       /* सौम्य ताण — yellowing */
  --short:#C29A1C;      /* तुटवडा — yellow */
  --severe:#8A5E0E;     /* तीव्र — parched */
  --gold:#B07A14;
  --forest:#2E7D5B;
  --nodata:#D5DDE1;

  --display:"Tiro Devanagari Marathi", serif;
  --body:"Mukta", sans-serif;
}

/* ------------------------------------------------------------ theme: water
   Per-ROLE brand theme. app.js reads /api/auth/me -> `theme` ("fodder" or
   "water"), stamps html[data-theme] and mirrors --gold/--forest into COLORS
   (applyTheme) BEFORE the map or any chart exists. Only brand identity moves
   here: the two accents, the page ground, hairlines, and the hard-coded
   amber/green chrome below. The DATA never changes per theme: --ok/--mild/
   --short/--severe, the SPI extreme and THI colours, the status-line tints,
   the category chips. One taluka is one colour on every screen and in the
   farmer app, whichever licence is signed in.

   Audit of every hard-coded amber/green in this file, one verdict each:
   OVERRIDDEN (brand): .brand-mark gradient #3a7a5a/#1c4636/#12271f + border
     #3c6d55 + the ::before motif #e0a93f; .brand-ai #E0A93F/#D0922B on #241A06
     with shadow #b07a1433; .role-badge #EAF3EE/#C6DED2; .btn-deck #b07a14xx
     (hidden for this role anyway: data-view="camps"); .narr-block:nth-child(1)
     #b07a1440/#b07a140a; .ai-orb + orbPulse #b07a14xx; .ai-strip and .ai-wave
     #35875f/#b07a14 tints; body::before ambient tints #2e7d5b0d/#b07a140a;
     .scan-swath + .sat .beam tints; the sat-1 satellite body stroke and
     antenna dot #B07A14 (index.html SVG); the selected-chip tint #FBF3DE on
     .dchip.is-on and .bl-kind[aria-checked]; login .login-btn:hover #25664a
     and the login-wrap ground (both in login.html's own <style>).
   LEFT (data, warning or sun): code{color:#8A5E0E} (= --severe, a data word);
     .flag-pill #8A6410/#b07a14xx (the amber HONESTY WARNING, must stay amber
     on every product); .live-dot pulse #35875f (= --ok, "connected"); .wx-sun
     #FFE08A/#E0A93F/#D0922B (it is a sun); .bf-live, .bf-badge.paused and
     .bf-badge.done, .bf-fill, .bf-btn.start and .bf-log .warn (backfill
     console: admin only, and its
     amber-then-green bars ARE the satellite-then-model legend); .chip-* and
     .pop-v .pv-warn (SPI/THI category and warning tints); .gc-empty #E9F3EE
     (= --ok tint, "no camps needed": government only); the STATUS.* tints in
     app.js; the satellite SVG's green solar panels (they are solar panels).
   ------------------------------------------------------------------------ */
:root[data-theme="water"]{
  --gold:#1E6E9C;
  --gold-d:#164E68;     /* selected-chip numeral; fodder uses its #8A5E0E fallback */
  --forest:#1B5E7A;
  --bg:#F3F7FA;
  --panel-2:#F2F6F8;
  --line:#D3DEE6;
  --line-soft:#E3EBF0;
}
/* the identity cue: a 3px band in the accent along the top of the header */
html[data-theme="water"] .topbar{border-top:3px solid var(--gold)}
html[data-theme="water"] .brand-mark{
  background:radial-gradient(120% 120% at 30% 20%, #2C7DA8 0%, #164A66 60%, #0E2E40 100%);
  border-color:#3B6E8C}
html[data-theme="water"] .brand-mark::before{
  background:
    linear-gradient(90deg,transparent 46%,#8fd3f488 46%,#8fd3f488 54%,transparent 54%),
    radial-gradient(circle at 50% 78%, #8FD3F4 0 3px, transparent 3px)}
html[data-theme="water"] .brand-ai{color:#062637;
  background:linear-gradient(135deg,#8FD3F4,#4FB0E0);box-shadow:0 1px 6px #1e6e9c33}
html[data-theme="water"] .role-badge{color:var(--forest);background:#E6F1F8;border-color:#BFD8E8}
html[data-theme="water"] .btn-deck{background:#1e6e9c0f;border-color:#1e6e9c4d}
html[data-theme="water"] .btn-deck:hover{background:#1e6e9c1c;border-color:#1e6e9c80}
html[data-theme="water"] .narr-block:nth-child(1){border-color:#1e6e9c40;background:#1e6e9c0a}
html[data-theme="water"] body::before{
  background:
    radial-gradient(560px 320px at 12% -4%, #1b5e7a0d, transparent 70%),
    radial-gradient(680px 380px at 96% 8%, #1e6e9c0a, transparent 70%),
    repeating-linear-gradient(0deg, transparent 0 39px, #1f2e3606 39px 40px),
    repeating-linear-gradient(90deg, transparent 0 39px, #1f2e3606 39px 40px)}
html[data-theme="water"] .ai-strip{
  background:
    linear-gradient(90deg,#1b5e7a07,#1e6e9c09 50%,#1b5e7a07),
    repeating-linear-gradient(115deg, transparent 0 14px, #1b5e7a08 14px 16px)}
/* same pulse, accent-coloured halo; prefers-reduced-motion's !important
   animation:none still wins over this animation-name */
html[data-theme="water"] .ai-orb{box-shadow:0 0 0 0 #1e6e9c66;animation-name:orbPulseWater}
@keyframes orbPulseWater{0%{box-shadow:0 0 0 0 #1e6e9c55}70%{box-shadow:0 0 0 8px #1e6e9c00}100%{box-shadow:0 0 0 0 #1e6e9c00}}
html[data-theme="water"] .ai-wave{
  background:repeating-linear-gradient(90deg,#1b5e7a38 0 2px,transparent 2px 6px)}
html[data-theme="water"] .scan-swath{
  background:linear-gradient(90deg,transparent,#1b5e7a10 35%,#1e6e9c16 60%,transparent);
  border-left-color:#1b5e7a1c;border-right-color:#1e6e9c22}
html[data-theme="water"] .sat .beam{
  background:linear-gradient(180deg,#1e6e9c55,#1b5e7a1e 60%,transparent)}
/* the lead satellite's body stroke + antenna dot are the accent; CSS beats the
   SVG presentation attributes, the solar panels keep their own stroke/fill */
html[data-theme="water"] .sat-1 svg > g{stroke:#1E6E9C}
html[data-theme="water"] .sat-1 svg circle{fill:#1E6E9C}
html[data-theme="water"] .dchip.is-on,
html[data-theme="water"] .bl-kind[aria-checked="true"]{background:#E6F1F8}

/* ------------------------------------------------------------- base/layout */
*{box-sizing:border-box;margin:0;padding:0}
html,body{background:var(--bg);color:var(--ink);font-family:var(--body);
  -webkit-font-smoothing:antialiased;font-weight:400;line-height:1.5}
body{overflow-x:hidden}
code{font-family:ui-monospace,Consolas,monospace;font-size:.85em;color:#8A5E0E;
  background:var(--panel-2);border:1px solid var(--line-soft);border-radius:6px;padding:2px 7px}
.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap}
:focus-visible{outline:2px solid var(--gold);outline-offset:2px;border-radius:4px}

.dash{max-width:1360px;margin:26px auto 0;padding:0 22px 40px;
  display:grid;grid-template-columns:1.55fr 1fr;gap:20px}

/* --------------------------------------------------------------- topbar */
.topbar{position:sticky;top:0;z-index:600;background:linear-gradient(180deg,#FFFFFF,#FFFFFFee);
  border-bottom:1px solid var(--line);backdrop-filter:blur(6px)}
.topbar-inner{max-width:1360px;margin:0 auto;padding:14px 22px;display:flex;
  align-items:center;gap:14px;flex-wrap:wrap}
.brand{display:flex;align-items:center;gap:13px}
.brand-mark{width:40px;height:40px;border-radius:9px;flex:0 0 auto;
  background:radial-gradient(120% 120% at 30% 20%, #3a7a5a 0%, #1c4636 60%, #12271f 100%);
  border:1px solid #3c6d55;position:relative;overflow:hidden}
.brand-mark::before{content:"";position:absolute;inset:0;
  background:
    linear-gradient(90deg,transparent 46%,#e0a93f88 46%,#e0a93f88 54%,transparent 54%),
    radial-gradient(circle at 50% 78%, #e0a93f 0 3px, transparent 3px);
  opacity:.9}
.brand-name{font-family:var(--display);font-size:1.36rem;line-height:1;color:#16242C;letter-spacing:.3px;
  display:inline-flex;align-items:center;gap:8px}
.brand-ai{font-family:var(--body);font-size:.6rem;font-weight:800;letter-spacing:1px;color:#241A06;
  background:linear-gradient(135deg,#E0A93F,#D0922B);padding:3px 7px;border-radius:6px;
  vertical-align:middle;box-shadow:0 1px 6px #b07a1433}
/* inline product name inside body text (guide, narrative): "चारा सुरक्षा" with a
   small gold AI tag, so the brand reads as the actor without a heavy badge. */
.brand-i{font-weight:600;color:var(--ink);white-space:nowrap}
.brand-i sup{font-size:.62em;font-weight:800;color:var(--gold);vertical-align:super;
  margin-inline-start:1px;letter-spacing:.5px}
.brand-sub{font-size:.72rem;color:var(--ink-dim);margin-top:3px;letter-spacing:.2px}
.brand-sub b{color:var(--ink);font-weight:700}
/* The company wordmark is Latin, so it takes the BODY face: the display face
   is a Devanagari serif whose Latin is an afterthought and reads as a
   fallback at wordmark size. */
.brand-latin{font-family:var(--body);font-weight:800;letter-spacing:-.2px}
.login-product{font-family:var(--display);font-size:.95rem;color:var(--ink-dim);
  margin-top:2px;letter-spacing:.2px}
.topbar-spacer{flex:1 1 auto}

.live-tag{display:inline-flex;align-items:center;gap:7px;font-size:.74rem;color:var(--ink-dim);
  border:1px solid var(--line);padding:6px 11px;border-radius:20px;background:var(--panel)}
.live-dot{width:8px;height:8px;border-radius:50%;background:var(--ok);flex:0 0 auto;
  box-shadow:0 0 0 0 #35875f66;animation:pulse 2.4s infinite}
.live-dot.err{background:var(--short);animation:none}
@keyframes pulse{0%{box-shadow:0 0 0 0 #35875f55}70%{box-shadow:0 0 0 7px #35875f00}100%{box-shadow:0 0 0 0 #35875f00}}

/* honesty flag pills — amber, tooltip via title attr */
.flag-pills{display:inline-flex;gap:8px;flex-wrap:wrap}
.flag-pill{display:inline-flex;align-items:center;gap:6px;font-size:.72rem;font-weight:600;
  color:#8A6410;background:#b07a1410;border:1px solid #b07a144d;
  padding:5px 11px;border-radius:20px;cursor:help;white-space:nowrap}
.flag-pill::before{content:"⚠";font-size:.78rem;line-height:1}

.district-pick select{font-family:var(--body);font-size:.86rem;color:var(--ink);
  background:#FFFFFF;border:1px solid var(--line);border-radius:9px;
  padding:7px 10px;cursor:pointer}

/* The fodder seller's own price (#ratePick), a sibling of the district picker. */
.rate-pick{display:inline-flex;align-items:center;gap:6px;font-size:.78rem;color:var(--ink-dim);
  background:#FFFFFF;border:1px solid var(--line);border-radius:9px;padding:3px 4px 3px 10px;
  white-space:nowrap;transition:border-color .2s}
.rate-pick label{font-weight:600}
.rate-pick input{width:82px;font-family:var(--body);font-size:.86rem;color:var(--ink);
  background:var(--bg-2);border:1px solid var(--line);border-radius:7px;padding:4px 6px}
.rate-pick input.bad{border-color:#B23B2A;outline:2px solid #F2C9C2}
.rate-btn{font-family:var(--body);font-size:.76rem;font-weight:600;color:#FFFFFF;
  background:var(--forest);border:0;border-radius:7px;padding:5px 10px;cursor:pointer}
.rate-pick.saved{border-color:var(--ok)}

/* "प्रेझेंटेशन पहा" — opens the minister pitch deck (presentation.html) */
.btn-deck{display:inline-flex;align-items:center;gap:7px;font-size:.78rem;font-weight:600;
  color:var(--gold);text-decoration:none;white-space:nowrap;
  background:#b07a140f;border:1px solid #b07a144d;border-radius:20px;padding:6px 13px;
  transition:background .15s,border-color .15s}
.btn-deck:hover{background:#b07a141c;border-color:#b07a1480}
.btn-deck svg{flex:0 0 auto}
/* "Validation with NEWS" sits beside the deck button and shares its chrome;
   the small tag is the only thing that marks it as the news cross-check. */
.btn-deck-tag{font-size:.58rem;font-weight:700;letter-spacing:.6px;line-height:1;
  padding:3px 6px;border-radius:10px;background:var(--gold);color:#fff}

/* A page this session was refused (api/main.py ?denied=, app.js showDenied):
   one line in the warning tint, above the dashboard. */
.denied-note{max-width:1316px;width:calc(100% - 44px);margin:14px auto 0;padding:10px 16px;
  border:1px solid #E4C4C0;background:#F8E2DE;color:#8A2B1C;border-radius:10px;font-size:.86rem;font-weight:600}
/* Super admin only: sign-up requests waiting for approval (app.js). */
.request-note{display:block;max-width:1316px;width:calc(100% - 44px);margin:14px auto 0;padding:10px 16px;
  border:1px solid #E8D6A8;background:#FBF3DE;color:#7A4F0B;border-radius:10px;font-size:.86rem;
  font-weight:600;text-decoration:none}
.request-note:hover{background:#F7EBCB}

/* ---------------------------------------------------- empty / error hero */
.hero-empty{max-width:1360px;margin:26px auto 0;padding:0 22px}
.he-box{background:linear-gradient(180deg,var(--panel),#FBFCFD);border:1px dashed var(--line);
  border-radius:14px;padding:34px 30px;text-align:center}
.he-head{font-family:var(--display);font-size:1.5rem;color:#16242C}
.he-box p{color:var(--ink-dim);margin-top:10px;font-size:.95rem}
.he-box code{display:inline-block;margin-top:14px;padding:8px 14px}

/* ------------------------------------------------------------------ cards */
.card{background:var(--panel);
  border:1px solid var(--line);border-radius:14px;overflow:hidden;
  box-shadow:0 1px 3px rgba(18,32,42,.05),0 8px 24px rgba(18,32,42,.04)}
.card-head{padding:15px 18px;border-bottom:1px solid var(--line-soft);display:flex;
  align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap}
.card-title{font-family:var(--display);font-size:1.16rem;color:#16242C}
.card-note{font-size:.74rem;color:var(--ink-faint)}

/* -------------------------------------------------------------------- map */
.map-wrap{position:relative}
#map{height:452px;width:100%;background:#E8EDF0}
.map-note{position:absolute;left:10px;bottom:8px;z-index:5;pointer-events:none;
  font-size:.68rem;color:var(--ink-faint);background:#ffffffd9;border:1px solid var(--line-soft);
  border-radius:6px;padding:3px 8px}

/* Village-boundary toggle (water licence). Bottom-left, above the map note,
   so it never collides with the layer picker and the back button top-left. */
.map-village-toggle{position:absolute;left:10px;bottom:36px;z-index:7;display:flex;
  align-items:center;gap:7px;font-size:.78rem;font-weight:600;color:var(--ink-dim);
  background:#ffffffef;border:1px solid var(--line);border-radius:9px;
  padding:5px 10px;box-shadow:0 2px 10px rgba(18,32,42,.12);cursor:pointer;user-select:none}
.map-village-toggle input{accent-color:var(--gold);margin:0;cursor:pointer}
/* display:flex above would beat the UA's [hidden]{display:none}; say it here */
.map-village-toggle[hidden]{display:none}

/* In-map data-layer switch (deficit / THI / SPI / soil). Sits above the
   satellite FX (z-4) and the map-note (z-5); same visual family as the
   district picker in the topbar. */
.map-layer-pick{position:absolute;top:10px;left:10px;z-index:7;display:flex;
  align-items:center;gap:7px;color:var(--ink-dim);
  background:#ffffffef;border:1px solid var(--line);border-radius:9px;
  padding:5px 8px 5px 10px;box-shadow:0 2px 10px rgba(18,32,42,.12)}
.map-layer-pick svg{flex:0 0 auto;color:var(--gold)}
.map-layer-pick select{font-family:var(--body);font-size:.8rem;font-weight:600;
  color:var(--ink);background:transparent;border:none;outline:none;cursor:pointer;
  max-width:240px;padding:2px 0}

/* ---- statewide scope ----
   body[data-scope] gates district-only vs state-only DOM (applyScope in
   app.js). Hidden, never removed: the same session drills state <-> district
   repeatedly and removed nodes cannot come back. */
body[data-scope="state"] [data-scope-only="district"]{display:none !important}
body:not([data-scope="state"]) [data-scope-only="state"]{display:none !important}
.btn-scope-back{position:absolute;top:56px;left:10px;z-index:7;
  font-family:var(--body);font-size:.8rem;font-weight:600;color:var(--ink);
  background:#ffffffef;border:1px solid var(--line);border-radius:9px;
  padding:6px 11px;cursor:pointer;box-shadow:0 2px 10px rgba(18,32,42,.12)}
.btn-scope-back:hover{border-color:var(--gold);color:var(--gold-d, var(--gold))}

/* ---- statewide district analysis card ---- */
.state-card{grid-column:1/-1}
.chip-pick{padding:2px 0 16px}
/* Inside the map card, under the legend: the card's own side padding. */
.chip-pick.map-chips{padding:12px 18px 16px;border-top:1px solid var(--line-soft)}
.chip-pick-lbl{font-size:.76rem;font-weight:700;color:var(--ink-dim);
  letter-spacing:.02em;margin-bottom:9px}
.chip-pick-note{font-size:.72rem;color:var(--ink-faint);margin-top:9px}
.dchips{display:flex;flex-wrap:wrap;gap:7px}
/* One button per district that has data. The level dot repeats the map's
   ramp, so a chip and its polygon can never disagree; the name is always
   present, colour is never the only carrier. */
.dchip{display:inline-flex;align-items:center;gap:7px;font-family:var(--body);
  font-size:.8rem;font-weight:600;color:var(--ink);background:#FFFFFF;
  border:1px solid var(--line);border-radius:999px;padding:5px 12px 5px 9px;
  cursor:pointer;transition:border-color .12s,box-shadow .12s,transform .12s}
.dchip:hover{border-color:var(--gold);box-shadow:0 2px 8px rgba(18,32,42,.10);
  transform:translateY(-1px)}
.dchip:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.dchip .dchip-dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
.dchip .dchip-n{font-size:.71rem;font-weight:600;color:var(--ink-faint)}
/* Districts still downloading: shown, never hidden, so partial coverage is
   visible rather than silently absent. Not clickable. */
.dchip.is-pending{cursor:default;color:var(--ink-faint);background:#F7F9FA;
  border-style:dashed}
.dchip.is-pending:hover{border-color:var(--line);box-shadow:none;transform:none}
/* 26 districts in the standard 250px canvas makes Chart.js autoSkip half the
   category labels, and an unnamed bar is not a reading. Give the statewide
   ranking room for every district instead of truncating the list. */
.sales-canvas.tall{height:530px}
.st-table-h{font-size:.84rem;font-weight:700;color:var(--ink);margin:18px 0 4px}
/* The all-districts table is tall; keep it scrollable inside the card. */
#stateTable{max-height:460px;overflow-y:auto}
#stateTable thead th{position:sticky;top:0;background:#FFFFFF;z-index:1;
  box-shadow:inset 0 -1px 0 var(--line)}
.st-rank{color:var(--ink-faint);font-variant-numeric:tabular-nums}
.maplibregl-canvas{outline:none}
.maplibregl-ctrl-group{background:#FFFFFF !important;border:1px solid var(--line)}
.maplibregl-ctrl-group button{background:transparent !important}
.maplibregl-ctrl-group button span{filter:none}

/* MapLibre popup, light-adapted */
.maplibregl-popup{font-family:var(--body);z-index:6}  /* above .sat-layer FX */
.maplibregl-popup-content{background:#FFFFFF;color:var(--ink);border:1px solid var(--line);
  border-radius:10px;box-shadow:0 8px 24px rgba(18,32,42,.18);padding:12px 14px}
.maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip,
.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip{border-top-color:#FFFFFF}
.maplibregl-popup-anchor-top .maplibregl-popup-tip,
.maplibregl-popup-anchor-top-left .maplibregl-popup-tip,
.maplibregl-popup-anchor-top-right .maplibregl-popup-tip{border-bottom-color:#FFFFFF}
.maplibregl-popup-anchor-left .maplibregl-popup-tip{border-right-color:#FFFFFF}
.maplibregl-popup-anchor-right .maplibregl-popup-tip{border-left-color:#FFFFFF}
.pop b{font-family:var(--display);font-size:1.05rem;color:#16242C}
.pop .row{display:flex;justify-content:space-between;gap:18px;font-size:.82rem;margin-top:5px;color:var(--ink-dim)}
.pop .row span:last-child{color:#16242C;font-weight:600}

/* ------------------------------------------------- weather badges on map */
/* One per taluka, anchored on its point-on-surface. Driven by real CHIRPS
   rainfall + the composite's cloud-free pixel share (see api/routes
   _weather_class) — not decoration, so it stays legible rather than loud.
   Pure CSS/DOM: no sprite sheet, no external asset, no canvas repaint. */
.wx-marker{pointer-events:auto;cursor:help;filter:drop-shadow(0 1px 3px rgba(20,32,40,.35))}
.wx{position:relative;display:block;width:26px;height:20px}

/* sun — slow rotating rays + gentle pulse */
.wx-sun .sun-core{position:absolute;left:50%;top:50%;width:10px;height:10px;margin:-5px 0 0 -5px;
  border-radius:50%;background:radial-gradient(circle at 35% 35%,#FFE08A,#E0A93F);
  box-shadow:0 0 6px #e0a93f88;animation:wxSunPulse 3s ease-in-out infinite}
.wx-sun .sun-rays{position:absolute;left:50%;top:50%;width:22px;height:22px;margin:-11px 0 0 -11px;
  background:
    conic-gradient(from 0deg,
      #D0922Bcc 0 6deg, transparent 6deg 45deg, #D0922Bcc 45deg 51deg, transparent 51deg 90deg,
      #D0922Bcc 90deg 96deg, transparent 96deg 135deg, #D0922Bcc 135deg 141deg, transparent 141deg 180deg,
      #D0922Bcc 180deg 186deg, transparent 186deg 225deg, #D0922Bcc 225deg 231deg, transparent 231deg 270deg,
      #D0922Bcc 270deg 276deg, transparent 276deg 315deg, #D0922Bcc 315deg 321deg, transparent 321deg 360deg);
  -webkit-mask:radial-gradient(circle,transparent 6px,#000 6.5px,#000 11px,transparent 11px);
  mask:radial-gradient(circle,transparent 6px,#000 6.5px,#000 11px,transparent 11px);
  animation:wxSpin 18s linear infinite;opacity:.85}
@keyframes wxSpin{to{transform:rotate(360deg)}}
@keyframes wxSunPulse{0%,100%{transform:scale(1)}50%{transform:scale(1.12)}}

/* cloud — two puffs drifting at different speeds */
.wx .cloud{position:absolute;left:2px;top:6px;width:17px;height:8px;border-radius:5px;
  background:#9FADB4;box-shadow:0 0 5px rgba(20,32,40,.25)}
.wx .cloud::before,.wx .cloud::after{content:"";position:absolute;border-radius:50%;background:#9FADB4}
.wx .cloud::before{width:8px;height:8px;left:3px;top:-4px}
.wx .cloud::after{width:6px;height:6px;left:9px;top:-3px}
.wx-cloud .cloud{animation:wxDrift 5s ease-in-out infinite}
.wx-cloud .cloud-2{left:7px;top:2px;width:12px;height:6px;background:#7E8B92;opacity:.75;
  animation:wxDrift 7s ease-in-out infinite reverse}
.wx-cloud .cloud-2::before{width:6px;height:6px;left:2px;top:-3px;background:#7E8B92}
.wx-cloud .cloud-2::after{width:5px;height:5px;left:7px;top:-2px;background:#7E8B92}
@keyframes wxDrift{0%,100%{transform:translateX(-2px)}50%{transform:translateX(2px)}}

/* rain — cloud + three falling drops, staggered */
.wx-rain .cloud{background:#7E8B92;animation:wxDrift 6s ease-in-out infinite}
.wx-rain .cloud::before,.wx-rain .cloud::after{background:#7E8B92}
.wx-rain .drop{position:absolute;top:14px;width:2px;height:5px;border-radius:0 0 2px 2px;
  background:#4A8FD0;opacity:0;animation:wxDrop 1.1s linear infinite}
.wx-rain .d1{left:5px;animation-delay:0s}
.wx-rain .d2{left:11px;animation-delay:.36s}
.wx-rain .d3{left:17px;animation-delay:.72s}
@keyframes wxDrop{0%{transform:translateY(-3px);opacity:0}
  25%{opacity:.95}100%{transform:translateY(6px);opacity:0}}

@media(prefers-reduced-motion:reduce){
  .wx-sun .sun-rays,.wx-sun .sun-core,.wx .cloud,.wx-rain .drop{animation:none !important}
  .wx-rain .drop{opacity:.9}
}

.legend-row{display:flex;gap:16px;flex-wrap:wrap;padding:12px 18px;border-top:1px solid var(--line-soft);
  font-size:.78rem;color:var(--ink-dim)}
.legend-row .sw{display:inline-flex;align-items:center;gap:7px}
.legend-row .dot{width:11px;height:11px;border-radius:50%;flex:0 0 auto}
.legend-row .wx-key{padding-inline-start:2px}
.legend-row .wx-key .wx{transform:scale(.85);flex:0 0 auto}
.legend-row .wx-note{color:var(--ink-faint);font-size:.7rem;margin-inline-start:auto}

/* -------------------------------------------------------------------- KPI */
.kpi-wrap{padding:6px 6px 14px}
.kpi{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:10px 12px}
.kpi .box{background:var(--panel-2);border:1px solid var(--line-soft);border-radius:11px;padding:14px 14px}
.kpi .k-l{font-size:.76rem;color:var(--ink-faint);letter-spacing:.2px}
.kpi .k-v{font-family:var(--display);font-size:1.62rem;color:#16242C;margin-top:5px;line-height:1}
.kpi .k-v small{font-family:var(--body);font-size:.75rem;color:var(--ink-dim);font-weight:500;
  display:block;margin-top:5px;line-height:1.3}
/* plain-Marathi explanation under every figure */
.kpi .k-x{font-size:.72rem;color:var(--ink-faint);line-height:1.55;margin-top:9px;
  padding-top:9px;border-top:1px solid var(--line-soft)}
.kpi .k-x b{color:var(--ink-dim);font-weight:600}
.kpi .box.alert .k-v{color:#B4441F}
.kpi .box.budget .k-v{color:var(--gold)}

.status-line{margin:6px 12px 0;padding:12px 14px;border-radius:11px;font-size:.9rem;
  background:var(--panel-2);color:var(--ink-dim);
  display:flex;align-items:center;gap:10px;line-height:1.35}
.status-line .badge{font-weight:700;font-size:.72rem;padding:4px 9px;border-radius:6px;
  white-space:nowrap;color:#FFFFFF}

/* Freshness strip: deliberately quieter than .status-line — it qualifies the
   numbers, it is not itself an alert. Gold left rule ties it to the "estimated
   / not measured" language used elsewhere. */
.freshness{margin:8px 12px 0;padding:9px 13px;border-radius:9px;font-size:.83rem;
  background:var(--panel-2);color:var(--ink-dim);line-height:1.45;
  border-left:3px solid var(--gold)}
.freshness b{color:#16242C;font-weight:700}
.freshness .fr-sep{opacity:.5;padding:0 6px}

/* --------------------------------------------------------------- timeline */
.timeline-card{grid-column:1 / -1}
.tl-body{padding:16px 20px 22px}
.tl-top{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:6px}
.week-read{font-family:var(--display);font-size:1.15rem;color:#16242C}
.week-read b{color:var(--gold)}
.playbar{display:flex;align-items:center;gap:12px}
.btn-play{cursor:pointer;background:var(--panel-2);border:1px solid var(--line);color:var(--ink);
  width:40px;height:40px;border-radius:9px;font-size:1rem;display:grid;place-items:center;transition:background .15s}
.btn-play:hover{background:#EDF1F3}
.btn-play:disabled{opacity:.4;cursor:default}

input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:6px;border-radius:6px;
  background:linear-gradient(90deg,var(--ok),var(--mild) 40%,var(--short) 66%,var(--severe));
  outline:none;margin:14px 0 8px}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:22px;height:22px;
  border-radius:50%;background:#FFFFFF;border:3px solid var(--gold);cursor:pointer;
  box-shadow:0 1px 5px rgba(18,32,42,.3)}
input[type=range]::-moz-range-thumb{width:22px;height:22px;border-radius:50%;background:#FFFFFF;
  border:3px solid var(--gold);cursor:pointer}
input[type=range]:disabled{opacity:.35}
input[type=range]:disabled::-webkit-slider-thumb{cursor:default}
input[type=range]:disabled::-moz-range-thumb{cursor:default}

.tl-scale{display:flex;justify-content:space-between;font-size:.7rem;color:var(--ink-faint);margin-bottom:14px}
.chart-holder{height:240px;position:relative}

/* --------------------------------------------------------------- backfill */
/* Topbar trigger */
.btn-backfill{display:inline-flex;align-items:center;gap:7px;font-family:var(--body);
  font-size:.78rem;font-weight:600;color:var(--ok);white-space:nowrap;cursor:pointer;
  background:#35875f10;border:1px solid #35875f55;border-radius:20px;padding:6px 13px;
  transition:background .15s,border-color .15s}
.btn-backfill:hover{background:#35875f1c;border-color:#35875f99}
.btn-backfill svg{flex:0 0 auto}
.bf-live{width:7px;height:7px;border-radius:50%;background:var(--gold);flex:0 0 auto;
  box-shadow:0 0 0 0 #b07a1466;animation:pulse 1.8s infinite}

/* Scrim + panel */
.bf-scrim{position:fixed;inset:0;z-index:900;background:rgba(20,32,40,.45);backdrop-filter:blur(3px)}
.bf-panel{position:fixed;z-index:901;top:50%;left:50%;transform:translate(-50%,-50%);
  width:min(760px,calc(100vw - 32px));max-height:min(86vh,880px);display:flex;flex-direction:column;
  background:#FFFFFF;border:1px solid var(--line);
  border-radius:16px;box-shadow:0 24px 70px rgba(15,30,40,.28);overflow:hidden}
.bf-head{display:flex;align-items:flex-start;gap:12px;padding:16px 18px;
  border-bottom:1px solid var(--line-soft)}
.bf-title{font-family:var(--display);font-size:1.2rem;color:#16242C}
.bf-sub{font-size:.74rem;color:var(--ink-faint);margin-top:3px}
.bf-x{margin-inline-start:auto;cursor:pointer;background:transparent;border:1px solid var(--line);
  color:var(--ink-dim);width:30px;height:30px;border-radius:8px;flex:0 0 auto;font-size:.8rem;
  transition:background .15s}
.bf-x:hover{background:#EDF1F3;color:var(--ink)}
.bf-body{padding:16px 18px;overflow-y:auto;flex:1}

.bf-row{display:flex;gap:12px;flex-wrap:wrap}
.bf-field{flex:1 1 200px;display:flex;flex-direction:column;gap:6px}
.bf-field span{font-size:.74rem;color:var(--ink-faint);letter-spacing:.2px}
.bf-field select{font-family:var(--body);font-size:.86rem;color:var(--ink);
  background:#FFFFFF;border:1px solid var(--line);border-radius:9px;padding:9px 10px;cursor:pointer}
.bf-note{font-size:.78rem;color:var(--ink-faint);line-height:1.6;margin-top:12px;
  padding:11px 13px;background:var(--panel-2);border:1px solid var(--line-soft);border-radius:10px}

/* Sticky: during a long run the user scrolls the bars/log, and the one thing
   they must never lose sight of is whether it is still alive. */
.bf-status{position:sticky;top:0;z-index:2;display:flex;align-items:center;gap:10px;
  margin:-16px -18px 14px;padding:12px 18px;flex-wrap:wrap;
  background:linear-gradient(180deg,#FFFFFF 72%,#ffffff00);
  border-bottom:1px solid var(--line-soft)}
.bf-beat{display:inline-flex;align-items:center;gap:6px;font-size:.72rem;color:var(--ink-faint);
  font-variant-numeric:tabular-nums}
.bf-beat::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--ok);
  box-shadow:0 0 0 0 #35875f66;animation:pulse 1.6s infinite}
.bf-beat.stale::before{background:var(--mild);animation:none}
.bf-beat.off{display:none}
.bf-badge{font-size:.72rem;font-weight:700;padding:4px 10px;border-radius:6px;
  background:var(--line);color:var(--ink);white-space:nowrap}
.bf-badge.run{background:#35875f1a;color:var(--ok);border:1px solid #35875f66}
.bf-badge.paused{background:#b07a141a;color:#8A6410;border:1px solid #b07a1466}
.bf-badge.done{background:#2e7d5b26;color:var(--ok);border:1px solid #35875f66}
.bf-badge.err{background:#c0562122;color:#B4441F;border:1px solid #c0562166}
.bf-phase{font-size:.76rem;color:var(--ink-dim)}
.bf-spacer{flex:1}
.bf-total{font-size:.78rem;color:var(--ink-dim);font-variant-numeric:tabular-nums}
.bf-total b{color:var(--gold)}

/* per-district / per-year bars */
.bf-dist{margin-bottom:14px}
.bf-dist-name{font-family:var(--display);font-size:1rem;color:#16242C;margin-bottom:8px;
  display:flex;align-items:baseline;gap:9px}
.bf-dist-name small{font-family:var(--body);font-size:.72rem;color:var(--ink-faint)}
.bf-year{display:flex;align-items:center;gap:10px;margin-bottom:5px}
.bf-year-lab{font-size:.76rem;color:var(--ink-dim);width:38px;flex:0 0 auto;
  font-variant-numeric:tabular-nums}
.bf-bar{flex:1;height:14px;border-radius:7px;background:var(--panel-2);
  border:1px solid var(--line-soft);overflow:hidden;position:relative}
/* two stacked fills: satellite (amber, the slow phase) then model (green) */
.bf-fill{position:absolute;inset:0 auto 0 0;background:linear-gradient(90deg,#CE9A28,var(--gold));
  transition:width .5s ease}
.bf-fill.model{background:linear-gradient(90deg,var(--forest),var(--ok));opacity:.95}
.bf-year-num{font-size:.72rem;color:var(--ink-faint);width:52px;flex:0 0 auto;text-align:end;
  font-variant-numeric:tabular-nums}
.bf-legend{display:flex;gap:14px;font-size:.7rem;color:var(--ink-faint);margin-top:10px}
.bf-legend i{width:9px;height:9px;border-radius:2px;display:inline-block;margin-inline-end:5px}

/* live log */
.bf-log-wrap{margin-top:6px}
.bf-log-head{font-size:.72rem;color:var(--ink-faint);letter-spacing:.3px;margin-bottom:6px}
.bf-log{max-height:170px;overflow-y:auto;background:#F7F9FA;border:1px solid var(--line-soft);
  border-radius:10px;padding:9px 11px;font-size:.74rem;line-height:1.65;color:var(--ink-dim)}
.bf-log div{padding:1px 0}
.bf-log .t{color:var(--ink-faint);margin-inline-end:7px;font-variant-numeric:tabular-nums}
.bf-log .warn{color:#8A6410}
.bf-log .error{color:#B4441F}

.bf-foot{display:flex;align-items:center;gap:9px;padding:13px 18px;
  border-top:1px solid var(--line-soft);background:#fafbfccc;flex-wrap:wrap}
.bf-btn{cursor:pointer;font-family:var(--body);font-size:.82rem;font-weight:600;
  background:var(--panel-2);border:1px solid var(--line);color:var(--ink);
  border-radius:9px;padding:9px 16px;transition:background .15s}
.bf-btn:hover{background:#EDF1F3}
.bf-btn:disabled{opacity:.4;cursor:default}
.bf-btn.start{background:#2E7D5B;border-color:#35875F;color:#FFFFFF}
.bf-btn.start:hover{background:#35875F}
.bf-btn.danger{color:#B4441F;border-color:#c0562155}
.bf-btn.danger:hover{background:#c0562114}
.bf-foot-note{font-size:.7rem;color:var(--ink-faint);flex:1 1 200px;line-height:1.5}

@media(max-width:620px){
  .bf-panel{width:calc(100vw - 16px);max-height:92vh}
  .bf-row{flex-direction:column}
}

/* -------------------------------------------------------- week narrative */
/* Dynamic plain-Marathi story of the selected week. Regenerates on slider
   move (renderNarrative in app.js). Full width, roomy, easy to read aloud. */
.narrative-card{grid-column:1 / -1}
.narr-body{display:grid;grid-template-columns:1fr 1fr;gap:16px;padding:18px 20px 22px}
.narr-block{background:var(--panel-2);border:1px solid var(--line-soft);border-radius:12px;
  padding:15px 17px}
.narr-h{display:flex;align-items:center;gap:9px;font-family:var(--display);font-size:1.02rem;
  color:#16242C;margin-bottom:9px;line-height:1.3}
.narr-num{display:inline-grid;place-items:center;width:24px;height:24px;flex:0 0 auto;
  font-family:var(--body);font-size:.85rem;font-weight:700;color:#FFFFFF;background:var(--gold);
  border-radius:7px}
.narr-p{font-size:.94rem;color:var(--ink-dim);line-height:1.75}
.narr-p b{color:var(--ink);font-weight:600}
/* first two blocks (situation + demand) span full width — they're the headline */
.narr-block:nth-child(1),.narr-block:nth-child(2){grid-column:1 / -1}
.narr-block:nth-child(1){border-color:#b07a1440;background:#b07a140a}
@media(max-width:900px){.narr-body{grid-template-columns:1fr}
  .narr-block:nth-child(1),.narr-block:nth-child(2){grid-column:auto}}

/* ------------------------------------------------------------------ guide */
/* ----------------------------------------------------------------- footer */
.foot-note{max-width:1360px;margin:0 auto;padding:6px 22px 60px;
  color:var(--ink-faint);font-size:.8rem;line-height:1.7}
.foot-note b{color:var(--ink-dim)}

/* ----------------------------------------------- live-AI / satellite FX  */
/* All decorative: pointer-events none, tiny opacities, GPU-friendly
   transforms only, and fully disabled under prefers-reduced-motion. */

/* ambient backdrop: faint drifting grid + two soft tints, whisper-quiet on white */
body::before{content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
  background:
    radial-gradient(560px 320px at 12% -4%, #2e7d5b0d, transparent 70%),
    radial-gradient(680px 380px at 96% 8%, #b07a140a, transparent 70%),
    repeating-linear-gradient(0deg, transparent 0 39px, #1f2e3606 39px 40px),
    repeating-linear-gradient(90deg, transparent 0 39px, #1f2e3606 39px 40px);
  animation:gridDrift 60s linear infinite}
@keyframes gridDrift{from{background-position:0 0,0 0,0 0,0 0}
  to{background-position:60px 40px,-80px 30px,0 40px,40px 0}}

/* AI processing ticker strip */
.ai-strip{display:flex;align-items:center;gap:10px;padding:8px 18px;
  border-bottom:1px solid var(--line-soft);
  background:
    linear-gradient(90deg,#35875f07,#b07a1409 50%,#35875f07),
    repeating-linear-gradient(115deg, transparent 0 14px, #35875f08 14px 16px);
  background-size:200% 100%, 240px 100%;
  animation:aiStream 7s linear infinite}
@keyframes aiStream{from{background-position:0 0,0 0}to{background-position:-200% 0,-240px 0}}
.ai-orb{width:9px;height:9px;border-radius:50%;flex:0 0 auto;background:var(--gold);
  box-shadow:0 0 0 0 #b07a1466;animation:orbPulse 2s ease-out infinite}
@keyframes orbPulse{0%{box-shadow:0 0 0 0 #b07a1455}70%{box-shadow:0 0 0 8px #b07a1400}100%{box-shadow:0 0 0 0 #b07a1400}}
.ai-msg{font-size:.76rem;color:var(--ink-dim);letter-spacing:.2px;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;transition:opacity .45s ease}
.ai-msg.swap{opacity:0}
.ai-wave{flex:1 1 auto;height:12px;min-width:60px;border-radius:6px;opacity:.5;
  background:repeating-linear-gradient(90deg,#35875f38 0 2px,transparent 2px 6px);
  -webkit-mask:linear-gradient(90deg,transparent,#000 18%,#000 82%,transparent);
  mask:linear-gradient(90deg,transparent,#000 18%,#000 82%,transparent);
  animation:waveEq 1.6s steps(12) infinite}
@keyframes waveEq{from{background-position:0 0}to{background-position:72px 0}}

/* satellite pass overlay on the map */
.sat-layer{position:absolute;inset:0;z-index:4;overflow:hidden;pointer-events:none}
.scan-swath{position:absolute;top:0;bottom:0;width:16%;left:-20%;
  background:linear-gradient(90deg,transparent,#35875f10 35%,#b07a1416 60%,transparent);
  border-left:1px solid #35875f1c;border-right:1px solid #b07a1422;
  animation:swathPass 11s ease-in-out infinite}
@keyframes swathPass{0%{left:-20%}62%{left:104%}100%{left:104%}}
.sat{position:absolute;display:flex;flex-direction:column;align-items:center;
  filter:drop-shadow(0 1px 3px rgba(20,32,40,.25))}
.sat-1{top:9%;left:-12%;animation:satFly1 26s linear infinite}
.sat-2{top:22%;left:108%;animation:satFly2 41s linear infinite;opacity:.75}
.sat-2 svg{transform:scaleX(-1)}
@keyframes satFly1{from{left:-12%}to{left:108%}}
@keyframes satFly2{from{left:108%}to{left:-12%}}
.sat .beam{width:2px;height:64px;margin-top:-2px;border-radius:2px;
  background:linear-gradient(180deg,#b07a1455,#35875f1e 60%,transparent);
  animation:beamPulse 1.8s ease-in-out infinite}
.sat-2 .beam{height:44px;animation-duration:2.6s}
@keyframes beamPulse{0%,100%{opacity:.25}50%{opacity:.85}}

@media(max-width:900px){
  .sat .beam{height:40px}
  .ai-msg{white-space:normal}
}

/* ------------------------------------------------------------- responsive */
@media(max-width:900px){
  .dash{grid-template-columns:1fr}
  #map{height:360px}
  .topbar-inner{gap:10px}
  .kpi{grid-template-columns:1fr 1fr}
}
@media(max-width:520px){
  .kpi{grid-template-columns:1fr}
}
@media(prefers-reduced-motion:reduce){
  *{animation:none !important;transition:none !important}
  html{scroll-behavior:auto}
}

/* ------------------------------------------------- role badge + logout */
/* Added with the subscription/login layer: the header must make it obvious
   which subscription is open, because the same URL renders three different
   products depending on who signed in. */
.role-badge{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;
  border-radius:999px;font-size:.76rem;font-weight:600;color:var(--forest);
  background:#EAF3EE;border:1px solid #C6DED2;white-space:nowrap}
.btn-logout{padding:5px 11px;border-radius:8px;font-family:var(--body);
  font-size:.76rem;font-weight:600;color:var(--ink-dim);background:var(--panel);
  border:1px solid var(--line);cursor:pointer}
.btn-logout:hover{background:var(--panel-2);color:var(--ink)}

/* --------------------------------------------- layer cards (new datasets) */
/* Heat, drought, soil-carbon and procurement panels. They span the full grid
   width because each is a per-taluka table, not a sidebar statistic. */
.layer-card{grid-column:1 / -1}
.layer-body{padding:4px 18px 18px}
.layer-table{width:100%;border-collapse:collapse;font-size:.86rem}
.layer-table th{text-align:left;font-weight:600;color:var(--ink-dim);
  font-size:.76rem;padding:7px 10px;border-bottom:1px solid var(--line)}
.layer-table td{padding:8px 10px;border-bottom:1px solid var(--line-soft)}
.layer-table tr:last-child td{border-bottom:0}
.layer-table td.num{text-align:right;font-variant-numeric:tabular-nums}
.layer-empty{padding:16px 4px;color:var(--ink-faint);font-size:.86rem}
/* good-news empty state inside a chart holder (e.g. no camps needed this week).
   min-height matters for the non-canvas holder (the forecast grid cell), whose
   height is content-driven. */
.gc-empty{height:100%;min-height:140px;display:flex;align-items:center;justify-content:center;
  text-align:center;color:var(--ink-dim);font-size:.84rem;
  background:#E9F3EE;border:1px dashed #35875F55;border-radius:10px;padding:14px}
.layer-note{margin-top:12px;padding-top:11px;border-top:1px solid var(--line-soft);
  font-size:.74rem;color:var(--ink-faint);line-height:1.65}
.chip{display:inline-block;padding:2px 9px;border-radius:999px;
  font-size:.72rem;font-weight:600;white-space:nowrap}
.chip-none{background:#EAF3EE;color:#2A6B4C}
.chip-mild{background:#F4F5E6;color:#5F7220}
.chip-moderate{background:#FBF3DE;color:#8A6A12}
.chip-severe{background:#F6EADA;color:#7A4F0B}
.chip-extreme{background:#F7E3DC;color:#8A2B1C}

/* ------------------------------------------------- vendor sales dashboards */
/* Four small multiples in a 2x2. Fixed-height canvases because Chart.js with
   maintainAspectRatio:false will otherwise grow without bound inside a grid
   cell that has no intrinsic height. */
.sales-card{grid-column:1 / -1}
.sales-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px;padding:6px 18px 18px}
.sales-cell{min-width:0}
.sales-h{font-size:.88rem;font-weight:600;color:var(--ink)}
.sales-sub{font-size:.74rem;color:var(--ink-faint);margin:2px 0 9px}
.sales-canvas{position:relative;height:250px}
.sales-canvas canvas{max-width:100%}

/* Priority call list (sales cell 4). An HTML list, not a chart: the rank and
   the printed rule ARE the visualization. Bar = ₹ potential relative to the
   top taluka; the level dot carries severity so the bar can stay one hue. */
.pri-list{display:flex;flex-direction:column;gap:6px;min-height:200px}
.pri-row{display:flex;align-items:center;gap:8px;padding:5px 8px;
  background:var(--panel-2,#F6F8F9);border:1px solid var(--line);border-radius:8px}
.pri-rank{flex:0 0 20px;text-align:center;font-weight:700;font-size:.82rem;
  color:var(--ink-dim)}
.pri-dot{flex:0 0 10px;width:10px;height:10px;border-radius:50%}
.pri-main{flex:1 1 auto;min-width:0}
.pri-name{font-size:.82rem;font-weight:600;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pri-dir{font-size:.68rem;font-weight:600;margin-inline-start:4px}
.pri-dir.up{color:var(--short)}
.pri-dir.flat{color:var(--ink-faint)}
.pri-dir.down{color:var(--ok)}
.pri-bar{height:5px;background:var(--grid,#E5EAED);border-radius:3px;
  margin-top:3px;overflow:hidden}
.pri-bar i{display:block;height:100%;background:var(--gold,#B07A14);
  border-radius:3px}
.pri-nums{flex:0 0 auto;text-align:right}
.pri-nums b{display:block;font-size:.8rem;color:var(--ink)}
.pri-nums small{display:block;font-size:.66rem;color:var(--ink-faint)}
.pri-rest,.pri-empty{font-size:.72rem;color:var(--ink-faint);padding:3px 8px}

/* ---------------------------------------- water scarcity panels (water licence) */
/* The ranked list reuses .pri-row; these are the parts it adds: the line of
   components under each name, and the index on the right with its 6-week
   direction beneath. */
.pri-sub{font-size:.68rem;color:var(--ink-faint);margin-top:2px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pri-val{flex:0 0 auto;text-align:right;font-size:.95rem;font-weight:700;color:var(--ink)}
.pri-val-sub{font-size:.66rem;font-weight:600;margin-top:1px}
.ws-up{color:var(--severe)}
.ws-down{color:var(--ok)}
.ws-flat{color:var(--ink-faint)}
/* The tanker cell reads as one headline number, or as an honest refusal. */
.ws-big{font-size:2.1rem;font-weight:700;color:var(--ink);line-height:1.1;margin-top:6px}
.ws-big span{font-size:.9rem;font-weight:600;color:var(--ink-dim)}
.ws-big-none{font-size:1.15rem;color:var(--ink-dim)}
.ws-big-sub{font-size:.74rem;color:var(--ink-faint);margin-top:3px;line-height:1.45}
.ws-actual{font-size:.78rem;color:var(--ink);margin-top:9px}
.ws-actual b{font-weight:700}

@media (max-width:1000px){
  .sales-grid{grid-template-columns:1fr}
  .sales-canvas{height:230px}
}

/* ------------------------------------------- vendor hover + durability table */
/* "No answer" chip. Deliberately NOT .chip-none: that one is green, and
   drought_category() returns 'none' for NaN by construction, so reusing it
   would paint "SPI could not be computed" as "no drought" — the exact
   misreading the honesty rules forbid. Grey + dashed reads as absent data. */
.chip-noanswer{background:#F1F4F5;color:#6B7A82;border:1px dashed #BFCBD1}

/* Small colour dot carrying the map's level ramp into a table row. Always
   paired with a title= word — colour is never the only carrier. */
.lvl-dot{display:inline-block;width:8px;height:8px;border-radius:50%;
  margin-right:7px;vertical-align:middle;flex:0 0 auto}
.cell-sub{display:block;font-size:.72rem;color:var(--ink-faint);margin-top:2px;
  font-weight:400}
.mech{display:block;font-size:.71rem;color:var(--ink-faint);margin-top:3px}
.next-step{font-weight:600;color:var(--ink)}
.muted-cell{color:var(--ink-faint)}

/* Vendor map popup. Wider than the officer popup because the headline number
   is the point and must not wrap. */
.pop-v{min-width:236px}
.pop-v .pv-head{display:flex;align-items:center;justify-content:space-between;
  gap:10px;margin-bottom:7px}
.pop-v .pv-name{font-family:var(--display);font-size:1rem;color:#16242C}
.pop-v .pv-big{font-size:1.42rem;font-weight:700;color:#16242C;line-height:1.15;
  font-variant-numeric:tabular-nums}
.pop-v .pv-sub{font-size:.72rem;color:var(--ink-faint);margin-top:1px}
.pop-v .pv-line{font-size:.78rem;color:var(--ink-dim);margin-top:7px;line-height:1.45}
.pop-v .pv-line b{color:#16242C}
.pop-v .pv-lbl{color:var(--ink-faint)}
.pop-v .pv-warn{margin-top:8px;padding:5px 8px;border-radius:6px;font-size:.73rem;
  background:#F7F0DC;color:#7A4F0B}
.pop-v .pv-act{margin-top:9px;padding-top:8px;border-top:1px solid var(--line-soft);
  font-size:.8rem;font-weight:600;color:#16242C}
.pop-v .pv-foot{margin-top:7px;font-size:.68rem;color:var(--ink-faint);line-height:1.4}
/* Neutral/informational chip. Used for "no rainfall deficit recorded", which
   is a fact about the RAIN, not about the opportunity — the row next to it may
   still be a 19,000-tonne gap. Green would make a scanning rep skip it. */
.chip-info{background:#EDF1F4;color:#41525C;border:1px solid #D3DCE2}

/* ------------------------------------------------- forecast heatmap (chart 6) */
/* Taluka x future week. A DOM grid rather than a canvas chart: no matrix
   plugin is vendored, and this keeps Devanagari taluka names properly shaped
   (matplotlib/canvas text has no complex-script shaping). */
.heatmap{display:flex;flex-direction:column;gap:2px;font-size:.7rem}
.hm-row{display:flex;gap:2px;align-items:stretch}
.hm-name{flex:0 0 78px;display:flex;align-items:center;color:var(--ink-dim);
  font-size:.72rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.hm-cell{flex:1 1 0;min-width:0;height:20px;border-radius:2px;color:#FFFFFF;
  display:flex;align-items:center;justify-content:center;font-size:.66rem;
  font-weight:600;font-variant-numeric:tabular-nums}
.hm-head .hm-cell{background:transparent!important;color:var(--ink-faint);
  height:auto;font-weight:400;line-height:1.15;padding-bottom:3px}


/* ==================================================== dairy panels (chart 3)
   Species heat-threshold ladder. A DOM chart for the same reason the forecast
   heatmap is one: the row labels are Devanagari species names, and canvas
   fillText has no complex-script shaping. The bands are the SAME status ramp
   the map uses, so a colour here never says anything the map does not. */
.dcl{display:flex;flex-direction:column;gap:9px;padding:2px 0 0}
.dcl-row{display:flex;align-items:center;gap:9px}
.dcl-name{flex:0 0 86px;font-size:.76rem;font-weight:600;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dcl-track{position:relative;flex:1 1 auto;min-width:0;height:22px;
  border-radius:4px;overflow:hidden;background:var(--nodata)}
.dcl-zone{position:absolute;top:0;bottom:0}
/* The observed THI window sits ON the bands rather than beside them: the
   whole point of the row is where this week lands inside that species' own
   ladder, and a marker in a separate lane makes the reader do the join. */
.dcl-mark{position:absolute;top:-2px;bottom:-2px;border-inline:2px solid #16242C;
  background:rgba(22,36,44,.14)}
.dcl-mark::after{content:"";position:absolute;left:0;right:0;top:50%;
  height:2px;background:#16242C;transform:translateY(-50%)}
.dcl-val{flex:0 0 auto;min-width:74px;text-align:right;font-size:.7rem;
  color:var(--ink-dim);font-variant-numeric:tabular-nums}
.dcl-val b{display:block;font-size:.76rem;color:var(--ink)}
.dcl-axis{display:flex;justify-content:space-between;margin-inline-start:95px;
  font-size:.64rem;color:var(--ink-faint);font-variant-numeric:tabular-nums}
.dcl-key{display:flex;flex-wrap:wrap;gap:4px 12px;margin-top:8px;
  font-size:.66rem;color:var(--ink-faint)}
.dcl-key span{display:inline-flex;align-items:center;gap:5px}
.dcl-key i{width:10px;height:10px;border-radius:2px;display:inline-block}

/* ==================================================== collection bulletin */
.bulletin-card{grid-column:1 / -1}
.bl-body{padding:4px 18px 18px}
.bl-controls{display:grid;grid-template-columns:auto minmax(0,1fr);gap:14px 22px;
  align-items:start;padding-bottom:14px;border-bottom:1px solid var(--line-soft)}
.bl-ctl-l{font-size:.76rem;font-weight:700;color:var(--ink-dim);
  letter-spacing:.02em;margin-bottom:7px}
.bl-ctl-note{font-size:.72rem;color:var(--ink-faint);margin-top:8px}
.bl-kinds{display:flex;flex-wrap:wrap;gap:7px}
.bl-kind{font-family:var(--body);font-size:.8rem;font-weight:600;color:var(--ink-dim);
  background:#FFFFFF;border:1px solid var(--line);border-radius:999px;
  padding:5px 13px;cursor:pointer;transition:border-color .12s,color .12s}
.bl-kind:hover{border-color:var(--gold)}
.bl-kind[aria-checked="true"]{color:#16242C;border-color:var(--gold);
  background:#FBF3DE;box-shadow:inset 0 0 0 1px var(--gold)}
/* Selected taluka chip. .dchip already carries the shape; this only adds the
   selected state, so the picker in the bulletin and the district picker in the
   state card stay visibly the same control. */
.dchip.is-on{border-color:var(--gold);background:#FBF3DE;
  box-shadow:inset 0 0 0 1px var(--gold)}
.dchip.is-on .dchip-n{color:var(--gold-d,#8A5E0E)}

.bl-out{padding-top:14px}
.bl-out-head{display:flex;align-items:baseline;justify-content:space-between;
  gap:12px;flex-wrap:wrap;margin-bottom:8px}
.bl-out-l{font-size:.76rem;font-weight:700;color:var(--ink-dim)}
.bl-acts{display:flex;flex-wrap:wrap;gap:7px}
.bl-btn{font-family:var(--body);font-size:.78rem;font-weight:600;color:#16242C;
  background:#FFFFFF;border:1px solid var(--line);border-radius:8px;
  padding:6px 13px;cursor:pointer;transition:border-color .12s,box-shadow .12s}
.bl-btn:hover{border-color:var(--gold);box-shadow:0 2px 8px rgba(18,32,42,.10)}
.bl-btn:disabled{color:var(--ink-faint);border-color:var(--line-soft);
  cursor:default;box-shadow:none}
/* Monospace-ish tabular text on purpose: this is a message that will be read
   in WhatsApp and pinned to a wall, so what is on screen must be exactly what
   comes out. No rich text, no markup. */
.bl-text{width:100%;font-family:var(--body);font-size:.84rem;line-height:1.7;
  color:var(--ink);background:var(--panel-2);border:1px solid var(--line);
  border-radius:10px;padding:13px 15px;resize:vertical;white-space:pre-wrap}
.bl-text:focus-visible{outline:2px solid var(--gold);outline-offset:1px}
.bl-msg{min-height:18px;margin-top:7px;font-size:.74rem;color:var(--forest)}
/* Print mirror of the textarea. Hidden on screen, shown by the print block
   at the end of this file; kept in sync by the same writer that fills the
   textarea, so the two can never disagree. */
.bl-print{display:none}
.bl-msg.warn{color:var(--severe)}

@media (max-width:1000px){
  .bl-controls{grid-template-columns:1fr}
}

/* ------------------------------------------------------------------ print
   The FIRST print rule in the dashboard, and deliberately narrow: a union
   prints the bulletin to pin on a centre wall, and printing the map, the week
   slider and every layer table instead would waste a page and bury the text.
   Backgrounds are off by default in a browser print dialog, so the bulletin
   is set to read correctly in pure ink: dark text, real borders, no colour
   carrying meaning on its own. */
@media print{
  body{background:#FFFFFF}
  /* :not() rather than a later override, because a plain `.bulletin-card`
     rule loses the specificity fight with `.dash > .card` even when both
     carry !important. */
  .topbar,.foot-note,.hero-empty,.dash > .card:not(.bulletin-card){display:none !important}
  .dash{display:block;max-width:none;margin:0;padding:0}
  .bulletin-card{grid-column:auto;border:0;box-shadow:none;border-radius:0}
  .card-head{border-bottom:1px solid #999999;padding-inline:0}
  .bl-controls,.bl-acts,.bl-msg,.bl-text{display:none !important}
  .bl-out-head{margin-bottom:6px}
  .bl-body{padding:0}
  /* A textarea prints only the scrolled-into-view slice in most browsers, so
     print takes the <pre> mirror instead. Same string, no reflow surprise. */
  .bl-print{display:block;font-family:var(--body);font-size:11pt;line-height:1.6;
    color:#000000;white-space:pre-wrap;word-break:break-word}
  .layer-note{color:#333333;border-top:1px solid #999999}
}
