/* --panel carries a little more of the ground than it used to, and --hud-dim a little more of the text: the HUD
   sits over a city that is sometimes a bright sky and sometimes a dark street, and it no longer has a backdrop
   blur to separate it from either. */
:root { color-scheme: dark; --hud: #e8eef7; --hud-dim: rgba(232, 238, 247, 0.82); --accent: #ffd23f; --panel: rgba(8, 14, 24, 0.62); --line: rgba(160, 200, 240, 0.45); --boot-bg: #070a11; }

/* The display cutout. game.html asks for viewport-fit=cover so the city fills the screen edge to edge, and that is
   exactly what puts the layout viewport under the notch and the home indicator: on an iPhone held in landscape —
   the way this game is played — the insets are ~59 px on the cutout side and 21 px along the bottom. Every fixed
   panel and every touch button therefore measures its offset from these, never from the raw viewport edge. They
   are named here rather than written inline so one probe can report them to the canvas HUD (which places the
   off-screen objective arrow in script, where env() cannot reach) and so the test harness can drive them: a
   headless browser has no cutout, so tools/game-test.mjs sets these four properties to stand in for one.
   Everything resolves to 0 px on a screen without insets, which is every desktop. */
:root { --sa-t: env(safe-area-inset-top, 0px); --sa-r: env(safe-area-inset-right, 0px); --sa-b: env(safe-area-inset-bottom, 0px); --sa-l: env(safe-area-inset-left, 0px); }
/* Read by src/game/ui/HUD.js: padding is a real property, so getComputedStyle always reports it resolved to
   pixels — a custom property holding env() is not guaranteed to come back substituted in every browser. */
#safe-probe { position: fixed; top: 0; left: 0; width: 0; height: 0; visibility: hidden; pointer-events: none; padding: var(--sa-t) var(--sa-r) var(--sa-b) var(--sa-l); }
/* overscroll-behavior: none is the belt to touch-action's braces. The canvas already tells the browser not to read
   a drag as a gesture of its own, but the HUD layer above it passes touches through and the intro card scrolls, and
   on Android Chrome a downward drag that reaches the document is pull-to-refresh — which reloads the game. */
html, body { margin: 0; height: 100%; overflow: hidden; overscroll-behavior: none; background: #0a0d14; font-family: 'Liberation Sans', 'Helvetica Neue', Arial, sans-serif; color: var(--hud); }
#game { position: fixed; inset: 0; width: 100%; height: 100%; display: block; touch-action: none; outline: none; }
#hud { position: fixed; inset: 0; pointer-events: none; user-select: none; }
#hud canvas { position: absolute; left: 0; top: 0; }
/* No backdrop-filter: behind these five panels the canvas changes every frame, so the compositor would re-blur
   the backdrop of each of them every frame for a 2 px softening that the panel's own fill already hides. */
#hud .panel { position: absolute; background: var(--panel); border: 1px solid var(--line); border-radius: 4px; padding: 8px 12px; }
#hud #status { left: calc(16px + var(--sa-l)); top: calc(14px + var(--sa-t)); min-width: 220px; }
#hud #status .level { display: flex; align-items: baseline; gap: 10px; }
#hud #status .level b { font-size: 26px; font-weight: 700; }
#hud #status .level span { font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--hud-dim); }
#hud .bar { position: relative; height: 6px; margin-top: 6px; background: rgba(255,255,255,0.14); border-radius: 3px; overflow: hidden; }
#hud .bar > i { position: absolute; left: 0; top: 0; bottom: 0; width: 0; background: #7cc7ff; border-radius: 3px; transition: width 0.2s ease; }
#hud #status .speed { margin-top: 6px; font-size: 13px; color: var(--hud-dim); font-variant-numeric: tabular-nums; }
#hud #objective { left: 50%; top: calc(14px + var(--sa-t)); transform: translateX(-50%); text-align: center; min-width: 260px; }
#hud #objective .title { font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent); }
#hud #objective .name { font-size: 18px; font-weight: 700; margin-top: 2px; }
#hud #objective .detail { font-size: 13px; color: var(--hud-dim); margin-top: 2px; }
#hud #objective .timer { font-size: 13px; margin-top: 2px; font-variant-numeric: tabular-nums; }
#hud #minimap { right: calc(16px + var(--sa-r)); bottom: calc(16px + var(--sa-b)); padding: 6px; }
#hud #minimap canvas { position: static; display: block; border-radius: 3px; }
#hud #dashes { position: absolute; left: calc(16px + var(--sa-l)); bottom: calc(16px + var(--sa-b)); display: flex; gap: 6px; }
#hud #dashes i { display: block; width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--line); }
/* the display above outranks the UA rule for [hidden], so locked charges need saying again: without this the HUD
   shows four dash pips from level 1 and the indicator never matches what the player has actually unlocked */
#hud #dashes i[hidden] { display: none; }
#hud #dashes i.on { background: #7cc7ff; border-color: #7cc7ff; }
#hud #toast { left: 50%; top: 22%; transform: translateX(-50%); font-size: 22px; font-weight: 700; color: var(--accent); text-shadow: 0 2px 8px rgba(0,0,0,0.6); opacity: 0; transition: opacity 0.3s ease; background: none; border: none; }
#hud #toast.show { opacity: 1; }
/* The hint teaches the controls, then retires; it comes back only to report that the mouse has been released,
   which is otherwise an invisible state (mouse look simply stops). src/game/ui/HUD.js drives the two classes. */
#hud #hint { position: absolute; left: 50%; bottom: calc(24px + var(--sa-b)); transform: translateX(-50%); font-size: 13px; color: var(--hud-dim); white-space: nowrap; text-shadow: 0 1px 3px rgba(0,0,0,0.7); opacity: 1; transition: opacity 600ms ease; }
#hud #hint.is-out { opacity: 0; }
#hud #hint.is-call { color: var(--hud); background: var(--panel); border: 1px solid var(--line); border-radius: 4px; padding: 6px 12px; }
/* pre-wrap rather than pre: this panel is how a phone reports what it decided about itself (src/game/ui/HUD.js),
   and a line that runs off the right edge of a 393 px screen is the one line nobody can read. */
#hud #debug { right: calc(16px + var(--sa-r)); top: calc(14px + var(--sa-t)); font: 11px/1.4 'DejaVu Sans Mono', monospace; white-space: pre-wrap; overflow-wrap: anywhere; max-width: min(360px, 62vw); }
#hud #debug[hidden] { display: none; }
#hud .flash { position: absolute; inset: 0; pointer-events: none; opacity: 0; background: radial-gradient(ellipse at center, rgba(0,0,0,0) 55%, rgba(255,255,255,0.35) 100%); transition: opacity 0.4s ease; }
#hud .flash.impact { background: radial-gradient(ellipse at center, rgba(0,0,0,0) 45%, rgba(0,0,0,0.6) 100%); }
#hud .flash.show { opacity: 1; transition: none; }

#overlay { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; padding: calc(20px + var(--sa-t)) calc(20px + var(--sa-r)) calc(20px + var(--sa-b)) calc(20px + var(--sa-l)); background: rgba(6, 10, 18, 0.72); z-index: 5; }
#overlay[hidden] { display: none; }
/* the card scrolls inside the viewport rather than running off it: a short landscape phone has ~350 px of height */
#overlay .card { max-width: 560px; max-height: 100%; overflow: auto; overscroll-behavior: contain; padding: 28px 32px; background: rgba(10, 16, 28, 0.92); border: 1px solid var(--line); border-radius: 6px; }
#overlay h1 { margin: 0 0 6px; font-size: 26px; letter-spacing: 0.02em; }
#overlay p { margin: 6px 0; color: var(--hud-dim); font-size: 14px; }
#overlay table { margin-top: 12px; border-collapse: collapse; font-size: 13px; }
#overlay td { padding: 3px 10px 3px 0; }
#overlay td:first-child { color: var(--accent); font-weight: 700; white-space: nowrap; }
#overlay button { margin-top: 16px; padding: 10px 22px; font-size: 16px; font-weight: 700; color: #0b1220; background: var(--accent); border: none; border-radius: 4px; cursor: pointer; touch-action: manipulation; }

/* The intro card's two control tables. The pointer one is the default so that the card is never blank — before
   src/game/ui/HUD.js has run, and on a page with no scripting at all, it is the table that shows. */
#overlay .controls--touch { display: none; }
html[data-input="touch"] #overlay .controls--pointer { display: none; }
html[data-input="touch"] #overlay .controls--touch { display: table; }

/* Above the veil (7) as well as the cards: a message saying the game cannot run must never be the thing that is
   covered up, least of all by the transition that was meant to reveal it. */
#unsupported { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: #0a0d14; z-index: 8; text-align: center; padding: calc(24px + var(--sa-t)) calc(24px + var(--sa-r)) calc(24px + var(--sa-b)) calc(24px + var(--sa-l)); }
#unsupported[hidden] { display: none; }

#touch { position: fixed; right: calc(18px + var(--sa-r)); bottom: calc(90px + var(--sa-b)); display: none; gap: 12px; z-index: 4; }
/* touch-action: none stops the browser claiming the press for a scroll, a pan or a double-tap zoom before the
   pointerdown ever reaches the game; user-select and the tap highlight stop a held button selecting its own label
   or flashing the platform's grey rectangle over it. */
#touch button { width: 64px; height: 64px; border-radius: 50%; border: 2px solid var(--line); background: rgba(8, 14, 24, 0.62); color: var(--hud); font-weight: 700; font-size: 13px; touch-action: none; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; }
/* a press has to be legible: without a pressed state the only feedback is what happens in the world */
#touch button:active { background: rgba(124, 199, 255, 0.35); border-color: #7cc7ff; }
@media (pointer: coarse) { #touch { display: flex; } #hud #hint { display: none; } }
/* A short landscape phone has ~350 px of height: full-size buttons stacked three deep run off it. */
@media (pointer: coarse) and (max-height: 430px) {
  #touch { bottom: calc(14px + var(--sa-b)); gap: 9px; }
  #touch button { width: 56px; height: 56px; font-size: 12px; }
}

/* Portrait on a phone. Not a blocker — the game keeps running behind it, and turning the device is the whole
   interaction — so it is a strip rather than a wall, and it exists only for the viewport that has the problem. */
#rotate { display: none; position: fixed; left: 0; right: 0; bottom: calc(12px + var(--sa-b)); z-index: 6; justify-content: center; pointer-events: none; }
#rotate > div { display: flex; align-items: center; gap: 10px; padding: 10px 16px; font-size: 14px; background: var(--panel); border: 1px solid var(--line); border-radius: 999px; }
.rotate__icon { display: block; width: 13px; height: 20px; border: 2px solid var(--accent); border-radius: 3px; animation: rotate-tip 2.6s ease-in-out infinite; }
@keyframes rotate-tip { 0%, 40% { transform: rotate(0); } 60%, 100% { transform: rotate(90deg); } }
@media (pointer: coarse) and (orientation: portrait) and (max-width: 560px) { #rotate { display: flex; } }
/* the intro card owns the screen while it is up: the strip would sit over its Play button on a portrait phone */
#overlay:not([hidden]) ~ #rotate, #unsupported:not([hidden]) ~ #rotate { display: none; }
@media (prefers-reduced-motion: reduce) { .rotate__icon { animation: none; transform: rotate(90deg); } }

/* keyboard focus stays visible on everything that can take it (the canvas suppresses its own ring by design) */
#overlay button:focus-visible, #touch button:focus-visible { outline: 2px solid #7cc7ff; outline-offset: 2px; }

/* Narrow or short viewports: the card and the hint stay inside the frame instead of running off its edges. */
@media (max-width: 560px), (max-height: 560px) {
  #overlay { padding: calc(12px + var(--sa-t)) calc(12px + var(--sa-r)) calc(12px + var(--sa-b)) calc(12px + var(--sa-l)); }
  #overlay .card { padding: 20px 18px 18px; }
  #overlay h1 { font-size: 21px; }
  #overlay p { font-size: 13px; }
  #overlay table { font-size: 12px; }
  #overlay td { padding: 3px 8px 3px 0; }
  #overlay td:first-child { white-space: normal; }
  #hud #hint { white-space: normal; max-width: min(92vw, 520px); line-height: 1.5; }
  #hud #status { min-width: 0; }
  #hud #objective { min-width: 0; max-width: min(76vw, 320px); }
}

/* Arriving from the landing page's Play now (the fall, src/game/fall.js). The old page's last frame is the dive
   already under way; the game resumes the same trajectory at the same instant, so the handoff is a crossfade in
   which the outgoing frame keeps closing in (scale, blur) over the live city rising behind it. The landing opts in
   with the same @view-transition rule and types the transition 'fall' at pageswap; any other navigation into the
   game is a plain crossfade. Browsers without cross-document view transitions get the #veil instead. */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: 320ms ease-out both vt-fade-out; }
::view-transition-new(root) { animation: 320ms ease-out both vt-fade-in; }
@keyframes vt-fade-out { to { opacity: 0; } }
@keyframes vt-fade-in { from { opacity: 0; } }
/* The transition only begins once the game has a frame to show, so whatever the outgoing snapshot spends on screen
   before that is already spent standing still. What it owes the visitor from that point is motion, quickly: it is
   short, it moves the way the camera was moving (closing in, drifting up), and the live city is fully in before the
   half-second the old timing spent fading. Both frames are the same instant of the same dive — the crossfade is
   there to cover the seam's lost time, not to introduce a shot. */
html:active-view-transition-type(fall)::view-transition-old(root) { animation: 380ms cubic-bezier(0.4, 0, 0.9, 0.5) both fall-out; }
html:active-view-transition-type(fall)::view-transition-new(root) { animation: 300ms cubic-bezier(0.3, 0, 0.5, 1) both fall-in; }
html:active-view-transition-type(fall)::view-transition-group(root) { animation-duration: 380ms; }
@keyframes fall-out { 45% { opacity: 1; } to { transform: scale(1.16) translateY(-1.8%); filter: blur(9px); opacity: 0; } }
@keyframes fall-in { from { opacity: 0; filter: blur(4px); } 60% { filter: blur(0); } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }

/* No view transition available: a dark veil is up before the first paint and clears once the live frame is in */
#veil { position: fixed; inset: 0; z-index: 7; background: #05070c; pointer-events: none; opacity: 1; transition: opacity 380ms ease-out; }
#veil.is-clear { opacity: 0; }
#veil[hidden] { display: none; }
/* raised by src/game/gate.js before the first paint, so there is something to fade from (see the note there) */
html[data-veil="up"] #veil { display: block; }

/* The dive: no HUD until the roof, then it comes in with the controls hint */
html[data-drop="falling"] #hud, html[data-drop="falling"] #touch { opacity: 0; }
html[data-drop="landed"] #hud, html[data-drop="landed"] #touch { opacity: 1; transition: opacity 700ms ease-out 250ms; }
