:root {
  --ink: #05060d;
  --panel: #0d0f1a;
  --edge: rgba(150, 165, 220, 0.16);
  --text: #e9ecf7;
  --muted: #8e95ae;
  --dim: #626981;
  --luna: #b9c4f0;
  --listening: #5fd3ff;
  --thinking: #a98bff;
  --speaking: #ffcf8b;
  --danger: #ff8b8b;
  --accent: var(--luna);
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}

body[data-state="listening"] { --accent: var(--listening); }
body[data-state="thinking"] { --accent: var(--thinking); }
body[data-state="speaking"] { --accent: var(--speaking); }

/* Someone who has told their OS to stop animating things gets a Luna that
   stops too. The orb's own motion is handled in JS — see orb.js — this only
   covers the two CSS entrance animations (chat bubbles rising in, the
   approval sheet fading up); ordinary hover/press transitions are a single
   quick property tween rather than a looping or entrance effect, and are
   left alone. */
@media (prefers-reduced-motion: reduce) {
  .bubble,
  #approval {
    animation: none;
  }
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--ink);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  overscroll-behavior: none;
}

body { display: flex; flex-direction: column; }
button { font: inherit; color: inherit; }

/* ------------------------------------------------------------- gate ----- */

#gate {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 24px calc(24px + env(safe-area-inset-left)) calc(24px + env(safe-area-inset-bottom));
  text-align: center;
}

#gate[hidden] { display: none; }
#gate canvas { width: 160px; height: 160px; }
#gate h1 { margin: 0; font-size: 20px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--muted); font-weight: 600; }
#gate p { margin: 0; max-width: 30ch; color: var(--dim); font-size: 14px; line-height: 1.5; }
#gate form { display: flex; flex-direction: column; gap: 10px; width: 100%; max-width: 340px; margin-top: 8px; }

#gate input, #input {
  padding: 14px 16px;
  border-radius: 14px;
  border: 1px solid var(--edge);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  font-size: 16px; /* 16px or iOS zooms the page on focus */
}

#gate input:focus, #input:focus { outline: none; border-color: rgba(150, 170, 240, 0.5); }

#gate button {
  padding: 14px;
  border: 0;
  border-radius: 14px;
  background: rgba(124, 140, 255, 0.28);
  font-weight: 600;
}

.error { color: var(--danger) !important; }

#gate-shared {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 2px 4px;
  font-size: 13px;
  color: var(--dim);
  text-align: left;
}

#gate-shared input { width: 18px; height: 18px; accent-color: #7c8cff; flex: none; }

/* -------------------------------------------------------------- app ----- */

#app { flex: 1; display: flex; flex-direction: column; min-height: 0; }
#app[hidden] { display: none; }

#bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: calc(10px + env(safe-area-inset-top)) 16px 8px;
}

#title { font-size: 13px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); font-weight: 600; }

#core-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--dim); margin-right: auto;
  transition: background 200ms ease;
}
body[data-core="ready"] #core-dot { background: #6ee7a8; }
body[data-core="connecting"] #core-dot { background: var(--speaking); }
body[data-core="rejected"] #core-dot { background: var(--danger); }

#btn-forget { background: none; border: 0; color: var(--dim); font-size: 13px; padding: 6px; }

#orb-stage { display: grid; place-items: center; }
#orb { width: 132px; height: 132px; display: block; }

#status { margin: 0 0 10px; text-align: center; font-size: 13px; color: var(--dim); min-height: 18px; }
#status.active { color: var(--accent); }

#transcript {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 0 16px 8px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Auto-margin spacer rather than justify-content, which would make content
   overflowing the top of a scroll container unreachable. */
#transcript::before { content: ""; margin-top: auto; }

.bubble {
  max-width: 86%;
  padding: 11px 14px;
  border-radius: 16px;
  line-height: 1.5;
  font-size: 15px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  animation: rise 200ms ease;
}

.bubble.user {
  align-self: flex-end;
  background: rgba(120, 140, 220, 0.24);
  border: 1px solid rgba(150, 170, 240, 0.24);
  border-bottom-right-radius: 6px;
}

.bubble.assistant {
  align-self: flex-start;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--edge);
  border-bottom-left-radius: 6px;
}

.bubble.system {
  align-self: center;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
  border: 1px dashed var(--edge);
}

.bubble.system.error { color: var(--danger); border-color: rgba(255, 139, 139, 0.3); }

@keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; } }

#composer { display: flex; gap: 8px; padding: 8px 16px; }
#input { flex: 1; min-width: 0; }

#btn-send {
  width: 50px; border: 0; border-radius: 14px;
  background: rgba(150, 170, 240, 0.2); color: var(--luna); font-size: 20px;
}

/* --------------------------------------------------------- talk key ---- */

#btn-talk {
  margin: 4px auto calc(18px + env(safe-area-inset-bottom));
  width: 78px; height: 78px;
  border-radius: 50%;
  border: 1px solid var(--edge);
  background: rgba(255, 255, 255, 0.05);
  color: var(--muted);
  display: grid;
  place-items: center;
  touch-action: none; /* stop the press turning into a scroll or a magnifier */
  user-select: none;
  transition: transform 120ms ease, background 160ms ease, color 160ms ease;
}

#btn-talk svg { width: 30px; height: 30px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
#btn-talk[data-holding="true"] { transform: scale(1.08); background: rgba(95, 211, 255, 0.2); color: var(--listening); }
body[data-state="speaking"] #btn-talk { color: var(--speaking); }

/* ---------------------------------------------------------- approval --- */

/* Fixed over the whole viewport, not part of the page's own scroll, so it is
   on screen the instant it appears regardless of where the transcript was
   scrolled to. The scrim also makes an accidental tap on the transcript
   underneath impossible while a decision is outstanding. */
#approval {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(2, 3, 8, 0.6);
  animation: approval-in 160ms ease;
}

#approval[hidden] { display: none; }

@keyframes approval-in { from { opacity: 0; } to { opacity: 1; } }

/* Anchored to the bottom: the one-handed thumb zone on a phone. Height is
   capped well inside the viewport so the action row at the foot of the card
   never needs the page — or the card itself — to scroll to be reached. */
#approval-card {
  width: 100%;
  max-width: 480px;
  max-height: min(80vh, 640px);
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px 18px calc(14px + env(safe-area-inset-bottom));
  border-radius: 20px 20px 0 0;
  border: 1px solid rgba(255, 207, 139, 0.35);
  border-bottom: 0;
  background: #12111b;
  box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.4);
}

/* The part that scrolls when content is long — the buttons below it never do. */
#approval-scroll {
  overflow-y: auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

#approval-text {
  margin: 0;
  font-size: 16px;
  line-height: 1.45;
  color: var(--speaking);
  overflow-wrap: anywhere;
}

/* What is about to be written. Its own scroll box, capped well short of the
   card's own cap, so a 2,000-character file never pushes the buttons away. */
#approval-preview {
  margin: 0;
  padding: 10px 12px;
  max-height: 220px;
  overflow: auto;
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.3);
  font-family: ui-monospace, Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(226, 232, 240, 0.9);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

#approval-preview[hidden] { display: none; }

/* Sol's objection, read before the buttons for the same reason it sits above
   them on the desktop. Cooler than the amber card so it reads as a second
   voice rather than more of Luna's own request. */
#approval-sol {
  margin: 0;
  padding: 10px 12px;
  border-radius: 12px;
  border-left: 3px solid rgba(150, 190, 255, 0.7);
  background: rgba(150, 190, 255, 0.1);
  font-size: 14px;
  line-height: 1.45;
  color: rgba(214, 230, 255, 0.95);
  overflow-wrap: anywhere;
}

#approval-sol[hidden] { display: none; }

/* A block is Sol saying it can see where this came from and it was not him. */
#approval-sol.block {
  border-left-color: rgba(255, 140, 140, 0.9);
  background: rgba(255, 140, 140, 0.12);
  color: rgba(255, 214, 214, 0.97);
}

#approval-timeout {
  margin: 0;
  padding: 10px 12px;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.05);
  font-size: 14px;
  line-height: 1.45;
  color: var(--dim);
}

#approval-timeout[hidden] { display: none; }

/* Deny gets exactly the same size, weight and reach as Approve. A layout
   where one is a easy thumb-hit and the other is not has quietly decided the
   answer for him. */
#approval-actions {
  display: flex;
  gap: 10px;
  flex: none;
}

#approval-actions[hidden] { display: none; }

#approval-actions button {
  flex: 1;
  padding: 16px;
  border-radius: 14px;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.01em;
}

#btn-deny {
  border: 1px solid rgba(255, 139, 139, 0.4);
  background: rgba(255, 139, 139, 0.14);
  color: var(--danger);
}

#btn-approve {
  border: 1px solid rgba(110, 231, 168, 0.4);
  background: rgba(110, 231, 168, 0.14);
  color: #6ee7a8;
}

#btn-approval-dismiss {
  flex: none;
  width: 100%;
  padding: 14px;
  border: 1px solid var(--edge);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
}

#btn-approval-dismiss[hidden] { display: none; }

/* ------------------------------------------------------------- voice --- */

#voice-bar {
  display: flex;
  /* Wrapping rather than squeezing, since Notify made it four pills wide. At
     375px the longest set — "Notify blocked" beside "Screen off — stopped" —
     fits on one line but only by letting two of them wrap their own text onto a
     second line, which reads as a layout accident. A pill that drops to its own
     row instead is still a pill. Measured at 375px; check it again before
     adding a fifth. */
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  justify-content: center;
  padding: 0 16px 8px;
  flex: none;
}

#voice-bar button {
  display: inline-flex;
  /* Never squeezed: a label cut in half says less than a second row does. */
  flex: none;
  white-space: nowrap;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border: 1px solid var(--edge);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--muted);
  font-family: var(--font);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

#voice-bar button[hidden] { display: none; }

#voice-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  /* Green is the free one. The colour is a second channel for the same fact
     the label carries, for a glance rather than a read. */
  background: #6ee7a8;
}

#btn-voice[data-route="core"] #voice-dot { background: var(--luna); }
#btn-voice[data-route="core"] { color: var(--luna); }

/* Armed is the same blue as listening, because it is the same idea: something
   is running that will not stop when he looks away. Stopped is amber rather
   than red — nothing is broken, she simply is not doing it any more, and that
   is a thing to notice rather than a thing to worry about. */
#btn-session[data-state="on"] {
  border-color: rgba(95, 211, 255, 0.45);
  background: rgba(95, 211, 255, 0.14);
  color: var(--listening);
}

#btn-session[data-state="dead"] {
  border-color: rgba(255, 207, 139, 0.4);
  background: rgba(255, 207, 139, 0.12);
  color: var(--speaking);
}

/* On is the same blue as the other two, for the same reason: something is
   armed that outlives him looking away. Blocked is amber rather than red — the
   browser said no, which is a thing to notice and fix in its settings, not a
   fault. */
#btn-notify[data-state="on"] {
  border-color: rgba(95, 211, 255, 0.45);
  background: rgba(95, 211, 255, 0.14);
  color: var(--listening);
}

#btn-notify[data-state="blocked"] {
  border-color: rgba(255, 207, 139, 0.4);
  background: rgba(255, 207, 139, 0.12);
  color: var(--speaking);
}

#btn-notify[data-state="working"] { opacity: 0.5; }

#btn-handsfree[aria-pressed="true"] {
  border-color: rgba(95, 211, 255, 0.45);
  background: rgba(95, 211, 255, 0.14);
  color: var(--listening);
}

/* In hands-free there is nothing to hold, and a prominent button that does
   nothing useful invites him to hold it anyway. */
body[data-handsfree="true"] #btn-talk { opacity: 0.35; }

#voice-panel {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 16px;
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
  background: rgba(5, 6, 13, 0.72);
  backdrop-filter: blur(6px);
}

#voice-panel[hidden] { display: none; }

#voice-card {
  width: 100%;
  max-width: 420px;
  max-height: 82vh;
  overflow-y: auto;
  padding: 20px;
  border: 1px solid var(--edge);
  border-radius: 20px;
  background: var(--panel);
}

#voice-card h2 {
  margin: 0 0 4px;
  font-size: 17px;
  font-weight: 700;
}

#voice-detail {
  margin: 0 0 16px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
}

.voice-route {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  margin-bottom: 10px;
  padding: 12px 14px;
  border: 1px solid var(--edge);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.03);
}

.voice-route input { margin-top: 3px; flex: none; accent-color: var(--luna); }
.voice-route span { display: flex; flex-direction: column; gap: 3px; }
.voice-route strong { font-size: 15px; font-weight: 600; }
.voice-route small { color: var(--dim); font-size: 12px; line-height: 1.4; }

#voice-pick-row {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin: 14px 0 0;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
}

#voice-pick-row[hidden] { display: none; }

#voice-pick {
  padding: 11px 12px;
  border: 1px solid var(--edge);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
}

#voice-note {
  margin: 14px 0 0;
  padding: 10px 12px;
  border: 1px solid rgba(255, 207, 139, 0.3);
  border-radius: 12px;
  background: rgba(255, 207, 139, 0.1);
  color: var(--speaking);
  font-size: 12px;
  line-height: 1.45;
}

#voice-note[hidden] { display: none; }

#session-block {
  margin: 18px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--edge);
}

#session-block h3 {
  margin: 0 0 8px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

#session-block p {
  margin: 0 0 7px;
  color: var(--dim);
  font-size: 12px;
  line-height: 1.45;
}

/* The one sentence he needs when it has stopped, in the colour that means
   "read this" rather than buried in the same grey as the explanation. */
#session-problem {
  margin-top: 10px;
  padding: 10px 12px;
  border: 1px solid rgba(255, 207, 139, 0.3);
  border-radius: 12px;
  background: rgba(255, 207, 139, 0.1);
  color: var(--speaking);
}

#session-problem[hidden] { display: none; }

#btn-voice-close {
  width: 100%;
  margin-top: 18px;
  padding: 14px;
  border: 1px solid var(--edge);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  font-weight: 600;
}
