/* The Repository — night-garden UI overlay.
   Ground: space indigo. Ink: warm off-white. Accent: lantern amber.
   Display: Bricolage Grotesque. UI: Atkinson Hyperlegible. */

:root {
  --ground: #0b0e1d;
  --ink: #f2efe6;
  --ink-2: #a9a3c4;
  --amber: #ffb84d;
  --panel-bg: rgba(11, 14, 29, 0.88);
  --accent: var(--amber); /* app.js retints this per world */
  --display: "Bricolage Grotesque", system-ui, sans-serif;
  --ui: "Atkinson Hyperlegible", system-ui, sans-serif;
}

* { box-sizing: border-box; }

/* The page behind the canvas. terrain.js keeps this in step with the sky's
   horizon colour, so on an iPhone in landscape the safe-area strips either
   side read as sky rather than as the black bars the owner reported. */
html { background: var(--ground); }

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  height: 100dvh;          /* dvh = the live visual viewport on mobile Safari */
  overflow: hidden;
  overscroll-behavior: none;
  color: var(--ink);
  font-family: var(--ui);
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
}

/* With viewport-fit=cover, a fixed full-inset body covers the WHOLE screen —
   notch strip, home-bar strip and all. The safe-area insets are then used for
   UI padding only; they never shrink the page or the canvas. */
body {
  position: fixed;
  inset: 0;
  background: transparent;
}

#stage {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  width: 100vw;
  height: 100dvh;
  display: block;
  touch-action: none;
  cursor: grab;
}
#stage.is-dragging { cursor: grabbing; }
#stage.is-hovering { cursor: pointer; }

/* ---------- Projected labels ---------- */

#labels { position: fixed; inset: 0; overflow: hidden; pointer-events: none; }

.label3d {
  position: absolute;
  left: 0;
  top: 0;
  transform: translate(-50%, -50%);
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(0.8rem, 1.6vw, 1.05rem);
  letter-spacing: 0.01em;
  color: var(--ink);
  text-shadow: 0 2px 12px rgba(5, 6, 14, 0.9);
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.35s ease-out;
  will-change: transform, opacity;
}
.label3d.is-visible { opacity: 1; }
.label3d__count {
  display: block;
  text-align: center;
  font-family: var(--ui);
  font-weight: 400;
  font-size: 0.72em;
  color: var(--ink-2);
}
/* Project markers read as self-contained dark chips: legible on any zone,
   day or night, so they never depend on the bright-world ink overrides. */
.label3d--object {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink);
  background: rgba(11, 14, 29, 0.78);
  border: 1px solid rgba(242, 239, 230, 0.18);
  border-radius: 999px;
  padding: 0.28rem 0.7rem;
  text-shadow: 0 1px 6px rgba(5, 6, 14, 0.9);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

/* ---------- HUD ---------- */

.hud {
  position: fixed;
  left: 0;
  right: 0;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  /* horizontal safe-area padding too: in landscape the notch strip would
     otherwise sit on top of the wordmark and the Browse-as-list link */
  padding: calc(env(safe-area-inset-top, 0px) + 1.1rem)
           calc(env(safe-area-inset-right, 0px) + 1.4rem)
           1.1rem
           calc(env(safe-area-inset-left, 0px) + 1.4rem);
  pointer-events: none;
  z-index: 20;
}
.hud--top { top: 0; }
.hud--bottom {
  top: auto;
  bottom: 0;
  align-items: flex-end;
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 1rem);
}
.hud a, .hud button { pointer-events: auto; }

.wordmark { text-decoration: none; color: var(--ink); line-height: 1.05; }
.wordmark__title {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.15rem, 2.6vw, 1.6rem);
}
.wordmark__site {
  display: block;
  font-size: 0.78rem;
  color: var(--ink-2);
  letter-spacing: 0.04em;
}

.hud__actions { display: flex; gap: 0.5rem; }

.hud-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--ui);
  font-size: 0.85rem;
  color: var(--ink);
  text-decoration: none;
  background: rgba(11, 14, 29, 0.55);
  border: 1px solid rgba(242, 239, 230, 0.22);
  border-radius: 999px;
  padding: 0.45rem 0.95rem;
  cursor: pointer;
  transition: border-color 0.2s ease-out, background 0.2s ease-out;
}
.hud-btn:hover { border-color: var(--accent); background: rgba(11, 14, 29, 0.8); }
.hud-btn:focus-visible,
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 2px;
}
.hud-btn[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }

.tagline {
  margin: 0;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(0.85rem, 1.8vw, 1.05rem);
  color: var(--ink-2);
  line-height: 1.45;
  max-width: 22ch;
}

.hint {
  margin: 0;
  font-size: 0.8rem;
  color: var(--ink-2);
  text-align: center;
  flex: 1;
  transition: opacity 0.6s ease-out;
}
.hint.is-hidden { opacity: 0; }
.hint__short { display: none; }

.hud__links {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.15rem;
  font-size: 0.8rem;
}
.hud__links a { color: var(--ink-2); text-decoration: none; }
.hud__links a:hover { color: var(--accent); }
.hud__addr { color: rgba(169, 163, 196, 0.55); }

/* ---------- In-world bar ---------- */

.worldbar {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 4.4rem);
  left: 1.4rem;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
}
.worldbar__name {
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.3rem, 3.4vw, 2.1rem);
  color: var(--ink);
  text-shadow: 0 2px 16px rgba(5, 6, 14, 0.8);
}

/* ---------- Project panel ---------- */

.panel {
  position: fixed;
  right: 1.2rem;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 1.2rem);
  z-index: 30;
  width: min(23.5rem, calc(100vw - 2.4rem));
  max-height: min(65vh, 32rem);
  overflow-y: auto;
  background: var(--panel-bg);
  border: 1px solid var(--accent);
  border-radius: 14px;
  box-shadow: 0 14px 40px rgba(4, 5, 12, 0.6);
  padding: 1.15rem 1.25rem 1.25rem;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  animation: panel-in 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes panel-in {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}

.panel__close {
  position: absolute;
  top: 0.55rem;
  right: 0.6rem;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-2);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}
.panel__close:hover { color: var(--ink); background: rgba(242, 239, 230, 0.08); }

.panel__kicker {
  margin: 0 0 0.2rem;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* --accent-bright: readable-on-dark variant for worlds whose accent is deep */
  color: var(--accent-bright, var(--accent));
}
.panel__title {
  margin: 0 0 0.45rem;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.35rem;
  line-height: 1.15;
  padding-right: 1.5rem;
}
.panel__desc {
  margin: 0 0 1rem;
  color: var(--ink-2);
  line-height: 1.5;
  font-size: 0.92rem;
  max-width: 60ch;
}

.panel__launch {
  display: inline-block;
  font-family: var(--display);
  font-weight: 600;
  font-size: 0.95rem;
  color: #14100a;
  background: var(--accent);
  text-decoration: none;
  border-radius: 999px;
  padding: 0.6rem 1.3rem;
  transition: transform 0.15s ease-out, box-shadow 0.15s ease-out;
}
.panel__launch:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
}

.panel__list { display: flex; flex-direction: column; gap: 0.15rem; margin: 0 0 0.6rem; }
.panel__list a {
  color: var(--ink);
  text-decoration: none;
  padding: 0.5rem 0.6rem;
  border-radius: 8px;
  line-height: 1.35;
}
.panel__list a:hover { background: rgba(242, 239, 230, 0.07); }
.panel__list a strong { font-family: var(--display); font-weight: 600; }
.panel__list a span { display: block; font-size: 0.8rem; color: var(--ink-2); }

/* ---------- Veil, boot, fallback ---------- */

.veil {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--ground);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.45s ease-out;
}
.veil.is-on { opacity: 1; pointer-events: auto; }

.boot {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  background: var(--ground);
  transition: opacity 0.6s ease-out;
}
.boot.is-done { opacity: 0; pointer-events: none; }
.boot__title {
  margin: 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.6rem, 5vw, 2.6rem);
  animation: boot-pulse 1.6s ease-in-out infinite alternate;
}
.boot__msg { margin: 0; font-size: 0.85rem; color: var(--ink-2); }
.boot__msg--error {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  align-items: center;
  max-width: min(46rem, 90vw);
  text-align: center;
}
.boot__err-head { color: #ff8a6b; font-weight: 700; }
.boot__err-where {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--ink-2);
  white-space: pre-wrap;
  text-align: left;
}
.boot__msg--error a { color: var(--amber); }
@keyframes boot-pulse {
  from { opacity: 0.55; }
  to { opacity: 1; }
}

.fallback {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 0.5rem;
  background: var(--ground);
  padding: 2rem;
}
.fallback h1 { font-family: var(--display); margin: 0; }
.fallback a { color: var(--amber); }

/* ---------- Region markers (clickable in-world labels) ---------- */

/* Region name chips are clickable fast-travel buttons again (v3) — the
   clearest "go there" affordance across the map. */
.label3d--regionmark { pointer-events: auto; }
/* Category markers sit over whatever the world happens to be doing behind
   them — pine canopy, bright grass, a lit crystal — and sharp text on busy
   ground is the hardest thing to read. Frosting what is BEHIND the label does
   the work: the type stays crisp while its backdrop goes quiet, so contrast
   comes from the panel rather than from shouting with the text. */
.regionmark {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  background: rgba(11, 14, 29, 0.62);
  border: 1px solid rgba(242, 239, 230, 0.2);
  border-radius: 12px;
  padding: 0.3rem 0.7rem;
  cursor: pointer;
  color: var(--ink);
  text-shadow: 0 1px 3px rgba(5, 6, 14, 0.95);
  box-shadow: 0 6px 22px rgba(4, 5, 12, 0.42);
  -webkit-backdrop-filter: blur(10px) saturate(0.85);
  backdrop-filter: blur(10px) saturate(0.85);
  transition: border-color 0.2s ease-out, background 0.2s ease-out;
}
.regionmark:hover { border-color: var(--accent); background: rgba(11, 14, 29, 0.7); }
.regionmark__name {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(0.85rem, 1.7vw, 1.1rem);
  white-space: nowrap;
}
.regionmark__meta {
  font-family: var(--ui);
  font-size: 0.72rem;
  /* lifted off --ink-2: that violet-grey is tuned for dark UI panels and goes
     muddy against a frosted plate over bright grass */
  color: rgba(242, 239, 230, 0.78);
  white-space: nowrap;
}
.is-active-region .regionmark { opacity: 0; pointer-events: none; }

/* ---------- Travel dial ---------- */

.hud__center {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  pointer-events: none;
}
.dial {
  display: flex;
  gap: 0.35rem;
  pointer-events: auto;
  background: rgba(11, 14, 29, 0.55);
  border: 1px solid rgba(242, 239, 230, 0.16);
  border-radius: 999px;
  padding: 0.3rem;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.dial__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  border: 0;
  background: transparent;
  color: var(--ink-2);
  font-family: var(--ui);
  font-size: 0.78rem;
  padding: 0.4rem 0.8rem;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.25s ease-out, color 0.25s ease-out;
}
.dial__btn:hover { color: var(--ink); background: rgba(242, 239, 230, 0.08); }
.dial__btn.is-active { color: var(--ink); background: rgba(242, 239, 230, 0.14); }
.dial__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
  box-shadow: 0 0 8px currentColor;
}

/* ---------- Region title card ---------- */

/* v7: the arrival title card is STAGED rather than faded. An accent rule
   draws on, the region name wipes up a word at a time out of its own mask, an
   accent edge-light blooms behind it as the last word lands, then the tagline
   and count arrive. Every step is expo-out (cubic-bezier(.16,1,.3,1)); the
   whole reveal is done in ~0.8 s because this fires on EVERY arrival and must
   never be something you wait through. Pure CSS: no per-frame JS. */
.titlecard {
  position: fixed;
  left: calc(env(safe-area-inset-left, 0px) + 1.4rem);
  top: 34%;
  z-index: 15;
  pointer-events: none;
  opacity: 0;
  --tc-step: 0.075s;          /* per-word cascade */
  --tc-words: 3;              /* set by app._showTitleCard */
}
.titlecard__rule {
  display: block;
  width: clamp(2rem, 6.5vw, 4.2rem);
  height: 3px;
  margin: 0 0 0.62rem 0.24rem;
  border-radius: 2px;
  background: var(--accent-bright, var(--accent));
  box-shadow: 0 0 16px var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  opacity: 0;
}
.titlecard__name {
  position: relative;
  margin: 0;
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2rem, 6vw, 4.2rem);
  line-height: 1;
  color: var(--ink);
  /* two dark layers carry legibility over bright ground, one heavily blurred
     accent layer is the glow that used to need a node */
  text-shadow:
    0 2px 10px rgba(5, 6, 14, 0.9),
    0 6px 30px rgba(5, 6, 14, 0.7),
    0 0 42px var(--accent);
}
/* the edge-light: a radial wash in the region's accent, no blur filter */
/* The glow must never reveal that it lives in a box. The old gradient was
   sized 60% x 130% of a tight inset, so vertically it was still bright when it
   hit the element's edge and the card wore a visible rectangle. `closest-side`
   ties the radius to the NEAREST edge, so a stop at 76% of it always lands
   inside the element on every axis — the falloff cannot clip, whatever the
   region name's length or the viewport's shape. */
/* No element. Twice now a box-shaped node behind the words has found a way to
   show its edges — first a clipped gradient, then this. A text-shadow is drawn
   from the glyphs themselves and has no box to reveal, so the class of bug is
   gone rather than re-tuned. app.js still creates the node; it renders nothing. */
.titlecard__glow { display: none; }
/* one mask per word; the inner span is what actually travels */
.tc-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: 0.09em;
}
.tc-word > span {
  display: inline-block;
  transform: translateY(110%);
  will-change: transform;
}
.titlecard__meta {
  margin: 0.5rem 0 0 0.2rem;
  font-family: var(--ui);
  font-size: clamp(0.8rem, 1.6vw, 0.95rem);
  color: var(--accent-bright, var(--accent));
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0;
}

.titlecard.is-on { animation: tc-card 4.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
.titlecard.is-on .titlecard__rule {
  animation: tc-rule 0.52s cubic-bezier(0.16, 1, 0.3, 1) 0.02s forwards;
}
.titlecard.is-on .tc-word > span {
  animation: tc-word 0.66s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  animation-delay: calc(0.09s + var(--i, 0) * var(--tc-step));
}
.titlecard.is-on .titlecard__meta {
  animation: tc-meta 0.62s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  animation-delay: calc(0.24s + var(--tc-words) * var(--tc-step));
}
/* the card itself only holds and exits; the reveal lives in the children */
@keyframes tc-card {
  0%   { opacity: 1; transform: none; }
  82%  { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-16px); }
}
@keyframes tc-rule { from { transform: scaleX(0); opacity: 0.35; } to { transform: scaleX(1); opacity: 1; } }
@keyframes tc-word { from { transform: translateY(110%); } to { transform: translateY(0); } }
@keyframes tc-glow { 0% { opacity: 0; } 35% { opacity: 0.30; } 100% { opacity: 0.13; } }
@keyframes tc-meta { from { opacity: 0; transform: translateY(9px); } to { opacity: 1; transform: none; } }
body.is-bright .titlecard__name { color: #2b1c0e; text-shadow: 0 2px 18px rgba(246, 231, 200, 0.9); }

/* ---------- Custom cursor ---------- */

body.has-cursor, body.has-cursor a, body.has-cursor button { cursor: none; }
body.has-cursor #stage, body.has-cursor #stage.is-hovering, body.has-cursor #stage.is-dragging { cursor: none; }
.cursor {
  position: fixed;
  left: 0; top: 0;
  z-index: 100;
  pointer-events: none;
  display: none;
}
body.has-cursor .cursor { display: block; }
.cursor__dot {
  position: absolute;
  width: 6px; height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: var(--accent-bright, var(--accent));
  transition: transform 0.15s ease-out;
}
.cursor__ring {
  position: absolute;
  width: 30px; height: 30px;
  margin: -15px 0 0 -15px;
  border-radius: 50%;
  border: 1.5px solid var(--accent-bright, var(--accent));
  opacity: 0.55;
  transition: transform 0.22s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.22s ease-out;
}
.cursor.is-hover .cursor__ring { transform: scale(1.5); opacity: 0.95; }
.cursor.is-down .cursor__ring { transform: scale(0.7); }
.cursor.is-down .cursor__dot { transform: scale(1.6); }

/* ---------- HUD stagger-in after boot ---------- */

.hud--top, .hud--bottom, .titlecard { opacity: 0; }
body.is-ready .hud--top { animation: hud-in 0.9s 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
body.is-ready .hud--bottom { animation: hud-in 0.9s 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
body.is-ready .titlecard { opacity: 0; }
@keyframes hud-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

/* ---------- Bright (daylight) worlds: switch to dark ink ---------- */

body.is-bright .label3d { color: #2b1c0e; text-shadow: 0 1px 8px rgba(246, 231, 200, 0.9); }
/* Chips are self-contained dark pills — keep their light ink even in bright
   worlds; the bright override above is only for the borderless labels. */
body.is-bright .label3d--object { color: var(--ink); text-shadow: 0 1px 6px rgba(5, 6, 14, 0.9); }
body.is-bright .label3d__count { color: #6a4a28; }
body.is-bright .worldbar__name { color: #2b1c0e; text-shadow: 0 1px 10px rgba(246, 231, 200, 0.85); }
body.is-bright .hint { color: #5a3d20; }
body.is-bright .hud-btn {
  color: #2b1c0e;
  background: rgba(246, 231, 200, 0.7);
  border-color: rgba(43, 28, 14, 0.35);
}
body.is-bright .hud__links a { color: #5a3d20; }
body.is-bright .wordmark { color: #2b1c0e; }
body.is-bright .wordmark__site { color: #6a4a28; }
body.is-bright .tagline { color: #5a3d20; }

/* ---------- Small screens ---------- */

@media (max-width: 640px) {
  .hud--top {
    padding: calc(env(safe-area-inset-top, 0px) + 0.8rem)
             calc(env(safe-area-inset-right, 0px) + 0.9rem) 0.8rem
             calc(env(safe-area-inset-left, 0px) + 0.9rem);
  }
  .hud--bottom {
    padding-left: calc(env(safe-area-inset-left, 0px) + 0.9rem);
    padding-right: calc(env(safe-area-inset-right, 0px) + 0.9rem);
    align-items: flex-end;
  }
  .hud-btn__text { display: none; }
  .hud-btn { padding: 0.45rem 0.7rem; }
  #listLink { font-size: 0.8rem; }
  .tagline { display: none; }
  .hud__links { flex-direction: column; gap: 0.2rem; }
  .hud__addr { display: none; }
  /* dial positioning is owned entirely by the v5 chrome section below */
  .dial__name { display: none; }
  .hint { font-size: 0.72rem; }
  .hint__full { display: none; }
  .hint__short { display: inline; }
  /* smaller project chips so they don't collide on a narrow screen */
  .label3d--object { font-size: 0.82rem; padding: 0.22rem 0.6rem; }
  .titlecard { left: calc(env(safe-area-inset-left, 0px) + 0.9rem); top: 30%; }
  /* v4: the panel becomes a bottom sheet — one thumb, full width, and it
     never fights the dial or the thumbstick for the same corner. */
  .panel {
    right: 0;
    left: 0;
    bottom: 0;
    width: auto;
    max-height: 68vh;
    border-radius: 18px 18px 0 0;
    border-width: 1px 0 0;
    padding: 1.1rem 1.1rem calc(env(safe-area-inset-bottom, 0px) + 1.2rem);
    animation-name: sheet-in;
  }
  .panel__close { width: 2.75rem; height: 2.75rem; top: 0.3rem; right: 0.3rem; font-size: 1.5rem; }
  .panel__launch { display: block; text-align: center; padding: 0.8rem 1.3rem; }
}

@keyframes sheet-in {
  from { opacity: 0; transform: translateY(100%); }
  to { opacity: 1; transform: none; }
}

body.is-bright .dial { background: rgba(246, 231, 200, 0.72); border-color: rgba(43, 28, 14, 0.25); }
body.is-bright .dial__btn { color: #5a3d20; }
body.is-bright .dial__btn:hover, body.is-bright .dial__btn.is-active { color: #2b1c0e; background: rgba(43, 28, 14, 0.1); }
/* The marker carries its own dark frosted plate now, so it is legible over the
   daylight region without help — and the old dark-ink override would have put
   near-black text on that dark plate. Same reasoning as the project chips. */

/* ---------- Mobile landscape: short, wide viewport ---------- */

@media (orientation: landscape) and (max-height: 520px) {
  .hud--top {
    padding: calc(env(safe-area-inset-top, 0px) + 0.5rem)
             calc(env(safe-area-inset-right, 0px) + 1rem) 0.5rem
             calc(env(safe-area-inset-left, 0px) + 1rem);
  }
  .hud--bottom {
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 0.5rem);
    padding-left: calc(env(safe-area-inset-left, 0px) + 1rem);
    padding-right: calc(env(safe-area-inset-right, 0px) + 1rem);
  }
  .wordmark__site { display: none; }
  .hud-btn__text { display: none; }
  .hud-btn { padding: 0.4rem 0.6rem; }
  .tagline { display: none; }
  .hud__addr { display: none; }
  /* dial positioning is owned entirely by the v5 chrome section below */
  .dial__name { display: none; }
  /* panel capped narrower so it never swallows the short viewport */
  .panel { max-width: 320px; max-height: 78vh; }
  /* titlecard scaled down, and kept off the bottom rail (v4.1: the enter
     button now lives there, so a bottom-anchored card would sit on it) */
  .titlecard { top: 26%; bottom: auto; }
  .titlecard__name { font-size: clamp(1.5rem, 8vh, 2.4rem); }
}

/* ========================================================================
   v4 — first-person mode: "Enter the world", thumbstick, proximity action
   Every touch target below is at least 44 x 44 CSS px and every fixed edge
   respects the safe-area insets, so nothing lands under a notch or a home bar.
   ======================================================================== */

/* ---------- Enter the world (map mode only) ---------- */

.enterbtn {
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  font-family: var(--display);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.01em;
  color: #14100a;
  background: var(--accent-bright, var(--accent));
  border: 0;
  border-radius: 999px;
  padding: 0.6rem 1.4rem;
  cursor: pointer;
  box-shadow: 0 8px 26px rgba(4, 5, 12, 0.5);
  transition: transform 0.16s ease-out, box-shadow 0.16s ease-out;
}
/* A slow breath so the way in is the one thing on screen that is alive, and a
   real zoom on hover/press. Scale only — the centring is margin-based now, so
   transforms here cannot knock the button off centre the way they used to. */
.enterbtn { animation: enter-breathe 3.4s ease-in-out infinite; }
@keyframes enter-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.035); }
}
.enterbtn:hover {
  animation: none;
  transform: scale(1.07) translateY(-1px);
  box-shadow: 0 12px 30px rgba(4, 5, 12, 0.6);
}
.enterbtn:active { animation: none; transform: scale(0.98) translateY(1px); }
.enterbtn__icon { font-size: 0.7em; opacity: 0.75; }
.enterbtn[hidden] { display: none; }

.hud-btn--mode { border-color: var(--accent); color: var(--accent-bright, var(--accent)); }
.hud-btn--mode[hidden] { display: none; }

/* ---------- First-person HUD ---------- */

.fpvhud { position: fixed; inset: 0; z-index: 26; pointer-events: none; }
.fpvhud[hidden] { display: none; }

.stick {
  position: absolute;
  left: calc(env(safe-area-inset-left, 0px) + 1.1rem);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 1.1rem);
  width: 124px;
  height: 124px;
  border-radius: 50%;
  pointer-events: auto;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  display: grid;
  place-items: center;
  background: rgba(11, 14, 29, 0.3);
  border: 1px solid rgba(242, 239, 230, 0.16);
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
  transition: background 0.2s ease-out, border-color 0.2s ease-out;
}
.stick[hidden] { display: none; }
.stick.is-active { background: rgba(11, 14, 29, 0.5); border-color: var(--accent); }
.stick__ring {
  position: absolute;
  inset: 26px;
  border-radius: 50%;
  border: 1px dashed rgba(242, 239, 230, 0.16);
  pointer-events: none;
}
.stick__knob {
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: rgba(242, 239, 230, 0.82);
  box-shadow: 0 4px 14px rgba(4, 5, 12, 0.55);
  pointer-events: none;
  will-change: transform;
}
.stick.is-active .stick__knob { background: var(--accent-bright, var(--accent)); }

.fpvopen {
  position: absolute;
  right: calc(env(safe-area-inset-right, 0px) + 1.1rem);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 1.1rem);
  max-width: min(18rem, 52vw);
  min-height: 48px;
  pointer-events: auto;
  font-family: var(--display);
  font-weight: 700;
  font-size: 0.92rem;
  color: #14100a;
  background: var(--accent-bright, var(--accent));
  border: 0;
  border-radius: 999px;
  padding: 0.7rem 1.25rem;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  box-shadow: 0 8px 24px rgba(4, 5, 12, 0.55);
  animation: prompt-in 0.24s cubic-bezier(0.16, 1, 0.3, 1);
}
.fpvopen[hidden] { display: none; }
@keyframes prompt-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

/* ---------- Mode-scoped HUD: never show both worlds' chrome at once ---------- */

body.is-fpv .tagline,
body.is-fpv .hud__links,
body.is-fpv .hint { display: none; }
/* the dial stays: in first person it teleports instead of flying the map */
body.is-fpv .dial { pointer-events: auto; }

/* first-person fast travel uses a fast blink, not the slow travel veil */
.veil--blink { transition-duration: 0.16s; }

/* ---------- Phones: keep the dial clear of the thumbstick ---------- */

@media (max-width: 640px) {
  .enterbtn { font-size: 0.9rem; padding: 0.65rem 1.2rem; }
  .hint { font-size: 0.72rem; }
  .stick { width: 112px; height: 112px; }
  .stick__knob { width: 50px; height: 50px; }
  .fpvopen { font-size: 0.86rem; max-width: 56vw; }
  /* dial buttons are dots on a phone — pad them out to a real touch target */
  .dial__btn { min-width: 44px; min-height: 44px; justify-content: center; }
}

/* ---------- Mobile landscape: short viewport, thumbs at the edges ---------- */

@media (orientation: landscape) and (max-height: 520px) {
  .enterbtn { padding: 0.5rem 1.1rem; font-size: 0.85rem; }
  /* dial dots become a real touch target here too */
  .dial__btn { min-height: 34px; min-width: 40px; justify-content: center; }
  .stick {
    width: 96px;
    height: 96px;
    left: calc(env(safe-area-inset-left, 0px) + 0.7rem);
    bottom: calc(env(safe-area-inset-bottom, 0px) + 0.7rem);
  }
  .stick__ring { inset: 20px; }
  .stick__knob { width: 46px; height: 46px; }
  .fpvopen {
    right: calc(env(safe-area-inset-right, 0px) + 0.7rem);
    bottom: calc(env(safe-area-inset-bottom, 0px) + 0.7rem);
    min-height: 44px;
    font-size: 0.82rem;
  }
}

/* ========================================================================
   v4.1 — the mobile bottom rail
   The owner's "on mobile the button to walk is hidden": on a phone the dial
   goes `position: fixed` bottom-centre at z-index 25, while "Enter the world"
   stayed in the flow of .hud__center — in the same strip of screen, painted
   over. Fix: three fixed, bottom-centred bands, each stacked off the one
   below, so they physically cannot share a pixel:

       hint      bottom = rail + dial-h + gap + enter-h + 0.45rem
       Enter     bottom = rail + dial-h + gap            (z-index 28)
       dial      bottom = rail                           (z-index 25)

   portrait : rail = safe + 11.2px, dial-h 56px (44px buttons + 2*0.3rem
              padding + 2px border), gap 11.2px
              → dial safe+11.2..67.2, Enter safe+78.4..122.4, hint from
                safe+129.6. .hud__links tops out near safe+68.5 on the right.
   landscape: rail = safe + 8px, dial-h 46px, gap 8px
              → dial safe+8..54, Enter safe+62..106, hint from safe+113.2
   Nothing else lives in that strip: the tagline is hidden, .hud__links tops
   out around safe+68px (portrait), and the thumbstick only exists in first
   person, where the enter button is [hidden].
   ======================================================================== */

:root {
  --rail-bottom: calc(env(safe-area-inset-bottom, 0px) + 0.7rem);
  --rail-gap: 0.7rem;
  --dial-h: 56px;
  --enter-h: 44px;
}
@media (orientation: landscape) and (max-height: 520px) {
  :root {
    --rail-bottom: calc(env(safe-area-inset-bottom, 0px) + 0.5rem);
    --rail-gap: 0.5rem;
    /* 34px buttons + 2 * 0.3rem padding + 2px border = 45.6px */
    --dial-h: 46px;
  }
}

@media (max-width: 640px), (orientation: landscape) and (max-height: 520px) {
  .dial {
    bottom: var(--rail-bottom);
    min-height: var(--dial-h);
  }
  .enterbtn {
    position: fixed;
    left: 0;
    right: 0;
    margin-inline: auto;
    width: max-content;
    bottom: calc(var(--rail-bottom) + var(--dial-h) + var(--rail-gap));
    z-index: 28;                 /* clear of the dial's 25 */
    min-height: var(--enter-h);
    white-space: nowrap;
  }
  .hint {
    position: fixed;
    left: 0;
    right: 0;
    margin-inline: auto;
    bottom: calc(var(--rail-bottom) + var(--dial-h) + var(--rail-gap) + var(--enter-h) + 0.45rem);
    width: max-content;
    max-width: min(92vw, 30rem);
    z-index: 24;
  }
}

@media (prefers-reduced-motion: reduce) {
  .panel { animation: none; }
  .enterbtn, .fpvopen, .stick { transition: none; animation: none; }
  .boot__title { animation: none; }
  .veil { transition-duration: 0.01s; }
  /* reduced motion: the card cuts straight to its finished state, holds, and
     fades. No wipes, no travel, same dwell time. */
  .titlecard.is-on { animation: tc-card-rm 4.3s linear forwards; }
  @keyframes tc-card-rm { 0% { opacity: 1; } 82% { opacity: 1; } 100% { opacity: 0; } }
  .titlecard.is-on .titlecard__rule { animation: none; opacity: 1; transform: scaleX(1); }
  .titlecard.is-on .tc-word > span { animation: none; transform: none; }
  .titlecard.is-on .titlecard__meta { animation: none; opacity: 1; transform: none; }
  body.is-ready .hud--top, body.is-ready .hud--bottom { animation: none; opacity: 1; }
  .regionmark, .cursor__dot, .cursor__ring { transition: none; }
}

/* ============================================================================
   v5 CHROME — one owner per control
   ----------------------------------------------------------------------------
   The HUD had grown five overlapping systems and the controls were fighting
   each other. This section owns every floating control outright; the rules
   above no longer position .dial or .enterbtn.

   Layout contract, bottom-up, so nothing can ever stack on anything else:
       dial        bottom rail
       enterbtn    one step above the rail   (map mode only)
       hint        one step above that       (map mode only)
   In first person the rail belongs to the thumbstick (left) and the Open
   prompt (right), so the dial moves to the right edge instead.

   The floating controls are children of <body>, NOT of .hud--bottom: that bar
   runs a forwards transform animation, and iOS Safari keeps such an element as
   a containing block for position:fixed descendants — which is what threw the
   dial into the bottom-right corner, clipped off screen.
   ========================================================================== */

:root {
  --rail: calc(env(safe-area-inset-bottom, 0px) + 0.85rem);
  --rail-h: 3rem;      /* dial height */
  --step: 0.6rem;      /* gap between stacked bottom controls */
}

/* ---- top bar: three controls, one height, one shape language ---- */

.hud__actions { display: flex; align-items: center; gap: 0.45rem; }

.iconbtn,
.textbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 40px;
  font-family: var(--ui);
  font-size: 0.85rem;
  line-height: 1;
  white-space: nowrap;          /* one line, always */
  color: var(--ink);
  text-decoration: none;
  background: rgba(11, 14, 29, 0.55);
  border: 1px solid rgba(242, 239, 230, 0.2);
  border-radius: 999px;
  cursor: pointer;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  transition: border-color 0.2s ease-out, background 0.2s ease-out, color 0.2s ease-out;
}
.iconbtn { width: 40px; padding: 0; font-size: 1rem; }
.textbtn { padding: 0 1rem; }
.iconbtn:hover,
.textbtn:hover { border-color: var(--accent); background: rgba(11, 14, 29, 0.8); }
.iconbtn[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }

/* ---- wordmark ---- */

.wordmark__title { white-space: nowrap; font-size: clamp(1.05rem, 2.4vw, 1.5rem); }
.wordmark__site { font-size: 0.74rem; }
.wordmark__tag {
  display: block;
  margin-top: 0.35rem;
  max-width: 24ch;
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--ink-2);
}

/* ---- bottom rail ---- */

.dial {
  position: fixed;
  z-index: 25;
  left: 0;
  right: 0;
  margin-inline: auto;
  width: max-content;
  bottom: var(--rail);
  min-height: var(--rail-h);
  align-items: center;
}

.enterbtn {
  position: fixed;
  z-index: 26;
  left: 0;
  right: 0;
  margin-inline: auto;
  width: max-content;
  bottom: calc(var(--rail) + var(--rail-h) + var(--step));
  min-height: 44px;
}

/* the hint sits above the enter button and never competes with it */
.hud--bottom {
  padding-bottom: calc(var(--rail) + var(--rail-h) + var(--step) + 44px + var(--step));
  align-items: flex-end;
}
.hint { flex: 1; text-align: center; }

/* ---- first person: rail is the thumbstick's, dial moves to the right ---- */

body.is-fpv .dial {
  left: auto;
  margin-inline: 0;
  width: auto;
  bottom: auto;
  right: calc(env(safe-area-inset-right, 0px) + 0.6rem);
  top: 50%;
  transform: translateY(-50%);
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.25rem;
  z-index: 27;
}
body.is-fpv .dial__name { display: none; }
body.is-fpv .dial__btn {
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  justify-content: center;
  border-radius: 50%;
}
body.is-fpv .dial__dot { width: 9px; height: 9px; }
body.is-fpv .dial__btn.is-active .dial__dot { width: 13px; height: 13px; }
body.is-fpv .enterbtn { display: none; }

/* ---- phones ---- */

@media (max-width: 640px) {
  .hud--top {
    padding-top: calc(env(safe-area-inset-top, 0px) + 0.7rem);
    align-items: center;
  }
  .wordmark__tag { display: none; }
  .iconbtn, .textbtn { height: 38px; font-size: 0.8rem; }
  .iconbtn { width: 38px; }
  .textbtn { padding: 0 0.85rem; }
  /* the link row is desktop chrome; on a phone it crowds the rail, and every
     one of those links is also on the list page one tap away */
  .hud__links { display: none; }
  .hint { font-size: 0.72rem; }
}

/* ---- short landscape: everything tightens, nothing overlaps ---- */

@media (orientation: landscape) and (max-height: 520px) {
  :root { --rail: calc(env(safe-area-inset-bottom, 0px) + 0.5rem); --rail-h: 2.6rem; --step: 0.4rem; }
  .hud--top { padding-top: calc(env(safe-area-inset-top, 0px) + 0.5rem); }
  .iconbtn, .textbtn { height: 34px; font-size: 0.78rem; }
  .iconbtn { width: 34px; }
  .hud__links, .hint { display: none; }
  body.is-fpv .dial { gap: 0; padding: 0.15rem; }
  body.is-fpv .dial__btn { min-height: 38px; min-width: 40px; }
}

/* ---- bright regions keep the same shapes, darker ink ---- */

body.is-bright .iconbtn,
body.is-bright .textbtn {
  color: #2b1c0e;
  background: rgba(246, 231, 200, 0.75);
  border-color: rgba(43, 28, 14, 0.28);
}

/* Pointer Lock hides the system cursor, so our custom one must go too —
   otherwise a frozen dot sits wherever the pointer was when the lock began. */
body.is-locked .cursor { display: none; }

/* ---------- First-person prompt: out of both thumbs' corners ----------
   The thumbstick owns bottom-left and the region rail owns the right edge.
   A bottom-RIGHT prompt shares that edge with the rail, and on a short
   landscape viewport the rail's five stacked buttons reach down into it.
   Centring the prompt gives it a lane of its own in every orientation. */
body.is-fpv .fpvopen {
  left: 0;
  right: 0;
  margin-inline: auto;
  width: max-content;
  max-width: min(24rem, 46vw);
}

/* A primary action deserves more than 48px when there is a mouse. */
@media (pointer: fine) and (min-width: 641px) {
  .fpvopen {
    font-size: 1.02rem;
    padding: 0.85rem 1.6rem;
    min-height: 54px;
    max-width: min(28rem, 46vw);
  }
}

/* ---------- Jump (touch only; desktop uses Space) ----------
   Bottom-right, mirroring the thumbstick on the left, and above the centred
   Open prompt's lane so the two never fight for the same pixels. */
.jumpbtn {
  position: absolute;
  right: calc(env(safe-area-inset-right, 0px) + 1.1rem);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 1.1rem);
  width: 76px;
  height: 76px;
  border-radius: 50%;
  pointer-events: auto;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  font-family: var(--ui);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--ink);
  background: rgba(11, 14, 29, 0.32);
  border: 1px solid rgba(242, 239, 230, 0.18);
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
  transition: transform 0.12s ease-out, background 0.2s ease-out;
}
.jumpbtn:active { transform: scale(0.92); background: rgba(11, 14, 29, 0.5); }
/* a real mouse has the spacebar, and the button would just be clutter */
@media (pointer: fine) { .jumpbtn { display: none; } }
@media (orientation: landscape) and (max-height: 520px) {
  .jumpbtn { width: 62px; height: 62px; font-size: 0.72rem;
    right: calc(env(safe-area-inset-right, 0px) + 0.7rem);
    bottom: calc(env(safe-area-inset-bottom, 0px) + 0.7rem); }
}
