/* Tokens lifted from javiercarnicer.com/assets/styles.css so the two sites read
   as one brand, minus the web fonts — see index.html for why. */
:root {
  --bg: #0a0a0c;
  --text: #ECEAE3;
  --muted: #9b9b92;
  --dim: #6a6a63;
  --accent: #ff7a2f;
  --border: rgba(255, 255, 255, 0.09);
  --sans: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, monospace;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  /* The whole page is a viewport onto the world; nothing here ever scrolls,
     and on iOS this is also what stops a drag on the canvas bouncing the page. */
  overflow: hidden;
  overscroll-behavior: none;
}

body { -webkit-text-size-adjust: 100%; }

#scene {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* Steering is a drag gesture, so the browser must not claim it as a scroll. */
  touch-action: none;
}

/* ── Loader ─────────────────────────────────────────────────────────────── */

.loader {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  background: var(--bg);
  transition: opacity 0.6s var(--ease);
}

/* Faded out rather than removed, so the world is already rendering behind it. */
.loader.is-done { opacity: 0; pointer-events: none; }

.loader-inner { width: min(420px, 82vw); text-align: center; }

.loader-kicker {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 0.6rem;
}

.loader-title {
  font-size: clamp(2.4rem, 9vw, 3.6rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1;
  margin-bottom: 1.6rem;
}

.loader-bar {
  height: 2px;
  background: var(--border);
  border-radius: 2px;
  overflow: hidden;
}

.loader-bar span {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--accent);
  transition: width 0.3s var(--ease);
}

.loader-status {
  margin-top: 0.9rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--muted);
  min-height: 1.2em;
}

.loader-status.is-error { color: var(--accent); }

/* ── Fallbacks ──────────────────────────────────────────────────────────── */

.fallback {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: grid;
  align-content: center;
  justify-items: start;
  gap: 0.9rem;
  padding: 2rem clamp(1.5rem, 8vw, 6rem);
  background: var(--bg);
}

.fallback[hidden] { display: none; }
.fallback h1 { font-size: clamp(2rem, 7vw, 3rem); font-weight: 600; letter-spacing: -0.02em; }
.fallback p { max-width: 46ch; color: var(--muted); line-height: 1.6; }
.fallback a { color: var(--accent); }

/* ── Chrome ─────────────────────────────────────────────────────────────── */

.home-link {
  position: fixed;
  z-index: 10;
  left: max(1rem, env(safe-area-inset-left));
  bottom: max(1rem, env(safe-area-inset-bottom));
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--dim);
  text-decoration: none;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: rgba(10, 10, 12, 0.5);
  backdrop-filter: blur(6px);
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}

.home-link:hover { color: var(--text); border-color: rgba(255, 255, 255, 0.2); }

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.01ms !important; }
}

/* ── Menu ──────────────────────────────────────────────────────────────────
   Top-left corner. The minimap moves down to sit under it — the home link owns
   bottom-left, reverse owns bottom-right and respawn owns top-right, so this is
   the last free spot and the two share it vertically. */

.menu {
  position: fixed;
  z-index: 19;
  top: max(0.9rem, env(safe-area-inset-top));
  left: max(0.9rem, env(safe-area-inset-left));
}

.menu-toggle {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: rgba(10, 10, 12, 0.55);
  backdrop-filter: blur(6px);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}

.menu-toggle:hover { border-color: rgba(255, 255, 255, 0.24); }
.menu.is-open .menu-toggle { border-color: var(--accent); }

.menu-bars { display: grid; gap: 4px; }
.menu-bars i {
  display: block;
  width: 17px;
  height: 1.5px;
  background: var(--text);
  transition: transform 0.2s var(--ease), opacity 0.2s var(--ease);
}
/* Into a cross when open — the button is its own close button. */
.menu.is-open .menu-bars i:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.menu.is-open .menu-bars i:nth-child(2) { opacity: 0; }
.menu.is-open .menu-bars i:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

.menu-panel {
  position: absolute;
  top: 50px;
  left: 0;
  width: min(21rem, calc(100vw - 2rem));
  padding: 1rem 1.1rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(10, 10, 12, 0.9);
  backdrop-filter: blur(10px);
  /* Taller than a short phone in landscape, so it scrolls rather than running
     off the bottom of the screen. */
  max-height: calc(100vh - 5rem);
  overflow-y: auto;
}

.menu-panel[hidden] { display: none; }

.menu-heading {
  font-family: var(--mono);
  font-size: 0.64rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 0.5rem;
}

.menu-heading + .menu-row, .menu-table + .menu-heading { margin-top: 1rem; }

.menu-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.74rem;
}

.menu-table th, .menu-table td {
  text-align: left;
  padding: 0.28rem 0.4rem 0.28rem 0;
  font-weight: 400;
  vertical-align: top;
}

.menu-table thead th {
  font-family: var(--mono);
  font-size: 0.58rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
  border-bottom: 1px solid var(--border);
  padding-bottom: 0.35rem;
}

.menu-table tbody th { color: var(--text); }
.menu-table tbody td { color: var(--muted); font-family: var(--mono); font-size: 0.68rem; }

.menu-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.4rem 0;
  font-size: 0.8rem;
  cursor: pointer;
  /* A comfortable target without making the panel enormous. */
  min-height: 34px;
}

.menu-row input { accent-color: var(--accent); width: 16px; height: 16px; cursor: pointer; }

.menu-note {
  margin-top: 0.8rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 0.62rem;
  line-height: 1.5;
  color: var(--dim);
}

/* ── Minimap ───────────────────────────────────────────────────────────────
   Under the menu button, not beside it. */

.minimap {
  position: fixed;
  z-index: 12;
  top: calc(max(0.9rem, env(safe-area-inset-top)) + 52px);
  left: max(0.9rem, env(safe-area-inset-left));
  /* Never takes input — a drag started here has to reach the world. */
  pointer-events: none;
  border-radius: 50%;
  backdrop-filter: blur(4px);
}

/* ── Project card ──────────────────────────────────────────────────────────
   Opens when you stop inside a project's ring. Anchored bottom-centre on a
   phone and bottom-left on a wide screen, so it never lands where the drag
   hand is and never covers the car. */

.board-panel {
  position: fixed;
  z-index: 18;
  left: 50%;
  bottom: max(1.2rem, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  width: min(30rem, calc(100vw - 2rem));
  /* Only the card itself takes clicks; the rest of the strip must not steal
     drags meant for the world. */
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s var(--ease), translate 0.25s var(--ease);
  translate: 0 12px;
}

.board-panel[hidden] { display: none; }
.board-panel.is-open { opacity: 1; translate: 0 0; }

@media (min-width: 60rem) {
  .board-panel {
    left: max(1.5rem, env(safe-area-inset-left));
    transform: none;
  }
}

.board-card {
  pointer-events: auto;
  padding: 1.25rem 1.4rem 1.1rem;
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent);
  border-radius: 12px;
  background: rgba(10, 10, 12, 0.82);
  backdrop-filter: blur(10px);
}

.board-year {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dim);
}

.board-title {
  margin-top: 0.35rem;
  font-size: 1.4rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.15;
}

.board-subtitle {
  margin-top: 0.4rem;
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--muted);
}

.board-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1rem;
}

.board-visit {
  font-family: var(--mono);
  font-size: 0.76rem;
  color: var(--bg);
  background: var(--accent);
  text-decoration: none;
  padding: 0.55rem 0.85rem;
  border-radius: 8px;
  /* Full touch target even though it reads as a small chip. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

.board-close {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--muted);
  background: none;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.5rem 0.75rem;
  min-height: 44px;
}

.board-close:hover { color: var(--text); }

.board-hint {
  margin-top: 0.7rem;
  font-family: var(--mono);
  font-size: 0.64rem;
  color: var(--dim);
}

/* ── Touch controls ────────────────────────────────────────────────────────
   There is no joystick and no buttons. Steering, throttle, boost, jump and horn
   are all one drag started anywhere on screen — see State/TouchControls.js for why,
   and for how folio-2019 does the same with invisible regions.

   The only other control is an invisible reverse region in the bottom-RIGHT.
   The bottom-left is reserved: that is where the "← javiercarnicer.com" link
   lives, and the old joystick sat directly on top of it. */

.touch-controls {
  position: fixed;
  inset: 0;
  z-index: 15;
  /* The container must not swallow drags meant for the world; only the buttons
     take input, and the drag is read from the window. */
  pointer-events: none;
}

@media (pointer: fine) {
  /* A mouse gets the keyboard. Hidden rather than not built, so a laptop with a
     touchscreen still has them if it reports coarse. */
  .touch-controls { display: none; }
}

.touch-controls button {
  pointer-events: auto;
  /* Stops the long-press callout, the double-tap zoom and the blue tap flash,
     all three of which fire constantly while driving. */
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  user-select: none;
  touch-action: none;
}

/* The drag indicator. Present only while a thumb is down and past the dead
   zone, so nothing is ever drawn over the world at rest. Both marks are
   positioned from the viewport origin and moved entirely by transform, which
   keeps them off the layout path at 60fps. */

.touch-drag {
  position: fixed;
  inset: 0;
  pointer-events: none;
}

.touch-drag[hidden] { display: none; }

/* Both marks are moved by `transform: translate(clientX, clientY)`, which puts
   their TOP-LEFT on the thumb. The negative margin is what centres them, and it
   has to be half the size — done here in CSS so the JS only ever writes the raw
   pointer coordinates and never has to know how big these are. */
.touch-drag-origin,
.touch-drag-cursor {
  position: absolute;
  top: 0;
  left: 0;
  border-radius: 50%;
}

.touch-drag-origin {
  width: 52px;
  height: 52px;
  margin: -26px 0 0 -26px;
  border: 1.5px solid rgba(255, 255, 255, 0.28);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.18);
}

.touch-drag-cursor {
  width: 18px;
  height: 18px;
  margin: -9px 0 0 -9px;
  background: rgba(255, 122, 47, 0.85);
  box-shadow: 0 0 24px rgba(255, 122, 47, 0.5);
}

.touch-drag.is-boosting .touch-drag-cursor {
  background: #fff;
  box-shadow: 0 0 32px rgba(255, 255, 255, 0.8);
}

/* Quiet, but present. These started invisible, the way folio-2019's controls
   are, and reverse then could not be found at all — which is the same as not
   having reverse. Big touch targets, small labels, low contrast: they read as
   part of the HUD rather than as buttons over the world, and the bottom-left
   still belongs to the home link. */
.touch-zone {
  position: absolute;
  display: grid;
  place-items: center;
  width: 96px;
  height: 68px;
  padding: 0;
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  color: var(--text);
  background: rgba(10, 10, 12, 0.34);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 12px;
  opacity: 0.5;
  backdrop-filter: blur(4px);
  transition: opacity 0.12s var(--ease), background 0.12s var(--ease),
              border-color 0.12s var(--ease), transform 0.12s var(--ease);
}

.touch-zone:active {
  opacity: 1;
  background: rgba(255, 122, 47, 0.3);
  border-color: var(--accent);
  transform: scale(0.96);
}

/* Reverse, under the thumb that is already resting there. */
.touch-zone-reverse {
  right: max(0.9rem, env(safe-area-inset-right));
  bottom: max(0.9rem, env(safe-area-inset-bottom));
}

/* Respawn. Diagonally opposite the drag hand and HELD rather than tapped, so it
   takes deliberate effort to throw the car back to spawn. Smaller, because it
   is wanted far less often than reverse. */
.touch-zone-reset {
  right: max(0.9rem, env(safe-area-inset-right));
  top: max(0.9rem, env(safe-area-inset-top));
  width: 84px;
  height: 44px;
  font-size: 0.5rem;
  opacity: 0.36;
}
