/* ═══════════════════════════════════════════════════════════════════════════
   TFP Visualizer — shared shell components  (Plan §2.1)
   ───────────────────────────────────────────────────────────────────────────
   ONE stepper component + ONE price-bar component, shared byte-for-byte across
   the print / frame / mirror SPAs so the customer perceives a single product.
   Served same-origin at /visualizer-shared/shell.css (see configurator.py) and
   paired with /visualizer-shared/stepper.js (the dynamic step-engine).

   The PRINT STUDIO's stepper + price bar are the design reference (the best of
   the three) — these rules are lifted verbatim from it, tokenised with fallbacks
   so any app whose :root defines the "Forest on Air" tokens renders identically,
   and any app that doesn't still gets a sensible default.

   Design tokens expected on :root (all three apps define them):
     --panel-b --line --line-soft --gold --gold-lt --gold-dk --glow
     --dim --faint --text --ease
   Every var() below carries a fallback so a missing token never blanks a rule.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ─── Stepper component ─────────────────────────────────────────────────────
   Markup contract (rendered by stepper.js — do NOT hand-author):
     #stepper[role=list]      → .step-dot[role=listitem]
                                  → .step-circle(.done|.active|.pending)
                                     • done  → role=button, tabindex=0, ✓
                                     • active→ aria-current=step, number
                                     • ahead-but-reachable → role=button, number
                                     • not-yet → number, no role
                                  → .step-line(.done|.pending)   (decorative)
     #step-labels[aria-hidden]→ .step-label(.done|.active|.pending)
   Inapplicable steps are ABSENT from the DOM (never dashed/disabled); the whole
   stepper is display:none while the journey sits on its gateway step. */
#stepper{display:flex;align-items:center;justify-content:center;padding:24px 18px 8px;flex-shrink:0;background:var(--panel-b,#FAF5F0);border-bottom:1px solid var(--line-soft,#E9E0D7)}
.step-dot{display:flex;align-items:center}
.step-circle{border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:600;transition:all .5s var(--ease,cubic-bezier(.22,1,.36,1));position:relative}
.step-circle.done{background:#ECE2D8;border:1.5px solid rgba(49,62,40,.4);color:var(--gold-dk,#2E2216);cursor:pointer}
.step-circle.active{background:radial-gradient(circle at 50% 35%,#46543A,#313E28);border:1.5px solid var(--gold,#313E28);color:#F2EAE4;box-shadow:0 0 0 4px rgba(49,62,40,.06),0 0 18px var(--glow,rgba(49,62,40,.18))}
.step-circle.pending{background:#FCF8F4;border:1.5px solid var(--line,#DED2C7);color:#C4B7A6}
/* ahead-but-already-reached steps read as pending but are clickable */
.step-circle.pending[role="button"]{cursor:pointer}
.step-line{height:1px;transition:background .5s var(--ease,cubic-bezier(.22,1,.36,1))}
.step-line.done{background:linear-gradient(90deg,rgba(49,62,40,.5),rgba(49,62,40,.25))}
.step-line.pending{background:var(--line-soft,#E9E0D7)}
#step-labels{display:flex;justify-content:center;padding:6px 18px 12px;flex-shrink:0;background:var(--panel-b,#FAF5F0);border-bottom:1px solid var(--line-soft,#E9E0D7)}
.step-label{text-align:center;text-transform:uppercase;font-weight:500;color:#CCC0B0;transition:color .4s var(--ease,cubic-bezier(.22,1,.36,1))}
.step-label.done{color:#566849}.step-label.active{color:var(--gold-lt,#4A5A3C)}

/* ─── Price-bar component ───────────────────────────────────────────────────
   Markup contract (each app populates its own numbers):
     #bottom
       #price-section[aria-live=polite][aria-atomic]   (display toggled by app)
         .price-row → .price-label + .price-display(#…)
         .price-note
         .bd-toggle[aria-expanded]        (breakdown disclosure)
         .breakdown(.open) → .bd-line[.gst|.tot] → .bl(.d) + .br
       .nav-row → back/next buttons (.btn / .btn-outline, app-local)
       .assist  → phone-order help line
   #bottom is a flex-shrink:0 footer; app supplies the .btn base styles. */
#bottom{padding:14px 22px;border-top:1px solid var(--line,#DED2C7);background:linear-gradient(180deg,#FBF7F2,#F2EAE4);flex-shrink:0}
#price-section{display:none;margin-bottom:12px}
.price-row{display:flex;justify-content:space-between;align-items:flex-end}
.price-label{font-size:9px;color:var(--dim,#8A7D6B);text-transform:uppercase;letter-spacing:1px}
.price-display{font-size:24px;font-weight:700;color:var(--gold,#313E28);font-variant-numeric:tabular-nums;letter-spacing:.3px;line-height:1;transition:opacity .25s}
#bottom.loading .price-display{opacity:.35}
.price-note{font-size:8px;color:var(--faint,#A3957F);letter-spacing:.8px;text-transform:uppercase;margin-top:4px}
.bd-toggle{font-size:10px;color:var(--gold-lt,#4A5A3C);background:none;border:none;cursor:pointer;font-family:inherit;padding:6px 0 0;text-decoration:underline;letter-spacing:.02em}
.breakdown{display:none;margin-top:10px;border-top:1px solid var(--line-soft,#E9E0D7);padding-top:10px}
.breakdown.open{display:block}
.bd-line{display:flex;justify-content:space-between;gap:10px;padding:3px 0;font-size:11px}
.bd-line .bl{color:#8C7357}.bd-line .bl .d{display:block;font-size:9px;color:var(--faint,#A3957F);margin-top:1px}
.bd-line .br{color:var(--text,#2E2216);font-variant-numeric:tabular-nums;font-weight:500}
.bd-line.gst{color:var(--dim,#8A7D6B)}.bd-line.tot{border-top:1px solid var(--line-soft,#E9E0D7);margin-top:4px;padding-top:7px;font-weight:700;color:var(--gold,#313E28)}
.nav-row{display:flex;gap:9px}
.nav-row .btn,.nav-row .btn-outline{flex:1}
.assist{text-align:center;margin-top:12px;font-size:11px;line-height:1.5;opacity:.72}
.assist a{text-decoration:underline}

/* ─── Door / content tiles component ────────────────────────────────────────
   The premium "What are we making?" tile language, lifted verbatim from the
   Design Studio gateway so the frame SPA's step-1 content picker reads as the
   SAME family (owner directive 23 Jul: content selection must be premium tiles,
   not a dropdown). The gateway keeps its own byte-identical inline copy of these
   rules (its inline <style> loads after this file, so it wins there — no visual
   change to the gateway); apps that don't redefine them (the frame SPA) inherit
   this shared component. Tokenised with fallbacks matching the gateway's values. */
.door-grid{display:grid;grid-template-columns:1fr;gap:12px}
.door-card{position:relative;display:flex;gap:15px;align-items:center;padding:20px 18px;border:1px solid var(--line,#DED2C7);border-radius:3px;cursor:pointer;
  background:linear-gradient(158deg,#EFE6DD,#F7F1EA);overflow:hidden;
  transition:transform .4s var(--ease,cubic-bezier(.22,1,.36,1)),border-color .4s var(--ease,cubic-bezier(.22,1,.36,1)),box-shadow .4s var(--ease,cubic-bezier(.22,1,.36,1)),background .4s var(--ease,cubic-bezier(.22,1,.36,1))}
.door-card::before{content:'';position:absolute;inset:0;background:radial-gradient(circle at 82% 12%,rgba(49,62,40,.1),transparent 55%);opacity:0;transition:opacity .4s var(--ease,cubic-bezier(.22,1,.36,1))}
.door-card:hover{transform:translateY(-2px);border-color:var(--gold-dk,#2E2216);box-shadow:0 16px 36px -20px rgba(46,34,22,.28)}
.door-card:hover::before,.door-card.selected::before{opacity:1}
.door-card.selected{border-color:var(--gold,#313E28);background:linear-gradient(158deg,rgba(49,62,40,.12),rgba(49,62,40,.02));box-shadow:0 12px 34px -16px var(--glow,rgba(49,62,40,.18))}
.door-icon{width:52px;height:52px;flex-shrink:0;border-radius:3px;border:1px solid var(--line,#DED2C7);background:radial-gradient(circle at 50% 30%,#fff,#EFE6DD);display:flex;align-items:center;justify-content:center;color:var(--gold,#313E28)}
.door-icon svg{width:30px;height:30px}
.door-body{flex:1;min-width:0}
.door-name{font-size:15px;font-weight:600;color:#2E2216;letter-spacing:.2px}
.door-blurb{font-size:11px;color:var(--dim,#8A7D6B);margin-top:5px;line-height:1.5}
.door-arrow{color:var(--gold-lt,#4A5A3C);flex-shrink:0}

/* ─── Pieces tray component (Plan §4B) ──────────────────────────────────────
   "Add another piece" — one multi-line order built from N configured pieces,
   shared byte-for-byte between the frame and mirror SPAs (each app supplies
   its own thumb/desc/qty/subtotal per row via JS; markup below is the
   contract). Empty (no pieces added) → #pieces-tray-card stays display:none
   and the single-piece payload is completely unaffected — see each app's own
   submit code for the byte-identical-when-empty guarantee. */
.pieces-tray{display:flex;flex-direction:column;gap:10px}
.piece-row{display:flex;gap:12px;align-items:center;padding:10px;border:1px solid var(--line,#DED2C7);border-radius:2px;background:linear-gradient(150deg,#FFFFFF,#F7F1EA)}
.piece-thumb{width:48px;height:48px;border-radius:2px;border:1px solid var(--line,#DED2C7);background:radial-gradient(circle at 50% 35%,#FFFFFF,#E7DDD2);overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;color:var(--faint,#A3957F);font-size:7px;letter-spacing:1.6px;text-transform:uppercase}
.piece-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.piece-body{flex:1;min-width:0}
.piece-name{font-size:11.5px;font-weight:600;color:#2E2216}
.piece-desc{font-size:10px;color:var(--dim,#8A7D6B);margin-top:2px;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.piece-meta{display:flex;gap:10px;margin-top:4px;font-size:9.5px;color:var(--faint,#A3957F);letter-spacing:.3px}
.piece-subtotal{font-size:12px;font-weight:600;color:var(--gold,#313E28);font-variant-numeric:tabular-nums;white-space:nowrap}
.piece-actions{display:flex;flex-direction:column;gap:4px;flex-shrink:0;align-items:flex-end}
.piece-edit,.piece-remove{background:none;border:none;padding:0;font-family:inherit;font-size:9.5px;text-decoration:underline;cursor:pointer;letter-spacing:.4px;text-transform:lowercase}
.piece-edit{color:var(--gold-lt,#4A5A3C)}
.piece-edit:hover{color:var(--gold-dk,#2E2216)}
.piece-remove{color:#B3402E}
.piece-remove:hover{color:#8A2F20}
.pieces-count-tag{font-size:8px;color:var(--faint,#A3957F);letter-spacing:1px;text-transform:none;font-weight:400}
.pieces-cap-note{font-size:10px;color:var(--dim,#8A7D6B);margin-top:10px;line-height:1.5}
.pieces-cap-note a{color:var(--gold-dk,#2E2216);text-decoration:underline}
#btn-add-piece{width:100%;margin-bottom:16px}

/* ═══════════════════════════════════════════════════════════════════════════
   CINEMATIC STUDIO SHELL  (Design System v3 · §06 + §07)  — Wave III
   ───────────────────────────────────────────────────────────────────────────
   ONE shared implementation of the "a room you design inside" grammar, consumed
   byte-for-byte by the frame / mirror / print SPAs. The 3D canvas becomes the
   full-bleed room; the config panel becomes a floating frosted-glass panel; the
   stepper becomes a hairline timeline across the top; the running price becomes a
   brass plaque pinned bottom-left. Component look lives HERE; each SPA supplies
   only markup classes + minimal positioning (see the FRAME reference in
   tfp-visualizer/index.html). Engines are untouched — presentation only.

   v3 gallery tokens (with fallbacks to the Forest-on-Air tokens each SPA already
   defines, so a missing token never blanks a rule). Each SPA may set --serif to
   its own display face (frame: Qeraldin); the fallback keeps the plaque legible.
   ═══════════════════════════════════════════════════════════════════════════ */
:root{
  --wall:#FBF8F4; --gesso:#F1E9E1; --forest:#313E28; --walnut:#7C664D;
  --ink:#262019; --brass:#A98D5F;
  --serif:'Cormorant Garamond',Georgia,'Times New Roman',serif;
}

/* ─── The floating frosted-glass panel ──────────────────────────────────────
   Look only — each SPA positions its own panel (inset over the room, internally
   scrollable, 0px radius). See FRAME: #panel gets .studio-glass-panel + inset. */
.studio-glass-panel{
  background:rgba(251,248,244,.68);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  border:1px solid rgba(38,32,25,.1);border-radius:0;
  box-shadow:0 30px 80px -34px rgba(38,32,25,.5)}

/* ─── The hairline timeline (restyled shared stepper) ───────────────────────
   BEHAVIOUR IS UNCHANGED: the shared step-engine still renders #stepper (the
   clickable, aria-labelled circles) + #step-labels; here we only recolour and
   shrink them into a timeline. Circles collapse to small nodes (font-size:0 hides
   the glyph but keeps the 44px-safe click target + aria on the element); the caps
   labels carry the reading — active = forest + brass underline, done = walnut,
   upcoming = faded. Each SPA wraps #stepper + #step-labels in .studio-timeline
   and positions it top-centre over the room. Inline geometry from stepper.js is
   overridden with !important (the engine sets width/height/font-size inline). */
.studio-timeline{pointer-events:none;text-align:center;
  background:linear-gradient(180deg,rgba(251,248,244,.92),rgba(251,248,244,0));
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 14%,#000 86%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 14%,#000 86%,transparent);
  padding:16px 26px 15px}
.studio-timeline #stepper,.studio-timeline #step-labels{
  background:none!important;border:none!important;padding:2px 0!important}
.studio-timeline #stepper{pointer-events:auto}
/* Keep the engine's circle BOX (so #step-labels, sized off it, stay aligned) but
   render the node as a small centred dot pseudo — glyph hidden, click/aria intact. */
.studio-timeline .step-circle{background:none!important;border:none!important;
  box-shadow:none!important;color:transparent!important;position:relative}
.studio-timeline .step-circle::before{content:'';position:absolute;top:50%;left:50%;
  width:10px;height:10px;transform:translate(-50%,-50%);border-radius:50%;background:#D9CFC3;
  transition:background .4s var(--ease,cubic-bezier(.22,1,.36,1))}
.studio-timeline .step-circle.active::before{background:var(--forest,#313E28);box-shadow:0 0 0 3px rgba(49,62,40,.14)}
.studio-timeline .step-circle.done::before{background:var(--walnut,#7C664D)}
.studio-timeline .step-line.done{background:var(--walnut,#7C664D)!important;opacity:.55}
.studio-timeline .step-line.pending{background:rgba(38,32,25,.14)!important}
.studio-timeline .step-label{font-size:10.5px!important;letter-spacing:.13em!important;
  font-weight:700;text-transform:uppercase;padding-bottom:5px;transition:color .4s var(--ease,cubic-bezier(.22,1,.36,1))}
.studio-timeline .step-label.active{color:var(--forest,#313E28)!important;
  border-bottom:1px solid var(--brass,#A98D5F)}
.studio-timeline .step-label.done{color:var(--walnut,#7C664D)!important}
.studio-timeline .step-label.pending{color:rgba(124,102,77,.42)!important}

/* ─── The brass plaque (running price) ──────────────────────────────────────
   Recipe from DESIGN_SYSTEM §03. Each SPA restyles its own price element to this
   markup (eyebrow · serif tabular price · sub-line) and pins it bottom-left over
   the room. See FRAME: #price-section.studio-plaque. */
.studio-plaque{display:inline-block;border-radius:0;
  background:linear-gradient(170deg,#C7AC7C,#A98D5F 55%,#B79A69);padding:2.5px;
  box-shadow:0 12px 30px -12px rgba(38,32,25,.55)}
.studio-plaque .sp-in{background:linear-gradient(180deg,#EFE3C8,#E3D3AF);padding:11px 22px;text-align:left}
.studio-plaque .sp-eyebrow{font-size:9px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;color:#6E5A35}
.studio-plaque .sp-price{font-family:var(--serif,'Cormorant Garamond',Georgia,serif);
  font-size:34px;line-height:1.05;color:#3D3016;margin:2px 0 1px;font-weight:500;
  font-variant-numeric:tabular-nums;letter-spacing:.5px;transition:opacity .25s}
.studio-plaque .sp-sub{font-size:9.5px;color:#84713F;letter-spacing:.02em;text-transform:none;font-weight:400}

/* ─── Moulding thumbnails (picker) ──────────────────────────────────────────
   These used to be clipped into an L with a drawn 45° mitre seam, on the idea
   that a framer hands you a corner sample. Owner call, 27 Jul: show the
   photographs as they are. The reasoning holds — a good half of the library is
   already photographed as a mitred corner, so the clip was cutting a corner out
   of a corner, and on the plain strip shots it removed the very part of the face
   the customer is trying to judge.
   The sizing hooks and the Forest selection outline stay; the frame picker's
   grid overrides the 44px box (index.html, #frame-catalogue). */
.cs-thumb{position:relative;width:44px;height:44px;flex-shrink:0;overflow:hidden;
  background:var(--gesso,#F1E9E1)}
.cs-thumb .thumb,.cs-thumb img.thumb{width:100%!important;height:100%!important;
  border-radius:0!important;box-shadow:none!important;object-fit:cover}
.item-card.selected .cs-thumb{outline:2px solid var(--forest,#313E28);outline-offset:2px}
.item-card.selected .cs-thumb::before{content:'';position:absolute;inset:0;pointer-events:none;
  box-shadow:0 0 0 1px rgba(49,62,40,.25)}

/* ─── Mobile: bottom-sheet + compressed timeline ────────────────────────────
   The glass panel becomes a bottom sheet (drag-handle affordance); the timeline
   compresses to dots + the current label only; the plaque docks above the sheet.
   Positioning is per-SPA; these are the shared component bits. */
@media(max-width:768px){
  .studio-timeline{padding:12px 16px 10px}
  .studio-timeline .step-label{display:none}
  .studio-timeline .step-label.active{display:block;font-size:10px!important}
  .studio-glass-panel::after{content:'';position:absolute;top:7px;left:50%;transform:translateX(-50%);
    width:40px;height:4px;border-radius:0;background:rgba(38,32,25,.24);z-index:6;pointer-events:none}
}

/* ─── Reduced-motion: collapse all shell motion ─────────────────────────────
   Motion rule (Design System v3): prefers-reduced-motion collapses ALL motion.
   The print SPA already carries a global guard, but the frame + mirror SPAs do
   not — so the cinematic-shell transitions introduced HERE (studio timeline /
   glass panel / plaque / door + piece tiles / corner samples / shared stepper)
   must neutralise themselves in the shared layer. Presentation only; the 3D
   engines animate via requestAnimationFrame, not CSS, so this never touches them. */
@media(prefers-reduced-motion:reduce){
  .studio-timeline,.studio-timeline *,.studio-glass-panel,.studio-glass-panel *,
  .studio-plaque,.studio-plaque *,.door-card,.door-card::before,.door-icon,
  .cs-thumb,.cs-thumb .thumb,.cs-thumb img.thumb,.piece-row,.piece-edit,.piece-remove,
  #stepper,.step-circle,.step-line,.step-label,#price-section,.price-display,.bd-toggle{
    transition-duration:.001ms!important;animation-duration:.001ms!important;
    transition-delay:0ms!important;animation-delay:0ms!important}
}
