* {
    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;
    /* Fill the viewport as a column; no page scroll on desktop */
    height: 100vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Brand on the left, title block centred in the remaining space. The logo is
   taken out of the centring by giving the text block flex:1 and reserving the
   same width on the right, so the title stays optically centred on the page. */
.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;
}

/* Balances the logo so the title block sits centred on the page. */
.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;
    /* Fill remaining viewport height under the header */
    flex: 1;
    min-height: 0;
    box-sizing: border-box;
}

.info-panel.left-panel {
    width: 300px;
    display: flex;
    flex-direction: column;
    gap: 15px;
}

/* Circuit Section - holds the workspace */
.circuit-section {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 500px;
    /* The start veil, the rotate prompt and the exit button all lay themselves
       over the workspace from here. */
    position: relative;
}

/* Circuit area - the workspace. The wooden board (parts/base.png) is the work
   surface the components sit on; a dark fill sits behind it as a fallback. */
.circuit-area {
    flex: 1;
    background: #2c3e50 url('parts/base.webp') center / 100% 100% no-repeat;
    background: #2c3e50 image-set(url('parts/base.webp') type('image/webp'), url('parts/base.png') type('image/png')) center / 100% 100% no-repeat;
    border-radius: 15px;
    border: 3px solid #34495e;
    position: relative;
    min-height: 400px;
    box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.3);
    overflow: hidden;
}

.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;
}

/* Buy This Kit card */
.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;
}

/* Link out to the build instructions. Card-shaped like its neighbours, but
   gold-on-dark so it reads as an action rather than another readout. */
.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);
}

/* Educational feedback console. Fixed height so a message of a different length
   never resizes the card and shifts its neighbours. */
.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;
}

/* Components are mounted at fixed spots in the workspace — the player wires them
   up rather than moving them, so there is no drag cursor and no hover scale that
   would fight the terminal anchoring. */
.component {
    position: absolute;
    user-select: none;
}

/* Layout: bell centred along the top of the board, battery in the bottom-left
   corner and switch in the bottom-right. Each part is held a small inset off
   the edge — parts/base.png is stretched to fill the workspace, so that inset
   IS the board's padding, and it has to leave room for the caption hanging
   under the battery and the switch as well as for the part itself.

   Anchoring the two bottom parts to `bottom` (rather than a fixed `top`) is
   what keeps that padding honest as the workspace grows and shrinks: they ride
   the bottom edge, the bell rides the top one, and the gap between them is the
   slack. Horizontal insets are in pixels for the same reason — a percentage
   would open the corner up on a wide board and close it on a narrow one.

   The bell keeps its 3-part share of the width: it is the widest part, its (−)
   post sits in the middle of its own picture, and the black return wire has to
   clear the top of the unit — which is why the bell is not flush to the top
   edge either. getWireRoute() in connections.js measures that clearance off the
   bell's live box, so moving these numbers moves the cable with them.

   Battery - the kit's 4xAA holder seen from above (parts/battery.png,
   1045x1015). It has no screw posts; both leads come out of the right side. */
.battery {
    width: 200px;
    aspect-ratio: 1045 / 1015;
    /* 34px of padding plus a twentieth of the board: lifted off the bottom edge
       so the caption and the Reset button have their own strip of bare board
       underneath, rather than crowding the rim. A percentage, so the lift grows
       with the board the same way the 10% inset does. */
    bottom: calc(5% + 34px);
    /* Pulled a tenth of the board in from the corner: hard against the edge the
       pack read as an afterthought parked outside the scene, and the red cable
       to the switch was the full width of the board. The 16px is still the
       padding; the 10% is the inset on top of it, and it stays a percentage so
       the two bottom parts close on the centre together as the board narrows. */
    left: calc(10% + 16px);
    background: url('parts/battery.webp') no-repeat center;
    background: image-set(url('parts/battery.webp') type('image/webp'), url('parts/battery.png') type('image/png')) no-repeat center;
    background-size: contain;
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5));
    transition: filter 0.2s ease;
}

/* Short Circuit Shake Animation */
@keyframes shortCircuitShake {
    0%, 100% { transform: translateX(0); }
    10% { transform: translateX(-5px) rotate(-1deg); }
    20% { transform: translateX(5px) rotate(1deg); }
    30% { transform: translateX(-4px) rotate(-1deg); }
    40% { transform: translateX(4px) rotate(1deg); }
    50% { transform: translateX(-3px) rotate(-0.5deg); }
    60% { transform: translateX(3px) rotate(0.5deg); }
    70% { transform: translateX(-2px) rotate(-0.5deg); }
    80% { transform: translateX(2px) rotate(0.5deg); }
    90% { transform: translateX(-1px); }
}

.battery.short-circuit-shake {
    animation: shortCircuitShake 0.6s ease-in-out infinite;
}

/* Overheating: a red glow around the cell. A drop-shadow (not box-shadow) so it
   traces the battery's shape in the transparent PNG rather than a rectangle. */
.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));
}

/* Caption below the battery image. */
.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;
}

/* Knife Switch - photographed states (parts/switch_open.png and
   switch_close.png, both 900x1048). The two shots were taken from different
   angles, so they are pre-aligned on a shared canvas: the brass screw posts —
   the electrical terminals the wires hook onto — land on the same pixel in both
   files (fx 0.314 / 0.698, fy 0.777), and the base plate is centred on the
   canvas with its bottom edge on the canvas bottom. That means the .closed class
   switch.js toggles only has to swap the image: no per-state size or offset, and
   nothing but the lever appears to move. Originals kept in parts/originals/.

   The plate is 69.8% of the canvas width, so the 198px box renders it at the
   same 138px it had before; the rest of the box is the headroom the raised
   lever needs. */
/* Anchored by its bottom-right corner, so growing the box pushes the artwork up
   and to the left, into the empty middle of the board, instead of off the edge. */
.switch {
    width: 189px;
    aspect-ratio: 900 / 1048; /* the shared canvas: plate at the bottom, lever above */
    bottom: calc(5% + 34px);  /* same inset as the battery, so switch-input (fy 0.777)
                                 stays level with battery + and the red cable between
                                 them runs straight across the bottom of the board */
    right: calc(10% + 16px);   /* mirrors the battery's inset */
    background: url('parts/switch_open.webp') no-repeat bottom center;
    background: image-set(url('parts/switch_open.webp') type('image/webp'), url('parts/switch_open.png') type('image/png')) no-repeat bottom center;
    background-size: 100% auto;
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5));
    transition: filter 0.2s ease;
}

.switch.closed {
    background-image: url('parts/switch_close.webp');
    background-image: image-set(url('parts/switch_close.webp') type('image/webp'), url('parts/switch_close.png') type('image/png'));
}

.switch:hover {
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5)) brightness(1.12);
}

/* The lever, posts and screws live in the switch images now. */
.switch-lever {
    display: none;
}

/* Only this central patch toggles the switch: the strip of bare plate between
   the two brass posts (the pair of mounting holes down the middle). The
   connectors now sit on the posts themselves (fx 0.314 / 0.698, fy 0.777), so
   the patch is kept narrow and low — inside the plate, clear of both posts — and
   a near-miss tap on a connector no longer flips the switch. */
.switch-hit {
    position: absolute;
    top: 83%;
    left: 50%;
    width: 22%;
    height: 30%;
    transform: translate(-50%, -50%);
    cursor: pointer;
    /* Above the switch background, below the connectors (z 45) so a direct tap on
       a connector circle still wins. */
    z-index: 20;
    touch-action: manipulation;
}

/* The lever is the other obvious place to tap, so it toggles too. It moves
   between the two states, so the patch tracks it — measured off the 900x1048
   canvas both photographs share:

     open   the blade and its black handle stand up from the hinge on the right
            post (fy ~0.62) to the top edge, across fx 0.60–0.83.
     closed the lever has swung left and lies flat: the gold bar runs fx 0–0.77
            at fy 0.59–0.66, with the handle overhanging the plate on the left
            (fx 0.01–0.22, fy 0.56–0.70).

   Both rects stop short of fy 0.742, where the connector circles on the posts
   begin, so a tap meant for a terminal is never caught by the lever. */
.switch-lever-hit {
    position: absolute;
    top: 0;
    left: 56%;
    width: 30%;
    height: 62%;
    cursor: pointer;
    /* Same layer as .switch-hit: over the artwork, under the connectors (z 45). */
    z-index: 20;
    touch-action: manipulation;
}

.switch.closed .switch-lever-hit {
    top: 55%;
    left: 0;
    width: 77%;
    height: 16%;
}

.switch-label {
    position: absolute;
    bottom: -20px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 0.9rem;
    color: #bdc3c7;
    font-weight: bold;
    text-shadow: 0 1px 2px rgba(0,0,0,0.8);
    white-space: nowrap;
    pointer-events: none;
}

/* Electric bell - not a photograph but an inline <svg> (see index.html), because
   the armature has to rotate about a pivot outside its own image and the sound
   waves, field lines and spark have no HTML equivalent. The box is 4:3 to match
   the SVG's viewBox, so the terminal fractions in connections.js hold at every
   size. The width is capped as a percentage as well as in pixels: the bell is
   the widest part on the board, and on a squeezed workspace it has to shrink
   rather than run off the right edge. */
.bell-assembly {
    width: min(436px, 50.8%);
    aspect-ratio: 4 / 3;
    /* Anchored by its top edge, so growing the box grows the unit downward into
       the open middle of the board and leaves the cable corridor above it alone.
       Not flush to that edge, though: the black return cable leaves the coil's
       (−) post upward and runs over the unit, and getWireRoute() takes its
       clearance from this gap. Flush would pin that leg to the 6px floor. */
    top: 34px;
    left: 50%;
    /* Centring transform. Safe because nothing else transforms this box — the
       ringing and held states are drop-shadow filters, not transforms — and
       because the terminal anchors read getBoundingClientRect(), which reports
       the transformed box. */
    transform: translateX(-50%);
    /* Above the wires (z 41) would hide the cables the player just ran, so the
       bell sits at the parts layer and the wires draw over it — which is also
       how the real kit looks, cables lying across the base plate. */
    z-index: 40;
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5));
    transition: filter 0.25s ease;
}

.bell-svg {
    display: block;
    width: 100%;
    height: 100%;
    /* The connection points are layered above the bell (z 45); letting the SVG
       take pointer events would steal taps aimed at the two binding posts. */
    pointer-events: none;
}

/* A bell makes no light, so there is no glow to fake — but the ringing state
   still has to be unmistakable with the sound off. An amber pulse around the
   whole unit, in step with nothing in particular: it reads as "this thing is
   alive" without competing with the 4.2 Hz armature it surrounds. */
@keyframes bellRingPulse {
    0%, 100% {
        filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5))
                drop-shadow(0 0 10px rgba(242, 169, 59, 0.55));
    }
    50% {
        filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5))
                drop-shadow(0 0 22px rgba(255, 196, 80, 0.9));
    }
}

.bell-assembly.is-ringing {
    animation: bellRingPulse 0.6s ease-in-out infinite;
}

/* Energised but stuck: the magnet is on and the armature is held against it,
   with nothing to break the current. Steady, not pulsing — the whole point is
   that nothing is happening, and a heartbeat glow would say the opposite. */
.bell-assembly.is-held {
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5))
            drop-shadow(0 0 14px rgba(242, 169, 59, 0.7));
}

/* Printed on the unit rather than hung underneath it. The bell's base plate has
   one genuinely empty area — the top-right quarter, between the coil and the
   right edge, above the anchor block — and putting the caption there frees the
   strip below the bell, which the U-shaped cable and the bottom row both want.
   The coordinates are fractions of the box, so they hold at every size: in bell
   coordinates this lands around (740, 92), clear of the coil (which ends at
   x 593) and well above the anchor block (which starts at y 236). */
.bell-label {
    position: absolute;
    top: 27%;
    left: 81%;
    transform: translate(-50%, -50%);
    font-size: 0.75rem;
    color: #ecf0f1;
    font-weight: bold;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
    white-space: nowrap;
    pointer-events: none;
    text-align: center;
}

/* Connection Points — small rings inside a gold bezel, handled the same way as
   carprop. The fill carries the polarity (red positive, dark negative) so no
   +/- glyph is needed. The switch posts have no polarity of their own, but this
   circuit puts them on the negative line — battery − in, the coil's black pole
   out — so they are listed with the negative terminals below rather than
   falling through to the red default; the black cable the spool hands a player
   who grabs one then matches the post they grabbed it from.
   connections.js hands us the exact terminal coordinate; the centring
   is done here, which is why every other transform below repeats the translate
   instead of replacing it. */
.connection-point {
    position: absolute;
    width: 13px;
    height: 13px;
    box-sizing: border-box;
    background: #e03a2f;
    border: 2px solid #ffd042;
    border-radius: 50%;
    cursor: pointer;
    /* Above the part images (z 40) so a terminal is never hidden behind the
       picture it sits on — including the bell's two posts, which are painted
       into the middle of its own artwork. */
    z-index: 45;
    transform: translate(-50%, -50%);
    /* Without this a touch drag starting on a terminal scrolls the page instead
       of pulling the cable. */
    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"],
.connection-point[data-terminal="switch-input"],
.connection-point[data-terminal="switch-output"] {
    background: #14181e;
}

/* Polarity is carried by colour now, so the +/- glyphs in the markup are no
   longer drawn — six captions over the parts read as clutter at this scale. */
.connection-point > strong {
    display: none;
}

/* Hover brightens the bezel, never the fill — the polarity colour has to stay
   readable at all times. */
.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);
}

/* Connected: the fill keeps its polarity colour and the gold bezel thickens,
   so a fully wired circuit still shows which terminal is which. */
.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);
}

/* The terminal the cable would land on if released now. */
.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);
}

/* The first contact tapped on touch. Bright and enlarged so it reads clearly
   over the finger while the user reaches for the second contact. */
.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;
}

/* Applied while a wire is being pulled, so the free terminals announce
   themselves exactly when the user is looking for somewhere to plug in. Glow
   only, no scale — a ring that resizes reads as jitter next to the parts. */
.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 Wires — one group per connected wire, holding a single SVG path
   traced along the whole route (see buildWireGroup in wires.js for why it is
   one path and not a rotated div per leg). Sits above the part images (the bell
   is z 40) so a cable runs OVER the part it plugs into, not hidden behind it. */
.circuit-wire-group {
    position: absolute;
    inset: 0;
    z-index: 42;
    pointer-events: none;
}

/* No viewBox on the element, so one SVG user unit is one CSS pixel and the
   route coordinates from connections.js can be written straight into the path.
   overflow visible because a stroke is centred on its path: half of it hangs
   outside the box wherever a cable runs along the edge of the board. */
.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;
}

/* Round join AND round cap, on all three strokes: the join is what removes the
   corner seam, the cap is what lets the cable end flush on its terminal. */
.wire-svg path {
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Stacked in the order they are appended: casing widest and darkest at the
   bottom, the cable colour over it, a thin bright core on top. Together they
   give a round cable whose shading follows the run rather than the leg. */
.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; }

/* The stripped copper end, where the cable meets a terminal. Mostly hidden
   under the connection point itself (z 45, and wider than this) — what shows is
   a brass rim around it, which is the point: it says the cable is landed. */
.wire-ferrule {
    fill: #e3a83a;
    stroke: #8b5a17;
    stroke-width: 1;
}

/* Lit by game-logic.js marking the group, so the whole run glows as one. The
   black cast shadow goes while it is lit — the glow stands in for it.
   Deliberately restrained: a drop-shadow traces the cable's outline exactly,
   where the old per-segment box-shadow blurred a rectangle around it, so the
   same numbers now read far stronger. Enough green to say "live", not enough to
   turn the black cable green. */
.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));
}

/* The wire (or wires) forming a short circuit flash white hot. An animation, so
   it beats the plain .active declaration above whatever the source order: a
   shorted wire always reads as the alarm, never as running. */
.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));
    }
}

/* The cable being pulled: same construction as a real one, just translucent so
   it reads as "not committed yet". */
.circuit-wire-group.wire-preview {
    opacity: 0.65;
    pointer-events: none;
}

/* Hint bubble that follows the cable end, narrating the gesture in progress. */
.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;
}

/* First beat: scissors nibbling at the insulation. */
.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); }
}

/* Dotted demo of the jumper's run, played on load to show the gesture. Built
   from the same route the real cable takes, so the trace and the finished cable
   land on the same pixels — but drawn as white dashes rather than in the
   cable's own colour, because it is a gesture being demonstrated, not a
   connection that exists yet. */
.circuit-wire-group.wire-demo .wire-casing,
.circuit-wire-group.wire-demo .wire-sheen,
.circuit-wire-group.wire-demo .wire-ferrule {
    display: none;
}

/* Dashes travelling along the path itself, so they follow the route round its
   corners instead of restarting at each one. */
.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; }
}

/* Electrons travelling the circuit, negative to positive — along the cables and,
   while the switch is closed and carrying, across its blade. */
.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; /* above the wires, which are above the part images */
    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;
}

/* The ones crossing the switch travel over a photographed brass blade rather
   than over bare board, so they carry a dark rim to sit them on it — without it
   the cyan reads as a speck of glare on the metal. A filter rather than another
   box-shadow: electronGlow animates box-shadow, and an animated property beats
   any declaration, so a shadow written here would never be drawn. */
.electron.on-blade {
    filter: drop-shadow(0 0 2px rgba(0, 18, 34, 0.9));
}

@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);
    }
}

/* Educational Info */
.concept-explanation {
    font-size: 0.85rem;
    line-height: 1.4;
    color: #ecf0f1;
}

.concept-explanation strong {
    color: #f39c12;
}

/* The two workspace controls, as one pair. They used to sit in opposite corners
   looking nothing alike — a red pill bottom-centre and a small dark circle
   top-right — which read as two unrelated things rather than as "the controls".
   Same box, same width, side by side.
   Both live inside .circuit-area rather than in a side panel on purpose: the
   panels are gone entirely in immersive mode, and a phone in a classroom is
   exactly where someone needs to reach the mute in a hurry.

   Top-right, stacked, sound above reset. That corner is also where the black
   return cable climbs on its way over the bell, so the cable is routed around
   this panel rather than behind it — see the workspace-tools guard in
   getWireRoute() (connections.js). Moving or resizing the panel moves the cable
   with it; nothing here needs to be kept in sync by hand. */
.workspace-tools {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 50;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.tool-btn {
    /* One width for both, which is what makes them a pair. 104px is set by the
       narrowest gap they have to live in, not by the text. */
    width: 104px;
    padding: 7px 6px;
    background: rgba(12, 18, 28, 0.85);
    border: 1px solid rgba(255, 255, 255, 0.22);
    border-radius: 8px;
    color: #ecf0f1;
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: bold;
    line-height: 1.15;
    text-align: center;
    white-space: nowrap;
    cursor: pointer;
    touch-action: manipulation;
    transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}

.tool-btn:hover {
    background: rgba(24, 34, 50, 0.95);
    border-color: rgba(255, 255, 255, 0.45);
}

.tool-icon {
    margin-right: 5px;
}

/* Reset keeps its red. Same box as its neighbour, different accent: it is the
   destructive one of the pair and still has to say so. */
.reset-button {
    background: rgba(140, 38, 30, 0.85);
    border-color: rgba(231, 76, 60, 0.55);
}

.reset-button:hover {
    background: rgba(192, 57, 43, 0.95);
    border-color: rgba(231, 76, 60, 0.95);
}

/* Sound is off by default; the glyph, the label and aria-pressed are written by
   sound.js. Lit gold while on, which is the only state worth calling out. */
.sound-toggle[aria-pressed="true"] {
    border-color: rgba(255, 215, 0, 0.8);
    color: #ffe9a8;
    box-shadow: 0 0 10px rgba(255, 215, 0, 0.35);
}

/* ==========================================================================
   Narrow desktop — still the three-column page, but a squeezed workspace.

   The side panels are fixed at 300px and 340px, so between the 1200px
   breakpoint and about 1440px the board is only ~500-730px wide while the
   battery and the switch stay the same number of pixels. Add the tenth-of-the-
   board inset each side and the bottom row closes up until the Reset button
   between them has nowhere to sit. Everything on that row shrinks together:
   the parts, their insets and the button.
   ========================================================================== */
@media (min-width: 1201px) and (max-width: 1440px) {
    .battery {
        width: 160px;
        left: calc(6% + 12px);
    }

    .switch {
        width: 154px;
        right: calc(6% + 12px);
    }

    /* Slimmer here for room, not for looks: the panel and the bell both have to
       fit across a ~570px board with a corridor left between them for the black
       cable to climb. At 104px there is none, and the cable ends up routed
       behind the panel (see the workspace-tools guard in connections.js). */
    .tool-btn {
        width: 92px;
        padding: 5px 4px;
        font-size: 0.7rem;
    }
}

/* ==========================================================================
   Mobile / narrow layout — one breakpoint, mirroring the carprop lab.
   ========================================================================== */
@media (max-width: 1200px) {
    /* The stacked layout can't fit one viewport, so let the page scroll. */
    body {
        height: auto;
        overflow: auto;
    }

    /* Header stacks: brand on top, then the title block, both full width. The
       spacer that balances the title beside the logo has nothing to balance
       once stacked, so it is removed rather than left adding dead height. */
    .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;
    }

    /* The left column dissolves so its cards become items of the container
       itself — only then can `order` interleave them with the workspace. The
       result is a mobile reading flow that puts the lab first and the shop
       last: play, read the result, learn, then buy. */
    .info-panel.left-panel { display: contents; }

    .circuit-section              { order: 1; }  /* the lab */
    .feedback-box                 { order: 2; }  /* what just happened */
    .status-panel                 { order: 3; }  /* the readouts */
    .instructions-link            { order: 4; }  /* build instructions */
    .info-panel:not(.left-panel)  { order: 5; }  /* reference panels */
    .kit-panel                    { order: 6; }  /* shop link, last */

    /* Everything full width and stacked; nothing scrolls sideways. */
    .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; }

    /* A fixed, self-contained workspace. Sized to hold the shrunk parts below
       with a little air, no more — so it does not leave a slab of empty board. */
    .circuit-area {
        height: 320px;
        min-height: 320px;
        width: 100%;
    }

    /* Shrink the parts to suit the narrower workspace, keeping the same
       composition as the desktop board: bell top-centre, battery bottom-left,
       switch bottom-right, everything held off the edges of the base image.
       Fractional anchors keep the terminals and wires on the parts
       automatically, so only the boxes move. The insets are smaller than the
       desktop ones in proportion to the 320px workspace — a 34px margin on a
       board this short would be a tenth of it. */
    .battery {
        width: 96px;
        left: calc(10% + 8px);   /* same tenth-of-the-board inset as the desktop */
        bottom: calc(5% + 28px);
    }

    .switch {
        width: 123px;            /* renders the plate at 86px */
        right: calc(10% + 8px);
        bottom: calc(5% + 28px);            /* same inset as the battery: switch-input stays
                                    level with battery + (fy 0.80 here, the touch
                                    override nudges it down the plate) */
    }

    .bell-assembly {
        width: min(182px, 48.4%);
        left: 50%;
        top: 22px;               /* the gap the return cable runs through */
    }

    /* The plate's empty corner shrinks with the bell, but the caption does not,
       so it is stepped down to keep it inside the green. */
    .bell-label {
        font-size: 0.55rem;
    }

    .connection-point {
        width: 12px;
        height: 12px;
    }

    /* A 320px-tall board, so the stacked pair has to give some height back. */
    .workspace-tools {
        top: 8px;
        right: 8px;
        gap: 6px;
    }

    /* Same reason as the narrow-desktop step down, plus a 320px-tall board that
       cannot spare the height either. */
    .tool-btn {
        width: 88px;
        padding: 4px 3px;
        font-size: 0.66rem;
    }
}

/* ==========================================================================
   Immersive mode — the lab, full screen and sideways, on touch devices.

   The stacked mobile layout squeezes the workspace into a 320px band between
   the header and the reference panels. That is enough to read, not enough to
   build in: the parts are small and every tap competes with page scroll. So on
   touch the lab is veiled until the player opts in, and opting in lifts the
   whole workspace out to fill the screen.

   One `.is-immersive` class drives both paths. On Android the class rides on
   top of a real Fullscreen API request plus an orientation lock; on iOS Safari,
   where neither exists for non-<video> elements, the same class alone produces
   a fixed, viewport-filling overlay and the rotate prompt asks for the turn the
   browser will not do itself. Nothing here rotates the lab with a transform:
   wire geometry is read straight off getBoundingClientRect() (connections.js),
   and a rotated ancestor would put every terminal and every pointer coordinate
   in the wrong place.
   ========================================================================== */

/* Hidden by default; the touch media query below opts them in. */
.lab-start-veil,
.rotate-prompt,
.lab-exit-btn {
    display: none;
}

/* --- The veil -------------------------------------------------------- */
.lab-start-veil {
    position: absolute;
    inset: 0;
    z-index: 60;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    border-radius: 15px;                 /* matches .circuit-area */
    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);
}

/* --- The immersive shell --------------------------------------------- */
.circuit-section.is-immersive {
    position: fixed;
    inset: 0;
    z-index: 9999;
    width: 100vw;
    height: 100vh;
    height: 100dvh;            /* so Safari's chrome does not crop the board */
    margin: 0;
    padding: 0;
    border-radius: 0;
    background: #0e1117;
}

.circuit-section.is-immersive .circuit-area {
    width: 100%;
    height: 100%;
    min-height: 0;
    border-radius: 0;
    overflow: hidden;
}

/* Stop the page underneath from scrolling behind the overlay. */
body.lab-immersive {
    overflow: hidden;
}

.lab-exit-btn {
    position: absolute;
    top: 10px;
    left: 10px;
    /* Above the rotate prompt (80): a player who does not want to turn the
       phone still has to be able to get out. */
    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;
}

/* --- "Turn your phone" ----------------------------------------------- */
/* Only while immersive AND upright. The lab's proportions are built for a wide
   board, so rather than show it squeezed the prompt covers it outright. */
@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); }
}

/* ==========================================================================
   Touch-only wiring for the above, plus the immersive scene scale.
   ========================================================================== */
@media (max-width: 1200px) and (pointer: coarse) {
    .lab-start-veil {
        display: flex;
    }

    /* Once the player is inside, the veil has done its job. */
    .circuit-section.is-immersive .lab-start-veil {
        display: none;
    }

    /* The whole scene rides on one unit, so the composition of the stacked
       mobile layout is preserved exactly and simply scaled up. `--u` is 1% of
       the workspace height there; here it takes whichever of height or width is
       the binding constraint, which on a landscape phone lands around 2.2x the
       stacked size. Positions are measured from the centre rather than from the
       edges, because the immersive board is far wider than the stacked one and
       percentage lefts would only fling the parts apart. */
    /* Measured, not guessed: the three parts occupy 115u across and 98.8u down —
       the bell's 45.4u, a 3u gap, and the switch's 50.4u, which is the taller of
       the two parts on the bottom row. Stacking the bell above the other two
       makes HEIGHT the binding constraint (it was width when the trio sat in a
       row), so the vh term is what actually sets the scale on a landscape phone;
       the vw term only guards a squat, very wide viewport.

       Which is why the bell and the switch growing 10% here does NOT make them
       10% bigger on screen: the block is what fills the viewport height, so a
       taller block simply buys a smaller u. What it does preserve is the
       proportion between the three parts, so the immersive board still reads as
       the same lab as the desktop one — which is the point of this scale.

       0.90 rather than 100/98.8: the leftover top and bottom is not slack. Above
       the bell it is the corridor the black return cable runs through, and below
       the bottom row it is where the battery's and the switch's captions hang.
       The horizontal margin holds the exit button, the reset button and the
       sound toggle.

       The block is NOT centred in that leftover: it sits 5.3u below the top edge
       and 7.1u above the bottom one. The captions hang below the bottom row and
       nothing hangs above the bell, so an even split clipped them against the
       edge of the board.

       Everything is measured from the centre, not from the edges: the immersive
       board is far wider than the stacked one, and percentage lefts would only
       fling the parts apart. The bottom row is bottom-ALIGNED (both parts end at
       +48.5u) rather than top-aligned, which is what keeps battery − and
       switch-input level and the black cable between them straight. */
    .circuit-section.is-immersive .circuit-area {
        --u: min(0.90vh, 0.80vw);
    }

    .circuit-section.is-immersive .battery {
        width: calc(32.8 * var(--u));
        left: calc(50% - 57.5 * var(--u));
        top: calc(50% + 16.6 * var(--u));   /* 31.9u tall, so it ends at +48.5u */
        bottom: auto;
    }

    .circuit-section.is-immersive .switch {
        width: calc(43.3 * var(--u));
        left: calc(50% + 14.2 * var(--u));  /* right edge at +57.5u, mirroring the battery */
        right: auto;
        top: calc(50% - 1.9 * var(--u));    /* 50.4u tall, so it ends at +48.5u */
        bottom: auto;
    }

    .circuit-section.is-immersive .bell-assembly {
        width: calc(60.5 * var(--u));
        left: 50%;
        top: calc(50% - 50.3 * var(--u));
    }

    /* Labels are the one thing that does not ride on the part's own width. */
    .circuit-section.is-immersive .battery-label,
    .circuit-section.is-immersive .switch-label,
    .circuit-section.is-immersive .bell-label {
        font-size: calc(1.9 * var(--u));
    }

    /* A finger-sized target, now that there is room for one. */
    .circuit-section.is-immersive .connection-point {
        width: calc(3.4 * var(--u));
        height: calc(3.4 * var(--u));
    }

    /* The exit button owns the top-left corner here, so the pair keeps the
       top-right it has everywhere else and the two never meet. */
    .circuit-section.is-immersive .workspace-tools {
        top: 12px;
        right: 12px;
    }
}

/* ==========================================================================
   Tap guide — the opening demo on touch devices.

   The dotted trace shows a drag, but a drag is not the gesture here: wires.js
   switches to tap-one-contact-then-the-other on coarse pointers. So on touch
   the trace is replaced by a hand that taps the first contact, travels to the
   second and taps again — the gesture, performed.
   ========================================================================== */
.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;     /* the fingertip, not the box, sits on the contact */
    opacity: 0;
    transform-origin: 50% 0;
    /* Positional moves are the "travel" beat; opacity and scale are the taps. */
    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; }
}
