/* ─────────────────────────────────────────────────────────────
   NETMAP — physical network operations
   Aesthetic: refined NOC console, phosphor accents, technical
   ───────────────────────────────────────────────────────────── */

:root {
  --bg-0: #0a0d10;
  --bg-1: #0f1418;
  --bg-2: #141a1f;
  --bg-3: #1a2128;
  --bg-4: #232b33;
  --line: #2a3440;
  --line-strong: #3a4654;
  --text: #d8e2ec;
  --text-dim: #8fa0b0;
  --text-faint: #5e6e7e;

  --phosphor: #6ee7b7;          /* primary accent */
  --phosphor-dim: #34d399;
  --amber: #fbbf24;
  --red: #f87171;
  --blue: #60a5fa;
  --magenta: #f0abfc;
  --cyan: #67e8f9;

  --status-up: #6ee7b7;
  --status-down: #f87171;
  --status-unknown: #fbbf24;
  --status-new: #f0abfc;

  --shadow: 0 1px 0 rgba(255,255,255,0.04) inset, 0 8px 24px rgba(0,0,0,0.45);

  --mono: 'JetBrains Mono', ui-monospace, monospace;
  --sans: 'Space Grotesk', system-ui, sans-serif;

  /* Adjustable typography (settings → ui_device_font_size / ui_room_font_size).
     The defaults here are also the seed values in the database. JS re-writes
     them at runtime when settings change, so this just establishes a sensible
     fallback for the moment between page load and the first /api/state. */
  --device-font-size: 11px;
  --room-font-size: 13px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg-0);
  color: var(--text);
  font-family: var(--sans);
  font-size: 14px;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

/* Subtle background grid + scanline grain */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(110,231,183,0.03) 1px, transparent 1px),
    linear-gradient(90deg, rgba(110,231,183,0.03) 1px, transparent 1px);
  background-size: 48px 48px;
  z-index: 0;
}

button { font-family: inherit; }

/* ───────────── Topbar ───────────── */
.topbar {
  position: relative;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 18px;
  background: linear-gradient(180deg, var(--bg-2), var(--bg-1));
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-right: 8px;
}
.brand-mark {
  width: 28px; height: 28px;
  border-radius: 6px;
  background:
    radial-gradient(circle at 30% 30%, var(--phosphor) 0%, transparent 60%),
    linear-gradient(135deg, #0c2a22 0%, #0a0d10 100%);
  border: 1px solid var(--phosphor-dim);
  box-shadow:
    0 0 18px rgba(110,231,183,0.35),
    inset 0 0 8px rgba(110,231,183,0.25);
  position: relative;
}
.brand-mark::after {
  content: "";
  position: absolute;
  inset: 5px;
  border: 1px solid rgba(110,231,183,0.55);
  border-radius: 3px;
}
.brand-title {
  font-family: var(--mono);
  font-weight: 700;
  letter-spacing: 0.18em;
  font-size: 16px;
}
.brand-title .dot { color: var(--phosphor); }
.brand-sub {
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.topbar-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  flex: 1;
  justify-content: flex-end;
}

/* ───────────── Floor tabs ───────────── */
.floor-tabs {
  display: flex;
  gap: 2px;
  background: var(--bg-3);
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 6px;
}
.floor-tab {
  padding: 5px 12px;
  font-size: 12px;
  font-family: var(--mono);
  letter-spacing: 0.04em;
  background: transparent;
  border: 0;
  color: var(--text-dim);
  cursor: pointer;
  border-radius: 4px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.floor-tab:hover { color: var(--text); }
.floor-tab.active {
  background: var(--bg-1);
  color: var(--phosphor);
  box-shadow: inset 0 0 0 1px var(--phosphor-dim), 0 0 12px rgba(110,231,183,0.15);
}
.floor-tab .x {
  opacity: 0.4;
  font-size: 10px;
  padding: 0 2px;
}
.floor-tab .x:hover { opacity: 1; color: var(--red); }

/* ───────────── Filter pills ───────────── */
.filter-group {
  display: flex;
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 3px;
}
.filter-btn {
  padding: 5px 12px;
  background: transparent;
  border: 0;
  color: var(--text-dim);
  font-size: 12px;
  font-family: var(--mono);
  letter-spacing: 0.04em;
  cursor: pointer;
  border-radius: 4px;
}
.filter-btn:hover { color: var(--text); }
.filter-btn.active {
  background: var(--bg-1);
  color: var(--phosphor);
  box-shadow: inset 0 0 0 1px var(--phosphor-dim);
}

#search {
  background: var(--bg-3);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 6px 10px;
  width: 240px;
  font-family: var(--mono);
  font-size: 12px;
}
#search:focus {
  outline: none;
  border-color: var(--phosphor-dim);
  box-shadow: 0 0 0 3px rgba(110,231,183,0.1);
}

/* ───────────── Buttons ───────────── */
.btn {
  background: var(--bg-3);
  color: var(--text);
  border: 1px solid var(--line);
  padding: 6px 12px;
  border-radius: 6px;
  font-size: 12px;
  font-family: var(--mono);
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: all 120ms ease;
}
.btn:hover {
  border-color: var(--phosphor-dim);
  color: var(--phosphor);
  box-shadow: 0 0 12px rgba(110,231,183,0.15);
}
.btn:active { transform: translateY(1px); }
.btn-ghost {
  background: transparent;
  border-color: transparent;
}
.btn-ghost:hover {
  background: var(--bg-3);
  border-color: var(--line);
}

/* ───────────── Layout ───────────── */
.layout {
  display: grid;
  /* The sidebar's width is configurable at runtime via the
     --sidebar-width custom property on this element. The resize handle
     updates that variable; the grid track follows. We can't use a hard
     "280px" here because the grid wouldn't recompute when the sidebar
     element itself changes size — we have to drive the column track.

     Default 280px when the variable isn't set (first load, before
     restoring from localStorage). */
  grid-template-columns: var(--sidebar-width, 280px) 1fr;
  height: calc(100vh - 53px);
  position: relative;
  z-index: 1;
}

/* ───────────── Sidebar ───────────── */
.sidebar {
  background: var(--bg-1);
  border-right: 1px solid var(--line);
  /* The sidebar itself fills the viewport height and lets its inner sections
     compete for vertical space. The "All devices" section grows to fill the
     leftover space while shorter sections (legend, counters, disabled) keep
     their natural size. */
  overflow: hidden;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 0;
}

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  font-size: 11px;
  font-family: var(--mono);
  color: var(--text-dim);
}
.legend-row { display: inline-flex; align-items: center; gap: 6px; }
.legend-row .dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  display: inline-block;
}
.legend-row .dot.up { background: var(--status-up); box-shadow: 0 0 6px var(--status-up); }
.legend-row .dot.down { background: var(--status-down); }
.legend-row .dot.unknown { background: var(--status-unknown); }
.legend-row .dot.new {
  background: var(--status-new);
  box-shadow: 0 0 8px var(--status-new);
  animation: pulse 1.6s ease-in-out infinite;
}

.counters {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px;
}
.counters > div {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.counters .num {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 18px;
  color: var(--text);
}
.counters .num.up { color: var(--status-up); }
.counters .num.down { color: var(--status-down); }
.counters .label {
  font-size: 9px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.sidebar-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* Default: don't grow; sections size to content. The "all devices" section
     overrides this below to take the remaining height. */
  min-height: 0;
}
.sidebar-section.grow {
  flex: 1 1 auto;
  min-height: 0;
}
.sidebar-title {
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-faint);
  border-bottom: 1px solid var(--line);
  padding-bottom: 6px;
  flex-shrink: 0;
}
/* When the title sits in a flex row alongside a control (e.g. sort
   dropdown), the border-bottom needs to span the whole row, not just
   the title text. We move the underline onto the row container and
   strip it from the title to avoid double lines / short underlines.
   See `.sidebar-section-row` below. */

.device-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Constrained lists (the disabled-devices list) stay short. The growing
     section's list inherits flex sizing instead. */
  max-height: 280px;
  overflow-y: auto;
}
.sidebar-section.grow .device-list {
  /* Inside a flex-grow section, drop the cap and fill the remaining space.
     The combination of `min-height:0` on the parent + `flex: 1` here is the
     standard recipe for a scrollable area inside a flex container. */
  max-height: none;
  flex: 1 1 auto;
  min-height: 0;
}
.device-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 12px;
  font-family: var(--mono);
}
.device-list li:hover { background: var(--bg-3); }
.device-list li .ip { color: var(--text-faint); margin-left: auto; font-size: 11px; }
.device-list li .status-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
}
.device-list li .status-dot.up { background: var(--status-up); }
.device-list li .status-dot.down { background: var(--status-down); }
.device-list li .status-dot.unknown { background: var(--status-unknown); }
.device-list li.disabled { opacity: 0.55; }

/* ───────────── Canvas ───────────── */
.canvas-wrap {
  position: relative;
  overflow: hidden;
  background: var(--bg-0);
}
.canvas-toolbar {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 4;
  display: flex;
  gap: 6px;
  align-items: center;
  background: rgba(15, 20, 24, 0.85);
  backdrop-filter: blur(8px);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 4px 8px;
}
.canvas-toolbar #zoom-readout {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-dim);
  min-width: 38px;
  text-align: center;
}
.canvas-help {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-faint);
  margin-left: 8px;
  letter-spacing: 0.04em;
}

.canvas {
  width: 100%;
  height: 100%;
  position: relative;
  cursor: grab;
  /* Disable browser-default touch gestures (pinch-zoom, scroll) on the
     canvas — we handle those ourselves via Pointer Events. Without this
     a one-finger pan would scroll the page on Android Chrome and a
     two-finger pinch would zoom Safari's viewport instead of our SVG.
     `none` is the right choice: we want full control of every gesture
     that starts on the canvas. */
  touch-action: none;
  /* Prevent iOS callout (long-press to copy/save) on the canvas — we
     use long-press for our own context menu. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  background-image:
    radial-gradient(circle at center, rgba(110,231,183,0.04) 0%, transparent 60%),
    linear-gradient(rgba(255,255,255,0.025) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,0.025) 1px, transparent 1px);
  background-size: 100% 100%, 32px 32px, 32px 32px;
  overflow: hidden;
}
.canvas.panning { cursor: grabbing; }

#world {
  position: absolute;
  top: 0; left: 0;
  width: 0; height: 0;
  transform-origin: 0 0;
}

#links {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}
#links line {
  stroke: var(--line-strong);
  stroke-width: 1.5;
  opacity: 0.65;
}
#links line.up { stroke: rgba(110,231,183,0.5); }
#links line.down { stroke: rgba(248,113,113,0.4); stroke-dasharray: 4 4; }
/* Cross-floor links: drawn from a device to the floor-connector icon
   on the same floor (the other end of the cable is "off the page" on
   another floor). Dashed pattern signals "this leaves the floor" so
   the user doesn't read it as a normal in-floor connection. */
#links line.cross-floor {
  stroke-dasharray: 6 4;
  opacity: 0.55;
}
#links line.cross-floor.up { stroke: rgba(110,231,183,0.55); }
#links line.cross-floor.down { stroke: rgba(248,113,113,0.5); }

/* Links inferred from RSTP (BRIDGE-MIB spanning-tree) rather than the
   topology JSON. These show up when the JSON didn't declare a parent
   but the network's STP told us "your uplink is over there". A short
   dotted pattern + reduced opacity makes them read as "derived" rather
   than "authoritative". */
#links line.rstp {
  stroke-dasharray: 2 3;
  opacity: 0.45;
}
#links line.rstp.up { stroke: rgba(110,231,183,0.45); }
#links line.rstp.down { stroke: rgba(248,113,113,0.4); }
/* If a line is BOTH rstp AND cross-floor, the rstp dotted pattern
   wins (more specific intent: "derived" carries more semantic
   weight here than "leaves the floor"). */
#links line.rstp.cross-floor {
  stroke-dasharray: 2 3;
}

/* ───────────── Device nodes ───────────── */
.node {
  position: absolute;
  width: 84px;
  text-align: center;
  cursor: grab;
  user-select: none;
  z-index: 2;
  transition: filter 120ms ease;
}
.node.dragging { cursor: grabbing; z-index: 20; }
.node .icon {
  width: 56px;
  height: 56px;
  margin: 0 auto;
  background: var(--bg-2);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  transition: all 150ms ease;
}
.node .icon svg {
  width: 32px;
  height: 32px;
  color: var(--text-dim);
  transition: color 150ms ease;
}
.node.up .icon {
  border-color: var(--status-up);
  box-shadow:
    0 0 0 1px rgba(110,231,183,0.15),
    0 0 18px rgba(110,231,183,0.18);
}
.node.up .icon svg { color: var(--phosphor); }
.node.down .icon {
  border-color: var(--line);
  filter: grayscale(1) brightness(0.55);
}
.node.unknown .icon { border-color: rgba(251,191,36,0.4); }
.node.unknown .icon svg { color: var(--amber); }

.node.is-new .icon {
  border-color: var(--status-new);
  box-shadow:
    0 0 0 1px rgba(240,171,252,0.4),
    0 0 22px rgba(240,171,252,0.5);
  animation: pulse 1.6s ease-in-out infinite;
}
.node.is-new::before {
  content: "NEW";
  position: absolute;
  top: -8px;
  right: -2px;
  background: var(--status-new);
  color: #1a1024;
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
  padding: 1px 5px;
  border-radius: 3px;
  letter-spacing: 0.08em;
  z-index: 3;
}

@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 1px rgba(240,171,252,0.4), 0 0 22px rgba(240,171,252,0.5); }
  50%      { box-shadow: 0 0 0 2px rgba(240,171,252,0.65), 0 0 30px rgba(240,171,252,0.7); }
}

.node .label {
  margin-top: 4px;
  font-family: var(--mono);
  font-size: var(--device-font-size);
  line-height: 1.3;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.node .ip {
  font-family: var(--mono);
  /* Slightly smaller than the hostname so it stays subordinate even when the
     user bumps device-font-size up. The 0.85x ratio keeps it readable. */
  font-size: calc(var(--device-font-size) * 0.85);
  color: var(--text-faint);
}
.node.down .label, .node.down .ip { color: var(--text-faint); }

/* Highlight when matching search */
.node.dim { opacity: 0.18; }
.node.match .icon {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

/* ───────────── Tooltip ───────────── */
.tooltip {
  position: fixed;
  z-index: 100;
  pointer-events: none;
  background: rgba(15, 20, 24, 0.97);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  padding: 10px 12px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text);
  box-shadow: var(--shadow);
  max-width: 360px;
  backdrop-filter: blur(6px);
}
.tooltip.hidden { display: none; }
.tooltip h4 {
  margin: 0 0 6px 0;
  color: var(--phosphor);
  font-size: 12px;
  font-family: var(--sans);
  letter-spacing: 0.03em;
}
.tooltip .row {
  display: grid;
  grid-template-columns: 90px 1fr;
  gap: 8px;
  margin: 2px 0;
}
.tooltip .key { color: var(--text-faint); }
.tooltip .val { color: var(--text); word-break: break-all; }
.tooltip .badge {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 9px;
  letter-spacing: 0.08em;
  margin-right: 4px;
}
.tooltip .badge.up { background: rgba(110,231,183,0.15); color: var(--status-up); }
.tooltip .badge.down { background: rgba(248,113,113,0.15); color: var(--status-down); }
.tooltip .badge.unknown { background: rgba(251,191,36,0.15); color: var(--amber); }

/* ───────────── Context menu ───────────── */
.ctxmenu {
  position: fixed;
  z-index: 110;
  background: var(--bg-2);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  padding: 4px;
  min-width: 180px;
  box-shadow: var(--shadow);
}
.ctxmenu.hidden { display: none; }
.ctxmenu button {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  color: var(--text);
  padding: 7px 10px;
  font-size: 12px;
  font-family: var(--mono);
  border-radius: 4px;
  cursor: pointer;
}
.ctxmenu button:hover { background: var(--bg-3); color: var(--phosphor); }
.ctxmenu button.danger:hover { color: var(--red); }

/* ───────────── Minimap ───────────── */
.minimap {
  position: absolute;
  bottom: 14px;
  right: 14px;
  width: 180px;
  height: 130px;
  background: rgba(15, 20, 24, 0.85);
  backdrop-filter: blur(6px);
  border: 1px solid var(--line);
  border-radius: 6px;
  z-index: 4;
  overflow: hidden;
}
.minimap canvas { display: block; width: 100%; height: 100%; }

/* ───────────── Modal ───────────── */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  backdrop-filter: blur(4px);
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal-backdrop.hidden { display: none; }
.modal {
  width: min(640px, 92vw);
  max-height: 86vh;
  overflow-y: auto;
  background: var(--bg-1);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  box-shadow: var(--shadow);
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
}
.modal-header h2 {
  margin: 0;
  font-size: 14px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--phosphor);
}
.modal-body { padding: 18px; }
.modal-body h3 {
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 8px 0;
}
.modal-body hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 18px 0;
}
.muted { color: var(--text-faint); font-size: 12px; margin: 4px 0 12px 0; }

.form-row {
  display: grid;
  grid-template-columns: 1fr 200px;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}
.form-row.right { grid-template-columns: 1fr; justify-items: end; }
.form-row label { font-size: 12px; color: var(--text-dim); }
.form-row input[type="number"],
.form-row input[type="text"],
.form-row select {
  background: var(--bg-3);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 6px 8px;
  font-family: var(--mono);
  font-size: 12px;
}
.form-row input[type="text"]:focus,
.form-row input[type="number"]:focus,
.form-row select:focus {
  outline: none;
  border-color: var(--phosphor-dim);
  box-shadow: 0 0 0 3px rgba(110,231,183,0.1);
}
.modal textarea {
  width: 100%;
  height: 160px;
  background: var(--bg-3);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 8px;
  font-family: var(--mono);
  font-size: 11px;
  resize: vertical;
}
.api-help {
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 10px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-dim);
  white-space: pre-wrap;
  margin: 0;
}

/* iOS-style switch */
.switch {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 22px;
}
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
  position: absolute;
  cursor: pointer;
  inset: 0;
  background: var(--bg-3);
  border: 1px solid var(--line);
  transition: 0.2s;
  border-radius: 22px;
}
.slider::before {
  content: "";
  position: absolute;
  height: 14px; width: 14px;
  left: 3px;
  bottom: 2px;
  background: var(--text-dim);
  transition: 0.2s;
  border-radius: 50%;
}
.switch input:checked + .slider {
  background: rgba(110,231,183,0.2);
  border-color: var(--phosphor-dim);
}
.switch input:checked + .slider::before {
  transform: translateX(17px);
  background: var(--phosphor);
}

.hidden { display: none !important; }

/* ───────────── Rooms (floor-plan rectangles) ───────────── */
#rooms-layer {
  position: absolute;
  top: 0; left: 0;
  width: 0; height: 0;
  pointer-events: none;
}
.room {
  position: absolute;
  border: 1.5px solid var(--room-border, #6ee7b7);
  background: var(--room-fill, rgba(110,231,183,0.15));
  border-radius: 4px;
  pointer-events: auto;
  cursor: pointer;
  transition: filter 120ms ease;
  z-index: 0;
}
.room:hover { filter: brightness(1.15); }
.room.selected {
  outline: 2px dashed var(--cyan);
  outline-offset: 2px;
  z-index: 1;
}
.room .room-label {
  position: absolute;
  top: 6px;
  left: 8px;
  font-family: var(--mono);
  font-size: var(--room-font-size);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text);
  text-shadow: 0 1px 2px rgba(0,0,0,0.6);
  pointer-events: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: calc(100% - 16px);
}

/* Live preview rectangle while drawing */
#draw-preview {
  position: absolute;
  border: 1.5px dashed var(--phosphor);
  background: rgba(110,231,183,0.08);
  pointer-events: none;
  z-index: 5;
  border-radius: 4px;
}

/* Draw mode cursor */
.canvas.draw-mode { cursor: crosshair; }
.canvas.draw-mode .node { pointer-events: none; }
.canvas.draw-mode .room { pointer-events: none; }

#btn-draw-room.active {
  background: var(--bg-1);
  color: var(--phosphor);
  box-shadow: inset 0 0 0 1px var(--phosphor-dim), 0 0 12px rgba(110,231,183,0.15);
  border-color: var(--phosphor-dim);
}

/* ───────────── Room edit popover ───────────── */
.popover {
  position: fixed;
  z-index: 150;
  background: var(--bg-1);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  width: 300px;
  box-shadow: var(--shadow);
  backdrop-filter: blur(6px);
}
.popover.hidden { display: none; }
.popover-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
}
.popover-title {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--phosphor);
}
.popover-body { padding: 10px; }
.popover-body .form-row {
  grid-template-columns: 70px 1fr;
  gap: 8px;
  margin-bottom: 8px;
  align-items: center;
}
.popover-body input[type="text"] {
  background: var(--bg-3);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 5px 8px;
  font-family: var(--mono);
  font-size: 12px;
  width: 100%;
}
.popover-body input[type="range"] { width: 100%; }
.color-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.color-swatch {
  width: 22px;
  height: 22px;
  border-radius: 4px;
  cursor: pointer;
  border: 2px solid transparent;
  transition: transform 100ms ease;
}
.color-swatch:hover { transform: scale(1.1); }
.color-swatch.selected {
  border-color: var(--text);
  box-shadow: 0 0 0 2px var(--bg-1), 0 0 0 3px var(--text);
}
.muted-mono {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-faint);
}
.btn.danger {
  border-color: rgba(248,113,113,0.3);
  color: var(--red);
}
.btn.danger:hover {
  background: rgba(248,113,113,0.1);
  border-color: var(--red);
  box-shadow: 0 0 12px rgba(248,113,113,0.2);
  color: var(--red);
}

/* ───────────── Port modal ───────────── */
.modal-wide { width: min(960px, 95vw) !important; }

.port-device-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-bottom: 16px;
  padding: 10px 12px;
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: 6px;
  font-family: var(--mono);
  font-size: 11px;
}
.port-device-meta .key { color: var(--text-faint); margin-right: 4px; }
.port-device-meta .val { color: var(--text); }

/* Port grid */
.port-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 8px;
  margin-bottom: 16px;
}
.port-grid.compact {
  grid-template-columns: repeat(auto-fill, minmax(85px, 1fr));
}
.port-card {
  position: relative;
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-left: 4px solid var(--port-color, #4a5568);
  border-radius: 4px;
  padding: 8px 10px;
  cursor: default;
  transition: transform 100ms ease, box-shadow 120ms ease;
}
.port-card:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.4), 0 0 0 1px var(--port-color, var(--line));
  border-color: var(--port-color, var(--line-strong));
}
.port-card.inactive { opacity: 0.55; }
.port-card.uplink {
  background: linear-gradient(135deg, var(--bg-3), rgba(110,231,183,0.06));
}
.port-card.uplink::before {
  content: "↑";
  position: absolute;
  top: 4px;
  right: 6px;
  color: var(--phosphor);
  font-size: 12px;
  font-weight: 700;
}
.port-card.sfp::after {
  content: "SFP";
  position: absolute;
  top: 4px;
  right: 6px;
  background: var(--bg-2);
  color: var(--text-dim);
  font-family: var(--mono);
  font-size: 8px;
  font-weight: 700;
  padding: 1px 4px;
  border-radius: 2px;
  letter-spacing: 0.08em;
}
.port-card .port-name {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 13px;
  color: var(--text);
  margin-bottom: 4px;
}
.port-card .port-speed {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--port-color, var(--text-dim));
}
.port-card .port-conn {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-dim);
  margin-top: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.port-card .port-throughput {
  display: flex;
  gap: 6px;
  margin-top: 6px;
  font-family: var(--mono);
  font-size: 10px;
}
.port-card .port-throughput .rx { color: var(--blue); }
.port-card .port-throughput .tx { color: var(--amber); }

/* SNMP-only ports get a subtle "SNMP" tag */
.port-card.snmp-only::before {
  content: "SNMP";
  position: absolute;
  top: 4px;
  right: 6px;
  background: rgba(96,165,250,0.15);
  color: var(--blue);
  font-family: var(--mono);
  font-size: 8px;
  font-weight: 700;
  padding: 1px 4px;
  border-radius: 2px;
  letter-spacing: 0.08em;
}
/* Errors / discards counter */
.port-card .port-errors {
  margin-top: 4px;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--red);
}
.port-card .port-errors:empty { display: none; }
.port-card .port-desc {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-faint);
  font-style: italic;
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* SNMP system info bar inside the port modal */
.snmp-info {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
  background: linear-gradient(135deg, rgba(110,231,183,0.05), rgba(96,165,250,0.05));
  border: 1px solid var(--line);
  border-left: 3px solid var(--phosphor);
  border-radius: 6px;
  padding: 10px 12px;
  margin-bottom: 16px;
  font-family: var(--mono);
  font-size: 11px;
}
.snmp-info .label {
  font-size: 9px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 2px;
}
.snmp-info .value { color: var(--text); }
.snmp-info .value.warn { color: var(--amber); }
.snmp-info .value.bad { color: var(--red); }

.snmp-error {
  background: rgba(248,113,113,0.08);
  border: 1px solid rgba(248,113,113,0.3);
  color: var(--red);
  border-radius: 4px;
  padding: 8px 10px;
  margin-bottom: 12px;
  font-family: var(--mono);
  font-size: 11px;
}

/* Per-device SNMP credentials block in the port modal */
.snmp-creds {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}
.snmp-creds h4 {
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0 0 8px 0;
}
.snmp-creds-row {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr auto;
  gap: 6px;
  align-items: center;
}
.snmp-creds-row input,
.snmp-creds-row select {
  background: var(--bg-3);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 5px 7px;
  font-family: var(--mono);
  font-size: 11px;
  width: 100%;
}
.snmp-creds-row input::placeholder { color: var(--text-faint); }

/* Speed legend */
.speed-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 10px 12px;
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: 6px;
  margin-bottom: 16px;
  font-family: var(--mono);
  font-size: 10px;
}
.speed-legend .item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--text-dim);
}
.speed-legend .swatch {
  width: 12px;
  height: 12px;
  border-radius: 2px;
  border: 1px solid rgba(255,255,255,0.1);
}

/* Port speed color tokens (reused for swatches and card borders) */
.spd-inactive { --port-color: #4a5568; }
.spd-10M  { --port-color: #fbbf24; }
.spd-100M { --port-color: #6ee7b7; }
.spd-1G   { --port-color: #60a5fa; }
.spd-10G  { --port-color: #c084fc; }
.spd-25G  { --port-color: #f472b6; }
.spd-100G { --port-color: #fb923c; }
.spd-200G { --port-color: #f87171; }

.swatch.spd-inactive { background: #4a5568; }
.swatch.spd-10M  { background: #fbbf24; }
.swatch.spd-100M { background: #6ee7b7; }
.swatch.spd-1G   { background: #60a5fa; }
.swatch.spd-10G  { background: #c084fc; }
.swatch.spd-25G  { background: #f472b6; }
.swatch.spd-100G { background: #fb923c; }
.swatch.spd-200G { background: #f87171; }

/* Inline tooltip on port card hover (uses native title for now;
   the rich data is shown in the card body itself) */

.port-section-title {
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0 0 8px 0;
}

/* Responsive */
@media (max-width: 900px) {
  .layout { grid-template-columns: 1fr; }
  .sidebar { display: none; }
  #search { width: 160px; }
  .canvas-help { display: none; }
}

/* ─────────────────────────────────────────────────────────────
   Login page
   ───────────────────────────────────────────────────────────── */
body.login-body {
  /* Center the login card on a clean dark background, no scrollbars. */
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
  background:
    radial-gradient(circle at 30% 20%, rgba(110,231,183,0.06) 0%, transparent 50%),
    radial-gradient(circle at 70% 80%, rgba(96,165,250,0.04) 0%, transparent 60%),
    var(--bg-0);
}
body.login-body::before {
  /* Inherit the global scanline grid */
}

.login-shell {
  width: min(420px, 92vw);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  padding: 24px 0;
}

.login-card {
  width: 100%;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 32px 28px;
  box-shadow: var(--shadow);
  z-index: 1;
}

.login-brand {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 24px;
}

.login-title {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.04em;
  margin: 0 0 18px 0;
  color: var(--text);
}

.login-card .form-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 14px;
}
.login-card .form-row label {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-family: var(--mono);
}
.login-card input[type="text"],
.login-card input[type="password"] {
  background: var(--bg-1);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  color: var(--text);
  padding: 10px 12px;
  font-family: var(--mono);
  font-size: 14px;
  outline: none;
  transition: border-color 80ms;
}
.login-card input[type="text"]:focus,
.login-card input[type="password"]:focus {
  border-color: var(--phosphor);
  box-shadow: 0 0 0 1px rgba(110,231,183,0.25);
}

.login-error {
  background: rgba(248,113,113,0.08);
  border: 1px solid rgba(248,113,113,0.35);
  color: var(--red);
  padding: 8px 12px;
  border-radius: 6px;
  font-size: 12px;
  margin-bottom: 14px;
}

.login-btn,
.btn-primary {
  width: 100%;
  background: var(--phosphor);
  color: #06281e;
  border: none;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 11px 14px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  transition: filter 80ms, transform 80ms;
}
.login-btn:hover,
.btn-primary:hover { filter: brightness(1.1); }
.login-btn:active,
.btn-primary:active { transform: translateY(1px); }
.login-btn:disabled,
.btn-primary:disabled { opacity: 0.6; cursor: default; }

.login-footer {
  font-size: 11px;
  color: var(--text-faint);
  font-family: var(--mono);
  letter-spacing: 0.08em;
  margin: 0;
}

/* ─────────────────────────────────────────────────────────────
   User chip in the topbar
   ───────────────────────────────────────────────────────────── */
.user-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  padding: 4px 8px 4px 4px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg-2);
}
.user-chip .user-role {
  display: inline-block;
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--bg-0);
  background: var(--text-faint);
  padding: 2px 8px;
  border-radius: 999px;
  font-family: var(--mono);
  font-weight: 700;
}
.user-chip .user-role[data-role="root"] {
  background: var(--magenta);
  color: #2a0c33;
}
.user-chip .user-role[data-role="admin"] {
  background: var(--phosphor);
  color: #06281e;
}
.user-chip .user-role[data-role="user"] {
  background: var(--text-faint);
  color: var(--bg-0);
}
.user-chip .user-name {
  color: var(--text);
  font-size: 12px;
  font-family: var(--mono);
}
.user-chip .btn { padding: 2px 6px; }

/* ─────────────────────────────────────────────────────────────
   Selection bar (for multi-select)

   Positioned as an overlay anchored to the top of the canvas region,
   not as a layout-shifting element. This was originally a banner that
   pushed the canvas down when it appeared — but the appearance/
   disappearance was triggered by clicks, which meant every click
   shifted the world half an inch. As an overlay it just slides in
   above the content; the canvas keeps its size.
   ───────────────────────────────────────────────────────────── */
.selection-bar {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px;
  background: rgba(20, 26, 31, 0.92);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45);
  flex-wrap: wrap;
  font-size: 12px;
  max-width: calc(100% - 80px);
}
.selection-bar.hidden { display: none; }
.selection-count {
  font-family: var(--mono);
  font-weight: 700;
  color: var(--phosphor);
  letter-spacing: 0.04em;
}
.selection-bar .muted { color: var(--text-faint); font-size: 11px; }
.selection-bar .grow { flex: 1; }
.selection-bar .btn { padding: 4px 10px; font-size: 12px; }
.sel-move-wrap { position: relative; }
.sel-floor-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  background: var(--bg-2);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  box-shadow: var(--shadow);
  min-width: 160px;
  display: flex;
  flex-direction: column;
  z-index: 50;
  padding: 4px;
}
.sel-floor-menu.hidden { display: none; }
.sel-floor-menu button {
  background: transparent;
  border: none;
  color: var(--text);
  padding: 6px 10px;
  text-align: left;
  cursor: pointer;
  font-size: 12px;
  border-radius: 4px;
}
.sel-floor-menu button:hover { background: var(--bg-3); }

/* The bar is an overlay (see above) — the layout no longer shrinks when
   it appears, so there's no `body.has-selection` rule. The class is still
   set by JS for any other consumer (e.g. CSS that wants to dim the
   topbar while a selection is active), but it's a no-op for layout. */

/* Selected-node outline on the canvas */
.node.selected .icon {
  box-shadow:
    0 0 0 2px var(--phosphor),
    0 0 14px rgba(110,231,183,0.55);
  border-color: var(--phosphor) !important;
}

/* Marquee preview (reuses #draw-preview with .marquee variant) */
#draw-preview.marquee {
  border: 1px dashed var(--cyan);
  background: rgba(103,232,249,0.08);
  pointer-events: none;
}

/* ─────────────────────────────────────────────────────────────
   User & token management (inside settings modal)
   ───────────────────────────────────────────────────────────── */
.admin-only.hidden,
.writer-only.hidden { display: none; }

.user-list,
.token-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 8px 0 12px 0;
  max-height: 220px;
  overflow-y: auto;
  padding: 4px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 6px;
}
.user-row,
.token-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border-radius: 4px;
  font-size: 12px;
  font-family: var(--mono);
}
.user-row:hover,
.token-row:hover { background: var(--bg-2); }
.u-name, .t-name { color: var(--text); font-weight: 600; min-width: 100px; }
.u-meta, .t-meta { color: var(--text-faint); font-size: 11px; }
.u-role {
  display: inline-block;
  font-size: 9px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 999px;
  font-weight: 700;
}
.u-role.role-root  { background: var(--magenta); color: #2a0c33; }
.u-role.role-admin { background: var(--phosphor); color: #06281e; }
.u-role.role-user  { background: var(--text-faint); color: var(--bg-0); }
.u-delete, .t-delete { margin-left: auto; }

.user-create,
.token-create-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
  margin: 6px 0;
}
.user-create h4 {
  width: 100%;
  margin: 8px 0 4px 0;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.user-create-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}
.user-create-row input,
.user-create-row select,
.token-create-row input {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--text);
  padding: 6px 8px;
  font-family: var(--mono);
  font-size: 12px;
}
.token-show {
  background: var(--bg-1);
  border: 1px solid var(--phosphor);
  padding: 8px 10px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 8px 0;
}
.token-show.hidden { display: none; }
.token-show code {
  flex: 1;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--phosphor);
  word-break: break-all;
}

/* Read-only visual hint */
.btn.readonly-disabled,
button.readonly-disabled { opacity: 0.45; cursor: not-allowed; }

/* Context menu header row (used for "N selected" labels) */
#ctxmenu button.header {
  background: transparent;
  color: var(--text-faint);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: default;
  pointer-events: none;
  border-bottom: 1px solid var(--line);
  margin-bottom: 4px;
  padding-bottom: 6px;
}

/* ─────────────────────────────────────────────────────────────
   Smaller layout adjustments needed for new structure
   ───────────────────────────────────────────────────────────── */
.topbar-controls {
  /* Allow the user-chip to push to the right edge */
  flex: 1;
}

/* ─────────────────────────────────────────────────────────────
   Debug overlay (visible only when NETMAP_DEBUG=1 on the server
   and the user opens it with Ctrl+Shift+D)
   ───────────────────────────────────────────────────────────── */
.debug-overlay {
  position: fixed;
  right: 16px;
  bottom: 16px;
  width: min(520px, 96vw);
  max-height: min(70vh, 720px);
  background: rgba(15, 20, 24, 0.96);
  border: 1px solid var(--phosphor);
  border-radius: 8px;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.6);
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text);
  z-index: 9999;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.debug-overlay.hidden { display: none; }

.debug-overlay .dbg-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: linear-gradient(180deg, rgba(110,231,183,0.10), rgba(110,231,183,0.03));
  border-bottom: 1px solid var(--line-strong);
}
.debug-overlay .dbg-title {
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--phosphor);
  font-size: 10px;
}
.debug-overlay .dbg-sub {
  flex: 1;
  color: var(--text-faint);
  font-size: 10px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.debug-overlay .dbg-fetch,
.debug-overlay .dbg-close {
  padding: 2px 8px;
  font-size: 11px;
}

.debug-overlay .dbg-body {
  padding: 8px 10px 10px 10px;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.debug-overlay details {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 4px 8px 6px 8px;
}
.debug-overlay summary {
  cursor: pointer;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 4px 0;
  user-select: none;
}
.debug-overlay summary:hover { color: var(--text); }
.debug-overlay .dbg-hint {
  font-style: italic;
  color: var(--text-faint);
  text-transform: none;
  letter-spacing: 0;
  margin-left: 4px;
}
.debug-overlay pre {
  margin: 4px 0 2px 0;
  padding: 6px 8px;
  background: var(--bg-0);
  border-radius: 3px;
  color: var(--text);
  font-size: 11px;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 240px;
  overflow: auto;
}

.debug-overlay .dbg-api {
  display: flex;
  flex-direction: column;
  gap: 1px;
  max-height: 200px;
  overflow: auto;
  background: var(--bg-0);
  padding: 4px;
  border-radius: 3px;
  margin-top: 4px;
}
.debug-overlay .dbg-row {
  display: grid;
  grid-template-columns: 50px 1fr 50px 60px;
  gap: 6px;
  align-items: baseline;
  padding: 2px 4px;
  font-size: 10.5px;
  border-radius: 2px;
}
.debug-overlay .dbg-row:hover { background: var(--bg-2); }
.debug-overlay .dbg-method {
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
}
.debug-overlay .dbg-path {
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.debug-overlay .dbg-status {
  text-align: right;
  font-weight: 600;
}
.debug-overlay .dbg-ms {
  text-align: right;
  color: var(--text-faint);
}
.debug-overlay .dbg-detail {
  grid-column: 1 / -1;
  color: var(--red);
  font-size: 10px;
  padding-left: 56px;
}
.debug-overlay .dbg-row.dbg-ok   .dbg-status { color: var(--phosphor); }
.debug-overlay .dbg-row.dbg-warn .dbg-status { color: var(--amber); }
.debug-overlay .dbg-row.dbg-err  .dbg-status { color: var(--red); }

/* ─────────────────────────────────────────────────────────────
   Device attributes modal — narrow variant + icon picker grid
   ───────────────────────────────────────────────────────────── */
.modal.narrow { max-width: 520px; }

.icon-grid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 6px;
  flex: 1;
  min-width: 0;
}
.icon-grid .icon-tile {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
  color: var(--text-dim);
  padding: 0;
  transition: background 0.1s, border-color 0.1s, color 0.1s, transform 0.05s;
}
.icon-grid .icon-tile svg {
  width: 60%;
  height: 60%;
}
.icon-grid .icon-tile:hover {
  background: var(--bg-2);
  border-color: var(--line-strong);
  color: var(--text);
}
.icon-grid .icon-tile.selected {
  background: rgba(110,231,183,0.10);
  border-color: var(--phosphor);
  color: var(--phosphor);
  box-shadow: 0 0 0 1px var(--phosphor) inset;
}
.icon-grid .icon-tile:active { transform: scale(0.94); }

/* Tooltip: host-metric bars (Linux/Windows) */
.tooltip .host-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 2px 0;
  font-family: var(--mono);
  font-size: 10px;
}
.tooltip .host-bar .label {
  width: 36px;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.tooltip .host-bar .track {
  flex: 1;
  height: 6px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 3px;
  overflow: hidden;
  min-width: 80px;
}
.tooltip .host-bar .fill {
  height: 100%;
  background: var(--phosphor-dim);
  transition: width 0.2s;
}
.tooltip .host-bar .fill.warn { background: var(--amber); }
.tooltip .host-bar .fill.bad  { background: var(--red); }
.tooltip .host-bar .pct {
  width: 44px;
  text-align: right;
  color: var(--text);
}
.tooltip .profile-badge {
  display: inline-block;
  padding: 1px 6px;
  margin-right: 4px;
  border-radius: 3px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  color: var(--text-dim);
  font-family: var(--mono);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* ─────────────────────────────────────────────────────────────
   Full-size icon picker (opens from Edit Device modal)
   ───────────────────────────────────────────────────────────── */
.icon-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 10px;
  max-height: 60vh;
  overflow-y: auto;
  padding: 6px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 6px;
}
.icon-picker-grid .icon-pick {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 12px 6px;
  background: var(--bg-0);
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
  color: var(--text-dim);
  transition: background 0.1s, border-color 0.1s, color 0.1s, transform 0.05s;
}
.icon-picker-grid .icon-pick svg {
  width: 56px;
  height: 56px;
}
.icon-picker-grid .icon-pick .icon-pick-name {
  font-family: var(--mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
}
.icon-picker-grid .icon-pick:hover {
  background: var(--bg-2);
  border-color: var(--line-strong);
  color: var(--text);
}
.icon-picker-grid .icon-pick:hover .icon-pick-name {
  color: var(--text-dim);
}
.icon-picker-grid .icon-pick.selected {
  background: rgba(110,231,183,0.10);
  border-color: var(--phosphor);
  color: var(--phosphor);
  box-shadow: 0 0 0 1px var(--phosphor) inset;
}
.icon-picker-grid .icon-pick.selected .icon-pick-name {
  color: var(--phosphor);
}
.icon-picker-grid .icon-pick:active { transform: scale(0.96); }

/* ─────────────────────────────────────────────────────────────
   Edit Device modal — icon preview + "Choose icon…" button row
   ───────────────────────────────────────────────────────────── */
.icon-pick-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
  min-width: 0;
}
.icon-pick-preview {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--phosphor);
}
.icon-pick-preview svg {
  width: 28px;
  height: 28px;
}
#dev-attrs-icon-label {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* ─────────────────────────────────────────────────────────────
   Sidebar resize handle (drag the right edge to resize)
   ───────────────────────────────────────────────────────────── */
.sidebar {
  position: relative;  /* anchor for the resize handle */
}
.sidebar-resize-handle {
  position: absolute;
  top: 0;
  right: -3px;          /* sits half-on, half-off the sidebar's edge */
  width: 6px;
  height: 100%;
  cursor: ew-resize;
  z-index: 10;
  /* invisible by default; subtle phosphor band on hover/drag */
  background: transparent;
  transition: background 0.1s;
}
.sidebar-resize-handle:hover,
.sidebar-resize-handle.dragging {
  background: var(--phosphor);
  opacity: 0.4;
}
/* While dragging we apply col-resize cursor to the whole document so the
   user doesn't lose the cursor when their mouse leaves the 6px strip. */
body.sidebar-resizing {
  cursor: ew-resize !important;
  user-select: none;
}
body.sidebar-resizing * {
  cursor: ew-resize !important;
  pointer-events: none;
}
/* Re-allow pointer events on the handle itself so the mouseup lands. */
body.sidebar-resizing .sidebar-resize-handle {
  pointer-events: auto;
}

/* ─────────────────────────────────────────────────────────────
   Touch / mobile support
   ───────────────────────────────────────────────────────────── */

/* Min font-size on inputs prevents iOS Safari's auto-zoom on focus.
   Anything below 16px triggers it. We override the small inputs that
   were styled smaller. */
input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="number"],
input[type="url"],
select, textarea {
  font-size: 16px;
}

/* "Mobile-only" / "desktop-only" toggles. The hamburger lives in
   markup at all times; we hide it on wide viewports. */
.mobile-only { display: none; }
@media (max-width: 900px) {
  .mobile-only { display: inline-flex; }
  .desktop-only { display: none; }
}

/* Square icon button — used for icon-only controls so they hit the
   44px touch target standard without growing inline buttons. */
.btn.btn-icon {
  min-width: 40px;
  min-height: 40px;
  padding: 6px 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Touch-target sizing. On coarse pointer (touch primary), grow the
   common interactive elements so they're tappable without precision.
   Apple HIG says 44pt; Android Material says 48dp. We use 40px for the
   subtle controls and 44px for primary buttons.
   The `pointer: coarse` query catches phones, tablets, and 2-in-1
   devices in tablet mode. We don't blanket-grow everything because
   on a desktop with a touchscreen the user expects compact UI. */
@media (pointer: coarse) {
  .btn {
    min-height: 40px;
    padding: 8px 12px;
  }
  .filter-btn {
    min-height: 36px;
    padding: 6px 12px;
  }
  /* Sidebar device list rows — easier to tap. */
  .sidebar-list .device-row {
    padding: 10px 12px;
    min-height: 48px;
  }
  /* Floor tabs */
  .floor-tabs .floor-tab {
    min-height: 36px;
    padding: 6px 12px;
  }
  /* Modal close × button */
  .modal-header .btn-ghost {
    min-width: 40px;
    min-height: 40px;
  }
  /* Context menu items */
  .ctxmenu .ctxmenu-item {
    padding: 12px 16px;
    min-height: 44px;
  }
  /* Selection bar buttons */
  .selection-bar .btn {
    min-height: 36px;
  }
}

/* Sidebar drawer behavior at narrow viewports. Below 900px the sidebar
   becomes a slide-in overlay above the canvas, fully covering when open
   on phones. The drag handle hides (you'd just close the drawer instead). */
@media (max-width: 900px) {
  /* Collapse the sidebar grid track to zero so the canvas takes the
     full width — the sidebar floats above as an overlay (position:
     absolute below). The desktop --sidebar-width custom property is
     ignored at this breakpoint. */
  .layout {
    grid-template-columns: 0 1fr;
  }
  .sidebar {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    width: min(360px, 100vw);
    max-width: 100vw;
    z-index: 50;
    transform: translateX(-100%);
    transition: transform 0.18s ease-out;
    box-shadow: 4px 0 24px rgba(0, 0, 0, 0.4);
  }
  .sidebar.open {
    transform: translateX(0);
  }
  .sidebar-resize-handle {
    display: none;  /* not useful at this size */
  }
  /* Backdrop dim when sidebar is open; tap to close */
  .sidebar-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.35);
    z-index: 49;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s;
  }
  .sidebar-backdrop.visible {
    opacity: 1;
    pointer-events: auto;
  }
}

/* Topbar wraps better at narrow widths. The brand and floor tabs are
   the most important things; filters can wrap to a second line. */
@media (max-width: 900px) {
  .topbar {
    flex-wrap: wrap;
    gap: 6px;
    padding: 8px 10px;
  }
  .topbar-controls {
    flex-wrap: wrap;
    gap: 6px;
  }
  .brand-sub {
    display: none;  /* tagline removed on small screens */
  }
  /* The "user-chip" with role + name + logout shrinks; on phones we
     keep just the role badge and the logout button. */
  .user-chip .user-name {
    display: none;
  }
  /* Search collapses to a flexible width that grows to fill */
  #search {
    flex: 1 1 100%;
    order: 99;  /* push to the wrapped second row */
  }
}

/* Modals: full-screen on phone, centered card on tablet/desktop. */
@media (max-width: 700px) {
  .modal-backdrop {
    align-items: stretch;
  }
  .modal {
    max-width: 100vw !important;
    width: 100vw;
    height: 100vh;
    max-height: 100vh;
    border-radius: 0;
    border-left: 0;
    border-right: 0;
  }
  .modal-body {
    padding-bottom: 80px;  /* room for the on-screen keyboard */
  }
  /* The icon picker grid was 96px tiles — drop to 80px on phone */
  .icon-picker-grid {
    grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
  }
  .icon-picker-grid .icon-pick svg {
    width: 44px;
    height: 44px;
  }
}

/* Selection toggle button — when active, the canvas cursor changes
   and the button glows so the user knows they're in select mode. */
.btn#btn-select-mode.active {
  background: rgba(110, 231, 183, 0.15);
  border-color: var(--phosphor);
  color: var(--phosphor);
}
.canvas.select-mode {
  cursor: crosshair;
}

/* Touch tooltip — when shown via tap (instead of hover), it gets a
   little close-X button so the user can dismiss it without tapping
   somewhere specific. */
.tooltip.touch-mode {
  /* slightly more prominent on touch — bigger hit areas, more padding */
  padding: 12px 14px;
  max-width: min(360px, calc(100vw - 32px));
}
.tooltip-close {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 28px;
  height: 28px;
  display: none;  /* shown only when .touch-mode */
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  color: var(--text-faint);
  cursor: pointer;
  font-size: 18px;
  border-radius: 4px;
}
.tooltip-close:hover { background: var(--bg-1); color: var(--text); }
.tooltip.touch-mode .tooltip-close { display: flex; }

/* ─────────────────────────────────────────────────────────────
   Sidebar section header with inline controls (e.g. sort dropdown)
   ───────────────────────────────────────────────────────────── */
.sidebar-section-row {
  display: flex;
  align-items: baseline;
  /* baseline alignment makes the title's caps and the dropdown's caps
     sit on the same typographic line. With `center` the dropdown
     looked subtly higher because of its padding. */
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
  padding-bottom: 6px;
  /* The underline that used to live on .sidebar-title moves here, so
     it spans the full row including the dropdown. */
  border-bottom: 1px solid var(--line);
}
.sidebar-section-row .sidebar-title {
  /* Title styling is otherwise inherited from .sidebar-title; we only
     need to undo the underline + bottom padding it carried. */
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: 0;
}

/* The sort dropdown is presented as a quiet label that happens to be
   tappable — not as a form control. No box, no fill, just the same
   monospace caps used by the section title, plus a chevron. Hover /
   focus brighten the text. The intent: the dropdown reads as a
   sibling of the title ("ALL DEVICES" "SORT BY NAME"), not as a
   separate widget bolted on. */
.sort-label {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  cursor: pointer;
  /* The whole label is hoverable so the "Sort by" prefix and the
     dropdown value highlight together — they're one visual element. */
}
.sort-label-text {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-faint);
  transition: color 0.1s;
}
.sort-label:hover .sort-label-text { color: var(--text-dim); }
.sort-label:focus-within .sort-label-text { color: var(--phosphor); }

.sort-select {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  padding: 0 14px 0 0;
  margin: 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-faint);
  cursor: pointer;
  /* Chevron sits right against the text; same color as the text so
     they read as one element. We swap the stroke color on hover/focus
     via separate background-image rules below — SVG attributes can't
     be re-themed with CSS variables. */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><path fill='none' stroke='%2390a4a0' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M3 4.5L6 7.5L9 4.5'/></svg>");
  background-repeat: no-repeat;
  background-position: right 0 center;
  background-size: 9px;
  transition: color 0.1s, opacity 0.1s;
  /* Without an explicit line-height the select can render a couple of
     pixels taller than the title and break the shared baseline. */
  line-height: 1.4;
  /* Take only the width its content needs; without this it would
     stretch to fill the flex item. */
  width: auto;
  flex: 0 0 auto;
}
.sort-select:hover {
  color: var(--text-dim);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><path fill='none' stroke='%23c4d4d0' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M3 4.5L6 7.5L9 4.5'/></svg>");
}
.sort-select:focus {
  outline: none;
  color: var(--phosphor);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><path fill='none' stroke='%236ee7b7' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M3 4.5L6 7.5L9 4.5'/></svg>");
}
/* The native <option> popup is rendered by the OS — we can only set
   colors and basic font properties. We keep them readable and drop
   the inherited caps/letter-spacing so the actual menu is normal
   sentence case (otherwise items would render as "S O R T:   I P"). */
.sort-select option {
  background: var(--bg-1);
  color: var(--text);
  font-size: 12px;
  font-family: var(--mono);
  text-transform: none;
  letter-spacing: normal;
}

/* On coarse-pointer devices, grow the tap target. Padding inside a
   borderless control is invisible, but the click/tap area grows with
   it, which is what we actually need. */
@media (pointer: coarse) {
  .sort-select {
    padding: 4px 16px 4px 0;
    font-size: 11px;
    background-position: right 0 center;
    background-size: 10px;
  }
  .sidebar-section-row {
    padding-bottom: 8px;
  }
}

/* ─────────────────────────────────────────────────────────────
   Danger zone — destructive actions
   ───────────────────────────────────────────────────────────── */
.danger-title {
  color: var(--red);
}
/* Destructive button — used for "Clear topology" and similar. We keep
   the same bordered/ghost shape as other buttons but tint to red so
   it's visually distinct from the safe phosphor-green primaries. */
.btn.btn-danger {
  background: rgba(239, 68, 68, 0.08);
  color: var(--red);
  border-color: var(--red);
}
.btn.btn-danger:hover:not(:disabled) {
  background: rgba(239, 68, 68, 0.18);
  color: #fff;
}
.btn.btn-danger:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Checkbox + label row inside a modal form. The default browser
   checkbox is hard to align with phosphor text, so we use a custom
   look that matches the rest of the theme. */
.check-row {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-dim);
}
.check-row input[type="checkbox"] {
  /* Native control with a phosphor accent on supporting browsers.
     We keep it native for accessibility (screen readers, keyboard
     focus). The accent-color paints the check on Chrome/Firefox. */
  width: 18px;
  height: 18px;
  accent-color: var(--phosphor);
  cursor: pointer;
}

/* ─────────────────────────────────────────────────────────────
   Floor connectors — clickable icons that link two floor views
   ───────────────────────────────────────────────────────────── */
#connectors-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;   /* children opt back in */
}
.floor-connector {
  position: absolute;
  /* sits in the world transform like rooms + nodes */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 6px 8px 4px;
  background: rgba(15, 20, 24, 0.9);
  border: 1px dashed var(--phosphor);
  border-radius: 8px;
  color: var(--phosphor);
  font-family: var(--mono);
  cursor: pointer;
  user-select: none;
  pointer-events: auto;
  transition: background 0.1s, transform 0.05s;
  min-width: 96px;
  z-index: 3;
}
.floor-connector:hover {
  background: rgba(110, 231, 183, 0.10);
}
.floor-connector:active {
  transform: scale(0.97);
}
.floor-connector .fc-icon {
  font-size: 22px;
  line-height: 1;
  font-weight: 700;
}
.floor-connector .fc-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text);
  text-align: center;
  white-space: nowrap;
}
.floor-connector .fc-count {
  font-size: 9px;
  color: var(--text-faint);
  letter-spacing: 0.04em;
}
/* Danger ctxmenu item (used by connector "Delete" option) */
.ctxmenu .ctxmenu-item.danger {
  color: var(--red);
}
.ctxmenu .ctxmenu-item.danger:hover {
  background: rgba(239, 68, 68, 0.12);
}

/* ─────────────────────────────────────────────────────────────
   Undo toast — appears after a bulk move action
   ───────────────────────────────────────────────────────────── */
.undo-toast {
  position: fixed;
  /* Bottom-left, above where logs/footers would go. Z-index above
     canvas but below modals so a modal opening would cover it
     (modals are at z=400+). */
  bottom: 16px;
  left: 16px;
  z-index: 250;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px 8px 14px;
  background: var(--bg-1);
  border: 1px solid var(--phosphor);
  border-radius: 6px;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text);
  /* Subtle entrance animation — slide up + fade. The hidden class
     uses display:none so this only plays on initial show. */
  animation: undo-toast-in 0.18s ease-out;
}
@keyframes undo-toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.undo-toast.hidden { display: none; }
.undo-toast-text {
  white-space: nowrap;
  color: var(--text);
}
.undo-toast .btn {
  /* Tighter than default — this is a status-bar style affordance,
     not a primary action. */
  padding: 4px 10px;
  font-size: 11px;
}
.undo-toast .undo-toast-dismiss {
  padding: 4px 8px;
  color: var(--text-faint);
}
.undo-toast .undo-toast-dismiss:hover {
  color: var(--text);
}
/* On narrow screens, drop the toast width and let the long text wrap. */
@media (max-width: 600px) {
  .undo-toast {
    right: 16px;
    flex-wrap: wrap;
  }
  .undo-toast-text {
    white-space: normal;
  }
}
