/* ---------------------------------------------------------------------------
   Glow's front door.

   Realised from research/arena-golive-2026-09-30/title-screen/title.html, a
   standalone design pass. Its idea, in one line: the screen is lit by one lamp,
   and the two marks that matter, the stone and the word, are each painted warm
   on the lamp side and cold on the other. That is not decoration, it is the
   vision model the match itself renders with, printed large (vision.js: warm
   white is what you can see, cold blue is what a teammate can see).

   Every colour is either a token lifted verbatim from viewer/game-ui.css or a
   light constant lifted verbatim from viewer/arena/vision.js. Nothing was
   invented by eye.

   The house law it obeys (research/menu-design-rehaul.md): one moss glass panel,
   brass accents, an 11px type floor with NO exceptions token, one bordered
   container per screen region with interior structure done by background step
   instead, exactly one butter-gold call to action per stage, and one glowing
   element per screen, which is the lamp.

   Class names all carry the `hold-` prefix. index.html's HUD already owns
   `.panel`, `.card`, `.slot`, `.bar` and `.mode`, and a door that quietly
   restyled the vitals panel would be a very funny bug to find during a fight.
   --------------------------------------------------------------------------- */

/* The repo's own display stack. A failed fetch is a monospace door, not a
   broken one: `font-display: swap` plus the monospace tail below. Digits come
   from the rounded face because Pixelify's misread at every size (a 2 reads as
   Z, a 59 as S9), which is the same split game-ui.css makes.

   Both URLs are relative to THIS STYLESHEET, which is the same rule client.js
   follows for its art and its health check. A `url("/assets/...")` here would
   ask the ORIGIN ROOT, and beneath the router's token-gated /glow/ mount
   that is the public Worldhopper site, which does not serve the arena's tree at
   all — so the door silently fell back to monospace there. index.html's import
   map fixes the same problem for the 26 root-absolute `/sim/` specifiers, but an
   import map cannot reach a stylesheet. Pinned by scripts/arena.spec.mjs, which
   loads the mounted page and fails on any request that leaves the mount. */
@font-face {
  font-family: "Pixelify Sans";
  src: url("./assets/fonts/pixelify-sans-latin.woff2") format("woff2");
  font-weight: 400 700;
  font-display: swap;
}
/* The painterly pass (2026-10-05): the wordmark's two faces, both OFL files
   already in this repo (viewer/assets/fonts/OFL-PTSerif.txt, LICENSE-DejaVu.txt).
   Same relative-URL rule as above. */
/* perf-05 remainder (round-4 performance judge): the door draws the face from
   a 28 KB Latin subset (scripts/glow-title-font-subset.mjs writes it and these
   ranges) instead of the 165 KB gzipped TTF. The full TTF stays declared for
   every other code point, so a name in another script still sets in the face;
   the browser fetches it only when such a character is on screen. */
@font-face {
  font-family: "Glow Serif";
  src: url("./assets/fonts/PTSerif-Bold.ttf") format("truetype");
  font-weight: 700;
  font-display: swap;
  unicode-range: U+0-1F, U+7F-9F, U+100-2012, U+2015-2017, U+201F-2021, U+2023-2025, U+2027-10FFFF;
}
@font-face {
  font-family: "Glow Serif";
  src: url("./assets/fonts/PTSerif-Bold-latin.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
  unicode-range: U+20-7E, U+A0-FF, U+2013-2014, U+2018-201E, U+2022, U+2026;
}
/* "Glow Caps" is the bundled Barlow Condensed at 500 (aesthetics-21, round-4
   judge, 2026-10-08). It was DejaVu Sans, a Linux system face, which made a
   fourth typeface beside Glow Serif, Glow Condensed and Display Digits on the
   door, the tutorial keycaps, Moments and the clip page. Declared at 400 so
   every existing `400 ... "Glow Caps"` (CSS and canvas) takes the 500 file;
   the house is three faces now. DejaVu stays second in every stack. */
@font-face {
  font-family: "Glow Caps";
  src: url("./assets/fonts/barlow-condensed-latin-500.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Display Digits";
  src: url("./assets/fonts/fredoka-digits.woff2") format("woff2");
  font-weight: 400 800;
  font-display: swap;
  unicode-range: U+0030-0039;
}
/* The one condensed UI face, bundled (polish-hud, 2026-10-08). The HUD, the
   round cards, the shop and the door's microcopy all asked for "Avenir Next
   Condensed", which only Apple ships: Windows drew Arial Narrow, Android its
   default sans, and the score box a terminal monospace beside both. Barlow
   Condensed (OFL, viewer/assets/fonts/OFL-BarlowCondensed.txt), the Latin
   subset, four static weights of ~15 KB. Measured against Avenir Next
   Condensed on the HUD's own strings it sets within 1-7% of the same width,
   so nothing laid out for the Mac face reflows. Avenir stays second in every
   stack, so a Mac shows the same shapes while the file swaps in.
   index.html preloads the 400, the weight on almost every label. */
@font-face { font-family: "Glow Condensed"; src: url("./assets/fonts/barlow-condensed-latin-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Glow Condensed"; src: url("./assets/fonts/barlow-condensed-latin-500.woff2") format("woff2"); font-weight: 500; font-display: swap; }
@font-face { font-family: "Glow Condensed"; src: url("./assets/fonts/barlow-condensed-latin-600.woff2") format("woff2"); font-weight: 600; font-display: swap; }
@font-face { font-family: "Glow Condensed"; src: url("./assets/fonts/barlow-condensed-latin-700.woff2") format("woff2"); font-weight: 700 900; font-display: swap; }

#hold {
  /* ---- reused verbatim from viewer/game-ui.css ------------------------- */
  --ui-ink: #f2e6bd;
  --ui-muted: #a9b79e;
  --ui-dim: #73806b;                      /* disabled and hairlines only */
  --ui-brass: #c6a85b;
  --ui-brass-hi: #f2e6bd;
  --ui-hairline-soft: rgba(198, 168, 91, .16);
  --ui-moss-0: #09110d;                   /* the water: a cistern grows algae */
  --ui-moss-1: #152119;
  --ui-danger: #e77d72;
  /* the one gold CTA recipe, game-ui.css line 1086, unchanged */
  --ui-gold-a: #f1d27d;
  --ui-gold-b: #c99443;
  --ui-gold-edge: #7a5f38;
  --ui-gold-ink: #22170e;
  /* the one keyboard focus ring, everywhere on the door: cream, the colour
     selection already carries (menus-ux-15, 2026-10-08: it was pale green on
     some rows, cream on golds and brass on menu rows). */
  --ui-focus: #f2e6bd;

  /* ---- new tokens, same naming style, taken from arena/vision.js ------- */
  --ui-lamp: #f0b054;        /* LAMP_RGB  240,176,84  the light lighting you  */
  --ui-lamp-core: #fff3de;   /* SELF_RGB  255,243,222 what you can see        */
  --ui-mate: #56a8d8;        /* MATE_RGB   86,168,216 what a crewmate can see */
  --ui-stone-0: #04060b;     /* DARK_RGB     4,6,11   unlit cistern           */
  --ui-stone-1: #070b13;
  --ui-stone-2: #0b111a;
  --ui-stone-3: #101722;
  --ui-fog: #10141e;         /* SMOKE_RGB   16,20,30  fog, never a hole       */
  --ui-fog-lit: #1d2534;
  --ui-cold-ink: #8b98a8;    /* cold ink: locked things and far things        */
  --ui-cold-dim: #7b8896;

  --hold-display: "Display Digits", "Pixelify Sans", ui-monospace, "SF Mono", Menlo, monospace;
  /* The wordmark's faces (the painterly pass, 2026-10-05). */
  --hold-serif: "Glow Serif", "PT Serif", Georgia, "Times New Roman", serif;
  --hold-caps: "Glow Caps", "DejaVu Sans", "Helvetica Neue", Arial, sans-serif;
  /* The body face is the rounds lane's condensed UI sans, not a terminal
     monospace: three independent judges (research/glow-judging-2026-10-02/
     baseline, every front-door screen) read lowercase monospace microcopy as
     "a web prototype layered over nice art". One body face across the door,
     the lobby and the round overlays, bundled as "Glow Condensed" (2026-10-08). */
  --hold-body: "Glow Condensed", "Avenir Next Condensed", "Arial Narrow", "Helvetica Neue", sans-serif;

  /* the one light ramp, hard stepped. A pixel game ramps light in bands. */
  --hold-ramp: linear-gradient(97deg,
      var(--ui-lamp-core) 0 13%,
      var(--ui-lamp) 13% 33%,
      var(--ui-brass) 33% 51%,
      #7d8a97 51% 69%,
      #4b5a6d 69% 86%,
      #33415a 86% 100%);

  /* Six hard bands still, but all of them WARM, and that is the one place this
     departs from the door's own ramp rather than re-stopping it.
     The ramp above breaks at 51%, and title.md records why: it landed on the
     boundary of GLOW, so NAME was lit and HOLD was in shadow. That break
     was a property of an eight character word. GLOW has four, so a six band
     ramp that runs into the cold blues gives roughly one band per letter and
     the word reads as four different materials rather than one lit object: a
     white G, an orange L, a brass O and a grey-blue W. Rendered and rejected,
     research/arena-glow-2026-10-02/shots/.
     The cold end was there to make HOLD recede, and the franchise line now does
     that job above, so the hero does not need to recede at all. Weighted to the
     lamp instead: the first 40% is lamp-core, and the fall-off stays in brass.
     That is also what the concept plate shows, a bright word with a dark edge,
     and it keeps one light in the room.
     2026-10-03: the dark brown tail is gone. The judged round read the last W
     as "muddier and lower contrast than the others" (research/glow-judging-
     2026-10-02/baseline, title), so every band is now a lit warm: cream, butter
     gold, lamp and deep gold, and the word reads as one lit object. */
  --hold-ramp-lit: linear-gradient(97deg,
      var(--ui-lamp-core) 0 30%,
      var(--ui-gold-a) 30% 56%,
      var(--ui-lamp) 56% 80%,
      var(--ui-gold-b) 80% 100%);

  /* The hero line's size, as ONE named number the franchise line divides, so
     the two lines cannot drift out of proportion in a media query. */
  --hold-mark-size: clamp(64px, 11.5vw, 150px);
  /* The franchise line, as a fraction of the hero, and the tracking that makes
     that fraction work. The pair is one decision: at any ratio there is exactly
     one tracking at which WORLDHOPPER's eleven characters measure the same
     width as GLOW's four, and sharing that edge is what makes two lines a
     lockup instead of two stacked words.
     The ratio comes off the concept plate, whose franchise line is 29px of cap
     against the hero's ~120 and whose two lines measure 663 and 683 px, so:
     matched measure, hero about four times the franchise. The plate's own
     0.242 is not reachable here, because it is drawn in a narrow display serif
     and this is a pixel face whose caps advance 0.602 em: holding the measure
     at 0.242 needs 0.56 em of tracking, which stops being a word. 0.30 is as
     far toward the plate as this face goes with a tracking that still reads.
     Derived in research/arena-glow-2026-10-02/, pinned by naming.test.mjs. */
  --hold-mark-ratio: .30;
  --hold-mark-track: .344em;
  --hold-mark-gap: clamp(7px, 1.5vh, 17px);

  /* ============================================================ the cistern
     Wet stone in hard steps, not a gradient pretending to be a wall. Darkest
     at the edges because the lamp is one lamp and it does not reach the
     corners. Slot 9 in the ladder, which is where #join stood: #buy is 8 and
     #binds is 12, and a rebind card still has to be able to open over this. */
  position: fixed;
  inset: 0;
  z-index: 9;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: clamp(16px, 3vh, 34px) 16px;
  overflow-y: auto;
  /* The lamp's pool is 118vmax across on purpose, so the room is lit past the
     edge of the screen. Without this it is also a horizontal scrollbar on a
     phone, where #hold is the scroll container rather than the page. */
  overflow-x: hidden;
  overscroll-behavior: contain;
  color: var(--ui-ink);
  font: 400 13px/1.5 var(--hold-body);
  background:
    radial-gradient(120% 90% at 22% 8%,
      var(--ui-stone-3) 0 24%, var(--ui-stone-2) 24% 46%,
      var(--ui-stone-1) 46% 70%, var(--ui-stone-0) 70%);
}
/* The attribute has to win over the display above it. */
#hold[hidden] { display: none; }
/* animation-06: the outgoing screen's copy (screen-fade.js fadeGhost). It is
   inert already; this makes every part of it click-through too, so nothing on
   the new screen ever waits on the fade. */
.ui-ghost, .ui-ghost * { pointer-events: none !important; }

#hold svg { image-rendering: pixelated; }

/* One focus ring for everything, lifted from game-ui.css. */
#hold :where(button, [role="button"], input, a, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--ui-focus);
  outline-offset: 2px;
}

/* The water. A cistern holds water, and the water is green black, not blue
   black: --ui-moss-0 doing the one job moss is honestly for down here. */
.hold-water {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: clamp(64px, 13vh, 124px);
  z-index: 0;
  pointer-events: none;
  background:
    linear-gradient(101deg, rgba(240, 176, 84, .075) 0 26%, rgba(240, 176, 84, .02) 26% 44%, transparent 44%),
    linear-gradient(180deg, var(--ui-moss-1) 0 2px, var(--ui-moss-0) 2px 100%);
}

/* ============================================================ the one lamp
   In the corner on every stage, because it is a fixture of the room and not of
   the menu. Drawn on an integer grid: hard pixel rectangles, no glow spam. The
   law allows one glowing element per screen. This is it. */
.hold-lamp {
  position: absolute;
  top: clamp(14px, 3.4vh, 38px);
  left: clamp(14px, 3.6vw, 54px);
  z-index: 2;
  width: clamp(24px, 3vw, 36px);
  pointer-events: none;
}
.hold-lamp svg { display: block; width: 100%; height: auto; }
.hold-pool {
  position: absolute;
  left: 46%; top: 56%;
  width: 118vmax; height: 118vmax;
  translate: -50% -50%;
  pointer-events: none;
  /* hard stepped, exactly the way vision.js steps SELF_TINT */
  background: radial-gradient(closest-side,
    rgba(240, 176, 84, .17) 0 9%,
    rgba(240, 176, 84, .105) 9% 20%,
    rgba(240, 176, 84, .055) 20% 34%,
    rgba(240, 176, 84, .022) 34% 50%,
    transparent 50%);
  animation: hold-breathe 4600ms ease-in-out infinite alternate;
}
.hold-flame { animation: hold-breathe 4600ms ease-in-out infinite alternate; }
/* vision.js: "A slow breath, so a lamp never sits on the screen as a dead sprite." */
@keyframes hold-breathe { from { opacity: .86; } to { opacity: 1; } }

/* ============================================================ the vault
   Four nested arches. Each ring steps darker as it recedes, and then the far
   plug steps back up, because fog scatters light: the end of the cistern is
   PALER than the shadow beside you. The stone stands against it, backlit, with
   one warm edge. That is the whole picture. */
.hold-vault {
  position: relative;
  z-index: 0;
  width: min(556px, 86vw);
  margin-bottom: clamp(10px, 2.2vh, 24px);
}
.hold-vault svg { display: block; width: 100%; height: auto; }
.hold-vault[hidden] { display: none; }

/* ============================================================ the wordmark
   Lit from the left by the same lamp, in the same six band ramp, with a hard
   two pixel drop and no blur. The gate's own wordmark tracks at .12em; this one
   tracks at .14em and is otherwise its sibling. */
.hold-word {
  position: relative;
  z-index: 3;
  display: inline-block;
  margin: 0;
  font: 400 clamp(40px, 8.4vw, 92px)/.94 var(--hold-display);
  letter-spacing: .14em;
  padding-left: .14em;
  text-align: center;
  background: var(--hold-ramp);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(2px 2px 0 var(--ui-stone-0));
}
.hold-word[hidden] { display: none; }

/* ---- the two line mark: WORLDHOPPER over GLOW ----------------------------
   THE PRIMARY LOCKUP of docs/art/glow-identity-2026-10-05 (15), set as real
   type over the clean plate rather than baked in: WORLDHOPPER small and widely
   letterspaced in a plain sans, a thin brass rule with a diamond on it, GLOW
   huge in a high-contrast serif, brushed ivory with a jade inner light and a
   luminous four-point star inside the O.

   The faces are the repo's own OFL files, served from this tree (see the
   @font-face block at the top): PT Serif Bold for the hero, DejaVu Sans for the
   franchise line. Dan, 2026-10-05: "The title menu right now is a little too
   pixelated for me... make it look a little bit more polished." So the pixel
   logo (viewer/arena/glow-logo.js) is retired from the page and kept as an
   asset for the promo cards. */
.hold-mark {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0;
  color: var(--ui-ink);
}
.hold-mark[hidden] { display: none; }

.hold-mark-franchise {
  /* Never under 11px: the corner lockup on the tutorial and the other inner
     stages is 46px, which put WORLDHOPPER at 8.74px (aesthetics-21). */
  font: 400 max(11px, calc(var(--hold-mark-size) * .19))/1 var(--hold-caps);
  letter-spacing: .46em;
  padding-left: .46em;   /* tracking trails the last letter; pad to recentre */
  color: var(--ui-ink);
  text-shadow: 0 1px 0 rgba(4, 6, 11, .8), 0 0 18px rgba(4, 6, 11, .9);
}

/* The rule, from the study: a brass hairline that fades at both ends with a
   small diamond on it, two thirds of the franchise line's measure. */
.hold-mark-rule {
  display: flex;
  align-items: center;
  gap: .5em;
  width: 70%;
  margin: calc(var(--hold-mark-size) * .07) 0 calc(var(--hold-mark-size) * .02);
  color: var(--ui-brass);
  font-size: calc(var(--hold-mark-size) * .1);
}
.hold-mark-rule::before, .hold-mark-rule::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: linear-gradient(90deg, transparent, currentColor 35%, var(--ui-gold-a) 50%, currentColor 65%, transparent);
}
.hold-mark-rule svg { width: .9em; min-width: 7px; height: auto; fill: var(--ui-gold-a); filter: drop-shadow(0 0 4px rgba(241, 210, 125, .8)); }

/* The hero line. Ivory brushed toward jade at the foot, with the glow the word
   is named for: a soft turquoise bloom, never a hard pixel drop. */
.hold-mark-line { position: relative; display: block; }
.hold-mark-hero {
  display: inline-block;
  font: 700 var(--hold-mark-size)/1 var(--hold-serif);
  letter-spacing: .06em;
  padding-left: .06em;
  background-image: linear-gradient(180deg, #fffaf0 0%, var(--ui-ink) 42%, #d9e3d0 64%, #8fd6d8 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 2px 0 rgba(4, 6, 11, .75))
          drop-shadow(0 0 calc(var(--hold-mark-size) * .12) rgba(101, 200, 205, .55));
}
/* The O is plain inline text, painted by the parent's `background-clip: text`.
   It must NOT be positioned or an inline-block: either one puts it in a box the
   parent's clip does not paint, and Chrome draws GL W around a star. (A
   positioned O did exactly that in headless Chromium, 2026-10-05.) */
.hold-mark-o { position: static; }
/* The star's anchor: a zero-size box at the O's ORIGIN on the baseline, so
   the star is placed from the glyph's measured ink, not from the inline box
   (whose centre is the font's ascent/descent midpoint, well above the O's).
   PT Serif Bold, measured in the browser at 1000px: the O's ink runs
   x 39..714 and y -12..712, so its optical centre is .3765em right of the
   origin and .35em above the baseline, at every size. */
.hold-glint-at {
  display: inline-block;
  position: relative;
  width: 0;
  height: 0;
  vertical-align: baseline;
  /* The breath lives on this HTML anchor, about the star's own centre (the
     svg's left and bottom below), not on the svg: Chrome never hands an SVG
     element's animation to the compositor, so the star restyled and repainted
     the wordmark on the main thread every frame (glow-r4-power-css). */
  transform-origin: .3765em -.35em;
  animation: hold-glint 4600ms ease-in-out infinite alternate;
}
/* The star in the O: a tall, thin four-point glint (long vertical spikes,
   short arms) over a soft jade core, after the PRIMARY lockup of the wordmark
   study (docs/art/glow-identity-2026-10-05, 15). The same slow breath the lamp
   used to carry, so the screen has one pulse. */
.hold-glint {
  position: absolute;
  left: .3765em;
  bottom: .35em;
  width: .56em;
  height: .56em;
  translate: -50% 50%;
  overflow: visible;
  pointer-events: none;
  fill: #f6fffd;
  filter: drop-shadow(0 0 .03em #fff) drop-shadow(0 0 .1em rgba(101, 200, 205, .95)) drop-shadow(0 0 .22em rgba(101, 200, 205, .55));
}
.hold-lockup.is-flat .hold-glint { filter: drop-shadow(0 0 .03em #fff) drop-shadow(0 0 .08em rgba(101, 200, 205, .9)); }
@keyframes hold-glint { from { opacity: .78; scale: .94; } to { opacity: 1; scale: 1.04; } }

/* The compact lockup, top left of the deeper stages: the FLAT variant of the
   study, small, no bloom. */
.hold-lockup.is-flat {
  --hold-mark-size: 46px;
  position: absolute;
  top: clamp(14px, 2.6vh, 28px);
  left: clamp(16px, 2.8vw, 40px);
  /* fb-glow-front (Dan, 2026-10-06): "This Worldhopper should be centered
     above the Glow". Both lines centre on one axis, as on the title. */
  align-items: center;
  margin: 0;
  padding: 4px 6px;
  border-radius: 2px;
  /* ...and it is the home button. */
  cursor: pointer;
  transition: opacity 160ms ease;
}
.hold-lockup.is-flat:hover { opacity: .82; }
.hold-lockup.is-flat .hold-mark-rule { width: 100%; }
.hold-lockup.is-flat .hold-mark-hero {
  background-image: linear-gradient(180deg, #fffaf0, var(--ui-ink) 55%, #bfe3df);
  filter: drop-shadow(0 1px 0 rgba(4, 6, 11, .8)) drop-shadow(0 0 10px rgba(101, 200, 205, .35));
}
.hold-lockup[hidden] { display: none; }

/* A failed `background-clip: text` must be a flat warm word, never a warm BOX
   with a transparent word punched in it. This has to sit AFTER both ramps are
   assigned, because `@supports` adds no specificity and source order is the
   only thing deciding which background wins. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .hold-word, .hold-mark-hero { color: var(--ui-ink); background-image: none; }
}
/* ============================================================ the press beat
   Sibling of the gate's #gpress: two hairline wings and a slow pulse. Lowercase
   and untracked, because the menu law forbids tracked lowercase and because a
   tracked capital label is the tell every generated screen wears.

   No gold here, deliberately: gold marks the one thing to do on a stage that
   offers choices, and this stage offers none. */
#hold-press {
  position: relative;
  z-index: 3;
  margin-top: clamp(16px, 3.4vh, 34px);
  min-height: 48px;
  padding: 14px 34px;
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--ui-lamp-core);
  font: 400 15px/1 var(--hold-body);
  cursor: pointer;
  animation: hold-pulse 2800ms ease-in-out infinite;
}
#hold-press::before, #hold-press::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 28px;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 243, 222, .5));
}
#hold-press::before { right: 100%; }
#hold-press::after { left: 100%; rotate: 180deg; }
@keyframes hold-pulse { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }

/* ============================================================ the panels
   Scrim law: a takeover surface dims the room behind it. The press stage is not
   a takeover, so it does not. */
.hold-panel {
  position: relative;
  z-index: 3;
  width: min(516px, 100%);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.hold-panel[hidden] { display: none; }
#hold:not([data-stage="press"])::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: rgba(4, 6, 11, .58);
}

/* ---- a menu row. Interior structure is a background step, never a border.
        Two rows on this screen have earned a border and they say so below. */
.hold-row {
  display: grid;
  grid-template-columns: 34px 1fr auto;
  align-items: center;
  gap: 4px 14px;
  min-height: 58px;
  padding: 11px 16px;
  border: 0;
  border-radius: 2px;
  background: rgba(16, 23, 34, .74);
  color: var(--ui-ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 140ms linear;
}
.hold-row svg { grid-row: 1 / 3; width: 30px; height: auto; }
.hold-row b {
  grid-column: 2;
  font: 400 20px/1.1 var(--hold-display);
  letter-spacing: .06em;
}
.hold-row span {
  grid-column: 2;
  color: var(--ui-muted);
  font-size: 12px;
  line-height: 1.35;
}
.hold-row:hover { background: rgba(26, 36, 50, .86); }
/* Selection is a 2px cream stroke, per the law. Focus ring may coexist. */
.hold-row:focus-visible { background: rgba(26, 36, 50, .9); box-shadow: inset 0 0 0 2px var(--ui-brass-hi); }

/* ---- the one gold call to action. Gold is where the light is. */
.hold-go {
  background: linear-gradient(var(--ui-gold-a), var(--ui-gold-b));
  border: 1px solid var(--ui-gold-edge);        /* BORDER 1 of 2 on this screen */
  color: var(--ui-gold-ink);
}
.hold-go span { color: rgba(34, 23, 14, .8); }
.hold-go:hover { background: linear-gradient(#f6dc94, #d5a04c); }
/* The background has to be restated here, and this is not a tidiness rule.
   `.hold-row:focus-visible` is a two-part selector and `.hold-go` is a one-part
   selector, so the moment a keyboard landed on the gold call to action it lost
   its gold and painted its dark gold ink onto the dark row fill: the one row on
   the screen that matters, unreadable, exactly when somebody is using the
   keyboard. The supplied design file has the same hole and never showed it,
   because nothing in it ever took focus on its own. */
.hold-go:focus-visible {
  background: linear-gradient(#f6dc94, #d5a04c);
  box-shadow: inset 0 0 0 2px var(--ui-gold-ink);
}
.hold-go[hidden] { display: none; }
.hold-go:disabled { opacity: .45; cursor: default; }

/* ---- which body you are bringing ----------------------------------------
   Two cards, side by side, pictures first. The house rule is pictures over copy
   and the law is a border budget of two on this screen, both already spent (the
   gold call to action and the locked row), so a card's state is a BACKGROUND
   step and a cream stroke on the chosen one, exactly as a menu row's is.

   The silhouettes are placeholders (viewer/arena/classes.js) and are drawn in
   `currentColor` so they take the card's own temperature: the chosen body is lit
   warm like everything else you can see on this screen, and the other one is
   cold like everything a teammate can see. That is the match's own vision model,
   which is what the whole door is painted out of. Real art swaps in as an <img>
   at the same size with no CSS change. */
.hold-pick { display: grid; gap: 7px; }
.hold-pick-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0;
  color: var(--ui-brass);
  font: 400 11px/1.2 var(--hold-body);
  letter-spacing: .06em;
}
.hold-pick-head em { font-style: normal; color: var(--ui-muted); letter-spacing: 0; }
/* A separator, or the heading and its qualifier run together into one sentence
   that parses wrong: "Pick a body change it between rounds". */
.hold-pick-head em::before { content: '\00b7'; margin-right: 7px; }
.hold-pick-row { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.hold-pickcard {
  display: grid;
  /* The last row takes the slack, so the stat bars sit at the same height on both
     cards however many lines the sentence under them wraps to. Two stat rows that
     do not line up are two pictures instead of one comparison. The bar row itself
     is a flex row (.hold-pick-bars), so the count is whatever STAT_COLUMNS holds:
     it went from four to five when `punch` was added on 2026-10-02 and this file
     needed no edit. */
  grid-template-rows: auto auto auto auto 1fr;
  justify-items: center;
  gap: 3px;
  padding: 9px 8px 10px;
  border: 0;
  border-radius: 2px;
  background: rgba(16, 23, 34, .74);
  color: var(--ui-cold-ink);
  font: inherit;
  cursor: pointer;
  transition: background 140ms linear, color 140ms linear;
}
.hold-pickcard:hover { background: rgba(26, 36, 50, .86); }
.hold-pickcard.is-on { background: rgba(38, 30, 18, .82); color: var(--ui-lamp); }
/* The chosen card wears the cream stroke selection carries everywhere else on
   this screen, and the focus ring is allowed to sit on top of it. */
.hold-pickcard.is-on { box-shadow: inset 0 0 0 2px var(--ui-brass-hi); }
/* The focus ring is an INSET OUTLINE, not a second 2px stroke. Both as strokes
   and the keyboard made the card it was merely sitting on look more chosen than
   the one that actually was: the stage focuses its first item when the gold call
   to action is hidden, which in the muster it is until the roster is drawn, so the
   very first thing a lobby showed was a contradiction. */
.hold-pickcard:focus-visible {
  background: rgba(26, 36, 50, .9);
  outline: 2px solid var(--ui-gold-edge);
  outline-offset: -5px;
}
.hold-pickcard.is-on:focus-visible { background: rgba(38, 30, 18, .82); }
/* A fixed box with the art bottom-aligned inside it, so the two bodies share one
   ground line and the height difference is the first thing read. */
.hold-pick-body { display: flex; align-items: flex-end; justify-content: center; height: 76px; }
.hold-pick-art { width: auto; height: 100%; max-width: 100%; color: inherit; fill: currentColor; }
.hold-pickcard b {
  font: 400 19px/1.1 var(--hold-display);
  letter-spacing: .06em;
  color: var(--ui-ink);
}
.hold-pickcard.is-on b { color: var(--ui-lamp-core); }
.hold-pick-tag { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--ui-muted); }
/* Four columns of pips. Vertical, because four side-by-side bars read as one
   shape the eye can compare across two cards without reading any label. */
.hold-pick-bars { display: flex; gap: 8px; margin-top: 2px; }
.hold-pick-bar { display: flex; flex-direction: column-reverse; align-items: center; gap: 2px; font-style: normal; }
/* The unlit track has to stay VISIBLE. A column with nothing lit is a real
   reading — the Doe has no armour at all — and a track too faint to see turns
   "none of this" into "this bar is missing", which is a different claim. */
.hold-pick-bar u { width: 12px; height: 3px; border-radius: 1px; background: rgba(139, 152, 168, .45); }
.hold-pick-bar u.on { background: var(--ui-cold-ink); }
.hold-pickcard.is-on .hold-pick-bar u.on { background: var(--ui-lamp); }
.hold-pickcard.is-on .hold-pick-bar u { background: rgba(240, 176, 84, .28); }
.hold-pick-line { align-self: start; margin-top: 4px; font-size: 11px; line-height: 1.35; color: var(--ui-muted); text-align: center; }
.hold-pick-note { margin: 0; font-size: 11px; color: var(--ui-lamp); }
.hold-pick-note[hidden] { display: none; }
/* In the muster the roster is the thing being read, so the picker shrinks to the
   two bodies and drops the bars and the sentence. */
.hold-pick.is-compact .hold-pick-body { height: 44px; }
.hold-pick.is-compact .hold-pick-bars,
.hold-pick.is-compact .hold-pick-line { display: none; }
.hold-pick.is-compact .hold-pickcard b { font-size: 15px; }
/* Which body a seat in the muster brought. A quiet third column on the row, not
   a chip: the roster is already dense and this is the least urgent thing on it. */
.hold-seatclass {
  text-decoration: none;
  font-size: 11px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ui-brass);
}
.hold-seatline.is-bot .hold-seatclass { color: var(--ui-cold-dim); }

/* The same gold recipe as a plain button rather than a three-column row: Start,
   Ready and the road back out of a finished match have nothing to put in an
   icon column and nothing to say underneath. */
.hold-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  padding: 12px 22px;
  border-radius: 2px;
  font: inherit;
  cursor: pointer;
}
.hold-cta b { font: 400 17px/1.15 var(--hold-display); letter-spacing: .05em; }
.hold-cta[hidden] { display: none; }
/* A readied-up row is not the thing left to do, so it gives the gold back and
   keeps a tick. The word on it never changes. */
.hold-cta:not(.hold-go) { background: rgba(26, 36, 50, .86); color: var(--ui-ink); }
.hold-tick { display: none; width: 16px; height: 16px; margin-right: 9px; color: var(--ui-lamp); }
[aria-pressed="true"] > .hold-tick { display: block; }

/* ---- the padlock on a locked tab (Leaderboards' RANKED). */
.hold-lock {
  grid-column: 3; grid-row: 1 / 3;
  width: 20px !important;
  color: var(--ui-brass);
  opacity: .72;
}

/* ---- the quiet tertiary row: sound, and the way back out of the cistern. */
.hold-aside {
  display: flex;
  gap: 8px;
  margin-top: clamp(8px, 2vh, 20px);
}
.hold-ghost {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 8px 14px;
  border: 0;
  border-radius: 2px;
  background: rgba(255, 255, 255, .035);
  color: var(--ui-muted);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
  transition: background 140ms linear, color 140ms linear;
}
.hold-ghost:hover { background: rgba(255, 255, 255, .075); color: var(--ui-ink); }
.hold-ghost svg { flex: none; width: 20px; height: auto; }
/* One source of truth for the toggle: the state IS the picture. */
#hold-sound .hold-slash { display: none; }
#hold-sound[aria-pressed="false"] .hold-slash { display: inline; }
#hold-sound[aria-pressed="false"] .hold-waves { display: none; }
.hold-ghost i { display: block; font-style: normal; }
.hold-ghost em { display: block; font-style: normal; font-size: 11px; color: var(--ui-dim); }
.hold-ghost:hover em { color: var(--ui-muted); }
/* Back reads as one step, Leave reads as leaving: different jobs, so Back is
   not dressed as a second exit. */
.hold-ghost.hold-solo { flex: 0 0 auto; padding-right: 20px; }

/* ---- a refusal. Its own element, because a status line is polite and a
        refusal has to be read. fb-glow-front (Dan, 2026-10-06): it used to sit
        in the flow above the panel as a 12px strip, which pushed the whole
        lobby down until READY fell below the fold. It is an overlay toast now:
        fixed, out of the flow, readable, and it never moves anything. */
.hold-deny {
  position: fixed;
  top: clamp(12px, 2.4vh, 24px);
  left: 50%;
  translate: -50% 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0;
  padding: 12px 20px 12px 16px;
  width: max-content;
  max-width: min(560px, calc(100vw - 32px));
  border-radius: 2px;
  background: var(--ui-stone-2);
  box-shadow: inset 0 0 0 1px var(--ui-gold-edge), inset 3px 0 0 var(--ui-danger), 0 14px 40px rgba(0, 0, 0, .6);
  color: var(--ui-ink);
  font: 400 15px/1.35 var(--hold-body);
  letter-spacing: .02em;
  pointer-events: none;
  animation: hold-deny-in 180ms ease-out;
}
.hold-deny::before {
  content: "!";
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--ui-danger);
  color: var(--ui-stone-0);
  font: 700 14px/1 var(--hold-serif);
}
@keyframes hold-deny-in { from { opacity: 0; translate: -50% -8px; } to { opacity: 1; translate: -50% 0; } }
@media (prefers-reduced-motion: reduce) { .hold-deny { animation: none; } }
.hold-deny[hidden] { display: none; }


/* ============================================================ the muster
   Two crews side by side. Interior structure by background step: the crew
   column is a step up from the room and the rows are a step up from that, and
   neither wears a border. */
.hold-crews {
  display: flex;
  gap: 8px;
  align-items: flex-start;
}
.hold-crew {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 11px;
  border-radius: 2px;
  background: rgba(11, 17, 26, .72);
}
.hold-crew h3 {
  margin: 0 0 4px;
  color: var(--ui-brass);
  font: 400 11px/1.2 var(--hold-body);
  letter-spacing: .06em;
}
.hold-seatline {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 4px 8px;
  border-radius: 2px;
  background: rgba(26, 36, 50, .7);
  font-size: 11px;
}
.hold-seatline.is-open { background: rgba(26, 36, 50, .28); }
.hold-seatline b {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 400 12px/1.2 var(--hold-display);
  letter-spacing: .03em;
}
.hold-seatline.is-self b { color: var(--ui-lamp-core); }
.hold-seatline em { font-style: normal; font-size: 11px; color: var(--ui-muted); }
.hold-seatline.is-bot b, .hold-seatline.is-bot em { color: var(--ui-cold-ink); }

.hold-chip {
  min-height: 44px;
  padding: 6px 10px;
  border: 0;
  border-radius: 2px;
  background: rgba(255, 255, 255, .05);
  color: var(--ui-ink);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
  transition: background 140ms linear;
}
.hold-chip:hover { background: rgba(255, 255, 255, .1); }
.hold-step {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
}
.hold-step span { flex: 1 1 auto; text-align: center; color: var(--ui-muted); font-size: 11px; }
.hold-step .hold-chip { min-width: 44px; }

/* The invite. A field rather than a line of text, so a tap selects the whole
   link on a browser whose clipboard API is not available. */
.hold-invite-row {
  display: flex;
  gap: 6px;
  align-items: center;
  padding: 7px 9px;
  border-radius: 2px;
  background: rgba(11, 17, 26, .72);
}
.hold-invite-row[hidden] { display: none; }
.hold-invite-row label { flex: none; color: var(--ui-muted); font-size: 11px; }
#hold-invite {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  border: 0;
  border-radius: 2px;
  padding: 6px 9px;
  background: rgba(4, 6, 11, .6);
  color: var(--ui-ink);
  font: inherit;
  font-size: 11px;
  user-select: text;
  -webkit-user-select: text;
}

/* ============================================================ results
   The road out. MATCH_END used to be terminal and the client's whole response
   was the words `match over` in a 12px element, so a player who finished a
   match had nowhere to go. */
.hold-results { align-items: center; text-align: center; }
#hold-outcome {
  margin: 0;
  color: var(--ui-ink);
  font: 400 clamp(20px, 4vw, 26px)/1.15 var(--hold-display);
  letter-spacing: .05em;
}
#hold-score {
  margin: 6px 0 14px;
  color: var(--ui-lamp-core);
  font: 400 34px/1 var(--hold-body);
  font-variant-numeric: tabular-nums;
  letter-spacing: .06em;
}
.hold-tallies { width: 100%; }
.hold-tallies[hidden] { display: none; }
.hold-tallies table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.hold-tallies th {
  padding: 4px 8px;
  color: var(--ui-muted);
  font-weight: 400;
  text-align: right;
}
.hold-tallies th:first-child, .hold-tallies td:first-child { text-align: left; }
.hold-tallies td { padding: 5px 8px; text-align: right; }
.hold-tallies tr.is-mine td { background: rgba(240, 176, 84, .07); }
.hold-tallies tr.is-theirs td { background: rgba(86, 168, 216, .06); }
.hold-tallies b { font-weight: 400; color: var(--ui-lamp-core); }
.hold-tallies u { text-decoration: none; color: var(--ui-dim); font-size: 11px; }
.hold-results .hold-go { margin-top: 14px; justify-content: center; }

/* ============================================================ leaving
   The dissolve into the match. One line, and then the door is gone. */
.hold-leaving { align-items: center; text-align: center; }
.hold-leaving p {
  margin: 0;
  color: var(--ui-muted);
  font-size: 12px;
  letter-spacing: .08em;
}
#hold[data-stage="leaving"] { animation: hold-dissolve 520ms ease-in forwards; }
@keyframes hold-dissolve { to { opacity: 0; } }
/* Held at full while the match rehearses under it (front-door.js leaveSoon);
   `playing()` lets it go. `html` outranks the drop-in's own animation rule. */
html #hold[data-stage="leaving"][data-rehearse] { animation-play-state: paused; }
/* AND IT TAKES NO CLICKS WHILE IT IS DISSOLVING, which is the half of this
   rule that was missing and that cost a playtest.
   `forwards` holds the last keyframe, so from 520ms until `hide()` runs the
   door is at opacity 0 -- invisible -- while it is still `position: fixed;
   inset: 0`, still `pointer-events: auto` and still above the canvas at
   z-index 9. Every pointer event on the screen lands on it, and the screen
   underneath looks like an ordinary live match.
   Measured on 2026-10-05: in that state 520 of 520 sampled points across the
   viewport hit-tested INSIDE #hold -- including `input#name`. Dan's playtest
   recorder caught the same thing on the shipping build: the moment where he
   said "I can't get him to look right no matter what I do" recorded the
   pointer as sitting on `div#hold "PLAYER The name it calls you by..."` for
   11.1 seconds, over a frame that shows a live round. With the aim bound to a
   `mousemove` on the canvas, that is a body that cannot be turned.
   The leaving stage is a one-way dissolve into a match with nothing on it to
   press, so it never wanted the pointer. Taking it away costs nothing and
   stops an invisible panel owning the screen. See facing.test.mjs. */
#hold[data-stage="leaving"] { pointer-events: none; }

/* A closed disclosure, one 11px line, holding the fifteen move yards and the
   two dev hints that used to sit on the player's title screen. Reachable, off
   the shot. `?gym=1` and `?yard=<move>` still walk straight past the door. */
.hold-dev {
  margin-top: clamp(8px, 2vh, 18px);
  color: var(--ui-dim);
  font-size: 11px;
}
.hold-dev summary { min-height: 22px; cursor: pointer; }
.hold-dev summary:hover { color: var(--ui-muted); }
.hold-dev p { margin: 8px 0 0; line-height: 1.6; }
.hold-dev a { color: var(--ui-muted); }

.hold-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ============================================================ narrow
   The vault keeps its share of the screen, the wordmark gives some back. */
@media (max-width: 460px) {
  .hold-vault { width: 90vw; }
  .hold-row { grid-template-columns: 28px 1fr auto; gap: 3px 12px; }
  .hold-row svg { width: 26px; }
  .hold-row b { font-size: 18px; }
  .hold-aside { flex-direction: column; }
  .hold-ghost.hold-solo { flex: 1 1 auto; }
  /* Two crews will not fit side by side at 390px without the names truncating
     to three letters, so they stack. */
  .hold-crews { flex-direction: column; }
}
/* Roomy desktops let the vault run bigger. It is the shot. */
@media (min-width: 1024px) and (min-height: 760px) {
  .hold-vault { width: min(660px, 52vw); }
}
/* Short landscape phones: the vault is the first thing to give way. The mark
   gives way through its own size token, so the franchise line shrinks with the
   hero and the shared edge survives the media query. */
@media (max-height: 520px) {
  .hold-vault { width: min(360px, 44vw); margin-bottom: 8px; }
  #hold { --hold-mark-size: clamp(34px, 6.4vw, 60px); }
}

@media (prefers-reduced-motion: reduce) {
  .hold-pool, .hold-flame, #hold-press, .hold-glint-at { animation: none; }
  #hold-press { opacity: 1; }
  #hold[data-stage="leaving"] { animation: none; }
}

/* The picker stays two columns at every width: the two cards ARE the comparison,
   and stacking them turns a glance into a scroll. It shrinks instead. */
@media (max-width: 420px) {
  .hold-pick-body { height: 62px; }
  .hold-pickcard b { font-size: 16px; }
  .hold-pick-bars { gap: 5px; }
  .hold-pick-bar u { width: 9px; }
}

/* ---- the class kit and the weapon pick (config.mjs CLASS_KITS) -------------
   Each card says what the body carries, as glyphs with the one-line kit under
   them, and offers its weapons as chips OUTSIDE the card button (a button may
   not hold a button). The column wrapper keeps the two cards on one grid row. */
.hold-pickcol { display: grid; grid-template-rows: 1fr auto; gap: 4px; min-width: 0; }
/* A sixth row for the kit, and it is the one that takes the slack, so both
   cards' kits sit on one line at the foot whatever the sentence above wraps to. */
.hold-pickcol > .hold-pickcard { height: 100%; grid-template-rows: auto auto auto auto auto 1fr; }
.hold-pick-kit { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; align-self: end; gap: 2px 4px; margin-top: 3px; }
.hold-pick-kit i { display: inline-flex; width: 20px; height: 20px; color: var(--ui-lamp); }
.hold-pick-kit i .gl { width: 100%; height: 100%; }
.hold-pick-kit em { flex-basis: 100%; font-style: normal; font-size: 11px; line-height: 1.3; color: var(--ui-cold-ink); text-align: center; }
.hold-pick-arms { display: flex; flex-wrap: wrap; justify-content: center; gap: 3px; }
.hold-pick-arm {
  display: inline-flex; align-items: center; gap: 3px;
  min-height: 30px; padding: 3px 5px;
  border: 0; border-radius: 2px;
  background: rgba(16, 23, 34, .74);
  box-shadow: inset 0 0 0 1px rgba(198, 168, 91, .3);
  color: var(--ui-muted);
  font: inherit; font-size: 11px; letter-spacing: .04em;
  cursor: pointer;
}
.hold-pick-arm .gl { width: 16px; height: 16px; flex: none; }
.hold-pick-arm b { font-weight: 400; }
.hold-pick-arm:hover { color: var(--ui-ink); box-shadow: inset 0 0 0 1px var(--ui-brass-hi); }
.hold-pick-arm.is-on { background: rgba(38, 30, 18, .86); color: var(--ui-lamp-core); box-shadow: inset 0 0 0 1px var(--ui-brass-hi); }
.hold-pick-arm:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
.hold-pick.is-compact .hold-pick-kit em { display: none; }

/* fb-glow-front: the home lockup sits top left on every sub-stage, so no stage
   title may run under it. Stages whose panel reaches the top-left corner keep
   a band clear for it on a desktop window (the identity stages already pad
   their own top). */
@media (min-width: 761px) {
  #hold:is([data-stage="moments"], [data-stage="board"], [data-stage="watching"], [data-stage="settings"], [data-stage="tutorial"]):has(> #hold-lockup:not([hidden])) {
    padding-top: clamp(92px, 11vh, 120px);
  }
}

/* ---- glow-guest-door: the GUEST tag (guest-tag.js) ---------------------------------
   One small cold tag beside every guest's name: chip, seat line, scoreboard,
   results, kill feed, Moments cards, clip page. Outside #hold too (the feed
   and the clip page), so every colour carries its own fallback. 11px floor. */
.glow-guesttag {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 5px 1px;
  border: 1px solid var(--ui-cold-ink, #8b98a8);
  border-radius: 2px;
  font: 400 11px/1.1 var(--hold-body, "Glow Condensed", "Avenir Next Condensed", "Arial Narrow", sans-serif);
  font-style: normal;
  letter-spacing: .16em;
  text-decoration: none;
  text-transform: uppercase;
  color: var(--ui-cold-ink, #8b98a8);
  vertical-align: middle;
  white-space: nowrap;
}
#feed .glow-guesttag { margin: 0 4px 0 5px; padding: 0 4px; line-height: 1.3; }
/* The display above beats the browser's own [hidden], so the menu chip's tag
   (hidden for a MOSS character) showed GUEST beside a signed-in name. */
.glow-guesttag[hidden] { display: none; }

/* ---- net-connection: RECONNECTING (client.js netWatch, 2026-10-08) -----------------
   The door's voice over a match whose frames stopped coming: one lamp and one
   word, centred, over the world it froze on (dimmed, still visible, so the
   player can see the match is holding, not gone). Nothing to press, so it takes
   no pointer. Outside #hold, so every token carries the door's own value as its
   fallback, like .glow-guesttag. Above #hold (9) and the buy card, because a
   drop on the results screen gets the same card. No shadows. */
#net-wait {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(16, 20, 30, .42);
  pointer-events: none;
}
#net-wait[hidden] { display: none; }
#net-wait .nw-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  min-width: 180px;
  padding: 18px 28px 16px;
  border: 1px solid var(--ui-hairline-soft, rgba(198, 168, 91, .16));
  border-radius: 4px;
  background: rgba(16, 20, 30, .86);
  color: var(--ui-ink, #f2e6bd);
}
/* The lamp breathes: the only motion, and the only sign it is still trying. */
#net-wait .nw-lamp {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--ui-lamp-core, #fff3de) 0 22%, var(--ui-lamp, #f0b054) 23% 48%, rgba(240, 176, 84, 0) 72%);
  animation: nw-breathe 1.8s ease-in-out infinite;
}
#net-wait .nw-word {
  font: 400 14px/1 "Glow Caps", "DejaVu Sans", "Helvetica Neue", Arial, sans-serif;
  letter-spacing: .3em;
  margin-right: -.3em;
  color: var(--ui-brass, #c6a85b);
}
#net-wait .nw-held {
  font: 400 13px/1.3 "Glow Condensed", "Avenir Next Condensed", "Arial Narrow", sans-serif;
  color: var(--ui-muted, #a9b79e);
}
#net-wait .nw-held[hidden] { display: none; }
@keyframes nw-breathe {
  0%, 100% { opacity: .45; transform: scale(.82); }
  50% { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) { #net-wait .nw-lamp { animation: none; } }
/* menus-ux-40: over the shop (a reload reopens it) the card sat on the spell
   cards like a stuck tooltip. There it docks as one line above the shop's
   head, clear of its cards, and the dim says the shop is waiting too. */
body.is-shopping #net-wait { align-items: flex-start; padding-top: 18px; }
body.is-shopping #net-wait .nw-card { flex-direction: row; gap: 14px; padding: 10px 22px; }
body.is-shopping #net-wait .nw-lamp { width: 14px; height: 14px; }
