/* Vault Zip List Builder — one design system, two deliberate themes.
 *
 * Every colour below is a token, and both themes are built from the same set.
 * The bug this replaces: the pages declared `color-scheme: light` and nothing
 * else, so an OS in dark mode painted its own near-black behind text chosen for
 * white. Nothing inherits a background it was not styled for any more — both
 * themes set every surface explicitly, and `color-scheme` follows the theme so
 * form controls and scrollbars come along too.
 *
 * Palette derived from vaultroofing.com: #004F73 deep blue (the brand's dominant
 * colour), #112337 navy, #F9F5F0 warm off-white, #C02B0A brick red. The red is
 * reserved — destructive actions, FAIL, never-go — and never decorative.
 *
 * Contrast: every text/surface pair in here clears WCAG AA (4.5:1 body, 3:1
 * large). tests/test_contrast.py parses this file and proves it, so a future
 * colour tweak cannot quietly drop a pair below the line.
 */

:root {
  color-scheme: light;

  /* --- surfaces, light: warm rather than grey, from the brand's off-white --- */
  --bg:          #f3efe9;
  --surface:     #ffffff;
  --surface-2:   #faf7f3;   /* zebra rows, subtle fills */
  --surface-3:   #f0ebe4;   /* table headers, inset blocks */
  --line:        #ded6cb;
  --line-strong: #9c8f7c;

  /* --- ink --- */
  --ink:    #18212b;        /* 14.9:1 on --surface */
  --ink-2:  #4a5563;        /* 7.4:1  */
  --ink-3:  #5d6673;        /* 6.0:1 on white, 4.6:1 on --surface-3 */

  /* --- accent: the brand blue --- */
  --accent:      #004f73;   /* 8.6:1 on white; white on it is 8.6:1 */
  --accent-hi:   #00405e;
  --accent-on:   #ffffff;
  --accent-soft: #e2edf3;
  --accent-ink:  #00415f;   /* accent text on --surface */

  /* --- semantic state. Red is reserved. --- */
  --good:      #146b4f;
  --good-soft: #e0f0e8;
  --warn:      #8a5500;
  --warn-soft: #f9edd9;
  --danger:    #a82408;     /* brand brick, darkened to clear AA on white */
  --danger-soft: #fbe8e3;
  --info:      #00546e;
  --info-soft: #e0eef2;

  /* --- type --- */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
          Arial, sans-serif;
  --mono: "SF Mono", ui-monospace, "Cascadia Mono", "Roboto Mono", Menlo,
          Consolas, monospace;
  --fs-base:  14px;
  --fs-sm:    12.5px;
  --fs-xs:    11.5px;
  --fs-lg:    16px;
  --fs-h2:    15px;
  --fs-h1:    19px;

  /* --- space + shape --- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 28px;
  --r-sm: 4px;  --r:    7px;  --r-lg: 10px;
  --shadow: 0 1px 2px rgba(24, 33, 43, .06), 0 1px 3px rgba(24, 33, 43, .04);
  --ring: 0 0 0 3px rgba(0, 79, 115, .25);

  /* --- categorical series: zone polygons and zone chips ------------------ */
  /* Eight hues that stay distinguishable from each other AND from the map's
     basemap in both themes. Not semantic: a zone is not good or bad, so none of
     these may be the reserved red or the PASS green. */
  --s1: #2a78d6; --s2: #c2521f; --s3: #147f5c; --s4: #9a6b00;
  --s5: #a8447a; --s6: #4a3aa7; --s7: #0f6f86; --s8: #6b6f24;
}

/* The manual toggle wins in both directions, which is why the dark tokens are
 * declared twice rather than only under the media query. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark; }
}
:root[data-theme="dark"] { color-scheme: dark; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:          #0d151e;
    --surface:     #151f2b;
    --surface-2:   #1a2532;
    --surface-3:   #1f2b39;
    --line:        #2a3746;
    --line-strong: #61758e;

    --ink:    #e9eef4;      /* 13.6:1 on --surface */
    --ink-2:  #b3c0ce;      /* 8.0:1  */
    --ink-3:  #8d9bab;      /* 5.2:1  */

    --accent:      #57a6cc; /* the brand blue lifted until it clears AA on navy */
    --accent-hi:   #7cbcdb;
    --accent-on:   #06202d;
    --accent-soft: #14303f;
    --accent-ink:  #7cbcdb;

    --good:      #4cbe8d;
    --good-soft: #10302a;
    --warn:      #e3a84a;
    --warn-soft: #33270f;
    --danger:    #ff8a70;   /* the brick lifted; still the only red in the app */
    --danger-soft: #3a1a13;
    --info:      #6fb6cf;
    --info-soft: #122c37;

    --shadow: 0 1px 2px rgba(0, 0, 0, .5), 0 1px 3px rgba(0, 0, 0, .35);
    --ring: 0 0 0 3px rgba(87, 166, 204, .3);
    --s1: #6fb0f0; --s2: #f08a5c; --s3: #4fc294; --s4: #dfae4a;
    --s5: #e386b4; --s6: #a79bf0; --s7: #5fc0d8; --s8: #b9c065;
  }
}
:root[data-theme="dark"] {
  --bg:          #0d151e;
  --surface:     #151f2b;
  --surface-2:   #1a2532;
  --surface-3:   #1f2b39;
  --line:        #2a3746;
  --line-strong: #61758e;
  --ink:    #e9eef4;
  --ink-2:  #b3c0ce;
  --ink-3:  #8d9bab;
  --accent:      #57a6cc;
  --accent-hi:   #7cbcdb;
  --accent-on:   #06202d;
  --accent-soft: #14303f;
  --accent-ink:  #7cbcdb;
  --good:      #4cbe8d;
  --good-soft: #10302a;
  --warn:      #e3a84a;
  --warn-soft: #33270f;
  --danger:    #ff8a70;
  --danger-soft: #3a1a13;
  --info:      #6fb6cf;
  --info-soft: #122c37;
  --shadow: 0 1px 2px rgba(0, 0, 0, .5), 0 1px 3px rgba(0, 0, 0, .35);
  --ring: 0 0 0 3px rgba(87, 166, 204, .3);
  --s1: #6fb0f0; --s2: #f08a5c; --s3: #4fc294; --s4: #dfae4a;
  --s5: #e386b4; --s6: #a79bf0; --s7: #5fc0d8; --s8: #b9c065;
}

/* ---------------------------------------------------------------- base ---- */
* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-base);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

.wrap { max-width: 1180px; margin: 0 auto; padding: var(--sp-5) var(--sp-4) 48px; }

h1 { font-size: var(--fs-h1); font-weight: 650; letter-spacing: -.01em;
     margin: 0 0 var(--sp-1); }
h2 { font-size: var(--fs-h2); font-weight: 640; letter-spacing: -.005em;
     margin: 0 0 var(--sp-3); color: var(--ink); }
.sub { color: var(--ink-2); font-size: var(--fs-sm); margin: 0 0 var(--sp-5); }
.note { color: var(--ink-2); font-size: var(--fs-sm); line-height: 1.5; }
.note-dim { color: var(--ink-3); font-size: var(--fs-xs); }
a { color: var(--accent-ink); }

code, .mono { font-family: var(--mono); font-size: .92em;
              font-variant-numeric: tabular-nums; }

/* -------------------------------------------------------------- layout ---- */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-4) var(--sp-5);
  margin-bottom: var(--sp-4);
  box-shadow: var(--shadow);
}
.panel > h2 { display: flex; align-items: baseline; gap: var(--sp-2); }

.topbar {
  display: flex; align-items: center; gap: var(--sp-3);
  justify-content: space-between; margin-bottom: var(--sp-4);
}
.topnav { display: flex; gap: var(--sp-2); align-items: center; }

.row { display: flex; gap: var(--sp-5); flex-wrap: wrap; }
.row > div { min-width: 0; }
.lbl {
  font-size: var(--fs-xs); font-weight: 620; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3);
  margin: 0 0 var(--sp-2);
}

/* ------------------------------------------------------------- buttons ---- */
/* One family. Primary carries the brand, secondary is the quiet default,
 * destructive is the only place the red appears on a control. */
button, .btn {
  font: inherit; font-size: var(--fs-sm); font-weight: 560;
  padding: 7px 13px; border-radius: var(--r-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface); color: var(--ink);
  cursor: pointer; transition: background .12s, border-color .12s, color .12s;
  white-space: nowrap;
}
button:hover, .btn:hover { background: var(--surface-3); border-color: var(--ink-3); }
button:focus-visible, .btn:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible,
.chip:focus-visible { outline: none; box-shadow: var(--ring); }

button.primary, .btn.primary {
  background: var(--accent); border-color: var(--accent); color: var(--accent-on);
}
button.primary:hover, .btn.primary:hover {
  background: var(--accent-hi); border-color: var(--accent-hi);
}
button.ghost, .btn.ghost {
  background: transparent; border-color: var(--line);
  color: var(--ink-2); padding: 5px 10px; font-size: var(--fs-xs);
}
button.ghost:hover, .btn.ghost:hover { background: var(--surface-3); color: var(--ink); }
button.danger, .btn.danger {
  background: transparent; border-color: var(--danger); color: var(--danger);
}
button.danger:hover, .btn.danger:hover { background: var(--danger-soft); }
button:disabled, .btn:disabled { opacity: .5; cursor: not-allowed; }

/* The export buttons stay prominent — a prior, still-standing request. */
.exportbar { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }
.exportbar button { padding: 9px 16px; font-size: var(--fs-base); font-weight: 600; }

/* --------------------------------------------------------------- chips ---- */
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface-2);
  color: var(--ink-2); font-size: var(--fs-xs); font-weight: 560;
  cursor: pointer; user-select: none;
  transition: background .12s, border-color .12s, color .12s;
}
.chip:hover { border-color: var(--line-strong); color: var(--ink); }
.chip.on {
  background: var(--accent); border-color: var(--accent);
  color: var(--accent-on); font-weight: 600;
}
.chip.art::before, .chip.pinned::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: var(--warn);
}
.chip.on.art::before, .chip.on.pinned::before { background: var(--accent-on); }
.chip[aria-disabled="true"] { opacity: .45; cursor: default; }

/* Non-interactive status pills. Same shape family, no pointer. */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px; border-radius: 999px;
  font-size: var(--fs-xs); font-weight: 620; letter-spacing: .01em;
  border: 1px solid transparent; white-space: nowrap;
}
.pill.pass, .pill.licensed, .pill.live {
  background: var(--good-soft); color: var(--good); border-color: var(--good);
}
.pill.fail, .pill.nevergo {
  background: var(--danger-soft); color: var(--danger); border-color: var(--danger);
}
.pill.warn, .pill.roadmap, .pill.quarantined {
  background: var(--warn-soft); color: var(--warn); border-color: var(--warn);
}
.pill.info, .pill.seed, .pill.pinned {
  background: var(--info-soft); color: var(--info); border-color: var(--info);
}
.pill.mute { background: var(--surface-3); color: var(--ink-3); border-color: var(--line); }

/* --------------------------------------------------------------- cards ---- */
.tiles { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.tile {
  flex: 1 1 120px; min-width: 112px;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r); padding: var(--sp-3) var(--sp-3) 10px;
}
.tile .v {
  font-size: 20px; font-weight: 650; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; color: var(--ink); line-height: 1.15;
}
.tile .k {
  font-size: var(--fs-xs); color: var(--ink-3); margin-top: 2px;
  font-weight: 560; letter-spacing: .02em;
}
.tile.accent { background: var(--accent-soft); border-color: var(--accent); }
.tile.accent .v { color: var(--accent-ink); }

/* -------------------------------------------------------------- tables ---- */
/* The tables ARE the product: these two pages get screenshotted and forwarded. */
.tablewrap {
  border: 1px solid var(--line); border-radius: var(--r);
  overflow: auto; max-height: 70vh; background: var(--surface);
}
table { border-collapse: separate; border-spacing: 0; width: 100%;
        font-size: var(--fs-sm); }
th, td { padding: 7px 11px; text-align: left; vertical-align: top;
         border-bottom: 1px solid var(--line); }
thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface-3); color: var(--ink-2);
  font-size: var(--fs-xs); font-weight: 640; letter-spacing: .04em;
  text-transform: uppercase; white-space: nowrap;
  border-bottom: 1px solid var(--line-strong);
}
tbody tr:nth-child(even) > td { background: var(--surface-2); }
tbody tr:hover > td { background: var(--accent-soft); }
tbody tr:last-child > td { border-bottom: none; }

/* Numerics line up and compare. */
td.num, th.num, .num { text-align: right; font-variant-numeric: tabular-nums; }
td.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* Sortable headers. Presentation only — it reorders rows already on the page
 * and never asks the server for anything. */
th.sortable { cursor: pointer; user-select: none; padding-right: 20px;
              position: sticky; }
th.sortable::after {
  content: "↕"; position: absolute; right: 7px; opacity: .3; font-size: 10px;
  font-weight: 400;
}
th.sortable[aria-sort="ascending"]::after { content: "↑"; opacity: 1;
                                            color: var(--accent-ink); }
th.sortable[aria-sort="descending"]::after { content: "↓"; opacity: 1;
                                             color: var(--accent-ink); }
th.sortable:hover { color: var(--ink); }

/* --------------------------------------------------------------- forms ---- */
input, select, textarea {
  font: inherit; font-size: var(--fs-sm);
  padding: 6px 9px; border-radius: var(--r-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface); color: var(--ink);
}
input:disabled { background: var(--surface-3); color: var(--ink-3); }
input.w5 { width: 72px; } input.w8 { width: 104px; }
input.w12 { width: 156px; } input.w20 { width: 256px; }

.field { display: inline-flex; flex-direction: column; gap: 3px; }
.field > label {
  font-size: var(--fs-xs); color: var(--ink-3); font-weight: 560;
}
.field .unit { color: var(--ink-3); font-size: var(--fs-xs); }
.fieldset {
  border: 1px solid var(--line); border-radius: var(--r);
  padding: var(--sp-3); margin: 0 0 var(--sp-3); background: var(--surface-2);
}
.fieldset > legend {
  font-size: var(--fs-xs); font-weight: 640; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3); padding: 0 6px;
}

/* --------------------------------------------------------------- misc ---- */
.callout {
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  background: var(--surface-2); border-radius: var(--r-sm);
  padding: var(--sp-3); font-size: var(--fs-sm); color: var(--ink-2);
}
.callout.bad { border-left-color: var(--danger); background: var(--danger-soft); }
.callout.warn { border-left-color: var(--warn); background: var(--warn-soft); }

.theme-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; font-size: var(--fs-xs);
}

/* The map keeps its own box. Size and parentage are untouched on purpose:
 * Leaflet caches container dimensions, and a hidden or resized container is how
 * this app once shipped a map stuck at zoom 18. Only the frame is styled. */
#map, .mapbox {
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface-2);
}
.leaflet-container { background: var(--surface-2) !important; }

/* ---------------------------------------------------------------- tabs ---- */
/* Shared by the admin page and the main page. Grouping only: a tab changes
   what is shown, never what a control does. */
.tabs{display:flex;flex-wrap:wrap;gap:4px;margin:18px 0 6px;
 border-bottom:1px solid var(--line)}
.tab{background:none;border:0;border-bottom:2px solid transparent;
 padding:9px 14px;margin-bottom:-1px;border-radius:var(--r) var(--r) 0 0;
 font:inherit;font-size:var(--fs-sm);font-weight:600;color:var(--ink-2);
 cursor:pointer}
.tab:hover{color:var(--ink);background:var(--surface-2)}
.tab[aria-selected="true"]{color:var(--accent);border-bottom-color:var(--accent)}
.tab .badge{display:inline-block;min-width:17px;padding:0 5px;margin-left:6px;
 border-radius:9px;background:var(--accent-soft);color:var(--accent);
 border:1px solid var(--accent);font-size:11px;line-height:15px;
 text-align:center;font-variant-numeric:tabular-nums}
.tab .badge.warn{background:var(--warn-soft);color:var(--warn);
 border-color:var(--warn)}
