/* ============================================================
   QUANTDIGITALS — INTERFACE
   One stylesheet for the whole platform.

   The workstation is the product: a fixed application shell with
   a top bar, a left layer tree, the map stage, an entity table
   docked beneath it, a detail panel that opens on selection, and
   a status bar. Content pages (tools, about, legal) reuse the
   same bar and rail but scroll normally.

   The visual direction is the operational-intelligence
   convention played straight — the arrangement an analyst
   already knows how to drive, executed at craft rather than
   reinterpreted. Palantir Gotham / Foundry is the named bar.

   Rules the system holds to:
     - nothing in the interface renders below 12px
     - the ground ramp separates by real value steps, not by
       hairlines alone; neighbours sit ~4-5 L* apart, where the
       grade this replaced was near-zero
     - one action colour (blue) for interactive affordance,
       selection and focus; status colours mean status only;
       entity hues mean class only
     - IBM Plex Sans for language, IBM Plex Mono for every
       measured value (coordinates, identifiers, counts, timings)
     - shadows carry an offset and a blur, and only surfaces that
       genuinely float above the shell get one
     - the imagery stays the brightest thing on screen

   Contents
     1. Fonts
     2. Tokens
     3. Reset + browser surfaces
     4. Shell
     5. Top bar
     6. Rail — layer tree
     7. Stage + HUD + readout
     8. Entity table
     9. Detail panel
    10. Status bar
    11. Markers
    12. Content pages
    13. Motion + responsive
   ============================================================ */

/* ---------- 1. FONTS -------------------------------------- */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('/assets/fonts/plex-mono-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal; font-weight: 600; font-display: swap;
  src: url('/assets/fonts/plex-mono-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal; font-weight: 400 600; font-stretch: 100%; font-display: swap;
  src: url('/assets/fonts/plex-sans-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2122, U+2212, U+FEFF, U+FFFD;
}

/* ---------- 2. TOKENS ------------------------------------- */
/* A cool near-black ramp with real distance between the steps.
   The previous grade held its neighbours ~1.06:1 apart, which is
   arithmetically six surfaces and optically one flat field — every
   boundary in the product ended up resting on its hairline alone.

   Contrast ratio is the wrong measure at this end of the scale: two
   near-blacks a clear step apart still compute near 1:1. The measure
   that matters here is perceptual lightness, and this grade runs
   3.8 / 4.8 / 5.2 / 5.5 L* between neighbours (1.08-1.19:1), so a
   panel reads as a panel before you find its border. Re-grade against
   L*, not against the ratio. */
:root {
  --void:    #05070A;   /* behind the map canvas */
  --bg:      #090C11;   /* application floor */
  --s1:      #11161D;   /* bar, rail, table, status bar */
  --s2:      #182029;   /* headers, detail panel, raised chrome */
  --s3:      #212B36;   /* inputs, wells, controls */
  --s4:      #2B3745;   /* hover, pressed, selected row */

  --line:    #222C38;   /* structural divider */
  --line-2:  #2F3B4A;   /* container-grade border */
  --line-3:  #41505F;   /* interactive border */

  /* The ink ramp descends, and it has to descend monotonically: --ink-4
     used to sit at #8A97A6, a full 4.6 L* ABOVE --ink-3, so the token
     named as the faintest step was the brighter of the two and every
     pair that used them together — heading over value, label over
     reading — inverted its own hierarchy. --ink-4 now takes the value
     --ink-3 held, and --ink-3 moves up into the gap under --ink-2.
     L*: 95.3 / 73.6 / 65.0 / 57.3. On --s1 that is 16.2 / 8.8 / 6.8 /
     5.2:1, so the faintest ink still clears text contrast. */
  --ink:     #EEF2F7;
  --ink-2:   #AAB6C6;
  --ink-3:   #939FAF;
  --ink-4:   #7D8B9C;

  /* One action colour. Interactive affordance, selection, focus.
     `--ac` is the drawn value (text, borders, rings); `--ac-solid`
     is the fill, darkened so white sits on it at 4.5:1. */
  --ac:       #4C8DFF;
  --ac-solid: #2E6FE6;
  --ac-wash:  rgba(76, 141, 255, 0.14);

  --pass:    #3FBF7F;
  /* Status amber used to sit at #E0A030 against a camera hue of
     #E8A93C — 2 degrees of hue apart, which is the same colour. The
     rule this file states is that status means status and entity hues
     mean class, and it cannot hold while `reported` in the table is
     drawn in the KC Scout layer's own gold. Status swings orange. */
  --warn:    #F0913C;
  --fail:    #F0524A;

  /* Entity hues. qd-globe.js reads these with getPropertyValue, which
     returns the specified token rather than a resolved one — so the
     literals have to live on the --globe-* names it asks for, and the
     shorter --e-* names alias them for use inside this stylesheet. */
  --globe-camera:   #E8A93C;
  /* Pale and desaturated, so an aircraft marker is never read as the
     action blue that means affordance and selection everywhere else. */
  --globe-aircraft: #9EC5E8;
  /* Magenta because the globe's ground is satellite imagery, and
     imagery has no magenta in it — greens, browns and greys, which
     is why the amber and blue above survive on top of it. The dusty
     --globe-org rose is a graph hue and is not drawn on the map, so
     the two never meet at marker size. */
  --globe-datacenter: #E06BB4;
  --globe-domain:   #8AAEC4;
  --globe-ip:       #4FB5A6;
  --globe-identity: #A797CE;
  --globe-cert:     #CFA75C;
  --globe-org:      #CC909A;
  --globe-landmark: #96A49E;
  /* Parcels are line work over imagery, not a marker hue: a pale
     cyan reads as drawn cadastre against greens, browns and greys
     without competing with the camera amber or the aircraft blue
     that sit on top of it. */
  --globe-parcel:   #86D2E4;
  --globe-select:   #4C8DFF;
  --globe-label:    #EEF2F7;
  --globe-ocean:    #0A1622;
  --globe-coast:    #7FB8E0;

  --e-camera:     var(--globe-camera);
  --e-aircraft:   var(--globe-aircraft);
  --e-datacenter: var(--globe-datacenter);
  --e-domain:    var(--globe-domain);
  --e-ip:        var(--globe-ip);
  --e-identity:  var(--globe-identity);
  --e-cert:      var(--globe-cert);
  --e-org:       var(--globe-org);
  --e-landmark:  var(--globe-landmark);
  --e-parcel:    var(--globe-parcel);

  --sans: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

  /* The type scale. The workstation used to run on four sizes inside a
     2px band — 11 / 12 / 12.5 / 13 — with exactly one element on the
     whole screen above 13px. Four sizes that close apart are not a
     scale: size had been taken off the table as a carrier, and every
     distinction in the product was left to colour and weight, which is
     why the shell read as one undifferentiated field.

     Five steps with real distance between them, and each one owns a
     rank rather than a place in the markup:
       micro  measured values, provenance, counts, uppercase labels
       body   dense table cells, secondary readings
       ui     anything a person acts on — tabs, layer names, entity names
       lead   the subhead inside a panel
       title  the one title on screen at a time
     The floor is 12px, which is the rule this file states at the top
     and used to break in 83 places. */
  --fs-micro: 12px;
  --fs-body:  13px;
  --fs-ui:    14px;
  --fs-lead:  16px;
  --fs-title: 22px;

  --r:      4px;     /* controls, panels, cards */
  --r-s:    3px;     /* tags, keycaps, small chrome */
  --r-pill: 999px;   /* anything whose radius is just "fully round" */

  /* Pure white is not a ground step — it is the label on the action fill
     and on the live badge, where it has to hold 4.9:1. Naming it keeps it
     from reading as a sixth ink. */
  --on-ac:  #FFFFFF;

  /* Only surfaces that genuinely float above the shell take one, and
     it carries an offset and a blur — a zero-offset halo is decoration,
     not depth. */
  --shadow:    0 8px 24px -10px rgba(0, 0, 0, 0.75);
  --shadow-lg: 0 16px 40px -12px rgba(0, 0, 0, 0.8);

  --bar:    48px;
  --rail:   280px;
  --insp:   344px;
  --tableh: 248px;
}

/* ---------- 3. RESET + BROWSER SURFACES ------------------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-body);
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}
button, input, select { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }

/* The parts we did not draw still belong to the design. */
::selection { background: var(--ac-solid); color: var(--on-ac); }
:root { accent-color: var(--ac); caret-color: var(--ac); color-scheme: dark; }
* { scrollbar-width: thin; scrollbar-color: var(--line-3) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--line-2); border-radius: var(--r-pill);
  border: 3px solid transparent; background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--line-3); background-clip: content-box; }
*::-webkit-scrollbar-corner { background: transparent; }

:focus-visible { outline: 2px solid var(--ac); outline-offset: 2px; border-radius: var(--r-s); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ac-solid); color: var(--on-ac); border-radius: var(--r);
  padding: 10px 16px; font-size: var(--fs-body); font-weight: 600;
}
.skip-link:focus { left: 10px; top: 10px; }

/* ---------- 4. SHELL -------------------------------------- */
.app {
  position: fixed; inset: 0;
  display: grid;
  grid-template-rows: var(--bar) minmax(0, 1fr);
  background: var(--bg);
}
.app-body {
  position: relative;
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  /* The stage has no intrinsic height — the row has to be told to
     fill, or the map container collapses to zero and paints nothing. */
  grid-template-rows: minmax(0, 1fr);
  min-height: 0;
}
.app-body.has-inspector { grid-template-columns: var(--rail) minmax(0, 1fr) var(--insp); }
.app-body.is-rail-collapsed { grid-template-columns: 0 minmax(0, 1fr); }
.app-body.is-rail-collapsed.has-inspector { grid-template-columns: 0 minmax(0, 1fr) var(--insp); }
.app-body.is-rail-collapsed .rail { overflow: hidden; padding: 0; border-right: 0; }

/* The centre column carries the map and the table it is bound to. */
.centre {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto var(--tableh);
  min-width: 0; min-height: 0;
}
.centre.is-collapsed { grid-template-rows: minmax(0, 1fr) auto 40px; }

/* ---------- 5. TOP BAR ------------------------------------ */
.bar {
  display: flex; align-items: center;
  padding: 0 12px 0 16px; gap: 4px;
  background: var(--s1);
  border-bottom: 1px solid var(--line);
}
.bar-mark {
  display: flex; align-items: center; gap: 10px;
  height: var(--bar); padding-right: 18px; margin-right: 14px;
  border-right: 1px solid var(--line);
}
.bar-mark i {
  width: 16px; height: 16px; flex: none; background: var(--ac);
  clip-path: polygon(50% 0, 100% 27%, 100% 73%, 50% 100%, 0 73%, 0 27%);
}
.bar-mark b { font-size: var(--fs-ui); font-weight: 600; letter-spacing: 0.1em; }

.bar-tabs { display: flex; align-items: center; gap: 2px; }
.bar-tab {
  display: flex; align-items: center; height: 30px; padding: 0 13px;
  border-radius: var(--r);
  font-size: var(--fs-ui); font-weight: 500; color: var(--ink-3);
}
.bar-tab:hover { color: var(--ink-2); background: var(--s2); }
.bar-tab[aria-current] { color: var(--ink); background: var(--s3); }

.omni {
  position: relative;
  flex: 1; max-width: 440px; margin-left: 20px;
  display: flex; align-items: center; gap: 9px;
  height: 30px; padding: 0 10px;
  background: var(--s3); border: 1px solid var(--line-2); border-radius: var(--r);
  color: var(--ink-3); font-size: var(--fs-ui);
}
.omni:hover, .omni:focus-within { border-color: var(--line-3); }
.omni svg { width: 14px; height: 14px; flex: none; }
.omni-input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  color: var(--ink); font: inherit;
}
.omni-input::placeholder { color: var(--ink-3); }
.omni-input::-webkit-search-cancel-button { display: none; }
.omni kbd {
  /* On --s3 rather than the shell floor, so it takes the step above:
     --ink-4 lands at 4.13:1 against this ground, --ink-3 at 5.34:1. */
  font-family: var(--mono); font-size: var(--fs-micro); line-height: 16px; color: var(--ink-3);
  border: 1px solid var(--line-2); border-radius: var(--r-s); padding: 0 5px; flex: none;
}
.omni-input:not(:placeholder-shown) ~ kbd { display: none; }

.omni-results {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 40;
  max-height: 340px; overflow-y: auto;
  background: var(--s2); border: 1px solid var(--line-2); border-radius: var(--r);
  box-shadow: var(--shadow-lg);
}
.omni-row {
  display: flex; flex-direction: column; gap: 2px; width: 100%;
  padding: 8px 12px; text-align: left; border: 0; background: none; color: inherit;
  border-bottom: 1px solid var(--line);
}
.omni-row:last-child { border-bottom: 0; }
.omni-row:hover, .omni-row.is-active { background: var(--s3); }
.omni-row b { font-weight: 500; font-size: var(--fs-body); color: var(--ink); }
.omni-row s {
  text-decoration: none; font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.omni-empty {
  padding: 10px 12px; font-size: var(--fs-ui); color: var(--ink-3);
}

.bar-r { margin-left: auto; display: flex; align-items: center; gap: 4px; padding-left: 12px; }
.bar-stat {
  display: flex; align-items: center; gap: 7px;
  height: 30px; padding: 0 11px; margin-right: 4px;
  background: var(--s2); border-radius: var(--r);
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.08em; color: var(--ink-2);
}
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--pass); flex: none; }
.dot.is-warn { background: var(--warn); }
.dot.is-fail { background: var(--fail); }
.dot.is-off  { background: var(--ink-4); }

.bar-ico {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: var(--r); color: var(--ink-3);
}
.bar-ico:hover { color: var(--ink); background: var(--s2); }
.bar-ico svg { width: 16px; height: 16px; }

/* ---------- 6. RAIL — LAYER TREE -------------------------- */
.rail {
  background: var(--s1); border-right: 1px solid var(--line);
  display: flex; flex-direction: column; min-height: 0; overflow-y: auto;
}
.rail-sec { border-bottom: 1px solid var(--line); padding-bottom: 8px; }
.rail-sec:last-child { border-bottom: 0; }
.rail-h {
  display: flex; align-items: center; height: 32px; padding: 0 16px;
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-3);
}
.rail-h em {
  margin-left: auto; font-style: normal;
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.04em; color: var(--ink-4);
}
.rail-b { padding: 0 8px; }

/* group node — the branch
   The section was named a layer tree and drawn as five siblings, which
   is the shape any list takes when nothing decided what the levels
   were. Sources belong to domains, so the domain is the branch: a
   disclosure that folds it away, a name, and a box that acts on
   everything under it. Two levels is the whole depth the product needs
   — enough that a source lands somewhere, not so much that reaching a
   layer costs two clicks. */
.grp { padding-bottom: 2px; }
/* One control column for both ranks: the branch box at 16px, its
   children's boxes at 33px, and a single text column at 56px that
   branch and leaf labels share. */
.grp-h {
  display: grid; grid-template-columns: 14px minmax(0, 1fr);
  align-items: center; gap: 9px;
  height: 28px; padding: 0 8px;
}
/* A 14px box is a 14px target. The branch box is the one checkbox in
   the rail that is its own button rather than a mark inside a 42px row,
   so it grows a transparent pad out to a real one. */
.grp-ck { position: relative; }
.grp-ck::before { content: ""; position: absolute; inset: -7px; }
.tw {
  display: grid; grid-template-columns: 10px minmax(0, 1fr);
  align-items: center; gap: 9px; min-width: 0;
  height: 100%; text-align: left;
}
/* Cut from a real box rather than built from a zero-size element's
   borders — same caret, and it reads as the glyph it is both to a
   person and to anything scanning the sheet. */
.tw-g {
  justify-self: center;
  width: 7px; height: 8px;
  background: var(--ink-3);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
  transition: transform 130ms cubic-bezier(0.2, 0.8, 0.2, 1), background-color 90ms ease;
}
.grp:not(.is-closed) .tw-g { transform: rotate(90deg); }
/* A step below the section heading it sits under, or the rail reads as
   two headings of equal rank stacked on each other. */
.tw-t {
  font-size: var(--fs-micro); font-weight: 500; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tw:hover .tw-t { color: var(--ink-2); }
.tw:hover .tw-g { background: var(--ink-2); }

/* A branch with nothing live in it says so through its box, which
   renders disabled, and through its children, which carry the reason.
   The heading itself is not dimmed: --ink-3 sits at 5.23:1 on --s1, so
   there is no headroom under it — 0.9 opacity already lands at 4.45:1
   and 0.55 at 2.44:1. Anything legible enough to read is too close to
   full strength to signal anything, which means luminance is the wrong
   carrier for this state, not that the dimming needs tuning. Re-check
   this if the ink ramp is ever re-graded. */

/* The children hang off a spine dropped from the twisty, so the nesting
   is carried by a line rather than by indentation alone — the rail is
   264px and cannot spend 16 of them per level. */
.grp-b { position: relative; }
.grp-b::before {
  content: ""; position: absolute; left: 22px; top: 0; bottom: 3px;
  width: 1px; background: var(--line);
}
.grp.is-closed .grp-b { display: none; }
.grp-b .layer { padding-left: 25px; }

/* layer row
   Two glyphs carrying two different facts, not one fact twice. The
   checkbox is the visibility control — the category's own convention,
   played straight, in the one action colour that already means
   affordance and selection everywhere else in the shell. The square
   right of the label is the class key, and it holds its entity hue in
   every state: a legend that goes grey when you switch the layer off
   stops answering the question it exists to answer, which is what that
   colour meant. */
.layer {
  display: grid; grid-template-columns: 14px minmax(0, 1fr);
  align-items: center; gap: 11px;
  width: 100%; height: 42px; padding: 0 8px; text-align: left;
  border-radius: var(--r);
}
.layer:hover { background: var(--s2); }

/* the control */
.ck {
  width: 14px; height: 14px; position: relative;
  border: 1px solid var(--line-3); border-radius: var(--r-s);
  background: var(--s3);
  transition: background-color 90ms ease, border-color 90ms ease;
}
.ck::after {
  content: ""; position: absolute; left: 4px; top: 1px;
  width: 4px; height: 8px; opacity: 0;
  border: solid var(--on-ac); border-width: 0 1.6px 1.6px 0;
  transform: rotate(43deg);
}
/* The leaf box takes its state from the row it sits in; the branch box
   declares its own, and has a third state the leaf cannot have. */
.layer:hover .ck, .grp-ck:hover { border-color: var(--ink-3); }
.layer[aria-pressed="true"] .ck,
.grp-ck[aria-checked="true"],
.grp-ck[aria-checked="mixed"] {
  background: var(--ac-solid); border-color: var(--ac-solid);
}
.layer[aria-pressed="true"] .ck::after,
.grp-ck[aria-checked="true"]::after { opacity: 1; }
.layer[aria-pressed="true"]:hover .ck,
.grp-ck[aria-checked="true"]:hover,
.grp-ck[aria-checked="mixed"]:hover { background: var(--ac); border-color: var(--ac); }

/* Mixed is a bar, not a check — the branch is reporting that its
   children disagree, which is a different fact from being on. */
.grp-ck[aria-checked="mixed"]::after {
  opacity: 1; left: 3px; top: 5px; width: 6px; height: 0;
  border-width: 0 0 1.6px 0; transform: none;
}
.grp-ck:disabled { background: none; border-color: var(--line-2); cursor: default; }
.grp-ck:disabled:hover { border-color: var(--line-2); }

/* The title line: the name, the count it contributes, and the class
   key. The count used to ride the provenance line below, where it was
   the fourth thing competing for 184px and the source name truncated
   to "County assess…" to make room for it. A count belongs to the
   layer, not to the feed's cadence, and up here it has a column of its
   own that nothing pushes around. The key moves with it, so the hue
   sits beside the name it classifies rather than floating against the
   provenance line as though it described the source. */
.layer .t { min-width: 0; }
.layer .tt { display: flex; align-items: center; gap: 9px; min-width: 0; }
.layer .tt > i {
  width: 10px; height: 10px; border-radius: var(--r-s); flex: none;
  background: var(--lc);
}
.layer .t b {
  flex: 1; min-width: 0;
  font-size: var(--fs-ui); font-weight: 500; color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.layer[aria-pressed="true"] .t b { color: var(--ink); }
/* The source line. Health, where it comes from and how often, and the
   count it is contributing — three slots in fixed positions, so the
   count reads down a column and the prose never pushes it around. */
.layer .src {
  display: grid; grid-template-columns: 7px minmax(0, auto) minmax(0, 1fr);
  align-items: center; gap: 7px; margin-top: 1px;
}
/* The name is sized to its content and the tail takes what is left, so
   a short line loses its cadence before it loses the source it names. */
.layer .sn, .layer .sx {
  font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-4);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.layer .ct {
  flex: none;
  font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.layer[aria-pressed="true"] .ct { color: var(--ink-2); }
/* A layer with nothing behind it is not a control. It keeps its hue at
   a weight that reads as inventory rather than signal, and its box
   stops looking like something that would answer a click. */
.layer[disabled] { cursor: default; }
.layer[disabled]:hover { background: transparent; }
.layer[disabled] .t b { color: var(--ink-4); }
.layer[disabled] .ck { background: none; border-color: var(--line-2); }
.layer[disabled]:hover .ck { border-color: var(--line-2); }
.layer[disabled] .tt > i { opacity: 0.32; }

/* Loading is its own state, and it outranks the layer's intended
   position. A box that reads fully CHECKED while the feed is still
   resolving, then silently clears when it fails, reverses itself
   without ever showing the user a change — so while the request is
   open the box holds the unchecked ground, drops the mark, and pulses
   its border to say the answer is still coming. */
.layer.is-loading .ck,
.layer.is-loading:hover .ck {
  background: var(--s3); border-color: var(--line-3);
  animation: ck-pulse 1.4s ease-in-out infinite;
}
.layer.is-loading .ck::after { opacity: 0; }
@keyframes ck-pulse {
  0%, 100% { border-color: var(--line-3); }
  50%      { border-color: var(--ac); }
}

/* key/value block */
.kv { display: grid; grid-template-columns: 78px minmax(0, 1fr); gap: 0 12px; padding: 0 16px; margin: 0; }
.kv dt {
  font-size: var(--fs-micro); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-4); padding: 6px 0;
}
.kv dd {
  margin: 0; font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-2);
  padding: 6px 0; overflow: hidden; text-overflow: ellipsis;
}
.kv dd.is-ok { color: var(--pass); }

/* ---------- 7. STAGE + HUD + READOUT ---------------------- */
/* maplibre-gl.css is injected by qd-globe.js and therefore lands after
   this file, where `.maplibregl-map { position: relative }` would beat
   an absolutely positioned container at equal specificity. The stage is
   a flex box instead, so the map is sized by the layout rather than by
   a position rule the vendor sheet can win. */
.stage { position: relative; display: flex; min-width: 0; background: var(--void); overflow: hidden; }
.qd-globe { flex: 1; min-width: 0; }
.qd-globe canvas { outline: none; }
.maplibregl-control-container { display: none; }

.hud { position: absolute; z-index: 5; }
.hud-tr {
  right: 12px; top: 12px; display: flex; flex-direction: column;
  background: var(--s2); border: 1px solid var(--line-2);
  border-radius: var(--r); box-shadow: var(--shadow); overflow: hidden;
}
.hud-tr button {
  width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
  color: var(--ink-2); border-bottom: 1px solid var(--line);
}
.hud-tr button:last-child { border-bottom: 0; }
.hud-tr button:hover { color: var(--ink); background: var(--s4); }
.hud-tr svg { width: 15px; height: 15px; }

/* The legend sits over live imagery, so it carries its own ground —
   over bright desert or snow, bare text on the canvas is unreadable.

   It answers one question — which hue is which class — and it used to
   answer a second one it had no business with, restating the rail's
   counts verbatim eighteen inches away from them. The counts are gone;
   the symbol key is the whole job. It also runs as a row rather than a
   stack, so it stops occupying the same corner as the readout. */
.legend {
  position: absolute; left: 12px; bottom: 42px; z-index: 5;
  display: flex; flex-wrap: wrap; gap: 2px; padding: 3px;
  background: rgba(12, 16, 22, 0.9); backdrop-filter: blur(6px);
  border: 1px solid var(--line-2); border-radius: var(--r);
}
.legend:empty { display: none; }
.legend div {
  display: flex; align-items: center; gap: 8px; padding: 4px 9px;
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.04em; color: var(--ink-2);
}
.legend i { width: 8px; height: 8px; border-radius: 50%; flex: none; }
/* A layer whose geometry is gated above the current zoom is listed but
   not drawn. Saying so beats listing a hue the map is not using. */
.legend div.is-dormant { color: var(--ink-4); }
.legend div.is-dormant i { opacity: 0.4; }

/* The strip scrimmed from 0.95 at its floor to 0.55 at its top edge,
   and 0.55 does not cover: the basemap's own city labels were legible
   THROUGH the readout, crossing behind its text. A scrim either hides
   what is under it or it is decoration. */
.readout {
  position: absolute; left: 0; right: 0; bottom: 0; height: 32px; z-index: 5;
  display: flex; align-items: center; padding: 0 14px;
  background: linear-gradient(to top, rgba(7, 10, 14, 0.97), rgba(7, 10, 14, 0.88));
  backdrop-filter: blur(3px);
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.06em; color: var(--ink-4);
  overflow-x: auto; white-space: nowrap; scrollbar-width: none;
}
.readout::-webkit-scrollbar { display: none; }
/* Dividers go BETWEEN items, so they belong to the leading edge of the
   second one. As a trailing border on every span, the last item drew a
   rule against nothing — and the empty attribution span at the end left
   the strip shipping with a stray divider after TERRAIN. */
.readout span:empty { display: none; }
.readout span + span {
  padding-left: 14px; margin-left: 14px; border-left: 1px solid var(--line-2);
}
.readout b { color: var(--ink-2); font-weight: 400; }
.readout .ro-view { display: inline-flex; align-items: center; gap: 8px; color: var(--ink-2); }
.readout .ro-view svg { width: 13px; height: 13px; color: var(--ac); }
.readout .ro-view b { color: var(--ink); font-weight: 500; letter-spacing: 0.08em; }
.readout .ro-view i { font-style: normal; color: var(--ink-4); }
.readout .att { margin-left: auto; border: 0; padding: 0; margin-right: 0; }
.readout .att a { color: var(--ink-4); }
.readout .att a:hover { color: var(--ink-3); }

/* ---------- 8. ENTITY TABLE ------------------------------- */
/* The region that separates a workstation from a map with pins:
   what is plotted, as a real sortable table, bound both ways to
   the markers. */
.splitter {
  height: 7px; background: var(--s1); border-top: 1px solid var(--line);
  cursor: row-resize; position: relative; touch-action: none;
}
.splitter::after {
  content: ""; position: absolute; left: 50%; top: 3px;
  width: 34px; height: 2px; margin-left: -17px; border-radius: var(--r-pill);
  background: var(--line-3);
}
.splitter:hover::after, .splitter:focus-visible::after { background: var(--ac); }
/* Collapsed, there is nothing to resize, so the grip leaves rather than
   sitting there taking hover. The dock's own top hairline is the edge. */
.centre.is-collapsed .splitter { display: none; }

.etable {
  background: var(--s1); border-top: 1px solid var(--line);
  display: flex; flex-direction: column; min-height: 0; overflow: hidden;
}
/* The control that folds the table lives inside the table, so folding it
   away used to take the only way back with it. Collapsed now keeps the
   dock's own 40px bar — heading, count and the expand control — and drops
   only the rows. The filter and the In-view toggle go with them: neither
   can act on a list that is not showing. */
.centre.is-collapsed .et-scroll,
.centre.is-collapsed .et-filter,
.centre.is-collapsed .et-toggle { display: none; }
.centre.is-collapsed .et-bar { border-bottom: 0; }
.centre.is-collapsed .et-close { margin-left: auto; }

.et-bar {
  display: flex; align-items: center; gap: 10px;
  height: 36px; padding: 0 14px; flex: none;
  border-bottom: 1px solid var(--line);
}
.et-bar h2 {
  margin: 0; font-size: var(--fs-micro); font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3);
}
.et-count { font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-4); white-space: nowrap; }
.et-filter {
  margin-left: auto; height: 28px; width: 210px; padding: 0 10px;
  background: var(--s3); border: 1px solid var(--line-2); border-radius: var(--r);
  font-size: var(--fs-body); color: var(--ink);
}
.et-filter::placeholder { color: var(--ink-2); }
.et-filter:focus { outline: none; border-color: var(--ac); box-shadow: 0 0 0 3px var(--ac-wash); }
.et-toggle {
  display: flex; align-items: center; gap: 7px; height: 28px; padding: 0 11px;
  background: var(--s3); border: 1px solid var(--line-2); border-radius: var(--r);
  font-size: var(--fs-body); color: var(--ink-2); white-space: nowrap;
}
.et-toggle:hover { border-color: var(--line-3); color: var(--ink); }
.et-toggle[aria-pressed="true"] {
  background: var(--ac-solid); border-color: var(--ac-solid); color: var(--on-ac);
}
.et-toggle[aria-pressed="true"]:hover { background: var(--ac); border-color: var(--ac); }
.et-toggle i {
  width: 8px; height: 8px; border-radius: 50%; background: var(--ink-4);
}
.et-toggle[aria-pressed="true"] i { background: var(--on-ac); }
.et-close { display: flex; color: var(--ink-3); padding: 6px; border-radius: var(--r); }
.et-close:hover { color: var(--ink); background: var(--s3); }
.et-close svg { width: 14px; height: 14px; }

.et-scroll { flex: 1; min-height: 0; overflow: auto; }
.etable table { width: 100%; border-collapse: separate; border-spacing: 0; }
.etable thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--s2); text-align: left;
  height: 26px; padding: 0 12px; white-space: nowrap;
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-3);
  border-bottom: 1px solid var(--line);
}
.etable thead th button {
  display: flex; align-items: center; gap: 6px; width: 100%;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
}
.etable thead th button:hover { color: var(--ink); }
.etable thead th svg { width: 9px; height: 9px; opacity: 0; flex: none; }
.etable thead th[aria-sort] { color: var(--ink); }
.etable thead th[aria-sort] svg { opacity: 1; color: var(--ac); }
.etable thead th[aria-sort="descending"] svg { transform: rotate(180deg); }

.etable tbody tr { cursor: pointer; }
.etable tbody tr:focus-visible {
  outline: 2px solid var(--ac); outline-offset: -2px; background: var(--s2);
}
.etable tbody tr:hover { background: var(--s2); }
.etable tbody tr.is-selected { background: var(--ac-wash); }
.etable tbody tr.is-selected td:first-child { box-shadow: inset 2px 0 0 var(--ac); }
/* 32px rows behind a 40px bar and a 30px header put five of two
   hundred and fifty rows on screen inside a 232px dock. 27px rows
   and a shorter header carry nine, which is the difference between
   a list you scan and a list you scroll. */
.etable tbody td {
  height: 27px; padding: 0 12px; border-bottom: 1px solid var(--line);
  font-size: var(--fs-body); color: var(--ink-2); white-space: nowrap;
  max-width: 320px; overflow: hidden; text-overflow: ellipsis;
}
.etable td.t-name { font-size: var(--fs-ui); color: var(--ink); font-weight: 500; }
/* Class is a two-value field here and the name cell's dot already
   carries it. It keeps its column so it can still be sorted on, at
   the weight of a tag rather than of a reading. */
.etable td.t-class {
  font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-4);
  letter-spacing: 0.03em;
}
/* Both numeric columns align on the same edge. Position was left and
   Range was right, in the same mono face, two columns apart. */
.etable th.t-num, .etable td.t-num { text-align: right; }
.etable th.t-num button { justify-content: flex-end; }
.etable td.t-name span { display: inline-flex; align-items: center; gap: 9px; }
.etable td.t-name i { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--e-landmark); }
.etable td.t-mono { font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-3); }
.etable td.t-conf { font-family: var(--mono); font-size: var(--fs-micro); }
.etable .c-confirmed { color: var(--pass); }
.etable .c-reported  { color: var(--warn); }
.et-empty {
  padding: 28px 14px; text-align: center;
  font-size: var(--fs-body); color: var(--ink-3);
}

/* ---------- 9. DETAIL PANEL ------------------------------- */
/* The one surface that floats above the shell rather than sitting
   beside it, so the one that carries a shadow. */
.inspector {
  background: var(--s2); border-left: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; min-height: 0; overflow: hidden;
  animation: panel-in 220ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes panel-in {
  from { transform: translateX(14px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
/* It arrived on an easing curve and left by being removed from the
   DOM mid-frame. A surface that animates in one direction only reads
   as two different surfaces. */
/* It leaves the grid the moment it starts leaving, so the column
   closes and the map takes the width back on the same frame rather
   than 150ms later. The panel plays out over the stage's right edge. */
.inspector.is-closing {
  position: absolute; right: 0; top: 0; bottom: 0; width: var(--insp);
  animation: panel-out 150ms cubic-bezier(0.4, 0, 1, 1) forwards;
  pointer-events: none;
}
@keyframes panel-out {
  from { transform: none; opacity: 1; }
  to   { transform: translateX(14px); opacity: 0; }
}
.insp-h { padding: 14px 16px; border-bottom: 1px solid var(--line); }
.insp-kind { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; }
.insp-kind i { width: 8px; height: 8px; border-radius: 50%; background: var(--e-camera); }
.insp-kind em {
  font-style: normal; font-size: var(--fs-micro); font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3);
}
.insp-kind button {
  margin-left: auto; color: var(--ink-3); display: flex; padding: 5px; border-radius: var(--r);
}
.insp-kind button:hover { color: var(--ink); background: var(--s4); }
.insp-kind svg { width: 14px; height: 14px; }
.insp-h h2 { margin: 0; font-size: var(--fs-title); font-weight: 600; letter-spacing: -0.015em; line-height: 1.3; }
.insp-h p { margin: 6px 0 0; font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-3); }
.insp-b { flex: 1; min-height: 0; overflow-y: auto; }

.feedbox {
  position: relative; aspect-ratio: 16 / 9; background: #000;
  border-bottom: 1px solid var(--line);
}
.feedbox video { width: 100%; height: 100%; object-fit: cover; display: block; }
.feedbox .state {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.1em; color: var(--ink-3);
}
.feedbox.is-live .state { display: none; }
.feedbox .live {
  position: absolute; top: 9px; left: 9px; display: none; align-items: center; gap: 6px;
  padding: 3px 8px; border-radius: var(--r-s); background: rgba(0, 0, 0, 0.7);
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.1em; color: var(--on-ac);
}
.feedbox.is-live .live { display: flex; }
.feedbox .live i { width: 6px; height: 6px; border-radius: 50%; background: var(--fail); }
.feedbox .ts {
  position: absolute; right: 9px; bottom: 9px; padding: 3px 8px; border-radius: var(--r-s);
  background: rgba(0, 0, 0, 0.7); font-family: var(--mono); font-size: var(--fs-micro); color: #D6DEE6;
}
.feedbox .fs {
  position: absolute; top: 9px; right: 9px; display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: var(--r-s); background: rgba(0, 0, 0, 0.7); color: #D6DEE6;
}
.feedbox .fs:hover { background: rgba(0, 0, 0, 0.85); color: var(--on-ac); }
.feedbox .fs svg { width: 14px; height: 14px; }
.feedbox:fullscreen { display: flex; align-items: center; justify-content: center; aspect-ratio: auto; }
.feedbox:fullscreen video { width: 100%; height: 100%; object-fit: contain; }
.feedbox:-webkit-full-screen { display: flex; align-items: center; justify-content: center; }
.feedbox:-webkit-full-screen video { width: 100%; height: 100%; object-fit: contain; }

/* Overhead chip. Same frame as .feedbox — a facility's imagery and a
   camera's stream are the same kind of thing in this panel — but the
   plate inside is a fixed pixel block of satellite tiles, positioned
   so the facility lands dead centre rather than scaled to fit. */
.chip {
  position: relative; aspect-ratio: 4 / 3; overflow: hidden;
  background: var(--void); border-bottom: 1px solid var(--line);
}
.chip-plate {
  position: absolute; top: 50%; left: 50%;
  opacity: 0; transition: opacity 240ms ease;
}
.chip.is-ready .chip-plate { opacity: 1; }
.chip-grid { display: grid; line-height: 0; }
.chip-grid img { display: block; width: 256px; height: 256px; }
/* The traced outline sits in the plate's own pixel space, so it moves
   with the tiles and cannot drift off the roof. */
.chip-fp {
  position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible;
}
.chip-fp path {
  fill: color-mix(in srgb, var(--e-datacenter) 18%, transparent);
  stroke: var(--e-datacenter); stroke-width: 2.5;
  stroke-linejoin: round; vector-effect: non-scaling-stroke;
}
.chip .state {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.1em; color: var(--ink-3);
}
.chip.is-ready .state { display: none; }
.chip .ts {
  position: absolute; right: 9px; bottom: 9px; padding: 3px 8px; border-radius: var(--r-s);
  background: rgba(0, 0, 0, 0.7); font-family: var(--mono); font-size: var(--fs-micro); color: #D6DEE6;
}

/* Networks present. A mono column because every value in it is a
   measured identifier, per the type rule at the top of this file. */
.nets { padding: 0 16px 4px; }
.nets h3 {
  display: flex; align-items: baseline; gap: 8px; margin: 14px 0 8px;
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3);
}
.nets h3 em {
  font-style: normal; font-family: var(--mono); letter-spacing: 0;
  color: var(--ink-2);
}
.nets-note { margin: 0; font-size: var(--fs-micro); line-height: 1.5; color: var(--ink-4); }
.nets-ix { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.ix-tag {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 3px 8px; border-radius: var(--r-s);
  border: 1px solid var(--line-2); background: var(--s3);
}
.ix-tag b { font-size: var(--fs-micro); font-weight: 500; color: var(--ink); }
.ix-tag s { text-decoration: none; font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-4); }
.nets-l { list-style: none; margin: 0; padding: 0; max-height: 236px; overflow-y: auto; }
.nets-l li {
  display: grid; grid-template-columns: 62px minmax(0, 1fr) auto;
  gap: 10px; align-items: baseline; padding: 5px 0;
  border-bottom: 1px solid var(--line);
}
.nets-l li:last-child { border-bottom: 0; }
.nets-l b {
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 400; color: var(--e-datacenter);
}
.nets-l span {
  font-size: var(--fs-body); color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nets-l s {
  text-decoration: none; font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-4);
}

.rows { padding: 4px 16px; margin: 0; }
.rows > div {
  display: grid; grid-template-columns: 84px minmax(0, 1fr); gap: 12px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.rows > div:last-child { border-bottom: 0; }
.rows dt {
  font-size: var(--fs-micro); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-4); padding-top: 2px;
}
.rows dd { margin: 0; font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink); overflow-wrap: anywhere; }

.pill {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.05em;
  color: var(--pass); background: rgba(63, 191, 127, 0.1);
  border: 1px solid rgba(63, 191, 127, 0.32); border-radius: var(--r-s); padding: 3px 8px;
}
.pill i { width: 6px; height: 6px; border-radius: 50%; background: var(--pass); }

.nearby { padding: 0 16px 16px; }
.nearby h3 {
  margin: 0; font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-4); padding: 16px 0 8px;
}
.nearby a {
  display: grid; grid-template-columns: 8px minmax(0, 1fr) auto; align-items: center; gap: 11px;
  height: 32px; margin: 0 -8px; padding: 0 8px; border-radius: var(--r);
}
.nearby a:hover { background: var(--s4); }
/* --ink-4 lands at 3.48:1 on the hover ground, so the range lifts with it. */
.nearby a:hover s { color: var(--ink-2); }
.nearby i { width: 8px; height: 8px; border-radius: 50%; background: var(--e-camera); }
.nearby i.is-off { background: var(--ink-4); }
.nearby b { font-size: var(--fs-body); font-weight: 400; color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nearby s { text-decoration: none; font-family: var(--mono); font-size: var(--fs-micro); color: var(--ink-4); }

.acts { display: grid; grid-template-columns: 1fr auto; gap: 9px;
  padding: 14px 16px; border-top: 1px solid var(--line); }
.acts.is-single { grid-template-columns: 1fr; }
.btn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  height: 34px; padding: 0 15px; border-radius: var(--r);
  font-size: var(--fs-body); font-weight: 600;
}
.btn svg { width: 15px; height: 15px; }
.btn-primary { background: var(--ac-solid); color: var(--on-ac); }
.btn-primary:hover { background: var(--ac); }
.btn-secondary { border: 1px solid var(--line-2); color: var(--ink-2); background: var(--s3); }
.btn-secondary:hover { border-color: var(--line-3); color: var(--ink); background: var(--s4); }

/* ---------- 11. MARKERS ----------------------------------- */
/* Marker chrome is deliberately small: at metro zoom a few hundred
   of these have to read as a constellation, not a wall of pins. */
.qd-marker { display: flex; align-items: center; gap: 6px; cursor: pointer; }
.qd-marker-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--qd-marker-colour); border: 1px solid rgba(6, 9, 12, 0.85);
  box-shadow: 0 0 0 2.5px color-mix(in srgb, var(--qd-marker-colour) 26%, transparent);
}
.qd-marker.is-selected .qd-marker-dot {
  width: 13px; height: 13px; background: transparent;
  border: 2px solid var(--ac); box-shadow: 0 0 0 4px var(--ac-wash);
}
.qd-marker-label {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.03em;
  color: var(--ink); background: rgba(8, 11, 16, 0.82); border-radius: var(--r-s);
  border: 1px solid rgba(48, 60, 74, 0.9); padding: 2px 6px; white-space: nowrap;
}
/* Dense layers keep their labels off until hover or selection. */
.qd-marker[data-type="camera"] .qd-marker-label,
.qd-marker[data-type="datacenter"] .qd-marker-label,
.qd-marker[data-type="aircraft"] .qd-marker-label { display: none; }
.qd-marker[data-type="camera"]:hover .qd-marker-label,
.qd-marker[data-type="camera"].is-selected .qd-marker-label,
.qd-marker[data-type="datacenter"]:hover .qd-marker-label,
.qd-marker[data-type="datacenter"].is-selected .qd-marker-label,
.qd-marker[data-type="aircraft"]:hover .qd-marker-label,
.qd-marker[data-type="aircraft"].is-selected .qd-marker-label { display: block; }

.qd-marker-aperture { width: 13px; height: 13px; flex: none; overflow: visible; }
/* Dark ring, not light: these sit on satellite imagery that runs from
   snow to asphalt, and a dark outline is the one that holds on both. */
.qd-marker-aperture-ring { fill: none; stroke: rgba(6, 9, 12, 0.85); }
.qd-marker-aperture-core, .qd-marker-aperture-wedge { fill: var(--qd-marker-colour); }
.qd-marker.is-selected .qd-marker-aperture { width: 19px; height: 19px; }
.qd-marker.is-selected .qd-marker-aperture-ring { stroke: var(--ac); stroke-width: 2; }

.qd-marker-plane { width: 14px; height: 14px; flex: none; overflow: visible; }
.qd-marker-plane-glyph { fill: var(--qd-marker-colour); stroke: rgba(6, 9, 12, 0.9); stroke-width: 0.6; }
.qd-marker-plane-pulse { fill: none; stroke: var(--qd-marker-colour); stroke-width: 1.2;
  transform-origin: 10px 10px; transform-box: fill-box; animation: blip 2.6s ease-out infinite; }
@keyframes blip { 0% { r: 3; opacity: 0.5; } 100% { r: 9; opacity: 0; } }

/* A facility is a building, so its marker is the only square one on
   the stage — silhouette separates the three live classes before hue
   does. Same dark shell as the aperture ring, for the same reason. */
.qd-marker-vault { width: 13px; height: 13px; flex: none; overflow: visible; }
.qd-marker-vault-shell {
  fill: rgba(6, 9, 12, 0.72); stroke: var(--qd-marker-colour);
}
.qd-marker-vault-bar { fill: var(--qd-marker-colour); }
.qd-marker.is-selected .qd-marker-vault { width: 19px; height: 19px; }
.qd-marker.is-selected .qd-marker-vault-shell { stroke: var(--ac); stroke-width: 2; }

/* Density: an entity folded into a count leaves the stage entirely
   rather than fading, because a field of half-visible pins is the
   thing being fixed. */
.qd-marker.is-clustered,
.qd-marker.is-hidden { display: none; }

/* One object, sized by what it holds, with the count punched out of
   it. The chip that used to hang off the side was the darkest and
   highest-contrast thing on the stage — near-black rectangles sitting
   on top of the satellite imagery this system says stays the brightest
   thing on screen, so the labels out-shouted the data they labelled.
   Dark text on the entity's own hue inverts that: the marker is bright,
   the count is a hole in it, and the imagery keeps the top of the
   value range. */
.qd-cluster {
  display: flex; cursor: pointer;
  -webkit-appearance: none; appearance: none;
  background: none; border: 0; padding: 0; margin: 0;
  color: inherit; font: inherit; line-height: 0;
}
.qd-cluster-puck {
  width: var(--qd-clu, 22px); height: var(--qd-clu, 22px);
  border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--qd-marker-colour); border: 1px solid rgba(6, 9, 12, 0.55);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--qd-marker-colour) 22%, transparent);
  transition: box-shadow 120ms ease;
}
.qd-cluster:hover .qd-cluster-puck,
.qd-cluster:focus-visible .qd-cluster-puck {
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--qd-marker-colour) 30%, transparent);
}
.qd-cluster-count {
  font-family: var(--mono); font-size: var(--fs-micro); font-weight: 600;
  line-height: 1; letter-spacing: 0.01em;
  color: rgba(7, 10, 14, 0.92);
  font-variant-numeric: tabular-nums;
}
.qd-cluster:focus-visible { outline: 2px solid var(--ac); outline-offset: 3px; }

/* globe states */
.globe-state {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 3;
  display: none; padding: 12px 16px; text-align: center; border-radius: var(--r);
  background: var(--s2); border: 1px solid var(--line-2); box-shadow: var(--shadow);
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-3);
}
.qd-globe.is-loading .globe-state[data-state="loading"] { display: block; }
.qd-globe.is-failed  .globe-state[data-state="failed"]  { display: block; }
.qd-globe.is-failed  .globe-state[data-state="failed"]::after {
  content: attr(data-detail); display: block; margin-top: 7px;
  letter-spacing: 0.03em; text-transform: none; color: var(--ink-2);
}
.qd-globe.is-failed ~ .hud-tr { display: none; }

/* ---------- 12. CONTENT PAGES ----------------------------- */
/* Same bar and rail, ordinary scrolling document underneath. */
.app.is-page { position: static; height: auto; min-height: 100vh; grid-template-rows: var(--bar) 1fr; }
.app.is-page .bar { position: sticky; top: 0; z-index: 50; }
.app.is-page .app-body { min-height: calc(100vh - var(--bar)); }
.app.is-page .rail { position: sticky; top: var(--bar); height: calc(100vh - var(--bar)); }

.doc { padding: 48px 56px 80px; overflow-y: auto; }
.doc-in { max-width: 1000px; }

.doc .crumb { font-family: var(--mono); font-size: 12px; color: var(--ink-4); margin-bottom: 26px; }
.doc .crumb a { color: var(--ink-3); }
.doc .crumb a:hover { color: var(--ink-2); }

.doc h1 { margin: 0 0 16px; font-size: 40px; font-weight: 600; letter-spacing: -0.03em; line-height: 1.08; text-wrap: balance; }
.doc h2 { margin: 0 0 12px; font-size: 20px; font-weight: 600; letter-spacing: -0.015em; text-wrap: balance; }
.doc .lede { margin: 0; font-size: 16px; line-height: 1.65; color: var(--ink-2); max-width: 62ch; }
.doc p { font-size: 15px; line-height: 1.7; color: var(--ink-2); max-width: 68ch; }

.doc-head { display: flex; align-items: flex-end; gap: 48px; margin-bottom: 40px; }
.doc-figs { display: flex; gap: 40px; flex: none; padding-bottom: 6px; }
.doc-fig b { display: block; font-size: 28px; font-weight: 600; letter-spacing: -0.02em; line-height: 1; }
.doc-fig s {
  display: block; text-decoration: none; margin-top: 8px;
  font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-4);
}

.rule { height: 1px; background: var(--line); margin: 44px 0; }

.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
@media (max-width: 1180px) { .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 720px)  { .grid-3 { grid-template-columns: minmax(0, 1fr); } }

.card {
  display: block; border: 1px solid var(--line); background: var(--s1);
  border-radius: var(--r); padding: 20px;
}
.card:hover { border-color: var(--line-3); background: var(--s2); }
.card h3 { margin: 0 0 8px; font-size: 15.5px; font-weight: 600; letter-spacing: -0.01em; }
.card p { margin: 0; font-size: 13.5px; line-height: 1.6; color: var(--ink-3); max-width: none; }
.card-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.card-top svg { width: 18px; height: 18px; }
.card-top s { text-decoration: none; font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.08em; color: var(--ink-4); }
.card.is-soon h3 { color: var(--ink-2); }

.tag {
  font-family: var(--mono); font-size: 11.5px; color: var(--ink-2);
  border: 1px solid var(--line-2); border-radius: var(--r-s); padding: 3px 8px;
}
.tags { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }

.doc-sub { display: flex; align-items: baseline; gap: 14px; margin-bottom: 20px; }
.doc-sub h2 { margin: 0; }
.doc-sub p { margin: 0; font-size: 13.5px; color: var(--ink-3); }

/* Feature card — the one shipped tool. A single card in a three-column
   grid reads as a gap where two more should be; giving the live tool the
   full width and a sample of its own output makes the row honest. */
.card-feature { display: grid; grid-template-columns: minmax(0, 1fr) 356px; padding: 0; overflow: hidden; }
.feature-main { padding: 28px 30px 26px; }
.feature-meta { margin-bottom: 14px; }
.feature-note { font-family: var(--mono); font-size: 11.5px; color: var(--ink-4); }
.card-feature h2 { margin: 0 0 10px; font-size: 23px; letter-spacing: -0.02em; }
.card-feature p { margin: 0 0 20px; font-size: 15px; line-height: 1.65; color: var(--ink-2); max-width: 52ch; }
.feature-cta { display: inline-flex; margin-top: 22px; height: 36px; }

.feature-side { border-left: 1px solid var(--line); background: var(--s2); padding: 22px 24px; }
/* Two classes deep on purpose: `.card p` above would otherwise win. */
.feature-side .feature-side-h {
  margin: 0 0 14px; font-family: var(--mono); font-size: 11px; line-height: 1.4;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-4);
}
.verdicts { margin: 0 0 14px; }
.verdicts > div { display: grid; grid-template-columns: 56px minmax(0, 1fr); padding: 3px 0; }
.verdicts dt { font-family: var(--mono); font-size: 12px; color: var(--ink-4); }
.verdicts dd { margin: 0; font-family: var(--mono); font-size: 12px; display: flex; align-items: center; gap: 7px; }
.verdicts dd::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.verdicts .is-pass { color: var(--pass); }
.verdicts .is-warn { color: var(--warn); }

.hops { margin: 0; padding: 12px 0 0; border-top: 1px solid var(--line); list-style: none;
  counter-reset: hop; font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); line-height: 1.9; }
.hops li { counter-increment: hop; }
.hops li::before { content: counter(hop); color: var(--ink-4); margin-right: 9px; }
.feature-side .hops-note {
  margin: 8px 0 0; font-family: var(--mono); font-size: 11.5px; line-height: 1.55; color: var(--warn);
}
.feature-side .hops-note::before { content: "! "; }

@media (max-width: 1100px) {
  .card-feature { grid-template-columns: minmax(0, 1fr); }
  .feature-side { border-left: 0; border-top: 1px solid var(--line); }
}

/* ---------- 13. MOTION + RESPONSIVE ----------------------- */
.bar-tab, .layer, .card, .btn, .nearby a, .bar-ico, .omni,
.et-toggle, .et-close, .hud-tr button, .etable tbody tr, .etable thead th button {
  transition: color 120ms ease, background-color 120ms ease, border-color 120ms ease;
}

@media (max-width: 1280px) {
  :root { --rail: 232px; --insp: 312px; }
  /* Moving the count up to the title line bought the provenance line
     about 60px, which is what 280px needed to hold "County assessors ·
     nightly" whole. 232px is still short of it, so the cadence tail
     leaves here and the health dot carries the state — the same
     principle as the icon rail dropping labels rather than shrinking
     them. The name is the part worth keeping. */
  .layer .sx { display: none; }
}

/* Columns drop by how much they earn their width, not all at once:
   the source string is the widest and the most repetitive, so it
   goes first; position goes next; name, class, confidence and range
   are the four that must survive to the narrowest screen. */
@media (max-width: 1240px) { .et-hide-md { display: none; } }
/* The column budget is set by viewport width, but the inspector takes
   344px out of the table's own width when it opens — so at the sizes
   most people run this, opening a panel pushed Range off its edge and
   the dock shipped readings truncated mid-digit ("2658 k"). A column
   the panel is going to cut in half is better spent on the panel. */
@media (max-width: 1600px) {
  .app-body.has-inspector .et-hide-md { display: none; }
}
@media (max-width: 900px)  { .et-hide-sm { display: none; } }

@media (max-width: 1080px) {
  :root { --rail: 56px; }
  /* At icon width a feed row and a key/value block are bare dots and
     orphaned values — no label, no meaning. They leave rather than
     degrade into decoration; the layer toggles keep their colour,
     their pressed state and their accessible name. */
  .rail-sec:not(:first-child) { display: none; }
  .rail-h, .layer .t b, .layer .ct, .layer .src { display: none; }
  /* A branch whose name is hidden is not a branch, it is an indent with
     no explanation — so at icon width the tree flattens honestly: the
     headers and the spine go, every group is forced open, and what is
     left is the set of keys. The grouping returns with the labels. */
  .grp-h, .grp-b::before { display: none; }
  .grp.is-closed .grp-b { display: block; }
  .grp + .grp { border-top: 1px solid var(--line); padding-top: 4px; margin-top: 2px; }
  .grp-b .layer { padding-left: 0; }
  .layer { grid-template-columns: 1fr; justify-items: center; padding: 0; }
  /* 56px has room for one glyph, and the key is the one that cannot be
     inferred — with the labels gone, hue is the only thing telling the
     rows apart. So the key absorbs the state the checkbox was carrying,
     and it does it by going hollow in its own colour rather than grey,
     which keeps the class readable in both states. */
  .layer .ck { display: none; }
  .layer .t, .layer .tt { display: flex; justify-content: center; min-width: 0; }
  .layer .tt > i { width: 12px; height: 12px; }
  .layer[aria-pressed="false"] .tt > i {
    background: none; box-shadow: inset 0 0 0 1.5px var(--lc);
  }

  .app-body.has-inspector { grid-template-columns: var(--rail) minmax(0, 1fr); }
  /* The panel stops being a column and becomes a sheet over the shell,
     so it needs a ground of its own to read as a layer rather than as
     a torn-off piece of the table. */
  .app-body.has-inspector::after {
    content: ""; position: fixed; inset: 0; z-index: 55;
    background: rgba(4, 6, 9, 0.5);
  }
  .app-body.has-inspector .inspector {
    position: fixed; top: var(--bar); right: 0; bottom: 0;
    width: min(420px, 92vw); z-index: 60; border-left: 1px solid var(--line-2);
  }
  .et-filter { width: 150px; }
  .doc { padding: 32px 24px 64px; }
  .doc-head { flex-direction: column; align-items: flex-start; gap: 28px; }
  .doc h1 { font-size: 32px; }
}
@media (max-width: 900px) {
  /* The wordmark yields before the navigation does. Losing the way to
     the rest of the site is not a density trade. */
  .bar-mark { padding-right: 12px; margin-right: 10px; }
  .bar-mark b { display: none; }
  .omni { display: none; }
  .bar-tab { padding: 0 10px; }
}
@media (max-width: 760px) {
  :root { --tableh: 200px; }
  .bar { padding: 0 8px 0 10px; }
  .et-bar { gap: 8px; }
  .et-filter { flex: 1; width: auto; min-width: 0; }
  .et-toggle span { display: none; }
  .et-toggle { padding: 0 9px; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 1ms !important; transition-duration: 1ms !important; }
}
