:root {
  color-scheme: dark;
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  color: #f2eadc;
  background: #17151e;
  font-synthesis: none;
  --lime: #d9f795;
  --amber: #efb665;
  --border: #665b6e;
  /* Hand copies, used until scene.json arrives (view-fit.test.mjs checks both against manifest.json): the base
     picture's aspect `aspect`, 1440 / 1212, and the width `cropWidth` below which the frame stops shrinking and
     crops. arcade.mjs reads them, then takes scene.json's. */
  --aspect: 1.188119;
  --crop-w: 670.9px;
}
* {
  box-sizing: border-box;
}
button,
input,
a {
  -webkit-tap-highlight-color: transparent;
}
button,
a {
  touch-action: manipulation;
}
button,
input {
  font: inherit;
}
button {
  cursor: pointer;
}
button:disabled {
  cursor: default;
  opacity: 0.5;
}
a {
  color: inherit;
}
button {
  color: inherit;
}
button:focus-visible,
a:focus-visible,
input:focus-visible {
  outline: 3px solid var(--lime);
  outline-offset: 4px;
}
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}
.skip {
  position: absolute;
  top: -100px;
  z-index: 50;
  padding: 14px;
  background: #222;
}
.skip:focus {
  top: 0;
}
/* The status line (#139) is read out as it changes and shown in the About panel. On the page it shows when the
   shelf could not load, and while a browse shortcut has focused it. */
.preview-status {
  position: fixed;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.preview-status.failed,
.preview-status:focus {
  z-index: 7;
  left: 50%;
  bottom: max(16px, env(safe-area-inset-bottom));
  width: auto;
  max-width: calc(
    100% - 32px - 2 * max(env(safe-area-inset-left), env(safe-area-inset-right))
  );
  height: auto;
  overflow: visible;
  clip-path: none;
  white-space: normal;
  translate: -50% 0;
  text-align: center;
  font:
    12px/1.5 ui-monospace,
    monospace;
  background: #17151e;
  color: #f2eadc;
  border: 2px solid var(--border);
  border-radius: 7px;
  padding: 8px 12px;
}
.preview-status:focus {
  outline: 3px solid var(--lime);
  outline-offset: -3px;
}
/* Measures the device's safe-area insets for the room's frame (arcade.mjs `insets`). */
.safe-area {
  position: fixed;
  visibility: hidden;
  pointer-events: none;
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
    env(safe-area-inset-bottom) env(safe-area-inset-left);
}
/* The header floats over the room (#139), translucent, over the wall above the frame; the room's frame starts
   under it (arcade.mjs writes its height into the room as `insetTop`). No backdrop blur: it would recomposite
   the WebGL canvas under it on every animated frame. */
.control-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 6;
  display: flex;
  gap: 12px;
  align-items: center;
  padding: max(12px, env(safe-area-inset-top))
    max(22px, env(safe-area-inset-right)) 12px
    max(22px, env(safe-area-inset-left));
  /* Zoomed in, a tall header must not eat the window: it scrolls inside half of it, and the room keeps the rest. */
  max-height: 50vh;
  max-height: 50dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 4px solid #493e4bcc;
  border-top: 0;
  border-radius: 0 0 14px 14px;
  background: linear-gradient(#47414ee0, #2b2935d6 58%, #3d333bd6);
  box-shadow:
    inset 0 2px 2px #a2929766,
    0 8px 12px #100e1766;
}
.wordmark {
  flex-shrink: 0;
  text-decoration: none;
  font-size: clamp(21px, 2.55vw, 38px);
  font-weight: 900;
  letter-spacing: -1.5px;
  color: #f6f5ec;
  border: 3px solid #62566a;
  border-radius: 7px;
  padding: 9px 18px;
  background: #171c28;
  box-shadow:
    inset 0 4px 8px #090b11,
    0 2px #111;
}
.wordmark span {
  color: var(--lime);
  text-shadow: 0 0 13px #bfe89055;
}
.control-bar nav {
  display: flex;
  gap: 6px;
}
.nav-key {
  white-space: nowrap;
  min-height: 49px;
  display: flex;
  gap: 9px;
  align-items: center;
  justify-content: center;
  border: 2px solid #71616e;
  border-radius: 6px;
  padding: 10px 17px;
  background: linear-gradient(#393448, #211f30);
  box-shadow:
    inset 0 2px #544858,
    0 4px 0 #151522;
  color: #d9c8d7;
  font-weight: bold;
  font-size: 15px;
}
.account {
  max-width: 12rem;
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;
  flex-shrink: 0;
}
.account[hidden] {
  display: none;
}
.nav-key.active {
  background: linear-gradient(#ffd496, #df9d50);
  border-color: #ab7746;
  color: #171923;
  box-shadow:
    inset 0 2px #ffefd1,
    0 4px 0 #533c30,
    0 0 17px #eea56340;
}
/* Search waits behind its own key only in the landscape-phone rail. */
.search-toggle {
  display: none;
}
.search-toggle.has-query {
  border-color: #bdcc8d;
  color: var(--lime);
}
.search {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 90px;
  border: 3px solid #756474;
  border-radius: 7px;
  padding: 6px 12px;
  background: #1c1b26;
  color: #b7a1b7;
  box-shadow: inset 0 2px 9px #0c0c12;
}
.search span {
  font-size: 32px;
  line-height: 1;
}
.search input {
  width: 100%;
  min-width: 0;
  background: transparent;
  border: 0;
  padding: 7px 0;
  color: #f5eeee;
  font-size: 15px;
}
.search input::placeholder {
  color: #a597a6;
}
.library {
  font-size: 14px;
}
.library[aria-pressed="true"] {
  background: linear-gradient(#4a4360, #2b2739);
  border-color: #bdcc8d;
  color: #f4e5cd;
  box-shadow:
    inset 0 2px #6b5f7d,
    inset 0 0 0 1px #bdcc8d,
    0 4px 0 #151522,
    0 0 15px #d9f79540;
}
.library small {
  color: var(--lime);
  font-size: 12px;
}
/* The page is the room (#139): it fills the window, the header floats over it, and nothing scrolls. */
body {
  margin: 0;
}
.room {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100svh;
  height: 100dvh;
  overflow: hidden;
  background: #0b0c16;
}
/* The clip is the whole window and holds the canvas, which the room fills at the window's aspect (Fit.Layout);
   past what the shell covers it shows the room's clear colour, matched here. The stage is the frame: where the
   room's base picture sits, placed by arcade.mjs from view-fit.mjs `frameFor` (the room's own arithmetic), so
   the poster, the TV copy and the buttons keep their base percent. Before that runs it fills the clip at the
   picture's aspect. The stage is a container, so the overlays on it (.browse-status) scale with it. */
.room-clip {
  position: absolute;
  inset: 0;
  overflow: clip;
  background: #0b0c16;
}
.room-stage {
  position: absolute;
  left: var(--frame-x, 0px);
  top: var(--frame-y, 0px);
  width: var(--frame-w, 100%);
  height: var(--frame-h, auto);
  aspect-ratio: var(--aspect);
  container-type: inline-size;
}
/* A control over part of the room that the window leaves out (the light switch and the roadmap board in a
   portrait crop) is not offered. */
.out-of-view {
  display: none;
}
/* The canvas covers the window, but the poster covers only the frame, so the canvas stays invisible (it still
   draws, for the first-draw check) until a room has qualified: no splash or clear colour shows round the poster. */
#room-canvas {
  visibility: hidden;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
#room-canvas.live {
  visibility: visible;
}
.room-poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
/* From 6:5 the <picture> serves the wide poster (the room at its widest painted aspect, 7:3, at the frame's
   height): it keeps the frame's height and is centred on the frame, so its middle is the base poster and wider
   windows show its sides, as the live room does. The clip cuts what the window leaves out. */
@media (min-aspect-ratio: 6/5) {
  .room-poster {
    left: 50%;
    right: auto;
    width: auto;
    aspect-ratio: 2828 / 1212;
    transform: translateX(-50%);
  }
}
#room-canvas[hidden],
.room-poster[hidden] {
  display: none;
}
/* The vertical pan surface over the band the game shelves fill (placed by the host, hidden when nothing scrolls). */
#shelf-drag {
  position: absolute;
  pointer-events: auto;
  touch-action: none;
}
#shelf-drag[hidden] {
  display: none;
}
#shelf-hotspots {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* Each button's hit area is its cartridge padded by half the gap to its neighbours, so taps land on small
   phone stages without overlapping; ::after draws the cartridge's own box (--inset-x/-y, set by the host). */
.shelf-hotspot {
  position: absolute;
  pointer-events: auto;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.shelf-hotspot::after {
  content: "";
  position: absolute;
  inset: var(--inset-y, 0) var(--inset-x, 0);
  border-radius: 4px;
  transition:
    box-shadow 0.15s ease,
    background-color 0.15s ease;
}
/* Hover only where a pointer hovers, so a tap on a phone leaves no stuck ring. */
@media (hover: hover) {
  .shelf-hotspot:hover::after {
    box-shadow:
      0 0 0 2px rgba(255, 230, 160, 0.5),
      0 0 18px rgba(255, 210, 120, 0.35);
  }
}
.shelf-hotspot:focus-visible {
  outline: none;
}
/* Focus rings on the stage are drawn inside their control, so they never paint over a neighbouring
   cartridge on short stages. */
.shelf-hotspot:focus-visible::after {
  outline: 3px solid var(--lime);
  outline-offset: -3px;
}
.browse-status button:focus-visible {
  outline-offset: -3px;
}
/* Search and the library light the matching game cartridges and disable the rest. */
.highlighting .shelf-hotspot[data-game]:enabled::after {
  box-shadow:
    0 0 0 2px var(--lime),
    0 0 16px rgba(217, 247, 149, 0.45);
}
.shelf-hotspot:disabled {
  cursor: default;
  opacity: 1;
}
.shelf-hotspot:disabled::after {
  background: rgba(9, 10, 20, 0.72);
  box-shadow: none;
}
/* The status sits in the band below the lowest cartridges (padded bottom row ends at 81.2% of the stage). It
   is one line, 3em tall at most, so with the font floor it clears the shelf down to a 200px stage. */
.browse-status {
  position: absolute;
  left: 50%;
  bottom: 2%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 0.8em;
  /* Of the frame, but never wider than what the window shows beside the rail (the stage is centred in the safe
     rectangle and may run off its sides in a portrait crop). */
  max-width: min(
    96cqw,
    calc(100vw - var(--inset-left, 0px) - var(--inset-right, 0px) - 16px)
  );
  padding: 0.35em 0.4em 0.35em 0.8em;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: #17151ee6;
  font-size: clamp(11px, 1.8cqw, 15px);
  line-height: 1.2;
  white-space: nowrap;
  /* It fades in and out with the light and dim (#58). render() in arcade.mjs
     keeps the text until this has run, so a fade out never shows an empty box. */
  transition: opacity 0.15s ease;
}
#results {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Kept rendered while idle, so the status stays a live region. */
#room-stage:not(.highlighting) .browse-status {
  opacity: 0;
  pointer-events: none;
}
.browse-status button {
  flex: none;
  border: 1px solid #806f78;
  border-radius: 4px;
  background: #241f2a;
  padding: 0.3em 0.6em;
  font-size: inherit;
}
/* The committed projection of the CRT screen (arcade-ui/room/manifest.json `screen`, its bounding rectangle;
   `screenFrame` for the transform, #50; and `screenShape` for the clip, the glass outline in the frame's own
   units, #62). The host applies the same geometry from
   scene.json, so a pick before the metadata arrives boots on the right geometry. */
.tv-content {
  position: absolute;
  left: 47.357%;
  top: 6.958%;
  width: 26.908%;
  height: 22.748%;
  clip-path: polygon(3.65% 7.83%, 5.32% 7.05%, 16.12% 5.32%, 27.01% 3.83%, 43.55% 2.04%, 60.32% 0.8%, 77.33% 0.13%, 94.56% 0.03%, 96.36% 0.57%, 97.92% 2.05%, 99.01% 4.24%, 99.46% 6.81%, 99.99% 34.17%, 99.76% 61.45%, 98.79% 88.62%, 98.3% 91.21%, 97.18% 93.49%, 95.61% 95.13%, 93.82% 95.87%, 76.72% 97.78%, 65.46% 98.72%, 48.77% 99.65%, 37.76% 99.95%, 21.45% 99.92%, 5.36% 99.35%, 3.7% 98.77%, 2.25% 97.32%, 1.24% 95.21%, 0.81% 92.76%, 0.21% 73.28%, 0% 53.71%, 0.18% 34.05%, 0.75% 14.28%, 1.17% 11.75%, 2.19% 9.49%);
  overflow: hidden;
  background: radial-gradient(
    ellipse at 60% 20%,
    #28456c,
    #17223f 65%,
    #10162a
  );
  /* The screen's own frame (#50, `screenFrame`): the TV is turned, so the copy box maps onto the glass's
     parallelogram. */
  transform-origin: 0 0;
  transform: translate(0%, -6.04%) matrix(1, 0.0649, 0, 1.0129, 0, 0);
  display: flex;
  flex-direction: column;
  /* Percentages of the stage width: about a sixteenth of the TV's width across, a twentieth down. */
  padding: 1.4% 1.8%;
  container-type: inline-size;
  box-shadow: inset 0 0 25px #050919;
}
.tv-content:after {
  content: "";
  pointer-events: none;
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(transparent 0 3px, #121b3022 3px 4px);
}
.tv-topline {
  display: flex;
  justify-content: space-between;
  font-size: max(10px, 3.3cqw);
  line-height: 1.2;
  white-space: nowrap;
  margin-bottom: 1.5cqw;
}
.tv-topline > span:first-child {
  color: #eb6a88;
}
.tv-menu {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
}
.tv-menu p {
  font-size: max(10px, 3cqw);
  letter-spacing: 0.13em;
  color: #a8bed8;
  margin: 0 0 2cqw;
}
.tv-menu h1 {
  font-size: max(12px, 7.6cqw);
  line-height: 1.02;
  margin: 0 0 3cqw;
  text-wrap: balance;
  color: #f5f0d8;
  letter-spacing: -0.055em;
}
.tv-menu button {
  font-weight: bold;
  font-size: max(11px, 3.7cqw);
  padding: 2cqw 3cqw;
  border: 1px solid #a6bc93;
  border-radius: 3px;
  background: #dff6a7;
  color: #1f3034;
  box-shadow: 0 3px #1a2841;
}
.tv-menu small {
  font-size: max(10px, 2.9cqw);
  line-height: 1.3;
  text-wrap: balance;
  color: #abc0d4;
  margin-top: 2.2cqw;
}
.sponsor {
  border-top: 1px solid #69829455;
  padding-top: 2cqw;
}
.sponsor > span {
  font-size: max(10px, 2.6cqw);
  color: #afbec7;
  letter-spacing: 0.08em;
}
.sponsor button {
  display: block;
  width: 100%;
  text-align: left;
  font-size: max(10px, 3cqw);
  padding: 1cqw 0;
  background: transparent;
  border: 0;
}
.sponsor b {
  float: right;
  color: var(--lime);
  font-size: 1.4em;
  line-height: 0.8;
}
/* The TV copy fits its size (#62): no line under 10 px, so a smaller screen shows less. The copy width follows
   the stage, which the window's height caps too (about 245 px at 1440 x 1000, 210 px at 1440 x 900, 190 px at
   1024 x 800). Under 235 px the note and the sponsor strapline go, under 200 px the hearts line too; under
   140 px (phones) only the title and the browse button stay, at 12 px, and support is the footer's. A picked
   category needs neither the browse button nor the note: the shelf is already open on it. */
@container (width < 235px) {
  .tv-menu small,
  .sponsor > span {
    display: none;
  }
}
@container (width < 200px) {
  .tv-topline {
    display: none;
  }
}
@container (width < 140px) {
  .tv-menu p,
  .sponsor,
  .tv-glyph {
    display: none;
  }
  .tv-menu h1 {
    font-size: 12px;
    margin-bottom: 5px;
  }
  .tv-menu button {
    font-size: 12px;
    padding: 2px 3px;
    white-space: nowrap;
  }
  /* Loading says what it is doing at every size. */
  .loading .tv-menu p {
    display: block;
    font-size: 10px;
    margin-bottom: 3px;
  }
}
.tv-content.picked :is(#browse, #tv-note) {
  display: none;
}
/* On touch the whole screen is the browse button (at least 44 px each way at every stage size), and the
   sponsor link is the About panel's. */
@media (pointer: coarse) {
  #browse:before {
    content: "";
    position: absolute;
    inset: 0;
  }
  .tv-content .sponsor {
    display: none;
  }
}
.footer-link,
.footer-support {
  background: none;
  border: 0;
  padding: 4px 0;
  font-size: inherit;
  text-decoration: underline;
  color: inherit;
  cursor: pointer;
}
/* The wall light switch (#54): a clear button over the room's own switch, placed from scene.json
   (`lightSwitch`); the committed rectangle here holds until it arrives. It appears once the room is live,
   since the poster has no switch, and steps aside while the view is zoomed into the TV. */
.light-switch {
  position: absolute;
  left: 94.053%;
  top: 13.987%;
  width: 3.333%;
  height: 5.218%;
  background: transparent;
  border: 0;
  border-radius: 4px;
  padding: 0;
  cursor: pointer;
}
/* On small stages the plate is only a few pixels; the hit area never drops below 24 x 24 CSS px (WCAG 2.5.8),
   centred on the switch, without changing what is drawn. */
.light-switch::before {
  content: "";
  position: absolute;
  inset: min(0px, calc(50% - 12px));
}
.light-switch:focus-visible {
  outline-offset: 0;
}
.light-switch:disabled,
.launching .light-switch,
.loading .light-switch {
  visibility: hidden;
}
/* The roadmap chalkboard (#70): a clear button over the room's board, placed from scene.json (`roadmap`) like the
   light switch, opening the roadmap panel. */
.roadmap-board {
  position: absolute;
  left: 84.742%;
  top: 0.303%;
  width: 13.176%;
  height: 13.684%;
  background: transparent;
  border: 0;
  border-radius: 4px;
  padding: 0;
  cursor: pointer;
}
.roadmap {
  display: grid;
  gap: 0.5em;
  padding-left: 1.2em;
}
.roadmap-board:disabled,
.launching .roadmap-board,
.loading .roadmap-board {
  visibility: hidden;
}
/* The play page (#4) covers the window less the header or rail; the room under it is taken down. */
.game-page {
  position: absolute;
  inset: var(--inset-top, 0px) var(--inset-right, 0px)
    var(--inset-bottom, 0px) var(--inset-left, 0px);
  z-index: 5;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  background: #0f0e16;
  border: 2px solid var(--border);
}
.game-page[hidden] {
  display: none;
}
.game-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px;
  background: #272332;
}
.game-bar h2 {
  flex: 1;
  margin: 0;
  font-size: 16px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.game-bar button {
  background: #222a30;
  border: 1px solid #7c826b;
  border-radius: 4px;
  padding: 8px 12px;
  font-size: 13px;
}
.game-bar button[aria-pressed="true"] {
  background: var(--amber);
  color: #222738;
}
.game-bar button:focus-visible,
#game-canvas:focus-visible {
  outline: 3px solid var(--lime);
  outline-offset: -3px;
}
.game-screen {
  position: relative;
  min-height: 0;
}
#game-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  touch-action: none;
}
#game-status {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  margin: 0;
  padding: 24px;
  text-align: center;
  font-size: 15px;
  line-height: 1.6;
  /* Dims the game's first frame while it gets ready. */
  background: #0f0e16d9;
}
#game-status[hidden] {
  display: none;
}
.game-page[data-state="failed"] #game-status {
  color: var(--amber);
}
.game-result-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 6px 12px;
  padding: 8px 10px;
  background: #272332;
  border-top: 1px solid var(--border);
  font-size: 13px;
}
.game-result-summary p {
  margin: 0;
}
.game-result-summary button {
  min-height: 44px;
  padding: 8px 12px;
  background: #222a30;
  border: 1px solid #7c826b;
  border-radius: 4px;
}
.game-account-prompt {
  position: fixed;
  z-index: 30;
  right: max(12px, env(safe-area-inset-right));
  bottom: max(12px, env(safe-area-inset-bottom));
  width: min(380px, calc(100vw - 24px));
  max-height: calc(100dvh - 100px);
  overflow-y: auto;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #272332;
  box-shadow: 0 6px 24px #0006;
  font-size: 13px;
}
.game-account-prompt h3 {
  margin-top: 0;
}
.game-account-prompt button {
  margin-right: 8px;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid #7c826b;
  border-radius: 4px;
  background: #222a30;
}
.game-account-prompt[hidden] {
  display: none;
}
.game-result-summary[hidden],
#game-leaderboard-dialog [hidden] {
  display: none;
}
.leaderboard-body {
  max-height: calc(
    100dvh - 108px - env(safe-area-inset-top) - env(safe-area-inset-bottom)
  );
  overflow-y: auto;
  overflow-x: hidden;
  overflow-wrap: anywhere;
  overscroll-behavior: contain;
  padding: 4px;
  margin: -4px;
}
#game-leaderboard-dialog .close {
  min-width: 44px;
  min-height: 44px;
}
#game-leaderboard-dialog .action {
  min-height: 44px;
}
#game-leaderboard-dialog label {
  display: block;
  margin: 12px 0 8px;
}
#leaderboard-partition,
#leaderboard-name {
  width: 100%;
  min-height: 44px;
  padding: 10px;
  border: 2px solid var(--border);
  border-radius: 4px;
  color: inherit;
  background: #171c28;
  font: inherit;
}
#leaderboard-partition:focus-visible {
  outline: 3px solid var(--lime);
  outline-offset: 2px;
}
#leaderboard-table {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  font-size: 13px;
}
#leaderboard-table caption {
  text-align: left;
  padding: 10px 0;
}
#leaderboard-table th,
#leaderboard-table td {
  padding: 10px 4px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
  overflow-wrap: anywhere;
}
#leaderboard-table th:first-child {
  width: 16%;
}
#leaderboard-table th:last-child {
  width: 30%;
}
#leaderboard-table th:last-child,
#leaderboard-table td:last-child {
  text-align: right;
}
#leaderboard-publish {
  margin-top: 20px;
  border-top: 1px solid var(--border);
}
#game-leaderboard-dialog .leaderboard-consent {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-height: 44px;
}
#leaderboard-consent {
  flex: none;
  width: 22px;
  height: 22px;
  margin: 0;
  accent-color: var(--lime);
}
@media (max-height: 500px) {
  .leaderboard-body {
    max-height: calc(
      100dvh - 76px - env(safe-area-inset-top) - env(safe-area-inset-bottom)
    );
  }
}
/* Short labels on narrow stages: the glyph shows, the words stay the name. */
@media (max-width: 700px) {
  .game-bar .label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}
/* The About panel (#139) carries what the footer and the status line did. */
.about-status {
  color: #b8acbb;
  font-size: 12px;
}
.about-links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  align-items: baseline;
  font-size: 13px;
}
dialog {
  position: fixed;
  background: #272332;
  color: #f4e6d4;
  border: 2px solid #8c7483;
  border-radius: 10px;
  max-width: 560px;
  /* Inside the safe areas on every side; a tall panel scrolls in its body and the close key stays in view. */
  width: calc(
    100% - 32px - 2 * max(env(safe-area-inset-left), env(safe-area-inset-right))
  );
  max-height: calc(
    100dvh - 32px - env(safe-area-inset-top) - env(safe-area-inset-bottom)
  );
  overflow: hidden;
  padding: 36px;
  box-shadow: 0 20px 100px #000;
}
#dialog-body {
  max-height: calc(
    100dvh - 108px - env(safe-area-inset-top) - env(safe-area-inset-bottom)
  );
  overflow-y: auto;
  overflow-x: hidden;
  overflow-wrap: anywhere;
  overscroll-behavior: contain;
  /* Room for the focus rings the scroller would otherwise clip. */
  padding: 4px;
  margin: -4px;
}
/* A landscape phone is short: tighter padding leaves the panel more of the height. */
@media (max-height: 500px) {
  dialog {
    padding: 20px 28px;
  }
  #dialog-body {
    max-height: calc(
      100dvh - 76px - env(safe-area-inset-top) - env(safe-area-inset-bottom)
    );
  }
}
dialog::backdrop {
  background: #0d0b16c9;
  backdrop-filter: blur(5px);
}
.close {
  position: absolute;
  top: 8px;
  right: 12px;
  font-size: 28px;
  background: none;
  border: 0;
}
dialog h2 {
  font-size: 26px;
  line-height: 1.2;
  margin: 12px 0;
}
dialog p {
  font-size: 16px;
  line-height: 1.6;
  color: #d4c3ce;
}
dialog .cover {
  display: block;
  width: 65%;
  margin: auto;
}
dialog [data-play] {
  margin-right: 8px;
}
dialog a,
dialog .action {
  display: inline-block;
  background: #d7eab0;
  color: #222738;
  text-decoration: none;
  padding: 12px 16px;
  border: 0;
  border-radius: 4px;
  font:
    700 14px/1.3 ui-monospace,
    monospace;
  margin-top: 10px;
}
@media (hover: hover) {
  dialog a:hover,
  dialog .action:hover {
    background: var(--lime);
  }
}
/* Remove undoes Save, so it is the quieter outline button (#65). A real border,
   not a shadow, so the outline survives forced colours; the padding keeps the
   button's size. */
dialog .action.remove {
  background: transparent;
  color: #d7eab0;
  border: 2px solid;
  padding: 10px 14px;
}
@media (hover: hover) {
  dialog .action.remove:hover {
    background: #d7eab01f;
  }
}
.account-updates-option {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-height: 44px;
  margin: 16px 0;
  line-height: 1.6;
  cursor: pointer;
}
.account-updates-option input {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin: 3px 0 0;
  accent-color: var(--lime);
}
#account-email-form {
  margin-top: 24px;
}
#account-email-form label {
  display: block;
  margin-bottom: 8px;
}
#account-email {
  width: 100%;
  min-height: 44px;
  padding: 10px;
  border: 2px solid var(--border);
  border-radius: 4px;
  color: inherit;
  background: #171c28;
}
#account-panel .action {
  min-height: 44px;
}
#account-panel:has(#account-progress:not(:empty)) #account-score-status {
  display: none;
}
#account-panel a {
  padding: 0;
  margin: 0;
  color: var(--lime);
  background: transparent;
  text-decoration: underline;
  font: inherit;
}
#account-panel button:disabled {
  opacity: 0.6;
  cursor: wait;
}
#account-status {
  overflow-wrap: anywhere;
}
/* The dialog fades up as it opens; the reduced-motion rule below removes it. */
dialog[open] {
  animation: dialog-in 0.18s ease-out;
}
dialog[open]::backdrop {
  animation: backdrop-in 0.18s ease-out;
}
@keyframes dialog-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}
@keyframes backdrop-in {
  from {
    opacity: 0;
  }
}
.booting .tv-menu {
  opacity: 0.15;
  transition: opacity 0.1s;
}
.booting .tv-content {
  filter: brightness(0.45);
}
/* The new TV copy fades up from the booting dim as each boot lands (#57). The
   room owns the swap's timing; without a live room the swap is instant, and
   this fade is its only feedback. The reduced-motion rule below removes it. */
.tv-swap .tv-menu {
  animation: tv-swap-menu 0.4s ease-out;
}
.tv-swap .tv-content {
  animation: tv-swap-content 0.4s ease-out;
}
@keyframes tv-swap-menu {
  from {
    opacity: 0.15;
  }
}
@keyframes tv-swap-content {
  from {
    filter: brightness(0.45);
  }
}
@media (max-width: 1150px) {
  .control-bar {
    gap: 8px;
    padding: max(9px, env(safe-area-inset-top)) max(14px, env(safe-area-inset-right))
      9px max(14px, env(safe-area-inset-left));
  }
  .nav-key {
    padding: 10px 12px;
  }
  /* Icon-only keys keep their words for screen readers (#63). The three nav keys
     have no other name, so never hide the words with `display: none`. */
  .key-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .wordmark {
    padding: 8px 12px;
  }
}
@media (max-width: 1150px) {
  .account {
    font-size: 13px;
    max-width: 9rem;
    min-height: 44px;
    padding: 7px 9px;
  }
}
@media (max-width: 700px) {
  .preview-status {
    font-size: 11px;
  }
  .control-bar {
    flex-wrap: wrap;
    padding: max(8px, env(safe-area-inset-top)) max(10px, env(safe-area-inset-right))
      8px max(10px, env(safe-area-inset-left));
    border-width: 3px;
    gap: 8px;
  }
  /* The wordmark, the keys and the library share the first row down to a 360 px
     screen; search takes the second (#63). */
  .wordmark {
    font-size: clamp(19px, 5.7vw, 23px);
    padding: 8px 10px;
    margin-right: auto;
    letter-spacing: -1px;
  }
  .control-bar nav {
    gap: 5px;
  }
  .nav-key {
    font-size: 20px;
    min-height: 42px;
    padding: 7px 8px;
  }
  .library {
    font-size: 19px;
  }
  .search {
    order: 4;
    flex-basis: 100%;
    padding: 3px 10px;
  }
  .search span {
    font-size: 28px;
  }
  .search input {
    font-size: 16px;
    padding: 7px 0;
  }
  dialog {
    padding: 28px 24px;
  }
}
/* A landscape phone (#139): the header becomes a rail down the left edge, so the room keeps the window's
   height, and search waits behind its own key. arcade.mjs writes the rail's width into the room as
   `insetLeft` instead of the header's height. Its media query is arcade.mjs `RAIL`. It comes after the width
   breakpoints so that it, not they, sets the padding (their `max(…, env(safe-area-inset-*))` terms would
   otherwise be replaced) and the single column. */
@media (orientation: landscape) and (pointer: coarse) and (max-height: 500px) {
  .control-bar {
    bottom: 0;
    right: auto;
    width: calc(66px + env(safe-area-inset-left));
    flex-direction: column;
    flex-wrap: nowrap;
    max-height: none;
    justify-content: flex-start;
    gap: 6px;
    padding: max(8px, env(safe-area-inset-top)) 6px
      max(8px, env(safe-area-inset-bottom))
      max(6px, env(safe-area-inset-left));
    border: 3px solid #493e4bcc;
    border-left: 0;
    border-radius: 0 14px 14px 0;
    overflow-y: auto;
  }
  .control-bar .wordmark {
    font-size: 0;
    letter-spacing: 0;
    padding: 6px 12px;
  }
  .control-bar .wordmark span {
    font-size: 22px;
  }
  .control-bar .key-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .control-bar nav {
    flex-direction: column;
    gap: 6px;
  }
  .control-bar .nav-key {
    width: 52px;
    min-height: 44px;
    padding: 6px;
    font-size: 19px;
  }
  /* Keep a recognisable account key in the narrow rail. The button's explicit
     accessible name retains the full user name; generated art is only visual. */
  .control-bar .account {
    font-size: 0;
  }
  .control-bar .account::before {
    content: "👤";
    font-size: 22px;
  }
  .control-bar .library {
    flex-direction: column;
    gap: 0;
  }
  .search-toggle {
    display: flex;
  }
  .search-toggle[aria-expanded="true"] {
    border-color: #bdcc8d;
  }
  .control-bar .search {
    display: none;
    position: fixed;
    top: max(8px, env(safe-area-inset-top));
    left: calc(76px + env(safe-area-inset-left));
    width: min(
      340px,
      calc(100vw - 96px - env(safe-area-inset-left) - env(safe-area-inset-right))
    );
    flex: none;
    background: #1c1b26;
    box-shadow:
      inset 0 2px 9px #0c0c12,
      0 6px 14px #000a;
  }
  .control-bar.searching .search {
    display: flex;
  }
}
@media (prefers-reduced-motion: reduce) {
  *,
  *:before,
  *:after,
  ::backdrop {
    scroll-behavior: auto !important;
    transition: none !important;
    animation: none !important;
  }
}
/* A game whose row the live room has scrolled away (#85): still in the tab order, so focus can bring its
   row back into view, but out of sight until it does. */
.shelf-hotspot.off-shelf {
  opacity: 0;
  pointer-events: none;
}
/* The page never scrolls: the host owns both axes of touch over the shelf (pan and category scroll). */
.shelf-hotspot {
  touch-action: none;
}
/* A game launch (#57): the TV copy gives way while the view zooms into the screen, then says the game is
   loading over the zoomed screen until the play page grows out of it. */
.launching .tv-content {
  opacity: 0;
  transition: opacity 0.15s;
  pointer-events: none;
}
.loading .tv-content {
  animation: tv-swap-content 0.4s ease-out;
}
/* Loading says only what is loading: nothing to browse or support until the visitor is back. */
.loading :is(#browse, #tv-note, .sponsor) {
  display: none;
}
