/* Lamplight - the night-watch cabinet at /lamplight/.
 *
 * Self-contained: this file is the only stylesheet the game owns, and nothing in
 * this directory reaches outside it except the two lines of the cabinet contract.
 *
 * ⚠️ THE FIRST RULE IS LOAD-BEARING. `hidden` is enforced by the UA stylesheet
 *    alone, so any author rule carrying a `display` beats it. AGENT.md gotcha 9;
 *    it has caught this repo out three times.
 *
 *    As of this commit exactly ONE element in this file needs it: `.wave-banner`,
 *    which is `display: flex` and ships `hidden`. The three views and the records
 *    panel ship hidden too but carry no `display` of their own, so they would
 *    survive without this rule today. That was measured by deleting the rule and
 *    re-reading `getComputedStyle`, not assumed - the comment copied from /mine/
 *    named all four, because all four are load-bearing THERE, and a claim about
 *    another file is not evidence about this one.
 *
 *    The rule stays regardless, and so does the check: this stylesheet is going to
 *    grow a `display` onto a view or a panel sooner or later, and the failure mode
 *    is the game booting with every view stacked on top of each other.
 */
[hidden] {
  display: none !important;
}

/* ⚠️ `touch-action` ON `*`, NOT ON `body`. This is a game played by tapping the
 *    same area fast, which is exactly the gesture Safari reads as double-tap zoom -
 *    and a player stranded at 2x has to pinch their way back mid-wave. The property
 *    DOES NOT INHERIT: Safari reads it off the element actually under the finger,
 *    so `body { touch-action }` leaves every heading, gap and panel still zooming.
 *    /jonathan/ shipped it scoped once and came back still zooming. Deliberately
 *    not `user-scalable=no` in the viewport meta: iOS has ignored that since iOS
 *    10, and where it is honoured it takes pinch-zoom from people who need it. */
*,
*::before,
*::after {
  box-sizing: border-box;
  touch-action: manipulation;
}

/* Ink-violet night, bone-white halo, gold firefly. Violet because no other cabinet
   uses it (the shell is navy + cyan), and gold because the firefly has to read as
   "the good one" in the half-second before you hit it.
   ⚠️ THESE ARE ALSO THE SPRITE FALLBACK COLOURS. Every creature keeps its CSS
   colour underneath its background-image, so a 404 in assets/ leaves a playable
   game of coloured shapes rather than a grid of empty buttons - and if the art is
   ever regenerated in a different key, these move in the same edit. */
:root {
  --night: #14111c;
  --night-2: #1b1726;
  --panel: #221d30;
  --panel-edge: #3a3250;
  --dusk: #453c5e;
  --dusk-lit: #7a6f97;
  --ink: #ece9f3;
  --ink-dim: #a79fbd;
  --halo: #f6f0dc;
  --halo-dim: #8d8468;
  --moth: #b0a3b5;
  --moth-edge: #d5cbd8;
  --carapace: #6b7486;
  --carapace-crack: #9aa4b8;
  --firefly: #ffc94a;

  /* ⚠️ THE CELL TINTS: each creature's colour at 35% over --night-2, AND THE FIRST
     CUT OF THE ART PASS DID NOT HAVE THEM, WHICH IS WHY THEY EXIST. The four colours
     above filled the whole cell while the game was coloured squares, and that was
     right. The moment a sprite went on top, every drawing was sitting on a large flat
     block of its own colour: mauve moths on mauve, slate shells on slate, and a
     firefly whose gold abdomen vanished into a gold cell. The art was invisible and
     every automated check was green - a screenshot found it, which is the third time
     in this workspace that sentence has been true.
     THE RECIPE IS ONE NUMBER APPLIED FOUR TIMES, deliberately, because mixing each by
     eye would have destroyed the relationships the palette was measured for. At 35%
     the separations keep their ordering at a third of the amplitude: carapace to
     cracked carapace 16.6 of 255 (two taps left versus one - the reading that costs
     you), moth to carapace 18.1, and moth to cracked carapace 1.5, which stays
     collapsed on purpose because both mean ONE TAP LEFT. The firefly sits 14 clear of
     its nearest neighbour and in a hue nothing else on the board uses.
     ⚠️ AND THEY ARE STILL THE 404 FALLBACK. Dark is not absent: with assets/
     unreachable this is four distinguishable coloured squares, which is a playable
     game, and the tokens above remain the SPRITE palette that the sheet prompt names
     object by object. */
  --moth-cell: #4f4858;
  --carapace-cell: #373848;
  --carapace-crack-cell: #474859;
  --firefly-cell: #6b5533;
  --snuff: #d8574e;
  --good: #7fd08a;
  --shadow: 0 0.5rem 1.4rem rgba(0, 0, 0, 0.5);
}

html {
  background: var(--night);
}

body {
  margin: 0;
  padding: 1rem 0.9rem 2.5rem;
  background:
    radial-gradient(120% 70% at 50% -10%, #241d33 0%, transparent 60%),
    var(--night);
  color: var(--ink);
  font-family: "Trebuchet MS", "Gill Sans", "Segoe UI", system-ui, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  min-height: 100vh;
}

.site {
  max-width: 34rem;
  margin: 0 auto;
}

/* ⚠️ THE OTHER HALF OF THE CABINET CONTRACT. Below 760px /arcade-return.css drops
 *    the chip from `fixed` to `absolute` so it scrolls away instead of riding down
 *    over the game - but it still starts in the top-left corner, and this cabinet's
 *    content starts there too. Only the cabinet knows its own layout, so the
 *    cabinet reserves the strip. Two cabinets shipped without this and nobody saw
 *    it, because both were only ever checked at desktop width. If the chip is ever
 *    removed from this page, remove this block in the same edit. */
@media (max-width: 760px) {
  .site {
    padding-top: 52px;
  }
}

/* ---------------------------------------------------------------- chrome --- */

.masthead {
  text-align: center;
  margin: 0 0 1.1rem;
}

.eyebrow {
  margin: 0 0 0.3rem;
  color: var(--firefly);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}

h1 {
  margin: 0 0 0.5rem;
  font-size: clamp(2rem, 11vw, 2.8rem);
  line-height: 1;
  letter-spacing: 0.02em;
  text-shadow: 0 0.18rem 0 rgba(0, 0, 0, 0.55);
}

.lede {
  margin: 0 auto;
  max-width: 30rem;
  color: var(--ink-dim);
  font-size: 0.95rem;
}

.lede strong {
  color: var(--ink);
}

.panel {
  margin: 0 0 1rem;
  padding: 1.1rem 1rem;
  border: 1px solid var(--panel-edge);
  border-radius: 0.7rem;
  background: var(--panel);
  box-shadow: var(--shadow);
}

.panel-title {
  margin: 0 0 0.7rem;
  font-size: 1.1rem;
  letter-spacing: 0.02em;
}

.panel-subtitle {
  margin: 1.2rem 0 0.6rem;
  color: var(--firefly);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.panel-line {
  margin: 0 0 0.9rem;
  color: var(--ink-dim);
  font-size: 0.92rem;
}

.fineprint {
  margin: 0.8rem 0 0;
  color: var(--dusk-lit);
  font-size: 0.75rem;
}

.noscript {
  max-width: 30rem;
  margin: 2rem auto;
  padding: 1rem;
  border: 1px solid var(--panel-edge);
  border-radius: 0.6rem;
  background: var(--panel);
  color: var(--ink);
  text-align: center;
}

/* --------------------------------------------------------------- buttons --- */

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.button-row.tight {
  gap: 0.4rem;
}

.button-row.tight .text-button {
  flex: 1 1 8rem;
  width: auto;
  margin: 0.6rem 0 0;
}

.primary-button {
  flex: 1 1 10rem;
  min-height: 3rem;
  padding: 0.7rem 1rem;
  border: 1px solid #c79a2c;
  border-radius: 0.55rem;
  background: linear-gradient(180deg, var(--firefly) 0%, #d9a223 100%);
  color: #241a02;
  font: inherit;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
}

.primary-button.ghost {
  border-color: var(--panel-edge);
  background: transparent;
  color: var(--ink);
}

.primary-button:hover,
.primary-button:focus-visible {
  filter: brightness(1.08);
}

.primary-button.ghost:hover,
.primary-button.ghost:focus-visible {
  border-color: var(--firefly);
  filter: none;
}

.button-note {
  display: block;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  opacity: 0.75;
  text-transform: uppercase;
}

/* ⚠️ `min-height` IS NOT DECORATION HERE. Copied from /mine/, this rule produced a
   38px-tall target - under the 48px floor, on every one of 320, 390 and 1280. The
   underline makes it look like a link and links are held to nothing, but it is a
   button and a thumb has to hit it. /mine/ and /thumbspell/ share the inherited
   rule and are owed the same fix; noted rather than silently reached into. */
.text-button {
  display: block;
  width: 100%;
  min-height: 3rem;
  margin: 0.8rem 0 0;
  padding: 0.55rem;
  border: 0;
  border-radius: 0.4rem;
  background: transparent;
  color: var(--ink-dim);
  font: inherit;
  font-size: 0.85rem;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
}

.text-button:hover,
.text-button:focus-visible {
  color: var(--firefly);
}

button:focus-visible,
.arcade-return:focus-visible {
  outline: 2px solid var(--halo);
  outline-offset: 2px;
}

/* ⚠️ Headings take focus when a view opens so a screen reader lands in the new
 *    view rather than at the top of the document - but a mouse player must not see
 *    a ring appear on a paragraph they never touched. */
.view h2:focus,
.view h1:focus {
  outline: none;
}

/* ----------------------------------------------------------------- rules --- */

.rules {
  margin: 0;
  padding-left: 1.1rem;
  color: var(--ink-dim);
  font-size: 0.92rem;
}

.rules li {
  margin-bottom: 0.7rem;
}

.rules strong {
  color: var(--ink);
}

.firefly-word {
  color: var(--firefly);
  font-weight: 700;
}

.legend {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: 0.55rem;
  margin: 0 0 1.1rem;
  padding: 0;
  list-style: none;
}

.legend li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
}

.legend-text {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}

/* ⚠️ GREW FROM 1.6rem AND STOPPED BEING A CIRCLE WHEN THE ART LANDED, and both halves
   of that are the same point. A colour dot was an honest legend while the board was
   colour dots. Now the board shows a drawing, so a legend that shows anything else is
   a legend of a different game - and a moth at 26px inside a circle is a smudge. The
   square matches the cell it is describing, and the radius matches the cell's. */
.legend-swatch {
  flex: 0 0 auto;
  width: 2.4rem;
  height: 2.4rem;
  border: 1px solid var(--dusk);
  border-radius: 0.4rem;
  background-position: center;
  background-repeat: no-repeat;
  background-size: 82% auto;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.45);
}

.legend-note {
  color: var(--dusk-lit);
  font-size: 0.78rem;
}

/* ------------------------------------------------------------------- hud --- */

.play-panel {
  padding: 0.9rem 0.8rem 1rem;
}

.hud {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.3rem;
  margin: 0 0 0.5rem;
}

.hud-cell {
  margin: 0;
  padding: 0.4rem 0.2rem;
  border-radius: 0.4rem;
  background: var(--night-2);
  text-align: center;
}

.hud-label {
  display: block;
  color: var(--dusk-lit);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.hud-value {
  display: block;
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.2;
}

/* The wicks read as flames, not as a number: "3" is a score, three little flames
   are a thing you are about to lose. The aria-label on the same node carries the
   count in words, because the glyphs are decoration to a screen reader. */
.wicks {
  display: flex;
  justify-content: center;
  gap: 0.22rem;
  min-height: 1.38rem;
}

.wick {
  width: 0.5rem;
  height: 1.1rem;
  border-radius: 50% 50% 45% 45%;
  background: linear-gradient(180deg, var(--halo) 0%, var(--firefly) 55%, #b4761a 100%);
}

.wick.is-spent {
  background: var(--dusk);
}

.mode-line {
  margin: 0 0 0.6rem;
  color: var(--dusk-lit);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-align: center;
  text-transform: uppercase;
}

/* ----------------------------------------------------------------- board --- */

/* 3 columns x 4 rows at every width. Three columns is what keeps a tap target near
   90px on a 320px screen; four rows is the room the "more at once" escalation needs
   and a 3x3 grid does not have.
   ⚠️ NO PER-CELL TINT, AT ANY OPACITY. An alternating wash across a 12-cell grid
   IS a checkerboard - larkfen's was 4.3 of 255 in luminance, "a whisper by the
   number", and came back off a phone as "the floor looks like a checker board".
   Halving it was still legible. There is no weight that works, because weight was
   never the axis. Separation here comes from the border and the gap. */
/* ⚠️ THE BOARD IS SIZED BY THE HEIGHT IT HAS, NOT BY THE WIDTH IT IS GIVEN. The
   first cut gave each cell `aspect-ratio: 4/3` and let the grid take the panel's
   full width. Measured, that put the board 267px tall inside a 568px screen with
   410px of chrome around it - the bottom two rows below the fold on a game where
   missing a cell costs a wick - and 547px tall at 1280, where three columns of
   176px cells is a different game again. Neither was visible at the width the
   layout was designed at. So the rows are `1fr` of an explicit height:

     - the floor (13.5rem) is four 48px tap targets plus three gaps, so the board
       can never shrink a cell under the touch floor - it lets the page scroll
       instead, which costs a thumb-reach and not a wick;
     - the middle term is what the viewport actually has left after the chrome;
     - the ceiling (24rem) is what stops a desktop board becoming a billboard.

   `svh`, not `dvh` or `vh`: `dvh` re-sizes the board when a phone's URL bar slides
   away, which is a moving target mid-wave, and `vh` is the LARGE viewport, so the
   bottom row hides under the bar that is actually on screen. `svh` is the height
   with the browser chrome showing - the one that is always true. The `vh` line
   below is the fallback for browsers that do not know `svh` and is deliberately
   the more conservative of the two. */
/* The box the board and the between-wave banner share, so the banner can sit ON
   the board instead of under it. The width and centring live here rather than on
   .board so the two are the same rectangle by construction and not by a second
   copy of the same two numbers. */
.board-wrap {
  position: relative;
  max-width: 24rem;
  margin: 0 auto 0.5rem;
}

.board {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(4, 1fr);
  gap: 0.4rem;
  height: clamp(13.5rem, calc(100vh - 25rem), 24rem);
  height: clamp(13.5rem, calc(100svh - 25rem), 24rem);
}

.cell {
  min-height: 48px;
  /* The sprite's margin, and it costs the tap target nothing: a finger hits the
     border box, and `background-origin` only moves where the DRAWING is allowed to
     start. A percentage resolves against the cell's width on all four sides, which
     is what keeps the air around a creature proportional at every viewport. */
  padding: 5%;
  border: 1px solid var(--dusk);
  border-radius: 0.5rem;
  background-color: var(--night-2);
  background-position: center;
  background-repeat: no-repeat;
  /* ⚠️ `contain`, NOT A PERCENTAGE OF THE WIDTH - AND THE PERCENTAGE SHIPPED AND WAS
     WRONG. This read `72% auto`, which sizes a square drawing to 72% of the cell's
     WIDTH and lets the height fall where it may. A Lamplight cell is never square:
     the board is 3x4 in a box whose height is clamped against the fold, so a cell is
     always wider than it is tall, and on a short screen it is much wider. Measured
     on the shipped stylesheet: 18% of every creature cut off the top and bottom at
     320x568 - the smallest viewport this cabinet supports and one the suite already
     ran at - and 45% at 844x390, which is a phone turned sideways.
     It is larkfen's floating flowers reached from the other end: there the cutter
     padded a drawing into a square and the stylesheet assumed where it sat; here the
     drawings are square and correct and the stylesheet asked for a size the box
     could not hold. Neither is visible to a check that measures the FILES, and
     neither is visible at the width the layout was designed at. `contain` cannot
     overflow by construction; docs/lamplight-art-check.js section 6 measures the ink
     rather than trusting that sentence. */
  background-origin: content-box;
  background-size: contain;
  color: transparent;
  font: inherit;
  cursor: pointer;
}

/* ⚠️ The sprite is painted ON the button, never as a child element. A child needs
   `pointer-events: none` to stop it eating the tap (/jonathan/ needed exactly that)
   and it is a second thing to keep aligned; a background-image is neither. */

.cell:hover {
  border-color: var(--dusk-lit);
}

/* `is-up` carries the affordance and NOT a colour. A cell is only ever `is-up`
   together with a creature class, so a background here would never be seen - and
   would quietly outrank the creature's own colour if the two rules were ever
   reordered. */
.cell.is-up {
  border-color: var(--dusk-lit);
}

/* ⚠️ THE CREATURE PALETTE IS SHARED BY THE BOARD AND THE LEGEND, AND THE SELECTOR
   LIST IS WHAT MAKES THAT TRUE. app.js draws both the how-to swatch and the board
   cell from the same `swatch` field in CREATURES, on the stated grounds that the
   two "cannot disagree about what a moth looks like". They can, and they did: the
   first cut scoped these to `.cell`, so the swatches inherited nothing and the
   how-to screen shipped three identical empty circles. A suite had no opinion about
   it; a screenshot did.
   The pairs are spelled out rather than collapsed to a bare `.is-moth`, because a
   single-class selector loses to `.cell.is-hit` and the hit flash has to win. */
.cell.is-moth,
.legend-swatch.is-moth {
  background-color: var(--moth-cell);
  border-color: var(--moth-edge);
}

.cell.is-carapace,
.legend-swatch.is-carapace {
  background-color: var(--carapace-cell);
  border-color: var(--carapace-crack);
}

/* ⚠️ THE SEPARATION HERE IS MEASURED, AND IT IS DELIBERATELY UNEVEN. Relative
   luminance, out of 255, between the states a player has to tell apart at speed:

     carapace vs cracked carapace  (two taps left vs one)   47.9
     moth     vs carapace          (one tap vs two)         51.7
     moth     vs cracked carapace  (both one tap left)       3.7
     firefly  vs its nearest neighbour                      36.2

   That last-but-one number is 3.7 of 255 - Larkfen's checkerboard floor was 4.3,
   "a whisper by the number and a chessboard on the screen" - and here it is
   CORRECT. What a player has to read off this board is not which species it is, it
   is HOW MANY TAPS ARE LEFT, and a moth and a cracked carapace are the same answer:
   one. Confusing them costs nothing. The two readings that do cost something - two
   taps rather than one, and the gold one you must not touch - are the two that are
   separated by forty-odd points and by hue as well as by value.
   ⚠️ SO DO NOT "FIX" THE 3.7 WHEN THE SPRITES LAND. Pulling the moth and the
   cracked carapace apart would be spending contrast on a distinction that does not
   exist, and the budget it came out of is the one holding the other three up. */
.cell.is-carapace.is-cracked {
  background-color: var(--carapace-crack-cell);
}

.cell.is-firefly,
.legend-swatch.is-firefly {
  background-color: var(--firefly-cell);
  border-color: var(--halo);
}

/* ⚠️ THE SPRITES GO OVER THE COLOURS, NEVER INSTEAD OF THEM. Every rule above stays
   exactly as it was: `background-image` paints on top of `background-color`, so a 404
   in assets/ leaves this a playable game of coloured squares rather than a grid of
   empty buttons. That is larkfen's rule and the same instinct as the arcade's "every
   game plays identically with the backend unreachable".

   ⚠️ AND THE LEGEND IS IN EVERY SELECTOR LIST HERE, for the reason the colours above
   carry a comment about. app.js draws the how-to swatch and the board cell from the
   same `swatch` field on the grounds that the two "cannot disagree about what a moth
   looks like" - they can, and they did: scoping the colours to `.cell` shipped a
   how-to with three identical empty circles while the board was correct. Adding
   sprites to the board and not to the legend would do it again, and this time
   docs/lamplight-checks.js's "legend matches the board" would stay GREEN through it,
   because that check compares colours and the colours would still agree.

   The legend shows variant 1 of each creature. There is no better answer - a legend
   entry is one square and a moth is three drawings - and it is why variant 1 of each
   family is drawn as the most typical of it rather than the most interesting. */
.cell.is-moth.is-var-1,
.legend-swatch.is-moth {
  background-image: url("assets/moth-1.webp");
}

.cell.is-moth.is-var-2 {
  background-image: url("assets/moth-2.webp");
}

.cell.is-moth.is-var-3 {
  background-image: url("assets/moth-3.webp");
}

.cell.is-carapace.is-var-1,
.legend-swatch.is-carapace {
  background-image: url("assets/carapace-1.webp");
}

.cell.is-carapace.is-var-2 {
  background-image: url("assets/carapace-2.webp");
}

/* ⚠️ FOUR CLASSES, SO THESE BEAT THE THREE-CLASS RULES ABOVE BY SPECIFICITY RATHER
   THAN BY SOURCE ORDER. (0,4,0) against (0,3,0). A cracked shell that fell back to
   its whole drawing would be the two-tap creature silently losing the only signal it
   has for "one more tap", and it would look completely normal. */
.cell.is-carapace.is-var-1.is-cracked {
  background-image: url("assets/carapace-1-cracked.webp");
}

.cell.is-carapace.is-var-2.is-cracked {
  background-image: url("assets/carapace-2-cracked.webp");
}

.cell.is-firefly,
.legend-swatch.is-firefly {
  background-image: url("assets/firefly.webp");
}

.cell.is-hit {
  background-color: var(--night-2);
  border-color: var(--halo);
}

.cell.is-missed {
  border-color: var(--snuff);
}

/* ⚠️ THE TWO FIREFLY MARKS, AND THEY ARE A PAIR: the same event, and the whole
   difference is which of them you caused. A firefly that got through leaves the
   board wearing the gold it was carrying; one you hit leaves the same square wearing
   that gold gone out. `--halo-dim` is doing real work there - a grey border would
   read as nothing happening, which is precisely the wrong lesson for the one tap in
   this game that is always a mistake. Neither is the primary signal (both last
   FLASH, and the status line and the lamp carry the news), but a player watching
   their finger rather than the HUD sees only this. */
.cell.is-fed {
  border-color: var(--firefly);
}

.cell.is-wasted {
  border-color: var(--halo-dim);
}

/* ⚠️ PAUSE COVERS THE BOARD, AND THIS RULE IS THE ANTI-CHEAT HALF OF IT. A pause
   that freezes a VISIBLE board turns a reflex game into a turn-based one - tap,
   pause, read the board at leisure, resume - and after the firefly lands it would
   also be a way to find the gold one without ever having to recognise one at speed.
   app.js freezes the clock; this hides what is up.
   ⚠️ THE FIRST VERSION OF THIS NOTE DID THE ARITHMETIC WRONG AND THE RED PROOF IS
   WHAT CAUGHT IT. It claimed `.board.is-paused .cell` scores (0,2,1) and therefore
   loses to the three-class `.cell.is-carapace.is-cracked`. It does not: `.cell` in
   the descendant position is a CLASS, not an element, so the selector is (0,3,0)
   and the two TIE. Measured: with `!important` deleted, all six creature states
   still read as covered - because a tie is settled by source order and this rule
   sits below them.
   The `!important` stays, on the reason that survives the correction. An anti-cheat
   guarantee resting on source order is a guarantee that a tidy-up can delete: move
   this block above the creature rules, or add one creature state with four classes,
   and a covered board starts showing through - silently, for that state only. This
   rule has to beat every present and future creature selector by construction, and
   `!important` is the only form of that which does not have to be re-won each time
   a creature is added. */
.board.is-paused .cell {
  background-color: var(--night-2) !important;
  background-image: none !important;
  border-color: var(--dusk) !important;
}

/* ------------------------------------------------------------------ lamp --- */

/* Tracks the board's width and centring so the glow sits under the board rather
   than under the panel. */
/* ⚠️ WARM, AND WITH A CORE. The first cut used the bone-white halo token at a
   single soft falloff; rendered, it read as a drop-shadow under the board rather
   than as a light - a grey smudge, because a flame that is only diffuse is just a
   blur. The fix is two stops rather than one: a small near-white core that reads as
   a source, then a warm gold falloff around it. Caught by looking at a 390px
   screenshot, not by a check - a suite has no opinion about what a lamp looks
   like. */
.lamp {
  position: relative;
  height: 2.1rem;
  max-width: 24rem;
  margin: 0 auto 0.5rem;
  border-radius: 0.5rem;
  background:
    radial-gradient(22% 70% at 50% 108%, rgba(255, 248, 224, 0.95) 0%, rgba(255, 226, 150, 0.55) 55%, transparent 100%),
    radial-gradient(72% 200% at 50% 104%, rgba(255, 201, 74, 0.32) 0%, rgba(255, 201, 74, 0.08) 45%, transparent 74%),
    var(--night-2);
}

.lamp.is-snuffed {
  background:
    radial-gradient(22% 70% at 50% 108%, rgba(255, 190, 175, 0.6) 0%, rgba(216, 87, 78, 0.5) 55%, transparent 100%),
    radial-gradient(72% 200% at 50% 104%, rgba(216, 87, 78, 0.3) 0%, transparent 72%),
    var(--night-2);
}

.lamp.is-fed {
  background:
    radial-gradient(30% 92% at 50% 110%, rgba(255, 252, 236, 1) 0%, rgba(255, 214, 110, 0.7) 55%, transparent 100%),
    radial-gradient(86% 230% at 50% 104%, rgba(255, 201, 74, 0.46) 0%, rgba(255, 201, 74, 0.14) 48%, transparent 78%),
    var(--night-2);
}

/* ⚠️ THE THIRD THING THAT CAN HAPPEN TO A WICK, AND THE LAMP HAD NOTHING TO SAY
   ABOUT IT. A cold review instrumented the lamp across three hundred firefly swats
   and got `lamp -> lamp` every time: of snuffed, fed and swatted, the only one the
   player does ON PURPOSE was the only one with no state here - so the game's most
   common mistake was quieter than both its reward and its ordinary miss.
   DIMMER THAN THE BASE GLOW, not redder than it. `is-snuffed` is red because
   something was taken from you; this is a light that was on its way and is not
   coming, so it is the lamp's own gold at roughly half amplitude with the near-white
   core gone. The core is what reads as a source (see the base rule), and losing it
   is the whole signal: for a moment the lamp stops looking lit from within. */
.lamp.is-wasted {
  background:
    radial-gradient(22% 70% at 50% 108%, rgba(255, 226, 150, 0.34) 0%, rgba(255, 201, 74, 0.2) 55%, transparent 100%),
    radial-gradient(72% 200% at 50% 104%, rgba(255, 201, 74, 0.12) 0%, transparent 62%),
    var(--night-2);
}

/* ⚠️ THE LANTERN IS A SEPARATE LAYER, NOT A FIFTH BACKGROUND ON THE RULES ABOVE, and
   that is a mechanical necessity rather than tidiness. Each of the four lamp states
   re-declares `background` as a SHORTHAND, which resets background-image - so a sprite
   added to the base rule would vanish the moment a wick was snuffed, a firefly got
   through or one was swatted, i.e. at exactly the three moments the lamp is worth
   looking at.
   ⚠️ AND THE DRAWING BRINGS NO LIGHT WITH IT. The sheet prompt forbids glow on every
   object and leads with it, because the firefly and the lamp are the two a model
   reaches for a halo on by default. The light here is the gradients underneath; the
   lantern is a flat unlit object standing in them, and it is why the glow still reads
   as the game's rather than as part of a picture. Measured on the sheet: the alpha
   bounding box at threshold 16 and at 200 differ by one pixel on all nine cells.
   No hit area - the wick count in the HUD is the readable copy of this. */
.lamp::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("assets/lamp.webp");
  background-position: center bottom;
  background-repeat: no-repeat;
  background-size: auto 118%;
  pointer-events: none;
}

/* ---------------------------------------------------------------- status --- */

.status {
  margin: 0 0 0.4rem;
  min-height: 2.6rem;
  color: var(--ink-dim);
  font-size: 0.88rem;
  text-align: center;
}

.status.is-good {
  color: var(--good);
}

.status.is-bad {
  color: var(--snuff);
}

/* ⚠️ ABSOLUTE, AND THE WHOLE POINT IS THAT IT COSTS NO HEIGHT. This shipped as a
   block below the lamp and every check stayed green: a suite reads the banner's
   text, not the 38px of page it pushed ahead of itself. `pointer-events: none`
   is belt-and-braces - tapCell already refuses anything outside phase "running" -
   but a cover that can eat the first tap of the next wave is the kind of thing
   that only shows up under a thumb. */
.wave-banner {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  border-radius: 0.5rem;
  border: 1px solid var(--dusk);
  background: var(--night-2);
  color: var(--firefly);
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-align: center;
  pointer-events: none;
}

/* --------------------------------------------------------------- summary --- */

.summary-title {
  margin: 0 0 0.6rem;
  font-size: 1.5rem;
}

.score-slab {
  margin: 0 0 1rem;
  padding: 0.9rem;
  border-radius: 0.6rem;
  background: var(--night-2);
  text-align: center;
}

.score-value {
  margin: 0;
  color: var(--firefly);
  font-size: 2.6rem;
  font-weight: 700;
  line-height: 1;
}

.score-label {
  margin: 0.2rem 0 0;
  color: var(--dusk-lit);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.haul,
.record-list {
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
  font-size: 0.9rem;
}

.haul li,
.record-list li {
  display: flex;
  justify-content: space-between;
  gap: 0.8rem;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--panel-edge);
  color: var(--ink-dim);
}

.haul li:last-child,
.record-list li:last-child {
  border-bottom: 0;
}

.haul strong,
.record-list strong {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------- reduced motion --- */

/* ⚠️ REDUCED MOTION MUST NOT CHANGE DIFFICULTY. This block flattens decoration and
   nothing else - no spawn interval, no up-time and no wave plan is read from CSS,
   so the wave a player gets is identical either way. The behavioural suite checks
   that rather than trusting it. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
