:root { color-scheme: dark; }
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; overflow: hidden; background: #0b0f14;
  font-family: ui-sans-serif, -apple-system, "Segoe UI", Roboto, sans-serif; }
#view { display: block; width: 100vw; height: 100vh; cursor: grab; }
#view:active { cursor: grabbing; }

.hud { position: fixed; pointer-events: none; user-select: none;
  text-shadow: 0 2px 10px rgba(0,0,0,.85); }

#street { top: 18px; left: 22px; font-size: 30px; font-weight: 650; letter-spacing: -.02em;
  color: #fff; }
#place { top: 56px; left: 22px; font-size: 15px; color: #ffd166; font-weight: 600;
  transition: opacity .35s; opacity: 0; }
#clock { top: 20px; right: 22px; font-size: 14px; color: #e6edf3; background: rgba(10,14,20,.55);
  padding: 7px 13px; border-radius: 999px; backdrop-filter: blur(8px);
  border: 1px solid rgba(255,255,255,.1); }
#prompt { bottom: 96px; left: 50%; transform: translateX(-50%); font-size: 15px;
  color: #fff; background: rgba(12,18,26,.7); padding: 8px 15px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.16); backdrop-filter: blur(8px);
  transition: opacity .2s; opacity: 0; }
#audio { top: 20px; left: 50%; transform: translateX(-50%); font-size: 13px;
  color: #dbe6f0; background: rgba(12,18,26,.6); padding: 6px 13px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.12); backdrop-filter: blur(8px);
  transition: opacity .5s; }
#speed { top: 62px; right: 22px; font: 600 26px ui-monospace, Menlo, monospace;
  color: #fff; text-align: right; transition: opacity .25s; opacity: 0; }
#speed small { display: block; font: 500 11px ui-sans-serif, sans-serif; color: #9fb0c0;
  letter-spacing: .08em; }

/* Only ever visible from the driver's seat, and tappable because a phone has no
   G key — the chip is the entire radio interface there.

   Off, it is a single "Radio on" button. On, it splits: the left half is the
   station and cycles, the right half switches it off. The two halves are real
   buttons inside one pill so the hit targets are unambiguous under a thumb.

   It sits under the street name on the left, not in the right-hand column with
   the clock and the speedo, because the minimap owns the bottom right and grows
   to 66vh when opened with M. In landscape on a phone that big map reaches the
   top of the screen and covered the chip completely. The left column holds only
   two lines of text and the touch stick is at the bottom, so nothing collides. */
#radio { top: 88px; left: 22px; display: flex; align-items: stretch;
  background: rgba(12,18,26,.6); border: 1px solid rgba(255,255,255,.12);
  border-radius: 999px; backdrop-filter: blur(8px); overflow: hidden;
  opacity: 0; transition: opacity .3s; }
#radio.shown { opacity: 1; }
#radio button { font: inherit; font-size: 13px; color: #cfe0ee; background: none;
  border: 0; padding: 6px 13px; cursor: pointer; pointer-events: none;
  text-shadow: inherit; transition: background .15s, color .3s; }
#radio.shown button { pointer-events: auto; }
#radio button:hover { background: rgba(255,255,255,.09); }
#radio.on #r-main { color: #ffd166; }
#r-off { display: none; color: #9fb0c0; padding: 6px 11px;
  border-left: 1px solid rgba(255,255,255,.15); }
#radio.on #r-off { display: block; }

#minimap { position: fixed; bottom: 18px; right: 18px; width: 190px; height: 190px;
  border-radius: 14px; border: 1px solid rgba(255,255,255,.16); opacity: .9;
  box-shadow: 0 10px 32px rgba(0,0,0,.5); transition: width .22s, height .22s; }
#minimap.big { width: min(66vh, 560px); height: min(66vh, 560px); opacity: .97; }

#help { position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 7px; flex-wrap: wrap; justify-content: center; max-width: 62vw;
  pointer-events: none; }
#help.gone { display: none; }
#help kbd { font: 600 11px ui-monospace, Menlo, monospace; color: #cfe0f0;
  background: rgba(12,18,26,.72); border: 1px solid rgba(255,255,255,.13);
  border-bottom-color: rgba(255,255,255,.06); padding: 5px 9px; border-radius: 7px;
  backdrop-filter: blur(8px); white-space: nowrap; }

#loading { position: fixed; inset: 0; display: grid; place-content: center; gap: 14px;
  justify-items: center; background: radial-gradient(ellipse at 50% 40%, #1b2735, #0b0f14 70%);
  z-index: 10; transition: opacity .6s; }
#loading.gone { opacity: 0; pointer-events: none; }
#loading h1 { margin: 0; font-size: 34px; font-weight: 680; letter-spacing: -.03em; color: #f0f6fc; }
#loading h1 span { color: #ffd166; }
#loading p { margin: 0; color: #8a97a5; font-size: 13.5px; }
.bar { width: 230px; height: 3px; border-radius: 3px; background: rgba(255,255,255,.1);
  overflow: hidden; }
.bar i { display: block; height: 100%; width: 40%; border-radius: 3px; background: #ffd166;
  animation: slide 1.25s ease-in-out infinite; }
@keyframes slide { 0% { transform: translateX(-110%); } 100% { transform: translateX(340%); } }

/* ---- the couplet, over the running world ----
   The loading screen leaves the moment the world is ready, so this sits over a
   scene you can already walk around in. It never takes input and nothing
   dismisses it — it runs its course.

   Each line rises and settles into place, a beat apart.

   Legibility is the constraint: pale text on a bright sky disappears, so a soft
   dark pool sits behind it and the shadow does more work than the glow. */
#dream { position: fixed; inset: 0; z-index: 8; margin: 0;
  display: grid; place-content: center; pointer-events: none; opacity: 0;
  padding-bottom: 26vh;                 /* lifts the text above mid-screen */
  transition: opacity 2.2s ease;
  background: radial-gradient(ellipse 44% 22% at 50% 37%,
    rgba(5,9,15,.56), rgba(5,9,15,.32) 46%, rgba(5,9,15,0) 73%);
  /* Hoefler Text and Baskerville are the literary ones and both ship on Apple
     devices; Android falls through to Noto Serif, Windows to Georgia. Every
     name in the chain has a real italic rather than a slanted roman. */
  font-family: 'Hoefler Text', Baskerville, 'Palatino Linotype', Palatino,
               'Noto Serif', Georgia, serif;
  font-style: italic; line-height: 2.0; text-align: center;
  font-size: 22px; letter-spacing: .012em;
  /* not solid — the world shows faintly through the letters, which is the point
     of putting it over a live scene. The shadow keeps it readable anyway. */
  color: rgba(240, 245, 252, .80);
  text-shadow: 0 1px 12px rgba(0,0,0,.75), 0 0 26px rgba(150,190,235,.32); }
#dream span { display: block; opacity: 0;
  transform: translateY(16px) scale(1.014);
  /* tell the compositor up front, so the first frame of the reveal is not also
     the frame that promotes the layer */
  will-change: opacity, transform; }
#dream.show { opacity: 1; }
#dream.show.done { opacity: 0; }        /* after .show, so this wins */

/* Only opacity and transform are animated, and deliberately so. The previous
   version swept a soft mask across each line, which looks better but cannot be
   composited: mask-position forces a repaint of the text every frame. That is
   fine on an idle page and not fine here — the world streams chunks for up to
   5ms of every frame right after it appears, which is exactly when this runs,
   so the reveal stuttered against it. Opacity and transform are handled off the
   main thread and are immune to whatever the world is doing. */
#dream.show span { animation: arrive 2.6s cubic-bezier(.22,.68,.24,1) .35s forwards; }
#dream.show span:nth-child(2) { animation-delay: 1.2s; }
@keyframes arrive {
  0%   { opacity: 0; transform: translateY(16px) scale(1.014); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

@media (max-width: 560px) {
  #dream { font-size: 17px; line-height: 1.95; padding-bottom: 30vh;
    background: radial-gradient(ellipse 68% 17% at 50% 35%,
      rgba(5,9,15,.58), rgba(5,9,15,.34) 46%, rgba(5,9,15,0) 75%); }
}
@media (prefers-reduced-motion: reduce) {
  #dream.show span { animation: none; opacity: 1; transform: none; }
}

#credit { position: fixed; bottom: 16px; right: 220px; font-size: 10.5px; color: #6b7683; }

/* ---- touch controls: only created on a coarse-pointer device ---- */
#touch { position: fixed; inset: 0; pointer-events: none; z-index: 5; }
#touch button { pointer-events: auto; }

#t-stick {
  position: absolute; left: 20px; bottom: 30px;
  width: 138px; height: 138px; border-radius: 50%;
  background: rgba(14,20,28,.34); border: 1px solid rgba(255,255,255,.18);
  backdrop-filter: blur(6px); pointer-events: auto; touch-action: none;
}
#t-knob {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
  width: 62px; height: 62px; border-radius: 50%;
  background: rgba(240,246,252,.55); border: 1px solid rgba(255,255,255,.45);
  box-shadow: 0 3px 14px rgba(0,0,0,.4);
}

#t-buttons {
  position: absolute; right: 18px; bottom: 30px;
  display: flex; flex-direction: column; align-items: flex-end; gap: 12px;
}
.t-row { display: flex; gap: 12px; }
.t-btn {
  width: 68px; height: 68px; border-radius: 50%; flex: none;
  font: 600 14px ui-sans-serif, -apple-system, sans-serif; color: #eaf1f8;
  background: rgba(14,20,28,.42); border: 1px solid rgba(255,255,255,.2);
  backdrop-filter: blur(6px); touch-action: none; -webkit-tap-highlight-color: transparent;
}
.t-btn.t-wide { width: 148px; height: 48px; border-radius: 24px; }
.t-btn.on { background: rgba(255,209,102,.32); border-color: rgba(255,209,102,.6); }
/* a toggle that is currently on, as opposed to being pressed right now */
.t-btn.active { background: rgba(126,196,255,.26); border-color: rgba(126,196,255,.55); }

/* the desktop key hints are meaningless on a phone */
body.touch #help { display: none; }
body.touch #credit { display: none; }
body.touch #minimap { width: 128px; height: 128px; bottom: auto; top: 88px; }
body.touch 
#meter { top: 20px; left: 22px; font: 500 12px ui-monospace, Menlo, monospace;
  color: #9fe0a8; background: rgba(10,14,20,.6); padding: 5px 10px; border-radius: 6px; }
