/* ============================ WHAT IS DRAWN ON THE MAP ============================
   The ground, the rock, the line, the buildings and BOLTZ on the board — the
   art that `render.js` emits, and the one place --pad-r is spent.
   Every square on the board wears the same footprint; see lab/README.md.

   ORDER MATTERS. This is file 3 of 10 and the cascade is the order
   pages/app.html links them in:
     base.css · layout.css · board.css · screens.css · home.css · editor.css · helper.css · panels.css · drill.css · class.css
   Later files override earlier ones on purpose. Moving a rule between files
   without keeping that order is how a Chinese override or a `won` state
   quietly stops applying. */

/* ---------------- THE GROUND ----------------
   Regolith, not graph paper. Every tile is the same moulded block -- a bright
   top-left, a warm shadow bottom-right, a lip of light along the top edge and
   a bevel underneath -- but its lightness and its two grit specks come from a
   per-tile hash (`--j`, `--sx`, `--sy`, set in render.js). Same tile, same
   grain, every repaint; no two neighbours quite alike.
   All of this is BASE styling. Nothing here depends on an animation running. */
.tile{width:var(--tile);height:var(--tile);position:relative;
  /* PERCENT, not px. It was a fixed 14px: a fair block at 76px (18%), but the
     board shrinks tiles to 40px and there the same 14px is 35% -- 41% at 34px
     -- and a board of rounded-off squares reads as a keypad of soft buttons,
     not as ground. 18.5% is the same 14px at 76 and keeps the shape all the
     way down. The rock went to percentages for the same reason. */
  border-radius:calc(var(--pad-r) + var(--j,0) * 1.8%);
  background-color:hsl(
    calc(26deg + var(--j,0) * 2deg)
    calc(54% + var(--j,0) * 4%)
    calc(87% + var(--j,0) * 1.9%));
  background-image:
    linear-gradient(72deg,
      rgba(150,96,60,.055) 0 9%,transparent 44%,
      rgba(255,255,255,.17) 74%,transparent 100%),
    linear-gradient(135deg,rgba(255,255,255,.30),rgba(255,255,255,.08) 46%,
      rgba(158,98,58,.05) 70%,rgba(158,98,58,.11));
  /* A FLAT TOP. There used to be a big soft white bloom on the face as well as
     the chamfer below, so the tile was a pillow with a bevel -- that is what
     made 76px look puffy. A building block's light changes at the EDGE and
     hardly at all across the face, so the face is now a gentle linear fall-off
     and the chamfer carries the shape alone, one step crisper.
     A CHAMFER, not a blob. The old tile was lit by one soft radial at 26% 16%
     and nothing else, so it had no EDGE and read as a pillow. These insets are
     offset DIAGONALLY -- light on the top-left rim, shade on the bottom-right
     -- so the block has a moulded edge the whole way round. It is the only
     part of this that survives at 40px, where every soft interior detail is
     gone. Two steps, not one: a 1px hairline hard on the corner, then the soft
     2.5px falloff inside it, because moulded plastic catches a crisp specular
     line before the surface turns away.
     The 72deg sweep is the same angle on EVERY tile -- a prevailing wind is
     what makes 54 squares a place rather than a grid. Hue and saturation
     jitter with the hash as well as lightness, but narrowly: at 5deg the tiles
     went mauve and the basin stopped having one colour. */
  box-shadow:
    inset 1px 1px 0 rgba(255,255,255,.75),
    inset 2px 2px 2px -1px rgba(255,255,255,.9),
    inset -1px -1px 0 rgba(152,98,62,.2),
    inset -2px -2px 3px -1px rgba(152,98,62,.2),
    0 2px 0 rgba(150,96,60,.12),
    1px 4px 7px -3px rgba(120,74,44,.28);
  transition:background-color .5s}
/* NINE pieces of grit, not three, at roughly half the old contrast and in four
   sizes, warm and cool mixed. Count up, contrast down, is the difference
   between grain and dirt: the old three were pin-sharp dots that read as dust
   on the SCREEN. All nine are hashed off the same two numbers, and the
   multipliers are prime-ish fractions so the derived positions never stack into
   a line or a clump -- a clump reads as a stain and undoes the whole thing.
   Anything PERIODIC loses here: a repeating-gradient streak is corduroy, and
   two tiled dot grids at 3.7px and 6.1px still line up into graph paper, which
   is the exact thing the ground exists not to be. */
.tile::before{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background-image:
    radial-gradient(circle 2.2px at var(--sx,30%) var(--sy,40%),rgba(126,78,48,.15),transparent 100%),
    radial-gradient(circle 1.5px at calc(100% - var(--sx,30%)) calc(100% - var(--sy,40%)),rgba(126,78,48,.12),transparent 100%),
    radial-gradient(circle 1.4px at var(--sy,40%) calc(100% - var(--sx,30%)),rgba(255,255,255,.6),transparent 100%),
    radial-gradient(circle 1.8px at calc(var(--sx,30%) * .37 + 55%) calc(var(--sy,40%) * .61 + 11%),rgba(126,78,48,.11),transparent 100%),
    radial-gradient(circle 1.1px at calc(var(--sy,40%) * .43 + 9%) calc(var(--sx,30%) * .29 + 61%),rgba(126,78,48,.13),transparent 100%),
    radial-gradient(circle 1.2px at calc(var(--sx,30%) * .71 + 17%) calc(var(--sy,40%) * .23 + 71%),rgba(255,255,255,.5),transparent 100%),
    radial-gradient(circle .9px at calc(100% - var(--sy,40%) * .83) calc(var(--sx,30%) * .53 + 27%),rgba(126,78,48,.13),transparent 100%),
    radial-gradient(circle 1.6px at calc(var(--sy,40%) * .19 + 68%) calc(100% - var(--sx,30%) * .47),rgba(126,78,48,.09),transparent 100%),
    radial-gradient(circle .9px at calc(var(--sx,30%) * .13 + 31%) calc(var(--sy,40%) * .89 + 5%),rgba(255,255,255,.45),transparent 100%)}
/* ONE ROCK, and the intro card wears the same one -- both selectors on the
   same rule, because a copy under its own selector is how the two drift apart.

   A boulder seen from above is not a shaded square. Soft radial shading on an
   evenly rounded square reads as a BUTTON, whatever colour it is. It is a flat
   TOP FACE with SIDES falling away, and the hard break between them is the
   whole signal -- so it is built as three layers:

     the tile   the sides: basalt in shadow, darkest where it turns away at
                the bottom-right;
     ::before   the top face: a clip-path polygon running off the top and left
                edges and cutting back across the bottom-right, so the sides
                show exactly where the light says they should. All six vertices
                move with --j, which is what makes a ridge of five read as five
                rocks instead of one rock five times;
     ::after    the speckle, over the face.

   Then the details that finish it: a bright rim the clip lets through only
   where the face reaches the top-left edge, so it lands on the brow; and four
   different hashed corner radii, because a moulded block has one radius and a
   broken lump has none. (The face also carries a 1px drop-shadow that was
   meant to step it DOWN onto the side. It draws nothing: CSS applies `filter`
   BEFORE `clip-path`, so the clip removes it. Left in place only because the
   rock was kept exactly as it is.)
   That last one is what finally stops it being a UI panel.

   The rock also used to inherit the SAND's warm brown specks from
   `.tile::before` -- nothing ever overrode them -- so every boulder was
   wearing grit that belongs to the dust. The speckle is its own mineral now:
   hard-edged bright chips, because stone catches light in points.

   The card's rock carries no --j/--sx/--sy, so every fallback is 0 and it
   draws one fixed, near-symmetric lump. That is correct: the card is a
   rehearsal, not a second board. */
.tile.rock,.drock .dtrack i.rk{
  /* PERCENTAGES, not px. These were 10/12/9/11px, which is 13-16% of a 76px
     tile and a fair boulder -- but the board shrinks tiles to 40px on a big
     board, where the same 15px is 39% of the square and the rock rounds off
     into a blob. A percentage radius is a fraction of the element, so the lump
     keeps its shape all the way down. */
  /* NO border-radius of its own. A rock is a ground square that happens to be
     stone, so it takes the pad from `.tile` above and its outline is exactly
     the outline of every sand square around it. Its lumpiness comes from the
     FACE, never from the silhouette -- corners that differed from the ground's
     were the thing that made a ridge look like a different set of blocks. */
  background-color:#5b616d;
  background-image:
    radial-gradient(130% 120% at 20% 10%,rgba(255,255,255,.1),transparent 58%),
    radial-gradient(110% 100% at 88% 96%,rgba(10,12,18,.34),transparent 62%);
  /* THE SAME EDGE AS THE GROUND, only darker. Every offset, blur and spread
     below matches `.tile` exactly, so a rock and a sand square catch the light
     and sit on the board identically.
     What was here before was a different edge in FIXED pixels: a hard 4px band
     across the bottom, a black 2px lip and a cast shadow pushed 2px right. At
     76px that reads as weight; at 61px the 4px band is 6.5% of the square, and
     where the rounded bottom-left corner cuts through it, it breaks into a
     small step -- a notch on the corner of every rock, at play size only. */
  box-shadow:
    inset 1px 1px 0 rgba(255,255,255,.2),
    inset 2px 2px 2px -1px rgba(255,255,255,.26),
    inset -1px -1px 0 rgba(0,0,0,.28),
    inset -2px -2px 3px -1px rgba(0,0,0,.3),
    0 2px 0 rgba(38,32,30,.22),
    1px 4px 7px -3px rgba(24,22,28,.45)}
/* The face goes on ::before and the speckle on ::after, NOT the other way
   round. Two positioned pseudos with z-index:auto paint in DOM order, so a
   face on ::after would cover the specks on ::before -- and reaching for
   z-index to fix that drops the rock into the same stack as .cable (2) and
   .glow (1), which is a quiet way to get a cable painted under a boulder.
   Order, not z-index. */
.tile.rock::before,.drock .dtrack i.rk::before{
  /* border-radius:inherit is what keeps the face inside the rock. The polygon
     deliberately runs off the top and left edges so the face reaches the brow;
     with a square box under it, it then painted the box's SQUARE corner right
     past the rock's rounded one. */
  content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
  clip-path:polygon(
    -12% -12%,
    calc(74% + var(--j,0) * 10%) -12%,
    calc(96% - var(--j,0) * 8%)  calc(34% + var(--j,0) * 10%),
    calc(70% + var(--j,0) * 9%)  calc(90% - var(--j,0) * 7%),
    calc(24% - var(--j,0) * 9%)  calc(97% + var(--j,0) * 6%),
    -12% calc(62% - var(--j,0) * 10%));
  /* exactly ONE break across the face. Three bands were tried and the face
     went back to reading as ribbons -- the fault the whole approach exists to
     leave behind. Two planes on the face plus the sides makes three, which is
     as many as a lump this size can carry. */
  background:
    linear-gradient(calc(132deg + var(--j,0) * 16deg),
      #858c9b 0 calc(40% + var(--j,0) * 13%),
      #717886 calc(40.7% + var(--j,0) * 13%) 100%),
    #787f8d;
  /* The `filter:drop-shadow` that used to be here is gone. It was meant to step
     the face down onto the sides, but a filter is applied BEFORE `clip-path`
     and is not clipped by `border-radius`, so it drew along the polygon's
     diagonals and spilled outside the tile at the corners instead. The break
     between face and side is carried by their colours, which is enough. */
  box-shadow:inset 2px 2px 0 -1px rgba(255,255,255,.18)}
.tile.rock::after,.drock .dtrack i.rk::after{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:
    radial-gradient(circle 1.6px at var(--sx,30%) var(--sy,40%),
      rgba(255,255,255,.3) 0 55%,transparent 58%),
    radial-gradient(circle 1.1px at calc(100% - var(--sx,30%)) calc(100% - var(--sy,40%)),
      rgba(255,255,255,.22) 0 55%,transparent 58%),
    radial-gradient(circle 1.3px at calc(100% - var(--sy,40%) * .5) calc(var(--sx,30%) * .7 + 12%),
      rgba(255,255,255,.2) 0 55%,transparent 58%),
    radial-gradient(circle 2px at calc(var(--sy,40%) * .7 + 20%) calc(var(--sx,30%) * .6 + 26%),
      rgba(0,0,0,.16),transparent 100%)}
/* BEDROCK (mission 24 on): the drill bounces off it. Same shape, edge offsets
   and speckle as ordinary rock, because it IS rock; what changes is the stone:
   near-black cold basalt, a heavier edge (same geometry, deeper tones), and a
   face of big angular dark and mid-grey patches, the Minecraft bedrock cue.
   Options: lab #bedrock. */
.tile.rock.hard,.drock .dtrack i.rk.hard{background-color:#252933;
  box-shadow:
    inset 1px 1px 0 rgba(200,220,255,.16),
    inset 2px 2px 2px -1px rgba(200,220,255,.2),
    inset -1px -1px 0 rgba(0,0,0,.5),
    inset -2px -2px 3px -1px rgba(0,0,0,.5),
    0 2px 0 rgba(20,18,24,.4),
    1px 4px 7px -3px rgba(10,10,16,.6)}
/* hard-edged patches, sized in % so they stay blocks at 40px; --j shuffles them */
.tile.rock.hard::before,.drock .dtrack i.rk.hard::before{
  background:
    linear-gradient(#626979 0 0) calc(12% + var(--j,0) * 8%) 14% / 34% 24% no-repeat,
    linear-gradient(#1f232b 0 0) calc(88% - var(--j,0) * 8%) 18% / 26% 22% no-repeat,
    linear-gradient(#565d6d 0 0) 70% calc(62% + var(--j,0) * 8%) / 28% 26% no-repeat,
    linear-gradient(#1f232b 0 0) 16% calc(70% - var(--j,0) * 6%) / 30% 22% no-repeat,
    linear-gradient(#4a5160 0 0) 42% 44% / 20% 18% no-repeat,
    #383e4b}

/* ---------------- THE LINE BOLTZ LAYS ----------------
   A laid cable, not a rule: a tube shaded across its width. Dead cable is
   dusty grey-brown; a live one takes the terracotta (cable) or cyan (pipe),
   glows softly, and a light runs along it the way the power travels.

   ONE SVG PATH PER RUN (render.js, cableRun). The cable used to be one rounded
   pill per move, centre to centre, overlapping its neighbour by its own width
   so the corners met -- so every tile held half of two pieces joined in its
   middle, and every look drawn on a piece (a shadow, a translucency, a light)
   showed that joint. Box-shadows traced each pill's cap into a collar; a light
   per pill died at the tile centre; one light offset per pill still had the
   next pill's opaque cap painted over it. One stroked path with round caps and
   round joins has no joint at a tile centre or at a corner, so there is
   nothing to hide.

   The tube is three strokes of that path: a dark edge, a body nudged up-left,
   and a narrow highlight nudged further up-left -- lit from the top-left like
   the rocks, so it reads as round on a horizontal leg and a vertical one, and
   the shading turns every corner with the path. Stroke widths are fixed, not
   scaled with the tile: 9px of cable at every board size, as before.
   --e / --b / --h are the edge, body and highlight, --k the coupler, --a the
   halo; the dead colours sit on .cbl-run and the live ones on .t-lit. */
svg.cable{position:absolute;left:0;top:0;width:100%;height:100%;overflow:visible;
  pointer-events:none;z-index:2}
.cbl-run{--e:#8d7566;--b:#c3ac9c;--h:#e6d8cb;--k:#a08878}
.cbl-run.pipe{--e:#8e9ea7;--b:#c4d1d7;--h:#eef3f6;--k:#9fb0b9}
.t-lit,.cbl-run .cpl.on,.cbl-run.flow .cpl{
  --e:#9c3d19;--b:var(--terracotta);--h:#f7b48f;--k:var(--terra-deep);--a:rgba(224,102,58,.5)}
.cbl-run.pipe .t-lit,.cbl-run.pipe .cpl.on,.cbl-run.pipe.flow .cpl{
  --e:#0b6478;--b:var(--cyan);--h:#a3e8f4;--k:var(--cyan-deep);--a:rgba(36,184,209,.5)}
.cable path{fill:none;stroke-linecap:round;stroke-linejoin:round}
/* --ck scales the tube for a smaller tile (a card); 1 on the board */
.t-edge{stroke:var(--e);stroke-width:calc(9px * var(--ck,1))}
.t-body{stroke:var(--b);stroke-width:calc(6.4px * var(--ck,1))}
.t-hi{stroke:var(--h);stroke-width:calc(2px * var(--ck,1))}
/* the halo: a wide stroke through a blur, so it is as seamless as the tube */
.t-aura{stroke:var(--a);stroke-width:calc(10px * var(--ck,1))}
/* LIVE is a second tube over the dead one. Hidden until the run is won; while
   it lights up (`.lighting`) render.js reveals it along the path a move at a
   time with stroke-dasharray; a `.flow` run shows it whole and drops the dead
   one underneath. */
.t-lit{display:none}
.cbl-run.lighting .t-lit,.cbl-run.flow .t-lit{display:inline}
.cbl-run.flow .t-base{display:none}
/* POWER ARRIVING -- ONE LIGHT, THE LENGTH OF THE RUN. A dash pattern on the
   run's own path: a head every --sp (two tiles, set per run in render.js),
   moved forward by animating stroke-dashoffset. The path runs in drive order,
   which is the order the power travels, so the light always goes from the
   source out to the consumer, west and up included, and it follows the path
   round every corner. It cannot restart at a joint: there is none. The dash
   is blurred and masked to the tube (render.js) so it is a soft glow inside
   the cable, not a bar on it. --s0 carries the pattern across a run the camera
   splits in two. The pattern repeats every --sp, so the loop has no jump. */
.cable .t-light{display:none;stroke:rgba(255,248,232,.95);stroke-width:calc(7px * var(--ck,1));stroke-linecap:butt;
  stroke-dasharray:calc(var(--sp) * .22) calc(var(--sp) * .78);
  animation:cablelight 1.3s linear infinite}
.cbl-run.flow .t-light{display:inline}
@keyframes cablelight{from{stroke-dashoffset:calc(var(--s0,0px) + var(--sp))}to{stroke-dashoffset:var(--s0,0px)}}
/* THE COUPLER. A run is laid a move at a time, tile centre to tile centre,
   so the gap between two tiles is where one length of cable is joined to the
   next: a short sleeve, wider than the tube, shaded as a cylinder, with two
   ridges round it. Only where BOTH tiles are ground -- a run that ends into a
   building is joined by the building, and a coupler there is clutter. Drawn
   after the light, so the light runs inside the cable, under the sleeve. Its
   body is --k; the cylinder shading is a colourless gradient over it. A
   coupler turns live (`.on`) as the lighting passes it. */
.cpl-sh{fill:rgba(50,28,16,.3)}
.cpl-k{fill:var(--k);transition:fill .2s}
.cpl-gl{stroke:rgba(40,18,8,.38);stroke-width:.8}
.cable .cpl-rib{stroke:rgba(30,12,4,.28);stroke-width:.8;stroke-linecap:butt}

/* ---------------- THE BUILDINGS ----------------
   Unlit is a dead machine on a cold plinth: grey core, no rays, flat card.
   Lit is the same machine with its core in its own colour, rays out, a warm
   card and a dashed service ring turning round it. `--pc` (the colour) and
   `--pcrgb` (the same as an rgb triple, for shadows) are set per prop in
   render.js, so one rule set dresses every building. */
.prop{position:absolute;z-index:3;pointer-events:none;
  /* the pad, not a fixed 16px: that was rounder than the ground at 76px and
     40% of a 40px tile, so the card and the square under it never matched */
  border-radius:var(--pad-r);
  display:flex;align-items:center;justify-content:center;
  /* The card was #fff -> #f4efe9, near enough the same value as the sand it
     stands on that the object had no edge. Warmer and a shade deeper, so it
     reads as a poured pad. The cast shadow is the bigger fix: BOLTZ has a
     shadow ellipse and the buildings had none, so he sat IN the world and they
     sat ON it, like stickers. */
  background:linear-gradient(180deg,#fffdfa,#f0e8df);
  box-shadow:
    inset 0 0 0 2px #e2d9cf,
    inset 0 2px 0 rgba(255,255,255,.9),
    0 2px 0 rgba(150,96,60,.13),
    2px 7px 13px -6px rgba(120,74,44,.45);
  transition:background .45s,box-shadow .45s}
/* the machine sits ON the pad, not printed on it */
.prop svg{display:block;width:78%;height:78%;overflow:visible;
  filter:drop-shadow(1.2px 1.8px 1.2px rgba(96,64,44,.22))}
/* the plinth and post were flat grey shapes with no outline, beside a shell
   drawn with a 2.6 ink stroke -- two drawing styles in one 48px object */
.p-plinth,.p-post{fill:#aeb4c0;stroke:var(--ink);stroke-width:1.8;stroke-linejoin:round}
.p-shell{fill:#fbfcfe;stroke:var(--ink);stroke-width:2.6;stroke-linejoin:round}
/* markings PAINTED ON THE GROUND rather than built: the charging bay's corner
   brackets. Shell weight, because they are the bay's whole outline, not detail */
.p-mark{fill:none;stroke:var(--ink);stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}
.p-brace,.p-slat{fill:none;stroke:var(--ink);stroke-linecap:round}
.p-brace{stroke-width:1.5;opacity:.45}   /* the lighthouse's lattice */
.p-slat{stroke-width:1.5;opacity:.3}     /* the depot door's ribs */
.p-core{fill:#c9ced8;stroke:#c9ced8;stroke-width:0;transition:fill .5s,stroke .5s,filter .5s}
.p-ray{stroke:var(--pc,#f0a83c);stroke-width:3;stroke-linecap:round;fill:none;
  opacity:0;transition:opacity .5s}
.p-vane{stroke:#fff;stroke-width:2.6;stroke-linecap:round;fill:none;opacity:.75}
.p-ring{stroke:var(--pc,#f0a83c);stroke-width:2.4;stroke-linecap:round;opacity:0;transition:opacity .5s}
/* greenhouse, shed and ice draw their core as strokes, not a filled shape */
.p-green .p-core,.p-ice .p-core{fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}

/* A REAL BUG, not a matter of taste. This used to be `filter` on the whole
   element -- and a building this mission needs is `off want`, so the terracotta
   ring, the board's only way of saying THIS is the thing to connect, was
   desaturated along with the machine and arrived as mud-brown. The machine is
   unpowered; the pad it stands on is not, and neither is the ring the board
   drew round it. So the filter goes on the DRAWING, never the card. */
.prop.off{filter:none}
.prop.off svg{filter:saturate(.15) brightness(.99)
                     drop-shadow(1.2px 1.8px 1.2px rgba(96,64,44,.22))}
.prop.want{box-shadow:inset 0 0 0 2.5px var(--terracotta),0 2px 0 rgba(182,74,36,.18),
    2px 7px 13px -6px rgba(120,74,44,.45);
  animation:want 2s ease-in-out infinite}
@keyframes want{0%,100%{transform:scale(1)}50%{transform:scale(1.045)}}
.prop.lit{filter:none;background:linear-gradient(180deg,#fffdf6,rgba(var(--pcrgb,255,206,138),.5));
  box-shadow:inset 0 0 0 2.5px var(--pc,#f0a83c),0 2px 0 rgba(150,96,60,.13),
             0 10px 26px -8px rgba(var(--pcrgb,255,206,138),.95);
  animation:propwake .55s cubic-bezier(.3,1.5,.5,1)}
/* the payoff: the core takes its colour and starts to burn */
.prop.lit .p-core{fill:var(--pc,#f0a83c);stroke:var(--pc,#f0a83c);
  filter:drop-shadow(0 0 5px rgba(var(--pcrgb,255,206,138),.95))}
.prop.lit .p-plinth,.prop.lit .p-post{fill:#8d93a0}
.prop.lit .p-ray{opacity:1;animation:rayout 1.6s ease-in-out infinite}
.prop.lit .p-ring{opacity:.55;transform-box:fill-box;transform-origin:center;
  animation:ringspin 9s linear infinite}
.prop.src .p-ring{opacity:.4}
.prop.src .p-vane{animation:ringspin 6s linear infinite;transform-box:fill-box;transform-origin:center}
@keyframes propwake{0%{transform:scale(1)}45%{transform:scale(1.14)}100%{transform:scale(1)}}
@keyframes rayout{0%,100%{opacity:.55}50%{opacity:1}}
@keyframes ringspin{to{transform:rotate(360deg)}}

.glow{position:absolute;border-radius:50%;pointer-events:none;z-index:1;opacity:0;transition:opacity .8s;
  background:radial-gradient(circle,rgba(var(--pcrgb,255,206,138),.55),transparent 68%)}
.glow.on{opacity:1}
.drop{position:absolute;font-size:15px;z-index:4;color:var(--cyan-deep);animation:dp 1.1s ease-in infinite}
.drop.spark{color:#e8a020;animation:sp 1.1s ease-out infinite}
@keyframes sp{0%{transform:translate(0,0) scale(.6);opacity:1}100%{transform:translate(9px,-14px) scale(1.2);opacity:0}}
.damp.hot{background:radial-gradient(circle,rgba(232,160,32,.35),transparent 70%)}
@keyframes dp{0%{transform:translate(0,0);opacity:1}100%{transform:translate(6px,20px);opacity:0}}
.damp{position:absolute;border-radius:50%;background:rgba(60,90,105,.16);z-index:1;
  animation:damp 1.2s ease-out forwards}
@keyframes damp{from{transform:scale(.3);opacity:0}to{transform:scale(1);opacity:1}}

/* ---------------- BOLTZ ON THE BOARD ----------------
   One drawing, three sizes (board, demo card, and the same character in the
   corner sprite). The container carries position and rotation; the SVG just
   scales, so `.mini` is a width and nothing else. */
.rover{position:absolute;width:var(--tile);height:var(--tile);z-index:5;
  transition:transform .42s cubic-bezier(.34,1.15,.5,1)}
.rover svg{display:block;width:100%;height:100%;overflow:visible}
.r-shadow{fill:rgba(120,80,55,.16)}
.r-wheel{fill:#3f4550;stroke:var(--ink);stroke-width:2.4}
.r-tread{stroke:rgba(255,255,255,.34);stroke-width:2.4;stroke-linecap:round;fill:none}
.r-spool{fill:#5b626d;stroke:var(--ink);stroke-width:2.6}
.r-reel{fill:#f0dccb;stroke:var(--ink);stroke-width:2}
.r-body{fill:#fff;stroke:var(--ink);stroke-width:3;stroke-linejoin:round}
.r-seam{stroke:var(--ink);stroke-width:1.4;opacity:.26;fill:none}
.r-rivet{fill:#8b909b}
.r-tape{fill:var(--terracotta);opacity:.42}
.r-plow{fill:var(--terracotta);stroke:var(--ink);stroke-width:3;stroke-linejoin:round}

.r-deck{fill:var(--cyan)}
.r-slat{stroke:rgba(255,255,255,.55);stroke-width:2;stroke-linecap:round;fill:none}
.r-mast{fill:#fff;stroke:var(--ink);stroke-width:2.4}
.r-bulb{fill:var(--terracotta)}  /* the same antenna bulb the portrait wears, seen from above */
/* headlamps sit ON the plow, so "awake" and "which way is forward" are the
   same read */
.r-lamp{fill:#fff4dd;stroke:var(--ink);stroke-width:2;transition:fill .5s,filter .5s}
.rover.awake .r-lamp{fill:#ffc24d;filter:drop-shadow(0 0 5px rgba(255,194,77,.95))}
.rover.mini{width:46px;height:46px}
/* a win is danced on the spot (main.js celebrate): crouch, leap-and-spin
   toward the camera, squashy landing, happy wiggle. Seen from above, "up" is bigger. */
#rover.dance > svg{transform-origin:50% 50%;animation:rdance 2.2s ease-in-out}
@keyframes rdance{
  0%  {transform:none}
  11% {transform:scale(1.1,.82)}
  30% {transform:scale(1.36) rotate(200deg)}
  45% {transform:scale(1.3) rotate(360deg)}
  54% {transform:scale(1.16,.84) rotate(360deg)}
  62% {transform:scale(.95,1.06) rotate(360deg)}
  70% {transform:rotate(350deg)}
  78% {transform:rotate(369deg)}
  86% {transform:rotate(353deg)}
  93% {transform:rotate(363deg)}
  100%{transform:rotate(360deg)}}
/* the shadow stays on the ground, so it shrinks and fades while he is airborne */
#rover.dance .r-shadow{transform-box:fill-box;transform-origin:center;animation:rshadow 2.2s ease-in-out}
@keyframes rshadow{0%,12%,54%,100%{transform:none;opacity:1}30%,45%{transform:scale(.6);opacity:.35}}
/* headlamps blaze in the air, then blink three times on landing */
#rover.dance .r-lamp{transform-box:fill-box;transform-origin:center;animation:rlamp 2.2s linear}
@keyframes rlamp{
  22%,44%,56%,68%,80%{fill:#fffbe8;filter:drop-shadow(0 0 9px rgba(255,210,120,1));transform:scale(1.35)}
  50%,62%,74%{fill:#ffc24d;filter:drop-shadow(0 0 3px rgba(255,194,77,.8));transform:none}}
/* antenna bulb pops amber on the leap and the landing; the mast waggles */
#rover.dance .r-bulb{transform-box:fill-box;transform-origin:center;animation:rbulb 2.2s ease-out}
@keyframes rbulb{0%,100%{transform:none}14%,56%{transform:scale(1.7);fill:#f0a83c}36%,72%{transform:none}}
#rover.dance .r-mast{transform-box:fill-box;transform-origin:50% 100%;animation:rmast 2.2s ease-in-out}
@keyframes rmast{0%,52%,100%{transform:none}60%{transform:rotate(-16deg)}70%{transform:rotate(13deg)}80%{transform:rotate(-8deg)}90%{transform:rotate(4deg)}}
/* treads flick between two positions: wheels spinning with excitement */
#rover.dance .r-tread{animation:rtread .16s steps(2) 13}
@keyframes rtread{to{transform:translateY(3.9px)}}
/* confetti and little stars thrown out as he lands (main.js celebrate) */
.cheer{position:absolute;width:10px;height:6px;margin:-3px 0 0 -5px;border-radius:1.5px;background:var(--c);
  z-index:6;pointer-events:none;opacity:0;animation:cheer 1s cubic-bezier(.15,.75,.35,1) var(--dl,0s) forwards}
.cheer.cheer-star{width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:0;
  clip-path:polygon(50% 0,62% 38%,100% 50%,62% 62%,50% 100%,38% 62%,0 50%,38% 38%)}
@keyframes cheer{
  0%  {transform:translate(0,0) scale(.3) rotate(0);opacity:1}
  65% {opacity:1}
  100%{transform:translate(var(--ax),var(--ay)) scale(1) rotate(var(--sp));opacity:0}}
@media(prefers-reduced-motion:reduce){
  #rover.dance > svg,#rover.dance .r-shadow,#rover.dance .r-bulb,#rover.dance .r-mast,#rover.dance .r-tread{animation:none}
  #rover.dance .r-lamp{animation:rlampcalm 2.2s ease-in-out}
  .cheer{display:none}}
@keyframes rlampcalm{50%{filter:drop-shadow(0 0 9px rgba(255,210,120,1))}}
.puff{position:absolute;width:16px;height:16px;border-radius:50%;background:rgba(200,179,164,.7);
  pointer-events:none;z-index:4;animation:puff .55s ease-out forwards}
@keyframes puff{0%{transform:scale(.4);opacity:.75}100%{transform:scale(2.1);opacity:0}}

@media(prefers-reduced-motion:reduce){
  .prop.lit .p-ray,.prop.lit .p-ring,.prop.src .p-vane,
  .prop.want,.prop.lit{animation:none}
  .prop.lit .p-ring{opacity:.55}.prop.src .p-ring{opacity:.4}
  .prop.lit .p-ray{opacity:1}
  /* the cable is still there and lit; only the travelling light goes */
  .cable .cbl-run.flow .t-light{display:none}}
