/* ROOFTOP QUEST — the phone layer.
 *
 * Load this LAST, after assets/hud.css. Two reasons, and both bite:
 *
 *  - several rules below deliberately shrink a HUD panel. They win on
 *    specificity (body.is-touch .hud-zone scores 0,2,1 against hud.css's
 *    0,1,0), but the pad container's own [hidden] rule does not win on
 *    specificity alone and depends on source order. See the note there.
 *  - assets/hud.css already hides the key legend at (pointer: coarse). The
 *    duplicate here is not redundant: `body.is-touch` is set from JS and can be
 *    forced on with ?touch, and the two must agree in that case.
 *
 * EVERY rule that touches something owned by another module is scoped under
 * `body.is-touch`, which src/ui/mobile.js sets from matchMedia('(pointer:
 * coarse)'). Nothing in here fires on a desktop, so the desktop HUD cannot
 * regress from an edit to this file.
 *
 * Only .mob-* is owned outright. The HUD grid is NOT restructured — no area
 * moves, no template changes. Panels get smaller, two of them get hidden, and
 * the progress rail gets narrower so it clears the thumbs. That is all.
 */

/* =================================================================== pads
 *
 * JUMP, DIVE and RESPAWN, in the bottom-right corner where a right thumb
 * already is. The left half of the screen belongs to the floating stick in
 * src/game/player.js — nothing here may sit there.
 *
 * The staircase (RESPAWN above DIVE, JUMP outboard and largest) is deliberate:
 * the two you press mid-jump are lowest and closest to the thumb's rest
 * position, and the one that throws your run away is the smallest and furthest.
 */

.mob-pads {
  position: fixed;
  z-index: calc(var(--z-hud) + 1);
  right: calc(var(--gutter) + env(safe-area-inset-right, 0px));
  bottom: calc(var(--gutter) + env(safe-area-inset-bottom, 0px));
  display: none;
  grid-template-areas:
    "resp ."
    "dive jump";
  align-items: end;
  justify-items: end;
  gap: var(--s-2) var(--s-3);
  /* The gaps between the buttons are not buttons. A drag that starts in one
   * should reach the canvas and turn the camera. */
  pointer-events: none;
}

body.is-touch .mob-pads { display: grid; }

/* ⚠ ORDER AND SPECIFICITY, both. `body.is-touch .mob-pads` scores 0,2,1 and
 * beats a plain `.mob-pads[hidden]` at 0,2,0 — the pads would then be visible
 * on the landing page and over the results card with no error anywhere. The
 * hide rule has to carry the same scope, and come after. */
body.is-touch .mob-pads[hidden] { display: none; }

.mob-btn {
  display: grid;
  place-items: center;
  border-radius: var(--r-pill);
  border: 2px solid rgba(255, 255, 255, 0.32);
  font-family: var(--font);
  font-weight: var(--fw-black);
  font-size: var(--fs-sm);
  line-height: 1;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  box-shadow: var(--sh-lip), var(--sh-2);
  pointer-events: auto;
  /* Without this the press pans the page on Android before pointerdown even
   * lands, and preventDefault() cannot get it back. */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  user-select: none;
  -webkit-user-select: none;
  transition:
    transform var(--dur-1) var(--ease-out),
    box-shadow var(--dur-1) var(--ease-out),
    filter var(--dur-1) var(--ease-out);
}

/* Deliberately NOT .btn. theme.css gives .btn:active a translateY of 3px at
 * 0,2,0 — the same score as this rule — so which one won would come down to
 * source order, and :active is unreliable on touch anyway (it sticks after the
 * finger leaves, and some browsers delay it). The class is set from
 * pointerdown, so the press reads at the same instant the jump is sent. */
.mob-btn.is-down {
  transform: translateY(4px) scale(0.95);
  box-shadow: none;
  filter: brightness(1.2);
}

.mob-btn--jump {
  grid-area: jump;
  width: 92px;
  height: 92px;
  background: var(--c-green);
  color: var(--c-ink);
  font-size: var(--fs-md);
}

.mob-btn--dive {
  grid-area: dive;
  width: 74px;
  height: 74px;
  background: var(--c-pink-hot);
  color: var(--c-text);
}

/* No backdrop-filter here, unlike every other dark surface in the game. A blur
 * that samples a live 3D canvas is re-read by the compositor on every frame the
 * canvas paints, which is all of them; the HUD already pays that four times and
 * a phone GPU is where it starts to show. A flat 88% ink is indistinguishable
 * at 58px and costs nothing. */
.mob-btn--resp {
  grid-area: resp;
  width: 58px;
  height: 58px;
  background: rgba(28, 23, 48, 0.88);
  border-color: var(--c-line-strong);
  color: var(--c-text-dim);
  box-shadow: var(--sh-lip-sm), var(--sh-1);
  --icon-size: 24px;
}

/* src/ui/icons.js emits class `ico` sized at 1em, so the --icon-size token on
 * the button never reaches the glyph on its own. Same bind as hud.css does,
 * scoped to this layer. */
.mob-pads .ico {
  display: block;
  flex: 0 0 auto;
  width: var(--icon-size);
  height: var(--icon-size);
}

/* A small landscape phone (iPhone SE is 667x375) has less room under the thumb
 * than a big one, not more — the buttons come down with the viewport so the
 * cluster keeps clearing the progress rail. 56px is the floor everywhere. */
@media (max-width: 720px), (max-height: 380px) {
  .mob-pads { gap: var(--s-1) var(--s-2); }
  .mob-btn--jump { width: 78px; height: 78px; font-size: var(--fs-sm); }
  .mob-btn--dive { width: 64px; height: 64px; font-size: var(--fs-xs); }
  .mob-btn--resp { width: 56px; height: 56px; --icon-size: 22px; }
}

/* ============================================================ turn it over
 *
 * The fallback for every phone where the orientation lock did not take: iOS
 * Safari, which has no lock at all, and anything that refused fullscreen.
 * src/ui/mobile.js toggles .is-on straight off a matchMedia change, so this
 * appears and disappears on the turn with no frame of its own. */

.mob-rotate {
  position: fixed;
  inset: 0;
  /* Over everything, including the results card at --z-modal. */
  z-index: calc(var(--z-modal) + 20);
  display: none;
  place-items: center;
  padding: calc(var(--s-6) + env(safe-area-inset-top, 0px))
           calc(var(--s-6) + env(safe-area-inset-right, 0px))
           calc(var(--s-6) + env(safe-area-inset-bottom, 0px))
           calc(var(--s-6) + env(safe-area-inset-left, 0px));
  text-align: center;
  color: var(--c-text);
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(63, 216, 242, 0.16), transparent 68%),
    rgba(18, 14, 32, 0.95);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  /* It covers the canvas, so it must also eat the drag that would otherwise
   * spin the camera behind it. */
  pointer-events: auto;
}

.mob-rotate.is-on { display: grid; }

/* ⚠ The BACKDROP never animates. Fading the overlay itself means its opacity is
 * whatever the animation has reached, and a browser that throttles animations —
 * a backgrounded tab, a hidden pane, reduced-motion — leaves the gate sitting at
 * 0.87 with the results card legible straight through it. A gate that can be
 * read through is not a gate. Only the contents fade. */
.mob-rotate__inner {
  display: grid;
  justify-items: center;
  gap: var(--s-4);
  max-width: 30ch;
  animation: ps-fade-in var(--dur-2) var(--ease-out);
}

.mob-rotate__plate {
  display: grid;
  place-items: center;
  width: 128px;
  height: 128px;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 42%, rgba(92, 192, 106, 0.22), rgba(92, 192, 106, 0) 68%);
  color: var(--c-green);
}

/* The glyph is an <svg> laid out as a replaced element, so transform-origin is
 * the plain box centre and none of the SVG transform-box mess applies. */
.mob-phone {
  width: 84px;
  height: 84px;
  animation: mob-turn 2.8s var(--ease-out) infinite;
  transform-origin: 50% 50%;
}

/* Upright, turn, hold sideways, return. The hold is what makes it read as an
 * instruction rather than a spinner. */
@keyframes mob-turn {
  0%, 20% { transform: rotate(0deg); }
  44%, 76% { transform: rotate(-90deg); }
  100% { transform: rotate(0deg); }
}

.mob-rotate__head {
  font-family: var(--display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--tr-title);
  text-transform: uppercase;
  text-shadow: var(--sh-text);
}

.mob-rotate__copy {
  font-size: var(--fs-sm);
  color: var(--c-text-dim);
  text-wrap: balance;
}

/* Portrait on a phone is short of width, not height; the glyph is the first
 * thing that can go. */
@media (max-width: 380px) {
  .mob-rotate__plate { width: 104px; height: 104px; }
  .mob-phone { width: 68px; height: 68px; }
  .mob-rotate__head { font-size: var(--fs-lg); }
}

/* ================================================================ the HUD
 *
 * Shrink only. No grid area moves and no template changes — src/selftest.js
 * asserts that no two [data-panel] boxes intersect, and that guarantee comes
 * from the grid in assets/hud.css being the only thing placing them. */

body.is-touch .hud {
  /* The notch is on the LEFT or the RIGHT in landscape, which is the whole
   * reason this is four values and not one. --gutter alone puts the brand
   * lockup under the sensor housing on a notched phone held with the camera
   * on the left. */
  padding:
    calc(var(--gutter) * 0.6 + env(safe-area-inset-top, 0px))
    calc(var(--gutter) * 0.6 + env(safe-area-inset-right, 0px))
    calc(var(--gutter) * 0.6 + env(safe-area-inset-bottom, 0px))
    calc(var(--gutter) * 0.6 + env(safe-area-inset-left, 0px));
  gap: var(--s-2);
}

/* No keyboard, no keys worth legending; and the look hint names a key that is
 * not there. They are the only two panels that are pure noise on a phone, and
 * dropping them is what gives the pads their corner back. Both are
 * [data-panel]s, but a display:none box measures 0x0 and the selftest's overlap
 * check filters those out before it compares anything. */
body.is-touch .hud-legend,
body.is-touch .hud-hint { display: none; }

body.is-touch .hud-brand { --icon-size: 20px; }
body.is-touch .hud-brand__name { font-size: var(--fs-sm); }

body.is-touch .hud-zone,
body.is-touch .hud-qual {
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-md);
}

body.is-touch .hud-zone__course { font-size: var(--fs-sm); }
body.is-touch .hud-qual__n { font-size: var(--fs-md); }

body.is-touch .hud-clock {
  padding: var(--s-1) var(--s-3);
  font-size: var(--fs-sm);
  --icon-size: 16px;
}

/* 0,3,1 — has to outscore hud.css's own `.hud .btn--round` at 0,2,0 inside its
 * max-width query, or the round buttons stay at whatever that rule last said. */
body.is-touch .hud .btn--round {
  width: 40px;
  height: 40px;
  --icon-size: 18px;
}

body.is-touch .hud-toast {
  padding: var(--s-2) var(--s-4);
  font-size: var(--fs-sm);
}

/* The rail is `width: min(620px, 100%)` centred across the full width, which on
 * a landscape phone runs its right-hand end straight under the JUMP button and
 * its left end under the stick. Reserve a thumb's worth of column on each side
 * and let it have the middle.
 *
 * 220px is measured, not guessed. The cluster is 92 + 12 + 74 = 178 wide and
 * sits ~8px further in than the HUD's own content edge (the HUD pads at 0.6
 * gutter, the pads at a full one), and the finish crown hangs 13px PAST the
 * right end of the rail because it is pinned at left:100% and pulled back by
 * half its width. Reserve for the crown, not for the track, or the badge lands
 * on the DIVE button on a 852px phone with room apparently to spare.
 *
 * The clamp floor matters too: on a 568px viewport the subtraction goes to
 * almost nothing, and a 0-wide rail is a rail with the player marker stacked
 * on top of that same crown. */
body.is-touch { --mob-thumb: 220px; }

/* The buttons came down at this size, so the column can too. */
@media (max-width: 720px), (max-height: 380px) {
  body.is-touch { --mob-thumb: 190px; }
}

body.is-touch .hud-rail {
  width: clamp(150px, calc(100% - 2 * var(--mob-thumb)), 620px);
}

/* ============================================================== the landing
 *
 * Owned by assets/landing.css; the one thing it cannot know is where the notch
 * is. Same four-value pad as the HUD, and nothing else. */
body.is-touch .ld {
  padding:
    calc(var(--gutter) + env(safe-area-inset-top, 0px))
    calc(var(--gutter) + env(safe-area-inset-right, 0px))
    calc(var(--gutter) + env(safe-area-inset-bottom, 0px))
    calc(var(--gutter) + env(safe-area-inset-left, 0px));
}

/* There is deliberately no `@media (pointer: fine) { .mob-pads { display:none } }`
 * to close this file. It would be dead: .mob-pads is display:none by default
 * and only `body.is-touch .mob-pads` turns it on, so a fine pointer never sees
 * a pad — and at 0,1,0 that rule would lose to the 0,2,1 one above anyway,
 * which is exactly the sort of thing that reads as a working guard and is not. */
