/* web/assets/ops.css — the operations analytics surface.
 *
 * FONTS ARE SELF-HOSTED, NOT FETCHED.
 * The dashboard's Content-Security-Policy is style-src 'self' 'unsafe-inline'
 * https://unpkg.com, so a Google Fonts stylesheet is blocked outright — and
 * correctly so. Widening the policy to admit a font CDN would trade a real
 * security boundary for two typefaces, on a system holding farmer personal data
 * under RA 10173. Loosening CSP is the wrong direction.
 *
 * Self-hosting also fixes the offline case that the CDN never handled: a branch
 * office on a filtered line got fallback faces and a console error. These six
 * woff2 files are 148 KB total, cached after the first load, and served from the
 * same origin that already serves Leaflet for the same reason. fontSrc is
 * already 'self', so no policy change was needed.
 *
 * Files from @fontsource (Inter 400/600/700/800, JetBrains Mono 500/600), both
 * under the SIL Open Font License — licences alongside the woff2 files.
 */

@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('/assets/fonts/inter-latin-400-normal.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('/assets/fonts/inter-latin-600-normal.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('/assets/fonts/inter-latin-700-normal.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 800; font-display: swap;
  src: url('/assets/fonts/inter-latin-800-normal.woff2') format('woff2'); }

@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('/assets/fonts/jetbrains-mono-latin-500-normal.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('/assets/fonts/jetbrains-mono-latin-600-normal.woff2') format('woff2'); }

/*
 * DESIGN NOTE — where the palette comes from
 * Not a generic dark dashboard theme. The sequential scale below is the
 * curing progression of a Virginia leaf: field green, yellowing, lemon, flue
 * gold, cured brown. It is the one colour ramp every person reading this
 * screen already knows by eye, so ordinal data (age bands, pipeline stages,
 * grade distribution) reads without a legend. NTA navy and gold stay exactly
 * as branded; the void behind them is darker than the brand navy so the navy
 * panels sit ON the surface instead of dissolving into it.
 */

:root {
  /* Surfaces — darkest to lightest */
  --void:        #060D18;   /* page */
  --panel:       #0B1F3A;   /* NTA navy, brand-locked */
  --panel-hi:    #102B4E;   /* raised / hover */
  --line:        #1B3A5E;   /* hairline */
  --line-soft:   #14304F;

  /* Type */
  --ink:         #F2F6FB;   /* primary */
  --ink-dim:     #94AAC4;   /* secondary */
  --ink-faint:   #5C7594;   /* axis, micro-copy */

  /* Curing ramp — ordinal scale, green leaf to cured brown */
  --cure-1:      #3E6B3A;
  --cure-2:      #6F9440;
  --cure-3:      #C9B037;
  --cure-4:      #D4A839;   /* NTA gold sits naturally at stage 4 */
  --cure-5:      #A9702F;
  --cure-6:      #7A4A22;

  /* Brand */
  --gold:        #D4A839;
  --gold-lt:     #F2D27A;

  /* Semantic */
  --ok:          #4E9F6E;
  --warn:        #E0A21B;
  --alert:       #E5544B;
  --info:        #4A90C4;

  --r:           10px;
  --gap:         14px;
}

/* ── Type scale ──────────────────────────────────────────────────────────
 * Sans carries language; mono carries every numeral without exception,
 * including axis ticks and table figures. Mixing proportional digits into a
 * column of hectarages makes them impossible to compare at a glance, which is
 * the whole job of this page. */
.ops { background: var(--void); color: var(--ink);
  font-family: Inter, 'Segoe UI', system-ui, sans-serif; font-size: 13px; line-height: 1.55; }
.ops .num { font-family: 'JetBrains Mono', 'Roboto Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }

.ops .eyebrow { font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 10px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-faint); }
.ops .panel-title { font-size: 13px; font-weight: 700; color: var(--ink); letter-spacing: -0.01em; }
.ops .micro { font-size: 11px; color: var(--ink-faint); line-height: 1.5; }

.ops .kpi-val { font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 40px; font-weight: 600; letter-spacing: -0.03em; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--ink); }
.ops .kpi-unit { font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 13px; font-weight: 500; color: var(--ink-faint); margin-left: 5px; }

/* ── Structure ───────────────────────────────────────────────────────────
 * A 12-column grid. Panels declare their span; nothing is centred for its own
 * sake and nothing is padded to fill a row. */
.ops-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--gap); }
.ops-panel { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 15px; min-width: 0; }
.ops-panel > header { display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-bottom: 12px; padding-bottom: 9px; border-bottom: 1px solid var(--line-soft); }

.span-3 { grid-column: span 3; } .span-4 { grid-column: span 4; }
.span-5 { grid-column: span 5; } .span-6 { grid-column: span 6; }
.span-7 { grid-column: span 7; } .span-8 { grid-column: span 8; }
.span-12 { grid-column: span 12; }
@media (max-width: 1180px) { .span-3, .span-4, .span-5 { grid-column: span 6; } }
@media (max-width: 780px) {
  .span-3, .span-4, .span-5, .span-6, .span-7, .span-8 { grid-column: span 12; }
}

/* ── KPI card ────────────────────────────────────────────────────────────
 * The gold rule is a load bar, not decoration: its width is this metric's
 * share of the whole, so a card carries a proportion without a second chart. */
.kpi { position: relative; overflow: hidden; }
.kpi::after { content: ''; position: absolute; left: 0; bottom: 0; height: 2px;
  width: var(--fill, 0%); background: var(--accent, var(--gold));
  transition: width 900ms cubic-bezier(.22,.61,.36,1); }
.kpi:hover { background: var(--panel-hi); }
.ops-panel, .kpi { transition: background 160ms ease, border-color 160ms ease; }

/* ── Charts ──────────────────────────────────────────────────────────────
 * Entry animation is a single orchestrated sweep: axes, then geometry, then
 * annotation. Staggered per series rather than per element, so a hundred
 * scatter points do not arrive as a hundred separate events. */
.ops svg { display: block; width: 100%; overflow: visible; }
.ops .axis line, .ops .axis path { stroke: var(--line); stroke-width: 1; }
.ops .axis text { fill: var(--ink-faint); font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 10px; }
.ops .grid-line { stroke: var(--line-soft); stroke-width: 1; stroke-dasharray: 2 4; }

.enter-bar { transform-origin: left center; animation: growX 760ms cubic-bezier(.22,.61,.36,1) both; }
.enter-col { transform-origin: center bottom; animation: growY 760ms cubic-bezier(.22,.61,.36,1) both; }
.enter-pt  { animation: pop 520ms cubic-bezier(.34,1.56,.64,1) both; }
.enter-fade{ animation: fade 600ms ease both; }
.enter-draw{ animation: draw 1100ms cubic-bezier(.4,0,.2,1) both; }

@keyframes growX { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes growY { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes pop   { from { opacity: 0; transform: scale(0); } to { opacity: 1; transform: scale(1); } }
@keyframes fade  { from { opacity: 0; } to { opacity: 1; } }
@keyframes draw  { from { stroke-dashoffset: var(--len, 1000); } to { stroke-dashoffset: 0; } }

/* Filtering re-renders rather than tweens, so geometry carries a transition of
 * its own: a bar that changes length slides, it does not jump. */
.ops rect.bar, .ops circle.pt {
  transition: x 520ms cubic-bezier(.22,.61,.36,1), y 520ms cubic-bezier(.22,.61,.36,1),
              width 520ms cubic-bezier(.22,.61,.36,1), height 520ms cubic-bezier(.22,.61,.36,1),
              cx 520ms cubic-bezier(.22,.61,.36,1), cy 520ms cubic-bezier(.22,.61,.36,1),
              r 300ms ease, opacity 240ms ease, fill 240ms ease;
}

/* Hover: dim the field, do not brighten the target. Brightening one mark in a
 * dense plot makes it louder; dimming the rest makes it findable. */
.ops .plot:hover circle.pt { opacity: 0.22; }
.ops .plot circle.pt:hover { opacity: 1; r: 7; stroke: var(--ink); stroke-width: 1.5; }
.ops .series:hover rect.bar { opacity: 0.3; }
.ops .series rect.bar:hover { opacity: 1; }

/* ── Tooltip ─────────────────────────────────────────────────────────────*/
.ops-tip { position: fixed; z-index: 90; pointer-events: none; opacity: 0;
  background: #04101F; border: 1px solid var(--gold); border-radius: 8px;
  padding: 9px 11px; min-width: 150px; max-width: 260px;
  box-shadow: 0 10px 28px rgba(0,0,0,.55); transition: opacity 120ms ease; }
.ops-tip.on { opacity: 1; }
.ops-tip .t-head { font-size: 12px; font-weight: 700; color: var(--gold-lt); margin-bottom: 5px; }
.ops-tip .t-row { display: flex; justify-content: space-between; gap: 14px; font-size: 11px;
  color: var(--ink-dim); }
.ops-tip .t-row b { font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 600; }

/* ── Filter chips ────────────────────────────────────────────────────────*/
.chip { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 11px;
  padding: 5px 11px; border-radius: 999px; border: 1px solid var(--line);
  background: transparent; color: var(--ink-dim); cursor: pointer;
  transition: all 160ms ease; }
.chip:hover { border-color: var(--gold); color: var(--ink); }
.chip[aria-pressed="true"] { background: var(--gold); border-color: var(--gold);
  color: #08182C; font-weight: 700; }
.chip:focus-visible, .ops button:focus-visible { outline: 2px solid var(--gold-lt); outline-offset: 2px; }

/* ── Data grid ───────────────────────────────────────────────────────────*/
.ops-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.ops-table thead th { position: sticky; top: 0; background: var(--panel); z-index: 1;
  text-align: left; padding: 7px 9px; border-bottom: 1px solid var(--line);
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 10px; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-faint); }
.ops-table tbody td { padding: 7px 9px; border-bottom: 1px solid var(--line-soft); color: var(--ink-dim); }
.ops-table tbody tr:hover td { background: var(--panel-hi); color: var(--ink); }
.ops-table td.n { font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums; text-align: right; color: var(--ink); }
/* An inline bar behind a figure turns a column of numbers into a shape you can
 * scan, without spending a panel on a chart that says the same thing. */
.ops-table td.bar-cell { position: relative; }
.ops-table td.bar-cell::before { content: ''; position: absolute; left: 0; top: 3px; bottom: 3px;
  width: var(--w, 0%); background: var(--gold); opacity: 0.26; border-radius: 2px;
  transition: width 700ms cubic-bezier(.22,.61,.36,1); }

.scroll-y { max-height: 420px; overflow-y: auto; }
.scroll-y::-webkit-scrollbar { width: 8px; }
.scroll-y::-webkit-scrollbar-thumb { background: var(--line); border-radius: 4px; }

@media (prefers-reduced-motion: reduce) {
  .ops *, .ops *::after, .ops *::before {
    animation-duration: 1ms !important; transition-duration: 1ms !important;
  }
}
