* {
    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: battery standing on the left, switch centred and dropped so its input
   lines up with the battery's bottom (+) terminal, lamp on the right. Horizontal
   positions are percentages so the trio stays balanced whatever the workspace
   width; verticals are fixed.

   Battery - a D-cell standing vertical in its holder (parts/batteryD-V.png,
   1080x1080 square). The "+" terminal is at the BOTTOM, the negative at the top. */
.battery {
    width: 360px;              /* +60%, held to the bottom-left of the group */
    aspect-ratio: 1 / 1;
    top: calc(115px + 3%);
    left: 1%;                  /* transparent margin runs off-screen; the visible
                                  cell sits toward the left */
    background: url('parts/batteryD-V.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. */
.switch {
    width: 198px;
    aspect-ratio: 900 / 1048; /* the shared canvas: plate at the bottom, lever above */
    top: calc(242px + 3%);    /* switch-input (fy 0.777) level with battery + at 421px */
    left: calc(55% - 99px);   /* just right of centre (99 = half the width) */
    background: url('parts/switch_open.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.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;
}

/* Lamp - a bulb in a round holder (parts/lamp.png 775x1023, portrait). The bulb
   sits in the top third of the image; the two screw terminals are near the base. */
.lamp-with-bulb {
    width: 162px;
    aspect-ratio: 775 / 1023;
    top: calc(19px + 3%);      /* raised so its terminals line up with battery − */
    left: 71%;
    background: url('parts/lamp.png') no-repeat center;
    background-size: contain;
    /* Above the darkening overlay (z 30), so the lit bulb and its bloom stay
       bright while everything around them dims. */
    z-index: 40;
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5));
    transition: filter 0.25s ease;
}

/* Glow overlay for the lit bulb — a warm radial bloom parked over the bulb (top
   centre of the image), faded in by the .has-power class since the photographed
   bulb is unlit. */
.lamp-with-bulb::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 21%;
    width: 62%;
    aspect-ratio: 1;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    background: radial-gradient(circle,
        rgba(255, 250, 210, 0.95) 0%,
        rgba(255, 226, 120, 0.75) 35%,
        rgba(255, 200, 60, 0) 70%);
    opacity: 0;
    transition: opacity 0.3s ease;
    pointer-events: none;
}

.lamp-with-bulb.has-power::after {
    opacity: 1;
}

.lamp-with-bulb.has-power {
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5))
            drop-shadow(0 0 16px rgba(255, 220, 90, 0.95))
            drop-shadow(0 0 30px rgba(255, 210, 70, 0.6));
}

/* Darkening spotlight. A full-workspace veil that is transparent over the bulb
   (its centre, placed by lamp.js via --lx/--ly) and grows dark toward the edges,
   so the surrounding parts fall into shadow and the yellow glow reads far
   brighter by contrast. Sits above the parts and wires (z 30) but below the lamp
   image (z 40) and the reset button (z 50). */
.light-overlay {
    position: absolute;
    inset: 0;
    z-index: 30;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.4s ease;
    background: radial-gradient(circle at var(--lx, 50%) var(--ly, 40%),
        rgba(0, 0, 0, 0)    3%,
        rgba(0, 0, 0, 0.15) 12%,
        rgba(0, 0, 0, 0.55) 38%,
        rgba(0, 0, 0, 0.78) 72%);
}

.light-overlay.on {
    opacity: 1;
}

.lamp-label {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 2px;
    font-size: 0.8rem;
    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 sit on the positive line and their
   terminal names end in -input / -output, so they fall through to the positive
   default. 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 lamp image (z 40) and the darkening overlay (z 30) so the lamp's
       own terminals are never hidden behind its photograph. */
    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"] {
    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 (lamp is
   z 40) so a cable runs OVER the holder 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; }

/* kitsec runs two red cables and one black. The spool gives both reds the
   colour value 'red', so there is no third stroke set here — a lighter shade
   was styled once but never reachable. */
.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 (which is 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. */
.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 first cable'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 along the wires (negative to positive). */
.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;
}

@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;
}

.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;
}

/* ==========================================================================
   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
       alignment relationships (battery + ↔ switch, battery − ↔ lamp). Fractional
       anchors keep the terminals and wires on the parts automatically, and the
       verticals stay in `calc(px + 3%)` so the trio still moves together. */
    .battery {
        width: 150px;
        left: -3.9%;
        top: calc(64.8px + 3%);
    }

    .switch {
        width: 126px;            /* renders the plate at the same 88px as before */
        left: calc(55% - 63px);
        top: calc(73.8px + 3%);    /* switch-input stays level with battery + (fy 0.80
                                    here, the touch override nudges it down the plate) */
    }

    .lamp-with-bulb {
        width: 96px;
        left: 65%;
        top: calc(10px + 3%);    /* lamp terminals stay level with battery − */
    }

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

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

/* ==========================================================================
   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 106.6u across and 68.3u
       down, so the binding constraint on a landscape phone is height. The
       0.94 leaves a margin for the exit button and the reset button. */
    .circuit-section.is-immersive .circuit-area {
        --u: min(1.37vh, 0.9vw);
    }

    .circuit-section.is-immersive .battery {
        width: calc(42.21 * var(--u));
        left: calc(50% - 61.615 * var(--u));
        top: calc(50% - 21.4896 * var(--u));
    }

    .circuit-section.is-immersive .switch {
        width: calc(31.52 * var(--u));
        left: calc(50% - 21.84 * var(--u));
        top: calc(50% - 13.8996 * var(--u));
    }

    .circuit-section.is-immersive .lamp-with-bulb {
        width: calc(30 * var(--u));
        left: calc(50% + 23.3 * var(--u));
        top: calc(50% - 34.2 * 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 .lamp-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));
    }

    .circuit-section.is-immersive .reset-button {
        bottom: 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; }
}
