/* Copyright (c) 2026 Chris Ferguson. All rights reserved. */
*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

:root {
    /* Shared Shock-family palette */
    --bg: #1a1a2e;
    --surface: #16213e;
    --surface-2: #0f3460;
    --accent: #e94560;
    --accent-glow: #e9456066;
    --text: #eee;
    --text-dim: #999;

    /* Well cell size: fits 12 columns on narrow screens and 22 rows on short
       ones (the px reserve covers topbar + control pad + padding). */
    --cell: min(6.2vw, calc((100dvh - 240px) / 22), 30px);
    /* Preview mini-cell */
    --pcell: max(7px, calc(var(--cell) * 0.42));
}

html, body {
    touch-action: manipulation;
    -webkit-text-size-adjust: 100%;
}

body {
    background: #000;
    color: var(--text);
    font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
    min-height: 100vh;
    overflow-x: hidden;
    overscroll-behavior-y: contain;
    -webkit-user-select: none;
    user-select: none;
}

#app {
    position: relative;
    z-index: 1;
    max-width: 900px;
    margin: 0 auto;
    padding: clamp(8px, 2vw, 18px);
}

/* --- Scrolling starfield background (see js/starfield.js) --- */

#starfield {
    position: fixed;
    inset: 0;
    z-index: 0;
    background: #000;
    overflow: hidden;
    pointer-events: none;
}

/* One depth layer: moved only by translate3d, so drifting is compositor-only. */
.sf-layer {
    position: absolute;
    left: 0;
    width: 100%;
    will-change: transform;
}

/* Band of content, duplicated one period down to make the loop seamless. */
.sf-band {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
}

/* Far-layer photographic backdrop: a deep-field tile behind everything.
   Size/opacity are set inline (they depend on the band period). */
.sf-backdrop {
    position: absolute;
    left: 0;
    width: 100%;
    background-repeat: repeat;
    pointer-events: none;
}

.sf-star {
    position: absolute;
    border-radius: 50%;
}

.sf-twinkle {
    animation-name: sf-twinkle;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    will-change: opacity;
}
@keyframes sf-twinkle {
    from { opacity: 0.3; }
    to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .sf-twinkle,
    .sf-navlight { animation: none; }
    /* Its flash is the only thing keeping a pulsar dim most of the time. */
    .sf-pulsar { animation: none; opacity: 0.5; }
}

/* Celestial objects are centered on their (left, top) anchor by their own
   transform, which the generator writes inline along with rotation/scale. */
.sf-object {
    position: absolute;
}

.sf-nebula {
    border-radius: 50%;
}

.sf-galaxy {
    border-radius: 50%;
    background: radial-gradient(closest-side,
        rgba(255, 255, 255, 0.5),
        rgba(184, 206, 255, 0.18) 34%,
        rgba(142, 118, 220, 0.09) 60%,
        transparent 76%);
}

/* Wrapper: carries only the glow, so the ring's far arc can sit under the
   disc. Needs the radius to keep that glow circular. */
.sf-planet {
    border-radius: 50%;
}

.sf-planet-body {
    position: absolute;
    inset: 0;
    border-radius: 50%;
}

/* Saturn-style ring: an ellipse outline crossing the planet, drawn as two
   halves split along its major axis and stacked either side of the disc. */
.sf-ring {
    position: absolute;
    left: 50%;
    top: 50%;
    border-style: solid;
    border-radius: 50%;
}

/* Split an ellipse either side of a body so its far half is occluded: used by
   planet rings and accretion discs. Clips are in the element's
   own rotated frame, so they follow the tilt. The far half runs a subpixel
   past the axis — the halves only meet where the stroke crosses it (the two
   tips), and abutting antialiased edges can leave a hairline gap there. */
.sf-half-far { clip-path: inset(0 0 calc(50% - 0.5px) 0); }
.sf-half-near { clip-path: inset(50% 0 0 0); }

/* Real astronomy photographs (see block-game/img/CREDITS.md). The sources are
   rectangles of sky on black, so the frame edge is feathered away rather than
   butted against the background — a hard edge reads as a pasted-in photo.
   Note the mask also clips box-shadow, so these can't carry an outer glow. */
.sf-photo {
    background-position: center;
    background-repeat: no-repeat;
    background-size: cover;
    -webkit-mask-image: radial-gradient(closest-side, #000 50%, transparent 88%);
    mask-image: radial-gradient(closest-side, #000 50%, transparent 88%);
}

/* A planet's limb is a real edge: crop to the disc and let the glow through. */
.sf-photo-disc {
    border-radius: 50%;
    -webkit-mask-image: none;
    mask-image: none;
}

/* Cutout photographs (Saturn's rings, Io's plume, the Sun's corona…) carry
   their own alpha channel, so like the home rock they need neither a feather
   nor a crop. The element box matches the image aspect, so stretch to fill. */
.sf-photo-cut {
    -webkit-mask-image: none;
    mask-image: none;
    background-size: 100% 100%;
}

/* The home asteroid: a photographed rock carrying its own alpha channel, so it
   needs neither a feather nor a crop — the sky shows through its real
   silhouette. It never rotates, because the colony built on it has to stay put. */
.sf-home {
    background-position: center;
    background-repeat: no-repeat;
    background-size: contain;
}

/* A colony structure, anchored by its FOOTPRINT rather than its centre: the
   origin is the bottom edge, so the generator's (left, top) is the spot on the
   rock the piece stands on, and any tilt pivots about that contact point. */
.sf-struct {
    position: absolute;
    transform-origin: 50% 100%;
    background-position: center bottom;
    background-repeat: no-repeat;
    background-size: contain;
}

/* Contact shadow, so a piece sits on the surface instead of hovering over it.
   Sprites deliberately ship without a baked-in shadow, since the same piece can
   land anywhere on the rock. */
.sf-struct::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: 0;
    width: 86%;
    height: 16%;
    transform: translate(-50%, 38%);
    background: radial-gradient(closest-side, rgba(0, 0, 0, 0.55), transparent 72%);
    pointer-events: none;
}

/* Supernova remnant: hollow shell, so it reads apart from nebula and galaxy. */
.sf-remnant { border-radius: 50%; }

/* Dust cloud: subtracts light instead of adding it, hiding stars behind it. */
.sf-dark-nebula {
    border-radius: 50%;
    background: radial-gradient(closest-side,
        rgba(3, 5, 11, 0.94),
        rgba(3, 5, 11, 0.66) 50%,
        transparent 78%);
}

/* Asteroid belt: rubble along a tilted line, ends dissolving into the dark. */
.sf-belt {
    -webkit-mask-image: linear-gradient(to right, transparent, #000 20%, #000 80%, transparent);
    mask-image: linear-gradient(to right, transparent, #000 20%, #000 80%, transparent);
}

.sf-belt-rock {
    position: absolute;
    transform: translate(-50%, -50%);
}

/* Pulsar: opposed beams that flare together on a hard, quick beat. */
.sf-pulsar {
    animation-name: sf-pulse;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
    /* Hold the dim keyframe through the random delay, so a pulsar doesn't sit
       at full brightness until its first flash. */
    animation-fill-mode: both;
    will-change: opacity;
}
@keyframes sf-pulse {
    0%, 100% { opacity: 0.22; }
    44%      { opacity: 0.30; }
    50%      { opacity: 1; }
    58%      { opacity: 0.34; }
}

.sf-pulsar-beam {
    position: absolute;
    left: 0;
    width: 100%;
    height: 50%;
}

/* Cones taper to a point at the star and open outward. */
.sf-beam-up {
    top: 0;
    clip-path: polygon(0 0, 100% 0, 50% 100%);
}

.sf-beam-down {
    bottom: 0;
    clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

.sf-pulsar-core {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    background: #fff;
}

/* Black hole: shadow with a photon ring, wrapped by its accretion disc. */
.sf-hole-core {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: #000;
}

.sf-disc {
    position: absolute;
    left: 50%;
    top: 50%;
    border-radius: 50%;
    background: radial-gradient(closest-side,
        transparent 38%,
        rgba(255, 206, 138, 0.85) 52%,
        rgba(255, 128, 62, 0.45) 68%,
        transparent 84%);
}

/* Beacon: reads as "built, not natural" for the price of one node. */
.sf-navlight {
    position: absolute;
    border-radius: 50%;
    background: #ffe9b8;
    box-shadow: 0 0 6px rgba(255, 208, 130, 0.95);
    animation-name: sf-blink;
    animation-duration: 3.4s;
    animation-iteration-count: infinite;
    animation-fill-mode: both;
    will-change: opacity;
}
@keyframes sf-blink {
    0%, 42%, 100% { opacity: 0.12; }
    48%, 56%      { opacity: 1; }
}

/* Pressure dome on a planet's limb: a half-circle standing on its flat base,
   which is also the point the placement code rotates it about. */
.sf-dome {
    position: absolute;
    transform-origin: 50% 100%;
    border-radius: 50% 50% 0 0;
    background: linear-gradient(to top, rgba(120, 138, 172, 0.95), rgba(214, 228, 255, 0.95));
    box-shadow: 0 0 3px rgba(190, 214, 255, 0.5);
}

/* Landmarks arriving or retiring mid-game never pop in or out. */
.sf-enter { animation: sf-fade-in 1.6s ease-out both; }
.sf-leave { animation: sf-fade-out 1.8s ease-in both; }
@keyframes sf-fade-in  { from { opacity: 0; } to { opacity: 1; } }
@keyframes sf-fade-out { from { opacity: 1; } to { opacity: 0; } }

/* Crescent: a lit sphere with an offset circle masked away. */
.sf-moon {
    border-radius: 50%;
    background: radial-gradient(circle at 34% 30%, #fff, #d3dae9 58%, #8f98b0 100%);
    -webkit-mask-image: radial-gradient(circle at 118% 44%, transparent 62%, #000 63%);
    mask-image: radial-gradient(circle at 118% 44%, transparent 62%, #000 63%);
}

.sf-comet-tail {
    position: absolute;
    inset: 0;
    /* Wedge: a point at the far end, full thickness at the head. */
    clip-path: polygon(0 50%, 100% 0, 100% 100%);
}

.sf-comet-head {
    position: absolute;
    right: 0;
    top: 50%;
    transform: translate(35%, -50%);
    border-radius: 50%;
    background: #fff;
}

/* --- Top bar (matches Card Shock) --- */

#topbar {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    padding: 8px 14px;
    background: rgba(22, 33, 62, 0.7);
    border: 1px solid var(--surface-2);
    border-radius: 12px;
    margin-bottom: clamp(8px, 2vw, 16px);
}

.brand {
    display: flex;
    flex-direction: column;
    line-height: 1.1;
}

.logo {
    font-size: clamp(18px, 4vw, 26px);
    font-weight: 800;
    letter-spacing: 0.5px;
    color: var(--accent);
    text-shadow: 0 2px 10px var(--accent-glow);
}

.mode-badge {
    font-size: 12px;
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 1px;
}

.stats {
    display: flex;
    gap: 14px;
    margin-left: auto;
}

.stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    font-size: 12px;
    color: var(--text-dim);
}

.stat b {
    font-size: 18px;
    color: var(--text);
}

/* --- Buttons --- */

.btn {
    border: none;
    border-radius: 8px;
    padding: 9px 16px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    color: var(--text);
    background: var(--surface-2);
    transition: filter 0.15s, transform 0.05s;
}
.btn:active { transform: scale(0.96); }
.btn-primary { background: var(--accent); }
.btn-primary:hover { filter: brightness(1.15); }
.btn-secondary:hover { filter: brightness(1.3); }

/* --- In-game volume popover (js/volume-popover.js) --- */

#btn-volume { padding: 9px 12px; }

#vol-pop {
    display: none;
    position: fixed;
    z-index: 2100; /* above the pause/over overlays (2000) */
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-radius: 10px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
}
#vol-pop.show { display: flex; }

.vol-row {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
}
.vol-row label { width: 44px; color: var(--text-dim); }
.vol-row input[type="range"] { width: 140px; accent-color: var(--accent); }
.vol-row .vol-val { width: 40px; text-align: right; font-size: 13px; }

/* --- Play area: well + preview side panel --- */

#play-area {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    gap: clamp(8px, 2.5vw, 20px);
}

#well {
    position: relative; /* anchor for .boom-fly overlay clones */
    display: grid;
    grid-template-columns: repeat(12, var(--cell));
    grid-auto-rows: var(--cell);
    gap: 1px;
    padding: 3px;
    /* Translucent so the starfield drifts visibly behind the play area;
       filled cells get an opaque color inline from the renderer. */
    background: rgba(9, 13, 24, 0.35);
    border: 1px solid var(--surface-2);
    border-radius: 8px;
}

.wcell {
    width: var(--cell);
    height: var(--cell);
    background: rgba(22, 31, 54, 0.38);
    border-radius: 2px;
}

/* Locked + active cells get their color inline from the renderer. */
.wcell.filled {
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25),
                inset 0 calc(var(--cell) * -0.18) 0 rgba(0, 0, 0, 0.28);
}

/* Ghost landing outline: dashed border in the piece color (inline). */
.wcell.ghost {
    box-shadow: none;
    border: 2px dashed rgba(255, 255, 255, 0.45);
    background: rgba(22, 31, 54, 0.38);
}

/* Wild (rainbow) cells — match any color in boom groups. */
.wcell.wild,
.pcell.wild {
    background: linear-gradient(135deg,
        #ff3b3b 0%, #ffb13b 22%, #ffee3b 42%,
        #3bff6e 60%, #3bb8ff 80%, #b03bff 100%) !important;
}

/* Wrapper so the attack callout can overlay the well (Suicide/Duel). */
#well-wrap {
    position: relative;
}

/* Attack announcement: centered over the well for 0.5 s before it applies. */
#attack-callout {
    position: absolute;
    inset: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0 10px;
    font-weight: 900;
    font-size: clamp(16px, calc(var(--cell) * 1.0), 26px);
    letter-spacing: 1.5px;
    line-height: 1.25;
    color: #fff;
    text-shadow: 0 0 12px rgba(233, 69, 96, 0.95), 0 2px 6px rgba(0, 0, 0, 0.95);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.08s;
}
#attack-callout.show { opacity: 1; }

/* Shield pips (max 3). */
#shield-pips {
    display: flex;
    gap: 6px;
}
.pip {
    width: 13px;
    height: 13px;
    border-radius: 50%;
    background: #1b2440;
    border: 1px solid var(--surface-2);
}
.pip.on {
    background: #00e5ff;
    border-color: #00e5ff;
    box-shadow: 0 0 8px #00e5ff88;
}

/* Per-color effect meters: swatch + fill bar + banked/threshold. Each row's
   tooltip names the effect, so the panel stays compact. */
#fx-meters {
    display: flex;
    flex-direction: column;
    gap: 3px;
    width: calc(var(--pcell) * 5 + 20px);
}
.fx-meter {
    display: flex;
    align-items: center;
    gap: 5px;
}
.fx-meter-swatch {
    width: 9px;
    height: 9px;
    flex: 0 0 auto;
    border-radius: 2px;
    border: 1px solid #00000066;
}
/* The line-clear meter has no color of its own; mark it with a bar glyph. */
.fx-meter-swatch.fx-meter-lines {
    background: linear-gradient(#e8ecf7 0 40%, transparent 40% 60%, #e8ecf7 60%);
    border-color: var(--surface-2);
}
.fx-meter-bar {
    position: relative;
    flex: 1 1 auto;
    height: 5px;
    border-radius: 3px;
    background: #131c33;
    overflow: hidden;
}
.fx-meter-bar > i {
    position: absolute;
    inset: 0 auto 0 0;
    width: 0;
    background: #8fa3c8;
    transition: width 0.12s;
}
.fx-meter > b {
    flex: 0 0 auto;
    min-width: 26px;
    font-size: 9px;
    font-weight: 700;
    text-align: right;
    color: #8fa3c8;
}

/* Active-attack status lines under the shields. */
#fx-banner {
    display: flex;
    flex-direction: column;
    gap: 3px;
    /* Same fixed width as #fx-meters: a long banner line must wrap, not widen
       the side column — #play-area is centered, so a width change there
       visibly nudges the whole well sideways and back. */
    width: calc(var(--pcell) * 5 + 20px);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-align: center;
    color: #ffb13b;
}

.side-label-gap {
    margin-top: 6px;
}

/* Radar-jammed preview slots render as "?" boxes. */
.preview-box.hidden-piece {
    background: #10182c;
    border: 1px dashed var(--surface-2);
    border-radius: 6px;
}
.preview-q {
    font-size: calc(var(--pcell) * 2.2);
    font-weight: 800;
    color: var(--text-dim);
}

/* --- Boom animation --- */

/* White-fill flash (inset spread covers any block color, black included). */
.wcell.boom-flash {
    animation: boom-flash 130ms linear 2;
}
@keyframes boom-flash {
    0%, 100% {
        box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25),
                    inset 0 calc(var(--cell) * -0.18) 0 rgba(0, 0, 0, 0.28);
    }
    50% {
        box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.92);
    }
}

/* --- Line-clear electric shock (Sep 3, 2026) --- */

/* Cleared-row cells flicker blue-white and jitter while the bolt arcs.
   87 ms x 3 ~= FLASH_MS (260 ms in renderer.js). */
.wcell.shock-cell {
    animation: shock-cell 87ms linear 3;
}
@keyframes shock-cell {
    0%, 100% {
        box-shadow: inset 0 0 0 999px rgba(190, 240, 255, 0.95);
        transform: translate(0, 0);
    }
    50% {
        box-shadow: inset 0 0 0 999px rgba(80, 190, 255, 0.45);
        transform: translate(1px, -1px);
    }
}

/* Lightning bolt SVG overlaid on a cleared row (built by _showShockBolt). */
.shock-bolt {
    position: absolute;
    left: 0;
    z-index: 4;
    overflow: visible; /* branch forks poke past the row */
    pointer-events: none;
    opacity: 0;
    /* `both` holds opacity 0 through any stagger delay set inline. */
    animation: shock-flicker 450ms linear both;
}
.shock-bolt polyline {
    fill: none;
    stroke: #cdf3ff;
    stroke-width: 2.2;
    stroke-linejoin: miter;
    filter: drop-shadow(0 0 3px #4fd8ff) drop-shadow(0 0 9px rgba(79, 216, 255, 0.55));
}
.shock-bolt .shock-echo {
    stroke: rgba(255, 255, 255, 0.65);
    stroke-width: 1;
    filter: none;
}
@keyframes shock-flicker {
    0%   { opacity: 0; }
    6%   { opacity: 1; }
    18%  { opacity: 0.3; }
    30%  { opacity: 1; }
    48%  { opacity: 0.45; }
    62%  { opacity: 0.9; }
    100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .shock-bolt { animation: shock-fade 450ms linear both; }
    .wcell.shock-cell {
        animation: none;
        box-shadow: inset 0 0 0 999px rgba(190, 240, 255, 0.8);
    }
}
@keyframes shock-fade {
    0%   { opacity: 0.55; }
    100% { opacity: 0; }
}

/* Death animation: after the third flash, the cells that actually killed
   you (piece cells overlapping the stack, or every block of the garbage row
   that couldn't be inserted) are darkened. */
.wcell.death-dark {
    filter: brightness(0.35);
}

/* Scale-up + fade-out pop. */
.wcell.boom-pop {
    animation: boom-pop 170ms ease-out forwards;
    position: relative;
    z-index: 2;
}
@keyframes boom-pop {
    from {
        transform: scale(1);
        opacity: 1;
        box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.85);
    }
    to {
        transform: scale(1.5);
        opacity: 0;
        box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.4);
    }
}

/* Overlay clone that slides down during the post-boom fall. */
.boom-fly {
    position: absolute;
    z-index: 3;
    border-radius: 2px;
    pointer-events: none;
    will-change: transform;
}

/* Floating score callout for chain booms (boom #2 and later). */
.boom-score {
    position: absolute;
    z-index: 5;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    font-weight: 800;
    font-size: clamp(18px, calc(var(--cell) * 1.1), 30px);
    line-height: 1;
    color: #ffd54a;
    text-shadow: 0 0 6px rgba(0, 0, 0, 0.9), 0 2px 4px rgba(0, 0, 0, 0.8);
    pointer-events: none;
    white-space: nowrap;
    animation: boom-score-rise 1.25s ease-out forwards;
}
.boom-score .boom-score-chain {
    font-size: 0.45em;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: #ffffff;
    opacity: 0.85;
}
@keyframes boom-score-rise {
    0% {
        opacity: 0;
        transform: translate(-50%, -50%) scale(0.6);
    }
    12% {
        opacity: 1;
        transform: translate(-50%, -50%) scale(1.25);
    }
    24% {
        transform: translate(-50%, -50%) scale(1);
    }
    72% {
        opacity: 1;
    }
    100% {
        opacity: 0;
        transform: translate(-50%, calc(-50% - var(--cell) * 1.6)) scale(1);
    }
}

/* --- Next-5 preview panel --- */

#side {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
}

#side .side-label {
    font-size: 11px;
    letter-spacing: 2px;
    color: var(--text-dim);
    font-weight: 700;
}

#preview {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 8px;
    background: rgba(22, 33, 62, 0.7);
    border: 1px solid var(--surface-2);
    border-radius: 8px;
    min-width: calc(var(--pcell) * 5 + 16px);
}

.preview-box {
    display: flex;
    justify-content: center;
    align-items: center;
    /* Tallest base shape is 4 rows; reserve 4 so boxes don't jiggle. */
    min-height: calc(var(--pcell) * 4);
}

.preview-grid {
    display: grid;
    gap: 1px;
}

.pcell {
    width: var(--pcell);
    height: var(--pcell);
    border-radius: 1px;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
}

/* --- Control pad --- */

#pad {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    margin-top: clamp(8px, 2vw, 14px);
}

.pad-row {
    display: flex;
    gap: clamp(6px, 2vw, 12px);
}

.pad-btn {
    width: clamp(56px, 14vw, 76px);
    height: clamp(42px, 10vw, 52px);
    border: 1px solid var(--surface-2);
    border-radius: 10px;
    background: var(--surface);
    color: var(--text);
    font-size: clamp(16px, 4vw, 20px);
    font-weight: 700;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    line-height: 1;
    gap: 2px;
    touch-action: none;
}
.pad-btn small {
    font-size: 9px;
    font-weight: 600;
    color: var(--text-dim);
    letter-spacing: 0.5px;
}
.pad-btn:active { background: var(--surface-2); }

/* Hard-drop button dimmed during the 250 ms spawn grace (drop is ignored). */
.pad-btn.pad-grace {
    opacity: 0.35;
    cursor: default;
}

/* --- Game over overlay --- */

#over-overlay,
#pause-overlay {
    position: fixed;
    inset: 0;
    display: none;
    align-items: center;
    justify-content: center;
    background: rgba(10, 10, 20, 0.75);
    z-index: 2000;
}
#over-overlay.show,
#pause-overlay.show { display: flex; }

.over-card {
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-radius: 14px;
    padding: 26px 34px;
    text-align: center;
    box-shadow: 0 18px 60px rgba(0, 0, 0, 0.55);
}
.over-card h2 {
    color: var(--accent);
    margin-bottom: 10px;
}
.over-card p {
    color: var(--text-dim);
    margin-bottom: 16px;
    line-height: 1.6;
}
.over-card p b { color: var(--text); }
.over-card .btn { margin: 0 4px; }

/* ============================== Skills Test =============================
   Two 5x5 grids — your piece on top, the target below. Cells reuse the
   .wcell styling at a larger size via a page-scoped --cell override
   (budget: 10 cell rows + divider + padding + chrome). */

body.skills-page #skills-area {
    --cell: min(9vw, calc((100dvh - 340px) / 11.5), 44px);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    margin: 0 auto;
}

body.skills-page .skills-grid {
    display: grid;
    grid-template-columns: repeat(5, var(--cell));
    grid-auto-rows: var(--cell);
    gap: 2px;
    padding: 6px;
    background: rgba(9, 13, 24, 0.35);
    border: 1px solid rgba(233, 69, 96, 0.18);
    border-radius: 10px;
}

/* The target frame reads slightly brighter so "match THAT one" is obvious. */
body.skills-page #target-grid {
    border-color: rgba(233, 69, 96, 0.55);
}

body.skills-page .skills-divider {
    font-size: 0.72rem;
    letter-spacing: 0.16em;
    color: var(--text-dim);
    user-select: none;
}

/* 250 ms blank between rounds: cells vanish, the frames stay put. */
body.skills-page #skills-area.blank .wcell {
    visibility: hidden;
}

/* ============================== Puzzles mode ============================== */

/* Target cells: the exact 5 cells the piece must lock onto. Dashed gold
   outline + faint fill so they read as a socket, not terrain. */
.wcell.target {
    border: 2px dashed rgba(255, 208, 74, 0.75);
    background: rgba(255, 208, 74, 0.10);
}

/* Solved: the landed piece pulses green before the next puzzle loads. */
.wcell.target-solved {
    animation: target-solved 0.45s ease-in-out 2;
}
@keyframes target-solved {
    50% { filter: brightness(1.8) saturate(0.4); box-shadow: inset 0 0 0 3px #22c55e; }
}

/* Wrong lock: the landed piece pulses red before the puzzle resets. */
.wcell.wrong-lock {
    animation: wrong-lock 0.4s ease-in-out 2;
}
@keyframes wrong-lock {
    50% { filter: brightness(0.45); box-shadow: inset 0 0 0 3px #dc2626; }
}

/* Puzzle name + hint banner above the well. */
#puzzle-banner {
    text-align: center;
    margin: 2px 0 8px;
}
#puzzle-banner b {
    display: block;
    font-size: 1.02rem;
    letter-spacing: 0.04em;
}
#puzzle-banner span {
    display: block;
    margin-top: 2px;
    font-size: 0.78rem;
    color: var(--text-dim);
}

/* ============================= Puzzle Builder ============================= */

body.builder-page {
    background: var(--bg);
}

#builder-main {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: flex-start;
    gap: 18px;
    padding: 10px;
}

body.builder-page #topbar .controls {
    display: flex;
    gap: 8px;
}
body.builder-page #topbar a.btn {
    text-decoration: none;
}

#builder-grid {
    --bcell: min(4.4vw, calc((100dvh - 170px) / 22), 26px);
    display: grid;
    grid-template-columns: repeat(12, var(--bcell));
    grid-auto-rows: var(--bcell);
    gap: 2px;
    padding: 6px;
    background: rgba(9, 13, 24, 0.6);
    border: 1px solid rgba(233, 69, 96, 0.25);
    border-radius: 10px;
    touch-action: none;
    user-select: none;
}
.bcell {
    background: rgba(22, 31, 54, 0.6);
    border-radius: 2px;
}
.bcell.btarget {
    border: 2px dashed rgba(255, 208, 74, 0.75);
    background: rgba(255, 208, 74, 0.10);
}

#builder-panel {
    width: min(340px, 94vw);
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.bsec {
    background: var(--surface);
    border: 1px solid var(--surface-2);
    border-radius: 10px;
    padding: 10px 12px;
}
.bsec h3 {
    margin: 0 0 8px;
    font-size: 0.8rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-dim);
}
.brow {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin: 6px 0;
    font-size: 0.88rem;
}
.brow label {
    color: var(--text-dim);
    font-size: 0.82rem;
}
.brow input[type="text"] {
    flex: 1;
    min-width: 0;
    padding: 6px 8px;
    background: rgba(9, 13, 24, 0.6);
    border: 1px solid var(--surface-2);
    border-radius: 6px;
    color: var(--text);
    font: inherit;
}
.brow select {
    padding: 6px 8px;
    background: rgba(9, 13, 24, 0.6);
    border: 1px solid var(--surface-2);
    border-radius: 6px;
    color: var(--text);
    font: inherit;
}
.bspacer { flex: 1; }
.bnote {
    margin: 6px 0 0;
    font-size: 0.75rem;
    color: var(--text-dim);
}
.swatch-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 6px;
}
.swatch {
    width: 26px;
    height: 26px;
    border: 2px solid transparent;
    border-radius: 6px;
    cursor: pointer;
    padding: 0;
}
.swatch.sel {
    border-color: #fff;
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.35);
}
body.builder-page .btn.sel {
    background: var(--accent);
    color: #fff;
}
#builder-panel .ok { color: #22c55e; }
#builder-panel .bad { color: #ef4444; }
#builder-msg { min-height: 1.2em; }
