/* ==========================================================================
   neetmap — styles.css
   Tokens + components. Recolour in theme.css (loaded last), not here.
   Fully self-hosted: fonts are local (@font-face below), no CDNs.

   The token block, overlay layer, .hex-frame and header are inherited from
   the Cyberpunk HUD spec in DESIGN.md — see that document before adding a
   colour or a size, and compose from the tokens rather than inventing one.
   ========================================================================== */

@font-face {
  font-family: "JetBrains Mono Local";
  src: url("fonts/JetBrainsMono-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono Local";
  src: url("fonts/JetBrainsMono-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   DEFAULT PALETTE.
   Accent colours are raw "R G B" channels so every border, glow and gradient
   derives its own alpha via rgb(var(--accent-rgb) / 0.35). Change a channel
   and everything downstream follows. A literal rgba() in a component below
   is a bug — it silently opts out of theming.
   -------------------------------------------------------------------------- */
:root {
  --accent-rgb: 0 255 245;        /* primary   : cyan    */
  --accent-alt-rgb: 255 43 214;   /* secondary : magenta */
  --warn-rgb: 255 183 0;          /* warning   : amber   */
  --ok-rgb: 57 255 136;           /* healthy   : green   */
  --alert-rgb: 255 59 92;         /* critical  : red     */

  --accent: rgb(var(--accent-rgb));
  --accent-alt: rgb(var(--accent-alt-rgb));
  --warn: rgb(var(--warn-rgb));
  --ok: rgb(var(--ok-rgb));
  --alert: rgb(var(--alert-rgb));

  --cyan: var(--accent);
  --cyan-dim: rgb(var(--accent-rgb) / 0.65);
  --magenta: var(--accent-alt);
  --amber: var(--warn);
  --green: var(--ok);
  --red: var(--alert);
  --border-cyan: rgb(var(--accent-rgb) / 0.35);

  --bg-void: #05070c;
  --bg-panel: rgba(10, 18, 28, 0.55);
  --bg-panel-solid: #0a1220;
  --bg-raised: rgb(var(--accent-rgb) / 0.04);
  --text-primary: #d9fbff;
  --text-dim: #6f97a3;

  --scanline-opacity: 0.18;
  --noise-opacity: 0.035;
  --glow-strength: 1;

  /* Map geometry. Exposed as tokens so theme.css can retune the picture
     without touching lib/map.js — see the notes at the bottom of theme.css.
     --map-core is the radius of the infrastructure disc at the middle, as a
     fraction of the usable radius; --map-node is the icon size in px, and it
     also sets the row spacing in the client ring. */
  --map-core: 0.28;
  --map-node: 26;
  /* 1 = draw the rotated slice names around the chart, 0 = don't. They cost
     ~42px of margin on every side, which a narrow screen cannot spare — the
     breakpoint that turns them off lives with the other media queries at the
     bottom of this file, and lib/map.js just reads the token. */
  --map-labels: 1;

  --font-mono: "JetBrains Mono Local", ui-monospace, "Cascadia Mono", "Share Tech Mono",
    Consolas, "SFMono-Regular", "Courier New", monospace;
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg-void);
  color: var(--text-primary);
  font-family: var(--font-mono);
  overflow: hidden;
}

body {
  background:
    radial-gradient(ellipse at 20% 0%, rgb(var(--accent-rgb) / 0.07), transparent 55%),
    radial-gradient(ellipse at 85% 100%, rgb(var(--accent-alt-rgb) / 0.06), transparent 55%),
    var(--bg-void);
}

::selection {
  background: var(--cyan);
  color: #000;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------------- Overlays: scanlines / vignette / noise ----------------
   pointer-events: none is mandatory on all three — without it the map
   canvas underneath receives no clicks at all. */
.scanlines {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 100;
  background: repeating-linear-gradient(
    to bottom,
    rgb(0 0 0 / 0) 0px,
    rgb(0 0 0 / 0) 1px,
    rgb(0 0 0 / var(--scanline-opacity)) 2px,
    rgb(0 0 0 / 0) 3px
  );
  mix-blend-mode: overlay;
  animation: scan-drift 9s linear infinite;
}
@keyframes scan-drift {
  0% { background-position-y: 0; }
  100% { background-position-y: 100px; }
}

.vignette {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 99;
  box-shadow: inset 0 0 220px rgb(0 0 0 / 0.85);
}

.noise {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 98;
  opacity: var(--noise-opacity);
  background-image: radial-gradient(circle, #fff 1px, transparent 1px);
  background-size: 3px 3px;
}

/* -------------------------------- Layout -------------------------------- */
.hud {
  position: relative;
  z-index: 1;
  height: 100vh;
  padding: 14px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Angled "HUD frame" corners via clip-path, plus a glowing border.
   NOTE: clip-path clips EVERYTHING, including absolutely-positioned
   children. The map tooltip is therefore portalled to <body>, not nested
   inside the map panel. */
.hex-frame {
  position: relative;
  background: var(--bg-panel);
  border: 1px solid var(--border-cyan);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  clip-path: polygon(
    16px 0, 100% 0, 100% calc(100% - 16px), calc(100% - 16px) 100%,
    0 100%, 0 16px
  );
  box-shadow:
    0 0 0 1px rgb(var(--accent-rgb) / 0.05) inset,
    0 0 18px rgb(var(--accent-rgb) / 0.06),
    0 4px 24px rgb(0 0 0 / 0.4);
}
.hex-frame::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  clip-path: polygon(
    16px 0, 100% 0, 100% calc(100% - 16px), calc(100% - 16px) 100%,
    0 100%, 0 16px
  );
  border: 1px solid transparent;
  background:
    linear-gradient(135deg, rgb(var(--accent-rgb) / 0.5), transparent 30%, transparent 70%, rgb(var(--accent-alt-rgb) / 0.35))
    border-box;
  -webkit-mask:
    linear-gradient(#fff 0 0) padding-box,
    linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

/* ------------------------------- Header --------------------------------- */
.hud-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 26px 12px 18px;
  flex-shrink: 0;
  gap: 20px;
}

.header-left {
  flex: 0 0 auto;
  min-width: 0;
}
.alias {
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--cyan);
  text-shadow: 0 0 8px rgb(var(--accent-rgb) / 0.7), 0 0 22px rgb(var(--accent-rgb) / 0.35);
}
.hostname {
  font-size: 0.72rem;
  color: var(--text-dim);
  letter-spacing: 0.08em;
  margin-top: 2px;
}
.hostname span { color: var(--text-primary); }
.hostname .untrusted { color: var(--warn); }

.header-mid {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  justify-content: center;
}
/* A GRID, not a wrapping flex row. Wrapping produces a ragged, centred
   staircase; a fixed column count keeps the block aligned at every width. */
.spec-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, auto));
  justify-content: center;
  align-items: center;
  gap: 2px clamp(16px, 2.4vw, 52px);
  width: 100%;
}
.spec {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
}
.spec-label {
  font-size: 0.6rem;
  color: var(--text-dim);
  letter-spacing: 0.15em;
}
.spec-value {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text-primary);
  margin-top: 2px;
  letter-spacing: 0.03em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  /* Values update in place; without tabular figures they shimmy as digits
     change width. */
  font-variant-numeric: tabular-nums;
}
.spec-value.accent { color: var(--cyan); }

.header-right {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}
.clock {
  font-size: 1.3rem;
  letter-spacing: 0.08em;
  color: var(--text-primary);
  text-shadow: 0 0 6px rgb(var(--accent-rgb) / 0.4);
  font-variant-numeric: tabular-nums;
}
.conn-status {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  color: var(--text-dim);
}
.conn-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--red);
  box-shadow: 0 0 8px var(--red);
  transition: background 0.3s, box-shadow 0.3s;
}
.conn-status.live .conn-dot {
  background: var(--green);
  box-shadow: 0 0 8px var(--green);
  animation: pulse 2s ease-in-out infinite;
}
.conn-status.live .conn-text { color: var(--green); }
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

/* -------------------------------- Grid ----------------------------------- */
/* The map is the product, so it takes the whole left column at every size.
   Explicit row fractions rather than "one auto + the rest": a leftover track
   can be starved to zero and then go negative. */
.grid {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(290px, 24vw, 400px);
  grid-template-rows: minmax(0, 3fr) minmax(0, 2fr);
  gap: 14px;
}
.panel-map      { grid-column: 1; grid-row: 1 / -1; }
.panel-devices  { grid-column: 2; grid-row: 1; }
.panel-detail   { grid-column: 2; grid-row: 2; }

.panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  padding: 12px 16px 16px;
  /* inline-size, NOT size: a size container must have a fixed height, which
     forces content to overlap or be clipped when it doesn't fit. */
  container-type: inline-size;
  overflow: hidden;
}

.panel-title {
  font-size: clamp(0.72rem, 2.2cqw, 1rem);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--cyan);
  text-shadow: 0 0 6px rgb(var(--accent-rgb) / 0.5);
  border-bottom: 1px solid rgb(var(--accent-rgb) / 0.18);
  padding-bottom: 8px;
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
.title-spacer { flex: 1; }
.panel-meta {
  color: var(--text-dim);
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  font-variant-numeric: tabular-nums;
}

.panel-body {
  flex: 1;
  min-height: 0;
  display: flex;
}

/* --------------------------------- Map ----------------------------------- */
.map-body {
  position: relative;
  padding: 0;
}
.map-canvas {
  /* Canvas has no intrinsic size — without explicit dimensions it renders at
     300x150 regardless of its container. */
  width: 100%;
  height: 100%;
  display: block;
  cursor: crosshair;
}

/* Legend sits over the canvas corner rather than stealing layout height. */
.map-legend {
  position: absolute;
  left: 2px;
  bottom: 2px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  max-width: 70%;
  pointer-events: none;
}
.legend-item {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 0.58rem;
  letter-spacing: 0.1em;
  color: var(--text-dim);
  text-transform: uppercase;
}
.legend-swatch {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
}
/* Key for the map's second encoding: edge style = link medium. */
.legend-line {
  width: 16px;
  height: 0;
  flex-shrink: 0;
  border-top: 1px solid rgb(var(--accent-rgb) / 0.6);
}
.legend-line.dotted { border-top-style: dotted; }

/* Inline secondary detail — measurements behind an estimate, extra interface
   counts. Dim and small so it never competes with the value it qualifies. */
.hint {
  color: var(--text-dim);
  font-size: 0.9em;
}

.map-scanstate {
  position: absolute;
  right: 4px;
  bottom: 4px;
  font-size: 0.58rem;
  letter-spacing: 0.12em;
  color: var(--text-dim);
  text-transform: uppercase;
  display: flex;
  align-items: center;
  gap: 6px;
  pointer-events: none;
}
.map-scanstate.active { color: var(--accent); }

/* Portalled to <body>: a tooltip nested inside .hex-frame is clipped by its
   clip-path. */
.map-tip {
  position: fixed;
  z-index: 200;
  pointer-events: none;
  padding: 7px 10px;
  background: var(--bg-panel-solid);
  border: 1px solid var(--border-cyan);
  border-radius: 3px;
  box-shadow: 0 4px 20px rgb(0 0 0 / 0.6);
  font-size: 0.68rem;
  line-height: 1.5;
  max-width: 280px;
  opacity: 0;
  transition: opacity 0.12s ease;
}
.map-tip.show { opacity: 1; }
.map-tip .tip-name { color: var(--text-primary); font-weight: 700; }
.map-tip .tip-ip { color: var(--cyan); font-variant-numeric: tabular-nums; }
.map-tip .tip-sub { color: var(--text-dim); letter-spacing: 0.08em; }

/* ------------------------------ Device list ------------------------------ */
.list-body {
  flex-direction: column;
  overflow-y: auto;
  gap: 3px;
}

/* Each row is its own grid, so `auto` tracks would size per row and the
   trailing columns would stagger. Fixed ch tracks are what make a list of
   rows read as a table. */
/* Track widths are fixed in ch, not auto: the address column must not
   resize per row or the whole list goes ragged. 15ch is sized for a full
   "192.168.100.199" plus the "+1" marker a merged multi-NIC device carries —
   at 11ch a long address ran straight into the role badge. */
.dev-row {
  display: grid;
  grid-template-columns: 8px minmax(0, 1fr) 15ch 5.5ch;
  align-items: center;
  gap: 8px;
  padding: 5px 7px;
  background: var(--bg-raised);
  border: 1px solid rgb(var(--accent-rgb) / 0.1);
  border-radius: 3px;
  font-size: 0.72rem;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: inherit;
  width: 100%;
  transition: background 0.15s, border-color 0.15s;
}
.dev-row:hover {
  background: rgb(var(--accent-rgb) / 0.09);
  border-color: rgb(var(--accent-rgb) / 0.3);
}
.dev-row.selected {
  border-color: var(--accent);
  background: rgb(var(--accent-rgb) / 0.12);
  box-shadow: 0 0 10px rgb(var(--accent-rgb) / 0.25);
}
.dev-row.offline { opacity: 0.45; }

.dev-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-dim);
  flex-shrink: 0;
}
.dev-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-primary);
}
.dev-ip {
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  font-size: 0.66rem;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Badge text length varies wildly (IOT vs HYPERVISOR); a fixed ch width is
   what stops the column going ragged. */
.dev-role {
  font-size: 0.55rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: center;
  color: var(--text-dim);
  overflow: hidden;
}

/* ------------------------------- Inspector -------------------------------- */
.detail-body {
  flex-direction: column;
  overflow-y: auto;
  gap: 8px;
}
.detail-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.detail-name {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
}
.detail-ip {
  font-size: 0.72rem;
  color: var(--cyan);
  font-variant-numeric: tabular-nums;
}

.kv {
  display: grid;
  grid-template-columns: 8.5ch minmax(0, 1fr);
  gap: 2px 10px;
  font-size: 0.68rem;
}
.kv dt {
  color: var(--text-dim);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 0.58rem;
  align-self: center;
}
.kv dd {
  margin: 0;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.chip {
  font-size: 0.58rem;
  letter-spacing: 0.05em;
  padding: 1px 5px;
  border: 1px solid currentColor;
  border-radius: 2px;
  color: var(--text-dim);
  white-space: nowrap;
}
.chip.accent { color: var(--cyan); }
.chip.ok { color: var(--green); }
.chip.warn { color: var(--warn); }

.svc-block {
  border-left: 2px solid rgb(var(--accent-rgb) / 0.3);
  padding-left: 8px;
  font-size: 0.66rem;
}
.svc-type { color: var(--magenta); }
.svc-txt {
  color: var(--text-dim);
  font-size: 0.62rem;
  overflow-wrap: anywhere;
}

/* -------------------------------- Controls -------------------------------- */
.btn {
  font-family: inherit;
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  background: rgb(var(--accent-rgb) / 0.08);
  border: 1px solid rgb(var(--accent-rgb) / 0.45);
  border-radius: 3px;
  padding: 4px 9px;
  cursor: pointer;
  transition: background 0.15s, box-shadow 0.15s;
}
.btn:hover {
  background: rgb(var(--accent-rgb) / 0.16);
  box-shadow: 0 0 12px rgb(var(--accent-rgb) / 0.35);
}
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; }
.btn-ghost {
  background: transparent;
  border-color: rgb(var(--accent-rgb) / 0.25);
  color: var(--text-dim);
}

.input {
  font-family: inherit;
  font-size: 0.7rem;
  color: var(--text-primary);
  background: rgb(0 0 0 / 0.35);
  border: 1px solid rgb(var(--accent-rgb) / 0.25);
  border-radius: 3px;
  padding: 4px 7px;
  caret-color: var(--accent);
  min-width: 0;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.input::placeholder { color: var(--text-dim); }
.input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 1px rgb(var(--accent-rgb) / 0.4);
}
.rename-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 6px;
}

/* ------------------------------ States ------------------------------------ */
/* Every list needs empty, loading and error. Never leave a blank box. */
.state {
  color: var(--text-dim);
  text-align: center;
  padding: 24px 12px;
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  width: 100%;
  margin: auto;
}
.state-alert { color: var(--alert); }

/* Capability banner — explains an empty or MAC-less map instead of letting
   it look broken. */
.notice {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  margin-bottom: 8px;
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  color: var(--warn);
  background: rgb(var(--warn-rgb) / 0.07);
  border-left: 2px solid var(--warn);
  border-radius: 2px;
}
.notice.hidden { display: none; }

.toasts {
  position: fixed;
  z-index: 300;
  bottom: 22px;
  right: 22px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.toast {
  padding: 9px 14px;
  background: var(--bg-panel-solid);
  border-left: 3px solid var(--accent);
  border-radius: 3px;
  box-shadow: 0 4px 24px rgb(0 0 0 / 0.5);
  font-size: 0.7rem;
  animation: toast-in 0.3s ease;
}
.toast-alert { border-left-color: var(--alert); }
@keyframes toast-in {
  from { opacity: 0; transform: translateX(16px); }
  to { opacity: 1; transform: none; }
}

/* -------------------------------- Scrollbars ------------------------------ */
* {
  scrollbar-width: thin;
  scrollbar-color: rgb(var(--accent-rgb) / 0.4) transparent;
}
*::-webkit-scrollbar { width: 6px; height: 6px; }
*::-webkit-scrollbar-thumb {
  background: rgb(var(--accent-rgb) / 0.35);
  border-radius: 3px;
}

/* -------------------------------- Responsive ------------------------------ */
/* Below this the side column can't hold a readable row, so the map goes
   full width and the panels stack under it. The page starts scrolling here
   too — a kiosk display never hits this, a phone always does. */
@media (max-width: 900px) {
  .grid {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(320px, 60vh) minmax(200px, auto) minmax(200px, auto);
  }
  .panel-map     { grid-column: 1; grid-row: 1; }
  .panel-devices { grid-column: 1; grid-row: 2; }
  .panel-detail  { grid-column: 1; grid-row: 3; }
  html, body { overflow-y: auto; }
  .hud { height: auto; min-height: 100vh; }
}

@media (max-width: 1150px) {
  .spec-row {
    grid-template-columns: repeat(2, minmax(0, auto));
    gap: 6px clamp(20px, 4vw, 60px);
  }
}

@media (max-width: 720px) {
  .hud-header {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  .header-right { align-items: flex-start; }
  .spec-row { grid-template-columns: repeat(2, minmax(0, auto)); }

  /* The chart is the whole point here and the margin around it is not
     affordable: the rotated slice names ate ~42px on every side of an
     already-small circle, and the icons were too big for the room left.
     lib/map.js re-reads both tokens on resize, so this applies live. */
  :root {
    --map-labels: 0;
    --map-node: 21;
  }

  /* The legend STAYS. With the slice names gone it is the only key to what
     the colours mean, so it shrinks rather than disappearing. */
  .map-legend {
    max-width: 100%;
    gap: 2px 9px;
  }
  .legend-item { font-size: 0.52rem; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
