:root {
  --nav-col-width: clamp(240px, 19vw, 340px);
  --detail-col-width: 280px;
  --altitude-width: 64px;
  /* One shared feel for the floating panels: how they slide/settle, and the
     lifted-off-the-map shadow every floating control shares. */
  --panel-ease: cubic-bezier(0.2, 0, 0, 1);
  --float-shadow: 0 4px 18px rgba(0, 0, 0, 0.45);

  /* ---- Color system: slate surfaces, one blue accent for everything
     interactive, FICSIT orange reserved for the brand + the "get a save
     onto the map" path (logo, Load save, progress), and the existing pink
     kept as the "something is hidden from view" signal. */
  --surface-0: #131519;   /* deepest chrome: top bar, sidebar footer */
  --surface-1: #1a1d22;   /* panels: sidebar, altitude rail */
  --surface-2: #21242b;   /* popups: modals, dropdown, tooltip, menus */
  --raised: #23262c;      /* controls: buttons, fields, cards */
  --raised-hover: #2b2f37;
  --inset: #171a20;       /* wells sunk into a popup (icon frames, bar tracks) */
  --border: #30333b;
  --border-popup: #3a3f4a;
  --border-hover: #4a505c;
  --text: #d9dde4;
  --text-bright: #fff;
  --muted: #8a92a3;
  --faint: #6a7180;
  --accent: #5ba3e0;
  --accent-soft: #8ab4f8;
  --accent-bg: #2a3545;
  --accent-bg-hover: #33435a;
  --accent-border: #3a5070;
  --brand: #f2913d;       /* FICSIT orange */
  --brand-bright: #ffa552;
  --hidden-pink: #ff3b81;
  --hidden-pink-soft: #ff9ec2;
  --hidden-pink-bg: #3a2330;
  --font: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", sans-serif;
}

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  font-family: var(--font);
  font-size: 13px;
  background: #000;
}

/* Dark, slim scrollbars everywhere -- the browser's default light-gray bars
   were the single most visibly "un-themed" element left on Windows. */
* {
  scrollbar-width: thin;
  scrollbar-color: #3d424c transparent;
}

::-webkit-scrollbar {
  width: 9px;
  height: 9px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: #3d424c;
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}

::-webkit-scrollbar-thumb:hover {
  background-color: #4d535f;
}

::selection {
  background: rgba(91, 163, 224, 0.35);
}

/* One consistent hover/press feel for every button in the app, instead of
   each one transitioning (or snapping) on its own. */
button {
  font-family: inherit;
  transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

button:focus-visible, select:focus-visible, input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ---- Top controls: no bar anymore -- a transparent, non-blocking overlay
   row of individually floating controls sitting directly over the map
   (Spotlight-style): menu + logo top-left, the search field centered, the
   per-save status buttons top-right. pointer-events:none on the row itself
   so the map stays pannable between the controls; each control re-enables
   its own pointer events below. Right padding leaves room for the altitude
   rail docked at the right edge. */
#topBar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  box-sizing: border-box;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px 0 14px;
  padding-right: calc(var(--altitude-width) + 12px);
  z-index: 60; /* Above the sidebar overlay (40) so the suggestions dropdown paints over it. */
  pointer-events: none;
  background: none;
}

#menuButton, #logoButton, #searchBox, .topIconButton {
  pointer-events: auto;
}

/* Top-left hamburger -- shows/hides the whole sidebar overlay (panels.js). */
#menuButton {
  flex: none;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border-popup);
  border-radius: 10px;
  box-shadow: var(--float-shadow);
  cursor: pointer;
}

#menuButton:hover {
  background: var(--raised-hover);
  border-color: var(--border-hover);
  color: var(--text-bright);
}

/* While the panel is hidden, the button stays quietly accented so it's
   findable as "the way to get the panel back". */
#menuButton.panelHidden {
  background: var(--accent-bg);
  border-color: var(--accent-border);
  color: var(--accent-soft);
}

#menuButton svg {
  display: block;
}

/* The one brand moment in the chrome: a two-line FICSIT-orange wordmark
   (small-caps kicker over the name) instead of a generic gray button. Still
   a real button -- it navigates back to mode selection. */
#logoButton {
  flex: none;
  height: 42px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 1px;
  padding: 0 14px 0 11px;
  background: var(--surface-2);
  border: 1px solid var(--border-popup);
  border-left: 3px solid var(--brand);
  border-radius: 10px;
  box-shadow: var(--float-shadow);
  cursor: pointer;
  box-sizing: border-box;
  text-align: left;
}

.logoKicker {
  font-size: 9px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--faint);
  line-height: 1;
}

.logoName {
  font-size: 14.5px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--brand);
  line-height: 1.1;
  white-space: nowrap;
}

#logoButton:hover {
  background: var(--raised-hover);
  border-color: var(--border-hover);
  border-left-color: var(--brand-bright);
}

#logoButton:hover .logoName {
  color: var(--brand-bright);
}

#logoButton:hover .logoKicker {
  color: var(--muted);
}

/* Spotlight-style search: a free-floating pill centered over the map (no
   band behind it), with a search glyph inside it and a custom suggestions
   dropdown (see #searchSuggestions) anchored directly beneath -- replacing
   the native <datalist>, whose dropdown the browser positioned and styled
   itself (badly, and with no room for item icons). */
#mainSearchWrap {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  justify-content: center;
  pointer-events: none;
}

#searchBox {
  position: relative;
  width: 100%;
  max-width: 560px;
  display: flex;
  align-items: center;
}

#searchIcon {
  position: absolute;
  left: 16px;
  color: #8a92a3;
  pointer-events: none;
}

#mainSearchInput {
  width: 100%;
  box-sizing: border-box;
  height: 42px;
  font-family: var(--font);
  font-size: 15px;
  padding: 0 18px 0 44px;
  border-radius: 999px;
  border: 1px solid var(--border-popup);
  background: var(--surface-2);
  color: #eee;
  box-shadow: var(--float-shadow);
  transition: border-color 0.12s ease, background-color 0.12s ease, box-shadow 0.12s ease;
}

#mainSearchInput:hover {
  border-color: var(--border-hover);
}

#mainSearchInput:focus {
  outline: none;
  border-color: var(--accent);
  background: #262a31;
  box-shadow: var(--float-shadow), 0 0 0 3px rgba(91, 163, 224, 0.18);
}

#mainSearchInput::placeholder {
  color: #888;
}

/* Custom suggestions dropdown -- anchored to #searchBox (position:relative),
   so it always matches the search field's width and sits just below it. */
#searchSuggestions {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  max-height: min(70vh, 440px);
  overflow-y: auto;
  background: #21242b;
  border: 1px solid #3a3f4a;
  border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55);
  padding: 6px;
  z-index: 20;
}

.searchSuggestionRow {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 10px;
  border-radius: 8px;
  cursor: pointer;
  transition: background-color 0.08s ease;
}

.searchSuggestionRow.active {
  background: #2f6bd8;
}

.searchSuggestionRow.active .searchSuggestionLabel {
  color: #fff;
}

.searchSuggestionIcon {
  width: 26px;
  height: 26px;
  flex: none;
  object-fit: contain;
}

/* Vehicle glyphs (icons/vehicles/*.png) are the game's white-on-transparent
   monochrome icons -- backed by the same solid vehicle-orange circle their
   map pins use (filters.js's VEHICLE_COLOR) so they hold up on any row state
   and read as vehicles next to the full-color item/building icons. */
.searchSuggestionVehicleIcon {
  background: #f39c12;
  border-radius: 50%;
  padding: 4px;
  box-sizing: border-box;
}

.searchSuggestionLabel {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 14px;
  color: #eee;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* "ITEMS" / "BUILDINGS" section divider inside the dropdown -- see
   finditem.js's renderSuggestions, which now groups matches by kind. */
.searchSuggestionGroupLabel {
  padding: 7px 10px 4px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #6a7180;
}

/* Show/hide eye toggle -- on building suggestion rows AND in the building
   modal's header (#buildingModalVisibilityToggle). Flips the exact same
   sidebar checkbox this building's row owns (see filters.js's
   buildingSearchEntries), so it stays in perfect sync with the sidebar no
   matter which one is clicked. stopPropagation'd on suggestion rows so it
   never also triggers the row's click-to-open-modal behavior.
   A real filled chip in both states (not a bare gray glyph): accent blue
   while shown, and the app's "hidden from view" pink while hidden, so the
   state is legible at a glance. */
.visibilityToggle {
  flex: none;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #6b2b47;
  border-radius: 8px;
  background: var(--hidden-pink-bg);
  color: var(--hidden-pink-soft);
  cursor: pointer;
}

.visibilityToggle:hover {
  border-color: var(--hidden-pink);
  color: #fff;
}

.visibilityToggle.isShown {
  background: var(--accent-bg);
  border-color: var(--accent-border);
  color: var(--accent-soft);
}

.visibilityToggle.isShown:hover {
  background: var(--accent-bg-hover);
  border-color: var(--accent);
  color: #fff;
}

/* On the keyboard/hover-highlighted (solid blue) suggestion row, the chip's
   own tints would vanish into the background -- switch to a white outline. */
.searchSuggestionRow.active .visibilityToggle {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.55);
  color: #fff;
}

.visibilityToggle svg {
  display: block;
}

/* The modal header's eye sits next to a 44px icon -- one size up. */
#buildingModalVisibilityToggle {
  width: 34px;
  height: 34px;
}

.searchSuggestionEmpty {
  padding: 10px 12px;
  color: #888;
  font-style: italic;
  font-size: 13px;
}

/* The per-save status buttons (Dimensional Depot + the progression views --
   see index.html's #topBarStatusButtons), grouped into one floating pill at
   the top right. Each button gets its own lighter tile inside the pill --
   the game-icon PNGs are mostly dark, so sitting them directly on the dark
   chrome made them near-invisible. */
#topBarStatusButtons {
  flex: none;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px;
  background: var(--surface-2);
  border: 1px solid var(--border-popup);
  border-radius: 12px;
  box-shadow: var(--float-shadow);
}

.topIconButton {
  flex: none;
  width: 38px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #363d49;
  border: 1px solid #454c5a;
  border-radius: 8px;
  cursor: pointer;
}

.topIconButton:hover {
  background: #424a59;
  border-color: #5a6272;
}

.topIconButton img {
  width: 24px;
  height: 24px;
  object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
}

/* The map owns the whole viewport (minus the altitude rail docked right) --
   the sidebar and top controls float OVER it, so showing/hiding/resizing
   panels never moves or re-fits the map. */
#map {
  position: absolute;
  top: 0;
  left: 0;
  right: var(--altitude-width);
  bottom: 0;
  background: #000;
  /* Contain Leaflet's internal stacking -- its panes/zoom controls use
     z-index values up to ~1000, which would otherwise live in the root
     stacking context and paint over the top bar's search-suggestions
     dropdown (z-index 20 inside #topBar's z-index:10). isolate keeps all of
     that self-contained so #topBar (and thus the dropdown) sits cleanly
     above the map. */
  isolation: isolate;
}

/* No level-1 category selected -- the detail column (content pane) collapses
   to zero width rather than sitting there empty. Toggled on <body> by
   filters.js's selectCategory/deselectAllCategories. Animated via the width
   transitions on #sidebar/#categoryDetailColumn (the sidebar floats over the
   map, so the map itself never moves). <body> ships WITH this class already
   set (see index.html) so the empty detail column never flashes on screen
   during the initial load before filters.js runs -- nothing is ever selected
   until a save is loaded and the user clicks a category anyway. */
body.no-category-selected #categoryDetailColumn {
  width: 0;
}

body.no-category-selected #sidebar {
  width: var(--nav-col-width);
}

.leaflet-container {
  background: #000;
}

/* Leaflet's stock CSS puts mix-blend-mode: plus-lighter on tile images (a
   Chromium tile-fade flicker workaround) -- ADDITIVE compositing. Our base
   tiles overlap on purpose (1px shared-content bleed, see MapTileLayer in
   map.js): under plus-lighter the overlap pixels sum to a bright grid, and
   the per-tile render surfaces it forces can show hairline seams on some
   GPU/driver combos. Source-over + identical-content overlap is seamless. */
.leaflet-container img.leaflet-tile {
  mix-blend-mode: normal;
}

/* Leaflet's default zoom control ships as white boxes with black glyphs --
   the one stock-Leaflet element still visible in the chrome. Re-skinned to
   match the app's raised-control treatment. */
.leaflet-touch .leaflet-bar,
.leaflet-bar {
  border: 1px solid var(--border);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}

.leaflet-bar a,
.leaflet-touch .leaflet-bar a {
  background: var(--raised);
  color: var(--text);
  border-bottom-color: var(--border);
}

.leaflet-bar a:hover,
.leaflet-touch .leaflet-bar a:hover {
  background: var(--raised-hover);
  color: var(--text-bright);
}

.leaflet-bar a.leaflet-disabled,
.leaflet-touch .leaflet-bar a.leaflet-disabled {
  background: var(--surface-1);
  color: var(--faint);
}

/* ---- Sidebar: a two-column browser -- the fixed-width nav panel (level-1
   category list, plus the save-loading footer at its bottom) alongside the
   detail pane (shown only when a category is selected). A flex ROW so the
   two columns sit side by side and each runs full height; the footer lives
   INSIDE the nav panel (not spanning the whole sidebar), so it stays at the
   nav panel's width and doesn't stretch when the detail pane appears.
   Floats OVER the map as a rounded card (starting below the top-left menu/
   logo cluster) -- toggling or resizing it never moves the map. The width
   transition animates the detail column opening/closing; the transform/
   opacity pair is the menu button's show/hide slide. */
#sidebar {
  position: absolute;
  top: 66px;
  left: 12px;
  width: calc(var(--nav-col-width) + var(--detail-col-width));
  bottom: 12px;
  background: var(--surface-1);
  color: var(--text);
  box-sizing: border-box;
  display: flex;
  flex-direction: row;
  overflow: hidden;
  border: 1px solid var(--border-popup);
  border-radius: 14px;
  box-shadow: 0 10px 36px rgba(0, 0, 0, 0.5);
  z-index: 40;
  transition: width 0.18s var(--panel-ease),
              transform 0.28s var(--panel-ease),
              opacity 0.22s ease;
}

#sidebar.hidden {
  transform: translateX(calc(-100% - 24px));
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  #sidebar, #categoryDetailColumn { transition: none; }
}

/* Level-1 categories -- a plain list of names (see filters.js's
   renderTopLevelCategory), each with its own visibility toggle switch.
   Clicking a row (anywhere but the switch) selects it as the one shown in
   categoryDetailPane; the switch works independently of selection. Laid out
   as a column: the Check/Uncheck-all header, then the scrollable category
   list, then the save-loading footer pinned at the bottom. Its width is set
   by filters.js's autoSizeNavPanel (--nav-col-width) to fit the widest
   category label instead of a fixed guess, so no horizontal space is
   wasted. */
#categoryNavPanel {
  flex: none;
  width: var(--nav-col-width);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-right: 1px solid var(--border);
}

/* Drag-to-resize handles (see panels.js) -- invisible 7px grab strips that
   light up as a thin accent stripe on hover/drag. Positioned over the two
   column right edges as direct children of #sidebar: the nav handle
   straddles the nav/detail border; the detail handle hugs the sidebar's
   right edge. */
.panelResizeHandle {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 7px;
  cursor: col-resize;
  z-index: 5;
  touch-action: none;
}

#navResizeHandle {
  left: calc(var(--nav-col-width) - 4px);
}

#detailResizeHandle {
  right: 0;
}

/* With the detail column closed, its handle would sit exactly on top of the
   nav handle at the sidebar's right edge -- there is nothing to resize, so
   it goes away entirely. */
body.no-category-selected #detailResizeHandle {
  display: none;
}

.panelResizeHandle::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 2px;
  width: 3px;
  background: transparent;
  transition: background-color 0.12s ease;
}

.panelResizeHandle:hover::before,
.panelResizeHandle.dragging::before {
  background: var(--accent);
}

/* Kill the width transitions while a handle is being dragged -- the panel
   must track the pointer 1:1, not ease toward it. */
body.panelResizing {
  cursor: col-resize;
  user-select: none;
}

body.panelResizing #sidebar,
body.panelResizing #categoryDetailColumn {
  transition: none;
}

#categoryNavHeader {
  flex: none;
  display: flex;
  gap: 8px;
  padding: 10px;
  border-bottom: 1px solid var(--border);
}

#categoryNavHeader button {
  flex: 1 1 50%;
  font-size: 12px;
  padding: 7px 0;
}

/* Pink accent matches #activeFilterBanner/#activeFilterClear -- the app's
   existing "something is hidden from view, here's how to undo it" color. */
#resetHiddenButton {
  flex: none;
  margin: 0 10px 10px;
  padding: 7px 0;
  font-size: 12px;
  font-weight: 600;
  text-align: center;
  color: #ff9ec2;
  background: #3a2330;
  border: 1px solid #ff3b81;
  border-radius: 6px;
  cursor: pointer;
}

#resetHiddenButton:hover {
  background: #4a2a3c;
  color: #fff;
}

#categoryNavColumn {
  flex: 1 1 auto;
  min-height: 0;
  box-sizing: border-box;
  overflow-y: auto;
  padding: 8px 6px;
}

#categoryDetailColumn {
  flex: none;
  width: var(--detail-col-width);
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transition: width 0.18s var(--panel-ease);
}

/* Fixed at the column's full open width (the column itself clips it) so the
   open/close animation slides the content in as one piece instead of
   reflowing the text at every frame. */
#categoryDetailPane {
  flex: 1 1 auto;
  min-height: 0;
  width: var(--detail-col-width);
  overflow-y: auto;
  padding: 6px 8px;
  box-sizing: border-box;
}

/* One row per top-level category in the nav column -- reuses .groupTitle's
   existing flex/toggle/icon/label layout (see filters.js's
   renderTopLevelCategory, which relocates a real .groupTitle element here
   rather than rebuilding an equivalent one from scratch) with a few
   nav-specific overrides layered on top. Laid out as a self-contained card
   ([icon] label ............ [switch]) -- the colored icon and name sit on
   the left, the visibility switch is pushed to the far right (see the
   margin-left:auto below), so the row's full width is actually used
   instead of everything crowding the left edge. */
.categoryNavRow {
  /* Explicit flex here because the base "display:flex" only lives on
     ".filterGroup > .groupTitle", and renderTopLevelCategory relocates this
     row OUT of its .filterGroup -- so that selector no longer matches and
     the row would otherwise fall back to plain inline flow (which is why the
     order/margin-left tricks below need this to take effect). */
  display: flex;
  align-items: center;
  cursor: pointer;
  user-select: none;
  padding: 11px 14px !important;
  margin-bottom: 6px;
  border-radius: 9px;
  background: var(--raised);
  border: 1px solid transparent;
  font-size: 14.5px !important;
  text-transform: none !important;
  letter-spacing: normal !important;
  gap: 11px !important;
  transition: background-color 0.1s ease, border-color 0.1s ease;
}

.categoryNavRow .groupLabel {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.3;
  flex: 1 1 auto;
  min-width: 0;
}

.categoryNavRow:hover {
  background: var(--raised-hover);
  border-color: var(--border);
}

.categoryNavRow.active {
  background: #2a3d54;
  color: #8ab4f8;
  box-shadow: inset 3px 0 0 #5ba3e0;
}

/* Bigger than the default .toggleSwitch (used everywhere else -- the
   detail pane's own rows, subcategories, etc.) -- these are the single
   biggest interactive control on this panel, so they get to be easy
   targets, not an afterthought next to the label. Pushed to the far right
   edge of the row (margin-left:auto) after the flex-growing label. */
.categoryNavRow .toggleSwitch {
  width: 40px;
  height: 22px;
  /* The switch is the first child in DOM order (see renderGroup), so
     order:2 moves it visually after the flex-growing label, and the label's
     flex-grow then pushes it to the row's far-right edge. */
  order: 2;
  flex: none;
}

.categoryNavRow .toggleSlider::before {
  width: 16px;
  height: 16px;
  top: 3px;
  left: 3px;
}

.categoryNavRow .toggleSwitch input:checked + .toggleSlider::before {
  transform: translateX(18px);
}

/* A slightly larger, rounded-square color chip reads more like a category
   badge than the tiny dot the detail-pane rows use. */
.categoryNavRow .icon {
  width: 16px;
  height: 16px;
  border-radius: 4px !important;
}

.categoryNavRow .icon-circle {
  border-radius: 50% !important;
}

.categoryDetailGroup {
  display: none;
}

.categoryDetailGroup.active {
  display: block;
}

#checkAllButton, #uncheckAllButton {
  font-size: 11px;
  padding: 5px 7px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
}

#checkAllButton:hover, #uncheckAllButton:hover {
  color: var(--text);
  border-color: var(--border-hover);
  background: var(--raised);
}

/* ---- Sidebar footer: save file picker + game settings -- everything only
   touched around load time, tucked below the category browser instead of
   pinned above it. */
#sidebarFooter {
  flex: none;
  max-height: 46%;
  overflow-y: auto;
  padding: 12px;
  box-sizing: border-box;
  border-top: 1px solid var(--border);
  background: var(--surface-0);
}

#loadPanelHeader {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 7px;
  color: #8a92a3;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.loadPanelIcon {
  flex: none;
  color: var(--brand);
}

/* The load target (see data.js's loadLocalFile) -- dashed outline so it
   reads as a drop zone rather than yet another button, amber accent on
   hover/drag. */
#uploadDropZone {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  margin-top: 8px;
  padding: 9px 8px;
  font-size: 12px;
  color: var(--muted);
  border: 1px dashed #4a4f5a;
  border-radius: 6px;
  cursor: pointer;
}

#uploadDropZone:hover, #uploadDropZone.drag-over {
  color: #ffd7ae;
  border-color: #8a5e2e;
  background: #3d2d1c;
}

#uploadDropZone.uploading {
  opacity: 0.6;
  pointer-events: none;
}

#loadStatus {
  margin-top: 8px;
  color: #999;
  font-size: 11.5px;
  min-height: 1.2em;
}

/* The one number this whole panel exists to report -- given its own small
   stat-chip treatment (bold accent value + muted label, same pairing as
   .buildingStatValue/.buildingStatLabel in the building modal) instead of a
   single flat gray line, so it actually reads as a result rather than a
   caption. */
#totalObjectCount {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid #2c2f37;
  min-height: 1.2em;
}

.totalObjectCountValue {
  color: #8ab4f8;
  font-weight: 700;
  font-family: "Consolas", "SF Mono", monospace;
  font-size: 13px;
}

.totalObjectCountLabel {
  color: #777;
  font-size: 11px;
}

#loadProgressBar {
  display: none;
  margin-top: 8px;
  height: 6px;
  background: #2a2d33;
  border-radius: 999px;
  overflow: hidden;
}

#loadProgressFill {
  height: 100%;
  width: 0%;
  background: linear-gradient(to right, #c96f22, var(--brand));
  border-radius: 999px;
  transition: width 0.2s ease-out;
}

/* Game-mode settings (Power Cost Multiplier / Node Purity / Node
   Randomization) -- see data.js's showGameSettings(). Hidden via inline
   display:none until a save with at least one of these set is loaded. */
#gameSettingsPanel {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid #444;
  font-size: 12px;
}

.gameSettingRow {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 1px 0;
}

.gameSettingLabel {
  color: #999;
}

.gameSettingValue {
  color: #ddd;
  font-weight: 500;
}

/* Shared by the item-search modal's results list and the Dimensional Depot
   contents list (see finditem.js's renderLocationList) -- a scrollable,
   bordered list of label/count rows. */
.itemLocationList {
  max-height: 56vh;
  overflow-y: auto;
  margin-bottom: 10px;
  border: 1px solid #3a3a3a;
  border-radius: 6px;
}

.itemLocationRow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  font-size: 14px;
  border-bottom: 1px solid #2c2f37;
  /* Long lists (a big group expanded, the Depot's hundreds of item types)
     shouldn't pay layout/paint for rows far offscreen -- lets the browser
     skip them entirely until scrolled near. The intrinsic-size hint keeps
     the scrollbar length stable while skipped. */
  content-visibility: auto;
  contain-intrinsic-size: auto 40px;
}

.itemLocationRow:hover {
  background: rgba(255, 255, 255, 0.05);
}

.itemLocationRow:last-child {
  border-bottom: none;
}

/* Same exact->fuzzy->generic icon chain as the search dropdown (see
   finditem.js's renderLocationList) -- keeps result lists scannable by
   shape, not just by reading every label. */
.itemLocationIcon {
  width: 26px;
  height: 26px;
  flex: none;
  object-fit: contain;
}

.itemLocationLabel {
  flex: 1 1 auto;
  min-width: 0;
  color: #ddd;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.itemLocationCount {
  color: #8ab4f8;
  font-weight: 600;
  font-size: 14px;
  flex: none;
}

/* ---- Grouped item-location list (see finditem.js's
   renderGroupedLocations) -- one expandable header row per building type,
   individual machines as indented child rows underneath. */
.itemLocationGroupHeader {
  cursor: pointer;
  user-select: none;
}

.itemLocationGroupHeader:hover {
  background: rgba(255, 255, 255, 0.07);
}

.itemLocationChevron {
  flex: none;
  display: inline-flex;
  color: #7a8190;
  line-height: 0;
}

.itemLocationChevron svg {
  transition: transform 0.12s ease-out;
}

.itemLocationGroupHeader.expanded .itemLocationChevron svg {
  transform: rotate(90deg);
}

/* "× 1,234" -- how many individual machines this summed row stands for. */
.itemLocationGroupBadge {
  flex: none;
  background: #262a31;
  border: 1px solid #333844;
  border-radius: 999px;
  padding: 1px 9px;
  font-size: 12px;
  color: #9aa0ab;
}

.itemLocationChildren {
  background: rgba(0, 0, 0, 0.18);
  border-bottom: 1px solid #2c2f37;
}

/* Child rows show only position + count (the type is the header's job);
   the extra left padding indents them under the header's label column. */
.itemLocationChildRow {
  padding-left: 48px;
  font-size: 13px;
}

.itemLocationChildRow .itemLocationLabel {
  color: #a7adb8;
  font-family: "Consolas", "SF Mono", monospace;
  font-size: 12.5px;
}

.itemLocationShowMore {
  display: block;
  width: 100%;
  padding: 7px 0 7px 48px;
  text-align: left;
  background: none;
  border: none;
  border-bottom: 1px solid #2c2f37;
  color: #8ab4f8;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}

.itemLocationShowMore:hover {
  background: rgba(255, 255, 255, 0.05);
  color: #aecbfa;
}

/* ---- Item search / Dimensional Depot results modal -- a single shared
   dialog (see finditem.js's openModal), centered over the map, used for
   both the search bar's results and the depot-icon button's contents. */
#itemModalOverlay, #selectionModalOverlay, #buildingModalOverlay, #progressionModalOverlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  /* No backdrop-filter here on purpose: blurring the backdrop means every
     repaint inside the modal (row hover, scroll, the cursor-following
     tooltip) re-filters the entire viewport-sized map underneath. Fine on
     the GPU, but with hardware acceleration off (user setting, RDP,
     blocklisted driver) that software re-blur runs per frame and drops the
     whole modal to ~10 FPS. The dim alone separates the layers well enough. */
  background: rgba(8, 10, 14, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1500;
}

#itemModal, #selectionModal {
  width: 640px;
  max-width: 92vw;
  max-height: 86vh;
  background: var(--surface-2);
  border: 1px solid var(--border-popup);
  border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
  padding: 16px 18px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  color: var(--text);
  transform: scale(0.96);
  opacity: 0;
  animation: buildingModalIn 0.16s ease-out forwards;
}

@media (prefers-reduced-motion: reduce) {
  #itemModal, #selectionModal { animation: none; transform: none; opacity: 1; }
}

#itemModalHeader, #selectionModalHeader {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-bottom: 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border-popup);
  flex: none;
}

/* Same framed-icon treatment as #buildingModalIcon, one size down -- the
   item modal's list rows are denser than the building modal's stat tiles. */
#itemModalIcon {
  width: 42px;
  height: 42px;
  flex: none;
  object-fit: contain;
  background: var(--inset);
  border: 1px solid #2c2f37;
  border-radius: 8px;
  padding: 4px;
  box-sizing: border-box;
}

#itemModalTitle, #selectionModalTitle {
  font-weight: bold;
  font-size: 19px;
  color: #fff;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#itemModalClose, #selectionModalClose {
  background: none;
  border: none;
  color: #999;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 0 2px;
}

#itemModalClose:hover, #selectionModalClose:hover {
  color: #fff;
}

#itemModalSummary, #selectionModalSummary {
  color: #bbb;
  font-size: 14px;
  margin-bottom: 8px;
  flex: none;
}

#itemModalHighlightToggle {
  flex: none;
  width: 100%;
  margin-top: 4px;
  padding: 9px 0;
  border-radius: 8px;
  border: 1px solid var(--accent-border);
  background: var(--accent-bg);
  color: var(--accent-soft);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

#itemModalHighlightToggle:hover {
  background: var(--accent-bg-hover);
  color: #fff;
}

/* ---- Building info window -- see finditem.js's Building object. Opened
   from a building search suggestion (row click or Enter); shares the same
   overlay/backdrop rule as #itemModal above, but its own dialog -- a
   building's save-wide summary (count, recipe mix, power, combined
   inventory) has a richer shape than an item's plain location list, so it
   gets stat tiles and a small recipe bar chart instead of a single list. */
#buildingModal {
  width: 600px;
  max-width: 92vw;
  max-height: 86vh;
  background: #20232a;
  border: 1px solid #3a3f4a;
  border-radius: 12px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.65);
  padding: 18px 20px 16px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  color: #ddd;
  transform: scale(0.96);
  opacity: 0;
  animation: buildingModalIn 0.16s ease-out forwards;
}

@keyframes buildingModalIn {
  to { transform: scale(1); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  #buildingModal { animation: none; transform: none; opacity: 1; }
}

#buildingModalHeader {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 12px;
  margin-bottom: 10px;
  border-bottom: 1px solid #3a3f4a;
  flex: none;
}

#buildingModalIcon {
  width: 44px;
  height: 44px;
  flex: none;
  object-fit: contain;
  background: #171a20;
  border: 1px solid #2c2f37;
  border-radius: 8px;
  padding: 4px;
  box-sizing: border-box;
}

/* Vehicle-orange circle behind the white monochrome vehicle glyphs -- same
   reasoning as .searchSuggestionVehicleIcon above. */
#buildingModalIcon.vehicleModalIcon {
  background: #f39c12;
  border-radius: 50%;
  padding: 7px;
}

#buildingModalHeadings {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

#buildingModalTitle {
  font-weight: 700;
  font-size: 18px;
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Background/text color set inline per-category (see finditem.js's
   Filters.buildingCategoryColor) -- ties this chip back to the exact color
   the same building uses in the sidebar. */
#buildingModalCategory {
  display: inline-flex;
  align-self: flex-start;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

#buildingModalClose {
  flex: none;
  background: none;
  border: none;
  color: #999;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 0 2px;
}

#buildingModalClose:hover {
  color: #fff;
}

#buildingModalSummary {
  color: #bbb;
  margin-bottom: 10px;
  flex: none;
}

#buildingModalStats {
  display: flex;
  gap: 10px;
  margin-bottom: 14px;
  flex: none;
}

.buildingStatTile {
  flex: 1 1 0;
  min-width: 0;
  background: #262a31;
  border: 1px solid #333844;
  border-radius: 8px;
  padding: 10px 12px;
}

.buildingStatValue {
  display: block;
  font-family: "Consolas", "SF Mono", monospace;
  font-size: 19px;
  font-weight: 700;
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.buildingStatLabel {
  display: block;
  font-size: 11px;
  color: #9aa0ab;
  margin-top: 3px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

#buildingModalScroll {
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}

.buildingModalSectionLabel {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #9aa0ab;
  margin: 4px 0 8px;
}

#buildingModalRecipes {
  margin-bottom: 14px;
}

.recipeBarRow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 3px 0;
}

.recipeBarLabel {
  flex: 0 0 42%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: #ddd;
  font-size: 12.5px;
}

.recipeBarTrack {
  flex: 1 1 auto;
  height: 8px;
  background: #171a20;
  border-radius: 999px;
  overflow: hidden;
}

.recipeBarFill {
  height: 100%;
  border-radius: 999px;
  transition: width 0.25s ease-out;
}

.recipeBarCount {
  flex: none;
  width: 36px;
  text-align: right;
  color: #8ab4f8;
  font-size: 12px;
  font-weight: 600;
}

#buildingModalHighlightToggle {
  flex: none;
  width: 100%;
  margin-top: 14px;
  padding: 9px 0;
  border-radius: 8px;
  border: 1px solid #3a5070;
  background: #2a3545;
  color: #8ab4f8;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

#buildingModalHighlightToggle:hover {
  background: #33435a;
  color: #fff;
}

/* ---- Right-click context menu -- see contextmenu.js. -------------------- */

#contextMenu {
  position: fixed;
  z-index: 1700; /* Above modals (1500) and the tooltip (1000) -- the most immediate, transient UI on screen. */
  min-width: 190px;
  max-width: 280px;
  background: #20232a;
  border: 1px solid #3a3f4a;
  border-radius: 8px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
  padding: 6px;
  font-size: 12.5px;
  color: #ddd;
}

.contextMenuItem {
  padding: 8px 10px;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.contextMenuItem:hover {
  background: #2f6bd8;
  color: #fff;
}

.contextMenuDivider {
  height: 1px;
  margin: 4px 2px;
  background: #333;
}

/* ---- Rectangle selection (right-drag) -- see selection.js. -------------- */

/* The drag rectangle itself: a fixed-position box tracking the pointer while
   the right button is held. */
#selectionRect {
  position: fixed;
  z-index: 1400;
  border: 1.5px solid #5ba3e0;
  background: rgba(91, 163, 224, 0.15);
  pointer-events: none;
}

/* Floating panel shown after a selection is made -- object count plus the
   "List objects" / "Total inventory" actions. Bottom-centre of the map so it
   never sits under the top bar or the just-drawn rectangle. */
#selectionPanel {
  position: absolute;
  bottom: 22px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 600;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 10px 10px 16px;
  background: #20232a;
  border: 1px solid #3a3f4a;
  border-radius: 10px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.55);
  color: #eee;
  font-size: 13px;
}

#selectionCount {
  font-weight: 600;
  white-space: nowrap;
}

#selectionPanelButtons {
  display: flex;
  gap: 8px;
  align-items: center;
}

#selectionPanelButtons button {
  flex: none;
  font-size: 12px;
  padding: 6px 12px;
  border-radius: 6px;
  cursor: pointer;
  border: 1px solid #3a5070;
  background: #2a3545;
  color: #8ab4f8;
}

#selectionPanelButtons button:hover {
  background: #33435a;
  color: #fff;
}

#selectionClearBtn {
  padding: 4px 10px !important;
  font-size: 16px !important;
  line-height: 1;
  color: #999 !important;
  background: none !important;
  border-color: #3a3f4a !important;
}

#selectionClearBtn:hover {
  color: #fff !important;
  background: #2a2e36 !important;
}

/* Floating "a find-item filter is active" banner -- shown over the map (the
   modal is dismissed so the highlighted results are actually visible, see
   finditem.js) once "Show only these on map" is clicked. Lets the user
   navigate the filtered map and revert ("Show all") or reopen the list
   ("Details") without the modal covering everything. Centered near the top
   of the map, above it (isolation:isolate on #map keeps its Leaflet z-index
   contained, so a plain z-index here sits cleanly on top). */
#activeFilterBanner {
  position: absolute;
  top: 68px; /* Just below the floating search pill. */
  left: 50%;
  transform: translateX(-50%);
  z-index: 500;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 90vw;
  padding: 8px 8px 8px 14px;
  background: #20232a;
  border: 1px solid #ff3b81;
  border-radius: 999px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
  color: #eee;
  font-size: 13px;
}

#activeFilterLabel {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

#activeFilterBanner button {
  flex: none;
  font-size: 12px;
  padding: 4px 12px;
  border-radius: 999px;
  cursor: pointer;
  border: 1px solid #3a3f4a;
  background: #2a2e36;
  color: #ddd;
}

#activeFilterBanner button:hover {
  border-color: #666;
  color: #fff;
}

#activeFilterClear {
  border-color: #ff3b81 !important;
  background: #3a2330 !important;
  color: #ff9ec2 !important;
}

#activeFilterClear:hover {
  background: #4a2a3c !important;
  color: #fff !important;
}

/* Vertical altitude filter, docked to the right edge of the screen --
   vertical gives much more usable travel/precision than a narrow
   horizontal slider would in the same sidebar width. */
#altitudePanel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: var(--altitude-width);
  background: var(--surface-1);
  color: var(--text);
  box-sizing: border-box;
  padding: 4px;
  border-left: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  overflow: hidden;
}

.altitudeTitle {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #888;
}

.altitudeTitleIcon {
  color: #5ba3e0;
}

.altitudeValue {
  font-size: 11px;
  color: #bbb;
  margin: 2px 0;
  flex: none;
}

/* A single visual track (.altitudeTrackBg + .altitudeTrackFill) with two
   real <input type="range"> elements stacked exactly on top of it -- rather
   than two side-by-side sliders -- so it reads as one ruler with two handles
   instead of two independent controls that happen to be neighbors. */
.altitudeTrackWrap {
  position: relative;
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
}

/* Inset by half the thumb box (altitude.js's THUMB_PX / 2) so the track's
   ends sit where the thumb centers actually stop, matching the fill. */
.altitudeTrackBg {
  position: absolute;
  top: 11.5px;
  bottom: 11.5px;
  left: 50%;
  transform: translateX(-50%);
  width: 4px;
  background: #3a3a3a;
  border-radius: 2px;
  pointer-events: none;
}

/* top/height are set inline by altitude.js to span exactly the currently
   selected min..max range. Draggable (see altitude.js's pointerdown handler
   on this element) to shift the whole [min, max] window at once, keeping
   its span fixed, instead of only being able to move one handle at a time.
   The actual colored bar stays a slim 4px (background-clip: content-box
   confines the fill color to the width box), but the invisible padding on
   either side widens the *hit target* to something actually grabbable --
   a bare 4px-wide strip would be nearly impossible to grab precisely. */
.altitudeTrackFill {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 4px;
  padding: 0 8px;
  background-color: var(--accent);
  background-clip: content-box;
  border-radius: 2px;
  pointer-events: auto;
  cursor: grab;
  touch-action: none;
}

/* altitude.js toggles .dragging for the duration of a fill-bar drag (pointer
   capture keeps events flowing to the element, so the class -- not :hover
   state -- is the reliable signal that a grab is in progress). */
.altitudeTrackFill.dragging {
  cursor: grabbing;
}

/* writing-mode is the standards-track way to get a vertical range input --
   unlike -webkit-appearance:slider-vertical, width/height keep their normal
   meaning (no axis swap from rotation), so height:100% reliably fills all
   available vertical space. direction:rtl flips the default min-at-top
   orientation so the top of the track is the maximum, matching the usual
   "up = higher altitude" expectation.
   Both inputs are stacked exactly on top of each other (position:absolute,
   inset 0) rather than placed side by side, with their own native track
   hidden (transparent) since .altitudeTrackBg/.altitudeTrackFill draw the
   one shared visual track underneath. pointer-events:none on the control
   itself -- re-enabled only on the thumb pseudo-elements below -- is what
   lets a click reach whichever handle is actually under the cursor instead
   of the topmost of the two stacked <input>s swallowing every click across
   its entire (invisible) track. */
.altitudeRangeInput {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  writing-mode: vertical-lr;
  direction: rtl;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  pointer-events: none;
}

.altitudeRangeInput::-webkit-slider-runnable-track {
  background: transparent;
}

.altitudeRangeInput::-moz-range-track {
  background: transparent;
  border: none;
}

/* The visible handle stays a 20x11 pill, but a thick transparent border
   (excluded from the paint via background-clip: padding-box) inflates the
   pointer target to 32x23 -- an 11px-tall handle on its own is a fiddly
   thing to hit. The old 1px solid #555 outline can't coexist with the
   transparent hit-area border, so an inset box-shadow (drawn at the padding
   edge, i.e. around the visible pill) redraws it. */
.altitudeRangeInput::-webkit-slider-thumb {
  -webkit-appearance: none;
  pointer-events: auto;
  box-sizing: border-box;
  width: 32px;
  height: 23px;
  border: 6px solid transparent;
  border-radius: 9px;
  background: #ddd;
  background-clip: padding-box;
  box-shadow: inset 0 0 0 1px #555;
  cursor: ns-resize;
}

.altitudeRangeInput::-moz-range-thumb {
  pointer-events: auto;
  box-sizing: border-box;
  width: 32px;
  height: 23px;
  border: 6px solid transparent;
  border-radius: 9px;
  background: #ddd;
  background-clip: padding-box;
  box-shadow: inset 0 0 0 1px #555;
  cursor: ns-resize;
}

/* Previously a bare, unstyled native <button> (rendered with the browser's
   default light-gray chrome, the one visibly "un-themed" control against
   this app's otherwise all-dark UI) squeezed into a 64px column as a text
   label. A compact icon button reads as a deliberate control at this width
   and matches the icon-button treatment used elsewhere (#depotIconButton). */
#altitudeResetButton {
  flex: none;
  width: 28px;
  height: 28px;
  margin-top: 8px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #999;
  background: #23262c;
  border: 1px solid #3a3a3a;
  border-radius: 7px;
  cursor: pointer;
}

#altitudeResetButton:hover {
  color: #fff;
  border-color: #666;
}

#altitudeResetButton svg {
  display: block;
}

.filterGroup {
  margin-bottom: 2px;
}

.filterGroup > .groupTitle {
  font-weight: bold;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 4px 3px;
  border-radius: 4px;
  user-select: none;
}

/* Animated on/off switch standing in for a native checkbox (see filters.js's
   makeToggle) -- the real <input type=checkbox> is visually hidden but still
   drives all the existing .checked/"change" logic, so this is pure styling. */
.toggleSwitch {
  position: relative;
  display: inline-block;
  flex: none;
  width: 26px;
  height: 15px;
  cursor: pointer;
}

.toggleSwitch input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}

.toggleSlider {
  position: absolute;
  inset: 0;
  background: #4a4d54;
  border-radius: 999px;
  transition: background-color 0.15s ease;
}

.toggleSlider::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #ddd;
  transition: transform 0.15s ease;
}

.toggleSwitch input:checked + .toggleSlider {
  background: var(--accent);
}

.toggleSwitch input:checked + .toggleSlider::before {
  transform: translateX(11px);
  background: #fff;
}

.toggleSwitch input:focus-visible + .toggleSlider {
  box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.6);
}

.expandToggle {
  display: inline-block;
  width: 10px;
  color: #888;
  cursor: pointer;
  flex: none;
}

.groupLabel {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.icon {
  display: inline-block;
  width: 10px;
  height: 10px;
  flex: none;
}

.icon-circle {
  border-radius: 50%;
}

.icon-rect {
  border-radius: 2px;
}

.icon-line {
  height: 0;
  width: 14px;
  margin-top: 5px;
}

/* Thin left guide line marks nesting depth at a glance (resource type ->
   mined/unmined -> purity, subcategory -> merged building rows, etc.)
   without costing extra horizontal indent on top of the existing margin. */
.filterChildren {
  margin-left: 14px;
  padding-left: 7px;
  border-left: 1px solid #35383e;
}

.filterRow {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 3px;
  border-radius: 4px;
}

.filterRow:hover, .filterGroup > .groupTitle:hover {
  background: rgba(255, 255, 255, 0.05);
}

.filterRow label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: default;
}

.filterRow .count {
  color: #888;
  margin-left: auto;
  padding-left: 4px;
  flex: none;
}

/* ---- Detail-pane rows: same clean feel as the nav column's category
   cards, at a smaller scale -- roomier rows and the visibility switch
   pushed to the far-right edge (instead of crammed on the left next to
   the label), so the pane reads as a proper list that actually uses its
   width. Scoped to #categoryDetailPane so the nav column's own
   .categoryNavRow overrides and the modal/tooltip toggles are untouched. */
#categoryDetailPane .filterGroup > .groupTitle {
  padding: 7px 9px;
  border-radius: 6px;
  font-size: 13.5px;
  gap: 9px;
}

#categoryDetailPane .filterRow {
  padding: 6px 9px;
  border-radius: 6px;
  gap: 9px;
  font-size: 13.5px;
}

/* The label grows to fill so whatever comes after it (the switch, and for
   leaf rows the count) is pushed to the right edge; order:5 on the switch
   moves it past the icon/label/count despite being first in DOM order (see
   renderGroup's append order). NB: a leaf row's text label is a bare
   <label>, but the toggle switch is ALSO a <label> (class toggleSwitch, see
   makeToggle) -- so :not(.toggleSwitch) is essential here, otherwise the
   flex-grow would land on the switch and stretch it into a wide pill. */
#categoryDetailPane .groupTitle .groupLabel,
#categoryDetailPane .filterRow > label:not(.toggleSwitch) {
  flex: 1 1 auto;
  min-width: 0;
}

#categoryDetailPane .toggleSwitch {
  order: 5;
  flex: none;
  width: 34px;
  height: 19px;
}

#categoryDetailPane .toggleSlider::before {
  width: 13px;
  height: 13px;
  top: 3px;
  left: 3px;
}

#categoryDetailPane .toggleSwitch input:checked + .toggleSlider::before {
  transform: translateX(15px);
}

/* Count sits just before the switch at the right edge (order:4, one less
   than the switch) rather than tight against the label -- with the label
   flex-growing above, both end up right-aligned as a "1069  [switch]" pair. */
#categoryDetailPane .filterRow .count {
  order: 4;
  margin-left: 0;
}

/* :not(.icon-line) -- line icons (belts/power lines) are a zero-height
   element with just a top border, so forcing a height would break them. */
#categoryDetailPane .icon:not(.icon-line) {
  width: 13px;
  height: 13px;
}

/* The detail pane's top-level content IS a .filterChildren (the childrenDiv
   renderGroup built, relocated here by renderTopLevelCategory) -- but at the
   top level its nesting-guide border-left/indent is just a stray vertical
   line down the left edge of the panel. Strip it here; genuinely nested
   .filterChildren (subcategories, resource mined/unmined, etc.) keep theirs. */
#categoryDetailPane > .categoryDetailGroup > .filterChildren {
  margin-left: 0;
  padding-left: 0;
  border-left: none;
}

/* Hover tooltip -- a plain fixed-position div that follows the cursor (see
   tooltip.js); not a Leaflet popup, since popups are anchored/click-oriented. */
#tt-tooltip {
  display: none;
  position: fixed;
  z-index: 1000;
  background: #20232a;
  color: #ddd;
  border: 1px solid #3a3f4a;
  border-radius: 8px;
  padding: 12px 14px;
  pointer-events: none;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.6);
  min-width: 260px;
  max-width: 380px;
  font-family: sans-serif;
}

.tt-popup {
  font-size: 13px;
  line-height: 1.5;
  max-height: 70vh;
  overflow-y: auto;
}

.tt-title {
  font-weight: bold;
  font-size: 15px;
  color: #fff;
  margin-bottom: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid #3a3f4a;
}

/* Lifts the tooltip over the modal overlay (z 1500) when shown from a
   modal's location list (see tooltip.js's Tooltip.showFloating) -- the
   default 1000 only ever had to beat the map. Still under #contextMenu
   (1700). */
#tt-tooltip.tt-above-modals {
  z-index: 1600;
}

/* The find-item "quantity in here" callout (see tooltip.js's
   highlightSection) -- the one number the search was about, so it's louder
   than every other row: the highlight bucket's pink, tinted background,
   larger text. */
.tt-highlight {
  margin-top: 8px;
}

.tt-highlight-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  background: rgba(255, 59, 129, 0.13);
  border: 1px solid rgba(255, 59, 129, 0.4);
  border-radius: 6px;
  padding: 5px 9px;
  margin-top: 4px;
  font-size: 13.5px;
}

.tt-highlight-row .tt-row-label {
  color: #ff9ec2;
  font-weight: 600;
}

.tt-highlight-row .tt-row-value {
  color: #fff;
  font-weight: 700;
  font-size: 14.5px;
}

.tt-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 2px 0;
}

.tt-row-label {
  color: #9aa0ab;
}

.tt-row-value {
  text-align: right;
  overflow-wrap: anywhere;
  color: #eee;
  font-weight: 500;
}

/* Wraps a row's label alongside a copy icon (see tooltip.js's
   coordinatesRow) -- only the Coordinates row uses this instead of plain
   .tt-row-label. Keeping the icon on the label side (not the value side)
   is what keeps the value column aligned with the Altitude row above it. */
.tt-row-label-with-icon {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: #9aa0ab;
}

.tt-copy-icon-btn {
  display: inline-flex;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  color: #8ab4f8;
  cursor: pointer;
  line-height: 0;
  /* Tooltips are pointer-events:none until pinned (see #tt-tooltip) -- this
     button is only ever actually clickable once that's true, same as
     "All properties" below, so it doesn't need its own pointer-events rule. */
}

.tt-copy-icon-btn:hover {
  color: #aecbfa;
}

.tt-copy-icon-btn-done {
  color: #6fcf97;
}

.tt-section {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid #2c2f37;
}

.tt-section-title {
  color: #8ab4f8;
  font-weight: bold;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 4px;
}

/* Mixed-mark conveyor line callout (see tooltip.js's bottleneckSection) --
   a genuine "your factory is silently underperforming" warning, so amber
   and tinted louder than a regular section, matching bottleneck.js's
   marker color. The .tt-section border-top is overridden by the box's own
   full border. */
.tt-warning {
  background: rgba(255, 176, 32, 0.10);
  border: 1px solid rgba(255, 176, 32, 0.45);
  border-radius: 5px;
  padding: 7px 8px 8px;
}

.tt-warning .tt-section-title {
  color: #ffb020;
}

.tt-warning-text {
  color: #ffd48a;
  font-size: 12.5px;
  line-height: 1.4;
}

.tt-warning-btn {
  display: block;
  margin-top: 7px;
  padding: 4px 9px;
  background: rgba(255, 176, 32, 0.16);
  border: 1px solid rgba(255, 176, 32, 0.55);
  border-radius: 4px;
  color: #ffcf7d;
  font-size: 12px;
  cursor: pointer;
  /* Inherits the tooltip's own pointer-events (none until pinned) -- the
     button is only ever actually clickable once pinned, same as the copy
     icon and "All properties". */
}

.tt-warning-btn:hover {
  background: rgba(255, 176, 32, 0.28);
  color: #ffe0a3;
}

.tt-loading, .tt-error {
  color: #999;
  font-style: italic;
}

.tt-error {
  color: #e07070;
}

.tt-raw {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid #2c2f37;
}

.tt-raw summary {
  cursor: pointer;
  color: #8ab4f8;
  font-size: 12px;
  font-weight: 500;
}

.tt-raw summary:hover {
  color: #aecbfa;
}

.tt-raw-list {
  margin-top: 6px;
  max-height: 280px;
  overflow-y: auto;
  padding-right: 2px;
}

.tt-raw-row {
  padding: 5px 0;
  border-bottom: 1px solid #2c2f37;
}

.tt-raw-row:last-child {
  border-bottom: none;
}

.tt-raw-row-label {
  color: #8ab4f8;
  font-size: 11px;
  font-weight: 600;
  margin-bottom: 2px;
}

.tt-raw-row-value {
  color: #ccc;
  font-family: "Consolas", "SF Mono", monospace;
  font-size: 11px;
  line-height: 1.4;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

/* ---- Progression modal (see progression.js) -- the shared dialog behind
   the top bar's MAM / alternate recipes / AWESOME Shop / HUB milestones /
   Space Elevator buttons. Same overlay as #itemModal (selector above);
   the dialog itself is a bit wider and its body is one scroll area of
   sections instead of a single flat list. */
#progressionModal {
  width: 540px;
  max-width: 92vw;
  max-height: 84vh;
  background: var(--surface-2);
  border: 1px solid var(--border-popup);
  border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
  padding: 16px 18px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  color: var(--text);
  transform: scale(0.96);
  opacity: 0;
  animation: buildingModalIn 0.16s ease-out forwards;
}

@media (prefers-reduced-motion: reduce) {
  #progressionModal { animation: none; transform: none; opacity: 1; }
}

#progressionModalHeader {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-bottom: 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border-popup);
  flex: none;
}

#progressionModalIcon {
  width: 36px;
  height: 36px;
  flex: none;
  object-fit: contain;
  background: var(--inset);
  border: 1px solid #2c2f37;
  border-radius: 8px;
  padding: 4px;
  box-sizing: border-box;
}

#progressionModalTitle {
  font-weight: bold;
  font-size: 17px;
  color: #fff;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#progressionModalClose {
  background: none;
  border: none;
  color: #999;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 0 2px;
}

#progressionModalClose:hover {
  color: #fff;
}

#progressionModalSummary {
  color: #bbb;
  margin-bottom: 8px;
  flex: none;
}

#progressionModalBody {
  overflow-y: auto;
  min-height: 0;
}

.progressionSection {
  margin-bottom: 12px;
  /* Some views hold 500+ rows across their sections; skip layout/paint for
     the offscreen ones or scrolling the modal visibly stutters. The
     intrinsic-size fallback only matters before a section's real size has
     been measured once. */
  content-visibility: auto;
  contain-intrinsic-size: auto 320px;
}

.progressionSectionHeader {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 5px;
}

.progressionSectionTitle {
  font-size: 13px;
  font-weight: 600;
  color: #eee;
}

/* Small "n / total" tally on a section header; turns green when complete. */
.progressionSectionCount {
  font-size: 12px;
  color: #8a919e;
  flex: none;
  margin-left: auto;
}

.progressionSectionCount.complete {
  color: #7fd18b;
}

/* e.g. the MAM's "not discovered yet" tag on a research tree the save has
   never unlocked (tree header still listed so the gap is visible). */
.progressionSectionTag {
  font-size: 11px;
  color: #b48a5a;
  border: 1px solid #4a3b28;
  background: rgba(180, 138, 90, 0.12);
  border-radius: 4px;
  padding: 1px 6px;
}

/* Thin per-section completion bar under the header. */
.progressionBarTrack {
  height: 3px;
  border-radius: 2px;
  background: var(--inset);
  overflow: hidden;
  margin-bottom: 6px;
}

.progressionBarFill {
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
}

.progressionBarFill.complete {
  background: #58a565;
}

.progressionList {
  border: 1px solid #3a3a3a;
  border-radius: 4px;
}

/* A row stacks its always-visible main line over an optional expandable
   cost list (see .progressionCostList), so the row itself is a column and
   .progressionRowMain is the flex line the icon/label/status sit on. */
.progressionRow {
  display: flex;
  flex-direction: column;
  padding: 5px 8px;
  border-bottom: 1px solid #2c2f37;
}

.progressionRowMain {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.progressionRow:hover {
  background: rgba(255, 255, 255, 0.04);
}

.progressionRow:last-child {
  border-bottom: none;
}

/* Entries the save hasn't unlocked/purchased yet -- kept in the list (seeing
   what's missing is the point of a progression view) but visibly dimmed.
   Only the icon + name dim: the right-hand cost/status text and the cost
   expander are exactly what a locked row exists to communicate, so they
   stay at full opacity and readable. */
.progressionRow.locked .progressionRowIcon,
.progressionRow.locked .progressionRowLabel {
  opacity: 0.55;
}

.progressionRowIcon {
  width: 20px;
  height: 20px;
  flex: none;
  object-fit: contain;
}

.progressionRowLabel {
  flex: 1 1 auto;
  min-width: 0;
  color: #ddd;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Right-hand side of a row: a green check for done entries, or a short
   dimmed note (research/coupon cost, "imported x / y") for pending ones.
   Multi-item costs never render here (they get the expander below); the
   ellipsis is just a guard so one long single-item label can't overflow. */
.progressionRowStatus {
  flex: none;
  max-width: 45%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  color: #c3c9d4;
}

/* "▸ cost" expander on rows whose requirement list is too long to show
   inline -- unfolds .progressionCostList below the row's main line. */
.progressionCostToggle {
  flex: none;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid #4a505c;
  border-radius: 5px;
  color: #c3c9d4;
  font-size: 12px;
  padding: 3px 9px;
  cursor: pointer;
}

.progressionCostToggle:hover {
  color: #fff;
  border-color: #6a7180;
  background: rgba(255, 255, 255, 0.08);
}

.progressionCostToggle .chev {
  display: inline-block;
  transition: transform 0.12s ease;
}

.progressionCostToggle.open .chev {
  transform: rotate(90deg);
}

.progressionCostList {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 6px 0 2px 30px; /* left-aligns with the row label (icon width + gap) */
}

.progressionCostLine {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: #d3d8e0;
}

.progressionCostIcon {
  width: 18px;
  height: 18px;
  flex: none;
  object-fit: contain;
}

.progressionCostAmount {
  flex: none;
  color: #9ec4fa;
  font-weight: 600;
}

.progressionRowStatus.done {
  color: #7fd18b;
  font-size: 13px;
  font-weight: 600;
}

.progressionRowStatus.countValue {
  color: #8ab4f8;
  font-weight: 500;
  font-size: 13px;
}

/* Space Elevator view: headline phase status + per-part delivery rows with
   their own mini progress bars. */
.progressionPhaseBanner {
  border: 1px solid #3a3f4a;
  background: var(--inset);
  border-radius: 8px;
  padding: 9px 12px;
  margin-bottom: 10px;
  color: #ddd;
}

.progressionPhaseBanner .phaseTitle {
  font-weight: 600;
  color: #fff;
  font-size: 14px;
}

.progressionPhaseBanner .phaseNote {
  font-size: 12px;
  color: #8a919e;
  margin-top: 2px;
}

.progressionPhaseBanner.complete {
  border-color: #3b5a41;
  background: rgba(88, 165, 101, 0.10);
}

/* The Space Elevator part rows stack a delivery bar under the label
   (their main line is a regular .progressionRowMain), so they're taller
   than plain progression rows. */
.sePartRow {
  padding: 7px 8px;
}

.sePartBarTrack {
  height: 4px;
  border-radius: 2px;
  background: var(--inset);
  overflow: hidden;
  margin: 5px 0 1px 30px;
}

.sePartBarFill {
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
}

.sePartBarFill.complete {
  background: #58a565;
}
