/* ============================================================
   Workshop — machine list + exploded-view canvas + diag/test panels.
   ============================================================ */
.workshop-stage {
  position: relative;
}

/* Workshop Radio upgrade cosmetic — a small "Now playing" line under
   the workshop subtitle. Purely decorative. */
.workshop-radio {
  margin-top: 8px;
  padding: 4px 10px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(120, 200, 255, 0.10);
  border: 1px solid rgba(120, 200, 255, 0.30);
  border-radius: 999px;
  font-size: 11px;
  color: var(--text-dim);
}
.workshop-radio em { color: var(--text); font-style: italic; }
/* Empty-state variant: no machine pane to give the stage height, so the
   stage takes its height from this min so the wall + tumbleweed have a
   place to live. */
.workshop-stage-empty {
  min-height: 560px;
  border-radius: var(--r-lg);
  overflow: hidden;
}
.workshop-empty-foreground {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  overflow: hidden;
}
.workshop-empty-foreground .empty-state-card {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: rgba(20, 28, 38, 0.85);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: 0 8px 24px rgba(0,0,0,0.5);
  max-width: 460px;
  backdrop-filter: blur(2px);
}
.workshop-empty-foreground .esc-icon {
  display: inline-flex;
  color: var(--accent);
  flex: 0 0 auto;
}
.workshop-empty-foreground .esc-text { flex: 1 1 auto; }
.workshop-empty-foreground .esc-msg {
  font-weight: var(--fw-bold);
  font-size: var(--fs-md);
  color: var(--text);
}

/* Tumbleweed — rolls across the empty workshop floor every ~14s.
   Sits idle off-screen for most of the cycle; bottom 18% places it
   on the "floor" line of the workshop scene. */
.workshop-empty-foreground .tumbleweed {
  position: absolute;
  bottom: 18%;
  left: -60px;
  width: 38px;
  height: 38px;
  pointer-events: none;
  opacity: 0;
  animation: tumbleweed-roll 14s linear infinite;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45));
}
@keyframes tumbleweed-roll {
  0%, 62%  { transform: translateX(0)      rotate(0deg);    opacity: 0; }
  64%      { transform: translateX(0)      rotate(0deg);    opacity: 0.9; }
  96%      { transform: translateX(120vw)  rotate(1620deg); opacity: 0.9; }
  100%     { transform: translateX(120vw)  rotate(1620deg); opacity: 0; }
}
.workshop-wall {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  border-radius: var(--r-lg);
}
.workshop-wall .wall-back {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, #2a3340 0%, #3a4654 60%, #475360 100%);
}
.workshop-layout {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: var(--sp-3);
}

/* ===== Test-pass burst (Phase 2 P2.3) =====================
   Fires once when a machine first crosses the pass threshold.
   Big green ✓ scales in over the canvas center, holds, fades. */
.test-pass-tick {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0);
  font-size: 96px;
  font-weight: 900;
  color: var(--good);
  text-shadow: 0 0 18px rgba(76, 175, 80, 0.7),
               0 4px 12px rgba(0, 0, 0, 0.5);
  pointer-events: none;
  z-index: 30;
  animation: testPassTick 1.1s var(--ease-bounce, cubic-bezier(.34,1.56,.64,1)) forwards;
}
@keyframes testPassTick {
  0%   { transform: translate(-50%, -50%) scale(0)   rotate(-20deg); opacity: 0; }
  25%  { transform: translate(-50%, -50%) scale(1.3) rotate(  5deg); opacity: 1; }
  50%  { transform: translate(-50%, -50%) scale(1.0) rotate(  0deg); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(0.9) rotate(  0deg); opacity: 0; }
}

/* Machine tab green-border flash when its machine first passes. */
.machine-tab.test-pass-flash {
  animation: tabPassFlash 0.9s var(--ease-snap, ease-out);
}
@keyframes tabPassFlash {
  0%, 100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); }
  30%      { box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.55),
                         0 0 14px rgba(76, 175, 80, 0.45); }
}

/* Test-pass particle uses the celebrate-particle base but in green. */
.celebrate-particle.test-pass-particle {
  color: var(--good);
  text-shadow: 0 0 6px rgba(76, 175, 80, 0.7),
               0 1px 2px rgba(0, 0, 0, 0.5);
}

/* Frame that flanks the exploded view with workbench rails on each
   side. The rails are inside the work area (machine-pane) so they
   frame the CPAP rather than push the outer layout. */
.exploded-frame {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: var(--sp-2);
}
.exploded-frame > .exploded-view {
  flex: 1 1 auto;
  min-width: 0;
}
.exploded-frame > .wall-rail {
  flex: 0 0 64px;
}

/* ===== Outer rails (relocated wall content) ===== */
/* Both rails share the workbench gradient backdrop + a faint horizontal
   grain so they read as the same wall as before, just surfaced. */
.wall-rail {
  position: relative;
  z-index: 1;
  background: linear-gradient(180deg, #2a3340 0%, #3a4654 60%, #475360 100%);
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: inset 0 2px 8px rgba(0,0,0,0.4);
  padding: 8px 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.wall-rail::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    repeating-linear-gradient(0deg, transparent 0 18px, rgba(0,0,0,0.04) 18px 19px),
    radial-gradient(ellipse at top, rgba(255,255,255,0.04), transparent 60%);
  pointer-events: none;
}
/* Shelf dividers — same aesthetic as the original .shelf-rail but
   relative to the rail flex flow rather than absolute-positioned. */
.wall-rail .rail-shelf {
  width: calc(100% - 8px);
  height: 4px;
  background: linear-gradient(180deg, #76808e, #4a5664);
  border-top: 1px solid #cbd4e1;
  box-shadow: 0 3px 8px rgba(0,0,0,0.5);
  border-radius: 1px;
  margin: 4px 0;
  flex-shrink: 0;
}
/* LEFT rail content */
.wall-rail .rail-bins {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  align-items: center;
}
.wall-rail .rail-bins .parts-bin {
  /* Compact bin sized for the 64px rail width (less 8px padding × 2). */
  width: 48px;
  height: 32px;
  background: linear-gradient(180deg, #cbd4e1, #a8b0bc);
  border: 1px solid #5a6470;
  border-radius: 3px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.4);
  position: relative;
  overflow: hidden;
}
.wall-rail .rail-bins .parts-bin .bin-label {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 11px;
  background: var(--bin-color, #4a5664);
  color: #fff;
  font-size: 7px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.5px;
  text-align: center;
  line-height: 11px;
  font-family: "Segoe UI", sans-serif;
  text-shadow: 0 1px 1px rgba(0,0,0,0.4);
}
.wall-rail .rail-bins .parts-bin .bin-icon {
  position: absolute;
  top: 11px; left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #2a3340;
}
.wall-rail .rail-tools {
  position: relative;
  width: 52px;
  height: 90px;
  margin-top: 4px;
  flex-shrink: 0;
}
.wall-rail .rail-tools .tool {
  position: absolute;
  background: linear-gradient(90deg, #5a6470, #76808e);
  width: 4px;
  border-radius: 2px;
  box-shadow: 1px 0 2px rgba(0,0,0,0.4);
}
.wall-rail .rail-tools .tool.handle::after {
  content: "";
  position: absolute;
  top: 100%;
  left: -3px;
  width: 10px;
  height: 6px;
  background: #8b6f47;
  border-radius: 1px;
}
/* RIGHT rail: mini-instruments. The originals (oscope 110px, mmeter
   78px) are too wide for a 64px rail; these are tailored versions that
   preserve the look-and-feel (green CRT trace, amber LCD, pulsing freq
   display, animated bar meter) at rail width. */
.wall-rail .mini-instr {
  position: relative;
  flex-shrink: 0;
}
.mini-oscope {
  width: 52px; height: 36px;
  background: #0a1419;
  border: 2px solid #76808e;
  border-radius: 3px;
  box-shadow: inset 0 0 6px rgba(0,0,0,0.6);
  overflow: hidden;
}
.mini-oscope::before {
  content: ""; position: absolute; inset: 2px;
  background-image:
    linear-gradient(rgba(76, 217, 100, 0.10) 1px, transparent 1px),
    linear-gradient(90deg, rgba(76, 217, 100, 0.10) 1px, transparent 1px);
  background-size: 8px 8px;
}
.mini-oscope svg {
  position: absolute; inset: 0;
  width: 200%; height: 100%;
  animation: mini-scope-scroll 2.4s linear infinite;
}
.mini-oscope .mini-trace {
  fill: none;
  stroke: #4cd964;
  stroke-width: 1.2;
  filter: drop-shadow(0 0 2px #4cd964);
}
@keyframes mini-scope-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

.mini-mm {
  width: 52px; height: 34px;
  background: linear-gradient(180deg, #c2622a, #8e3f15);
  border: 2px solid #2d1a0a;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.15);
  display: flex; align-items: center; justify-content: center;
}
.mini-mm-lcd {
  background: #2d2a1a;
  color: #f0c63a;
  font-family: "Courier New", monospace;
  font-size: 11px;
  font-weight: bold;
  padding: 2px 5px;
  border-radius: 2px;
  border: 1px solid #6a5818;
  text-shadow: 0 0 3px #f0c63a;
  letter-spacing: 0.5px;
  min-width: 30px;
  text-align: right;
  animation: mini-mm-cycle 2.5s steps(1, end) infinite;
}
.mini-mm-lcd::before { content: "24.0"; }
@keyframes mini-mm-cycle {
  0%, 100% { color: #f0c63a; text-shadow: 0 0 3px #f0c63a; }
  50%      { color: #ffe070; text-shadow: 0 0 4px #f0c63a; }
}

.mini-freq {
  width: 52px; height: 28px;
  background: #1a2128;
  border: 2px solid #76808e;
  border-radius: 3px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  box-shadow: inset 0 0 4px rgba(0,0,0,0.6);
}
.mini-freq-label {
  color: #76808e;
  font-size: 7px;
  font-weight: bold;
  letter-spacing: 1px;
}
.mini-freq-display {
  color: #4cd964;
  font-family: "Courier New", monospace;
  font-size: 10px;
  font-weight: bold;
  text-shadow: 0 0 3px #4cd964;
  animation: mini-freq-pulse 1.4s ease-in-out infinite;
}
.mini-freq-display::before { content: "440"; }
@keyframes mini-freq-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.6; }
}

.mini-bargraph {
  width: 52px; height: 34px;
  background: #1a2128;
  border: 2px solid #76808e;
  border-radius: 3px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 2px;
  padding: 3px;
  box-shadow: inset 0 0 4px rgba(0,0,0,0.6);
}
.mini-bargraph .mini-bar {
  flex: 1;
  background: linear-gradient(180deg, #4cd964, #2a8a3a);
  border-radius: 0.5px;
  animation: mini-bar-pulse 0.9s ease-in-out infinite;
}
.mini-bargraph .mini-bar:nth-child(1) { animation-delay: 0.00s; }
.mini-bargraph .mini-bar:nth-child(2) { animation-delay: 0.12s; }
.mini-bargraph .mini-bar:nth-child(3) { animation-delay: 0.24s; }
.mini-bargraph .mini-bar:nth-child(4) { animation-delay: 0.36s; }
.mini-bargraph .mini-bar:nth-child(5) { animation-delay: 0.48s; }
.mini-bargraph .mini-bar:nth-child(6) { animation-delay: 0.60s; }
@keyframes mini-bar-pulse {
  0%, 100% { height: 30%; }
  50%      { height: 90%; }
}
.machine-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  /* When the workshop fills up (10-machine cap is now reachable), the list
     used to push the rest of the workshop off-screen, forcing the player to
     scroll the entire window to reach the bench. Cap the list height to the
     viewport (minus topbar + panel header + a little padding) and let it
     scroll independently. The `overscroll-behavior: contain` keeps wheel
     scroll from bleeding back into the page once the list reaches its
     edges. */
  max-height: calc(100vh - 200px);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Reserve a couple pixels so the scrollbar doesn't pinch the active-tab
     accent border when it appears. */
  padding-right: 2px;
}
.machine-tab {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.machine-tab:hover { background: var(--bg-card-hover); }
.machine-tab.active {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(79,195,247,0.18);
}
.mt-name {
  font-weight: var(--fw-semi);
  font-size: var(--fs-sm);
}
.mt-stage { margin-top: var(--sp-1); }
.mt-id {
  display: inline-block;
  margin-left: 6px;
  font-size: 0.85em;
  font-weight: 500;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
}

/* Machine-tab badge strip (WR4) — small flex row of status chips
   under the stage badge. Each chip is colour-coded by severity. */
.mt-badges {
  margin-top: 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.mt-badge {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 1px 6px;
  font-size: 10px;
  font-weight: var(--fw-semi);
  border-radius: 3px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  color: var(--text-dim);
  cursor: help;
}
.mt-faults    { background: rgba(232, 80, 58, 0.18); border-color: rgba(232, 80, 58, 0.55); color: #f0b4a8; }
.mt-score.ok  { background: rgba(76, 175, 80, 0.18); border-color: rgba(76, 175, 80, 0.55); color: #bce4be; }
.mt-score.warn{ background: rgba(255, 179, 0, 0.18); border-color: rgba(255, 179, 0, 0.55); color: #ffe0a3; }
.mt-collector { background: rgba(255, 200, 80, 0.18); border-color: rgba(255, 200, 80, 0.55); color: #ffe0a3; }
.mt-cleaning  { background: rgba(120, 200, 255, 0.16); border-color: rgba(120, 200, 255, 0.45); color: #c8e6ff; }
.mt-days.ok   { background: rgba(155, 172, 193, 0.18); border-color: rgba(155, 172, 193, 0.45); color: #c0d0e0; }
.mt-days.warn { background: rgba(255, 179, 0, 0.18); border-color: rgba(255, 179, 0, 0.55); color: #ffe0a3; }
/* "Newly Returned" marker — amber pill with a gentle pulse so a machine
   fresh back from a rental is easy to spot in the workshop list. */
.mt-returned {
  margin-top: var(--sp-1);
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--fs-micro);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: #3a2a05;
  background: var(--warn);
  border-radius: 999px;
  padding: 2px 7px;
  animation: mtReturnedPulse 1.8s ease-in-out infinite;
}
@keyframes mtReturnedPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 170, 58, 0); }
  50%      { box-shadow: 0 0 0 4px rgba(255, 170, 58, 0.30); }
}

.machine-pane {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
}
.md-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: var(--sp-3);
  gap: var(--sp-3);
}
.md-title {
  font-weight: var(--fw-bold);
  font-size: var(--fs-lg);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.machine-detail { border-top: 3px solid var(--brand-color, transparent); padding-top: var(--sp-2); margin-top: calc(-1 * var(--sp-2)); }
.md-meta {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  margin-top: 3px;
}
/* Machine-provenance 2-line history shown under .md-meta. */
.md-history {
  font-size: var(--fs-xs);
  color: var(--text-dim);
  margin-top: 4px;
  line-height: 1.35;
}
.md-history em {
  display: block;
  font-style: italic;
}
/* Returned-rental feedback shown in the header history line, tinted by
   sentiment: complaint = red, grumble = amber, praise = green. */
.md-history em.md-complaint {
  color: var(--bad);
  font-weight: var(--fw-semi);
}
.md-history em.md-grumble {
  color: var(--warn);
  font-weight: var(--fw-semi);
}
.md-history em.md-praise {
  color: var(--good);
  font-weight: var(--fw-semi);
}
.md-actions {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.md-actions button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.md-actions svg { width: 14px; height: 14px; }

/* ---------- workbench surface ---------- */
.machine-canvas {
  position: relative;
  background: linear-gradient(180deg, var(--bg-bench), var(--bg-bench-2));
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  border: 1px solid var(--border-bench);
  position: relative;
  min-height: 440px;
  margin-bottom: var(--sp-3);
  box-shadow: inset 0 2px 10px rgba(0,0,0,0.08);
  background-image:
    repeating-linear-gradient(0deg,  transparent 0px, transparent 22px, rgba(0,0,0,0.04) 22px, rgba(0,0,0,0.04) 23px),
    repeating-linear-gradient(90deg, transparent 0px, transparent 22px, rgba(0,0,0,0.04) 22px, rgba(0,0,0,0.04) 23px),
    linear-gradient(180deg, var(--bg-bench), var(--bg-bench-2));
  transition: filter var(--dur-slow) var(--ease);
  overflow: hidden;
}
.machine-canvas.testing {
  animation: testpulse 700ms ease;
}
@keyframes testpulse {
  0%   { box-shadow: inset 0 2px 10px rgba(0,0,0,0.08); }
  50%  { box-shadow: inset 0 0 30px rgba(79,195,247,0.6); }
  100% { box-shadow: inset 0 2px 10px rgba(0,0,0,0.08); }
}
/* Bench clunk — a 1-2px shudder when a part seats home. Subtle and
   physical, deliberately NOT a screen-shake. */
.machine-canvas.seat-shudder { animation: seatShudder 260ms ease; }
@keyframes seatShudder {
  0%, 100% { transform: translate(0, 0); }
  25%      { transform: translate(0, 1.6px); }
  55%      { transform: translate(-1.1px, 0.6px); }
  80%      { transform: translate(0.6px, 0); }
}

/* ---------- exploded view container ---------- */
.exploded-view {
  position: relative;
  width: 100%;
  aspect-ratio: 800 / 450;
  max-width: 920px;
  margin: 0 auto;
}
.exploded-view svg.cpap-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}
.exploded-view svg.cpap-svg .cal-anchor { pointer-events: auto; }
.exploded-view svg.cpap-svg .lead-line {
  fill: none;
  stroke-width: 1.6;
  pointer-events: none;
}
.exploded-view svg.cpap-svg .lead-line.empty {
  stroke: var(--lead-empty);
  stroke-dasharray: 4 5;
  animation: leadShimmer 1.6s linear infinite;
}
.exploded-view svg.cpap-svg .lead-line.healthy {
  stroke: var(--lead-healthy);
  stroke-dasharray: 3 4;
  opacity: 0.85;
}
.exploded-view svg.cpap-svg .lead-line.damaged {
  stroke: var(--lead-damaged);
  stroke-width: 2;
  animation: leadDamagedPulse 1.2s ease-in-out infinite;
}
.exploded-view svg.cpap-svg .lead-line.untested {
  stroke: var(--lead-untested);
  stroke-dasharray: 2 4;
}
@keyframes leadShimmer {
  to { stroke-dashoffset: -36; }
}
@keyframes leadDamagedPulse {
  0%, 100% { opacity: 0.7; }
  50%      { opacity: 1; }
}

/* CPAP illustration parts (stroke + fill via CSS-friendly classes) */
/* Restrained "living machine" idle — a slow, barely-there breath so the
   unit on the bench reads as powered-on rather than inert. ~0.6% scale. */
.exploded-view svg.cpap-svg .cpap-housing {
  transition: opacity var(--dur-slow) var(--ease);
  transform-box: fill-box;
  transform-origin: center;
  animation: machineBreathe 6.5s ease-in-out infinite;
}
@keyframes machineBreathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.006); }
}
.exploded-view svg.cpap-svg .cpap-shadow  { fill: rgba(0,0,0,0.10); }
.exploded-view svg.cpap-svg .cpap-shell   { fill: #d8d2bc; stroke: #8d8260; stroke-width: 2; stroke-linejoin: round; }
.exploded-view svg.cpap-svg .cpap-shell-light { fill: #e5dec6; }
.exploded-view svg.cpap-svg .cpap-shell-dark  { fill: #c4bda1; stroke: #8d8260; stroke-width: 1.5; }
.exploded-view svg.cpap-svg .cpap-screen-bg { fill: #2a3645; }
.exploded-view svg.cpap-svg .cpap-screen-text { fill: var(--accent); font-size: 22px; letter-spacing: 2px; font-weight: 700; font-family: "Segoe UI", system-ui, sans-serif; }
.exploded-view svg.cpap-svg .cpap-port      { fill: #b8ad88; stroke: #8d8260; stroke-width: 1.5; }
.exploded-view svg.cpap-svg .cpap-port-dark { fill: #6e6440; }
.exploded-view svg.cpap-svg .cpap-vent-line { stroke: #8d8260; stroke-width: 1.2; }
.exploded-view svg.cpap-svg .cpap-label     { fill: #6e6440; font-size: 16px; font-weight: 600; font-family: "Segoe UI", system-ui, sans-serif; }

/* ---------- recessed sockets (one per slot, behind the cards) ----------
   A faint inset well so a seated part-card reads as dropped INTO the
   bench rather than floating on it. Sits below the cards (z-index 1). */
.part-socket {
  position: absolute;
  width: clamp(100px, 14%, 150px);
  height: 56px;
  transform: translate(-50%, -50%) scale(1.12);
  border-radius: var(--r-md);
  background: rgba(60, 52, 32, 0.16);
  box-shadow:
    inset 0 2px 6px rgba(0, 0, 0, 0.34),
    inset 0 -1px 0 rgba(255, 255, 255, 0.20);
  z-index: 1;
  pointer-events: none;
}

/* ---------- floating part cards (exploded positions) ---------- */
.part-card {
  position: absolute;
  width: clamp(100px, 14%, 150px);
  background: rgba(255,255,255,0.92);
  border: 2px solid var(--border-bench);
  border-radius: var(--r-md);
  padding: var(--sp-1) 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  color: var(--text-bench);
  font-size: var(--fs-xs);
  transform: translate(-50%, -50%);
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
  z-index: 2;
  pointer-events: auto;
}
.part-card:hover {
  transform: translate(-50%, -50%) translateY(-2px);
  box-shadow: 0 5px 14px rgba(0,0,0,0.14);
  z-index: 3;
}
.part-card.ok      { border-color: var(--good); background: rgba(195, 232, 195, 0.78); }
.part-card.new     { border-color: var(--accent-2); background: rgba(195, 226, 246, 0.86); }
/* Quality band states — surface visible wear at-a-glance so a returned
   rental or aging build doesn't read as "all healthy". Thresholds:
     aging  → q < 80%    (amber, showing wear, still functional)
     worn   → q < 50/70% (red-tinted, replace-or-clean territory) */
.part-card.aging   { border-color: #d6a13e; background: rgba(245, 220, 155, 0.78); }
.part-card.worn    {
  border-color: #c25a3e;
  background: rgba(245, 195, 165, 0.78);
  /* a calm "needs attention" pulse — far gentler than broken-known's badpulse */
  animation: wornAttention 2.8s ease-in-out infinite;
}
.part-card.empty   { border-color: var(--border-bench); background: rgba(255,255,255,0.7); border-style: dashed; }
.part-card.broken-known {
  border-color: var(--bad);
  background: rgba(245, 195, 185, 0.82);
  animation: badpulse 1.6s ease-in-out infinite;
}
.part-card.uninspected {
  border-color: #8d8260;
  background: rgba(220, 200, 160, 0.7);
}
.part-card.drag-hover {
  border-color: var(--accent);
  background: rgba(185, 220, 240, 0.9);
  box-shadow: 0 0 0 3px rgba(79,195,247,0.35);
}
.part-card.invalid-drop {
  opacity: 0.35;
  filter: grayscale(0.6);
}
.part-card.shake { animation: shake 0.3s ease; }

/* ===== Drag-active animation pause =====================================
   While a part is being dragged from inventory, the HTML5 drag-preview
   spins up its own compositor layer. Combined with the workshop's many
   concurrent infinite animations (lead-line shimmer, badpulse,
   machineBreathe, mini-instrument cycles, fit-glow on every fitting
   chip, postit sway on the job board, …) this has been observed to
   exhaust Chrome's GPU process and crash the tab outright (no "Aw Snap"
   — the tab just exits). See repair.js:269 + ui.js dragstart.

   `body.drag-active` is set in ui.js on dragstart and cleared on
   dragend / visibilitychange. Pausing animations is cheap and fully
   reversible; the player is focused on the drag ghost so they don't
   notice the freeze. Keep this list aligned with grep
   "animation:.*infinite" across css/ — every infinite animation that
   sits IN the workshop viewport while a drag could be in flight should
   be paused here. */
body.drag-active .lead-line,
body.drag-active .part-card,
body.drag-active .part-socket,
body.drag-active .cpap-svg,
body.drag-active .mini-scope-trace,
body.drag-active .mini-mm-value,
body.drag-active .mini-freq-bars,
body.drag-active .mini-bar,
body.drag-active .scope-trace,
body.drag-active .mmeter-value,
body.drag-active .led-dot,
body.drag-active .needle,
body.drag-active .trace-value,
body.drag-active .trace-line,
body.drag-active .trace-dot,
body.drag-active .machine-tab.returned,
body.drag-active .fit-glow,
body.drag-active .postit,
body.drag-active .pin-counter,
body.drag-active .tumbleweed {
  animation-play-state: paused !important;
}
/* Tactile install — a fresh part is dropped in: it falls, clunks into
   the socket, overshoots, rebounds and settles. Has heft over a pop. */
.part-card.seating { animation: seatDrop 0.62s both; z-index: 4; }
.seat-dust {
  position: absolute;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: radial-gradient(circle, #c4b78f, #8a7d56);
  pointer-events: none;
  z-index: 4;
}

.part-card .brand-tag {
  position: absolute;
  top: -9px;
  left: 3px;
  font-size: 9px;
  padding: 1px 4px;
  min-width: 22px;
  height: 13px;
  border-radius: 3px;
  z-index: 2;
  /* The toolbar (on hover) and the brand-tag both float above the card's
     top edge. On narrow cards they overlap visually. Fade the brand-tag
     out on hover so the action buttons own that real estate — the brand
     identity is already shown in the tooltip + card status line. */
  transition: opacity var(--dur-fast) var(--ease);
}
.part-card:hover .brand-tag {
  opacity: 0;
  pointer-events: none;
}

@keyframes badpulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(232,80,58,0); }
  50%      { box-shadow: 0 0 0 4px rgba(232,80,58,0.30); }
}
@keyframes shake {
  0%, 100% { transform: translate(-50%, -50%) translateX(0); }
  25%      { transform: translate(-50%, -50%) translateX(-5px); }
  75%      { transform: translate(-50%, -50%) translateX(5px); }
}
@keyframes seatDrop {
  0%   { transform: translate(-50%, -50%) translateY(-66px) scale(1.05); opacity: 0.5;
         animation-timing-function: cubic-bezier(.42, 0, .78, .16); }   /* heavy fall */
  46%  { transform: translate(-50%, -50%) translateY(8px)   scale(.97);
         animation-timing-function: cubic-bezier(.34, .55, .5, 1); }    /* clunk home */
  64%  { transform: translate(-50%, -50%) translateY(-5px)  scale(1.02); }   /* rebound */
  82%  { transform: translate(-50%, -50%) translateY(2px)   scale(.997); }
  100% { transform: translate(-50%, -50%) translateY(0)     scale(1); }
}
@keyframes wornAttention {
  0%, 100% { box-shadow: 0 0 0 0 rgba(194, 90, 62, 0); }
  50%      { box-shadow: 0 0 0 4px rgba(194, 90, 62, 0.15); }
}

.pc-head {
  display: flex;
  align-items: center;
  gap: 5px;
  width: 100%;
}
.pc-icon {
  width: 22px;
  height: 22px;
  border-radius: var(--r-sm);
  background: rgba(255,255,255,0.5);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-bench);
  flex-shrink: 0;
}
.pc-icon svg { width: 14px; height: 14px; }
.pc-name {
  flex: 1;
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-align: left;
  line-height: 1.1;
  /* Wrap to at most two lines — part names like "Tubing Connector"
     don't fit the narrow card on a single line. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Allow long single words like "Mainboard" or "Humidifier" to break
     mid-word when the card is narrower than the word. Without this the
     line-clamp can't wrap a single word and the text gets clipped at
     the right edge of the card. */
  word-break: break-word;
  overflow-wrap: anywhere;
}
.pc-status {
  font-size: 10px;
  color: var(--text-bench);
  margin-top: 2px;
  width: 100%;
  text-align: left;
  opacity: 0.85;
}
.pc-toolbar {
  position: absolute;
  top: -10px;
  right: -6px;
  display: flex;
  gap: 3px;
  opacity: 0;
  transform: translateY(-2px);
  transition: opacity var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
  pointer-events: none;
  /* Sit above the brand-tag (z:2) so the action buttons are always on
     top. The brand-tag also fades to 0 on .part-card:hover (see above)
     so the two never compete visually when the player is interacting
     with the toolbar. */
  z-index: 5;
}
.part-card:hover .pc-toolbar {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.pc-toolbar button {
  padding: 3px 6px;
  font-size: 10px;
  font-weight: var(--fw-bold);
  background: var(--bg-panel);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.pc-toolbar button:hover { background: var(--bg-card-hover); }
.pc-toolbar svg { width: 11px; height: 11px; }

/* Clean-mode picker (WR2) — a small popover anchored to the Sparkle
   button when clicked. Lists the three cleaning modes with their
   time / cost trade-offs. Outside-click closes. */
.clean-mode-picker {
  background: var(--bg-panel);
  border: 1px solid var(--accent, #4fc3f7);
  border-radius: var(--r-md);
  padding: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.55);
  min-width: 180px;
}
.clean-mode-picker[hidden] { display: none; }
.clean-mode-picker .cmp-title {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  margin-bottom: 6px;
}
.clean-mode-picker .cmp-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  padding: 6px 10px;
  margin-bottom: 4px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text);
  cursor: pointer;
  text-align: left;
}
.clean-mode-picker .cmp-btn:last-child { margin-bottom: 0; }
.clean-mode-picker .cmp-btn:hover {
  border-color: var(--accent);
  background: var(--bg-card-hover);
}
.clean-mode-picker .cmp-name { font-weight: var(--fw-semi); font-size: var(--fs-sm); }
.clean-mode-picker .cmp-meta { font-size: var(--fs-xs, 11px); color: var(--text-dim); }

/* Right-click context menu on slot cards (Inspect/Clean/Detach/Discard). */
.slot-context-menu {
  position: fixed;
  z-index: 200;
  min-width: 160px;
  padding: 4px;
  background: var(--bg-elev, var(--bg-card));
  border: 1px solid var(--border-strong, var(--border));
  border-radius: var(--r-sm);
  box-shadow: 0 6px 18px rgba(0,0,0,0.25);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.slot-context-menu[hidden] { display: none; }
.slot-context-menu .scm-item {
  appearance: none;
  background: transparent;
  border: 0;
  border-radius: 4px;
  padding: 6px 10px;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--text);
  text-align: left;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
}
.slot-context-menu .scm-item:hover:not(:disabled) {
  background: var(--bg-card-hover, rgba(255,255,255,0.06));
}
.slot-context-menu .scm-item:disabled {
  color: var(--text-dim);
  cursor: not-allowed;
  opacity: 0.55;
}

/* "Strip parts first" checkbox sitting next to the Scrap button. */
.scrap-strip-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-right: var(--sp-2);
  padding: 4px 8px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: var(--fs-xs, 11px);
  color: var(--text-dim);
  cursor: pointer;
  user-select: none;
}
.scrap-strip-toggle:hover { color: var(--text); border-color: var(--border-strong); }
.scrap-strip-toggle input { margin: 0; }

/* ---------- legend ---------- */
.legend {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-3);
  /* Nudge down 25px below the exploded-view + bump up one size step
     (xs → sm) so the legend reads as a real footer line, not a tiny
     caption riding up against the bottom-row cards. */
  margin-top: calc(var(--sp-2) + 25px);
  font-size: var(--fs-sm);
  color: var(--text-bench);
}
.leg-item { display: flex; align-items: center; gap: 5px; }
.leg-dot {
  width: 10px; height: 10px; border-radius: 50%;
  display: inline-block;
  border: 2px solid;
}
.leg-dot.good  { background: rgba(180,230,180,0.6);  border-color: var(--good); }
.leg-dot.bad   { background: rgba(245,180,170,0.6);  border-color: var(--bad); }
.leg-dot.empty { background: rgba(255,255,255,0.5);  border-color: var(--border-bench); }

/* ---------- diagnostics + test panels ---------- */
.diag-panel {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-3);
}
.diag-col {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
}
.diag-col h4 {
  margin: 0 0 var(--sp-2) 0;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-dim);
  font-weight: var(--fw-semi);
}
.known-symptoms { font-size: var(--fs-sm); }
.symptom { padding: 2px 0; }
.symptom.warn { color: var(--warn); }
.symptom.dim  { color: var(--text-dim); }

.test-results {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.tb-row {
  display: grid;
  grid-template-columns: 100px 1fr 40px;
  gap: var(--sp-2);
  align-items: center;
  font-size: var(--fs-sm);
}
.tb-label { color: var(--text-dim); }
.tb-track {
  background: rgba(0,0,0,0.3);
  border-radius: 3px;
  overflow: hidden;
  height: 8px;
}
.tb-fill {
  height: 100%;
  transition: width 520ms var(--ease);
}
.tb-fill.good { background: var(--good); }
.tb-fill.ok   { background: var(--ok); }
.tb-fill.bad  { background: var(--bad); }
.tb-pct {
  font-weight: var(--fw-semi);
  text-align: right;
}
.overall {
  margin-top: var(--sp-1);
  font-size: var(--fs-md);
}
.slot-breakdown {
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px dashed var(--border);
}
.sb-title {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semi);
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: var(--sp-1);
}
.tb-row.with-readout {
  display: grid;
  grid-template-columns: 1fr 80px 40px 80px;
  gap: var(--sp-2);
  align-items: center;
}
/* Calibrator waveform — small SVG appended to the Pressure row when
   the upgrade is owned. Wave amplitude tracks the measurement value
   (lower scores → wobblier line). */
.tb-row.with-addon {
  grid-template-columns: 100px 1fr 40px 90px;
}
.tb-waveform {
  color: var(--accent);
  opacity: 0.75;
  /* Shrink the waveform on narrow viewports so the test row doesn't
     overflow horizontally. preserveAspectRatio="none" on the SVG itself
     lets it squash gracefully when the column is tight. */
  max-width: 100%;
  width: 90px;
  height: auto;
}
@media (max-width: 720px) {
  .tb-row.with-addon { grid-template-columns: 90px 1fr 36px 60px; }
  .tb-waveform { width: 60px; }
}

/* ---------- Narrow-screen workshop responsiveness ----------
   The action toolbar lives at top-right of each part card (default).
   Brand-tag fades on hover so the two never compete visually, and
   the toolbar z-index ensures it's always on top.
     - ≤1500px: hide the decorative left + right outer rails (64px
       each = 128px reclaimed). The bins / tools are pure flavour
       and small-to-mid monitors need the breathing room more. ALSO
       add horizontal padding to the .exploded-view so part cards
       anchored near the edges (left:5% / right:5%) don't spill off
       the visible viewport.
     - ≤1100px: shrink card + tighter padding + smaller text + tighter
       toolbar buttons so the action cluster fits the narrower top
       edge without bleeding into neighbour cards.
     - ≤900px: cards shrink further; toolbar buttons tighter still.
*/
@media (max-width: 1500px) {
  .exploded-frame > .wall-rail { display: none; }
  /* Safe-zone padding so cards anchored near the edges stay inside the
     visible workshop. The SVG machine art continues to fill the inner
     area (it absolute-positions to the padding-box edges). */
  .exploded-frame > .exploded-view {
    padding: 0 64px;
    box-sizing: border-box;
  }
  /* Shrink cards across the whole small-monitor range so they don't
     overlap each other. Anchor positions in the artwork data are fixed
     %-of-view, so on a narrow workshop two cards anchored ~15% apart
     can collide if cards are at their original 130-150px width. Smaller
     cards = wider effective gaps between centres. */
  .part-card {
    width: clamp(88px, 11%, 120px);
    padding: 4px 5px;
  }
  .part-card .pc-name { font-size: 10px; }
  .part-card .pc-status { font-size: 9px; }
  .pc-icon { width: 18px; height: 18px; }
  .pc-icon svg { width: 12px; height: 12px; }
  .pc-toolbar button { padding: 2px 6px; font-size: 10px; }
  .pc-toolbar svg { width: 10px; height: 10px; }
  /* Brand-tag stays at its original top-LEFT-above-the-card position
     (top: -6px; left: 6px from the base rule). At small viewports we
     just shrink it a little. Card-to-card overlap is prevented by the
     radial-spread (explodePositions in repair.js) plus the hover-fade
     on the chip — the chip never competes with the toolbar buttons
     even when neighbour cards are close. */
  .part-card .brand-tag {
    font-size: 8px;
    padding: 0 3px;
    min-width: 18px;
    height: 11px;
    line-height: 11px;
  }
}
@media (max-width: 1100px) {
  /* Tighter still — anchor positions can cluster within ~12% at this
     viewport, so cap card width below that to keep them clear. */
  .part-card { width: clamp(78px, 10%, 105px); }
}
@media (max-width: 900px) {
  .part-card {
    width: clamp(68px, 9%, 92px);
    padding: 3px 4px;
  }
  .part-card .pc-name { font-size: 9px; }
  .part-card .pc-status { font-size: 8px; }
  .pc-icon { width: 16px; height: 16px; }
  .pc-icon svg { width: 11px; height: 11px; }
  .pc-toolbar button { padding: 2px 5px; font-size: 9px; }
  .pc-toolbar svg { width: 9px; height: 9px; }
  /* Tighter safe-zone padding on very small screens so the machine art
     doesn't lose too much of its footprint. */
  .exploded-frame > .exploded-view {
    padding: 0 40px;
  }
}
/* Avg-of-3 button — inline next to the Overall score. Re-runs the
   test 3× silently and averages the rows. */
.tb-avg-toggle {
  margin-left: var(--sp-2);
  padding: 3px 10px;
  font-size: var(--fs-xs, 11px);
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text-dim);
  cursor: pointer;
}
.tb-avg-toggle:hover { color: var(--text); border-color: var(--accent); }
.tb-readout {
  font-family: "Courier New", monospace;
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--accent);
  text-align: right;
  text-shadow: 0 0 4px rgba(79,195,247,0.45);
}

/* ---------- calibration mode ---------- */
/* Calibrate mode wins by specificity (0,3,0 beats the state rules at 0,2,0) — no !important needed. */
.exploded-view.calibrate .part-card {
  border-color: var(--premium);
  cursor: move;
}
.exploded-view.calibrate .cal-anchor {
  fill: var(--premium);
  stroke: white;
  stroke-width: 2;
  cursor: move;
  pointer-events: auto;
}
.cal-banner {
  position: absolute;
  top: 8px;
  left: 8px;
  background: var(--premium);
  color: white;
  padding: 4px 10px;
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  z-index: 10;
}

/* (Dead rules removed in commit cleaning up the outer-rail refactor:
   the wall is now a pure gradient backdrop, with bins / instruments /
   tools relocated to the .exploded-frame rails. Old selectors targeted
   `.workshop-wall .bins-row` / `.shelf-rail` / `.instr-row` etc., which
   no longer exist in the DOM.) */

/* oscilloscope */
.oscope {
  width: 110px; height: 56px;
  background: var(--oscope-bg);
  border: 2px solid #76808e;
  border-radius: 4px;
  box-shadow: inset 0 0 8px rgba(0,0,0,0.6);
  overflow: hidden;
}
.oscope::before {
  content: ""; position: absolute; inset: 4px;
  background-image:
    linear-gradient(90deg, rgba(76,175,80,0.18) 1px, transparent 1px),
    linear-gradient(0deg,  rgba(76,175,80,0.18) 1px, transparent 1px);
  background-size: 10px 10px;
}
.oscope-label {
  position: absolute; top: 2px; left: 4px;
  color: var(--oscope-green);
  font-family: monospace;
  font-size: 8px;
  z-index: 1;
}
.oscope-screen {
  position: absolute; left: 0; right: 0; top: 50%;
  height: 1px;
}
.oscope-screen svg {
  width: 200%;
  position: absolute;
  left: 0;
  top: -16px;
  height: 32px;
  animation: scope-scroll 1.8s linear infinite;
}
@keyframes scope-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* multimeter */
.mmeter {
  width: 78px; height: 56px;
  background: linear-gradient(180deg, var(--mmeter-orange), var(--mmeter-orange-dk));
  border-radius: 4px;
  border: 1px solid #6b3a14;
  box-shadow: 0 3px 6px rgba(0,0,0,0.5);
}
.mmeter-lcd {
  position: absolute; top: 4px; left: 5px; right: 5px; height: 18px;
  background: var(--mmeter-lcd-bg);
  border: 1px solid #1a2c1a;
  border-radius: 2px;
  color: var(--mmeter-lcd-text);
  font-family: "Courier New", monospace;
  font-size: 11px;
  font-weight: 700;
  text-align: right;
  padding-right: 4px;
  line-height: 16px;
  overflow: hidden;
}
.mmeter-lcd .mmeter-value { display: none; }
.mmeter-lcd::after {
  content: "24.0V";
  animation: mmeter-cycle 2.5s steps(1, end) infinite;
}
@keyframes mmeter-cycle {
  0%   { content: "24.0V"; }
  25%  { content: "23.8V"; }
  50%  { content: "24.1V"; }
  75%  { content: "23.9V"; }
  100% { content: "24.0V"; }
}
.mmeter-knob {
  position: absolute; bottom: 6px; left: 50%; transform: translateX(-50%);
  width: 22px; height: 22px;
  background: #2a2a2a;
  border-radius: 50%;
  border: 1px solid #4a4a4a;
}
.mmeter-knob::after {
  content: ""; position: absolute; top: 2px; left: 50%; transform: translateX(-50%);
  width: 2px; height: 8px;
  background: var(--price-tag);
  border-radius: 1px;
}
.mmeter-led {
  position: absolute; top: 26px; left: 6px;
  width: 5px; height: 5px;
  background: var(--good);
  border-radius: 50%;
  box-shadow: 0 0 4px var(--good);
  animation: led-pulse 1.2s ease-in-out infinite;
}
@keyframes led-pulse {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 1; }
}

/* pressure gauge */
.gauge {
  width: 56px; height: 56px;
  background: radial-gradient(circle, #f0e6c8 0 60%, #c5b58b 60% 100%);
  border-radius: 50%;
  border: 2px solid #6b4a2b;
  box-shadow: 0 3px 6px rgba(0,0,0,0.45);
}
.gauge::before {
  content: ""; position: absolute; inset: 4px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, #e8e0c8 80%);
}
.gauge-label {
  position: absolute; bottom: 6px; left: 50%; transform: translateX(-50%);
  font-size: 6px;
  font-weight: 700;
  color: #5a513a;
  font-family: "Segoe UI", sans-serif;
}
.gauge-tick {
  position: absolute; left: 50%; top: 8px;
  width: 1px; height: 3px;
  background: #5a513a;
  transform-origin: bottom center;
}
.gauge-needle {
  position: absolute; left: 50%; top: 50%;
  width: 2px; height: 18px;
  background: var(--bad);
  border-radius: 1px;
  transform-origin: bottom center;
  transform: translate(-50%, -100%) rotate(-30deg);
  animation: needle-wobble 2.4s ease-in-out infinite;
}
@keyframes needle-wobble {
  0%, 100% { transform: translate(-50%, -100%) rotate(-32deg); }
  50%      { transform: translate(-50%, -100%) rotate(28deg); }
}
.gauge-pivot {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 6px; height: 6px;
  background: #2a2a2a;
  border-radius: 50%;
  z-index: 2;
}

/* cable tester / LED bank */
.ledbank {
  width: 90px; height: 56px;
  background: linear-gradient(180deg, #4a5664, #303a48);
  border: 1px solid var(--bg-inset);
  border-radius: 4px;
  padding: 6px 8px;
  box-shadow: 0 3px 6px rgba(0,0,0,0.5);
}
.ledbank-title {
  color: #cbd4e1;
  font-size: 7px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  font-family: "Segoe UI", sans-serif;
  margin-bottom: 5px;
}
.led-row { display: flex; gap: 5px; }
.led-row + .led-row { margin-top: 5px; }
.led-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: #2a2a2a;
  box-shadow: inset 0 0 2px rgba(0,0,0,0.5);
}
.led-dot.on    { background: var(--good); box-shadow: 0 0 5px var(--good); }
.led-dot.amber { background: var(--price-tag); box-shadow: 0 0 5px var(--price-tag); }
.led-dot.red   { background: var(--bad); box-shadow: 0 0 5px var(--bad); }
.led-dot.blink-fast { animation: led-blink 0.8s ease-in-out infinite; }
.led-dot.blink-slow { animation: led-blink 2.0s ease-in-out infinite; }
@keyframes led-blink {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 1; }
}
.led-seq .led-dot { animation: led-seq 1.6s steps(1) infinite; }
.led-seq .led-dot:nth-child(1) { animation-delay: 0.0s; }
.led-seq .led-dot:nth-child(2) { animation-delay: 0.2s; }
.led-seq .led-dot:nth-child(3) { animation-delay: 0.4s; }
.led-seq .led-dot:nth-child(4) { animation-delay: 0.6s; }
.led-seq .led-dot:nth-child(5) { animation-delay: 0.8s; }
.led-seq .led-dot:nth-child(6) { animation-delay: 1.0s; }
@keyframes led-seq {
  0%, 60%  { background: #2a2a2a; box-shadow: inset 0 0 2px rgba(0,0,0,0.5); }
  20%, 50% { background: var(--good); box-shadow: 0 0 5px var(--good); }
}

/* flow trace monitor */
.trace-monitor {
  width: 130px; height: 56px;
  background: var(--bg-deep);
  border: 2px solid #76808e;
  border-radius: 4px;
  overflow: hidden;
}
/* In the workshop wall, the flow monitor takes the full strip width so the
   gauges (pressure gauge + cable tester) naturally wrap onto the row below. */
.workshop-wall .trace-monitor {
  width: 100%;
}
.trace-label {
  position: absolute; top: 2px; left: 4px;
  color: var(--accent);
  font-size: 7px;
  font-family: monospace;
  letter-spacing: 1px;
  z-index: 1;
}
/* Animated L/min readout in the upper-right of the flow trace. */
.trace-value {
  position: absolute;
  top: 2px;
  right: 6px;
  z-index: 2;
  color: var(--accent);
  font-family: "Courier New", monospace;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-shadow: 0 0 4px rgba(79,195,247,0.55);
}
.trace-value::after {
  content: "24.0 L/min";
  animation: trace-value-cycle 2.4s steps(1, end) infinite;
}
@keyframes trace-value-cycle {
  0%   { content: "24.0 L/min"; }
  16%  { content: "26.3 L/min"; }
  33%  { content: "22.8 L/min"; }
  50%  { content: "25.4 L/min"; }
  66%  { content: "23.1 L/min"; }
  83%  { content: "27.0 L/min"; }
  100% { content: "24.0 L/min"; }
}
.trace-monitor svg {
  position: absolute;
  left: 0;
  top: 0;
  width: 200%;
  height: 100%;
  animation: trace-scroll 2.6s linear infinite;
}
.trace-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.2;
  animation: trace-pulse 2s ease-in-out infinite;
}
@keyframes trace-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.6; }
}
@keyframes trace-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* ===== frequency counter (under the oscilloscope row) ===== */
.freq-counter {
  width: 90px; height: 56px;
  background: linear-gradient(180deg, #2a3340, #161e29);
  border: 1px solid #5a6470;
  border-radius: 4px;
  box-shadow: 0 3px 6px rgba(0,0,0,0.5), inset 0 0 6px rgba(0,0,0,0.4);
}
.freq-label {
  position: absolute; top: 3px; left: 5px;
  color: var(--accent);
  font-size: 7px;
  font-family: monospace;
  letter-spacing: 1px;
  font-weight: 700;
}
.freq-display {
  position: absolute;
  left: 0; right: 0; bottom: 6px;
  text-align: center;
  font-family: "Courier New", monospace;
  font-size: 16px;
  font-weight: 700;
  color: var(--price-tag);
  text-shadow: 0 0 5px rgba(255, 146, 68, 0.65);
  letter-spacing: 1px;
}
.freq-display::after {
  content: "50.00";
  animation: freq-cycle 1.6s steps(1, end) infinite;
}
.freq-counter::after {
  content: "Hz";
  position: absolute;
  bottom: 6px; right: 6px;
  color: var(--price-tag);
  font-size: 8px;
  font-family: "Courier New", monospace;
  font-weight: 700;
  text-shadow: 0 0 4px rgba(255, 146, 68, 0.5);
}
@keyframes freq-cycle {
  0%   { content: "50.00"; }
  20%  { content: "50.02"; }
  40%  { content: "49.99"; }
  60%  { content: "50.01"; }
  80%  { content: "49.98"; }
  100% { content: "50.00"; }
}

/* ===== signal bargraph (under the oscilloscope row) ===== */
.bargraph {
  width: 110px; height: 56px;
  background: linear-gradient(180deg, #2a3340, #161e29);
  border: 1px solid #5a6470;
  border-radius: 4px;
  box-shadow: 0 3px 6px rgba(0,0,0,0.5), inset 0 0 6px rgba(0,0,0,0.4);
}
.bg-label {
  position: absolute; top: 3px; left: 5px;
  color: var(--accent);
  font-size: 7px;
  font-family: monospace;
  letter-spacing: 1px;
  font-weight: 700;
  z-index: 1;
}
.bg-bars {
  position: absolute;
  left: 6px; right: 6px;
  top: 16px; bottom: 6px;
  display: flex;
  gap: 3px;
  align-items: flex-end;
}
.bg-bar {
  flex: 1;
  height: 30%;
  background: linear-gradient(180deg, var(--bad) 0%, var(--price-tag) 45%, var(--good) 75%);
  border-radius: 1px;
  box-shadow: 0 0 3px rgba(76, 175, 80, 0.4);
  animation: bg-bar-bounce 1.4s ease-in-out infinite;
}
.bg-bar:nth-child(1) { animation-delay: 0.00s; }
.bg-bar:nth-child(2) { animation-delay: 0.10s; }
.bg-bar:nth-child(3) { animation-delay: 0.22s; }
.bg-bar:nth-child(4) { animation-delay: 0.35s; }
.bg-bar:nth-child(5) { animation-delay: 0.48s; }
.bg-bar:nth-child(6) { animation-delay: 0.62s; }
.bg-bar:nth-child(7) { animation-delay: 0.76s; }
.bg-bar:nth-child(8) { animation-delay: 0.90s; }
@keyframes bg-bar-bounce {
  0%   { height: 18%; }
  30%  { height: 80%; }
  50%  { height: 55%; }
  75%  { height: 92%; }
  100% { height: 28%; }
}

/* Workshop machine-tab stage badge for rented machines: distinct
   amber-on-slate look so the player spots an out-on-rental unit at
   a glance from the list. */
.m-stage.stage-rented {
  background: linear-gradient(180deg, #2a3b4a, #1a2632);
  color: #ffd66e;
  border: 1px solid rgba(255,214,110,0.4);
}
.md-rented-note {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  font-weight: var(--fw-bold);
  color: #ffd66e;
  background: rgba(255,214,110,0.10);
  border: 1px solid rgba(255,214,110,0.35);
  border-radius: 4px;
}

/* ===========================================================
   Workshop status banner — large "LISTED FOR SALE" / "OUT ON
   RENTAL" overlay shown across the machine canvas when the
   machine isn't actively being worked on. Pointer-events: none
   on the wrapper so hover/inspect on individual parts still
   works underneath; the headline plate uses a translucent
   backdrop so the parts read "behind" the banner.
   =========================================================== */
.md-status-banner {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  background: rgba(8,16,24,0.45);
  border-radius: inherit;
  z-index: 5;
  gap: 14px;
  text-align: center;
  padding: var(--sp-3);
}
.msb-headline {
  /* Scales down on narrow canvases and may wrap rather than overflow
     and clip — "LISTED FOR SALE" / "OUT ON RENTAL" are long strings. */
  font-size: clamp(22px, 6vw, 44px);
  font-weight: 900;
  letter-spacing: 3px;
  padding: 8px 22px;
  border: 4px solid;
  border-radius: 10px;
  background: rgba(0,0,0,0.20);
  transform: rotate(-3deg);
  text-shadow: 0 4px 14px rgba(0,0,0,0.75);
  max-width: 92%;
  text-align: center;
}
.md-status-listed .msb-headline {
  color: #b8d97a;
  border-color: #b8d97a;
}
.md-status-rented .msb-headline {
  color: #ffd66e;
  border-color: #ffd66e;
}
.msb-sub {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semi);
  color: #f0f0f0;
  text-shadow: 0 2px 6px rgba(0,0,0,0.8);
}
.msb-hint {
  font-size: var(--fs-sm);
  color: #c8c8c8;
  text-shadow: 0 1px 3px rgba(0,0,0,0.8);
}

/* ===========================================================
   Incident note — sticky postit pinned to the workbench when a
   rental came back with a story. Two states:
     collapsed (default) → small card with "📝 From <name>"
     expanded (clicked)  → enlarges to show the full text
   Second click on the expanded state dismisses + animates out.
   =========================================================== */
.incident-note {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 132px;
  min-height: 56px;
  padding: 10px 12px 8px;
  background: linear-gradient(180deg, #fff39a 0%, #ffe966 100%);
  color: var(--bg-inset);
  border-radius: 2px;
  box-shadow:
    4px 6px 14px rgba(0,0,0,0.45),
    inset 0 -10px 12px rgba(180,150,30,0.18);
  transform: rotate(3deg);
  transform-origin: center top;
  cursor: pointer;
  z-index: 6;
  font-family: "Comic Sans MS", "Marker Felt", "Bradley Hand", cursive;
  animation: incidentSlideIn 520ms cubic-bezier(0.34, 1.56, 0.64, 1);
  transition:
    width  220ms cubic-bezier(0.34, 1.56, 0.64, 1),
    transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1),
    box-shadow 220ms ease;
}
.incident-note .in-pin {
  position: absolute;
  top: -8px;
  left: 50%;
  transform: translateX(-50%);
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, #ff7a7a 0%, #c22 65%, #800 100%);
  box-shadow: 0 2px 3px rgba(0,0,0,0.55), inset 0 -2px 3px rgba(0,0,0,0.3);
}
.incident-note .in-icon {
  font-size: 18px;
  margin-right: 4px;
}
.incident-note .in-from {
  font-weight: bold;
  font-size: 12px;
}
.incident-note .in-expanded-content { display: none; }
.incident-note[data-expanded="true"] {
  width: 240px;
  transform: rotate(-2deg) scale(1.05);
  box-shadow: 6px 10px 22px rgba(0,0,0,0.55);
}
.incident-note[data-expanded="true"] .in-collapsed { display: none; }
.incident-note[data-expanded="true"] .in-expanded-content {
  display: block;
}
.incident-note .in-text {
  font-size: 13px;
  line-height: 1.4;
  margin-bottom: 8px;
}
.incident-note .in-sign {
  font-size: 12px;
  text-align: right;
  font-style: italic;
  color: #4a3a10;
}
.incident-note .in-dismiss-hint {
  margin-top: 8px;
  font-size: 10px;
  text-align: center;
  color: #6a5a20;
  font-style: italic;
}
.incident-note.incident-dismissing {
  animation: incidentFly 280ms cubic-bezier(0.45, 0, 0.55, 1) forwards;
  pointer-events: none;
}

/* Complaint variant — an unsatisfied renter's note. Red/orange paper so it
   reads as an alert, distinct from the yellow flavor incident note. */
.incident-note.complaint {
  background: linear-gradient(180deg, #ffd3c4 0%, #ff9f86 100%);
  box-shadow:
    4px 6px 14px rgba(0,0,0,0.45),
    inset 0 -10px 12px rgba(170,60,30,0.20);
}
.incident-note.complaint .in-sign { color: #6a2718; }
.incident-note.complaint .in-dismiss-hint { color: #8a3a26; }

/* Praise variant — a happy renter's note. Green paper so a good rental
   reads as a win at a glance. */
.incident-note.praise {
  background: linear-gradient(180deg, #d4f5d0 0%, #a3e699 100%);
  box-shadow:
    4px 6px 14px rgba(0,0,0,0.45),
    inset 0 -10px 12px rgba(50,140,40,0.20);
}
.incident-note.praise .in-sign { color: #2a5a1c; }
.incident-note.praise .in-dismiss-hint { color: #3a6a2a; }

/* Grumble variant — a lukewarm note from a full-term but unimpressed
   renter. Amber paper: a notch below the yellow neutral note, a notch
   above the red complaint. */
.incident-note.grumble {
  background: linear-gradient(180deg, #ffe7c4 0%, #ffcf8e 100%);
  box-shadow:
    4px 6px 14px rgba(0,0,0,0.45),
    inset 0 -10px 12px rgba(170,110,30,0.18);
}
.incident-note.grumble .in-sign { color: #6a4718; }
.incident-note.grumble .in-dismiss-hint { color: #8a5a26; }
/* Refunded-deposit line — always visible (collapsed and expanded) so the
   cost of an unsatisfied return is noticeable without opening the note. */
.incident-note .in-refund {
  margin-top: 5px;
  font-size: 11px;
  font-weight: bold;
  color: #8a2718;
}

@keyframes incidentSlideIn {
  0%   { transform: translateY(-40px) rotate(10deg); opacity: 0; }
  60%  { transform: translateY(6px)   rotate(1deg);  opacity: 1; }
  100% { transform: translateY(0)     rotate(3deg);  opacity: 1; }
}
@keyframes incidentFly {
  0%   { transform: rotate(-2deg) scale(1.05); opacity: 1; }
  100% { transform: rotate(-12deg) translate(40px, -60px) scale(0.6); opacity: 0; }
}
