/* THUGS(red) Wardrive - flat dark UI.
   Single stylesheet, no build step. Custom properties carry the palette so a
   light theme can be added later with [data-theme="light"] overrides. */

:root {
  --bg: #0e1013;
  --surface: #16191f;
  --surface-2: #1d2129;
  --line: #272c36;
  --text: #e6e9ef;
  --muted: #97a0b0;
  --accent: #e2223b;
  --accent-dim: #8f1626;
  --ok: #35c98b;
  --warn: #e8a33d;
  --bad: #e2223b;
  --radius: 6px;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  /* Fixed header geometry, so the bar does not change height on sign-in. */
  --topbar-height: 3.75rem;
  --account-control-height: 2.25rem;
}

* { box-sizing: border-box; }

/* Always reserve room for the vertical scrollbar, so navigating between a short
   page and a long one does not shift the layout sideways. scrollbar-gutter is
   the modern way to reserve the space; overflow-y covers browsers without it. */
html {
  overflow-y: scroll;
  scrollbar-gutter: stable;
  /* Match the scrollbar to the dark UI rather than leaving a light strip. */
  scrollbar-color: var(--line) var(--bg);
  scrollbar-width: thin;
}

/* WebKit/Blink equivalent, since they do not implement scrollbar-color. */
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--line); border: 3px solid var(--bg); border-radius: 6px; }
::-webkit-scrollbar-thumb:hover { background: #39404e; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-text-size-adjust: 100%;
}

body.centered { display: grid; place-items: center; min-height: 100vh; }

a { color: var(--text); text-decoration: none; }
a:hover { color: #fff; }
main a:not(.cta) { color: #9fc4ff; text-decoration: underline; text-underline-offset: 2px; }

h1 { font-size: 1.65rem; margin: 0 0 .6rem; letter-spacing: -.01em; }
h2 { font-size: 1.15rem; margin: 2rem 0 .6rem; }
.muted { color: var(--muted); }
.mono { font-family: var(--mono); font-size: .92em; }

.skip { position: absolute; left: -9999px; }
.skip:focus { left: 1rem; top: 1rem; background: var(--accent); padding: .5rem 1rem; z-index: 10; }

/* ---- Layout ---- */
/* The bar is a fixed height regardless of what the account area contains.
   Signed out it holds a "Join" button (padded); signed in it holds plain text and
   a link-styled button. Without a fixed height and equal-height children, the
   header visibly changes height on sign-in. */
.topbar {
  display: flex; align-items: center; gap: 1.5rem;
  min-height: var(--topbar-height);
  padding: 0 1.25rem;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.brand { font-weight: 700; letter-spacing: -.02em; }
.brand-red { color: var(--accent); }
.brand-sub { font-weight: 400; color: var(--muted); }

.topbar nav { display: flex; gap: 1rem; margin-right: auto; align-items: center; height: var(--account-control-height); }
.topbar nav a { display: inline-flex; align-items: center; color: var(--muted); border-bottom: 2px solid transparent; height: 100%; }
.topbar nav a:hover { color: var(--text); }
.topbar nav a[aria-current="page"] { color: var(--text); border-bottom-color: var(--accent); }

/* Every child is the same height, so the signed-in and signed-out headers are
   dimensionally identical. */
.account { display: flex; align-items: center; gap: .9rem; font-size: .9rem; height: var(--account-control-height); }
.account form { margin: 0; display: flex; align-items: center; height: 100%; }
.account > a,
.account button,
.account .who { display: inline-flex; align-items: center; height: var(--account-control-height); }
.account .cta { padding-block: 0; }
.account button.link { height: var(--account-control-height); }
.who { color: var(--muted); gap: .4rem; }

.role { font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; padding: .1rem .35rem; border-radius: 3px; }
.role-admin { background: var(--accent-dim); color: #fff; }
.role-moderator { background: #2b4a6f; color: #fff; }
.role-member { background: var(--surface-2); color: var(--muted); }

main { max-width: 1180px; margin: 0 auto; padding: 1.75rem 1.25rem 4rem; }
.footer { border-top: 1px solid var(--line); color: var(--muted); padding: 1.25rem; text-align: center; font-size: .85rem; }

/* ---- Buttons and forms ---- */
button, .cta {
  font: inherit; cursor: pointer;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .5rem .9rem;
}
button:hover, .cta:hover { border-color: #3a4150; }
.cta { background: var(--accent); border-color: var(--accent); color: #fff; display: inline-block; font-weight: 600; }
.cta:hover { background: #c81c33; color: #fff; }
.cta-ghost { background: transparent; border-color: var(--line); color: var(--text); }
button.danger { background: var(--accent-dim); border-color: var(--accent-dim); color: #fff; }
button.link, a.link { background: none; border: none; padding: 0; color: var(--muted); text-decoration: underline; }

label { display: block; margin-bottom: .9rem; color: var(--muted); font-size: .9rem; }
input, select {
  display: block; width: 100%; margin-top: .3rem;
  font: inherit; color: var(--text);
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .5rem .6rem;
}
input:focus, select:focus, button:focus-visible, a:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
label small { display: block; margin-top: .25rem; color: var(--muted); font-size: .8rem; }

.form-card { max-width: 26rem; margin: 2rem auto; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.5rem; }

.filters { display: flex; gap: .9rem; align-items: flex-end; flex-wrap: wrap; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem; margin-bottom: 1.25rem; }
.filters label { margin: 0; min-width: 9rem; }
.filters input, .filters select { min-width: 9rem; }

/* ---- Flash messages ---- */
.flashes { list-style: none; margin: 0; padding: .75rem 1.25rem 0; max-width: 1180px; margin-inline: auto; }
.flash { padding: .6rem .9rem; border-radius: var(--radius); margin-bottom: .5rem; border-left: 3px solid var(--muted); background: var(--surface); }
.flash-success { border-left-color: var(--ok); }
.flash-error { border-left-color: var(--bad); }

/* ---- Hero and tiles ---- */
.hero { padding: 1rem 0 2rem; max-width: 46rem; }
.hero p { color: var(--muted); font-size: 1.05rem; }
.hero-actions { display: flex; gap: .75rem; margin-top: 1.25rem; }

.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: .9rem; }
.tile { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.1rem; }
.tile-value { display: block; font-size: 1.9rem; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.tile-label { display: block; color: var(--muted); font-size: .82rem; text-transform: uppercase; letter-spacing: .06em; margin-top: .2rem; }

.empty { text-align: center; padding: 3rem 1rem; color: var(--muted); }
.empty h1 { font-size: 3rem; }

/* ---- Tables ---- */
.table-scroll { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); }
table.data { width: 100%; border-collapse: collapse; font-size: .9rem; }
table.data th, table.data td { text-align: left; padding: .55rem .7rem; border-bottom: 1px solid var(--line); white-space: nowrap; }
table.data thead th { background: var(--surface-2); color: var(--muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .05em; position: sticky; top: 0; }
table.data thead th a { color: inherit; text-decoration: none; }
table.data thead th a:hover { color: var(--text); }
table.data tbody tr:hover { background: var(--surface); }
table.data td.num { text-align: right; font-variant-numeric: tabular-nums; }
table.data tr.is-hidden { opacity: .55; }
.empty-cell { text-align: center; color: var(--muted); padding: 2rem; }
.result-count { color: var(--muted); font-size: .88rem; }

.badge { font-size: .7rem; text-transform: uppercase; padding: .1rem .35rem; border-radius: 3px; margin-left: .4rem; }
.badge-hidden { background: var(--accent-dim); color: #fff; }

/* Encryption chips.
   Encryption is an ORDERED scale (open → wpa3), so it wears an ordinal ramp: one
   hue, light = weakest, dark = strongest. The previous red/orange/amber/green set
   was validated and failed every gate — open against wep measured ΔE 3.7 under
   deuteranopia and 10.3 with full colour vision, i.e. indistinguishable for
   everyone. `unknown` sits outside the ramp in neutral gray because it is missing
   information, not a level.
   Risk is carried by the adjacent "insecure" label, never by hue alone. */
.enc { font-size: .78rem; font-weight: 600; padding: .15rem .4rem; border-radius: 3px; }
.enc-open, .bar-fill.enc-open { background: #cde2fb; color: #11151b; }
.enc-wep, .bar-fill.enc-wep { background: #9ec5f4; color: #11151b; }
.enc-wpa, .bar-fill.enc-wpa { background: #6da7ec; color: #11151b; }
.enc-wpa2, .bar-fill.enc-wpa2 { background: #3987e5; color: #fff; }
.enc-wpa2_wpa3, .bar-fill.enc-wpa2_wpa3 { background: #256abf; color: #fff; }
.enc-wpa3, .bar-fill.enc-wpa3 { background: #184f95; color: #fff; }
.enc-unknown, .bar-fill.enc-unknown { background: #6b7280; color: #fff; }

/* Risk stated in words, because the status palette does not survive the
   colour-vision gates as a series. */
.enc-insecure-flag { color: var(--accent); font-weight: 600; font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; margin-left: .3rem; }

.pager { display: flex; gap: 1rem; margin-top: 1rem; }

/* ---- Detail page ---- */
.detail header { margin-bottom: 1.5rem; }
.subtitle { color: var(--muted); margin: .2rem 0 0; }
.banner { padding: .7rem .9rem; border-radius: var(--radius); margin-top: 1rem; }
.banner-hidden { background: rgba(226, 34, 59, .12); border: 1px solid var(--accent-dim); }

.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: .8rem; margin: 0; }
.facts dt { color: var(--muted); font-size: .76rem; text-transform: uppercase; letter-spacing: .05em; }
.facts dd { margin: .15rem 0 0; font-variant-numeric: tabular-nums; }

.moderation { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.1rem; margin: 1.75rem 0; }
.moderation form { display: flex; gap: .75rem; align-items: flex-end; flex-wrap: wrap; }

/* ---- Stat bars ---- */
.bars { list-style: none; padding: 0; margin: 0; }
.bars li { display: grid; grid-template-columns: 7rem 1fr 9rem; gap: .75rem; align-items: center; margin-bottom: .45rem; font-size: .88rem; }
.bar-track { background: var(--surface-2); border-radius: 3px; height: 1.1rem; overflow: hidden; }
.bar-fill { display: block; height: 100%; background: var(--accent); }
.bar-value { color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }

/* ---- Map ---- */
.map-toolbar { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }
.map-toolbar fieldset { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); padding: .6rem .9rem; }
.map-toolbar legend { color: var(--muted); font-size: .76rem; text-transform: uppercase; letter-spacing: .05em; padding: 0 .3rem; }
.map-toolbar label { display: inline-flex; align-items: center; gap: .3rem; margin: 0 .8rem 0 0; color: var(--text); }
.map-toolbar input { width: auto; margin: 0; }
.map-toolbar .map-status { margin: 0 0 0 auto; align-self: center; color: var(--muted); font-size: .85rem; }
.map-canvas { height: 65vh; min-height: 24rem; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
.map-attribution { font-size: .78rem; margin-top: .6rem; }

/* Leaflet overrides: the default light chrome clashes with the dark UI. */
.leaflet-container { background: #0b0d10; font: inherit; }
.leaflet-popup-content-wrapper, .leaflet-popup-tip { background: var(--surface-2); color: var(--text); box-shadow: 0 2px 12px rgba(0, 0, 0, .5); }
.leaflet-popup-content-wrapper { border-radius: var(--radius); }
.leaflet-popup-content { margin: .7rem .9rem; font-size: .88rem; }
.leaflet-popup-content code { font-family: var(--mono); font-size: .85em; color: var(--muted); }
.leaflet-container a.leaflet-popup-close-button { color: var(--muted); }
.leaflet-bar, .leaflet-bar a { background: var(--surface-2); color: var(--text); border-color: var(--line); }
.leaflet-bar a:hover { background: var(--line); color: #fff; }
.leaflet-control-attribution { background: rgba(14, 16, 19, .8) !important; color: var(--muted); }
.leaflet-control-attribution a { color: #9fc4ff; }

table.popup-facts { border-collapse: collapse; margin: .5rem 0; font-size: .84rem; }
table.popup-facts th { text-align: left; padding: .1rem .6rem .1rem 0; color: var(--muted); font-weight: 400; }
table.popup-facts td { padding: .1rem 0; font-variant-numeric: tabular-nums; }

/* ---- Uploads, tokens, admin ---- */
.upload-grid { display: grid; grid-template-columns: minmax(18rem, 24rem) 1fr; gap: 2rem; align-items: start; }
.upload-form { margin: 0; }
.upload-help h2 { margin-top: 0; }
.upload-help h2 ~ h2 { margin-top: 1.75rem; }
.upload-help ul { color: var(--muted); padding-left: 1.2rem; }
.upload-help code { font-family: var(--mono); font-size: .85em; background: var(--surface); padding: .1rem .3rem; border-radius: 3px; }

.code-block { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: .9rem 1rem; overflow-x: auto; font-family: var(--mono); font-size: .8rem; line-height: 1.5; }
.code-block code { background: none; padding: 0; }

/* Upload/moderation status chips. */
.status { font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; padding: .15rem .4rem; border-radius: 3px; background: var(--surface-2); color: var(--muted); white-space: nowrap; }
.status-approved { background: #2f7f5b; color: #fff; }
.status-rejected, .status-failed { background: var(--accent-dim); color: #fff; }
.status-needs_review, .status-received { background: var(--warn); color: #17191d; }
.status-processing { background: #2b4a6f; color: #fff; }
.status-pending { background: var(--warn); color: #17191d; }
.status-active { background: #2f7f5b; color: #fff; }
.status-suspended, .status-banned { background: var(--accent-dim); color: #fff; }

.badge:not(.badge-hidden) { background: var(--surface-2); color: var(--muted); }

.skip-list { list-style: none; padding: 0; color: var(--muted); font-size: .88rem; }
.skip-list code { font-family: var(--mono); }

.inline-form { display: inline-flex; gap: .4rem; align-items: center; margin: 0; }
.inline-form select { width: auto; margin: 0; padding: .3rem .4rem; font-size: .85rem; }
.inline-form button { padding: .3rem .6rem; font-size: .85rem; }

.inline-list { list-style: none; padding: 0; display: flex; gap: 1.25rem; flex-wrap: wrap; }
.admin-nav { display: flex; gap: .75rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
.settings-form { max-width: 32rem; margin-inline: 0; }
.settings-form label.checkbox { display: flex; align-items: center; gap: .5rem; color: var(--text); }
.settings-form label.checkbox input { width: auto; margin: 0; }

.banner-token { background: rgba(53, 201, 139, .1); border: 1px solid #2f7f5b; padding: 1rem 1.1rem; border-radius: var(--radius); margin-bottom: 1.5rem; }
.banner-token h2 { margin-top: 0; }
.token-value { font-family: var(--mono); font-size: .95rem; background: var(--bg); border: 1px solid var(--line); padding: .5rem .7rem; border-radius: var(--radius); display: inline-block; word-break: break-all; user-select: all; }

/* ---- Prose pages (about, guides) ---- */
.prose { max-width: 46rem; }
.prose .lede { font-size: 1.12rem; color: var(--text); }
.prose p, .prose li { color: var(--muted); }
.prose h2 { margin-top: 2.25rem; padding-top: .5rem; border-top: 1px solid var(--line); }
.prose h3 { margin-top: 1.5rem; font-size: 1rem; color: var(--text); }
.prose ul, .prose ol { padding-left: 1.3rem; }
.prose li { margin-bottom: .4rem; }
.prose code { font-family: var(--mono); font-size: .86em; background: var(--surface); padding: .1rem .3rem; border-radius: 3px; color: var(--text); }
.prose strong { color: var(--text); }
.prose .facts { margin: 1rem 0; }
.prose .facts dd { color: var(--muted); }
.prose .cta { color: #fff; }

.page-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; flex-wrap: wrap; }
.page-head h1 { margin-bottom: .3rem; }

/* ---- Guides ---- */
.guide-nav { display: flex; gap: .9rem; flex-wrap: wrap; margin-bottom: 1.5rem; padding-bottom: .9rem; border-bottom: 1px solid var(--line); font-size: .88rem; }
.guide-nav a { color: var(--muted); }
.guide-nav a[aria-current="page"] { color: var(--text); font-weight: 600; }

/* Guide diagrams. The SVGs carry their own dark background so they read the same
   opened on their own as they do embedded, hence no fill here - just the frame. */
.guide-figure { margin: 1.5rem 0; }
.guide-figure a { display: block; border-radius: var(--radius); }
.guide-figure img { display: block; width: 100%; height: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.guide-figure a:hover img { border-color: var(--accent); }
.guide-figure figcaption { margin-top: .5rem; color: var(--muted); font-size: .85rem; }
.guide-figure a.download { color: var(--accent); }

.guide-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1rem; margin: 1.5rem 0 2rem; }
.guide-card { display: block; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.1rem; text-decoration: none !important; }
.guide-card:hover { border-color: var(--accent); }
.guide-card h2 { margin: 0 0 .4rem; font-size: 1.02rem; border: none; padding: 0; color: var(--text); }
.guide-card p { margin: 0 0 .7rem; font-size: .9rem; }
.guide-card-more { color: var(--accent); font-size: .85rem; font-weight: 600; }

/* ---- API reference ----
   Wider than ordinary prose because the reference is mostly tables, and the
   nowrap that keeps the network list scannable would make a description column
   unreadable. Anchors get scroll-margin so a #fragment does not land under the
   sticky table headers. */
.api-doc { max-width: 62rem; }
.api-doc h2, .api-doc h3 { scroll-margin-top: 1.5rem; }
.api-doc table.data td { white-space: normal; vertical-align: top; }
.api-doc table.data td code { white-space: nowrap; }
/* Definition terms are uppercased for scanning, but an error string or a URL in
   one is a literal - "INVALID_BSSID" is not what the API returns. */
.api-doc .facts dt code { text-transform: none; }
.api-doc .banner { margin: 1rem 0; }

.api-toc { display: flex; gap: .9rem; flex-wrap: wrap; margin-bottom: 1.5rem; padding-bottom: .9rem; border-bottom: 1px solid var(--line); font-size: .88rem; }
.api-toc a { color: var(--muted); }
.api-toc a:hover { color: var(--text); }

/* ---- Forum ---- */
.thread .post { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem 1.1rem; margin-bottom: .9rem; }
.thread .post > header { display: flex; gap: .6rem; align-items: baseline; flex-wrap: wrap; margin-bottom: .6rem; padding-bottom: .5rem; border-bottom: 1px solid var(--line); font-size: .9rem; }
.thread .post-time { font-size: .82rem; margin-left: auto; }
.post-deleted { opacity: .6; border-style: dashed; }
.post-body p { margin: 0 0 .7rem; }
.post-body p:last-child { margin-bottom: 0; }
.post-body { overflow-wrap: anywhere; }
.post-actions { margin-top: .7rem; padding-top: .5rem; border-top: 1px solid var(--line); }
.reply-form { max-width: none; margin: 1.5rem 0 0; }
textarea { display: block; width: 100%; margin-top: .3rem; font: inherit; font-family: inherit; color: var(--text); background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius); padding: .6rem; resize: vertical; line-height: 1.5; }
textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- Webhooks ---- */
.webhook-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.1rem; margin-bottom: 1rem; }
.webhook-card h3 { margin: 0 0 .3rem; font-size: 1rem; }
.webhook-actions { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: .8rem; padding-top: .8rem; border-top: 1px solid var(--line); }
.events-fieldset { border: 1px solid var(--line); border-radius: var(--radius); padding: .8rem 1rem; margin: 0 0 1rem; }
.events-fieldset legend { color: var(--muted); font-size: .76rem; text-transform: uppercase; letter-spacing: .05em; padding: 0 .3rem; }
.events-fieldset label.checkbox { margin-bottom: .35rem; font-size: .9rem; }
.events-fieldset code { font-family: var(--mono); font-size: .78em; color: var(--muted); }

/* Footer nav */
.footer nav { display: flex; gap: 1rem; justify-content: center; margin-top: .6rem; flex-wrap: wrap; }
.footer nav a { color: var(--muted); }
.footer nav a:hover { color: var(--text); }

.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

@media (max-width: 860px) {
  .upload-grid { grid-template-columns: 1fr; }
}

@media (max-width: 700px) {
  /* The nav wraps onto its own row here, so the fixed height has to relax. */
  .topbar { gap: .75rem; min-height: 0; padding: .7rem 1rem; }
  .topbar nav { order: 3; width: 100%; overflow-x: auto; height: auto; }
  .bars li { grid-template-columns: 5rem 1fr; }
  .bar-value { grid-column: 2; text-align: left; }
  main { padding: 1.25rem 1rem 3rem; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* Members-only thread affordances. */
.badge-members { background: #2b4a6f; color: #fff; }
.banner-members { background: rgba(43, 74, 111, .18); border: 1px solid #2b4a6f; }
.audience-fieldset { border: 1px solid var(--line); border-radius: var(--radius); padding: .8rem 1rem; margin: 0 0 1rem; }
.audience-fieldset legend { color: var(--muted); font-size: .76rem; text-transform: uppercase; letter-spacing: .05em; padding: 0 .3rem; }
.audience-fieldset label.checkbox { margin-bottom: .35rem; color: var(--text); }
.audience-fieldset input { width: auto; margin: 0; }
.audience-fieldset small { display: block; margin-top: .5rem; color: var(--muted); font-size: .8rem; }
.moderation label.checkbox { display: flex; align-items: center; gap: .5rem; color: var(--text); margin-bottom: .4rem; }
.moderation label.checkbox input { width: auto; margin: 0; }

/* ---- Map basemap brightness ----
   The filter is applied to the tile pane only. Leaflet keeps markers, polylines
   and popups in separate panes, so lifting the basemap does not wash out the data
   drawn on top of it. */
.map-canvas { --tile-brightness: 1.15; --tile-contrast: 0.98; }
.map-canvas .leaflet-tile-pane {
  filter: brightness(var(--tile-brightness)) contrast(var(--tile-contrast));
}

.brightness-control { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); padding: .35rem .9rem .6rem; display: flex; align-items: center; gap: .6rem; }
.brightness-control legend { color: var(--muted); font-size: .76rem; text-transform: uppercase; letter-spacing: .05em; padding: 0 .3rem; }
.brightness-control input[type="range"] { width: 9rem; margin: 0; padding: 0; background: none; border: none; accent-color: var(--accent); }
.brightness-control output { color: var(--muted); font-size: .82rem; font-variant-numeric: tabular-nums; min-width: 2.8rem; }

/* Leaflet's layer switcher, restyled for the dark chrome. */
.leaflet-control-layers { background: var(--surface-2) !important; border: 1px solid var(--line) !important; border-radius: var(--radius); color: var(--text); }
.leaflet-control-layers-toggle { filter: invert(1) hue-rotate(180deg); }
.leaflet-control-layers-expanded { padding: .6rem .8rem; font-size: .88rem; }
.leaflet-control-layers-list label { display: flex; align-items: center; gap: .4rem; margin: 0 0 .25rem; color: var(--text); }
.leaflet-control-layers-list input { width: auto; margin: 0; }

/* ============================================================================
   Charts
   Server-rendered inline SVG. Colour roles are custom properties so the light/dark
   values would swap in one place; this site is dark-only today.
   Specs held fixed across every chart: hairline solid grid, thin marks, 2px
   surface gaps, text in ink tokens and never in a series colour.
   ========================================================================== */
.viz-root {
  --chart-surface: #16191f;
  --chart-ink: #e6e9ef;
  --chart-ink-secondary: #97a0b0;
  --chart-ink-muted: #898781;
  --chart-grid: #272c36;
  --chart-axis: #383835;
}

.chart { display: block; width: 100%; height: auto; overflow: visible; }

/* Text inside charts wears ink tokens, never the data colour. */
.chart-label { fill: var(--chart-ink); font-size: 11px; }
.chart-value { fill: var(--chart-ink-secondary); font-size: 11px; font-variant-numeric: tabular-nums; }
.chart-tick { fill: var(--chart-ink-muted); font-size: 10px; font-variant-numeric: tabular-nums; }
.chart-grid { stroke: var(--chart-grid); stroke-width: 1; }
.chart-axis { stroke: var(--chart-axis); stroke-width: 1; }
.chart-radar-ring { fill: none; stroke: var(--chart-grid); stroke-width: 1; }

.chart-centre-value { fill: var(--chart-ink); font-size: 26px; font-weight: 600; }
.chart-centre-label { fill: var(--chart-ink-muted); font-size: 11px; text-transform: uppercase; letter-spacing: .07em; }

/* Ribbons lift slightly on hover; the hit area is the ribbon itself, which is
   already far larger than the 24px minimum. */
.chart-sankey-link { transition: fill-opacity .12s ease; }
.chart-sankey-link:hover { fill-opacity: .62; }
@media (prefers-reduced-motion: reduce) { .chart-sankey-link { transition: none; } }

.chart-block { margin: 2.5rem 0; padding-top: 1.5rem; border-top: 1px solid var(--line); }
.chart-block > h2 { margin-top: 0; border: none; padding: 0; }
.chart-block h3 { font-size: .95rem; margin: 0 0 .75rem; color: var(--text); }
.chart-note { color: var(--muted); font-size: .9rem; max-width: 60rem; }
.chart-note strong { color: var(--text); }

.chart-row { display: flex; gap: 2rem; align-items: flex-start; flex-wrap: wrap; margin: 1.25rem 0; }
.chart-row-split > .chart-figure { flex: 1 1 20rem; min-width: 0; }
.chart-figure { margin: 0; flex: 0 1 auto; min-width: 0; max-width: 100%; }
.chart-figure.chart-wide { flex: 1 1 100%; }
.chart-aside { flex: 1 1 18rem; min-width: 0; }
.chart-empty { padding: 2rem 0; }

/* Legend: always present for two or more series, so identity never rests on
   colour-matching alone. */
.chart-legend { list-style: none; padding: 0; margin: .9rem 0 0; display: flex; flex-wrap: wrap; gap: .35rem 1.1rem; font-size: .85rem; }
.chart-legend li { display: flex; align-items: center; gap: .4rem; color: var(--text); }
.chart-swatch { width: 10px; height: 10px; border-radius: 2px; background: var(--swatch); flex: 0 0 auto; }
.chart-legend-note { color: var(--muted); font-variant-numeric: tabular-nums; }

/* Table view twin: every value reachable without hovering. */
.chart-table { margin-top: 1.25rem; }
.chart-table > summary { cursor: pointer; color: var(--muted); font-size: .88rem; padding: .35rem 0; }
.chart-table > summary:hover { color: var(--text); }
.chart-table[open] > summary { margin-bottom: .75rem; }

/* Hero figure — one per view, same sans as everything else, proportional figures. */
.hero-figure { margin: 1.5rem 0 2rem; }
.hero-value { font-size: 3.25rem; line-height: 1; font-weight: 600; letter-spacing: -.02em; margin: 0; color: var(--text); }
.hero-label { color: var(--muted); margin: .4rem 0 0; max-width: 30rem; }

@media (max-width: 760px) {
  .chart-row { gap: 1.25rem; }
  .hero-value { font-size: 2.5rem; }
}

/* ---- Access point location section ---- */
.location { margin: 2rem 0; }
.location .facts { margin-bottom: 1rem; }
.location-confidence { max-width: 44rem; margin: 0 0 1rem; }
.ap-map { height: 22rem; border: 1px solid var(--line); border-radius: var(--radius); background: #0b0d10; }
.map-actions { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; margin: 1rem 0 .5rem; }
