/* The prize room.

   It wears the opening sequence's language rather than the in-game card's: the cream panel, the
   blue ink, the pixel monospace and `.pixelButton`. Somebody walks from this page to the title
   screen, and two visual families one link apart would read as two products.

   🔴 Self-contained on purpose. `opening.css` and `packs.css` carry most of these rules already,
   and this file repeats the handful it needs rather than importing either. The whole point of the
   surface is that it loads the minimum, and `opening.css` is 8.4 KB of title screen, intro scroll
   and registration form that this page has no use for. The duplication is about forty
   declarations and it is named in the A-065 report.

   ⚠️ There is no countdown anywhere in here, and there is not meant to be. Build sheet row 28
   shows phase times as facts; a clock ticking down is the same information turned into pressure,
   and it is the one thing the clerk rule (row 26) forbids. */

:root {
  --ink: #17254a;
  --cream: #e9efc1;
  --panel: #fffef0;
  --muted: #4a5c7d;
  --gold: #f7d85c;
  --blue: #244c93;
  --bad: #9b2226;
  /* 🔴 A-114. THE GREEN WAS ALREADY IN THE PRODUCT AND HAD NO NAME.
     `.pixelButton` has painted `linear-gradient(#3a9a57 50%, #2e7d46 50%)` since the door went in,
     so the board's "open to your wallet" mark is the CONNECT WALLET button's own colour rather
     than a fourth hue invented for it. Naming it is the whole change; nothing was recoloured. */
  --good: #3a9a57;
  --good-deep: #2e7d46;
}
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  min-height: 100vh;
  background: #07142d;
  color: var(--ink);
  font-family: monospace;
}

/* 🔴 A-075. `.store` IS NO LONGER A CARD, it is the page, and the two surfaces inside it are what
   a visitor is looking at. The cream card that used to be here has moved to `.saleIn`, which is the
   only one of the two that is a document; the other one is a room. */
.store {
  position: relative;
  z-index: 1;
}

/* =================================================================================================
   A-075. THE PUBLIC FACE.

   The sky is the title screen's own, and that is the point rather than a flourish: somebody walks
   from this page to `index.html` one link away, and two different skies would read as two products.
   `store-sky.webp` is a 48 KB 1200px derivative of the 1.5 MB `title-sky.png` the title screen
   uses -- the whole reason this surface exists is that it loads the minimum, and the original is
   seven times the entire rest of the bundle. See the A-075 report for how it was made.
   ================================================================================================= */
.storeSale {
  position: fixed;
  z-index: 1;
  inset: 0;
  display: grid;
  align-content: center;
  overflow: auto;
  padding: clamp(56px, 9vh, 96px) 14px clamp(28px, 5vh, 48px);
}
.storeSale[hidden] {
  display: none !important;
}
.saleSky {
  position: fixed;
  z-index: -1;
  inset: 0;
  background: #07142d url("../assets/ui/store-sky.webp") center / cover no-repeat;
  image-rendering: pixelated;
}
/* The title screen's own gradient, from `.opening:after` in `game/opening.css`, at the same
   strength. It is what keeps cream panels legible over a moonlit sky without dimming the sky into
   texture. */
.saleSky:after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(#07142d22, #07142daa);
}
.saleIn {
  display: grid;
  gap: clamp(12px, 2vw, 18px);
  width: min(720px, 100%);
  margin: 0 auto;
}
/* Where `#storeBoard` lives on each side of the door. It is a mount point and nothing else, so it
   contributes no box of its own -- the board carries its own border either way. */
.boardHome {
  display: contents;
}

/* 🔴 The honesty strip, and A-075 PINNED IT. Loud enough that a screenshot of this page cannot be
   mistaken for a price list, quiet enough that it is not the first thing anyone reads.

   `position: fixed` is the load-bearing part and a test asserts it: full window there is no page
   flow left for it to sit in, and it has to be present on both sides of the door -- the side where
   the figures are read and the side where they are spent. */
.storeFixture {
  position: fixed;
  z-index: 100003;
  left: 12px;
  top: 12px;
  max-width: min(46ch, calc(100% - 24px));
  margin: 0;
  padding: 7px 10px;
  border-left: 3px solid var(--bad);
  border-radius: 3px;
  background: #07142dd9;
  color: #f0d4c4;
  font: 700 10px/1.5 monospace;
}
.storeFixture[hidden] {
  display: none !important;
}

/* =================================================================================================
   🔴 A-086. THE GAME'S TOP-RIGHT CHROME, AND EVERY DECLARATION BELOW IS A COPY.

   The owner, 2026-09-11: *"just like the primary game we are missing the option to see the volume |
   Disconnect wallet option at the top right."*

   🔴 WHERE THE ORIGINALS LIVE, because it is not where a reader would look. `game/opening.css` holds
   NONE of these three class names -- `grep` finds none of them there -- and importing it is what
   A-065 argues this page must never do anyway: it would pull the title screen, the intro scroll and
   the registration form onto a page that has none of them. The real homes are:

     `.titleChrome` `.walletChip` `.walletAccount` `#walletDisconnect`   game/wallet-access.css
     `.titleSound` `.titleSound.muted`                                   game/music.css

   🔴 THE CLASS NAMES ARE THE GAME'S OWN ON PURPOSE. The two stylesheets are never loaded together --
   a test asserts this page links neither -- so there is no collision, and identical selectors mean a
   diff of the two blocks is a diff of the look. `.walletHelp` further down this file is the same
   trick for the same reason. The one selector that could not be copied verbatim is the disconnect
   button: the game writes its rules on `#walletDisconnect`, and an ID cannot be shared across two
   documents that name their own nodes, so the declarations are identical and the selector is a
   class.

   🔴 IT IS `fixed` AND OUTSIDE BOTH SURFACES, like the honesty strip above it. The music starts
   past the door, so a control that lived on the sale page would be gone for the whole time there
   was something to mute -- and the room has no menu to reach one by.

   ⚠️ IT SHARES THE STRIP'S Z-INDEX AND THEREFORE THE STRIP'S ONE SURPRISE: **the buy window does
   not cover it**, scrim and all. Measured with `elementFromPoint` over the sound button with the
   window open, and the answer is the button. The reason is nesting rather than arithmetic --
   `#storeWindow`'s 100005 is sealed inside `#storeInside`'s own stacking context at `z-index: 2`,
   while these two are direct children of `#store` at 100003, so the comparison that actually runs
   is 100003 against 2. **It is the behaviour both elements want** and it is why the strip was put
   here in the first place: a disclaimer about figures has to be legible over the window where the
   figures are spent, and a mute button is no use only while there is nothing playing. Do not
   "tidy" either number without re-measuring which context it lands in.
   ================================================================================================= */
.titleChrome {
  position: fixed;
  z-index: 100003;
  top: 12px;
  right: 12px;
  display: flex;
  gap: 8px;
  align-items: center;
}
.titleSound {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 2px solid #2e7d46;
  border-radius: 4px;
  background: #12331f;
  color: #d8f5ef;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}
.titleSound.muted {
  border-color: #3a4a5e;
  background: #0d1526;
  filter: grayscale(1) opacity(0.6);
}
.titleSound:hover,
.titleSound:focus-visible {
  outline: 2px solid #fff7cf;
}
/* 🔴 `:not([hidden])` and it is not decoration, in the original's own words: the `hidden` attribute
   works by a UA rule of `display:none`, so ANY author `display` on the same element beats it and the
   chip shows -- empty address and all -- for everybody who has not connected. */
.walletChip:not([hidden]) {
  display: flex;
  align-items: center;
  gap: 6px;
}
.walletChip {
  padding: 5px 6px 5px 10px;
  border: 2px solid #2e7d46;
  border-radius: 4px;
  background: #0a1839db;
}
/* Plain text and not a button, which is the original's argument and holds here too: five and five is
   enough to recognise which account you are about to spend from, and somebody reading their own
   address must not be able to disconnect by aiming badly. */
.walletAccount {
  color: #9df2ad;
  font: 700 11px monospace;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.walletDisconnect {
  padding: 6px 9px;
  border: 2px solid #17482c;
  background: #2e7d46;
  color: #f6f2cc;
  font: 900 9px monospace;
  letter-spacing: 0.1em;
  cursor: pointer;
}
.walletDisconnect:hover,
.walletDisconnect:focus-visible {
  background: #3a9a57;
  outline: 2px solid #fff7cf;
}

/* 🔴 A-075: the logo carries it and nothing else is named. The owner's instruction of 2026-09-09:
   *"it's not called the Prize room. It's just a store, and there is no need to even write it here,
   just make the logo larger"*. So the wordmark beside it is deleted rather than restyled, and the
   logo gets the size a heading was taking. */
/* 🔴 A-083. THE MASTHEAD IS OUT OF THE READING COLUMN, and that is what lets it be the game's
   size. `.saleIn` is `min(720px, 100%)` because a board of figures wants a measure; at 1280 the
   game's logo paints 807 px, so a title bound by that column could never match the title screen.
   The board keeps its 720; the title does not have to share it. */
.storeHead {
  display: flex;
  justify-content: center;
  width: 100%;
  margin: 0 auto clamp(18px, 3vh, 34px);
  padding: 4px 0;
}

/* 🔴 A-079. THE GAME'S TITLE, WRITTEN HERE RATHER THAN IMPORTED.
   `index.html` builds the same three parts as `.atlasLogo` and styles them in `game/opening.css`.
   Importing that file is the thing A-065 built this stylesheet to avoid: 8.4 KB of title screen,
   intro scroll and registration form on a page that has none of them. So the treatment is copied
   and the numbers are this page's, which is the whole of the difference.

   ⚠️ EVERY SIZE HERE IS SMALLER THAN THE GAME'S, AND THAT IS NOT TIDINESS. The title screen is the
   whole window; `.saleIn` is `min(720px, 100%)`. The game's ATLAS is `clamp(58px, 10.2vw, 126px)`
   and its logo `clamp(340px, 62vw, 780px)`; at 390 px those overflow a phone, and A-071 already
   proved a page that widens itself and then fits what it widened looks fine in a screenshot.
   Two tests hold this: the masthead's text, and the page not scrolling sideways at 390 px.

   The single dark drop-shadow is the game's own override (`.atlasLogo` is redeclared near the end
   of `opening.css` with exactly one). The pair in its first declaration puts a hard cream edge
   under the tagline, which at this size reads as the line printed twice. */
.atlasMark {
  display: grid;
  justify-items: center;
  text-align: center;
  width: 100%;
  /* The game's own override, which is a SINGLE dark shadow. `.atlasLogo`'s first declaration in
     `opening.css` has two, and the second puts a hard cream edge under the tagline; the file
     redeclares it near the end with one, and that is what the title screen paints. */
  filter: drop-shadow(0 7px 0 #0a173c);
}
.atlasMark img {
  display: block;
  width: clamp(340px, 62vw, 780px);
  max-width: 92vw;
  height: auto;
  /* ⚠️ `auto`, not `pixelated`. The logo is a smooth WebP and the game does not pixelate it; the
     store did, which at this size is visible as a hard stair on every curve. */
  image-rendering: auto;
}
.atlasMark strong {
  display: block;
  color: #f6f2cc;
  font: 900 clamp(58px, 10.2vw, 126px) / 1 Impact, system-ui;
  letter-spacing: 0.25em;
  text-indent: 0.25em;
  -webkit-text-stroke: 5px #223767;
}
.atlasMark small {
  display: block;
  margin-top: 12px;
  color: #d8f5ef;
  font: 900 clamp(8px, 1.3vw, 14px) / 1 monospace;
  letter-spacing: 0.3em;
}


/* 🔴 A-075. HER BOX, PINNED TO THE BOTTOM OF THE WINDOW, and it holds her voice and nothing else.

   It takes its shape from `.dialogue` in `game/interactions.css` -- bottom-anchored, one speaker
   label, one paragraph -- and its colours from this page, which is the cream and blue of the
   opening sequence rather than the game's dark green. Both halves of that are deliberate: the
   shape is the game's because this is a conversation in a room, and the palette is this page's
   because a visitor walks from here to the title screen and two families one link apart read as
   two products.

   ⚠️ **Her portrait is gone.** She is standing eight feet away in the same window, and a picture of
   her beside her own speech was a page's idea of a conversation. The game never does it. */
.storeCounter {
  position: fixed;
  z-index: 100004;
  left: 50%;
  bottom: 26px;
  display: grid;
  align-content: center;
  gap: 7px;
  width: min(760px, calc(100% - 40px));
  min-height: 112px;
  padding: 16px 22px;
  border: 4px solid var(--blue);
  outline: 4px solid var(--ink);
  background: var(--panel);
  box-shadow: 0 14px 0 #0c183788, 0 26px 60px #000a;
  transform: translateX(-50%);
}
.clerkWho {
  color: var(--blue);
  font: 700 10px monospace;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
/* 🔴 A-079. MINT JOURNEY, SET APART WHEREVER SHE SAYS IT.
   Dialogue is plain text and this box renders no markup, so no string carries a colour: `writeLine`
   in `game/store.js` finds the name and wraps it. A writer gets the treatment by typing it in
   capitals, which is what keeps the admin editor working.
   ⚠️ A true pastel green is unreadable on the cream box, so this is the game's own button green --
   the greenest value that still holds contrast on `--panel`. */
.mintJourney {
  color: #2e7d46;
  font-weight: 900;
  letter-spacing: 0.04em;
}

/* 🔴 A-079. THE QUESTION'S TWO ANSWERS. YES opens the counter; NOT NOW is the way back, and the
   two are deliberately not the same weight -- a green button beside a grey one says which is the
   ordinary answer without making the other one hard to find. */
.clerkChoice {
  display: flex;
  gap: 10px;
  margin-top: 4px;
}
.clerkChoice[hidden] {
  display: none !important;
}
.clerkChoice button {
  flex: 1;
  padding: 12px 10px;
  font-size: 14px;
}
.clerkNo {
  background: linear-gradient(#5d6f8f 50%, #4b5b78 50%);
}
.clerkNo:hover,
.clerkNo:focus-visible {
  background: #5d6f8f;
}

.clerkLine {
  margin: 0;
  font: 700 17px/1.5 monospace;
  white-space: pre-line;
}

.storeBoard {
  display: grid;
  gap: 10px;
  padding: 14px;
  border: 4px solid var(--blue);
  background: var(--panel);
}
.boardFacts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 10px;
  margin: 0;
}
.boardFacts dt {
  color: var(--muted);
  font: 900 10px monospace;
  letter-spacing: 0.14em;
}
.boardFacts dd {
  margin: 3px 0 0;
  font: 900 14px/1.35 monospace;
}
.supplyBar {
  height: 14px;
  border: 3px solid var(--ink);
  background: #cfd4b8;
}
.supplyBar i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--gold);
}
.boardSchedule {
  display: grid;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.boardSchedule li {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--muted);
  font: 700 11px/1.5 monospace;
}
/* The name takes the slack so the mark sits hard left and the time hard right. Before A-114 this
   row had two children and `space-between` did the job; a third child in the middle needs saying. */
.boardSchedule .phaseName {
  flex: 1;
}
.boardSchedule li.now {
  color: var(--ink);
  font-weight: 900;
}

/* =================================================================================================
   🔴 A-114. THE PER-PHASE MARK, AND ITS THREE STATES ARE NOT TWO.
   =================================================================================================
   `storefront.phaseLock()` decides which one; this file only paints them. The rule is in
   `game/storefront.js` and is written up there.

   🔴 **Grey is a fact about the PHASE. Colour is a fact about the WALLET.** So `.unknown` covers
   both "nobody has connected" and "the list did not load", and it never reads as a refusal --
   which is what lets the board show locks to a visitor who has not connected, as the owner asked,
   without telling a listed wallet it is off a list nobody read.

   ⚠️ **A TICK against a PADLOCK, never two padlocks.** 🔓 and 🔒 differ by the angle of one
   shackle and are indistinguishable at 11px; the owner tried them and said so. Different
   silhouettes mean the colour reinforces rather than carries the whole signal, which is also what
   keeps this legible to a colourblind eye. */
.lockMark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 26px;
  height: 22px;
  border: 3px solid var(--ink);
  font: 900 12px/1 monospace;
  color: var(--panel);
}
.lockMark.yes {
  background: linear-gradient(var(--good) 50%, var(--good-deep) 50%);
}
.lockMark.no {
  background: var(--bad);
}
.lockMark.unknown {
  background: #d7dbcd;
  border-color: #8a94a8;
  color: var(--muted);
}

/* The door, and what it opens onto (A-070, build sheet row 39).

   The board above this is public and stays public, so the only thing that changes at the door is
   whether the room below is on the page. `.storeInside` is a plain grid because the sections it
   holds already carry their own borders and spacing -- it exists to be hidden, not to be seen. */
/* 🔴 A-083. NO CARD AROUND THE WAY IN, on the owner's instruction of 2026-09-11 and as the
   mockup drew it. The board above is a panel because it is a table of figures and a table wants an
   edge. A button with one line under it is not a table, and boxing the two made the page read as
   two documents stacked rather than a title with a door beneath it. */
.storeDoor {
  display: grid;
  gap: 12px;
  padding: 4px 14px;
}
.storeDoor[hidden] {
  display: none !important;
}
/* Under the button and on the sky now, so it takes the title screen's own footer type rather than
   the panel's body type. ⚠️ `pre-line` stays: `refusalLine()` builds two-sentence answers with a
   real newline in them and they are meant to break there. */
/* =================================================================================================
   🔴 A-114. THE DOOR'S LINE IS A BOX, because on that sky it was not being read.
   =================================================================================================
   The owner, 18 September: *"the messages that display below the wallet connect button, they need
   to be inside a box so they know to read them, right now there is just text and they might miss
   it."* It was pale 12px type centred on pixel-art night sky and it carries every invitation,
   every refusal, both retries, and now the wallet check as well.

   🔴 **The ring is `#eef7cd`, which is the CONNECT WALLET button's own outline.** Board, button
   and box are then three things in one material, which is what the owner meant by matching the
   page rather than decorating it.

   ⚠️ `pre-line` stays and is now load-bearing twice over: `refusalLine()` builds two-sentence
   answers with a real newline in them, and `render()` joins the standing wallet line onto the
   invitation with another. */
.doorNote {
  margin: 0;
  padding: 12px 16px;
  border: 4px solid #17264c;
  outline: 4px solid #eef7cd;
  border-radius: 2px;
  background: var(--panel);
  color: var(--ink);
  font: 700 12px/1.7 monospace;
  letter-spacing: 0.06em;
  text-align: center;
  white-space: pre-line;
}
/* 🔴 ONLY A REFUSAL IS RED, and `render()` decides which lines those are: `refused.dated` and
   `refused.undated`, and nothing else. `retry.board` and `retry.list` keep `calm`, because they
   mean *nobody has looked yet* -- the distinction `DOOR` in `game/store.js` is built around, and
   the same one the grey padlock draws on the board above.

   Border and type rather than a red fill: the owner's call, 18 September. This page is meant to be
   kind to somebody who is not on a list, and a filled red panel under the button shouts. */
.doorNote[data-tone="refused"] {
  border-color: var(--bad);
  color: var(--bad);
}
.doorNote:empty {
  display: none;
}
/* 🔴 A-075. PAST THE DOOR THE ROOM OWNS THE WINDOW. `inset: 0`, fixed, above the sale page it
   replaces. It is not a container with gaps any more -- everything inside it is positioned against
   the window, exactly as the game's HUD is positioned against `#viewport`. */
.storeInside {
  position: fixed;
  z-index: 2;
  inset: 0;
}
.storeInside[hidden] {
  display: none !important;
}

/* =================================================================================================
   A-071. THE ROOM, build sheet rows 38 to 40. A-075 gave it the window.

   9 x 10 tiles at the game's TILE = 64, so 576 x 640 px. 🔴 **A-075 REVERSED THE SENTENCE THAT
   STOOD HERE.** It said there was no camera and nothing scrolled, and that was true and load-bearing
   while the room was a panel: the whole room fitted, so a rule that translated the stage would have
   been a camera it did not need.

   Full window it does not fit. A 9-tile-wide room cannot cover a 16:9 window without cropping, so
   `view()` in `game/store-room.js` picks a scale and clamps a camera to the room's own edges --
   `viewMetrics()` and `scene()`'s rule, out of `game/main.js`, because the engine already solved
   this for 425 maps and a second answer here would drift from it. **Both stops of that clamp are
   what stop it showing the void past a wall**, and a test asserts them.

   Everything below is nearest-neighbour. `image-rendering: pixelated` is load-bearing on all of it:
   the room art is 144 x 160 drawn at 4x and the trainer is a 32 x 38 sprite, and the browser's
   default smoothing turns both into a blur. Every sprite in this project is nearest-neighbour for
   the same reason.
   ================================================================================================= */
/*
 * 🔴 `min-width: 0` ON THE GRID ITEMS. A grid item's `min-width` defaults to `auto`, which is its
 * CONTENT's intrinsic width, and this room's content is a 576 px stage. While the room was a panel
 * that pushed the whole track out on any narrower screen: measured on a 390 px phone, the document
 * went 390 px wide before the door and 509 px after it, and the board, the schedule and the honesty
 * strip went with it.
 *
 * ⚠️ **A-075 removed the room from that flow entirely, so this rule no longer protects the room --
 * it protects the SALE PAGE, whose board and schedule are still grid items on a 390 px phone.**
 * `the prize room fits a phone instead of widening the page` still asserts
 * `scrollWidth === clientWidth` on both sides of the door and is still the check that would catch
 * a regression here.
 */
.saleIn > * {
  min-width: 0;
}
/* 🔴 A-075. THE ROOM IS THE VIEWPORT. `position: fixed; inset: 0`, the same two declarations
   `#viewport` carries in `game/styles.css`, and no border: a frame is what made this read as a
   panel on a page, and the world in the game has no edge of its own.

   What the room cannot cover is the title screen's sky rather than flat colour or the room's own
   art dimmed. Art out here is what read as a page; a plain bar reads as a letterbox; the sky reads
   as weather, and it is the sky the game opens on. */
.roomFrame {
  position: fixed;
  inset: 0;
  overflow: hidden;
  background: #07142d url("../assets/ui/store-sky.webp") center / cover no-repeat;
  image-rendering: pixelated;
  touch-action: none;
}
/* Held back so the room stays the lit object. Same gradient as `.saleSky:after`, darker, because
   out here it is behind a room rather than behind type. */
.roomFrame:before {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 0;
  background: linear-gradient(#07142d55, #07142d99);
}
/* 🔴 The stage stays a fixed 576 x 640 and is scaled AND translated from its top-left corner.
   `view()` writes both into one transform; `transform-origin: 0 0` is what makes the translate
   read in room pixels rather than in scaled ones. */
.roomStage {
  position: absolute;
  z-index: 2;
  left: 0;
  top: 0;
  width: 576px;
  height: 640px;
  transform-origin: 0 0;
  will-change: transform;
}
.roomLayer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
  pointer-events: none;
}
/* 🔴 A-075. THE ALLOWANCE, STANDING ON THE COUNTER.

   One pack for every pack this wallet may still buy. `store-room.js` draws them from the figure
   `store.js` hands it, positioned in room pixels on the counter's top surface at y = 206 and
   centred on the clerk whatever the count -- three in front of her, not three pushed to one end of
   a row of five.

   ⚠️ A-079 REVERSED THE DEPTH THIS COMMENT DESCRIBED. See `.counterPack` below. */
.roomPacks {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* 🔴 A-079. THE PACKS DRAW IN FRONT OF THE COUNTER, and this reverses A-075's comment above.
   That reading treated a pack as a thing standing BEHIND the lip, the way a sprite behind the
   counter is. A pack is ON the desk, so the lip is behind it, and the owner found the old order by
   looking: the back lip took the top off all five. 95002 clears `.roomCanopy` at 95000 and the
   interact mark at 95001. The mark cannot hide a pack either way -- `PACK_SLOTS` in
   `game/store-room.js` keeps the trainer's own column empty at every count. */
.counterPack {
  position: absolute;
  z-index: 95002;
  width: 25px;
  image-rendering: pixelated;
  transform: translate(-50%, -100%);
  filter: drop-shadow(0 3px 2px #0a1a2c99);
}
/* 🔴 The foreground, above every sprite. The counter's back lip and the tops of the four prize
   shelves live only in this layer, so a sprite standing behind one is drawn over it without this.
   95000 is the game's own `#foreground` z-index, kept so the two rooms sort alike. */
.roomCanopy {
  z-index: 95000;
}
/*
 * The two people in the room. Sizes and anchors copied from `#player` and `.npc` in
 * `game/map-assets.css` rather than invented: 64 px wide on a 64 px tile, capped at the sprite's
 * own height, and anchored so the FEET land on the tile rather than the box. A trainer who stood
 * differently here than in the game would read as a different trainer one link away.
 */
.roomActor {
  position: absolute;
  width: 64px;
  object-fit: contain;
  image-rendering: pixelated;
  filter: drop-shadow(0 6px 3px #0a1a2c88);
}
#roomPlayer {
  max-height: 76px;
  transform: translate(-50%, -69%);
}
#roomClerk {
  max-height: 80px;
  transform: translate(-50%, -70%);
  cursor: pointer;
}
/* The `!`, over the visitor's head. `#interact` in `game/interactions.css` is the same mark in the
   same place for the same reason, and it is hidden rather than dimmed so it takes hit-testing with
   it: a mark that lingers after the clerk is out of reach is promising a conversation. */
.roomMark {
  position: absolute;
  /* 🔴 A-079: ABOVE THE PACKS, on the owner's instruction of 2026-09-09. A-075 put the packs at 50
     and the mark at 95001, so the mark sat over them; A-079's first pass raised the packs to 95002
     and put the mark underneath, which clipped its corners. 95003 puts the `!` on top of
     everything in the room, which is what a call to attention has to be. It no longer overlaps a
     pack at all -- see `PACK_TOP` in `game/store-room.js` -- so this decides a touching edge
     rather than who hides whom. */
  z-index: 95003;
  width: 32px;
  height: 32px;
  transform: translate(-50%, -100%);
  image-rendering: pixelated;
  pointer-events: none;
  animation: room-mark 0.32s ease-out;
}
.roomMark[hidden] {
  display: none !important;
}
@keyframes room-mark {
  0% {
    scale: 0.4;
    opacity: 0;
  }
  60% {
    scale: 1.15;
  }
  100% {
    scale: 1;
    opacity: 1;
  }
}

/* =================================================================================================
   🔴 A-075. THE CONTROLS ARE A PHONE'S, AND EVERY DECLARATION IS THE GAME'S OWN.

   The owner's rule of 2026-09-09: *"the buttons only display on mobile view only and they should
   display in a similar style as the game. since the controls won't be visible display a quick
   message to press the keys and the E to interact"*.

   So `display: none` here and `display: block` only inside the media query at the foot of this
   file -- the same `(max-width: 700px), (pointer: coarse)` query `game/navigation.css` uses for the
   game's own -- and the geometry, the radial gradients and the gold knob are copied out of
   `.joystick`, `.joystickBase`, `#joystickKnob` and `#touchInteract` in that file rather than
   invented, so the two rooms are operated identically rather than similarly.
   ================================================================================================= */
.roomStick,
.roomTalk {
  display: none;
  position: fixed;
  z-index: 100002;
  bottom: 24px;
  width: 132px;
  padding: 0;
  border: 0;
  background: none;
  color: #b9c8ba;
  font: 800 10px monospace;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  text-align: center;
  touch-action: none;
  user-select: none;
}
.roomStick {
  left: 28px;
  cursor: grab;
}
.roomTalk {
  right: 28px;
  touch-action: manipulation;
  cursor: pointer;
}
.roomStickBase,
.roomTalk i {
  position: relative;
  display: grid;
  place-items: center;
  width: 118px;
  height: 118px;
  margin: auto;
  border: 2px solid #ffffff25;
  border-radius: 50%;
  background: radial-gradient(circle, #213b2bd9 0 31%, #102319ed 33% 68%, #0a1710ed 70%);
  box-shadow:
    inset 0 0 24px #0008,
    0 12px 32px #0007;
  backdrop-filter: blur(10px);
}
.roomTalk i {
  width: 104px;
  height: 104px;
  color: #f1d868;
  font: 900 36px/1 monospace;
  font-style: normal;
}
#roomKnob {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 52px;
  height: 52px;
  border: 2px solid #fff4;
  border-radius: 50%;
  background: linear-gradient(145deg, #f1d868, #b6972c);
  box-shadow:
    inset 0 2px 5px #fff7,
    0 6px 13px #0007;
  transform: translate(-50%, -50%);
  will-change: transform;
}
.roomStick small,
.roomTalk small {
  display: block;
  margin-top: 7px;
  font: inherit;
}
/* Disabled means the clerk is out of reach, and it is dimmed rather than hidden: a control that
   disappears when you walk away is a control people stop looking for. */
.roomTalk[disabled] i {
  border-color: #ffffff12;
  color: #6c7a67;
}

/* 🔴 The hint, and it survives on BOTH branches because the mat is the way out on both. A room
   whose exit nobody finds is a room with no exit, and `the prize room fits a phone instead of
   widening the page` asserts this element is visible on a 390 px screen.

   `.roomKeys` inside it is the desktop half -- the keys, said once, because there is nothing drawn
   to press -- and the media query at the foot of this file hides it on touch. */
.roomHint {
  position: fixed;
  z-index: 100002;
  left: 50%;
  bottom: 26px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
  max-width: calc(100% - 40px);
  margin: 0;
  padding: 10px 18px 11px;
  border: 1px solid #fff2;
  border-radius: 13px;
  background: #102319e8;
  box-shadow: 0 10px 30px #0007;
  color: #f7f4db;
  font: 700 13px/1.4 monospace;
  text-align: center;
  transform: translateX(-50%);
}
.roomHint kbd {
  padding: 2px 6px;
  border-radius: 5px;
  background: #f3d15e;
  color: #1b291d;
  font: 900 11px monospace;
}

/* 🔴 The counter, and it is hidden until the clerk offers it. Build sheet row 40: the buy window
   opens from the conversation and from nothing else.

   A-075 made it an overlay over the room instead of a section below it. The room stays VISIBLE
   behind the scrim rather than being replaced, which is what makes this read as a counter somebody
   walked up to rather than a page they were sent to. */
.storeWindow {
  position: fixed;
  z-index: 100005;
  inset: 0;
}
.storeWindow[hidden] {
  display: none !important;
}
.storeWindowScrim {
  position: absolute;
  inset: 0;
  background: #07142daa;
}
.storeWindowCard {
  position: absolute;
  left: 50%;
  top: 50%;
  display: grid;
  gap: clamp(10px, 1.6vw, 16px);
  width: min(680px, calc(100% - 28px));
  max-height: calc(100% - 28px);
  overflow: auto;
  padding: clamp(14px, 2.4vw, 22px);
  border: 6px solid var(--cream);
  outline: 6px solid var(--ink);
  background: var(--cream);
  box-shadow: 14px 14px 0 #0c1837;
  transform: translate(-50%, -50%);
}
.windowHead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.windowHead strong {
  font: 900 15px monospace;
  letter-spacing: 0.16em;
}
.windowHead span {
  color: var(--muted);
  font: 700 10px monospace;
  letter-spacing: 0.14em;
}
/* Her dialogue box, hidden until she has been spoken to. `.storeCounter` sets `display: grid`, so
   the attribute needs saying out loud here or it does nothing. */
.storeCounter[hidden] {
  display: none !important;
}

/* The shelf. Build sheet row 34: the packs are visible. ⚠️ This said *"even though a credit is
   what is bought"*; A-121 made the thing bought and the thing held one token. */
.storeShelf {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 16px;
  padding: 14px;
  border: 4px solid var(--ink);
  background: var(--panel);
}
/* A-075: the 128 px minting-site render rather than the 32 px Bag icon, at a size that lets the
   illustration be looked at. It is the one picture in the window and it is what is being bought. */
.packArt {
  width: 140px;
  height: 88px;
  object-fit: contain;
  image-rendering: pixelated;
}
/* A-079. The name of the thing over the description of it, in the same green the MINT JOURNEY
   wears, so the shop has one accent rather than two. */
.shelfBody {
  display: grid;
  gap: 7px;
}
.shelfTitle {
  margin: 0;
  color: #2e7d46;
  font: 900 14px/1.3 monospace;
  letter-spacing: 0.04em;
}
.shelfNote {
  margin: 0;
  font: 700 12px/1.6 monospace;
  white-space: pre-line;
}

.storeBuy {
  display: grid;
  gap: 12px;
}
.buyRow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}
.storeStepper {
  display: flex;
  align-items: center;
  gap: 8px;
}
/*
 * 🔴 **WHITE AT REST, GOLD WHEN SELECTED** -- the owner's call, 2026-09-19, against a screenshot of
 * the counter: *"the minus or plus should be default white background and yellow when selected."*
 *
 * It shipped the other way round, and that was backwards on its own terms: **gold is what the
 * selection means everywhere else on this page.** `.buyBack` fills gold when selected and
 * `.pixelButton`'s outline turns gold; the stepper alone wore gold at rest, so the one colour that
 * says *this is the control that will act* was already spent before anything was selected.
 */
.storeStepper button {
  width: 34px;
  height: 34px;
  border: 3px solid var(--ink);
  background: #fff;
  color: var(--ink);
  font: 900 18px/1 monospace;
  cursor: pointer;
}
/*
 * ⚠️ **Hover and focus keep the blue ring and no longer touch the background.** They used to paint
 * white, which was the visible half of the old treatment and is now what the button already is --
 * a hover that changes nothing reads as a dead control. The ring is the browser's own *this has
 * focus*, which is a different question from *this is selected* and still worth answering.
 */
.storeStepper button:hover:not([disabled]),
.storeStepper button:focus-visible:not([disabled]) {
  outline: 3px solid var(--blue);
  outline-offset: 1px;
}
.storeStepper button[disabled] {
  border-color: #8394a2;
  background: #cfd4b8;
  color: #8394a2;
  cursor: default;
}
.buyCount {
  min-width: 30px;
  font: 900 22px/1 monospace;
  text-align: center;
}
.buyTotal {
  margin: 0;
  font: 900 13px/1.5 monospace;
  text-align: right;
}
.buyTerms {
  margin: 0;
  color: var(--muted);
  font: 700 11px/1.6 monospace;
}
.storeActions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  flex-wrap: wrap;
}
/*
 * 🔴 **A-113. THE KEYBOARD'S SELECTION IS A HIGHLIGHT ON THE CONTROL, and it is the game's.**
 *
 * The owner, 2026-09-18: *"there is no arrow and instead it looks like a real button selection...
 * The little arrow that points to the buttons is not a good option."* A-104 had already retired
 * A-094's caret on the game's three cards two days earlier, on the same call. **This page was the
 * half left behind**, and a keyboard that looks different one surface later reads as a different
 * product.
 *
 * 🔴 **Copied from `game/packs.css` declaration for declaration** -- one selection language across
 * the product is the entire point, and a third shade is the thing this rule exists to remove.
 * **Two shapes, because the controls are two shapes:**
 * - **a `.pixelButton`** (YES, NOT NOW, BUY) already carries a 4 px cream outline, and that
 *   outline turns gold. 🔴 **It keeps its own background** -- green already means *pressable*, and
 *   filling one gold would say something about the control rather than about the selection.
 *   ⚠️ **A-113 measured something on the way past and did not fix it:** `.clerkNo`'s grey
 *   (`linear-gradient(#5d6f8f...)`, above) NEVER RENDERS. It is one class, and `.pixelButton`'s
 *   green is one class six hundred lines further down, so source order hands it to the green --
 *   both answers compute `linear-gradient(rgb(58,154,87)...)` in the page. A-079's *"a green
 *   button beside a grey one says which is the ordinary answer"* has never been true on screen.
 *   **It is left alone deliberately**: recolouring an answer is the owner's call, not a side
 *   effect of a selection job, and the outline marks either colour equally well.
 * - **a text control** (`.buyBack`) fills gold with an ink edge instead, because an outline around
 *   10 px type reads as a box rather than as a choice. Its edge is a transparent border it wears
 *   at rest, so selecting it recolours rather than resizes and moves nothing beside it.
 *
 * ⚠️ **What A-082 got right and keeps: the stepper's ± are 34 px square**, and neither a caret nor
 * a gold outline suits one. They take the page's own `:focus-visible` treatment, already in this
 * file a few rules down and already meaning *this control is the one that will act*. Two shapes,
 * one meaning, and neither invented. 🟢 **`game/packs.css` reached the identical answer for the
 * identical control independently**, which is the half of A-082's argument that outlived its
 * caret.
 *
 * ⚠️ **Specificity, and A-104 paid for this one.** In the game `#packLater{background:none}` is an
 * id rule and outranked the class highlight, so CONTINUE never turned yellow in the product while
 * the mockup looked right. Here every marked control is reached by a class: `[data-sel="true"]`
 * plus the class plus `:not([disabled])` is three, against one for each base rule below, so these
 * win wherever they sit in the file. 🔴 **Measured in a rendered page rather than read off the
 * rule** -- the computed `outline-color` and `background-color`, both states, both places.
 *
 * `:not([disabled])` on all of them, because `selectable()` in `game/store.js` filters disabled
 * controls out of the walk -- so a mark on one would be a mark on something `E` refuses.
 */
.pixelButton[data-sel="true"]:not([disabled]) {
  outline-color: var(--gold);
}
.storeStepper button[data-sel="true"]:not([disabled]) {
  background: var(--gold);
  outline: 3px solid var(--blue);
  outline-offset: 1px;
}
.pixelButton {
  padding: 15px 26px;
  border: 4px solid #17264c;
  outline: 4px solid #eef7cd;
  border-radius: 2px;
  background: linear-gradient(#3a9a57 50%, #2e7d46 50%);
  box-shadow: 0 8px 0 #111c3b;
  color: #f6f2cc;
  font: 900 15px/1 monospace;
  letter-spacing: 0.12em;
  cursor: pointer;
}
.pixelButton:hover:not([disabled]),
.pixelButton:focus-visible:not([disabled]) {
  background: #3a9a57;
}
.pixelButton[disabled] {
  background: #7d8a72;
  box-shadow: 0 8px 0 #3c4436;
  cursor: default;
}
.pixelButton[hidden] {
  display: none !important;
}
/* Pinned like the honesty strip and for the same reason: it speaks on both sides of the door and
   there is no page flow left on one of them. */
.storeStatus {
  position: fixed;
  z-index: 100003;
  left: 50%;
  bottom: 6px;
  margin: 0;
  max-width: calc(100% - 24px);
  min-height: 18px;
  color: #dbe7c6;
  font: 700 12px/1.5 monospace;
  text-align: center;
  text-shadow: 0 1px 3px #07142d;
  transform: translateX(-50%);
}
.storeStatus:empty {
  display: none;
}
.storeStatus.error {
  color: var(--bad);
}
/* A-158. A wrong network or a wrong payment, inside the buy window above BUY. The sentence and any action that
   answers it, in the flow so it can never cover the stepper. Calm ink, not red: it is the
   player's wallet and nothing the shop did wrong. */
.buyNotice {
  display: grid;
  justify-items: center;
  gap: 10px;
  margin: 12px 0 0;
  text-align: center;
}
.buyNotice[hidden],
.buyNotice .pixelButton[hidden] {
  display: none !important;
}
.buyNoticeText {
  margin: 0;
  color: var(--ink);
  font: 700 12px/1.6 monospace;
}
.buyNotice .pixelButton {
  max-width: 100%;
  font-size: 12px;
  white-space: normal;
}
.walletHelp {
  color: var(--gold);
  font: 900 11px monospace;
  letter-spacing: 0.14em;
  text-align: center;
}
.walletHelp[hidden] {
  display: none !important;
}

/* What a pack is. Hidden until a purchase lands (build sheet row 31). */
.storeBought {
  display: grid;
  gap: 8px;
  padding: 14px 16px;
  border: 4px solid var(--blue);
  background: #dbe7c6;
}
.storeBought[hidden] {
  display: none !important;
}
.storeBought h2 {
  margin: 0;
  font: 900 15px monospace;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.storeBought p {
  margin: 0;
  font: 700 12px/1.7 monospace;
  white-space: pre-line;
}

/* =================================================================================================
   🔴 A-075. THE ONE QUERY THAT DECIDES WHETHER CONTROLS ARE DRAWN.

   `(max-width: 700px), (pointer: coarse)` is not a number picked here -- it is the query
   `game/navigation.css` uses to decide the game's own joystick and Interact button, quoted so the
   two surfaces agree about what a phone is. A store that drew a thumbstick on a laptop while the
   game did not would be two answers to one question.
   ================================================================================================= */
@media (max-width: 700px), (pointer: coarse) {
  .roomStick {
    display: block;
  }
  .roomTalk {
    display: block;
  }
  /* Nothing to press on a keyboard nobody has. The rest of the hint -- the mat -- stays. */
  .roomKeys {
    display: none;
  }
  /* Above the controls, never behind them. A box that opened under a thumbstick was the one thing
     about the first full-window pass a phone could not read. */
  .storeCounter {
    bottom: 186px;
  }
  .roomHint {
    bottom: 160px;
    font-size: 11px;
  }
  .storeStatus {
    bottom: 142px;
  }
}

/* 🔴 A-086. The chrome's own small-screen rules, at the GAME's breakpoint and not at either of this
   file's two. `game/wallet-access.css` and `game/music.css` both change at 650, and a copy that
   changed somewhere else would look like the game at one width and not at another. */
@media (max-width: 650px) {
  .titleChrome {
    top: 12px;
    right: 12px;
    gap: 6px;
  }
  .titleSound {
    width: 44px;
    height: 44px;
    font-size: 19px;
  }
  .walletChip {
    padding: 4px 5px 4px 8px;
  }
  .walletAccount {
    font-size: 9px;
  }
  /* The word goes, the touch target and the accessible name stay: the button carries
     aria-label="Disconnect wallet" in the markup, so the glyph is never the only signal. */
  .walletDisconnect {
    width: 34px;
    height: 34px;
    padding: 0;
    font-size: 0;
  }
  .walletDisconnect:after {
    content: "\2715";
    font: 900 14px monospace;
  }
}

@media (max-width: 560px) {
  .storeShelf {
    grid-template-columns: 1fr;
  }
  /* 🔴 A-086. THE HONESTY STRIP GETS OUT FROM UNDER THE CHROME. Both are `fixed` at the top and
     the game has only one of them, so this collision is this page's alone: measured at 390 px the
     strip's 46ch reaches 288 px and the chrome starts at 218 px, and they overlapped. The strip
     keeps its full width and drops below instead -- 12 px top plus a 44 px touch target is 56, and
     this clears it. */
  .storeFixture {
    top: 64px;
  }
  .storeCounter {
    width: calc(100% - 20px);
    padding: 13px 15px;
  }
  .clerkLine {
    font-size: 15px;
  }
  .packArt {
    width: 108px;
    justify-self: center;
  }
  .buyTotal {
    text-align: left;
  }
}

/* A-079. The way out of the counter, under the buy button and deliberately quiet: it is a way back
   rather than a second thing to weigh up, and the mat is four tiles away behind a scrim. */
/* 🔴 A-113. The counter's NOT NOW is a text link rather than a `.pixelButton`, so it takes the
   game's OTHER shape: `#packLater` fills gold with an ink edge, and so does this.
   🔴 **A fill, not a gold underline, and the reason is this control specifically: it is underlined
   ALREADY, at rest, all the time.** An underline treatment could therefore only recolour a mark
   that is on screen either way -- a hue change on 1 px of 10 px type, which is a difference a
   player has to be told about rather than one they see. A fill changes the shape of the thing.
   The ink edge is a transparent border it wears at rest, so the selection recolours the box
   instead of growing it; `outline` is deliberately left alone here so `:focus-visible` keeps the
   ring the browser draws, which `.pixelButton` has already spent on its frame. */
.buyBack {
  padding: 8px 12px;
  border: 3px solid transparent;
  background: none;
  color: var(--blue);
  font: 900 10px monospace;
  letter-spacing: 0.16em;
  text-decoration: underline;
  cursor: pointer;
}
.buyBack[data-sel="true"]:not([disabled]) {
  border-color: var(--ink);
  background: var(--gold);
  color: var(--ink);
}
.buyBack:hover,
.buyBack:focus-visible {
  color: var(--ink);
}

/* =================================================================================================
   🔴 A-108. THE DEV PHASE SWITCHER.
   =================================================================================================
   Bottom-left and fixed, so it survives the door: past it the store is full-screen (A-075) and row
   13 asks for the phase to change with a visitor already standing in the room. The honesty strip
   is top-left at 100003, and the counter's window is 100005, so this sits one above the highest
   thing on the page -- a switcher a buy window covers is a switcher you cannot reach mid-scenario.

   Collapsed by default. A 390px phone is the target and fourteen presets standing open would be
   the page.
   ============================================================================================== */
#devSwitcher {
  position: fixed;
  z-index: 100006;
  left: 12px;
  bottom: 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
  max-width: min(44ch, calc(100% - 24px));
}

#devSwitcherToggle {
  min-height: 44px;
  padding: 8px 12px;
  border: 0;
  border-left: 3px solid var(--bad);
  border-radius: 3px;
  background: #07142de6;
  color: #f0d4c4;
  font: 700 10px/1.5 monospace;
  letter-spacing: 0.08em;
  cursor: pointer;
}

/* 🔴 A-114. The note a press leaves behind, saying what to watch for. Two of the fifteen presets
   prove an ABSENCE -- a visitor who is not ejected, a read that retries -- and the owner read both
   as "nothing happened". Row 13's note also says it needs row 4 pressed first, because it is the
   only one of the fifteen that is not self-contained. */
#devPresetNote {
  margin: 8px 0 0;
  padding: 8px 9px;
  max-width: 340px;
  border-left: 3px solid var(--gold);
  background: rgba(23, 37, 74, 0.55);
  color: #e7eed6;
  font: 600 10px/1.55 monospace;
  white-space: normal;
}
#devSwitcherList {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 52vh;
  overflow-y: auto;
  padding: 8px;
  border-left: 3px solid var(--bad);
  border-radius: 3px;
  background: #07142df2;
}

/* The list is a flex column, so `hidden` alone loses to it. Same reason `.storeFixture` carries
   this declaration. */
#devSwitcherList[hidden] {
  display: none !important;
}

.devPreset {
  min-height: 38px;
  padding: 6px 10px;
  border: 1px solid #2b4a7e;
  border-radius: 3px;
  background: #0d1f3d;
  color: #e9efc1;
  font: 700 10px/1.4 monospace;
  text-align: left;
  cursor: pointer;
}

.devPreset:hover,
.devPreset:focus-visible {
  border-color: var(--gold);
  color: var(--gold);
}

/* =============================================================================================
   A-166. THE STALE-PAGE NOTICE -- the game's `.staleNotice` from `packs.css` (A-159), COPIED
   rather than imported, the way this file copies from `opening.css`. Above every layer this page
   has (the status line is 100003), because it is the only word on the page that is still true.
   ============================================================================================= */
.staleNotice{position:fixed;z-index:100010;left:50%;top:14px;transform:translateX(-50%);
  display:flex;align-items:center;gap:14px;width:min(620px,calc(100% - 32px));box-sizing:border-box;
  padding:12px 14px;border:4px solid #17254a;outline:4px solid #e9efc1;background:#fffef0;
  color:#17254a;box-shadow:8px 8px 0 #0c1837}
.staleNotice[hidden]{display:none!important}
.staleNotice p{margin:0;flex:1;font:700 12px/1.5 monospace}
.staleReload{flex:none;padding:10px 14px;border:4px solid #17254a;background:#f7d85c;color:#17254a;
  font:900 13px/1 monospace;letter-spacing:.08em;cursor:pointer}
.staleReload:focus-visible{outline:3px solid #244c93;outline-offset:2px}
@media(max-width:480px){.staleNotice{flex-direction:column;align-items:stretch;text-align:center}}
