/* Psychrometric chart and engineering diagrams. */

.chart-wrap { position: relative; flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; }
.chart-wrap .chart-host { flex: 1 1 auto; }
.chart-wrap .chart-host:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-line); border-radius: 0; }
.panel-reopen { position: absolute; top: 50%; z-index: 4; width: 22px; height: 58px; transform: translateY(-50%); display: grid; place-items: center; color: var(--text-3); background: var(--surface-hi); box-shadow: var(--shadow-2), 0 0 0 1px var(--line); }
.panel-reopen:hover { color: var(--accent-hi); }
.panel-reopen.left { left: 0; border-radius: 0 10px 10px 0; }
.panel-reopen.right { right: 0; border-radius: 10px 0 0 10px; }
@media (max-width: 860px) { .panel-reopen { display: none; } }

.chart-host { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; background: var(--chart-bg); touch-action: none; user-select: none; -webkit-user-select: none; }
.chart-host svg.psy { position: absolute; inset: 0; width: 100%; height: 100%; display: block; font-family: var(--font-ui); }
.chart-host.tool-add { cursor: crosshair; }
.chart-host.tool-pan { cursor: grab; }
.chart-host.is-panning { cursor: grabbing; }
.chart-host.is-dragging { cursor: grabbing; }

.psy .plot-bg { fill: var(--chart-plot); }
.psy .fog { fill: var(--chart-fog); }
.psy .grid { stroke: var(--chart-grid); stroke-width: 1; fill: none; shape-rendering: crispEdges; }
.psy .grid.major { stroke: var(--chart-grid-2); }
.psy .rh { stroke: var(--chart-rh); stroke-width: 1; fill: none; }
.psy .rh.r50 { stroke-width: 1.4; }
.psy .wb { stroke: var(--chart-wb); stroke-width: 1; fill: none; stroke-dasharray: 4 4; }
.psy .enth { stroke: var(--chart-h); stroke-width: 1; fill: none; }
.psy .vol { stroke: var(--chart-v); stroke-width: 1; fill: none; stroke-dasharray: 10 5; }
.psy .dp { stroke: var(--chart-wb); stroke-width: 1; fill: none; stroke-dasharray: 1 4; }
.psy .sat-glow { stroke: var(--chart-sat-glow); stroke-width: 7; fill: none; filter: url(#psy-blur); opacity: .55; }
.psy .sat { stroke: var(--chart-sat); stroke-width: 2.2; fill: none; stroke-linecap: round; }
.psy .axis-label { fill: var(--chart-label); font-size: 11px; font-variant-numeric: tabular-nums; }
.psy .axis-title { fill: var(--chart-axis); font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }
.psy .rh-label { fill: var(--chart-rh-label); font-size: 10.5px; font-weight: 600; }
.psy .marker-label { font-size: 11px; font-weight: 600; }
.psy .wb-label { fill: var(--cool); font-size: 10.5px; opacity: .85; }
.psy .h-label { fill: var(--text-3); font-size: 10.5px; }
.psy .v-label { fill: var(--violet); font-size: 10px; opacity: .8; }
.psy .frame { fill: none; stroke: var(--line-2); }
.psy .halo { paint-order: stroke; stroke: var(--chart-plot); stroke-width: 3px; stroke-linejoin: round; }

.psy .envelope { fill: var(--chart-envelope); stroke: var(--chart-envelope-line); stroke-width: 1.4; stroke-dasharray: 6 4; }
.psy .envelope-label { fill: var(--ok); font-size: 10.5px; font-weight: 700; letter-spacing: .04em; }
.psy .envelope.allowable { fill: rgba(233, 177, 76, .05); stroke: rgba(233, 177, 76, .5); }

.psy .proc { fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.psy .proc-glow { fill: none; stroke-width: 8; stroke-linecap: round; filter: url(#psy-blur); }
.psy .proc.dashed { stroke-dasharray: 6 5; }
.psy .proc.is-selected { stroke-width: 3.4; }
.psy .proc-hit { fill: none; stroke: transparent; stroke-width: 16; cursor: pointer; }
.psy .adp-line { fill: none; stroke-width: 1.3; stroke-dasharray: 2 4; opacity: .8; }
.psy .adp-dot { stroke-width: 1.6; fill: var(--chart-bg); }
.psy .adp-dot.hollow { fill: none; }

.psy .pt { cursor: grab; }
.psy .pt .pt-halo { opacity: 0; transition: opacity var(--dur-1); }
.psy .pt:hover .pt-halo, .psy .pt.is-selected .pt-halo { opacity: .28; }
.psy .pt .pt-ring { fill: var(--chart-bg); stroke-width: 2.6; }
.psy .pt .pt-core { stroke: none; }
.psy .pt.is-derived .pt-ring { stroke-dasharray: 3 2; }
.psy .pt.is-locked { cursor: default; }
.psy .pt-tag rect { fill: var(--surface-hi); stroke: var(--line-2); }
.psy .pt-tag text { fill: var(--text-1); font-size: 11px; font-weight: 650; }
.psy .pt.is-selected .pt-tag rect { stroke: var(--accent-line); }

.psy .cross { stroke: var(--text-4); stroke-width: 1; stroke-dasharray: 3 3; pointer-events: none; }
.psy .hourly { pointer-events: none; }

.chart-readout { position: absolute; z-index: 3; pointer-events: none; min-width: 168px; padding: 8px 10px; border-radius: var(--r-sm); background: color-mix(in srgb, var(--surface-hi) 92%, transparent); box-shadow: var(--shadow-3), 0 0 0 1px var(--line-2); backdrop-filter: blur(6px); font-size: var(--fs-11); color: var(--text-2); display: grid; grid-template-columns: auto auto; gap: 2px 12px; }
.chart-readout b { color: var(--text-1); font-weight: 650; text-align: right; font-variant-numeric: tabular-nums; }
.chart-readout .ro-title { grid-column: 1 / -1; font-size: var(--fs-10); font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--accent-hi); margin-bottom: 3px; }
.chart-readout.fog .ro-title { color: var(--cool); }

.chart-legend { position: absolute; left: 58px; top: 26px; z-index: 2; display: flex; gap: 12px; flex-wrap: wrap; padding: 6px 10px; border-radius: var(--r-sm); background: color-mix(in srgb, var(--surface) 85%, transparent); box-shadow: 0 0 0 1px var(--line); font-size: var(--fs-11); color: var(--text-3); pointer-events: none; }
.chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
.chart-legend i { display: inline-block; width: 16px; height: 0; border-top: 2px solid; }
.chart-zoom { position: absolute; right: 76px; bottom: 50px; z-index: 2; display: flex; flex-direction: column; gap: 2px; padding: 3px; border-radius: var(--r-md); background: var(--surface-hi); box-shadow: var(--shadow-2), 0 0 0 1px var(--line); }
.chart-zoom button { width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; color: var(--text-3); }
.chart-zoom button:hover { color: var(--text-1); background: var(--accent-soft); }
/* Phones: the plot needs every pixel. The line legend is dropped (the line styles are the
   standard chart conventions) and the zoom buttons shrink; pinch zoom still works. */
@media (max-width: 600px) {
  .chart-legend { display: none; }
  .chart-zoom { right: 70px; bottom: 46px; }
  .chart-zoom button { width: 26px; height: 26px; }
}
.chart-hint { position: absolute; left: 50%; top: 14px; transform: translateX(-50%); z-index: 2; padding: 6px 12px; border-radius: var(--r-pill); background: var(--surface-hi); box-shadow: var(--shadow-2), 0 0 0 1px var(--accent-line); font-size: var(--fs-12); color: var(--text-2); pointer-events: none; white-space: nowrap; }

/* Equipment schematic (air path) */
.schematic { position: relative; flex: none; height: 168px; overflow-x: auto; overflow-y: hidden; background: var(--bg-1); box-shadow: inset 0 1px 0 var(--line); }
.schematic svg { display: block; height: 100%; }
/* On phones, scaling the strip to the screen width would make its text unreadable: keep it
   at natural size and let the strip scroll sideways. */
@media (max-width: 600px) { .schematic svg { max-width: none; } }
.schematic .duct { fill: var(--surface-lo); stroke: var(--line-2); }
.schematic .flow-arrow { fill: var(--text-4); }
.schematic .flow-line { stroke: var(--line-3); stroke-width: 1.5; stroke-dasharray: 2 6; animation: flow 1.2s linear infinite; fill: none; }
@keyframes flow { to { stroke-dashoffset: -16; } }
.schematic .unit { cursor: pointer; }
.schematic .unit rect.body { fill: var(--raised-grad); fill: var(--surface-hi); stroke: var(--line-2); stroke-width: 1; }
.schematic .unit:hover rect.body, .schematic .unit.is-selected rect.body { stroke: var(--accent); stroke-width: 1.5; }
.schematic .unit.is-error rect.body { stroke: var(--bad); }
.schematic .unit text { fill: var(--text-2); font-size: 10.5px; font-weight: 600; }
.schematic .unit .u-load { fill: var(--text-1); font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums; }
.schematic .state-tag text { fill: var(--text-3); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.schematic .state-tag .st-t { fill: var(--text-1); font-weight: 650; }
.schematic .pipe { stroke-width: 2; fill: none; }
.schematic .empty-msg { fill: var(--text-4); font-size: 12px; }

/* ── Lab time-series and performance charts ─────────────────────────────────────
   Series colors are the dataviz reference categorical palette, validated with its
   checker against LabHub's chart surfaces (dark #121316, light #fdfcfb) on 22 Oct 2026:
   lightness band, chroma, adjacent colorblind separation and the normal-vision floor
   all pass. Three light-mode slots sit below 3:1 contrast, so these charts always carry
   a legend, a readout and a table view. LabHub's accent hues failed the same check
   (blue and teal too close), so they are not used for data series. Colors follow the
   reading, never its position. The tokens live on :root so script can resolve them for
   SVG attributes (the psychrometric chart's measured states use --data-1). */
:root { --data-1: #3987e5; --data-2: #d95926; --data-3: #199e70; --data-4: #c98500; --data-5: #d55181; --data-6: #008300; --data-7: #9085e9; --data-8: #e66767; }
:root[data-theme="light"] { --data-1: #2a78d6; --data-2: #eb6834; --data-3: #1baf7a; --data-4: #eda100; --data-5: #e87ba4; --data-6: #008300; --data-7: #6250d6; --data-8: #e34948; }
.ts-host, .pm-chart, .ts-legend { --s1: var(--data-1); --s2: var(--data-2); --s3: var(--data-3); --s4: var(--data-4); --s5: var(--data-5); --s6: var(--data-6); --s7: var(--data-7); --s8: var(--data-8); }

.ts-host { position: relative; overflow: hidden; background: var(--chart-bg); border-radius: var(--r-md); box-shadow: var(--well); touch-action: pan-y; user-select: none; -webkit-user-select: none; }
.ts-host:focus-visible { outline: none; box-shadow: var(--well), 0 0 0 2px var(--accent-line); }
.ts-host svg.ts { position: absolute; inset: 0; width: 100%; height: 100%; display: block; font-family: var(--font-ui); cursor: crosshair; }
.ts .plot-bg { fill: var(--chart-plot); }
.ts .grid { stroke: var(--chart-grid); stroke-width: 1; shape-rendering: crispEdges; }
.ts .axis-label { fill: var(--chart-label); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.ts .axis-title { fill: var(--chart-axis); font-size: 10.5px; font-weight: 600; letter-spacing: .03em; }
.ts .ts-title { fill: var(--text-2); font-size: 10.5px; font-weight: 650; letter-spacing: .06em; text-transform: uppercase; }
.ts .ts-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ts .ts-key { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.ts .ts-target { stroke-width: 1.5; stroke-dasharray: 6 4; }
.ts .ts-tol { opacity: .1; }
.ts .ts-outlier { stroke: var(--chart-plot); stroke-width: 2; }
.ts .ts-window { fill: var(--accent); opacity: .07; }
.ts .ts-excl { fill: var(--text-3); opacity: .18; }
.ts .ts-band-label { fill: var(--text-3); font-size: 10px; font-weight: 600; }
/* Steps of a functional test run behind logged data: every other step shaded, each numbered. */
.ts .ts-step { fill: var(--text-3); opacity: .08; }
.ts .ts-step-num { fill: var(--text-3); font-size: 9.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.chart-readout .ro-sub { grid-column: 1 / -1; color: var(--text-1); font-weight: 600; margin-bottom: 3px; max-width: 280px; }
.ts .ts-sel { fill: var(--text-1); opacity: .08; stroke: var(--line-3); }
.ts .cross { stroke: var(--text-4); stroke-width: 1; pointer-events: none; }
.ts .ts-end-label { fill: var(--text-2); font-size: 10.5px; }
.ts .s1 { stroke: var(--s1); } .ts .s2 { stroke: var(--s2); } .ts .s3 { stroke: var(--s3); } .ts .s4 { stroke: var(--s4); }
.ts .s5 { stroke: var(--s5); } .ts .s6 { stroke: var(--s6); } .ts .s7 { stroke: var(--s7); } .ts .s8 { stroke: var(--s8); }
.ts .ts-outlier.s1, .ts .ts-tol.s1 { fill: var(--s1); stroke: var(--chart-plot); } .ts .ts-outlier.s2, .ts .ts-tol.s2 { fill: var(--s2); stroke: var(--chart-plot); }
.ts .ts-outlier.s3, .ts .ts-tol.s3 { fill: var(--s3); stroke: var(--chart-plot); } .ts .ts-outlier.s4, .ts .ts-tol.s4 { fill: var(--s4); stroke: var(--chart-plot); }
.ts .ts-outlier.s5, .ts .ts-tol.s5 { fill: var(--s5); stroke: var(--chart-plot); } .ts .ts-outlier.s6, .ts .ts-tol.s6 { fill: var(--s6); stroke: var(--chart-plot); }
.ts .ts-outlier.s7, .ts .ts-tol.s7 { fill: var(--s7); stroke: var(--chart-plot); } .ts .ts-outlier.s8, .ts .ts-tol.s8 { fill: var(--s8); stroke: var(--chart-plot); }
.ts .ts-tol { stroke: none !important; }

.ts-readout { grid-template-columns: 14px auto minmax(0, 1fr); min-width: 190px; max-width: 280px; }
.ts-readout .ro-row { display: contents; }
.ts-readout .ro-key { align-self: center; width: 12px; height: 0; border-top: 2px solid var(--text-3); }
.ts-readout span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ts-readout .s1 { border-color: var(--s1); } .ts-readout .s2 { border-color: var(--s2); } .ts-readout .s3 { border-color: var(--s3); } .ts-readout .s4 { border-color: var(--s4); }
.ts-readout .s5 { border-color: var(--s5); } .ts-readout .s6 { border-color: var(--s6); } .ts-readout .s7 { border-color: var(--s7); } .ts-readout .s8 { border-color: var(--s8); }

/* Legend chips double as series toggles: a line key in the series color, the name in text ink. */
.ts-legend { display: flex; flex-wrap: wrap; gap: 4px 6px; align-items: center; }
.ts-chip { display: inline-flex; align-items: center; gap: 7px; min-height: 28px; padding: 4px 10px; border-radius: 14px; font-size: var(--fs-12); line-height: 1.25; text-align: left; color: var(--text-2); background: var(--surface-lo); box-shadow: inset 0 0 0 1px var(--line); }
.ts-chip:hover { color: var(--text-1); box-shadow: inset 0 0 0 1px var(--line-3); }
.ts-chip i { flex: none; display: inline-block; width: 16px; height: 0; border-top: 2px solid var(--text-3); }
/* Grouped by panel: colors repeat from panel to panel, so each is read within its quantity. */
.ts-legend.grouped { display: grid; gap: 6px; }
.ts-group { display: grid; grid-template-columns: 10.5rem minmax(0, 1fr); gap: 4px 12px; align-items: center; }
.ts-group-title { font-size: var(--fs-11); font-weight: 650; letter-spacing: .06em; text-transform: uppercase; color: var(--text-3); }
.ts-group-chips { display: flex; flex-wrap: wrap; gap: 4px 6px; }
@media (max-width: 640px) { .ts-group { grid-template-columns: minmax(0, 1fr); } }
.ts-chip.dashed i { border-top-style: dashed; }
.ts-chip[aria-pressed="false"] { color: var(--text-4); }
.ts-chip[aria-pressed="false"] i { opacity: .35; }
.ts-chip .s1 { border-color: var(--s1); } .ts-chip .s2 { border-color: var(--s2); } .ts-chip .s3 { border-color: var(--s3); } .ts-chip .s4 { border-color: var(--s4); }
.ts-chip .s5 { border-color: var(--s5); } .ts-chip .s6 { border-color: var(--s6); } .ts-chip .s7 { border-color: var(--s7); } .ts-chip .s8 { border-color: var(--s8); }
.ts-toolbar { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; }
.ts-wrap { position: relative; flex: none; }
.ts-wrap > .ts-host { height: 100%; }
.ts-selbar { padding: 6px 10px; border-radius: var(--r-md); background: var(--surface-lo); box-shadow: inset 0 0 0 1px var(--line-2); }

/* Performance map (VDOM SVG; few points, so it re-renders on hover). */
.pm-chart { position: relative; }
.pm { width: 100%; height: auto; display: block; background: var(--chart-bg); border-radius: var(--r-md); box-shadow: var(--well); font-family: var(--font-ui); }
.pm .plot-bg { fill: var(--chart-plot); }
.pm .grid { stroke: var(--chart-grid); stroke-width: 1; shape-rendering: crispEdges; }
.pm .axis-label { fill: var(--chart-label); font-size: 11px; font-variant-numeric: tabular-nums; }
.pm .axis-title { fill: var(--chart-axis); font-size: 11px; font-weight: 600; }
.pm .pm-dot { stroke: var(--chart-plot); stroke-width: 2; }
.pm .pm-dot.s1 { fill: var(--s1); } .pm .pm-dot.s2 { fill: var(--s2); } .pm .pm-dot.s3 { fill: var(--s3); }
.pm .pm-ref { fill: none; stroke: var(--text-3); stroke-width: 1.6; }
.pm .pm-limit { stroke: var(--text-3); stroke-width: 1.2; stroke-dasharray: 6 4; }
.pm .pm-limit-label, .pm .pm-flag { fill: var(--text-2); font-size: 11px; font-weight: 600; }
.pm-readout { transform: translate(14px, -50%); }
.pm-readout.flip { transform: translate(-14px, -50%); }
.pm-controls { display: flex; flex-wrap: wrap; gap: 6px 8px; align-items: center; }
.pm-controls .select { width: auto; max-width: 100%; }
.ts-chip i.pm-key { width: 9px; height: 9px; border: 0; border-radius: 50%; background: var(--text-3); }
.ts-chip i.pm-key.s1 { background: var(--s1); } .ts-chip i.pm-key.s2 { background: var(--s2); } .ts-chip i.pm-key.s3 { background: var(--s3); }
.ts-chip i.pm-key.ref { background: none; box-shadow: inset 0 0 0 1.6px var(--text-3); }
.pm-chart + .ts-legend, .ts-legend + .pm-chart { margin-top: 2px; }
/* The map and its data table side by side when the card is wide enough, stacked when it is not
   (flex wrapping follows the card's width, not the window's). The chart stays at or below the
   SVG's own width so its text keeps its designed size. */
.pm-body { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.pm-body > .pm-chart { flex: 1 1 620px; max-width: 720px; min-width: 0; }
.pm-body > .pm-table { flex: 1 1 300px; min-width: 0; }
.pm-table th { white-space: normal; vertical-align: bottom; }
.pm-table th:first-child, .pm-table td:first-child { min-width: 9rem; }
@media (max-width: 640px) { .pm-table th:first-child, .pm-table td:first-child { min-width: 6.5rem; } }
.pm-table td .tiny { white-space: nowrap; }

/* Control chart (src/pages/validity.js): one series of values in order; centre, warning (2σ) and
   action (3σ) lines labelled at their right ends; a signal is a diamond with the rule beside it,
   in the status colour; an excluded value is a hollow ring left out of the line. */
.pm.cc .cc-line { fill: none; stroke: var(--s1); stroke-width: 2; stroke-linejoin: round; }
.pm.cc .cc-dot { fill: var(--s1); stroke: var(--chart-plot); stroke-width: 2; }
.pm.cc .cc-signal { fill: var(--bad); stroke: var(--chart-plot); stroke-width: 2; }
.pm.cc .cc-signal-label { fill: var(--text-1); font-size: 10.5px; font-weight: 700; paint-order: stroke; stroke: var(--chart-plot); stroke-width: 3px; }
.pm.cc .cc-excluded { fill: var(--chart-plot); stroke: var(--text-3); stroke-width: 1.6; }
.pm.cc .cc-centre { stroke: var(--text-2); stroke-width: 1.4; }
.pm.cc .cc-warn { stroke: var(--text-3); stroke-width: 1.2; stroke-dasharray: 6 4; }
.pm.cc .cc-action { stroke: var(--text-2); stroke-width: 1.4; stroke-dasharray: 2 3; }
.pm.cc .cc-line-label { fill: var(--text-2); font-size: 10.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.pm.cc .cc-cross { stroke: var(--chart-axis); stroke-width: 1; stroke-dasharray: 2 3; }
.ts-chip i.cc-key { width: 9px; height: 9px; border: 0; border-radius: 50%; background: var(--s1); }
.ts-chip i.cc-key.signal { border-radius: 1px; transform: rotate(45deg); background: var(--bad); }
.ts-chip i.cc-key.excluded { background: none; box-shadow: inset 0 0 0 1.6px var(--text-3); }

/* Uncertainty budget: each input's share of the combined variance as a bar (one hue, value in
   text ink beside it), above the GUM table that holds the numbers. */
.budget { display: grid; gap: 10px; padding: 12px; border-radius: var(--r-md); background: var(--surface-lo); box-shadow: inset 0 0 0 1px var(--line); }
.budget-bars { display: grid; gap: 6px; }
.budget-row { display: grid; grid-template-columns: minmax(7rem, 16rem) minmax(0, 1fr) 6.5rem; gap: 12px; align-items: center; }
.budget-row .bar-cell { display: block; height: 10px; }
.budget-row .bar { display: block; height: 10px; min-width: 2px; border-radius: 0 4px 4px 0; background: var(--data-1); }
.budget-row .num { text-align: right; font-variant-numeric: tabular-nums; }
@media (max-width: 640px) {
  .budget-row { grid-template-columns: minmax(0, 1fr) 6.5rem; grid-template-areas: 'label value' 'bar bar'; row-gap: 4px; }
  .budget-row > :nth-child(1) { grid-area: label; } .budget-row > :nth-child(2) { grid-area: bar; } .budget-row > :nth-child(3) { grid-area: value; }
}
.budget-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }
.budget-head .subhead { margin: 0 auto 0 0; }
.budget-head .select { width: auto; max-width: 100%; }

/* Setup › Correlated readings: one row per declared pair, wrapping on narrow screens. */
.corr-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
.corr-row .select { width: auto; flex: 1 1 13rem; max-width: 100%; }
.corr-row .corr-r { flex: 0 0 8.5rem; display: flex; align-items: center; gap: 8px; }
.corr-row .corr-r > :last-child { flex: 1; min-width: 0; }
.corr-row .corr-basis { flex: 2 1 16rem; min-width: 0; }
.ts-chip i.pm-key.env { width: 14px; height: 10px; border-radius: 2px; background: var(--chart-envelope); border: 1.4px dashed var(--chart-envelope-line); }
