/* ==========================================================================
   Base layout, panels, feedback box, status dots — copied verbatim from
   kitsec/style.css. None of it names a component, so none of it needed to
   change for MMK's three build stages.
   ========================================================================== */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html, body { height: 100%; }

body {
    font-family: 'Arial', sans-serif;
    background: linear-gradient(135deg, #1a1a2e, #16213e);
    color: white;
    user-select: none;
    height: 100vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.game-header {
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 8px 20px;
    background: rgba(0, 0, 0, 0.3);
    backdrop-filter: blur(10px);
    flex-shrink: 0;
}

.brand-link {
    flex-shrink: 0;
    width: 180px;
    display: block;
    line-height: 0;
}

.brand-logo {
    width: 100%;
    height: auto;
    display: block;
}

.header-text {
    flex: 1;
    text-align: center;
}

.game-header::after {
    content: '';
    flex-shrink: 0;
    width: 180px;
}

.game-title {
    font-size: 1.6rem;
    line-height: 1.1;
    background: linear-gradient(45deg, #ffd700, #ffeb3b);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    margin-bottom: 4px;
}

.game-subtitle {
    font-size: 0.95rem;
    opacity: 0.9;
    margin-bottom: 6px;
}

.instructions {
    max-width: 900px;
    margin: 0 auto;
    font-size: 0.85rem;
    line-height: 1.35;
    color: #ecf0f1;
    opacity: 0.85;
}

.instructions strong {
    color: #f39c12;
}

.game-container {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    max-width: 1600px;
    width: 100%;
    margin: 0 auto;
    padding: 12px 20px;
    flex: 1;
    min-height: 0;
    box-sizing: border-box;
}

.info-panel.left-panel {
    width: 300px;
    display: flex;
    flex-direction: column;
    gap: 15px;
}

.circuit-section {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 500px;
    position: relative;
}

.info-panel {
    width: 340px;
    display: flex;
    flex-direction: column;
    gap: 15px;
}

.panel {
    width: 100%;
    background: rgba(0, 0, 0, 0.4);
    border-radius: 12px;
    padding: 20px;
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.1);
}

.panel h3 {
    color: #ffd700;
    margin-bottom: 15px;
    font-size: 1.1rem;
}

.kit-link { display: block; text-decoration: none; }

.kit-image {
    width: 100%;
    height: auto;
    display: block;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.15);
    margin-bottom: 8px;
}

.kit-cta {
    display: block;
    text-align: center;
    color: #ffd700;
    font-weight: bold;
    font-size: 0.95rem;
}

.instructions-link {
    display: block;
    padding: 14px 16px;
    background: rgba(0, 0, 0, 0.4);
    border: 1px solid rgba(255, 215, 0, 0.35);
    border-radius: 12px;
    backdrop-filter: blur(10px);
    color: #ffd700;
    font-size: 0.95rem;
    font-weight: bold;
    text-align: center;
    text-decoration: none;
    transition: background 0.2s ease, border-color 0.2s ease;
}

.instructions-link:hover {
    background: rgba(255, 215, 0, 0.12);
    border-color: rgba(255, 215, 0, 0.75);
}

.status-item {
    display: flex;
    align-items: center;
    margin: 8px 0;
    font-size: 0.9rem;
}

.status-dot {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    margin-right: 10px;
    background: #e74c3c;
    transition: all 0.3s ease;
}

.status-dot.active {
    background: #2ecc71;
    box-shadow: 0 0 12px rgba(46, 204, 113, 0.6);
}

.feedback-box {
    background: rgba(52, 152, 219, 0.95);
    color: white;
    padding: 10px 15px;
    border-radius: 8px;
    font-size: 0.8rem;
    line-height: 1.3;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-left: 4px solid rgba(255, 255, 255, 0.8);
    text-align: center;
    height: 120px;
    overflow-y: auto;
    transition: background 0.3s ease, border-color 0.3s ease;
    font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
    display: flex;
    align-items: center;
    justify-content: center;
}

.feedback-box.success { background: rgba(46, 204, 113, 0.95); border-left-color: #2ecc71; }
.feedback-box.warning { background: rgba(230, 126, 34, 0.95); border-left-color: #f39c12; }
.feedback-box.error   { background: rgba(231, 76, 60, 0.95); border-left-color: #e74c3c; }
.feedback-box.info    { background: rgba(52, 152, 219, 0.95); border-left-color: #3498db; }

.concept-explanation {
    font-size: 0.85rem;
    line-height: 1.4;
    color: #ecf0f1;
}

.concept-explanation strong { color: #f39c12; }
.small-note { color: #9fb0bd; font-size: 0.8rem; }

.reset-button {
    position: absolute;
    bottom: 15px;
    right: 15px;
    padding: 8px 16px;
    background: #e74c3c;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 0.9rem;
    z-index: 50;
    transition: background 0.3s ease;
}

.reset-button:hover { background: #c0392b; }

/* ==========================================================================
   Build stages — MMK-specific. Stage 1 (winding) and Stage 2 (sanding) are
   self-contained scenes; Stage 3 (wiring) is the familiar circuit-area
   pattern from the sibling labs. Only one stage is visible at a time,
   toggled by setStage() in game-logic.js — there is no way back, matching
   the kit's own one-way build.
   ========================================================================== */
.build-stage {
    display: none;
    position: relative; /* anchors .scrape-detail's corner placement in Stage 2 */
    flex: 1;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    background: linear-gradient(180deg, #2c3e50, #24313f);
    border-radius: 15px;
    border: 3px solid #34495e;
    box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.3);
    padding: 16px;
    min-height: 400px;
    animation: stageFadeIn 0.4s ease;
}

.build-stage.active { display: flex; }

@keyframes stageFadeIn {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
}

.stage-caption {
    color: #ffd700;
    font-weight: bold;
    font-size: 1rem;
    letter-spacing: 0.02em;
}

.stage-svg {
    width: min(420px, 92%);
    height: auto;
}

.stage-readout {
    color: #ecf0f1;
    font-size: 0.9rem;
    min-height: 1.2em;
}

.stage-hint {
    color: #9fb0bd;
    font-size: 0.8rem;
    text-align: center;
    max-width: 380px;
}

.stage-btn,
.stage-btn-row .stage-btn {
    padding: 12px 22px;
    border: 2px solid #ffd042;
    border-radius: 999px;
    background: linear-gradient(135deg, #f6b60c, #e07b00);
    color: #1a1206;
    font-size: 0.95rem;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.stage-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5); }
.stage-btn:disabled { opacity: 0.6; cursor: default; transform: none; }

.stage-btn-row { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }

/* --- Stage 1: winding ------------------------------------------------- */
/* An 8-frame photographed flip-book (parts/wind/1.png .. 8.png), all shot on
   the same 1536x1024 canvas — winding.js only ever swaps background-image,
   never the box itself, so nothing shifts between frames. */
.wind-frame {
    width: min(480px, 96%);
    aspect-ratio: 1536 / 1024;
    background: no-repeat center;
    background-size: contain;
    filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.4));
}

/* --- Stage 2: sanding --------------------------------------------------- */
/* A 3-frame photographed flip-book (scrape/1.png .. 3.png, 1254x1254), both
   ends scraping at once — same convention as .wind-frame above: one box,
   sanding.js only ever swaps which file its background points to. */
.scrape-frame {
    position: relative; /* anchors .scrape-tool */
    width: min(360px, 82%);
    aspect-ratio: 1 / 1;
    background: no-repeat center;
    background-size: contain;
    filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.4));
}

/* The "in progress" symbol — swept back and forth like the sandpaper it
   stands for, shown only while sanding.js has the sequence actively playing
   (.active, toggled in scrapeEnds()). Centred over the coil rather than at
   either wire end specifically, since both scrape at once now. */
.scrape-tool {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 2.4rem;
    opacity: 0;
    pointer-events: none;
    filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.6));
}
.scrape-tool.active {
    opacity: 1;
    animation: scrapeSweep 0.4s ease-in-out infinite;
}
@keyframes scrapeSweep {
    0%, 100% { transform: translate(-50%, -50%) translateX(-16px) rotate(-8deg); }
    50%      { transform: translate(-50%, -50%) translateX(16px) rotate(8deg); }
}

/* Two reference photos framing the flip-book, mirrored corners: the
   technique on the left (scrape/Sanding_2.png), the finished close-up on the
   right (scrape/Sanding_1.png). Neither is interactive. */
.scrape-detail {
    position: absolute;
    top: 14px;
    width: 195px;
    height: auto;
    border-radius: 8px;
    border: 2px solid rgba(255, 208, 66, 0.55);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);
    background: #fff;
}
.scrape-detail-left  { left: 14px; }
.scrape-detail-right { right: 14px; }

/* ==========================================================================
   Stage 3 — the circuit workspace. Same shape as kitsec's .circuit-area, a
   wood-toned gradient standing in for a base.png photo (see the top-level
   comment in this repo about swapping in real part photography later).
   ========================================================================== */
/* Wood-block base (parts/base.png / base.webp, reused from kitsec). No
   fallback colour behind it — transparent, so only the photo itself ever
   shows, whatever is or isn't loaded. */
.circuit-area {
    flex: 1;
    width: 100%;
    background: transparent url('parts/base.webp') center / 100% 100% no-repeat;
    background: transparent image-set(url('parts/base.webp') type('image/webp'), url('parts/base.png') type('image/png')) center / 100% 100% no-repeat;
    border-radius: 12px;
    position: relative;
    min-height: 400px;
    box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.35);
    overflow: hidden;
}

.component {
    position: absolute;
    user-select: none;
}

.part-svg {
    width: 100%;
    height: 100%;
    display: block;
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5));
}

/* Battery holder — a photographed single-AA holder (parts/PBH1AA.png,
   1013x320), shot from above with its two wire leads at the top corners:
   black/negative top-left, red/positive top-right (see connections.js'
   TERMINAL_ANCHORS for the exact fractions). Sits above the two supports. */
.battery {
    width: 27%;
    aspect-ratio: 1013 / 320;
    top: 66%;
    left: 53%;
    /* Read by name, not hardcoded, so the short-circuit shake keyframes below
       can reuse whatever horizontal offset is actually centring the battery
       here — the immersive layout overrides this same variable to a
       different value, and hardcoding -50% into the keyframes made the
       battery jump sideways for the shake's duration there. */
    --battery-x: -50%;
    transform: translateX(var(--battery-x));
    background: url('parts/PBH1AA.png') no-repeat center;
    background-size: contain;
}

.battery-label {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 4px;
    font-size: 0.85rem;
    font-weight: bold;
    color: #ecf0f1;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
    white-space: nowrap;
    pointer-events: none;
}

/* Two safety-pin supports, screwed to the board on either side of where the
   armature will sit. Both come from one photo (parts/clip-base.png,
   1504x500 — the two clips shot together, facing each other) shown as a
   sprite: the same image at double width, shifted to its left or right edge
   so each box only ever shows its own clip. The screw — where the wire
   lands, per connections.js — sits low and toward the outer edge of each
   half (left clip: bottom-left; right clip: bottom-right).

   Width is a PERCENTAGE of the circuit-area, not a fixed px value: the coil's
   own size (armature.js' positionOnAxle()) is derived from the real distance
   between the two supports, which itself scales with the circuit-area's
   width — a fixed px width here fell out of proportion with the coil
   whenever the actual viewport landed between the desktop and phone sizes
   these were tuned for (e.g. a desktop window with DevTools docked open,
   still "mobile" by the breakpoint but far wider than an actual phone),
   which is what made the coil look huge next to tiny supports. Percentage
   width tracks the same container the coil's gap is measured against, so
   the two stay in proportion at any width.
   The left offset on .support-a/.support-b below (2.538%) keeps the loop
   roughly centred as growth is applied evenly from that side, expressed as
   a percentage of the *same* container width so it scales together with
   the width above — unlike a top offset, which would need to be relative to
   this element's own width while `top` itself resolves against the
   container's height, two different bases with no fixed ratio between them
   on a flexible-height area. So `top` here is a plain percentage: the
   support may land a few px off perfectly "growing from the screw" at
   extreme sizes, a minor trade for fixing the width disproportion. */
.support {
    width: 22%;
    aspect-ratio: 752 / 500;
    top: 27%;
    background-image: url('parts/clip-base.png');
    background-repeat: no-repeat;
    background-size: 200% 100%;
    filter: drop-shadow(0 3px 5px rgba(0, 0, 0, 0.45));
    /* Above the coil (z-index 15): the coil is deliberately oversized (see
       armature.js' SIZE_SCALE) so its wire ends run past each loop, and
       sitting in front is what hides that overshoot — the illusion of the
       axle threaded through the eye, not just touching it. */
    z-index: 25;
    /* The clip photo has a transparent background (RGBA), and its bounding
       box overlaps a good part of the coil underneath by design. This div has
       no click behaviour of its own — the screw's connection-point is a
       separate element at z-index 45, always reachable — so letting clicks
       fall through keeps the coil clickable wherever the two boxes overlap. */
    pointer-events: none;
}
.support-a { left: 27.46%; background-position: left top; }
.support-b { left: 53.46%; background-position: right top; }

.support-label {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 4px;
    font-size: 0.78rem;
    font-weight: bold;
    color: #ecf0f1;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
    white-space: nowrap;
    pointer-events: none;
}

/* Ceramic magnet (parts/C51000200.png, 760x635) — three states, driven by
   armature.js:
     (default) invisible: the armature isn't mounted yet, nothing to place.
     .ready    a dashed, pulsing outline once the coil is mounted: an
               explicit "click me" step (Step 9 of the real instructions),
               not something that just appears on its own. No photo shown
               yet — the outline alone is the invitation.
     .placed   the real photo, once clicked.
   left/top are set live by armature.js' positionMagnet() — centred under the
   coil, its top edge just touching the coil's bottom — not fixed here, same
   reasoning as .coil-assembly above. */
.magnet {
    width: 14.6%;
    aspect-ratio: 760 / 635;
    top: 0;
    left: 0;
    border-radius: 50%;
    opacity: 0;
    transition: opacity 0.4s ease;
    /* Below the coil (15): physically the magnet sits under the armature. */
    z-index: 10;
    pointer-events: none;
}

.magnet.ready {
    opacity: 1;
    cursor: pointer;
    pointer-events: auto;
    border: 3px dashed #1a1a1a;
    background: rgba(0, 0, 0, 0.06);
    animation: magnetInvite 1.6s ease-in-out infinite;
}
.magnet.ready:focus-visible {
    outline: 3px solid #78dcff;
    outline-offset: 4px;
}

@keyframes magnetInvite {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.04); }
}

.magnet-prompt {
    display: none;
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 4px;
    padding: 3px 9px;
    border-radius: 999px;
    background: rgba(12, 18, 28, 0.85);
    border: 1px solid rgba(255, 208, 66, 0.5);
    color: #ffe9a8;
    font-size: 0.72rem;
    font-weight: bold;
    white-space: nowrap;
    pointer-events: none;
}
.magnet.ready .magnet-prompt { display: block; }

.magnet.placed {
    opacity: 1;
    pointer-events: none;
    animation: none;
    border: none;
    background: url('parts/C51000200.png') no-repeat center;
    background-size: contain;
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5));
}

/* The magnet's own field — a permanent magnet, so unlike the coil's it needs
   no circuit at all: subtle the instant it's placed, and turned up to match
   the coil's field the moment armature.js' startSpinning() adds .spin-field
   (removed again by stopSpinning()). Sized larger than the magnet itself so
   the rings read as radiating from it rather than sitting on top of it. */
.magnet-field {
    position: absolute;
    inset: -35%;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.4s ease;
}
.magnet.placed .magnet-field {
    opacity: 0.35;
}
.magnet.placed.spin-field .magnet-field {
    opacity: 1;
}

.magnet-ring {
    position: absolute;
    inset: 0;
    border: 2px solid rgba(140, 190, 255, 0.8);
    border-radius: 50%;
    opacity: 0;
    animation: magnetPulse 2.6s ease-out infinite;
}
.magnet-ring:nth-child(2) { animation-delay: 0.85s; }
.magnet-ring:nth-child(3) { animation-delay: 1.7s; }

/* Spinning: the same rings pulse faster, echoing the coil's own em-field
   speeding up at the same moment (see .coil-assembly.spinning .em-ring). */
.magnet.placed.spin-field .magnet-ring {
    animation-duration: 1.3s;
}

@keyframes magnetPulse {
    0%   { transform: scale(0.55); opacity: 0.85; }
    100% { transform: scale(1.5); opacity: 0; }
}

/* The finished armature. Sits above the magnet, resting on the axle formed
   by the two supports. Two photographed frames stack on top of each other —
   parts/bobine.png (face-on) and parts/bobine2.png (caught edge-on, mid-turn)
   — and .spinning alternates which one is visible, hard-cut, like flipping
   between two photos rather than smoothly rotating one (contrast carprop's
   propeller, which fakes the same idea with a scaleY transform because it
   only has a single photo to work with). */
/* Width, height, left and top are set live by armature.js'
   positionOnAxle() — not fixed here — so the coil's wire ends always land on
   the loop of each support. These are only the pre-mount fallback (invisible
   anyway) and the mount-in scale, which needs no translate compensation since
   JS already places left/top at the box's own top-left corner. */
.coil-assembly {
    width: 120px;
    aspect-ratio: 1;
    top: 0;
    left: 0;
    transform: scale(0.4);
    opacity: 0;
    /* Below the supports (25) so their loop artwork hides the oversized
       coil's overshoot, above the magnet (10) so the coil still reads as
       sitting over it. */
    z-index: 15;
    cursor: default;
    transition: opacity 0.5s ease, transform 0.5s ease;
}
.coil-assembly.mounted {
    opacity: 1;
    transform: scale(1);
    cursor: pointer;
}
.coil-assembly:focus-visible {
    outline: 3px solid #78dcff;
    outline-offset: 4px;
    border-radius: 50%;
}

.coil-frame {
    position: absolute;
    inset: 0;
    background: no-repeat center;
    background-size: contain;
    transition: filter 0.2s ease;
}
.coil-frame-a { background-image: url('parts/bobine.png'); opacity: 1; }
.coil-frame-b { background-image: url('parts/bobine2.png'); opacity: 0; }

.coil-assembly.mounted:hover .coil-frame {
    filter: drop-shadow(0 0 8px rgba(255, 215, 0, 0.6));
}

/* Spinning — the two frames swap with a hard cut (steps(1)), not a fade, so
   it reads as a flip rather than a dissolve. B is offset half a cycle behind
   A, so exactly one of the two is ever showing. */
.coil-assembly.spinning .coil-frame-a {
    animation: coilFrameA 0.12s steps(1, end) infinite;
}
.coil-assembly.spinning .coil-frame-b {
    animation: coilFrameB 0.12s steps(1, end) infinite;
}
@keyframes coilFrameA { 0%, 49.9% { opacity: 1; } 50%, 100% { opacity: 0; } }
@keyframes coilFrameB { 0%, 49.9% { opacity: 0; } 50%, 100% { opacity: 1; } }

/* A couple of decaying wobbles when the player clicks the coil before the
   circuit can sustain a spin — applied to the whole assembly, since there is
   no single SVG layer to target any more. */
.coil-assembly.nudge {
    animation: coilNudge 0.6s ease-out;
}
@keyframes coilNudge {
    0%   { transform: rotate(0deg); }
    20%  { transform: rotate(10deg); }
    40%  { transform: rotate(-7deg); }
    60%  { transform: rotate(4deg); }
    80%  { transform: rotate(-2deg); }
    100% { transform: rotate(0deg); }
}

.coil-label {
    position: absolute;
    bottom: -20px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 0.78rem;
    font-weight: bold;
    color: #ecf0f1;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
    white-space: nowrap;
    pointer-events: none;
}

/* "Click me" hint: only visible while game-logic.js' updateCircuitStatus()
   has added .needs-spin to the coil (a complete circuit, not yet spinning),
   cleared the instant spin() succeeds — see armature.js. Stacked, back to
   front: the armature, the finger on top of it, the label on top of the
   finger — centred over the coil rather than pinned to a corner. */
.spin-hint {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column-reverse;
    align-items: center;
    gap: 4px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}
.coil-assembly.needs-spin .spin-hint {
    opacity: 1;
}

/* Hinged at its own bottom-right corner, so the swing reads as a downward
   tap striking the coil rather than a wave hovering beside it. */
.spin-hint-finger {
    transform-origin: 100% 100%;
    filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.7));
    animation: spinHintTap 0.9s ease-in-out infinite;
}

@keyframes spinHintTap {
    0%, 20%  { transform: rotate(-28deg); }
    45%      { transform: rotate(2deg); }
    60%      { transform: rotate(-6deg); }
    100%     { transform: rotate(-28deg); }
}

.spin-hint-label {
    padding: 2px 9px;
    border-radius: 999px;
    background: rgba(12, 18, 28, 0.88);
    border: 1px solid rgba(255, 208, 66, 0.5);
    color: #ffe9a8;
    font-size: 0.7rem;
    font-weight: bold;
    white-space: nowrap;
}

/* The field the armature's current generates — three rings expanding out
   from the coil and fading, staggered so one is always mid-pulse. Two
   strengths, both driven by game-logic.js' updateCircuitStatus():
     .circuit-ready  a complete circuit, current already flowing, but not yet
                     spinning (armature.js' showSpinHint() adds this
                     alongside the tapping-finger hint) — a subtle, slow pulse.
     .spinning       the coil actually turning — full opacity, faster pulse,
                     echoed by the magnet's own field at the same moment (see
                     .magnet-field above).
   Standing in for the segment of the electron animation that would
   otherwise have to cross the coil itself (connections.js' support
   electrons stop at the loop on one side and resume at the other — see
   getSupportInternalRoute()). */
.em-field {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 55%;
    height: 55%;
    transform: translate(-50%, -50%);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.3s ease;
    z-index: 4;
}
.coil-assembly.circuit-ready .em-field {
    opacity: 0.3;
}
.coil-assembly.spinning .em-field {
    opacity: 1;
}

.em-ring {
    position: absolute;
    inset: 0;
    border: 2px solid rgba(120, 200, 255, 0.85);
    border-radius: 50%;
    opacity: 0;
    animation: emPulse 3.2s ease-out infinite;
}
.em-ring:nth-child(2) { animation-delay: 1.05s; }
.em-ring:nth-child(3) { animation-delay: 2.1s; }

/* Spinning speeds the same rings up, rather than swapping in a different
   animation — the field is the same field, just stronger. */
.coil-assembly.spinning .em-ring {
    animation-duration: 1.8s;
}

@keyframes emPulse {
    0%   { transform: scale(0.35); opacity: 0.9; }
    100% { transform: scale(2.1); opacity: 0; }
}

/* ==========================================================================
   Connection points, wires, electrons, drag hint, opening demo — copied
   verbatim from kitsec/style.css. None of it names battery/switch/lamp; it
   only reads data-terminal, .connected/.drop-target/.selected state classes
   and the wireId used for the short-circuit highlight, all supplied the same
   way here as there.
   ========================================================================== */
.connection-point {
    position: absolute;
    width: 13px;
    height: 13px;
    box-sizing: border-box;
    background: #e03a2f;
    border: 2px solid #ffd042;
    border-radius: 50%;
    cursor: pointer;
    z-index: 45;
    transform: translate(-50%, -50%);
    touch-action: none;
    transition: background 0.15s ease, border-color 0.15s ease,
                box-shadow 0.15s ease, transform 0.15s ease;
    box-shadow:
        0 0 0 1px rgba(0, 0, 0, 0.55),
        0 0 6px rgba(255, 208, 66, 0.5);
}

.connection-point[data-terminal$="negative"] { background: #14181e; }

/* The supports have no polarity of their own (the real motor spins either
   way round), but the dots are still coloured to match the battery lead each
   sits under in the layout — black on the left (battery-negative's side),
   red on the right (battery-positive's side) — as a wiring hint, not a rule
   the game enforces. */
.connection-point[data-terminal="support-A"] { background: #14181e; }
.connection-point[data-terminal="support-B"] { background: #e03a2f; }

.connection-point > strong { display: none; }

.connection-point:hover {
    transform: translate(-50%, -50%) scale(1.35);
    border-color: #fff3c4;
    box-shadow:
        0 0 0 1px rgba(0, 0, 0, 0.55),
        0 0 10px rgba(255, 215, 0, 0.9);
}

.connection-point.connected {
    border-width: 3px;
    box-shadow:
        0 0 0 1px rgba(0, 0, 0, 0.5),
        0 0 12px 2px rgba(255, 208, 66, 0.95);
}

.connection-point.drop-target {
    transform: translate(-50%, -50%) scale(1.5);
    border-color: #fff3c4;
    box-shadow:
        0 0 0 1px rgba(0, 0, 0, 0.55),
        0 0 14px 3px rgba(255, 215, 0, 1);
}

.connection-point.selected {
    transform: translate(-50%, -50%) scale(1.5);
    border-color: #ffffff;
    box-shadow:
        0 0 0 2px rgba(0, 0, 0, 0.5),
        0 0 14px 4px rgba(120, 220, 255, 1);
    z-index: 46;
}

.connection-point.ready-for-connection {
    animation: readyPulse 1.1s ease-in-out infinite;
}

@keyframes readyPulse {
    0%, 100% {
        border-color: #ffd042;
        box-shadow:
            0 0 0 1px rgba(0, 0, 0, 0.55),
            0 0 6px rgba(255, 208, 66, 0.6);
    }
    50% {
        border-color: #fff3c4;
        box-shadow:
            0 0 0 1px rgba(0, 0, 0, 0.55),
            0 0 16px 3px rgba(255, 208, 66, 0.95);
    }
}

.circuit-wire-group {
    position: absolute;
    inset: 0;
    z-index: 42;
    pointer-events: none;
}

.wire-svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
    filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.45));
    transition: filter 0.3s ease;
}

.wire-svg path {
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.wire-casing { stroke-width: 10; }
.wire-core   { stroke-width: 8; }
.wire-sheen  { stroke-width: 2.4; opacity: 0.45; }

.red-circuit .wire-casing { stroke: #a93226; }
.red-circuit .wire-core   { stroke: #e74c3c; }
.red-circuit .wire-sheen  { stroke: #ff9d93; }

.black-circuit .wire-casing { stroke: #11181f; }
.black-circuit .wire-core   { stroke: #2c3e50; }
.black-circuit .wire-sheen  { stroke: #7c8b9b; }

.wire-ferrule {
    fill: #e3a83a;
    stroke: #8b5a17;
    stroke-width: 1;
}

.circuit-wire-group.active .wire-svg {
    filter: drop-shadow(0 0 3px rgba(46, 204, 113, 0.85))
            drop-shadow(0 0 8px rgba(46, 204, 113, 0.35));
}

.circuit-wire-group.shorted .wire-svg {
    animation: wireShort 0.32s ease-in-out infinite;
}

@keyframes wireShort {
    0%, 100% { filter: brightness(1.3) drop-shadow(0 0 8px rgba(255, 90, 30, 0.85)); }
    50%      { filter: brightness(2.6) saturate(0.4) drop-shadow(0 0 18px rgba(255, 220, 120, 1)); }
}

.circuit-wire-group.wire-preview {
    opacity: 0.65;
    pointer-events: none;
}

.drag-hint {
    position: absolute;
    z-index: 40;
    margin: -38px 0 0 14px;
    padding: 6px 10px;
    max-width: 190px;
    border-radius: 8px;
    background: rgba(12, 18, 28, 0.92);
    border: 1px solid rgba(255, 215, 0, 0.45);
    backdrop-filter: blur(6px);
    color: #ffe9a8;
    font-size: 0.72rem;
    font-weight: bold;
    line-height: 1.25;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s ease;
}

.drag-hint.visible { opacity: 1; }

.drag-hint.stripping::before {
    content: '✂️ ';
    display: inline-block;
    animation: snip 0.4s ease-in-out infinite;
}

@keyframes snip {
    0%, 100% { transform: rotate(-12deg); }
    50%      { transform: rotate(8deg); }
}

.circuit-wire-group.wire-demo .wire-casing,
.circuit-wire-group.wire-demo .wire-sheen,
.circuit-wire-group.wire-demo .wire-ferrule {
    display: none;
}

.circuit-wire-group.wire-demo .wire-core {
    stroke: rgba(255, 255, 255, 0.85);
    stroke-width: 4;
    stroke-dasharray: 9 9;
    animation: demoDashes 1.8s linear infinite;
}

@keyframes demoDashes {
    to { stroke-dashoffset: -18; }
}

.wire-demo-dot {
    position: absolute;
    width: 16px;
    height: 16px;
    margin: -8px 0 0 -8px;
    border-radius: 50%;
    background: radial-gradient(circle at 35% 35%, #fff 0%, #ffe9a8 45%, rgba(255, 208, 66, 0.2) 75%, transparent 78%);
    box-shadow: 0 0 12px 3px rgba(255, 208, 66, 0.8);
    pointer-events: none;
}

.wire-demo-tip {
    position: absolute;
    z-index: 41;
    transform: translate(-50%, 14px);
    padding: 6px 10px;
    white-space: nowrap;
    border-radius: 8px;
    background: rgba(12, 18, 28, 0.92);
    border: 1px solid rgba(255, 215, 0, 0.45);
    backdrop-filter: blur(6px);
    color: #ffe9a8;
    font-size: 0.72rem;
    font-weight: bold;
    pointer-events: none;
    animation: demoTip 1.8s ease-in-out infinite;
}

@keyframes demoTip {
    0%, 100% { opacity: 0.75; }
    50%      { opacity: 1; }
}

.electron {
    position: absolute;
    width: 6px;
    height: 6px;
    margin: -3px 0 0 -3px;
    background: radial-gradient(circle, #ffffff 0%, #00ffff 40%, #0099ff 100%);
    border: 1px solid #00ccff;
    border-radius: 50%;
    z-index: 43;
    box-shadow:
        0 0 6px rgba(0, 255, 255, 0.9),
        0 0 12px rgba(0, 153, 255, 0.6),
        inset 0 0 3px rgba(255, 255, 255, 0.9);
    animation: electronGlow 1s ease-in-out infinite alternate;
}

@keyframes electronGlow {
    0%   { opacity: 0.8; box-shadow: 0 0 4px rgba(0, 255, 255, 0.7), 0 0 8px rgba(0, 153, 255, 0.4), inset 0 0 2px rgba(255, 255, 255, 0.8); }
    100% { opacity: 1;   box-shadow: 0 0 8px rgba(0, 255, 255, 1),   0 0 16px rgba(0, 153, 255, 0.8), inset 0 0 4px rgba(255, 255, 255, 1); }
}

/* Overheating battery, on a short circuit — same animation as the sibling
   labs, applied straight to the photographed holder. */
/* The battery is centred with transform: translateX(var(--battery-x)) (see
   .battery above); every step here reads that same variable and adds the
   shake on top of it. Hardcoding -50% here instead used to work on desktop,
   where that happens to be --battery-x's value — but the immersive layout
   overrides --battery-x to -20% (see .circuit-section.is-immersive .battery),
   and a hardcoded -50% would replace that outright, snapping the battery
   sideways to a completely different spot for the whole shake. */
@keyframes shortCircuitShake {
    0%, 100% { transform: translateX(var(--battery-x)) translateX(0); }
    10% { transform: translateX(var(--battery-x)) translateX(-5px) rotate(-1deg); }
    20% { transform: translateX(var(--battery-x)) translateX(5px) rotate(1deg); }
    30% { transform: translateX(var(--battery-x)) translateX(-4px) rotate(-1deg); }
    40% { transform: translateX(var(--battery-x)) translateX(4px) rotate(1deg); }
    50% { transform: translateX(var(--battery-x)) translateX(-3px) rotate(-0.5deg); }
    60% { transform: translateX(var(--battery-x)) translateX(3px) rotate(0.5deg); }
    70% { transform: translateX(var(--battery-x)) translateX(-2px) rotate(-0.5deg); }
    80% { transform: translateX(var(--battery-x)) translateX(2px) rotate(0.5deg); }
    90% { transform: translateX(var(--battery-x)) translateX(-1px); }
}

.battery.short-circuit-shake { animation: shortCircuitShake 0.6s ease-in-out infinite; }

.battery.short-circuit-danger {
    filter:
        drop-shadow(0 0 10px rgba(255, 0, 0, 0.95))
        drop-shadow(0 0 22px rgba(255, 40, 0, 0.7));
}

/* ==========================================================================
   Mobile / narrow layout — one breakpoint, mirroring the sibling labs.
   ========================================================================== */
@media (max-width: 1200px) {
    body { height: auto; overflow: auto; }

    .game-header { flex-direction: column; gap: 10px; }
    .game-header::after { display: none; }
    .brand-link { width: min(200px, 55%); }

    .game-container {
        flex-direction: column;
        flex: none;
        min-height: 0;
        gap: 12px;
    }

    .info-panel.left-panel { display: contents; }

    .circuit-section              { order: 1; }
    .feedback-box                 { order: 2; }
    .status-panel                 { order: 3; }
    .instructions-link            { order: 4; }
    .info-panel:not(.left-panel)  { order: 5; }
    .kit-panel                    { order: 6; }

    .info-panel {
        width: 100%;
        flex-direction: column;
        overflow: visible;
    }

    .panel, .feedback-box, .instructions-link {
        width: 100%;
        min-width: 0;
    }

    .feedback-box { font-size: 0.8rem; }
    .circuit-section { min-width: 0; }

    .build-stage { min-height: 320px; }
    .circuit-area { height: 320px; min-height: 320px; width: 100%; }

    /* .circuit-area here has a FIXED 320px height but a width that still
       ranges all the way up to the 1200px breakpoint — so the desktop
       percentages above (tuned for a wide, tall workspace) grow the parts
       past what this short, fixed-height box can hold before they'd ever
       shrink to phone size. width: min(%, px) caps that growth: the %
       keeps them shrinking smoothly on a genuinely narrow phone (the
       DevTools-drift bug those percentages fixed still applies at the top
       of this range), the px is a hard ceiling sized for the 320px box. */
    .battery { width: min(27%, 105px); top: 62%; left: 54%; }
    .support { width: min(22%, 95px); top: 22%; }
    .support-a { left: 25.46%; }
    .support-b { left: 57.46%; }
    .magnet { width: min(14.6%, 62px); }
    /* .coil-assembly's size and position are computed live by
       armature.js' positionOnAxle() from the (now-repositioned) supports
       above — nothing to override here. */

    .connection-point { width: 12px; height: 12px; }

    .reset-button { bottom: 10px; right: 10px; padding: 6px 12px; font-size: 0.8rem; }
}

/* ==========================================================================
   Immersive mode — copied verbatim from kitsec/style.css. Drives the same
   #circuitSection / #labStartBtn / #labExitBtn ids used here.
   ========================================================================== */
.lab-start-veil, .rotate-prompt, .lab-exit-btn { display: none; }

.lab-start-veil {
    position: absolute;
    inset: 0;
    z-index: 60;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    border-radius: 15px;
    background: rgba(20, 26, 36, 0.45);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    text-align: center;
    padding: 20px;
}

.lab-start-btn {
    min-height: 52px;
    padding: 14px 28px;
    border: 2px solid #ffd042;
    border-radius: 999px;
    background: linear-gradient(135deg, #f6b60c, #e07b00);
    color: #1a1206;
    font-size: 1.05rem;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45), 0 0 0 6px rgba(255, 208, 66, 0.12);
    animation: labStartPulse 2.2s ease-in-out infinite;
    touch-action: manipulation;
}

@keyframes labStartPulse {
    0%, 100% { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45), 0 0 0 6px rgba(255, 208, 66, 0.12); }
    50%      { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45), 0 0 0 14px rgba(255, 208, 66, 0.03); }
}

.lab-start-note {
    color: #ffe9a8;
    font-size: 0.8rem;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}

.circuit-section.is-immersive {
    position: fixed;
    inset: 0;
    z-index: 9999;
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    margin: 0;
    padding: 0;
    border-radius: 0;
    background: #0e1117;
}

.circuit-section.is-immersive .build-stage,
.circuit-section.is-immersive .circuit-area {
    width: 100%;
    height: 100%;
    min-height: 0;
    border-radius: 0;
}

body.lab-immersive { overflow: hidden; }

.lab-exit-btn {
    position: absolute;
    top: 10px;
    left: 10px;
    z-index: 90;
    width: 38px;
    height: 38px;
    border: 1px solid rgba(255, 255, 255, 0.25);
    border-radius: 50%;
    background: rgba(12, 18, 28, 0.85);
    color: #ffe9a8;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
    touch-action: manipulation;
}

.circuit-section.is-immersive .lab-exit-btn { display: block; }

@media (orientation: portrait) {
    .circuit-section.is-immersive .rotate-prompt {
        display: flex;
        position: absolute;
        inset: 0;
        z-index: 80;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 22px;
        background: #0e1117;
        color: #ffe9a8;
        text-align: center;
        padding: 24px;
    }
}

.rotate-phone {
    width: 54px;
    height: 92px;
    border: 3px solid #ffd042;
    border-radius: 10px;
    box-shadow: inset 0 0 0 2px rgba(255, 208, 66, 0.2);
    animation: rotatePhone 2.4s ease-in-out infinite;
}

@keyframes rotatePhone {
    0%, 25%  { transform: rotate(0deg); }
    55%, 85% { transform: rotate(-90deg); }
    100%     { transform: rotate(0deg); }
}

@media (max-width: 1200px) and (pointer: coarse) {
    .lab-start-veil { display: flex; }
    .circuit-section.is-immersive .lab-start-veil { display: none; }

    /* One knob, same convention as bellkit: --u scales the whole composition
       as a block. Sized so the composition actually fills most of the
       screen — not just matched to the old fixed pixel sizes, which is what
       left it looking small and unchanged in the middle of an otherwise
       full-screen board. Whichever of height or width is tighter on a given
       phone sets u. */
    .circuit-section.is-immersive .circuit-area {
        --u: min(1.7vh, 1.5vw);
    }

    /* Battery is centred with left: 53% + translateX(-50%) (see .battery
       above) — already resolution-independent, so only its width needs a
       --u term here. Pushed down near the bottom edge (was 68%) so the
       composition spans most of the board's height instead of clustering in
       the middle third of it. */
    .circuit-section.is-immersive .battery {
        width: calc(29.4912 * var(--u));
        top: 71.6%;
        left: 42.4%;
        /* .battery's base rule centres it with translateX(var(--battery-x)),
           -50% by default — shifting its own box left by half its width from
           the `left` anchor. Here only 20% of the width shifts left instead,
           landing the anchor closer to the box's left edge than its centre,
           which reads as the whole battery sliding right compared to the
           centred version. Setting the variable itself (not just transform
           directly) keeps @keyframes shortCircuitShake reading the right
           offset here too, instead of jumping to the desktop one mid-shake. */
        --battery-x: -20%;
        transform: translateX(var(--battery-x));
    }

    /* Support's left/top offsets keep the same proportion to its own width
       (and, for top, to the width-to-height ratio of parts/clip-base.png)
       that .support's own 30%-bigger desktop version uses, so growing the
       box with --u keeps the axle-loop anchor point in the same place
       relative to it. Pushed up near the top edge (was 28%) to match the
       battery's move toward the bottom — together they stretch the whole
       clip+coil+magnet+battery cluster across nearly the full board height,
       and armature.js' positionOnAxle() follows the supports here exactly
       like it does everywhere else, so the coil and magnet move with them. */
    .circuit-section.is-immersive .support {
        width: calc(22.464 * var(--u));
        top: calc(26.96% - 3.456 * var(--u));
    }
    .circuit-section.is-immersive .support-a { left: calc(30% - 2.592 * var(--u)); }
    .circuit-section.is-immersive .support-b { left: calc(56% - 2.592 * var(--u)); }

    .circuit-section.is-immersive .magnet { width: calc(12.672 * var(--u)); }
    /* .coil-assembly again computed live — see the note above. */

    .circuit-section.is-immersive .connection-point {
        width: calc(5.4 * var(--u));
        height: calc(5.4 * var(--u));
    }
}

/* ==========================================================================
   Tap guide — copied verbatim from kitsec/style.css.
   ========================================================================== */
.tap-demo {
    position: absolute;
    inset: 0;
    z-index: 41;
    pointer-events: none;
}

.tap-finger {
    position: absolute;
    left: 0;
    top: 0;
    width: 34px;
    height: 34px;
    margin: 2px 0 0 -5px;
    opacity: 0;
    transform-origin: 50% 0;
    transition: left 600ms cubic-bezier(0.4, 0, 0.2, 1),
                top 600ms cubic-bezier(0.4, 0, 0.2, 1),
                opacity 220ms ease,
                transform 140ms ease;
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.7));
}

.tap-finger.is-visible  { opacity: 1; }
.tap-finger.is-pressing { transform: scale(0.82); }

.tap-ripple {
    position: absolute;
    width: 18px;
    height: 18px;
    margin: -9px 0 0 -9px;
    border: 2px solid rgba(120, 220, 255, 0.95);
    border-radius: 50%;
    opacity: 0;
    animation: tapRipple 620ms ease-out forwards;
}

@keyframes tapRipple {
    0%   { transform: scale(0.4); opacity: 1; }
    100% { transform: scale(3.4); opacity: 0; }
}
