:root {
  --bg: #0b1420;
  --bg-elev: #111d2c;
  --bg-elev2: #172637;
  --line: #243648;
  --text: #e6edf5;
  --text-dim: #9fb0c3;
  --text-faint: #6b7d91;
  --accent: #4ea1ff;
  --accent-2: #78c48a;
  --warn: #f0b35e;
  --danger: #ff6b6b;
  --radius: 10px;
  --shadow: 0 10px 30px rgba(0,0,0,.45);
  --bar1-h: 44px;    /* tab layer (title + folder tabs + search) */
  --bar2-h: 90px;    /* toolbar: active folder-tab content */
  --ticker-h: 26px;  /* scrolling alerts */
  --statusbar-h: 28px;
  --leftpane-w: 108px;
  --panel-w: 380px;          /* design width (also the search width) */
  --panel-col: var(--panel-w); /* the panel's grid column; 0 when collapsed */
  font-family: "Segoe UI", system-ui, -apple-system, Roboto, Helvetica, Arial, sans-serif;
  color-scheme: dark;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--text); overflow: hidden; }
button, input { font: inherit; color: inherit; }

#app {
  position: fixed; inset: 0; display: grid;
  grid-template-columns: var(--leftpane-w) 1fr var(--panel-col);
  grid-template-rows: var(--bar1-h) var(--bar2-h) var(--ticker-h) 1fr var(--statusbar-h);
}
#leftpane { grid-column: 1; grid-row: 4; }
#map { grid-column: 2; grid-row: 4; position: relative; }
#map canvas { outline: none; }

/* ---------- center crosshair ---------- */
.crosshair {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 5; pointer-events: none;
}
.crosshair svg { stroke: #ff5a5a; stroke-width: 1.6; fill: none; filter: drop-shadow(0 0 1px rgba(0,0,0,.8)); }
.crosshair circle { fill: none; }

/* ---------- left pane: vertical zoom with named notches ---------- */
.leftpane {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 10px 0; background: var(--bg-elev); border-right: 1px solid var(--line); z-index: 15;
  min-height: 0; overflow: hidden;
}
.zoom-btn {
  flex: 0 0 auto; width: 30px; height: 30px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--bg-elev2); color: var(--text); cursor: pointer; font-size: 18px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.zoom-btn:hover { background: #20344a; border-color: #30465c; }
.zoom-btn:active { background: var(--accent); color: #04172c; }
/* Track height is strictly the flex space (bounded by the grid cell); the slider
   and notches are absolutely positioned to fill it, so nothing can overflow. */
.zoom-track { position: relative; flex: 1 1 0; min-height: 0; width: 100%; }
.zoom-slider {
  position: absolute; top: 0; bottom: 0; left: 10px; width: 20px; margin: 0;
  writing-mode: vertical-lr; direction: rtl;   /* vertical, max (zoom-in) at top */
  cursor: pointer; accent-color: var(--accent); background: transparent;
}
/* Inset top/bottom so the end levels (e.g. Satellite I / Street) sit inside the
   track instead of half-overflowing its ends, and line up with the slider thumb,
   whose centre can't travel all the way to the track edges. */
.zoom-notches { position: absolute; top: 10px; bottom: 10px; left: 36px; right: 4px; }
.zoom-notch { position: absolute; left: 0; right: 0; transform: translateY(50%); display: flex; align-items: center; gap: 5px; }
.notch-tick { flex: 0 0 auto; width: 7px; height: 1px; background: var(--text-faint); }
.notch-label { font-size: 10px; color: var(--text-dim); white-space: nowrap; cursor: pointer; line-height: 1; }
.notch-label:hover { color: var(--text); }
.notch-label.current { color: var(--accent); font-weight: 600; }

/* ---------- toolbar layer 1: tab bar ---------- */
#bar-tab {
  grid-column: 1 / -1; grid-row: 1; z-index: 20; display: flex; align-items: center; gap: 14px; padding: 0 14px;
  background: linear-gradient(180deg, rgba(11,20,32,.98), rgba(11,20,32,.92));
  border-bottom: 1px solid var(--line);
  overflow: visible;   /* let the active folder tab overlap into the toolbar below */
}

/* ---------- file-folder tabs (on the top bar) ---------- */
.toolbar-tabs { display: flex; align-items: flex-end; gap: 4px; align-self: stretch; margin-left: 6px; }
.ttab {
  padding: 4px 16px; margin-bottom: -1px; position: relative; cursor: pointer; font-size: 13px; line-height: 1.6;
  color: var(--text-dim); background: var(--bg);
  border: 1px solid var(--line); border-bottom: none; border-radius: 8px 8px 0 0;
}
.ttab:hover { color: var(--text); }
.ttab.active { color: var(--text); font-weight: 600; background: var(--bg-elev); z-index: 21; }
/* position: relative anchors #iface-menu. #bar-tab is already overflow:visible
   (so the active folder tab can overlap the toolbar) and sits at z-index 20
   against the toolbar's 19, so the dropdown escapes the bar without clipping. */
.brand { display: flex; align-items: center; gap: 9px; white-space: nowrap; position: relative; }
.brand-mark { width: 20px; height: 20px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #6fb6ff, #1c6fd1 60%, #0d3f82); box-shadow: inset -3px -3px 6px rgba(0,0,0,.4); }
.brand-text { display: flex; flex-direction: column; line-height: 1.02; }
.brand-name { font-weight: 700; font-size: 15px; letter-spacing: .2px; }
.brand-beta { font-size: 10px; font-weight: 700; letter-spacing: 1.2px; color: #ff3b3b; }

/* ---------- interface switcher (the brand IS the switcher) ----------
   The wordmark is a menu button. It has to still READ as the logo at rest —
   hence a transparent border and background that only materialise on hover or
   when open — while the caret says it is something you can press. The border is
   transparent rather than absent so the box does not resize on hover. */
.brand-btn {
  display: flex; align-items: center; gap: 9px; margin-left: -5px; padding: 3px 7px 3px 5px;
  background: transparent; border: 1px solid transparent; border-radius: 9px;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.brand-btn:hover { background: var(--bg-elev); border-color: var(--line); }
.brand-btn[aria-expanded="true"] { background: var(--bg-elev); border-color: var(--line); }
.brand-btn:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(78,161,255,.18); }
.brand-caret { flex: none; width: 11px; height: 11px; color: var(--text-faint); transition: transform .13s ease; }
.brand-btn:hover .brand-caret { color: var(--text-dim); }
.brand-btn[aria-expanded="true"] .brand-caret { transform: rotate(180deg); color: var(--text-dim); }

/* The mark changes colour and glyph per interface, so two interfaces never look
   alike at a glance — the disc is the fastest "where am I" signal on the page.
   Both selectors are needed: the attribute form paints the brand button on its
   own page, the class form paints the discs inside the menu. */
.brand-mark.is-media,
[data-interface="media"] .brand-mark {
  background: radial-gradient(circle at 35% 35%, #d9a4ff, #8b3fd1 60%, #46186f);
  display: grid; place-items: center;
}
.brand-mark.is-media::after,
[data-interface="media"] .brand-mark::after {
  content: '\266A'; font-size: 13px; line-height: 1; color: rgba(255,255,255,.94);
  text-shadow: 0 1px 2px rgba(0,0,0,.45);
}
/* Teams: green, with two heads drawn as an SVG data URI rather than set in a
   font. There is no people glyph in the BMP that renders dependably, and the
   search box already establishes the data-URI pattern for exactly this. */
.brand-mark.is-teams,
[data-interface="teams"] .brand-mark {
  background: radial-gradient(circle at 35% 35%, #8fe3b4, #27a567 60%, #0f5f3a);
  display: grid; place-items: center;
}
.brand-mark.is-teams::after,
[data-interface="teams"] .brand-mark::after {
  content: ''; width: 13px; height: 13px;
  background: no-repeat center / 13px 13px
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='rgba(255,255,255,.96)'%3E%3Ccircle cx='9' cy='8' r='3.2'/%3E%3Ccircle cx='17' cy='9' r='2.5'/%3E%3Cpath d='M3 19c0-3.3 2.7-5.2 6-5.2s6 1.9 6 5.2z'/%3E%3Cpath d='M16 13.6c2.6 0 5 1.6 5 4.4h-4.3c0-1.7-.5-3.2-1.4-4.3z'/%3E%3C/svg%3E");
}

.brand-mark-sm { width: 16px; height: 16px; box-shadow: inset -2px -2px 4px rgba(0,0,0,.4); }
.brand-mark-sm.is-media::after { font-size: 10px; }
.brand-mark-sm.is-teams::after { width: 11px; height: 11px; background-size: 11px 11px; }

.iface-menu {
  position: absolute; top: calc(100% + 7px); left: 0; z-index: 60; min-width: 236px; padding: 6px;
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow);
}
.iface-menu[hidden] { display: none; }
.iface-menu-label {
  font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  color: var(--text-faint); padding: 3px 9px 6px;
}
.iface-item {
  display: flex; align-items: center; gap: 10px; padding: 7px 9px; border-radius: 7px;
  text-decoration: none; color: var(--text);
}
.iface-item:hover, .iface-item:focus-visible { background: var(--bg-elev2); outline: none; }
.iface-item.is-current { background: rgba(78,161,255,.10); }
.iface-item-text { display: flex; flex-direction: column; line-height: 1.16; min-width: 0; }
.iface-item-name { font-size: 13px; font-weight: 600; }
.iface-item-desc { font-size: 11px; color: var(--text-faint); }
.iface-item-check { margin-left: auto; color: var(--accent); font-size: 12px; font-weight: 700; }
.badge { font-size: 11px; font-weight: 700; letter-spacing: .8px; padding: 2px 7px; border-radius: 5px; background: var(--warn); color: #1a1200; }

.bar-right { margin-left: auto; display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; justify-content: flex-end; }
.coords { font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.search { position: relative; flex: 1 1 auto; max-width: var(--panel-w); }
.search input {
  width: 100%; height: 30px; padding: 0 12px 0 32px; border-radius: 8px; border: 1px solid var(--line);
  background: var(--bg-elev) no-repeat 10px center;
  /* magnifying-glass icon */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%239fb0c3' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M21 21l-4.3-4.3'/%3E%3C/svg%3E");
  color: var(--text); outline: none;
}
.search input::-webkit-search-cancel-button { filter: invert(.6); }
.search input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(78,161,255,.18); }
.search-results {
  position: absolute; top: 38px; left: 0; right: 0; margin: 0; padding: 6px; list-style: none;
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow);
  max-height: 60vh; overflow: auto;
}
.search-results li { display: flex; justify-content: space-between; gap: 10px; padding: 8px 10px; border-radius: 7px; cursor: pointer; }
.search-results li:hover, .search-results li[aria-selected="true"] { background: var(--bg-elev2); }
.search-results .meta { color: var(--text-faint); font-size: 12px; white-space: nowrap; }

/* ---------- toolbar layer 2: active folder-tab content ---------- */
#bar-toolbar {
  grid-column: 1 / -1; grid-row: 2; z-index: 19; display: flex; align-items: stretch;
  background: var(--bg-elev); border-bottom: 1px solid var(--line);
}
/* Content sits near the top; the extra room is padding BELOW the filters. */
.toolbar-content { display: flex; align-items: flex-start; padding: 10px 16px 16px; flex: 1; min-width: 0; overflow-x: auto; }
/* Every tab panel lays its sections out in a row with the same 18px rhythm
   between the master toggle, its divider and the controls.
   This used to be duplicated as #ttab-home and #ttab-satellites only, so every
   OTHER tab — Weather, Surveillance, Cyber, Roads — silently got gap:0 and its
   controls sat jammed against the On/Off toggle. Setting it on the class means
   the next tab added cannot inherit that bug. */
.ttab-panel { display: flex; flex-direction: row; align-items: stretch; gap: 18px; }
.filter-section { display: flex; flex-direction: column; justify-content: flex-start; gap: 4px; }
.filter-section-title { margin: 0; font-size: 10px; letter-spacing: .5px; color: var(--text-faint); text-align: center; }
/* checkboxes flow down 2 rows, then into the next column (States goes right of Countries) */
.filter-items { display: grid; grid-auto-flow: column; grid-template-rows: auto auto; gap: 5px 18px; }
.filter-divider { width: 1px; background: var(--line); align-self: stretch; margin: 10px 0; }

/* A divider that doubles as a collapse control for the section to its right.
   The bar itself stays a 1px line (drawn by ::before) while the button gets a
   real hit area around it — a 1px target is unclickable. */
.filter-collapse {
  appearance: none; -webkit-appearance: none; background: none; border: 0; padding: 0;
  width: 11px; flex: none; align-self: stretch; margin: 8px 0; cursor: pointer;
  position: relative; display: flex; flex-direction: column; gap: 3px;
  /* Caret pinned to the TOP, not centred. Centring put it at a different height
     on every bar, because the stack is caret + label and the labels are
     different lengths — and on the longest ones the whole stack overflowed the
     bar and the caret disappeared under the bottom edge of the toolbar.
     Top-aligned, every caret sits on the same line; overflow:hidden guarantees
     nothing can spill past the bar again. */
  align-items: center; justify-content: flex-start; overflow: hidden;
  transition: width .14s ease;
}
/* The caret says the bar is a control and which way it will move the section.
   Expanded: "‹", the section folds left into the bar. Collapsed: "›", it comes
   back out.

   It is a REAL element, first in the markup, not a ::after. A pseudo-element
   generated with ::after is always the LAST flex item, so the caret sat BELOW
   the label — and because the labels are different lengths ("Settings" vs
   "Buildings"), every caret ended up at a different height. First in DOM order
   puts them all on one line, and being a real element means its position can
   actually be measured. */
.filter-collapse-caret {
  flex: none; position: relative; z-index: 1;
  font-size: 12px; line-height: 1; color: var(--text-faint);
  background: var(--bg-elev); padding: 2px 1px 3px;
  transition: color .14s;
}
.filter-collapse-caret::before { content: '\2039'; }
.filter-collapse.is-collapsed .filter-collapse-caret::before { content: '\203A'; }
.filter-collapse:hover .filter-collapse-caret,
.filter-collapse:focus-visible .filter-collapse-caret { color: var(--accent); }
.filter-collapse::before {
  content: ''; position: absolute; top: 0; bottom: 0; left: 50%; width: 1px;
  background: var(--line); transition: background .14s;
}
.filter-collapse:hover::before, .filter-collapse:focus-visible::before { background: var(--accent); }
.filter-collapse:focus-visible { outline: none; }
/* The label only exists while collapsed. It reads top-to-bottom, starting just
   under the caret — no rotate(180deg), so a name too long for the bar truncates
   with an ellipsis at the bottom instead of being cut off mid-word at the top. */
.filter-collapse-label {
  display: none; position: relative; z-index: 1; min-height: 0;
  writing-mode: vertical-rl;
  font-size: 9px; letter-spacing: .3px; color: var(--text-faint);
  background: var(--bg-elev); padding: 2px 0; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
/* Collapsed, a bar stops being a divider and becomes a THING you can click: a
   boxed tab with the section's name down it. Expanded it goes back to being a
   1px rule, so the toolbar does not grow a row of competing boxes. */
.filter-collapse.is-collapsed {
  width: 22px; padding: 4px 0;
  background: var(--bg-elev2); border: 1px solid var(--line); border-radius: 7px;
  gap: 2px;
}
.filter-collapse.is-collapsed:hover { background: #20344a; border-color: #30465c; }
.filter-collapse.is-collapsed::before { display: none; }          /* no rule inside a box */
.filter-collapse.is-collapsed .filter-collapse-caret,
.filter-collapse.is-collapsed .filter-collapse-label { background: none; }
.filter-collapse.is-collapsed .filter-collapse-label { display: block; }
.filter-collapse.is-collapsed:hover .filter-collapse-label,
.filter-collapse.is-collapsed:hover .filter-collapse-caret { color: var(--accent); }
/* Collapsed: the section goes, and so does the divider on its far side, or two
   vertical rules end up sitting next to each other. */
.filter-section.is-collapsed { display: none; }
/* ...but NOT when the next divider is itself another section's collapse
   control, or collapsing one section would hide the handle for the next. */
.filter-section.is-collapsed + .filter-divider:not(.filter-collapse) { display: none; }
/* When a section is collapsed its bar ends up right next to the NEXT section's
   bar (the hidden section between them takes no width), and the panel's 18px
   flex gap then reads as a wide empty channel between two thin rules. Pull
   those pairs back together — the gap still applies either side of whichever
   section is open, where the breathing room is actually wanted. */
.filter-collapse.is-collapsed + .filter-section.is-collapsed + .filter-collapse {
  margin-left: -12px;
}
/* Tabs gated by zoom (see TAB_ZOOM). .ttab sets its own display, which would
   otherwise beat the [hidden] default. */
.ttab[hidden] { display: none !important; }
#bar-toolbar .chk { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; font-size: 13px; white-space: nowrap; }
#bar-toolbar .chk input { accent-color: var(--accent); }

/* ---------- Layer tabs: vertical master toggle + grayable controls ---------- */
/* (the row/gap layout is on .ttab-panel now, shared by every tab) */
.sat-power { display: flex; align-items: center; }
.vtoggle {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  background: none; border: none; padding: 0; cursor: pointer;
  font-size: 9px; font-weight: 700; letter-spacing: .6px; color: var(--text-faint);
}
.vtoggle-track {
  position: relative; width: 24px; height: 46px; border-radius: 13px;
  background: var(--line); transition: background .18s ease;
}
.vtoggle-knob {
  position: absolute; left: 3px; bottom: 3px; width: 18px; height: 18px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.45);
  transition: bottom .18s ease;
}
.vtoggle[aria-checked="true"] .vtoggle-track { background: var(--accent); }
.vtoggle[aria-checked="true"] .vtoggle-knob { bottom: 25px; }
.vtoggle[aria-checked="true"] .vtoggle-label-on { color: var(--accent); }
.vtoggle:not([aria-checked="true"]) .vtoggle-label-off { color: var(--text); }
.vtoggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

.sat-controls { display: flex; transition: opacity .18s ease; }
/* Only where a collapse bar sits next to the section, so the other tabs'
   control groups keep their existing spacing. */
.sat-controls.has-collapse { gap: 14px; align-items: stretch; }
/* When the controls START with a collapse bar, that bar IS the divider after the
   master toggle and the standalone .filter-divider is omitted. The bar is an
   11px hit area with its 1px rule drawn centred by ::before, so without this it
   sits 5px further right than the plain 1px divider on the tabs that still have
   one — Roads and Weather read 5px out of step with Satellites and Cyber. Pull
   it back by half the hit area so every tab's rule lands on the same x. */
.sat-power + .sat-controls.has-collapse { margin-left: -5px; }
.sat-controls.disabled { opacity: .4; pointer-events: none; filter: grayscale(1); }
/* ...but the collapse bars stay live. Folding a section into its divider is a
   layout choice, not a control of the layer, and the bar is the only control
   for it — so with the wrapper's pointer-events:none covering the bar too, a
   section could only be minimised while its tab happened to be switched On.
   Note this is invisible to a scripted .click(), which ignores pointer-events
   entirely: it only ever failed for a real mouse. */
.sat-controls.disabled .filter-collapse { pointer-events: auto; }
.sat-placeholder { font-size: 12px; color: var(--text-faint); font-style: italic; align-self: center; }

/* Satellite overlay: a canvas over the globe that draws the dots lifted to their
   true altitude. Non-interactive (clicks fall through to the map, which hit-tests). */
.sat-canvas { position: absolute; inset: 0; pointer-events: none; z-index: 2; }
/* The Moon sits under the satellite overlay and never takes clicks. */
.moon-canvas { position: absolute; inset: 0; pointer-events: none; z-index: 1; }

/* ---------- admin: draggable building footprint handles ---------- */
.fp-handle {
  width: 12px; height: 12px; border-radius: 3px; cursor: grab;
  background: #ffffff; border: 2px solid #4ea1ff; box-shadow: 0 0 0 1px rgba(0,0,0,.6);
}
.fp-handle:active { cursor: grabbing; background: #4ea1ff; }
.fp-hint { font-size: 12px; color: var(--text-dim); margin: 0 0 10px; line-height: 1.4; }
.fp-size { color: var(--accent); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------- admin Settings tab: live-data source rows ---------- */
.settings-section { min-width: 0; }
.settings-row { display: flex; align-items: center; gap: 10px; }
.set-dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; background: var(--text-faint); box-shadow: 0 0 0 2px rgba(255,255,255,.04); }
.set-dot.dot-green  { background: #39d353; box-shadow: 0 0 6px rgba(57,211,83,.7); }
.set-dot.dot-orange { background: #e3a008; box-shadow: 0 0 6px rgba(227,160,8,.6); }
.set-dot.dot-red    { background: #e5484d; box-shadow: 0 0 6px rgba(229,72,77,.6); }
.set-dot.dot-grey   { background: var(--text-faint); }
.set-meta { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.set-name { font-size: 12px; color: var(--text); white-space: nowrap; }
.set-sub  { font-size: 11px; color: var(--text-dim); white-space: nowrap; }
.btn-sm { font-size: 12px; padding: 3px 12px; }
.btn.active { background: var(--accent); color: #04172c; border-color: var(--accent); }

/* Home-tab View buttons: Center and Clear share a row, Flow spans underneath. */
.view-buttons { display: flex; flex-direction: column; gap: 6px; }
.view-row { display: flex; gap: 6px; }
.view-row > .btn { flex: 1; }

/* Satellite category filters */
.satcat-items { gap: 4px 16px; }
.satcat-sw { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin: 0 1px; vertical-align: middle; }
.sw-starlink { background: #6fb6ff; } .sw-oneweb { background: #b48ead; } .sw-stations { background: #ffe08a; }
.sw-payload { background: #86e0c0; } .sw-rocketbody { background: #c0c0c0; } .sw-debris { background: #ff7a7a; }
.sw-meo { background: #ff9f4a; }
/* Paths (US road classes) — swatches match the globe line colours */
.sw-interstate { background: #4da3ff; } .sw-freeway { background: #7fc4ff; }
.sw-ushwy { background: #ffcf70; } .sw-statehwy { background: #ffe7a6; }
.sw-majorroad { background: #9aa4ad; }
.sw-localroad { background: #7f878d; } .sw-ramp { background: #6e767c; }
.sw-alley { background: #565d63; } .sw-walkway { background: #8fd39a; }
.sw-historic { background: #e0a458; } .sw-government { background: #5fb3d4; }
.sw-radar { background: linear-gradient(90deg,#39d353,#f0e14a,#ff5a5a); }
.sw-radarsite { background: #5ee8d0; border-radius: 50%; }
/* Green / orange / red, which is the index itself in one swatch. */
.sw-whi { background: linear-gradient(90deg,#39d353 0 34%,#f0a24a 34% 67%,#ff5a5a 67%); border-radius: 2px; }
.sw-forecast { background: linear-gradient(90deg,#ffd27f,#8fd0ff); border-radius: 2px; }
/* Swatch and legend share the heatmap ramp, so the control and the map agree. */
.sw-precip { background: linear-gradient(90deg,#227ad6,#19b5a5,#6ed04a,#f0e14a,#f0953a,#ff4a4a); border-radius: 2px; }
.precip-legend { display: inline-flex; flex-direction: column; gap: 2px; vertical-align: middle; }
.precip-scale {
  display: block; width: 120px; height: 7px; border-radius: 3px; border: 1px solid var(--line);
  background: linear-gradient(90deg,#227ad6,#19b5a5,#6ed04a,#f0e14a,#f0953a,#ff4a4a);
}
.precip-ends { display: flex; justify-content: space-between; width: 120px; }
.precip-ends i { font-style: normal; font-size: 9px; color: var(--text-faint); font-variant-numeric: tabular-nums; }

/* Mirrors COLORS.oceanTint in app.js — the standing tint, not the hover blue. */
.sw-ocean { background: #7cc0ef; border-radius: 2px; }
/* Mirror COLORS.terrain / .volcano / .cave in app.js — change both together. */
.sw-mountain { background: #d9b38c; border-radius: 2px; }
.sw-volcano  { background: #ff3b2f; border-radius: 2px; }
.sw-cave     { background: #ff8fd0; border-radius: 2px; }

/* SPC outlook swatch carries the risk ramp, in SPC's own published colours. */
.sw-outlook { background: linear-gradient(90deg,#c1e9c1,#66a366,#ffe066,#ffa366,#ff6666,#ff66ff); border-radius: 2px; }
/* Storm cell / track / report swatches, mirrored from STORM_COLORS in app.js. */
.st-meso    { background: #ff9e3d; }
.st-track   { background: #7fd4ff; }
.rp-tornado { background: #ff4d4d; }

/* MoDOT incidents. These three are MoDOT's OWN marker colours, sampled from the
   PNGs in their config.json (ti_closed / ti_high / ti_med), so the legend here
   matches the map people cross-check against. Mirrored in INCIDENT_COLORS. */
.sw-inc-closed   { background: #b90000; border-radius: 50%; }
.sw-inc-expect   { background: #ff7e00; border-radius: 50%; }
.sw-inc-possible { background: #fcff00; border-radius: 50%; }
.inc-pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 600; }
.inc-pill-closed   { background: rgba(185,0,0,.18);   color: #ff6b6b; border: 1px solid rgba(185,0,0,.5); }
.inc-pill-expect   { background: rgba(255,126,0,.16); color: #ff9e4a; border: 1px solid rgba(255,126,0,.45); }
.inc-pill-possible { background: rgba(252,255,0,.14); color: #e8e46a; border: 1px solid rgba(252,255,0,.4); }
/* MoDOT's detail text is several short lines; preserve them without letting a
   long route description blow the pane's width out. */
.inc-detail { white-space: pre-wrap; overflow-wrap: anywhere; }

/* NWS alerts. Swatch carries the three tiers in the order they matter. */
.sw-alerts { background: linear-gradient(90deg,#ff4d4d 0 34%,#f0b429 34% 67%,#6ab0e8 67%); border-radius: 2px; }
/* Three rows flowing across, inside a WIDTH-CAPPED scroller.
   The cap is the point: the active hazard list is as long as the weather makes
   it (14 keys on an ordinary night), and letting it size itself pushed RADAR,
   Indexes and Forecast off the toolbar entirely. Three rows fit the toolbar's
   height, ~3 columns are visible at once, and the rest scrolls within the
   section instead of displacing its neighbours. */
.alert-row { display: flex; align-items: flex-start; gap: 16px; }
/* Nine fixed hazard families in three rows — no scroller, no overflow, nothing
   hidden. The earlier version generated these from whatever was active and put
   them in a horizontally scrolling strip, so the controls shifted around as the
   weather changed and most were off-screen. A stable set fits because the ~120
   NWS event names fold into a handful of families. */
.alert-types {
  display: grid; grid-auto-flow: column; grid-template-rows: repeat(3, auto);
  gap: 3px 14px;
}
.alert-types .chk, #section-winter .chk { font-size: 11px; }
/* Family swatches. Deliberately NOT the tier colours — the map already says
   warning/watch/advisory by colour, so these identify the hazard instead. */
.hz-tornado  { background: #ff4d4d; }
.hz-storm    { background: #ffa033; }
.hz-flood    { background: #4aa3ff; }
.hz-tropical { background: #c792ea; }
.hz-wind     { background: #8fd0c0; }
.hz-fire     { background: #ff7a45; }
.hz-heat     { background: #ffd166; }
.hz-vis      { background: #9aa7b4; }
.hz-other    { background: #6b7a89; }
/* Winter's section filters by TIER, so those swatches use the tier colours and
   match what is drawn on the map. */
.tier-warning  { background: #ff4d4d; }
.tier-watch    { background: #f0b429; }
.tier-advisory { background: #6ab0e8; }
.alert-types .chk { font-size: 11px; }
.alert-types .alert-count { color: var(--text-faint); font-variant-numeric: tabular-nums; }
.alert-sw { width: 9px; height: 9px; border-radius: 2px; display: inline-block; flex: none; }
.alert-pop { font-size: 12px; line-height: 1.35; max-width: 280px; }
.alert-pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 600; }
.alert-pill-Warning  { background: rgba(255,77,77,.16);  color: #ff6b6b; border: 1px solid rgba(255,77,77,.45); }
.alert-pill-Watch    { background: rgba(240,180,41,.16); color: #f0b429; border: 1px solid rgba(240,180,41,.45); }
.alert-pill-Advisory { background: rgba(106,176,232,.16); color: #6ab0e8; border: 1px solid rgba(106,176,232,.45); }

/* Internet Status. The three severity colours are shared with the map layers in
   app.js (NET_COLORS) — change them in both places or the legend starts lying. */
.sw-net { background: linear-gradient(90deg,#f2c14e 0 34%,#f0853a 34% 67%,#ff4d4d 67%); border-radius: 2px; }
.net-legend { display: inline-flex; align-items: center; gap: 10px; vertical-align: middle; }
.net-key { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; color: var(--text-faint); }
.net-sw { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.net-minor    { background: #f2c14e; }
.net-major    { background: #f0853a; }
.net-critical { background: #ff4d4d; }

/* Flow speed: a second vertical slider beside the zoom one, only while Flow is
   running. The left pane widens to make room so the zoom-level labels keep the
   space they had. */
.flow-speed {
  position: absolute; top: 0; bottom: 0; left: 32px; width: 20px; margin: 0;
  writing-mode: vertical-lr; direction: rtl;      /* fast at the top */
  cursor: pointer; accent-color: #5ee8d0; background: transparent;
}
#app.flow-on { --leftpane-w: 132px; }
#app.flow-on .zoom-notches { left: 60px; }
.whi-pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.whi-green  { background: rgba(57,211,83,.16);  color: #5ee87f; border: 1px solid rgba(57,211,83,.4); }
.whi-orange { background: rgba(240,162,74,.16); color: #f0a24a; border: 1px solid rgba(240,162,74,.45); }
.whi-red    { background: rgba(255,90,90,.16);  color: #ff7a7a; border: 1px solid rgba(255,90,90,.45); }
/* "Turn On" / "Turn Off" for a radar site, in the detail pane. */
.radar-power { margin: 10px 0 2px; display: flex; align-items: center; gap: 10px; }
.radar-live { font-size: 12px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.radar-live.on { color: #5ee8d0; }
.radar-status { font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; padding-left: 2px; }
.sw-airport { background: #d0d6de; } .sw-attraction { background: #6fcf97; } .sw-museum { background: #c792ea; }
/* Surveillance camera swatches — match the globe dot colours */
/* Mirrors CAM_COLORS in app.js — change both together. */
.sw-cam-snapshot { background: #ffbe1a; } .sw-cam-hls { background: #19e36b; }
.sw-cam-embed { background: #d46aff; }    .sw-cam-bad { background: #ff3b3b; }
/* 11 categories: three rows keeps the toolbar from running too wide */
.camcat-items { grid-template-rows: auto auto auto; gap: 4px 16px; }

/* ---------- camera preview in the details pane ---------- */
.cam-preview {
  margin-top: 6px; border: 1px solid var(--line); border-radius: 6px; overflow: hidden;
  background: #05080d; min-height: 120px; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 6px; padding: 6px;
}
.cam-frame { width: 100%; aspect-ratio: 16 / 9; display: block; border: 0; background: #000; object-fit: contain; }
.cam-note { font-size: 11px; line-height: 1.3; text-align: center; }

/* Internet Status popup. Shares the camera popup's dark treatment, because a
   default white MapLibre popup over this map is a flashbang. */
.net-popup .maplibregl-popup-content {
  background: var(--bg-elev); color: var(--text);
  border: 1px solid var(--line); border-radius: 8px;
  padding: 9px 10px 10px; box-shadow: 0 8px 28px rgba(0,0,0,.55);
}
.net-popup .maplibregl-popup-tip { border-top-color: var(--bg-elev); border-bottom-color: var(--bg-elev); }
.net-popup .maplibregl-popup-close-button { color: var(--text-dim); font-size: 17px; line-height: 1; padding: 0 5px; }
.net-popup .maplibregl-popup-close-button:hover { color: var(--text); background: transparent; }
.net-pop { font-size: 12px; line-height: 1.35; max-width: 260px; }
/* Same treatment as the Waffle House pills: a translucent ground with the
   severity colour as the text, which stays readable at any size. A solid fill
   needs its own foreground colour and gets unreadable on the yellow. */
.net-pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 600; }
.net-pill-minor    { background: rgba(242,193,78,.16); color: #f2c14e; border: 1px solid rgba(242,193,78,.45); }
.net-pill-major    { background: rgba(240,133,58,.16); color: #f0853a; border: 1px solid rgba(240,133,58,.45); }
.net-pill-critical { background: rgba(255,77,77,.16);  color: #ff6b6b; border: 1px solid rgba(255,77,77,.45); }

/* ---------- camera popup, over the map ---------- */
.cam-popup .maplibregl-popup-content {
  background: var(--bg-elev); color: var(--text);
  border: 1px solid var(--line); border-radius: 8px;
  padding: 9px 10px 10px; box-shadow: 0 8px 28px rgba(0,0,0,.55);
}
.cam-popup .maplibregl-popup-tip { border-top-color: var(--bg-elev); border-bottom-color: var(--bg-elev); }
.cam-popup .maplibregl-popup-close-button { color: var(--text-dim); font-size: 19px; line-height: 1; padding: 0 6px; }
.cam-popup .maplibregl-popup-close-button:hover { color: var(--text); background: transparent; }
.cam-pop { width: 330px; max-width: 78vw; }
/* Title and the open-in-new-tab button share the top row. The title takes the
   slack so a long camera name wraps instead of shoving the button off; the
   close button MapLibre draws sits in the corner, so the padding keeps clear. */
.cam-pop-head { display: flex; align-items: flex-start; gap: 8px; padding-right: 18px; }
.cam-pop-title { font-size: 13px; font-weight: 600; line-height: 1.25; flex: 1; min-width: 0; }
.cam-pop-open {
  flex: none; font-size: 11px; font-weight: 600; text-decoration: none; white-space: nowrap;
  padding: 2px 8px; border-radius: 6px;
  border: 1px solid var(--line); color: var(--text-dim); background: var(--bg-elev2);
}
.cam-pop-open:hover { color: var(--text); border-color: var(--accent); }
.cam-pop-meta { font-size: 11px; color: var(--text-dim); line-height: 1.35; margin-top: 2px; }
.cam-pop .cam-preview { margin-top: 7px; min-height: 100px; }
.satcat-count { color: var(--text-faint); font-size: 11px; }
.edit-newrow { gap: 6px; }
.edit-newkey { flex: 0 0 38%; }

/* ---------- toolbar layer 3: alerts ticker ---------- */
#ticker {
  grid-column: 1 / -1; grid-row: 3; z-index: 18; display: flex; align-items: center; overflow: hidden;
  background: rgba(6,10,18,.98); border-bottom: 1px solid var(--line);
  color: var(--text-dim); font-size: 12px;
}
#ticker-track { display: inline-block; white-space: nowrap; padding-left: 100%; will-change: transform; animation: ticker 32s linear 1 forwards; }
#ticker:hover #ticker-track { animation-play-state: paused; }
.ticker-item { padding: 0 2.5em; }
.ticker-item .sep { color: var(--text-faint); }
@keyframes ticker { from { transform: translateX(0); } to { transform: translateX(-100%); } }

/* ---------- buttons ---------- */
.btn {
  height: 32px; padding: 0 12px; border-radius: 8px; border: 1px solid var(--line); background: var(--bg-elev); cursor: pointer;
  transition: background .12s, border-color .12s;
}
.btn:hover { background: var(--bg-elev2); border-color: #30465c; }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
.btn-ghost { background: transparent; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #04172c; font-weight: 600; }
.btn-icon { width: 32px; padding: 0; font-size: 20px; line-height: 1; }
/* Destructive. Muted until hovered so it does not shout, but never reads as the
   same kind of thing as the button it sits next to. */
.btn-danger { color: #e08a8a; border-color: #4a2b2b; }
.btn-danger:hover { background: #3a1f1f; border-color: #8c4a4a; color: #ffb0b0; }

/* ---------- detail panel (right, static column) ---------- */
.panel {
  grid-column: 3; grid-row: 4; z-index: 15;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--bg-elev); border-left: 1px solid var(--line);
  overflow: hidden;
}
/* Placeholder (no feature selected): center a short prompt. */
.panel-empty { margin: auto; padding: 24px; text-align: center; color: var(--text-faint); font-size: 13px; line-height: 1.5; }
.panel-empty .mark { display: block; width: 34px; height: 34px; margin: 0 auto 12px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #6fb6ff, #1c6fd1 60%, #0d3f82); opacity: .55; }

/* ---------- collapse toggle on the panel's left edge ---------- */
.panel-collapse {
  position: absolute; top: 50%; right: var(--panel-col); transform: translateY(-50%);
  width: 18px; height: 56px; z-index: 16; cursor: pointer; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-elev); color: var(--text-dim);
  border: 1px solid var(--line); border-right: none; border-radius: 7px 0 0 7px;
  font-size: 15px; line-height: 1; transition: right .2s ease;
}
.panel-collapse:hover { color: var(--text); background: var(--bg-elev2); }
/* collapsed: panel column shrinks to nothing, content hidden, caret flips.
   Only --panel-col changes, so --panel-w (the search width) is unaffected. */
#app.panel-collapsed { --panel-col: 0px; }
#app.panel-collapsed .panel > * { display: none !important; }

/* ---------- admin panel tabs + dev notes ---------- */
.panel-tabs { display: flex; gap: 2px; padding: 8px 10px 0; border-bottom: 1px solid var(--line); flex: 0 0 auto; }
.panel-tabs .tab {
  padding: 7px 14px; background: transparent; border: none; border-bottom: 2px solid transparent;
  color: var(--text-dim); cursor: pointer; font-size: 13px; line-height: 1.2;
}
.panel-tabs .tab:hover { color: var(--text); }
.panel-tabs .tab.active { color: var(--text); border-bottom-color: var(--accent); font-weight: 600; }

.tab-dev { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.dev-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 12px 6px; font-size: 11px; color: var(--text-faint);
}
.dev-status { flex: 1; min-width: 0; }
.dev-history {
  max-width: 190px; font-size: 11px; padding: 2px 4px;
  background: var(--bg-elev2); color: var(--text-dim);
  border: 1px solid var(--line); border-radius: 5px;
}
/* A notes box that could not load what is stored must look inert, because it
   is: it refuses to save rather than risk writing an empty box over real
   notes. See the `loaded` interlock in app.js. */
.dev-notes.is-locked { background: var(--bg-elev); color: var(--text-faint); cursor: not-allowed; }
.dev-notes.is-locked:focus { background: var(--bg-elev); }
.dev-notes {
  flex: 1; min-height: 0; width: 100%; box-sizing: border-box; resize: none;
  padding: 10px 12px; border: none; border-top: 1px solid var(--line);
  background: var(--bg); color: var(--text);
  font-family: ui-monospace, "Cascadia Code", Consolas, "Courier New", monospace;
  font-size: 13px; line-height: 1.5; tab-size: 2; outline: none;
  white-space: pre-wrap; overflow-wrap: break-word;
}
.dev-notes:focus { background: #0a1220; }
.panel-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; padding: 14px 14px 10px; border-bottom: 1px solid var(--line); }
.panel-type { font-size: 11px; letter-spacing: .5px; color: var(--accent-2); font-weight: 700; }
.panel h2 { margin: 2px 0 0; font-size: 20px; line-height: 1.2; }
.panel-body { flex: 1; overflow: auto; padding: 8px 14px 14px; }
.panel-admin { display: flex; gap: 8px; padding: 12px 14px; border-top: 1px solid var(--line); }

.kv { display: grid; grid-template-columns: 128px 1fr; gap: 6px 12px; margin: 10px 0; }
.kv dt { color: var(--text-faint); font-size: 12px; padding-top: 2px; }
.kv dd { margin: 0; overflow-wrap: anywhere; }
.kv dd.num { font-variant-numeric: tabular-nums; }
.section-title { font-size: 11px; letter-spacing: .4px; color: var(--text-faint); margin: 16px 0 4px; }
.chip-list { display: flex; flex-wrap: wrap; gap: 6px; }
.chip { padding: 3px 9px; border-radius: 999px; background: var(--bg-elev2); border: 1px solid var(--line); font-size: 12px; cursor: pointer; }
.chip:hover { border-color: var(--accent); }
.link { color: var(--accent); cursor: pointer; text-decoration: none; }
.link:hover { text-decoration: underline; }
.status-pill { display: inline-block; padding: 2px 8px; border-radius: 6px; font-size: 12px; background: rgba(240,179,94,.15); color: var(--warn); border: 1px solid rgba(240,179,94,.35); }
.loading { color: var(--text-faint); padding: 20px 0; }

/* ---------- edit mode (admin) ---------- */
.edit-form { display: flex; flex-direction: column; gap: 8px; padding: 12px 0; }
.edit-row { display: grid; grid-template-columns: 120px 1fr; gap: 6px 12px; align-items: center; }
.edit-k { color: var(--text-faint); font-size: 12px; overflow-wrap: anywhere; }
.edit-input {
  width: 100%; box-sizing: border-box; padding: 5px 8px; border: 1px solid var(--line);
  border-radius: 6px; background: var(--bg); color: var(--text); font-size: 13px; outline: none;
}
.edit-input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(78,161,255,.18); }
/* Notes has no label column, so it spans the full width of the form instead of
   sitting in the 1fr half of the usual two-column grid row. */
.edit-row-notes { align-items: start; grid-template-columns: 1fr; }
.edit-textarea {
  min-height: 90px; resize: none; font-family: inherit; line-height: 1.5;
  white-space: pre-wrap; overflow-wrap: break-word;
}

/* ---------- per-object notes ---------- */
.obj-notes {
  width: 100%; box-sizing: border-box; min-height: 110px; resize: vertical;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--text);
  font-family: inherit; font-size: 13px; line-height: 1.5; outline: none;
  white-space: pre-wrap; overflow-wrap: break-word;
}
.obj-notes:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(78,161,255,.18); }
.obj-notes-view { font-size: 13px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: break-word; }

/* ---------- hover tip / toast / status ---------- */
.hover {
  position: absolute; z-index: 18; pointer-events: none; transform: translate(12px, 12px);
  padding: 5px 9px; border-radius: 7px; background: rgba(11,20,32,.92); border: 1px solid var(--line); font-size: 13px; white-space: nowrap;
}
.hover .sub { color: var(--text-faint); font-size: 11px; margin-left: 6px; }
.toast {
  position: absolute; z-index: 30; left: 50%; bottom: calc(var(--statusbar-h) + 18px); transform: translateX(-50%);
  padding: 8px 14px; border-radius: 8px; background: var(--bg-elev2); border: 1px solid var(--line); box-shadow: var(--shadow); font-size: 13px;
}
/* The status bar carries only the credit line now, so it is centred rather than
   laid out as a left/right strip. */
#statusbar {
  grid-column: 1 / -1; grid-row: 5; z-index: 20; display: flex; align-items: center; justify-content: center;
  gap: 8px; padding: 0 12px; font-size: 12px; color: var(--text-dim);
  background: rgba(11,20,32,.98); border-top: 1px solid var(--line); font-variant-numeric: tabular-nums;
}
/* Time-zone clocks. These are DOM markers rather than a GL symbol layer so the
   seconds can tick by rewriting one text node — a symbol layer would have to
   re-tile its whole source every second, which made the labels flash.
   pointer-events:none so a drag beginning on a clock still pans the globe;
   selection is hit-tested by rectangle in the map's click handler.
   tabular-nums keeps the digits from jittering as the seconds change. */
.tz-chip {
  cursor: pointer;                 /* these are selectable; say so on hover */
  display: flex; flex-direction: column; align-items: center;
  padding: 2px 6px; border-radius: 7px;
  border: 1px solid transparent; background: transparent;
  font: 700 11px/1.15 "Segoe UI", system-ui, -apple-system, Roboto, Helvetica, Arial, sans-serif;
  color: #f2f2f2; white-space: nowrap; text-align: center;
  text-shadow: 0 0 2px rgba(4,9,16,.95), 0 0 2px rgba(4,9,16,.95), 0 0 3px rgba(4,9,16,.95);
  transition: background .12s, border-color .12s;
}
/* Only the SELECTED zone shows its clock; the rest show just their name. */
.tz-chip .tz-clock { display: none; font-variant-numeric: tabular-nums; }
.tz-chip.selected .tz-clock { display: block; }
.tz-chip:hover { background: rgba(10,16,26,.55); border-color: rgba(255,255,255,.35); }
/* Selected zone: the chip matches the highlight drawn over the zone itself. */
.tz-chip.selected {
  background: rgba(10,16,26,.8); border-color: #ffffff; color: #ffffff;
  box-shadow: 0 0 0 1px rgba(255,255,255,.25), 0 2px 8px rgba(0,0,0,.5);
}

#statusbar .credit {
  letter-spacing: .05em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#statusbar .sep { color: var(--text-faint); }
#statusbar .grow { flex: 1; }
#statusbar .muted { color: var(--text-faint); }

/* ---------- maplibre chrome tweaks ---------- */
.maplibregl-ctrl-group { background: var(--bg-elev) !important; border: 1px solid var(--line); box-shadow: none !important; }
.maplibregl-ctrl-group button + button { border-top-color: var(--line) !important; }
.maplibregl-ctrl button .maplibregl-ctrl-icon { filter: invert(.85); }
.maplibregl-ctrl-attrib { display: none; }

/* ---------- responsive ---------- */
@media (max-width: 1100px) { :root { --panel-w: 320px; } }

/* Narrow: stack the panel below the map so it stays visible without crowding. */
@media (max-width: 760px) {
  #app {
    grid-template-columns: 1fr;
    grid-template-rows: var(--bar1-h) var(--bar2-h) var(--ticker-h) 1fr 42vh var(--statusbar-h);
  }
  #leftpane { display: none; }         /* touch devices pinch-zoom */
  #map   { grid-column: 1; grid-row: 4; }
  .panel { grid-column: 1; grid-row: 5; border-left: none; border-top: 1px solid var(--line); }
  #statusbar { grid-row: 6; }
  .panel-collapse { display: none; }   /* collapse only applies to the side-by-side layout */
  .search { max-width: none; }
  #statusbar .muted { display: none; }
}
