/* The tour's shell. Colours are the game's own front-end palette so the page
   and the WebGL behind it read as one surface. */
:root {
  --cv-deep:   #04121c;
  --cv-water:  #0b2b3d;
  --cv-edge:   #1d5877;
  --cv-glow:   #4fc3f7;
  --cv-text:   #bfe9ff;
  --cv-dim:    #7fa9c0;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--cv-deep);
  color: var(--cv-text);
  font: 15px/1.6 system-ui, -apple-system, "Segoe UI", sans-serif;
  /* The tour owns the scroll; rubber-banding past the ends fights the camera. */
  overscroll-behavior: none;
}

#stage {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  cursor: grab;
  /* The canvas IS the control surface: orbit.js reads pointer events off it.
     touch-action: none is what stops the browser claiming a drag as a page
     scroll or a pinch as a page zoom before the handler ever sees it. */
  touch-action: none;
}
#stage:active { cursor: grabbing; }

/* ---- boot veil ---------------------------------------------------------- */
#boot {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: grid;
  place-content: center;
  gap: 18px;
  background: var(--cv-deep);
  transition: opacity .6s ease;
}
#boot.done { opacity: 0; pointer-events: none; }

#boot-title {
  margin: 0;
  font: 600 13px/1 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--cv-glow);
  text-align: center;
}

#boot-track {
  width: min(320px, 70vw);
  height: 2px;
  background: var(--cv-water);
  overflow: hidden;
}
#boot-bar {
  width: 0;
  height: 100%;
  background: var(--cv-glow);
  transition: width .45s ease;
}

#boot-msg {
  margin: 0;
  font: 11px/1.5 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .18em;
  color: var(--cv-dim);
  text-align: center;
  min-height: 1.5em;
}

#boot.failed #boot-bar { background: #ff6b6b; width: 100% !important; }
#boot.failed #boot-msg { color: #ff9d9d; max-width: 46ch; letter-spacing: .04em; }

/* ---- footer ------------------------------------------------------------- */
/* Governance, not decoration: Polyp appears in public as a rigged 3D Coralite,
   and the campaign rule is that we never imply Coralites are playable. This
   line is how that gets said on every page (decision 1, 2026-09-17). */
#footer {
  position: fixed;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .85), 0 0 10px rgba(0, 0, 0, .55);

  left: 0; right: 0; bottom: 0;
  z-index: 5;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
  font: 11px/1.4 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .06em;
  color: var(--cv-dim);
  text-align: center;
  background: linear-gradient(transparent, rgba(4, 18, 28, .82) 46%);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  #boot { transition: none; }
}

/* ---- tap hint ----------------------------------------------------------- */
/* Also the audio unlock. Safari ties playback permission to a real gesture on
   the element, so the first line cannot play until something like this is
   clicked — which is why it is a button and not a caption. */
#tap-hint {
  /* Positioned by #dock's flex column, not by an offset of its own — one
     element owning this edge is the whole point of the dock. */
  /* ...which means it inherits the dock's `pointer-events: none` and has to
     opt back in, exactly as #take-control and #talk do. Without this the button
     is INERT and the click falls through to the canvas, where the orbit's tap
     handler catches it — so it still looked like it worked, while actually
     running the wrong code path. */
  pointer-events: auto;
  padding: 10px 20px;
  border: 1px solid var(--cv-edge);
  border-radius: 999px;
  background: rgba(11, 43, 61, .72);
  color: var(--cv-text);
  font: 11px/1 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .2em;
  text-transform: uppercase;
  cursor: pointer;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: opacity .5s ease, transform .5s ease;
}
#tap-hint:hover { background: rgba(29, 88, 119, .8); }
#tap-hint.gone { opacity: 0; pointer-events: none; transform: translateY(8px); }

/* ---- controls hint ------------------------------------------------------ */
/* Sits above the footer line, not over Polyp. It never disappears: unlike the
   tap hint it is not a one-time instruction, and a visitor who has been
   orbiting for a minute still benefits from knowing pinch zooms. */
#controls-hint {
  margin: 0;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .85), 0 0 10px rgba(0, 0, 0, .55);

  font: 10px/1.4 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--cv-dim);
  text-align: center;
  opacity: .95;
  max-width: 92vw;
  /* A scrim, not just a shadow. This line sits directly over the courtyard
     floor and the bleached coral — the two brightest surfaces in the scene —
     and a shadow behind dim text is still dim text on white. The pill matches
     #talk and #take-control, so the dock reads as one control strip. */
  padding: 5px 12px;
  border-radius: 999px;
  background: rgba(8, 26, 38, .58);
}

/* ---- take control -------------------------------------------------------- */
/* The game's #promptBar, in the game's voice: a visitor who has played already
   knows what "take control" means and where this bar lives. It sits centred
   and low, clear of Polyp's face at every framing the orbit allows. */
#take-control {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: center;
  padding: 12px 22px;
  border: 1px solid var(--cv-edge);
  border-radius: 999px;
  background: rgba(11, 43, 61, .78);
  color: var(--cv-text);
  cursor: pointer;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  pointer-events: auto;
  transition: opacity .45s ease, transform .45s ease;
}
#take-control:hover { background: rgba(29, 88, 119, .85); }
#take-control .tc-line {
  font: 12px/1 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .22em;
  text-transform: uppercase;
}
/* The controls are ON the button, not in a modal. One glance, no dismissing,
   and it leaves the screen the moment they are no longer news. */
#take-control .tc-sub {
  font: 10px/1.4 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--cv-dim);
}
/* `display:none` and not just opacity: a hidden flex child still takes a
   row in the dock, and the battery would sit a button's height too high. */
#take-control.gone { display: none; }

/* ---- battery ------------------------------------------------------------- */
/* Twenty lines of our own. The game's hud.js reads score state and cannot be
   lifted piecemeal — but every NUMBER below comes from the synced state.js, so
   a rebalance in the game moves this meter with it. */
#battery {
  display: flex;
  align-items: center;
  gap: 10px;
  font: 10px/1 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--cv-dim);
  /* Appears at lesson 7 and not before — a meter nobody has been taught to
     read is decoration, and during training it cannot run out anyway. */
  transition: opacity .5s ease;
}
#battery[hidden] { display: none; }
#battery-track {
  width: 168px; height: 6px;
  border: 1px solid var(--cv-edge);
  border-radius: 999px;
  overflow: hidden;
  background: rgba(11, 43, 61, .7);
}
#battery-bar {
  height: 100%;
  width: 100%;
  background: #35e0ff;
  transform-origin: left center;
  transition: width .18s linear, background-color .3s ease;
}
#battery.low #battery-bar { background: #ffb347; }
#battery.low #battery-secs { color: #ffb347; }
#battery-secs { min-width: 34px; text-align: left; }

/* ---- the lesson card ----------------------------------------------------- */
/* Top-centre, not bottom: the bottom is where the battery and the controls
   line live, and a card that shares a corner with a gauge is a card that gets
   read as part of the gauge. High enough that it never covers the fish or the
   horizon a pilot is steering by. */
#lesson {
  position: fixed;
  left: 50%;
  top: calc(28px + env(safe-area-inset-top));
  transform: translateX(-50%) translateY(-8px);
  z-index: 7;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 14px 24px;
  max-width: min(84vw, 460px);
  border: 1px solid var(--cv-edge);
  border-radius: 14px;
  background: rgba(11, 43, 61, .82);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  text-align: center;
  opacity: 0;
  /* Never interactive. There is nothing to dismiss — it leaves when the lesson
     is mastered — and a card that eats clicks over a flight control is a bug. */
  pointer-events: none;
  transition: opacity .35s ease, transform .35s ease;
}
#lesson[hidden] { display: none; }
#lesson.in { opacity: 1; transform: translateX(-50%) translateY(0); }

.lesson-text {
  margin: 0;
  color: var(--cv-text);
  font: 15px/1.35 ui-sans-serif, -apple-system, system-ui, sans-serif;
  letter-spacing: .01em;
}

.lesson-glyphs { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; }
/* A real key gets a keycap. A phrase ("Move mouse", "Follow Polyp") is an
   instruction, not something to press, and dressing it as a key is a lie the
   hands have to unlearn. */
.lesson-glyphs kbd {
  min-width: 26px;
  padding: 5px 8px;
  border: 1px solid var(--cv-edge);
  border-bottom-width: 3px;
  border-radius: 6px;
  background: rgba(4, 18, 28, .8);
  color: var(--cv-glow);
  font: 12px/1 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.lesson-glyphs kbd.phrase {
  border: 0;
  background: none;
  color: var(--cv-dim);
  letter-spacing: .18em;
  padding: 5px 0;
}

/* ---- the bottom dock ----------------------------------------------------- */
/* One flex column for everything along the bottom edge, so the take-control
   button, the battery and the controls line cannot overlap each other or the
   footer however their text wraps. Sits ABOVE the footer by giving the column
   the footer's height as padding rather than by guessing offsets. */
#dock {
  position: fixed;
  left: 0; right: 0;
  bottom: calc(38px + env(safe-area-inset-bottom));
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  /* The canvas below has to stay draggable: the dock is a layout box, and only
     the button inside it takes clicks. */
  pointer-events: none;
}
/* The footer is two lines on a narrow window, so the dock clears more there. */
@media (max-width: 560px) {
  #dock { bottom: calc(64px + env(safe-area-inset-bottom)); gap: 10px; }
  #take-control .tc-sub { display: none; }   /* the card teaches the keys now */
}

/* ---- talk button --------------------------------------------------------- */
/* Lives in the dock beside TAKE CONTROL, and unlike it, SURVIVES taking
   control: R2 says a visitor must never have to sit through anything to ask a
   question, and that holds just as much for a pilot mid-flight. */
#talk {
  padding: 9px 18px;
  border: 1px solid var(--cv-edge);
  border-radius: 999px;
  background: rgba(11, 43, 61, .72);
  color: var(--cv-text);
  font: 11px/1 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .2em;
  text-transform: uppercase;
  cursor: pointer;
  pointer-events: auto;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
#talk:hover { background: rgba(29, 88, 119, .82); }

/* ---- the chat panel ------------------------------------------------------ */
/* Right-hand column on a desktop, bottom sheet on a phone. Either way it never
   covers the middle of the frame, because Polyp is in the middle of the frame
   and the whole point is talking TO him rather than to a chat window. */
#chat {
  position: fixed;
  right: 16px;
  top: 16px;
  bottom: 16px;
  z-index: 9;
  width: min(380px, 40vw);
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--cv-edge);
  border-radius: 16px;
  background: rgba(4, 18, 28, .88);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  opacity: 0;
  transform: translateX(12px);
  transition: opacity .3s ease, transform .3s ease;
}
#chat[hidden] { display: none; }
#chat.in { opacity: 1; transform: none; }

.chat-head { display: flex; align-items: center; justify-content: space-between; }
.chat-title {
  font: 11px/1 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .24em; text-transform: uppercase; color: var(--cv-glow);
}
.chat-close {
  border: 0; background: none; color: var(--cv-dim);
  font-size: 22px; line-height: 1; cursor: pointer; padding: 0 4px;
}
.chat-close:hover { color: var(--cv-text); }

.chat-log {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-right: 4px;
  scrollbar-width: thin;
}
.msg { max-width: 92%; }
.msg p { margin: 0; font-size: 14px; line-height: 1.5; }
.msg.you { align-self: flex-end; text-align: right; color: var(--cv-dim); }
.msg.you p {
  display: inline-block;
  padding: 7px 12px;
  border-radius: 12px 12px 2px 12px;
  background: rgba(29, 88, 119, .42);
  text-align: left;
  color: var(--cv-text);
}
.msg.polyp p { color: var(--cv-text); }

/* Rendered from the Worker's own card RECORDS, never from the model's text, so
   an invented citation cannot manufacture a source badge — it can only fail to
   produce one. */
.msg-sources { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; }
.msg-sources a {
  padding: 3px 8px;
  border: 1px solid var(--cv-edge);
  border-radius: 999px;
  font: 10px/1.3 ui-monospace, SFMono-Regular, monospace;
  color: var(--cv-dim);
  text-decoration: none;
}
.msg-sources a:hover { color: var(--cv-glow); border-color: var(--cv-glow); }

.msg-disclaimer {
  margin-top: 8px !important;
  padding-top: 8px;
  border-top: 1px solid rgba(29, 88, 119, .5);
  font-size: 11px !important;
  line-height: 1.5 !important;
  color: var(--cv-dim);
}

.msg-more {
  margin-top: 8px;
  padding: 5px 10px;
  border: 1px solid var(--cv-edge);
  border-radius: 999px;
  background: none;
  color: var(--cv-glow);
  font: 10px/1 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .1em;
  text-transform: uppercase;
  cursor: pointer;
}
.msg-more:hover { background: rgba(29, 88, 119, .35); }

.chat-status {
  margin: 0;
  font: 10px/1.5 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .08em;
  color: var(--cv-dim);
}
.chat-status[hidden] { display: none; }

.chat-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  padding: 7px 12px;
  border: 1px solid var(--cv-edge);
  border-radius: 999px;
  background: rgba(11, 43, 61, .6);
  color: var(--cv-text);
  font: 12px/1.2 system-ui, -apple-system, sans-serif;
  text-align: left;
  cursor: pointer;
}
.chip:hover { background: rgba(29, 88, 119, .6); }
/* A chosen card greys but stays pickable: the path so far should read as a
   trail, not as doors closing behind you. */
.chip.used { opacity: .45; }

.chat-form { display: flex; gap: 6px; }
.chat-input {
  flex: 1; min-width: 0;
  padding: 9px 12px;
  border: 1px solid var(--cv-edge);
  border-radius: 999px;
  background: rgba(11, 43, 61, .6);
  color: var(--cv-text);
  font: 14px/1 system-ui, -apple-system, sans-serif;
}
.chat-input:focus { outline: 1px solid var(--cv-glow); }
.chat-mic, .chat-send {
  padding: 9px 14px;
  border: 1px solid var(--cv-edge);
  border-radius: 999px;
  background: rgba(11, 43, 61, .6);
  color: var(--cv-text);
  font: 11px/1 ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .12em;
  text-transform: uppercase;
  cursor: pointer;
}
.chat-mic { font-size: 14px; letter-spacing: 0; }
.chat-mic.listening { border-color: #ff6b6b; color: #ff9d9d; }
.chat-mic[hidden] { display: none; }
.chat-mic:hover, .chat-send:hover { background: rgba(29, 88, 119, .7); }

@media (max-width: 720px) {
  /* Bottom sheet. It covers the dock, which is fine — while the panel is open
     the visitor is talking, not flying. */
  #chat {
    left: 8px; right: 8px; top: auto;
    bottom: 8px;
    width: auto;
    /* vh on iOS Safari is the address-bar-HIDDEN viewport, so 70vh can be
       taller than what the visitor can actually see and the sheet's input row
       lands under the toolbar. dvh is the visible box; the vh line above is
       the fallback for anything that lacks it. */
    height: min(70vh, 560px);
    height: min(70dvh, 560px);
    transform: translateY(12px);
  }
}
