/* ── Panel & Plate ──────────────────────────────────────────────────────
   Apex Outlines design system (Design.pdf §07/§08). Two rooms: PAPER (Door,
   Spec Sheet, Garage) on daylight paper; CHASSIS (Studio) on workshop dark.
   Loaded by every customer-facing template — door/studio/garage/spec sheet —
   plus the Workshop (admin), which layers its own dense-table rules on top.
*/
@import url('/static/fonts/fonts.css');

:root {
  /* Color */
  --paper:        #FDFCF9;
  --paper-2:      #F2EFE8;
  --chassis:      #1A1712;
  --chassis-2:    #2A2620;
  --line:         #D8D3C8;
  --line-dark:    #3A342A;
  --signal:       #C2410C;
  --signal-press: #9A3412;
  /* Two gauges, one role. #8A8478 is 4.8:1 on chassis but only 3.6:1 on
     paper - under AA for the 11-13px it carries (hints, footers, tertiary
     buttons). .room-paper re-points --gauge at the darker value below. */
  --gauge:        #8A8478;
  --gauge-paper:  #6E685C;
  --walnut:       #6B4F2A;
  --fault:        #B3261E;
  --pass:         #2E6B3F;
  --ink:          var(--chassis);   /* text on paper */
  --paper-ink:    var(--paper);     /* text on chassis */
  /* signal/fault/pass are tuned against paper (5-6.4:1) but drop to 2.7-3.5:1
     on chassis, and lower still on the slightly-lighter chassis-2 card fill
     - under AA for text either way. These -hi variants are bumped to clear
     chassis-2 (the stricter of the two dark surfaces, so they clear plain
     chassis too) and are for TEXT on a dark room only (manifest status rows,
     toasts, Workshop labels); buttons, borders and focus rings keep the base
     tokens, which already clear the 3:1 non-text minimum on both. */
  --signal-hi:    #F15E22;
  --fault-hi:     #E4665F;
  --pass-hi:      #45A05E;
  --gauge-hi:     #938D82;
  /* Contrast advisory (Studio's Configure stage): a room-aware trio per room,
     tuned like the -hi variants - the paper pair measures 8.1:1, the dark
     pair 6.2:1 against chassis-2, so either is safe as small text. */
  --advisory-bg:          #FFF9E6;
  --advisory-border:      #E6D38A;
  --advisory-text:        #5C4A1E;
  --advisory-bg-dark:     #2A2418;
  --advisory-border-dark: #5C4A1E;
  --advisory-text-dark:   #D4C38A;
  /* Lightbox scrims (Spec Sheet): chassis at two opacities over the paper
     room. Once written as rgba literals in the page; promoted so the overlay
     tone tracks the chassis token if it ever moves. */
  --scrim:      rgba(26, 23, 18, .92);  /* full-size viewer overlay */
  --scrim-soft: rgba(26, 23, 18, .7);   /* the viewer's close button fill */
  /* Dropzone (Door): the tinted fills, named for the component and tuned
     against the paper room — base and drag-over states. */
  --dropzone-bg:     #FDF6F0;
  --dropzone-bg-hot: #FFEDD5;

  /* Type */
  --font-display: 'Barlow Condensed', sans-serif;
  --font-body:    'Archivo', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-mono:    'IBM Plex Mono', ui-monospace, "SF Mono", Consolas, monospace;

  /* Type scale — every size in the customer journey comes from this ladder.
     No page invents a step of its own; the Door's hero is the one deliberate
     exception (--t-hero, the brand moment). */
  --t-hero:   44px;   /* Door h1, 72px from 900px up */
  --t-title:  34px;   /* page + moment titles: Your plates, There she is */
  --t-lead:   26px;   /* .pp h2 default — sheet + panel headings */
  --t-sub:    22px;   /* secondary headings, .pp h2 under 900px */
  --t-action: 17px;   /* button face */
  --t-body:   15px;
  /* Base copy and input text. 16px is also the iOS floor that stops
     focus-zooming on inputs, so the inputs share the copy size. */
  --t-copy:   16px;
  --t-small:  13px;
  --t-micro:  12px;
  --t-label:  11px;   /* mono labels / eyebrows */
  --t-stamp:  10px;   /* mono captions inside components */
  /* The two sanctioned steps off the ladder (like --t-hero): plate-number
     readouts (Design-ID plate, seam plate input) and icon glyphs. Sized by
     name so future uses stop hand-typing 18px/20px literals. */
  --t-plate:  20px;   /* mono plate numbers - .pp-plate-number, seam input */
  --t-icon:   18px;   /* advisory icon + dismiss glyph */
  /* Workshop (admin) is the same chassis room at higher density: it maps the
     ladder's small steps directly (10-13px are --t-stamp/-label/-micro/-small)
     and adds one sanctioned size of its own - the 15px wordmark, between
     label and action, sized by name so it isn't hand-typed across pages. */
  --wk-wordmark: 15px;

  /* Space — 4px rhythm */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;  --sp-5: 20px;
  --sp-6: 24px; --sp-8: 32px; --sp-12: 48px; --sp-16: 64px;
  --gutter: 20px;
  --measure: 1200px;

  /* Radius — machined, not friendly. 2px everywhere; full round is the
     loop marker ring and the mobile sticky pill only, by name at their
     own rule. */
  --r: 2px;
  --r-pill: 999px;

  /* Motion */
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --dur-state:   150ms;
  --dur-element: 250ms;
  --dur-room:    450ms;

  /* Hit targets */
  --tap-min: 44px;
}

@media (prefers-reduced-motion: reduce) {
  :root { --dur-state: 0.01ms; --dur-element: 0.01ms; --dur-room: 0.01ms; }
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body.pp {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--t-copy);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
@media (min-width: 900px) {
  body.pp { font-size: 15px; }
}
/* Customer journey (Door/Studio/Garage/Spec Sheet): tablet widths keep the
   mobile layout verbatim, centered in a 430px column, rather than reflowing.
   Page chrome is the exception - a header/footer hairline that stops at 430px
   reads as a broken layout, so the bars stay full-bleed and their contents are
   pushed onto the same 430px column with padding instead. */
@media (min-width: 430px) and (max-width: 899px) {
  .pp-journey > main, .pp-journey > .nf-card, .pp-journey .studio-body {
    max-width: 430px; margin-left: auto; margin-right: auto; width: 100%;
  }
  .pp-journey .door-header, .pp-journey .garage-header,
  .pp-journey .spec-header, .pp-journey .studio-topbar,
  .pp-journey .privacy-header,
  .pp-journey .door-footer, .pp-journey .spec-footer {
    padding-left: calc((100% - 430px) / 2 + var(--gutter));
    padding-right: calc((100% - 430px) / 2 + var(--gutter));
  }
}
.pp h1, .pp h2, .pp h3 {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  line-height: 0.95;
  margin: 0;
}
.pp h2 { font-weight: 600; font-size: var(--t-lead); line-height: 1.05; letter-spacing: 0; }
@media (max-width: 899px) { .pp h2 { font-size: var(--t-sub); } }
/* Page + moment title — the one step above .pp h2, used by the Garage, the
   Spec Sheet's not-found and the Studio's reveal so all three match. */
.pp .pp-title { font-size: var(--t-title); line-height: 1; }
.pp p { margin: 0; }
.pp a { color: inherit; }
.pp .mono {
  font-family: var(--font-mono);
  font-weight: 500;
  letter-spacing: 0.06em;
}
.pp .label {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.pp .icon { width: 1em; height: 1em; display: inline-block; vertical-align: -0.15em;
            flex-shrink: 0; stroke: currentColor; stroke-width: 1.5; fill: none; }

/* ── Rooms ──────────────────────────────────────────────────────────── */
.room-paper   { background: var(--paper);   color: var(--ink); --gauge: var(--gauge-paper); }
.room-chassis { background: var(--chassis); color: var(--paper-ink); }

/* ── Focus ──────────────────────────────────────────────────────────── */
.pp a:focus-visible, .pp button:focus-visible, .pp input:focus-visible,
.pp [tabindex]:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 2px;
  border-radius: var(--r);
}
.room-chassis :focus-visible { outline-color: var(--signal); }

/* ── Container ──────────────────────────────────────────────────────── */
.pp-container {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 var(--gutter);
}
@media (min-width: 900px) { .pp-container { padding: 0 var(--sp-8); } }

/* ── Page header/footer bars — shared base for Door/Garage/Privacy/Spec
   Sheet/Studio. Each page's own <style> layers only its delta (Door's 72px
   desktop height, Studio's 52px base height + dark-room border) on top of
   this, relying on source order (this file loads first) rather than
   specificity to win. Door/Garage/Privacy/Spec Sheet footers have no delta
   at all - .pp-footer is the whole rule for all four. */
.pp-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 var(--gutter); height: 56px; border-bottom: 1px solid var(--line);
}
@media (min-width: 900px) { .pp-header { height: 64px; padding: 0 var(--sp-8); } }

.pp-footer {
  border-top: 1px solid var(--line); padding: var(--sp-6) var(--gutter);
  display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-1) var(--sp-6);
  font-family: var(--font-mono); font-size: var(--t-label); color: var(--gauge);
}
.pp-footer a { color: var(--gauge); text-decoration: none; }
.pp-footer a:hover { color: var(--ink); }

/* Page chrome sits on the same vertical grid line as the content below it.
   The bars stay full-bleed (their hairline is the room's edge) but their
   contents land on the --measure column, so the wordmark is flush with the
   headline and the right-hand link is flush with the panel's right edge. */
@media (min-width: 900px) {
  body.pp .door-header, body.pp .garage-header, body.pp .spec-header,
  body.pp .studio-topbar, body.pp .privacy-header {
    padding-left:  max(var(--sp-8), calc((100% - var(--measure)) / 2 + var(--sp-8)));
    padding-right: max(var(--sp-8), calc((100% - var(--measure)) / 2 + var(--sp-8)));
  }
}

/* ── Buttons — exactly one primary per screen, enforced by usage not CSS ─ */
.pp-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--t-action);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  border-radius: var(--r);
  border: none;
  cursor: pointer;
  min-height: var(--tap-min);
  padding: 0 var(--sp-6);
  transition: background var(--dur-state) var(--ease), border-color var(--dur-state) var(--ease),
              color var(--dur-state) var(--ease), transform var(--dur-state) var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.pp-btn:active { transform: translateY(1px); }
.pp-btn:disabled { cursor: not-allowed; opacity: 0.5; transform: none; }

.pp-btn--primary {
  background: var(--signal);
  color: var(--paper);
  height: var(--tap-min);
}
.pp-btn--primary:hover:not(:disabled) { background: var(--signal-press); }
.pp-btn--primary:disabled { background: var(--line); color: var(--gauge); }
.room-chassis .pp-btn--primary:disabled { background: var(--line-dark); color: var(--gauge); }

.pp-btn--secondary {
  background: none;
  border: 1.5px solid var(--ink);
  color: var(--ink);
}
.pp-btn--secondary:hover:not(:disabled) { border-width: 2px; }
.room-chassis .pp-btn--secondary { border-color: var(--paper-2); color: var(--paper-2); }

/* `.pp a { color: inherit }` is (0,1,1) and outranks the bare `.pp-btn--primary`/
   `.pp-btn--secondary` rules above (0,1,0) whenever the button is rendered as
   an <a> (Garage's "Start a commission", Spec Sheet's "Order on Etsy") - same
   specificity bug the tertiary fix below already documents, just not yet
   applied here. Without this, those links render dark ink text on the signal-
   orange fill (3.45:1) instead of the intended paper text (5.05:1). */
.pp a.pp-btn--primary { color: var(--paper); }
.pp a.pp-btn--secondary { color: var(--ink); }
/* .room-chassis and .pp are always the same <body> element here, never
   ancestor/descendant - ".room-chassis .pp a..." can never match that, so
   this must be a same-element compound (no space before .pp) to actually
   override the rule above on chassis-room pages (QA: Studio's "Try another
   photo" intake-failure link rendered ink-on-chassis, invisible). */
.room-chassis.pp a.pp-btn--secondary { color: var(--paper-2); }

/* .pp a { color: inherit } is (0,1,1) and outranked a bare .pp-btn--tertiary,
   so every tertiary rendered as a link came out ink-coloured while the <button>
   version came out grey - two of them sit side by side on the Spec Sheet. The
   class is scoped under .pp here so it wins for both element types. */
.pp .pp-btn--tertiary {
  background: none;
  border: none;
  padding: var(--sp-2) var(--sp-1);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--t-small);
  letter-spacing: normal;
  text-transform: none;
  color: var(--gauge);
  text-decoration: none;
  border-bottom: 1px solid var(--gauge);
  border-radius: 0;
  min-height: auto;
}
.pp .pp-btn--tertiary:hover:not(:disabled) { color: var(--ink); border-color: var(--ink); }
.room-chassis .pp .pp-btn--tertiary:hover:not(:disabled),
.pp.room-chassis .pp-btn--tertiary:hover:not(:disabled) { color: var(--paper-2); border-color: var(--paper-2); }

@media (max-width: 899px) {
  .pp-btn--primary, .pp-btn--secondary { height: 52px; width: 100%; font-size: var(--t-action); }
}

/* Workshop density (admin bench, desktop-first per DESIGN.md): the 36px row
   height that matches pp-input/select and the sticky-pill's compact buttons.
   Sits after the mobile block so it also pins the height at narrow viewports
   instead of inheriting the marketing 52px full-width buttons above. */
.pp-btn--wk { height: var(--tap-min); min-height: var(--tap-min); padding: 0 var(--sp-4); }

/* Loading — scan sweep is the ONLY loading treatment, everywhere */
.pp-btn.is-loading { position: relative; overflow: hidden; pointer-events: none; }
.pp-btn.is-loading::after {
  content: ""; position: absolute; inset: 0; width: 25%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.35), transparent);
  animation: pp-scan 1.2s linear infinite;
}
@keyframes pp-scan { from { transform: translateX(-100%); } to { transform: translateX(400%); } }
@media (prefers-reduced-motion: reduce) { .pp-btn.is-loading::after { animation: none; opacity: 0.4; } }

/* ── The Plate — number + mono type + stamp edge ───────────────────── */
.pp-plate {
  border: 1.5px solid var(--ink);
  background: var(--paper-2);
  border-radius: var(--r);
  padding: 10px 14px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-3);
  cursor: pointer;
  transition: transform var(--dur-state) var(--ease), border-color var(--dur-state) var(--ease);
}
.pp-plate:hover { border-width: 2px; }
.pp-plate:active { transform: scale(0.99); }
.room-chassis .pp-plate { border-color: var(--paper-2); background: var(--chassis-2); }
.pp-plate-label { font-family: var(--font-mono); font-size: var(--t-stamp); letter-spacing: 0.12em; color: var(--gauge); }
.room-chassis .pp-plate-label { color: var(--gauge-hi); }
.pp-plate-number { font-family: var(--font-mono); font-weight: 600; font-size: var(--t-plate); letter-spacing: 0.14em; }
.pp-plate-status { font-family: var(--font-mono); font-size: var(--t-stamp); letter-spacing: 0.08em; color: var(--signal); text-align: right; white-space: nowrap; }
.pp-plate-status.is-copied { color: var(--pass); }
/* Back-out curve does the overshoot (control point y>1), not a hand-authored
   middle keyframe - one settle, not a double-bounce. Reads as a mechanical
   stamp, not a spring toy (Motion Director review, "premium timing"). */
.pp-plate.stamp-in { animation: pp-stamp 320ms cubic-bezier(.2, 1.15, .4, 1); }
@keyframes pp-stamp {
  0%   { transform: scale(0.94); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .pp-plate.stamp-in { animation: pp-fade var(--dur-element) var(--ease); } }
@keyframes pp-fade { from { opacity: 0; } to { opacity: 1; } }

/* ── The Manifest — build stages / order checklist, one implementation ─ */
.pp-manifest { display: flex; flex-direction: column; gap: 2px; font-family: var(--font-mono); font-size: var(--t-small); letter-spacing: 0.05em; }
.pp-manifest-row { display: flex; align-items: baseline; gap: var(--sp-2); padding: 6px 0; color: var(--gauge);
  transition: color var(--dur-state) var(--ease); }
.pp-manifest-row .row-glyph { flex: none; width: 1em; transition: color var(--dur-state) var(--ease); }
.pp-manifest-row .row-body { color: var(--gauge); transition: color var(--dur-state) var(--ease); }
.pp-manifest-row.is-done   { color: var(--paper-ink); }
.room-paper .pp-manifest-row.is-done { color: var(--ink); }
.pp-manifest-row.is-done .row-glyph { color: var(--pass-hi); }
.room-paper .pp-manifest-row.is-done .row-glyph { color: var(--pass); }
/* A step completing during a 45s+ wait is a confirmation beat, not a silent
   text swap - one quick tick on the glyph is the only feedback the user gets
   that something just finished (Motion Director review, "Loading" category). */
.pp-manifest-row.is-done .row-glyph { animation: pp-tick 280ms var(--ease); }
@keyframes pp-tick { 0% { transform: scale(1.35); } 100% { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) { .pp-manifest-row.is-done .row-glyph { animation: none; } }
.pp-manifest-row.is-active { color: var(--signal-hi); }
.pp-manifest-row.is-active .row-body { color: var(--signal-hi); }
.room-paper .pp-manifest-row.is-active,
.room-paper .pp-manifest-row.is-active .row-body { color: var(--signal); }
.pp-manifest-row.is-failed { color: var(--fault-hi); animation: pp-shake 300ms var(--ease) 1; }
.room-paper .pp-manifest-row.is-failed { color: var(--fault); }
@keyframes pp-shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-2px); }
  75% { transform: translateX(2px); }
}
@media (prefers-reduced-motion: reduce) { .pp-manifest-row.is-failed { animation: none; } }

/* ── Forms ──────────────────────────────────────────────────────────── */
.pp-field-label { font-family: var(--font-mono); font-size: var(--t-label); letter-spacing: 0.1em; text-transform: uppercase; color: var(--gauge); display: block; margin-bottom: var(--sp-2); }
.pp-input {
  width: 100%; background: none; border: none; border-bottom: 1.5px solid var(--line);
  color: var(--ink); font-family: var(--font-body); font-size: var(--t-copy); padding: var(--sp-2) 2px;
  min-height: var(--tap-min);
  transition: border-color var(--dur-state) var(--ease);
}
.pp-input:focus { outline: none; border-color: var(--signal); }
.room-chassis .pp-input { border-color: var(--line-dark); color: var(--paper-ink); }
.pp-field-error { color: var(--fault); font-size: var(--t-small); margin-top: var(--sp-1); }

/* ── Motion utility (room change crossfade) ────────────────────────── */
.pp-room-enter { animation: pp-fade var(--dur-room) var(--ease); }

/* ── Sticky mobile pill (full round — the one other exception besides
       the loop marker ring) ───────────────────────────────────────── */
.pp-sticky-pill {
  position: fixed; left: 50%; bottom: calc(16px + env(safe-area-inset-bottom));
  transform: translateX(-50%) translateY(0);
  z-index: 40;
  background: var(--chassis); color: var(--paper);
  border-radius: var(--r-pill);
  padding: 10px 10px 10px 18px;
  display: flex; align-items: center; gap: var(--sp-3);
  box-shadow: 0 8px 24px rgba(26,23,18,0.18);
  transition: opacity var(--dur-state) var(--ease), transform var(--dur-state) var(--ease),
              visibility 0s linear var(--dur-state);
}
/* display:flex (not none) so the transform/opacity transition can still run,
   but visibility is delayed to AFTER that transition - same pattern as
   #seam-overlay/#lightbox below. Without the visibility half of this, the
   pill's own [hidden] never actually left the tab order: its Order link
   stayed keyboard-focusable (and Enter-activatable) while opacity:0 and
   pointer-events:none only blocked the mouse. */
.pp-sticky-pill[hidden] { opacity: 0; visibility: hidden; pointer-events: none; transform: translateX(-50%) translateY(12px); }
.pp-sticky-pill .mono { font-size: var(--t-micro); letter-spacing: 0.1em; }
.pp-sticky-pill .pp-btn { padding: 0 var(--sp-4); height: var(--tap-min); min-height: var(--tap-min); font-size: var(--t-small); border-radius: var(--r-pill); }

/* ── Chip selector (loop position, and reused by the Workshop's Advanced
       panel for the same field) ───────────────────────────────────────── */
.loop-chip {
  border: 1.5px solid var(--line-dark); background: none; color: var(--gauge);
  font-family: var(--font-mono); font-size: var(--t-small); letter-spacing: 0.06em; text-transform: uppercase;
  padding: 10px 0; border-radius: var(--r); cursor: pointer; min-height: var(--tap-min);
  transition: border-color var(--dur-state) var(--ease), color var(--dur-state) var(--ease), background var(--dur-state) var(--ease);
}
.room-paper .loop-chip { border-color: var(--line); }
.loop-chip:hover { color: var(--paper-ink); border-color: var(--paper-ink); }
.room-paper .loop-chip:hover { color: var(--ink); border-color: var(--ink); }
.loop-chip.active { background: var(--signal); border-color: var(--signal); color: var(--paper); }

/* ── Spec-sheet loop selector (Spec Sheet LOOP cell) ──────────────── */
/* Smaller than the studio's .loop-chip: the spec-grid cell is a 10px-padded
   half-column, so these run at stamp size with a 5px vertical rhythm. */
.spec-loop-chips { display: flex; gap: 4px; }
.spec-loop-chip {
  flex: 1; border: 1px solid var(--line); background: none; color: var(--gauge);
  font-family: var(--font-mono); font-size: var(--t-stamp); letter-spacing: 0.06em; text-transform: uppercase;
  padding: 5px 0; border-radius: var(--r); cursor: pointer; text-align: center; min-height: 0;
  transition: border-color var(--dur-state) var(--ease), color var(--dur-state) var(--ease), background var(--dur-state) var(--ease);
}
.spec-loop-chip:hover { color: var(--ink); border-color: var(--ink); }
.spec-loop-chip.active { background: var(--signal); border-color: var(--signal); color: var(--paper); }
/* Rendering state: the cell itself carries the scan band while the POST is
   in flight - the same signal-tinted band as .build-scan, CSS-only here. */
.spec-loop-cell.rendering { position: relative; overflow: hidden; pointer-events: none; }
.spec-loop-cell.rendering::after {
  content: ""; position: absolute; inset: 0; width: 30%;
  background: linear-gradient(90deg, transparent, rgba(194,65,12,0.16), transparent);
  animation: rerender-scan 0.8s linear infinite;
}

/* ── Re-render micro-reveal (Spec Sheet loop change) ──────────────── */
/* Hatch phase: the tile reads as a blueprint blank while the new render
   prepares (borrowed from the Door's placeholder hatch, tighter spacing). */
.spec-render-wrap.hatch-phase { background-image: repeating-linear-gradient(45deg, var(--line) 0 1px, transparent 1px 10px); }
.spec-render-wrap.scan-phase { position: relative; overflow: hidden; }
.spec-render-wrap.scan-phase::after {
  content: ""; position: absolute; inset: 0; width: 30%; z-index: 2;
  background: linear-gradient(90deg, transparent, rgba(194,65,12,0.18), transparent);
  animation: rerender-scan 600ms linear forwards;
  pointer-events: none;
}
.spec-render-wrap .spec-render-img { transition: opacity var(--dur-element) var(--ease); }
.spec-render-wrap.rerendering .spec-render-img { opacity: 0; }
.spec-render-wrap.reveal-phase .spec-render-img { opacity: 1; }
@keyframes rerender-scan { from { transform: translateX(-100%); } to { transform: translateX(400%); } }
@media (prefers-reduced-motion: reduce) {
  .spec-render-wrap.hatch-phase { background-image: none; }
  .spec-render-wrap.scan-phase::after { display: none; }
}

/* ── Lightbox ──────────────────────────────────────────────────────── */
#lightbox {
  position: fixed; inset: 0; z-index: 50; background: var(--scrim);
  display: flex; align-items: center; justify-content: center; padding: 40px 16px;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity var(--dur-element) var(--ease), visibility 0s linear var(--dur-element);
}
#lightbox.show { opacity: 1; visibility: visible; pointer-events: auto; transition: opacity var(--dur-element) var(--ease); }
#lightbox-img {
  max-width: 100%; max-height: calc(100vh - 80px); max-height: calc(100dvh - 80px);
  object-fit: contain;
}
#lightbox-close { position: fixed; top: 14px; right: 14px; width: 44px; height: 44px; border-radius: var(--r-pill);
  border: none; background: var(--scrim-soft); color: var(--paper); font-size: var(--t-action); cursor: pointer; z-index: 52; }
@media (prefers-reduced-motion: reduce) {
  #lightbox-img { transition: none; }
}

/* ── Garage nav chip (global header badge) ────────────────────────── */
.garage-nav-chip {
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
  border: 1px solid var(--line); border-radius: var(--r); padding: 5px 10px;
  font-family: var(--font-mono); font-size: var(--t-label); letter-spacing: 0.06em;
  color: var(--gauge); text-decoration: none;
  transition: border-color var(--dur-state) var(--ease), color var(--dur-state) var(--ease);
}
.garage-nav-chip:hover { color: var(--ink); border-color: var(--ink); }
.room-chassis .garage-nav-chip { border-color: var(--line-dark); }
.room-chassis .garage-nav-chip:hover { color: var(--paper-ink); border-color: var(--paper-ink); }

/* ── Visually hidden (skip links, sr-only labels) ──────────────────── */
.pp-vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
         clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
