/* Dartec HA Manager — ops console.
 *
 * Tokens follow the Dartec brand system (smarthome-planner:
 * assets/design-tokens.css + docs/brand-guidelines.md). Light is the brand
 * default ("blueprint paper": warm cream ground, warmed ink, one teal);
 * dark is a designed variant, not an inversion. Theme resolution matches the
 * planner: explicit data-theme wins, else prefers-color-scheme.
 *
 * Brand rules enforced here:
 *  - Copper is RESERVED for AI surfaces ("the machine's voice") — the AI
 *    advisor panel is the only copper in the product.
 *  - Tan is hairlines/borders only, never text.
 *  - Light-mode warning/error hues fail contrast as text on cream, so light
 *    badges are tinted fills with ink text; dark may use coloured text.
 *  - Display type is IBM Plex Sans; body UI runs system-ui deliberately.
 */

:root {
  color-scheme: light;

  /* ground & surfaces */
  --bg: #f5f5f0;             /* cream */
  --bg-raised: #fffdf8;      /* paper */
  --bg-card: #fffdf8;        /* paper */
  --bg-well: #ebe9df;        /* cream dim — recessed */
  --border: #ddd6c4;         /* tan light: hairlines, never text */

  /* ink */
  --text: #22251f;
  --text-dim: #52564c;       /* ink soft */
  --text-faint: #666a5c;     /* ink faint */

  /* teal — the one interactive colour */
  --accent: #1a6b6b;
  --accent-deep: #0f4f4f;    /* hover / pressed */
  --accent-soft: #e6f0f0;    /* tinted fills, selected rows */
  --on-accent: #fffdf8;

  /* copper — AI ONLY */
  --copper: #a8551f;
  --copper-soft: #f6e8dc;

  /* semantic (info deliberately = teal, no fourth hue) */
  --ok: #047857;
  --ok-soft: #d1fae5;
  --warn: #d97706;           /* NEVER as text on cream — fills only */
  --warn-soft: #fffbeb;
  --warn-border: #fde68a;
  --crit: #dc2626;           /* AA-large only on cream */
  --crit-soft: #fef2f2;
  --crit-border: #fecaca;
  --info: #1a6b6b;
  --info-soft: #e6f0f0;
  --offline: #666a5c;
  --offline-soft: #ebe9df;

  /* badge text colours — ink on tinted fills in light (contrast) */
  --badge-ok-text: #065f46;
  --badge-warn-text: #22251f;
  --badge-crit-text: #991b1b;
  --badge-info-text: #0f4f4f;

  /* shape, type, motion */
  --radius: 8px;             /* card */
  --radius-control: 6px;
  --radius-modal: 10px;
  --font-display: "IBM Plex Sans", system-ui, "Segoe UI", sans-serif;
  --font-body: system-ui, "Segoe UI", Roboto, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
  --motion-fast: 150ms;
  --motion-normal: 180ms;
  --motion-ease: cubic-bezier(0.16, 1, 0.3, 1);

  --shadow-card: 0 1px 2px rgba(34, 37, 31, 0.06);
}

/* Dark is a designed variant: ink and cream swap roles, accents lighten to
   hold contrast, ground is #17181a (never pure black). */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #17181a;
  --bg-raised: #1e2024;
  --bg-card: #1e2024;
  --bg-well: #212328;
  --border: #3a3a38;
  --text: #e8e6df;
  --text-dim: #a8a79e;
  --text-faint: #85857c;
  --accent: #4db3ac;
  --accent-deep: #7fd0c9;
  --accent-soft: #16302f;
  --on-accent: #17181a;
  --copper: #e08b52;
  --copper-soft: #33221a;
  --ok: #34d399;
  --ok-soft: #12291f;
  --warn: #fbbf24;
  --warn-soft: #2b2313;
  --warn-border: #5c4a1f;
  --crit: #f87171;
  --crit-soft: #2a1a1a;
  --crit-border: #5c2d2d;
  --info: #4db3ac;
  --info-soft: #16302f;
  --offline: #85857c;
  --offline-soft: #212328;
  --badge-ok-text: #34d399;
  --badge-warn-text: #fbbf24;
  --badge-crit-text: #f87171;
  --badge-info-text: #7fd0c9;
  --shadow-card: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #17181a;
    --bg-raised: #1e2024;
    --bg-card: #1e2024;
    --bg-well: #212328;
    --border: #3a3a38;
    --text: #e8e6df;
    --text-dim: #a8a79e;
    --text-faint: #85857c;
    --accent: #4db3ac;
    --accent-deep: #7fd0c9;
    --accent-soft: #16302f;
    --on-accent: #17181a;
    --copper: #e08b52;
    --copper-soft: #33221a;
    --ok: #34d399;
    --ok-soft: #12291f;
    --warn: #fbbf24;
    --warn-soft: #2b2313;
    --warn-border: #5c4a1f;
    --crit: #f87171;
    --crit-soft: #2a1a1a;
    --crit-border: #5c2d2d;
    --info: #4db3ac;
    --info-soft: #16302f;
    --offline: #85857c;
    --offline-soft: #212328;
    --badge-ok-text: #34d399;
    --badge-warn-text: #fbbf24;
    --badge-crit-text: #f87171;
    --badge-info-text: #7fd0c9;
    --shadow-card: none;
  }
}

* { box-sizing: border-box; margin: 0; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 15px;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }
h1, h2, h3, .brand { font-family: var(--font-display); }

button {
  font: inherit; cursor: pointer; border: 1px solid var(--border);
  background: var(--bg-card); color: var(--text);
  padding: 7px 14px; border-radius: var(--radius-control);
  transition: background var(--motion-fast) var(--motion-ease),
              border-color var(--motion-fast) var(--motion-ease),
              color var(--motion-fast) var(--motion-ease);
}
button:hover { border-color: var(--accent); color: var(--accent); }
button.primary {
  background: var(--accent); border-color: var(--accent);
  color: var(--on-accent); font-weight: 600;
}
button.primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); color: var(--on-accent); }
button:disabled { opacity: .55; cursor: not-allowed; }

input, select, textarea {
  font: inherit; background: var(--bg-raised); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-control);
  padding: 8px 10px; width: 100%;
  transition: border-color var(--motion-fast) var(--motion-ease);
}
input::placeholder, textarea::placeholder { color: var(--text-faint); }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); }

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

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

/* ---- shell ---- */
/* The bar wraps rather than pushing the page sideways: below ~930px the
   identity + buttons drop onto a second row instead of making the whole
   document scroll horizontally (a tablet then scrolls only the content, as
   the tables and filter bars already do). A wide bar is untouched — one row,
   still exactly 56px tall, since min-height covers the shorter content. */
.topbar {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 8px 24px;
  padding: 8px 24px; min-height: 56px;
  background: var(--bg-raised); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 10;
}
.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 16px; }
.brand img { height: 26px; display: block; }
/* img.logo-* (class+type) so these outrank `.brand img` above — a bare class
   here silently loses the specificity contest and both logos render. */
img.logo-light { display: block; }
img.logo-dark { display: none; }
:root[data-theme="dark"] img.logo-light { display: none; }
:root[data-theme="dark"] img.logo-dark { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) img.logo-light { display: none; }
  :root:not([data-theme="light"]) img.logo-dark { display: block; }
}
.brand small { color: var(--text-dim); font-weight: 500; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }
.topbar nav { display: flex; flex-wrap: wrap; gap: 4px; }
.topbar nav a {
  padding: 6px 12px; border-radius: var(--radius-control); color: var(--text-dim); font-weight: 500;
  transition: background var(--motion-fast) var(--motion-ease), color var(--motion-fast) var(--motion-ease);
}
.topbar nav a.active, .topbar nav a:hover { color: var(--accent); background: var(--accent-soft); }
.topbar .spacer { flex: 1; }
/* `margin-left: auto` rather than `justify-content` on the bar: an auto margin
   takes the free space on whichever row the chip lands on, so it right-aligns
   the wrapped identity/buttons row while leaving the brand and nav flush left
   on the row above. On a one-row bar `.spacer` has already grown into all of
   that free space, so the auto margin resolves to zero and the tail sits
   exactly where it always did — a wide header is pixel-for-pixel unchanged.
   A long name + org ellipsizes rather than growing past a phone's viewport. */
.userchip {
  color: var(--text-dim); font-size: 13px;
  margin-left: auto; max-width: 100%; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.userchip b { color: var(--text); }
.rolepill {
  font-size: 11px; padding: 2px 8px; border-radius: 9999px; margin-left: 6px;
  background: var(--accent-soft); color: var(--badge-info-text); font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em;
}
.themebtn { padding: 6px 10px; font-size: 14px; line-height: 1; }
main { max-width: 1280px; margin: 0 auto; padding: 24px; }

/* ---- login ---- */
.login-wrap { min-height: 100vh; display: grid; place-items: center; }
.login-card {
  width: 400px; max-width: calc(100vw - 32px);
  background: var(--bg-card); border: 1px solid var(--border);
  border-radius: var(--radius-modal); padding: 36px; box-shadow: var(--shadow-card);
}
.login-card h1 { font-size: 20px; margin-bottom: 4px; }
.login-card p.sub { color: var(--text-dim); margin-bottom: 24px; }
.login-card label { display: block; color: var(--text-dim); font-size: 12px; margin: 14px 0 6px; }
.login-card button { width: 100%; margin-top: 22px; padding: 10px; }
.login-card img.lockup { height: 34px; margin-bottom: 18px; display: block; }
.login-error { color: var(--crit); font-weight: 500; font-size: 13px; margin-top: 12px; min-height: 18px; }

/* ---- fleet ---- */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 22px; }
.kpi {
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 18px; box-shadow: var(--shadow-card);
}
.kpi .num { font-size: 26px; font-weight: 700; font-family: var(--mono); }
.kpi .lbl { color: var(--text-dim); font-size: 12px; margin-top: 2px; }
.kpi.crit .num { color: var(--crit); }
.kpi.warn .num { color: var(--warn); }
.kpi.ok .num { color: var(--ok); }

.fleet-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 14px; }
.inst-card {
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px; display: block; box-shadow: var(--shadow-card);
  transition: border-color var(--motion-fast) var(--motion-ease);
  border-left: 3px solid var(--offline);
}
.inst-card:hover { border-color: var(--accent); border-left-color: var(--accent); }
.inst-card.s-ok { border-left-color: var(--ok); }
.inst-card.s-info { border-left-color: var(--info); }
.inst-card.s-warning { border-left-color: var(--warn); }
.inst-card.s-critical { border-left-color: var(--crit); }
.inst-card.s-offline { border-left-color: var(--offline); opacity: .8; }
.inst-card .cust { color: var(--text-dim); font-size: 12px; }
.inst-card .name { font-weight: 600; font-size: 15px; margin: 2px 0 10px; font-family: var(--font-display); }
.inst-card .meta { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

.badge {
  font-size: 11px; font-weight: 600; padding: 3px 9px; border-radius: 9999px;
  text-transform: uppercase; letter-spacing: .04em; border: 1px solid transparent;
}
.badge.ok { background: var(--ok-soft); color: var(--badge-ok-text); }
.badge.info { background: var(--info-soft); color: var(--badge-info-text); }
.badge.warning { background: var(--warn-soft); color: var(--badge-warn-text); border-color: var(--warn-border); }
.badge.critical { background: var(--crit-soft); color: var(--badge-crit-text); border-color: var(--crit-border); }
.badge.offline { background: var(--offline-soft); color: var(--text-dim); }

.ver { font-family: var(--mono); font-size: 12px; color: var(--text-dim); }
.alert-dots { display: flex; gap: 6px; font-size: 12px; color: var(--text-dim); }
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 3px; }
.dot.c { background: var(--crit); } .dot.w { background: var(--warn); } .dot.i { background: var(--info); }

/* ---- tiers ---- */
.tier {
  font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 9999px;
  text-transform: uppercase; letter-spacing: .06em; vertical-align: middle;
  border: 1px solid var(--border); background: var(--bg-well); color: var(--text-dim);
}
.tier.gold { background: var(--warn-soft); color: var(--badge-warn-text); border-color: var(--warn-border); }
.tier.platinum { background: var(--accent-soft); color: var(--badge-info-text); border-color: var(--accent); }

/* ---- instance detail ---- */
.detail-head { display: flex; align-items: center; gap: 16px; margin-bottom: 18px; flex-wrap: wrap; }
.detail-head h1 { font-size: 20px; }
.detail-head .crumb { color: var(--text-dim); }
.detail-head .crumb:hover { color: var(--accent); }
.refresh-note { color: var(--text-faint); font-size: 12px; margin-left: auto; }
.alerts { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; }
.alert-row {
  display: flex; gap: 12px; align-items: baseline;
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 10px 14px;
}
.alert-row .t { font-weight: 600; }
.alert-row .d { color: var(--text-dim); font-size: 13px; }
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin-bottom: 16px; flex-wrap: wrap; }
.tabs button {
  background: none; border: none; border-bottom: 2px solid transparent; border-radius: 0;
  color: var(--text-dim); padding: 9px 14px; font-weight: 500;
}
.tabs button:hover { color: var(--accent); }
.tabs button.active { color: var(--accent); border-bottom-color: var(--accent); }

table { width: 100%; border-collapse: collapse; background: var(--bg-card); border-radius: var(--radius); overflow: hidden; }
th, td { text-align: left; padding: 9px 14px; border-bottom: 1px solid var(--border); }
th { color: var(--text-dim); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; }
tr:last-child td { border-bottom: none; }
td.mono, .mono { font-family: var(--mono); font-size: 12.5px; }

.state-pill { font-size: 12px; font-weight: 600; }
.state-pill.good { color: var(--ok); }
.state-pill.bad { color: var(--crit); }
.state-pill.mid { color: var(--badge-warn-text); background: var(--warn-soft); border: 1px solid var(--warn-border); border-radius: 4px; padding: 1px 6px; }

.hostbars { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin-bottom: 18px; }
.hostbar { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; box-shadow: var(--shadow-card); }
.statv { font-size: 26px; font-weight: 600; line-height: 1.1; margin-bottom: 4px; font-variant-numeric: tabular-nums; }
.hostbar .lbl { color: var(--text-dim); font-size: 12px; display: flex; justify-content: space-between; margin-bottom: 8px; }
.bar { height: 6px; border-radius: 3px; background: var(--bg-well); overflow: hidden; }
.bar > div { height: 100%; background: var(--ok); border-radius: 3px; transition: width var(--motion-normal) var(--motion-ease); }
.bar > div.warn { background: var(--warn); }
.bar > div.crit { background: var(--crit); }

/* Server log: a fixed-height, scrolling well so the page keeps its shape
   whether the tail is 3 lines or 2000, and long lines scroll rather than
   wrapping into an unreadable block. */
.logview {
  max-height: 460px; overflow: auto;
  background: var(--bg-well); border: 1px solid var(--border);
  border-radius: var(--radius-control);
  /* Scrolling is not enough on its own: the panel around it is a grid, and a
     grid item is at least as wide as its widest content — here the longest
     unwrapped line. One long request URL in the log pushed the whole admin
     page thousands of pixels wide. Containment takes the lines out of that
     sum, so they scroll inside the box as intended. */
  contain: inline-size;
}
.logview .log-line { white-space: pre; padding: 4px 12px; }
.logview .log-line:nth-child(odd) { background: color-mix(in srgb, var(--bg-card) 45%, transparent); }
.logview .lv-INFO { color: var(--text-faint); font-weight: 600; }
.logview .lv-DEBUG { color: var(--text-faint); font-weight: 600; }
.logview .lv-CRITICAL { color: var(--crit); font-weight: 700; }

.log-line { padding: 8px 14px; border-bottom: 1px solid var(--border); font-family: var(--mono); font-size: 12.5px; }
.log-line .lv-ERROR { color: var(--crit); font-weight: 600; }
.log-line .lv-WARNING { color: var(--badge-warn-text); font-weight: 600; }
.log-line .src { color: var(--text-faint); }
.panel { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-card); }
.empty { color: var(--text-dim); padding: 28px; text-align: center; }

/* ---- mesh membership rows (Admin > Mesh) ----
   Two lines per home, not three table columns: .admin-grid is 1fr 1fr, so this
   panel is half the page, and at that width a name/status/address table wraps
   the name onto three lines. Name and status share the first line because that
   is the pair anyone scans; the address is detail and sits under it. */
.lk-home { padding: 7px 0; border-bottom: 1px solid var(--border); }
.lk-home:last-child { border-bottom: 0; }
.lk-home-top { display: flex; gap: 8px; align-items: center; justify-content: space-between; }
.lk-home-name { font-size: 13px; min-width: 0; }
.lk-home-badges { display: flex; gap: 5px; flex-shrink: 0; }
.lk-home-addr { font-family: var(--mono); font-size: 11.5px; color: var(--text-dim); margin-top: 2px; }
.lk-home-addr.prose { font-family: var(--font-body); font-size: 12px; }

/* ---- AI surfaces: the ONLY copper in the product ---- */
.ai-panel { border-left: 3px solid var(--copper); }
.ai-panel .section-title, .ai-tag { color: var(--copper); }
.ai-tag {
  font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 9999px;
  background: var(--copper-soft); text-transform: uppercase; letter-spacing: .06em;
}
.ai-panel button.primary { background: var(--copper); border-color: var(--copper); color: var(--bg-raised); }
.ai-panel button.primary:hover { filter: brightness(0.92); }

/* ---- Live activity: which phones ----
   A checkbox list rather than a dropdown: the answer is often more than one
   phone, and a multi-select that hides the unchosen makes "who else gets
   this?" a question you have to open a menu to answer. The action name stays
   under each label because that string is what is written into the
   automation, and it is what an installer greps for later. */
.la-targets { border-top: 1px solid var(--border); margin-top: 10px; padding-top: 10px; }
.la-targets-head {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--text-faint); margin-bottom: 8px;
}
.la-target {
  display: flex; align-items: flex-start; gap: 9px; padding: 6px 8px;
  border-radius: 7px; cursor: pointer; font-size: 13px;
}
.la-target:hover { background: var(--bg-well); }
/* The global `input` rule sets width:100% and a text-field's padding and
   border. On a checkbox inside a flex row that stretches the box across the
   whole row and pushes the label to the far right — which is exactly what it
   did here. .target-picker carries the same override for the same reason. */
.la-target input[type="checkbox"] {
  width: auto; flex: none; margin: 2px 0 0;
  padding: 0; border: 0; background: none; accent-color: var(--accent);
}
.la-target > span { min-width: 0; }
.la-target-id { display: block; font-size: 11px; color: var(--text-faint); }
.la-target-hint { margin-top: 6px; }

/* ---- Ask transcript ----
   A conversation, not a log: each turn is attributed, and the two speakers
   are told apart by ground rather than by colour alone, so the distinction
   survives a copper-blind reader and a printed screenshot. The thread scrolls
   inside its own box because the page around it must not grow without bound
   as a technician keeps asking. */
/* Why a suggestion cannot be created. Listed rather than summarised: each
   line is a separate thing to fix, and a Live Activity that fails does so
   silently, so the reason has to be readable before anyone clicks. */
.ai-problems {
  margin: 0 0 8px; padding: 8px 12px 8px 28px; border-radius: 8px;
  background: var(--warn-soft); border: 1px solid var(--warn-border);
  font-size: 12.5px; line-height: 1.5;
}
.ai-problems li { margin: 2px 0; }
/* A warning is not a problem. It is styled apart from .ai-problems on
   purpose: one says "this cannot be created", the other says "this works,
   and here is what to watch". Blocking on the second is what stopped a
   perfectly good 3D-printer activity from being built. */
.ai-warnings {
  margin: 0 0 8px; padding: 8px 12px 8px 28px; border-radius: 8px;
  background: var(--bg-well); border: 1px solid var(--border);
  font-size: 12.5px; line-height: 1.5; color: var(--text-dim);
}
.ai-warnings li { margin: 2px 0; }
.ai-warnings-head {
  list-style: none; margin-left: -16px; font-weight: 600;
  color: var(--text); margin-bottom: 4px;
}

.ask-thread { max-height: 460px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; }
.ask-thread:empty { display: none; }
.ask-turn { padding: 10px 12px; border-radius: 8px; font-size: 13px; line-height: 1.55; }
.ask-turn div { white-space: pre-wrap; overflow-wrap: anywhere; }
.ask-who {
  display: block; font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; margin-bottom: 4px; color: var(--text-faint);
}
.ask-you { background: var(--bg-well); }
.ask-ai { background: var(--copper-soft); }
.ask-ai .ask-who { color: var(--copper); }
.ai-panel textarea {
  flex: 1; font: inherit; resize: vertical; min-height: 42px;
}

/* ---- filter chips ----
   A chip is a control, so it is a <button> and carries a real pressed state:
   colour alone would leave the active filter invisible to anyone who cannot
   see the teal. The count sits in its own well so the label stays scannable. */
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 10px; min-height: 32px;
  background: var(--bg-card); border: 1px solid var(--border);
  border-radius: 9999px; color: var(--text-dim);
  font-family: var(--mono); font-size: 12.5px; line-height: 1;
  cursor: pointer; transition: border-color var(--motion-fast) var(--motion-ease),
                              background var(--motion-fast) var(--motion-ease);
}
.chip:hover { border-color: var(--accent); color: var(--text); }
.chip .n {
  font-family: var(--font-body); font-size: 11px; font-weight: 600;
  /* text-dim, not text-faint: faint on the recessed well measured 4.22:1 in
     dark mode, under the 4.5 minimum for text this size. */
  background: var(--bg-well); color: var(--text-dim);
  padding: 2px 6px; border-radius: 9999px;
}
.chip[aria-pressed="true"] {
  background: var(--accent-soft); border-color: var(--accent);
  color: var(--badge-info-text); font-weight: 600;
}
.chip[aria-pressed="true"] .n { background: var(--accent); color: var(--on-accent); }
.chip[aria-pressed="true"]::before { content: "✓"; font-size: 11px; }

/* ---- filter bar ---- */
.filterbar {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  margin-bottom: 12px;
}
.filterbar input[type="search"] { max-width: 280px; }
.filterbar select { max-width: 190px; }
.filterbar .count { color: var(--text-dim); font-size: 12.5px; margin-left: auto; }
.filterbar .count b { color: var(--text); font-variant-numeric: tabular-nums; }

/* Wide tables scroll inside their own panel rather than pushing the page
   sideways, and the header stays put while you scan. */
.table-scroll { overflow-x: auto; }
.table-scroll thead th { position: sticky; top: 0; background: var(--bg-card); z-index: 1; }

/* ---- fleet-wide action panels (themes, branding) ---- */
.target-picker {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 6px; margin: 10px 0 12px;
  max-height: 210px; overflow-y: auto;
  padding: 10px; background: var(--bg-well); border-radius: var(--radius-control);
}
.target-picker label {
  display: flex; gap: 8px; align-items: center;
  padding: 5px 6px; border-radius: var(--radius-control);
  font-size: 13px; min-height: 30px; cursor: pointer;
}
.target-picker label:hover { background: var(--bg-card); }
.target-picker input { width: auto; flex: none; }
.target-picker label.off { color: var(--text-faint); cursor: not-allowed; }
.picker-head {
  display: flex; align-items: center; gap: 10px;
  font-size: 12px; color: var(--text-dim);
}
.picker-head button { font-size: 11px; padding: 3px 8px; }

.catalog-card {
  background: var(--bg-card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 14px 16px;
  display: flex; flex-direction: column; gap: 8px;
  box-shadow: var(--shadow-card);
}
.catalog-card .name { font-weight: 600; font-size: 14.5px; font-family: var(--font-display); }
.catalog-card .desc { color: var(--text-dim); font-size: 13px; flex: 1; line-height: 1.5; }
.catalog-card .foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.catalog-card .known-issue { font-size: 12.5px; line-height: 1.45; color: var(--text-dim); border-left: 3px solid var(--warn); padding: 4px 0 4px 10px; }

/* ---- admin ----
   Tables need room: a six-column customer list inside a half-width column
   wraps every cell. Table-heavy panels run full width; the short settings
   cards share a denser grid below them. */
.admin-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
.admin-grid.wide { grid-template-columns: 1fr; }
.settings-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
  gap: 20px; align-items: start;
}
@media (max-width: 900px) { .admin-grid { grid-template-columns: 1fr; } }
.panel h2 { font-size: 15px; padding: 14px 16px; border-bottom: 1px solid var(--border); }
.panel .body { padding: 14px 16px; }
.formrow { display: flex; gap: 10px; margin-bottom: 10px; }
.token { font-family: var(--mono); font-size: 11.5px; word-break: break-all; color: var(--text-dim); }
.copybtn { font-size: 11px; padding: 3px 8px; }
h1.page { font-size: 20px; margin-bottom: 18px; }
.section-title { font-size: 13px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .06em; margin: 22px 0 10px; font-family: var(--font-display); }


/* ---------- paging, partial lists, and the maintenance form ---------- */

.pager { display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 10px; padding: 12px 4px 4px; }
.pager-range { font-size: 13px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.pager-range b { color: var(--text); }
.pager-btns { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* 44px is the touch-target floor, and a pager is exactly the control people
   hit repeatedly on a tablet in someone's hallway. */
.pager-btns button { min-height: 44px; min-width: 44px; padding: 0 12px; }
.pager-btns button[disabled] { opacity: 0.4; cursor: not-allowed; }
.pager-page { font-size: 13px; color: var(--text-dim); padding: 0 6px;
  font-variant-numeric: tabular-nums; white-space: nowrap; }

.partial { display: flex; align-items: flex-start; gap: 10px; margin: 0 0 12px;
  padding: 10px 14px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-well); font-size: 13px; line-height: 1.5; color: var(--text-dim); }
.partial b { color: var(--text); font-variant-numeric: tabular-nums; }
.partial-icon { color: var(--accent); font-size: 15px; line-height: 1.4; }
.partial.warn { border-color: var(--warn-border); background: var(--warn-soft); }
.partial.warn .partial-icon { color: var(--warn); }

/* The table stays put while a page loads rather than collapsing to nothing —
   a list that empties and refills reads as data having been lost. */
.table-scroll.is-busy { opacity: 0.55; transition: opacity 120ms ease; }
.loading-dot::after { content: ""; animation: dots 1.2s steps(4, end) infinite; }
@keyframes dots { 0% { content: ""; } 25% { content: "."; }
                  50% { content: ".."; } 75% { content: "..."; } }
@media (prefers-reduced-motion: reduce) {
  .loading-dot::after { animation: none; content: "…"; }
  .table-scroll.is-busy { transition: none; }
}

.kpi-note { display: block; font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--text-dim); opacity: 0.75; margin-top: 2px; }

.stackform { display: flex; flex-direction: column; gap: 8px; }
.stackform label { font-size: 13px; font-weight: 600; }
.stackform textarea { width: 100%; font: inherit; padding: 9px 11px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg-raised);
  color: var(--text); resize: vertical; min-height: 62px; }
.stackform textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.stackform textarea[aria-invalid="true"] { border-color: var(--crit); }
.fieldhint { font-size: 12px; color: var(--text-dim); line-height: 1.5; }
.fieldhint #mwCount { font-variant-numeric: tabular-nums; }
.formmsg { font-size: 13px; min-height: 18px; color: var(--text-dim); }
.formmsg.ok { color: var(--ok); }
.formmsg.bad { color: var(--crit); }

/* Home Assistant updates page (renderUpdates) and the home's update card. */
.hau .formrow { flex-wrap: wrap; align-items: center; }
.hau .formrow select, .hau .formrow input { width: auto; }
.hau .formrow input[type=number] { width: 80px; }
.hau p.lead { color: var(--text-dim); font-size: 13px; margin: 0 0 10px; }
.hau h2 + .body > .formrow:last-child { margin-bottom: 0; }
/* The hardware page: the model the home list is filtered to. */
tr.sel td { background: var(--accent-soft); }

/* confirm / prompt / alert drawn by the page (uiDialog in app.js) */
.ui-dialog { border: 1px solid var(--border); border-radius: 12px; background: var(--bg-card);
  color: var(--text); padding: 18px 20px; max-width: min(520px, calc(100vw - 32px)); }
.ui-dialog::backdrop { background: rgba(0, 0, 0, 0.45); }
.ui-dialog-msg { margin: 0 0 10px; white-space: pre-wrap; line-height: 1.5; }


/* ---------- the floor plan, read-only (static/floorplan.js) ----------
   Colours are theme tokens only, so light and dark both hold. The device
   marker's look is its mapping state, deliberately neutral: an outline says
   how far matching got, not whether anything is wrong with the device. The
   .fp-status group is Phase 1's live layer (below), shown once a marker has
   data-live. */
.fp-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.fp-tabs button { padding: 5px 12px; border-radius: 999px; }
.fp-tabs button.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-deep); }
.fp-stage { position: relative; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-raised); overflow: hidden; }
.fp-svg { display: block; width: 100%; height: min(62vh, 560px); touch-action: none;
  cursor: grab; user-select: none; }
.fp-svg:active { cursor: grabbing; }
.fp-room { fill: var(--bg-well); stroke: var(--border); stroke-width: 0.03; }
.fp-wall { stroke: var(--text); stroke-linecap: square; }
.fp-window { stroke: var(--accent); stroke-linecap: butt; }
.fp-door-leaf { stroke: var(--text-dim); stroke-width: 0.04; }
.fp-door-arc { fill: none; stroke: var(--text-faint); stroke-width: 0.025; stroke-dasharray: 0.08 0.06; }
/* Labels sit above the markers, haloed in the room colour, so a device at a
   room's centre never hides its name; they take no clicks, so the marker
   underneath still does. */
.fp-room-name { fill: var(--text); text-anchor: middle; dominant-baseline: middle;
  font-family: var(--font-display); font-weight: 600; pointer-events: none;
  paint-order: stroke; stroke: var(--bg-well); stroke-width: 0.09; stroke-linejoin: round; }
.fp-room-sub { fill: var(--text-dim); text-anchor: middle; dominant-baseline: middle;
  font-family: var(--font-body); pointer-events: none;
  paint-order: stroke; stroke: var(--bg-well); stroke-width: 0.06; stroke-linejoin: round; }
.fp-device { cursor: pointer; }
.fp-device:focus { outline: none; }
.fp-dot { fill: var(--bg-card); stroke: var(--text-dim); stroke-width: 0.05; }
.fp-glyph { fill: none; stroke: var(--text); stroke-width: 0.16; stroke-linecap: round;
  stroke-linejoin: round; pointer-events: none; }
.fp-status { display: none; }
.fp-device[data-live] .fp-status { display: inline; }
[data-state="matched"] .fp-dot { fill: var(--accent-soft); stroke: var(--accent); }
[data-state="partial"] .fp-dot { fill: var(--accent-soft); stroke: var(--accent); stroke-dasharray: 0.12 0.08; }
[data-state="unmatched"] .fp-dot { stroke-dasharray: 0.06 0.08; }
[data-state="wrong_room"] .fp-dot { fill: var(--warn-soft); stroke: var(--warn); stroke-width: 0.08; }
.fp-device.is-selected .fp-dot, .fp-device:focus-visible .fp-dot { stroke-width: 0.12; stroke: var(--accent-deep); }
.fp-zoom { position: absolute; top: 8px; inset-inline-end: 8px; display: flex; gap: 4px; }
.fp-zoom button { min-width: 34px; padding: 4px 8px; background: var(--bg-card); }
.fp-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 10px 0 4px; font-size: 12px;
  color: var(--text-dim); }
.fp-key { display: inline-flex; align-items: center; gap: 6px; }
.fp-key svg { width: 14px; height: 14px; }
.fp-key .fp-dot { stroke-width: 0.25; }
.fp-key[data-state="partial"] .fp-dot { stroke-dasharray: 0.5 0.35; }
.fp-key[data-state="unmatched"] .fp-dot { stroke-dasharray: 0.3 0.3; }
.fp-detail { font-size: 13px; min-height: 20px; margin-top: 8px; }

/* ---------- live status on the plan (integration Phase 1) ----------
   Each state is told three ways, so none depends on colour alone: the
   colour, the ring's dash pattern, and the badge's shape and glyph
   (online ✓ circle, offline ✕ circle with a heavy ring, missing ? diamond
   with a dashed ring, attention ! triangle, unknown – with a dotted ring).
   Only theme tokens, so light and dark both hold. */
.fp-ring { fill: none; stroke: transparent; stroke-width: 0.06; }
.fp-badge { stroke: var(--bg-card); stroke-width: 0.04; }
.fp-badge-glyph { fill: var(--bg-card); text-anchor: middle; dominant-baseline: central;
  font-family: var(--font-body); font-weight: 700; pointer-events: none; }
.fp-device[data-live="online"] .fp-ring { stroke: var(--ok); }
.fp-device[data-live="online"] .fp-badge { fill: var(--ok); }
.fp-device[data-live="offline"] .fp-ring { stroke: var(--crit); stroke-width: 0.14; }
.fp-device[data-live="offline"] .fp-badge { fill: var(--crit); }
.fp-device[data-live="attention"] .fp-ring { stroke: var(--warn); stroke-width: 0.1; }
.fp-device[data-live="attention"] .fp-badge { fill: var(--warn); }
.fp-device[data-live="attention"] .fp-badge-glyph { fill: var(--text); }
.fp-device[data-live="missing"] .fp-ring { stroke: var(--text-dim); stroke-dasharray: 0.16 0.1; }
.fp-device[data-live="missing"] .fp-badge { fill: var(--text-dim); }
.fp-device[data-live="missing"] .fp-dot { fill: none; stroke-dasharray: 0.06 0.08; }
.fp-device[data-live="unknown"] .fp-ring { stroke: var(--offline); stroke-dasharray: 0.03 0.08;
  stroke-linecap: round; }
.fp-device[data-live="unknown"] .fp-badge { fill: var(--offline); }
.fp-device.is-filtered { opacity: 0.16; pointer-events: none; }
.fp-room[data-health="problems"] { fill: var(--crit-soft); }
.fp-room[data-health="attention"] { fill: var(--warn-soft); }
.fp-note { color: var(--text-dim); font-size: 12px; }
.fp-room-health[data-health="attention"] { fill: var(--badge-warn-text); stroke: var(--warn-soft); }
.fp-room-health { fill: var(--badge-crit-text); text-anchor: middle; dominant-baseline: middle;
  font-family: var(--font-body); font-weight: 600; pointer-events: none;
  paint-order: stroke; stroke: var(--crit-soft); stroke-width: 0.06; stroke-linejoin: round; }
.fp-tab-count { display: inline-block; min-width: 18px; margin-inline-start: 4px; padding: 0 5px;
  border-radius: 999px; background: var(--crit-soft); color: var(--badge-crit-text);
  border: 1px solid var(--crit-border); font-size: 11px; font-weight: 600; }
.fp-filter { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.fp-filter button { padding: 4px 12px; border-radius: 999px; font-size: 12px; }
.fp-filter button.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-deep); }
.fp-banner { margin-bottom: 10px; }
.fp-layout.has-side { display: grid; grid-template-columns: minmax(0, 1fr) minmax(220px, 300px); gap: 12px; }
@media (max-width: 860px) { .fp-layout.has-side { grid-template-columns: minmax(0, 1fr); } }
.fp-side { font-size: 13px; max-height: min(62vh, 560px); overflow: auto; border: 1px solid var(--border);
  border-radius: var(--radius); padding: 10px 12px; background: var(--bg-card); }
.fp-side .section-title { margin: 14px 0 6px; }
.fp-side-floor { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding-bottom: 8px;
  border-bottom: 1px solid var(--border); }
.fp-side-rooms, .fp-extra, .fp-units { list-style: none; margin: 0; padding: 0; }
.fp-side-room { padding: 7px 0; border-bottom: 1px solid var(--border); }
.fp-side-room[data-health="problems"] .fp-side-head > span:first-child,
.fp-side-room[data-health="attention"] .fp-side-head > span:first-child { font-weight: 600; }
.fp-side-head { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.fp-extra li { padding: 2px 0 2px 4px; }
/* A pill carries the glyph as well as the colour, for the same reason. */
.fp-pill { display: inline-flex; align-items: center; gap: 3px; padding: 0 6px; border-radius: 999px;
  font-size: 11px; font-weight: 600; line-height: 18px; border: 1px solid transparent; white-space: nowrap; }
.fp-pill[data-live="online"] { background: var(--ok-soft); color: var(--badge-ok-text); }
.fp-pill[data-live="offline"] { background: var(--crit-soft); color: var(--badge-crit-text); border-color: var(--crit-border); }
.fp-pill[data-live="attention"] { background: var(--warn-soft); color: var(--badge-warn-text); border-color: var(--warn-border); }
.fp-pill[data-live="missing"] { background: var(--bg-well); color: var(--text); border-style: dashed; border-color: var(--text-faint); }
.fp-pill[data-live="unknown"] { background: var(--offline-soft); color: var(--text-dim); border-style: dotted; border-color: var(--offline); }
/* The household's own device on the plan (dartec-ops#25). */
.fp-pill.fp-owned { background: var(--info-soft); color: var(--badge-info-text); }
.fp-units { margin-top: 6px; }
.fp-unit { padding: 6px 0; border-top: 1px solid var(--border); }
.fp-unit-links { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 2px; font-size: 12px; }
.fp-unit-links a { color: var(--accent); text-decoration: underline; }
.linkish { background: none; border: none; padding: 0; color: var(--accent); text-decoration: underline;
  cursor: pointer; font: inherit; }
