/*
  Signal-inspired chat UI — clean flat surfaces, ultramarine accent, first-class
  light & dark themes.

  THEMING: one attribute switch — <html data-theme="light|dark"> (set pre-paint by the
  inline script in index.html, toggled live by js/settings.js). Every color below is a
  semantic token defined twice (light in :root, dark in :root[data-theme="dark"]).
  Components only ever reference tokens, never raw colors — except the game's trust
  triplet (green/yellow/red), which is mirrored in main.js constants and in the
  keyframes' rgb() triplets and must not drift.

  Layout contract (owned by JS — keep intact):
  --app-height / --app-offset-top   set by the visualViewport script in index.html
  --pulse-strength / FX vars        set inline per-reply by main.js
  #trust-marker style.left          set by renderTrust()
  #bg-pattern / .big-reaction       inline top/height matched to #thread's box
  thread.style.paddingBottom        set when the suggestions strip shows
*/

/* ═══════════════════════════════════════════════════════════════════════════
   1. DESIGN TOKENS
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
  color-scheme: light;

  /* Accent — Signal ultramarine */
  --accent: #3a76f0;
  --accent-strong: #2258c6;          /* pressed / hover-darken */
  --accent-soft: #e8f0fe;            /* selected rows, segmented control */
  --on-accent: #ffffff;

  /* Surfaces */
  --bg-page: #e9e9eb;                /* backdrop behind the phone card */
  --bg-chat: #ffffff;                /* thread wallpaper (under the dynamic tint) */
  --bg-bar: #ffffff;                 /* header, trust panel, composer, wizard bars */
  --bg-sheet: #ffffff;               /* settings sheet */
  --bg-input: #f0f0f2;               /* pill inputs, chips, tonal buttons */
  --bg-elevated: #ffffff;            /* date pills, reaction badges, chips over wallpaper */
  --scrim: rgba(0, 0, 0, 0.45);

  /* Bubbles */
  --bubble-sent: #3a76f0;
  --bubble-sent-text: #ffffff;
  --bubble-received: #ffffff;
  --bubble-received-text: #1b1b1d;

  /* Text */
  --text-primary: #1b1b1d;
  --text-secondary: #6e6e74;

  /* Lines & depth */
  --divider: rgba(0, 0, 0, 0.08);
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.08);
  --shadow-2: 0 12px 40px rgba(0, 0, 0, 0.22);
  --scrollbar-thumb: rgba(0, 0, 0, 0.18);

  /* Game semantics — values mirrored in main.js (BG_GREEN/…) and keyframe triplets. */
  --trust-good: #25d366;
  --trust-mid: #e7c000;
  --trust-bad: #e74c3c;
  --trust-marker-bg: #ffffff;
  --trust-marker-border: #2b2b2b;

  /* Component tokens */
  --toggle-off: #d1d1d6;
  --avatar-bg: #f0f0f2;
  --note-bg: #fff4c2;                --note-text: #6b5b00;
  --note-info-bg: #e8f0fe;           --note-info-text: #1e3a5f;
  --note-event-bg: #4a1f1f;          --note-event-text: #ffd9d9;
  --voice-wave: #b8c4e8;
  --suggestions-strip-bg: rgba(255, 255, 255, 0.55);

  /* Shape & type */
  --radius-bubble: 18px;
  --radius-bubble-group: 6px;        /* tightened corner between grouped messages */
  --radius-pill: 999px;
  --radius-md: 12px;
  --font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans",
    "Helvetica Neue", Arial, sans-serif;

  --app-height: 100vh; /* legacy fallback; refined below and overridden live by the head script */
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --accent: #6e92f7;                 /* brightened for contrast on dark */
  --accent-strong: #8aa7f9;
  --accent-soft: #1e2a4a;
  --on-accent: #ffffff;

  --bg-page: #000000;
  --bg-chat: #121212;
  --bg-bar: #1b1b1d;
  --bg-sheet: #1e1e21;
  --bg-input: #2c2c2e;
  --bg-elevated: #2c2c2e;
  --scrim: rgba(0, 0, 0, 0.6);

  --bubble-sent: #3a76f0;            /* Signal keeps ultramarine in dark */
  --bubble-sent-text: #ffffff;
  --bubble-received: #2c2c2e;
  --bubble-received-text: #e9e9eb;

  --text-primary: #e9e9eb;
  --text-secondary: #a0a0a6;

  --divider: rgba(255, 255, 255, 0.09);
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-2: 0 12px 40px rgba(0, 0, 0, 0.6);
  --scrollbar-thumb: rgba(255, 255, 255, 0.2);

  --trust-good: #2fd66d;             /* slightly brighter for the dark track */
  --trust-mid: #e7c000;
  --trust-bad: #ff6b5e;
  --trust-marker-bg: #e9e9eb;
  --trust-marker-border: #121212;

  --toggle-off: #39393d;
  --avatar-bg: #2c2c2e;              /* dimmed backing plate so the light SVG disc doesn't glare */
  --note-bg: #3a3312;                --note-text: #f0d878;
  --note-info-bg: #14293b;           --note-info-text: #bcd9f2;
  --note-event-bg: #3a1414;          --note-event-text: #ffb3ab;
  --voice-wave: #4a5580;
  --suggestions-strip-bg: rgba(18, 18, 18, 0.55);
}

@supports (height: 100svh) {
  /* Stable across iOS Safari's toolbar show/hide, before the head script's JS measurement runs. */
  :root { --app-height: 100svh; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   2. BASE
   ═══════════════════════════════════════════════════════════════════════════ */
* { box-sizing: border-box; }

button {
  touch-action: manipulation; /* kills the 300ms tap delay + double-tap-zoom on buttons */
  -webkit-tap-highlight-color: transparent; /* replaced by our own :active/:hover states */
  font-family: inherit;
}

html, body {
  margin: 0;
  height: 100%;
  font-family: var(--font-stack);
  background: var(--bg-page);
}

body {
  display: grid;
  place-items: center;
  padding: 16px;
}

/* One consistent keyboard-focus ring everywhere (replaces per-component rules). */
:where(button, input, textarea, select, [role="switch"]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
#message-input:focus-visible { outline: none; } /* the composer pill is its own affordance */

/* Thin, theme-aware scrollbars on every internal scroll container. */
#thread, #wizard, #settings-sheet, #call-captions, #message-input {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) transparent;
}
#thread::-webkit-scrollbar, #wizard::-webkit-scrollbar, #settings-sheet::-webkit-scrollbar,
#call-captions::-webkit-scrollbar, #message-input::-webkit-scrollbar { width: 6px; }
#thread::-webkit-scrollbar-thumb, #wizard::-webkit-scrollbar-thumb,
#settings-sheet::-webkit-scrollbar-thumb, #call-captions::-webkit-scrollbar-thumb,
#message-input::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: var(--radius-pill);
}

/* Inline SVG icons (stroke follows the button's currentColor). */
.icon {
  width: 22px;
  height: 22px;
  flex: none;
  pointer-events: none; /* clicks land on the button, not the svg */
}

/* The phone frame */
#phone {
  position: relative;
  /* z-index (even at 0) turns this into a stacking context of its own, so #bg-pattern's
     negative z-index (below) is scoped to "behind #phone's other children" instead of
     escaping past #phone's own background to paint behind the whole page. */
  z-index: 0;
  width: 100%;
  max-width: 430px;
  height: min(calc(var(--app-height) * 0.9), 800px);
  display: flex;
  flex-direction: column;
  background: var(--bg-chat);
  border-radius: 24px;
  overflow: hidden;
  box-shadow: var(--shadow-2);
}

/* ═══════════════════════════════════════════════════════════════════════════
   3. TITLE / SETUP WIZARD
   Mirrors the chat's own layout and materials so it reads as the same app:
   app bar (.title-topbar ~ #chat-header) → colored meter (#wizard-progress ~ #trust-meter) →
   scrolling content (#wizard ~ #thread) → composer-style footer (#wizard-footer ~ #composer).
   ═══════════════════════════════════════════════════════════════════════════ */
#title-screen {
  position: absolute;
  inset: 0;
  z-index: 50;
  display: flex;
  flex-direction: column;
  background: var(--bg-chat);
  color: var(--text-primary);
  text-align: center;
  overflow: hidden;
}
#title-screen.hidden { display: none; }
/* Subtle decorative texture (not a hero) — same tiled-emoji technique as the chat's dynamic
   wallpaper, opacity dialed way down so it reads as texture, not a loud graphic. */
#title-screen::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140' viewBox='0 0 140 140'%3E%3Ctext x='18' y='40' font-size='26' opacity='0.09'%3E%F0%9F%92%9A%3C/text%3E%3Ctext x='92' y='34' font-size='24' opacity='0.08'%3E%F0%9F%91%8D%3C/text%3E%3Ctext x='60' y='78' font-size='22' opacity='0.08'%3E%E2%9C%A8%3C/text%3E%3Ctext x='16' y='116' font-size='24' opacity='0.08'%3E%F0%9F%92%94%3C/text%3E%3Ctext x='104' y='112' font-size='24' opacity='0.09'%3E%F0%9F%91%8E%3C/text%3E%3C/svg%3E");
  background-size: 140px 140px;
  opacity: 0.5;
  pointer-events: none;
}

/* App bar — same material as #chat-header. */
.title-topbar {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 10px 12px;
  background: var(--bg-bar);
  border-bottom: 1px solid var(--divider);
}

.title-inner { width: 100%; }
#game-title {
  font-size: 27px;
  margin: 6px 0 10px;
  line-height: 1.15;
  color: var(--text-primary);
  font-weight: 800;
  letter-spacing: -0.01em;
}
#game-tagline { font-size: 14.5px; color: var(--text-secondary); line-height: 1.45; margin: 0 0 22px; }
#player-name {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin: 0 auto;
  padding: 13px 16px;
  border: none;
  border-radius: 14px;
  font-size: 16px;
  font-family: inherit;
  text-align: center;
  color: var(--text-primary);
  background: var(--bg-input);
}
#player-name::placeholder { color: var(--text-secondary); }

/* ── Setup wizard: scrolling content, same role as #thread ─────────────────── */
#wizard {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  width: 100%;
  box-sizing: border-box;
  padding: 20px 22px 16px;
  background: transparent;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Progress meter — same role/material as #trust-meter. Segments sample the real trust
   gradient as they're reached; unreached segments use the themed "off" grey. */
#wizard-progress {
  flex: 0 0 auto;
  display: flex;
  gap: 7px;
  padding: 10px 14px;
  background: var(--bg-bar);
  border-bottom: 1px solid var(--divider);
}
.wizard-dot {
  flex: 1 1 auto;
  height: 5px;
  border-radius: var(--radius-pill);
  background: var(--toggle-off);
  transition: background 0.2s;
  cursor: pointer;
  border: none;
  padding: 0;
}
.wizard-dot.active:nth-child(1) { background: #25d366; }
.wizard-dot.active:nth-child(2) { background: #a3c623; }
.wizard-dot.active:nth-child(3) { background: #e7a300; }
.wizard-dot.active:nth-child(4) { background: #e74c3c; }

.wizard-step { display: none; width: 100%; max-width: 340px; margin: 0 auto; text-align: center; }
.wizard-step.active { display: block; }

.wizard-step-title {
  font-size: 22px;
  margin: 0 0 8px;
  color: var(--text-primary);
  font-weight: 700;
  letter-spacing: -0.01em;
}
.wizard-step-copy { font-size: calc(14px * var(--text-scale, 1)); color: var(--text-secondary); line-height: 1.5; margin: 0 0 20px; }

/* Language picker (step 2) — flat tonal rows with a right-side circular check. */
.wizard-lang-list { display: flex; flex-direction: column; gap: 8px; }
.wizard-lang-option {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  border: none;
  border-radius: 14px;
  padding: 14px 16px;
  font-size: 15px;
  font-weight: 600;
  background: var(--bg-input);
  color: var(--text-primary);
  cursor: pointer;
  transition: background 0.15s;
}
.wizard-lang-option::after {
  content: "";
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 2px solid var(--toggle-off);
  background: transparent center / 14px 14px no-repeat;
  transition: background-color 0.15s, border-color 0.15s;
}
.wizard-lang-option.active { background: var(--accent-soft); }
.wizard-lang-option.active::after {
  border-color: var(--accent);
  background-color: var(--accent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

/* Preference rows (step 3) */
.wizard-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 2px;
  border-bottom: 1px solid var(--divider);
  text-align: left;
}
.wizard-row:last-of-type { border-bottom: none; }
.wizard-row-label { font-size: calc(14px * var(--text-scale, 1)); color: var(--text-primary); }

.wizard-textarea-label {
  display: block;
  text-align: left;
  font-size: 14px;
  color: var(--text-primary);
  font-weight: 600;
  margin: 16px 0 6px;
}
.wizard-hint { display: block; font-size: 12px; font-weight: 400; color: var(--text-secondary); margin-top: 2px; }
#wizard-about-me {
  width: 100%;
  box-sizing: border-box;
  border: none;
  border-radius: var(--radius-md);
  padding: 10px 12px;
  font-size: 14px;
  font-family: inherit;
  resize: vertical;
  color: var(--text-primary);
  background: var(--bg-input);
}
#wizard-about-me::placeholder { color: var(--text-secondary); }

/* Hobby chip picker (step 3) — compact multi-select, same tonal + accent recipe. */
.wizard-hobbies-block { margin: 16px 0 4px; text-align: left; }
.wizard-hobbies-label { display: block; margin-bottom: 8px; color: var(--text-primary); font-weight: 600; }

.wizard-chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.wizard-chip {
  border: none;
  border-radius: var(--radius-pill);
  padding: 7px 14px;
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  background: var(--bg-input);
  color: var(--text-primary);
  cursor: pointer;
  line-height: 1.25;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  transition: background 0.15s, color 0.15s;
}
.wizard-chip[aria-pressed="true"] {
  background: var(--accent);
  color: var(--on-accent);
}
@media (hover: hover) {
  .wizard-chip:hover { filter: brightness(0.96); }
  .wizard-chip[aria-pressed="true"]:hover { filter: brightness(1.08); }
}

.wizard-hobby-other-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin-top: 10px;
  border: none;
  border-radius: var(--radius-md);
  padding: 9px 12px;
  font-size: 13.5px;
  font-family: inherit;
  color: var(--text-primary);
  background: var(--bg-input);
}
.wizard-hobby-other-input::placeholder { color: var(--text-secondary); }
.wizard-hobby-other-input[hidden] { display: none; }

/* Consent row (step 4) */
.wizard-consent-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  text-align: left;
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--text-primary);
  cursor: pointer;
}
.wizard-consent-row input[type="checkbox"] { width: 18px; height: 18px; margin-top: 2px; flex-shrink: 0; accent-color: var(--accent); }

/* ── Pinned footer: Back + primary CTA + settings link — same material as #composer ── */
#wizard-footer {
  flex: 0 0 auto;
  width: 100%;
  box-sizing: border-box;
  padding: 12px 22px calc(16px + env(safe-area-inset-bottom, 0px));
  background: var(--bg-bar);
  border-top: 1px solid var(--divider);
}
#wizard-nav { display: flex; align-items: center; gap: 12px; width: 100%; }
.wizard-nav-btn {
  border: none;
  border-radius: var(--radius-pill);
  padding: 15px 24px;
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
}
.wizard-nav-btn-primary {
  flex: 1 1 auto;
  color: var(--on-accent);
  background: var(--accent);
  box-shadow: 0 4px 14px rgba(58, 118, 240, 0.3);
}
.wizard-nav-btn-primary:disabled { opacity: 0.55; cursor: not-allowed; box-shadow: none; }
@media (hover: hover) {
  .wizard-nav-btn-primary:hover:not(:disabled) { filter: brightness(1.08); }
}
/* Back — a quiet tonal pill. */
#wizard-back-btn {
  flex: 0 0 auto;
  background: var(--bg-input);
  color: var(--text-primary);
  padding: 15px 22px;
}

/* Play-again button at the ending */
#play-again {
  align-self: center;
  margin: 4px 0 8px;
  border: none;
  border-radius: var(--radius-pill);
  padding: 10px 26px;
  font-size: 15px;
  font-weight: 600;
  color: var(--on-accent);
  background: var(--accent);
  cursor: pointer;
}

/* ═══════════════════════════════════════════════════════════════════════════
   4. HEADER + TRUST METER
   Header and trust panel share one flat bar material; a single hairline under
   the trust meter separates the bar from the thread.
   ═══════════════════════════════════════════════════════════════════════════ */
#chat-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px 6px;
  background: var(--bg-bar);
  color: var(--text-primary);
}

/* Shared recipe for the header's round icon buttons. */
#restart-btn, #mute-btn, #settings-btn, #title-dark-toggle {
  flex: 0 0 auto;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-primary);
  font-size: 17px;
  cursor: pointer;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  transition: background 0.15s;
}
@media (hover: hover) {
  #restart-btn:hover, #mute-btn:hover, #settings-btn:hover, #title-dark-toggle:hover {
    background: var(--bg-input);
  }
}

#avatar {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--avatar-bg);
  flex: 0 0 auto;
  transition: opacity 0.15s ease;
  --pulse-strength: 0;
  --pulse-color: 37, 211, 102;
}
#avatar.reacting {
  animation: avatarFlinch 0.35s ease;
}
/* A reply that actually scored (delta != 0) layers a colored ring on top of the flinch —
   more specific than #avatar.reacting alone, so it wins when both classes are present.
   animation is a shorthand: two same-property rules replace rather than merge, so the
   "flinch only" vs. "flinch + ring" cases need distinct selectors like this. */
#avatar.reacting.pulse-pos, #avatar.reacting.pulse-neg {
  animation: avatarFlinch 0.35s ease, avatarRing 0.6s ease;
}
#avatar.pulse-pos { --pulse-color: 37, 211, 102; }
#avatar.pulse-neg { --pulse-color: 231, 76, 60; }
/* Both keyframes scale with --pulse-strength (0..1, set inline per reply from scoreDelta —
   see reactAvatar() in main.js). At 0 (a mood-only change, no score) avatarFlinch matches
   the original fixed values exactly. */
@keyframes avatarFlinch {
  0%   { transform: none; }
  35%  { transform: scale(calc(0.92 - 0.10 * var(--pulse-strength))) rotate(calc(-4deg - 6deg * var(--pulse-strength))); }
  70%  { transform: scale(calc(1.05 + 0.10 * var(--pulse-strength))) rotate(calc(2deg + 6deg * var(--pulse-strength))); }
  100% { transform: none; }
}
@keyframes avatarRing {
  0%   { box-shadow: 0 0 0 0 rgba(var(--pulse-color), 0); }
  35%  { box-shadow: 0 0 calc(6px + 10px * var(--pulse-strength)) calc(2px + 3px * var(--pulse-strength)) rgba(var(--pulse-color), calc(0.6 * var(--pulse-strength))); }
  100% { box-shadow: 0 0 0 0 rgba(var(--pulse-color), 0); }
}

#header-info { flex: 1 1 auto; min-width: 0; }
#contact-name { font-weight: 600; font-size: 16px; letter-spacing: -0.01em; }
#contact-status { font-size: 12px; color: var(--text-secondary); }

/* Trust meter: tug-of-war between You and the AI (under the header, same bar). */
#trust-meter {
  flex: 0 0 auto;
  padding: 2px 14px 10px;
  background: var(--bg-bar);
  border-bottom: 1px solid var(--divider);
}
#trust-word {
  text-align: center;
  font-size: 12px;
  color: var(--text-secondary);
  letter-spacing: 0.2px;
  margin-bottom: 5px;
}
#trust-word.word-bump {
  animation: wordBump 0.4s ease;
}
@keyframes wordBump {
  0%   { transform: scale(1); color: var(--text-secondary); }
  40%  { transform: scale(1.14); color: var(--text-primary); }
  100% { transform: scale(1); color: var(--text-secondary); }
}
.tm-row { display: flex; align-items: center; gap: 8px; }
.tm-end { flex: 0 0 auto; font-size: 12px; color: var(--text-secondary); white-space: nowrap; }
#trust-track {
  position: relative;
  flex: 1 1 auto;
  height: 6px;
  border-radius: var(--radius-pill);
  background: linear-gradient(to right, var(--trust-good) 0%, var(--trust-mid) 50%, var(--trust-bad) 100%);
  --pulse-strength: 0;
  --pulse-color: 37, 211, 102;
}
#trust-track.pulse-pos, #trust-track.pulse-neg {
  animation: trackGlow 0.65s ease-out;
}
#trust-track.pulse-pos { --pulse-color: 37, 211, 102; }
#trust-track.pulse-neg { --pulse-color: 231, 76, 60; }
@keyframes trackGlow {
  0%   { box-shadow: 0 0 0 rgba(var(--pulse-color), 0); filter: brightness(1); }
  30%  {
    box-shadow: 0 0 calc(6px + 10px * var(--pulse-strength)) calc(2px + 4px * var(--pulse-strength))
      rgba(var(--pulse-color), calc(0.7 * var(--pulse-strength)));
    filter: brightness(calc(1 + 0.3 * var(--pulse-strength)));
  }
  100% { box-shadow: 0 0 0 rgba(var(--pulse-color), 0); filter: brightness(1); }
}
#trust-marker {
  position: absolute;
  top: 50%;
  left: 0%;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--trust-marker-bg);
  border: 2px solid var(--trust-marker-border);
  transform: translate(-50%, -50%);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transition: left 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
  --pulse-strength: 0;
  --pulse-color: 37, 211, 102;
}
#trust-marker.pulse-pos, #trust-marker.pulse-neg {
  animation: trustPulse 0.55s ease;
}
#trust-marker.pulse-pos { --pulse-color: 37, 211, 102; } /* matches --trust-good */
#trust-marker.pulse-neg { --pulse-color: 231, 76, 60; }  /* matches the track's red end */
/* Expanding "sonar ping" ring layered behind the dot's own pulse (trustPulse below). */
#trust-marker::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  border: 2px solid rgba(var(--pulse-color), 0.9);
  opacity: 0;
  pointer-events: none;
}
#trust-marker.pulse-pos::after, #trust-marker.pulse-neg::after {
  animation: trustRipple calc(0.45s + 0.3s * var(--pulse-strength)) ease-out;
}
@keyframes trustRipple {
  0%   { transform: scale(1); opacity: calc(0.75 * var(--pulse-strength)); }
  100% { transform: scale(calc(2 + 2 * var(--pulse-strength))); opacity: 0; }
}
@keyframes trustPulse {
  0% {
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3),
      0 0 0 rgba(var(--pulse-color), calc(0.7 * var(--pulse-strength)));
  }
  40% {
    transform: translate(-50%, -50%) scale(calc(1 + 0.9 * var(--pulse-strength)));
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3),
      0 0 calc(9px + 16px * var(--pulse-strength)) calc(3px + 6px * var(--pulse-strength))
        rgba(var(--pulse-color), calc(0.75 * var(--pulse-strength)));
  }
  100% {
    transform: translate(-50%, -50%) scale(1);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 0 0 rgba(var(--pulse-color), 0);
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   5. THREAD + BUBBLES
   ═══════════════════════════════════════════════════════════════════════════ */
#thread {
  flex: 1 1 auto;
  min-height: 0; /* allow the thread to shrink so suggestions/composer never get clipped */
  overflow-y: auto;
  padding: 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Dynamic emoji wallpaper — the default player-facing chat background (trust-tinted). It is
   positioned as a sibling of #thread (child of #phone, which never scrolls) rather than inside
   #thread, because #thread is the scroll container — anything placed inside it, even
   position:absolute, scrolls away with the message history. Same technique as .big-reaction
   below. Must stay a child of #phone for z-index:-1 to sink it behind #thread's bubbles. */
#bg-pattern {
  position: absolute;
  left: 0;
  width: 100%;
  overflow: hidden;
  pointer-events: none;
  z-index: -1;
  opacity: 0;
  transition: opacity 0.5s ease, background-color 0.6s ease;
}
#bg-pattern.visible {
  opacity: 1;
}
.bg-emoji {
  position: absolute;
  line-height: 1;
  opacity: 0.35; /* subtle/muted so message bubbles stay easy to read on top */
  user-select: none;
}
/* The wallpaper color is JS-mixed toward WHITE (scoreToColor in main.js), so in dark mode it
   is dimmed into a muted tint over the dark chat surface instead of glowing pastel.
   The accessibility-purposed solid wallpaper ("color" style, the High-contrast setting)
   stays stronger so the low-vision trust signal survives. */
:root[data-theme="dark"] #bg-pattern.visible { opacity: 0.3; }
:root[data-theme="dark"] #bg-pattern[data-style="color"].visible { opacity: 0.55; }
:root[data-theme="dark"] .bg-emoji { opacity: 0.25; }

.bubble {
  position: relative;
  max-width: 78%;
  padding: 8px 12px;
  border-radius: var(--radius-bubble);
  font-size: calc(15px * var(--text-scale));
  line-height: 1.4;
  color: var(--text-primary);
  white-space: pre-wrap;
  word-wrap: break-word;
  animation: pop 0.18s ease;
}

/* Signal-style message grouping: consecutive bubbles from the same side tighten the
   corners that face each other. (:has() covers the earlier bubble; unsupported browsers
   simply keep the full radius there — graceful.) */
.bubble.received + .bubble.received { border-top-left-radius: var(--radius-bubble-group); }
.bubble.received:has(+ .bubble.received) { border-bottom-left-radius: var(--radius-bubble-group); }
.bubble.sent + .bubble.sent { border-top-right-radius: var(--radius-bubble-group); }
.bubble.sent:has(+ .bubble.sent) { border-bottom-right-radius: var(--radius-bubble-group); }

/* Emoji reaction (tapback) on a message */
.reaction-badge {
  position: absolute;
  bottom: -10px;
  right: 6px;
  font-size: 14px;
  background: var(--bg-elevated);
  border-radius: 10px;
  padding: 1px 4px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  animation: pop 0.2s ease;
}
.bubble.dev-test { border: 1px dashed #999; }
.reaction-badge.strong {
  font-size: 18px;
  animation: reactionPopStrong 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes reactionPopStrong {
  0%   { transform: translateY(6px) scale(0.5); opacity: 0; }
  60%  { transform: scale(1.35); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

@keyframes pop {
  from { transform: translateY(6px) scale(0.98); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* Mood-flavored entrance animations for grandma's text bubbles (see .bubble.received.mood-*
   below). "neutral" has no override — the base .bubble animation (pop) is its animation. */
@keyframes popHappy {
  0%   { transform: translateY(8px) scale(0.94); opacity: 0; }
  70%  { transform: translateY(-1px) scale(1.02); opacity: 1; }
  100% { transform: none; opacity: 1; }
}
@keyframes popSad {
  0%   { transform: translateY(-3px) scale(0.99); opacity: 0; }
  100% { transform: translateY(0) scale(1); opacity: 1; }
}
@keyframes popAngry {
  0%   { transform: translateX(-4px) scale(0.97); opacity: 0; }
  60%  { transform: translateX(1px) scale(1.01); opacity: 1; }
  100% { transform: none; opacity: 1; }
}
@keyframes popSurprised {
  0%   { transform: translateY(4px) scale(0.9); opacity: 0; }
  65%  { transform: scale(1.04); opacity: 1; }
  100% { transform: none; opacity: 1; }
}

/* Grandma — left */
.bubble.received {
  align-self: flex-start;
  background: var(--bubble-received);
  color: var(--bubble-received-text);
  /* A white bubble needs a hairline to stay legible against the white/near-white chat
     surface and the pale trust wallpaper — Signal's own light-grey fill doesn't need this. */
  border: 1px solid var(--divider);
}
.bubble.received.mood-happy { animation: popHappy 0.26s cubic-bezier(0.34, 1.56, 0.64, 1); }
.bubble.received.mood-sad { animation: popSad 0.42s ease-out; }
.bubble.received.mood-angry { animation: popAngry 0.16s cubic-bezier(0.3, 0, 0.15, 1); }
.bubble.received.mood-surprised { animation: popSurprised 0.22s cubic-bezier(0.34, 1.56, 0.64, 1); }

/* You — right */
.bubble.sent {
  align-self: flex-end;
  background: var(--bubble-sent);
  color: var(--bubble-sent-text);
}

/* Photo message bubble */
.bubble.image-bubble {
  padding: 4px;
  max-width: min(78%, 280px);
}
.chat-image {
  display: block;
  width: 100%;
  border-radius: 15px; /* bubble radius minus the 4px frame — corners stay concentric */
}
.image-caption {
  padding: 6px 8px 4px;
  font-size: calc(14px * var(--text-scale));
  line-height: 1.3;
}

/* Voice-message bubble */
.bubble.voice-bubble {
  display: flex;
  flex-direction: column;
  min-width: min(220px, 78%);
}
.voice-row {
  display: flex;
  align-items: center;
  gap: 9px;
}
.voice-transcript {
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px solid var(--divider);
  font-size: calc(13px * var(--text-scale));
  line-height: 1.4;
  color: var(--text-secondary);
}
.voice-transcript.clamped .vt-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.vt-toggle {
  background: none;
  border: none;
  padding: 2px 0 0;
  color: var(--accent);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.voice-play {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 14px;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.voice-play .icon { width: 16px; height: 16px; }
/* Play/pause pair — JS toggles .playing on the button (see addVoiceBubble in main.js). */
.voice-play .icon-pause { display: none; }
.voice-play.playing .icon-play { display: none; }
.voice-play.playing .icon-pause { display: block; }
.voice-wave {
  position: relative;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 2px;
  height: 24px;
  overflow: hidden;
}
.voice-wave span {
  flex: 1 1 auto;
  background: var(--voice-wave);
  border-radius: 2px;
}
.voice-progress {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0%;
  background: rgba(58, 118, 240, 0.28);
  border-radius: 3px;
  pointer-events: none;
}
.voice-time {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--text-secondary);
  min-width: 30px;
  text-align: right;
}
/* The player's own sent voice memo sits on the solid accent bubble — flip the player
   controls to white-on-blue so nothing drowns. */
.bubble.sent .voice-play { background: var(--on-accent); color: var(--bubble-sent); }
.bubble.sent .voice-wave span { background: rgba(255, 255, 255, 0.45); }
.bubble.sent .voice-progress { background: rgba(255, 255, 255, 0.35); }
.bubble.sent .voice-time { color: rgba(255, 255, 255, 0.85); }
.bubble.sent .voice-transcript { color: rgba(255, 255, 255, 0.85); border-top-color: rgba(255, 255, 255, 0.25); }
.bubble.sent .vt-toggle { color: #ffffff; }

/* System note (e.g. "local AI offline") — centered, not a chat bubble */
.system-note {
  align-self: center;
  max-width: 88%;
  text-align: center;
  background: var(--note-bg);
  color: var(--note-text);
  font-size: calc(13px * var(--text-scale));
  line-height: 1.4;
  padding: 7px 12px;
  border-radius: var(--radius-md);
}

/* "How to play" primer cards — calm, friendly, instructional */
.system-note.info {
  background: var(--note-info-bg);
  color: var(--note-info-text);
  max-width: 90%;
}

/* A scripted "gut-punch" event banner */
.system-note.event {
  background: var(--note-event-bg);
  color: var(--note-event-text);
  font-weight: 600;
  letter-spacing: 0.3px;
}

/* Grandma's message when it's the opening of an event scene — subtly heavier */
.bubble.received.event-bubble {
  border-left: 3px solid var(--trust-bad);
}

/* Date/time separator between moments */
.date-sep {
  align-self: center;
  margin: 6px 0;
  padding: 4px 12px;
  background: var(--bg-elevated);
  color: var(--text-secondary);
  font-size: calc(12px * var(--text-scale));
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-1);
}

/* The call's trace in the chat thread afterwards — a call-log line. */
.call-record {
  align-self: center;
  margin: 4px 0;
  padding: 6px 14px;
  background: var(--bg-elevated);
  color: var(--text-primary);
  font-size: calc(13px * var(--text-scale));
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-1);
}

/* Typing indicator — reads as a received bubble being written */
.typing {
  align-self: flex-start;
  background: var(--bubble-received);
  border: 1px solid var(--divider);
  padding: 12px 14px;
  border-radius: var(--radius-bubble);
  display: inline-flex;
  gap: 4px;
}
.typing span {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--text-secondary);
  animation: blink 1.2s infinite ease-in-out;
}
.typing span:nth-child(2) { animation-delay: 0.2s; }
.typing span:nth-child(3) { animation-delay: 0.4s; }
@keyframes blink {
  0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }
  40% { opacity: 1; transform: translateY(-3px); }
}

/* Loading wheel shown in the empty thread while /api/start is in flight, until the first date
   separator appears (see showThreadLoader / start() in main.js). margin:auto centers it in the
   flex-column #thread. Under body.reduce-motion the global animation reset freezes it into a
   static ring, which still reads as "loading". */
.thread-loader {
  width: 34px;
  height: 34px;
  margin: auto;
  border-radius: 50%;
  border: 3px solid var(--divider);
  border-top-color: var(--accent);
  animation: threadSpin 0.8s linear infinite;
}
@keyframes threadSpin { to { transform: rotate(360deg); } }

/* Skip-intro control (hidden unless the intro is playing) — floats over the wallpaper. */
#skip-intro {
  display: none;
  align-self: center;
  margin: 0 0 8px;
  padding: 6px 16px;
  border: none;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 13px;
  cursor: pointer;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
#skip-intro.visible {
  display: block;
}

/* ═══════════════════════════════════════════════════════════════════════════
   6. LIVE PHONE CALL OVERLAY ("Grandma calls you")
   js/call.js shows this and pins its top edge just below #trust-meter, so the header and the
   LIVE trust bar stay visible while she's on the phone. data-state drives the two layouts:
   "incoming" (ringing) vs "connecting"/"active" (on the call). Deliberately dark in BOTH themes
   — real phones dim the screen for a call, and it sets the beat apart from the chat.
   ═══════════════════════════════════════════════════════════════════════════ */
#call-overlay {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40; /* above the thread + composer, below #title-screen (50) */
  display: flex;
  flex-direction: column;
  align-items: center;
  background: linear-gradient(180deg, #1c2431, #0e0f12 85%);
  color: #e9e9eb;
  padding: 26px 20px calc(24px + env(safe-area-inset-bottom));
}
#call-overlay[hidden] { display: none; }

#call-card { display: flex; flex-direction: column; align-items: center; gap: 7px; }

#call-avatar-ring {
  position: relative;
  width: 128px;
  height: 128px;
  display: grid;
  place-items: center;
}
#call-avatar {
  width: 112px;
  height: 112px;
  border-radius: 50%;
  background: #2c2c2e;
  object-fit: cover;
}
/* Pulsing halo: while she rings, and again whenever she's the one speaking mid-call. */
#call-overlay[data-state="incoming"] #call-avatar-ring::before,
#call-avatar-ring.speaking::before {
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  border: 3px solid rgba(37, 211, 102, 0.85);
  animation: call-pulse 1.6s ease-out infinite;
}
@keyframes call-pulse {
  0% { transform: scale(0.94); opacity: 0.9; }
  70% { transform: scale(1.16); opacity: 0; }
  100% { transform: scale(1.16); opacity: 0; }
}

#call-name { font-size: 24px; font-weight: 600; letter-spacing: -0.01em; }
#call-status { font-size: 14px; opacity: 0.8; min-height: 18px; }
#call-timer { font-size: 14px; opacity: 0.8; font-variant-numeric: tabular-nums; }

/* Live captions of the conversation (her lines left, yours right), newest at the bottom. */
#call-captions {
  flex: 1;
  width: 100%;
  max-width: 340px;
  margin-top: 12px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 6px;
}
.call-caption {
  max-width: 85%;
  padding: 6px 10px;
  border-radius: var(--radius-md);
  font-size: calc(13px * var(--text-scale, 1));
  line-height: 1.35;
}
.call-caption.received { align-self: flex-start; background: rgba(255, 255, 255, 0.13); }
.call-caption.sent { align-self: flex-end; background: rgba(58, 118, 240, 0.45); }

#call-actions { display: flex; gap: 46px; margin-top: auto; padding-top: 16px; }
.call-action {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  opacity: 0.95;
}
.call-action[hidden] { display: none; }
.call-btn {
  width: 62px;
  height: 62px;
  border-radius: 50%;
  border: none;
  font-size: 26px;
  color: #fff;
  cursor: pointer;
  display: grid;
  place-items: center;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
}
.call-btn .icon { width: 26px; height: 26px; }
.call-btn:active { transform: scale(0.94); }
.call-btn-green { background: #25d366; }
.call-btn-red { background: #e74c3c; }
.call-btn-grey { background: rgba(255, 255, 255, 0.18); }
.call-btn-grey.muted { background: #ffffff; color: #1b1b1d; }
/* Mic / mic-off pair — call.js toggles .muted on the button. */
#call-mute .icon-mic-off { display: none; }
#call-mute.muted .icon-mic { display: none; }
#call-mute.muted .icon-mic-off { display: block; }
.call-btn-hangup { transform: rotate(135deg); } /* the handset tips over into "hang up" */
.call-btn-hangup:active { transform: rotate(135deg) scale(0.94); }

/* ═══════════════════════════════════════════════════════════════════════════
   7. HIDDEN DEVELOPER PANEL (Ctrl+Shift+D)
   ═══════════════════════════════════════════════════════════════════════════ */
#dev-panel {
  position: fixed;
  top: 0;
  right: 0;
  width: 340px;
  max-width: 92vw;
  height: 100vh;
  overflow-y: auto;
  background: #1d1f23;
  color: #e6e6e6;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 12px;
  z-index: 1000;
  box-shadow: -4px 0 18px rgba(0, 0, 0, 0.4);
}
#dev-panel[hidden] { display: none; }
.dev-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 12px;
  background: #111;
  font-weight: 700;
  position: sticky;
  top: 0;
}
.dev-close { cursor: pointer; opacity: 0.7; }
.dev-close:hover { opacity: 1; }
.dev-sec { padding: 10px 12px; border-top: 1px solid #33363c; }
.dev-sec h4 { margin: 0 0 8px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #9aa0a6; }
.dev-mono { line-height: 1.6; }
.dev-hint { font-size: 10px; color: #9aa0a6; white-space: nowrap; }
.dev-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 6px; }
#dev-panel input, #dev-panel textarea, #dev-panel select {
  background: #2a2d33;
  border: 1px solid #3a3e45;
  color: #e6e6e6;
  border-radius: 5px;
  padding: 5px 7px;
  font: inherit;
}
#dev-panel textarea { width: 100%; resize: vertical; }
#dev-panel input { flex: 1 1 auto; min-width: 60px; }
#dev-panel select { flex: 1 1 auto; }
#dev-panel button {
  background: #3a6df0;
  color: #fff;
  border: none;
  border-radius: 5px;
  padding: 5px 9px;
  font: inherit;
  cursor: pointer;
}
#dev-panel button:hover { filter: brightness(1.1); }
#dev-panel [data-preset] { background: #4b4f57; }
#dev-panel details { margin-top: 6px; }
#dev-panel summary { cursor: pointer; color: #9aa0a6; }
#dev-panel pre {
  white-space: pre-wrap;
  word-break: break-word;
  background: #15171a;
  padding: 8px;
  border-radius: 5px;
  max-height: 220px;
  overflow: auto;
  font-size: 11px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   8. REPLY SUGGESTIONS (hidden until the player hesitates)
   A frosted strip that floats over the thread, anchored to the composer's top
   edge via a zero-height hook so it tracks the textarea as it auto-grows.
   ═══════════════════════════════════════════════════════════════════════════ */
#suggestions-anchor {
  position: relative;
  height: 0;
}
#suggestions {
  display: none;
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 5; /* above the thread's content, below the call/title overlays (z-index 50) */
  flex-wrap: wrap;
  gap: 8px;
  padding: 8px 10px 10px;
  background: var(--suggestions-strip-bg);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
#suggestions.visible { display: flex; }
.suggestion-chip {
  background: var(--bg-elevated);
  color: var(--accent);
  border: 1px solid var(--divider);
  border-radius: var(--radius-pill);
  padding: 8px 14px;
  font-size: calc(13.5px * var(--text-scale));
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
  line-height: 1.25;
  max-width: 100%;
  white-space: normal;
  word-break: break-word;
  box-shadow: var(--shadow-1);
  animation: pop 0.18s ease;
  transition: background-color 0.15s ease, border-color 0.15s ease,
    box-shadow 0.15s ease, transform 0.1s ease;
}
@media (hover: hover) {
  .suggestion-chip:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
  }
}
.suggestion-chip:active { transform: scale(0.96); }
.suggestion-chip.used {
  color: var(--text-secondary);
  border-color: transparent;
  background: var(--bg-input);
  box-shadow: none;
  opacity: 0.7;
}
.suggestion-chip.used:hover { background: var(--bg-input); border-color: transparent; }

/* The intro's single tap-to-send reply button (a scripted-cutscene player line). Reads as THE
   action: full-width accent pill with a send arrow, vs. the small tonal chips used in play. */
.suggestion-chip.intro-reply {
  flex: 1 1 100%;
  justify-content: center;
  align-items: center;
  display: flex;
  gap: 6px;
  text-align: center;
  background: var(--accent);
  color: var(--on-accent);
  border-color: transparent;
  font-weight: 600;
  padding: 12px 16px;
}
.suggestion-chip.intro-reply::after {
  content: "";
  width: 15px;
  height: 15px;
  flex: none;
  background: currentColor;
  /* up-arrow send glyph, masked so it inherits the button's text color */
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 19V5'/%3E%3Cpath d='m5 12 7-7 7 7'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 19V5'/%3E%3Cpath d='m5 12 7-7 7 7'/%3E%3C/svg%3E") center / contain no-repeat;
}
@media (hover: hover) {
  .suggestion-chip.intro-reply:hover { background: var(--accent-strong); border-color: transparent; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   9. COMPOSER + VOICE RECORDER
   ═══════════════════════════════════════════════════════════════════════════ */
#composer {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 8px 10px;
  background: var(--bg-bar);
  border-top: 1px solid var(--divider);
}
/* Without this, main.js's form.hidden = true (while recording a voice memo) has no visual
   effect: the ID-selector rule above already sets display:flex, which beats the browser's
   own [hidden] {display:none} rule in the cascade regardless of specificity. Same pattern as
   #mic-btn[hidden] / #voice-recorder[hidden] below. */
#composer[hidden] { display: none; }

#message-input {
  flex: 1 1 auto;
  border: none;
  border-radius: 22px;
  padding: 11px 16px;
  font-size: calc(15px * var(--text-scale));
  font-family: inherit;
  line-height: 1.35;
  outline: none;
  resize: none;
  max-height: 120px;
  overflow-y: auto;
  background: var(--bg-input);
  color: var(--text-primary);
}
#message-input::placeholder { color: var(--text-secondary); }
#message-input:disabled { opacity: 0.5; cursor: default; }

#send {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 18px;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background 0.15s, transform 0.1s;
}
#send:active:not(:disabled) { transform: scale(0.92); }
#send:disabled { opacity: 0.5; cursor: default; }
@media (hover: hover) {
  #send:hover:not(:disabled) { background: var(--accent-strong); }
}

#mic-btn {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-secondary);
  font-size: 19px;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background 0.15s;
}
#mic-btn[hidden] { display: none; }
#mic-btn:disabled { opacity: 0.5; cursor: default; }
@media (hover: hover) {
  #mic-btn:hover:not(:disabled) { background: var(--bg-input); }
}

/* Active-recording bar, swapped in for #composer while recording a voice-memo reply. */
#voice-recorder {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 10px;
  background: var(--bg-bar);
  border-top: 1px solid var(--divider);
}
#voice-recorder[hidden] { display: none; }

#voice-recorder-dot {
  flex: 0 0 auto;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: #e04a3f;
  animation: recordPulse 1.1s ease-in-out infinite;
}
@keyframes recordPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

#voice-recorder-time {
  flex: 0 0 auto;
  font-size: 13px;
  color: var(--text-secondary);
  min-width: 34px;
  font-variant-numeric: tabular-nums;
}

/* Reuses the voice-bubble waveform's visual language for the live-recording indicator. */
#voice-recorder-wave {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 3px;
  height: 20px;
}
#voice-recorder-wave span {
  flex: 1 1 auto;
  height: 60%;
  background: var(--voice-wave);
  border-radius: 2px;
  animation: recordWave 0.9s ease-in-out infinite;
}
#voice-recorder-wave span:nth-child(2) { animation-delay: 0.15s; }
#voice-recorder-wave span:nth-child(3) { animation-delay: 0.3s; }
#voice-recorder-wave span:nth-child(4) { animation-delay: 0.45s; }
#voice-recorder-wave span:nth-child(5) { animation-delay: 0.6s; }
@keyframes recordWave {
  0%, 100% { transform: scaleY(0.4); }
  50% { transform: scaleY(1); }
}

#voice-cancel, #voice-stop {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  border: none;
  border-radius: 50%;
  font-size: 15px;
  cursor: pointer;
  display: grid;
  place-items: center;
}
#voice-cancel { background: var(--bg-input); color: var(--text-secondary); }
#voice-stop { background: var(--accent); color: var(--on-accent); }

/* ═══════════════════════════════════════════════════════════════════════════
   10. SETTINGS
   ═══════════════════════════════════════════════════════════════════════════ */

/* Settings link — small and muted, under the CTA in the composer-style footer. */
#settings-title-btn {
  display: block;
  margin: 10px auto 0;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-size: 13px;
  cursor: pointer;
  padding: 4px 8px;
}
@media (hover: hover) {
  #settings-title-btn:hover { color: var(--accent); }
}

/* Dark-mode toggle icons — pure CSS swap off the theme attribute (no JS text writes). */
#title-dark-toggle .icon-sun { display: none; }
:root[data-theme="dark"] #title-dark-toggle .icon-moon { display: none; }
:root[data-theme="dark"] #title-dark-toggle .icon-sun { display: block; }

/* Sound toggle icons — settings.js/main.js toggle .muted on the button. */
#mute-btn .icon-sound-off { display: none; }
#mute-btn.muted .icon-sound-on { display: none; }
#mute-btn.muted .icon-sound-off { display: block; }

/* ── Settings panel overlay ─────────────────────────────────────────────────── */
#settings-panel {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
#settings-panel[hidden] { display: none; }

#settings-overlay {
  position: absolute;
  inset: 0;
  background: var(--scrim);
  opacity: 0;
  transition: opacity 250ms ease-in;
}

#settings-sheet {
  position: relative;
  background: var(--bg-sheet);
  border-radius: 20px 20px 0 0;
  padding: 0 0 24px;
  max-height: 65%;
  overflow-y: auto;
  transform: translateY(100%);
  transition: transform 250ms ease-in; /* keep ≤250ms — settings.js closes on a 260ms timeout */
}
/* Grabber pill — purely cosmetic bottom-sheet affordance. */
#settings-sheet::before {
  content: "";
  display: block;
  width: 36px;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--toggle-off);
  margin: 8px auto 0;
}

#settings-panel.open #settings-overlay {
  opacity: 1;
  transition: opacity 250ms ease-out;
}

#settings-panel.open #settings-sheet {
  transform: translateY(0);
  transition: transform 250ms ease-out;
}

#settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 20px 12px;
  border-bottom: 1px solid var(--divider);
  font-weight: 600;
  font-size: 16px;
  color: var(--text-primary);
}

#settings-close {
  border: none;
  background: transparent;
  font-size: 18px;
  color: var(--text-secondary);
  cursor: pointer;
  padding: 4px;
  line-height: 1;
  border-radius: 50%;
  display: grid;
  place-items: center;
}
@media (hover: hover) {
  #settings-close:hover { color: var(--text-primary); background: var(--bg-input); }
}

#settings-body { padding: 8px 0; }

.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px;
  border-bottom: 1px solid var(--divider);
}
.settings-row:last-child { border-bottom: none; }

.settings-section-title {
  margin: 4px 20px 0;
  padding-top: 14px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
  border-top: 1px solid var(--divider);
}

.settings-label {
  font-size: 15px;
  color: var(--text-primary);
}

/* Sliding on/off switch — accent-blue when on (Signal-style, not messenger green). */
.settings-toggle {
  position: relative;
  flex: none;
  width: 50px;
  height: 30px;
  padding: 0;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--toggle-off);
  cursor: pointer;
  transition: background 0.2s ease;
}
.settings-toggle::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
  transition: transform 0.2s ease;
}
.settings-toggle[data-on="true"] { background: var(--accent); }
.settings-toggle[data-on="true"]::before { transform: translateX(20px); }

/* Text size — segmented control */
.size-picker {
  display: flex;
  gap: 4px;
  background: var(--bg-input);
  border-radius: var(--radius-md);
  padding: 3px;
}
.size-btn {
  border: none;
  border-radius: 9px;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  font-family: inherit;
  line-height: 1;
  min-width: 44px;
  min-height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.12s, color 0.12s;
}
.size-btn:nth-child(1) { font-size: 13px; }
.size-btn:nth-child(2) { font-size: 17px; }
.size-btn:nth-child(3) { font-size: 21px; }
.size-btn.active {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

/* ── Text-size scaling ──────────────────────────────────────────────────────── */
#phone { --text-scale: 1; }
#phone[data-text-size="small"]   { --text-scale: 0.87; }
#phone[data-text-size="large"]   { --text-scale: 1.2; }

/* ═══════════════════════════════════════════════════════════════════════════
   11. BIG SWING MOMENT (very good / very bad reply)
   Scoped to the chat window, but appended to #phone (not #thread) and positioned with an
   inline top/height matching #thread's box — see showBigReaction() in main.js. #thread
   scrolls internally (and stays scrolled to the bottom), so an element positioned *inside*
   it with inset: 0 would resolve against the unscrolled content and end up off-screen.
   #phone never scrolls, so anchoring there keeps the overlay reliably pinned in place.
   ═══════════════════════════════════════════════════════════════════════════ */
.big-reaction {
  position: absolute;
  left: 0;
  right: 0;
  pointer-events: none;
  z-index: 5;
  overflow: hidden;
  --intensity: 0;
}
.big-reaction::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 60%; /* stays low in the chat window instead of sweeping most of the way up */
  /* Anchor scaling to the bottom edge, not the box's center — otherwise the springy scale
     pulse in bigReactionFill lifts the bottom edge away from the container's clipped edge
     and exposes a hard seam where the gradient abruptly stops. */
  transform-origin: 50% 100%;
  filter: brightness(calc(1 + 0.35 * var(--intensity)));
  animation: bigReactionFill var(--fx-duration-glow, 1.8s) cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.big-reaction.positive::before {
  background: radial-gradient(ellipse at 50% 100%, rgba(37, 211, 102, 0.6), rgba(37, 211, 102, 0) 55%);
}
.big-reaction.negative::before {
  background: radial-gradient(ellipse at 50% 100%, rgba(231, 76, 60, 0.6), rgba(231, 76, 60, 0) 55%);
}
@keyframes bigReactionFill {
  0%   { transform: scale(1); opacity: 0; }
  28%  { transform: scale(1.06); opacity: 1; }
  45%  { transform: scale(0.97); opacity: 1; }
  65%  { transform: scale(1.02); opacity: 1; }
  100% { transform: scale(1); opacity: 0; }
}
/* A fast "moment of impact" flash layered on top of the glow, on the way in. Tinted per
   sign (a white mix-blend-screen flash would vanish on the light theme's white surface). */
.big-reaction::after {
  content: "";
  position: absolute;
  inset: 0;
  animation: bigReactionFlash 0.5s ease-out forwards;
}
.big-reaction.positive::after {
  background: radial-gradient(circle at 50% 100%, rgba(37, 211, 102, 0.35), rgba(37, 211, 102, 0) 60%);
}
.big-reaction.negative::after {
  background: radial-gradient(circle at 50% 100%, rgba(231, 76, 60, 0.35), rgba(231, 76, 60, 0) 60%);
}
@keyframes bigReactionFlash {
  0%   { opacity: 0; }
  15%  { opacity: 1; }
  100% { opacity: 0; }
}
.big-reaction-thumb {
  position: absolute;
  bottom: -10%;
  animation: bigReactionFloat var(--fx-duration-thumb, 2s) cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes bigReactionFloat {
  0%   { transform: translateY(0) translateX(0) rotate(0deg) scale(0.5); opacity: 0; }
  15%  { opacity: 1; }
  45%  { transform: translateY(calc(var(--rise, 300px) * -0.45)) translateX(calc(var(--drift, 0px) * 0.5)) rotate(calc(var(--rot, 12deg) * 0.6)) scale(1.2); }
  100% { transform: translateY(calc(var(--rise, 300px) * -1)) translateX(var(--drift, 0px)) rotate(var(--rot, 12deg)) scale(0.85); opacity: 0; }
}

/* A quick shake on the whole phone frame for the big-swing "moment of impact". */
#phone.fx-shake {
  animation: fxShake 0.4s ease-in-out;
}
@keyframes fxShake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px) rotate(-0.3deg); }
  40% { transform: translateX(4px) rotate(0.3deg); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(2px); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   12. ACCESSIBILITY + THEME TRANSITIONS
   ═══════════════════════════════════════════════════════════════════════════ */
body.reduce-motion, body.reduce-motion * {
  transition: none !important;
  animation: none !important;
}
body.reduce-motion .big-reaction { display: none; }

/* Smooth, restrained transition on the primary surfaces when the theme flips. The existing
   body.reduce-motion * { transition: none !important; } rule (universal selector,
   !important) already neutralizes this for players with that preference — no extra guard needed. */
body,
#phone,
#title-screen,
.title-topbar,
#wizard-progress,
#wizard-footer,
#chat-header,
#trust-meter,
#composer,
#voice-recorder,
#settings-sheet {
  transition: background-color 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}

/* ═══════════════════════════════════════════════════════════════════════════
   13. MOBILE / RESPONSIVE — real phone-width portrait, or landscape phones (short
   viewport height). Above this breakpoint the "phone mockup card centered on
   a backdrop" presentation is left completely unchanged — that reads as an
   intentional demo frame on a desktop/tablet, but on an actual phone screen
   the backdrop margin is just wasted space, so the chat goes edge-to-edge.
   Tablets (e.g. iPad 768×1024 either orientation) stay above both conditions
   below and keep the card look.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 600px), (max-height: 500px) and (orientation: landscape) {
  body { padding: 0; }

  #phone {
    position: fixed;
    top: var(--app-offset-top, 0px);
    left: 0;
    width: 100%;
    max-width: 100%;
    height: var(--app-height, 100svh);
    border-radius: 0;
    box-shadow: none;
  }

  /* Safe-area padding only matters once the frame touches real screen edges. */
  #chat-header {
    padding-top: calc(10px + env(safe-area-inset-top));
    padding-left: calc(12px + env(safe-area-inset-left));
    padding-right: calc(12px + env(safe-area-inset-right));
  }
  #composer, #voice-recorder {
    padding-bottom: calc(8px + env(safe-area-inset-bottom));
  }
  #settings-sheet {
    padding-bottom: calc(24px + env(safe-area-inset-bottom));
  }
  /* Full-bleed on real phones; the topbar/footer manage their own safe-area insets. */
  #title-screen { padding: 0; }
  .title-topbar {
    padding-top: calc(10px + env(safe-area-inset-top));
    padding-left: calc(12px + env(safe-area-inset-left));
    padding-right: calc(12px + env(safe-area-inset-right));
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   AD MOCKUP (dev-only, screenshot-ready)
   Covers the phone with a fake friend chat promoting the game. Reuses the real
   .bubble received/sent styles; only the frame around them is defined here.
   Opened from the dev panel via window.GAME.showAd(); see #ad-overlay in HTML.
   ═══════════════════════════════════════════════════════════════════════════ */
#ad-overlay {
  position: absolute;
  inset: 0;
  z-index: 70;
  display: flex;
  flex-direction: column;
  background: var(--bg-chat);
}
#ad-overlay[hidden] { display: none; }

#ad-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 12px 10px;
  background: var(--bg-bar);
  border-bottom: 1px solid var(--divider);
}
#ad-avatar {
  width: 42px;
  height: 42px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 18px;
}
#ad-header-info { flex: 1 1 auto; min-width: 0; }
#ad-name { font-weight: 600; font-size: 16px; letter-spacing: -0.01em; color: var(--text-primary); }
#ad-status { font-size: 12px; color: var(--text-secondary); }
#ad-close {
  flex: 0 0 auto;
  border: none;
  border-radius: 50%;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
}
@media (hover: hover) { #ad-close:hover { background: var(--bg-input); } }

#ad-thread {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px 12px;
}

/* The QR message: a small photo bubble, capped so it "isn't too big". */
.ad-qr-bubble { max-width: min(60%, 172px); }
.ad-qr {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  border-radius: 15px;
  background: #fff; /* white quiet zone so the code scans even in dark mode */
}

/* Decorative, non-functional composer strip — sells the "real chat" look. */
#ad-composer {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
  background: var(--bg-bar);
  border-top: 1px solid var(--divider);
}
#ad-composer-placeholder {
  flex: 1 1 auto;
  padding: 9px 14px;
  border-radius: var(--radius-pill);
  background: var(--bg-input);
  color: var(--text-secondary);
  font-size: 15px;
}
#ad-composer-send {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--on-accent);
}
