:root {
  --bg: #07080d;
  --bg-elev: rgba(22, 25, 37, 0.55);
  --bg-elev-2: rgba(31, 35, 52, 0.55);
  --border: rgba(255, 255, 255, 0.08);
  --border-strong: rgba(255, 255, 255, 0.16);
  --glass: rgba(18, 20, 30, 0.45);
  --glass-strong: rgba(18, 20, 30, 0.62);
  --text: #f5f6fa;
  --text-dim: rgba(245, 246, 250, 0.70);
  --text-faint: rgba(245, 246, 250, 0.48);

  --brand: #8b5cf6;
  --brand-2: #ec4899;
  --brand-3: #22d3ee;
  --accent: #a78bfa;
  --accent-2: #22d3ee;
  --listening: #22d3ee;
  --speaking: #a78bfa;
  --processing: #fbbf24;
  --error: #ff6a75;
  --success: #34d399;

  --brand-gradient: linear-gradient(135deg, #8b5cf6 0%, #ec4899 50%, #22d3ee 100%);
  --brand-soft-gradient: linear-gradient(135deg, color-mix(in srgb, #8b5cf6 55%, transparent),
                                          color-mix(in srgb, #ec4899 48%, transparent) 55%,
                                          color-mix(in srgb, #22d3ee 55%, transparent));

  /* Mono is the "machine voice": reserved for system text the app emits —
   * the orb's status caption, tool calls, the transport tag. Body/UI stays
   * Inter. */
  --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  --font-sans: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  /* Thesis: color belongs to the voice. The chrome is monochrome; saturated
   * hue appears only on the orb and on these tiny role echoes, which mirror
   * the orb's own state colors (you listening = cyan, assistant = violet,
   * tool = amber) so the transcript reads in the same color language. */
  --voice-user: var(--accent-2);
  --voice-assistant: var(--accent);
  --voice-tool: var(--processing);

  /* Smoothed mic RMS in [0..1], updated every frame from JS while a session
   * is active. Used by audio-reactive circle states. */
  --audio-level: 0;
  /* Five log-spaced frequency bands extracted from the mic analyser;
   * drive each bar's height independently for a real "spectrum" feel. */
  --bar0: 0;
  --bar1: 0;
  --bar2: 0;
  --bar3: 0;
  --bar4: 0;

  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 26px;
  --radius-xl: 34px;

  --shadow-soft: 0 10px 40px rgba(0, 0, 0, 0.35);
  --shadow-glow-brand: 0 0 40px -10px color-mix(in srgb, var(--brand) 60%, transparent),
                       0 0 80px -30px color-mix(in srgb, var(--brand-2) 55%, transparent);
  --shadow-glass: 0 8px 40px rgba(0, 0, 0, 0.32), 0 2px 8px rgba(0, 0, 0, 0.18);

  color-scheme: dark;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
}

body {
  font-family: var(--font-sans);
  color: var(--text);
  /* Guarantee the page at least fills the viewport; if content is taller we
   * scroll the document naturally (so footer is reachable via scroll in any
   * case). Sticky .app-footer pins it to the BOTTOM of the viewport while
   * scrolling / at rest. */
  min-height: 100vh;
  min-height: 100svh;
  min-height: 100dvh;
  width: 100%;
  /* ─── iOS / notch / rounded-screen safe-area insets ───
     The user reported the header brand card is slightly clipped (avatar AV
     touches the top-right viewport edge). Mobile OSes use a non-rectangular
     bezel/notch and an iOS Home Indicator bar at the bottom. We reserve
     padding at document scope so NOTHING (topbar orb chat footer) is ever
     painted into the OS reserved bezels. The `max(10px, env(...))` fallback
     gives a minimum 10px breathing even on desktop / devices where env()
     resolves to 0. */
  padding-top:    max(10px, env(safe-area-inset-top   , 0px));
  padding-right:  max(10px, env(safe-area-inset-right , 0px));
  padding-left:   max(10px, env(safe-area-inset-left  , 0px));
  /* Bottom handled by #app padding-bottom below (footer bar + home indicator). */
  overflow-x: hidden;
  overflow-y: auto;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  position: relative;
  background:
    radial-gradient(1200px 800px at 15% -10%, rgba(139, 92, 246, 0.30) 0%, transparent 60%),
    radial-gradient(900px 700px at 110% 10%, rgba(236, 72, 153, 0.22) 0%, transparent 55%),
    radial-gradient(1000px 800px at 50% 120%, rgba(34, 211, 238, 0.22) 0%, transparent 60%),
    radial-gradient(ellipse at 50% 50%, #0e1019 0%, var(--bg) 65%);
  background-attachment: fixed;
}
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
    radial-gradient(rgba(139, 92, 246, 0.035) 1px, transparent 1px);
  background-size: 42px 42px, 42px 42px;
  background-position: 0 0, 21px 21px;
  mix-blend-mode: screen;
  opacity: 0.55;
  mask-image: linear-gradient(180deg, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.4) 70%, transparent 100%);
}
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(600px 600px at 20% 80%, rgba(139, 92, 246, 0.10), transparent 70%),
    radial-gradient(500px 500px at 80% 20%, rgba(34, 211, 238, 0.08), transparent 70%);
  animation: auroraShift 22s ease-in-out infinite alternate;
}
@keyframes auroraShift {
  0%   { transform: translate3d(0, 0, 0) scale(1);   opacity: .9; }
  50%  { transform: translate3d(3%, -2%, 0) scale(1.08); opacity: 1; }
  100% { transform: translate3d(-2%, 2%, 0) scale(1.02); opacity: .85; }
}

/* ─── Overlay unlocker TALK mode (S2S_START_SESSION_MODE=TALK) ───
   Cattura QUALSIASI primo tap/click dell'utente sulla pagina, perché
   i browser bloccano getUserMedia() fuori da una user gesture. Senza
   questo layer il doStart() del setTimeout falliva e non si sentiva
   il greet. Sopra TUTTO tranne i modali <dialog> (z-index auto di dialog
   vince). */
.talk-unlocker {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(180deg, rgba(14,16,25,0.35) 0%, rgba(14,16,25,0.55) 60%, rgba(14,16,25,0.75) 100%);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  cursor: pointer;
  animation: talkUnlockFade 260ms ease-out both;
}
.talk-unlocker[hidden] {
  display: none !important;
}
@keyframes talkUnlockFade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.talk-unlocker-hint {
  width: min(86vw, 420px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 28px 22px 24px;
  border-radius: 22px;
  background: rgba(22, 25, 40, 0.78);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow:
    0 20px 50px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
  text-align: center;
  animation: talkUnlockPop 380ms cubic-bezier(.2,.9,.3,1.2) 80ms both;
}
@keyframes talkUnlockPop {
  from { opacity: 0; transform: translateY(14px) scale(0.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.talk-unlocker-spinner {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 3px solid rgba(255,255,255,0.1);
  border-top-color: #8b5cf6;
  border-right-color: #ec4899;
  animation: talkSpinnerSpin 900ms linear infinite;
}
@keyframes talkSpinnerSpin {
  to { transform: rotate(360deg); }
}
.talk-unlocker-title {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text, #f3f4f6);
}
.talk-unlocker-sub {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: rgba(226, 232, 240, 0.68);
}

button {
  font-family: inherit;
}

a {
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid var(--border-strong);
}
a:hover {
  border-bottom-color: var(--text);
}

#app {
  /* Simple flex column layout: header → stage → chat-panel.
     At least fills viewport, grows if contents taller. The footer below is a
     sibling position:sticky bottom=0 (NOT a child of #app) so to prevent the
     sticky footer from COVERING the last line of the chat history (which
     would otherwise be painted under the ~32-44px of glass footer) we reserve
     a bottom padding matching the real footer height + breathing margin +
     iOS Home Indicator (safe-area-inset-bottom). */
  display: flex;
  flex-direction: column;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  min-height: 100dvh;
  /* 120px minimum = glass footer bar (32-44) + large breathing (76-88) + any
     iOS home indicator safe inset. We intentionally OVER-provision here: the
     last assistant bubble in the screenshot was clipped because the total
     reserved room (56 + chat-inner 96) was smaller than the real floating
     footer + text-wrap delta + OS safe area. */
  padding-bottom: max(120px, calc(env(safe-area-inset-bottom, 0px) + 120px));
  padding-right: 0;  /* side safe areas already on <body>; don't double-add */
  padding-left:  0;
  padding-top:   0;
  box-sizing: border-box;
}

.hidden {
  display: none !important;
}

/* ─── Footer ─────────────────────────────────────────────────────────── */
/* #app uses flex column layout so footer sits as the last child.
   We make it STICKY at the very bottom of the viewport so:
   - if page fits within viewport → footer is at bottom, visible.
   - if page is taller and we scroll document → footer STAYS pinned at
     viewport bottom until the very end where it joins the end of content.
   Glass tinted bg prevents footer text becoming unreadable when overlaying
   the orb / aurora gradients during mid-scroll. */
.app-footer {
  position: sticky;
  bottom: 0;
  left: 0;
  width: 100%;
  flex: 0 0 auto;   /* never shrink or grow the footer */
  display: flex;
  align-items: center;
  justify-content: center;
  /* On iOS the floating "home indicator" pill is drawn at the very bottom of
     the screen inside env(safe-area-inset-bottom) and obscures content. We
     extend the footer glass padding to sit ABOVE the pill. Desktop browsers
     resolve env() to 0, so clamp(max(12px,...)) is 12–20px there. */
  padding:
    clamp(8px, 1.6vw, 12px)
    clamp(16px, 4vw, 24px)
    max(clamp(12px, 2.4vw, 20px), env(safe-area-inset-bottom, 0px));
  user-select: none;
  pointer-events: none;
  z-index: 3;       /* above aurora bg & stage pedestal, below modals (z 200+) */
  box-sizing: border-box;
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  background:
    linear-gradient(180deg,
      transparent 0%,
      color-mix(in srgb, var(--bg) 72%, transparent) 40%,
      color-mix(in srgb, var(--bg) 90%, transparent) 100%);
}
.app-footer::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(640px, 70vw);
  height: 1px;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--brand) 40%, transparent) 30%,
                                            color-mix(in srgb, var(--brand-2) 40%, transparent) 70%, transparent);
  opacity: 0.7;
}
.app-footer-text {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: clamp(10px, 1.5vw, 11px);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  background: var(--brand-gradient);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  text-align: center;
  white-space: normal;
  word-break: break-word;
  overflow-wrap: anywhere;
  line-height: 1.35;
  max-width: 100%;
  filter: drop-shadow(0 1px 0 rgba(0,0,0,.5));
}

/* ─── Topbar ──────────────────────────────────────────────────────────── */

/* Topbar is just a floating row of controls over the stage - no
 * separator line, no background. Same story for the footer. Keeps the
 * app feeling like one continuous canvas. */
.topbar {
  display: flex;
  /* Top-align so the right control cluster sits up with the title instead of
   * floating to the vertical middle of the tall identity block. */
  align-items: flex-start;
  justify-content: space-between;
  /* Before 2026-09-04: padding-top 10px. Was clipped on mobile because the
     rounded bezel of the phone + OS notch/inset sits on top. <body> now
     reserves safe-area-inset-top (10px minimum on desktop) and we push the
     topbar itself another 6px down so the brand glass card rounded corners
     are fully visible, not half-painted behind the screen bezel. */
  padding: 16px 16px 0 16px;
  margin-top: max(6px, calc(env(safe-area-inset-top, 0px) * 0.2));
  /* Default stacking (below the conversation panel, z 200) so the panel drawer
   * covers the controls when it's open. */
  position: relative;
  z-index: 2;
  gap: 8px;
  flex-wrap: wrap;
  flex: 0 0 auto;   /* never let the topbar shrink/stretch inside flex col */
  /* Prevent brand card edge from touching the right screen bezel on mobile:
     box-sizing so padding is taken inside the topbar width. */
  width: 100%;
  box-sizing: border-box;
}

.brand {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.01em;
  color: var(--text-dim);
  flex: 1 1 360px;
  min-width: 0;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  background:
    linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0)) ,
    var(--glass);
  border: 1px solid var(--border);
  backdrop-filter: blur(14px) saturate(135%);
  -webkit-backdrop-filter: blur(14px) saturate(135%);
  box-shadow: var(--shadow-glass), inset 0 1px 0 rgba(255,255,255,0.05);
  position: relative;
  overflow: hidden;
}
.brand::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(135deg, color-mix(in srgb, var(--brand) 45%, transparent),
                                        transparent 40%,
                                        color-mix(in srgb, var(--brand-2) 35%, transparent));
  -webkit-mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
          mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  opacity: 0.7;
}

/* Transport tag rides the wordmark as a system label, not prose — mono,
 * dimmed, the only mono in the topbar. */
.brand-tag {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.02em;
  opacity: 0.65;
  color: var(--text-faint);
}

.brand .brand-logo {
  width: 30px;
  height: 30px;
  object-fit: contain;
  flex: none;
  filter: drop-shadow(0 2px 12px color-mix(in srgb, var(--brand) 60%, transparent));
  transition: transform 0.35s cubic-bezier(.2,.7,.2,1), filter .35s ease;
}
.brand:hover .brand-logo {
  transform: translateY(-2px) rotate(-4deg) scale(1.04);
  filter: drop-shadow(0 4px 20px color-mix(in srgb, var(--brand-2) 70%, transparent));
}

.ident-title {
  font-weight: 700;
  font-size: 20px;
  letter-spacing: -0.01em;
  line-height: 1.1;
  background: var(--brand-gradient);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  filter: drop-shadow(0 1px 0 rgba(0,0,0,.25));
  display: block;
  /* Previously a stacked two-line block had a small bottom spacer; now the
   * title and avatar share the row → we don't need it any more. */
  margin-bottom: 0;
  transition: transform .3s cubic-bezier(.2,.7,.2,1), filter .3s ease;
}
.brand:hover .ident-title {
  filter: drop-shadow(0 2px 10px color-mix(in srgb, var(--brand-2) 45%, transparent));
  transform: translateY(-0.5px);
}

/* ── User avatar circle (instead of old "Utente: Nome" text) ──
 *  - perfectly round badge, matches the 42px icon-btn size in the topbar
 *  - glass + brand gradient 1px border
 *  - two uppercase initials (Alessandro Valenti → AV), bold white
 *  - hovering shows the full name via HTML title="" (still accessible), and
 *    lifts slightly with a soft brand glow like the topbar icon buttons. */
.ident-user-name {
  position: relative;
  flex: none;
  width: 40px;
  height: 40px;
  aspect-ratio: 1 / 1;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--brand) 32%, var(--border));
  color: #fff;
  background:
    radial-gradient(120% 120% at 30% 20%, rgba(255,255,255,0.18), transparent 55%),
    linear-gradient(135deg,
      color-mix(in srgb, var(--brand) 72%, transparent),
      color-mix(in srgb, var(--brand-2) 60%, transparent) 55%,
      color-mix(in srgb, var(--brand-3) 55%, transparent));
  box-shadow:
    0 0 0 1px rgba(255,255,255,0.05) inset,
    0 1px 0 rgba(255,255,255,0.08) inset,
    0 2px 10px rgba(0,0,0,0.24),
    0 0 22px -8px color-mix(in srgb, var(--brand) 58%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-family: var(--font-sans);
  font-weight: 800;
  font-size: 14px;
  line-height: 1;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-shadow: 0 1px 2px rgba(0,0,0,0.35);
  user-select: none;
  cursor: default;
  transition:
    transform .22s cubic-bezier(.2,.7,.2,1),
    box-shadow .3s ease,
    border-color .3s ease;
}
.ident-user-name::before {
  /* Subtle 1px highlight rim on the top-left quadrant. */
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(135deg, rgba(255,255,255,0.22), transparent 42%);
  opacity: .9;
  pointer-events: none;
  mix-blend-mode: screen;
}
.ident-user-name:hover {
  transform: translateY(-1.5px) scale(1.02);
  box-shadow:
    0 0 0 1px rgba(255,255,255,0.07) inset,
    0 1px 0 rgba(255,255,255,0.1) inset,
    0 6px 22px rgba(0,0,0,0.32),
    0 0 34px -6px color-mix(in srgb, var(--brand-2) 70%, transparent);
  border-color: color-mix(in srgb, var(--brand-2) 55%, var(--border));
}
/* Accessibility: keyboard-focusable style if ever wrapped in a <button> or gets
   tabIndex (future-proof, we set :focus-visible for robustness). */
.ident-user-name:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--brand-3) 45%, transparent),
    0 1px 0 rgba(255,255,255,0.08) inset,
    0 6px 22px rgba(0,0,0,0.28),
    0 0 34px -6px color-mix(in srgb, var(--brand) 60%, transparent);
}
@keyframes pulseDot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: .78; transform: scale(.9); }
}

/* Narrow viewports (typically the mobile shell iframe and phone-sized
   browser windows): the full product name pushes the topbar's right
   cluster off-screen. Keep the logo as the brand cue and drop the
   wordmark - the orb already gives enough context. */
@media (max-width: 600px) {
  /* The full identity stack is too tall for a phone topbar. Show only the
   * title + (i); the popup carries the blurb, credits and pipeline.
   * `.brand` prefix raises specificity so this beats the later base
   * `.ident-meta { display: flex }` rule regardless of source order. */
  .brand .ident-blurb,
  .brand .ident-meta {
    display: none;
  }
  .ident-title {
    font-size: 18px;
  }
  /* (i) moves into the right-hand control cluster on phones, leaving the
   * title alone on the left. */
  .brand .about-btn {
    display: none;
  }
  .about-btn-mobile {
    display: inline-flex;
  }
  /* Keep phone icons at the current compact size — the desktop bump above
   * shouldn't leak into the mobile topbar. */
  .topbar-right .icon-btn {
    width: 36px;
    height: 36px;
  }
  .topbar-right .icon-btn svg {
    width: 18px;
    height: 18px;
  }
  /* Keep the account control compact on phones: avatar-only chip, shorter pill. */
  .account-chip {
    height: 36px;
    padding: 0 6px;
  }
  .account-handle {
    display: none;
  }
  .signin-pill {
    height: 36px;
    padding: 0 12px;
  }
  /* ── Footer (narrow viewport): riduci tracking per non farlo andare
   *    a capo in malo modo; padding compatto + line-height maggiore */
  .app-footer {
    padding: 8px 14px 14px;
  }
  .app-footer::before {
    width: min(320px, 82vw);
  }
  .app-footer-text {
    letter-spacing: 0.12em;
    line-height: 1.5;
    font-size: 10px;
  }
}

.topbar-right {
  display: flex;
  align-items: center;
  gap: 10px;
}
/* Conversation history button: always-visible sibling of .topbar-right.
   Same visual styling, but never gated by the ?debug=true flag. */
.chat-btn-always {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Keep visual gap consistent whether debug buttons are shown or not. */
  margin-right: 4px;
}
.chat-btn-always .icon-btn {
  position: relative;
}
/* Safety fallback for any debug-only container: stays hidden unless the JS
   bootstrap explicitly enables it (sets data-debug-enabled="true"). */
[data-debug-only="true"]:not([data-debug-enabled="true"]) {
  display: none !important;
  visibility: hidden !important;
  opacity: 0 !important;
  pointer-events: none !important;
  width: 0 !important;
  height: 0 !important;
  margin: 0 !important;
  padding: 0 !important;
  overflow: hidden !important;
}

/* The HF user pill: compact avatar + handle. Rendered as a flex row so
 * the avatar stays aligned with the text baseline even when the text
 * wraps on narrow viewports. */
.hf-user {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-dim);
  padding: 4px 10px 4px 4px;
  border-radius: 999px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  line-height: 1;
}

.hf-avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  object-fit: cover;
  background: color-mix(in srgb, var(--accent) 25%, var(--bg-elev-2));
  /* Hidden until an actual URL loads (see `setHfAvatar` in main.ts).
   * Keeps the initial login flash from showing a broken image icon. */
  opacity: 0;
  transition: opacity 0.25s ease;
  flex: none;
}
.hf-avatar.loaded {
  opacity: 1;
}

.hf-user-name {
  white-space: nowrap;
}

/* ─── Transport pill ──────────────────────────────────────────────
 * Surface the actual network path WebRTC picked for the robot peer
 * connection (LAN, direct-through-NAT, or TURN-relayed). Lets the
 * user spot at a glance when audio is going through the internet
 * instead of staying on-prem.
 */
.transport-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  padding: 5px 10px;
  border-radius: 999px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--text-dim);
  user-select: none;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.transport-pill .transport-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 25%, transparent);
  flex: none;
}

/* Bitrate readout sits after the kind label. Dimmer + monospace so the
 * kind (LAN / Direct / Relayed) stays the primary info and the numbers
 * don't wiggle the layout as digits change. */
.transport-pill .transport-bitrate {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  color: color-mix(in srgb, currentColor 75%, var(--text-dim));
  opacity: 0.85;
  padding-left: 6px;
  border-left: 1px solid color-mix(in srgb, currentColor 25%, transparent);
}
.transport-pill .transport-bitrate:empty {
  display: none;
}

.transport-pill.transport-checking {
  color: var(--text-dim);
}
.transport-pill.transport-checking .transport-dot {
  animation: transport-blink 1.2s ease-in-out infinite;
}

.transport-pill.transport-lan {
  color: #4ade80;
  border-color: color-mix(in srgb, #4ade80 35%, var(--border));
  background: color-mix(in srgb, #4ade80 10%, var(--bg-elev));
}

.transport-pill.transport-direct {
  color: #60a5fa;
  border-color: color-mix(in srgb, #60a5fa 35%, var(--border));
  background: color-mix(in srgb, #60a5fa 10%, var(--bg-elev));
}

.transport-pill.transport-relay {
  color: #fbbf24;
  border-color: color-mix(in srgb, #fbbf24 35%, var(--border));
  background: color-mix(in srgb, #fbbf24 10%, var(--bg-elev));
}

@keyframes transport-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

.icon-btn {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--text-dim);
  width: 36px;
  height: 36px;
  border-radius: var(--radius-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  touch-action: manipulation;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.icon-btn:hover {
  background: var(--bg-elev-2);
  color: var(--text);
  border-color: var(--border-strong);
}
/* Topbar control cluster reads a touch bigger on desktop. Phones keep the
 * compact 36px (reset in the mobile media query). */
.topbar-right .icon-btn {
  width: 40px;
  height: 40px;
}
.topbar-right .icon-btn svg {
  width: 20px;
  height: 20px;
}

/* ─── Account (HF login chip + sign-in pill + popover) ─────────────────────── */
.account {
  position: relative;
  display: inline-flex;
  align-items: center;
}
/* Signed-out: a compact pill that reads as the primary affordance. */
.signin-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 40px;
  padding: 0 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--bg-elev);
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  transition: background 0.15s, border-color 0.15s;
}
.signin-pill svg {
  width: 16px;
  height: 16px;
  flex: none;
}
.signin-pill:hover {
  background: var(--bg-elev-2);
  border-color: var(--text);
}
/* Narrow viewports: collapse the pill to an icon-only square. */
@media (max-width: 800px) {
  .signin-pill {
    gap: 0;
    width: 40px;
    padding: 0;
    justify-content: center;
  }
  .signin-pill span {
    display: none;
  }
}
/* Signed-in: avatar + handle chip. */
.account-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 10px 0 6px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--text-dim);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.account-chip:hover {
  background: var(--bg-elev-2);
  color: var(--text);
  border-color: var(--border-strong);
}
.account-avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}
.account-avatar-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-elev-2);
  color: var(--text);
  font-size: 12px;
  font-weight: 700;
}
.account-handle {
  font-size: 13px;
  font-weight: 600;
  max-width: 12ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.account-pro {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 5px;
  border-radius: 5px;
  background: var(--text);
  color: var(--bg);
}
/* Org "Team" badge: same pill as PRO, accent-coloured so it reads as
 * unlimited without claiming a paid PRO subscription. */
.account-team {
  background: var(--accent);
  color: var(--bg);
}
.account-pop {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 200px;
  padding: 6px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-strong);
  background: var(--bg-elev);
  box-shadow: var(--shadow-soft);
  z-index: 50;
}
.account-pop[hidden] {
  display: none;
}
.account-pop-row {
  padding: 8px 10px;
}
.account-pop-name {
  font-weight: 600;
  font-size: 13px;
}
.account-pop-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-top: 0;
  font-size: 12px;
  color: var(--text-dim);
}
.account-tier {
  font-weight: 600;
  color: var(--text);
}
.account-pop-link {
  display: block;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: 13px;
  text-decoration: none;
  transition: background 0.15s, color 0.15s;
}
.account-pop-link:hover {
  background: var(--bg-elev-2);
  color: var(--text);
}
.account-signout {
  border-top: 1px solid var(--border);
  margin-top: 4px;
  padding-top: 10px;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}

/* ─── Daily-limit modal ────────────────────────────────────────────────────── */
.limit-modal {
  width: min(400px, 92vw);
}
.limit-card {
  position: relative;
  align-items: center;
  text-align: center;
  gap: 14px;
  padding: 34px 28px 26px;
}
.limit-close {
  position: absolute;
  top: 12px;
  right: 12px;
}
/* HF smiling face (brand yellow) on a neutral badge — friendly, not a stop sign.
 * The logo is the single pop of colour, so the badge itself stays quiet. */
.limit-badge {
  width: 66px;
  height: 66px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  margin: 2px auto 2px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border-strong);
  box-shadow: 0 0 0 6px rgba(255, 210, 30, 0.06);
}
.limit-badge .hf-logo {
  width: 46px;
  height: auto;
}
.limit-title {
  margin: 0;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 0;
}
.limit-msg {
  color: var(--text-dim);
  font-size: 14px;
  line-height: 1.55;
  margin: 0;
  max-width: 30ch;
}
.limit-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 4px;
}
.limit-cta .hf-logo {
  width: 20px;
  height: auto;
  flex: none;
}
.limit-note {
  margin: 0;
  font-size: 12px;
  color: var(--text-faint);
}
#limit-cta[hidden] {
  display: none;
}

/* ─── Stage ───────────────────────────────────────────────────────────── */

.stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Pack the orb + caption up top and make stage MIN-content (no longer eat
     all remaining vertical space) — so the inline chat panel slides up and
     sits immediately under "TAP OR SAY HOLA VOCERO" with a thin breathing
     gap instead of a huge empty area. */
  justify-content: flex-start;
  padding: clamp(10px, 2vw, 20px) clamp(12px, 3vw, 24px) clamp(6px, 1.2vh, 14px);
  gap: clamp(12px, 2.5vw, 20px);
  padding-top: clamp(14px, 3vh, 36px);
  /* Shrink to content. The chat panel below is what grows to fill slack. */
  flex: 0 1 auto;
  min-height: 0;
  position: relative;
  z-index: 1;
}
/* Subtle radial "light pedestal" behind the orb — makes the whole cluster
   feel anchored instead of floating on the dark grid. */
.stage::before {
  content: "";
  position: absolute;
  top: 46%;
  left: 50%;
  width: clamp(260px, 58vw, 620px);
  height: clamp(260px, 58vw, 620px);
  transform: translate(-50%, -50%);
  background: radial-gradient(circle,
    color-mix(in srgb, var(--brand) 22%, transparent) 0%,
    color-mix(in srgb, var(--brand-2) 12%, transparent) 45%,
    transparent 75%);
  filter: blur(12px);
  opacity: .65;
  pointer-events: none;
  border-radius: 999px;
  animation: pedestalBreathe 8s ease-in-out infinite alternate;
}
@keyframes pedestalBreathe {
  0%   { transform: translate(-50%, -50%) scale(1);    opacity: .55; }
  50%  { transform: translate(-50%, -50%) scale(1.06); opacity: .75; }
  100% { transform: translate(-50%, -50%) scale(.98);  opacity: .5;  }
}

/* ─── Central circle ──────────────────────────────────────────────────── */

/* Wraps the orb and its two side controls so they stay aligned on one row. */
.orb-wrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(18px, 3vw, 32px);
}

.circle {
  position: relative;
  /* Cap by smallest-dim viewport units — svw/svh = smallest-viewport w/h so the
     URL bar showing/hiding on mobile never makes the orb change size suddenly.
     Since ~30% of the viewport is already header, we use 42svh as max height. */
  width: clamp(120px, min(38svw, 42svh), 300px);
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
  outline: none;
  display: grid;
  place-items: center;
  color: var(--glow, var(--accent));
  transition: transform 0.18s ease, filter 0.18s ease;
  -webkit-tap-highlight-color: transparent;
  /* Treat taps as immediate clicks (no 300ms delay / double-tap-zoom). */
  touch-action: manipulation;
}
.circle::before {
  content: "";
  position: absolute;
  inset: -8%;
  border-radius: 50%;
  background: conic-gradient(from 0deg,
    color-mix(in srgb, var(--brand) 65%, transparent) 0%,
    color-mix(in srgb, var(--brand-2) 55%, transparent) 35%,
    color-mix(in srgb, var(--brand-3) 55%, transparent) 65%,
    color-mix(in srgb, var(--brand) 65%, transparent) 100%);
  filter: blur(14px);
  opacity: 0.55;
  z-index: 0;
  pointer-events: none;
  animation: orbConicSpin 18s linear infinite;
}
@keyframes orbConicSpin {
  to { transform: rotate(360deg); }
}
.circle:hover {
  filter: brightness(1.08);
}
.circle:hover::before { opacity: .8; }
.circle:active {
  transform: scale(0.97);
  filter: brightness(0.92);
}
.circle:focus-visible .circle-core {
  outline: 2px solid var(--glow, var(--accent));
  outline-offset: 6px;
}
.circle[disabled] {
  cursor: default;
  opacity: 0.75;
}

.circle-glow {
  position: absolute;
  inset: -8%;
  border-radius: 50%;
  background: radial-gradient(circle at center, var(--glow, var(--accent)) 0%, transparent 60%);
  filter: blur(34px);
  opacity: 0.55;
  transform: scale(1);
  transition: opacity 0.25s, background 0.25s, transform 1.4s ease-in-out;
  pointer-events: none;
  z-index: 0;
}

/* Idle-only slow "breathe" pulse for the outer glow — keeps the orb alive
   even when the user is doing nothing. */
.circle.state-idle .circle-glow,
.circle.state-idle::before {
  animation:
    orbConicSpin 22s linear infinite,
    orbBreatheGlow 5s ease-in-out infinite alternate;
}
@keyframes orbBreatheGlow {
  0%   { opacity: .42; transform: scale(.985); filter: blur(30px); }
  50%  { opacity: .72; transform: scale(1.04); filter: blur(40px); }
  100% { opacity: .48; transform: scale(1);    filter: blur(32px); }
}

/* Two nested circular rings: the inner tracks the core edge, the outer
 * expands / fades to convey "audio radiating out" during speaking. */
.circle-ring,
.circle-ring-outer {
  position: absolute;
  top: 50%;
  left: 50%;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  transition: opacity 0.4s ease, border-color 0.4s ease, transform 0.3s ease;
  z-index: 1;
}
.circle-ring {
  width: 86%;
  height: 86%;
  border: 1.5px solid transparent;
  background:
    linear-gradient(var(--bg, #07080d), var(--bg, #07080d)) padding-box,
    conic-gradient(from 0deg,
      color-mix(in srgb, var(--brand) 80%, transparent),
      color-mix(in srgb, var(--brand-2) 70%, transparent) 40%,
      color-mix(in srgb, var(--brand-3) 75%, transparent) 70%,
      color-mix(in srgb, var(--brand) 80%, transparent)) border-box;
  border-color: transparent;
  mask:
    linear-gradient(#000, #000) content-box,
    linear-gradient(#000, #000);
  -webkit-mask:
    linear-gradient(#000, #000) content-box,
    linear-gradient(#000, #000);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  box-shadow:
    0 0 20px -6px color-mix(in srgb, var(--brand) 55%, transparent);
  opacity: 0.75;
}
.circle-ring-outer {
  width: 98%;
  height: 98%;
  border: 1px dashed color-mix(in srgb, var(--glow, var(--accent)) 30%, transparent);
  opacity: 0.55;
}
.circle.state-idle .circle-ring-outer {
  animation: outerRingPulse 6s ease-in-out infinite;
}
@keyframes outerRingPulse {
  0%, 100% { opacity: .4;  transform: translate(-50%,-50%) scale(1); }
  50%      { opacity: .65; transform: translate(-50%,-50%) scale(1.02); }
}

.circle-core {
  position: relative;
  width: 72%;
  height: 72%;
  border-radius: 50%;
  background:
    radial-gradient(circle at 30% 22%,
      rgba(255,255,255,0.18) 0%,
      rgba(255,255,255,0.04) 20%,
      transparent 45%),
    radial-gradient(circle at 35% 28%,
      color-mix(in srgb, var(--glow, var(--accent)) 32%, transparent),
      color-mix(in srgb, var(--glow, var(--accent)) 12%, transparent) 55%,
      color-mix(in srgb, var(--glow, var(--accent)) 5%, transparent));
  border: 1.5px solid color-mix(in srgb, var(--glow, var(--accent)) 48%, transparent);
  display: grid;
  place-items: center;
  box-shadow:
    0 0 44px -8px color-mix(in srgb, var(--glow, var(--accent)) 42%, transparent),
    inset 0 0 34px color-mix(in srgb, var(--glow, var(--accent)) 22%, transparent),
    inset 0 1px 0 rgba(255,255,255,0.12);
  transition: background 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease, transform 0.4s ease;
  z-index: 2;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.circle.state-idle .circle-core {
  animation: coreBreathe 5s ease-in-out infinite alternate;
}
@keyframes coreBreathe {
  0%   { transform: scale(0.992); box-shadow: 0 0 36px -8px color-mix(in srgb, var(--brand) 40%, transparent), inset 0 0 26px color-mix(in srgb, var(--brand) 16%, transparent); }
  50%  { transform: scale(1.01);  box-shadow: 0 0 54px -4px color-mix(in srgb, var(--brand-2) 48%, transparent), inset 0 0 38px color-mix(in srgb, var(--brand-2) 22%, transparent); }
  100% { transform: scale(1);     box-shadow: 0 0 44px -6px color-mix(in srgb, var(--brand-3) 42%, transparent), inset 0 0 32px color-mix(in srgb, var(--brand-3) 20%, transparent); }
}

/* Indicator slot: a single SVG / spinner / bar group is visible at a time,
 * driven by the state class on `.circle`. */
.circle-indicator {
  position: relative;
  width: 44%;
  height: 44%;
  display: grid;
  place-items: center;
  color: var(--glow, var(--accent));
}
.circle-indicator > .ind {
  grid-area: 1 / 1;
  opacity: 0;
  transform: scale(0.85);
  transition: opacity 0.25s ease, transform 0.25s ease;
  pointer-events: none;
}
.circle-indicator > svg.ind {
  width: 60%;
  height: 60%;
}

/* Spinner: a rotating ring gap, CSS-only.
 *
 * Note: the base `.circle-indicator > .ind` rule forces `transform:
 * scale(.85)` / `scale(1)` on every indicator to drive the show/hide
 * transition. If we only rotate here, the browser has to interpolate
 * between `scale(1)` and `rotate(360deg)` (two different transform
 * functions), which produces a broken, barely-moving animation. So we
 * include the scale explicitly in the keyframes and bump specificity
 * with `!important` so the spinner always wins over the state rule. */
.ind-spinner {
  width: 48%;
  height: 48%;
  border: 3px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  opacity: 0;
  animation: ind-spin 0.9s linear infinite;
}

/* Thinking dots: 3 soft pulsing dots while the model is composing a
 * response. Apple-style cadence: each dot scales up and brightens in
 * turn, staggered by ~160 ms. Per-dot animation is on the child, so
 * the parent's scale(.85 → 1) show/hide transform composes cleanly. */
.ind-thinking {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 60%;
  height: 60%;
}
.ind-thinking .dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.3;
  animation: thinking-dot 1.25s ease-in-out infinite;
}
.ind-thinking .dot:nth-child(1) { animation-delay: 0s; }
.ind-thinking .dot:nth-child(2) { animation-delay: 0.16s; }
.ind-thinking .dot:nth-child(3) { animation-delay: 0.32s; }

/* Bars: 5 vertical pills driven by --bar0..--bar4 CSS vars. */
.ind-bars {
  display: flex;
  align-items: center;
  gap: 5px;
  height: 42%;
}
.ind-bars .bar {
  width: 4px;
  min-height: 4px;
  border-radius: 3px;
  background: currentColor;
  opacity: 0.7;
  --h: var(--bar0, 0);
  height: calc(4px + var(--h) * 36px);
  transition: height 0.08s ease-out, opacity 0.08s ease-out;
}
.ind-bars .bar:nth-child(1) { --h: var(--bar0); }
.ind-bars .bar:nth-child(2) { --h: var(--bar1); }
.ind-bars .bar:nth-child(3) { --h: var(--bar2); }
.ind-bars .bar:nth-child(4) { --h: var(--bar3); }
.ind-bars .bar:nth-child(5) { --h: var(--bar4); }
.ind-bars .bar {
  opacity: calc(0.55 + 0.45 * var(--h));
}

/* Active indicator per state.
 *
 * Note: `ai-speaking` deliberately has NO indicator here. The orb
 * itself becomes the indicator by pulsing on Reachy's voice (see the
 * `--ai-audio-level` rules further down), which is a lot clearer and
 * less confusing than reusing the mic-bars (which viewers would read
 * as "you are speaking"). */
.state-signed-out .ind-connect,
.state-authenticated .ind-mic,
.state-ready .ind-mic,
.state-connecting .ind-spinner,
.state-connected .ind-spinner,
.state-auto-selecting .ind-spinner,
.state-starting .ind-spinner,
.state-queued .ind-spinner,
.state-processing .ind-thinking,
.state-listening .ind-bars,
.state-user-speaking .ind-bars,
.state-ai-speaking .ind-voice,
.state-error .ind-error {
  opacity: 1;
  transform: scale(1);
}

/* AI speaking indicator: speaker + two sound waves.
 *
 * Each wave pulses outward (opacity + stroke grow) with a quarter-
 * beat offset so it reads as sound radiating out. Kept as a pure
 * CSS animation so the icon is always visually alive even between
 * syllables when --ai-audio-level momentarily dips. */
.ind-voice .wave {
  transform-origin: 50% 50%;
  animation: voice-wave 1.35s ease-out infinite;
}
.ind-voice .wave-1 { animation-delay: 0s; }
.ind-voice .wave-2 { animation-delay: 0.35s; }

/* Idle indicators: a chain-link "connect" glyph for the signed-out
 * step (invites the user to authenticate with HF) and a microphone
 * once a session is ready. Both picked up by the generic
 * `.circle-indicator > svg.ind` sizing rule (60% × 60% of the slot)
 * and rely on per-state opacity transitions for show / hide. */
.ind-connect,
.ind-mic {
  color: color-mix(in srgb, var(--glow, var(--accent)) 85%, white);
}

/* ─── Caption below the circle ────────────────────────────────────────── */

/* The caption under the orb is meant to whisper, not shout: micro-label
 * vibe, uppercase, letter-spaced, muted. Only appears for actionable /
 * transitional states (see STATE_VIEWS). During a live conversation the
 * orb alone carries the state so we collapse this row entirely. */
/* Orbit-caption keeps the "machine voice" position — mono, uppercase,
 * letter-spaced. For idle state we tint it with the brand gradient so it
 * reads as a CTA rather than dead metadata. */
.circle-caption {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--text-faint);
  min-height: 1.3em;
  text-align: center;
  opacity: 0.78;
  transition: opacity 0.25s ease, color 0.25s ease, transform 0.25s ease,
    min-height 0.25s ease, background 0.3s ease, filter 0.3s ease;
  line-height: 1.2;
}
.circle-caption:empty::before { content: "\00a0"; }
/* Idle CTA styling: brand gradient + bob. We use two ways to ensure
   coverage: the modern :has() selector, plus a .idle-cta class toggled
   from JS as a fallback for older embedded WebViews. */
.stage:has(.circle.state-idle) .circle-caption,
.circle-caption.idle-cta {
  background: var(--brand-gradient);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  opacity: 1;
  filter: drop-shadow(0 2px 10px color-mix(in srgb, var(--brand) 40%, transparent));
  animation: idleCaptionBob 3.6s ease-in-out infinite alternate;
}
@keyframes idleCaptionBob {
  0%, 100% { transform: translateY(0);    letter-spacing: 0.24em; }
  50%      { transform: translateY(-1px); letter-spacing: 0.26em; }
}
.circle-caption.empty {
  opacity: 0;
  min-height: 0;
  transform: translateY(-4px);
  pointer-events: none;
}
.circle-caption.muted {
  color: var(--text-faint);
  opacity: 0.65;
}
.circle-caption.error {
  color: var(--error);
  opacity: 1;
  letter-spacing: 0.08em;
  background: none;
  -webkit-text-fill-color: var(--error);
          text-fill-color: var(--error);
  filter: drop-shadow(0 0 8px color-mix(in srgb, var(--error) 55%, transparent));
}

/* Warm, human line under the mono caption — sentence case, only while queued.
 * The caption keeps the terse position; this reassures. */
.circle-subcaption {
  margin: 8px 0 0;
  max-width: 32ch;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-dim);
  text-align: center;
  text-wrap: balance;
  transition: opacity 0.25s ease;
}
.circle-subcaption[hidden] { display: none; }

/* Queue actions sit under the caption: "Join now" (primary, only when it's your
 * turn) stacked above the quiet "Leave queue" escape hatch. */
.queue-actions {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.queue-actions[hidden] { display: none; }

/* "Join now": the one call to action in the queue flow, so it reads as inviting
 * (filled accent) while everything around it stays quiet. */
.join-queue-btn {
  padding: 10px 26px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #0b0b10;
  background: var(--accent);
  border: none;
  border-radius: 999px;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  transition: transform 0.12s ease, filter 0.2s ease;
}
.join-queue-btn:hover { filter: brightness(1.08); }
.join-queue-btn:active { transform: scale(0.97); }
.join-queue-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.join-queue-btn[hidden] { display: none; }

/* "Leave queue": a quiet outlined pill, understated so it reads as an escape
 * hatch, not a CTA. */
.leave-queue-btn {
  padding: 7px 16px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--text-faint) 35%, transparent);
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}
.leave-queue-btn:hover {
  color: var(--text);
  border-color: color-mix(in srgb, var(--text-faint) 60%, transparent);
  background: color-mix(in srgb, var(--text-faint) 8%, transparent);
}
.leave-queue-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.leave-queue-btn[hidden] { display: none; }

/* ─── Tool-call toaster ───────────────────────────────────────────────── */

/* A small, non-interactive pill that appears below the circle when the
 * model invokes a tool (move_head, play_move). Sits just under the
 * state caption, collapses to zero height when no toast is active. */
.tool-toast {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  background: color-mix(in srgb, var(--bg-elev-2) 78%, transparent);
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 1;
  white-space: nowrap;
  max-width: 80vw;
  overflow: hidden;
  text-overflow: ellipsis;

  opacity: 0;
  transform: translateY(-4px) scale(0.96);
  pointer-events: none;
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.tool-toast.visible {
  opacity: 1;
  transform: translateY(0) scale(1);
}
.tool-toast-icon {
  width: 14px;
  height: 14px;
  flex: none;
  color: color-mix(in srgb, var(--voice-tool) 80%, white);
  animation: tool-toast-spin 3.2s linear infinite;
  animation-play-state: paused;
}
.tool-toast.visible .tool-toast-icon {
  animation-play-state: running;
}
.tool-toast-text {
  display: inline-block;
  overflow: hidden;
  text-overflow: ellipsis;
}
@keyframes tool-toast-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ─── Side controls (mic / stop) ──────────────────────────────────────── */

/* Mic button + its radial noise-gate arc. The wrapper centres the button; the
   arc SVG is an absolute overlay larger than the button, revealed with the
   live session (it has no layout footprint, so the idle collapse is unaffected). */
.mic-gate {
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.mic-gate-arc {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 80px;
  height: 80px;
  transform: translate(-50%, -50%);
  pointer-events: none; /* only the hit-path below catches drags */
  opacity: 0;
  transition: opacity 0.25s ease;
}
.orb-wrap.live .mic-gate-arc { opacity: 1; }
.mga-track {
  stroke: rgba(255, 255, 255, 0.1); /* hairline; recedes until needed */
  stroke-width: 1.5;
  stroke-linecap: round;
}
.mga-fill {
  stroke: var(--accent-2);
  stroke-width: 2;
  stroke-linecap: round;
  opacity: 0.85;
  transition: stroke-dashoffset 0.06s linear;
}
/* Threshold setpoint: a bead riding the ring. White with a thin dark outline so
   it stays legible over the moving fill; it recolors to cyan with a soft glow
   the moment the live level crosses it (the gate opening). */
.mga-handle {
  fill: var(--text);
  stroke: none;
  transition: fill 0.2s ease, filter 0.2s ease;
}
.mic-gate.gate-open .mga-handle {
  fill: var(--accent-2);
  filter: drop-shadow(0 0 3px var(--accent-2));
}
.mga-hit {
  stroke: transparent;
  stroke-width: 16;
  pointer-events: none; /* enabled only while live (below) */
  cursor: pointer;
  touch-action: none;
}
/* Only catch drags during a live call; when idle the arc is hidden and must
   not steal clicks near the collapsed mic button / orb. */
.orb-wrap.live .mga-hit { pointer-events: stroke; }

.side-btn {
  flex: none;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  /* Brighter background so the buttons actually stand out against the
   * deep-blue stage gradient; previous var(--bg-elev) was too close to
   * the page bg to be readable. */
  background: color-mix(in srgb, var(--bg-elev-2) 80%, #2a2e3c);
  color: var(--text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  touch-action: manipulation;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
  /* Hidden by default: take up no space until the session is live. The
   * `width: 0` collapse keeps the orb centered on the idle screen. */
  opacity: 0;
  transform: scale(0.55);
  width: 0;
  padding: 0;
  pointer-events: none;
  overflow: hidden;
  transition: opacity 0.25s ease, transform 0.25s ease, width 0.25s ease,
    background 0.15s, color 0.15s, border-color 0.15s;
}
.side-btn:hover {
  background: color-mix(in srgb, var(--bg-elev-2) 60%, #323746);
  border-color: var(--text-dim);
}
.side-btn svg {
  width: 22px;
  height: 22px;
  flex: none;
}
.side-btn .mic-off { display: none; }
.side-btn.muted {
  color: #fff;
  border-color: var(--error);
  background: color-mix(in srgb, var(--error) 70%, #1a0e13);
}
.side-btn.muted .mic-on { display: none; }
.side-btn.muted .mic-off { display: block; }

/* Stop button: subtle warm tint so "end" reads as destructive. */
#stop-btn:hover {
  color: #fff;
  border-color: color-mix(in srgb, var(--error) 60%, var(--border-strong));
  background: color-mix(in srgb, var(--error) 25%, var(--bg-elev-2));
}

/* Reveal when the session is live: buttons flank the orb on a flex row. */
.orb-wrap.live .side-btn {
  opacity: 1;
  transform: scale(1);
  width: 52px;
  pointer-events: auto;
}

/* Disable every transition / animation during the first paint so the
 * orb doesn't fade-and-scale in when the page loads. `main.ts` removes
 * the class after one animation frame. */
body.booting,
body.booting *,
body.booting *::before,
body.booting *::after {
  transition: none !important;
  animation-duration: 0s !important;
  animation-delay: 0s !important;
}

/* ─── Circle animation keyframes ──────────────────────────────────────── */

/* Slow, subtle breathing for "warm idle" states. */
@keyframes breathe {
  0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.4; }
  50%      { transform: translate(-50%, -50%) scale(1.06); opacity: 0.15; }
}

/* Outer ring expanding and fading - conveys "I am producing audio". */
@keyframes ring-out {
  0%   { transform: translate(-50%, -50%) scale(1); opacity: 0.35; }
  100% { transform: translate(-50%, -50%) scale(1.18); opacity: 0; }
}

/* Soft inner scale for the core while talking. */
@keyframes core-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.04); }
}

/* Subtle glow throb used for "thinking" — dimmer than speaking. */
@keyframes thinking {
  0%, 100% { transform: scale(1); opacity: 0.7; }
  50%      { transform: scale(0.96); opacity: 0.45; }
}

/* Individual dot pulse for the 3-dot processing indicator. */
@keyframes thinking-dot {
  0%, 60%, 100% { transform: scale(0.7); opacity: 0.3; }
  30%           { transform: scale(1.15); opacity: 1; }
}

/* Sound-wave pulse: arc fades in, scales up slightly, fades out.
 * The transform-origin is the speaker's center (roughly x=12), so
 * the waves feel like they're emanating from the cone. */
@keyframes voice-wave {
  0%   { opacity: 0; transform: scale(0.7); }
  30%  { opacity: 1; transform: scale(1); }
  70%  { opacity: 0.2; transform: scale(1.12); }
  100% { opacity: 0; transform: scale(0.7); }
}

@keyframes ind-spin {
  /* Scale kept at 1 so we don't fight with the indicator's base
   * show/hide transform (see `.ind-spinner` for the rationale). */
  from { transform: scale(1) rotate(0deg); }
  to   { transform: scale(1) rotate(360deg); }
}

/* ─── State-specific colors ──────────────────────────────────────────── */

.circle.state-signed-out   { --glow: #8b7dff; }
.circle.state-authenticated,
.circle.state-ready        { --glow: #34d399; }
.circle.state-connecting,
.circle.state-connected,
.circle.state-auto-selecting,
.circle.state-starting     { --glow: #facc15; }
/* Queued: a calm slate glow, distinct from connecting's active yellow — this is
 * waiting, not working. The spinner turns slowly and the ring breathes. */
.circle.state-queued       { --glow: #94a3b8; }
.circle.state-queued .ind-spinner { animation-duration: 2.4s; }
.circle.state-queued .circle-ring { animation: breathe 2.6s ease-in-out infinite; }
/* Your turn: a slot is held for you — the orb warms to the accent and breathes a
 * little quicker, an invitation to join. */
.circle.state-your-turn    { --glow: var(--accent); }
.circle.state-your-turn .circle-ring { animation: breathe 1.6s ease-in-out infinite; }
.circle.state-listening,
.circle.state-user-speaking { --glow: var(--listening); }
.circle.state-processing   { --glow: var(--processing); }
.circle.state-ai-speaking  { --glow: var(--speaking); }
.circle.state-error        { --glow: var(--error); }

/* Idle / ready: gentle breathing of the inner ring. Kept out of the
 * `signed-out` state so the very first paint on page load stays quiet
 * (the orb now shows the Reachy head silhouette, no need to also pulse). */
.circle.state-authenticated .circle-ring,
.circle.state-ready .circle-ring {
  animation: breathe 2.4s ease-in-out infinite;
}

/* Connecting flows: subtle glow throb so the orb feels thoughtful
 * while the session is being negotiated. Kept off `processing` on
 * purpose - the 3 thinking dots already pulse, and layering another
 * throb on top competes with them for attention. */
.circle.state-connecting .circle-core,
.circle.state-connected .circle-core,
.circle.state-auto-selecting .circle-core,
.circle.state-starting .circle-core {
  animation: thinking 1.4s ease-in-out infinite;
}

/* User is speaking: the mic RMS drives scale + opacity via --audio-level. */
.circle.state-user-speaking .circle-ring {
  animation: none;
  opacity: calc(0.25 + 0.55 * var(--audio-level));
  transform: translate(-50%, -50%) scale(calc(1 + 0.08 * var(--audio-level)));
  transition: transform 0.08s linear, opacity 0.08s linear;
}
.circle.state-user-speaking .circle-ring-outer {
  opacity: calc(0.1 + 0.35 * var(--audio-level));
  transform: translate(-50%, -50%) scale(calc(1 + 0.12 * var(--audio-level)));
  transition: transform 0.08s linear, opacity 0.08s linear;
}
.circle.state-listening .circle-ring {
  animation: breathe 2s ease-in-out infinite;
}

/* Assistant is speaking: the whole orb breathes in sync with Reachy's
 * voice instead of running a fixed timer. `--ai-audio-level` (0-1) is
 * updated at display rate by AiLevelMonitor from the OpenAI output
 * track, so every syllable visibly moves the core + outer ring. This
 * reads instantly as "the orb is the voice" and completely avoids the
 * ambiguity of bars-vs-mic the user flagged. */
.circle.state-ai-speaking .circle-core {
  animation: none;
  transform: scale(calc(1 + 0.09 * var(--ai-audio-level, 0)));
  transition: transform 0.08s linear;
}
.circle.state-ai-speaking .circle-ring {
  animation: none;
  opacity: calc(0.3 + 0.5 * var(--ai-audio-level, 0));
  transform: translate(-50%, -50%) scale(calc(1 + 0.05 * var(--ai-audio-level, 0)));
  transition: transform 0.08s linear, opacity 0.08s linear;
}
.circle.state-ai-speaking .circle-ring-outer {
  animation: none;
  opacity: calc(0.15 + 0.55 * var(--ai-audio-level, 0));
  transform: translate(-50%, -50%) scale(calc(1 + 0.18 * var(--ai-audio-level, 0)));
  transition: transform 0.08s linear, opacity 0.08s linear;
}
.circle.state-ai-speaking .circle-glow {
  opacity: calc(0.35 + 0.45 * var(--ai-audio-level, 0));
  transition: opacity 0.08s linear;
}

/* ─── Robot picker ────────────────────────────────────────────────────── */

.robot-picker {
  width: min(420px, 92vw);
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 14px 16px;
  box-shadow: var(--shadow-soft);
}

.picker-title {
  margin: 0 0 10px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.robot-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.robot-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elev-2);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.robot-card:hover {
  border-color: var(--border-strong);
}
.robot-card.selected {
  border-color: var(--border-strong);
  background: var(--bg-elev);
}

.robot-card .name {
  font-weight: 600;
  font-size: 14px;
}
.robot-card .id {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-faint);
}

.robot-empty {
  padding: 14px;
  text-align: center;
  color: var(--text-faint);
  font-size: 13px;
}

/* ─── Footer ──────────────────────────────────────────────────────────── */

.footer {
  padding: 12px 28px 18px;
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--text-faint);
  display: flex;
  justify-content: center;
  opacity: 0.55;
  transition: opacity 0.25s ease;
}
.footer:hover {
  opacity: 0.9;
}
.footer a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dotted currentColor;
}
.footer a:hover {
  color: var(--text-dim);
}
/* While the webcam preview sits bottom-left, push the credit to the
 * bottom-right so the two don't collide. */
body.cam-on .footer {
  justify-content: flex-end;
}

/* ─── Modal ───────────────────────────────────────────────────────────── */

.modal {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0;
  background: var(--bg-elev);
  color: var(--text);
  width: min(440px, 92vw);
  box-shadow: var(--shadow-soft);
}
.modal::backdrop {
  background: rgba(8, 9, 13, 0.65);
  backdrop-filter: blur(4px);
}

.modal-content {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 22px 24px 20px;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 4px;
}
.modal-header h2 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.field[hidden] { display: none; }
.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-dim);
}
.field > span {
  color: var(--text);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.field input,
.field select,
.field textarea {
  font-family: inherit;
  font-size: 14px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  outline: none;
  transition: border-color 0.15s;
  resize: vertical;
}
.field textarea {
  min-height: 84px;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--text-dim);
}
/* Deploy-pinned server URL: shown for transparency, not editable. */
.field input[readonly] {
  color: var(--text-dim);
  border-style: dashed;
  cursor: default;
}
.field input[readonly]:focus {
  border-color: var(--border);
}
.field small {
  color: var(--text-faint);
  font-size: 12px;
  line-height: 1.4;
}
.field small.error { color: var(--error); }
.field small code {
  background: var(--bg);
  padding: 1px 5px;
  border-radius: 4px;
  border: 1px solid var(--border);
}

/* Noise gate: a header with a live value, a level meter, and a range slider
   that shares the meter's horizontal (dB) axis. */
.field-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}
.field-value {
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-dim);
}
/* The slider and the level meter are one widget: the range input is overlaid
   on the meter track (its native track is transparent), so the live-level fill
   shows through behind the thumb and the thumb itself is the threshold. */
.gate {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.gate-track {
  position: relative;
  height: 14px;
  display: flex;
  align-items: center;
}
.gate-track::before {
  /* the visible meter track */
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 8px;
  border-radius: 999px;
  background: var(--bg);
  border: 1px solid var(--border);
}
.gate-meter-fill {
  position: absolute;
  left: 1px;
  top: 50%;
  transform: translateY(-50%);
  height: 6px;
  width: 0;
  border-radius: 999px;
  background: var(--accent-2);
  transition: width 0.06s linear;
  pointer-events: none;
}
.gate-ends {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--text-faint);
  letter-spacing: 0;
  text-transform: none;
}
/* The range input sits transparently on top of the meter track. */
.gate-track input[type="range"] {
  position: relative;
  z-index: 1;
  width: 100%;
  margin: 0;
  -webkit-appearance: none;
  appearance: none;
  padding: 0;
  border: none;
  background: transparent;
  height: 14px;
  cursor: pointer;
}
.gate-track input[type="range"]::-webkit-slider-runnable-track {
  height: 14px;
  background: transparent;
}
.gate-track input[type="range"]::-moz-range-track {
  height: 14px;
  background: transparent;
}
.gate-track input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 6px;
  height: 18px;
  border-radius: 3px;
  background: var(--text);
  border: 2px solid var(--bg-elev);
  box-shadow: 0 0 0 1px var(--border-strong);
}
.gate-track input[type="range"]::-moz-range-thumb {
  width: 6px;
  height: 18px;
  border-radius: 3px;
  background: var(--text);
  border: 2px solid var(--bg-elev);
  box-shadow: 0 0 0 1px var(--border-strong);
}
.gate-track input[type="range"]:focus { border: none; }

.modal-footer {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding-top: 6px;
}

.btn {
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--bg-elev-2);
  color: var(--text);
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, transform 0.05s;
}
.btn:hover {
  border-color: var(--text);
}
.btn:active {
  transform: translateY(1px);
}
.btn.primary {
  background: var(--text);
  border-color: var(--text);
  color: var(--bg);
}
.btn.primary:hover {
  background: #fff;
  border-color: #fff;
}
.btn.ghost {
  background: transparent;
  border-color: var(--border);
  color: var(--text-dim);
}
.btn.wide {
  width: 100%;
  padding: 12px 16px;
}
.btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
}
.btn[disabled]:hover {
  border-color: var(--border-strong);
}

/* ─── Settings tabs ───────────────────────────────────────────────────── */

.tabs {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.tab {
  flex: 1;
  padding: 8px 12px;
  border: none;
  background: transparent;
  color: var(--text-dim);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  border-radius: calc(var(--radius-sm) - 3px);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.tab:hover {
  color: var(--text);
}
.tab.active {
  background: var(--bg-elev-2);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}

.tab-panels {
  display: flex;
  flex-direction: column;
}
.tab-panel {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.tab-panel[hidden] {
  display: none;
}

/* Horizontal layout for Voice + Model. */
.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

/* ─── Chat button + badge ────────────────────────────────────────────── */

#chat-btn {
  position: relative;
}

.chat-badge {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text);
  border: 1.5px solid var(--bg-elev);
  opacity: 0;
  transform: scale(0);
  transition: opacity 0.2s ease, transform 0.2s ease;
  pointer-events: none;
}
.chat-badge.visible {
  opacity: 1;
  transform: scale(1);
}

/* ─── Ephemeral bubble stack ─────────────────────────────────────────── */

.bubble-stack {
  position: fixed;
  /* Clear the topbar control row so the first bubble doesn't sit at the same
   * height as the buttons. */
  top: 96px;
  right: 28px;
  width: min(300px, calc(100vw - 56px));
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
  z-index: 100;
}

.bubble {
  --bubble-dx: 10px;
  pointer-events: auto;
  padding: 10px 14px;
  border-radius: var(--radius-md);
  font-size: 13px;
  line-height: 1.5;
  border: 1px solid var(--border);
  background: var(--bg-elev);
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.32);
  max-width: 100%;
  word-break: break-word;
  opacity: 0;
  transform: translateX(var(--bubble-dx));
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.bubble.in {
  opacity: 1;
  transform: translateX(0);
}
.bubble.out {
  opacity: 0;
  transform: translateX(var(--bubble-dx));
  pointer-events: none;
  transition: opacity 0.3s ease, transform 0.3s ease;
}

/* Surfaces stay neutral; the side they sit on plus the mono role label
 * carry the distinction. No tinted fills — color is the orb's job. */
.bubble.user {
  --bubble-dx: -10px;
  align-self: flex-start;
}
.bubble.assistant {
  --bubble-dx: 10px;
  align-self: flex-end;
}
.bubble.tool {
  --bubble-dx: -10px;
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 12px;
}
.bubble.tool .bubble-tool-icon {
  width: 13px;
  height: 13px;
  flex: none;
  color: var(--voice-tool);
}

.bubble-role {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 4px;
  opacity: 0.7;
}
.bubble.user .bubble-role { color: var(--voice-user); }
.bubble.assistant .bubble-role { color: var(--voice-assistant); }

/* ─── Conversation history panel (NOW INLINE in #app, below stage, above footer) ─ */
/* Old rule: position:fixed right-side overlay.
   New rule: direct flex-col child of #app, 100% width, grows if content is
   taller up to a max of ~40% viewport height, scrolls the history inside.
   Keeps all the "ChatView" internals intact (bubble history renderer, etc). */
.chat-panel {
  position: static;
  inset: auto;
  z-index: 1;
  pointer-events: auto;
  width: 100%;
  /* Stage now stays compact above us; chat-panel absorbs any remaining vertical
     space so it sits IMMEDIATELY below the orb caption and extends down to the
     sticky footer — while keeping a sensible max so it never squishes stage. */
  flex: 1 1 auto;
  min-height: 160px;
  max-height: 65svh;
  max-height: 65dvh;
  display: flex;
  flex-direction: column;
  /* Horizontal margins match the 920px inner max-width line-up + side rails.
     BOTTOM PADDING (critical 2026-09-04 bump): was clamp(40px, 9vh, 96px).
     The last assistant bubble line was STILL clipped against the sticky
     footer because (a) the footer is rendered outside #app as a floating
     overlay so it covers 32-56px of the chat area regardless of history
     internal height and (b) when a bubble wraps to >5 lines on narrow mobile
     its rendered height is larger than the 96px breathing. New minimum
     60/160 (12vh = 160 at ~1333 svh but max 160px) adds another 64px. */
  padding: clamp(8px, 1.4vh, 14px) clamp(16px, 3vw, 24px) clamp(60px, 12vh, 160px);
  box-sizing: border-box;
  overflow: hidden;
}
/* Inner wrapper: SEAMLESS (no glass card / no border / no shadow) per user
   request 2026-09-04 — the chat area visually melts into the aurora page
   background, no chrome box or visible frame around the history. We keep the
   layout sizing + z-ordering but neutralise all decoration. */
.chat-panel-inner {
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0 auto;
  max-width: 920px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  /* Decoration removed — seamless transparent on aurora bg */
  background: transparent;
  border: 0 solid transparent;
  border-radius: 0;
  overflow: hidden;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.chat-panel-inner::before {
  /* Top aurora fade removed (would add an artificial tint vs the page bg). */
  content: none;
}
.chat-panel-inner > * { position: relative; z-index: 1; }

/* The inline chat is ALWAYS shown so we never need .open / backdrop rules. */
.chat-panel-backdrop { display: none; }

.chat-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Padding sides 14px so "CONVERSACIÓN" lines up with the 14px content insets
     of the bubbles below and leaves a clear breathing column near the right
     edge so the assistant bubble never touches the screen border. */
  padding: 0 14px 6px;
  border-bottom: 0 solid transparent;
  flex: none;
}
.chat-panel-header h3 {
  margin: 0;
  font-size: 13px;
  letter-spacing: 0.04em;
  background: var(--brand-gradient);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  font-weight: 800;
  text-transform: uppercase;
  opacity: .92;
}

/* Chat history: flex column of user/assistant bubbles — scrolled independently
   while header + input row stay pinned. Transparent seamless on page aurora (no own bg).
   Padding 14px sides for alignment with the header.
   NOTE: the critical "not attached to screen border" rule is enforced by
   `margin-right:22px` on `.hist-msg.assistant` and `margin-left:6px` on `.hist-msg.user`
   (hard rules on the bubbles themselves so nothing can hide them).
   Bottom padding BUMP 2026-09-04: 96 → 160px. The screenshot showed the last
   assistant line "...Necesitas más detalles o" cut clean by the footer. 160px
   inside the scroll container is 5× the real footer glass height; after we
   snap scrollTop to the absolute max, the last bubble terminates at the TOP
   of a 160px-high transparent region → fully visible, fully above the floating
   sticky footer even on very short (800px svh) mobile viewports. */
.chat-history {
  flex: 1 1 auto;
  overflow-y: auto;
  /* 160px bottom padding inside the scrollbox means scrollHeight grows to include
     those 160px. When we snap scrollTop = max, the last bubble ends at the top
     of that 160px void → visible and well above the floating footer line. */
  padding: 4px 14px 160px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  scrollbar-width: thin;
  /* Disable Chrome's "scroll anchoring" which sometimes fights our snap-to-bottom by
     re-anchoring the scroll pos whenever a new token is appended. We truly want
     the tail, always, no exceptions. */
  overflow-anchor: none;
  /* Instant snap (never smooth) — the default was causing perceived "scroll
     scroll does not go far enough" because smooth animates while new content
     outpaces the animation frame budget on long streams). */
  scroll-behavior: auto !important;
  /* gutter removed: spacing is guaranteed by per-bubble margin rules instead */
}
.chat-history::-webkit-scrollbar { width: 6px; }
.chat-history::-webkit-scrollbar-track { background: transparent; }
.chat-history::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--brand) 30%, transparent);
  border-radius: 999px;
  border: 1px solid transparent;
  background-clip: content-box;
}

/* ─── Chat input row (bottom of chat panel: "sobre el botón") ──────── */
/* NOTE: currently HIDDEN in index.html. Kept seamless here so if/when we
   re-enable it the form melts visually into the page bg, no separator frame. */
.chat-form {
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 8px 2px 0;
  border-top: 0 solid transparent;
  background: transparent;
  flex: none;
}
.chat-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 40px;
  padding: 0 14px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--bg) 70%, rgba(255,255,255,0.02));
  border: 1px solid color-mix(in srgb, var(--brand) 18%, var(--border));
  color: var(--text);
  font-size: 14px;
  font-family: var(--font-sans);
  line-height: 1;
  transition: border-color .2s ease, box-shadow .2s ease, background .2s ease;
  outline: none;
  box-shadow: 0 0 0 0 transparent;
}
.chat-input::placeholder {
  color: color-mix(in srgb, var(--text-dim) 72%, transparent);
  letter-spacing: 0.01em;
}
.chat-input:hover {
  border-color: color-mix(in srgb, var(--brand-2) 44%, var(--border));
}
.chat-input:focus {
  border-color: color-mix(in srgb, var(--brand-3) 60%, transparent);
  background: color-mix(in srgb, var(--bg) 82%, rgba(255,255,255,0.04));
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--brand-3) 22%, transparent),
    0 0 26px -8px color-mix(in srgb, var(--brand-3) 70%, transparent);
}
.chat-send-btn {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  color: #fff;
  background:
    radial-gradient(120% 120% at 30% 18%, rgba(255,255,255,0.18), transparent 58%),
    linear-gradient(135deg,
      color-mix(in srgb, var(--brand) 82%, transparent),
      color-mix(in srgb, var(--brand-2) 70%, transparent) 55%,
      color-mix(in srgb, var(--brand-3) 70%, transparent));
  border: 1px solid color-mix(in srgb, var(--brand-2) 50%, var(--border));
  box-shadow:
    0 1px 0 rgba(255,255,255,0.1) inset,
    0 6px 18px rgba(0,0,0,0.30),
    0 0 24px -8px color-mix(in srgb, var(--brand-2) 70%, transparent);
  cursor: pointer;
  transition: transform .22s ease, box-shadow .3s ease, filter .3s ease;
}
.chat-send-btn:hover {
  transform: translateY(-1px);
  filter: brightness(1.05);
  box-shadow:
    0 1px 0 rgba(255,255,255,0.12) inset,
    0 10px 22px rgba(0,0,0,0.34),
    0 0 34px -6px color-mix(in srgb, var(--brand-2) 80%, transparent);
}
.chat-send-btn:active { transform: translateY(0) scale(0.98); }
.chat-send-btn:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--brand-3) 40%, transparent),
    0 6px 18px rgba(0,0,0,0.30);
}

/* ─── Reports sidebar (LEFT) ───────────────────────────────────────── */
/* Behaviour:
   - desktop (>= 1024px): FIXED to the left edge, 320px, always visible.
     Stage/header/footer leave room via #app padding-left 320px so the orb
     is not covered.
   - mobile / tablet: OFF-CANVAS slide-in from the left (like chat panel on
     right), only shown on toggle, closed via backdrop / close button / ESC.
   backdrop is shared between both breakpoints (only visible when open on
   mobile; desktop uses no backdrop). */
.reports-sidebar {
  position: fixed;
  inset: 0;
  z-index: 190;          /* below chat-panel (z 200), above stage/header (z 1-2) */
  pointer-events: none; /* backdrop + inner opt-in to clicks separately */
}
.reports-sidebar-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(8, 9, 13, 0.42);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.reports-sidebar-inner {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: min(320px, 88vw);
  background:
    linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0)),
    var(--bg-elev);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  transform: translateX(-105%);
  transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1);
  pointer-events: auto;
  box-shadow: 8px 0 32px rgba(0, 0, 0, 0.35);
  overflow: hidden;
}
.reports-sidebar.open .reports-sidebar-backdrop {
  opacity: 1;
  pointer-events: auto;
}
.reports-sidebar.open .reports-sidebar-inner {
  transform: translateX(0);
}
/* Header — same pattern as chat panel header but tinted brand gradient line */
.reports-sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
  flex: none;
  position: relative;
  overflow: hidden;
}
.reports-sidebar-header::after {
  content: "";
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: -1px;
  height: 1px;
  background: linear-gradient(90deg,
    transparent,
    color-mix(in srgb, var(--brand) 60%, transparent) 45%,
    color-mix(in srgb, var(--brand-2) 55%, transparent) 65%,
    transparent);
  opacity: .85;
}
.reports-sidebar-header h3 {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.02em;
  background: var(--brand-gradient);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
}
/* Hint block lives inside the sidebar now. Reset any "topbar" flavour of the
   card: no max-height cap here (the sidebar scrolls the full panel instead).
   Keep full-height so reports-list chips can breathe. */
.reports-sidebar .reports-hint {
  margin: 12px 14px 4px 14px;
  padding: 12px 12px 10px 12px;
  max-height: none;
  max-width: none;
  width: auto;
  flex: none;
  box-shadow:
    var(--shadow-glass),
    0 0 0 1px color-mix(in srgb, var(--brand) 16%, transparent) inset,
    0 0 24px -14px color-mix(in srgb, var(--brand) 62%, transparent);
}
.reports-sidebar .reports-hint .reports-list {
  gap: 6px 8px;
}
/* Scrollbar of the sidebar (NOT reports-hint — that one still has its cap
   when embedded in a smaller view) */
.reports-sidebar-inner {
  scrollbar-width: thin;
  scrollbar-gutter: stable;
  overflow-y: auto;
}
.reports-sidebar-inner::-webkit-scrollbar { width: 6px; }
.reports-sidebar-inner::-webkit-scrollbar-track { background: transparent; }
.reports-sidebar-inner::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--brand) 32%, transparent);
  border-radius: 999px;
  border: 1px solid transparent;
  background-clip: content-box;
}
/* ── Toggle button in the topbar control cluster ─────────────────── */
button[data-reports-sidebar-toggle] {
  /* Visible ONLY on mobile/tablet by default (desktop has sidebar always on) */
  display: inline-flex;
}
/* Toggle button badge (count of reports scheduled, shown only if >0) */
#reports-sidebar-badge {
  position: absolute;
  top: -3px;
  right: -3px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--brand-2), var(--brand));
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 18px;
  text-align: center;
  display: none;
  box-shadow: 0 0 0 2px var(--bg), 0 0 10px rgba(0,0,0,.3);
  pointer-events: none;
}
#reports-sidebar-badge[data-count]:not([data-count="0"]):not([data-count=""]) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ── DESKTOP (>=1024px): sidebar always visible on the LEFT ────────── */
@media (min-width: 1024px) {
  /* Leave room for sidebar at left edge of the app */
  #app {
    padding-left: 320px;
    box-sizing: border-box;
  }
  .reports-sidebar {
    pointer-events: auto;
  }
  .reports-sidebar-backdrop {
    display: none;   /* never darken the page on desktop */
  }
  .reports-sidebar-inner {
    transform: translateX(0) !important;   /* keep always in */
    width: 320px;
    box-shadow: none;  /* embedded sidebar, no floating shadow */
    border-right: 1px solid color-mix(in srgb, var(--brand) 22%, var(--border));
    background:
      linear-gradient(180deg,
        rgba(255,255,255,0.02),
        rgba(255,255,255,0) 14%),
      var(--bg);
  }
  /* Hide the toggle button in the topbar control cluster (sidebar always on) */
  button[data-reports-sidebar-toggle] { display: none !important; }
  /* Give the orb some extra breathing room since we stole 320px on the left */
  .stage {
    padding-left: clamp(24px, 4vw, 48px);
    padding-right: clamp(24px, 4vw, 48px);
  }
}
/* Mid-range tablet 768..1023 : sidebar is off-canvas (toggle shown) but we
   can still show the toggle and use 320px sliding panel. */
@media (min-width: 768px) and (max-width: 1023px) {
  .reports-sidebar-inner { width: min(360px, 78vw); }
  button[data-reports-sidebar-toggle] { display: inline-flex; }
}
/* Very narrow (<=767): same off-canvas, narrower panel. */
@media (max-width: 767px) {
  .reports-sidebar-inner { width: min(320px, 86vw); }
}

.chat-empty {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  color: var(--text-faint);
  padding: 48px 0;
  opacity: 0.65;
}
.chat-empty svg {
  margin-bottom: 6px;
  opacity: 0.8;
}
.chat-empty-title {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.chat-empty-hint {
  font-size: 12px;
  color: var(--text-faint);
  opacity: 0.7;
}

/* ─── History messages ───────────────────────────────────────────────── */

.hist-msg {
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* 76% leaves a clear 24% lateral column: at 14px chat side padding + 6px
     scrollbar-gutter this guarantees the ASSISTANT bubble (align-self flex-end)
     never touches the screen border, even with its outer glass shadow. */
  max-width: 76%;
}
.hist-msg.user {
  align-self: flex-start;
  /* Hard safety margin-left: guarantees the left bubble NEVER touches the
     viewport border, regardless of parent padding / cache drift. */
  margin-left: 6px;
  margin-right: auto;
}
.hist-msg.assistant {
  align-self: flex-end;
  /* Hard safety margin-right: 22px GUARANTEED between the rounded corner of
     the assistant bubble and the right viewport/screen border. Always visible
     even when no scrollbar is rendered (scrollbar-gutter was creating
     inconsistent spacing between empty history and scrolled history). */
  margin-right: 22px;
  margin-left: auto;
}
.hist-msg.tool { align-self: flex-start; max-width: 100%; margin-right: 6px; margin-left: 6px; }

.hist-role {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 0 4px;
  text-shadow: 0 1px 0 rgba(0,0,0,0.45);
}
.hist-msg.user .hist-role { color: color-mix(in srgb, var(--voice-user) 92%, white 8%); }
.hist-msg.assistant .hist-role { color: color-mix(in srgb, var(--voice-assistant) 92%, white 8%); }
.hist-msg.tool .hist-role { color: color-mix(in srgb, var(--voice-tool) 92%, white 8%); }

.hist-body {
  padding: 11px 14px;
  border-radius: 14px;
  font-size: 14px;
  line-height: 1.55;
  word-break: break-word;
  color: var(--text);
  /* Glass base — visible against the seamless aurora page background. */
  background: color-mix(in srgb, var(--bg-elev-2) 88%, rgba(255,255,255,0.03));
  border: 1px solid color-mix(in srgb, var(--border) 92%, transparent);
  box-shadow:
    0 10px 28px rgba(0, 0, 0, 0.28),
    0 0 0 1px rgba(255, 255, 255, 0.035) inset;
  backdrop-filter: blur(8px) saturate(130%);
  -webkit-backdrop-filter: blur(8px) saturate(130%);
}
/* Role-tinted bubbles so you can scan who's talking at a glance even when the
   history is long. User bubble gets a soft cyan/violet rim; assistant gets a
   pink/violet rim. Both stay readable against the page aurora. */
.hist-msg.user .hist-body {
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--voice-user) 10%, var(--bg-elev-2)),
      color-mix(in srgb, var(--voice-user) 6%, var(--bg-elev-2))) padding-box;
  border-color: color-mix(in srgb, var(--voice-user) 44%, var(--border));
  box-shadow:
    0 10px 28px rgba(0, 0, 0, 0.28),
    0 0 0 1px color-mix(in srgb, var(--voice-user) 16%, rgba(255,255,255,0.05)) inset,
    0 0 28px -14px color-mix(in srgb, var(--voice-user) 66%, transparent);
}
.hist-msg.assistant .hist-body {
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--voice-assistant) 10%, var(--bg-elev-2)),
      color-mix(in srgb, var(--voice-assistant) 5%, var(--bg-elev-2))) padding-box;
  border-color: color-mix(in srgb, var(--voice-assistant) 42%, var(--border));
  box-shadow:
    0 10px 28px rgba(0, 0, 0, 0.28),
    0 0 0 1px color-mix(in srgb, var(--voice-assistant) 14%, rgba(255,255,255,0.05)) inset,
    0 0 28px -14px color-mix(in srgb, var(--voice-assistant) 62%, transparent);
}
.hist-msg.user .hist-body.partial { opacity: 0.68; }

/* ─── Tool call history item ─────────────────────────────────────────── */

.hist-tool-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  background: color-mix(in srgb, var(--processing) 10%, var(--bg-elev-2));
  border: 1px solid color-mix(in srgb, var(--processing) 22%, var(--border));
  border-bottom: 1px solid color-mix(in srgb, var(--processing) 15%, var(--border));
  cursor: pointer;
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  width: 100%;
  text-align: left;
  transition: background 0.15s;
}
.hist-tool-header:only-child {
  border-radius: var(--radius-md);
  border-bottom: 1px solid color-mix(in srgb, var(--processing) 22%, var(--border));
}
.hist-tool-header:hover {
  background: color-mix(in srgb, var(--processing) 16%, var(--bg-elev-2));
}
.hist-tool-icon {
  width: 13px;
  height: 13px;
  flex: none;
  color: var(--processing);
}
.hist-tool-name {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--voice-tool);
  font-weight: 600;
}
.hist-tool-chevron {
  margin-left: auto;
  width: 13px;
  height: 13px;
  color: var(--text-faint);
  transition: transform 0.2s ease;
  flex: none;
}
.hist-tool-header[aria-expanded="true"] .hist-tool-chevron {
  transform: rotate(180deg);
}
.hist-tool-body {
  padding: 10px 12px 12px;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  background: var(--bg);
  border: 1px solid color-mix(in srgb, var(--processing) 22%, var(--border));
  border-top: none;
  display: none;
}
.hist-tool-body.open { display: block; }
.hist-tool-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 8px 0 4px;
}
.hist-tool-label:first-child { margin-top: 0; }
.hist-tool-block {
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.65;
  color: var(--text-dim);
  white-space: pre-wrap;
  word-break: break-word;
  overflow-x: auto;
}
.hist-tool-output { color: var(--text); }

/* ─── Phone layout ────────────────────────────────────────────────────────
 * On phones the floating bubble stream overlaps the orb and there isn't room
 * for it, so we drop it entirely and rely on the conversation panel (opened
 * from the top-right chat button) as the single place to read the transcript.
 * The badge still pulses there when new messages arrive while it's closed.
 * We also stack the mic / stop controls above and below the orb (instead of
 * left/right) so the wide live row never overflows, and let the panel take
 * the full width. */
@media (max-width: 600px) {
  .bubble-stack {
    display: none;
  }

  .topbar {
    padding: 14px 16px;
  }

  .stage {
    padding: 16px 12px 24px;
  }

  /* Stack vertically: mic above the orb, stop below it (DOM order is
   * mic → circle → stop). */
  .orb-wrap {
    flex-direction: column;
    gap: 14px;
  }

  .circle {
    /* Height-capped like the base rule so a narrow AND short box still fits.
     * Use svh/svw (smallest-viewport units) to avoid URL bar resize jumps. */
    width: clamp(110px, min(56svw, 36svh), 220px);
  }

  /* In the column layout the side controls must collapse by HEIGHT, not
   * width, so they take no vertical space until the session is live. */
  .side-btn {
    width: 44px;
    height: 0;
    transition: opacity 0.25s ease, transform 0.25s ease, height 0.25s ease,
      background 0.15s, color 0.15s, border-color 0.15s;
  }
  .side-btn svg {
    width: 19px;
    height: 19px;
  }
  .orb-wrap.live .side-btn {
    width: 44px;
    height: 44px;
  }
  .mic-gate-arc { width: 70px; height: 70px; }

  /* Full-screen conversation on phones — feels more deliberate than a
   * narrow slide-over. It already spans top-to-bottom (inset 0); make it
   * span edge-to-edge too and drop the now-pointless border/shadow. */
  .chat-panel-inner {
    width: 100vw;
    border-left: none;
    box-shadow: none;
  }
}

/* ─── About panel ─────────────────────────────────────────────────────────
 * Opened from the (i) by the wordmark. Reuses the <dialog> .modal shell.
 * Strictly monochrome per DESIGN.md — the only "color" is the orb, never
 * here. Machine identifiers (model IDs, role tags, usernames) ride Geist
 * Mono; everything human stays Inter. */
.about-modal {
  /* Roomier on tablet/desktop; the 92vw cap keeps phones full-width. */
  width: min(680px, 92vw);
  max-height: 88vh;
}
.about-modal .modal-content {
  max-height: 88vh;
  overflow-y: auto;
  gap: 20px;
  padding: 26px 30px 24px;
}
.about-modal .modal-content::-webkit-scrollbar { width: 3px; }
.about-modal .modal-content::-webkit-scrollbar-track { background: transparent; }
.about-modal .modal-content::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 3px; }

/* Links here are quiet: no permanent underline (the global dotted rule is
 * too loud for a dense credit block), brighten + underline on hover only. */
.about-modal a {
  color: var(--text);
  border-bottom: none;
  text-decoration: none;
  transition: color 0.15s ease;
}
.about-modal a:hover {
  color: #fff;
}
.about-intro a:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}
.about-modal .ext {
  width: 12px;
  height: 12px;
  flex: none;
  opacity: 0.5;
}

/* ─── User Profile Modal (opened by clicking the avatar circle) ──────── */
.user-profile-modal {
  width: min(480px, 90vw);
  max-height: 90vh;
}
.user-profile-card {
  position: relative;
  padding: 30px 28px 28px;
  gap: 22px;
  /* Glass + brand-gradient 1px border (same trick used in the .brand card). */
  background:
    linear-gradient(var(--bg-elev), var(--bg-elev)) padding-box,
    linear-gradient(135deg,
      color-mix(in srgb, var(--brand) 55%, transparent),
      color-mix(in srgb, var(--brand-2) 48%, transparent) 55%,
      color-mix(in srgb, var(--brand-3) 55%, transparent)) border-box;
  border: 1px solid transparent;
  border-radius: 18px;
  box-shadow:
    0 30px 80px rgba(0, 0, 0, 0.45),
    0 0 0 1px rgba(255, 255, 255, 0.04) inset,
    0 0 60px -20px color-mix(in srgb, var(--brand) 60%, transparent);
  backdrop-filter: blur(20px) saturate(150%);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  overflow: hidden;
}
.user-profile-card::before {
  /* Top-edge soft aurora fade so the card feels "lit" from the top-left,
     matching the orb + body aurora palette. */
  content: "";
  position: absolute;
  inset: -1px;
  background:
    radial-gradient(420px 220px at 15% -10%, color-mix(in srgb, var(--brand) 26%, transparent), transparent 60%),
    radial-gradient(380px 220px at 110% 15%, color-mix(in srgb, var(--brand-2) 22%, transparent), transparent 55%);
  pointer-events: none;
  opacity: .95;
  z-index: 0;
}
.user-profile-card > * { position: relative; z-index: 1; }

/* Close button: absolute top-right, 32px so it doesn't feel cramped near
   the large hero avatar. */
.user-profile-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 32px;
  height: 32px;
  z-index: 5;
  opacity: 0.88;
}

/* HERO — big glass avatar circle, name, email fallback subtitle. */
.user-profile-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding-top: 6px;
  text-align: center;
}
.user-profile-avatar {
  width: 88px;
  height: 88px;
  aspect-ratio: 1 / 1;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--brand) 40%, var(--border));
  color: #fff;
  background:
    radial-gradient(140% 140% at 28% 22%, rgba(255,255,255,0.22), transparent 58%),
    linear-gradient(135deg,
      color-mix(in srgb, var(--brand) 76%, transparent),
      color-mix(in srgb, var(--brand-2) 64%, transparent) 55%,
      color-mix(in srgb, var(--brand-3) 62%, transparent));
  box-shadow:
    0 0 0 1px rgba(255,255,255,0.05) inset,
    0 2px 0 rgba(255,255,255,0.08) inset,
    0 10px 30px rgba(0,0,0,0.32),
    0 0 48px -10px color-mix(in srgb, var(--brand-2) 72%, transparent);
  font-family: var(--font-sans);
  font-weight: 800;
  font-size: 34px;
  line-height: 1;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-shadow: 0 2px 4px rgba(0,0,0,0.35);
  user-select: none;
  position: relative;
}
.user-profile-avatar::after {
  /* subtle specular rim */
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(135deg, rgba(255,255,255,0.22), transparent 46%);
  opacity: .85;
  pointer-events: none;
  mix-blend-mode: screen;
}
.user-profile-title {
  margin: 0;
  font-size: 22px;
  line-height: 1.2;
  font-weight: 800;
  letter-spacing: -0.01em;
  background: var(--brand-gradient);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  filter: drop-shadow(0 1px 0 rgba(0,0,0,0.35));
  max-width: 100%;
  overflow-wrap: anywhere;
}
.user-profile-subtitle {
  margin: 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--text-dim);
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* METADATA — 2-col grid: label (left, dim/caps) + value (right, bright). */
.user-profile-metadata {
  display: grid;
  grid-template-columns: 1fr 1.35fr;
  row-gap: 12px;
  column-gap: 16px;
  padding: 10px 4px 4px;
  border-top: 1px solid color-mix(in srgb, var(--brand) 18%, var(--border));
  padding-top: 18px;
}
.user-profile-metadata:empty { display: none; }

.user-profile-row {
  display: contents; /* let children participate in the outer grid directly */
}
.user-profile-row-label {
  grid-column: 1 / 2;
  align-self: start;
  font-size: 11px;
  line-height: 1.5;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--brand-3) 80%, var(--text-dim));
  padding-top: 2px;
}
.user-profile-row-value {
  grid-column: 2 / 3;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--text);
  font-weight: 500;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.user-profile-row-value a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dashed color-mix(in srgb, var(--brand-2) 50%, transparent);
  padding-bottom: 1px;
}
.user-profile-row-value a:hover {
  color: #fff;
  border-bottom-color: color-mix(in srgb, var(--brand-2) 90%, #fff);
}
.user-profile-row-value code,
.user-profile-row-value .mono {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 2px 7px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--brand) 12%, rgba(255,255,255,0.03));
  border: 1px solid color-mix(in srgb, var(--brand) 20%, var(--border));
  color: color-mix(in srgb, var(--brand-2) 90%, #fff);
  display: inline-block;
  max-width: 100%;
  overflow-wrap: anywhere;
}

@media (max-width: 480px) {
  .user-profile-card { padding: 24px 20px 20px; }
  .user-profile-avatar { width: 72px; height: 72px; font-size: 28px; }
  .user-profile-title { font-size: 19px; }
  .user-profile-metadata {
    grid-template-columns: 1fr;     /* stack label/value on super-narrow */
    row-gap: 6px;
    padding-top: 16px;
  }
  .user-profile-row-label { grid-column: 1 / -1; padding-top: 6px; }
  .user-profile-row-value { grid-column: 1 / -1; }
}
/* Title row: the demo name and the (i) read as one unit. */
.ident-head {
  display: flex;
  align-items: center;
  gap: 9px;
}
/* (i) trigger sits right after the title. A faint outline keeps it
 * catchable without turning into a card; it fills in on hover. */
.about-btn {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--text-dim);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  touch-action: manipulation;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.about-btn:hover {
  background: var(--bg-elev);
  border-color: var(--text-dim);
  color: var(--text);
}
.about-btn svg {
  width: 20px;
  height: 20px;
}

/* The mobile twin of (i) lives in the right-hand control cluster and is
 * hidden on desktop (the in-title one shows there instead). */
.about-btn-mobile {
  display: none;
}

/* ── Popup intro: a plain paragraph on the project + a repo link ── */
.about-intro p {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--text-dim);
}
.about-repo {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 12px;
  font-size: 14px;
  font-weight: 500;
}

/* ── Corner identity (replaces the wordmark) ──
 * A compact stack in the topbar: title, one-line blurb, two meta rows.
 * Monochrome; only the role/name identifiers ride the machine typeface. */
.ident {
  /* Align the wordmark (left) and the user avatar circle (right) on the SAME
   * row. Vertical-align: both centered relative to each other — so "AV" in the
   * round badge lines up perfectly with the "Vocero" gradient wordmark. */
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-width: 0;
  width: 100%;
}
/* Reset the global underlined-anchor styling inside the identity block. */
.ident a {
  border-bottom: none;
  color: inherit;
  transition: color 0.15s ease;
}
.ident a:hover {
  color: var(--text);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.ident-title {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: 0.005em;
  line-height: 1.1;
  color: var(--text);
}
.ident-user-name {
  max-width: 32ch;
  font-size: 13px;
  line-height: 1.35;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.reports-hint {
  max-width: min(720px, 100%);
  /* IMPORTANT: hard cap height so even 20 report chips never grow the topbar
   * beyond ~35% of the viewport. If more space is needed the user scrolls
   * inside the card itself (branded thin scrollbar below). */
  max-height: 34svh;
  max-height: 36dvh;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-gutter: stable;
  font-size: 12px;
  line-height: 1.3;
  color: var(--text-dim);
  background:
    linear-gradient(180deg, rgba(255,255,255,0.028), rgba(255,255,255,0)) ,
    var(--glass);
  border: 1px solid color-mix(in srgb, var(--brand) 26%, var(--border));
  border-radius: var(--radius-sm);
  backdrop-filter: blur(12px) saturate(135%);
  -webkit-backdrop-filter: blur(12px) saturate(135%);
  padding: 8px 10px;
  margin-top: 4px;
  box-shadow:
    var(--shadow-glass),
    0 0 0 1px color-mix(in srgb, var(--brand) 14%, transparent) inset,
    0 0 20px -10px color-mix(in srgb, var(--brand) 55%, transparent);
  position: relative;
}
/* thin scrollbar inside the reports card so chips are still readable. */
.reports-hint::-webkit-scrollbar { width: 6px; }
.reports-hint::-webkit-scrollbar-track { background: transparent; }
.reports-hint::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--brand-3) 32%, transparent);
  border-radius: 999px;
  border: 1px solid transparent;
  background-clip: content-box;
}
.reports-hint::before {
  content: "";
  position: absolute;
  top: -1px;
  left: -1px;
  right: -1px;
  height: 1px;
  background: linear-gradient(90deg,
    transparent 0%,
    color-mix(in srgb, var(--brand) 70%, transparent) 25%,
    color-mix(in srgb, var(--brand-2) 60%, transparent) 55%,
    color-mix(in srgb, var(--brand-3) 65%, transparent) 80%,
    transparent 100%);
  opacity: .85;
  pointer-events: none;
}
.reports-hint .reports-summary {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 4px 8px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 6px;
  font-size: 11.5px;
  line-height: 1.15;
}
.reports-hint .reports-summary::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--success);
  box-shadow:
    0 0 0 2.5px color-mix(in srgb, var(--success) 18%, transparent),
    0 0 10px 1px color-mix(in srgb, var(--success) 68%, transparent);
  animation: pulseDot 2.2s ease-in-out infinite;
  flex: none;
}
.reports-hint .reports-summary-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--text);
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--brand) 24%, transparent),
    color-mix(in srgb, var(--brand-2) 20%, transparent));
  border: 1px solid color-mix(in srgb, var(--brand) 30%, transparent);
  white-space: nowrap;
}
.reports-hint .reports-summary-badge + .reports-summary-badge {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--brand-3) 20%, transparent),
    color-mix(in srgb, var(--brand) 18%, transparent));
  border-color: color-mix(in srgb, var(--brand-3) 28%, transparent);
}
.reports-hint .reports-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
}
.reports-hint .reports-list li {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 8px 3px 6px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.1;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  position: relative;
  border: 1px solid var(--border);
  background:
    linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0)),
    rgba(20, 22, 32, 0.52);
  box-shadow: 0 1px 0 rgba(255,255,255,0.035) inset, 0 1px 4px rgba(0,0,0,.16);
  transition:
    transform .2s cubic-bezier(.2,.7,.2,1),
    box-shadow .25s ease,
    border-color .25s ease,
    background .25s ease,
    color .25s ease;
}
.reports-hint .reports-list li::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: var(--brand);
  box-shadow: 0 0 7px 0 color-mix(in srgb, var(--brand) 75%, transparent);
  flex: none;
}
/* Per-chip tint cycles (keyword-based, if no data attribute → fallback brand) */
.reports-hint .reports-list li[data-tint="mortalidad"] { background: linear-gradient(180deg, color-mix(in srgb, #ff6a75 14%, rgba(20,22,32,.55)), rgba(20,22,32,.55)); border-color: color-mix(in srgb, #ff6a75 35%, var(--border)); }
.reports-hint .reports-list li[data-tint="mortalidad"]::before { background: #ff6a75; box-shadow: 0 0 8px 0 #ff6a75aa; }
.reports-hint .reports-list li[data-tint="metricas"] { background: linear-gradient(180deg, color-mix(in srgb, #22d3ee 14%, rgba(20,22,32,.55)), rgba(20,22,32,.55)); border-color: color-mix(in srgb, #22d3ee 35%, var(--border)); }
.reports-hint .reports-list li[data-tint="metricas"]::before { background: #22d3ee; box-shadow: 0 0 8px 0 #22d3eeaa; }
.reports-hint .reports-list li[data-tint="diario"] { background: linear-gradient(180deg, color-mix(in srgb, #8b5cf6 14%, rgba(20,22,32,.55)), rgba(20,22,32,.55)); border-color: color-mix(in srgb, #8b5cf6 35%, var(--border)); }
.reports-hint .reports-list li[data-tint="diario"]::before { background: #8b5cf6; box-shadow: 0 0 8px 0 #8b5cf6aa; }
.reports-hint .reports-list li[data-tint="productos"] { background: linear-gradient(180deg, color-mix(in srgb, #ec4899 14%, rgba(20,22,32,.55)), rgba(20,22,32,.55)); border-color: color-mix(in srgb, #ec4899 35%, var(--border)); }
.reports-hint .reports-list li[data-tint="productos"]::before { background: #ec4899; box-shadow: 0 0 8px 0 #ec4899aa; }
.reports-hint .reports-list li[data-tint="synapse"] { background: linear-gradient(180deg, color-mix(in srgb, #fbbf24 14%, rgba(20,22,32,.55)), rgba(20,22,32,.55)); border-color: color-mix(in srgb, #fbbf24 35%, var(--border)); }
.reports-hint .reports-list li[data-tint="synapse"]::before { background: #fbbf24; box-shadow: 0 0 8px 0 #fbbf24aa; }
.reports-hint .reports-list li[data-tint="supply"] { background: linear-gradient(180deg, color-mix(in srgb, #34d399 14%, rgba(20,22,32,.55)), rgba(20,22,32,.55)); border-color: color-mix(in srgb, #34d399 35%, var(--border)); }
.reports-hint .reports-list li[data-tint="supply"]::before { background: #34d399; box-shadow: 0 0 8px 0 #34d399aa; }
.reports-hint .reports-list li[data-tint="finanzas"] { background: linear-gradient(180deg, color-mix(in srgb, #22c55e 14%, rgba(20,22,32,.55)), rgba(20,22,32,.55)); border-color: color-mix(in srgb, #22c55e 35%, var(--border)); }
.reports-hint .reports-list li[data-tint="finanzas"]::before { background: #22c55e; box-shadow: 0 0 8px 0 #22c55eaa; }
.reports-hint .reports-list li:hover {
  transform: translateY(-1px) scale(1.01);
  border-color: color-mix(in srgb, var(--brand) 55%, var(--border-strong));
  background:
    linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0)),
    rgba(24, 27, 40, 0.7);
  box-shadow:
    0 6px 16px rgba(0,0,0,.3),
    0 0 14px -4px color-mix(in srgb, var(--brand) 55%, transparent),
    inset 0 1px 0 rgba(255,255,255,.07);
}
.reports-hint .reports-suggestions {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px dashed color-mix(in srgb, var(--brand) 30%, transparent);
  position: relative;
}
.reports-hint .reports-suggestions::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 10%;
  right: 10%;
  height: 1px;
  background: linear-gradient(90deg, transparent,
    color-mix(in srgb, var(--brand-2) 55%, transparent) 50%,
    transparent);
  opacity: .7;
}
.reports-hint .reports-suggestions-title {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 6px;
  font-size: 12.5px;
  line-height: 1.2;
}
.reports-hint .reports-suggestions-title::before {
  content: "💡";
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--brand-3) 65%, transparent));
}
.reports-hint .reports-suggestions-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 5px;
  color: var(--text-dim);
}
.reports-hint .reports-suggestions-list li {
  white-space: normal;
  word-break: break-word;
  padding: 5px 8px 5px 26px;
  border-radius: 10px;
  position: relative;
  font-size: 12.5px;
  transition: background .2s ease, color .2s ease;
}
.reports-hint .reports-suggestions-list li::before {
  content: "→";
  position: absolute;
  left: 8px;
  top: 5px;
  color: var(--brand-2);
  font-weight: 700;
  filter: drop-shadow(0 0 4px color-mix(in srgb, var(--brand-2) 70%, transparent));
}
.reports-hint .reports-suggestions-list li:hover {
  background: color-mix(in srgb, var(--brand) 8%, transparent);
  color: var(--text);
}
.ident-blurb {
  margin: 0;
  max-width: 48ch;
  font-size: 14.5px;
  line-height: 1.5;
  font-weight: 400;
  color: var(--text-dim);
}
.ident-meta {
  display: flex;
  flex-direction: column;
  gap: 7px;
  font-size: 14px;
  font-weight: 400;
  color: var(--text-dim);
}
.ident-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
}
.ident-label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* Shared credit bits — now used in the corner identity block. */
.sep {
  color: var(--text-faint);
  opacity: 0.6;
}
.hf-credit {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
/* Brand marks keep their own color — a deliberate exception to the
 * monochrome rule, for the HF and Cerebras logos only. */
.hf-mark {
  width: 14px;
  height: 14px;
  flex: none;
  color: #ffd21e;
}
.cerebras-credit {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.cerebras-mark {
  width: 14px;
  height: 14px;
  flex: none;
}
/* Usernames are identifiers, so they ride the machine typeface. */
.handle {
  font-family: var(--font-mono);
  font-size: 13.5px;
}

/* ── Pipeline (the signal flow) ── */
.about-pipeline {
  border-top: 1px solid var(--border);
  padding-top: 16px;
}
.pipeline-title {
  margin: 0 0 14px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.pipeline {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
}
/* One continuous rail threading every node, dot centers at x=8.5px. */
.pipeline::before {
  content: "";
  position: absolute;
  left: 8px;
  top: 7px;
  bottom: 7px;
  width: 1px;
  background: var(--border-strong);
}
.pipeline > li {
  position: relative;
  padding-left: 30px;
}
/* Stages: solid node. Endpoints (you / orb): hollow node. */
.pipe-stage::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 4px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-dim);
}
.pipe-endpoint::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 3px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: 1px solid var(--text-faint);
  background: var(--bg-elev);
}
.pipe-endpoint {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding-bottom: 12px;
}
.pipeline > li.pipe-endpoint:last-child {
  padding-bottom: 0;
}
.pipe-stage {
  padding-bottom: 16px;
}
.pipe-tag {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--text);
  margin-right: 9px;
}
.pipe-job {
  font-size: 14px;
  color: var(--text-dim);
}
/* Middot between the job and its model link, matching the separators
 * used elsewhere. */
.pipe-job::after {
  content: "·";
  margin: 0 7px;
  color: var(--text-faint);
}
.pipe-note {
  color: var(--text-faint);
}
/* The Cerebras link stays as quiet as the note; brightens + underlines on hover. */
.pipe-note a {
  color: inherit;
}
.pipe-note a:hover {
  color: var(--text);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.pipe-model {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-dim);
  word-break: break-all;
}
.pipe-model:hover {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.pipe-model .ext {
  width: 11px;
  height: 11px;
}

/* "Interrupted" tag on an assistant reply the user barged in on. */
.hist-note {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 0 2px;
}
.hist-msg.assistant .hist-note {
  align-self: flex-end;
}
.hist-msg.interrupted .hist-body {
  opacity: 0.7;
}

/* Captured webcam frame shown in the transcript (camera tool result). */
.hist-image {
  display: block;
  width: 100%;
  max-width: 240px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  margin-top: 2px;
}

/* ─── Tools panel ──────────────────────────────────────────────────────────
 * Reuses the modal/field shell. Switches are monochrome (checked = near-white,
 * the same high-contrast treatment as the primary button): color belongs to
 * the voice, not the chrome. */
.tools-intro {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-dim);
}
.tool-list {
  display: flex;
  flex-direction: column;
}
.tool-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0;
}
.tool-row-sep {
  border-top: 1px solid var(--border);
  margin-top: 4px;
}
.tool-info {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.tool-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}
.tool-desc {
  font-size: 12.5px;
  color: var(--text-dim);
}
.tool-row.disabled .tool-name,
.tool-row.disabled .tool-desc {
  opacity: 0.5;
}
.tool-hint {
  display: block;
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-faint);
}
.agent-tools-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
}
.agent-tool-item {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
}
.agent-tool-summary {
  list-style: none;
  cursor: pointer;
  padding: 10px 12px;
}
.agent-tool-summary::-webkit-details-marker {
  display: none;
}
.agent-tool-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
}
.agent-tool-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 2px 8px;
  border: 1px solid rgba(116, 167, 255, 0.35);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: #9cc0ff;
  background: rgba(90, 140, 255, 0.12);
}
.agent-tool-badge.suggested {
  border-color: rgba(123, 223, 168, 0.35);
  color: #9fe0bc;
  background: rgba(79, 168, 124, 0.14);
}
.agent-tool-match-banner {
  padding: 10px 12px;
  border: 1px solid rgba(123, 223, 168, 0.2);
  border-radius: 12px;
  background: rgba(79, 168, 124, 0.08);
}
.agent-tool-match-text {
  margin-top: 6px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text);
}
.agent-tool-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 12px 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
}
.agent-tool-call-label {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.agent-tool-call-copy {
  font-size: 12px;
  color: var(--text-faint);
}
.agent-tool-call-args {
  width: 100%;
  min-height: 88px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
  color: var(--text);
  font: inherit;
  font-family: "Geist Mono", monospace;
  resize: vertical;
}
.agent-tool-call-actions {
  display: flex;
  justify-content: flex-start;
}
.agent-tool-call-btn {
  min-width: 128px;
}
.agent-tool-call-result {
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  background: rgba(255, 255, 255, 0.03);
}
.agent-tool-call-result.success {
  border-color: rgba(123, 223, 168, 0.25);
  background: rgba(79, 168, 124, 0.08);
}
.agent-tool-call-result.error {
  border-color: rgba(255, 120, 120, 0.24);
  background: rgba(120, 40, 40, 0.16);
}
.agent-tool-call-result.running {
  border-color: rgba(90, 140, 255, 0.24);
  background: rgba(90, 140, 255, 0.08);
}
.agent-tool-call-result-title {
  margin-bottom: 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.agent-tool-call-output {
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 260px;
  overflow: auto;
  font: inherit;
  font-family: "Geist Mono", monospace;
  font-size: 12px;
  line-height: 1.5;
}
.agent-tool-section-title {
  margin-top: 10px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.agent-param-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
}
.agent-param-name {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
}
.agent-param-type {
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--text-faint);
}
.agent-tool-item .tool-name {
  display: block;
}
.agent-tool-empty {
  padding: 10px 12px;
  border: 1px dashed var(--border);
  border-radius: 12px;
  color: var(--text-faint);
  font-size: 12px;
}
.agent-tool-empty.small {
  padding: 8px 10px;
  font-size: 11.5px;
}
.tools-key {
  margin: 0 0 4px;
}

/* Toggle switch */
.switch {
  position: relative;
  display: inline-flex;
  flex: none;
  width: 40px;
  height: 24px;
  cursor: pointer;
}
.switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.switch-track {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background: var(--bg);
  border: 1px solid var(--border-strong);
  transition: background 0.15s, border-color 0.15s;
}
.switch-track::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 3px;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--text-dim);
  transition: transform 0.18s ease, background 0.15s;
}
.switch input:checked + .switch-track {
  background: var(--text);
  border-color: var(--text);
}
.switch input:checked + .switch-track::after {
  transform: translate(16px, -50%);
  background: var(--bg);
}
.switch input:focus-visible + .switch-track {
  outline: 2px solid var(--text-dim);
  outline-offset: 2px;
}
.switch input:disabled {
  cursor: not-allowed;
}
.switch input:disabled + .switch-track {
  opacity: 0.5;
}

/* ─── Webcam preview (camera tool) — HIDDEN BY DEFAULT OGGI ──────────────────
 * Floating self-view, bottom-left. Mirrored for the user; the frame sent to
 * the model is drawn un-mirrored. Markup esiste per riattivazione futura.
 * Per usarlo: togli hidden su cam-pip in index.html + scommenta listener in main.js. */
.cam-pip {
  position: fixed;
  left: 20px;
  bottom: 20px;
  width: 280px;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--border-strong);
  background: var(--bg-elev);
  box-shadow: var(--shadow-soft);
  z-index: 90;
  opacity: 0;
  transform: translateY(8px) scale(0.96);
  pointer-events: none;
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.cam-pip.visible {
  opacity: 1;
  transform: none;
}
.cam-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scaleX(-1); /* mirror the self-view only */
  background: var(--bg);
}
.cam-label {
  position: absolute;
  left: 8px;
  bottom: 6px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text);
  opacity: 0.8;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.cam-flash {
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}
.cam-pip.flash .cam-flash {
  animation: cam-flash 0.4s ease;
}
@keyframes cam-flash {
  0% { opacity: 0; }
  12% { opacity: 0.85; }
  100% { opacity: 0; }
}
@media (max-width: 600px) {
  /* Bottom-centred on phones. Auto margins centre it without touching the
   * transform, so the slide/scale-in animation still works. */
  .cam-pip {
    left: 0;
    right: 0;
    margin-inline: auto;
    bottom: 16px;
    width: min(188px, 52vw);
  }
  /* The credit would sit under the centred preview, so drop it while the
   * camera is on. */
  body.cam-on .footer {
    display: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .cam-pip {
    transition: opacity 0.22s ease;
    transform: none;
  }
  .cam-pip.flash .cam-flash {
    animation: none;
  }
}

/* ─── Desktop type scale ────────────────────────────────────────────────────
 * A uniform step up (~+1px, title +2) for all UI text on larger screens.
 * Scoped to min-width: 601px so it can't reach phones — the ≤600px layout
 * keeps every size exactly as it was. */
@media (min-width: 601px) {
  .cam-label { font-size: 10.5px; }

  .bubble-role,
  .hist-role { font-size: 11px; }

  .circle-caption,
  .footer,
  .chat-empty-title,
  .hist-tool-block,
  .ident-label,
  .pipeline-title,
  .pipe-endpoint { font-size: 12px; }

  .field > span,
  .field small,
  .bubble.tool,
  .chat-empty-hint,
  .hist-tool-name,
  .pipe-tag,
  .pipe-model,
  .tool-hint { font-size: 13px; }

  .tool-desc { font-size: 13.5px; }

  .btn,
  .bubble,
  .field,
  .hist-body,
  .hist-tool-header,
  .tools-intro { font-size: 14px; }

  .handle { font-size: 14.5px; }

  .about-intro p,
  .about-repo,
  .brand,
  .chat-panel-header h3,
  .field textarea,
  .ident-meta,
  .pipe-job,
  .tool-name { font-size: 15px; }

  .ident-blurb { font-size: 15.5px; }

  .modal-header h2 { font-size: 17px; }

  .ident-title { font-size: 24px; }
}

/* ─── Short viewports: fit without a vertical scrollbar ─────────────────────
 * The base media queries only react to WIDTH, so a short container (e.g. the
 * app embedded in a small box, or a laptop window once the browser chrome eats
 * into the viewport) used to overflow vertically and get clipped. We KEEP the
 * identity header, orb, caption and credits — we just undo the desktop type
 * bump, tighten the vertical rhythm and let the height-capped orb shrink, so
 * everything fits with comfortable margin. No opt-in needed.
 *
 * The single biggest space eater is the identity block (~180px at the desktop
 * type scale), so this tier compacts it first. */
@media (max-height: 780px) {
  .topbar { padding: 12px 20px 0; }
  .ident { gap: 5px; }
  .ident-title { font-size: 20px; }
  .ident-user-name { font-size: 12px; }
  .ident-blurb { font-size: 13.5px; line-height: 1.4; }
  .ident-meta { gap: 5px; font-size: 13px; }
  .stage { padding: 10px 18px 12px; gap: 12px; }
  .app-footer { padding: 8px 24px 10px; }
  .reports-hint { max-height: 30svh; }
  .circle { width: clamp(130px, min(36svw, 38svh), 280px); }
}

@media (max-height: 560px) {
  .topbar { padding: 6px 14px 0; gap: 6px; }
  .ident-title { font-size: 17px; }
  .ident-user-name { font-size: 11px; }
  /* Below this, room is tight enough that the one-line blurb and the footer
   * (a verbatim duplicate of the header's "Powered by" credit) are dropped so
   * the title + credits still read; the rest of the header stays. */
  .ident-blurb { display: none; }
  .stage { padding: 6px 12px 8px; gap: 6px; }
  .app-footer { padding: 3px 12px 5px; }
  .app-footer-text { font-size: 9px; letter-spacing: 0.1em; line-height: 1.25; }
  .reports-hint { max-height: 28svh; padding: 6px 8px; }
  .circle { width: clamp(90px, min(40svw, 34svh), 200px); }
}

/* ═══════════════════════════════════════════════════════════════════════
   THEME GATING (shared between purple-dark S2S_THEME="dark" / empty and
   light mobile S2S_THEME="white")
   ═══════════════════════════════════════════════════════════════════════ */

/* Anti-FOUC veil: until fetchConfig() resolves, <html> does not carry any
   `data-theme` attribute yet. Keep every parallel UI block tagged with
   `data-theme-only` INVISIBLE in that window so the user never sees the
   purple orb flash-and-disappear on `S2S_THEME=white` deploys, nor the
   light-blue mic flash on dark deploys.
   As soon as fetchConfig runs (main.js fetchConfig → root.setAttribute)
   the html tag receives either data-theme="dark" or data-theme="white"
   → this guard stops applying → the correctly gated branch paints. */
html:not([data-theme]) [data-theme-only] {
  visibility: hidden !important;
  pointer-events: none !important;
}
/* Explicit defaults when data-theme is SET (so stray [hidden] attributes
   left by previous runs / partial hydration do not accidentally suppress
   the visible branch). Rules only re-show elements that belong to the
   currently active theme. */
html[data-theme="white"] [data-theme-only="white"]:not([data-debug-only]),
html[data-theme="dark"]  [data-theme-only="dark"]:not([data-debug-only]) {
  visibility: visible;
  pointer-events: auto;
}
/* Hard guarantee: sidebar reports (desktop: always-on left column) is NEVER
   rendered on the light Vocero mobile theme. Double-safety even if the
   `data-theme-only="dark"` gate is somehow stripped or JS runtime fails. */
html[data-theme="white"] #reports-sidebar,
html[data-theme="white"] .reports-sidebar,
html[data-theme="white"] .reports-sidebar-inner,
html[data-theme="white"] #reports-sidebar-toggle,
html[data-theme="white"] #reports-hint,
html[data-theme="white"] .reports-hint {
  display: none !important;
  width: 0 !important;
  height: 0 !important;
  padding: 0 !important;
  margin: 0 !important;
  transform: translateX(-9999px) !important;
  visibility: hidden !important;
}
/* In white theme, app column must not leave a 320px gutter for the desktop
   reports-sidebar (which was the dark-theme default layout). Reset paddings. */
html[data-theme="white"] #app,
html[data-theme="white"] .app-column {
  padding-left: 16px !important;
  padding-right: 16px !important;
  margin-left: 0 !important;
}
/* ─────────────────────────────────────────────────────────────────────
   USER FIX 2026-09-10 "la chat deve apparire solo nel chat box":
   The dark theme renders a SECOND ephemeral transcript into #bubble-stack
   (floating anchored top-right — the grey/light blue bubbles the user
   circled in red with an X on the last screenshot). Since the white
   mobile theme shows ALL conversation exclusively inside the persistent
   "Conversación" card (chat-history inside .chat-panel bounded 58dvh),
   we kill the parallel floating bubble stack HARD in white theme — both
   the ID and the class, zero width/height/transform so it can't leave
   layout artifacts. */
html[data-theme="white"] #bubble-stack,
html[data-theme="white"] .bubble-stack {
  display: none !important;
  width: 0 !important;
  height: 0 !important;
  padding: 0 !important;
  margin: 0 !important;
  transform: translateX(-9999px) !important;
  visibility: hidden !important;
  pointer-events: none !important;
}

/* ═══════════════════════════════════════════════════════════════════════
   THEME WHITE — S2S_THEME="white"
   Light mobile "Vocero app" theme matching the provided screenshot:
   pure white background, deep blue accents, card-based UI, avatar+timestamp
   per chat bubble, rounded input with clip+send.
   Activated on <html data-theme="white"> (main.js sets the attr from
   /api/config response). All selectors below are scoped to that attribute
   so the native dark theme is 100% preserved when the flag is off.
   ═══════════════════════════════════════════════════════════════════════ */

html[data-theme="white"] {
  color-scheme: light;
  /* ── Token overrides ─────────────────────────────────────────────── */
  --bg:            #ffffff;
  --bg-elev:       #f8fafc;
  --bg-elev-2:     #f1f5f9;
  --border:        rgba(15, 23, 42, 0.08);
  --border-strong: rgba(15, 23, 42, 0.16);
  --glass:         rgba(255, 255, 255, 0.85);
  --glass-strong:  rgba(248, 250, 252, 0.92);
  --text:          #0f172a;                 /* slate-900 */
  --text-dim:      rgba(15, 23, 42, 0.72);
  --text-faint:    rgba(15, 23, 42, 0.55);

  --brand:         #1f6feb;                 /* deep corporate blue */
  --brand-2:       #3b82f6;                 /* medium blue */
  --brand-3:       #60a5fa;                 /* light blue */
  --accent:        #2563eb;                 /* primary blue */
  --accent-2:      #3b82f6;                 /* call-to-action blue */
  --listening:     #2563eb;
  --speaking:      #1d4ed8;
  --processing:    #d97706;
  --error:         #dc2626;
  --success:       #16a34a;

  --brand-gradient: linear-gradient(135deg, #2563eb 0%, #3b82f6 55%, #60a5fa 100%);
  --brand-soft-gradient: linear-gradient(135deg, rgba(37, 99, 235, 0.18),
                                                      rgba(59, 130, 246, 0.14) 55%,
                                                      rgba(96, 165, 250, 0.18));

  --voice-user:      #2563eb;
  --voice-assistant: #64748b;
  --voice-tool:      #d97706;

  --shadow-soft:        0 6px 20px rgba(15, 23, 42, 0.06);
  --shadow-glow-brand:  0 0 28px -6px rgba(37, 99, 235, 0.45),
                        0 0 60px -18px rgba(59, 130, 246, 0.40);
  --shadow-glass:       0 2px 12px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.04);

  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 22px;
  --radius-xl: 28px;
}

/* ── Page chrome (strip aurora gradients / dot patterns) ────────────── */
html[data-theme="white"] body {
  background: #ffffff !important;
  color: var(--text);
  background-image: none;
}
html[data-theme="white"] body::before,
html[data-theme="white"] body::after {
  display: none !important;
  background-image: none;
  animation: none;
}

/* ── App column: slightly reduce reserved bottom room (footer is thinner in white theme) */
html[data-theme="white"] #app {
  padding-bottom: max(100px, calc(env(safe-area-inset-bottom, 0px) + 100px));
}

/* ── Talk-unlocker (S2S_START_SESSION_MODE=TALK) — restyle for light bg */
html[data-theme="white"] .talk-unlocker {
  background: linear-gradient(180deg, rgba(255,255,255,0.70) 0%, rgba(255,255,255,0.88) 100%);
}
html[data-theme="white"] .talk-unlocker-hint {
  background: #ffffff;
  border-color: rgba(15, 23, 42, 0.08);
  color: #0f172a;
  box-shadow: 0 20px 60px rgba(15, 23, 42, 0.10), inset 0 1px 0 rgba(255,255,255,1);
}
html[data-theme="white"] .talk-unlocker-spinner {
  border-color: rgba(37,99,235,0.10);
  border-top-color: #2563eb;
  border-right-color: #3b82f6;
}
html[data-theme="white"] .talk-unlocker-title { color: #0f172a; }
html[data-theme="white"] .talk-unlocker-sub   { color: rgba(15,23,42,0.65); }

/* ── Topbar — flat, no card, center brand, hamburger left, avatar right ── */
html[data-theme="white"] .topbar {
  padding: 18px 18px 6px 18px;
  align-items: center;
  gap: 10px;
}
html[data-theme="white"] .menu-btn {
  color: #0f172a;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 12px;
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  opacity: 0.9;
}
html[data-theme="white"] .menu-btn:hover {
  background: rgba(15, 23, 42, 0.05);
  opacity: 1;
}
/* ── Topbar layout (theme=white): 3-column grid ──────────────────────────
   Col1 = hamburger 52 px  ·  Col2 = brand centered  ·  Col3 = user avatar 52 px
   Using a real grid eliminates absolute-position overlap bugs between the
   avatar circle and the 2-line brand title/subtitle that plagued the previous
   implementation. Every item lives inside its own grid cell → no collision. */
html[data-theme="white"] .topbar {
  display: grid !important;
  grid-template-columns: 52px minmax(0, 1fr) 52px;
  grid-auto-rows: min-content;
  align-items: start;
  justify-items: center;
  column-gap: 4px;
  row-gap: 0;
  padding: 16px 10px 0;
  position: static !important;
  box-sizing: border-box;
  width: 100%;
  background: transparent;
  border: none;
}
html[data-theme="white"] .menu-btn {
  grid-column: 1 / 2;
  grid-row: 1 / 2;
  justify-self: start;
  align-self: center;
  margin: 0 0 0 2px;
}
/* Brand card → strip glass, becomes a transparent centered title block */
html[data-theme="white"] .brand {
  grid-column: 2 / 3;
  grid-row: 1 / 2;
  flex: none;
  width: 100%;
  max-width: 100%;
  padding: 0;
  margin: 0;
  background: transparent !important;
  border: none !important;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
  display: flex;
  justify-content: center;
  text-align: center;
  border-radius: 0;
  justify-self: center;
}
html[data-theme="white"] .brand::before { display: none; }
html[data-theme="white"] .ident {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 2px;
  width: 100%;
  max-width: 100%;
  padding: 0 4px !important;
  box-sizing: border-box;
}
html[data-theme="white"] .ident-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 2px;
  width: 100%;
  max-width: 100%;
  margin: 0 auto;
  /* Prevent 2-line brand words from ever overflowing on narrow phones */
  word-break: keep-all;
  overflow-wrap: normal;
}
html[data-theme="white"] .ident-user-name {
  grid-column: 3 / 4;
  grid-row: 1 / 2;
  position: static !important;
  justify-self: end;
  align-self: center;
  margin: 0 2px 0 0;
  z-index: 2;
  width: 42px;
  height: 42px;
  border-radius: 999px;
  background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
  color: #ffffff;
  font-size: 13px;
  font-weight: 700;
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  border: 2px solid rgba(59, 130, 246, 0.25);
  box-shadow: 0 3px 10px rgba(37, 99, 235, 0.22);
}
html[data-theme="white"] .ident-title {
  font-size: clamp(24px, 6vw, 32px);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: #0f172a !important;
  background: none !important;
  -webkit-background-clip: initial;
          background-clip: initial;
  -webkit-text-fill-color: initial;
  text-shadow: none;
  filter: none;
  line-height: 1.05;
  white-space: nowrap;
}
/* Subtitle "Agente conversacional" below Vocero (theme=white only) */
html[data-theme="white"] .ident-subtitle {
  margin: 0;
  font-size: clamp(12px, 2.6vw, 14px);
  color: rgba(15, 23, 42, 0.60);
  font-weight: 500;
  letter-spacing: -0.005em;
  display: block;
  line-height: 1.3;
  white-space: nowrap;
}
html[data-theme="white"] .topbar-right {
  /* debug-only controls (tools/settings) hidden; keep them out of grid flow */
  display: none;
}

/* ── Status card: "Listo para conversar / Informes, análisis..." ───────
     User request 2026-09-10 "nascondi box listo para conversar" — hide the
     entire status card in white theme (the grey rounded banner with the
     chart icon and arrow between topbar and mic stage, which the user
     circled with a red X). Previously we hid only the right chevron; now
     we kill the full container with the same double-guarantee pattern
     used for the sidebar / bubble-stack. ──────────────────────────────── */
html[data-theme="white"] .status-card,
html[data-theme="white"] .status-card-icon,
html[data-theme="white"] .status-card-text,
html[data-theme="white"] .status-card-title,
html[data-theme="white"] .status-card-sub,
html[data-theme="white"] .status-card-arrow {
  display: none !important;
  width: 0 !important;
  height: 0 !important;
  padding: 0 !important;
  margin: 0 !important;
  transform: translateX(-9999px) !important;
  visibility: hidden !important;
  pointer-events: none !important;
}

/* ── Stage: tighten room (purple orb + arc hidden by data-theme-only=dark) */
html[data-theme="white"] .stage {
  padding: 10px 16px 6px;
  gap: 12px;
  min-height: 0;
}
/* Safety: hide orb wrapper even if data-theme-only=dark fails for any reason */
html[data-theme="white"] .orb-wrap,
html[data-theme="white"] #mic-gate,
html[data-theme="white"] #stop-btn {
  display: none !important;
}
html[data-theme="white"] #circle-caption    { display: none !important; }
html[data-theme="white"] #circle-subcaption { display: none !important; }

/* ── White mic stage (116px blue circle, waveform banks, caption, stop pill) ── */
html[data-theme="white"] .white-mic-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 22px 0 6px;
  position: relative;
  box-sizing: border-box;
}
/* Row: waveform-left · mic-circle · waveform-right (absolute centered row) */
html[data-theme="white"] .white-mic-circle {
  position: relative;
  z-index: 2;
  width: 116px;
  height: 116px;
  border-radius: 999px;
  border: none;
  padding: 0;
  cursor: pointer;
  background: #ffffff;
  color: #ffffff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  overflow: visible;
  flex: none;
  transition: transform 180ms cubic-bezier(.2,.7,.2,1), box-shadow 180ms ease;
}
/* Concentric layers of the blue mic button */
html[data-theme="white"] .white-mic-circle .wmc-halo {
  position: absolute;
  inset: -22px;
  border-radius: 999px;
  background: radial-gradient(closest-side,
    rgba(59, 130, 246, 0.22),
    rgba(59, 130, 246, 0.10) 55%,
    transparent 75%);
  z-index: 0;
  pointer-events: none;
  animation: wmcHaloPulse 3.2s ease-in-out infinite alternate;
}
@keyframes wmcHaloPulse {
  0%   { transform: scale(0.98); opacity: 0.75; }
  100% { transform: scale(1.08); opacity: 1; }
}
html[data-theme="white"] .white-mic-circle .wmc-ring {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  padding: 4px;
  background: linear-gradient(135deg, #2563eb 0%, #3b82f6 45%, #60a5fa 100%);
  z-index: 0;
  pointer-events: none;
  box-sizing: border-box;
  display: block;
}
/* The ring paints a thin blue 4-px border around a transparent inner circle
   (NOT a full white disc that would occlude the solid blue core behind it). */
html[data-theme="white"] .white-mic-circle .wmc-ring::before {
  content: "";
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 999px;
  background: transparent;
}
/* Solid blue disc filling everything inside the 4-px ring. This sits BEHIND
   the white mic icon so the button looks like a solid blue circle with a
   thin gradient light-blue border — exactly the screenshot look. */
html[data-theme="white"] .white-mic-circle .wmc-core {
  position: absolute;
  inset: 4px;                 /* sit flush inside the 4-px wmc-ring padding */
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb 0%, #3b82f6 55%, #60a5fa 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    inset 0 -3px 6px rgba(15, 23, 42, 0.12),
    0 8px 22px rgba(37, 99, 235, 0.22);
  z-index: 1;
  pointer-events: none;
}
/* Icon layer: WHITE on top of the solid blue disc, never occluded. */
html[data-theme="white"] .white-mic-circle .wmc-icon {
  position: relative;
  z-index: 5;                     /* above halo / ring / core */
  color: #ffffff !important;
  fill: #ffffff;
  stroke: #ffffff;
  width: 48px;
  height: 48px;
  flex: none;
  display: none;
  filter: drop-shadow(0 2px 4px rgba(15, 23, 42, 0.14));
}
/* Force visibility of the mic SVG in idle / your-turn / error states — the
   most common customer-reported missing-icon bug comes from a stale state
   where `state-idle` exists but `.wmc-mic` still has `display:none`. The
   rules below are written with high specificity and !important on the
   display property so they cannot be overridden by accidental resets. */
html[data-theme="white"] .white-mic-circle.state-idle > svg.wmc-icon.wmc-mic,
html[data-theme="white"] .white-mic-circle.state-idle .wmc-icon.wmc-mic {
  display: inline-flex !important;
}
html[data-theme="white"] .white-mic-circle.state-your-turn > svg.wmc-icon.wmc-mic,
html[data-theme="white"] .white-mic-circle.state-your-turn .wmc-icon.wmc-mic {
  display: inline-flex !important;
}
html[data-theme="white"] .white-mic-circle.state-error > svg.wmc-icon.wmc-mic,
html[data-theme="white"] .white-mic-circle.state-error .wmc-icon.wmc-mic {
  display: inline-flex !important;
}
html[data-theme="white"] .white-mic-circle.state-processing > .wmc-icon.wmc-spinner,
html[data-theme="white"] .white-mic-circle.state-processing .wmc-icon.wmc-spinner,
html[data-theme="white"] .white-mic-circle.state-connecting > .wmc-icon.wmc-spinner,
html[data-theme="white"] .white-mic-circle.state-connecting .wmc-icon.wmc-spinner,
html[data-theme="white"] .white-mic-circle.state-queued > .wmc-icon.wmc-spinner,
html[data-theme="white"] .white-mic-circle.state-queued .wmc-icon.wmc-spinner {
  display: block !important;
  width: 40px !important;
  height: 40px !important;
  border-radius: 999px;
  border: 4px solid rgba(255, 255, 255, 0.22) !important;
  border-top-color: #ffffff !important;
  animation: talkSpinnerSpin 850ms linear infinite;
  margin: 4px !important;
  padding: 0 !important;
  background: transparent !important;
  box-sizing: border-box;
}
html[data-theme="white"] .white-mic-circle.state-ai-speaking > svg.wmc-icon.wmc-stop,
html[data-theme="white"] .white-mic-circle.state-ai-speaking .wmc-icon.wmc-stop {
  display: inline-flex !important;
  width: 34px !important;
  height: 34px !important;
  margin: 7px !important;
  border-radius: 4px !important;
}
/* Subtle ring pulse while listening — uses box-shadow because the ring
   itself is a hard 4-px painted border and transform:scale() would also
   scale the icon. */
html[data-theme="white"] .white-mic-circle.state-your-turn .wmc-ring {
  animation: wmcListeningPulse 1.6s ease-in-out infinite;
}
@keyframes wmcListeningPulse {
  0%, 100% { box-shadow: 0 0 0 0   rgba(59, 130, 246, 0.45); }
  50%      { box-shadow: 0 0 0 18px rgba(59, 130, 246, 0); }
}

/* ── Waveform banks (7 bars each side) ──────────────────────────────── */
html[data-theme="white"] .white-waveform {
  position: absolute;
  top: calc(22px + 116px / 2);
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 7px;
  height: 70px;
  pointer-events: none;
  z-index: 1;
}
html[data-theme="white"] .white-waveform-left  { left:  calc(50% - 58px - 90px); }
html[data-theme="white"] .white-waveform-right { right: calc(50% - 58px - 90px); }
html[data-theme="white"] .white-waveform .w-bar {
  display: block;
  width: 6px;
  border-radius: 999px;
  background: linear-gradient(180deg, #3b82f6, #60a5fa);
  opacity: 0.8;
  transform-origin: center;
  height: calc(var(--bar-h, 0.5) * 70px);
  min-height: 8px;
}
html[data-theme="white"] .white-mic-circle.state-your-turn ~ * .w-bar,
html[data-theme="white"] .white-mic-stage:has(.state-your-turn) .w-bar {
  animation: wBarDance 900ms ease-in-out infinite alternate;
}
@keyframes wBarDance {
  from { transform: scaleY(0.55); }
  to   { transform: scaleY(1.05); }
}
html[data-theme="white"] .white-waveform .wb-1 { animation-delay: -0.6s; }
html[data-theme="white"] .white-waveform .wb-2 { animation-delay: -0.5s; }
html[data-theme="white"] .white-waveform .wb-3 { animation-delay: -0.4s; }
html[data-theme="white"] .white-waveform .wb-4 { animation-delay: -0.3s; }
html[data-theme="white"] .white-waveform .wb-5 { animation-delay: -0.2s; }
html[data-theme="white"] .white-waveform .wb-6 { animation-delay: -0.1s; }
html[data-theme="white"] .white-waveform .wb-7 { animation-delay:  0.0s; }
html[data-theme="white"] .white-waveform-right .wb-1 { animation-delay: -0.0s; }
html[data-theme="white"] .white-waveform-right .wb-2 { animation-delay: -0.1s; }
html[data-theme="white"] .white-waveform-right .wb-3 { animation-delay: -0.2s; }
html[data-theme="white"] .white-waveform-right .wb-4 { animation-delay: -0.3s; }
html[data-theme="white"] .white-waveform-right .wb-5 { animation-delay: -0.4s; }
html[data-theme="white"] .white-waveform-right .wb-6 { animation-delay: -0.5s; }
html[data-theme="white"] .white-waveform-right .wb-7 { animation-delay: -0.6s; }

/* ── Mic caption "Escuchando... / Hablando..." ───────────────────────── */
html[data-theme="white"] .white-mic-caption {
  margin: 4px 0 0;
  color: #2563eb;
  font-size: clamp(17px, 4.2vw, 20px);
  font-weight: 600;
  letter-spacing: -0.005em;
  text-align: center;
  line-height: 1.25;
}
html[data-theme="white"] .white-mic-caption.muted   { color: #64748b; }
html[data-theme="white"] .white-mic-caption.error   { color: #dc2626; }

/* ── "Detener" pill ─────────────────────────────────────────────────── */
html[data-theme="white"] .white-stop-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 22px 10px 16px;
  background: #e2e8f0;
  color: #0f172a;
  border: none;
  border-radius: 999px;
  cursor: pointer;
  font-size: clamp(14px, 3.4vw, 16px);
  font-weight: 600;
  letter-spacing: -0.005em;
  box-shadow: 0 2px 8px rgba(15, 23, 42, 0.06);
  transition: background 160ms ease, transform 160ms ease;
}
html[data-theme="white"] .white-stop-btn:hover {
  background: #cbd5e1;
  transform: translateY(-1px);
}
html[data-theme="white"] .white-stop-btn svg {
  width: 18px;
  height: 18px;
  border-radius: 3px;
  color: #0f172a;
  flex: none;
}

/* ── Quick actions — 3 pills (Resumen / Consultar datos / Crear respuesta).
     User request 2026-09-10 "nascondi le cose in rosso": all 3 quick action
     pills are circled in red → hide the entire quick-actions container. */
html[data-theme="white"] .quick-actions {
  display: none !important;
}
html[data-theme="white"] .quick-action-btn {
  display: none !important;
}

/* ── Chat panel (Conversación card): white rounded card + "En línea" pill.
     CRITICAL SCROLL FIX 2026-09-10 (per "non fa lo scrolling della chatbox"):
     The previous white-theme override set `flex: 0 0 auto` + `max-height: none`
     which removed every vertical constraint on the chat panel → it grew beyond
     the viewport edge → overflow on the *page* body instead of the chat
     history scrollbox. Instead we preserve the dark-theme flex-col panel
     contract (chat-panel grows into the free space, chat-panel-inner and
     chat-history are flex items with min-height:0 to enable shrinking) and
     we cap the whole card at 58dvh so the topbar + status + mic stage + quick
     actions are always visible above it.  ─────────────────────────────────── */
html[data-theme="white"] .chat-panel {
  margin: 6px 18px 0;
  padding: 0;
  background: #ffffff;
  border: 1px solid rgba(15, 23, 42, 0.06);
  border-radius: 22px;
  box-shadow: var(--shadow-soft);
  width: auto;
  max-width: none;
  /* Layout contract for scrollable chat: absorb remaining vertical room,
     shrink as low as needed, cap the card for a sensible upper bound,
     and hide any panel-level overflow (chat-history will own the scroll). */
  flex: 1 1 auto !important;
  min-height: 180px;
  max-height: 58dvh !important;
  max-height: 58svh !important;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-sizing: border-box;
}
html[data-theme="white"] .chat-panel-inner {
  padding: 14px 14px 12px;
  /* Critical: pass the chat-panel's fixed height down into the inner so
     chat-history can later compute its own flex shrink/scroll correctly. */
  display: flex !important;
  flex-direction: column !important;
  height: 100% !important;
  width: 100%;
  box-sizing: border-box;
  min-height: 0;
  overflow: hidden;
}
html[data-theme="white"] .chat-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 2px 4px 12px;
  margin: 0 0 4px;
  border-bottom: 1px solid rgba(15, 23, 42, 0.06);
  /* Header never shrinks out of existence; keep it pinned. */
  flex: none;
}
html[data-theme="white"] .chat-panel-header h3 {
  margin: 0;
  color: #0f172a !important;        /* slate-900 flat, no brand gradient */
  background: none !important;
  -webkit-background-clip: initial !important;
          background-clip: initial !important;
  -webkit-text-fill-color: initial !important;
          text-fill-color: initial !important;
  text-shadow: none;
  filter: none;
  opacity: 1;
  font-size: clamp(18px, 4.3vw, 22px);
  font-weight: 700 !important;
  letter-spacing: -0.01em !important;
  text-transform: none !important;      /* TitleCase, no ALLCAPS */
  text-decoration: none !important;     /* kill the underline */
  border-bottom: none !important;
  box-shadow: none !important;
}
/* Also force the header <hr>-like bottom border to be a thin divider (not a
   thick blue underline visually sitting right below "CONVERSACIÓN" — which is
   what the user's screenshot is showing as the blue underline effect). */
html[data-theme="white"] .chat-panel-header {
  border-bottom-style: solid !important;
  border-bottom-color: rgba(15, 23, 42, 0.06) !important;
  border-bottom-width: 1px !important;
  text-decoration: none !important;
}
/* "En línea" status pill — green dot + text */
html[data-theme="white"] .chat-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px 5px 8px;
  background: rgba(22, 163, 74, 0.08);
  border-radius: 999px;
  color: #16a34a;
  font-size: 12.5px;
  font-weight: 600;
  flex: none;
}
html[data-theme="white"] .chat-status-dot {
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: #16a34a;
  box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.14);
  display: block;
  flex: none;
  animation: chatStatusDotPulse 2s ease-in-out infinite;
}
@keyframes chatStatusDotPulse {
  0%, 100% { box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.14); }
  50%      { box-shadow: 0 0 0 5px rgba(22, 163, 74, 0.08); }
}
/* ──────────────────────────────────────────────────
   THE SCROLLABLE HISTORY BOX (root cause of "non scorre")
   Before: max-height:none. Now it's a flex item with flex:1 1 auto inside
   .chat-panel-inner, which has a bounded 100% height of its parent.
   min-height:0 is MANDATORY — without it, flex column children never shrink
   below their content size, and overflow-y:auto never activates.      */
html[data-theme="white"] .chat-history {
  padding: 12px 4px 40px !important;   /* 40 px bottom air inside scroll */
  gap: 14px;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto !important;
  min-height: 0 !important;            /* MAGIC: enable shrink for scroll */
  max-height: 100% !important;
  overflow-y: auto !important;
  overflow-x: hidden !important;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

/* ── Chat history bubbles (white theme rewrite).
     The real renderer (ui/chat.js _buildMessageEl) builds rows as:
        <div class="hist-msg user|assistant">
          <div class="hist-role">You / Assistant</div>   ← HIDE in white theme
          <div class="hist-body">…message text…</div>   ← restyle as flat bubble
     We target those classes (not the .bubble class, which is for the floating
     ephemeral stack) because the persistent conversation rows live here. ─── */

/* 1) Kill the old "You / Assistant / Tool call" uppercase mono eyebrow. The
      Vocero mobile design replaces it with a name+timestamp meta line above
      each bubble instead. */
html[data-theme="white"] .chat-history .hist-role,
html[data-theme="white"] .chat-history .bubble-role {
  display: none !important;
}

/* 2) Layout: each row is a flex column with room for the 36 px avatar circle
      sitting outside the bubble on the role side. */
html[data-theme="white"] .chat-history .hist-msg,
html[data-theme="white"] #bubble-stack .bubble {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-width: calc(100% - 56px);
  box-sizing: border-box;
  position: relative;
  margin-top: 8px;
  padding-top: 22px;      /* 22 px reserved for the "Vocero 16:14" meta label above */
}
/* Anchor hist-body/bubble-body so absolute-positioned floating meta labels
   (painted via ::before on the body) use the bubble itself as the reference
   box instead of climbing up to chat-history. This is the ROOT FIX of the
   "16:14 timestamp floats in the middle of the page" bug from the previous
   screenshot.   */
html[data-theme="white"] .chat-history .hist-msg .hist-body,
html[data-theme="white"] #bubble-stack .bubble .bubble-body {
  position: relative;
}

/* 3) FLIP the default dark-theme alignment. Screenshot reference:
      ASSISTANT = left side (blue bot avatar left of bubble, grey bubble)
      USER      = right side (EM/AV initials circle on RIGHT, light-blue bubble)
      The default dark purple theme stores them the opposite way, so we flip
      align-self AND the auto margin here. */
html[data-theme="white"] .chat-history .hist-msg.assistant,
html[data-theme="white"] #bubble-stack .bubble.assistant {
  align-self: flex-start !important;
  margin-right: auto !important;
  margin-left: 52px !important;    /* 36 avatar + 16 gap */
}
html[data-theme="white"] .chat-history .hist-msg.user,
html[data-theme="white"] #bubble-stack .bubble.user {
  align-self: flex-end !important;
  margin-left: auto !important;
  margin-right: 52px !important;
}

/* 4) The actual speech bubble body.
      Assistant (grey flat 200-level): #F3F4F6 rounded 18 except bottom-left corner 6px
      User  (light-blue 100):        #DBEAFE rounded 18 except bottom-right corner 6px
      Drop every original dark-theme decoration: glass backdrop, 28px deep
      drop shadow, gradient borders. Keep it perfectly flat. */
html[data-theme="white"] .chat-history .hist-msg .hist-body,
html[data-theme="white"] #bubble-stack .bubble .bubble-body {
  padding: 12px 15px !important;
  border-radius: 18px !important;
  font-size: clamp(14.5px, 3.3vw, 16.5px) !important;
  line-height: 1.45 !important;
  letter-spacing: -0.005em !important;
  color: #0f172a !important;
  background: #ffffff;
  border: 1px solid transparent !important;
  box-shadow: none !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  word-break: break-word;
  white-space: pre-wrap;
}
html[data-theme="white"] .chat-history .hist-msg.assistant .hist-body,
html[data-theme="white"] #bubble-stack .bubble.assistant .bubble-body {
  background: #f3f4f6 !important;
  border-bottom-left-radius: 6px !important;
  color: #0f172a !important;
}
html[data-theme="white"] .chat-history .hist-msg.user .hist-body,
html[data-theme="white"] #bubble-stack .bubble.user .bubble-body {
  background: #dbeafe !important;
  border-bottom-right-radius: 6px !important;
  color: #0f172a !important;
}
/* Partial-streaming bubble (growing AI response) = same shape, slightly lower opacity */
html[data-theme="white"] .chat-history .hist-body.partial,
html[data-theme="white"] .bubble-body.partial {
  opacity: 0.92;
}

/* 5) AVATAR circles outside each bubble (painted on the row wrapper ::before)
      Assistant: blue circle + inline robot SVG,  LEFT, outside bubble
      User     : light-blue "AV"/"EM" initials, RIGHT, outside bubble
      Initials placeholder for user is pulled from data-user-initials attribute
      if the chat renderer writes it; fallback is the letters "AV" (matches the
      Alessandro V… user shown in the customer screenshot).

      NOTE 2026-09-10: we replaced the 🤖 emoji with a tiny hand-crafted SVG
      robot icon painted via background-image. The emoji rendered as a
      crown-head robot on some Android versions; the SVG is pixel-perfect
      cross-platform and matches the deep-blue assistant avatar.   */
html[data-theme="white"] .chat-history .hist-msg::before,
html[data-theme="white"] #bubble-stack .bubble::before {
  position: absolute !important;
  top: 22px !important;              /* line up with bubble body, below the meta line */
  width: 36px;
  height: 36px;
  border-radius: 999px;
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  color: #ffffff;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: -0.01em;
  z-index: 2;
  content: "";
  box-shadow: 0 2px 8px rgba(15, 23, 42, 0.10);
}
html[data-theme="white"] .chat-history .hist-msg.assistant::before,
html[data-theme="white"] #bubble-stack .bubble.assistant::before {
  left: -52px;
  background-color: #2563eb !important;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='7' width='18' height='12' rx='3'/><path d='M12 2v4'/><circle cx='9' cy='13' r='1.1' fill='white' stroke='none'/><circle cx='15' cy='13' r='1.1' fill='white' stroke='none'/><path d='M9 17h6'/></svg>");
  background-repeat: no-repeat;
  background-position: center center;
  background-size: 20px 20px;
  border: 1px solid rgba(59, 130, 246, 0.25);
}
/* Remove the old ::after with the robot emoji that was overlaying the avatar. */
html[data-theme="white"] .chat-history .hist-msg.assistant::after,
html[data-theme="white"] #bubble-stack .bubble.assistant::after {
  display: none !important;
  content: none !important;
}
html[data-theme="white"] .chat-history .hist-msg.user::before,
html[data-theme="white"] #bubble-stack .bubble.user::before {
  right: -52px;
  background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
  border: 1px solid rgba(96, 165, 250, 0.35);
  /* Pull initials from a custom data attribute when chat.js sets it, else show
     "AV" as a generic 2-letter default that matches the customer screenshot */
  content: attr(data-user-initials, "AV");
  align-items: center;
  justify-content: center;
}

/* 6) META label "name HH:MM" on the same single line, ONE pseudo-element.
      ──── USER FIX 2026-09-10 "perche mette sempre Emilio" ────
      The previous iteration hardcoded the Spanish names "Vocero" (assistant)
      and "Emilio" (user) plus a fake "16:14" timestamp. Instead we now read
      BOTH values dynamically from data attributes written by ui/chat.js at the
      moment each row is inserted:
        • assistant: data-asst-name + data-timestamp  on .hist-body
        • user     : data-user-name + data-timestamp  on .hist-body
      Names fall back to "Vocero" / "Usuario" (Spanish generic) when chat.js
      hasn't resolved a profile yet; timestamp falls back to "" which leaves
      just the name visible. The concat format uses two non-breaking spaces
      (\A0\A0 = U+00A0 ×2) between name and time, matching the screenshot.

      ──── ROOT CAUSE FIX of the previous "timestamp in the middle of the page" bug ────
      The earlier implementation split the meta across TWO selectors that used
      different relative reference boxes:
        • `> :first-child::before` → targetted .hist-role (hidden, display:none)
          with `position:absolute; top:-20px` which had NO containing block, so
          the browser walked up and used chat-history (full-width) → label
          ended up at left=50% → "16:14" alone in the page centre.
      NEW approach:
        • Use the BUBBLE body itself (.hist-body / .bubble-body) as the single
          relative reference box. Paint both NAME + HH:MM in ONE `content:`
          string via `.hist-body::before`. Same absolute positioning as
          before, but now contained inside the width of each bubble → name and
          timestamp sit *perfectly aligned* above their bubble on every row,
          at any viewport width.   */
html[data-theme="white"] .chat-history .hist-msg.assistant .hist-body::before,
html[data-theme="white"] #bubble-stack .bubble.assistant .bubble-body::before {
  /* Assistant meta = "<real assistant name>  <real HH:MM>" */
  content: attr(data-asst-name, "Vocero") "\A0\A0" attr(data-timestamp, "");
  position: absolute;
  top: -22px;
  left: 0;
  width: auto;
  height: 16px;
  line-height: 16px;
  font-size: 12.5px;
  font-weight: 600;
  color: #64748b;
  letter-spacing: -0.01em;
  white-space: nowrap;
  pointer-events: none;
  display: inline-block !important;
}
html[data-theme="white"] .chat-history .hist-msg.user .hist-body::before,
html[data-theme="white"] #bubble-stack .bubble.user .bubble-body::before {
  /* User meta = "<real user name>  <real HH:MM>"
     Falls back to "Usuario" when no caller profile has been injected yet,
     never "Emilio" — user explicitly flagged the hard-coded name as wrong. */
  content: attr(data-user-name, "Usuario") "\A0\A0" attr(data-timestamp, "");
  position: absolute;
  top: -22px;
  right: 0;
  width: auto;
  height: 16px;
  line-height: 16px;
  font-size: 12.5px;
  font-weight: 600;
  color: #64748b;
  letter-spacing: -0.01em;
  white-space: nowrap;
  pointer-events: none;
  text-align: right;
  display: inline-block !important;
}
/* Tool-call rows: shrink to pale inline card with no avatar */
html[data-theme="white"] .chat-history .hist-msg.tool,
html[data-theme="white"] .chat-history .bubble.tool {
  max-width: 100% !important;
  padding-top: 0;
  margin: 6px 0 !important;
  align-self: center !important;
}
html[data-theme="white"] .chat-history .hist-msg.tool::before,
html[data-theme="white"] .chat-history .bubble.tool::before,
html[data-theme="white"] .chat-history .hist-msg.tool::after,
html[data-theme="white"] .chat-history .bubble.tool::after {
  display: none !important;
  content: none;
}
/* Also strip the floating meta from tool rows (they don't have a human role). */
html[data-theme="white"] .chat-history .hist-msg.tool .hist-body::before {
  content: none !important;
  display: none !important;
}

/* ── Chat form (input footer bar): clip · rounded input · blue send ────── */
html[data-theme="white"] .chat-form {
  display: flex !important;
  align-items: center;
  gap: 10px;
  padding: 6px 6px 6px 6px;
  background: #ffffff;
  border: 1px solid rgba(15, 23, 42, 0.08);
  border-radius: 999px;
  margin: 4px 0 0;
  box-shadow: var(--shadow-soft);
  /* Keep the input row pinned at the bottom of the chat card header; never
     let the history (flex:1) squeeze the form out of the panel. */
  flex: none !important;
}
html[data-theme="white"] .chat-form[hidden],
html[data-theme="white"] .chat-form[style*="display:none"] {
  display: flex !important;
}
html[data-theme="white"] .chat-attach-btn {
  flex: none;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 999px;
  color: #64748b;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
html[data-theme="white"] .chat-attach-btn:hover {
  color: #2563eb;
  background: rgba(37, 99, 235, 0.08);
}
html[data-theme="white"] .chat-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 44px;
  padding: 0 10px 0 6px;
  margin: 0;
  background: transparent !important;
  border: none !important;
  color: #0f172a;
  font-size: clamp(14px, 3.2vw, 16px);
  border-radius: 0;
  box-shadow: none !important;
  outline: none !important;
}
html[data-theme="white"] .chat-input::placeholder {
  color: rgba(15, 23, 42, 0.48);
}
html[data-theme="white"] .chat-input:focus {
  border-color: transparent;
  box-shadow: none;
  outline: none;
}
/* Hide original square send button in white theme */
html[data-theme="white"] .chat-form .chat-send-btn {
  display: none !important;
}
/* New big round blue send button with paper airplane */
html[data-theme="white"] .chat-send-btn-round {
  flex: none;
  width: 48px;
  height: 48px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #3b82f6);
  color: #ffffff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(37, 99, 235, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.3);
  transition: transform 140ms ease, filter 140ms ease;
}
html[data-theme="white"] .chat-send-btn-round:hover {
  transform: translateY(-1px);
  filter: brightness(1.05);
}
html[data-theme="white"] .chat-send-btn-round svg {
  width: 22px;
  height: 22px;
  stroke-width: 2.2;
  fill: #ffffff;
  stroke: #ffffff;
}

/* ── App footer: thin white bar, just a subtle separator + brand line ─── */
html[data-theme="white"] .app-footer {
  background: #ffffff;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  padding-top: 10px;
  padding-bottom: max(10px, env(safe-area-inset-bottom, 0px));
}
html[data-theme="white"] .app-footer::before {
  background: linear-gradient(90deg, transparent, rgba(37, 99, 235, 0.28) 30%, rgba(59, 130, 246, 0.28) 70%, transparent);
  opacity: 0.6;
}
html[data-theme="white"] .app-footer-text {
  color: rgba(15, 23, 42, 0.60);
  background: none;
  -webkit-text-fill-color: inherit;
  -webkit-background-clip: initial;
          background-clip: initial;
  filter: none;
  letter-spacing: 0.12em;
  font-weight: 600;
}

/* ── Modals: light theme restyle to match the white palette ─────────── */
html[data-theme="white"] .modal-content {
  background: #ffffff;
  color: #0f172a;
  border: 1px solid rgba(15, 23, 42, 0.08);
  box-shadow: 0 30px 80px rgba(15, 23, 42, 0.18);
}
html[data-theme="white"] .modal-header h2 { color: #0f172a; }
html[data-theme="white"] .modal-header { border-bottom-color: rgba(15, 23, 42, 0.08); }
html[data-theme="white"] .modal-footer { border-top-color: rgba(15, 23, 42, 0.08); }
html[data-theme="white"] .modal input[type=text],
html[data-theme="white"] .modal input[type=password],
html[data-theme="white"] .modal textarea,
html[data-theme="white"] .modal select {
  background: #f8fafc;
  border-color: rgba(15, 23, 42, 0.10);
  color: #0f172a;
}
html[data-theme="white"] .modal input[type=text]:focus,
html[data-theme="white"] .modal input[type=password]:focus,
html[data-theme="white"] .modal textarea:focus,
html[data-theme="white"] .modal select:focus {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
html[data-theme="white"] .btn.primary {
  background: linear-gradient(135deg, #2563eb, #3b82f6);
  color: #ffffff;
  border-color: transparent;
}
html[data-theme="white"] .btn.primary:disabled {
  opacity: 0.6;
  filter: grayscale(0%);
}
html[data-theme="white"] .btn.secondary {
  background: #f1f5f9;
  color: #0f172a;
  border-color: rgba(15, 23, 42, 0.10);
}
html[data-theme="white"] .btn.secondary:hover {
  background: #e2e8f0;
}

/* ── Reports sidebar restyle (the off-canvas panel + left sidebar) ───── */
html[data-theme="white"] .reports-sidebar,
html[data-theme="white"] .reports-sidebar-inner {
  background: #ffffff;
  color: #0f172a;
  border-color: rgba(15, 23, 42, 0.08);
}
html[data-theme="white"] .reports-sidebar-header {
  border-bottom-color: rgba(15, 23, 42, 0.08);
  color: #0f172a;
}
html[data-theme="white"] .reports-sidebar-header h3 { color: #0f172a; }

/* ── Limit modal (budget) ───────────────────────────────────────────── */
html[data-theme="white"] .limit-card {
  background: #ffffff;
  border: 1px solid rgba(15, 23, 42, 0.08);
}
html[data-theme="white"] .limit-title,
html[data-theme="white"] .limit-msg { color: #0f172a; }
html[data-theme="white"] .limit-note   { color: rgba(15, 23, 42, 0.62); }

/* ── User profile modal ─────────────────────────────────────────────── */
html[data-theme="white"] .user-profile-card {
  background: #ffffff;
}
html[data-theme="white"] .user-profile-avatar {
  background: linear-gradient(135deg, #2563eb, #60a5fa);
  color: #ffffff;
  border-color: rgba(37, 99, 235, 0.20);
}
html[data-theme="white"] .user-profile-title    { color: #0f172a; }
html[data-theme="white"] .user-profile-subtitle { color: rgba(15, 23, 42, 0.65); }

/* ── Tools modal switches ───────────────────────────────────────────── */
html[data-theme="white"] .switch-track {
  background: #cbd5e1;
}
html[data-theme="white"] .switch input:checked + .switch-track {
  background: #2563eb;
}

/* ── Mobile: tighten when room is short ─────────────────────────────── */
@media (max-width: 480px) {
  html[data-theme="white"] .topbar { padding: 14px 14px 4px; }
  html[data-theme="white"] .status-card { margin: 10px 14px 4px; }
  html[data-theme="white"] .chat-panel { margin: 4px 14px 0; }
  html[data-theme="white"] .white-waveform-left  { left:  calc(50% - 58px - 72px); }
  html[data-theme="white"] .white-waveform-right { right: calc(50% - 58px - 72px); }
  html[data-theme="white"] .white-waveform { gap: 5px; }
  html[data-theme="white"] .white-waveform .w-bar { width: 5px; }
}
@media (max-height: 640px) {
  html[data-theme="white"] .status-card { padding: 10px; }
  html[data-theme="white"] .white-mic-stage { padding: 14px 0 4px; }
  html[data-theme="white"] .white-mic-circle { width: 98px; height: 98px; }
  html[data-theme="white"] .white-mic-circle .wmc-icon { width: 40px; height: 40px; }
  html[data-theme="white"] .white-mic-circle.state-processing .wmc-spinner,
  html[data-theme="white"] .white-mic-circle.state-connecting .wmc-spinner,
  html[data-theme="white"] .white-mic-circle.state-queued .wmc-spinner { width: 34px; height: 34px; }
  html[data-theme="white"] .white-mic-circle.state-ai-speaking .wmc-stop { width: 30px; height: 30px; }
  html[data-theme="white"] .white-waveform .w-bar { height: calc(var(--bar-h, 0.5) * 56px); }
  html[data-theme="white"] .white-waveform { height: 56px; }
  html[data-theme="white"] .white-waveform-left  { left:  calc(50% - 49px - 76px); top: calc(14px + 49px); }
  html[data-theme="white"] .white-waveform-right { right: calc(50% - 49px - 76px); top: calc(14px + 49px); }
}
