/* Solera controller dashboard. Layered on base.css (brand typeface, color/type
 * tokens, reset, and the SOLERA wordmark): the schematic, the readings table,
 * and the dashboard controls. All colors come from the base tokens. */

/* Objectivity with overridden vertical metrics, used only on the setpoint number
 * input. The text-selection highlight follows the font's content area, and
 * Objectivity's native metrics seat the figures tight to the cap top with the
 * descent hanging below — so a selected number sits in a lopsided highlight.
 * These overrides make the content area symmetric around the digits. Chrome and
 * Firefox honor them for selection; Safari ignores the descriptors and falls
 * back to plain Objectivity via the font stack below (no fix there, no
 * regression either). */
@font-face {
  font-family: "ObjectivityFit";
  src: url("/fonts/Objectivity-Medium.woff2") format("woff2");
  font-weight: 500 600;
  ascent-override: 82%;
  descent-override: 10%;
  line-gap-override: 0%;
}

/* Non-responsive: the app and inset wrap tightly to the explicitly-sized
 * schematic (laptop/tablet; a phone layout would be its own thing). Pinned to
 * the left so nothing shifts as the window widens. */
.app { width: fit-content; margin: 0; padding: 26px 24px; }

/* Header: two compact, content-width rows shared by both views (no stretch) —
 * brand/label on top, the controls and status pills below. */
.topbar { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; margin-bottom: 20px; width: fit-content; }
.topbar-main, .topbar-controls { display: flex; align-items: center; gap: 12px; }
.divider { width: 1px; height: 26px; background: var(--vessel-stroke); }
.title { line-height: 1; }
.title-name { font-size: 15px; color: var(--text-dim); font-weight: 500; letter-spacing: 0.02em; }
.title-id { font-size: 11px; color: var(--text-faint); font-weight: 400; letter-spacing: 0.04em; margin-top: 4px; }
/* Padding is asymmetric on both axes to optically center the text:
 *  - left +1 compensates for trailing letter-spacing (text reads left of box center)
 *  - top +1 / bottom -1 compensates for font metrics (cap-to-baseline mass sits
 *    above the line-box center). */
.pill { font-size: 11px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; padding: 7px 13px 5px 14px; border-radius: 999px; border: 1px solid var(--vessel-stroke); color: var(--text-dim); }
.pill.mode { color: var(--solera-orange); border-color: var(--solera-orange-dim); }
.status { font-size: 12px; color: var(--text-faint); display: flex; align-items: center; gap: 7px; }
.status .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); }
/* Optical centering: the cap-to-baseline mass of the font sits above the line-
 * box center, so nudge the text down by 1px to match the dot. */
.status #status-text { transform: translateY(1px); }
.status.stale .dot { background: var(--warn); }
.status.down .dot { background: var(--down); }

/* Panel + schematic */
.panel { background: var(--panel); border: 1px solid var(--vessel-stroke); border-radius: 16px; padding: 12px; width: fit-content; }
.schematic { display: block; }  /* explicit width/height set by fitSchematic — do not force width:100% */

.vessel { fill: var(--vessel); stroke: var(--vessel-stroke); stroke-width: 1.5; }
.vessel-label { fill: var(--text); font-size: 15px; font-weight: 600; letter-spacing: 0.03em; }
/* Iconographic detail inside vessels (plate pack, fan, lid seam). The bright
 * detail matches the component line idiom; the faint variant is for seams. */
.vessel-detail { fill: none; stroke: var(--text-dim); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.vessel-detail-faint { fill: none; stroke: var(--vessel-stroke); stroke-width: 1.5; }
.vessel-hub { fill: var(--text-dim); stroke: none; }

.pipe { fill: none; stroke-width: 4; stroke-linecap: round; stroke-linejoin: round; }
.pipe.hot { stroke: var(--hot); }
.pipe.warm { stroke: var(--warm); }
.pipe.cold { stroke: var(--cold); }
.port { fill: var(--text-faint); font-size: 11px; letter-spacing: 0.12em; }

/* Component symbols — stroked, engineering-native (P&ID-literate) line idiom.
 * Shape geometry lives in schematic.js; these tokens carry the look. */
.sym-line { fill: none; stroke: var(--text-dim); stroke-width: 2; }
.sym-line-inner { fill: none; stroke: var(--text-dim); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
/* Tags/state labels aren't click targets — only the symbol shape is (below). */
.sym-tag { fill: var(--text-dim); font-size: 12px; font-weight: 700; letter-spacing: 0.04em; pointer-events: none; }
/* Transparent hit area so a symbol is clickable across its whole box, not just
 * its strokes (the bowtie/circle interiors are unpainted). */
.sym-hit { fill: transparent; }

/* RTD well / taped-on thermocouple — small circle that interrupts the pipe. */
.rtd-well { fill: var(--bg); stroke: var(--text-dim); stroke-width: 2; }

.flow-arrow { stroke: none; }
.flow-arrow.hot { fill: var(--hot); }
.flow-arrow.warm { fill: var(--warm); }
.flow-arrow.cold { fill: var(--cold); }

.temp { fill: var(--text); font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
/* A faulted reading on the schematic, in the same red the table uses. */
.temp.fault { fill: var(--down); }
/* A stale reading on the schematic, in the same amber the table uses. */
.temp.stale { fill: var(--warn); }
.temp-tag { fill: var(--text-dim); font-size: 12px; font-weight: 700; letter-spacing: 0.04em; }

/* Relays — interactive, in place */
.relay { cursor: pointer; }
.relay .node { fill: var(--relay-off); stroke: var(--relay-off-stroke); stroke-width: 1.5; transition: fill .15s ease, stroke .15s ease; }
.relay .glyph { fill: var(--text-dim); font-size: 11px; font-weight: 700; }
.relay .label { fill: var(--text-dim); font-size: 10.5px; }
.relay .state { fill: var(--text-dim); font-size: 11px; font-weight: 700; letter-spacing: 0.12em; pointer-events: none; }
.relay:hover .node { stroke: var(--solera-orange); }
.relay.on .node { fill: var(--solera-orange); stroke: var(--solera-orange); }
.relay.on .glyph { fill: #1b1205; }
.relay.on .state { fill: var(--solera-orange); }

/* Same interactive encoding for the line symbols. */
.relay.on .sym-line, .relay.on .sym-line-inner { stroke: var(--solera-orange); }
.relay.on .sym-tag { fill: var(--solera-orange); }

/* A relay-driven valve shows open/closed: green when open, dim when closed. */
.relay.open .sym-line, .relay.open .sym-line-inner { stroke: var(--ok); }
.relay.open .sym-tag { fill: var(--ok); }
.relay.open .state { fill: var(--ok); }

/* Hover cue for a clickable symbol: brighter and a touch heavier. After the
 * on/open rules so it wins regardless of state. */
.relay:hover .sym-line, .relay:hover .sym-line-inner { stroke: var(--text); stroke-width: 3; }

/* Generic toggle. The `<scope>.hidden` rules below re-state `display: none` only
 * to out-order an equal-specificity layout rule that happens to be defined first. */
.hidden { display: none; }

/* View controls (in the header): tabs to switch schematic/readings, and the
 * unit-system toggle. Conversion happens in JS at render time; the wire stays SI. */
.tabs, .units { display: inline-flex; gap: 4px; padding: 4px; background: var(--panel); border: 1px solid var(--vessel-stroke); border-radius: 999px; }
/* Vertical padding is asymmetric (top +1 / bottom -1) to optically center the
 * text: the font's cap-to-baseline mass sits above the line-box center, same as
 * the .pill above. */
.tab, .unit { font-family: var(--font); font-size: 12px; font-weight: 600; letter-spacing: 0.03em; padding: 7px 14px 5px; border: 0; border-radius: 999px; background: transparent; color: var(--text-dim); cursor: pointer; white-space: nowrap; }
/* Keep the toggle groups intact rather than wrapping their pills at a narrow window. */
.tabs, .units { flex: none; flex-wrap: nowrap; }
.tab.active, .unit.active { background: var(--solera-royal-blue); color: #fff; }
.tab:hover:not(.active), .unit:hover:not(.active) { color: var(--text); }

.view.hidden { display: none; }

/* Readings table — every kit point (handle + live value). The canonical MQTT
 * paths live in kit.rs; the table shows the human handles. */
/* Non-responsive like the schematic: sized to its content (the longest path +
 * value) so nothing wraps, and pinned there rather than stretching to fill. */
.readings-table { table-layout: fixed; width: 380px; border-collapse: collapse; font-size: 15px; white-space: nowrap; }
/* Fixed columns: the value column keeps its width as °C/°F/L·min/GPM text
 * changes length, so the "Value" header and numbers never shift on a unit swap. */
.readings-table th:first-child { width: 250px; }
.readings-table thead th { font-size: 12px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-faint); padding: 8px 14px; border-bottom: 1px solid var(--vessel-stroke); text-align: left; }
.readings-table thead th:last-child { text-align: right; }
.reading-row td { padding: 8px 14px; border-bottom: 1px solid var(--bg-glow); }
.reading-name { color: var(--text-dim); }
/* The MQTT/storage label, styled like the system ID in the header. */
.reading-path { font-size: 11px; color: var(--text-faint); font-weight: 400; letter-spacing: 0.04em; margin-top: 3px; }
.reading-value { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--text); }
/* Commandable settings rows (the heat pump's switch and setpoint): a pointer
 * and a hover accent say "this one you can change"; the click opens the
 * setpoint modal, so nothing is commanded by the click itself. */
.reading-row.commandable { cursor: pointer; }
.reading-row.commandable:hover .reading-value { color: var(--solera-orange); }

/* A brief notice for a refused command (e.g. commands disarmed): fades in over
 * the page bottom, dismisses itself. */
.toast {
  position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%);
  background: var(--panel); border: 1px solid var(--vessel-stroke);
  color: var(--text-dim); font-size: 13px; padding: 10px 18px;
  border-radius: 999px; z-index: 60;
  opacity: 0; pointer-events: none; transition: opacity 300ms ease;
}
.toast.show { opacity: 1; }
.reading-row.stale .reading-value { color: var(--warn); }
.reading-row.fault .reading-value { color: var(--down); }

/* Setpoint modal — set a 0-100% value for a modulating valve or pump speed.
 * The live number tracks the slider as it drags; the value is only commanded on
 * Set, so dragging never chatters real hardware. */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 50;
  display: flex; align-items: center; justify-content: center;
  background: rgba(7, 10, 24, 0.6);
  backdrop-filter: blur(2px);
}
.modal-backdrop.hidden { display: none; }
.modal-card {
  background: var(--panel); border: 1px solid var(--vessel-stroke); border-radius: 16px;
  padding: 22px 24px 18px; width: 300px; text-align: center;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
}
.modal-title { font-size: 15px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim); }
/* The value is a real text field — bordered, with a focus ring — so it reads as
 * "type here," not as a readout. The whole box (incl. the %) focuses the input. */
.modal-field {
  display: inline-flex; align-items: center; gap: 2px; margin: 16px 0 8px;
  padding: 4px 8px 4px 16px; background: var(--bg);
  border: 1px solid var(--vessel-stroke); border-radius: 10px; cursor: text;
}
/* Inset ring so focusing the field (e.g. when power turns on) doesn't grow its
 * footprint and nudge the modal height. */
.modal-field:focus-within { border-color: var(--solera-royal-blue); box-shadow: inset 0 0 0 2px rgba(95, 119, 188, 0.35); }
/* Speed field while power is off: inert and dimmed (the two-step gate). The
 * input stays `readonly` rather than `disabled` so its box height is unchanged. */
.modal-field.disabled { opacity: 0.45; cursor: not-allowed; pointer-events: none; }
.modal-number {
  /* "ObjectivityFit" carries symmetric metrics so the selection highlight sits
   * even around the digits; it falls back to plain Objectivity in Safari. */
  font-family: "ObjectivityFit", var(--font); font-size: 26px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums;
  color: var(--text); background: transparent; border: none; text-align: center;
  /* A little vertical padding gives the figures room: the overridden metrics
   * shrink the box, which would otherwise clip the digits. */
  width: 2.4ch; padding: 3px 0; -moz-appearance: textfield;
}
.modal-number:focus { outline: none; }
.modal-number::-webkit-outer-spin-button, .modal-number::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* Same metric-corrected family as the number so the % glyph centers on the box
 * (and thus lines up with the number) instead of riding high. */
.modal-unit { font-family: "ObjectivityFit", var(--font); font-size: 20px; font-weight: 500; line-height: 1; color: var(--text-faint); }
/* Single instruction line — no buttons; Enter confirms, Esc cancels. */
.modal-hint { font-size: 13px; color: var(--text-faint); letter-spacing: 0.02em; margin-top: 4px; }

/* Pump power toggle (shown only for pumps): label + a segmented Off/On switch
 * with both labels always visible and the active one filled. */
/* The toggle on its own line (pumps only), centered on the modal's centerline so
 * it lines up with the value field below it. */
.modal-power { display: flex; align-items: center; justify-content: center; margin: 14px 0 4px; }
.modal-power.hidden { display: none; } /* valves (no power control) hide it */

/* Confirm modal actions: Cancel and Yes side by side; Yes carries the accent
 * because it is the one that acts. */
.modal-actions { display: flex; gap: 10px; justify-content: center; margin-top: 18px; }
.modal-btn {
  font: inherit; font-size: 13px; font-weight: 600; letter-spacing: 0.04em;
  color: var(--text-dim); background: var(--vessel); border: 1px solid var(--vessel-stroke);
  border-radius: 8px; padding: 8px 22px; cursor: pointer;
}
.modal-btn:hover { color: var(--text); }
.modal-btn.primary { color: var(--solera-orange); border-color: var(--solera-orange-dim); }
.modal-btn.primary:hover { color: var(--text); background: var(--solera-orange-dim); }
.modal-toggle { display: inline-flex; gap: 4px; padding: 4px; background: var(--bg); border: 1px solid var(--vessel-stroke); border-radius: 999px; }
.modal-toggle-opt {
  font-family: var(--font); font-size: 13px; font-weight: 600; letter-spacing: 0.04em;
  padding: 5px 16px; border: 0; border-radius: 999px; background: transparent;
  color: var(--text-dim); cursor: pointer;
}
.modal-toggle-opt[data-power="on"].active { background: var(--ok); color: #0c1a10; }
.modal-toggle-opt[data-power="off"].active { background: var(--relay-off-stroke); color: var(--text); }

/* Login gate: passphrase field and submit button. There is no app "remember
 * me"; the browser's password manager saves and autofills the passphrase. */
.login-input {
  font-family: var(--font); font-size: 16px; width: 100%; margin-top: 16px;
  padding: 10px 12px; background: var(--bg); color: var(--text);
  border: 1px solid var(--vessel-stroke); border-radius: 10px;
}
.login-input:focus { outline: none; border-color: var(--solera-royal-blue); box-shadow: inset 0 0 0 2px rgba(95, 119, 188, 0.35); }
.login-submit {
  font-family: var(--font); font-size: 14px; font-weight: 600; width: 100%;
  margin-top: 12px; padding: 10px 12px; cursor: pointer; color: #fff;
  background: var(--solera-royal-blue); border: 1px solid var(--solera-royal-blue); border-radius: 10px;
}
.login-submit:hover { filter: brightness(1.08); }
.login-submit:focus { outline: none; box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.25); }
/* The login hint turns red to flag a wrong passphrase. */
.modal-hint.error { color: var(--down); }

/* Brand backdrop, shared by the login page and the dashboard's intro: a solid
 * brand-blue surface the real page emerges through. `lifting` fades it out to
 * reveal the live view. A color, not an image: nothing to capture per kit,
 * nothing to go stale, and nothing about the plant served before login. */
.backdrop {
  position: fixed; inset: 0; z-index: 40;
  background: var(--solera-dark-blue);
}
.backdrop.lifting {
  opacity: 0; pointer-events: none;
  transition: opacity 700ms ease;
}
/* The login card, centered over the backdrop. */
.login-screen { position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center; }

/* Plots view: the range toggle, the chart, and the series legend. The container
 * is the positioning context for the hover tooltip. */
.plots { position: relative; display: flex; flex-direction: column; gap: 20px; width: 1040px; }
.plots-bar { display: flex; gap: 8px; align-items: center; }
.plot-nav {
  font-family: var(--font); font-size: 18px; line-height: 1; font-weight: 700;
  width: 34px; height: 34px; border: 1px solid var(--vessel-stroke); border-radius: 999px;
  background: var(--panel); color: var(--text-dim); cursor: pointer;
}
.plot-nav:hover:not(:disabled) { color: var(--text); border-color: var(--solera-royal-blue); }
.plot-nav:disabled { opacity: 0.3; cursor: default; }
.plot-today {
  font-family: var(--font); font-size: 12px; font-weight: 600; letter-spacing: 0.03em;
  /* Intrinsic height from padding, with 2px more on top than bottom — the same
     optical-centering trick the .tab pills use (7px/5px there), scaled up so this
     standalone button fills the row height the tabs reach via their container. */
  padding: 11px 14px 9px; border: 1px solid var(--vessel-stroke); border-radius: 999px;
  background: var(--panel); color: var(--text-dim); cursor: pointer;
}
.plot-today:hover { color: var(--text); border-color: var(--solera-royal-blue); }
.plot-label { font-family: var(--font); font-size: 13px; font-weight: 600; color: var(--text-dim); margin-left: 4px; }
.chart {
  width: 100%; height: auto; display: block;
  background: var(--panel); border: 1px solid var(--vessel-stroke); border-radius: 16px;
}
.chart-grid { stroke: var(--vessel-stroke); stroke-opacity: 0.4; stroke-width: 1; }
.chart-band-edge { stroke: var(--vessel-stroke); stroke-width: 1; }
.chart-line { fill: none; stroke-width: 1.6; }
.chart-cursor { stroke: var(--text-faint); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-capture { cursor: crosshair; }
.chart-tick, .chart-axis-label, .chart-empty { font-family: var(--font); fill: var(--text-dim); }
.chart-tick { font-size: 11px; }
.chart-axis-label { font-size: 11px; font-weight: 600; fill: var(--text-faint); }
.chart-empty { font-size: 15px; fill: var(--text-faint); }

/* Series legend: columns by group, each a select-all/none header over a stack of
 * toggle chips; a dimmed chip (or header) is a hidden series (or group). */
.legend { display: flex; flex-wrap: wrap; gap: 24px; align-items: flex-start; }
.legend-col { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; }
.legend-head {
  font-family: var(--font); font-size: 11px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-dim); text-align: left; cursor: pointer;
  background: none; border: 0; border-bottom: 1px solid var(--vessel-stroke);
  padding: 2px 2px 5px; margin-bottom: 2px; width: 100%;
}
.legend-head:hover { color: var(--text); }
.legend-head.off { color: var(--text-faint); }
.legend-head-unit { text-transform: none; font-weight: 600; color: var(--text-faint); margin-left: 6px; }
.legend-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font); font-size: 11px; font-weight: 600; letter-spacing: 0.02em;
  padding: 5px 10px; border: 1px solid var(--vessel-stroke); border-radius: 999px;
  background: var(--panel); color: var(--text); cursor: pointer; white-space: nowrap;
}
.legend-chip.off { opacity: 0.4; }
.legend-swatch { width: 10px; height: 10px; border-radius: 3px; }

/* Hover readout, positioned over the chart by chart.js. */
.chart-tip {
  position: absolute; display: none; z-index: 10; pointer-events: none;
  width: 200px; box-sizing: border-box;
  background: var(--bg-glow); border: 1px solid var(--vessel-stroke); border-radius: 10px;
  padding: 8px 10px; font-family: var(--font); font-size: 11px; color: var(--text);
  max-height: 92%; overflow: hidden; box-shadow: 0 6px 20px rgba(0,0,0,0.4);
}
.chart-tip .tip-time { color: var(--text-dim); font-weight: 600; padding-bottom: 6px; margin-bottom: 6px; border-bottom: 1px solid var(--vessel-stroke); }
/* Group rows into the plot's unit-buckets, with a divider between them. */
.chart-tip .tip-group + .tip-group { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--vessel-stroke); }
.tip-row { display: flex; align-items: center; gap: 6px; line-height: 1.5; }
.tip-row b { margin-left: auto; padding-left: 12px; font-variant-numeric: tabular-nums; }
.tip-sw { width: 9px; height: 9px; border-radius: 2px; flex: none; }
