/*
 * LAN devices — styles for the lan-ingest dashboard.
 * Compact internal look; light + dark via prefers-color-scheme; system fonts only,
 * no icon library (unicode glyphs), tables scroll inside their own container.
 * Author: Kombo — Claudio Novus <claudio@kombo.so>
 */

:root {
  --bg: #ffffff;
  --bg-soft: #f6f7f9;
  --fg: #16181d;
  --muted: #6b7280;
  --border: #e2e5ea;
  --accent: #2563eb;
  --on: #16a34a;
  --off: #b8bcc4;
  --warn: #b45309;
  --bad: #dc2626;
  --sel: #eef3ff;
  --shadow: 0 2px 18px rgba(15, 20, 30, .14);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14161a;
    --bg-soft: #1b1e24;
    --fg: #e6e8ec;
    --muted: #949aa6;
    --border: #2a2f39;
    --accent: #6899ff;
    --on: #34d17f;
    --off: #4b515c;
    --warn: #e0a33a;
    --bad: #ff6b6b;
    --sel: #1f2733;
    --shadow: 0 2px 18px rgba(0, 0, 0, .5);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
}

h1 { font-size: 16px; font-weight: 650; margin: 0; letter-spacing: .01em; }
h2 { font-size: 15px; font-weight: 650; margin: 0; overflow-wrap: anywhere; }
h3 { font-size: 12px; font-weight: 650; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 22px 0 8px; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.mono { font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; font-size: 12.5px; }
.muted { color: var(--muted); }
.small { font-size: 12px; }
.strong { font-weight: 600; }
.err { color: var(--bad); }

/* ------------------------------------------------------------- top bar */

.topbar {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-soft);
  position: sticky; top: 0; z-index: 5;
}
.who { margin-left: auto; display: flex; align-items: center; gap: 10px; font-size: 12.5px; }

/* ---------------------------------------------------------- site selector */

/* A native <select>, not a row of chips: with 4+ sites the chips wrapped into a wall. */
.sites { padding: 12px 16px 4px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; }
.sitepick {
  display: inline-flex; align-items: center; gap: 8px;
  max-width: 100%;
}
.sitepick-label { color: var(--muted); font-size: 12.5px; }
.sitepick select {
  font: inherit; color: inherit;
  background: var(--bg);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 7px 10px; min-width: 22em; max-width: 100%;
  cursor: pointer;
}
.sitepick select:hover { border-color: var(--accent); }
.sitepick select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.sitepick select:disabled { color: var(--muted); cursor: default; }
/* Freshness of the SELECTED site's last sync — the colour the chips used to carry. */
.sitepick.warn select { border-color: var(--warn); }
.sitepick.bad  select { border-color: var(--bad); }

/* ------------------------------------------------------------- scan mode */

/* What this site's client is ALLOWED to do on the LAN. The wanted value is a choice; the effective
   one is what the device reports back — they differ while a change propagates (up to one 5-minute
   run) and for good when the device's env file pins SCAN_MODE. Both are always spelled out. */
.modepick select { min-width: 26em; }
.modepick.busy select { opacity: .6; }
.modehint {
  margin: 6px 0 0; color: var(--muted); font-size: 12.5px;
  display: flex; flex-wrap: wrap; gap: 4px 10px;
  flex: 1 0 100%;                     /* .sites is a flex row: the hint keeps its own line */
}
.modehint .warn { color: var(--warn); }
.modehint .err { color: var(--bad); }
.modehint .loud { color: var(--bad); }

/* ------------------------------------------------------------- services */

/* Only sites in active-scan ever have these. One row per open port; the version string is the
   product plus its version, exactly as nmap reported it. */
.services { display: flex; flex-direction: column; gap: 2px; }
.svc {
  display: grid; grid-template-columns: 5.5em 1fr; gap: 6px 10px;
  padding: 5px 0; border-bottom: 1px solid var(--border);
}
.svc:last-child { border-bottom: 0; }
.svc-port { color: var(--muted); }
.svc-what { overflow-wrap: anywhere; }

/* ---------------------------------------------------------------- tags */

/* Asset type per MAC (replaced the free-text note). A chip set by SOMEONE ELSE — a colleague, or
   automation acting as e.g. `claudio` — is dashed, so "who says this is a kiosk" is visible at a
   glance; your own tags are plain. */
.tagline { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; margin-top: 3px; }
.tagchip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 7px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-soft); color: var(--muted);
  font-size: 11.5px; line-height: 17px; white-space: nowrap;
}
.tagchip.by { border-style: dashed; }
.tagchip .x {
  font: inherit; font-size: 11px; line-height: 1;
  background: none; border: 0; padding: 0;
  color: var(--muted); cursor: pointer;
}
.tagchip .x:hover { color: var(--bad); }

/* ------------------------------------------------- properties (panel) */

/* Tags and Attributes are two PROPERTIES of the same device and read as one block: the same label
   column, the same spacing, the same borders. Tags is a multi-select (pick from what exists or type
   something new), Attributes free key/value text — the difference is the control, not the styling. */
.props { display: flex; flex-direction: column; gap: 12px; }
.prop { display: flex; flex-direction: column; gap: 6px; }
.prop-label { color: var(--muted); font-size: 12.5px; }
.prop input { font: inherit; }

/* The tag multi-select: chips + one input, with the option menu hanging under it. */
.tagselect { position: relative; display: flex; flex-direction: column; gap: 6px; }
.tagselect > input { width: 100%; }
.tagselect.open > input { border-color: var(--accent); }

/* ------------------------------------------------------ multi-select menu */

/* One menu widget, two users: the panel's tag editor and the toolbar's tag filter. Options carry a
   check when they are on and the number of devices that use them — the vocabulary IS what is in
   use, so that count is the whole story of where a tag comes from. */
.multiselect { position: relative; display: inline-flex; align-items: center; gap: 4px; }
.ms-toggle {
  font: inherit; color: inherit;
  background: var(--bg);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 7px 10px; min-width: 10em; max-width: 100%;
  text-align: left; cursor: pointer;
}
.ms-toggle:hover { border-color: var(--accent); }
.ms-toggle.on { border-color: var(--accent); color: var(--accent); }
.ms-clear {
  font: inherit; font-size: 12px; line-height: 1;
  background: none; border: 0; padding: 4px;
  color: var(--muted); cursor: pointer;
}
.ms-clear:hover { color: var(--bad); }
.ms-menu {
  position: absolute; z-index: 20; top: calc(100% + 4px); left: 0;
  min-width: 100%; max-width: min(22em, 90vw); max-height: 16em; overflow-y: auto;
  display: flex; flex-direction: column;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: var(--shadow); padding: 4px;
}
.ms-menu[hidden] { display: none; }   /* display:flex would otherwise beat the UA's [hidden] rule */
.ms-opt {
  display: grid; grid-template-columns: 1.2em 1fr auto; gap: 8px; align-items: center;
  font: inherit; text-align: left;
  background: none; border: 0; border-radius: 6px; padding: 5px 6px;
  color: inherit; cursor: pointer; white-space: nowrap;
}
.ms-opt:hover, .ms-opt.active { background: var(--sel); }
.ms-opt.on .ms-name { color: var(--accent); }
.ms-opt.create .ms-name { color: var(--accent); }
.ms-check { color: var(--accent); font-size: 12px; }
.ms-name { overflow: hidden; text-overflow: ellipsis; }
.ms-count { color: var(--muted); font-size: 11.5px; }
.ms-empty { padding: 6px; }

/* ---------------------------------------------------------- attributes */

/* Custom key/value per MAC (platformId, platformUrl, hostname, …). A URL value is a real link —
   that is the point: one click from "this MAC" to the platform record for that box. */
.attrs { display: grid; gap: 4px; }
.attr {
  display: grid; grid-template-columns: minmax(6em, 34%) 1fr auto;
  gap: 8px; align-items: baseline;
  padding: 3px 0; border-bottom: 1px dashed var(--border);
}
.attr:last-child { border-bottom: 0; }
.attr-k { color: var(--muted); overflow-wrap: anywhere; }
.attr-v { overflow-wrap: anywhere; }
.attr-by { grid-column: 1 / -1; margin-top: -3px; }
.attr-actions { display: flex; gap: 6px; }
.attr-actions .x {
  font: inherit; font-size: 12px; line-height: 1;
  background: none; border: 0; padding: 0;
  color: var(--muted); cursor: pointer;
}
.attr-actions .x:hover { color: var(--accent); }
.attr-actions .x[data-act="remove-attr"]:hover { color: var(--bad); }
.attr-add { display: flex; gap: 6px; flex-wrap: wrap; }
.attr-add input { flex: 1 1 8em; min-width: 0; font: inherit; }
.attrs-editor .attrs { margin-bottom: 2px; }

/* ---------------------------------------------------------- suggestions */

/* "suggested: <person> · <model>" under an unnamed device — the correlation's output. */
.sugg { color: var(--muted); }
.link {
  font: inherit; font-size: 12px;
  background: none; border: 0; padding: 0;
  color: var(--accent); cursor: pointer; text-decoration: underline;
}
.link:hover { color: var(--fg); }

/* ------------------------------------------------------------- controls */

.main { padding: 12px 16px 40px; }
.controls { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.chk { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); user-select: none; cursor: pointer; }
.chk input { accent-color: var(--accent); }

input[type="search"], input[type="text"] {
  padding: 6px 9px;
  border: 1px solid var(--border); border-radius: 7px;
  background: var(--bg); color: var(--fg);
  font: inherit;
  min-width: 0;
}
input[type="search"] { flex: 1 1 220px; max-width: 380px; }
input:focus-visible, button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.btn, .ghost {
  font: inherit; cursor: pointer;
  border-radius: 7px; padding: 6px 12px;
  border: 1px solid var(--border);
  background: var(--bg-soft); color: var(--fg);
}
.btn { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.btn:hover { filter: brightness(1.07); }
.ghost:hover { border-color: var(--accent); color: var(--accent); }
#refresh { margin-left: auto; }

.status { padding: 10px 0; color: var(--muted); display: flex; align-items: center; gap: 10px; }

/* --------------------------------------------------------------- table */

.tablewrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 9px; }
table { border-collapse: collapse; width: 100%; }
th, td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--border); vertical-align: top; white-space: nowrap; }
th { font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 600; background: var(--bg-soft); position: sticky; top: 0; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr[data-mac] { cursor: pointer; }
tbody tr[data-mac]:hover { background: var(--bg-soft); }
tbody tr.sel { background: var(--sel); }
tr.empty td { color: var(--muted); text-align: center; padding: 22px; }

/* Sortable headers: the whole cell is the target, the arrow marks the active column. */
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--fg); }
th.sorted { color: var(--accent); }
th .ind { display: inline-block; width: 1em; font-size: 9px; vertical-align: 1px; }

.c-dot { width: 22px; }
/* The Platform column carries the platformUrl attribute; it hides itself when no device on the
   LAN has one, so an untagged site keeps the same table it always had. */
.c-plat { max-width: 22ch; overflow: hidden; text-overflow: ellipsis; }
body:not(.has-platform) .c-plat { display: none; }
.c-num { text-align: right; font-variant-numeric: tabular-nums; }
.c-up { width: 4.5em; }
body:not(.has-uptime) .c-up { display: none; }
.c-name, .c-mac { white-space: normal; }
.sub { display: block; color: var(--muted); font-size: 12px; }

.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--off); vertical-align: middle; }
.dot.on { background: var(--on); }
.tag {
  display: inline-block; font-size: 10.5px; line-height: 1.5; padding: 0 5px;
  border: 1px solid var(--border); border-radius: 4px; color: var(--muted); vertical-align: 1px;
}
.here { color: var(--on); font-weight: 600; }
.count { padding: 10px 2px; color: var(--muted); font-size: 12.5px; }

/* --------------------------------------------------------------- panel */

.panel {
  position: fixed; top: 0; right: 0; bottom: 0; width: 420px; max-width: 100vw;
  overflow-y: auto; padding: 14px 16px 40px;
  background: var(--bg); border-left: 1px solid var(--border); box-shadow: var(--shadow);
  z-index: 10;
}
.panel-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.panel-head .ghost { margin-left: auto; }

.ident { margin: 0; display: grid; gap: 4px; }
.kv { display: grid; grid-template-columns: 92px 1fr; gap: 10px; align-items: baseline; }
.kv dt { color: var(--muted); font-size: 12.5px; }
.kv dd { margin: 0; overflow-wrap: anywhere; }

.editor { margin-top: 18px; display: grid; gap: 8px; }
.editor label { display: grid; gap: 3px; font-size: 12.5px; color: var(--muted); }
.editor-actions { display: flex; align-items: center; gap: 10px; margin-top: 2px; }
.save { font-size: 12.5px; }
.save.ok { color: var(--on); }
.save.err { color: var(--bad); }
.save.busy { color: var(--muted); }
.editor p { margin: 2px 0 0; }

.history th, .history td { padding: 6px 9px; font-size: 12.5px; }

/* ---------------------------------------------------------- uptime strip */

/* One bar per bucket (168 at 7 d, 180 at 30/90 d) in a flex row, so the strip is exactly as wide as
   the panel and every bar the same width — no chart library, no canvas, no axis maths. Three
   states: green seen, grey not seen, HATCHED for "the site was not scanning then", which is a
   different claim and must never look like downtime. A partly-seen bucket fills from the bottom
   (`--f`), so an hour with 20 minutes of presence reads as a third of a bar. */
.upswitch { display: flex; gap: 6px; margin: 0 0 8px; }
.upswitch .ghost { padding: 3px 9px; font-size: 12px; }
.upswitch .ghost.on { border-color: var(--accent); color: var(--accent); font-weight: 600; }
.uphead { margin: 0 0 10px; }
.uphead .strong { font-size: 15px; }
.upsrc { margin-bottom: 10px; }
.upsrc-head { display: flex; align-items: baseline; gap: 10px; font-size: 12.5px; color: var(--muted); }
.upsrc-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.upsrc-pct { margin-left: auto; font-variant-numeric: tabular-nums; }
.upstrip {
  /* No gap: at ~2 px a bar, a 1 px separator turns a solid green day into stripes — contiguous
     buckets must read as one block, and the hatch is what carries the "not scanning" meaning. */
  display: flex; align-items: stretch;
  height: 30px; margin-top: 4px;
  border: 1px solid var(--border); border-radius: 5px; overflow: hidden;
  background: var(--bg-soft);
}
.upstrip .b { flex: 1 1 0; min-width: 1px; background: var(--off); }
.upstrip .b.up { background: var(--on); }
.upstrip .b.part { background: linear-gradient(to top, var(--on) var(--f), var(--off) var(--f)); }
.upstrip .b.blind {
  background: repeating-linear-gradient(45deg, var(--border) 0 2px, var(--bg-soft) 2px 5px);
}
.upaxis { display: flex; justify-content: space-between; color: var(--muted); font-size: 11.5px; margin-top: -2px; }

/* ---------------------------------------------------------------- gate */

.gate {
  min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 14px;
  padding: 24px; text-align: center;
}
.gate-msg { margin: 0; color: var(--muted); max-width: 34ch; }
.gate .btn { padding: 10px 18px; text-decoration: none; display: inline-block; }

/* -------------------------------------------------------------- mobile */

@media (max-width: 720px) {
  .topbar, .sites, .main { padding-left: 12px; padding-right: 12px; }
  .sitepick { display: flex; width: 100%; }
  .sitepick select { flex: 1; min-width: 0; }
  .multiselect { flex: 1; min-width: 0; }
  .ms-toggle { flex: 1; min-width: 0; }
  .panel { width: 100%; border-left: 0; }
  .kv { grid-template-columns: 84px 1fr; }
  body.panel-open .main, body.panel-open .sites { display: none; }
}
