/* =================================================================
   PAES Practice · Workstation Engine styling
   Picture-first by default. Big tiles, big drop zones, big buttons.
   ================================================================= */

/* Real PAES brand fonts (Roboto Slab display + Work Sans body) */
@import url('https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@500;700;800&family=Work+Sans:wght@400;500;600;700;800&display=swap');

/* Round-8 prescription Section 10: respect prefers-reduced-motion.
   Some IDD students with autism or vestibular sensitivity cannot
   tolerate animation. When the OS-level "Reduce motion" setting is
   on, kill all keyframe animation and turn transitions into
   instantaneous state changes. Pulse / spin / glow / draw / shimmer
   / steam / bubble-rise / blink + every juiced state-change all
   become snap-to. Keeps function. Drops motion. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
   Round-8 prescription · juice pass + accessibility extras
   ------------------------------------------------------------
   Section 6: anticipation, squash/stretch, screen shake, particles,
   counter pop. Section 10: high-contrast mode.
   ============================================================ */

/* Drop-zone pulse on a compatible target. */
@keyframes ws-target-pulse {
  0%, 100% { filter: drop-shadow(0 0 4px rgba(255,209,92,0.45)); }
  50%      { filter: drop-shadow(0 0 14px rgba(255,209,92,0.85)); }
}

/* Screen shake on game-complete burst. */
@keyframes ws-screen-shake {
  0%, 100% { transform: translate(0, 0); }
  10% { transform: translate(-2px, 0); }
  30% { transform: translate(2px, -1px); }
  50% { transform: translate(-1px, 1px); }
  70% { transform: translate(1px, 0); }
}
.ws-stage.ws-celebrate { animation: ws-screen-shake 0.36s ease-in-out; }

/* Counter pop on number changes (XP, streak, hearts). */
@keyframes ws-counter-pop {
  0%   { transform: scale(0.92); }
  60%  { transform: scale(1.18); }
  100% { transform: scale(1); }
}
.ws-counter-pop {
  animation: ws-counter-pop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
  display: inline-block;
}

/* Drop-zone reject shake. */
@keyframes ws-target-reject {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(2px); }
}
.ws-target-reject { animation: ws-target-reject 0.36s ease-in-out; }

/* Drag trail dots — engine spawns these during drag. */
.ws-drag-trail {
  position: fixed;
  width: 14px; height: 14px;
  margin-left: -7px; margin-top: -7px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,209,92,0.85) 0%, rgba(255,209,92,0) 70%);
  pointer-events: none;
  z-index: 9100;
  animation: ws-drag-trail-fade 0.45s ease-out forwards;
}
@keyframes ws-drag-trail-fade {
  0%   { transform: scale(1); opacity: 0.8; }
  100% { transform: scale(0.4); opacity: 0; }
}

/* Snap-to-target flash — drop zone briefly flashes when a near-miss
   drag locks on within 16px. */
@keyframes ws-snap-flash {
  0%   { box-shadow: 0 0 0 0 rgba(93,216,160,0); }
  50%  { box-shadow: 0 0 0 10px rgba(93,216,160,0.4); }
  100% { box-shadow: 0 0 0 20px rgba(93,216,160,0); }
}
.ws-snap-flash { animation: ws-snap-flash 0.45s ease-out; }

/* Round-8 prescription Section 10: switch control support. Every
   interactive surface needs a clearly visible focus ring so a switch-
   control user knows what they're about to activate. WCAG 2.1 AA
   requires 3:1 contrast on the focus indicator. */
.ws-verb-btn:focus-visible,
.ws-proc-step:focus-visible,
[data-target]:focus-visible,
[data-drag]:focus-visible,
.a11y-btn:focus-visible,
.ws-btn:focus-visible,
button:focus-visible {
  outline: 3px solid #FFD15C !important;
  outline-offset: 3px !important;
  box-shadow: 0 0 0 6px rgba(255,209,92,0.35) !important;
  z-index: 10;
}

/* Manual rubric override edit button + editable state. */
.ws-rubric-edit-btn {
  background: transparent;
  border: 1px solid rgba(255,255,255,0.4);
  color: inherit;
  font-size: 11px;
  padding: 3px 10px;
  border-radius: 12px;
  margin-left: 8px;
  cursor: pointer;
  font-weight: 600;
}
.ws-rubric-edit-btn:hover { background: rgba(255,255,255,0.1); }
.ws-rubric-row.ws-rubric-editable .ws-rubric-pill {
  outline: 2px dashed #FFD15C;
  outline-offset: 2px;
}

/* Round-10: explicit resume chip in the top bar. */
.ws-resume-chip {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 4px 16px 0;
  padding: 8px 14px;
  background: linear-gradient(180deg, rgba(255,209,92,0.16), rgba(255,209,92,0.06));
  border: 1.5px solid rgba(255,209,92,0.55);
  border-radius: 8px;
  font-size: 13px;
  color: #FFD15C;
  font-weight: 700;
  transition: opacity 0.5s ease, transform 0.5s ease;
}
.ws-resume-chip.ws-resume-fade { opacity: 0; transform: translateY(-6px); }
.ws-resume-chip .ws-resume-msg { flex: 1; }
.ws-resume-chip button {
  background: rgba(0,0,0,0.4);
  color: #FFD15C;
  border: 1px solid rgba(255,209,92,0.5);
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 11.5px;
  font-weight: 700;
  cursor: pointer;
  font-family: inherit;
}
.ws-resume-chip button:hover { background: rgba(0,0,0,0.65); }
.ws-resume-chip .ws-resume-dismiss {
  padding: 4px 8px;
  border-color: rgba(255,255,255,0.25);
  color: rgba(255,255,255,0.7);
}

/* Time trial CTA in win modal. */
.ws-time-trial-cta {
  margin: 6px 0 12px;
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 700;
  color: #FFD15C;
  background: rgba(255,209,92,0.15);
  border: 1.5px solid rgba(255,209,92,0.55);
  border-radius: 10px;
  text-align: center;
}

/* Job-link narrative line in the win modal. */
.ws-job-link-line {
  margin: 6px 0 14px;
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 700;
  color: #FFD15C;
  background: rgba(255,209,92,0.10);
  border-radius: 8px;
  text-align: center;
  letter-spacing: 0.3px;
}

/* Mastery note in the win modal. */
.ws-mastery-note {
  margin: 8px 0;
  padding: 8px 14px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 700;
  background: rgba(39,169,225,0.12);
  color: #B9E5FB;
  border: 1px solid rgba(39,169,225,0.4);
  text-align: center;
}
.ws-mastery-note.ws-mastery-done {
  background: rgba(93,216,160,0.22);
  color: #5DD8A0;
  border-color: rgba(93,216,160,0.6);
}
.ws-mastery-note.ws-mastery-info {
  background: rgba(255,255,255,0.06);
  color: #ddd;
  border-color: rgba(255,255,255,0.18);
}

/* Confidence-interval text. */
.ws-rubric-confidence {
  margin: 6px 0 12px;
  padding: 6px 12px;
  font-size: 11px;
  font-style: italic;
  color: #FFD15C;
  background: rgba(255,209,92,0.08);
  border-left: 3px solid #FFD15C;
  border-radius: 4px;
  text-align: left;
}

/* Idle-finger hint — after 8s of inactivity on a drag step, the
   engine adds .ws-idle-finger to the source element. CSS animates a
   ghost finger from source to target so the student sees the gesture. */
.ws-idle-finger {
  position: fixed;
  width: 32px; height: 32px;
  z-index: 9120;
  pointer-events: none;
  animation: ws-idle-finger-loop 2.4s ease-in-out infinite;
}
.ws-idle-finger::before {
  content: '👆';
  font-size: 28px;
}
@keyframes ws-idle-finger-loop {
  0%   { opacity: 0; transform: translate(var(--from-x, 0), var(--from-y, 0)) scale(1); }
  10%  { opacity: 1; }
  85%  { opacity: 1; transform: translate(var(--to-x, 80px), var(--to-y, 0)) scale(1); }
  100% { opacity: 0; transform: translate(var(--to-x, 80px), var(--to-y, 0)) scale(1.2); }
}

/* High-contrast mode — toggled via a11y bar. Boosts contrast on
   every readable surface for low-vision users. */
html.paes-high-contrast { background: #000 !important; }
html.paes-high-contrast body { background: #000 !important; color: #fff !important; }
html.paes-high-contrast .ws-proc-rail,
html.paes-high-contrast .ws-proc-stage,
html.paes-high-contrast .ws-proc-side,
html.paes-high-contrast .ws-rubric-card {
  background: #0a0a0a !important;
  color: #fff !important;
  border: 1.5px solid #fff !important;
}
html.paes-high-contrast .ws-proc-step,
html.paes-high-contrast .ws-proc-rule,
html.paes-high-contrast .ws-rubric-pill {
  background: #000 !important;
  color: #fff !important;
  border: 1px solid #fff !important;
}
html.paes-high-contrast .ws-proc-step.active {
  background: #FFD15C !important;
  color: #000 !important;
  border: 2px solid #fff !important;
}
html.paes-high-contrast .ws-verb-btn {
  background: #000 !important;
  color: #fff !important;
  border: 2px solid #fff !important;
}
html.paes-high-contrast .ws-verb-btn.is-active-step {
  background: #FFD15C !important;
  color: #000 !important;
  border: 2px solid #000 !important;
}
html.paes-high-contrast .coach-bubble {
  background: #FFD15C !important;
  color: #000 !important;
  border: 2px solid #fff !important;
}

:root {
  /* Real PAES brand palette · matches the PAES sales deck */
  --paes-navy: #0A4D9B;       /* PAES cobalt · primary */
  --paes-navy-deep: #073876;  /* Deeper cobalt · gradient base */
  --paes-cyan: #27A9E1;       /* PAES cyan splash · accent */
  --paes-cyan-soft: #B9E5FB;  /* PAES soft cyan · fills */
  --paes-gold: #27A9E1;       /* Legacy alias → PAES cyan */
  --paes-teal: #27A9E1;       /* Legacy alias → PAES cyan */
  --paes-cream: #F7F3EC;      /* Paper */
  --paes-green: #5DD8A0;      /* Success rubric tier */
  --paes-red: #EF402F;        /* PAES red · alert */
  --paes-font-display: 'Roboto Slab', Georgia, serif;
  --paes-font-body: 'Work Sans', -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; padding: 0; }
body {
  font-family: var(--paes-font-body);
  background: linear-gradient(180deg, var(--paes-navy-deep) 0%, var(--paes-navy) 100%);
  color: #fff;
  -webkit-font-smoothing: antialiased;
  user-select: none;
  -webkit-user-select: none;
  overflow-x: hidden;
}

/* Workstation-scoped focus rings (matches site-wide style). Without these
   any switch-access student moving through verb buttons / drag sources
   has no visible cue. :focus-visible suppresses the ring on mouse-click. */
.ws-root :focus-visible,
.ws-root button:focus-visible,
.ws-root [role="button"]:focus-visible,
.ws-root [data-drag]:focus-visible,
.ws-root [data-target]:focus-visible {
  outline: 3px solid #FFD15C;
  outline-offset: 2px;
  box-shadow: 0 0 0 5px rgba(255,209,92,0.3);
}
.ws-root {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  position: relative;
}

/* ---------- Header (glassmorphism HUD bar) ---------- */
.ws-header {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 22px;
  background:
    linear-gradient(180deg, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0.015) 100%),
    rgba(0,0,0,0.4);
  backdrop-filter: blur(16px) saturate(120%);
  -webkit-backdrop-filter: blur(16px) saturate(120%);
  border-bottom: 1px solid rgba(255,255,255,0.12);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.18),
    0 4px 18px rgba(0,0,0,0.35);
  z-index: 50;
}
.ws-exit {
  background: linear-gradient(180deg, rgba(255,255,255,0.10) 0%, rgba(255,255,255,0.03) 100%);
  border: 1px solid rgba(255,255,255,0.22);
  color: #fff;
  padding: 8px 16px;
  border-radius: 100px;
  font-size: 13px; font-weight: 700;
  text-decoration: none;
  font-family: 'Roboto Slab', Georgia, serif;
  letter-spacing: 0.3px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.2),
    0 2px 0 rgba(0,0,0,0.3);
  transition: all 0.18s ease;
}
.ws-exit:hover {
  background: linear-gradient(180deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.06) 100%);
  color: #fff;
  transform: translateY(-1px);
}
.ws-progress {
  flex: 1;
  height: 26px;
  background: rgba(0,0,0,0.4);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 100px;
  overflow: hidden;
  position: relative;
  display: flex;
  align-items: center;
  box-shadow: inset 0 2px 4px rgba(0,0,0,0.4);
}
.ws-progress-fill {
  height: 100%;
  /* Round-3 roast: "On EP1 step 1 (1/5) the bar fills ~25%. On HC4 step
     1 (1/2) the bar is closer to ~80%." Cause: the old 3-stop gradient
     ended in a near-white #B9E5FB at 100% which made the fill's right
     edge appear to bleed past its actual width via the 14px outer glow.
     Now: a tight 2-stop gradient (no near-white) and a softer glow so
     the fill's right edge reads sharply where it actually ends. */
  background: linear-gradient(90deg, #5DD8A0 0%, #27A9E1 100%);
  transition: width 0.5s cubic-bezier(.34, 1.56, .64, 1);
  position: absolute; top: 0; left: 0;
  border-radius: 100px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.5),
    0 0 6px rgba(39,169,225,0.35);
}
/* Travelling shine across the fill bar — feeds back "the bar is alive". */
.ws-progress-fill::after {
  content: '';
  position: absolute;
  top: 0; left: -50%;
  width: 50%; height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.45), transparent);
  animation: ws-progress-shine 2.4s linear infinite;
}
@keyframes ws-progress-shine {
  0%   { transform: translateX(0); }
  100% { transform: translateX(400%); }
}
/* Step X of N text overlaid on the progress bar.
   Roast caught the white text becoming illegible mid-progress where the
   gradient transitions from cyan-blue (dark) to pale-blue (light) —
   half the text white-on-white. Now the text gets a solid dark backplate
   pill behind it so contrast is constant regardless of fill position. */
.ws-progress-text {
  position: relative;
  z-index: 2;
  width: auto;
  margin: 0 auto;
  padding: 2px 14px;
  background: rgba(8,26,45,0.85);
  border-radius: 100px;
  text-align: center;
  font-size: 11.5px; font-weight: 800;
  letter-spacing: 1.2px;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,0.6);
  font-family: 'Roboto Slab', Georgia, serif;
  white-space: nowrap;
}
.ws-timer {
  font-family: 'Roboto Slab', Georgia, serif;
  font-size: 15px; font-weight: 700;
  color: rgba(255,255,255,0.92);
  min-width: 56px;
  text-align: right;
  letter-spacing: 1px;
  font-variant-numeric: tabular-nums;
}

/* =============================================================
   HUD · streak + XP + hearts
   Sits between the progress bar and the timer in the header. Each
   element is a small badge that pulses when the value changes.
   ============================================================= */
.ws-hud {
  display: flex;
  gap: 10px;
  align-items: center;
  margin: 0 10px;
}
.ws-hud > div {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  border-radius: 100px;
  background:
    linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%),
    rgba(0,0,0,0.4);
  border: 1px solid rgba(255,255,255,0.14);
  font-size: 13px; font-weight: 800;
  color: #fff;
  letter-spacing: 0.4px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.18),
    0 2px 4px rgba(0,0,0,0.3);
  font-family: 'Roboto Slab', Georgia, serif;
}
.ws-hud-icon { font-size: 14px; line-height: 1; }
.ws-hud-streak { color: #FFD15C; }
.ws-hud-xp     { color: #5DD8A0; }
.ws-hud-hearts { gap: 3px; padding: 5px 10px; }
.ws-hud-heart {
  font-size: 16px; line-height: 1;
  /* Empty heart shows as an outlined glyph (♡), full heart shows the solid
     glyph (♥) — the roast caught colourblind users having no way to read
     "1 of 3" because the only difference was saturation. Now an empty
     heart literally renders different glyph. */
  color: rgba(255,255,255,0.55);
  transition: color 0.25s ease, transform 0.25s ease;
}
.ws-hud-heart::before { content: '♡'; }
.ws-hud-heart.full::before { content: '♥'; }
.ws-hud-heart.full {
  color: #EF402F;
  text-shadow: 0 0 6px rgba(239,64,47,0.55);
}
/* Hide the original raw glyph since we're using ::before. The HTML still
   contains a ♥ for screen readers; visually it's clipped. */
.ws-hud-heart { font-size: 0; }
.ws-hud-heart::before { font-size: 16px; }
.ws-hud-heart.lose { animation: ws-heart-lose 0.85s cubic-bezier(.34, 1.56, .64, 1); }
@keyframes ws-heart-lose {
  0%   { color: #EF402F; transform: scale(1) rotate(0); }
  20%  { color: #EF402F; transform: scale(1.6) rotate(-12deg); text-shadow: 0 0 18px rgba(239,64,47,0.9); }
  40%  { color: #EF402F; transform: scale(1.4) rotate(12deg); }
  65%  { color: rgba(255,255,255,0.4); transform: scale(0.7) rotate(0) translateY(8px); }
  100% { color: rgba(255,255,255,0.18); transform: scale(1) rotate(0) translateY(0); text-shadow: none; }
}
/* Number bump on increment — pops once per change. */
.ws-hud-streak span:last-child.bump,
.ws-hud-xp span:last-child.bump {
  animation: ws-num-bump 0.4s cubic-bezier(.34,1.56,.64,1);
  display: inline-block;
}
@keyframes ws-num-bump {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.4); }
  100% { transform: scale(1); }
}

/* +XP popup that floats up from the scene every correct step.
   Tilted gold pill with an inset highlight + drop shadow + glow halo. */
/* +XP popup. Repositioned to the TOP-RIGHT corner of the stage (was floating
   centred upward through the workspace). The roast caught the centred
   version overlapping the next action verb (Tape / Tighten / Cook / Stir)
   right at the moment of triumph — covering the very thing the student
   needs to see for step N+1. Now it floats up by the score badge in the
   header where the eye naturally goes for "did that count?" feedback. */
.ws-xp-pop {
  position: absolute;
  right: 18px;
  top: 18px;
  padding: 8px 18px;
  border-radius: 100px;
  background: linear-gradient(180deg, #FFD15C 0%, #F5A623 100%);
  color: #2a1a05;
  font-weight: 900;
  font-size: 18px;
  font-family: 'Roboto Slab', Georgia, serif;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.6),
    0 4px 0 rgba(180,100,0,0.8),
    0 14px 28px rgba(245,166,35,0.45);
  pointer-events: none;
  z-index: 20;
  animation: ws-xp-float 1.4s cubic-bezier(.34, 1.56, .64, 1) forwards;
  letter-spacing: 0.4px;
  text-shadow: 0 1px 0 rgba(255,255,255,0.5);
  white-space: nowrap;
}
@keyframes ws-xp-float {
  0%   { opacity: 0; transform: translateY(20px) scale(0.7) rotate(-4deg); }
  20%  { opacity: 1; transform: translateY(0) scale(1.1) rotate(2deg); }
  40%  { transform: translateY(-6px) scale(1) rotate(0); }
  100% { opacity: 0; transform: translateY(-32px) scale(0.95) rotate(0); }
}

/* Streak milestone toast — fires at 3 / 5 / 7 / 10 streak hits.
   Anchors top-right next to the streak HUD pill (was centred above the
   workspace, where the roast caught it covering the bulb mid-EP2 every
   time it fired). Now it docks beside the score badges so a 3-in-a-row
   feels like an upgrade to the streak indicator the eye is already
   tracking. */
.ws-streak-toast {
  position: fixed;
  /* Round-3 roast: "BLAZING 15 in a row badge top-right is huge and
     partially covers the rubric." Cause: top-right anchor ate the first
     rubric items. Now docked top-CENTER so it's celebratory without
     covering scoring criteria. */
  top: 18px;
  left: 50%;
  transform: translate(-50%, -16px) scale(0.85);
  z-index: 9800;
  pointer-events: none;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 22px;
  background: linear-gradient(180deg, #FFD15C 0%, #F5A623 100%);
  color: #2a1a05;
  border-radius: 100px;
  font-family: 'Roboto Slab', Georgia, serif;
  font-weight: 800;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.6),
    0 4px 0 rgba(180,100,0,0.85),
    0 18px 36px rgba(245,166,35,0.55),
    0 0 36px rgba(255,209,92,0.6);
  text-shadow: 0 1px 0 rgba(255,255,255,0.45);
  opacity: 0;
  transition: opacity 0.32s cubic-bezier(.34, 1.5, .64, 1), transform 0.5s cubic-bezier(.34, 1.5, .64, 1);
}
.ws-streak-toast.show {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
}
.ws-streak-toast .icon { font-size: 26px; filter: drop-shadow(0 0 10px rgba(255,140,0,0.9)); animation: ws-streak-flicker 0.4s ease-in-out infinite alternate; }
.ws-streak-toast .big { font-size: 22px; letter-spacing: 1.5px; }
.ws-streak-toast .meta { font-size: 13px; opacity: 0.7; font-family: 'Work Sans', sans-serif; font-weight: 600; }
/* Streak fire icon scales with streak count — small at 1-2, medium at
   3-5, huge at 6+. The hud span reads the numeric value via CSS
   custom property set by the engine. */
.ws-hud-streak[data-streak-tier="0"] .ws-hud-icon { font-size: 13px; opacity: 0.6; filter: grayscale(0.5); }
.ws-hud-streak[data-streak-tier="1"] .ws-hud-icon { font-size: 14px; }
.ws-hud-streak[data-streak-tier="2"] .ws-hud-icon { font-size: 17px; filter: drop-shadow(0 0 6px rgba(255,140,0,0.6)); }
.ws-hud-streak[data-streak-tier="3"] .ws-hud-icon {
  font-size: 22px;
  filter: drop-shadow(0 0 10px rgba(255,140,0,0.85)) drop-shadow(0 0 18px rgba(239,64,47,0.55));
  animation: ws-streak-flicker 0.4s ease-in-out infinite alternate;
}
@keyframes ws-streak-flicker {
  0%   { transform: rotate(-2deg) scale(1); }
  100% { transform: rotate(2deg) scale(1.06); }
}

/* Scene-shake on wrong action — physical mistake feedback. */
.ws-proc-scene.ws-scene-shake {
  animation: ws-scene-shake 0.55s cubic-bezier(.36,.07,.19,.97) both;
}
@keyframes ws-scene-shake {
  0%       { transform: translateX(0); filter: brightness(1) saturate(1); }
  8%       { transform: translateX(-12px) rotate(-1deg); filter: brightness(0.85) saturate(0.85); }
  18%      { transform: translateX(14px) rotate(1deg); }
  28%      { transform: translateX(-12px) rotate(-1deg); filter: brightness(0.95) saturate(1.2) hue-rotate(-8deg); }
  38%      { transform: translateX(10px) rotate(0.5deg); }
  48%      { transform: translateX(-8px) rotate(-0.5deg); }
  58%      { transform: translateX(6px); }
  78%      { transform: translateX(-3px); }
  100%     { transform: translateX(0); filter: brightness(1) saturate(1); }
}
/* Red flash overlay on wrong action — adds urgency without being
   alarmist. Sits over the scene as an absolute layer. */
.ws-proc-scene.ws-scene-shake::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, rgba(239,64,47,0.25) 0%, transparent 70%);
  pointer-events: none;
  z-index: 5;
  animation: ws-red-flash 0.55s ease-out;
}
@keyframes ws-red-flash {
  0%   { opacity: 0; }
  30%  { opacity: 1; }
  100% { opacity: 0; }
}

/* ---------- Stage ----------
   Round-11 brief: was 1080px max-width which caged the picture stage
   into ~440px of horizontal real estate on a 1440 monitor. Bumped
   to 1600 so the scene + rails can breathe on standard laptops. POS
   already runs edge-to-edge; this brings the workstation in line. */
.ws-stage {
  flex: 1;
  padding: 28px 28px 100px;
  max-width: 1600px;
  width: 100%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 22px;
  transition: opacity 0.25s ease;
}

/* Fades the lesson chrome out cleanly during the brief moment between the
   final step completing and the win card mounting, so the win-card never
   appears to overlap the still-visible scene. */
.ws-stage.ws-finishing { opacity: 0; pointer-events: none; }

.ws-prompt-wrap { text-align: center; }
.ws-prompt-eye {
  display: inline-block;
  background: rgba(39,169,225,0.18);
  color: var(--paes-gold);
  font-size: 11px;
  letter-spacing: 2px;
  text-transform: uppercase;
  font-weight: 800;
  padding: 5px 14px;
  border-radius: 4px;
  margin-bottom: 10px;
}
.ws-prompt {
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.6px;
  margin: 0;
  line-height: 1.15;
  color: #fff;
}
.ws-prompt-rule {
  margin-top: 10px;
  font-size: 14px;
  color: rgba(255,255,255,0.6);
  font-weight: 500;
}

/* ---------- Canvas ---------- */
.ws-canvas {
  display: flex;
  flex-direction: column;
  gap: 22px;
}

/* ---------- Sequence (drag into ordered slots) ---------- */
.ws-canvas {
  background:
    linear-gradient(180deg, rgba(160,120,72,0.12) 0%, rgba(90,56,24,0.18) 100%);
  border-radius: 18px;
  padding: 20px;
  position: relative;
  overflow: hidden;
}
.ws-canvas:before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    repeating-linear-gradient(0deg, transparent 0px, transparent 14px, rgba(0,0,0,0.04) 14px, rgba(0,0,0,0.04) 15px),
    repeating-linear-gradient(90deg, transparent 0px, transparent 80px, rgba(0,0,0,0.05) 80px, rgba(0,0,0,0.05) 81px);
  pointer-events: none;
  border-radius: 18px;
}
.ws-canvas > * { position: relative; z-index: 1; }

.ws-seq-shelf, .ws-bucket-shelf {
  background: rgba(0,0,0,0.32);
  border: 1.5px solid rgba(255,255,255,0.08);
  border-radius: 16px;
  padding: 16px;
}
.ws-seq-shelf-h, .ws-seq-line-h {
  font-size: 11px; letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.55);
  font-weight: 800;
  margin-bottom: 12px;
}
.ws-seq-pile, .ws-bucket-pile {
  display: flex; flex-wrap: wrap; gap: 12px;
  min-height: 84px;
}
.ws-seq-line { padding-top: 4px; }
.ws-seq-slots {
  display: flex; gap: 12px; flex-wrap: wrap;
  background: linear-gradient(180deg, #2a2230 0%, #181420 100%);
  padding: 18px 14px;
  border-radius: 14px;
  border: 2px solid #0a0a14;
  min-height: 110px;
  box-shadow:
    inset 0 4px 8px rgba(0,0,0,0.45),
    inset 0 -2px 4px rgba(255,255,255,0.04);
  position: relative;
}
.ws-seq-slots:before {
  content: '';
  position: absolute;
  top: 4px; bottom: 4px; left: 4px; right: 4px;
  border: 1px dashed rgba(255,255,255,0.08);
  border-radius: 10px;
  pointer-events: none;
}
.ws-seq-slot {
  flex: 0 0 130px; height: 88px;
  border: 2px dashed rgba(255,255,255,0.18);
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  position: relative;
  background: rgba(255,255,255,0.02);
  transition: all 0.15s ease;
}
.ws-seq-slot.over { border-color: var(--paes-gold); background: rgba(39,169,225,0.1); transform: scale(1.04); }
.ws-seq-slot .ws-slot-num {
  position: absolute; top: 6px; left: 8px;
  font-size: 10px; letter-spacing: 0.8px;
  color: rgba(255,255,255,0.35);
  font-weight: 800;
}

/* ---------- Buckets (drop into named bins) ---------- */
.ws-bucket-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  padding: 14px 0;
}
.ws-bucket {
  min-height: 140px;
  background:
    linear-gradient(180deg, #3a3022 0%, #1f1810 100%);
  border: 2px solid #0a0608;
  border-radius: 8px 8px 4px 4px;
  padding: 14px 10px;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  transition: all 0.15s ease;
  position: relative;
  box-shadow:
    inset 0 2px 4px rgba(0,0,0,0.5),
    inset 0 -3px 0 rgba(0,0,0,0.4),
    0 4px 8px rgba(0,0,0,0.3);
}
.ws-bucket:before {
  content: '';
  position: absolute;
  top: 8px; left: 50%;
  width: 30px; height: 6px;
  background: linear-gradient(180deg, #FFD15C 0%, #a87830 100%);
  border-radius: 2px;
  transform: translateX(-50%);
  box-shadow: inset 0 -1px 1px rgba(0,0,0,0.4);
}
.ws-bucket.over { border-color: var(--paes-gold); background: linear-gradient(180deg, #5a4830 0%, #3a2818 100%); transform: scale(1.04); }
.ws-bucket-glyph { font-size: 30px; line-height: 1; margin-top: 12px; }
.ws-bucket-label {
  font-size: 13.5px; font-weight: 800;
  color: rgba(255,255,255,0.92);
  letter-spacing: 1px;
  text-transform: uppercase;
  background: rgba(0,0,0,0.4);
  padding: 4px 10px;
  border-radius: 3px;
  margin-top: 4px;
}

/* ---------- Tile (the draggable item · file-folder style) ---------- */
.ws-tile {
  background: linear-gradient(180deg, #FFD15C 0%, #d8a838 60%, #a87830 100%);
  color: var(--paes-navy);
  border-radius: 4px 4px 6px 6px;
  padding: 14px 16px 12px;
  font-weight: 700;
  font-size: 15px;
  box-shadow:
    inset 0 1px 2px rgba(255,255,255,0.5),
    inset 0 -3px 0 rgba(0,0,0,0.15),
    0 4px 10px rgba(0,0,0,0.4);
  cursor: grab;
  display: inline-flex; align-items: center; gap: 8px;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  border: 1.5px solid #5a3818;
  user-select: none;
  min-width: 110px;
  justify-content: center;
  position: relative;
}
.ws-tile:before {
  content: '';
  position: absolute;
  top: -3px; left: 14px;
  width: 38px; height: 6px;
  background: linear-gradient(180deg, #FFD15C 0%, #a87830 100%);
  border-radius: 2px 2px 0 0;
  border: 1.5px solid #5a3818;
  border-bottom: none;
  box-shadow: inset 0 1px 1px rgba(255,255,255,0.5);
}
.ws-tile:hover { transform: translateY(-2px); box-shadow: 0 8px 18px rgba(0,0,0,0.35); }
.ws-tile:active { cursor: grabbing; }
.ws-tile.dragging { opacity: 0.4; transform: scale(1.05); }
.ws-tile[data-right="1"] { border-color: var(--paes-green); box-shadow: 0 0 0 3px rgba(93,216,160,0.45); }
.ws-tile[data-wrong="1"] { border-color: var(--paes-red); box-shadow: 0 0 0 3px rgba(227,107,92,0.45); }
.ws-tile.shake { animation: ws-shake 0.4s ease; }
@keyframes ws-shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-6px); }
  50% { transform: translateX(6px); }
  75% { transform: translateX(-3px); }
}
.ws-tile-glyph { font-size: 22px; }
.ws-tile-label { font-size: 14px; }

/* ----- Tile kind variants -----
   Default tile is a manila folder (yellow with a tab on top). Each "kind"
   restyles the tile so the visual matches the THING the student is sorting.
   This is the fix for "every drag-sort game looks identical because every
   item is the same yellow folder." */

/* Sheet of paper — flat white card with a folded top-right corner. Used for
   page-numbered packets, document collation, mailroom sorts. */
.ws-tile.ws-tile--page {
  background: #fafafa;
  color: #1a1a1a;
  border: 1.5px solid #888;
  border-radius: 2px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.25), inset 0 -2px 0 rgba(0,0,0,0.06);
}
.ws-tile.ws-tile--page:before {
  content: '';
  position: absolute;
  top: -1px; right: -1px;
  width: 14px; height: 14px;
  background: linear-gradient(135deg, transparent 50%, #d8d8d8 50%);
  border-left: 1px solid #888;
  border-bottom: 1px solid #888;
  border-radius: 0;
  border-top: none;
  left: auto;
}
.ws-tile.ws-tile--page .ws-tile-label { color: #1a1a1a; font-weight: 700; }

/* Name file — manila folder with a name tab on top-left, like a real filing
   cabinet folder. Default tile is already a folder, this just darkens the
   tab so a name reads as a name, not a generic page. */
.ws-tile.ws-tile--name:before {
  background: linear-gradient(180deg, #FFD15C 0%, #c89028 100%);
  width: 50px;
  left: 8px;
}

/* Wrench — steel-grey with a faux open-end profile. Width scales by size class
   (kind="wrench-sm/md/lg") so the student can tell wrench sizes apart at a
   glance. */
.ws-tile.ws-tile--wrench {
  background: linear-gradient(180deg, #d8d8d8 0%, #888 60%, #444 100%);
  color: #1a1a1a;
  border: 1.5px solid #222;
  border-radius: 6px;
  box-shadow: 0 4px 8px rgba(0,0,0,0.45), inset 0 1px 1px rgba(255,255,255,0.5);
}
.ws-tile.ws-tile--wrench:before {
  background: #888;
  border-color: #222;
  width: 30px;
  height: 8px;
  border-radius: 3px 3px 0 0;
}
.ws-tile.ws-tile--wrench .ws-tile-label { color: #1a1a1a; font-weight: 800; }

/* Coin — round metal puck (used in standalone sort games, separate from POS) */
.ws-tile.ws-tile--coin {
  background: radial-gradient(circle at 35% 30%, #fff 0%, #cfcfcf 50%, #888 100%);
  color: #1a1a1a;
  border-radius: 50%;
  border: 1.5px solid #444;
  min-width: 90px;
  width: 90px;
  height: 90px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ws-tile.ws-tile--coin:before { display: none; }
.ws-tile.ws-tile--coin .ws-tile-label { color: #1a1a1a; font-weight: 800; font-size: 13px; }

/* Box — cardboard for warehouse/packing sorts */
.ws-tile.ws-tile--box {
  background: linear-gradient(180deg, #c89460 0%, #a87038 100%);
  color: #1a0808;
  border: 1.5px solid #5a3018;
  border-radius: 3px;
  box-shadow: 0 4px 8px rgba(0,0,0,0.4), inset 0 -3px 0 rgba(0,0,0,0.18);
}
.ws-tile.ws-tile--box:before {
  background: #5a3018;
  width: 100%;
  left: 0;
  height: 4px;
  top: -1px;
}

/* Wire — cable end, for electrical wire-color sorts */
.ws-tile.ws-tile--wire {
  background: linear-gradient(180deg, #2a2a2a 0%, #0a0a0a 100%);
  color: #FFD15C;
  border: 1.5px solid #000;
  border-radius: 5px;
  box-shadow: 0 4px 8px rgba(0,0,0,0.5);
}
.ws-tile.ws-tile--wire:before {
  background: #d8a838;
  width: 16px; height: 10px;
  left: 50%;
  top: -3px;
  transform: translateX(-50%);
  border-radius: 2px 2px 0 0;
}
.ws-tile.ws-tile--wire .ws-tile-label { color: #FFD15C; font-weight: 800; }

/* Ingredient — shows kitchen items with a softer, less-industrial look */
.ws-tile.ws-tile--ingredient {
  background: linear-gradient(180deg, #fff8ec 0%, #e0d8c0 100%);
  color: #3a1808;
  border: 1.5px solid #a87838;
  border-radius: 8px;
}
.ws-tile.ws-tile--ingredient:before {
  background: #a87838;
  width: 24px;
  border-radius: 50%;
  height: 6px;
  border: none;
}

/* ---------- Buttons ---------- */
.ws-actions {
  display: flex; gap: 12px; justify-content: center;
  padding-top: 8px;
  flex-wrap: wrap;
}
.ws-btn-primary, .ws-btn-tertiary {
  padding: 13px 28px;
  border-radius: 100px;
  font-weight: 800;
  font-size: 14.5px;
  cursor: pointer;
  border: none;
  font-family: inherit;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.ws-btn-primary {
  /* Round-10 reviewer: "Keep going as dark text on a dark blue
     gradient (low contrast)". Cause: gradient went gold→navy with
     navy text — at the navy end the text was navy-on-navy. Now: solid
     gold fill with dark navy text. WCAG AA contrast 7:1+. */
  background: #FFD15C;
  color: #0A2848;
  box-shadow: 0 6px 18px rgba(255,209,92,0.45), inset 0 1px 0 rgba(255,255,255,0.4);
  letter-spacing: 0.3px;
}
.ws-btn-primary:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(255,209,92,0.55), inset 0 1px 0 rgba(255,255,255,0.5); background: #FFE08A; }
.ws-btn-tertiary {
  /* Round-10 reviewer: "Pause and exit as white text on a near-white
     pill (effectively invisible)". Cause: rgba white on rgba white.
     Now: solid dark navy bg + white text. WCAG AA. */
  background: #0A2848;
  color: #fff;
  border: 1.5px solid #FFD15C;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.15),
    0 2px 0 rgba(0,0,0,0.4),
    0 6px 14px rgba(0,0,0,0.45);
  letter-spacing: 0.3px;
  font-family: var(--paes-font-display);
}
.ws-btn-tertiary:hover {
  background: linear-gradient(180deg, rgba(255,209,92,0.2) 0%, rgba(255,179,71,0.06) 100%);
  border-color: #FFD15C;
  transform: translateY(-2px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.3),
    0 4px 0 rgba(0,0,0,0.3),
    0 10px 20px rgba(255,179,71,0.2);
}
/* Hint button stuck-nudge — fires after 10s of inactivity. Big yellow
   glow halo + subtle bounce so the student remembers Hint exists. */
.ws-btn-tertiary.ws-hint-nudge {
  animation: ws-hint-nudge 1.4s ease-in-out infinite;
  border-color: #FFD15C;
}
@keyframes ws-hint-nudge {
  0%, 100% {
    transform: translateY(0);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,0.3),
      0 2px 0 rgba(0,0,0,0.3),
      0 6px 14px rgba(255,179,71,0.3);
  }
  50% {
    transform: translateY(-3px);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,0.5),
      0 5px 0 rgba(0,0,0,0.3),
      0 14px 28px rgba(255,179,71,0.55),
      0 0 24px rgba(255,209,92,0.6);
  }
}

/* ---------- Hint bubble ---------- */
.ws-hint-bubble {
  /* Round-3 roast: bottom-right hint covered the right rubric panel.
     Now anchored bottom-left, same side as the coach pip. */
  position: fixed; bottom: 100px; left: 100px;
  max-width: 280px;
  background: linear-gradient(180deg, var(--paes-gold), #0A4D9B);
  color: var(--paes-navy);
  padding: 12px 16px;
  border-radius: 12px;
  font-size: 13.5px;
  font-weight: 600;
  box-shadow: 0 12px 28px rgba(0,0,0,0.45);
  z-index: 200;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.ws-hint-bubble.show { opacity: 1; transform: translateY(0); }

/* ---------- Flash toast (good/bad/info notes from the engine) ----------
   Positioned high enough to clear the dial keypad's bottom row (Clr / 0 / Set
   keys live ~80px from viewport bottom on standard layouts). The roast
   caught a "Dropped." pill physically blocking the 0 key on KA4/KA6,
   making the lesson hard-stuck. */
.ws-flash {
  /* Round-7 roast: "Cash-register error toast renders OVER the bill
     buttons, partially blocking $1 and $5." Cause: toast was centered
     vertically. Now: anchored at the top, well clear of the action
     buttons at the bottom of every shell. */
  position: fixed; top: 88px; left: 50%;
  transform: translate(-50%, -40px) scale(0.85);
  pointer-events: none;
  background: linear-gradient(180deg, rgba(8,26,45,0.96) 0%, rgba(0,0,0,0.92) 100%);
  color: #fff;
  padding: 14px 28px;
  border-radius: 100px;
  font-size: 14.5px; font-weight: 700;
  letter-spacing: 0.2px;
  font-family: 'Roboto Slab', Georgia, serif;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.15),
    0 14px 36px rgba(0,0,0,0.55);
  /* Round-3 roast: "Resumed from step 2. Carry on." rendered behind
     action buttons. Bumped z-index above every UI layer (toolbar,
     character bubble, chat blob) so toasts are always legible. */
  z-index: 9500;
  opacity: 0;
  transition: transform 0.4s cubic-bezier(.34, 1.5, .64, 1), opacity 0.3s ease;
  border: 1px solid rgba(255,255,255,0.18);
}
.ws-flash.show {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
  top: 88px;
}
.ws-flash.good {
  background: linear-gradient(180deg, #5DD8A0 0%, #1d8b5c 100%);
  color: #fff;
  border-color: rgba(255,255,255,0.3);
  text-shadow: 0 1px 0 rgba(0,0,0,0.2);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.4),
    0 14px 36px rgba(29,139,92,0.45);
}
.ws-flash.bad {
  /* Round-2 roast: "the FIRST wrong-answer toast renders in pale text
     on dark blue — had to zoom to read it." Strengthening with !important
     on background + a heavier text-shadow + a thicker outline ring so
     the first toast can never lose its red identity to a stale base
     class. */
  background: linear-gradient(180deg, #EF402F 0%, #a82820 100%) !important;
  color: #fff !important;
  font-weight: 800 !important;
  font-size: 15.5px !important;
  border-color: rgba(255,255,255,0.45) !important;
  border-width: 2px !important;
  text-shadow: 0 1px 0 rgba(0,0,0,0.5), 0 0 4px rgba(0,0,0,0.4);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.45),
    0 0 0 3px rgba(239,64,47,0.35),
    0 14px 36px rgba(239,64,47,0.55);
  animation: ws-flash-bad-attention 0.55s cubic-bezier(.34, 1.6, .64, 1);
}
@keyframes ws-flash-bad-attention {
  0%   { transform: translate(-50%, 60px) scale(0.6); }
  60%  { transform: translate(-50%, -6px) scale(1.08); }
  100% { transform: translate(-50%, 0) scale(1); }
}

/* Pause-and-exit modal. Replaces the native confirm() dialog the
   Round-2 roast called out as "wrong primitive — froze the Chrome
   extension for 2 minutes." */
.ws-exit-modal {
  /* Round-2 audit: scene was bleeding through behind the modal body
     text. Round-3 audit: still bleeding through at 0.94 alpha when
     backdrop-filter isn't supported. Now fully opaque (0.985) +
     blur as enhancement only. The modal is now a wall, not a veil. */
  position: fixed; inset: 0;
  background: rgba(8, 26, 45, 0.985);
  backdrop-filter: blur(14px) saturate(0.5);
  -webkit-backdrop-filter: blur(14px) saturate(0.5);
  display: flex; align-items: center; justify-content: center;
  z-index: 9700;
  opacity: 0;
  transition: opacity 0.22s ease;
  padding: 24px;
}
.ws-exit-modal.show { opacity: 1; }
.ws-exit-card {
  background: linear-gradient(180deg, #fff 0%, #F7F3EC 100%);
  color: var(--paes-navy);
  border-radius: 22px;
  padding: 28px 30px 24px;
  max-width: 460px;
  width: 100%;
  text-align: left;
  box-shadow: 0 28px 64px rgba(0,0,0,0.55), 0 0 0 1px rgba(255,255,255,0.18);
  transform: scale(0.94);
  transition: transform 0.28s cubic-bezier(.34, 1.5, .64, 1);
}
.ws-exit-modal.show .ws-exit-card { transform: scale(1); }
.ws-exit-eye {
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--paes-cyan);
  font-weight: 800;
  margin-bottom: 8px;
}
.ws-exit-title {
  font-size: 22px;
  font-weight: 800;
  margin: 0 0 8px;
  letter-spacing: -0.2px;
}
.ws-exit-body {
  font-size: 15px;
  line-height: 1.5;
  color: rgba(10,77,155,0.78);
  margin: 0 0 22px;
}
.ws-exit-actions {
  display: flex; gap: 12px; justify-content: flex-end; flex-wrap: wrap;
}
.ws-flash.info {
  background: linear-gradient(180deg, #27A9E1 0%, #0A4D9B 100%);
  color: #fff;
  border-color: rgba(255,255,255,0.3);
  text-shadow: 0 1px 0 rgba(0,0,0,0.2);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.4),
    0 14px 36px rgba(39,169,225,0.5);
}

/* Compact dial-status pill — used when the scene already has a painted
   keypad. Just shows "Set X · live value" instead of duplicating a
   keypad widget below the canvas. */
.ws-proc-dial-mini {
  display: flex; align-items: center; gap: 12px;
  background: rgba(0,0,0,0.4);
  border: 1px solid rgba(39,169,225,0.4);
  border-radius: 12px;
  padding: 8px 14px;
  font-size: 13px;
  color: rgba(255,255,255,0.9);
}
.ws-proc-dial-mini .lab { font-weight: 700; }
.ws-proc-dial-mini .ws-dial-display {
  background: #0a1a0d;
  color: #2bdf6e;
  font-family: monospace;
  font-weight: 800;
  font-size: 16px;
  padding: 4px 10px;
  border-radius: 6px;
  letter-spacing: 1.2px;
  border: 1px solid rgba(43,223,110,0.3);
  min-width: 60px;
  text-align: center;
}

/* Right-panel PAES rubric preview — replaces the duplicate-of-left-rail
   step checklist with a one-line preview of what the student is being
   graded on. Visible during play; the win card has the actual scored
   rubric. */
.ws-proc-rubric-preview {
  margin-top: 18px;
  padding: 14px 16px;
  background: linear-gradient(180deg, rgba(39,169,225,0.10) 0%, rgba(39,169,225,0.04) 100%);
  border: 1px solid rgba(39,169,225,0.28);
  border-radius: 14px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.06);
}
.ws-proc-rubric-h {
  font-size: 10.5px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.55);
  font-weight: 800;
  margin-bottom: 8px;
}
.ws-proc-rubric-pills {
  display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px;
}
.ws-proc-rubric-pill {
  font-size: 11px; font-weight: 700;
  padding: 4px 10px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 100px;
  color: rgba(255,255,255,0.82);
  cursor: help;
}
.ws-proc-rubric-foot {
  font-size: 10.5px;
  color: rgba(255,255,255,0.5);
  font-style: italic;
}

/* Three-strikes fail modal — Round-3 roast S10: "If you can't fail, it
   isn't a game — it's an interactive checklist." Fires when the third
   heart drops. Student picks Restart (full reset) or Keep going. The
   modal is a sibling of the existing exit-modal pattern. */
.ws-fail-modal {
  position: fixed; inset: 0;
  background: rgba(8, 26, 45, 0.78);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center;
  z-index: 9700;
  opacity: 0;
  transition: opacity 0.25s ease;
  padding: 24px;
}
.ws-fail-modal.show { opacity: 1; }
.ws-fail-card {
  background: linear-gradient(180deg, #fff 0%, #F7F3EC 100%);
  color: var(--paes-navy);
  border-radius: 22px;
  padding: 32px 30px 26px;
  max-width: 460px; width: 100%;
  text-align: center;
  box-shadow: 0 28px 64px rgba(0,0,0,0.6), 0 0 0 2px rgba(239,64,47,0.5);
  transform: scale(0.92);
  transition: transform 0.32s cubic-bezier(.34, 1.5, .64, 1);
}
.ws-fail-modal.show .ws-fail-card { transform: scale(1); }
.ws-fail-icon {
  font-size: 56px; line-height: 1;
  margin-bottom: 12px;
  animation: ws-fail-shake 0.6s cubic-bezier(.34,1.6,.64,1);
}
@keyframes ws-fail-shake {
  0%, 100% { transform: translateX(0) rotate(0); }
  25% { transform: translateX(-6px) rotate(-4deg); }
  75% { transform: translateX(6px) rotate(4deg); }
}
.ws-fail-title {
  font-size: 22px; font-weight: 800; margin: 0 0 10px;
  font-family: 'Roboto Slab', Georgia, serif;
  color: #c83030;
}
.ws-fail-body {
  font-size: 15px; line-height: 1.5;
  color: rgba(10,77,155,0.85); margin: 0 0 22px;
}
.ws-fail-actions {
  display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;
}

/* ---------- Cash Register ---------- */
.ws-pos {
  display: grid;
  grid-template-columns: 360px 1fr;
  gap: 22px;
  align-items: start;
}
.ws-pos-receipt, .ws-pos-drawer {
  background: rgba(255,255,255,0.04);
  border: 1.5px solid rgba(255,255,255,0.08);
  border-radius: 16px;
  padding: 18px;
}
.ws-pos-receipt {
  background: linear-gradient(180deg, #FFF 0%, #F7F3EC 100%);
  color: var(--paes-navy);
  font-family: 'SF Mono', Menlo, monospace;
}
.ws-receipt-h {
  font-size: 11px; letter-spacing: 2px;
  text-transform: uppercase;
  font-weight: 800;
  color: rgba(10,77,155,0.55);
  margin-bottom: 10px;
}
.ws-pos-drawer .ws-receipt-h { color: rgba(255,255,255,0.55); }
.ws-receipt-row {
  display: flex; justify-content: space-between;
  padding: 6px 0;
  font-size: 14px;
  border-bottom: 1px dashed rgba(10,77,155,0.12);
}
.ws-receipt-row:last-child { border-bottom: none; }
/* Sub-line under a receipt row (e.g. tax-rounding caption). Right-aligned so
   the "→ $1.50" tracks the cent figure above it, italic so it reads as a
   note rather than another line item. */
.ws-receipt-sub {
  font-size: 11px;
  font-style: italic;
  color: rgba(10,77,155,0.6);
  text-align: right;
  padding: 2px 0 6px;
  line-height: 1.35;
  border-bottom: 1px dashed rgba(10,77,155,0.12);
  letter-spacing: 0.1px;
}
.ws-receipt-row.total { font-weight: 800; padding-top: 10px; }
.ws-receipt-row.paid { color: var(--paes-teal); font-weight: 700; }
.ws-receipt-row.change {
  background: rgba(39,169,225,0.18);
  margin: 8px -10px 0;
  padding: 10px;
  border-radius: 6px;
  font-weight: 800;
  font-size: 16px;
  border-bottom: none;
}
.ws-change-amount { color: var(--paes-gold); font-size: 18px; }
/* Reconcile diff indicator: gold by default, red when short, green when over */
.ws-change-amount.over  { color: var(--paes-green); }
.ws-change-amount.short { color: var(--paes-red); }

.ws-coin-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin-bottom: 16px;
}
.ws-coin-btn {
  background: linear-gradient(180deg, #fff 0%, #F0E8D8 100%);
  color: var(--paes-navy);
  border: 1.5px solid rgba(39,169,225,0.4);
  border-radius: 12px;
  padding: 14px 6px 10px;
  font-family: inherit;
  cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  transition: transform 0.1s ease, box-shadow 0.1s ease;
  font-weight: 800;
}
.ws-coin-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 14px rgba(0,0,0,0.3); }
.ws-coin-btn:active { transform: translateY(0); }
/* Fixed-height glyph slot tall enough for the largest coin (25¢ at 52px)
   so smaller denominations center cleanly and nothing bleeds into the
   label below. */
.ws-coin-glyph {
  font-size: 22px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  height: 56px;
}
.ws-coin-glyph svg { display: block; }
.ws-coin-label { font-size: 12.5px; font-weight: 800; }

.ws-pos-given {
  background: rgba(0,0,0,0.2);
  border-radius: 10px;
  padding: 12px;
  margin-top: 10px;
}
.ws-given-h {
  font-size: 10.5px; letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.55);
  font-weight: 800;
  margin-bottom: 6px;
}
.ws-given-amount {
  font-family: 'SF Mono', Menlo, monospace;
  font-size: 28px; font-weight: 800;
  color: var(--paes-gold);
  letter-spacing: 0.5px;
  margin-bottom: 6px;
}
.ws-given-pieces {
  display: flex; flex-wrap: wrap; gap: 4px;
}
.ws-piece-pill {
  background: rgba(39,169,225,0.18);
  color: var(--paes-gold);
  padding: 2px 8px;
  border-radius: 100px;
  font-size: 11px;
  font-weight: 700;
}

/* ---------- Type-target ---------- */
.ws-typing {
  display: flex; flex-direction: column; gap: 16px; align-items: center;
}
.ws-typing-monitor {
  width: 100%; max-width: 760px;
  display: flex; flex-direction: column; align-items: center;
}
.ws-typing-monitor-frame {
  width: 100%;
  background: linear-gradient(180deg, #2a2a2a 0%, #0a0a0a 100%);
  border: 8px solid #1a1a1a;
  border-radius: 10px;
  padding: 14px 18px;
  box-shadow: 0 12px 28px rgba(0,0,0,0.5), inset 0 0 14px rgba(0,0,0,0.6);
}
.ws-typing-monitor-top {
  font-size: 6px;
  letter-spacing: 6px;
  color: #555;
  text-align: right;
  margin-bottom: 6px;
}
.ws-typing-monitor-stand {
  width: 60px; height: 24px;
  background: linear-gradient(180deg, #222 0%, #111 100%);
  border-radius: 0 0 6px 6px;
  margin-top: -2px;
}
.ws-typing-monitor-base {
  width: 160px; height: 8px;
  background: linear-gradient(180deg, #333 0%, #111 100%);
  border-radius: 4px;
  box-shadow: 0 4px 8px rgba(0,0,0,0.4);
}
.ws-typing-keyboard {
  margin-top: 6px;
  width: 100%; max-width: 580px;
  text-align: center;
}
.ws-typing-keyboard svg { width: 100%; max-width: 480px; height: auto; filter: drop-shadow(0 6px 12px rgba(0,0,0,0.4)); }
.ws-typing-keyboard-hint {
  margin-top: 8px;
  font-size: 11px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.55);
  font-weight: 800;
}
.ws-typing-target, .ws-typing-input-wrap {
  background: rgba(255,255,255,0.04);
  border: 1.5px solid rgba(255,255,255,0.08);
  border-radius: 14px;
  padding: 18px;
}
.ws-typing-target { background: linear-gradient(180deg, #fff 0%, #F7F3EC 100%); color: var(--paes-navy); }
.ws-typing-eye {
  font-size: 11px; letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(10,77,155,0.55);
  font-weight: 800;
  margin-bottom: 8px;
}
.ws-typing-text {
  font-family: 'SF Mono', Menlo, monospace;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.4px;
  line-height: 1.6;
  color: var(--paes-navy);
  /* Preserve newlines in target text so multi-line passages (addresses,
     memos, todo lists) render the way the student needs to type them.
     `overflow-wrap: anywhere` is the belt-and-braces fix for long pangrams
     that previously bled off the right edge of the monitor card — long
     unbroken token? wrap at any character boundary. `max-width: 100%` and
     `box-sizing` make sure the text never escapes the card. */
  white-space: pre-wrap;
  /* Prefer space-boundary wrapping. Avoid mid-word breaks except when a
     genuinely unbreakable string (URL, long ID) would otherwise overflow. */
  overflow-wrap: break-word;
  word-break: normal;
  max-width: 100%;
  box-sizing: border-box;
}
.ws-typing-target {
  /* The parent card was previously letting child text overflow horizontally
     because the card's intrinsic width adapted to its content. Lock the
     content area so wrapping kicks in. */
  max-width: 100%;
  box-sizing: border-box;
  overflow: hidden;
}
.ws-tc { padding: 1px 0; transition: background 0.15s ease, color 0.15s ease; }
.ws-tc.right { background: rgba(93,216,160,0.4); color: var(--paes-navy); border-radius: 2px; }
.ws-tc.wrong { background: rgba(227,107,92,0.6); color: #fff; border-radius: 2px; }
.ws-tc.cursor { background: rgba(39,169,225,0.6); color: var(--paes-navy); animation: ws-blink 1s ease-in-out infinite; }
@keyframes ws-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }

.ws-typing-input {
  width: 100%;
  min-height: 110px;
  background: rgba(0,0,0,0.25);
  border: 1.5px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  padding: 14px;
  font-family: 'SF Mono', Menlo, monospace;
  font-size: 16px;
  color: #fff;
  resize: vertical;
  outline: none;
  letter-spacing: 0.4px;
  line-height: 1.6;
}
.ws-typing-input:focus { border-color: var(--paes-gold); box-shadow: 0 0 0 3px rgba(39,169,225,0.18); }
.ws-typing-stats {
  font-family: 'SF Mono', Menlo, monospace;
  font-size: 12px;
  color: rgba(255,255,255,0.6);
  margin-top: 6px;
  text-align: right;
}

/* ---------- Measure-read (ruler / tape / cup / scale / thermometer) ---------- */
.ws-measure-context {
  display: flex;
  justify-content: center;
  margin: 4px 0 8px;
  filter: drop-shadow(0 4px 6px rgba(0,0,0,0.4));
}
.ws-measure-context svg { background: rgba(255,255,255,0.04); border-radius: 8px; padding: 8px; }
.ws-measure-stage {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  padding: 18px 14px 0;
  background:
    linear-gradient(180deg, rgba(160,120,72,0.18) 0%, rgba(90,56,24,0.32) 100%),
    repeating-linear-gradient(0deg, transparent 0px, transparent 14px, rgba(0,0,0,0.04) 14px, rgba(0,0,0,0.04) 15px);
  border-radius: 18px;
  position: relative;
  min-height: 320px;
  border: 1.5px solid rgba(255,255,255,0.06);
  margin: 0 auto;
  max-width: 760px;
}
.ws-measure-stage:after {
  content: '';
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: 18px;
  background: linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.7) 100%);
  border-radius: 0 0 18px 18px;
  pointer-events: none;
}
.ws-tool {
  background:
    radial-gradient(ellipse at 50% 30%, rgba(255,255,255,0.06) 0%, transparent 60%),
    rgba(0,0,0,0.18);
  border: 1.5px solid rgba(255,255,255,0.08);
  border-radius: 16px;
  padding: 16px 22px 14px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  max-width: 100%;
  position: relative;
  z-index: 1;
  box-shadow: 0 6px 16px rgba(0,0,0,0.4);
}
.ws-tool-cap {
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.55);
  font-weight: 800;
}
.ws-measure-options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  max-width: 720px;
  margin: 0 auto;
  width: 100%;
}
.ws-measure-opt {
  background: linear-gradient(180deg, #fff 0%, #F7F3EC 100%);
  color: var(--paes-navy);
  border: 2px solid transparent;
  border-radius: 14px;
  padding: 18px 14px;
  font-family: inherit;
  font-size: 20px;
  font-weight: 800;
  letter-spacing: 0.3px;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(0,0,0,0.25);
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.2s ease;
  min-height: 64px;
}
.ws-measure-opt:hover { transform: translateY(-2px); box-shadow: 0 8px 18px rgba(0,0,0,0.35); }
.ws-measure-opt.right { border-color: var(--paes-green); box-shadow: 0 0 0 3px rgba(93,216,160,0.5); }
.ws-measure-opt.wrong { border-color: var(--paes-red); box-shadow: 0 0 0 3px rgba(227,107,92,0.5); animation: ws-shake 0.4s ease; }

/* ---------- Task-pick (tap-to-answer with picture tiles) ---------- */
.ws-task-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
  max-width: 880px;
  margin: 0 auto;
  width: 100%;
}
.ws-task-tile {
  background: linear-gradient(180deg, #fff 0%, #F7F3EC 100%);
  color: var(--paes-navy);
  border: 2px solid transparent;
  border-radius: 16px;
  padding: 22px 16px;
  font-family: inherit;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  box-shadow: 0 4px 14px rgba(0,0,0,0.28);
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.2s ease;
  min-height: 140px;
}
.ws-task-tile:hover { transform: translateY(-3px); box-shadow: 0 10px 22px rgba(0,0,0,0.4); }
.ws-task-tile.right { border-color: var(--paes-green); box-shadow: 0 0 0 4px rgba(93,216,160,0.5); }
.ws-task-tile.wrong { border-color: var(--paes-red); box-shadow: 0 0 0 4px rgba(227,107,92,0.5); animation: ws-shake 0.4s ease; }
.ws-task-glyph { font-size: 48px; line-height: 1; }
/* Round-9: SVG-icon variant. Different sizing rules than emoji glyphs. */
.ws-task-glyph-svg { display: flex; align-items: center; justify-content: center; padding: 6px; }
.ws-task-glyph-svg svg { width: 56px; height: 56px; }
.ws-task-label {
  font-size: 16px;
  font-weight: 800;
  letter-spacing: -0.2px;
  text-align: center;
  line-height: 1.25;
}
html.paes-picture-first .ws-task-glyph { font-size: 56px; }
html.paes-picture-first .ws-task-label { font-size: 18px; }
html.paes-picture-first .ws-measure-opt { font-size: 22px; }

/* Task-pick scene · illustrated context above the option tiles */
.ws-task-scene {
  display: flex;
  justify-content: center;
  align-items: center;
  background: linear-gradient(180deg, rgba(255,255,255,0.04) 0%, rgba(0,0,0,0.18) 100%);
  border: 1.5px solid rgba(255,255,255,0.08);
  border-radius: 16px;
  padding: 22px 28px;
  margin: 0 auto;
  /* Round-11 roast: was 720px which made the picture stage tiny
     even on 1440 viewports. Bumped to 1100 so the SVG illustration
     can grow into the full middle column. */
  max-width: 1100px;
  min-height: 280px;
}
/* SVG was capped at 660×320 — that's why the scene felt cramped even
   when the parent had room. Bumped to 1000×560 so the picture stage
   actually uses the column it has. SVGs are lossless — scaling up has
   no penalty. */
.ws-task-scene svg { width: 100%; max-width: 1000px; height: auto; max-height: 560px; }

/* =================================================================
   PROCEDURE TEMPLATE (high-fidelity)
   3-column layout · left rail steps · center scene + toolbar · right rail context
   ================================================================= */
.ws-proc {
  display: grid;
  /* Round-3 roast: right-rail rubric truncated 14 of 31 games at 36
     chars. Bumped right column to 320px so success criteria can wrap
     gracefully without the 33-char clip.
     Round-11 roast: scene was getting ~440px on a 1440 viewport which
     made the SVG illustration look like a postage stamp. Sidebars
     bumped to 300/360 so step text + rubric have room, max-width
     opened to 1600 so the center scene can be ~900px wide and the
     SVG can fill its column instead of capping at 660.
     Round-3 audit (round-25): "On 1568 wide viewport, canvas is ~800px,
     two-thirds of page is dead space." Now: bump max-width to full
     viewport (no cap), bring sidebars down to 260/300, and on body
     class .ws-canvas-max collapse the right rail to a 44px strip with
     just an info button so the canvas can stretch even further. */
  grid-template-columns: 260px 1fr 300px;
  gap: 22px;
  width: 100%;
  max-width: 1840px;
  margin: 0 auto;
  align-items: stretch;
}
body.ws-canvas-max .ws-proc { grid-template-columns: 260px 1fr 44px; }
body.ws-canvas-max .ws-proc-right { padding: 8px 4px; overflow: hidden; position: relative; }
body.ws-canvas-max .ws-proc-right > *:not(.ws-rail-toggle) { display: none; }
.ws-proc-right { position: relative; }
.ws-rail-toggle {
  position: absolute; top: 8px; right: 8px;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: rgba(39,169,225,0.18);
  border: 1px solid rgba(39,169,225,0.5);
  color: var(--paes-cyan, #27A9E1);
  font-size: 14px; font-weight: 800;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  z-index: 100;
}
.ws-rail-toggle:hover { background: rgba(39,169,225,0.32); }
/* Glassmorphism rail — translucent over a subtle gradient backdrop with
   a top highlight strip and inset bottom shadow. The "card floating in
   front of light" effect. */
.ws-proc-rail {
  position: relative;
  background:
    linear-gradient(180deg, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0.015) 100%),
    linear-gradient(180deg, rgba(10,30,55,0.55) 0%, rgba(0,0,0,0.55) 100%);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 22px;
  /* Round-3 + Round-4 roast: KEY RULE / step 5 / step 6 clipped behind
     the bottom-left a11y bar in 10+ games. Round-3 fix added 80px
     bottom padding inside the rail. Round-4 caught the deeper bug:
     when rail content was tall, the rail itself extended past the
     viewport (no max-height cap), so even with internal padding the
     bottom content lived BELOW the visible viewport — and the fixed
     a11y bar sat over the visible portion. Now the rail caps at
     viewport-height-minus-header so it scrolls INTERNALLY rather
     than pushing past the viewport. The 80px bottom padding still
     reserves clearance from the a11y bar at the rail's visible base. */
  padding: 26px 22px 80px;
  max-height: calc(100vh - 120px);
  display: flex;
  flex-direction: column;
  backdrop-filter: blur(14px) saturate(115%);
  -webkit-backdrop-filter: blur(14px) saturate(115%);
  overflow-y: auto;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.18),
    inset 0 -1px 0 rgba(0,0,0,0.35),
    0 16px 36px rgba(0,0,0,0.4),
    0 1px 2px rgba(0,0,0,0.6);
}
.ws-proc-rail::-webkit-scrollbar { width: 6px; }
.ws-proc-rail::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.18); border-radius: 3px; }
.ws-proc-rail::before {
  content: '';
  position: absolute;
  top: 0; left: 8%; right: 8%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.7), transparent);
  pointer-events: none;
}
.ws-proc-eye {
  font-size: 11px;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--paes-cyan);
  font-weight: 800;
  margin-bottom: 18px;
  font-family: var(--paes-font-display);
}
/* Connecting spine: a soft vertical line that runs from the first step
   number down to the last, behind the dots — turns "list of cards" into
   "journey/path." Built with a single ::before on .ws-proc-steps. */
.ws-proc-steps {
  display: flex;
  flex-direction: column;
  gap: 8px;
  position: relative;
  padding-left: 4px;
}
.ws-proc-steps::before {
  content: '';
  position: absolute;
  left: 21px; top: 26px; bottom: 26px;
  width: 2px;
  background: linear-gradient(180deg, rgba(39,169,225,0.4), rgba(255,255,255,0.08));
  border-radius: 1px;
  pointer-events: none;
}
.ws-proc-step {
  display: flex;
  gap: 10px;
  padding: 9px 12px;
  border-radius: 12px;
  background: rgba(255,255,255,0.025);
  border: 1px solid rgba(255,255,255,0.06);
  transition: all 0.28s cubic-bezier(.34, 1.5, .64, 1);
  position: relative;
}
/* Tighten step rows on 6-step games (EP6, KA3, FS2, WP1, WP5, WP6) so
   all six fit without scrolling. Reviewer Round-4: "EP6 step 1 of 6:
   only 5 steps fit; step 6 is below the fold."
   Round-3 audit (round-26): "HC5 7-step progress bar segments shrink to
   spaghetti." Now: 7+ step lessons get a more compact step row so all
   seven fit comfortably in the left rail without scrolling. */
.ws-proc-steps { gap: 6px; }
.ws-proc-steps:has(> .ws-proc-step:nth-child(7)) { gap: 4px; }
.ws-proc-steps:has(> .ws-proc-step:nth-child(7)) .ws-proc-step { padding: 6px 10px; }
.ws-proc-steps:has(> .ws-proc-step:nth-child(7)) .ws-proc-num { width: 28px; height: 28px; font-size: 12px; }
.ws-proc-step.active {
  background:
    linear-gradient(180deg, rgba(255,209,92,0.16) 0%, rgba(255,179,71,0.06) 100%);
  border-color: rgba(255,209,92,0.55);
  box-shadow:
    0 0 0 3px rgba(255,209,92,0.12),
    0 12px 28px rgba(255,179,71,0.15),
    inset 0 1px 0 rgba(255,255,255,0.18);
  transform: translateX(2px);
}
.ws-proc-step.done {
  opacity: 0.55;
  background: rgba(93,216,160,0.05);
  border-color: rgba(93,216,160,0.18);
}
.ws-proc-num {
  width: 34px; height: 34px;
  border-radius: 50%;
  background: linear-gradient(180deg, rgba(255,255,255,0.10) 0%, rgba(255,255,255,0.03) 100%);
  border: 1.5px solid rgba(255,255,255,0.20);
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 800;
  flex-shrink: 0;
  color: rgba(255,255,255,0.75);
  font-family: var(--paes-font-display);
  position: relative;
  z-index: 1;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.2),
    0 2px 4px rgba(0,0,0,0.3);
}
.ws-proc-step.active .ws-proc-num {
  background: linear-gradient(180deg, #FFD15C 0%, #F5A623 100%);
  color: #2a1a05;
  border-color: rgba(255,209,92,0.9);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.6),
    0 0 0 4px rgba(255,209,92,0.18),
    0 6px 14px rgba(255,179,71,0.4);
  animation: ws-num-glow 1.6s ease-in-out infinite alternate;
}
@keyframes ws-num-glow {
  0%   { box-shadow: inset 0 1px 0 rgba(255,255,255,0.6), 0 0 0 4px rgba(255,209,92,0.18), 0 6px 14px rgba(255,179,71,0.4); }
  100% { box-shadow: inset 0 1px 0 rgba(255,255,255,0.7), 0 0 0 6px rgba(255,209,92,0.28), 0 8px 22px rgba(255,179,71,0.6); }
}
.ws-proc-step.done .ws-proc-num {
  background: linear-gradient(180deg, #5DD8A0 0%, #1d8b5c 100%);
  color: #fff;
  border-color: rgba(93,216,160,0.9);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.4),
    0 4px 10px rgba(29,139,92,0.45);
}
.ws-proc-step.done .ws-proc-num span { display: none; }
.ws-proc-step.done .ws-proc-num:before { content: '✓'; font-size: 16px; }
.ws-proc-body { display: flex; flex-direction: column; gap: 5px; min-width: 0; padding-top: 2px; flex: 1; }
.ws-proc-text {
  font-size: 13.5px;
  line-height: 1.45;
  color: rgba(255,255,255,0.92);
  font-weight: 500;
  /* Roast caught step text being clipped mid-word ("...service-/etiq...",
     "...thermal", "...across the"). Allow proper wrapping + ensure the
     panel can scroll if there are too many or too long steps. */
  word-break: normal;
  overflow-wrap: anywhere;
  hyphens: none;
  white-space: normal;
}
.ws-proc-step.active .ws-proc-text { color: #fff; font-weight: 600; }
.ws-proc-text strong { color: #fff; }
/* Sub-hint that explicitly maps the prose to the toolbar button. */
.ws-proc-cta {
  display: none;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.4px;
  color: var(--paes-cyan);
  margin-top: 4px;
}
.ws-proc-step.active .ws-proc-cta { display: block; }
.ws-proc-cta-verb {
  background: linear-gradient(180deg, rgba(39,169,225,0.4) 0%, rgba(39,169,225,0.18) 100%);
  border: 1px solid rgba(39,169,225,0.6);
  border-radius: 8px;
  padding: 2px 8px;
  letter-spacing: 0.7px;
  color: #fff;
  font-size: 11px;
  text-transform: uppercase;
  font-weight: 800;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.25);
}

.ws-proc-rule {
  margin-top: 22px;
  padding: 14px 16px;
  background:
    linear-gradient(180deg, rgba(239,64,47,0.12) 0%, rgba(239,64,47,0.04) 100%);
  border: 1px solid rgba(239,64,47,0.35);
  border-radius: 14px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.08);
}
.ws-proc-rule-h {
  font-size: 11px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--paes-red);
  font-weight: 800;
  margin-bottom: 6px;
}
.ws-proc-rule-body {
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(255,255,255,0.85);
}

/* ---------- Center stage ---------- */
.ws-proc-stage {
  background:
    radial-gradient(circle at 50% 30%, rgba(255,179,71,0.06) 0%, transparent 60%),
    linear-gradient(180deg, rgba(255,255,255,0.03) 0%, rgba(0,0,0,0.25) 100%);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 20px;
  display: flex;
  flex-direction: column;
  padding: 28px 24px;
  min-height: 460px;
  position: relative;
}
.ws-proc-scene {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* Aspect ratio matches the standard 540×340 viewBox. Container sizes
     to the SVG natural aspect so there is NO dead vertical space below
     the artwork (which is what made the panel feel like a tiny diagram
     floating in a navy void). */
  aspect-ratio: 540 / 340;
  position: relative;
  margin-bottom: 6px;
  border-radius: 18px;
  overflow: hidden;
  isolation: isolate;
  box-shadow:
    inset 0 0 60px rgba(0, 0, 0, 0.55),
    0 18px 40px rgba(0, 0, 0, 0.45);
}
/* Vignette overlay — darkens corners, brightens center. Sits above the
   scene SVG via z-index, doesn't intercept clicks. */
.ws-proc-scene::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 50% 38%, rgba(255,209,92,0.10) 0%, transparent 45%),
    radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.55) 100%);
  pointer-events: none;
  z-index: 4;
  mix-blend-mode: multiply;
}
/* Floating dust motes / sparkles — pure CSS particle layer, no JS.
   Six radial gradients drifting at different speeds give the scene a
   "things are happening even at rest" pulse. */
.ws-proc-scene::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle 1.5px at 18% 22%, rgba(255,255,255,0.85), transparent 70%),
    radial-gradient(circle 1px   at 72% 38%, rgba(255,209,92,0.7), transparent 70%),
    radial-gradient(circle 2px   at 38% 64%, rgba(39,169,225,0.6), transparent 70%),
    radial-gradient(circle 1px   at 88% 78%, rgba(255,255,255,0.7), transparent 70%),
    radial-gradient(circle 1.5px at 12% 84%, rgba(255,209,92,0.55), transparent 70%),
    radial-gradient(circle 1px   at 58% 18%, rgba(255,255,255,0.6), transparent 70%);
  background-size:
    240px 240px, 320px 320px, 200px 200px,
    280px 280px, 360px 360px, 220px 220px;
  pointer-events: none;
  z-index: 3;
  animation: ws-mote-drift 18s linear infinite;
  opacity: 0.7;
}
@keyframes ws-mote-drift {
  0%   { background-position: 0 0,   0 0,   0 0,   0 0,   0 0,   0 0; }
  100% { background-position: 60px -120px, -90px -180px, 70px -100px, -110px -140px, 80px -160px, -50px -200px; }
}
.ws-proc-scene svg { position: relative; z-index: 2; }
/* Per-step mood tint. Steps add a class like "step-clear", "step-spray",
   "step-scrub", "step-rinse", "step-dry" to the scene root. The tint
   shifts the entire frame's white-balance to evoke that step's mood. */
.ws-proc-scene.step-clear  > svg { filter: brightness(1.02) saturate(1.05); }
.ws-proc-scene.step-spray  > svg { filter: brightness(1.0)  saturate(1.15) hue-rotate(-3deg); }
.ws-proc-scene.step-scrub  > svg { filter: brightness(1.05) saturate(1.1); }
.ws-proc-scene.step-rinse  > svg { filter: brightness(0.96) saturate(1.2)  hue-rotate(6deg); }
.ws-proc-scene.step-dry    > svg { filter: brightness(1.08) saturate(0.95); }
.ws-proc-scene > svg { transition: filter 0.6s ease; }
.ws-proc-scene svg {
  width: 100%;
  height: 100%;
  display: block;
}
/* SVG text uses its native font-size attribute. Earlier I added a CSS
   override that bumped every label up — that made text legible in
   isolation but blew through the artwork's positioning lanes
   ("PRESS · COOK · LISTEN", "FRONT-LOAD WASHER", "DRUM" all bleeding
   across neighbouring elements). The right answer is to render the
   SVG bigger overall (the center stage handles that), not to scale
   the text outside its layout. */
.ws-proc-scene svg text { font-weight: 700; }
.ws-proc-prompt {
  /* Round-4 roast: yellow speech-bubble dropped into dead space below
     scene. Now: tight inline element between scene and toolbar with
     minimal vertical margin so the dead band collapses without
     overlapping the toolbar via absolute positioning gymnastics. */
  margin: 6px 0 4px;
  min-height: 0;
  display: flex;
  justify-content: center;
}
/* Headline callout — replaces the tiny yellow pill. Reads as the primary
   thing on screen, like Duolingo's "Tap the matching pair" prompt. */
.ws-proc-step-cta {
  position: relative;
  text-align: center;
  background: linear-gradient(180deg, #FFD15C 0%, #F5A623 100%);
  color: #2A1A05;
  padding: 10px 24px 12px;
  border-radius: 14px;
  font-size: 17px;
  font-weight: 800;
  letter-spacing: 0.2px;
  line-height: 1.2;
  display: inline-block;
  margin: 0 auto;
  box-shadow:
    0 3px 0 rgba(180, 100, 0, 0.85),
    0 8px 18px rgba(0, 0, 0, 0.3),
    inset 0 1px 0 rgba(255, 255, 255, 0.6);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.45);
  font-family: var(--paes-font-display);
  animation: ws-cta-breathe 2.6s ease-in-out infinite;
  max-width: 92%;
}
/* Speech-bubble tail anchored to top, pointing into the scene above. */
.ws-proc-step-cta::before {
  content: '';
  position: absolute;
  top: -8px;
  left: 50%;
  transform: translateX(-50%);
  width: 0;
  height: 0;
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;
  border-bottom: 10px solid #FFD15C;
}
@keyframes ws-cta-breathe {
  0%, 100% { transform: translateY(0) scale(1); box-shadow: 0 4px 0 rgba(180,100,0,0.85), 0 12px 24px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.6); }
  50%      { transform: translateY(-2px) scale(1.015); box-shadow: 0 6px 0 rgba(180,100,0,0.85), 0 16px 32px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.7), 0 0 24px rgba(255,209,92,0.5); }
}

/* Toolbar - generic verb buttons, ALL enabled. Top padding leaves
   room for the "↑ in the scene" cue that sits above the active
   label-only verb without overlapping the prompt above. Vertical gap
   between rows ensures cues never bleed onto the row below either. */
.ws-proc-toolbar {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 28px 10px;
  padding-top: 22px;
}
.ws-verb-btn {
  background:
    linear-gradient(180deg, rgba(255,255,255,0.14) 0%, rgba(255,255,255,0.04) 100%);
  border: 1.5px solid rgba(255,255,255,0.22);
  color: #fff;
  padding: 14px 22px;
  border-radius: 16px;
  cursor: pointer;
  font-weight: 800;
  font-size: 14.5px;
  font-family: var(--paes-font-display);
  transition: all 0.18s cubic-bezier(.34, 1.5, .64, 1);
  display: flex;
  align-items: center;
  gap: 9px;
  letter-spacing: 0.3px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.25),
    0 2px 0 rgba(0,0,0,0.35),
    0 6px 12px rgba(0,0,0,0.25);
  text-shadow: 0 1px 1px rgba(0,0,0,0.4);
}
.ws-verb-btn:hover {
  border-color: #FFD15C;
  background: linear-gradient(180deg, rgba(255,209,92,0.22) 0%, rgba(255,179,71,0.08) 100%);
  transform: translateY(-2px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.35),
    0 4px 0 rgba(0,0,0,0.35),
    0 10px 20px rgba(255,179,71,0.25);
}
/* Round-3 audit Tip 5: tool icons rely on visual recognition. Show the
   tool name + action verb on hover so a student who can't parse the
   pictogram still gets the verbal cue. The aria-label already carries
   "<label> verb · <id>" so the tooltip uses that text directly. */
.ws-verb-btn::after {
  content: attr(aria-label);
  position: absolute;
  bottom: calc(100% + 6px); left: 50%;
  transform: translateX(-50%);
  background: rgba(8,26,45,0.96);
  color: #FFD15C;
  padding: 5px 10px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
  letter-spacing: 0.3px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
  border: 1px solid rgba(39,169,225,0.35);
  z-index: 200;
}
.ws-verb-btn:hover::after,
.ws-verb-btn:focus-visible::after { opacity: 1; }
.ws-verb-btn:active {
  /* Round-8 prescription Section 6: squash on tap. 5% scale + bounce
     gives the button real tactile feedback. */
  transform: translateY(1px) scale(0.96);
  transition: transform 80ms ease-out;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.2),
    0 1px 0 rgba(0,0,0,0.35),
    0 3px 6px rgba(0,0,0,0.3);
}
.ws-verb-btn.ws-pop {
  animation: ws-verb-pop 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes ws-verb-pop {
  0%   { transform: scale(0.92); }
  60%  { transform: scale(1.06); }
  100% { transform: scale(1); }
}
.ws-verb-btn.shake { animation: ws-shake 0.4s ease; border-color: var(--paes-red); }
/* Active-step verb: the button that matches the active step's action.
   Pulses softly + brightens border so the student knows which tool the
   step is asking for. */
.ws-verb-btn.is-active-step {
  border-color: var(--paes-cyan);
  background: linear-gradient(180deg, rgba(39,169,225,0.22) 0%, rgba(39,169,225,0.08) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.3),
    0 0 0 0 rgba(39,169,225,0.45),
    0 6px 16px rgba(0,0,0,0.3);
  animation: ws-verb-pulse 1.5s ease-in-out infinite;
}
@keyframes ws-verb-pulse {
  0%, 100% { box-shadow: inset 0 1px 0 rgba(255,255,255,0.3), 0 0 0 0 rgba(39,169,225,0.45), 0 6px 16px rgba(0,0,0,0.3); }
  50%      { box-shadow: inset 0 1px 0 rgba(255,255,255,0.4), 0 0 0 10px rgba(39,169,225,0), 0 8px 22px rgba(0,0,0,0.4); }
}
/* Auto-armed verbs (drag / dial / target / count steps) — the action
   happens IN the scene, not by clicking the button. So the button is
   downgraded to a LABEL: dimmed, cursor default, no hover lift. The
   pulsing active-step ring still highlights which verb the step
   is in, but the visual contract reads "info, not a button." */
.ws-verb-btn.is-label-only {
  cursor: default;
  opacity: 0.85;
}

/* Decoy: this verb is in the toolbar but never used by any step in this
   lesson (e.g. "Wash" in a microwave hot-chocolate game). Heavy dim,
   single 🔒 corner badge, click does nothing. Roast Round 2 caught the
   old text badge ("NOT FOR THIS JOB") wrapping to two lines and
   overflowing into the verb label below — "JOBCut", "JOBTape" etc.
   Now the badge is one character (🔒), 14px wide, can't wrap, and the
   button reserves padding-top so even at the smallest button width the
   label is never pushed over. */
.ws-verb-btn.is-decoy {
  opacity: 0.32;
  filter: grayscale(0.7);
  cursor: not-allowed;
  pointer-events: none;
  position: relative;
  padding-top: 16px;
}
.ws-verb-btn.is-decoy::before {
  content: '🔒';
  position: absolute;
  top: 2px;
  right: 4px;
  font-size: 11px;
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
}

/* Mid-dim: this verb IS used elsewhere in this lesson, but isn't the
   current step's action. The student can still see it as part of the
   tool set but it's clearly secondary. */
.ws-verb-btn.is-not-now {
  opacity: 0.55;
}
.ws-verb-btn.is-not-now:hover {
  opacity: 0.78;
}

/* In-scene appliance keys (microwave keypad, toaster oven, etc.) — when
   the engine arms them as input via [data-dial-key], we add this class
   so the painted button looks pressable and pulses subtly. */
[data-dial-key] { cursor: pointer; }
[data-dial-key]:hover rect:first-of-type {
  fill: #5a5a5a;
  stroke: #FFD15C;
  stroke-width: 1.5;
}
[data-dial-key].ws-scene-key rect:first-of-type {
  animation: ws-scene-key-pulse 2.2s ease-in-out infinite;
}
[data-dial-key].ws-scene-key:active rect:first-of-type {
  fill: #2a2a2a;
}
@keyframes ws-scene-key-pulse {
  0%, 100% { stroke: #666; }
  50%      { stroke: #FFD15C; }
}
.ws-verb-btn.is-label-only:hover {
  transform: none;
  border-color: rgba(255,255,255,0.22);
  background: linear-gradient(180deg, rgba(255,255,255,0.14) 0%, rgba(255,255,255,0.04) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.25),
    0 2px 0 rgba(0,0,0,0.35),
    0 6px 12px rgba(0,0,0,0.25);
}
.ws-verb-btn.is-label-only.is-active-step:hover {
  border-color: var(--paes-cyan);
  background: linear-gradient(180deg, rgba(39,169,225,0.22) 0%, rgba(39,169,225,0.08) 100%);
}
/* "Action is in the scene" cue. Lives ABOVE the verb button (between
   the scene and the toolbar) so it never collides with verb buttons
   on the next row of the toolbar. The arrow points UP into the scene. */
.ws-verb-btn.is-label-only.is-active-step::after {
  content: '\2191 in the scene';
  position: absolute;
  top: -18px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 10px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--paes-cyan);
  font-weight: 800;
  white-space: nowrap;
  font-family: 'Roboto Slab', Georgia, serif;
  opacity: 0;
  animation: ws-verb-hint-fade 0.5s ease-out 0.2s forwards;
  pointer-events: none;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
@keyframes ws-verb-hint-fade {
  from { opacity: 0; transform: translate(-50%, 4px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
.ws-verb-btn { position: relative; }
.ws-verb-glyph { font-size: 18px; line-height: 1; }
.ws-verb-label { font-size: 13.5px; }
/* Round-2 audit: tool id rendered as a subtle sub-label after the gesture
   verb, so "Drag · Strip" reads as one button with two pieces of info. */
.ws-verb-sub {
  font-size: 11.5px;
  font-weight: 600;
  opacity: 0.7;
  margin-left: 2px;
  letter-spacing: 0.2px;
}

/* Prop bump — fired when the student clicks an "auto-armed" verb to
   point them at where the real action lives in the scene.
   Round-3 roast: "Watch the workshop image. It does not change." Cause:
   the old bump (scale 1.18, 14px cyan glow, 0.5s) was easy to miss
   especially mid-saccade. Now: scale 1.35, layered glow (20px gold +
   30px cyan), 0.7s with a clear hold-at-peak so the eye latches on. */
.ws-prop-bump {
  animation: ws-prop-bump 0.7s cubic-bezier(.34, 1.56, .64, 1);
  transform-box: fill-box;
  transform-origin: center;
}
@keyframes ws-prop-bump {
  0%       { transform: scale(1);    filter: drop-shadow(0 0 0 transparent); }
  35%, 55% { transform: scale(1.35); filter: drop-shadow(0 0 22px #FFD15C) drop-shadow(0 0 10px var(--paes-cyan)); }
  100%     { transform: scale(1);    filter: drop-shadow(0 0 0 transparent); }
}

/* Sustained "look HERE" pulse — fires after the bump for ~1s. The bump
   is the immediate "hey" beat; the pulse is what guides the eye for
   the second or two it takes to spot the real target. Fixes the roast's
   "the dead Plug-in button" complaint by making the actual scene target
   un-missable when the verb button is tapped. */
.ws-prop-pulse {
  animation: ws-prop-pulse 0.9s ease-in-out 1;
  transform-box: fill-box;
  transform-origin: center;
}
@keyframes ws-prop-pulse {
  0%   { filter: drop-shadow(0 0 0 transparent); }
  35%  { filter: drop-shadow(0 0 16px #FFD15C) drop-shadow(0 0 6px #fff); }
  100% { filter: drop-shadow(0 0 0 transparent); }
}

/* ---------- Dial / keypad (for time + quantity entry) ---------- */
.ws-proc-dial {
  background: rgba(0,0,0,0.4);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 14px;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  max-width: 280px;
  margin: 0 auto;
}
.ws-proc-dial .lab {
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.6);
  font-weight: 800;
}
.ws-dial-display {
  font-family: 'SF Mono', Menlo, monospace;
  font-size: 32px;
  font-weight: 800;
  color: #5DD8A0;
  background: rgba(0,0,0,0.5);
  border-radius: 8px;
  padding: 6px 18px;
  letter-spacing: 4px;
  min-width: 100px;
  text-align: center;
}
.ws-dial-keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  width: 100%;
}
.ws-dial-key {
  background: linear-gradient(180deg, #fff 0%, #d8d8d8 100%);
  color: var(--paes-navy);
  border: 1.5px solid #888;
  border-radius: 8px;
  padding: 12px 0;
  font-size: 16px;
  font-weight: 800;
  cursor: pointer;
  font-family: inherit;
  transition: transform 0.1s ease;
}
.ws-dial-key:hover { transform: translateY(-1px); }
.ws-dial-key.clear { background: linear-gradient(180deg, #f5f5f5 0%, #c8c8c8 100%); font-size: 12px; }
.ws-dial-key.go { background: linear-gradient(135deg, var(--paes-gold), #0A4D9B); color: var(--paes-navy); border-color: #8a6a30; }

/* ---------- Counter (for "tap N times" steps) ---------- */
.ws-proc-counter {
  background: rgba(0,0,0,0.4);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 14px;
  padding: 14px 18px;
  display: flex;
  align-items: center;
  gap: 14px;
  max-width: 320px;
  margin: 0 auto;
}
.ws-proc-counter .lab {
  font-size: 11px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.55);
  font-weight: 800;
}
.ws-proc-counter .val {
  font-family: 'SF Mono', Menlo, monospace;
  font-size: 28px;
  font-weight: 800;
  color: var(--paes-gold);
}
.ws-proc-counter .goal {
  font-size: 12px;
  color: rgba(255,255,255,0.55);
  flex: 1;
}

/* ---------- Right rail (Why this matters) ---------- */
.ws-proc-why-icon {
  font-size: 42px;
  margin-bottom: 10px;
  display: inline-block;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.4));
  animation: ws-icon-bob 3.4s ease-in-out infinite;
}
@keyframes ws-icon-bob {
  0%, 100% { transform: translateY(0) rotate(0); }
  50%      { transform: translateY(-3px) rotate(-3deg); }
}
.ws-proc-why-h {
  font-size: 18px;
  font-weight: 800;
  color: #fff;
  margin-bottom: 10px;
  font-family: var(--paes-font-display);
  letter-spacing: -0.2px;
  line-height: 1.25;
}
.ws-proc-why-body {
  font-size: 13.5px;
  line-height: 1.55;
  color: rgba(255,255,255,0.78);
  margin-bottom: 14px;
  /* Pre-fix max-height: 6.5em truncated the educational copy on 15+
     lessons. The Round-2 roast called this "the differentiator vs PAES
     Lab — literally cut off mid-sentence." Now the body shows in full;
     the right rail itself scrolls vertically (overflow-y: auto on
     .ws-proc-rail) so any spillover is reachable, but the WHOLE body
     is reachable, not silently hidden. */
}
.ws-proc-check {
  list-style: none;
  padding: 14px 0 0;
  margin: 0;
  font-size: 13px;
  color: rgba(255,255,255,0.72);
  line-height: 1.5;
  border-top: 1px solid rgba(255,255,255,0.12);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ws-proc-check li {
  position: relative;
  padding: 8px 12px 8px 32px;
  margin: 0;
  border-radius: 9px;
  background: rgba(255,255,255,0.025);
  border: 1px solid rgba(255,255,255,0.05);
  transition: all 0.3s ease;
}
.ws-proc-check li:before {
  content: '';
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 14px; height: 14px;
  border-radius: 50%;
  background: rgba(255,255,255,0.06);
  border: 1.5px solid rgba(255,255,255,0.25);
  transition: all 0.3s ease;
}
/* Live-ticked checklist items — pill turns green, tick replaces dot. */
.ws-proc-check li.ticked {
  color: var(--paes-green);
  background: rgba(93,216,160,0.08);
  border-color: rgba(93,216,160,0.35);
}
.ws-proc-check li.ticked:before {
  content: '✓';
  background: linear-gradient(180deg, #5DD8A0 0%, #1d8b5c 100%);
  border-color: #1d8b5c;
  color: #fff;
  font-weight: 800;
  font-size: 9.5px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  box-shadow: 0 2px 4px rgba(29,139,92,0.4), inset 0 1px 0 rgba(255,255,255,0.4);
}

/* ---------- Scene helpers ---------- */
/* Pointer cursor only on ACTIVE targets — armed (drag in flight),
   hinted (current step's target), or hovered (drag over me). The
   default cursor stays on inactive targets so the student doesn't
   get a misleading "click here" affordance on every drawn shape. */
[data-target] { transition: filter 0.15s ease, transform 0.15s ease; }
[data-target].ws-target-armed,
[data-target].ws-target-hint,
[data-target].ws-target-hover { cursor: pointer; }
/* Rotate-mode pivot: cursor implies circular drag. */
[data-target].ws-target-armed[data-target] { /* keep specificity */ }
.ws-rotate-rotor { transition: transform 0.05s linear; transform-box: fill-box; }
.ws-rotate-active { filter: drop-shadow(0 0 14px var(--paes-cyan)); }
/* Resize handles — show grab cursor + cyan glow when armed, intensify
   when actively being dragged. The diagonal cursor on each corner
   matches its resize direction. */
[data-resize] { cursor: pointer; }
[data-resize="tl"], [data-resize="br"] { cursor: nwse-resize; }
[data-resize="tr"], [data-resize="bl"] { cursor: nesw-resize; }
.ws-resize-handle {
  filter: drop-shadow(0 0 6px var(--paes-cyan));
  transition: filter 0.15s ease;
}
.ws-resize-handle.ws-resize-active {
  filter: drop-shadow(0 0 14px #5DD8A0) drop-shadow(0 0 4px #fff);
}
/* Wrong-target flash — fires when student clicks a non-active target.
   Quick red glow + tiny shake. */
[data-target].ws-target-wrong {
  animation: ws-target-wrong-flash 0.55s ease-out;
}
@keyframes ws-target-wrong-flash {
  0%, 100% { filter: drop-shadow(0 0 0 transparent); transform: translateX(0); }
  20%      { filter: drop-shadow(0 0 16px #EF402F); transform: translateX(-3px); }
  40%      { filter: drop-shadow(0 0 16px #EF402F); transform: translateX(3px); }
  60%      { filter: drop-shadow(0 0 12px #EF402F); transform: translateX(-2px); }
  80%      { filter: drop-shadow(0 0 6px #EF402F); transform: translateX(0); }
}
[data-target].ws-target-armed {
  animation: ws-target-pulse 1.4s ease-in-out infinite;
  filter: drop-shadow(0 0 6px var(--paes-cyan));
}
/* Soft halo shown BEFORE the verb is pressed, so the student sees what
   they're aiming for in the scene the moment a target step becomes
   active. Cleared once the verb is pressed (or auto-replaced by armed). */
[data-target].ws-target-hint {
  animation: ws-target-hint-pulse 1.8s ease-in-out infinite;
}
[data-target].ws-target-hit {
  filter: drop-shadow(0 0 6px var(--paes-green));
}
@keyframes ws-target-pulse {
  0%, 100% { filter: drop-shadow(0 0 4px var(--paes-cyan)); }
  50% { filter: drop-shadow(0 0 12px var(--paes-cyan)); }
}
@keyframes ws-target-hint-pulse {
  0%, 100% { filter: drop-shadow(0 0 0 transparent); }
  50% { filter: drop-shadow(0 0 8px rgba(39,169,225,0.6)); }
}
[data-drag] {
  cursor: grab;
  transition: filter 0.15s ease;
  touch-action: none;
  transform-box: fill-box;
  transform-origin: center;
}
[data-drag].ws-drag-armed {
  /* Pulsing cyan drop-shadow + visible bob. Amplitude was 3px, which is
     invisible at SVG-scale rendering — bumped to 9px with a small rotate
     so the prop reads as alive even without watching for it. */
  animation: ws-drag-bob 1.1s ease-in-out infinite, ws-drag-glow 1.3s ease-in-out infinite;
}
[data-drag]:active, [data-drag].ws-dragging { cursor: grabbing; }
[data-drag].ws-dragging {
  filter:
    drop-shadow(0 18px 28px rgba(0,0,0,0.65))
    drop-shadow(0 0 20px var(--paes-cyan))
    brightness(1.08);
  animation: ws-drag-lift 0.22s cubic-bezier(.34, 1.56, .64, 1) forwards;
}
/* Pickup pop: scale up 18%, gentle tilt, brightness boost. The cubic
   bezier overshoots slightly so the pickup feels physical. */
@keyframes ws-drag-lift {
  0%   { transform: scale(1) rotate(0); }
  60%  { transform: scale(1.22) rotate(-3deg); }
  100% { transform: scale(1.18) rotate(-2deg); }
}
/* Drop bounce: target gets a quick squash+release when something lands
   in it. Scales target 0.9 then back to 1.04 then settles to 1. */
@keyframes ws-target-hit-bounce {
  0%   { transform: scale(1); }
  20%  { transform: scale(0.92); }
  60%  { transform: scale(1.06); }
  100% { transform: scale(1); }
}
[data-target].ws-target-hit {
  animation: ws-target-hit-bounce 0.5s cubic-bezier(.34, 1.56, .64, 1);
  filter: drop-shadow(0 0 16px #5DD8A0);
  transform-box: fill-box;
  transform-origin: center;
}
/* Burst particles — spawn around the source on pickup and around the
   target on drop. Rendered as pseudo-elements on a temporary host node. */
.ws-fx-burst {
  position: fixed;
  pointer-events: none;
  width: 0; height: 0;
  z-index: 9999;
}
.ws-fx-burst .spark {
  position: absolute;
  left: 0; top: 0;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0%, #FFD15C 40%, transparent 70%);
  animation: ws-spark 0.7s ease-out forwards;
  --dx: 0px; --dy: 0px;
}
@keyframes ws-spark {
  0%   { transform: translate(0, 0) scale(1);   opacity: 1; }
  100% { transform: translate(var(--dx), var(--dy)) scale(0.2); opacity: 0; }
}
@keyframes ws-drag-bob {
  0%, 100% { transform: translateY(0)   rotate(0); }
  25%      { transform: translateY(-9px) rotate(-1.5deg); }
  50%      { transform: translateY(-6px) rotate(0); }
  75%      { transform: translateY(-9px) rotate(1.5deg); }
}
@keyframes ws-drag-glow {
  0%, 100% { filter: drop-shadow(0 0 8px var(--paes-cyan)); }
  50%      { filter: drop-shadow(0 0 18px var(--paes-cyan)) drop-shadow(0 0 6px #fff); }
}
/* Drop-target halo when a drag is in progress — pulses cyan so the
   student sees where to release. */
[data-target].ws-target-armed {
  animation: ws-target-pulse 1.4s ease-in-out infinite;
  filter: drop-shadow(0 0 8px var(--paes-cyan));
}
/* Hover preview: cursor entered the drop zone. Switches the halo from
   cyan-armed to a brighter green so the student gets a "yes here"
   confirmation BEFORE release. This is the moment the dopamine fires. */
[data-target].ws-target-hover {
  animation: ws-target-hover-pulse 0.6s ease-in-out infinite;
  filter: drop-shadow(0 0 14px #5DD8A0);
}
@keyframes ws-target-hover-pulse {
  0%, 100% { filter: drop-shadow(0 0 10px #5DD8A0); }
  50%      { filter: drop-shadow(0 0 18px #5DD8A0) drop-shadow(0 0 6px #5DD8A0); }
}
/* Tap-fallback armed state: a click (not drag) on the source promotes
   it to "armed" — now a click on the target completes the step. Used
   by keyboard / switch / reduced-dexterity students who can't drag. */
[data-drag].ws-tap-armed {
  filter: drop-shadow(0 0 12px #FFD15C);
  animation: ws-tap-armed-pulse 0.8s ease-in-out infinite;
}
@keyframes ws-tap-armed-pulse {
  0%, 100% { filter: drop-shadow(0 0 8px #FFD15C); }
  50%      { filter: drop-shadow(0 0 18px #FFD15C); }
}
/* Ghost outline left at original position while a drag is in flight,
   so the student sees where it came from. Inserted by the drag handler. */
.ws-drag-ghost { transition: opacity 0.2s ease; }

/* Hint-tier 3+ flash. Fired by the hint escalation in showHint() when the
   student has pressed Hint enough times to need the "show me" treatment.
   A bright magenta-amber pulse on the active target/drag element so the
   eye can't miss it, even on a busy scene. */
.ws-hint-flash {
  animation: ws-hint-flash-pulse 0.6s ease-in-out 4;
}
@keyframes ws-hint-flash-pulse {
  0%, 100% { filter: drop-shadow(0 0 0 transparent); transform: scale(1); }
  50%      { filter: drop-shadow(0 0 18px #ff8a3a) drop-shadow(0 0 32px #FFD15C); transform: scale(1.06); }
}

/* Drag-sort touch ghost: a clone of the lifted tile that follows the
   finger. Sits above all UI via z-index. Has a subtle drop-shadow + tilt
   so it reads as "lifted off the page." Original tile gets ws-touch-source
   which dims it to ~40% so the student sees where it came from. */
.ws-touch-ghost {
  border-radius: 12px;
  transition: none;
}
.ws-tile.ws-touch-source {
  opacity: 0.35;
  transform: scale(0.96);
  filter: grayscale(0.4);
}

/* First-time drag teach overlay: a yellow finger-arc from source to
   target, shown ONCE per session on the first drag step. Dismisses on
   first pointerdown. The arc is an SVG path with a pulsing ring at the
   source. The label sits on top, in the universal "drag" affordance
   color (PAES amber). */
.ws-drag-teach {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 20;
  animation: ws-drag-teach-in 0.4s ease-out;
  /* Pre-fix the pulse circle bled outside the SVG viewbox onto the
     left/right rail gutter on EP2/3/5/6, KA1/2/6 — the roast called it
     "looks like a glitch every single time." Clip strictly to the
     proc-stage bounds so anything the SVG renders past the edge gets
     visually cut off. */
  overflow: hidden;
  border-radius: 18px;
}
.ws-drag-teach svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.ws-drag-teach-label {
  position: absolute;
  /* Round-4 roast: pill anchored bottom-left of the SCENE was covering
     in-canvas labels like FS3 "CENTER" → "ENTRE", FS4 leftmost cutlery,
     HC6 caution-sign label area, WP1 saw asset. Now anchored to the
     proc-stage TOP-EDGE (above the canvas, outside the SVG content
     area) so it informs without occluding any in-canvas content. */
  top: 6px;
  right: 16px;
  background: linear-gradient(180deg, rgba(255,209,92,0.95) 0%, rgba(216,168,56,0.95) 100%);
  color: #1a0808;
  font-family: 'Patrick Hand', cursive;
  font-size: clamp(10px, 1.1vw, 12px);
  font-weight: 800;
  letter-spacing: 0.8px;
  padding: 3px 9px;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.55);
  text-transform: uppercase;
  white-space: nowrap;
  z-index: 22;
  pointer-events: none;
}
@keyframes ws-drag-teach-in {
  0%   { opacity: 0; transform: translateY(8px); }
  100% { opacity: 1; transform: translateY(0); }
}
.ws-drag-teach.ws-drag-teach-out {
  animation: ws-drag-teach-out 0.28s ease-in forwards;
}
@keyframes ws-drag-teach-out {
  0%   { opacity: 1; }
  100% { opacity: 0; transform: scale(1.04); }
}

/* =============================================================
   MOTION MICRO-ANIMATIONS — opt-in via [data-anim] attribute.
   These bring the static SVG scenes to life: turntables spin,
   steam wiggles up, water flows, lights blink, fans rotate.
   Apply via SVG attribute, e.g. data-anim="spin-slow" or
   data-anim="steam".
   ============================================================= */

/* Step-gated animations: an element wrapped in .ws-runs-after-N
   pauses its child animations until the scene reaches sNdone. KA1's
   turntable spin should only start once the cycle starts (step 3),
   not at lesson load — otherwise the microwave already looks "on"
   when the student is still placing the mug. The wrapping group
   carries the gating class; child animations key off it. */
[class*="ws-runs-after-"] [data-anim] { animation-play-state: paused !important; }
.ws-proc-scene.s1done .ws-runs-after-1 [data-anim],
.ws-proc-scene.s2done .ws-runs-after-2 [data-anim],
.ws-proc-scene.s3done .ws-runs-after-3 [data-anim],
.ws-proc-scene.s4done .ws-runs-after-4 [data-anim],
.ws-proc-scene.s5done .ws-runs-after-5 [data-anim],
.ws-proc-scene.s6done .ws-runs-after-6 [data-anim],
.ws-proc-scene.s7done .ws-runs-after-7 [data-anim],
.ws-proc-scene.s8done .ws-runs-after-8 [data-anim] { animation-play-state: running !important; }
[data-anim="spin-slow"]    { transform-origin: center; transform-box: fill-box; animation: ws-spin 6s linear infinite; }
[data-anim="spin"]         { transform-origin: center; transform-box: fill-box; animation: ws-spin 2s linear infinite; }
[data-anim="spin-fast"]    { transform-origin: center; transform-box: fill-box; animation: ws-spin 0.6s linear infinite; }
[data-anim="steam"]        { transform-origin: center; transform-box: fill-box; animation: ws-steam 2.4s ease-in-out infinite; opacity: 0.85; }
[data-anim="steam-slow"]   { transform-origin: center; transform-box: fill-box; animation: ws-steam 3.6s ease-in-out infinite; opacity: 0.75; }
[data-anim="bob"]          { transform-origin: center; transform-box: fill-box; animation: ws-bob 1.6s ease-in-out infinite; }
[data-anim="bob-slow"]     { transform-origin: center; transform-box: fill-box; animation: ws-bob 2.6s ease-in-out infinite; }
[data-anim="pulse"]        { transform-origin: center; transform-box: fill-box; animation: ws-pulse 1.4s ease-in-out infinite; }
[data-anim="pulse-fast"]   { transform-origin: center; transform-box: fill-box; animation: ws-pulse 0.7s ease-in-out infinite; }
[data-anim="blink"]        { animation: ws-anim-blink 1.0s steps(2, end) infinite; }
[data-anim="blink-fast"]   { animation: ws-anim-blink 0.4s steps(2, end) infinite; }
[data-anim="wiggle"]       { transform-origin: center; transform-box: fill-box; animation: ws-wiggle 1.4s ease-in-out infinite; }
[data-anim="sway"]         { transform-origin: bottom center; transform-box: fill-box; animation: ws-sway 3s ease-in-out infinite; }
[data-anim="flow"]         { stroke-dasharray: 6 6; animation: ws-flow 0.8s linear infinite; }
[data-anim="flow-slow"]    { stroke-dasharray: 8 6; animation: ws-flow 1.6s linear infinite; }
[data-anim="bubble-rise"]  { transform-origin: center; transform-box: fill-box; animation: ws-bubble-rise 2.2s ease-in infinite; opacity: 0; }
[data-anim="bubble-rise-2"]{ transform-origin: center; transform-box: fill-box; animation: ws-bubble-rise 2.2s ease-in 0.6s infinite; opacity: 0; }
[data-anim="bubble-rise-3"]{ transform-origin: center; transform-box: fill-box; animation: ws-bubble-rise 2.2s ease-in 1.2s infinite; opacity: 0; }
[data-anim="float"]        { transform-origin: center; transform-box: fill-box; animation: ws-float 4s ease-in-out infinite; }
[data-anim="shimmer"]      { animation: ws-shimmer 2.5s linear infinite; }
[data-anim="glow"]         { animation: ws-glow 1.6s ease-in-out infinite alternate; }
[data-anim="tilt"]         { transform-origin: bottom center; transform-box: fill-box; animation: ws-tilt 2.4s ease-in-out infinite; }
[data-anim="ws-drip"]      { transform-origin: center; transform-box: fill-box; animation: ws-drip 2.8s ease-in infinite; opacity: 0; }
@keyframes ws-drip {
  0%   { transform: translateY(0)  scale(0.5); opacity: 0; }
  10%  { opacity: 0.85; }
  60%  { transform: translateY(38px) scale(1); opacity: 0.7; }
  100% { transform: translateY(50px) scale(0.4); opacity: 0; }
}
@keyframes ws-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
@keyframes ws-steam {
  0%   { transform: translateY(0)    translateX(0)   scale(1.0); opacity: 0.85; }
  40%  { transform: translateY(-12px) translateX(3px) scale(1.08); opacity: 0.6; }
  80%  { transform: translateY(-22px) translateX(-2px) scale(1.18); opacity: 0.25; }
  100% { transform: translateY(-26px) translateX(0)   scale(1.25); opacity: 0; }
}
@keyframes ws-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4px); }
}
@keyframes ws-pulse {
  0%, 100% { transform: scale(1.0); }
  50%      { transform: scale(1.07); }
}
@keyframes ws-anim-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.25; }
}
@keyframes ws-wiggle {
  0%, 100% { transform: rotate(0); }
  25%      { transform: rotate(-3deg); }
  75%      { transform: rotate(3deg); }
}
@keyframes ws-sway {
  0%, 100% { transform: rotate(-2deg); }
  50%      { transform: rotate(2deg); }
}
@keyframes ws-flow {
  0%   { stroke-dashoffset: 24; }
  100% { stroke-dashoffset: 0; }
}
@keyframes ws-bubble-rise {
  0%   { transform: translateY(0)    scale(0.4); opacity: 0; }
  20%  { opacity: 0.8; }
  100% { transform: translateY(-40px) scale(1.0); opacity: 0; }
}
@keyframes ws-float {
  0%, 100% { transform: translateY(0)   translateX(0); }
  33%      { transform: translateY(-3px) translateX(2px); }
  66%      { transform: translateY(-1px) translateX(-2px); }
}
@keyframes ws-shimmer {
  0%   { filter: brightness(1.0); }
  50%  { filter: brightness(1.18); }
  100% { filter: brightness(1.0); }
}
@keyframes ws-glow {
  0%   { filter: drop-shadow(0 0 0 transparent); }
  100% { filter: drop-shadow(0 0 6px rgba(255,209,92,0.85)); }
}
@keyframes ws-tilt {
  0%, 100% { transform: rotate(-1.5deg); }
  50%      { transform: rotate(1.5deg); }
}
/* Reduced motion: respect prefers-reduced-motion by killing the loops. */
@media (prefers-reduced-motion: reduce) {
  [data-anim] { animation: none !important; }
}

/* =============================================================
   STATE-DRIVEN SCENE VISIBILITY
   The procedure engine adds "sNdone" classes to .ws-proc-scene
   as steps complete. SVG elements declare:
     data-stage="initial"   - visible at start, hidden after step 1 done
     data-stage="after-N"   - hidden until step N completes, then visible
     data-stage="hidden"    - never visible (helper for stash-out elements)
   This is what fixes the "press place but the mug is already there"
   problem — initial-state visuals disappear, the post-action visuals
   reveal in their place.
   ============================================================= */
.ws-proc-scene [data-stage="hidden"] { display: none; }
/* Stage transitions tween via opacity + transform instead of display:none.
   That way an element fading out and another fading in feels continuous
   rather than snapping. The ones that should be physically gone keep
   pointer-events: none so they don't block clicks while invisible. */
.ws-proc-scene [data-stage="initial"] {
  opacity: 1;
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.ws-proc-scene.s1done [data-stage="initial"] {
  opacity: 0;
  transform: scale(0.92);
  pointer-events: none;
}

.ws-proc-scene [data-stage^="after-"] {
  opacity: 0;
  pointer-events: none;
  /* Round-3 roast: "Watch the workshop image. It does not change."
     The after-N reveal was a 6px translate + opacity fade — a soft
     change easy to miss. Now: a clear scale-in pop (0.85 → 1.0) and a
     longer hold so the eye has time to land on the new state. */
  transform: scale(0.85);
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 0.55s cubic-bezier(.34, 1.5, .64, 1), transform 0.55s cubic-bezier(.34, 1.5, .64, 1);
}
.ws-proc-scene.s1done [data-stage="after-1"],
.ws-proc-scene.s2done [data-stage="after-2"],
.ws-proc-scene.s3done [data-stage="after-3"],
.ws-proc-scene.s4done [data-stage="after-4"],
.ws-proc-scene.s5done [data-stage="after-5"],
.ws-proc-scene.s6done [data-stage="after-6"],
.ws-proc-scene.s7done [data-stage="after-7"],
.ws-proc-scene.s8done [data-stage="after-8"] {
  opacity: 1;
  pointer-events: auto;
  transform: scale(1);
}

/* "Vanish-after-N" pattern: element visible at start, gone after step N.
   Tweens via opacity + a small lift so the disappearance feels deliberate
   (a crumb being swept, dust being wiped, a bolt being removed). */
.ws-proc-scene [data-stage^="until-"] {
  opacity: 1;
  transition: opacity 0.4s ease, transform 0.4s ease;
}
.ws-proc-scene.s1done [data-stage="until-1"],
.ws-proc-scene.s2done [data-stage="until-2"],
.ws-proc-scene.s3done [data-stage="until-3"],
.ws-proc-scene.s4done [data-stage="until-4"],
.ws-proc-scene.s5done [data-stage="until-5"],
.ws-proc-scene.s6done [data-stage="until-6"],
.ws-proc-scene.s7done [data-stage="until-7"],
.ws-proc-scene.s8done [data-stage="until-8"] {
  opacity: 0;
  transform: scale(0.85) translateY(-4px);
  pointer-events: none;
}

/* Persistent reveal that DOESN'T disappear after a later step (default
   behaviour above keeps each "after-N" visible for all subsequent steps
   too — this is intentional). For "between" reveals (visible at step N
   but hidden again at step M) declare data-stage="after-N" and a class
   "remove-after-M" on the same element. Pre-fix only N≤4 was supported,
   so jobs with 5+ steps stacked stale state labels (FS2 "(✔fold f done)",
   WP4 "5 BEADS·APPLIED·30 SLOW BEAD·EVEN OPEN·TIME"). Now supports up to
   step 8 and any "after-A".remove-after-B combination where B > A. */
.ws-proc-scene.s2done [data-stage="after-1"].remove-after-2,
.ws-proc-scene.s3done [data-stage="after-1"].remove-after-3,
.ws-proc-scene.s4done [data-stage="after-1"].remove-after-4,
.ws-proc-scene.s5done [data-stage="after-1"].remove-after-5,
.ws-proc-scene.s6done [data-stage="after-1"].remove-after-6,
.ws-proc-scene.s7done [data-stage="after-1"].remove-after-7,
.ws-proc-scene.s8done [data-stage="after-1"].remove-after-8,
.ws-proc-scene.s3done [data-stage="after-2"].remove-after-3,
.ws-proc-scene.s4done [data-stage="after-2"].remove-after-4,
.ws-proc-scene.s5done [data-stage="after-2"].remove-after-5,
.ws-proc-scene.s6done [data-stage="after-2"].remove-after-6,
.ws-proc-scene.s7done [data-stage="after-2"].remove-after-7,
.ws-proc-scene.s8done [data-stage="after-2"].remove-after-8,
.ws-proc-scene.s4done [data-stage="after-3"].remove-after-4,
.ws-proc-scene.s5done [data-stage="after-3"].remove-after-5,
.ws-proc-scene.s6done [data-stage="after-3"].remove-after-6,
.ws-proc-scene.s7done [data-stage="after-3"].remove-after-7,
.ws-proc-scene.s8done [data-stage="after-3"].remove-after-8,
.ws-proc-scene.s5done [data-stage="after-4"].remove-after-5,
.ws-proc-scene.s6done [data-stage="after-4"].remove-after-6,
.ws-proc-scene.s7done [data-stage="after-4"].remove-after-7,
.ws-proc-scene.s8done [data-stage="after-4"].remove-after-8,
.ws-proc-scene.s6done [data-stage="after-5"].remove-after-6,
.ws-proc-scene.s7done [data-stage="after-5"].remove-after-7,
.ws-proc-scene.s8done [data-stage="after-5"].remove-after-8,
.ws-proc-scene.s7done [data-stage="after-6"].remove-after-7,
.ws-proc-scene.s8done [data-stage="after-6"].remove-after-8,
.ws-proc-scene.s8done [data-stage="after-7"].remove-after-8 { display: none; }

@keyframes ws-stage-reveal {
  0% { opacity: 0; transform: scale(0.92); }
  100% { opacity: 1; transform: scale(1); }
}

/* =============================================================
   STEP-COMPLETION BURST OVERLAY
   Big green checkmark that floats in over the scene whenever a step
   completes. Solves the "I pressed the button and nothing happened"
   feeling — the student always gets visible feedback, even when the
   scene itself doesn't change much.
   ============================================================= */
.ws-proc-burst {
  position: absolute;
  /* Pre-fix the burst spanned the entire .ws-proc-stage which meant the
     centred tick landed ON TOP of the verb-button row (covering Tape /
     Tighten on EP1). Now we bound the burst to the top portion of the
     stage — roughly the SVG scene's vertical extent — so the tick
     centres over the artwork, never over the toolbar. */
  top: 0;
  left: 0;
  right: 0;
  bottom: 38%;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
  z-index: 10;
  opacity: 0;
}
.ws-proc-burst.show { opacity: 1; }
.ws-burst-tick {
  width: 110px; height: 110px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #6fe6b0 0%, var(--paes-green) 60%, #2c8a5a 100%);
  color: #fff;
  font-size: 70px;
  font-weight: 800;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 0 rgba(93,216,160,0.7), 0 18px 38px rgba(0,0,0,0.5);
  animation: ws-burst-pop 0.7s cubic-bezier(.34,1.56,.64,1) forwards;
}
@keyframes ws-burst-pop {
  0%   { transform: scale(0.4); opacity: 0; box-shadow: 0 0 0 0 rgba(93,216,160,0.7), 0 18px 38px rgba(0,0,0,0.5); }
  35%  { transform: scale(1.18); opacity: 1; }
  60%  { transform: scale(1); opacity: 1; box-shadow: 0 0 0 24px rgba(93,216,160,0), 0 18px 38px rgba(0,0,0,0.5); }
  100% { transform: scale(1.06); opacity: 0; box-shadow: 0 0 0 36px rgba(93,216,160,0), 0 18px 38px rgba(0,0,0,0.5); }
}

/* ============================================================
   ACTION-GLYPH BURST (per-verb feedback)
   Replaces the generic ✓ with a verb-themed glyph that animates
   in a way matching the action — tear (✂ split apart), wrap
   (🌀 spin), turn (🔧 rotate), thrust (🔌 jab forward), sweep
   (🧹 swipe), pulse (🩹 expand). The roast called the old tick
   "rubber-stamp feedback with zero correspondence to physical
   change." This is a 30%-of-the-way-there fix; full per-element
   physics is a separate sprint.
   ============================================================ */
.ws-burst-action {
  font-size: 84px;
  filter: drop-shadow(0 6px 18px rgba(0,0,0,0.55));
  animation-duration: 0.8s;
  animation-timing-function: cubic-bezier(.34,1.56,.64,1);
  animation-fill-mode: forwards;
  position: relative;
  z-index: 2;
}
.ws-burst-mini-tick {
  position: absolute;
  bottom: 22%;
  right: 36%;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #6fe6b0 0%, var(--paes-green) 60%, #2c8a5a 100%);
  color: #fff;
  font-size: 22px;
  font-weight: 800;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 14px rgba(0,0,0,0.4);
  animation: ws-burst-pop 0.7s cubic-bezier(.34,1.56,.64,1) forwards 0.18s;
  opacity: 0;
}

.ws-burst-motion-tear   { animation-name: ws-act-tear; }
.ws-burst-motion-spin   { animation-name: ws-act-spin; }
.ws-burst-motion-turn   { animation-name: ws-act-turn; }
.ws-burst-motion-thrust { animation-name: ws-act-thrust; }
.ws-burst-motion-sweep  { animation-name: ws-act-sweep; }
.ws-burst-motion-pulse  { animation-name: ws-act-pulse; }

@keyframes ws-act-tear {
  /* Two halves of a scissor pull apart then settle */
  0%   { transform: scale(0.3) rotate(0); opacity: 0; }
  20%  { transform: scale(1.2) rotate(-8deg); opacity: 1; }
  45%  { transform: scale(1.05) rotate(8deg); }
  70%  { transform: scale(1) rotate(-3deg); }
  100% { transform: scale(0.95) rotate(0); opacity: 0; }
}
@keyframes ws-act-spin {
  /* Full clockwise rotation matching wrap/loop motion */
  0%   { transform: scale(0.3) rotate(-180deg); opacity: 0; }
  25%  { transform: scale(1.18) rotate(60deg); opacity: 1; }
  60%  { transform: scale(1.04) rotate(220deg); }
  100% { transform: scale(0.96) rotate(360deg); opacity: 0; }
}
@keyframes ws-act-turn {
  /* Quarter-turn clockwise — wrench / screw tightening */
  0%   { transform: scale(0.3) rotate(-30deg); opacity: 0; }
  20%  { transform: scale(1.2) rotate(0); opacity: 1; }
  55%  { transform: scale(1.04) rotate(45deg); }
  100% { transform: scale(0.96) rotate(90deg); opacity: 0; }
}
@keyframes ws-act-thrust {
  /* Forward jab — plug-in / pour / spray */
  0%   { transform: scale(0.3) translateY(-30px); opacity: 0; }
  25%  { transform: scale(1.2) translateY(0); opacity: 1; }
  60%  { transform: scale(1.04) translateY(8px); }
  100% { transform: scale(0.96) translateY(14px); opacity: 0; }
}
@keyframes ws-act-sweep {
  /* Side-to-side swipe — broom / mop / sandpaper */
  0%   { transform: scale(0.3) translateX(-40px); opacity: 0; }
  25%  { transform: scale(1.18) translateX(-20px); opacity: 1; }
  60%  { transform: scale(1.04) translateX(20px); }
  100% { transform: scale(0.96) translateX(40px); opacity: 0; }
}
@keyframes ws-act-pulse {
  /* Expanding ring — connect / press / pin */
  0%   { transform: scale(0.3); opacity: 0; }
  25%  { transform: scale(1.3); opacity: 1; }
  55%  { transform: scale(1.08); }
  100% { transform: scale(1.18); opacity: 0; }
}

/* ============================================================
   WIRE / PATH DRAW-IN ANIMATION
   When an SVG path inside an after-N stage carries the
   data-anim="draw" attribute, the path animates from end to end
   like a wire being routed. Uses stroke-dashoffset so it works
   on any path length. Fixes the roast's "no correspondence
   between input and physical change" complaint by letting wires
   visibly draw themselves into place after the connect step.
   ============================================================ */
.ws-proc-scene [data-stage^="after-"] [data-anim="draw"] {
  stroke-dasharray: 600;
  stroke-dashoffset: 600;
}
.ws-proc-scene.s1done [data-stage="after-1"] [data-anim="draw"],
.ws-proc-scene.s2done [data-stage="after-2"] [data-anim="draw"],
.ws-proc-scene.s3done [data-stage="after-3"] [data-anim="draw"],
.ws-proc-scene.s4done [data-stage="after-4"] [data-anim="draw"],
.ws-proc-scene.s5done [data-stage="after-5"] [data-anim="draw"],
.ws-proc-scene.s6done [data-stage="after-6"] [data-anim="draw"] {
  animation: ws-wire-draw-in 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) 0.15s forwards;
}
@keyframes ws-wire-draw-in {
  to { stroke-dashoffset: 0; }
}

/* ============================================================
   TEAR / STRIP physical-motion primitives
   When step 1 (tear) completes, the BLACK strand slides UP and
   the WHITE strand slides DOWN from a center line — visualising
   the cord splitting in two. When step 2 (strip) completes, the
   exposed copper rect slides OUT from the wire tip — visualising
   the insulation peeling back. Both replace the old "opacity
   pop" with motion that matches the verb.
   ============================================================ */
.ws-proc-scene [data-stage="after-1"] .ws-act-tear-up,
.ws-proc-scene [data-stage="after-1"] .ws-act-tear-down,
.ws-proc-scene [data-stage="after-2"] .ws-act-strip {
  transform-origin: center;
  transform-box: fill-box;
}
.ws-proc-scene.s1done [data-stage="after-1"] .ws-act-tear-up {
  animation: ws-act-tear-up 0.65s cubic-bezier(.34,1.4,.64,1) forwards;
}
.ws-proc-scene.s1done [data-stage="after-1"] .ws-act-tear-down {
  animation: ws-act-tear-down 0.65s cubic-bezier(.34,1.4,.64,1) forwards;
}
.ws-proc-scene.s2done [data-stage="after-2"] .ws-act-strip {
  animation: ws-act-strip-out 0.55s cubic-bezier(.34,1.56,.64,1) forwards;
}
@keyframes ws-act-tear-up {
  0%   { transform: translateY(10px); opacity: 0; }
  60%  { transform: translateY(-2px); opacity: 1; }
  100% { transform: translateY(0); opacity: 1; }
}
@keyframes ws-act-tear-down {
  0%   { transform: translateY(-10px); opacity: 0; }
  60%  { transform: translateY(2px); opacity: 1; }
  100% { transform: translateY(0); opacity: 1; }
}
@keyframes ws-act-strip-out {
  0%   { transform: translateX(-8px) scaleX(0.4); opacity: 0; }
  100% { transform: translateX(0) scaleX(1); opacity: 1; }
}

/* ============================================================
   STRUCTURAL DRAG MECHANICS — visual feedback
   The brutal Round-2 roast called "click-the-armed-button quizzes."
   These rules give the wrap / sweep / fold / tear / strip primitives
   a felt-like response — debris fades on sweep, halves animate on
   tear, fabric folds on fold-trigger.
   ============================================================ */

/* Sweep: pending crumbs glow gently to attract the broom. Cleared
   crumbs fade out + fly off. */
.ws-proc-scene [data-sweep].ws-sweep-pending {
  filter: drop-shadow(0 0 4px rgba(255,209,92,0.5));
  animation: ws-sweep-glow 1.6s ease-in-out infinite;
}
.ws-proc-scene [data-sweep].ws-sweep-cleared {
  animation: ws-sweep-clear 0.5s ease-out forwards;
  pointer-events: none;
}
@keyframes ws-sweep-glow {
  0%, 100% { filter: drop-shadow(0 0 4px rgba(255,209,92,0.5)); }
  50%      { filter: drop-shadow(0 0 10px rgba(255,209,92,0.85)); }
}
@keyframes ws-sweep-clear {
  0%   { transform: translateY(0) scale(1); opacity: 1; }
  100% { transform: translateY(8px) scale(0.4); opacity: 0; }
}

/* Fold: the target collapses along its perpendicular axis when the
   fold-line drag completes. CSS-only; the engine adds .ws-fold-collapse. */
.ws-proc-scene .ws-fold-collapse {
  transform-origin: center;
  transform-box: fill-box;
  animation: ws-fold-collapse 0.32s cubic-bezier(.55, 0, .65, 1.2) forwards;
}
@keyframes ws-fold-collapse {
  0%   { transform: scaleY(1); }
  60%  { transform: scaleY(0.45) translateY(-6px); }
  100% { transform: scaleY(0.55) translateY(0); }
}
.ws-proc-scene .ws-fold-handle {
  cursor: grab;
  filter: drop-shadow(0 0 6px rgba(39,169,225,0.85));
  animation: ws-fold-handle-pulse 1.4s ease-in-out infinite;
}
@keyframes ws-fold-handle-pulse {
  0%, 100% { filter: drop-shadow(0 0 6px rgba(39,169,225,0.85)); }
  50%      { filter: drop-shadow(0 0 14px rgba(39,169,225,1)); }
}

/* Tear: each half glows on hover to invite drag. While dragging, the
   half-element shows a stronger drop-shadow so the student sees their
   pull is registering. */
.ws-proc-scene [data-tear].ws-drag-armed {
  cursor: grab;
  filter: drop-shadow(0 0 6px rgba(255,209,92,0.7));
  animation: ws-tear-pulse 1.4s ease-in-out infinite;
}
.ws-proc-scene [data-tear].ws-dragging {
  cursor: grabbing;
  filter: drop-shadow(0 0 14px rgba(255,209,92,1)) drop-shadow(0 4px 8px rgba(0,0,0,0.4));
  animation: none;
}
@keyframes ws-tear-pulse {
  0%, 100% { filter: drop-shadow(0 0 6px rgba(255,209,92,0.7)); }
  50%      { filter: drop-shadow(0 0 12px rgba(255,209,92,1)); }
}

/* ============================================================
   UNIVERSAL TEXT-LEGIBILITY FALLBACK
   The brutal roast counted 18 distinct text-bleed bugs. Per-game
   stroke fixes were applied for the worst offenders, but new SVGs
   added without stroke would regress. This rule gives any bold
   text element in a procedure scene a halo via paint-order +
   text-shadow (CSS, not SVG attributes — so it doesn't fight with
   inline stroke="#x" declarations on artistic labels).
   ============================================================ */
.ws-proc-scene svg text {
  paint-order: stroke fill;
}
.ws-proc-scene svg text[font-weight="700"],
.ws-proc-scene svg text[font-weight="800"],
.ws-proc-scene svg text[font-weight="900"],
.ws-proc-scene svg text[font-weight="bold"] {
  /* CSS text-shadow on SVG text renders as a soft drop-shadow halo on
     most browsers (Chrome, Safari, Firefox 92+). Doesn't conflict with
     inline SVG stroke attributes — those continue to render their
     hard-edge outline. This is the soft-fallback layer. */
  text-shadow:
    0 0 2px rgba(10, 8, 8, 0.65),
    0 1px 0 rgba(10, 8, 8, 0.5);
}

/* =============================================================
   FINALE CELEBRATION (last step of a procedure job)
   Confetti pieces fly outward from the center, expanding ring,
   bigger tick that holds for the full beat. This is what takes
   the student from "I tapped the last button" to "I finished the
   job" — proper celebration before the win card lands.
   ============================================================= */
.ws-proc-burst.finale .ws-finale-ring {
  position: absolute;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(93,216,160,0.6) 0%, transparent 70%);
  animation: ws-finale-ring-expand 1.4s ease-out forwards;
}
.ws-proc-burst.finale .ws-finale-tick {
  position: relative;
  width: 140px; height: 140px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #88f3c0 0%, var(--paes-green) 60%, #2c8a5a 100%);
  color: #fff;
  font-size: 90px; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 22px 48px rgba(0,0,0,0.5), 0 0 50px rgba(93,216,160,0.4);
  animation: ws-finale-tick-pop 1.4s cubic-bezier(.34,1.56,.64,1) forwards;
}
@keyframes ws-finale-ring-expand {
  0%   { transform: scale(1); opacity: 0.85; }
  100% { transform: scale(18); opacity: 0; }
}
@keyframes ws-finale-tick-pop {
  0%   { transform: scale(0.3) rotate(-15deg); opacity: 0; }
  35%  { transform: scale(1.2) rotate(0deg); opacity: 1; }
  60%  { transform: scale(1) rotate(0deg); opacity: 1; }
  90%  { transform: scale(1) rotate(0deg); opacity: 1; }
  100% { transform: scale(1.05) rotate(0deg); opacity: 1; }
}
.ws-proc-burst.finale .ws-confetti {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.ws-confetti-piece {
  position: absolute;
  left: 50%; top: 50%;
  width: 10px; height: 14px;
  background: var(--col, #fff);
  border-radius: 2px;
  animation: ws-confetti-fly 1.2s ease-out forwards;
  animation-delay: var(--delay, 0ms);
  transform-origin: center;
  opacity: 0;
}
@keyframes ws-confetti-fly {
  0%   { transform: translate(-50%, -50%) rotate(0deg); opacity: 1; }
  100% {
    transform:
      translate(-50%, -50%)
      rotate(calc(var(--ang) * 2))
      translateY(calc(var(--dist) * -1));
    opacity: 0;
  }
}

/* =============================================================
   SMOOTH SCENE TRANSITIONS
   When a stage class flips, elements fading in/out should glide
   rather than snap. The reveal animation already handles incoming
   elements; this adds a graceful exit for elements that disappear
   between stages.
   ============================================================= */
.ws-proc-scene [data-stage] { transition: opacity 0.3s ease, transform 0.3s ease; }

/* ---------- Mobile / tablet ---------- */
@media (max-width: 1080px) {
  .ws-proc { grid-template-columns: 240px 1fr 240px; gap: 14px; }
  .ws-proc-rail { padding: 18px 14px; }
}
@media (max-width: 900px) {
  .ws-proc { grid-template-columns: 1fr; gap: 14px; }
  .ws-proc-rail { padding: 16px 14px; }
  .ws-proc-stage { min-height: 360px; padding: 18px 14px; }
}
@media (max-width: 540px) {
  .ws-verb-btn { padding: 10px 14px; font-size: 12.5px; }
  .ws-verb-glyph { font-size: 16px; }
  .ws-dial-key { padding: 10px 0; font-size: 14px; }
  .ws-dial-display { font-size: 26px; padding: 5px 14px; min-width: 80px; }
}

/* =================================================================
   WEIGHING-SCALE TEMPLATE  (high-fidelity · purpose-built)
   ================================================================= */
.ws-scale-stage {
  display: flex;
  flex-direction: column;
  gap: 18px;
  align-items: center;
  padding: 22px 18px;
}

/* Counter strip with food items */
.ws-scale-counter {
  width: 100%;
  background: linear-gradient(180deg, rgba(166,128,80,0.18) 0%, rgba(90,48,24,0.28) 100%);
  border: 1.5px solid rgba(39,169,225,0.25);
  border-radius: 14px;
  padding: 14px;
}
.ws-scale-counter-h {
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.7);
  font-weight: 800;
  margin-bottom: 10px;
}
.ws-scale-items {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}
.ws-food {
  background: linear-gradient(180deg, #fff 0%, #f0e8d8 100%);
  border: 2px solid rgba(39,169,225,0.4);
  border-radius: 12px;
  padding: 10px 14px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  cursor: grab;
  transition: transform 0.15s ease, box-shadow 0.15s ease, opacity 0.2s ease;
  min-width: 90px;
  user-select: none;
}
.ws-food:hover { transform: translateY(-3px); box-shadow: 0 8px 18px rgba(0,0,0,0.35); }
.ws-food:active { cursor: grabbing; }
.ws-food.dragging { opacity: 0.4; transform: scale(1.05); }
.ws-food.on-pan { opacity: 0.35; border-color: rgba(93,216,160,0.6); }
.ws-food-glyph { font-size: 36px; line-height: 1; }
.ws-food-art { width: 56px; height: 56px; display: flex; align-items: center; justify-content: center; }
.ws-food-art svg { width: 56px; height: 56px; filter: drop-shadow(0 2px 3px rgba(0,0,0,0.25)); }
.ws-food-label { font-size: 12px; font-weight: 800; color: var(--paes-navy); }
.ws-food-weight {
  font-size: 10px;
  letter-spacing: 0.6px;
  color: rgba(10,77,155,0.55);
  font-weight: 700;
  font-family: 'SF Mono', Menlo, monospace;
}

/* The scale itself */
.ws-scale-bench {
  position: relative;
  width: 100%;
  display: flex;
  justify-content: center;
  padding: 8px 0;
}
.ws-scale {
  position: relative;
  width: 320px;
}
.ws-scale-pan {
  position: relative;
  width: 280px;
  height: 60px;
  margin: 0 auto -12px;
  background:
    radial-gradient(ellipse at 50% 30%, rgba(255,255,255,0.4) 0%, transparent 60%),
    linear-gradient(180deg, #d8d8d8 0%, #888 60%, #555 100%);
  border-radius: 50% / 50%;
  border: 2px solid #444;
  box-shadow:
    0 6px 14px rgba(0,0,0,0.45),
    inset 0 -3px 6px rgba(0,0,0,0.3);
  z-index: 2;
  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), border-color 0.2s ease;
}
.ws-scale-pan.over {
  border-color: var(--paes-gold);
  box-shadow:
    0 0 0 3px rgba(39,169,225,0.4),
    0 8px 20px rgba(39,169,225,0.4),
    inset 0 -3px 6px rgba(0,0,0,0.3);
}
.ws-scale-pan-items {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px;
  transform: translateY(-22px);
  pointer-events: none;
}
.ws-pan-glyph {
  font-size: 28px;
  line-height: 1;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,0.4));
  animation: pan-bounce 0.35s ease-out;
}
.ws-pan-art { width: 44px; height: 44px; animation: pan-bounce 0.35s ease-out; }
.ws-pan-art svg { width: 44px; height: 44px; filter: drop-shadow(0 3px 4px rgba(0,0,0,0.5)); }
@keyframes pan-bounce {
  0% { transform: translateY(-30px) scale(0.6); opacity: 0; }
  60% { transform: translateY(2px) scale(1.05); opacity: 1; }
  100% { transform: translateY(0) scale(1); opacity: 1; }
}

.ws-scale-body {
  position: relative;
  background: linear-gradient(180deg, #f4f4f4 0%, #c8c8c8 60%, #888 100%);
  border: 2px solid #444;
  border-radius: 16px;
  padding: 22px 20px 14px;
  box-shadow:
    0 12px 26px rgba(0,0,0,0.45),
    inset 0 2px 0 rgba(255,255,255,0.7);
  z-index: 1;
}
.ws-scale-display {
  font-family: 'SF Mono', Menlo, monospace;
  font-size: 36px;
  font-weight: 800;
  color: #2bdf6e;
  background: #0a1a0d;
  padding: 8px 14px;
  border-radius: 6px;
  text-align: right;
  letter-spacing: 4px;
  border: 2px inset #444;
  box-shadow:
    inset 0 2px 6px rgba(0,0,0,0.5),
    0 0 12px rgba(43,223,110,0.18);
  text-shadow: 0 0 8px rgba(43,223,110,0.5);
  font-variant-numeric: tabular-nums;
}
.ws-scale-unit {
  position: absolute;
  top: 38px;
  right: 32px;
  font-size: 13px;
  font-weight: 800;
  color: #2bdf6e;
  font-family: 'SF Mono', Menlo, monospace;
  text-shadow: 0 0 6px rgba(43,223,110,0.6);
  letter-spacing: 1px;
  pointer-events: none;
}
.ws-scale-controls {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 10px;
}
.ws-scale-btn {
  background: linear-gradient(180deg, #fff 0%, #c8c8c8 100%);
  border: 1.5px solid #444;
  border-radius: 8px;
  padding: 8px 0;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1px;
  color: #222;
  cursor: pointer;
  font-family: inherit;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.7), 0 2px 0 rgba(0,0,0,0.3);
  transition: transform 0.08s ease;
}
.ws-scale-btn:hover { background: linear-gradient(180deg, #ffd86c 0%, #e0a838 100%); }
.ws-scale-btn:active { transform: translateY(1px); box-shadow: inset 0 1px 4px rgba(0,0,0,0.3); }
.ws-scale-dial {}
.ws-scale-dial .ws-scale-pan {
  width: 220px;
  height: 56px;
  margin: 0 auto -8px;
}
.ws-scale-dial-body {
  background: linear-gradient(180deg, #f4f4f4 0%, #c8c8c8 100%);
  border: 2px solid #444;
  border-radius: 16px;
  padding: 14px;
  box-shadow:
    0 12px 26px rgba(0,0,0,0.45),
    inset 0 2px 0 rgba(255,255,255,0.7);
  position: relative;
}
.ws-scale-dial-svg { width: 100%; max-width: 280px; height: auto; display: block; }

.ws-scale-brand {
  text-align: center;
  margin-top: 10px;
  font-size: 9px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(0,0,0,0.45);
  font-weight: 800;
}

/* Read-the-display question */
.ws-scale-readout-q {
  background: rgba(0,0,0,0.4);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 14px;
  padding: 14px 18px;
  width: 100%;
  max-width: 560px;
  margin: 6px auto 0;
}
.ws-scale-readout-q .lab {
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.6);
  font-weight: 800;
  margin-bottom: 10px;
  text-align: center;
}
.ws-scale-readout-q .opts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 8px;
}
.ws-scale-opt {
  background: linear-gradient(180deg, #fff 0%, #F7F3EC 100%);
  color: var(--paes-navy);
  border: 2px solid transparent;
  border-radius: 10px;
  padding: 12px 10px;
  font-family: 'SF Mono', Menlo, monospace;
  font-size: 16px;
  font-weight: 800;
  cursor: pointer;
  transition: transform 0.1s ease, border-color 0.2s ease;
}
.ws-scale-opt:hover { transform: translateY(-2px); }
.ws-scale-opt.right { border-color: var(--paes-green); box-shadow: 0 0 0 3px rgba(93,216,160,0.4); }
.ws-scale-opt.wrong { border-color: var(--paes-red); animation: ws-shake 0.4s ease; }

/* ---------- Win card ---------- */
.ws-win {
  position: fixed; inset: 0;
  background:
    radial-gradient(ellipse at 30% 20%, rgba(255,209,92,0.18) 0%, transparent 50%),
    radial-gradient(ellipse at 70% 80%, rgba(39,169,225,0.18) 0%, transparent 55%),
    linear-gradient(180deg, var(--paes-navy) 0%, var(--paes-navy-deep) 100%);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  overflow-y: auto;
  opacity: 0;
  transition: opacity 0.3s ease;
  z-index: 9500;
}
/* Floating motes layer for the win screen — same vocabulary as the
   workstation scene + day path so the celebration feels continuous
   with the rest of the experience. */
.ws-win::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle 1.5px at 14% 22%, rgba(255,255,255,0.85), transparent 70%),
    radial-gradient(circle 1px   at 78% 38%, rgba(255,209,92,0.7), transparent 70%),
    radial-gradient(circle 2px   at 32% 64%, rgba(39,169,225,0.6), transparent 70%),
    radial-gradient(circle 1px   at 88% 78%, rgba(255,255,255,0.7), transparent 70%),
    radial-gradient(circle 1.5px at 8% 84%, rgba(255,209,92,0.55), transparent 70%);
  background-size: 240px 240px, 320px 320px, 200px 200px, 280px 280px, 360px 360px;
  pointer-events: none;
  animation: ws-win-mote-drift 20s linear infinite;
  opacity: 0.6;
}
@keyframes ws-win-mote-drift {
  0%   { background-position: 0 0, 0 0, 0 0, 0 0, 0 0; }
  100% { background-position: 60px -120px, -90px -180px, 70px -100px, -110px -140px, 80px -160px; }
}
.ws-win.show { opacity: 1; }
.ws-win-card {
  position: relative;
  z-index: 2;
  background: linear-gradient(180deg, #fff 0%, #F7F3EC 100%);
  color: var(--paes-navy);
  border-radius: 28px;
  padding: 44px 48px 36px;
  text-align: center;
  max-width: 580px;
  width: 100%;
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,0.6),
    0 36px 96px rgba(0,0,0,0.55),
    0 0 0 1px rgba(255,255,255,0.18),
    0 0 60px rgba(255,209,92,0.25);
  transform: scale(0.88) translateY(20px);
  transition: transform 0.55s cubic-bezier(0.34, 1.6, 0.64, 1);
}
.ws-win.show .ws-win-card { transform: scale(1) translateY(0); }
.ws-win-trophy {
  font-size: 84px;
  margin-bottom: 14px;
  display: inline-block;
  filter: drop-shadow(0 8px 18px rgba(0,0,0,0.25));
  animation: ws-trophy-celebrate 1.2s cubic-bezier(0.34, 1.6, 0.64, 1);
}
@keyframes ws-trophy-celebrate {
  0%   { transform: scale(0) rotate(-180deg); opacity: 0; }
  60%  { transform: scale(1.25) rotate(20deg); opacity: 1; }
  80%  { transform: scale(0.95) rotate(-8deg); }
  100% { transform: scale(1) rotate(0); }
}
.ws-win-card h1 {
  font-size: 38px;
  font-weight: 800;
  letter-spacing: -0.8px;
  margin: 0 0 8px;
  font-family: 'Roboto Slab', Georgia, serif;
  background: linear-gradient(180deg, var(--paes-navy) 0%, #073876 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.ws-win-sub { color: rgba(10,77,155,0.7); font-size: 15.5px; margin: 0 0 24px; line-height: 1.5; font-weight: 500; }

.ws-win-rubric {
  background: rgba(39,169,225,0.06);
  border: 1px solid rgba(39,169,225,0.25);
  border-radius: 14px;
  padding: 16px 18px;
  margin-bottom: 18px;
  text-align: left;
}
.ws-rubric-h {
  font-size: 10.5px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: rgba(10,77,155,0.55);
  font-weight: 800;
  margin-bottom: 10px;
}
.ws-rubric-grid {
  display: grid; gap: 6px;
}
.ws-rubric-row {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 14px;
  padding: 8px 0;
  border-bottom: 1px solid rgba(10,77,155,0.06);
  font-size: 14px;
}
.ws-rubric-row:last-child { border-bottom: none; }
.ws-rubric-k { font-weight: 700; flex: 1; min-width: 0; text-align: left; }
.ws-rubric-k-label { font-weight: 700; font-size: 14px; color: var(--paes-navy); }
.ws-rubric-k-why { font-size: 11.5px; font-weight: 500; color: rgba(10,77,155,0.65); margin-top: 2px; line-height: 1.35; font-style: italic; }
.ws-rubric-pill { flex-shrink: 0; }
.ws-rubric-pill {
  display: inline-block;
  padding: 4px 14px;
  border-radius: 100px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.4px;
  /* Roast caught the badges reading as "deactivated" / strikethrough. Force
     no text decoration and a solid pill with a strong outline so the tier
     is unmistakable. */
  text-decoration: none !important;
  font-style: normal;
  border: 1.5px solid transparent;
  text-shadow: none;
}
.ws-rubric-pill.t4 { background: linear-gradient(180deg,#FFD700 0%,#E0A800 100%); color: #2A1A05; border-color: #B07810; box-shadow: inset 0 1px 0 rgba(255,255,255,0.5), 0 2px 0 rgba(180,100,0,0.6); }
.ws-rubric-pill.t3 { background: linear-gradient(180deg,#FFE8A0 0%,#E8C868 100%); color: #4A3018; border-color: #B88838; box-shadow: inset 0 1px 0 rgba(255,255,255,0.5), 0 2px 0 rgba(140,90,30,0.4); }
.ws-rubric-pill.t2 { background: linear-gradient(180deg,#E8985A 0%,#B87038 100%); color: #fff; border-color: #884818; box-shadow: inset 0 1px 0 rgba(255,255,255,0.4), 0 2px 0 rgba(80,40,10,0.5); }
.ws-rubric-pill.t1 { background: linear-gradient(180deg,#888 0%,#555 100%); color: #fff; border-color: #333; box-shadow: inset 0 1px 0 rgba(255,255,255,0.25), 0 2px 0 rgba(0,0,0,0.3); }
.ws-rubric-overall {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1.5px solid rgba(10,77,155,0.15);
  font-weight: 700;
  font-size: 14.5px;
}

.ws-win-stats {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 10px; margin-bottom: 24px;
}
.ws-win-stat {
  background: linear-gradient(180deg, #fff 0%, rgba(247,243,236,0.6) 100%);
  border: 1px solid rgba(10,77,155,0.12);
  border-radius: 14px;
  padding: 14px 8px;
  text-align: center;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.9),
    0 6px 14px rgba(10,77,155,0.08);
}
.ws-win-stat .lab {
  font-size: 9.5px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(10,77,155,0.6);
  font-weight: 800;
  margin-bottom: 5px;
}
.ws-win-stat .val {
  font-size: 22px; font-weight: 800;
  color: var(--paes-navy);
  font-family: 'Roboto Slab', Georgia, serif;
  letter-spacing: -0.4px;
}

.ws-win-actions {
  display: flex; gap: 12px; justify-content: center;
  flex-wrap: wrap;
}
.ws-btn {
  padding: 14px 26px;
  border-radius: 100px;
  font-weight: 800;
  font-size: 15px;
  cursor: pointer;
  font-family: 'Roboto Slab', Georgia, serif;
  border: none;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: all 0.18s cubic-bezier(.34, 1.5, .64, 1);
  letter-spacing: 0.2px;
}
.ws-btn.primary {
  background: linear-gradient(180deg, #FFD15C 0%, #F5A623 100%);
  color: #2a1a05;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.6),
    0 4px 0 rgba(180,100,0,0.85),
    0 12px 26px rgba(245,166,35,0.4);
  text-shadow: 0 1px 0 rgba(255,255,255,0.4);
}
.ws-btn.primary:hover {
  transform: translateY(-2px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.7),
    0 6px 0 rgba(180,100,0,0.85),
    0 16px 32px rgba(245,166,35,0.5);
}
.ws-btn.primary:active {
  transform: translateY(0);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.4),
    0 2px 0 rgba(180,100,0,0.85),
    0 6px 14px rgba(245,166,35,0.3);
}
.ws-btn.secondary {
  background: rgba(10,77,155,0.08);
  color: var(--paes-navy);
  border: 1.5px solid rgba(10,77,155,0.18);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.6);
}
.ws-btn.secondary:hover {
  background: rgba(10,77,155,0.14);
  border-color: var(--paes-navy);
  transform: translateY(-2px);
}

/* ---------- Picture-first defaults stronger ---------- */
html.paes-picture-first .ws-prompt { font-size: 36px; }
html.paes-picture-first .ws-tile { font-size: 17px; padding: 16px 22px; min-width: 130px; }
html.paes-picture-first .ws-tile-glyph { font-size: 28px; }
html.paes-picture-first .ws-bucket-glyph { font-size: 38px; }
html.paes-picture-first .ws-coin-btn .ws-coin-glyph { font-size: 28px; }
html.paes-picture-first .ws-typing-text { font-size: 20px; }

/* Round-3 roast: "Picture-first toggle is invisible — I never saw it
   activate any change." Cause: the procedure template (used by ~80% of
   live games — EP/KA/FS/HC/WP/CM/SW/etc.) had no picture-first overrides.
   Now picture-first scales up the procedure's step text, verb buttons,
   why-card body, and rubric text — visibly bigger so emerging readers
   get a noticeable affordance. */
html.paes-picture-first .ws-proc-step { font-size: 17px; line-height: 1.5; }
html.paes-picture-first .ws-proc-step.active { font-size: 18.5px; }
html.paes-picture-first .ws-verb-label { font-size: 17px; }
html.paes-picture-first .ws-verb-btn { padding: 18px 26px; }
html.paes-picture-first .ws-verb-glyph { font-size: 32px; }
html.paes-picture-first .ws-why-body { font-size: 16px; line-height: 1.55; }
html.paes-picture-first .ws-why-title { font-size: 18px; }
html.paes-picture-first .ws-proc-check { font-size: 15px; }
html.paes-picture-first .ws-proc-check li { padding: 11px 14px 11px 36px; }
html.paes-picture-first .ws-proc-counter .val { font-size: 38px; }
html.paes-picture-first .ws-proc-counter .lab,
html.paes-picture-first .ws-proc-counter .goal { font-size: 14px; }
html.paes-picture-first .ws-proc-step-cta { font-size: 18px; }
html.paes-picture-first .ws-flash { font-size: 16.5px; padding: 16px 32px; }

/* ---------- Tablet + phone ---------- */
@media (max-width: 900px) {
  .ws-pos { grid-template-columns: 1fr; }
  .ws-prompt { font-size: 24px; }
  .ws-stage { padding: 18px 14px 100px; gap: 16px; }
  .ws-tile { min-width: 92px; font-size: 13.5px; padding: 10px 13px; }
}
@media (max-width: 540px) {
  .ws-prompt { font-size: 21px; }
  .ws-prompt-rule { font-size: 13px; }
  .ws-coin-grid { grid-template-columns: repeat(4, 1fr); }
  .ws-given-amount { font-size: 24px; }
  .ws-tile { min-width: 80px; padding: 9px 11px; font-size: 13px; }
  .ws-seq-slot { flex: 0 0 100px; height: 78px; }
  .ws-bucket { min-height: 110px; padding: 10px 8px; }
  .ws-bucket-glyph { font-size: 24px; }
  .ws-bucket-label { font-size: 12px; }
  .ws-win-card { padding: 26px 22px; }
  .ws-win-card h1 { font-size: 24px; }
  .ws-win-stats { grid-template-columns: repeat(2, 1fr); }
  .ws-typing-text { font-size: 15px; }
  .ws-typing-input { font-size: 14px; }
  .ws-actions { padding-top: 6px; }
  .ws-btn-primary, .ws-btn-tertiary { padding: 11px 20px; font-size: 13.5px; }
  .ws-measure-options { grid-template-columns: repeat(2, 1fr); }
  .ws-measure-opt { font-size: 17px; padding: 14px 10px; min-height: 56px; }
  .ws-task-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .ws-task-tile { min-height: 118px; padding: 16px 10px; }
  .ws-task-glyph { font-size: 38px; }
  .ws-task-label { font-size: 14px; }
  .ws-tool { padding: 12px 12px 10px; }
}

/* ============================================================
   TEMPLATE 8: math-input
   ============================================================ */
.ws-math-stage { display: flex; justify-content: center; padding: 18px 0; }
.ws-math-card {
  background: linear-gradient(180deg, #fff8ec 0%, #e8d8b8 100%);
  border-radius: 14px;
  padding: 28px 36px;
  min-width: 380px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.5);
  border: 2px solid #5a3018;
}
.ws-math-label {
  font-size: 12px; font-weight: 800; letter-spacing: 2px;
  color: #7a5018; text-transform: uppercase; margin-bottom: 10px;
}
.ws-math-input-row { display: flex; align-items: baseline; gap: 6px; justify-content: center; }
.ws-math-unit { font-size: 28px; font-weight: 800; color: #5a3018; }
.ws-math-input {
  font-size: 42px; font-weight: 800;
  width: 220px; text-align: center;
  background: #fff; border: 2px solid #5a3018; border-radius: 8px;
  padding: 8px 14px;
  font-family: 'Roboto Slab', Georgia, serif;
  color: #1a3048;
  box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.ws-math-input:focus { border-color: var(--paes-blue, #5b8def); box-shadow: 0 0 0 3px rgba(91,141,239,0.3), inset 0 2px 4px rgba(0,0,0,0.15); }
.ws-math-input.right { border-color: var(--paes-green, #5DD8A0); box-shadow: 0 0 0 3px rgba(93,216,160,0.4); }
.ws-math-input.wrong { border-color: var(--paes-red, #E36B5C); animation: ws-shake 0.4s ease; }
.ws-math-feedback { font-size: 14px; margin-top: 14px; min-height: 22px; text-align: center; font-weight: 700; }
.ws-math-feedback.ok { color: var(--paes-green, #5DD8A0); }
.ws-math-feedback.bad { color: var(--paes-red, #E36B5C); }
.ws-math-feedback.nudge { color: #FFD15C; }

/* ============================================================
   TEMPLATE 9: doc-format
   ============================================================ */
.ws-doc-stage {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  padding: 12px 0;
}
.ws-doc-toolbar {
  display: flex; gap: 6px;
  background: #2a3848; border-radius: 6px;
  padding: 6px; box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}
.ws-doc-btn {
  width: 44px; height: 38px;
  background: #fafafa; color: #1a3048;
  border: 1.5px solid #888; border-radius: 4px;
  font-family: Georgia, serif; font-size: 18px;
  cursor: pointer;
  transition: background 0.12s, transform 0.1s;
}
.ws-doc-btn:hover { background: #FFD15C; transform: translateY(-1px); }
.ws-doc-btn:active { transform: translateY(1px); }
.ws-doc-page {
  background: #fff;
  width: 480px; min-height: 220px;
  padding: 36px 44px;
  border: 1px solid #888;
  border-radius: 4px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.45);
  font-family: Georgia, serif;
  color: #1a1a1a;
  display: flex; flex-direction: column; gap: 14px;
}
.ws-doc-line {
  font-size: 18px;
  padding: 6px 8px;
  border-radius: 3px;
  cursor: pointer;
  transition: background 0.12s, outline 0.12s;
}
.ws-doc-line:hover { background: #f0f4ff; }
.ws-doc-line.is-active { outline: 2px solid #5b8def; background: #e8efff; }
.ws-doc-line.is-bold { font-weight: 800; }
.ws-doc-line.is-italic { font-style: italic; }
.ws-doc-line.is-underline { text-decoration: underline; }
.ws-doc-line.is-correct { color: #1d8b5c; }
.ws-doc-help {
  font-size: 13px; color: #d8d8d8; opacity: 0.85;
}

/* ============================================================
   TEMPLATE 10: email-reply
   ============================================================ */
.ws-email-stage {
  display: grid; grid-template-columns: 1fr 1fr; gap: 18px;
  margin-top: 10px;
}
.ws-email-msg, .ws-email-compose {
  background: #fafafa; color: #1a1a1a;
  border-radius: 8px;
  padding: 16px 18px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.3);
  border: 1px solid #888;
}
.ws-email-msg-h { border-bottom: 1px solid #ddd; padding-bottom: 10px; margin-bottom: 12px; }
.ws-email-from, .ws-email-subject { font-size: 13px; color: #444; }
.ws-email-from { margin-bottom: 4px; }
.ws-email-body { font-size: 14px; line-height: 1.5; color: #222; white-space: pre-wrap; }
.ws-email-h { font-size: 12px; font-weight: 800; letter-spacing: 1.5px; text-transform: uppercase; color: #7a5018; margin-bottom: 10px; }
.ws-email-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.ws-email-row label { width: 60px; font-size: 12px; font-weight: 700; color: #555; }
.ws-email-row input { flex: 1; font-size: 13px; padding: 6px 8px; border: 1px solid #aaa; background: #f4f4f4; border-radius: 4px; color: #444; }
.ws-email-compose textarea {
  width: 100%; box-sizing: border-box;
  min-height: 110px;
  font-size: 14px;
  font-family: -apple-system, sans-serif;
  padding: 10px 12px;
  border: 1px solid #aaa; background: #fff; border-radius: 4px;
  resize: vertical;
  color: #1a1a1a;
}
.ws-email-compose textarea:focus { border-color: #5b8def; outline: none; box-shadow: 0 0 0 3px rgba(91,141,239,0.25); }
.ws-email-feedback { font-size: 13px; margin-top: 8px; min-height: 18px; font-weight: 700; }
.ws-email-feedback.ok { color: #1d8b5c; }
.ws-email-feedback.bad { color: #c83030; }

/* ============================================================
   TEMPLATE 11: structured-form
   ============================================================ */
.ws-form-stage {
  display: grid; grid-template-columns: 280px 1fr; gap: 22px;
  margin-top: 10px;
}
.ws-form-ticket {
  background: linear-gradient(180deg, #fff8ec 0%, #e8d8b8 100%);
  color: #1a1a1a;
  padding: 16px 18px;
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.5);
  border: 1.5px dashed #5a3018;
  font-family: 'Courier New', monospace;
}
.ws-form-ticket-h {
  font-size: 12px; font-weight: 800; letter-spacing: 2px;
  text-transform: uppercase; color: #5a3018;
  border-bottom: 1px dashed #888; padding-bottom: 8px; margin-bottom: 10px;
}
.ws-form-ticket-row { display: flex; justify-content: space-between; gap: 10px; padding: 4px 0; font-size: 13px; }
.ws-form-ticket-row .k { color: #5a3018; font-weight: 700; }
.ws-form-ticket-row .v { color: #1a1a1a; font-weight: 700; }
.ws-form-pane {
  background: rgba(255,255,255,0.05);
  border-radius: 8px;
  padding: 18px 20px;
}
.ws-form-row {
  display: grid; grid-template-columns: 130px 1fr 28px;
  align-items: center; gap: 10px;
  margin-bottom: 10px;
}
.ws-form-row label { font-size: 13px; font-weight: 700; color: #d8d8d8; }
.ws-form-row input {
  font-size: 14px;
  padding: 8px 10px;
  border: 1.5px solid #5a6878; background: #1a3048; color: #fff;
  border-radius: 4px;
  font-family: 'Courier New', monospace;
}
.ws-form-row input:focus { outline: none; border-color: #5b8def; box-shadow: 0 0 0 2px rgba(91,141,239,0.3); }
.ws-form-status { font-size: 18px; font-weight: 800; }
.ws-form-status.ok { color: #5DD8A0; }
.ws-form-status.bad { color: #E36B5C; }
.ws-form-feedback { font-size: 13px; margin-top: 12px; min-height: 18px; font-weight: 700; }
.ws-form-feedback.ok { color: #5DD8A0; }
.ws-form-feedback.bad { color: #E36B5C; }

/* ============================================================
   TEMPLATE 13: spatial-sequence
   Click numbered points in a specific order on a canvas.
   ============================================================ */
.ws-spatial-stage {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  padding: 12px 0;
}
.ws-spatial-stage svg {
  background: linear-gradient(180deg, #1a3048 0%, #0a1828 100%);
  border-radius: 12px;
  box-shadow: 0 8px 22px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.05);
}
.ws-spatial-point { transition: transform 0.12s ease; }
.ws-spatial-point:hover { transform: translateY(-1px); }
.ws-spatial-progress {
  font-size: 13px; color: #d8d8d8; opacity: 0.9;
  background: rgba(255,255,255,0.04);
  padding: 6px 14px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.08);
}
.ws-spatial-progress b { color: #FFD15C; }
