:root {
  --bg: #131b1a;
  --panel: #243330;
  --text: #edf5f2;
  --accent: #8ad7bf;
}

/* The page's own sizes follow the screen, as the game's pixels do (ui/canvas.ts
   picks a whole-number scale from the window's short side). Up to a short side
   of 1080 px the base size is the browser's own, 16 px; beyond that it grows in
   step with the window, to double at the most (a 4K screen). Every length in
   this file is written in rem — a sixteenth of it is the pixel it used to be —
   so the texts, their boxes and the room around them all grow together, and on
   a large display they keep the share of the screen they have at 1080p. (The
   conditions of @media stay in px: they do not follow the page's base size.) */
html {
  font-size: clamp(100%, calc(100vmin / 67.5), 200%);
}

* {
  box-sizing: border-box;
}

html,
body {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

body {
  margin: 0;
  font-family:
    Inter,
    system-ui,
    -apple-system,
    Segoe UI,
    Roboto,
    sans-serif;
  background: radial-gradient(circle at top, #1d2b2a, var(--bg));
  color: var(--text);
}

/* The canvas fills the screen; the HUD, panel and touch controls float on top. */
.viewport {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  background: #0b1010;
  overflow: hidden;
  touch-action: none;
}

/* Nothing of the game's surface can be selected: not the canvas, a button, a
   pill, the hint, a toast. A finger held on one — or a second tap, steering
   the rally car — used to mark its text blue and call up the copy bubble. Said
   for every element, with WebKit's own spelling: an iPhone does not take it
   from the parent, nor from the unprefixed property alone. What is there to
   be read, and copied, is given back below (see "Words are for reading"). */
.viewport,
.viewport * {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

canvas {
  display: block;
  /* Size is set in ui/canvas.ts: a whole-number scale of the virtual resolution, so
     every game pixel is an exact square block of screen pixels. */
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

/* The game canvas takes the focus back when a card closes; an outline round
   the whole screen would say nothing. */
#game:focus {
  outline: none;
}

/* Hidden until focused, then the first thing on screen. */
.skip-link {
  position: fixed;
  left: 0.75rem;
  top: 0.75rem;
  z-index: 10;
  padding: 0.5rem 0.875rem;
  border-radius: 0.5rem;
  background: var(--accent);
  color: #0f1817;
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-200%);
}

.skip-link:focus-visible {
  transform: none;
}

.visually-hidden {
  position: absolute;
  width: 0.0625rem;
  height: 0.0625rem;
  margin: -0.0625rem;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Overlay layer: transparent to clicks except where a control sits. */
.hud {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
}

.hud-corner {
  position: absolute;
  top: calc(env(safe-area-inset-top) + 0.625rem);
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
  max-width: calc(100vw - 1.25rem);
}

.hud-top-left {
  left: max(0.625rem, env(safe-area-inset-left));
  /* Never reach under the buttons in the opposite corner (four of them), and
     the objective pill is the widest thing in the HUD. */
  max-width: calc(100% - 11.5rem);
}

/* iPhone Safari has no fullscreen API, so that button hides itself: the
   objective gets its room back. */
.hud:has(#fullscreen-btn.hidden) .hud-top-left {
  max-width: calc(100% - 8.875rem);
}

.hud-top-right {
  right: max(0.625rem, env(safe-area-inset-right));
  pointer-events: auto;
}

/* Direct children only: the pills' own inner spans are not pills. */
.hud-corner > span {
  border: 0.0625rem solid #4f6f6a;
  background: rgba(15, 24, 23, 0.82);
  border-radius: 62.4375rem;
  padding: 0.25rem 0.625rem;
  font-size: 0.8rem;
}

/* Same look as the objective/progress pills, but it is a control. */
.hud-pill {
  pointer-events: auto;
  border: 0.0625rem solid #4f6f6a;
  background: rgba(15, 24, 23, 0.82);
  border-radius: 62.4375rem;
  padding: 0.25rem 0.625rem;
  color: var(--text);
  font: inherit;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.hud-pill:hover,
.hud-pill:focus-visible {
  border-color: var(--accent);
}

/* On a bench the HUD steps back, so the view and the saying have the screen. */
body.seated .hud-corner {
  opacity: 0.3;
  transition: opacity 0.6s;
}

/* On a phone "in Finland" goes, so the sky and season pills share a row. */
@media (max-width: 520px) {
  .pill-more {
    display: none;
  }
}

.hud-btn {
  pointer-events: auto;
  width: 2.125rem;
  height: 2.125rem;
  border-radius: 0.5rem;
  border: 0.0625rem solid #4f6f6a;
  background: rgba(15, 24, 23, 0.82);
  color: var(--text);
  font-size: 0.9375rem;
  line-height: 1;
  cursor: pointer;
}

.hud-btn:hover,
.hud-btn:focus-visible {
  border-color: var(--accent);
}

.hud-hint {
  position: absolute;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom) + 0.625rem);
  transform: translateX(-50%);
  margin: 0;
  padding: 0.25rem 0.75rem;
  border-radius: 62.4375rem;
  background: rgba(15, 24, 23, 0.7);
  font-size: 0.78rem;
  opacity: 0.85;
  white-space: nowrap;
}

.hud-hint a {
  /* The HUD layer lets clicks through to the canvas; this link takes them. */
  pointer-events: auto;
  color: var(--accent);
}

.hud-hint kbd {
  font: inherit;
  border: 0.0625rem solid #4f6f6a;
  border-radius: 0.25rem;
  padding: 0 0.25rem;
}

/* No keyboard: drop the key hints. */
@media (hover: none), (pointer: coarse) {
  .hud-hint {
    display: none;
  }

  /* A finger needs more to hit than a pointer: the buttons 40 px (from 34),
     the two pills 34 px high (from 28). */
  .hud-btn {
    width: 2.5rem;
    height: 2.5rem;
    font-size: 1.0625rem;
  }

  /* (By its height: its padding is set again further down, for narrow screens.) */
  .hud-pill {
    min-height: 2.125rem;
  }

  /* Four buttons of 40 with their gaps and the margin. */
  .hud-top-left {
    max-width: calc(100% - 13rem);
  }

  .hud:has(#fullscreen-btn.hidden) .hud-top-left {
    max-width: calc(100% - 10rem);
  }
}

/* The hint under the avatar's feet: what E / A does here, behind the key's cap
   (ui/prompt.ts moves it there each frame, from the window's corner). The text
   bubble's small sibling, on every screen — a phone's too. */
.prompt {
  position: fixed;
  left: 0;
  top: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.25rem 0.4375rem;
  /* As wide as what it says, wherever on the screen it is; a long line wraps. */
  width: max-content;
  max-width: min(calc(100vw - 1.5rem), 22rem);
  background: var(--panel);
  border: 0.0625rem solid #3b5652;
  border-radius: 0.625rem;
  padding: 0.3125rem 0.6875rem 0.375rem 0.4375rem;
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1.3;
  box-shadow: 0 0.375rem 1.125rem rgba(0, 0, 0, 0.45);
  z-index: 2;
}

/* A line with no key to it (a bench's saying, "The boat is frozen in"): a row of its own. */
.prompt-note {
  flex: 1 1 100%;
  padding-left: 0.25rem;
  font-weight: 500;
  text-align: center;
}

/* The key's cap, in the gold of the arrow that shows the way. */
.prompt kbd {
  flex: none;
  display: grid;
  place-items: center;
  min-width: 1.4375rem;
  height: 1.4375rem;
  padding: 0 0.3125rem;
  border-radius: 0.3125rem;
  background: #ffd27a;
  color: #2e200e;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 800;
  line-height: 1;
  box-shadow: 0 0.125rem 0 #a97a2c;
}

/* (With only a line to say there is no key to show: this rule's `display` would outdo .hidden's.) */
.prompt kbd.hidden {
  display: none;
}

.debug-readout {
  position: absolute;
  left: 0.625rem;
  top: 3.25rem;
  margin: 0;
  padding: 0.375rem 0.5rem;
  font-size: 0.6875rem;
  line-height: 1.25;
  color: #d5f4ec;
  background: rgba(10, 16, 16, 0.78);
  border: 0.0625rem solid #3d5752;
  border-radius: 0.375rem;
  cursor: copy;
  white-space: pre-wrap;
  max-width: min(86vw, 32.5rem);
}

/* A speech bubble anchored at the avatar (ui/panel.ts sets left/top each frame). */
.panel {
  position: fixed;
  left: 50%;
  top: 20%;
  width: min(44ch, calc(100vw - 1.5rem));
  max-height: min(70dvh, 35rem);
  background: var(--panel);
  border: 0.0625rem solid #3b5652;
  border-radius: 0.625rem;
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  overflow: auto;
  z-index: 3;
  box-shadow: 0 0.625rem 1.875rem rgba(0, 0, 0, 0.45);
}

/* Form fields: 16px at the least stops iOS zooming in when a field is focused. */
.panel input,
.panel textarea {
  padding: 0.5625rem 0.625rem;
  border: 0.0625rem solid #8a7a60;
  border-radius: 0.25rem;
  background: #f5efe6;
  color: #23201c;
  font: inherit;
  font-size: max(16px, 1rem);
}

.panel textarea {
  resize: vertical;
}

/* Darker than the browser's grey, which is hard to read on this paper (4:1). */
.panel input::placeholder,
.panel textarea::placeholder {
  color: #6b6257;
  opacity: 1;
}

.panel .form-submit {
  padding: 0.625rem;
  background: #2f4b67;
  color: #fff;
  border: none;
  border-radius: 0.25rem;
  cursor: pointer;
  font: inherit;
  align-self: stretch;
}

.panel form {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  /* Clear of the text above it */
  margin-top: 0.75rem;
}

.panel .form-status {
  font-size: 0.85em;
  min-height: 1.2em;
}

/* What happens to what you send, above the Send button (data/legal.ts) */
.panel .form-note {
  margin: 0.125rem 0.125rem 0.25rem;
  font-size: 0.8em;
  opacity: 0.8;
}

.panel .form-note a {
  color: inherit;
}

.cloud-note a {
  color: var(--accent);
}

.panel .is-ok {
  color: #2a6040;
}

.panel .is-error {
  color: #a03030;
}

.panel h2 {
  margin: 0;
  font-size: 1.1rem;
}

/* Quests speak in lines (gameplay/quests.ts): keep their breaks. */
#panel-body {
  white-space: pre-line;
}

.panel p {
  margin: 0;
  line-height: 1.45;
}

.panel button {
  align-self: flex-start;
  border: 0.0625rem solid #5b8580;
  background: #2f4542;
  color: var(--text);
  padding: 0.5rem 0.625rem;
  border-radius: 0.5rem;
  /* The size a browser gives a button's label, 13⅓ px, said in rem so that it grows like the rest. */
  font-size: calc(1rem * 5 / 6);
  cursor: pointer;
}

.hidden {
  display: none;
}

/* Shown instead of the game when JavaScript is off. */
.noscript-note {
  padding: 1rem;
  color: #e8f0ee;
  font: 1rem system-ui;
}

.noscript-note a {
  color: #8ad7bf;
}

.toast {
  position: absolute;
  bottom: calc(env(safe-area-inset-bottom) + 4.75rem);
  left: 50%;
  transform: translateX(-50%);
  background: rgba(19, 27, 26, 0.94);
  border: 0.0625rem solid #51706a;
  border-radius: 62.4375rem;
  padding: 0.625rem 1.125rem;
  font-size: 0.92rem;
  max-width: min(92vw, 35rem);
  text-align: center;
  z-index: 9;
  color: #ffd27a;
  font-weight: 600;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s;
}

.toast.toast-show {
  opacity: 1;
}

/* ?debug=canvas — nothing but the canvas, so the visual baselines capture the
   game itself and no surrounding chrome. */
.debug-canvas-only .hud,
.debug-canvas-only .panel,
.debug-canvas-only .map-screen,
.debug-canvas-only .overlay-close,
.debug-canvas-only .touch-ui {
  display: none !important;
}

/* ===== Mini-game close button ===================================== */

.overlay-close {
  display: none;
  position: absolute;
  left: 50%;
  top: calc(env(safe-area-inset-top) + 3.25rem);
  transform: translateX(-50%);
  pointer-events: auto;
  padding: 0.5rem 1rem;
  border-radius: 62.4375rem;
  border: 0.0625rem solid #5b8580;
  background: rgba(15, 24, 23, 0.9);
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
}

/* A mini-game has the screen. The pills and the key hints step aside — they
   sat on top of its own header and footer — and Close takes the corner the
   pills left, clear of the game in the middle. (The buttons on the right stay:
   the sound is one of them.) */
body.overlay-open .overlay-close {
  display: block;
  left: max(0.625rem, env(safe-area-inset-left));
  top: calc(env(safe-area-inset-top) + 0.625rem);
  transform: none;
}

body.overlay-open .hud-top-left,
body.overlay-open .hud-hint {
  display: none;
}

/* The world's hint (what E / A does here) means nothing under a mini-game. */
body.overlay-open .prompt {
  display: none;
}

/* On the rally stage (rally/) the canvas shows its own clock, notes and
   keys; the ✕ pill leaves it, as it closes a mini-game. */
body.racing .hud-top-left,
body.racing .hud-hint,
body.racing .prompt {
  display: none;
}

/* At the bottom, between the stick and the A button: the notes are at the top. */
body.racing .overlay-close {
  display: block;
  top: auto;
  bottom: calc(env(safe-area-inset-bottom) + 0.875rem);
}

/* On a touch screen, how the rally car steers (rally/touchDrive.ts): a pill
   just above the stick it is about (the gap beside the stick is too narrow on
   a small phone). Tapping to steer, the stick is gone and it stays put. */
.rally-steer {
  display: none;
  position: fixed;
  z-index: 5;
  left: max(0.625rem, env(safe-area-inset-left));
  bottom: calc(max(0.625rem, env(safe-area-inset-bottom)) + 9.125rem);
  pointer-events: auto;
  padding: 0.375rem 0.75rem;
  border-radius: 62.4375rem;
  border: 0.0625rem solid #5b8580;
  background: rgba(15, 24, 23, 0.9);
  color: var(--text);
  font: inherit;
  font-size: 0.8rem;
  white-space: nowrap;
  cursor: pointer;
}

@media (hover: none), (pointer: coarse) {
  body.racing .rally-steer {
    display: block;
  }

  /* Tapping to steer, the two halves of the screen are the controls: the stick goes. */
  body.racing.tap-drive .stick {
    visibility: hidden;
  }
}

/* ===== The full map screen (M) =================================== */

.map-screen {
  position: fixed;
  inset: 0;
  z-index: 6;
  display: flex;
  padding: max(0.625rem, env(safe-area-inset-top)) max(0.625rem, env(safe-area-inset-right))
    max(0.625rem, env(safe-area-inset-bottom)) max(0.625rem, env(safe-area-inset-left));
  /* No backdrop blur: a phone had to blur the whole screen again on every
     frame the map's pins moved, and through this backdrop the world hardly
     shows anyway. */
  background: rgba(8, 13, 13, 0.88);
}

/* Without the blur the HUD's pills would read through the backdrop: they are
   put away while the map or help screen is open (each has its own ✕). */
body:has(#map-screen:not(.hidden)) .hud,
body:has(#help-screen:not(.hidden)) .hud,
body:has(#welcome-screen:not(.hidden)) .hud {
  visibility: hidden;
}

/* .hidden is defined earlier in the file, so it loses to `display: flex` on
   equal specificity — say it again here. */
.map-screen.hidden {
  display: none;
}

.map-sheet {
  position: relative;
  margin: auto;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  min-height: 0;
  max-height: 100%;
  width: 100%;
  max-width: 68.75rem;
  padding: 0.75rem;
  border: 0.0625rem solid #3b5652;
  border-radius: 0.75rem;
  background: var(--panel);
  box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.5);
}

.map-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.map-tabs {
  display: flex;
  gap: 0.25rem;
}

.map-tab {
  padding: 0.375rem 0.625rem;
  border: 0;
  border-bottom: 0.125rem solid transparent;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0.6;
  cursor: pointer;
}

.map-tab:hover,
.map-tab:focus-visible {
  opacity: 0.9;
}

.map-tab[aria-selected='true'] {
  opacity: 1;
  border-bottom-color: var(--accent);
}

.map-tab-count {
  margin-left: 0.25rem;
  font-size: 0.8em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

/* Three tabs and the close button on one row of a narrow phone. */
@media (max-width: 420px) {
  .map-head {
    gap: 0.375rem;
  }

  .map-tabs {
    gap: 0;
  }

  .map-tab {
    padding: 0.375rem 0.3125rem;
    font-size: 0.78rem;
    letter-spacing: 0.01em;
  }
}

/* One tab's content: the map (stage + footer) or the trophy list. */
.map-panel {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  flex: 1;
  min-height: 0;
  /* The map is sized to fit (mapScreen.ts); should a screen be too short even
     for its smallest size, the tab scrolls rather than cutting it off. */
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* A phone held sideways, or a short window: the counters and the buttons
   stand beside the map instead of under it. Under it, they left the map a
   strip a quarter of which was cut off. */
@media (orientation: landscape) and (max-height: 520px) {
  #panel-map {
    flex-direction: row;
    align-items: flex-start;
    gap: 0.875rem;
    overflow: hidden;
  }

  #panel-map .map-foot {
    flex: 1 1 0;
    min-width: 0;
    align-self: stretch;
    align-content: flex-start;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* Same trap as .map-screen: .hidden comes earlier in the file. */
.map-panel.hidden {
  display: none;
}

/* As tall as the map tab was, so switching tabs does not resize the card —
   but on a portrait phone, where the map is short, use more of the screen. */
.trophies {
  /* Not flex: 1 like the map panel — a flex-basis would override the height.
     It may still shrink (with a scrollbar) on a short screen. */
  flex: 0 1 auto;
  height: max(var(--panel-h, 20rem), min(62dvh, 32.5rem));
  overflow: auto;
  overscroll-behavior: contain;
}

.trophy-summary {
  margin: 0;
  font-size: 0.85rem;
  opacity: 0.85;
}

.trophy-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14.375rem, 1fr));
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.trophy {
  display: grid;
  grid-template-columns: 2.125rem 1fr;
  gap: 0.0625rem 0.625rem;
  align-items: center;
  padding: 0.5rem 0.625rem;
  border: 0.0625rem solid #3b5652;
  border-radius: 0.625rem;
  background: rgba(15, 24, 23, 0.35);
}

.trophy-icon {
  grid-row: span 2;
  font-size: 1.375rem;
  line-height: 1;
  text-align: center;
}

.trophy-title {
  font-size: 0.9rem;
  font-weight: 600;
}

.trophy-text {
  font-size: 0.78rem;
  line-height: 1.3;
  opacity: 0.8;
}

.trophy.is-earned {
  border-color: rgba(138, 215, 191, 0.7);
  background: rgba(138, 215, 191, 0.09);
}

/* Known goal, not done yet. */
.trophy.is-locked .trophy-icon {
  filter: grayscale(1);
  opacity: 0.45;
}

.trophy.is-locked .trophy-title {
  opacity: 0.75;
}

/* The end of the route (ui/moreToFind.ts): a thank-you and what else there is to find. */
.more-to-find {
  margin-top: 0.75rem;
  padding-top: 0.625rem;
  border-top: 0.0625rem solid #3b5652;
}

.more-to-find h3 {
  margin: 0 0 0.25rem;
  font-size: 0.95rem;
}

.more-to-find p {
  margin: 0 0 0.375rem;
  font-size: 0.85rem;
  opacity: 0.85;
}

.more-to-find ul {
  display: grid;
  gap: 0.3125rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.85rem;
  line-height: 1.35;
}

.more-to-find li {
  display: grid;
  grid-template-columns: 1.5em 1fr;
  gap: 0.25rem;
}

/* Quests read as sentences: one to a row. */
.quest-list {
  grid-template-columns: 1fr;
}

.trophy.quest .trophy-text {
  font-size: 0.85rem;
}

.trophy.quest.is-open {
  border-color: rgba(240, 200, 110, 0.6);
}

/* A secret: nothing to read until it is found. */
.trophy.is-hidden {
  border-style: dashed;
  opacity: 0.65;
}

.map-close {
  flex-shrink: 0;
  width: 2.125rem;
  height: 2.125rem;
  border-radius: 0.5rem;
  border: 0.0625rem solid #5b8580;
  background: #2f4542;
  color: var(--text);
  font-size: 0.9375rem;
  line-height: 1;
  cursor: pointer;
}

.map-close:hover,
.map-close:focus-visible {
  border-color: var(--accent);
}

/* The stage is the space the map may occupy; map.ts picks a whole-number
   upscale that fits inside it, so the map pixels stay square. */
.map-stage {
  /* As tall as the map in it: never squeezed (that cut the map's south off). */
  flex: 0 0 auto;
  display: grid;
  place-items: center;
}

/* In a wooden frame with an N on top, like the corner map. The border
   sits outside the width map.ts sets, so the map keeps its whole-number
   scale. */
.map-frame {
  position: relative;
  box-sizing: content-box;
  width: 18rem;
  max-width: calc(100% - 0.5rem);
  aspect-ratio: 3 / 2;
  border: 0.25rem solid #5a3a20;
  border-radius: 0.1875rem;
  background: #0b1010;
  box-shadow:
    0 0 0 0.0625rem #2a1a0e,
    0 0.25rem 0.875rem rgba(0, 0, 0, 0.5);
  margin-top: 0.625rem;
}

.map-frame::after {
  content: '';
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 0 0.0625rem #a07850;
  pointer-events: none;
}

.map-frame::before {
  content: 'N';
  position: absolute;
  top: -0.9375rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  padding: 0.125rem 0.3125rem 0.0625rem;
  border-radius: 0.1875rem;
  background: #5a3a20;
  box-shadow: 0 0 0 0.0625rem #2a1a0e;
  color: #f4f0e6;
  font:
    bold 0.6875rem/1.1 ui-monospace,
    monospace;
}

.map-frame canvas {
  display: block;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
}

.map-pins {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.map-pin {
  position: absolute;
  width: 0;
  height: 0;
}

/* The place's little picture — the corner map's icons, three times over. */
.map-dot {
  position: absolute;
  left: -0.625rem;
  top: -0.625rem;
  width: 1.3125rem;
  height: 1.3125rem;
  background: transparent center / 100% 100% no-repeat;
  image-rendering: pixelated;
}

.map-label {
  position: absolute;
  left: 50%;
  top: 0.75rem;
  transform: translateX(-50%);
  font-size: 0.625rem;
  line-height: 1.1;
  white-space: nowrap;
  color: #f4fbf8;
  /* An outline, not a box: boxes cover the map at this size. */
  text-shadow:
    0 0.0625rem 0 #0b1010,
    0 -0.0625rem 0 #0b1010,
    0.0625rem 0 0 #0b1010,
    -0.0625rem 0 0 #0b1010;
}

.map-pin.is-objective .map-dot {
  border-radius: 0.25rem;
  animation: map-pulse 1.1s ease-in-out infinite;
}

.map-pin.is-objective .map-label {
  color: #ffd257;
}

.map-pin.is-unknown .map-dot {
  opacity: 0.55;
}

.map-pin.is-unknown .map-label {
  color: #9fb2ae;
}

.map-pin.is-secret .map-label {
  color: #d9bcf3;
}

.map-pin.is-quest .map-label {
  color: #ffd257;
}

.map-pin.is-quest .map-dot {
  animation: map-bob 1.4s ease-in-out infinite;
}

/* A quest's spot: smaller than a place — many may show at once (the blueberry bushes). */
.map-pin.is-spot .map-dot {
  left: -0.4375rem;
  top: -0.4375rem;
  width: 0.9375rem;
  height: 0.9375rem;
}

.map-pin.is-spot .map-label {
  top: 0.5625rem;
  color: #c4ccff;
}

/* Where to look for something lost: a dotted ring, not a pin on the spot. */
.map-pin.is-search {
  pointer-events: none;
}

.map-ring {
  position: absolute;
  inset: 0;
  border: 0.125rem dotted #7df0b0;
  border-radius: 50%;
  background: rgba(125, 240, 176, 0.12);
}

.map-pin.is-search .map-label {
  top: 50%;
  transform: translate(-50%, -50%);
  color: #b8f7d2;
  white-space: normal;
  width: max-content;
  max-width: 9em;
  text-align: center;
}

.map-pin.is-player {
  /* Above the place pins: knowing where you are is the point of the map. */
  z-index: 1;
}

.map-pin.is-player .map-dot {
  left: -0.8125rem;
  top: -0.8125rem;
  width: 1.6875rem;
  height: 1.6875rem;
}

.map-pin.is-player .map-label {
  top: auto;
  bottom: 0.875rem;
  color: #ffb3b3;
}

@keyframes map-pulse {
  0%,
  100% {
    transform: scale(1);
    box-shadow: 0 0 0 0 rgba(255, 210, 87, 0.7);
  }
  50% {
    transform: scale(1.35);
    box-shadow: 0 0 0 0.25rem rgba(255, 210, 87, 0);
  }
}

@keyframes map-bob {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-0.1875rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  .map-pin.is-objective .map-dot,
  .map-pin.is-quest .map-dot {
    animation: none;
  }
}

.map-foot {
  display: flex;
  align-items: center;
  gap: 0.625rem 0.875rem;
  flex-wrap: wrap;
}

/* The counters take a row of their own; under them, what you do with the map
   on the left, and the quiet things — the links out, and Reset — on the right. */
.map-foot .map-counters {
  flex: 1 0 100%;
}

.map-actions,
.map-foot-end {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.map-foot-end {
  margin-left: auto;
  gap: 0.875rem;
  /* "Tap again to erase everything" is longer than the row on a small phone */
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* Reset keeps to the right edge, on whichever row it lands. */
.map-foot-end .map-reset {
  margin-left: auto;
}

/* The text page and the privacy page (about.html, privacy.html) */
.map-links {
  display: flex;
  gap: 0.875rem;
  margin: 0;
  font-size: 0.78rem;
}

.map-links a {
  color: var(--accent);
}

.map-reset {
  border: 0.0625rem solid #5b8580;
  background: #2f4542;
  color: var(--text);
  border-radius: 62.4375rem;
  padding: 0.25rem 0.75rem;
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
  opacity: 0.75;
}

.map-reset:hover,
.map-reset:focus-visible {
  opacity: 1;
  border-color: var(--accent);
}

/* On a phone: the two map buttons share a row evenly, and the links face
   Reset on the row below. */
@media (max-width: 520px) {
  .map-actions {
    flex: 1 0 100%;
    display: grid;
    grid-template-columns: 1fr 1fr;
    /* Same height, whatever the ☁ does to its line */
    align-items: stretch;
  }

  .map-foot-end {
    flex: 1 0 100%;
    justify-content: space-between;
  }
}

/* Fingers need more to hit than a pointer does. */
@media (hover: none), (pointer: coarse) {
  .map-reset {
    padding: 0.625rem 0.75rem;
  }

  .map-links a {
    display: inline-block;
    padding: 0.625rem 0;
  }
}

/* The cloud save's card over the map (ui/cloudSheet.ts). */
.cloud-sheet {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  padding: 0.75rem;
  border-radius: 0.75rem;
  background: rgba(10, 16, 15, 0.72);
}

/* Same trap as .map-screen: .hidden comes earlier in the file. */
.cloud-sheet.hidden {
  display: none;
}

.cloud-card {
  width: min(26.25rem, 100%);
  max-height: 100%;
  overflow: auto;
  padding: 0.875rem 1rem 1rem;
  border: 0.0625rem solid #5b8580;
  border-radius: 0.75rem;
  background: var(--panel);
  box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.5);
}

.cloud-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.625rem;
}

.cloud-head h3 {
  margin: 0;
  font-size: 1rem;
}

.cloud-body {
  display: grid;
  gap: 0.625rem;
  margin-top: 0.5rem;
  font-size: 0.88rem;
  line-height: 1.45;
}

.cloud-body p {
  margin: 0;
}

.cloud-note {
  opacity: 0.75;
  font-size: 0.8rem;
}

.cloud-code {
  padding: 0.5rem 0.625rem;
  border: 0.0625rem dashed #5b8580;
  border-radius: 0.5rem;
  background: #1b2826;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.95rem;
  letter-spacing: 0.03em;
  word-break: break-word;
  user-select: all;
}

.cloud-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.cloud-btn {
  padding: 0.375rem 0.875rem;
  border: 0.0625rem solid var(--accent);
  border-radius: 62.4375rem;
  background: var(--accent);
  color: #0f1817;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.cloud-btn.quiet {
  background: transparent;
  color: var(--text);
  border-color: #5b8580;
  font-weight: 400;
}

.cloud-btn:disabled {
  opacity: 0.55;
  cursor: default;
}

.cloud-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.cloud-form input {
  flex: 1 1 13.75rem;
  min-width: 0;
  padding: 0.4375rem 0.625rem;
  border: 0.0625rem solid #5b8580;
  border-radius: 0.5rem;
  background: #1b2826;
  color: var(--text);
  font: inherit;
  text-transform: uppercase;
}

.cloud-status {
  font-size: 0.8rem;
  opacity: 0.8;
}

.cloud-error {
  color: #ffb4a4;
}

.cloud-error:empty {
  display: none;
}

/* Second tap erases everything, so it stops looking like a quiet little link. */
.map-reset.is-armed {
  opacity: 1;
  border-color: #d98a7a;
  background: #4a2f2d;
  color: #ffd9d0;
}

/* ===== Help (H / ?) — reuses the map screen's backdrop and card ===== */

.help-screen {
  /* Above the map screen, so it can be opened from there. */
  z-index: 8;
}

.help-sheet {
  max-width: 35rem;
  height: auto;
  overflow: auto; /* a phone held sideways is shorter than the table */
}

.help-title {
  margin: 0;
  font-size: 1.05rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.help-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.help-table th,
.help-table td {
  padding: 0.5rem 0.375rem;
  border-top: 0.0625rem solid #33504b;
  text-align: left;
  vertical-align: top;
}

.help-table th {
  width: 38%;
  font-weight: 600;
  opacity: 0.85;
}

.help-table kbd,
.welcome-keys kbd {
  display: inline-block;
  min-width: 1.6em;
  padding: 0 0.3125rem;
  border: 0.0625rem solid #5b8580;
  border-bottom-width: 0.125rem;
  border-radius: 0.25rem;
  font: inherit;
  font-size: 0.85em;
  text-align: center;
}

.help-foot {
  margin: 0;
  font-size: 0.85rem;
  opacity: 0.85;
}

.help-foot a {
  color: var(--accent);
}

/* One column per device: keys where there is a keyboard, touch otherwise. */
.help-table .t,
.welcome-keys .t {
  display: none;
}

@media (hover: none), (pointer: coarse) {
  .help-table .k,
  .welcome-keys .k {
    display: none;
  }

  .help-table .t,
  .welcome-keys .t {
    display: inline;
  }
}

/* ===== The welcome card (ui/welcome.ts) — once, on a first visit ===== */

.welcome-screen {
  /* Above everything: it is the first thing there is. */
  z-index: 9;
  /* Lighter than the map's backdrop: the world you are about to walk shows. */
  background: rgba(8, 13, 13, 0.6);
}

.welcome-sheet {
  max-width: 28.75rem;
  height: auto;
  gap: 0;
  padding: 0;
  overflow: hidden;
}

/* The words scroll if the screen is short (a phone held sideways); the
   button under them stays where it is. */
.welcome-body {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  min-height: 0;
  padding: 1.375rem 1.375rem 1rem;
  overflow: auto;
}

.welcome-sheet p {
  margin: 0;
  line-height: 1.5;
}

.welcome-eyebrow {
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

.welcome-title {
  margin: -0.5rem 0 0;
  font-size: 1.45rem;
  line-height: 1.2;
}

.welcome-list {
  display: grid;
  gap: 0.625rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.welcome-list li {
  display: grid;
  grid-template-columns: 1.5em 1fr;
  gap: 0.5rem;
  line-height: 1.45;
}

.welcome-small {
  font-size: 0.85rem;
  opacity: 0.75;
}

.welcome-keys {
  padding: 0.5625rem 0.75rem;
  border-radius: 0.5rem;
  background: rgba(0, 0, 0, 0.2);
  font-size: 0.85rem;
}

.welcome-actions {
  display: flex;
  align-items: center;
  gap: 0.625rem 0.875rem;
  flex-wrap: wrap;
  padding: 0.875rem 1.375rem 1.125rem;
  border-top: 0.0625rem solid #33504b;
}

.welcome-start {
  padding: 0.625rem 1.375rem;
  border: 0.0625rem solid var(--accent);
  border-radius: 62.4375rem;
  background: var(--accent);
  color: #0f1817;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.welcome-start:hover,
.welcome-start:focus-visible {
  background: #a5e6d1;
}

.welcome-start:focus-visible {
  outline: 0.125rem solid var(--text);
  outline-offset: 0.125rem;
}

.welcome-links {
  font-size: 0.8rem;
}

.welcome-links a {
  color: var(--accent);
}

.map-counters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem 0.625rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.map-counters li {
  display: flex;
  gap: 0.375rem;
  align-items: baseline;
  border: 0.0625rem solid #4f6f6a;
  border-radius: 62.4375rem;
  padding: 0.1875rem 0.625rem;
  font-size: 0.78rem;
}

.map-counter-name {
  opacity: 0.8;
}

.map-counter-value {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--accent);
}

.touch-ui {
  position: fixed;
  left: max(0.625rem, env(safe-area-inset-left));
  right: max(0.625rem, env(safe-area-inset-right));
  bottom: max(0.625rem, env(safe-area-inset-bottom));
  z-index: 4;
  touch-action: none;
  display: none;
  justify-content: space-between;
  align-items: flex-end;
  pointer-events: none;
}

/* Words are for reading — and for copying. The game's own surface refuses
   selection (.viewport, above); what a bubble, a card or a list says does
   not, nor what you type into a form. */
.viewport :is(.panel, .help-table, .welcome-body, .trophies, .cloud-body),
.viewport :is(.panel, .help-table, .welcome-body, .trophies, .cloud-body) *,
.viewport :is(input, textarea) {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

/* …but a button among the words is a button. */
.viewport :is(.panel, .help-table, .welcome-body, .trophies, .cloud-body) :is(button, summary) {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* The debug readout (?debug) is there to be copied. */
.viewport .debug-readout {
  -webkit-user-select: text;
  user-select: text;
}

/* The cloud save's code is taken whole, at one touch (after the rule above: it stands among the cloud's words). */
.viewport .cloud-code {
  -webkit-user-select: all;
  user-select: all;
}

/* The touch controls take every gesture themselves: with the browser handling
   a long press, a control never heard the finger lift. */
.touch-ui,
.touch-ui * {
  touch-action: none;
}

/* Thumbstick: a translucent base with a knob that follows the thumb. */
.stick {
  position: relative;
  width: 8.5rem;
  height: 8.5rem;
  border-radius: 50%;
  pointer-events: auto;
  border: 0.0625rem solid rgba(91, 133, 128, 0.9);
  background: radial-gradient(circle, rgba(36, 51, 48, 0.35) 0 34%, rgba(36, 51, 48, 0.72) 35% 100%);
  box-shadow: inset 0 0 0 0.625rem rgba(15, 24, 23, 0.18);
}

.stick-marks {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  fill: rgba(237, 245, 242, 0.45);
  pointer-events: none;
}

.stick-knob {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 3.75rem;
  height: 3.75rem;
  margin: -1.875rem 0 0 -1.875rem;
  border-radius: 50%;
  border: 0.0625rem solid var(--accent);
  background: radial-gradient(circle at 36% 30%, #7aa59d, #2f4542 72%);
  box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.45);
  pointer-events: none;
  /* A layer of its own: following the thumb is then the compositor's job, not
     a repaint of the knob and the stick under it on every touch move. */
  will-change: transform;
  /* Springs back to the middle on release… */
  transition: transform 0.12s ease-out;
}

/* …but follows the thumb with no lag while it is held. */
.stick.is-active .stick-knob {
  transition: none;
  border-color: #c9f3e6;
}

.action-btn {
  width: 4rem;
  height: 4rem;
  border-radius: 50%;
  border: 0.0625rem solid #5b8580;
  background: rgba(36, 51, 48, 0.9);
  color: #fff;
  font-size: 1.1rem;
  font-weight: 700;
  pointer-events: auto;
}

@media (max-width: 980px) {
  /* Bottom sheet: a bubble tracking the avatar is unreadable on a phone. */
  .panel {
    left: 0.5rem;
    right: 0.5rem;
    top: auto;
    width: auto;
    max-width: none;
    /* Clear of the thumbstick. */
    bottom: calc(env(safe-area-inset-bottom) + 6rem);
    max-height: 46dvh;
  }

  .hud-corner > span,
  .hud-pill {
    font-size: 0.74rem;
    padding: 0.1875rem 0.5rem;
  }

  .debug-readout {
    font-size: 0.625rem;
    padding: 0.3125rem 0.375rem;
  }
}

@media (hover: none), (pointer: coarse) {
  .touch-ui {
    display: flex;
  }
}

/* On touch the toast goes above the thumbstick, not on top of it — and no
   higher: the hint sits under the avatar's feet, in the middle of the screen. */
@media (hover: none), (pointer: coarse) {
  .toast {
    bottom: calc(env(safe-area-inset-bottom) + 10rem);
  }
}

/* A phone held sideways is too short for that: 160 px up from the bottom is
   the middle of the picture, where the avatar and its hint are. There the
   toast goes to the bottom edge, between the stick and the A button — clear
   of the avatar, and of a mini-game's screen. (After the rule above, so it
   wins.) */
@media (hover: none) and (orientation: landscape) and (max-height: 480px),
  (pointer: coarse) and (orientation: landscape) and (max-height: 480px) {
  .toast {
    bottom: calc(env(safe-area-inset-bottom) + 0.75rem);
    max-width: calc(100vw - 21.25rem);
  }
}

/* While a bubble is open the world is paused, so the d-pad is useless — and it
   would sit on top of the sheet. Hide it… */
@media (hover: none), (pointer: coarse) {
  body:has(#panel:not(.hidden)) .touch-ui {
    display: none;
  }
}

/* …and let the sheet use the space. Only where the bubble IS a sheet (the
   narrow screens above): on a wide touch screen — a tablet held sideways, a
   touch monitor — it is a bubble beside the avatar, placed from its top by
   ui/panel.ts. Pinned at the bottom as well, it was stretched from one to the
   other, and its height moved with its place, frame after frame. */
@media (hover: none) and (max-width: 980px), (pointer: coarse) and (max-width: 980px) {
  body:has(#panel:not(.hidden)) .panel {
    bottom: calc(env(safe-area-inset-bottom) + 0.75rem);
    max-height: 60dvh;
  }

  /* The contact form is taller than a text: room for it down to Close. */
  body:has(#panel:not(.hidden)) .panel:has(form) {
    max-height: 80dvh;
  }
}

/* The map covers the screen, so the d-pad underneath it is both useless and
   in the way — same reasoning as the open bubble above. */
@media (hover: none), (pointer: coarse) {
  body:has(#map-screen:not(.hidden)) .touch-ui {
    display: none;
  }
}

/* Same for the help card, and the welcome card. */
@media (hover: none), (pointer: coarse) {
  body:has(#help-screen:not(.hidden)) .touch-ui,
  body:has(#welcome-screen:not(.hidden)) .touch-ui {
    display: none;
  }
}
