/* Stylesheet. Named and worded so that it says nothing about what it styles:
 * it is fetched by the sign-in page, which may be a public door that must not
 * name what is behind it (portal.yaml `login_page:`).
 *
 * The colour tokens are copied from the other application's src/index.css on
 * purpose, so the two read as one product. Copied rather than shared because
 * the two ship separately - this one must not need a node build, and the
 * other must not need this one to exist.
 *
 * The palette discipline is load-bearing here too:
 *
 *   quality-*  the freshness dot. Staleness is a DATA-TRUST problem, not an
 *              alarm, and must not be mistakable for one across a room. This
 *              is the amber that means "I am not sure this number is current".
 *   alarm-*    the alarm badge, and nothing else. Red is reserved for an
 *              unacknowledged critical - an acknowledged one is amber,
 *              because red means "nobody has looked at this yet".
 */

:root {
  /* Breakpoints - docs/ui-layout-contract.md, the same two as the box SPA's
     index.css: phone < 768 px, tablet 768-1099 px, desktop >= 1100 px. Any
     @media query here uses exactly these numbers (tests/test_breakpoints.py).
     The portal's own phone layout is a later brief. */
  --bp-phone: 768px;
  --bp-tablet: 1100px;
}

/* --- the tokens: a COPY of frontend/src/index.css's two token blocks, value
   for value (tests/test_css_tokens.py fails on any drift). The portal never
   sets data-theme itself; the dark block is here so the copy is whole and a
   later portal dark mode has nothing to invent. A customer's accent is
   written over --color-accent* by shell.html after login (portal/branding.py). */
:root {
  /* --- THEMEABLE ----------------------------------------------------------- */
  --color-bg: #f5f6f8;
  --color-surface: #ffffff;
  --color-surface-hover: #eef0f3;
  --color-border: #d9dde3;
  --color-text: #1c2128;
  --color-text-muted: #5b6472;
  --color-accent: #2563eb;
  --color-accent-hover: #1d4ed8;
  /* The nav. Its own pair so a plant can have a dark sidebar on a light page;
     inactive links are the text mixed 75 % into the sidebar (AppShell.css),
     and that mix is what the 4.5:1 check measures. */
  --color-sidebar: #ffffff;
  --color-sidebar-text: #1c2128;

  /* --- DERIVED ------------------------------------------------------------- */
  /* White or near-black, whichever reads on the accent (scada/branding.py
     on_accent; applyTheme writes it with the accent). */
  --color-on-accent: #ffffff;
  --color-accent-bg: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface));
  --color-accent-border: color-mix(in srgb, var(--color-accent) 35%, var(--color-surface));

  /* --- SEMANTIC (not themeable, not alarms) -------------------------------- */
  --color-success: #15803d;
  --color-success-bg: #e7f6ec;
  --color-success-border: #a7dcb8;
  /* A caution about OUR state - pending, refused by the gate, a config file
     problem - never a plant condition (that is --alarm-warning). */
  --color-caution: #b45309;
  --color-caution-text: #8a6d1a;
  --color-caution-bg: #fef6e0;
  --color-caution-border: #f0d78c;
  /* The tint behind an error banner or a destructive hover. */
  --color-danger-bg: #fde8e8;
  --color-danger-border: #f5b5b5;
  /* Provenance / bookkeeping tags (confidence badges, poll checkboxes,
     golden snapshots): deliberately hues no alarm or quality token uses. */
  --tag-violet: #6d28d9;
  --tag-violet-bg: #ede9fe;
  --tag-teal: #0f766e;
  --tag-teal-bg: #ccfbf1;
  --color-star: #e0a41c;
  /* Overlays and shadows - black at any scheme. */
  --color-backdrop: rgba(15, 18, 23, 0.45);
  --color-shadow: rgba(0, 0, 0, 0.18);

  /* --- graph series (pages/graphs/seriesColor.ts reads these) -------------- */
  --series-1: #2563eb;
  --series-2: #d97706;
  --series-3: #059669;
  --series-4: #7c3aed;
  --series-5: #0891b2;
  --series-6: #db2777;
  --series-7: #65a30d;
  --series-8: #dc2626;

  /* --- RESERVED ------------------------------------------------------------ */
  --color-danger: #dc2626;
  --color-danger-hover: #b91c1c;
  --color-on-danger: #ffffff;

  /* --- alarm palette (ISA-18.2 / the audit's UI-UX section) --------------
     RESERVED. These four are the alarm layer's and nothing else's. The rule
     that picks between them lives in api/alarmTone.ts:

       critical  active and UNACKNOWLEDGED, severity critical. Red means
                 "nobody has looked at this yet" - that is why an
                 acknowledged critical is amber, not red.
       warning   severity warning, and ANY acknowledged-but-still-active
                 alarm whatever its severity.
       cleared   cleared but unacknowledged: the condition is gone, the ack
                 is still owed. Gray, never red.
       info      lowest severity.

     --color-danger stays what it always was: destructive AFFORDANCES (delete
     buttons, error banners). It is deliberately a different red from
     --alarm-critical so a Delete button can never be mistaken for an alarm
     across a room. Purely decorative red was removed where it existed - see
     .remove-icon-btn below. */
  --alarm-critical: #b91c1c;
  --alarm-critical-bg: #fee2e2;
  --alarm-critical-border: #f5a3a3;
  --alarm-warning: #a15c07;
  --alarm-warning-bg: #fef3c7;
  --alarm-warning-border: #f0d78c;
  --alarm-info: #1d4ed8;
  --alarm-info-bg: #e0e7ff;
  --alarm-info-border: #b6c2f5;
  --alarm-cleared: #5b6472;
  --alarm-cleared-bg: #eef0f3;

  /* --- data-quality palette (the staleness layer, NOT the alarm layer) ----
     A stale reading and a dead poller are data-trust problems, not plant
     alarms: an amber overview card must not be mistakable across a room for
     an amber alarm bar. So this is a deliberately different amber from
     --alarm-warning, and it is the amber the STALE badge in
     LiveParameterTile.css already uses - these tokens name that colour so
     the status strip and Overview cards inherit it instead of re-typing it.

     --quality-ok is positive confirmation ("this number is current"), used
     only for small dots, never to fill a region: at rest the page stays
     gray, per ISA-101. */
  --quality-ok: #22c55e;
  --quality-warn: #b45309;
  --quality-warn-bg: #fdf3e3;
  --quality-warn-border: #e6cba0;
  --quality-idle: var(--color-text-muted);

  color-scheme: light;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

:root[data-theme="dark"] {
  --color-bg: #0f1216;
  --color-surface: #171b21;
  --color-surface-hover: #222830;
  --color-border: #2f3640;
  --color-text: #e6e9ee;
  --color-text-muted: #9aa3b0;
  --color-accent: #4f8ef7;
  --color-accent-hover: #6ea2f8;
  --color-sidebar: #171b21;
  --color-sidebar-text: #e6e9ee;

  --color-on-accent: #111418;

  --color-success: #4ade80;
  --color-success-bg: #10301c;
  --color-success-border: #1f6b3a;
  --color-caution: #e8915a;
  --color-caution-text: #e8c15a;
  --color-caution-bg: #2e2610;
  --color-caution-border: #6b5618;
  --color-danger-bg: #3a1618;
  --color-danger-border: #7a2a2e;
  --tag-violet: #b79cf5;
  --tag-violet-bg: #2a2045;
  --tag-teal: #5eead4;
  --tag-teal-bg: #0f2f2c;
  --color-star: #f5c542;
  --color-backdrop: rgba(0, 0, 0, 0.6);
  --color-shadow: rgba(0, 0, 0, 0.5);

  --series-1: #60a5fa;
  --series-2: #fbbf24;
  --series-3: #34d399;
  --series-4: #a78bfa;
  --series-5: #22d3ee;
  --series-6: #f472b6;
  --series-7: #a3e635;
  --series-8: #f87171;

  --color-danger: #f05252;
  --color-danger-hover: #f87171;
  --color-on-danger: #111418;

  --alarm-critical: #ff6b6b;
  --alarm-critical-bg: #3d1214;
  --alarm-critical-border: #8f2a2e;
  --alarm-warning: #f5b83d;
  --alarm-warning-bg: #352808;
  --alarm-warning-border: #7a5a12;
  --alarm-info: #93b4fd;
  --alarm-info-bg: #172447;
  --alarm-info-border: #2f4a8a;
  --alarm-cleared: #9aa3b0;
  --alarm-cleared-bg: #222830;

  --quality-ok: #22c55e;
  --quality-warn: #e8915a;
  --quality-warn-bg: #33200f;
  --quality-warn-border: #6b4425;
  --quality-idle: var(--color-text-muted);

  color-scheme: dark;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
}

h1, h2, h3 { margin: 0 0 0.5rem; }

.muted { color: var(--color-text-muted); font-size: 0.85rem; }
code { background: var(--color-surface-hover); padding: 0.1rem 0.3rem; border-radius: 4px; }

button, .button-link {
  font: inherit;
  cursor: pointer;
  border-radius: 6px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  padding: 0.4rem 0.85rem;
  text-decoration: none;
  display: inline-block;
}
button:hover, .button-link:hover { background: var(--color-surface-hover); }

button.primary {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-on-accent);
}
button.primary:hover { background: var(--color-accent-hover); }

label {
  display: block;
  font-weight: 600;
  font-size: 0.85rem;
  margin: 0.75rem 0 0.25rem;
  color: var(--color-text-muted);
}

input[type="email"], input[type="password"] {
  font: inherit;
  width: 100%;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
  color: var(--color-text);
}

.banner-error {
  background: var(--alarm-critical-bg);
  border: 1px solid var(--alarm-critical-border);
  color: var(--alarm-critical);
  border-radius: 6px;
  padding: 0.5rem 0.7rem;
  font-size: 0.9rem;
  margin-bottom: 0.5rem;
}

/* --- login ------------------------------------------------------------- */

.gate {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 1rem;
}

.gate-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 1.5rem;
  width: min(24rem, 100%);
}

.gate-card button { width: 100%; margin-top: 1rem; }
.hint { color: var(--color-text-muted); font-size: 0.78rem; margin: 1rem 0 0; line-height: 1.5; }

/* --- fleet ------------------------------------------------------------- */

.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.7rem 1.2rem;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}
.topbar-left, .topbar-right { display: flex; align-items: center; gap: 0.9rem; }
/* A customer's logo and short name (shell.html, after login only). */
.topbar-brand { display: inline-flex; align-items: center; gap: 0.5rem; }
.topbar-logo { height: 24px; width: auto; max-width: 96px; object-fit: contain; }
.branding-logo-preview { max-height: 48px; max-width: 200px; }
.topbar form { margin: 0; }

.fleet {
  padding: 1.2rem;
  display: grid;
  gap: 0.9rem;
  /* min(22rem, 100%): a 22rem track on a 375 px phone is wider than the
     page, and the grouped (site) layout then scrolled sideways by 8 px
     (walk-defects F2). The track never asks for more than the page. */
  grid-template-columns: repeat(auto-fill, minmax(min(22rem, 100%), 1fr));
  align-items: start;
}

.empty {
  grid-column: 1 / -1;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 1.5rem;
}

.plant-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 0.9rem 1rem;
}

.plant-head { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 0.65rem; }
/* The title keeps a readable width; when the action strip (four buttons since
   saved graphs) cannot fit beside it, the strip wraps under the title instead
   of squeezing it to one word per line. The minimum is a REAL width (not 0),
   so the strip can never shrink the name into a one-word column: the title
   is at least 14rem or the whole card, whichever is less, and the strip takes
   the next line when it does not fit beside that (relay bench 2026-10-18). */
.plant-title { flex: 1 1 14rem; min-width: min(14rem, 100%); }
.plant-title h2 { font-size: 1.05rem; margin: 0; }
.plant-badges { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; flex-shrink: 0; max-width: 100%; }

/* At rest the page stays gray - the dot is the one small spot of positive
 * colour, never a filled region (ISA-101, same as the box's Overview). */
.dot {
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  margin-top: 0.35rem;
  flex-shrink: 0;
  background: var(--quality-idle);
}
.dot-ok { background: var(--quality-ok); }
.dot-warn { background: var(--quality-warn); }
.dot-idle { background: var(--quality-idle); }

.badge {
  font-size: 0.78rem;
  font-weight: 600;
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  white-space: nowrap;
}
.badge-critical {
  background: var(--alarm-critical-bg);
  border-color: var(--alarm-critical-border);
  color: var(--alarm-critical);
}
.badge-warning {
  background: var(--alarm-warning-bg);
  border-color: var(--alarm-warning-border);
  color: var(--alarm-warning);
}
.badge-quiet { background: transparent; }

.plant-body { margin-top: 0.7rem; padding-left: 1.35rem; }

.machines { list-style: none; margin: 0 0 0.6rem; padding: 0; }
.machines li {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.15rem 0;
  border-top: 1px solid var(--color-border);
}
.machines li:first-child { border-top: none; }
.machine-name { font-size: 0.9rem; font-weight: 600; }

.plant-meta { font-size: 0.8rem; color: var(--color-text-muted); display: grid; gap: 0.2rem; }

.quality-warn {
  color: var(--quality-warn);
  background: var(--quality-warn-bg);
  border: 1px solid var(--quality-warn-border);
  border-radius: 6px;
  padding: 0.2rem 0.45rem;
}

/* A relayed plant's link line (HANDOFF-portal-operator-view.md 2c). Up and
   healthy is plain text; a target not answering wears the data-trust warn
   colour (never the alarm colours); down is muted - between technician
   visits that is the normal state. */
.relay-health { color: var(--color-text); }
.relay-health-ok::before { content: ""; display: inline-block; width: 0.5rem; height: 0.5rem;
  border-radius: 50%; background: var(--quality-ok); margin-right: 0.35rem; vertical-align: 0.05rem; }
.relay-health-warn { color: var(--quality-warn); }
.relay-health-quiet { color: var(--color-text-muted); }

/* A cell holding an id or an exception string: wrap anywhere rather than
   push a 375 px page sideways (the hosted page's identity table). */
td.cell-wrap { overflow-wrap: anywhere; word-break: break-word; min-width: 6rem; }

/* The Relay page's in-page takeover confirm (2b) - replaces window.confirm,
   which an embedded browser never shows. */
.relay-confirm {
  border: 1px solid var(--quality-warn-border);
  background: var(--quality-warn-bg);
  border-radius: 6px;
  padding: 0.7rem 0.9rem;
  margin: 0.6rem 0;
}
.relay-confirm:focus { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.relay-confirm h3 { margin: 0 0 0.4rem; font-size: 1rem; }
.relay-confirm p { margin: 0.3rem 0; }
.relay-confirm .controls { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.6rem 0 0; }
/* A history row reached from a "taken over by" link. */
tr:target td { background: var(--color-accent-bg); }

/* --- P1: nav, banners, controls, groups, admin -------------------------- */

.nav { display: flex; gap: 0.75rem; align-items: center; }
.nav a {
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: 0.9rem;
  padding: 0.2rem 0;
  border-bottom: 2px solid transparent;
}
.nav a:hover { color: var(--color-text); }
.nav a.here { color: var(--color-text); border-bottom-color: var(--color-accent); font-weight: 600; }

/* The success banner is deliberately quiet: it confirms, it does not
 * celebrate, and it must never compete with the alarm palette. */
.banner-ok {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--quality-ok);
  border-radius: 6px;
  padding: 0.5rem 0.7rem;
  font-size: 0.9rem;
  margin: 0.7rem 1.2rem 0;
}
.banner-error { margin: 0.7rem 1.2rem 0; }
.gate-card .banner-error, .panel .banner-error { margin: 0 0 0.7rem; }

input[type="text"], input[type="number"], select {
  font: inherit;
  padding: 0.4rem 0.55rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface);
  color: var(--color-text);
}
input[type="checkbox"] { margin-right: 0.4rem; }
button.danger { color: var(--color-danger); border-color: var(--color-danger); }
button.danger:hover { background: var(--alarm-critical-bg); }
button[disabled] { opacity: 0.45; cursor: not-allowed; }
form.inline { display: inline-flex; gap: 0.4rem; align-items: center; margin: 0; }
form.inline label { margin: 0; display: inline-flex; gap: 0.35rem; align-items: center; }

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

.controls {
  padding: 0.8rem 1.2rem 0;
  display: grid;
  gap: 0.5rem;
}
.control-row { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: flex-end; margin: 0; }
.control-label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-text-muted);
  align-self: center;
}
.control-gap { flex: 0 0 1.5rem; }
.control-field { margin: 0; display: grid; gap: 0.2rem; }
.control-note { font-size: 0.82rem; color: var(--color-text-muted); }

.chip {
  font-size: 0.82rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: 0.25rem 0.7rem;
  background: var(--color-surface);
  color: var(--color-text-muted);
  text-decoration: none;
}
.chip:hover { background: var(--color-surface-hover); }
.chip-on { border-color: var(--color-accent); color: var(--color-accent); font-weight: 600; }

/* --- site groups -------------------------------------------------------- */

.site-group {
  grid-column: 1 / -1;
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 0.5rem 0.7rem;
  background: var(--color-surface);
}
.site-group > summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.95rem;
}
.site-group > summary .dot { margin-top: 0; }
.site-name { font-weight: 700; }
.site-plants {
  margin-top: 0.7rem;
  display: grid;
  gap: 0.9rem;
  /* min(22rem, 100%): a 22rem track on a 375 px phone is wider than the
     page, and the grouped (site) layout then scrolled sideways by 8 px
     (walk-defects F2). The track never asks for more than the page. */
  grid-template-columns: repeat(auto-fill, minmax(min(22rem, 100%), 1fr));
  align-items: start;
}

.plant-card[draggable="true"] { cursor: grab; }
.plant-card.dragging { opacity: 0.45; }
.reorder { display: inline-flex; gap: 0.15rem; margin: 0; }
.reorder button { padding: 0.1rem 0.4rem; line-height: 1.1; }

/* --- admin pages -------------------------------------------------------- */

.admin { padding: 1.2rem; display: grid; gap: 1rem; max-width: 70rem; }

.panel {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 1rem 1.1rem;
}
.panel > p:first-of-type { margin-top: 0; }

.stack { display: grid; gap: 0.6rem; }
.field-row { display: flex; flex-wrap: wrap; gap: 0.8rem; align-items: flex-end; }
.field-row label { margin: 0; display: grid; gap: 0.2rem; }
.field-row label.wide { flex: 1 1 22rem; }

.person {
  border-top: 1px solid var(--color-border);
  padding: 0.5rem 0;
}
.person > summary {
  cursor: pointer;
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  align-items: baseline;
}
.person-removed > summary strong { text-decoration: line-through; }
.person-body { padding: 0.6rem 0 0.2rem 1.2rem; display: grid; gap: 0.8rem; }
.person-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; }
.person-actions form { margin: 0; }

.state {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
}
.state-active { border-color: var(--quality-ok); color: var(--color-success); }
.state-invited { border-color: var(--quality-warn-border); color: var(--quality-warn); }
.state-removed { border-color: var(--color-border); }

.plant-access {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 0.6rem 0.8rem;
}
.plant-access legend { font-size: 0.8rem; font-weight: 600; color: var(--color-text-muted); padding: 0 0.3rem; }
.plant-access label { display: block; font-weight: 400; color: var(--color-text); margin: 0.2rem 0; }
.all-plants { border-bottom: 1px solid var(--color-border); padding-bottom: 0.4rem; margin-bottom: 0.4rem; }
.plant-checks { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: 0.1rem; }

.role-row { border-top: 1px solid var(--color-border); padding: 0.7rem 0 0.2rem; display: grid; gap: 0.5rem; }
.role-delete { margin: 0 0 0.5rem; }
.role-count { align-self: center; }
.caps { display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); gap: 0.4rem; }
.cap {
  margin: 0;
  font-weight: 400;
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 0.4rem 0.5rem;
  display: grid;
  gap: 0.15rem;
}
/* An inert capability looks like what it is: a real choice that does nothing
 * yet. Greyed out would say "broken"; unmarked would say "this works". */
.cap-inert { border-style: dashed; }
.cap-help { font-size: 0.75rem; line-height: 1.35; }
.tag {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border: 1px solid var(--quality-warn-border);
  background: var(--quality-warn-bg);
  color: var(--quality-warn);
  border-radius: 999px;
  padding: 0 0.4rem;
  margin-left: 0.3rem;
}

.plant-edit { border-top: 1px solid var(--color-border); padding-top: 0.7rem; }
.site-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  border-top: 1px solid var(--color-border);
  padding: 0.5rem 0;
}

/* ---------------------------------------------------------------------------
 * P2: graphs, shared graphs, and the claim code
 *
 * Same palette discipline as everything above. Note what is NOT here: no red
 * on a graph. A chart that could not be drawn is a data-trust problem - "this
 * plant did not answer" - so it wears the quality amber, never the alarm red,
 * which stays reserved for an unacknowledged critical on a real plant.
 * ------------------------------------------------------------------------ */

.graphs-page { padding: 1.2rem; display: grid; gap: 1rem; max-width: 74rem; }
.graphs-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
.graphs-head p { margin: 0.2rem 0 0; }

#graph-list { display: grid; gap: 1rem; }

.graph-card { margin: 0; }
.graph-card[draggable="true"] { cursor: grab; }
.graph-card.dragging { opacity: 0.45; }
.graph-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.8rem; }
.graph-title { min-width: 0; }
.graph-title h3 { font-size: 1rem; margin: 0; }
.graph-actions { display: flex; align-items: flex-start; gap: 0.4rem; flex-shrink: 0; flex-wrap: wrap; }
.graph-actions form { margin: 0; }

.graph-canvas { margin-top: 0.6rem; min-height: 3rem; }
.graph-canvas svg { display: block; width: 100%; height: auto; }
.graph-message { margin: 0.4rem 0; font-size: 0.85rem; color: var(--color-text-muted); }
.graph-message-bad {
  color: var(--quality-warn);
  background: var(--quality-warn-bg);
  border: 1px solid var(--quality-warn-border);
  border-radius: 6px;
  padding: 0.5rem 0.7rem;
}

.graph-legend {
  display: flex; flex-wrap: wrap; gap: 0.2rem 1rem;
  margin-top: 0.4rem; font-size: 0.78rem; color: var(--color-text-muted);
}
.graph-legend-item { display: inline-flex; align-items: center; gap: 0.35rem; }
.graph-swatch { width: 0.7rem; height: 0.2rem; border-radius: 1px; display: inline-block; }
.graph-legend-note { font-style: italic; }
.graph-legend-warn { color: var(--quality-warn); font-style: normal; }

/* The share popover on a graph card, and the picker's parameter table. */
.share > summary { cursor: pointer; font-size: 0.85rem; }
.share-form { padding: 0.5rem 0 0.2rem; max-width: 26rem; }
.share-target { display: block; font-weight: 400; margin: 0.15rem 0; }

.picker { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.picker th, .picker td { text-align: left; padding: 0.28rem 0.5rem; border-bottom: 1px solid var(--color-border); }
.picker th { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--color-text-muted); }

/* The dashboard's "shared with you" section and the shares page share these. */
.share-inbox { margin: 0.9rem 1.2rem 0; }
.share-offer {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem;
  border-top: 1px solid var(--color-border); padding: 0.6rem 0;
}
.share-offer:first-of-type { border-top: none; }
.share-offer-what { min-width: 0; }
.share-offer-actions { display: flex; gap: 0.4rem; flex-shrink: 0; }
.share-offer-actions form { margin: 0; }

/* A claim code is read off the screen and typed at a box. Big, spaced, and
 * in a monospace face so O/0 and 5/S cannot be confused - the alphabet in
 * portal/claim.py already leaves the worst offenders out, and this is the
 * other half of the same care. */
.claim-code-box {
  border: 1px solid var(--color-accent);
  border-radius: 8px;
  padding: 0.8rem 1rem;
  margin-bottom: 1rem;
  background: var(--color-surface);
}
.claim-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  margin: 0.4rem 0;
}
.claim-code-box p { margin: 0; }
.claim-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem;
  padding: 0 0 0.9rem; margin-top: -0.3rem;
  border-bottom: 1px solid var(--color-border);
}
.claim-row form { margin: 0; }

/* Deleting a plant. Folded away behind a summary rather than sat next to
   "Unbind box": everything else on this row is undone with a new claim code
   and this one is not, so it should take a deliberate click to even see. */
.plant-danger {
  padding: 0 0 0.9rem;
  border-bottom: 1px solid var(--color-border);
}
.plant-danger > summary {
  cursor: pointer;
  font-size: 0.85rem;
  padding: 0.2rem 0;
}
.plant-danger[open] > summary { color: var(--color-danger); }
.plant-danger form { margin: 0.4rem 0 0; }

/* --- incidents page: the disturbance-capture archive --- */
.table-scroll { overflow-x: auto; }
.incidents-table { width: 100%; border-collapse: collapse; }
.incidents-table td, .incidents-table th { vertical-align: top; text-align: left; }
.verified-pill {
  display: inline-block;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--quality-ok);
  border: 1px solid var(--quality-ok);
  white-space: nowrap;
}
.incident-actions { white-space: nowrap; }
.incident-delete { display: inline-flex; gap: 0.3rem; align-items: center; margin-left: 0.4rem; }
.incident-delete input { width: 6.5rem; padding: 0.2rem 0.4rem; border: 1px solid var(--color-border); border-radius: 4px; }

/* ------------------------------------------------------------------ */
/* FleetLink - the second-source card group on the fleet page          */
/* ------------------------------------------------------------------ */

/* Outside the .fleet grid (it is a distinct group, not more plant
   cards), so it carries the page margin itself - same as .share-inbox,
   plus a bottom margin because it is the last thing on the page. */
.fleetlink { margin: 0 1.2rem 1.2rem; }
.fleetlink > h2 { margin-top: 0; }
.fleetlink .plant-card { margin-top: 0.9rem; }
.fleetlink .inline select { max-width: 14rem; }

/* ---------------------------------------------------------------------------
 * Typed confirmations, and the authenticator setup screen
 * (HANDOFF-portal-writes.md, decisions 6 and the second-factor precondition).
 *
 * The confirmation box sits INLINE with the button it guards rather than in a
 * modal. Two reasons: the portal has one piece of JavaScript in it on purpose,
 * and a field beside the button shows what is being destroyed at the moment of
 * deciding - which is the whole job. The server enforces it either way
 * (portal/admin.py); this only makes the requirement visible before the click
 * instead of after it.
 * ------------------------------------------------------------------------- */
form.confirm-form { display: inline-flex; gap: 0.4rem; align-items: center; margin: 0; }
form.confirm-form input[type="text"] {
  width: 16rem;
  max-width: 40vw;
  margin: 0;
  font-size: 0.85rem;
}
form.confirm-form input[type="text"]::placeholder { opacity: 0.55; }

/* The setup key is read off the screen and typed into a phone, so it is
 * monospaced, spaced out and selectable in one gesture. */
.totp-secret code {
  display: block;
  font-size: 1.05rem;
  letter-spacing: 0.12em;
  word-break: break-all;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface-2, var(--color-surface));
  user-select: all;
}

/* --- boxless plant page (HANDOFF-boxless-bundles.md) ------------------- */
/* Deliberately styled like the admin pages, not like a fleet card: a
   boxless plant is a record and a config bundle, never a live tile. */
.boxless-page { padding: 1.2rem; display: grid; gap: 1rem; max-width: 70rem; }
.boxless-head h2 .state { vertical-align: middle; margin-left: 0.4rem; }
.boxless-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); gap: 0.8rem 1.2rem; margin: 0.8rem 0 0; }
.boxless-facts dt { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--color-text-muted); }
.boxless-facts dd { margin: 0.15rem 0 0; min-width: 0; overflow-wrap: anywhere; } /* paths, relayd errors at 375 px */
.bundle-editor textarea {
  width: 100%;
  box-sizing: border-box;
  font: 0.82rem/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-bg);
  color: var(--color-text);
  resize: vertical;
}
.bundle-editor textarea[readonly] { opacity: 0.8; }
.bundle-editor label.wide { display: grid; gap: 0.25rem; }
/* The fleet card of a boxless plant: what it is, and when it went dark. */
.plant-card.plant-boxless { border-style: dashed; }
.plant-card.plant-boxless .plant-body { opacity: 0.9; }

/* The per-plant people & keys page (portal/people_web.py). */
.people-table td { padding: 0.45rem 0.6rem; }
.people-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.people-actions .approve-form input { width: 9rem; }
.outstanding { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.4rem; }
.outstanding li { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.key-fresh .key-actions { display: flex; gap: 0.6rem; margin: 0.5rem 0; flex-wrap: wrap; }
.key-text, .key-instructions { white-space: pre-wrap; word-break: break-all; font-size: 0.85rem; margin: 0.4rem 0; }
.mint-form input[type=number] { width: 6rem; }
.settings-list { margin: 0; display: grid; gap: 0.6rem; }
.setting-row dt { font-weight: 600; display: flex; gap: 0.5rem; align-items: center; }
.setting-row dd { margin: 0.15rem 0 0 0; }
.setting-row dd form.inline { margin-top: 0.3rem; }

/* -- saved graphs and dashboards from the box (HANDOFF-saved-graphs.md) ----- */

.plant-tabs { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.6rem; }
.plant-tab {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 0.8rem;
  border: 1px solid var(--border, #d0d7de); border-radius: 6px; text-decoration: none;
}
.plant-tab-current { font-weight: 600; background: var(--surface-2, #f3f4f6); }
.box-graphs h3 { margin: 0 0 0.3rem; }
.graph-export { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.4rem; }
.graph-export button { min-height: 44px; }
.graph-hash { font-size: 0.75rem; word-break: break-all; }
.tile-value { font-size: 1.6rem; font-weight: 600; }

/* A dashboard: one column on a phone (widgets in saved order), two on a
   tablet, three from 1100 px - the box's own grid (frontend views.css). */
.dash-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; }
.dash-widget { min-width: 0; margin: 0; }
@media (min-width: 768px) {
  .dash-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dash-size-2, .dash-size-3 { grid-column: span 2; }
}
@media (min-width: 1100px) {
  .dash-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .dash-size-2 { grid-column: span 2; }
  .dash-size-3 { grid-column: span 3; }
}

/* Print / PDF: the "PDF" buttons are window.print(). Chart full width with
   its legend, title, range, plant, generated-at, and each chart's data hash. */
.print-only { display: none; }
@media print {
  @page { size: landscape; margin: 12mm; }
  .print-only { display: block; }
  .graph-hash.print-only { display: inline; }
  .topbar, nav, .no-print, .share, .reorder, form.inline, noscript, .quality-warn { display: none !important; }
  .graphs-page { padding: 0; max-width: none; }
  .graph-card, .dash-widget { break-inside: avoid; page-break-inside: avoid; box-shadow: none; }
  .dash-grid { display: block; }
  .graph-swatch { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .print-head { font-size: 9pt; margin-bottom: 3mm; }
  .print-foot { font-size: 8pt; border-top: 1px solid #999; padding-top: 2mm; }
}
