html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  background: #000;
  overflow: hidden;
  font-family: monospace;
  color: #ddd;
  /* Global text-selection/callout hardening (docs/known-issues.md's touch-text-selection entry):
     originally scoped only to `.touch-button`, which per direct report ("I still get text
     selectors coming up when I press the touch circle controls") wasn't enough on its own on a
     real device -- applied here too as a broader safety net, since nothing in this app's
     gameplay screens has any legitimate text-selection use case anyway. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  /* iOS Safari pinch/double-tap zoom hardening (docs/known-issues.md's ios-pinch-zoom entry) --
     the viewport meta tag (index.html) is the primary fix, this is a second layer: `touch-action`
     doesn't inherit from ancestors by default, but setting it here means anything that doesn't
     explicitly set its own (a gap between touch buttons, any future element) still isn't a place
     a stray two-finger pinch could register as a zoom gesture. */
  touch-action: none;
}

#app {
  width: 100%;
  height: 100%;
  position: relative;
}

/* #level-select/#game-over below set their own `display` for layout, which would otherwise
   override the browser default `[hidden] { display: none }` by CSS specificity (an ID beats an
   attribute selector) and make toggling the `hidden` property from JS a no-op. */
[hidden] {
  display: none !important;
}

#game-canvas {
  display: block;
  width: 100%;
  height: 100%;
  background: #000;
  /* Prevents default touch gestures (pan/zoom) on the canvas itself from competing with taps
     landing on it (docs/known-issues.md's touch-text-selection entry) -- the touch buttons
     overlaid on top already had this, but the canvas underneath (and the special-weapon tap
     target sitting on it) didn't. */
  touch-action: none;
}

/* Intro/platform-select (per direct request): the very first screen of all, shown before
   mode-select -- picking a platform sets main.js's `isMobile` flag, which mode-select and every
   screen/Game instance downstream read (single-ship no-split-screen rendering, on-screen touch
   controls). Shares #mode-select's layout below since it's the same "centered column of choices"
   shape. */
#intro-select,
#mode-select {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1em;
  height: 100%;
  box-sizing: border-box;
  padding: 1.2em 1em 1em;
}

/* Real extracted original title-screen art (docs/asset-extraction.md, tools/extract_intro_gfx.py)
   -- image-rendering: pixelated keeps it crisp at any display size, matching this project's other
   nearest-neighbor-scaled pixel art (bitmap font, race decal digits). */
#intro-logo {
  max-width: min(90vw, 480px);
  width: 100%;
  height: auto;
  image-rendering: pixelated;
}

#mode-select {
  position: relative; /* anchors #mode-back-button below */
}

#mode-back-button {
  position: absolute;
  top: 1.2em;
  left: 1em;
}

#mode-select h1 {
  margin: 0;
  font-size: 1.6em;
}

#platform-buttons,
#mode-buttons {
  display: flex;
  gap: 1em;
}

#desktop-button,
#mobile-button,
#dogfight-button,
#race-button {
  padding: 0.8em 1.6em;
  font-family: monospace;
  font-size: 1.1em;
  background: #111;
  color: #ddd;
  border: 1px solid #444;
  cursor: pointer;
}

#desktop-button:hover,
#desktop-button:focus,
#mobile-button:hover,
#mobile-button:focus,
#dogfight-button:hover,
#dogfight-button:focus,
#race-button:hover,
#race-button:focus {
  background: #234;
  border-color: #4cf;
}

#level-select {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Was centered with no scrolling, which is exactly why the level list overflowed the
     viewport with no way to reach anything past the fold -- this screen now sizes itself to
     the viewport and lets #level-grid (not the whole page) scroll instead. */
  justify-content: flex-start;
  gap: 0.5em;
  height: 100%;
  box-sizing: border-box;
  padding: 1.2em 1em 1em;
  overflow: hidden;
}

#level-select-back-buttons {
  flex: none;
  display: flex;
  gap: 0.5em;
}

#change-mode-button,
#level-back-button,
#mode-back-button {
  flex: none;
  padding: 0.3em 0.8em;
  font-family: monospace;
  font-size: 0.85em;
  background: #111;
  color: #ddd;
  border: 1px solid #444;
  cursor: pointer;
}

#change-mode-button:hover,
#change-mode-button:focus,
#level-back-button:hover,
#level-back-button:focus,
#mode-back-button:hover,
#mode-back-button:focus {
  background: #234;
  border-color: #4cf;
}

#level-select h1 {
  margin: 0;
  font-size: 1.6em;
  flex: none;
}

#level-select > p {
  margin: 0;
  flex: none;
}

/* One tab button per world (the 9 original level packs, see docs/level-format.md) -- lets the
   player narrow 180 levels down to a browsable ~15-50 before ever hitting a scrollbar. */
#level-categories {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.4em;
  max-width: 60em;
}

#level-categories button {
  padding: 0.4em 0.9em;
  font-family: monospace;
  font-size: 0.95em;
  background: #111;
  color: #ddd;
  border: 1px solid #444;
  cursor: pointer;
}

#level-categories button:hover,
#level-categories button:focus {
  background: #234;
  border-color: #4cf;
}

#level-categories button.active {
  background: #345;
  border-color: #6cf;
  color: #fff;
}

/* The actual fix for the original bug: a grid that wraps and scrolls internally, instead of a
   single-column list that just kept growing past the bottom of the screen. */
#level-grid {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  max-width: 60em;
  overflow-y: auto;
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  justify-content: center;
  gap: 0.6em;
  padding: 0.5em;
  box-sizing: border-box;
  border: 1px solid #222;
}

#level-grid button {
  width: 9em;
  padding: 0;
  font-family: monospace;
  font-size: 0.85em;
  background: #111;
  color: #ddd;
  border: 1px solid #444;
  cursor: pointer;
  text-align: left;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

#level-grid button:hover,
#level-grid button:focus {
  background: #234;
  border-color: #4cf;
}

#level-grid .level-thumb {
  width: 100%;
  height: 6em;
  object-fit: cover;
  object-position: top;
  background: #000;
  display: block;
}

#level-grid .level-name {
  padding: 0.4em 0.5em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

#game-over {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1em;
  background: rgba(0, 0, 0, 0.75);
  overflow-y: auto;
  box-sizing: border-box;
  padding: 1em;
}

#game-over-message {
  font-size: 1.5em;
}

/* Post-race report (docs/known-issues.md issue 19, per direct request): one bitmap-font line per
   div, generated in main.js's renderRaceReport -- the small top margin on every "Player N" line
   (matched by nth-child math: title, then Player/5 lines/blank per player) visually separates each
   player's block without needing per-line classes. */
#race-report {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3em;
  max-height: 90%;
}

/* title(1), then Player-header/5-stat-lines per player (6 lines/block) -> headers land on 2, 8, 14... */
#race-report > div:nth-child(6n + 2) {
  margin-top: 0.6em;
}

#restart-button {
  padding: 0.6em 1.2em;
  font-family: monospace;
  font-size: 1em;
  background: #111;
  color: #ddd;
  border: 1px solid #444;
  cursor: pointer;
}

#restart-button:hover,
#restart-button:focus {
  background: #234;
  border-color: #4cf;
}

/* Invisible tap target over the special-weapon label -- see index.html's comment. Fixed instead
   of absolute (matches #gamepad-cursor's own positioning below) since it needs to sit relative to
   the viewport, at the same top-left corner the canvas draws the label at regardless of any
   scroll/layout context. No visible chrome of its own (the real visible text is what render.js
   draws onto the canvas underneath) -- `background: transparent; border: none;` keeps it a purely
   invisible hit zone. Above the canvas but deliberately below #touch-controls' z-index (10): it
   only ever occupies the top-left corner, nowhere near the bottom-corner touch clusters, so there's
   no real overlap to worry about, but keeping it lower means a future UI element added there would
   still be able to sit above it without a fight. */
#special-weapon-tap-target {
  position: fixed;
  left: 0;
  top: 0;
  width: 220px;
  height: 48px;
  padding: 0;
  margin: 0;
  background: transparent;
  border: none;
  z-index: 5;
  cursor: pointer;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

/* On-screen touch controls (1-player mobile mode, per direct request): the container itself is
   `pointer-events: none` so it never intercepts clicks/touches meant for anything else -- nothing
   in the game "collides" with it -- only the individual buttons re-enable pointer-events. Sits
   above the canvas *and* the HUD panel (z-index) but is kept semi-transparent so it doesn't
   obscure the view beneath it. Only ever unhidden by main.js while isMobile gameplay is active. */
#touch-controls {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 10;
}

/* No text/icon labels (per direct request -- position and shape already read as an obvious D-pad
   plus a single big thrust button, so a label is redundant, and small arrow glyphs read as
   illegible clutter at typical button sizes anyway).
   No `position` set here (deliberately, per a fixed bug): the 4 dpad buttons are plain grid items
   of #touch-dpad below, and CSS Grid stretches a static-position item to fill its whole cell by
   default -- that's what actually makes each one match its clamp()-sized grid track. Only
   #touch-thrust (not a grid child) needs `position: absolute` for its own corner anchoring, set on
   that rule specifically rather than here. (Previously this rule set `position: absolute` on all
   five buttons, which pulled the 4 dpad buttons out of grid flow entirely -- collapsing each to
   its default browser button content-size, ~16px, regardless of the grid track's real clamp()ed
   size -- exactly the "arrows look far too small next to the thrust button" bug reported.) */
.touch-button {
  pointer-events: auto;
  background: rgba(255, 255, 255, 0.16);
  border: 2px solid rgba(255, 255, 255, 0.35);
  border-radius: 50%;
  touch-action: none;
  /* Per direct report ("touch controls appear to trigger text selection... make sure the browser
     doesn't think they are empty text boxes or anything like that"): `user-select: none` alone
     isn't enough on iOS Safari -- a held press (thrust is held continuously) still pops the
     "Copy/Select" callout bubble unless `-webkit-touch-callout` is also disabled explicitly, and
     older WebKit wants its own `-webkit-user-select` too even though the unprefixed property is
     set. `-webkit-user-drag: none` stops a press-and-drag (e.g. sliding a thumb across the D-pad)
     from being interpreted as starting an image/element drag instead of a button press. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  -webkit-tap-highlight-color: transparent;
  cursor: pointer;
}

.touch-button.pressed {
  background: rgba(255, 255, 255, 0.4);
  border-color: rgba(255, 255, 255, 0.6);
}

/* Rotate/fire/bomb cluster, bottom-left, laid out WASD-style (docs/known-issues.md's touch-
   controls-panel-clearance entry, per direct request: "use a WASD-like layout for the directions,
   i.e. the 'down' key in between 'left' and 'right'") -- fire/"up" on its own top row, left/down/
   right in a row underneath (down in the middle), matching a physical keyboard's W above A-S-D,
   instead of the previous 3x3 plus-shape (fire top, bomb bottom, left/right on the sides, with
   "down" isolated at the bottom rather than between left and right). Sized much closer to the
   thrust button (per an earlier direct request: they previously read as far too small next to it)
   while still leaving a clear gap between the two clusters -- clamp()/vmin scales both smoothly
   from a small phone up through a tablet and a desktop browser window (mobile mode is also used
   there for demoing/testing) without ever growing large enough for the two clusters to reach each
   other in the middle.
   `bottom` is anchored to `--panel-height` (main.js, set from render.js's own
   `PANEL_DISPLAY_HEIGHT` -- one shared source of truth) plus a fixed clearance gap, not a
   `vmin`-based offset -- the HUD panel is a fixed pixel height regardless of window size/aspect,
   so a `vmin` offset could still end up underneath it (or drift unpredictably far above it) on
   some aspect ratios; anchoring to the panel's own real height guarantees the controls start just
   clear of it everywhere, per direct request ("move them by design so they start just above the
   panel no matter the screen layout or aspect otherwise"). */
#touch-dpad {
  position: absolute;
  left: 3vmin;
  bottom: calc(var(--panel-height, 0px) + 12px);
  display: grid;
  grid-template-columns: repeat(3, clamp(60px, 16vmin, 96px));
  grid-template-rows: repeat(2, clamp(60px, 16vmin, 96px));
  gap: clamp(6px, 2vmin, 12px);
  pointer-events: none;
}

#touch-fire { grid-column: 2; grid-row: 1; }
#touch-left { grid-column: 1; grid-row: 2; }
#touch-bomb { grid-column: 2; grid-row: 2; }
#touch-right { grid-column: 3; grid-row: 2; }

/* Single big thrust button, bottom-right -- still the largest single button (it's held far more
   continuously than the others), but closer in scale to the dpad cluster (per an earlier direct
   request). Same panel-relative `bottom` anchoring as #touch-dpad above, for the same reason. */
#touch-thrust {
  position: absolute;
  right: 4vmin;
  bottom: calc(var(--panel-height, 0px) + 12px);
  width: clamp(88px, 24vmin, 132px);
  height: clamp(88px, 24vmin, 132px);
}

/* Gamepad menu-navigation selector (docs/known-issues.md's gamepad-menu-nav entry) -- the
   original's own menu-cursor art (tools/extract_cursor_gfx.py's cursor.png, decoded from
   Pil.bmap, traced palette from GFap.S's `pilcol`/`place_pil`). Created and positioned entirely
   by web/js/gamepad-menu-nav.js, appended once to <body> (not scoped to any one screen, since it
   points at whichever screen/button is currently focused) -- stays `hidden` (per this file's own
   `[hidden]` rule above) until a real gamepad button press is seen, the actual "non-intrusive if
   not connected/used" mechanism. `z-index: 1000`, deliberately far above every other z-indexed
   element in this file (the next-highest is #touch-controls at 10) so it's guaranteed to render
   on top regardless of what else is on screen; `pointer-events: none` so it's purely a visual
   pointer, never itself clickable/hoverable. `left`/`top` transition quickly in a straight line
   between focus targets (module toggles a `.no-transition` class for instant repositioning on
   activation/screen-change, matching the original's own cursor behavior of snapping to a fresh
   screen but gliding between options on it). */
#gamepad-cursor {
  position: fixed;
  z-index: 1000;
  width: 32px;
  height: 30px;
  image-rendering: pixelated;
  pointer-events: none;
  filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.8));
  transition: left 0.1s linear, top 0.1s linear;
}
#gamepad-cursor.no-transition {
  transition: none;
}
